From 3c46151cf966b9059ff8bdfedfe624d714515e43 Mon Sep 17 00:00:00 2001 From: Van Nguyen Date: Sat, 10 Oct 2026 23:43:16 +0700 Subject: [PATCH] fix(standard): move lefthook to 2.2.1 and Renovate to 44.149.2 Nothing keeps the tool versions the standard writes up to date: Dependabot watches package.json and the workflows' actions, not TOOL_VERSIONS. Two had fallen behind. Generated output changes for every repository, so the standard moves to 1.4.4. Moving the lefthook range is the first time update changes package.json and nothing else, which leaves the lockfile behind and makes npm ci stop. update now says to install again and commit the lockfile. This repository is updated to 1.4.4 with its lockfile. The tests that named the versions as literals read them from TOOL_VERSIONS, the playground follows the standard, and biome.json names the schema of the biome that is installed. --- .repokeeper.yml | 2 +- .repokeeper/lock.json | 4 +- biome.json | 2 +- package-lock.json | 88 +++++++++++++++++------------------ package.json | 2 +- playground/config.js | 6 +-- src/commands/report.ts | 10 +++- src/version.ts | 6 +-- test/e2e.test.ts | 6 ++- test/modules-core.test.ts | 3 +- test/platforms-gitlab.test.ts | 4 +- test/report.test.ts | 57 +++++++++++++++++++++++ 12 files changed, 129 insertions(+), 61 deletions(-) create mode 100644 test/report.test.ts diff --git a/.repokeeper.yml b/.repokeeper.yml index dd91416..30832cf 100644 --- a/.repokeeper.yml +++ b/.repokeeper.yml @@ -1,6 +1,6 @@ # repokeeper configuration: https://github.com/vannt-dev/repokeeper schema: 1 -standard: 1.4.3 +standard: 1.4.4 platform: github stacks: - node diff --git a/.repokeeper/lock.json b/.repokeeper/lock.json index 987a8c0..388257d 100644 --- a/.repokeeper/lock.json +++ b/.repokeeper/lock.json @@ -1,6 +1,6 @@ { "lockVersion": 1, - "standard": "1.4.3", + "standard": "1.4.4", "entries": [ { "id": "block:.gitattributes#editorconfig", @@ -179,7 +179,7 @@ { "id": "json:package.json#[\"devDependencies\",\"lefthook\"]", "module": "hooks", - "hash": "634625aaac511fc861cf38d9742fb883ecb007120475e3ff1cc8bedd458e6f55", + "hash": "4175be08aee972e10e005bc5581bb25bc6cf650ffa32127bc965d7926f88befa", "target": { "kind": "json", "path": "package.json", diff --git a/biome.json b/biome.json index 604688e..c1a968e 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.5.14/schema.json", + "$schema": "https://biomejs.dev/schemas/2.5.15/schema.json", "vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true }, "files": { "includes": [ diff --git a/package-lock.json b/package-lock.json index e52f74d..320fdda 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "@commitlint/cli": "^21.2.3", "@commitlint/config-conventional": "^21.2.3", "@types/node": "^22.20.4", - "lefthook": "^2.1.14", + "lefthook": "^2.2.1", "typescript": "^7.0.2", "vitest": "^5.0.2" }, @@ -1800,9 +1800,9 @@ "license": "MIT" }, "node_modules/lefthook": { - "version": "2.1.17", - "resolved": "https://registry.npmjs.org/lefthook/-/lefthook-2.1.17.tgz", - "integrity": "sha512-UWpD0VvZNIA2eA/+x4DvFlIT7uxH7aPrPrShE6nsIrX5qHJXxgDkoslAzF73X/YL2o0/9zDw3aOfpr98XchKFw==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/lefthook/-/lefthook-2.2.1.tgz", + "integrity": "sha512-aKEwhQzGbLMkZu5vrFYKJzLvLVblWATYITmN7Ak4qRCDPCoFJhmeJZFuYf3nTXuqG9xTBE+J8xaAEPL2tpuWyw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1810,22 +1810,22 @@ "lefthook": "bin/index.js" }, "optionalDependencies": { - "lefthook-darwin-arm64": "2.1.17", - "lefthook-darwin-x64": "2.1.17", - "lefthook-freebsd-arm64": "2.1.17", - "lefthook-freebsd-x64": "2.1.17", - "lefthook-linux-arm64": "2.1.17", - "lefthook-linux-x64": "2.1.17", - "lefthook-openbsd-arm64": "2.1.17", - "lefthook-openbsd-x64": "2.1.17", - "lefthook-windows-arm64": "2.1.17", - "lefthook-windows-x64": "2.1.17" + "lefthook-darwin-arm64": "2.2.1", + "lefthook-darwin-x64": "2.2.1", + "lefthook-freebsd-arm64": "2.2.1", + "lefthook-freebsd-x64": "2.2.1", + "lefthook-linux-arm64": "2.2.1", + "lefthook-linux-x64": "2.2.1", + "lefthook-openbsd-arm64": "2.2.1", + "lefthook-openbsd-x64": "2.2.1", + "lefthook-windows-arm64": "2.2.1", + "lefthook-windows-x64": "2.2.1" } }, "node_modules/lefthook-darwin-arm64": { - "version": "2.1.17", - "resolved": "https://registry.npmjs.org/lefthook-darwin-arm64/-/lefthook-darwin-arm64-2.1.17.tgz", - "integrity": "sha512-BHF3rD0qmwBulLLe5EdPYQVGvPibZZqHnSPpWT+LgPyYk2+GxtScLJvSD/NgCOFU49Fzs6XgOHg/zlfXcdKFfQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/lefthook-darwin-arm64/-/lefthook-darwin-arm64-2.2.1.tgz", + "integrity": "sha512-SyuDBT5HqNQfi607vMHrYJu45bRN7JoIFdyxLjEPVe34UnZJCesmeYBODEYTyCfRZjPOaTbton4Padu+qvqfCQ==", "cpu": [ "arm64" ], @@ -1837,9 +1837,9 @@ ] }, "node_modules/lefthook-darwin-x64": { - "version": "2.1.17", - "resolved": "https://registry.npmjs.org/lefthook-darwin-x64/-/lefthook-darwin-x64-2.1.17.tgz", - "integrity": "sha512-wRgqAKw2UUQz8eR5pBRo1Cfk2Jaao3u5b6n8EsLcJ6bCVP0pLLhFrNjE1UsTCOlriN7+GgI9SC0l1+zr2eVbTA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/lefthook-darwin-x64/-/lefthook-darwin-x64-2.2.1.tgz", + "integrity": "sha512-qkIdM89T3OxpLp9rssPSdRQ7QKkX0l07xWxxOaZfECn5iB2xVXSD9FQSjYUMNGXtJJWOGqdKqBngGTB/ShDZtQ==", "cpu": [ "x64" ], @@ -1851,9 +1851,9 @@ ] }, "node_modules/lefthook-freebsd-arm64": { - "version": "2.1.17", - "resolved": "https://registry.npmjs.org/lefthook-freebsd-arm64/-/lefthook-freebsd-arm64-2.1.17.tgz", - "integrity": "sha512-pECdcOu/oImbaQyBbmnQyNhdpu1L/bUageBoHSesDB3+2vyHEd6Q3buTUETrI+/gsL993FK5jHnyzsY1X2IFsQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/lefthook-freebsd-arm64/-/lefthook-freebsd-arm64-2.2.1.tgz", + "integrity": "sha512-0jdr/9Q5cGuazrEVquv29AOD77aPc2jAmFU4EtqVVg3PhMJjAyt7bQeA9qdwHhaHhLnaeUZwMwo3IleVm+D4wg==", "cpu": [ "arm64" ], @@ -1865,9 +1865,9 @@ ] }, "node_modules/lefthook-freebsd-x64": { - "version": "2.1.17", - "resolved": "https://registry.npmjs.org/lefthook-freebsd-x64/-/lefthook-freebsd-x64-2.1.17.tgz", - "integrity": "sha512-+z+GPl4mxZC0AcJICDazAWxExz4f8JPW7rvGUQ4Ao8flfmNUoyenrtsx9PUp00KdI+8zPjIujuLMSIEN52y1hQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/lefthook-freebsd-x64/-/lefthook-freebsd-x64-2.2.1.tgz", + "integrity": "sha512-JFKyNZ9qQp9NsuFyAg/CJIBurIHX3AUayFXxuoDMATWAIhhlyK0YMtpWgEFIl1M2iGLBLQ3TiLoCCRnbwndNDg==", "cpu": [ "x64" ], @@ -1879,9 +1879,9 @@ ] }, "node_modules/lefthook-linux-arm64": { - "version": "2.1.17", - "resolved": "https://registry.npmjs.org/lefthook-linux-arm64/-/lefthook-linux-arm64-2.1.17.tgz", - "integrity": "sha512-q01FgqafSJo4WQ6rHj4J1AUe8WLlReZ4Sx5qIjdXMjFuQsbLKlaWUWnJMmZkQQwsi195zDGtOycU44c0vozPiw==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/lefthook-linux-arm64/-/lefthook-linux-arm64-2.2.1.tgz", + "integrity": "sha512-na8Ezm7pK44+WsQqj9tES7v8VseM1MrNeNJhUnM8eINV22KYptWam8evfjZxY3NYxOTwn6f/Zpe/BBvcLKMaTQ==", "cpu": [ "arm64" ], @@ -1893,9 +1893,9 @@ ] }, "node_modules/lefthook-linux-x64": { - "version": "2.1.17", - "resolved": "https://registry.npmjs.org/lefthook-linux-x64/-/lefthook-linux-x64-2.1.17.tgz", - "integrity": "sha512-NyWgJyhmkPspgnhNSvkusKNjCyOOnwiE/IFKkAFkMNfTlst0Dv3UVNxe0e/DlgsGbbFLVieFKhwd4twI2RKoOQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/lefthook-linux-x64/-/lefthook-linux-x64-2.2.1.tgz", + "integrity": "sha512-wl7CR5uNkOiZ38SkW4l9O6wf/Nllbkw90ZZonXRFkbgrnPjLMhZGUt9ucewsPwBQ/ckU1t08JuBHeOywB/CFow==", "cpu": [ "x64" ], @@ -1907,9 +1907,9 @@ ] }, "node_modules/lefthook-openbsd-arm64": { - "version": "2.1.17", - "resolved": "https://registry.npmjs.org/lefthook-openbsd-arm64/-/lefthook-openbsd-arm64-2.1.17.tgz", - "integrity": "sha512-zhIY2sel/yHeIJbQfnlAx+xnIJf6Yh0/RG/Ont33VzCXrzMf4jczQwEAu6VdAD7Ms/Tiv3b9t8aTk7LrU4byJA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/lefthook-openbsd-arm64/-/lefthook-openbsd-arm64-2.2.1.tgz", + "integrity": "sha512-UFwL+AGSqkhQ15kuKl5FMRx/UqbcfcQbGFg5ggYzhT7qBOsTu3a9POXRxt3hsalBeMgkDWsv3IaPlj51YGpCAg==", "cpu": [ "arm64" ], @@ -1921,9 +1921,9 @@ ] }, "node_modules/lefthook-openbsd-x64": { - "version": "2.1.17", - "resolved": "https://registry.npmjs.org/lefthook-openbsd-x64/-/lefthook-openbsd-x64-2.1.17.tgz", - "integrity": "sha512-aHGnjzyyFBOQEJjmie7px67nNVRUJ+DUG2R/BOSABdH/sGwVYLg6GG0pjnSUkWPoIv3T+CFoG9YcGYO+h3MLoQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/lefthook-openbsd-x64/-/lefthook-openbsd-x64-2.2.1.tgz", + "integrity": "sha512-IMKEuxJLo91C1yjhJ42EadfXUb3ECMkvTKpSt17kwaiyZsvvZ8LGdrXwQZiLNJt2M5ECuTVCQUvDimM6mGraOw==", "cpu": [ "x64" ], @@ -1935,9 +1935,9 @@ ] }, "node_modules/lefthook-windows-arm64": { - "version": "2.1.17", - "resolved": "https://registry.npmjs.org/lefthook-windows-arm64/-/lefthook-windows-arm64-2.1.17.tgz", - "integrity": "sha512-NN6Ke+O019lhlGAbrbFDf719E4vGt0Y0O30Gr5Yoe3ZdFW6qQFOlP+gVq/shFvJ7iw6rFA/xyOu6wgM+Lytimg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/lefthook-windows-arm64/-/lefthook-windows-arm64-2.2.1.tgz", + "integrity": "sha512-Pi8vOvObjg8HJDl8d96s7jsI6TN33klpqv6tXSXpoKEeoAwEh5Y9NawO6bzoOpLfAxf2SfMBcuaBzFQ5zE2gjA==", "cpu": [ "arm64" ], @@ -1949,9 +1949,9 @@ ] }, "node_modules/lefthook-windows-x64": { - "version": "2.1.17", - "resolved": "https://registry.npmjs.org/lefthook-windows-x64/-/lefthook-windows-x64-2.1.17.tgz", - "integrity": "sha512-yV07jCYAzqr4UoMFtVs5frVgKzxppWd/tjr6OpXpxYqB47fomx3w9vxgEOlb2opODFTFm2wdeESLfwVO9Cy/DQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/lefthook-windows-x64/-/lefthook-windows-x64-2.2.1.tgz", + "integrity": "sha512-7FtuPrc1XzxIFLAbzneOF1hr9r/vGi2lcMpX14QG9d/mnLhwu4EyNBh48JNEqsQqrOB9QKavsNqxVl1e8zcukA==", "cpu": [ "x64" ], diff --git a/package.json b/package.json index a642093..e2be2b0 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "@commitlint/cli": "^21.2.3", "@commitlint/config-conventional": "^21.2.3", "@types/node": "^22.20.4", - "lefthook": "^2.1.14", + "lefthook": "^2.2.1", "typescript": "^7.0.2", "vitest": "^5.0.2" }, diff --git a/playground/config.js b/playground/config.js index b9547df..da0f79f 100644 --- a/playground/config.js +++ b/playground/config.js @@ -5,10 +5,10 @@ */ /** The standard the generated file names; the test holds it to STANDARD_VERSION. */ -export const STANDARD = "1.4.3"; +export const STANDARD = "1.4.4"; -/** The first repokeeper that writes everything the form offers: the last addition is strict on GitLab. */ -export const NEEDS_VERSION = "0.8.0"; +/** The first repokeeper that knows the standard the generated file names. */ +export const NEEDS_VERSION = "0.8.2"; export const STACKS = [ { id: "node", label: "Node.js" }, diff --git a/src/commands/report.ts b/src/commands/report.ts index 4ee1460..9a2d87b 100644 --- a/src/commands/report.ts +++ b/src/commands/report.ts @@ -72,7 +72,7 @@ export function printResult(io: Io, result: SyncResult): void { const WROTE: string[] = ["create", "write", "adopt"]; -/** What the user still has to do after a write: install the hooks, renormalize line endings. */ +/** What the user still has to do after a write: install the hooks or the dependencies, renormalize line endings. */ export async function nextSteps(root: string, ctx: ModuleContext, result: SyncResult): Promise { const steps: string[] = []; const wrote = (path: string) => result.decisions.some((d) => d.output.path === path && WROTE.includes(d.action)); @@ -82,6 +82,14 @@ export async function nextSteps(root: string, ctx: ModuleContext, result: SyncRe ? "install dependencies; this installs the git hooks" : `run \`npx --yes lefthook@${TOOL_VERSIONS.lefthook} install\` to enable the git hooks`, ); + } else if ( + result.decisions.some( + (d) => d.output.kind === "json" && /(^|\/)package\.json$/.test(d.output.path) && WROTE.includes(d.action), + ) + ) { + // A range written into package.json is not in the lockfile yet, and a frozen install (`npm ci`) refuses a + // lockfile that disagrees with it. A new standard moves such a range without touching lefthook.yml. + steps.push("package.json changed; install dependencies and commit the lockfile with it"); } if (wrote(".gitattributes")) { const crlf = await crlfTrackedFiles(root); diff --git a/src/version.ts b/src/version.ts index 721ab7f..d016536 100644 --- a/src/version.ts +++ b/src/version.ts @@ -5,10 +5,10 @@ export const PACKAGE_VERSION: string = ( ).version; /** The standard this build of repokeeper applies. Bump it whenever generated output changes. */ -export const STANDARD_VERSION = "1.4.3"; +export const STANDARD_VERSION = "1.4.4"; export const TOOL_VERSIONS = { - lefthook: "2.1.14", + lefthook: "2.2.1", commitlintCli: "21.2.3", commitlintConventional: "21.2.3", semanticRelease: "25.0.9", @@ -18,7 +18,7 @@ export const TOOL_VERSIONS = { semanticReleaseGitlab: "13.3.3", // 10.x needs conventional-changelog-writer 9, and semantic-release 25's notes generator bundles 8 conventionalCommitsPreset: "9.3.1", - renovate: "44.128.1", + renovate: "44.149.2", } as const; /** Repository hosting the reusable workflows that generated CI files call. */ diff --git a/test/e2e.test.ts b/test/e2e.test.ts index 3f80a4f..ca13e29 100644 --- a/test/e2e.test.ts +++ b/test/e2e.test.ts @@ -5,7 +5,7 @@ import { join } from "node:path"; import { describe, expect, it } from "vitest"; import { parse } from "yaml"; import { run } from "../src/cli.js"; -import { STANDARD_VERSION } from "../src/version.js"; +import { STANDARD_VERSION, TOOL_VERSIONS } from "../src/version.js"; import { capture, tempDir } from "./helpers.js"; const sh = (cwd: string, ...args: string[]) => execFileSync("git", args, { cwd, stdio: "pipe" }); @@ -49,7 +49,9 @@ describe("repokeeper end to end", () => { expect(config.modules.health.codeowners).toEqual(["@demo-owner"]); expect(config.modules.health.copyright).toMatch(/^\d{4} Demo User$/); expect(existsSync(join(dir, "lefthook.yml"))).toBe(true); - expect(JSON.parse(await readFile(join(dir, "package.json"), "utf8")).devDependencies.lefthook).toBe("^2.1.14"); + expect(JSON.parse(await readFile(join(dir, "package.json"), "utf8")).devDependencies.lefthook).toBe( + `^${TOOL_VERSIONS.lefthook}`, + ); commitAll(dir); expect((await repokeeper(dir, "check")).code).toBe(0); diff --git a/test/modules-core.test.ts b/test/modules-core.test.ts index 9f8d0b7..eb3fbe7 100644 --- a/test/modules-core.test.ts +++ b/test/modules-core.test.ts @@ -9,6 +9,7 @@ import { commitsModule } from "../src/modules/commits.js"; import { editorconfigModule } from "../src/modules/editorconfig.js"; import { gitignoreModule } from "../src/modules/gitignore.js"; import { hooksModule } from "../src/modules/hooks.js"; +import { TOOL_VERSIONS } from "../src/version.js"; import { makeContext, nodeResolved, tempDir } from "./helpers.js"; const execFileAsync = promisify(execFile); @@ -86,7 +87,7 @@ describe("hooks", () => { }); expect(config["pre-push"]).toEqual({ jobs: [{ name: "node:test", run: "npm test" }] }); const json = outputs.find((o) => o.kind === "json") as JsonOutput; - expect([json.keyPath.join("."), json.value]).toEqual(["devDependencies.lefthook", "^2.1.14"]); + expect([json.keyPath.join("."), json.value]).toEqual(["devDependencies.lefthook", `^${TOOL_VERSIONS.lefthook}`]); }); it("drops hooks that have nothing to run", () => { diff --git a/test/platforms-gitlab.test.ts b/test/platforms-gitlab.test.ts index 132b590..23f2c54 100644 --- a/test/platforms-gitlab.test.ts +++ b/test/platforms-gitlab.test.ts @@ -13,7 +13,7 @@ import { releaseModule } from "../src/modules/release.js"; import { planOutputs } from "../src/plan.js"; import { githubPlatform } from "../src/platforms/github.js"; import { platformFor } from "../src/platforms/index.js"; -import { PACKAGE_VERSION } from "../src/version.js"; +import { PACKAGE_VERSION, TOOL_VERSIONS } from "../src/version.js"; import { capture, gitlabContext, makeContext, nodeResolved, syncOnce, tempDir } from "./helpers.js"; const files = (outputs: Output[]) => @@ -282,7 +282,7 @@ describe("gitlab dependency updates", () => { it("runs Renovate only in scheduled pipelines with RENOVATE_TOKEN", () => { expect(keys(depsModule.outputs(gitlabContext())).renovate).toEqual({ stage: "test", - image: "renovate/renovate:44.128.1", + image: `renovate/renovate:${TOOL_VERSIONS.renovate}`, rules: [{ if: '$CI_PIPELINE_SOURCE == "schedule" && $RENOVATE_TOKEN' }], variables: { RENOVATE_PLATFORM: "gitlab", diff --git a/test/report.test.ts b/test/report.test.ts new file mode 100644 index 0000000..152bc35 --- /dev/null +++ b/test/report.test.ts @@ -0,0 +1,57 @@ +import { describe, expect, it } from "vitest"; +import { nextSteps } from "../src/commands/report.js"; +import type { Output } from "../src/model.js"; +import type { SyncResult } from "../src/sync/sync.js"; +import { TOOL_VERSIONS } from "../src/version.js"; +import { makeContext } from "./helpers.js"; + +const hooksFile: Output = { kind: "file", module: "hooks", path: "lefthook.yml", content: "" }; +const lefthookRange = (path = "package.json"): Output => ({ + kind: "json", + module: "hooks", + path, + keyPath: ["devDependencies", "lefthook"], + value: `^${TOOL_VERSIONS.lefthook}`, +}); + +const result = (...decisions: SyncResult["decisions"]): SyncResult => ({ decisions, removals: [] }); + +describe("what to do after a write", () => { + // A new standard can move a dependency's range and nothing else. The lockfile then names a version the range no + // longer allows, and `npm ci` stops there. + it("says to install again when only a range in package.json moved", async () => { + const steps = await nextSteps( + ".", + makeContext(), + result({ output: hooksFile, action: "unchanged" }, { output: lefthookRange(), action: "write" }), + ); + expect(steps).toEqual(["package.json changed; install dependencies and commit the lockfile with it"]); + }); + + it("says it for a package.json in a folder too", async () => { + const steps = await nextSteps( + ".", + makeContext(), + result({ output: lefthookRange("web/package.json"), action: "write" }), + ); + expect(steps).toEqual(["package.json changed; install dependencies and commit the lockfile with it"]); + }); + + it("says it once when the hooks are new: installing them is the same step", async () => { + const steps = await nextSteps( + ".", + makeContext(), + result({ output: hooksFile, action: "create" }, { output: lefthookRange(), action: "create" }), + ); + expect(steps).toEqual(["install dependencies; this installs the git hooks"]); + }); + + it("says nothing when nothing in package.json was written", async () => { + const steps = await nextSteps( + ".", + makeContext(), + result({ output: hooksFile, action: "unchanged" }, { output: lefthookRange(), action: "unchanged" }), + ); + expect(steps).toEqual([]); + }); +});