@acromedia/build-test
Local tool that validates the create-nextjs template tree by scaffolding one Next.js app per CI build-option combination into a single Turborepo inside this project, wiring every @acromedia/* dep to the live monorepo source, and running type-check / lint / build across all of them with a single root pnpm install.
Why this exists
The .ts/.tsx files under starter-kits/create-nextjs/templates/ are never type-checked or linted as part of the monorepo. They only become a valid Next.js app after create-nextjs composes them:
- The template dir has no
node_modules— imports ofnext,react,@acromedia/gesso-next, etc. can't resolve. - Templates import generated sibling modules (
src/cms.ts,src/commerce.ts,src/erp.ts,src/ai.ts,gesso.config.json) that only exist afterpnpm gesso construct/pnpm gesso configruns. - Files contain
//_|TAG|_///_|!TAG|_conditional blocks; a tagged file isn't valid in any single configuration untilparseTemplatesstrips the unused blocks.
@acromedia/build-test fills that gap.
This package is local-only
"private": true— never published.- Scripts are intentionally named
compile,check,fix,test:fast,test:full,test:cleanso they don't match the root turbo tasks (build,lint,lint:fix,test,test:ci,type-check). Runningpnpm build/pnpm lint/pnpm testfrom the repo root does not run anything from this package. - Invoke explicitly via
pnpm --filter @acromedia/build-test <script>.
Quick start
# 1. Build all gesso packages once so their dist/ trees are populated.
# (Or pass --build to do it automatically.)
pnpm build
# 2. (Optional) Copy .env.example to .env and fill in any values you want
# threaded into every generated app's .env / gesso.config.json.
cp starter-kits/build-test/.env.example starter-kits/build-test/.env
# 3. Fast tier — type-check + lint across every app in parallel:
pnpm --filter @acromedia/build-test test:fast
# 4. Full tier — also runs next build per app:
pnpm --filter @acromedia/build-test test:full
Typical cold-run time: ~25 s. Warm re-runs (workspace + node_modules already on disk) hit Turbo cache: 100s of ms for the validation tasks themselves, ~10–15 s end-to-end including the surrounding scaffold + lint:fix work.
Where the scratch workspace lives
<repo>/.build-test/workspace/ — inside this project, gitignored. Picked deliberately so a failure is one cd away in your IDE.
<repo>/
├── .build-test/ ← gitignored, all generated output goes here
│ └── workspace/
│ ├── apps/ ← wiped + rescaffolded every run
│ │ ├── bigcommerce-storyblok-acumatica-vertex/
│ │ ├── shopify-storyblok-acumatica-vertex/
│ │ └── drupal-commerce-drupal-acumatica-vertex/
│ ├── gesso-local/ ← refreshed from live source every run
│ │ ├── packages/
│ │ └── starter-kits/
│ ├── node_modules/ ← persists across runs (single shared install)
│ ├── .turbo/ ← persists (turbo task cache)
│ └── pnpm-lock.yaml ← persists
└── starter-kits/build-test/ ← this package's source
The workspace is persistent: re-running test:fast after test:fast reuses node_modules/ and the turbo cache, so a successful re-run usually takes only seconds. apps/ and gesso-local/ are wiped on each run so stale generated files (from gesso construct) never bleed between runs.
To start cold: pnpm --filter @acromedia/build-test test:clean (or node dist/bin.js --clean).
"Live" gesso source
Every @acromedia/* package in each generated app's package.json is rewritten to a file: dep pointing at <workspace>/gesso-local/<type>/<dir>/. Those entries are fresh copies of the live source under <repo>/packages/.../ and <repo>/starter-kits/.../, recomposed every run. The workflow:
- Edit
<repo>/packages/ui/next/src/Foo.tsx(or any gesso package source). - Rebuild that package's dist:
pnpm --filter @acromedia/gesso-next build(only the changed package, not the whole tree). - Re-run — fresh
dist/is copied intogesso-local/and pnpm install re-links it into every app.
Compared to the previous .tgz-based approach: no pnpm pack round-trip, no whole-tree rebuild — just rebuild the changed package and re-run. Total per-run overhead from the copy step: ~1 sec (gesso's combined dist/ footprint is ~64 MB).
Why copy and not symlink
pnpm 9.x packs file: deps into its store before linking them into node_modules. Top-level directory symlinks aren't followed during that pack step, so a symlinked dist/ arrives empty in the installed copy. Bytewise copy is the smallest reliable fix and is fast enough that the trade-off is uninteresting.
CLI flags
node dist/bin.js [--tasks <list>] [--scratchDir <path>] [--only <combo>] [--build] [--clean]
| Flag | Default | Meaning |
|---|---|---|
--tasks | type-check,lint | Comma-separated list of Turbo tasks. Any of type-check, lint, build. |
--scratchDir | <repo>/.build-test/ | Override the scratch parent dir. |
--only <combo> | all | Run a single combo (e.g. shopify-storyblok-acumatica-vertex). |
--build | off | Run pnpm build at repo root before copying deps. Useful when dist/ is stale. |
--clean | off | Remove the scratch dir tree and exit. |
Maintenance scripts (this package's own source)
pnpm --filter @acromedia/build-test compile # tsc → dist/
pnpm --filter @acromedia/build-test check # tsc --noEmit + eslint + prettier --check
pnpm --filter @acromedia/build-test fix # eslint --fix + prettier --write
Environment variables
.env.example documents every var each generated test app's .env and gesso.config.json consume. Names match .gitlab-ci.yml and templates/default/example.env, so a .env populated for one of the demos can be dropped in here as-is. Unset values fall back to stubs declared in src/env.ts (fine for type-check / lint; next build paths that fetch at compile time may need real values).
Dataflow: .env → src/loadEnv.ts (loaded at CLI startup) → src/env.ts (maps env vars to create-nextjs options) → create-nextjs/src/utils.ts:setEnv/setConfig (writes options into the scaffolded app's .env and gesso.config.json).
Combo matrix (today)
Mirrors the demos CI deploys via .lagoon/{bigcommerce,shopify,drupal-commerce}-demo/Dockerfile:
| Name | CMS | Commerce | ERP | AI |
|---|---|---|---|---|
bigcommerce-storyblok-acumatica-vertex | storyblok | bigcommerce | acumatica | vertex |
shopify-storyblok-acumatica-vertex | storyblok | shopify | acumatica | vertex |
drupal-commerce-drupal-acumatica-vertex | drupal | drupal-commerce | acumatica | vertex |
Add a new combo by editing src/combos.ts.
How it works
- Scaffold a Turborepo at
<repo>/.build-test/workspace/with@acromedia/create-mono-repo(only on the first run; subsequent runs reuse the existing workspace). - Rewrite
<workspace>/turbo.jsonwith our task graph (build,lint,type-check). - Wipe
<workspace>/gesso-local/and refill it from the live source: for each gesso package, copy every published top-level entry (dist/,bin/, etc.) and write a rewrittenpackage.jsonwhose deps point at siblinggesso-local/entries viafile:paths. - Wipe
<workspace>/apps/and scaffold each combo there with@acromedia/create-nextjs, passinguseLocal: '<workspace>/gesso-local',skipInstall: true, andskipPostInstall: true. - Patch each app's
package.jsonto add atype-checkscript (tsc --noEmit) if missing. - Run
pnpm installonce at the workspace root — pnpm dedupes everything because all apps share identicalfile:paths. - Run
pnpm gesso construct {provider} {platform}for each provider, thenpnpm gesso configper app. Generated sibling modules now exist; the//_|TAG|_blocks were already stripped duringcopyTemplateFilesinsidecreateProject. - Run
pnpm lint:fixper app to format generated files and post-parseTemplates code (mirrors thelintFixstep at the tail ofcreate-nextjs). - Run
pnpm turbo run <tasks>at the workspace root. Turbo reports per-app pass/fail natively.
Iterating on a failure
When something fails, the scratch workspace stays put. To dig in:
cd .build-test/workspace/apps/shopify-storyblok-acumatica-vertex
pnpm type-check # re-run just this app's type-check
pnpm lint
pnpm build
To rerun against a single combo:
node dist/bin.js --only shopify-storyblok-acumatica-vertex