From 775a94b2cd313bbcd4ee068204be9225ef142eeb Mon Sep 17 00:00:00 2001 From: Steve Calvert Date: Wed, 21 Oct 2026 14:27:10 -0700 Subject: [PATCH 1/3] chore(release): align credentials and drop dev-only overrides Use the documented token variable for both release steps and require the main branch. Production dependencies already pass the audit, so let development tools resolve their declared dependency ranges instead of overriding them. --- package-lock.json | 665 +++++++++++++++++++++++++++++++++++ package.json | 10 +- tests/release-config.test.ts | 31 ++ 3 files changed, 699 insertions(+), 7 deletions(-) create mode 100644 tests/release-config.test.ts diff --git a/package-lock.json b/package-lock.json index 2936e9e..a3aefcb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -142,6 +142,24 @@ "node": "12.* || 14.* || >= 16" } }, + "node_modules/@embroider/shared-internals/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@embroider/shared-internals/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/@embroider/shared-internals/node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -218,6 +236,7 @@ "os": [ "aix" ], + "peer": true, "engines": { "node": ">=18" } @@ -235,6 +254,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -252,6 +272,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -269,6 +290,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -286,6 +308,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=18" } @@ -303,6 +326,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=18" } @@ -320,6 +344,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -337,6 +362,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -354,6 +380,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -371,6 +398,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -388,6 +416,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -405,6 +434,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -422,6 +452,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -439,6 +470,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -456,6 +488,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -473,6 +506,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -490,6 +524,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -507,6 +542,7 @@ "os": [ "netbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -524,6 +560,7 @@ "os": [ "netbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -541,6 +578,7 @@ "os": [ "openbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -558,6 +596,7 @@ "os": [ "openbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -575,6 +614,7 @@ "os": [ "openharmony" ], + "peer": true, "engines": { "node": ">=18" } @@ -592,6 +632,7 @@ "os": [ "sunos" ], + "peer": true, "engines": { "node": ">=18" } @@ -609,6 +650,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -626,6 +668,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -643,6 +686,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -4813,6 +4857,24 @@ "node": ">= 10" } }, + "node_modules/cacache/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/cacache/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/cacache/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -5431,6 +5493,13 @@ "node": ">=22.12.0" } }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, "node_modules/confbox": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", @@ -5825,6 +5894,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -6427,6 +6497,19 @@ "node": ">= 14.*" } }, + "node_modules/fixturify-project/node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/fixturify-project/node_modules/type-fest": { "version": "4.41.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", @@ -6677,6 +6760,23 @@ "node": ">= 6" } }, + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/glob/node_modules/minimatch": { "version": "5.1.9", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", @@ -6845,6 +6945,23 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/ignore-walk/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ignore-walk/node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/ignore-walk/node_modules/minimatch": { "version": "5.1.9", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", @@ -8244,6 +8361,24 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/matcher-collection/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/matcher-collection/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/matcher-collection/node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", @@ -9476,6 +9611,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/p-defer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", @@ -10417,6 +10562,24 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/rimraf/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -11237,6 +11400,490 @@ } } }, + "node_modules/tsup/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -11752,6 +12399,24 @@ "node": "10.* || >= 12.*" } }, + "node_modules/walk-sync/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/walk-sync/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/walk-sync/node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", diff --git a/package.json b/package.json index 683e624..f003fac 100644 --- a/package.json +++ b/package.json @@ -103,18 +103,14 @@ "access": "public", "registry": "https://registry.npmjs.org" }, - "overrides": { - "esbuild": "^0.28.1", - "tmp": "^0.2.7", - "brace-expansion": "^5.0.8" - }, "release-it": { "git": { - "tagName": "v${version}" + "tagName": "v${version}", + "requireBranch": "main" }, "github": { "release": true, - "tokenRef": "GITHUB_TOKEN" + "tokenRef": "GITHUB_AUTH" }, "npm": { "publish": true diff --git a/tests/release-config.test.ts b/tests/release-config.test.ts new file mode 100644 index 0000000..2833ff3 --- /dev/null +++ b/tests/release-config.test.ts @@ -0,0 +1,31 @@ +import { readFileSync } from "node:fs"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; + +const root = path.resolve(import.meta.dirname, ".."); +const pkg = JSON.parse( + readFileSync(path.join(root, "package.json"), "utf8"), +) as { + overrides?: Record; + "release-it": { + git: { requireBranch?: string }; + github: { tokenRef: string }; + }; +}; +const release = readFileSync(path.join(root, "RELEASE.md"), "utf8"); + +describe("release configuration", () => { + it("uses the documented token variable for the GitHub release", () => { + expect(pkg["release-it"].github.tokenRef).toBe("GITHUB_AUTH"); + expect(release).toContain("GITHUB_AUTH"); + expect(release).not.toContain("GITHUB_TOKEN"); + }); + + it("requires releases from main", () => { + expect(pkg["release-it"].git.requireBranch).toBe("main"); + }); + + it("does not override dev-only dependency versions", () => { + expect(pkg.overrides).toBeUndefined(); + }); +}); From 8e73e87083c6d562e733d44c42b10d04b525d980 Mon Sep 17 00:00:00 2001 From: Steve Calvert Date: Wed, 21 Oct 2026 14:27:34 -0700 Subject: [PATCH 2/3] docs(release): define compatibility policy and label limits Document the required checks and their bypass behavior. Preserve repository-derived label acceptance while warning about labels omitted from release notes. Define correctness-fix versioning and opt-in host format migrations, with verification limits taken from the target citations. --- .github/workflows/pr-label.yml | 14 ++- RELEASE.md | 186 ++++++++++++++++++++------------- tests/release-tooling.test.ts | 151 ++++++++++++++++++++++++++ 3 files changed, 279 insertions(+), 72 deletions(-) create mode 100644 tests/release-tooling.test.ts diff --git a/.github/workflows/pr-label.yml b/.github/workflows/pr-label.yml index 047fa9b..7138850 100644 --- a/.github/workflows/pr-label.yml +++ b/.github/workflows/pr-label.yml @@ -20,8 +20,7 @@ jobs: // Dependabot and friends label their own PRs (dependencies, // javascript, github_actions) and cannot be asked to pick a // changelog label, so the gate does not apply to them. Passing - // rather than skipping the job keeps the check reportable if it - // ever becomes a required status check. + // rather than skipping the job keeps the required check reportable. if (pullRequest.user.type === "Bot") { core.info( `Skipping the changelog label check for bot author ${pullRequest.user.login}.`, @@ -41,4 +40,15 @@ jobs: core.setFailed( `This PR needs at least one repository label. Available labels: ${repositoryLabels.sort().join(", ")}.`, ); + return; + } + // Keep #33's repository-derived acceptance rule. These are + // lerna-changelog's default rendered categories, not an allowlist. + const changelogLabels = [ + "breaking", "enhancement", "bug", "documentation", "internal", + ]; + if (!matched.some((label) => changelogLabels.includes(label.toLowerCase()))) { + core.warning( + `This PR will not appear in the changelog with its current labels. Add a rendered label if it needs release notes: ${changelogLabels.join(", ")}.`, + ); } diff --git a/RELEASE.md b/RELEASE.md index f452149..40f0915 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,92 +1,138 @@ -# Release Process +# Release process -Releases are mostly automated using +A maintainer runs releases with [release-it](https://github.com/release-it/release-it/) and [lerna-changelog](https://github.com/lerna/lerna-changelog/). ## Preparation -Since the majority of the actual release process is automated, the primary -remaining task prior to releasing is confirming that all pull requests that -have been merged since the last release have been labeled with the appropriate -`lerna-changelog` labels and the titles have been updated to ensure they -represent something that would make sense to our users. Some great information -on why this is important can be found at -[keepachangelog.com](https://keepachangelog.com/en/1.0.0/), but the overall -guiding principle here is that changelogs are for humans, not machines. +Review every PR merged since the last release. Give each PR that needs release +notes a rendered `lerna-changelog` label and a title that describes the change +for users. Changelogs are for people; see +[Keep a Changelog](https://keepachangelog.com/en/1.0.0/). -When reviewing merged PR's the labels to be used are: +The rendered labels are: -- breaking - Used when the PR is considered a breaking change. -- enhancement - Used when the PR adds a new feature or enhancement. -- bug - Used when the PR fixes a bug included in a previous release. -- documentation - Used when the PR adds or updates documentation. -- internal - Used for internal changes that still require a mention in the - changelog/release notes. +- `breaking`: an incompatible change to the public contract described below. +- `enhancement`: a new feature or enhancement. +- `bug`: a fix for a bug in a previous release. +- `documentation`: a documentation change. +- `internal`: an internal change that needs release notes. -## Release - -Once the prep work is completed, the actual release is straight forward: +Check the generated changelog for omitted PRs, especially bot PRs and PRs with +only labels outside these five categories. The label check does not guarantee +that a change appears in the changelog or has the right category. -- First, ensure that you have installed your projects dependencies: +## Release -```sh -npm install -``` +1. Check out `main` and pull the reviewed commits. release-it requires `main`. +2. Install the locked dependencies with `npm ci`. +3. Run `npm run check` and `npm run audit`. +4. Set `GITHUB_AUTH` to a [GitHub personal access token][generate-token] with + the `repo` scope. Both lerna-changelog and release-it use this variable. +5. Choose the version increment explicitly. For the 0.12.0 release from 0.11.1: -- Second, ensure that you have obtained a - [GitHub personal access token][generate-token] with the `repo` scope (no - other permissions are needed). Make sure the token is available as the - `GITHUB_AUTH` environment variable. + ```sh + npm run release -- minor + ``` - For instance: +6. Review the proposed version and generated changelog in the prompts. Stop if + either is wrong. release-it then creates the release commit and tag, pushes + them, publishes to npm, and creates the GitHub Release. - ```bash - export GITHUB_AUTH=abc123def456 - ``` +Use `npm run release -- minor` for breaking 0.x releases, not an inferred bump +from a PR label. A `breaking` label controls the changelog category; it does +not choose the release increment. Use `npm run release -- patch` for a patch, +or `npm run release -- major` for a major version. [generate-token]: https://github.com/settings/tokens/new?scopes=repo&description=GITHUB_AUTH+env+variable -- And last (but not least 😁) do your release. - -```sh -npx release-it -``` - -[release-it](https://github.com/release-it/release-it/) manages the actual -release process. It will prompt you to to choose the version number after which -you will have the chance to hand tweak the changelog to be used (for the -`CHANGELOG.md` and GitHub release), then `release-it` continues on to tagging, -pushing the tag and commits, etc. - ## What counts as a breaking change -pluginpack's public contract isn't just its TypeScript API (`src/index.ts`) -and CLI flags — it's also the _shape of the files it writes into a consumer's -repo_ (managed-file layout, manifest field names, default directory -conventions). A change that alters generated output for an existing config -with no code-level signature change (e.g. a different default `outDir` -convention, a renamed manifest field, a different `.pluginpack/.json` -shape) is breaking for the same reason a database migration is breaking: it -can affect a consumer's already-generated, already-committed repo on their -next build, even though nothing in `import { ... } from "@gleanwork/pluginpack"` -changed. Label PRs with `breaking` under that broader definition, not just for -API/CLI signature changes. - -## PR label enforcement - -CI (`.github/workflows/ci.yml`, `labels` job) requires every PR to carry one -of the five labels above before merge — this catches an unlabeled PR, but it -cannot judge whether the label chosen is the _correct_ one. Whether a given -diff is actually breaking under the definition above is still a human call at -review time. +The public contract includes the TypeScript API (`src/index.ts`), CLI flags, +config, and the files written into a consumer's repository. An incompatible +change includes a new default directory, a renamed manifest field, a change to +JSON field order, or a different `.pluginpack/.json` shape. Output can +break a consumer's committed repository even when no API signature changes. +Label these PRs `breaking` and provide migration notes. During 0.x, breaking +changes require a minor release; starting with 1.0, they require a major release. + +### Correctness fixes + +Correctness fixes can ship in a patch release even when output changes, but only +when the old behavior violated the documented contract for the selected target +and format. Examples include preserving source bytes instead of corrupting them, +preventing data loss, and fixing output that the documented host format rejects. +Include a regression test that proves the violation. Label the PR `bug` and +explain the old and corrected behavior in release notes. Call out any action a +consumer must take to rebuild or recover files. + +This exception does not cover a new upstream format, a new default, or removing +previously valid config. Those changes follow the compatibility policy below +and need migration notes. A correctness fix is not a reason to silently replace +one supported host format with another. + +## Output compatibility + +For a fixed target and `format`, 1.x preserves the documented output contract +for existing valid config, including paths, manifest fields and field order. +New optional config may add output when a user opts in. The correctness-fix +exception above still applies. + +An upstream host format change ships as a new opt-in `format` value in a minor release. +The default changes only in a major release. Keep the existing format available +through the current major series. A host dropping support for that format does +not permit pluginpack to change it silently; document the affected host versions +and the opt-in migration. Starting with 1.0, removing a supported format also +requires a major release. The pre-1.0 `codex.format: "legacy"` migration follows +the breaking-minor rule: deprecated in 0.12 and removed in 0.13. + +### Verified against host versions + +These are the verification limits recorded in each target's citations in +`src/targets/.ts`, not a promise of compatibility with every past or +future client release. Where a client version was not recorded, the dated docs +snapshot is the available evidence. It is not a tested version range. Agent +Plugins 1.0.0 is a format version, not a Codex or Copilot client version. + +| Target | Verified version or evidence | Source | +| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `claude` | Claude Code client version was not recorded; manifest validation checked 2026-07-26, install commands 2026-07-25. | [Claude plugin reference](https://code.claude.com/docs/en/plugins-reference) | +| `cursor` | Cursor client version was not recorded; plugin and marketplace docs checked 2026-07-26. Vendored Glean schemas are pinned at `424c3485`, rechecked against `6f3ad6e6` on 2026-07-28. | [Cursor plugin reference](https://cursor.com/docs/reference/plugins.md), [schema provenance](tests/fixtures/cursor/SOURCE.md) | +| `copilot` | Copilot CLI client version was not recorded; native plugin docs checked 2026-07-25 and published manifest placement 2026-07-26. | [Copilot CLI plugin reference](https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-plugin-reference), [published plugin](https://github.com/github/copilot-advanced-security-plugin/blob/main/.github/plugin/plugin.json) | +| `antigravity` | Antigravity client version was not recorded; CLI layout and IDE hooks docs checked 2026-07-25. | [CLI plugins](https://antigravity.google/docs/cli/plugins), [IDE plugins](https://antigravity.google/docs/ide/plugins) | +| `codex` | Agent Plugins 1.0.0 package format; Codex client version was not recorded. Packaging and marketplace docs checked 2026-10-02; legacy manifest checked 2026-07-26, install commands 2026-07-25. | [Codex packaging](https://developers.openai.com/codex/plugins/build), [install commands](https://learn.chatgpt.com/codex/developer-commands), [schema provenance](tests/fixtures/agent-plugins/SOURCE.md) | +| `agent-plugins` | Agent Plugins 1.0.0, checked 2026-10-02. Client profiles do not imply a tested client version range. | [Agent Plugins specification](https://agent-plugins.org/specification), [schema provenance](tests/fixtures/agent-plugins/SOURCE.md) | + +Recheck the relevant citations and conformance tests when changing a target's +output. Record the exact client version when running a host CLI, or the spec +version and docs verification date when no versioned host oracle exists. +Update this list in the same PR. See [CONFORMANCE.md](CONFORMANCE.md) for the +strength and limits of each target's checks. Package-client citations also live +in `src/package-clients.ts`. + +## PR label check and branch protection + +`main` requires the checks `test (22.12.0)`, `test (24)`, and `labels`. +Admins can bypass these requirements. No approving review is required. +The two test checks run `.github/workflows/ci.yml`; the `labels` check runs +`.github/workflows/pr-label.yml`. Maintainers remain responsible for reviewing +changes and the changelog, including any use of the admin bypass. + +The label check accepts any label in the repository's current, paginated label +catalog, as decided in [PR #33](https://github.com/gleanwork/pluginpack/pull/33). +It does not require one of lerna-changelog's five rendered labels. For example, +a PR labeled only `question` passes if that label exists in the repository, +but it will not appear in the changelog. The check warns about this case; the +maintainer must add a rendered label if the change needs release notes. +Bot authors are exempt: their `labels` job reports success without checking +labels. A passing label check cannot judge whether a diff is breaking. ## Accepted risk: dev-only audit findings -`npm audit` (unscoped) reports vulnerabilities in `@release-it-plugins/lerna-changelog`'s -dependency chain (`lerna-changelog` → `make-fetch-happen` → `cacache`/`tar`), -with no upstream fix available. `npm run audit` (what CI runs) is scoped to -`--omit=dev` and reports clean, per `CLAUDE.md`'s documented rationale: -devDependencies never ship in the published package. This is a tracked, -accepted gap, not an oversight — revisit if `lerna-changelog` is ever run -against untrusted input, or replace it if a maintained alternative appears. +`npm audit` without `--omit=dev` reports vulnerabilities in development tools, +including lerna-changelog's dependency chain. `npm run audit` (what CI runs) +is scoped to production dependencies and reports clean. Development +dependencies do not ship in the published package. Do not add overrides for +dev-only findings; revisit this risk if release tooling processes untrusted +input, or replace it when a maintained alternative is available. diff --git a/tests/release-tooling.test.ts b/tests/release-tooling.test.ts new file mode 100644 index 0000000..6583a5d --- /dev/null +++ b/tests/release-tooling.test.ts @@ -0,0 +1,151 @@ +import { readFileSync } from "node:fs"; +import { createRequire } from "node:module"; +import path from "node:path"; +import { load } from "js-yaml"; +import { describe, expect, it, vi } from "vitest"; + +const root = path.resolve(import.meta.dirname, ".."); +const release = readFileSync(path.join(root, "RELEASE.md"), "utf8"); + +// Exercise the actual github-script body with API responses, not a second +// implementation of the workflow. No network or GitHub credentials are needed. +const workflow = load( + readFileSync(path.join(root, ".github/workflows/pr-label.yml"), "utf8"), +) as { jobs: { labels: { steps: { with: { script: string } }[] } } }; +const script = workflow.jobs.labels.steps[0].with.script; +const runLabelCheck = new Function( + "context", + "github", + "core", + `return (async () => { ${script} })();`, +) as (context: object, github: object, core: object) => Promise; + +async function checkLabels(labels: string[], authorType = "User") { + const core = { info: vi.fn(), warning: vi.fn(), setFailed: vi.fn() }; + const listLabelsForRepo = vi.fn(); + const paginate = vi.fn(async (_method, _params, map) => [ + ...map({ data: [{ name: "question" }, { name: "bug" }] }), + ...map({ data: [{ name: "enhancement" }] }), + ]); + await runLabelCheck( + { + repo: { owner: "example", repo: "plugins" }, + payload: { + pull_request: { + user: { type: authorType, login: "author" }, + labels: labels.map((name) => ({ name })), + }, + }, + }, + { paginate, rest: { issues: { listLabelsForRepo } } }, + core, + ); + return { core, paginate, listLabelsForRepo }; +} + +describe("PR label check", () => { + it("still derives accepted labels from the paginated repository catalog", async () => { + const { core, paginate, listLabelsForRepo } = await checkLabels([ + "enhancement", + ]); + expect(paginate).toHaveBeenCalledWith( + listLabelsForRepo, + { owner: "example", repo: "plugins", per_page: 100 }, + expect.any(Function), + ); + expect(core.setFailed).not.toHaveBeenCalled(); + expect(core.warning).not.toHaveBeenCalled(); + }); + + it("does not silently pass a label lerna-changelog does not render", async () => { + const { core } = await checkLabels(["question"]); + // #33 deliberately accepts every repository label, not just changelog + // categories. Warn without changing which labels the gate accepts. + expect(core.setFailed).not.toHaveBeenCalled(); + expect(core.warning).toHaveBeenCalledWith( + expect.stringContaining("will not appear in the changelog"), + ); + }); + + it("keeps the warning's rendered-label list in sync with lerna-changelog", async () => { + const require = createRequire(import.meta.url); + const { fromPath } = require("lerna-changelog/lib/configuration.js") as { + fromPath: (root: string) => { labels: Record }; + }; + const { core } = await checkLabels(["question"]); + expect(core.warning).toHaveBeenCalledWith( + expect.stringContaining(Object.keys(fromPath(root).labels).join(", ")), + ); + }); + + it("does not warn if a rendered changelog label is also present", async () => { + const { core } = await checkLabels(["question", "bug"]); + expect(core.warning).not.toHaveBeenCalled(); + expect(core.setFailed).not.toHaveBeenCalled(); + }); + + it("fails for no label or a label absent from the repository", async () => { + for (const labels of [[], ["not-in-the-catalog"]]) { + const { core } = await checkLabels(labels); + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringContaining("at least one repository label"), + ); + } + }); + + it("exempts bot authors without fetching the label catalog", async () => { + const { core, paginate } = await checkLabels([], "Bot"); + expect(paginate).not.toHaveBeenCalled(); + expect(core.info).toHaveBeenCalledWith( + expect.stringContaining("Skipping the changelog label check"), + ); + expect(core.setFailed).not.toHaveBeenCalled(); + }); +}); + +describe("release policy", () => { + it("records the actual branch protection and label workflow limits", () => { + for (const text of [ + "test (22.12.0)", + "test (24)", + "labels", + "Admins can bypass", + "No approving review is required", + ".github/workflows/pr-label.yml", + "Bot authors are exempt", + "question", + "will not appear in the changelog", + ]) { + expect(release).toContain(text); + } + expect(release).not.toContain("before merge"); + }); + + it("uses explicit increments for breaking 0.x releases", () => { + expect(release).toContain("npm run release -- minor"); + expect(release).toContain("0.12.0"); + }); + + it("distinguishes correctness fixes from host-format migrations", () => { + expect(release).toContain("Correctness fixes can ship in a patch release"); + expect(release).toContain("new opt-in `format` value in a minor release"); + expect(release).toContain("default changes only in a major release"); + expect(release).toContain("field order"); + }); + + it("lists evidence for every target without inventing client versions", () => { + expect(release).toContain("Verified against host versions"); + expect(release).toContain("client version was not recorded"); + expect(release).toContain("Agent Plugins 1.0.0"); + for (const target of [ + "claude", + "cursor", + "copilot", + "antigravity", + "codex", + "agent-plugins", + ]) { + expect(release).toMatch(new RegExp(`\\| \\x60${target}\\x60\\s+\\|`)); + } + }); +}); From 103d95be5ab8d514c532db981283f27da4417ccf Mon Sep 17 00:00:00 2001 From: Steve Calvert Date: Wed, 21 Oct 2026 14:27:34 -0700 Subject: [PATCH 3/3] fix(docs): reject missing command reference metadata Fail documentation generation when a command lacks examples or exit-code guidance. Name the missing case and its source file so new commands cannot silently publish incomplete reference sections. --- README.md | 2 +- src/cli.ts | 12 ++++--- tests/cli-docs.test.ts | 81 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 tests/cli-docs.test.ts diff --git a/README.md b/README.md index 626eded..34a61b6 100644 --- a/README.md +++ b/README.md @@ -869,6 +869,6 @@ Examples: Exit codes: - 0 when docs are current or updated -- 1 when --check finds stale docs +- 1 when --check finds stale docs or CLI reference metadata is missing diff --git a/src/cli.ts b/src/cli.ts index b18c24d..6f8981e 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -430,7 +430,7 @@ function commandUsage(command: Command): string { /** * Example invocations shown in the generated CLI reference, keyed by * command name. Hand-maintained, not derived from the command definitions — - * a new command needs a case added here or its docs section omits examples. + * a new command needs a case added here or docs generation fails. */ function commandExamples(commandName: string): string[] { switch (commandName) { @@ -459,7 +459,9 @@ function commandExamples(commandName: string): string[] { case "docs": return ["pluginpack docs", "pluginpack docs --check"]; default: - return []; + throw new Error( + `src/cli.ts: commandExamples is missing examples for command "${commandName}". Add a commandExamples case.`, + ); } } @@ -501,10 +503,12 @@ function commandExitCodes(commandName: string): string[] { case "docs": return [ "0 when docs are current or updated", - "1 when --check finds stale docs", + "1 when --check finds stale docs or CLI reference metadata is missing", ]; default: - return []; + throw new Error( + `src/cli.ts: commandExitCodes is missing exit codes for command "${commandName}". Add a commandExitCodes case.`, + ); } } diff --git a/tests/cli-docs.test.ts b/tests/cli-docs.test.ts new file mode 100644 index 0000000..a29d98b --- /dev/null +++ b/tests/cli-docs.test.ts @@ -0,0 +1,81 @@ +import { readFileSync } from "node:fs"; +import path from "node:path"; +import { createContext, runInContext } from "node:vm"; +import { Command } from "commander"; +import ts from "typescript"; +import { describe, expect, it } from "vitest"; + +function docsRenderer() { + // Load the private rendering functions without running the CLI entry point. + // This keeps CLI-only helpers out of the package's public exports. + const file = path.resolve(import.meta.dirname, "../src/cli.ts"); + const source = ts.createSourceFile( + file, + readFileSync(file, "utf8"), + ts.ScriptTarget.Latest, + true, + ); + const names = new Set([ + "renderCliReference", + "commandUsage", + "commandExamples", + "commandExitCodes", + ]); + const functions = source.statements + .filter( + (node) => + ts.isFunctionDeclaration(node) && + node.name !== undefined && + names.has(node.name.text), + ) + .map((node) => node.getText(source)) + .join("\n"); + const context = createContext({}); + runInContext(ts.transpileModule(functions, {}).outputText, context); + const render = runInContext("renderCliReference", context) as ( + program: Command, + ) => string; + return { context, render }; +} + +describe("CLI reference completeness", () => { + it("fails generation for a new command with no examples", () => { + const { render } = docsRenderer(); + const program = new Command(); + program.command("preview").description("Preview a package."); + expect(() => render(program)).toThrow( + 'src/cli.ts: commandExamples is missing examples for command "preview". Add a commandExamples case.', + ); + }); + + it("fails generation when examples exist but exit codes are missing", () => { + const { context, render } = docsRenderer(); + // Model a new command whose author added examples but forgot exit codes. + context.commandExamples = () => ["pluginpack preview"]; + const program = new Command(); + program.command("preview").description("Preview a package."); + expect(() => render(program)).toThrow( + 'src/cli.ts: commandExitCodes is missing exit codes for command "preview". Add a commandExitCodes case.', + ); + }); + + it("documents incomplete metadata as a docs command failure", () => { + const { render } = docsRenderer(); + const program = new Command(); + program.command("docs").description("Generate the CLI reference."); + expect(render(program)).toContain( + "1 when --check finds stale docs or CLI reference metadata is missing", + ); + }); + + it("still renders examples and exit codes for a documented command", () => { + const { render } = docsRenderer(); + const program = new Command(); + program.command("build").description("Build a package."); + const reference = render(program); + expect(reference).toContain("Examples:"); + expect(reference).toContain("pluginpack build --target cursor"); + expect(reference).toContain("Exit codes:"); + expect(reference).toContain("0 when all selected targets build"); + }); +});