From a51a06e6a1e9fef944a534ab2701f6770e27f5bd Mon Sep 17 00:00:00 2001 From: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Date: Sun, 21 Jun 2026 14:22:18 +0800 Subject: [PATCH] ci(release): pre-publish backward-compat smoke against live hotcrm (#2035) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The last layer of the third-party validation strategy from #2035: a live ceiling above the deterministic in-repo floor (@objectstack/downstream-contract, #2089). `scripts/downstream-smoke.sh` clones objectstack-ai/hotcrm at a pinned tag (v1.2.0), installs it against the PUBLISHED @objectstack/* packages, overlays the freshly-built — unreleased — @objectstack/spec dist, and runs hotcrm's own `typecheck` + `objectstack validate`. If the about-to-publish spec breaks a real, independently-authored third-party consumer, the release is blocked. Wired into release.yml between the build and the changesets publish step, so it runs only in the release pipeline (never reddens a feature PR). Mirrors the existing console-SPA step that already clones an external repo at a pinned ref. Verified end-to-end locally: clone hotcrm@v1.2.0 → install → overlay current spec → typecheck + validate both pass (15 objects / 15 pages / 10 reports / 11 actions / 17 flows). Override the pinned ref with HOTCRM_REF. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/release.yml | 12 ++++++++ scripts/downstream-smoke.sh | 55 +++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100755 scripts/downstream-smoke.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b04051c310..95913c210c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -58,6 +58,18 @@ jobs: # prepublishOnly guard in @objectstack/console passes. run: bash scripts/build-console.sh + - name: Downstream backward-compat smoke (live hotcrm) + # Pre-publish gate (#2035): the about-to-publish @objectstack/spec must + # not break a real third-party consumer pinned to a published release. + # Clones objectstack-ai/hotcrm@${HOTCRM_REF}, installs it (published + # deps), overlays the freshly-built spec dist, and runs hotcrm's own + # typecheck + `objectstack validate`. A red here blocks the publish. + # The deterministic in-repo floor is @objectstack/downstream-contract; + # this is the live ceiling. + env: + HOTCRM_REF: v1.2.0 + run: bash scripts/downstream-smoke.sh + - name: Create Release Pull Request or Publish to npm id: changesets uses: changesets/action@v1 diff --git a/scripts/downstream-smoke.sh b/scripts/downstream-smoke.sh new file mode 100755 index 0000000000..cc4dc33858 --- /dev/null +++ b/scripts/downstream-smoke.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# downstream-smoke.sh — pre-publish backward-compatibility gate (#2035). +# +# The spec package IS the third-party API. In-repo consumers (examples, +# @objectstack/dogfood, downstream-contract) co-evolve with the spec, so they +# cannot prove the *about-to-publish* spec still works for a real, independently +# authored consumer pinned to a PUBLISHED release. +# +# This clones objectstack-ai/hotcrm at a pinned tag, installs it (pulling the +# published @objectstack/* packages), overlays the freshly-built — i.e. +# unreleased — @objectstack/spec dist, and runs hotcrm's own typecheck + +# `objectstack validate`. A failure means the release would break a real third +# party: block the publish. +# +# Requires `pnpm run build` (or at least the spec build) to have run first. +# Override the pinned ref with HOTCRM_REF. +set -euo pipefail + +HOTCRM_REF="${HOTCRM_REF:-v1.2.0}" +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +SPEC_DIST="$REPO_ROOT/packages/spec/dist" + +if [ ! -d "$SPEC_DIST" ]; then + echo "::error::spec dist not found at $SPEC_DIST — build it first (pnpm --filter @objectstack/spec build)." + exit 1 +fi + +WORK="$(mktemp -d)" +cleanup() { rm -rf "$WORK"; } +trap cleanup EXIT + +echo "→ Cloning objectstack-ai/hotcrm@${HOTCRM_REF} ..." +git clone --quiet --depth 1 --branch "$HOTCRM_REF" https://github.com/objectstack-ai/hotcrm.git "$WORK/hotcrm" +cd "$WORK/hotcrm" + +echo "→ Installing hotcrm (published @objectstack/* deps) ..." +pnpm install --frozen-lockfile 2>/dev/null || pnpm install --no-frozen-lockfile + +DEST="node_modules/@objectstack/spec/dist" +if [ ! -d "$DEST" ]; then + echo "::error::hotcrm did not install @objectstack/spec — cannot run the gate." + exit 1 +fi + +echo "→ Overlaying the unreleased @objectstack/spec dist into hotcrm ..." +rm -rf "$DEST" +cp -R "$SPEC_DIST" "$DEST" + +echo "→ hotcrm typecheck (against unreleased spec) ..." +pnpm run typecheck + +echo "→ hotcrm validate (loader parses all metadata against unreleased spec) ..." +pnpm run validate + +echo "✅ Downstream smoke passed — unreleased @objectstack/spec is backward compatible with hotcrm@${HOTCRM_REF}."