From f1e4142480db3d28d9ce6a56b13e6a70a1209398 Mon Sep 17 00:00:00 2001 From: harlan-github-agent Date: Fri, 18 Sep 2026 21:07:53 +1000 Subject: [PATCH 1/4] chore(ci): run the PR gate through pnpm pipeline task cache --- .github/workflows/test.yml | 11 +-- package.json | 3 +- pnpm-lock.yaml | 158 +++++++++++++++++++++++++++++++++++++ pnpm-workspace.yaml | 69 ++++++++++++++++ 4 files changed, 235 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 13ef3a11..b47db71a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,16 +20,17 @@ concurrency: jobs: harlan-desktop: - uses: harlan-zw/harlan-nuxt/.github/workflows/harlan-desktop-site-ci.yml@main + uses: harlan-zw/harlan-nuxt/.github/workflows/harlan-desktop-site-ci.yml@feat-site-gate-task-cache with: # Ephemeral containers on the workstation. Safe here because the # repository is private; a self-hosted runner on a public repository would # let a fork pull request run code on that host. runs-on: '["self-hosted", "linux", "x64", "harlan-desktop-ci"]' - # vitest resolves aliases through `.nuxt/tsconfig.json`, and every suite - # fails to transform without it. `prepare-command` rather than dropping - # `--ignore-scripts`, so lint still skips the prepare it has no use for. - prepare-command: pnpm nuxt prepare + # Each check runs through `pnpm pipeline`, so an unchanged task restores + # from the pool's shared cache volume instead of re-running. The graph + # lives in pnpm-workspace.yaml: vitest still gets its `.nuxt/tsconfig.json` + # because every pipeline runs `dev:prepare` as a cacheable task. + task-cache: true timeout-minutes: 30 # The only gate between a merge and production. `Deploy to Cloudflare` fires on diff --git a/package.json b/package.json index 3390ff90..bdf16365 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skilld-dev", "type": "module", "private": true, - "packageManager": "pnpm@11.21.0", + "packageManager": "pnpm@12.4.2", "description": "Agent skills written by real maintainers in their own GitHub repos, with links to source", "author": { "name": "Harlan Wilton", @@ -17,6 +17,7 @@ "dev": "nuxi dev", "build": "nuxi prepare && nuxi build", "preview": "nuxi preview", + "dev:prepare": "nuxt prepare", "perf:cold-start": "node scripts/tools/measure-nitro-cold-start.mjs", "postinstall": "nuxt prepare", "lint": "eslint .", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 502778e2..fe9d26aa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,3 +1,161 @@ +--- +lockfileVersion: '9.0' + +importers: + + .: + configDependencies: {} + packageManagerDependencies: + pnpm: + specifier: 12.4.2 + version: 12.4.2 + +packages: + + '@pnpm/exe.android-arm64@12.4.2': + resolution: {integrity: sha512-E255MbcQ0V1577M2BV0ajWuP7KmTPYA0jqZnk3rK6Lq3kTvZWulMMb7iqRmnLsQbyWTkMEB2CfyM70loVDA8xg==} + cpu: [arm64] + os: [android] + + '@pnpm/exe.android-x64@12.4.2': + resolution: {integrity: sha512-J1pSeCUwuKxMG70ZzpWn8JzElxiEa8NN/3N0SlZRtU2xbztChaJCKF3HaBNIj9yPfeofWzJo/lwNvDDjraQuZw==} + cpu: [x64] + os: [android] + + '@pnpm/exe.darwin-arm64@12.4.2': + resolution: {integrity: sha512-A0WDo8iErfZBXgrLseQxw8i8Y9ctUpOEl/Uu+cubnTzpD8tT9ykIB548L8YTM2WD4OS+ZOHSxy8aGZcvKq8PaQ==} + cpu: [arm64] + os: [darwin] + + '@pnpm/exe.darwin-x64@12.4.2': + resolution: {integrity: sha512-MSgJdovBWHcb5DEOvfPH9yNi/T5O1Xa4ess+E1ESGo/5yuty7S4JoiIjami+fsNXfnoQMlwsMUqYU4zAvBcNCw==} + cpu: [x64] + os: [darwin] + + '@pnpm/exe.freebsd-x64@12.4.2': + resolution: {integrity: sha512-h2YumlQSNvgbRPv+RXwABohX65f9bOBZn+jMIt7bFDISZPCzQ+Nvpt6Awbp4ip5PwQgYxbu5iREJ1fHE39Fm8A==} + cpu: [x64] + os: [freebsd] + + '@pnpm/exe.linux-arm64-musl@12.4.2': + resolution: {integrity: sha512-LwSEtSEDTv6S51YLs3YvSkPyun/QmfMic1UGICUkPWFu6ByP43RdMlkKvmVkfGhAYCpnxO057vrmyJqtfZrPCA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@pnpm/exe.linux-arm64@12.4.2': + resolution: {integrity: sha512-2dSiDXyhx+RTHsewxex8f/jVjqQXWJ2oow4kCVHEWdZKeBpgMxvZ6fHkTAUBJXgqhbKIDHvuNlZBP7gJfUWL5A==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-ppc64@12.4.2': + resolution: {integrity: sha512-8Itc+jQk+MTz04LS9D1RcH+VctAWmzM4l1cJQ+Sx7pAJNo7EKHdRMbC0Tok1jyQ7eEHNJZD5EleYneZqWfZM+g==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-riscv64@12.4.2': + resolution: {integrity: sha512-hleOeqhTVpH+z9RVMGnxvU4ZnrkBUClWjCbHD9u6kwyqhGSpevoU1wTGish+CBRhmIgMAy9pAfFpqhbAKOKNfw==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-s390x@12.4.2': + resolution: {integrity: sha512-LAsQRRdP9aToENR6dtcIJ9l+e1zMYOX0tQcLGpRyLPVBQcYRLlvAPcmDshsiIHQjp05SDa9FI0czX1ZQ1a7a/A==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-x64-musl@12.4.2': + resolution: {integrity: sha512-kzfzH2/0BWdTABK14Yj5a1xsdkTEQUp2eXEPNakaD9jKL025lq3hyaKHIz/gIZaPDMe/1bFFK/En4ztFlbBJxw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@pnpm/exe.linux-x64@12.4.2': + resolution: {integrity: sha512-/pbt0UVTa8NMDhzOWLQRfZ6G9ROKXlJPZtx845BqyWfc7hCrWXhTLBd70yO2y8+E+IWN3oHM1s/JsIQNGk1yvg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.win32-arm64@12.4.2': + resolution: {integrity: sha512-PsW19e4dAUNpZ0cS9flaxFuAmpt2dKlH/Vvi8TZ4qyJjjQzue/CEsWm+6wKVP4CJ7IT8RdL4qh+soOPWIgF2Zw==} + cpu: [arm64] + os: [win32] + + '@pnpm/exe.win32-x64@12.4.2': + resolution: {integrity: sha512-+xGoeE0g55ztWvl8i5QqdmNfW3nIrTVcoQrNshEOwxthm9Ag48oXton3uxOA3/SANyz5AXexEjj2KO20NnOrEw==} + cpu: [x64] + os: [win32] + + pnpm@12.4.2: + resolution: {integrity: sha512-CK3GYTGAJ1x8ntraOdzwjJxhrU5+rzMKTzRh8QKw+QdCNFTRF/mOctR/7wYWBwZE17/8lzpqV/UJCm18NosHyQ==} + engines: {node: '>=18.*'} + hasBin: true + +snapshots: + + '@pnpm/exe.android-arm64@12.4.2': + optional: true + + '@pnpm/exe.android-x64@12.4.2': + optional: true + + '@pnpm/exe.darwin-arm64@12.4.2': + optional: true + + '@pnpm/exe.darwin-x64@12.4.2': + optional: true + + '@pnpm/exe.freebsd-x64@12.4.2': + optional: true + + '@pnpm/exe.linux-arm64-musl@12.4.2': + optional: true + + '@pnpm/exe.linux-arm64@12.4.2': + optional: true + + '@pnpm/exe.linux-ppc64@12.4.2': + optional: true + + '@pnpm/exe.linux-riscv64@12.4.2': + optional: true + + '@pnpm/exe.linux-s390x@12.4.2': + optional: true + + '@pnpm/exe.linux-x64-musl@12.4.2': + optional: true + + '@pnpm/exe.linux-x64@12.4.2': + optional: true + + '@pnpm/exe.win32-arm64@12.4.2': + optional: true + + '@pnpm/exe.win32-x64@12.4.2': + optional: true + + pnpm@12.4.2: + optionalDependencies: + '@pnpm/exe.android-arm64': 12.4.2 + '@pnpm/exe.android-x64': 12.4.2 + '@pnpm/exe.darwin-arm64': 12.4.2 + '@pnpm/exe.darwin-x64': 12.4.2 + '@pnpm/exe.freebsd-x64': 12.4.2 + '@pnpm/exe.linux-arm64': 12.4.2 + '@pnpm/exe.linux-arm64-musl': 12.4.2 + '@pnpm/exe.linux-ppc64': 12.4.2 + '@pnpm/exe.linux-riscv64': 12.4.2 + '@pnpm/exe.linux-s390x': 12.4.2 + '@pnpm/exe.linux-x64': 12.4.2 + '@pnpm/exe.linux-x64-musl': 12.4.2 + '@pnpm/exe.win32-arm64': 12.4.2 + '@pnpm/exe.win32-x64': 12.4.2 + +--- lockfileVersion: '9.0' settings: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 6fcf81e2..57ab9fcf 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -72,6 +72,75 @@ trustPolicy: no-downgrade packages: - workers/* +# The site root is a workspace project too, so `pnpm pipeline` can run its +# tasks. Every task that names no inputs hashes every tracked file, which is +# why lint carries no inputs: `eslint .` reads the whole repository. The +# narrowed inputs below exclude only trees those tasks never read, so a +# docs-only push leaves typecheck and test as cache hits. +includeWorkspaceRoot: true +tasks: + dev:prepare: + dependsOn: [] + inputs: + - nuxt.config.ts + - app/** + - modules/** + - layers/** + - server/** + - shared/** + - workers/** + - package.json + - pnpm-workspace.yaml + - tsconfig.json + outputs: ['.nuxt/**'] + lint: + dependsOn: [] + outputs: [] + test: + dependsOn: [dev:prepare] + inputs: + - app/** + - server/** + - shared/** + - workers/** + - modules/** + - layers/** + - test/** + - skills/** + - migrations/** + - schema.sql + - vitest.config.ts + - nuxt.config.ts + - tsconfig.json + - .dev.vars.example + - package.json + - pnpm-workspace.yaml + + outputs: [] + typecheck: + dependsOn: [dev:prepare] + env: [NODE_OPTIONS] + inputs: + - app/** + - server/** + - shared/** + - workers/** + - modules/** + - layers/** + - test/** + - '*.ts' + - '*.jsonc' + - '*.toml' + - tsconfig.json + - .dev.vars.example + - package.json + - pnpm-workspace.yaml + outputs: [] +pipelines: + lint: [lint] + test: [dev:prepare, test] + + typecheck: [dev:prepare, typecheck] patchedDependencies: hookable@5.5.3: patches/hookable@5.5.3.patch hookable@6.1.1: patches/hookable@6.1.1.patch From 2ce06b8d349346d5f02e880d00a05b6787ec353d Mon Sep 17 00:00:00 2001 From: harlan-github-agent Date: Fri, 18 Sep 2026 21:19:55 +1000 Subject: [PATCH 2/4] fix(ci): keep the artifact-signer suite out of the test pipeline --- pnpm-workspace.yaml | 6 ++++++ workers/artifact-signer/package.json | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 57ab9fcf..7365ab4b 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -77,6 +77,12 @@ packages: # why lint carries no inputs: `eslint .` reads the whole repository. The # narrowed inputs below exclude only trees those tasks never read, so a # docs-only push leaves typecheck and test as cache hits. +# +# The artifact-signer worker's suite is `test:worker`, outside the `test` +# pipeline on purpose. Its transform reaches files above the worker directory, +# whose tsconfig chain resolves through the root `.nuxt`, and a task in one +# project cannot wait on another project's `dev:prepare` without a dependency +# edge between them. includeWorkspaceRoot: true tasks: dev:prepare: diff --git a/workers/artifact-signer/package.json b/workers/artifact-signer/package.json index e16d4e00..3fffc77b 100644 --- a/workers/artifact-signer/package.json +++ b/workers/artifact-signer/package.json @@ -6,10 +6,10 @@ "build": "wrangler deploy --dry-run --config wrangler.jsonc --outdir dist", "deploy:production": "wrangler deploy --config wrangler.jsonc", "lint": "eslint src test", - "test": "vitest run --config vitest.config.ts", "typecheck": "tsc --noEmit", "types": "wrangler types worker-configuration.d.ts --config wrangler.jsonc --env-file .dev.vars.example --env-interface ArtifactSignerEnv", - "types:check": "wrangler types worker-configuration.d.ts --config wrangler.jsonc --env-file .dev.vars.example --env-interface ArtifactSignerEnv --check" + "types:check": "wrangler types worker-configuration.d.ts --config wrangler.jsonc --env-file .dev.vars.example --env-interface ArtifactSignerEnv --check", + "test:worker": "vitest run --config vitest.config.ts" }, "dependencies": { "zod": "catalog:" From 6af10a206013ad10df286979cefcecb9430df966 Mon Sep 17 00:00:00 2001 From: harlan-github-agent Date: Fri, 18 Sep 2026 22:20:34 +1000 Subject: [PATCH 3/4] fix(ci): hash scripts and checks trees into the test and typecheck keys 24 suites import scripts/ or checks/, so a scripts-only push replayed a stale green test result. vue-tsc does not reach scripts/ today, but the generated tsconfig that decides its reach can drift, so typecheck hashes the same trees. --- pnpm-workspace.yaml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 7365ab4b..7137e13c 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -76,7 +76,9 @@ packages: # tasks. Every task that names no inputs hashes every tracked file, which is # why lint carries no inputs: `eslint .` reads the whole repository. The # narrowed inputs below exclude only trees those tasks never read, so a -# docs-only push leaves typecheck and test as cache hits. +# docs-only push leaves typecheck and test as cache hits. `scripts/**` and +# `checks/**` count even where a task does not read them today: suites import +# both, and the generated tsconfig that sets typecheck's reach can drift. # # The artifact-signer worker's suite is `test:worker`, outside the `test` # pipeline on purpose. Its transform reaches files above the worker directory, @@ -112,6 +114,8 @@ tasks: - modules/** - layers/** - test/** + - scripts/** + - checks/** - skills/** - migrations/** - schema.sql @@ -121,7 +125,6 @@ tasks: - .dev.vars.example - package.json - pnpm-workspace.yaml - outputs: [] typecheck: dependsOn: [dev:prepare] @@ -134,6 +137,8 @@ tasks: - modules/** - layers/** - test/** + - scripts/** + - checks/** - '*.ts' - '*.jsonc' - '*.toml' From 80972596d08f234e77c643b0d477d9768a3bb73e Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Tue, 22 Sep 2026 00:21:55 +1000 Subject: [PATCH 4/4] ci(test): pin the site gate back to main harlan-nuxt#159 merged as 75ff091, so the reusable gate carries task-cache mode on main. The feature-branch pin was temporary, to prove the gate change end to end before it landed. --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b47db71a..8d62fd76 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,7 +20,7 @@ concurrency: jobs: harlan-desktop: - uses: harlan-zw/harlan-nuxt/.github/workflows/harlan-desktop-site-ci.yml@feat-site-gate-task-cache + uses: harlan-zw/harlan-nuxt/.github/workflows/harlan-desktop-site-ci.yml@main with: # Ephemeral containers on the workstation. Safe here because the # repository is private; a self-hosted runner on a public repository would