From 3d1866f42904513abad8c81775c66fc367230b4b Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Fri, 30 Oct 2026 11:12:30 +0000 Subject: [PATCH 1/4] chore(deps): add a 7-day npm min-release-age Set min-release-age=7 in a project .npmrc so npm install and npm update skip versions published in the last week. Document the rule, its override, and how it interacts with npm ci, npm audit fix, and Dependabot security updates. Change-Id: I9e4a7cf86a081e5fe732d10e9f910aa64349ed46 Signed-off-by: Thomas Kosiewski --- .npmrc | 1 + README.md | 11 +++++++++++ 2 files changed, 12 insertions(+) create mode 100644 .npmrc diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..7253a5c --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +min-release-age=7 diff --git a/README.md b/README.md index 9952b76..932b817 100644 --- a/README.md +++ b/README.md @@ -187,6 +187,16 @@ The npm dist-tag is derived from the package version: Manual dispatch and direct `v*.*.*` tag pushes are available for recovery, but merged `release/v*` PRs should be the default path. +## Dependency Updates + +The repository `.npmrc` sets `min-release-age=7`. When `npm install` or `npm update` picks a new version, npm skips every version published less than 7 days ago. A compromised or broken release then has a week to be found and pulled before it can reach `package-lock.json`. The setting needs npm 11.10 or later. The Node version pinned in `mise.toml` ships npm 11.20. + +- `npm ci` installs exactly what the lockfile pins. It does not check release age, and neither does `npm install` for packages that the lockfile already pins. +- An explicit request for a version that is too new fails with `ETARGET` ("No matching version found for ... with a date before ..."). +- To take a newer release on purpose, for example an urgent fix, pass `--min-release-age=0` to that one command and give the reason in the PR. +- `npm audit fix` follows the same rule. If the fixed version is less than 7 days old, npm keeps the vulnerable version, warns, and exits with a non-zero code. Rerun it with `--min-release-age=0` to take the fix. +- Dependabot security updates bypass this setting, so they are not delayed. The repository has no `.github/dependabot.yml`, so Dependabot opens no version-update PRs. + ## Development Notes The native layer currently uses these verified `libghostty-vt` C APIs: @@ -233,6 +243,7 @@ The package has four layers: - The addon can load in the main thread and in each `worker_threads` Worker, because `TerminalWrap::Init` keeps the class constructor in per-env instance data [native/terminal.cc:TerminalWrap::Init]. - The default Ghostty commit in [scripts/build-libghostty-vt.sh:GHOSTTY_COMMIT] is the commit that "Upstream References" names. Change both together. - Build order: `npm ci`, `npm run build:libghostty`, `npm run build:native`, `npm run build`, `npm test`, `npm run smoke` [mise.toml:tasks.ci]. `npm run build:native` fails early when the libghostty-vt header or static library is missing [scripts/resolve-libghostty-vt.mjs:fail]. +- `npm install` and `npm update` pick only dependency versions published at least 7 days ago [.npmrc:min-release-age]. `npm ci` installs the lockfile as pinned. - The npm package ships `dist`, `native`, `scripts`, `binding.gyp`, `prebuilds`, `CHANGELOG.md`, `README.md`, and `LICENSE` [package.json:files]. - A release tag is `v` from `package.json`. When `RELEASE_BRANCH_NAME` is set and not empty, it must be `release/v` [scripts/create-release-tag.mjs:releaseBranchName]. The tag must point at the checked-out `HEAD` [scripts/create-release-tag.mjs:releaseTargetSha]. - The npm dist-tag is the lower-cased first prerelease identifier, or `latest` for a plain version. A non-empty `NPM_PUBLISH_TAG` overrides it [scripts/resolve-npm-publish-tag.mjs:prerelease]. From 16141cd41af1512e61cb15c8251f5e813d33741f Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Fri, 30 Oct 2026 11:18:43 +0000 Subject: [PATCH 2/4] docs(readme): warn that npm update downgrades young lockfile entries Change-Id: I191b33b796b1ec9d33ab419f2c255ac93ae2a71d Signed-off-by: Thomas Kosiewski --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 932b817..583a318 100644 --- a/README.md +++ b/README.md @@ -192,7 +192,8 @@ Manual dispatch and direct `v*.*.*` tag pushes are available for recovery, but m The repository `.npmrc` sets `min-release-age=7`. When `npm install` or `npm update` picks a new version, npm skips every version published less than 7 days ago. A compromised or broken release then has a week to be found and pulled before it can reach `package-lock.json`. The setting needs npm 11.10 or later. The Node version pinned in `mise.toml` ships npm 11.20. - `npm ci` installs exactly what the lockfile pins. It does not check release age, and neither does `npm install` for packages that the lockfile already pins. -- An explicit request for a version that is too new fails with `ETARGET` ("No matching version found for ... with a date before ..."). +- `npm update` moves every lockfile entry younger than 7 days back to an older version, without a warning. This can undo a recent Dependabot security fix. Check the lockfile diff for downgrades and run `npm audit` before you commit. +- An explicit request for a version that is too new fails with `ETARGET` ("No matching version found for ... with a date before ..."). So does `npm update` when `package.json` asks for a version that is too new. - To take a newer release on purpose, for example an urgent fix, pass `--min-release-age=0` to that one command and give the reason in the PR. - `npm audit fix` follows the same rule. If the fixed version is less than 7 days old, npm keeps the vulnerable version, warns, and exits with a non-zero code. Rerun it with `--min-release-age=0` to take the fix. - Dependabot security updates bypass this setting, so they are not delayed. The repository has no `.github/dependabot.yml`, so Dependabot opens no version-update PRs. From 79116cf7cb94daaaacde6fdc777ccbf77bc789bd Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Fri, 30 Oct 2026 11:21:07 +0000 Subject: [PATCH 3/4] chore(deps): refuse npm older than 11.10 with devEngines npm before 11.10 ignores min-release-age, so a contributor on Node 22 (npm 10.9) would resolve brand-new releases with no error. devEngines stops npm 10.9 and later in this checkout. Consumers of the package are not checked. Change-Id: I39f8ed21c6aaff0dc8daa3eb2390cc7da0181e56 Signed-off-by: Thomas Kosiewski --- README.md | 4 ++-- package.json | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 583a318..6cd3398 100644 --- a/README.md +++ b/README.md @@ -189,7 +189,7 @@ Manual dispatch and direct `v*.*.*` tag pushes are available for recovery, but m ## Dependency Updates -The repository `.npmrc` sets `min-release-age=7`. When `npm install` or `npm update` picks a new version, npm skips every version published less than 7 days ago. A compromised or broken release then has a week to be found and pulled before it can reach `package-lock.json`. The setting needs npm 11.10 or later. The Node version pinned in `mise.toml` ships npm 11.20. +The repository `.npmrc` sets `min-release-age=7`. When `npm install` or `npm update` picks a new version, npm skips every version published less than 7 days ago. A compromised or broken release then has a week to be found and pulled before it can reach `package-lock.json`. The setting needs npm 11.10 or later, and older npm ignores it without an error. So `devEngines` in `package.json` makes npm 10.9 and later stop with `EBADDEVENGINES` in this checkout when npm is older than 11.10. The Node version pinned in `mise.toml` ships npm 11.20. Packages that depend on this one are not affected, because npm checks `devEngines` only for the project you run it in. - `npm ci` installs exactly what the lockfile pins. It does not check release age, and neither does `npm install` for packages that the lockfile already pins. - `npm update` moves every lockfile entry younger than 7 days back to an older version, without a warning. This can undo a recent Dependabot security fix. Check the lockfile diff for downgrades and run `npm audit` before you commit. @@ -244,7 +244,7 @@ The package has four layers: - The addon can load in the main thread and in each `worker_threads` Worker, because `TerminalWrap::Init` keeps the class constructor in per-env instance data [native/terminal.cc:TerminalWrap::Init]. - The default Ghostty commit in [scripts/build-libghostty-vt.sh:GHOSTTY_COMMIT] is the commit that "Upstream References" names. Change both together. - Build order: `npm ci`, `npm run build:libghostty`, `npm run build:native`, `npm run build`, `npm test`, `npm run smoke` [mise.toml:tasks.ci]. `npm run build:native` fails early when the libghostty-vt header or static library is missing [scripts/resolve-libghostty-vt.mjs:fail]. -- `npm install` and `npm update` pick only dependency versions published at least 7 days ago [.npmrc:min-release-age]. `npm ci` installs the lockfile as pinned. +- `npm install` and `npm update` pick only dependency versions published at least 7 days ago [.npmrc:min-release-age]. `npm ci` installs the lockfile as pinned. npm older than 11.10 refuses to run here [package.json:devEngines]. - The npm package ships `dist`, `native`, `scripts`, `binding.gyp`, `prebuilds`, `CHANGELOG.md`, `README.md`, and `LICENSE` [package.json:files]. - A release tag is `v` from `package.json`. When `RELEASE_BRANCH_NAME` is set and not empty, it must be `release/v` [scripts/create-release-tag.mjs:releaseBranchName]. The tag must point at the checked-out `HEAD` [scripts/create-release-tag.mjs:releaseTargetSha]. - The npm dist-tag is the lower-cased first prerelease identifier, or `latest` for a plain version. A non-empty `NPM_PUBLISH_TAG` overrides it [scripts/resolve-npm-publish-tag.mjs:prerelease]. diff --git a/package.json b/package.json index b4c477a..078cfe1 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,13 @@ "engines": { "node": ">=20.19" }, + "devEngines": { + "packageManager": { + "name": "npm", + "version": ">=11.10.0", + "onFail": "error" + } + }, "keywords": [ "ghostty", "libghostty", From 61bdc6c21db124d6ac5eea9a0ee9470877da4ec5 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Fri, 30 Oct 2026 11:43:00 +0000 Subject: [PATCH 4/4] docs: state that npm 10.8 and older skip the devEngines check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- _Generated with [`xum`](https://github.com/coder/xum) • Model: `anthropic:claude-opus-5-5` • Thinking: `high`_ --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6cd3398..77ae4f2 100644 --- a/README.md +++ b/README.md @@ -244,7 +244,7 @@ The package has four layers: - The addon can load in the main thread and in each `worker_threads` Worker, because `TerminalWrap::Init` keeps the class constructor in per-env instance data [native/terminal.cc:TerminalWrap::Init]. - The default Ghostty commit in [scripts/build-libghostty-vt.sh:GHOSTTY_COMMIT] is the commit that "Upstream References" names. Change both together. - Build order: `npm ci`, `npm run build:libghostty`, `npm run build:native`, `npm run build`, `npm test`, `npm run smoke` [mise.toml:tasks.ci]. `npm run build:native` fails early when the libghostty-vt header or static library is missing [scripts/resolve-libghostty-vt.mjs:fail]. -- `npm install` and `npm update` pick only dependency versions published at least 7 days ago [.npmrc:min-release-age]. `npm ci` installs the lockfile as pinned. npm older than 11.10 refuses to run here [package.json:devEngines]. +- `npm install` and `npm update` pick only dependency versions published at least 7 days ago [.npmrc:min-release-age]. `npm ci` installs the lockfile as pinned. npm 10.9 to 11.9 refuses to run here [package.json:devEngines]. npm 10.8 and older ignore both `min-release-age` and `devEngines`. - The npm package ships `dist`, `native`, `scripts`, `binding.gyp`, `prebuilds`, `CHANGELOG.md`, `README.md`, and `LICENSE` [package.json:files]. - A release tag is `v` from `package.json`. When `RELEASE_BRANCH_NAME` is set and not empty, it must be `release/v` [scripts/create-release-tag.mjs:releaseBranchName]. The tag must point at the checked-out `HEAD` [scripts/create-release-tag.mjs:releaseTargetSha]. - The npm dist-tag is the lower-cased first prerelease identifier, or `latest` for a plain version. A non-empty `NPM_PUBLISH_TAG` overrides it [scripts/resolve-npm-publish-tag.mjs:prerelease].