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([]); + }); +});