diff --git a/.npmrc b/.npmrc index 35cdb76..e72177a 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,5 @@ @mosaicstack:registry=https://git.mosaicstack.dev/api/packages/mosaicstack/npm/ +# Pin the pnpm store to the same path the ci-base image warms (Dockerfile.ci), +# so the pipeline `pnpm install --prefer-offline` consumes the baked store +# instead of repopulating a fresh one. +store-dir=/root/.local/share/pnpm/store diff --git a/.woodpecker/ci.yml b/.woodpecker/ci.yml index 92bb311..46a839f 100644 --- a/.woodpecker/ci.yml +++ b/.woodpecker/ci.yml @@ -1,5 +1,9 @@ +# &node_image is the pre-baked CI base built by .woodpecker/ci-image.yml: +# node:22-alpine + python3/make/g++/postgresql-client + pnpm + a warm pnpm +# store. The install step resolves from the baked store (--prefer-offline) +# instead of paying a ~731s cold fetch + native compile every run. variables: - - &node_image 'node:22-alpine' + - &node_image 'git.mosaicstack.dev/mosaicstack/stack/ci-base:latest' - &enable_pnpm 'corepack enable' when: @@ -15,8 +19,9 @@ steps: image: *node_image commands: - corepack enable - - apk add --no-cache python3 make g++ - - pnpm install --frozen-lockfile + # python3/make/g++ are baked into ci-base; --prefer-offline resolves from + # the baked pnpm store. + - pnpm install --frozen-lockfile --prefer-offline # Blocking gate: public framework package must contain no operator-specific # personal data or private $HOME defaults. Runs early (no node_modules needed). @@ -64,8 +69,7 @@ steps: DATABASE_URL: postgresql://mosaic:mosaic@ci-postgres:5432/mosaic commands: - *enable_pnpm - # Install postgresql-client for pg_isready - - apk add --no-cache postgresql-client + # postgresql-client (pg_isready) is baked into ci-base. # Wait up to 60s for CI postgres to be ready; fail fast if it never comes up. - | ready=0 diff --git a/.woodpecker/publish.yml b/.woodpecker/publish.yml index 8eb4685..9adc6e3 100644 --- a/.woodpecker/publish.yml +++ b/.woodpecker/publish.yml @@ -2,7 +2,9 @@ # Runs only on main branch push/tag variables: - - &node_image 'node:22-alpine' + # Pre-baked CI base (see .woodpecker/ci-image.yml): node:22-alpine + + # toolchain + warm pnpm store. Kills the second cold install publish pays. + - &node_image 'git.mosaicstack.dev/mosaicstack/stack/ci-base:latest' - &enable_pnpm 'corepack enable' # Heavy kaniko image builds (~25 min) — gate them so a merge that only touches # the npm-only CLI (@mosaicstack/mosaic) or docs does NOT rebuild the platform @@ -31,7 +33,8 @@ steps: image: *node_image commands: - corepack enable - - pnpm install --frozen-lockfile + # Resolve from the baked pnpm store instead of a cold network fetch. + - pnpm install --frozen-lockfile --prefer-offline build: image: *node_image diff --git a/packages/mosaic/framework/tools/quality/templates/monorepo/.woodpecker.yml b/packages/mosaic/framework/tools/quality/templates/monorepo/.woodpecker.yml index 13d19a1..2c37c5b 100644 --- a/packages/mosaic/framework/tools/quality/templates/monorepo/.woodpecker.yml +++ b/packages/mosaic/framework/tools/quality/templates/monorepo/.woodpecker.yml @@ -2,12 +2,20 @@ when: - event: [push, pull_request, manual] +# Dependencies are installed ONCE in the `install` step and every downstream +# step depends on it, reusing the populated node_modules from the shared +# workspace volume. Do NOT re-run `npm ci` per step — that pays the full cold +# install (network fetch + native rebuilds) N times and is the dominant cost +# in a pipeline. +# +# For best results, replace `&node_image` with a pre-baked CI base image that +# ships your toolchain (python3/make/g++ for native modules) and a warm npm +# cache, then keep `--prefer-offline` so installs resolve from the cache. See +# the Mosaic Stack repo's Dockerfile.ci + .woodpecker/ci-image.yml for the +# baked-image pattern. variables: - &node_image 'node:20-alpine' - &gitleaks_image 'ghcr.io/gitleaks/gitleaks:v8.24.0' - - &install_deps | - corepack enable - npm ci --ignore-scripts steps: # Secret scanning (runs in parallel with install, no deps) @@ -17,15 +25,18 @@ steps: - gitleaks git --redact --verbose --log-opts="HEAD~1..HEAD" depends_on: [] + # Single cached install. Every other step depends on this and reuses the + # node_modules it produces in the shared workspace. install: image: *node_image commands: - - *install_deps + - corepack enable + - npm ci --ignore-scripts --prefer-offline + depends_on: [] security-audit: image: *node_image commands: - - *install_deps - npm audit --audit-level=high depends_on: - install @@ -35,7 +46,6 @@ steps: environment: SKIP_ENV_VALIDATION: 'true' commands: - - *install_deps - npm run lint depends_on: - install @@ -45,7 +55,6 @@ steps: environment: SKIP_ENV_VALIDATION: 'true' commands: - - *install_deps - npm run type-check depends_on: - install @@ -55,7 +64,6 @@ steps: environment: SKIP_ENV_VALIDATION: 'true' commands: - - *install_deps - npm run test -- --coverage --coverageThreshold='{"global":{"branches":80,"functions":80,"lines":80,"statements":80}}' depends_on: - install @@ -66,7 +74,6 @@ steps: SKIP_ENV_VALIDATION: 'true' NODE_ENV: 'production' commands: - - *install_deps - npm run build depends_on: - lint