ci/woodpecker/push/publish Pipeline was canceled
Co-authored-by: code-be-01 <[email protected]>
530 lines
25 KiB
YAML
530 lines
25 KiB
YAML
# Build, publish npm packages, and push Docker images
|
|
# Runs on main for stable publishes and on next for integration-line prereleases/images
|
|
#
|
|
# SDLC-D-034 publish gate: every publish effect (publish-npm, publish-next-npm,
|
|
# and every image build/push step) depends DIRECTLY on the `verify` step below.
|
|
# `verify` (a) asserts the provider's commit identity matches the actual
|
|
# checkout (CI_COMMIT_SHA == git rev-parse HEAD, fail closed on mismatch or
|
|
# emptiness) and (b) runs the canonical terminal verification command
|
|
# (`pnpm verify:release`), which mirrors the PR CI pipeline's complete
|
|
# mandatory set (sanitization, upgrade-guard, preflight+typecheck, lint,
|
|
# format:check, test, build) — see scripts/verify-release.mjs. A missing,
|
|
# failed, skipped, cancelled, or inconclusive verification therefore skips the
|
|
# dependent publish effects (fail closed). Path-filtered short-circuits may
|
|
# skip publish EFFECTS (e.g. docs-only merges) but never bypass `verify` for a
|
|
# publish that does run: `verify` itself carries no path filter.
|
|
# scripts/verify-release.test.mjs enforces this DAG invariant at checkout time.
|
|
|
|
variables:
|
|
# Pre-baked CI base (see .woodpecker/ci-image.yml): node:24-alpine +
|
|
# toolchain + warm pnpm store. Kills the second cold install publish pays.
|
|
# PINNED to the immutable lock-tag, not :latest (#1328, brain D27): a mutable
|
|
# tag resolves per-pod at pull time on the k8s backend and made CI verdicts
|
|
# non-reproducible (#1324). Byte-identical to :latest at pin time (pushed
|
|
# atomically by the same kaniko run, main 712c770, 2026-07-26). Bump only via
|
|
# reviewed PR, per the procedure in .woodpecker/ci.yml's header comment.
|
|
- &node_image 'git.mosaicstack.dev/mosaicstack/stack/ci-base:lock-9cb7ffcd8828'
|
|
- &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
|
|
# images (gateway/appservice/web do not depend on @mosaicstack/mosaic). Releases
|
|
# (tags) always build everything. Exclude-list keeps the default SAFE: any
|
|
# non-excluded change still builds, so no transitive dep can silently go stale.
|
|
# (Woodpecker: `when` entries are OR'd; `path` applies to push/PR only — hence
|
|
# the separate `event: tag` entry.)
|
|
- &image_build_when
|
|
- event: tag
|
|
- event: [push, manual]
|
|
branch: main
|
|
path:
|
|
exclude:
|
|
- 'packages/mosaic/**'
|
|
- 'docs/**'
|
|
- '**/*.md'
|
|
- '.woodpecker/**'
|
|
- event: [push, manual]
|
|
branch: next
|
|
- &main_image_build_when
|
|
- event: tag
|
|
- event: [push, manual]
|
|
branch: main
|
|
path:
|
|
exclude:
|
|
- 'packages/mosaic/**'
|
|
- 'docs/**'
|
|
- '**/*.md'
|
|
- '.woodpecker/**'
|
|
|
|
when:
|
|
- branch: [main, next]
|
|
event: [push, manual, tag]
|
|
|
|
steps:
|
|
install:
|
|
image: *node_image
|
|
commands:
|
|
- corepack enable
|
|
# Resolve from the baked pnpm store instead of a cold network fetch.
|
|
- pnpm install --frozen-lockfile --prefer-offline
|
|
|
|
# SDLC-D-034 exact-commit publish gate. No `when`/path filter on purpose: it
|
|
# runs for every event this pipeline serves so no publish effect can ever
|
|
# start without it. Fails closed on commit-identity mismatch (or either SHA
|
|
# being empty) and on any incomplete verification.
|
|
verify:
|
|
image: *node_image
|
|
commands:
|
|
- *enable_pnpm
|
|
# (a) Commit identity: the provider's claimed SHA must equal the actual
|
|
# checkout HEAD — verification of anything else must never authorize a
|
|
# publish of this commit.
|
|
- |
|
|
if [ -z "$CI_COMMIT_SHA" ]; then
|
|
echo "[verify] FATAL: CI_COMMIT_SHA is empty — cannot certify commit identity" >&2
|
|
exit 1
|
|
fi
|
|
CHECKOUT_SHA="$(git rev-parse HEAD 2>/dev/null || true)"
|
|
if [ -z "$CHECKOUT_SHA" ]; then
|
|
echo "[verify] FATAL: git rev-parse HEAD returned nothing — cannot certify commit identity" >&2
|
|
exit 1
|
|
fi
|
|
if [ "$CI_COMMIT_SHA" != "$CHECKOUT_SHA" ]; then
|
|
echo "[verify] FATAL: provider commit ($CI_COMMIT_SHA) != checkout HEAD ($CHECKOUT_SHA)" >&2
|
|
exit 1
|
|
fi
|
|
echo "[verify] commit identity confirmed: $CHECKOUT_SHA"
|
|
# (b) Canonical terminal verification. Caller-provided prerequisites the
|
|
# runner expects (see .woodpecker/ci.yml comments): bash/rsync for the
|
|
# guard stages, openssl + the pinned pi binary for the test stage. git is
|
|
# baked into ci-base but re-asserted here so the identity check above can
|
|
# never silently depend on a stale baked image. DATABASE_URL is
|
|
# deliberately NOT set: the canonical command must hold on the PGlite
|
|
# path too and never sets or requires a database itself.
|
|
- apk add --no-cache bash rsync openssl git
|
|
- npm install -g @earendil-works/[email protected]
|
|
- pnpm verify:release
|
|
depends_on:
|
|
- install
|
|
|
|
build:
|
|
image: *node_image
|
|
commands:
|
|
- *enable_pnpm
|
|
- pnpm build
|
|
depends_on:
|
|
- install
|
|
- verify
|
|
|
|
publish-npm:
|
|
image: *node_image
|
|
# Publish only when a publishable package changed (or on a release tag); a
|
|
# pure-docs merge runs no publish. Cheap step, but gated for cleanliness.
|
|
when:
|
|
- event: tag
|
|
- event: [push, manual]
|
|
branch: main
|
|
path:
|
|
include:
|
|
- 'packages/**'
|
|
environment:
|
|
NPM_TOKEN:
|
|
from_secret: gitea_token
|
|
commands:
|
|
- *enable_pnpm
|
|
# Configure auth for Gitea npm registry
|
|
- |
|
|
echo "//git.mosaicstack.dev/api/packages/mosaicstack/npm/:_authToken=$NPM_TOKEN" > ~/.npmrc
|
|
echo "@mosaicstack:registry=https://git.mosaicstack.dev/api/packages/mosaicstack/npm/" >> ~/.npmrc
|
|
# Publish non-private packages to Gitea.
|
|
#
|
|
# The only publish failure we tolerate is "version already exists" —
|
|
# that legitimately happens when only some packages were bumped in
|
|
# the merge. Any other failure (registry 404, auth error, network
|
|
# error) MUST fail the pipeline loudly: the previous
|
|
# `|| echo "... continuing"` fallback silently hid a 404 from the
|
|
# Gitea org rename and caused every @mosaicstack/* publish to fall
|
|
# on the floor while CI still reported green.
|
|
- |
|
|
# Portable sh (Alpine ash) — avoid bashisms like PIPESTATUS.
|
|
set +e
|
|
pnpm --filter "@mosaicstack/*" --filter "!@mosaicstack/web" publish --no-git-checks --access public >/tmp/publish.log 2>&1
|
|
EXIT=$?
|
|
set -e
|
|
cat /tmp/publish.log
|
|
if [ "$EXIT" -eq 0 ]; then
|
|
echo "[publish] all packages published successfully"
|
|
exit 0
|
|
fi
|
|
# Hard registry / auth / network errors → fatal. Match npm's own
|
|
# error lines specifically to avoid false positives on arbitrary
|
|
# log text that happens to contain "E404" etc.
|
|
if grep -qE "npm (error|ERR!) code (E404|E401|ENEEDAUTH|ECONNREFUSED|ETIMEDOUT|ENOTFOUND)" /tmp/publish.log; then
|
|
echo "[publish] FATAL: registry/auth/network error detected — failing pipeline" >&2
|
|
exit 1
|
|
fi
|
|
# Only tolerate the explicit "version already published" case.
|
|
# npm returns this as E403 with body "You cannot publish over..."
|
|
# or EPUBLISHCONFLICT depending on version.
|
|
if grep -qE "EPUBLISHCONFLICT|You cannot publish over|previously published" /tmp/publish.log; then
|
|
echo "[publish] some packages already at this version — continuing (non-fatal)"
|
|
exit 0
|
|
fi
|
|
echo "[publish] FATAL: publish failed with unrecognized error — failing pipeline" >&2
|
|
exit 1
|
|
depends_on:
|
|
- build
|
|
- verify
|
|
|
|
publish-next-npm:
|
|
image: *node_image
|
|
# Durable @next integration-line publish. Runs only on next; never writes
|
|
# the latest dist-tag and never commits the computed prerelease versions.
|
|
when:
|
|
- event: [push, manual]
|
|
branch: next
|
|
environment:
|
|
NPM_TOKEN:
|
|
from_secret: gitea_token
|
|
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
|
|
CI_PIPELINE_NUMBER: ${CI_PIPELINE_NUMBER}
|
|
commands:
|
|
- *enable_pnpm
|
|
- |
|
|
if [ "$CI_COMMIT_BRANCH" != "next" ]; then
|
|
echo "[publish-next] FATAL: publish-next-npm may only run on next (got '$CI_COMMIT_BRANCH')" >&2
|
|
exit 1
|
|
fi
|
|
if [ -z "$CI_PIPELINE_NUMBER" ]; then
|
|
echo "[publish-next] FATAL: CI_PIPELINE_NUMBER is required for prerelease versioning" >&2
|
|
exit 1
|
|
fi
|
|
echo "//git.mosaicstack.dev/api/packages/mosaicstack/npm/:_authToken=$NPM_TOKEN" > ~/.npmrc
|
|
echo "@mosaicstack:registry=https://git.mosaicstack.dev/api/packages/mosaicstack/npm/" >> ~/.npmrc
|
|
DIST_TAGS_JSON="$(npm view @mosaicstack/mosaic dist-tags --registry https://git.mosaicstack.dev/api/packages/mosaicstack/npm/ --json)"
|
|
DIST_TAGS_JSON="$DIST_TAGS_JSON" node -e 'const tags = JSON.parse(process.env.DIST_TAGS_JSON || "{}"); if (!tags || typeof tags !== "object" || !Object.hasOwn(tags, "latest")) { throw new Error("Gitea npm registry did not return a usable dist-tags object"); } console.log("[publish-next] registry dist-tags OK: latest=" + tags.latest);'
|
|
# #1404: snapshot every publishable manifest BEFORE the transform so the
|
|
# workspace can be restored byte-exact after publish. The transform
|
|
# rewrites package.json in place (needed: pnpm publish reads the
|
|
# workspace manifests); without restore, later steps in this pipeline
|
|
# (build-gateway kaniko COPY + pnpm install --frozen-lockfile) see
|
|
# manifests that no longer match pnpm-lock.yaml and fail
|
|
# ERR_PNPM_OUTDATED_LOCKFILE. Snapshot dir is step-local tmp.
|
|
SNAPSHOT_DIR="$(mktemp -d /tmp/publish-next-manifests.XXXXXX)"
|
|
export SNAPSHOT_DIR
|
|
find apps packages plugins -name package.json -not -path "*/node_modules/*" -not -path "*/dist/*" | while read -r mf; do
|
|
mkdir -p "$SNAPSHOT_DIR/$(dirname "$mf")"
|
|
cp -p "$mf" "$SNAPSHOT_DIR/$mf"
|
|
done
|
|
echo "[publish-next] snapshotted $(find "$SNAPSHOT_DIR" -name package.json | wc -l) manifests to $SNAPSHOT_DIR"
|
|
node <<'NODE'
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
|
|
const pipelineNumber = process.env.CI_PIPELINE_NUMBER;
|
|
const roots = ['apps', 'packages', 'plugins'];
|
|
const updated = [];
|
|
const exactVersions = new Map(); // name -> bumped next version
|
|
|
|
function walk(dir, visit) {
|
|
if (!fs.existsSync(dir)) return;
|
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
if (entry.name === 'node_modules' || entry.name === 'dist' || entry.name === '.turbo') continue;
|
|
const fullPath = path.join(dir, entry.name);
|
|
if (entry.isDirectory()) {
|
|
const packagePath = path.join(fullPath, 'package.json');
|
|
if (fs.existsSync(packagePath)) {
|
|
const manifest = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
if (manifest.name?.startsWith('@mosaicstack/') && !manifest.private) {
|
|
visit(manifest, packagePath);
|
|
}
|
|
}
|
|
walk(fullPath, visit);
|
|
}
|
|
}
|
|
}
|
|
|
|
// #1389: two passes. Pass 1 bumps every publishable manifest to
|
|
// <stable+1>-next.<pipeline> exactly as before, recording name ->
|
|
// bumped version. Pass 2 rewrites every published manifest's
|
|
// @mosaicstack/* dependency entries (dependencies, devDependencies,
|
|
// peerDependencies, optionalDependencies) to the EXACT same-pipeline
|
|
// build. A caret range like ^0.0.3-next.2636 leaves the resolver free
|
|
// to pick any later build — and on a host with a stale cache, an
|
|
// installer-side scaffold pinned at stable, or a registry hiccup, that
|
|
// freedom is how a "next" install ends up executing stable-era code
|
|
// (web1 evidence: old tier validator, missing migrations). Exact pins
|
|
// make the defect class unrepresentable regardless of resolver path.
|
|
function bump(manifest, packagePath) {
|
|
const stableMatch = /^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(manifest.version);
|
|
if (!stableMatch) {
|
|
throw new Error(manifest.name + " has unsupported semver version '" + manifest.version + "'");
|
|
}
|
|
const [, major, minor, patch] = stableMatch;
|
|
const oldVersion = manifest.version;
|
|
manifest.version = major + '.' + minor + '.' + (Number(patch) + 1) + '-next.' + pipelineNumber;
|
|
fs.writeFileSync(packagePath, JSON.stringify(manifest, null, 2) + '\n');
|
|
exactVersions.set(manifest.name, manifest.version);
|
|
updated.push(manifest.name + ' ' + oldVersion + ' -> ' + manifest.version);
|
|
}
|
|
|
|
const DEP_FIELDS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'];
|
|
let pinnedEntries = 0;
|
|
function pin(manifest, packagePath) {
|
|
let changed = false;
|
|
for (const field of DEP_FIELDS) {
|
|
const deps = manifest[field];
|
|
if (!deps || typeof deps !== 'object') continue;
|
|
for (const [name, range] of Object.entries(deps)) {
|
|
if (!name.startsWith('@mosaicstack/')) continue;
|
|
const exact = exactVersions.get(name);
|
|
if (!exact) {
|
|
throw new Error(
|
|
manifest.name + ' depends on ' + name +
|
|
' which has no bumped version in this publish set — cannot pin');
|
|
}
|
|
if (range === exact) continue;
|
|
deps[name] = exact;
|
|
pinnedEntries++;
|
|
changed = true;
|
|
}
|
|
}
|
|
if (changed) fs.writeFileSync(packagePath, JSON.stringify(manifest, null, 2) + '\n');
|
|
}
|
|
|
|
for (const root of roots) walk(root, bump);
|
|
for (const root of roots) walk(root, pin);
|
|
if (updated.length === 0) throw new Error('No publishable @mosaicstack/* packages found');
|
|
console.log('[publish-next] computed prerelease versions for ' + updated.length + ' packages:');
|
|
for (const line of updated) console.log('[publish-next] ' + line);
|
|
console.log('[publish-next] pinned ' + pinnedEntries + ' @mosaicstack/* dep entries to exact same-pipeline versions across ' + updated.length + ' manifests');
|
|
NODE
|
|
pnpm --filter "@mosaicstack/*" --filter "!@mosaicstack/web" --filter "!@mosaicstack/mosaic-as" publish --no-git-checks --access public --tag next
|
|
EXPECTED_VERSION="$(node -p "require('./packages/mosaic/package.json').version")"
|
|
RESOLVED_VERSION="$(npm view @mosaicstack/mosaic@next version --registry https://git.mosaicstack.dev/api/packages/mosaicstack/npm/)"
|
|
if [ "$RESOLVED_VERSION" != "$EXPECTED_VERSION" ]; then
|
|
echo "[publish-next] FATAL: @mosaicstack/mosaic@next resolved '$RESOLVED_VERSION', expected '$EXPECTED_VERSION'" >&2
|
|
exit 1
|
|
fi
|
|
echo "[publish-next] @mosaicstack/mosaic@next resolves to $RESOLVED_VERSION"
|
|
# #1389 post-publish guard: every freshly published manifest must carry
|
|
# EXACT same-pipeline @mosaicstack/* dep pins (no ranges, no stable
|
|
# fallback). A leak here fails the pipeline instead of shipping.
|
|
node <<'GUARD'
|
|
const { execFileSync } = require('node:child_process');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const pipelineNumber = process.env.CI_PIPELINE_NUMBER;
|
|
const registry = 'https://git.mosaicstack.dev/api/packages/mosaicstack/npm/';
|
|
const roots = ['apps', 'packages', 'plugins'];
|
|
const published = [];
|
|
function walk(dir) {
|
|
if (!fs.existsSync(dir)) return;
|
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
if (entry.name === 'node_modules' || entry.name === 'dist' || entry.name === '.turbo') continue;
|
|
const fullPath = path.join(dir, entry.name);
|
|
if (entry.isDirectory()) {
|
|
const packagePath = path.join(fullPath, 'package.json');
|
|
if (fs.existsSync(packagePath)) {
|
|
const m = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
if (m.name?.startsWith('@mosaicstack/') && !m.private) published.push(m.name);
|
|
}
|
|
walk(fullPath);
|
|
}
|
|
}
|
|
}
|
|
for (const root of roots) walk(root);
|
|
let failures = 0;
|
|
for (const name of published) {
|
|
let manifest;
|
|
try {
|
|
const out = execFileSync('npm', ['view', name + '@next', '--json', '--registry', registry],
|
|
{ encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 });
|
|
const arr = JSON.parse(out);
|
|
manifest = Array.isArray(arr) ? arr[arr.length - 1] : arr;
|
|
} catch (e) {
|
|
console.error('[publish-next-guard] FAIL ' + name + ': npm view failed: ' + e.message);
|
|
failures++;
|
|
continue;
|
|
}
|
|
const fields = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'];
|
|
for (const field of fields) {
|
|
const deps = manifest[field];
|
|
if (!deps || typeof deps !== 'object') continue;
|
|
for (const [dep, range] of Object.entries(deps)) {
|
|
if (!dep.startsWith('@mosaicstack/')) continue;
|
|
const expected = dep === name ? manifest.version : null;
|
|
const isExactPin = /^\d+\.\d+\.\d+-next\./.test(range);
|
|
const samePipeline = range.endsWith('-next.' + pipelineNumber);
|
|
if (!isExactPin) {
|
|
console.error('[publish-next-guard] FAIL ' + name + ' -> ' + dep + ' range "' + range + '" is not an exact -next pin (stable-leak class, #1389)');
|
|
failures++;
|
|
} else if (!samePipeline) {
|
|
console.error('[publish-next-guard] FAIL ' + name + ' -> ' + dep + ' pinned "' + range + '" but this pipeline published -next.' + pipelineNumber + ' (cross-pipeline pin)');
|
|
failures++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (failures > 0) {
|
|
console.error('[publish-next-guard] FATAL: ' + failures + ' dep-pin violation(s) — stable-dep leak into next publish (#1389)');
|
|
process.exit(1);
|
|
}
|
|
console.log('[publish-next-guard] OK: all ' + published.length + ' published manifests carry exact same-pipeline @mosaicstack/* dep pins');
|
|
GUARD
|
|
# #1404 restore: put the workspace manifests back byte-exact so later
|
|
# steps (build-gateway frozen-lockfile install) see the committed tree.
|
|
RESTORE_FAIL=0
|
|
while read -r mf; do
|
|
if [ -f "$SNAPSHOT_DIR/$mf" ]; then
|
|
cp -p "$SNAPSHOT_DIR/$mf" "$mf"
|
|
else
|
|
echo "[publish-next] FATAL: no snapshot for $mf — cannot restore (snapshot incomplete?)" >&2
|
|
RESTORE_FAIL=1
|
|
fi
|
|
done < <(find apps packages plugins -name package.json -not -path "*/node_modules/*" -not -path "*/dist/*")
|
|
# Pristine guard (#1404 red-first control): the publish step must leave
|
|
# the workspace byte-identical to the checkout for every manifest.
|
|
# git diff is the arbiter — any residual mutation fails THIS step
|
|
# instead of surfacing as ERR_PNPM_OUTDATED_LOCKFILE in build-gateway.
|
|
if ! git diff --exit-code -- '**/package.json' >/dev/null 2>&1; then
|
|
echo "[publish-next] FATAL: workspace package.json files still differ from HEAD after restore (#1404 class)" >&2
|
|
git diff --stat -- '**/package.json' >&2 || true
|
|
RESTORE_FAIL=1
|
|
fi
|
|
rm -rf "$SNAPSHOT_DIR"
|
|
if [ "$RESTORE_FAIL" -ne 0 ]; then exit 1; fi
|
|
echo "[publish-next] workspace manifests restored byte-exact (git diff clean); later steps see the committed tree"
|
|
depends_on:
|
|
- build
|
|
- verify
|
|
|
|
# TODO: Uncomment when ready to publish to npmjs.org
|
|
# publish-npmjs:
|
|
# image: *node_image
|
|
# environment:
|
|
# NPM_TOKEN:
|
|
# from_secret: npmjs_token
|
|
# commands:
|
|
# - *enable_pnpm
|
|
# - apk add --no-cache jq bash
|
|
# - bash scripts/publish-npmjs.sh
|
|
# depends_on:
|
|
# - build
|
|
# - verify
|
|
# when:
|
|
# - event: [tag]
|
|
|
|
build-gateway:
|
|
image: gcr.io/kaniko-project/executor:debug
|
|
when: *image_build_when
|
|
environment:
|
|
REGISTRY_USER:
|
|
from_secret: REGISTRY_USERNAME
|
|
REGISTRY_PASS:
|
|
from_secret: REGISTRY_PASSWORD
|
|
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
|
|
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
|
|
CI_COMMIT_SHA: ${CI_COMMIT_SHA}
|
|
commands:
|
|
- mkdir -p /kaniko/.docker
|
|
- echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$REGISTRY_USER\",\"password\":\"$REGISTRY_PASS\"}}}" > /kaniko/.docker/config.json
|
|
- |
|
|
DESTINATIONS="--destination git.mosaicstack.dev/mosaicstack/stack/gateway:sha-${CI_COMMIT_SHA:0:7}"
|
|
if [ "$CI_COMMIT_BRANCH" = "next" ]; then
|
|
if [ -n "$CI_COMMIT_TAG" ]; then
|
|
echo "[publish] FATAL: next gateway publish must be sha-only; refusing tag '$CI_COMMIT_TAG'" >&2
|
|
exit 1
|
|
fi
|
|
echo "[publish] next gateway publish is sha-only"
|
|
elif [ "$CI_COMMIT_BRANCH" = "main" ]; then
|
|
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaicstack/stack/gateway:latest"
|
|
elif [ -z "$CI_COMMIT_TAG" ]; then
|
|
echo "[publish] FATAL: gateway image publish may only run for main, next, or tag events" >&2
|
|
exit 1
|
|
fi
|
|
if [ -n "$CI_COMMIT_TAG" ]; then
|
|
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaicstack/stack/gateway:$CI_COMMIT_TAG"
|
|
fi
|
|
/kaniko/executor --context . --dockerfile docker/gateway.Dockerfile $DESTINATIONS
|
|
depends_on:
|
|
- build
|
|
- verify
|
|
# #1411: publish-next-npm mutates workspace manifests in place during
|
|
# its transform window and restores them at step end. Any step that
|
|
# reads the pipeline workspace (kaniko COPY of manifests, later
|
|
# installs) must run AFTER publish-next-npm, never concurrently —
|
|
# pipeline 2648 raced a COPY inside the window and failed
|
|
# ERR_PNPM_OUTDATED_LOCKFILE despite a clean restore. This edge is the
|
|
# serialization invariant; add it to every new workspace consumer.
|
|
- publish-next-npm
|
|
|
|
build-appservice:
|
|
image: gcr.io/kaniko-project/executor:debug
|
|
when: *main_image_build_when
|
|
environment:
|
|
REGISTRY_USER:
|
|
from_secret: REGISTRY_USERNAME
|
|
REGISTRY_PASS:
|
|
from_secret: REGISTRY_PASSWORD
|
|
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
|
|
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
|
|
CI_COMMIT_SHA: ${CI_COMMIT_SHA}
|
|
commands:
|
|
- mkdir -p /kaniko/.docker
|
|
- echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$REGISTRY_USER\",\"password\":\"$REGISTRY_PASS\"}}}" > /kaniko/.docker/config.json
|
|
- |
|
|
DESTINATIONS="--destination git.mosaicstack.dev/mosaicstack/stack/appservice:sha-${CI_COMMIT_SHA:0:7}"
|
|
if [ "$CI_COMMIT_BRANCH" = "main" ]; then
|
|
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaicstack/stack/appservice:latest"
|
|
fi
|
|
if [ -n "$CI_COMMIT_TAG" ]; then
|
|
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaicstack/stack/appservice:$CI_COMMIT_TAG"
|
|
fi
|
|
/kaniko/executor --context . --dockerfile docker/appservice.Dockerfile $DESTINATIONS
|
|
depends_on:
|
|
- build
|
|
- verify
|
|
# #1411: publish-next-npm mutates workspace manifests in place during
|
|
# its transform window and restores them at step end. Any step that
|
|
# reads the pipeline workspace (kaniko COPY of manifests, later
|
|
# installs) must run AFTER publish-next-npm, never concurrently —
|
|
# pipeline 2648 raced a COPY inside the window and failed
|
|
# ERR_PNPM_OUTDATED_LOCKFILE despite a clean restore. This edge is the
|
|
# serialization invariant; add it to every new workspace consumer.
|
|
- publish-next-npm
|
|
|
|
build-web:
|
|
image: gcr.io/kaniko-project/executor:debug
|
|
when: *main_image_build_when
|
|
environment:
|
|
REGISTRY_USER:
|
|
from_secret: REGISTRY_USERNAME
|
|
REGISTRY_PASS:
|
|
from_secret: REGISTRY_PASSWORD
|
|
CI_COMMIT_BRANCH: ${CI_COMMIT_BRANCH}
|
|
CI_COMMIT_TAG: ${CI_COMMIT_TAG}
|
|
CI_COMMIT_SHA: ${CI_COMMIT_SHA}
|
|
commands:
|
|
- mkdir -p /kaniko/.docker
|
|
- echo "{\"auths\":{\"git.mosaicstack.dev\":{\"username\":\"$REGISTRY_USER\",\"password\":\"$REGISTRY_PASS\"}}}" > /kaniko/.docker/config.json
|
|
- |
|
|
DESTINATIONS="--destination git.mosaicstack.dev/mosaicstack/stack/web:sha-${CI_COMMIT_SHA:0:7}"
|
|
if [ "$CI_COMMIT_BRANCH" = "main" ]; then
|
|
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaicstack/stack/web:latest"
|
|
fi
|
|
if [ -n "$CI_COMMIT_TAG" ]; then
|
|
DESTINATIONS="$DESTINATIONS --destination git.mosaicstack.dev/mosaicstack/stack/web:$CI_COMMIT_TAG"
|
|
fi
|
|
/kaniko/executor --context . --dockerfile docker/web.Dockerfile $DESTINATIONS
|
|
depends_on:
|
|
- build
|
|
- verify
|
|
# #1411: publish-next-npm mutates workspace manifests in place during
|
|
# its transform window and restores them at step end. Any step that
|
|
# reads the pipeline workspace (kaniko COPY of manifests, later
|
|
# installs) must run AFTER publish-next-npm, never concurrently —
|
|
# pipeline 2648 raced a COPY inside the window and failed
|
|
# ERR_PNPM_OUTDATED_LOCKFILE despite a clean restore. This edge is the
|
|
# serialization invariant; add it to every new workspace consumer.
|
|
- publish-next-npm
|