diff --git a/.cspell.yml b/.cspell.yml index 7071f22526..3e99a4312a 100644 --- a/.cspell.yml +++ b/.cspell.yml @@ -8,4 +8,6 @@ words: - github - gohugoio - Goldydocs + - gomod + - hugoautogen - pageinfo diff --git a/.github/workflows/links.yaml b/.github/workflows/links.yaml index efe3788054..af73c642ff 100644 --- a/.github/workflows/links.yaml +++ b/.github/workflows/links.yaml @@ -27,6 +27,8 @@ jobs: node-version-file: .nvmrc cache: npm + # Scripts on: the one automated install that evaluates allowScripts + # (CONTRIBUTING.md#install-configuration). - run: npm ci - name: Regenerate generated files (drift-check input) diff --git a/.npmrc b/.npmrc index 0515324d11..dbda8fe952 100644 --- a/.npmrc +++ b/.npmrc @@ -1,13 +1,10 @@ -# The npm engines floor (package.json) is the version where the allowScripts -# policy landed; on older npm the key is silently ignored, an unprotected -# install that looks protected. engine-strict turns that floor into a hard -# install-time failure. -engine-strict=true +# npm supply-chain controls. Shared rationale: +# https://opentelemetry.io/site/design/supply-chain-security/ + +min-release-age=7 strict-allow-scripts=true +engine-strict=true +# No ignore-scripts setting here: see CONTRIBUTING.md#install-configuration. -# One script interpreter on every platform: without this, Windows npm hands -# scripts to cmd.exe, whose quoting rules silently diverge from sh (the -# single-quoted glob in test:site would no-match instead of erroring). Covers -# workspace runs too: npm resolves config at the workspace root. Git Bash -# ships with Git for Windows. +# Windows npm defaults to cmd.exe, whose quoting silently diverges from sh. script-shell=bash diff --git a/.nvmrc b/.nvmrc index b009dfb9d9..b7179aed06 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -lts/* +24.20.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 262bb39816..44b35d6bd7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,29 +9,18 @@ repo's [main ruleset][] mirrors Docsy's). ### Dependency updates -Renovate opens version-update PRs, created on Sundays, configured in -`renovate.json5`. Settings rationale: - -- `ignorePresets`: the preset's 3-day npm cooldown would override this repo's - 7-day `minimumReleaseAge`. Caution: this exclusion silently stops working if - the preset is renamed upstream. -- `lockFileMaintenance` off: wholesale lock re-resolves would churn the - committed lockfile; transitive security fixes arrive alert-driven instead. -- `gomod` off: the Docsy theme pin is updated manually; see - [Upgrade Docsy](#upgrade-docsy). All other detected managers are active. -- Package rules: - - Patch and minor updates are each grouped into a single PR per wave, to cut - review overhead. Majors stay individual, except families that Renovate's - presets keep in lockstep (for example, the GitHub artifact actions). - - `hugo-extended` is version-pinned and coupled to its `allowScripts` approval - (see [Update Hugo](#update-hugo)). - - Bootstrap and Font Awesome updates route through the theme - (`packages/hugoautogen` is regenerated from the theme, reverting any direct - bump). A Dependabot security PR may still bump these directly: close it and - route the fix through a theme update. - -Renovate's vulnerability-alert PRs stay on (immediate, cooldown-exempt), beside -GitHub's Dependabot security updates; a rare duplicate PR is accepted. +For the Renovate settings shared with Docsy and their rationale, the action-pin +requirements, and the action-bump merge checks, see Docsy's [Dependency +updates][]. What differs here: + +- The `gomod` manager is disabled; Docsy is [updated manually](#upgrade-docsy). +- `hugo-extended` tracks Docsy's version rather than the latest release; see + [Update Hugo](#update-hugo). +- `packages/hugoautogen` is regenerated from the theme, reverting any direct + Bootstrap or Font Awesome bump. If a security PR bumps them directly, route + the fix through a [Docsy update](#upgrade-docsy). +- No audit test guards the action-pin comments here; the PR reviewer checks + them. ### Deploy logs @@ -48,12 +37,33 @@ exactly what they pin. Both files are regenerated by `npm run update:docsy:pack`, which, unlike `install:safe`, needs `go` at run time (`hugo mod`); it also normalizes the generated package's name, otherwise derived from the checkout-directory name. -The Docsy-update scripts run it as their post phase, and CI reruns it to catch -drift. If a theme update changes either file, commit the result. Installs -themselves declare no lifecycle hooks (guarded by `tests/npm-scripts.test.mjs`), -so neither install mode runs root-package code; `install:safe` remains the -lock-exact, script-free path, while a plain `npm install` resolves and may -rewrite the lock. +The Docsy-update scripts and `approve:hugo` run it as their post phase, and CI +reruns it to catch drift. If a theme update changes either file, commit the +result. The manifests declare no lifecycle hooks (guarded by +`tests/npm-scripts.test.mjs`), so installs run no root-package code. A plain +`npm install` resolves and may rewrite the lock. + +### Install configuration + +`.npmrc` follows Docsy's, with two exceptions: + +- No `@docsy:registry` pin: this repo publishes no packages. +- No `ignore-scripts=true`: the `allowScripts` check in + [Update Hugo](#update-hugo) runs only on an install that runs scripts, and + this repo needs one to exist. + - Here, CI's `npm ci` is that install, and so is a plain `npm install` unless + a user-level `ignore-scripts` is set; either runs hugo-extended's installer. + `npm run install:safe` runs no scripts, so unlike in Docsy's flow it never + fails on a missing approval. + - Docsy can set the key because its `install:safe` re-enables scripts for + hugo-extended alone, in a rebuild step ([officially supported version][]), + so its CI still evaluates the allowlist; this repo's `install:safe` has no + such step. + - With the key set, a lock-synced hugo-extended bump with no approval would + arrive with a green `check-links`. + - The Netlify build is script-free (`install:safe`) and then runs `hugo`, + whose wrapper self-installs the pinned binary: that path checks no approval + here (Docsy's does, through its rebuild step). ### Upgrade Docsy @@ -71,12 +81,25 @@ npm run update:docsy:main ### Update Hugo -To update Hugo, run `npm run update:hugo`, review the new [hugo-extended][] -release, then run `npm run approve:hugo`: script-enabled installs fail until the -new version is approved. The approval gates the install script only (the hugo -binary self-installs at first use), so don't run builds between the two steps. -Automated update PRs skip hugo-extended version bumps for the same reason, -except security updates, which fail CI until approved via `approve:hugo`. +hugo-extended's installer downloads and executes a binary. Every pinned version +has a maintainer approval on record (`allowScripts`), and CI flags a bump +without one: `npm ci` fails ([Install configuration](#install-configuration) +names the installs that run scripts). The approval covers the install script +only; the binary self-installs at first use regardless. The two-step flow that +keeps the approval current is [Docsy's][officially supported version]. What +differs here: + +- The version tracks Docsy's [officially supported version][]. If a security PR + bumps it, take the fix through Docsy instead: once Docsy pins a version + carrying it, update to that version with the command below, then approve. +- Update using + `npm install --save-dev --save-exact --ignore-scripts hugo-extended@X.Y.Z`; + this repo has no `update:hugo` script. For a release younger than the npm + cooldown, add Docsy's [`NPM_CONFIG_MIN_RELEASE_AGE` + override][Dependency updates] to that command. +- `npm run approve:hugo` has no audit to re-run and no rebuild step; instead its + tail regenerates the manifest ([Lockfile and generated manifest][]), and the + `hugo` call in that step self-installs the just-approved binary. ### Develop against a local Docsy @@ -94,12 +117,14 @@ watches it, so theme edits hot-reload. [alternate dashboard]: https://app.netlify.com/sites/goldydocs/deploys [contribution guidelines]: https://main--docsydocs.netlify.app/docs/contributing/ +[Dependency updates]: https://main--docsydocs.netlify.app/project/about/maintainer-notes/#dependency-updates [deploys]: https://app.netlify.com/sites/docsy-example/deploys [Docsy]: https://github.com/docsy/docsy -[hugo-extended]: https://www.npmjs.com/package/hugo-extended [Hugo workspace]: https://gohugo.io/configuration/module/#top-level-settings +[Lockfile and generated manifest]: #lockfile-and-generated-manifest [main ruleset]: https://github.com/docsy/docsy-example/rules/23697395 [Merge requirements]: https://main--docsydocs.netlify.app/project/about/maintainer-notes/#merge-requirements +[officially supported version]: https://main--docsydocs.netlify.app/project/about/maintainer-notes/#official-hugo-version [workflow security analysis]: https://main--docsydocs.netlify.app/project/about/maintainer-notes/#workflow-security-analysis diff --git a/package.json b/package.json index 001ad6854d..0870f28f2e 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "_netlify:prepare": "npm run -s _is:clean && npm run install:safe && npm run -s _is:clean", "_serve": "npm run _hugo-dev -- --minify serve --renderToMemory", "_update:docsy:post": "hugo mod tidy && npm run update:docsy:pack", - "approve:hugo": "npm approve-scripts hugo-extended && npm run update:docsy:pack", + "approve:hugo": "npm run install:safe && npm approve-scripts --allow-scripts-pin hugo-extended && npm run update:docsy:pack", "build:preview": "npm run _hugo-dev -- --minify --baseURL \"${DEPLOY_PRIME_URL:-/}\"", "build:production": "npm run _hugo -- --minify", "build": "npm run _build -- ", @@ -49,7 +49,6 @@ "update:docsy:main": "hugo mod get -u github.com/google/docsy/theme@main && npm run _update:docsy:post", "update:docsy:mod": "hugo mod get -u github.com/google/docsy/theme && npm run _update:docsy:post", "update:docsy:pack": "hugo mod npm pack && npm pkg set 'name=\"@docsy/hugoautogen\"' private=true --json --prefix packages/hugoautogen && npm install --ignore-scripts", - "update:hugo": "npm install --save-dev --save-exact --ignore-scripts hugo-extended@latest", "update:main": "npm run update:packages && npm run update:docsy:main", "update:packages": "npm-check-updates -u -x hugo-extended", "update": "npm run update:packages && npm run update:docsy:mod" diff --git a/renovate.json5 b/renovate.json5 deleted file mode 100644 index d5afef709c..0000000000 --- a/renovate.json5 +++ /dev/null @@ -1,33 +0,0 @@ -{ - $schema: 'https://docs.renovatebot.com/renovate-schema.json', - extends: ['config:best-practices'], - // For details about this repo's settings, see - // CONTRIBUTING.md#dependency-updates - ignorePresets: ['security:minimumReleaseAgeNpm'], - lockFileMaintenance: { enabled: false }, - timezone: 'America/Montreal', - schedule: ['* * * * 0'], // Sunday - labels: ['dependencies'], - gomod: { enabled: false }, - minimumReleaseAge: '7 days', - packageRules: [ - { - groupName: 'all patch versions', - matchUpdateTypes: ['patch'], - }, - { - groupName: 'all minor versions', - matchUpdateTypes: ['minor'], - }, - { - matchPackageNames: ['hugo-extended'], - enabled: false, // Manually updated to match Docsy requirements - }, - { - // Updates through Docsy. - matchPackageNames: ['bootstrap', '@fortawesome/fontawesome-free'], - enabled: false, - }, - ], - // cSpell:ignore gomod hugo fortawesome fontawesome -} diff --git a/renovate.jsonc b/renovate.jsonc new file mode 100644 index 0000000000..54fafa7bab --- /dev/null +++ b/renovate.jsonc @@ -0,0 +1,41 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["config:best-practices"], + // For details about this repo's settings, see + // CONTRIBUTING.md#dependency-updates + "ignorePresets": ["security:minimumReleaseAgeNpm"], + "lockFileMaintenance": { "enabled": false }, + "schedule": ["* * * * 0"], + "timezone": "UTC", + "labels": ["dependencies"], + "gomod": { "enabled": false }, + "minimumReleaseAge": "7 days", + "packageRules": [ + { + "groupName": "all patch versions", + "matchUpdateTypes": ["patch"], + "matchManagers": ["!github-actions"], + }, + { + "groupName": "all minor versions", + "matchUpdateTypes": ["minor"], + "matchManagers": ["!github-actions"], + }, + { + "matchManagers": ["github-actions"], + // Not "runs-on" labels: they have no releases to look up. + "matchDepTypes": ["action", "workflow"], + "overrideDatasource": "github-releases", + "branchTopic": "{{{depNameSanitized}}}-{{{newDigest}}}", + }, + { + "matchPackageNames": ["hugo-extended"], + "enabled": false, + }, + { + "matchPackageNames": ["bootstrap", "@fortawesome/fontawesome-free"], + "enabled": false, + }, + ], + // cSpell:ignore datasource hugo fortawesome fontawesome +}