Skip to content

Commit 95c80d0

Browse files
authored
build(deps): standardize on pnpm 11 and build Docker image from repo root (#376)
* build(deps): standardize on pnpm 11 with config in pnpm-workspace.yaml Nothing pinned the pnpm version, so local/Docker ran pnpm 11 while CI pinned pnpm 9. pnpm 11 no longer reads the 'pnpm' field in package.json, so every local install silently stripped the overrides block from the lockfile and mismatched CI's pnpm 9 — forcing a manual re-gen that must not be committed. Pin packageManager to pnpm@11.5.0 (corepack/CI/Docker now agree) and move overrides, onlyBuiltDependencies, and minimumReleaseAge out of package.json#pnpm into pnpm-workspace.yaml — overrides: stays a map, onlyBuiltDependencies becomes the allowBuilds map, minimumReleaseAge: 0 replaces the env workaround. CI workflows drop the hardcoded 'version: 9' so pnpm/action-setup follows packageManager. The lockfile is byte-identical after the move, confirming the churn was purely the misplaced config; a frozen install is now stable and the full suite passes under pnpm 11. * build(docker): build testplanit image from repo root, drop duplicate lockfile The testplanit image now builds from the repository root so it uses the single root pnpm-lock.yaml + pnpm-workspace.yaml directly, eliminating the committed testplanit/pnpm-lock.yaml duplicate that had to be hand-mirrored on every install. Combined with the pnpm 11 packageManager pin, this ends the recurring re-gen/don't-commit churn. - docker-bake.hcl: context "..", dockerfile "testplanit/Dockerfile" - Dockerfile: pnpm workspace install at /app with the app at /app/testplanit; pnpm provisioned via corepack; production deps via `pnpm deploy --prod --legacy` for a self-contained node_modules; the generated Prisma client and ZenStack files are copied into the deploy tree by locating them there, since the prod peer-hash dir name differs from the build tree - .dockerignore (new): keeps the root context small; artifact patterns are anchored to real locations so a floating glob can't strip a source route such as app/api/test-results (only reached via dynamic fetch, so the build would not have failed -- it would just 404 at runtime) - docker-compose{,.dev,.prod}.yml: context "..", working_dir/volumes shifted to /app/testplanit - release.yml: drop the 4 `cp pnpm-lock.yaml testplanit/...` steps; add --allow=fs.read=.. to the bake calls (buildx parent-context entitlement) - ci.yml: drop the obsolete lockfile-mirror diff; the install gate now uses --frozen-lockfile to fail fast on root-lockfile drift - docs: manual-setup notes pnpm 11 via corepack/packageManager Validated locally on Colima (linux/arm64): `docker buildx bake production workers` builds both images; the production image boots and serves; the workers smoke test loads all 16 entrypoints; the test-results route compiles into the standalone; and the security overrides are present in the built tree.
1 parent ac7e77c commit 95c80d0

15 files changed

Lines changed: 331 additions & 46163 deletions

.dockerignore

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Root build context ignore. The testplanit image builds from the repo root so
2+
# it can use the single root pnpm-lock.yaml + pnpm-workspace.yaml. Excluding the
3+
# large generated/working directories keeps the context small enough to
4+
# transfer. Patterns for ambiguous directory names (build, dist, coverage,
5+
# test-results, e2e, ...) are ANCHORED to their real top-level locations so a
6+
# floating "**/" glob can never strip a source route such as
7+
# app/api/test-results — which Next.js only reaches via a dynamic fetch(), so
8+
# its absence would not fail the build, only 404 at runtime.
9+
10+
# Dependencies & framework caches — safe to match at any depth (never source).
11+
**/node_modules
12+
**/.next
13+
**/.turbo
14+
**/.docusaurus
15+
**/*.tsbuildinfo
16+
17+
# Build output & test artifacts — regenerated in-image; anchored, not floating.
18+
testplanit/dist
19+
testplanit/build
20+
testplanit/coverage
21+
testplanit/test-results
22+
testplanit/playwright-report
23+
testplanit/e2e
24+
docs/build
25+
docs/.docusaurus
26+
forge-app/dist
27+
cli/dist
28+
packages/*/dist
29+
30+
# Large local data & prebuilt binaries — never needed in the image.
31+
testplanit/backups
32+
**/docker-data/
33+
cli/releases
34+
.playwright-mcp
35+
demo/
36+
37+
# VCS, env, editor, OS cruft.
38+
.git
39+
**/.git
40+
**/.env
41+
**/.env.*
42+
!**/.env.example
43+
**/.DS_Store
44+
**/npm-debug.log*
45+
46+
# Planning (local-only).
47+
**/.planning/

.github/workflows/ci.yml

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,25 +34,21 @@ jobs:
3434

3535
- name: Install pnpm
3636
uses: pnpm/action-setup@v4
37-
with:
38-
version: 9
3937

4038
- name: Setup Node.js
4139
uses: actions/setup-node@v5
4240
with:
4341
node-version: '24'
4442

45-
- name: Verify workspace lockfile mirror
46-
# testplanit/pnpm-lock.yaml is a load-bearing byte-for-byte duplicate
47-
# of the root pnpm-lock.yaml — the testplanit Dockerfile copies it
48-
# from its build context. Fail loudly when they diverge (typically
49-
# after a Dependabot bump) so the mirror is fixed before merge.
50-
run: diff -q pnpm-lock.yaml testplanit/pnpm-lock.yaml
51-
5243
- name: Install dependencies
44+
# --frozen-lockfile is the pre-merge lockfile-integrity gate: it fails
45+
# fast if the root pnpm-lock.yaml has drifted from the manifests (e.g. a
46+
# Dependabot bump that didn't regenerate it), matching the frozen install
47+
# the release Docker build relies on. A non-frozen install would silently
48+
# rewrite the lockfile in-runner and pass, deferring the failure to release.
5349
run: |
5450
cd testplanit
55-
pnpm install --ignore-scripts
51+
pnpm install --frozen-lockfile --ignore-scripts
5652
5753
- name: Generate ZenStack and Prisma
5854
run: |

.github/workflows/cli-semantic-release.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@ jobs:
3131

3232
- name: Install pnpm
3333
uses: pnpm/action-setup@v4
34-
with:
35-
version: 9
3634

3735
- name: Setup Node.js
3836
uses: actions/setup-node@v5

.github/workflows/packages-release.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,6 @@ jobs:
4444

4545
- name: Setup pnpm
4646
uses: pnpm/action-setup@v4
47-
with:
48-
version: 9
4947

5048
- name: Setup Node.js
5149
uses: actions/setup-node@v5

.github/workflows/release.yml

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -77,17 +77,14 @@ jobs:
7777
mkdir -p public
7878
cp public/version.json .docker-version.json || echo "Version file not generated"
7979
80-
- name: Copy lockfile for Docker build
81-
run: cp pnpm-lock.yaml testplanit/pnpm-lock.yaml
82-
8380
- name: Build and push AMD64 images
8481
working-directory: ./testplanit
8582
run: |
8683
VERSION="${{ github.ref_name }}"
8784
VERSION_NUM="${VERSION#v}"
8885
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-8)
8986
90-
docker buildx bake -f docker-bake.hcl --push \
87+
docker buildx bake -f docker-bake.hcl --allow=fs.read=.. --push \
9188
--set "*.platform=linux/amd64" \
9289
--set "production.tags=ghcr.io/${REPO_LC}:${VERSION_NUM}-amd64" \
9390
--set "production.tags=ghcr.io/${REPO_LC}:${VERSION}-amd64" \
@@ -148,17 +145,14 @@ jobs:
148145
mkdir -p public
149146
cp public/version.json .docker-version.json || echo "Version file not generated"
150147
151-
- name: Copy lockfile for Docker build
152-
run: cp pnpm-lock.yaml testplanit/pnpm-lock.yaml
153-
154148
- name: Build and push ARM64 images
155149
working-directory: ./testplanit
156150
run: |
157151
VERSION="${{ github.ref_name }}"
158152
VERSION_NUM="${VERSION#v}"
159153
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-8)
160154
161-
docker buildx bake -f docker-bake.hcl --push \
155+
docker buildx bake -f docker-bake.hcl --allow=fs.read=.. --push \
162156
--set "*.platform=linux/arm64" \
163157
--set "production.tags=ghcr.io/${REPO_LC}:${VERSION_NUM}-arm64" \
164158
--set "production.tags=ghcr.io/${REPO_LC}:${VERSION}-arm64" \
@@ -286,14 +280,11 @@ jobs:
286280
mkdir -p public
287281
cp public/version.json .docker-version.json || echo "Version file not generated"
288282
289-
- name: Copy lockfile for Docker build
290-
run: cp pnpm-lock.yaml testplanit/pnpm-lock.yaml
291-
292283
- name: Build and push AMD64 images
293284
working-directory: ./testplanit
294285
run: |
295286
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-8)
296-
docker buildx bake -f docker-bake.hcl --push \
287+
docker buildx bake -f docker-bake.hcl --allow=fs.read=.. --push \
297288
--set "*.platform=linux/amd64" \
298289
--set "production.tags=ghcr.io/${REPO_LC}:${{ github.event.inputs.tag }}-amd64" \
299290
--set "workers.tags=ghcr.io/${REPO_LC}:${{ github.event.inputs.tag }}-workers-amd64" \
@@ -349,14 +340,11 @@ jobs:
349340
mkdir -p public
350341
cp public/version.json .docker-version.json || echo "Version file not generated"
351342
352-
- name: Copy lockfile for Docker build
353-
run: cp pnpm-lock.yaml testplanit/pnpm-lock.yaml
354-
355343
- name: Build and push ARM64 images
356344
working-directory: ./testplanit
357345
run: |
358346
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-8)
359-
docker buildx bake -f docker-bake.hcl --push \
347+
docker buildx bake -f docker-bake.hcl --allow=fs.read=.. --push \
360348
--set "*.platform=linux/arm64" \
361349
--set "production.tags=ghcr.io/${REPO_LC}:${{ github.event.inputs.tag }}-arm64" \
362350
--set "workers.tags=ghcr.io/${REPO_LC}:${{ github.event.inputs.tag }}-workers-arm64" \

.github/workflows/semantic-release.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ jobs:
3232

3333
- name: Install pnpm
3434
uses: pnpm/action-setup@v4
35-
with:
36-
version: 9
3735

3836
- name: Setup Node.js
3937
uses: actions/setup-node@v5

docs/docs/manual-setup.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ This guide explains how to set up TestPlanIt for local development manually, wit
1111
Before you begin, ensure you have the following installed:
1212

1313
- [Node.js](https://nodejs.org/) v24.x LTS
14-
- [pnpm](https://pnpm.io/) (version 10+ recommended)
14+
- [pnpm](https://pnpm.io/) v11 — the repository pins the exact version via the `packageManager` field in the root `package.json`, so the simplest setup is to run `corepack enable` (Corepack ships with Node.js) and let it provision the matching pnpm automatically
1515
- Git
1616

1717
**Required Services:**

package.json

Lines changed: 2 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"description": "TestPlanIt monorepo",
55
"author": "Brad DerManouelian",
66
"license": "ISC",
7+
"packageManager": "pnpm@11.5.0",
78
"scripts": {
89
"changeset": "changeset",
910
"changeset:version": "changeset version",
@@ -15,105 +16,5 @@
1516
"@changesets/changelog-github": "^0.5.2",
1617
"@changesets/cli": "^2.30.0",
1718
"vite": "^7.3.2"
18-
},
19-
"pnpm": {
20-
"onlyBuiltDependencies": [
21-
"@forge/cli",
22-
"@parcel/watcher",
23-
"@prisma/client",
24-
"@prisma/engines",
25-
"@scarf/scarf",
26-
"@swc/core",
27-
"@tree-sitter-grammars/tree-sitter-yaml",
28-
"aws-sdk",
29-
"bcrypt",
30-
"canvas",
31-
"cloudflared",
32-
"core-js",
33-
"core-js-pure",
34-
"esbuild",
35-
"keytar",
36-
"msgpackr-extract",
37-
"msw",
38-
"prisma",
39-
"sharp",
40-
"tree-sitter",
41-
"tree-sitter-json",
42-
"unrs-resolver",
43-
"zenstack"
44-
],
45-
"overrides": {
46-
"csstype": "^3.2.0",
47-
"postcss": "^8.5.10",
48-
"@csstools/css-syntax-patches-for-csstree": "^1.0.20",
49-
"form-data": "^4.0.4",
50-
"validator": "^13.15.22",
51-
"js-yaml": "^4.1.1",
52-
"gray-matter>js-yaml": "^3.14.1",
53-
"read-yaml-file>js-yaml": "^3.14.1",
54-
"tmp": "^0.2.6",
55-
"mdast-util-to-hast": "^13.2.1",
56-
"content-security-policy-parser": "^0.6.0",
57-
"webpack-dev-server": "^5.2.4",
58-
"@sentry/browser": "^7.119.1",
59-
"pdfjs-dist": "^4.2.67",
60-
"nth-check": "^2.0.1",
61-
"dompurify": "^3.3.2",
62-
"path-to-regexp": "^3.3.0",
63-
"prismjs": "^1.30.0",
64-
"systeminformation": "^5.31.1",
65-
"qs": ">=6.15.2",
66-
"brace-expansion@5": ">=5.0.6",
67-
"ws@8": ">=8.20.1",
68-
"@modelcontextprotocol/sdk": ">=1.26.0",
69-
"preact": ">=10.28.2",
70-
"@remix-run/router": ">=1.23.2",
71-
"hono": ">=4.12.18",
72-
"@hono/node-server": "1.19.13",
73-
"vite": "7.3.2",
74-
"basic-ftp": ">=5.3.1",
75-
"fast-uri": ">=3.1.2",
76-
"fast-xml-builder": ">=1.1.7",
77-
"ip-address": ">=10.1.1",
78-
"icu-minify": ">=4.9.2",
79-
"@babel/plugin-transform-modules-systemjs": ">=7.29.4",
80-
"nodemailer": ">=8.0.5",
81-
"cheerio": ">=1.0.0",
82-
"ioredis": "5.10.1",
83-
"lodash": ">=4.18.0",
84-
"lodash-es": ">=4.18.0",
85-
"undici": "^7.24.7",
86-
"diff": ">=8.0.3",
87-
"fast-xml-parser": ">=5.5.7",
88-
"uuid": ">=14.0.0",
89-
"esbuild": ">=0.25.0",
90-
"webpack": ">=5.104.1",
91-
"webpackbar": ">=7.0.0",
92-
"markdown-it": ">=14.1.1",
93-
"ajv": ">=8.18.0",
94-
"eslint>ajv": "^6.14.0",
95-
"@eslint/eslintrc>ajv": "^6.14.0",
96-
"schema-utils@2>ajv": "^6.14.0",
97-
"schema-utils@2>ajv-keywords": "^3.5.2",
98-
"schema-utils@3>ajv": "^6.14.0",
99-
"schema-utils@3>ajv-keywords": "^3.5.2",
100-
"svgo": ">=3.3.3",
101-
"minimatch@3": "^3.1.4",
102-
"minimatch@5": ">=5.1.8",
103-
"minimatch@7": ">=7.4.7",
104-
"minimatch@9": ">=9.0.6",
105-
"minimatch@10": ">=10.2.3",
106-
"node-forge": ">=1.3.2",
107-
"tar-fs@2": ">=2.1.4",
108-
"glob@10": ">=10.5.0",
109-
"bn.js": "5.2.3",
110-
"serialize-javascript": ">=7.0.5",
111-
"flatted": ">=3.4.0",
112-
"picomatch@4": ">=4.0.4",
113-
"smol-toml": ">=1.6.1",
114-
"yaml@1": ">=1.10.3",
115-
"yaml@2": ">=2.8.3",
116-
"defu": ">=6.1.5"
117-
}
11819
}
119-
}
20+
}

0 commit comments

Comments
 (0)