chore(release): --source-only — a home-only prerelease mode (The Source, never the storefront)
A prerelease the other engine devDeps from our own registry while the two are being proven together must never reach npmjs: --source-only tags, lets CI publish to The Source, creates the release page, and SKIPS the storefront publish, the cross-registry verification and the docs push — loudly, at each step. Refused for a non-prerelease version: a public floor always ships byte-identical on both registries.
This commit is contained in:
parent
4176439ba3
commit
dcbad1765a
1 changed files with 73 additions and 45 deletions
|
|
@ -15,6 +15,12 @@ NC='\033[0m' # No Color
|
|||
RELEASE_TYPE="${1:-patch}" # patch, minor, or major
|
||||
SKIP_TESTS=false
|
||||
DRY_RUN=false
|
||||
# --source-only: the HOME leg only — tag, CI's publish to The Source, and the
|
||||
# release page; NO storefront (npmjs) publish, NO pair verification, NO docs
|
||||
# push. The pair-gate shape: a prerelease the fleet's other engine devDeps
|
||||
# from our own registry while the pair is proven, never a public artifact.
|
||||
# Refused for a non-prerelease version — a public floor is always a pair.
|
||||
SOURCE_ONLY=false
|
||||
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
|
|
@ -24,6 +30,9 @@ for arg in "$@"; do
|
|||
--dry-run)
|
||||
DRY_RUN=true
|
||||
;;
|
||||
--source-only)
|
||||
SOURCE_ONLY=true
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
|
|
@ -100,7 +109,7 @@ else
|
|||
;;
|
||||
*)
|
||||
echo -e "${RED}❌ Invalid release type: ${RELEASE_TYPE}${NC}"
|
||||
echo "Usage: ./scripts/release.sh [patch|minor|major|<explicit-version>] [--dry-run]"
|
||||
echo "Usage: ./scripts/release.sh [patch|minor|major|<explicit-version>] [--dry-run] [--source-only (prereleases only)]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
|
@ -119,6 +128,13 @@ echo -e "${BLUE}New version: ${NEW_VERSION}${NC}"
|
|||
if [ "$PRERELEASE" = true ]; then
|
||||
echo -e "${YELLOW}⚠️ Prerelease → npm dist-tag '${NPM_TAG}', GitHub prerelease${NC}"
|
||||
fi
|
||||
if [ "$SOURCE_ONLY" = true ]; then
|
||||
if [ "$PRERELEASE" != true ]; then
|
||||
echo -e "${RED}❌ --source-only is for prereleases only: a non-prerelease version is a public floor and always ships as the byte-identical pair.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
echo -e "${YELLOW}⚠️ --source-only → The Source (home) ONLY: no npmjs publish, no pair verification, no docs push${NC}"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
if [ "$DRY_RUN" = true ]; then
|
||||
|
|
@ -221,31 +237,34 @@ else
|
|||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${BLUE}9️⃣½ Publishing to npmjs (storefront, dist-tag: ${NPM_TAG})...${NC}"
|
||||
# BYTE-IDENTITY LAW: the storefront republishes CI's EXACT artifact — download
|
||||
# the tarball The Source serves and publish that file, never a fresh local pack
|
||||
# (a local rebuild can differ byte-wise, and the fleet verifies the pair by
|
||||
# shasum across registries).
|
||||
STOREFRONT_TMP="$(mktemp -d)"
|
||||
(cd "$STOREFRONT_TMP" && npm pack "@soulcraft/brainy@${NEW_VERSION}" "--@soulcraft:registry=${SOURCE_NPM_REG}" >/dev/null)
|
||||
SOURCE_TARBALL="$(ls "$STOREFRONT_TMP"/soulcraft-brainy-*.tgz)"
|
||||
echo -e "${BLUE} home artifact: $(sha256sum "$SOURCE_TARBALL" | cut -d' ' -f1)${NC}"
|
||||
npm publish "$SOURCE_TARBALL" --tag "$NPM_TAG" "--@soulcraft:registry=https://registry.npmjs.org/"
|
||||
rm -rf "$STOREFRONT_TMP"
|
||||
# Brainy is the only PUBLIC @soulcraft package — verify visibility after every publish.
|
||||
npm access get status @soulcraft/brainy "--@soulcraft:registry=https://registry.npmjs.org/" || true
|
||||
# Verify the pair is byte-identical by registry-reported shasum — divergence
|
||||
# here means the storefront leg must be treated as failed, loudly. RETRIED
|
||||
# with raw curl: npmjs metadata propagates with a lag measured in minutes,
|
||||
# and a one-shot npm-view probe fired a false DIVERGENCE on 10.0.0 while a
|
||||
# raw curl of the registry document already confirmed byte-identity. The
|
||||
# probe now reads the registry JSON directly (no npm cache in the path) and
|
||||
# gives propagation up to 5 minutes before calling the pair divergent.
|
||||
NPMJS_VERIFY_ATTEMPTS=20
|
||||
NPMJS_VERIFY_INTERVAL_S=15 # 20 × 15s = 5 minutes of propagation grace
|
||||
SOURCE_SHA=$(npm view "@soulcraft/brainy@${NEW_VERSION}" dist.shasum "--@soulcraft:registry=${SOURCE_NPM_REG}" 2>/dev/null || echo "source-unavailable")
|
||||
PAIR_IDENTICAL=false
|
||||
for ((attempt = 1; attempt <= NPMJS_VERIFY_ATTEMPTS; attempt++)); do
|
||||
if [ "$SOURCE_ONLY" = true ]; then
|
||||
echo -e "${YELLOW}9️⃣½ Storefront (npmjs) leg SKIPPED — --source-only: v${NEW_VERSION} lives on The Source under dist-tag '${NPM_TAG}' only${NC}\n"
|
||||
else
|
||||
echo -e "${BLUE}9️⃣½ Publishing to npmjs (storefront, dist-tag: ${NPM_TAG})...${NC}"
|
||||
# BYTE-IDENTITY LAW: the storefront republishes CI's EXACT artifact — download
|
||||
# the tarball The Source serves and publish that file, never a fresh local pack
|
||||
# (a local rebuild can differ byte-wise, and the fleet verifies the pair by
|
||||
# shasum across registries).
|
||||
STOREFRONT_TMP="$(mktemp -d)"
|
||||
(cd "$STOREFRONT_TMP" && npm pack "@soulcraft/brainy@${NEW_VERSION}" "--@soulcraft:registry=${SOURCE_NPM_REG}" >/dev/null)
|
||||
SOURCE_TARBALL="$(ls "$STOREFRONT_TMP"/soulcraft-brainy-*.tgz)"
|
||||
echo -e "${BLUE} home artifact: $(sha256sum "$SOURCE_TARBALL" | cut -d' ' -f1)${NC}"
|
||||
npm publish "$SOURCE_TARBALL" --tag "$NPM_TAG" "--@soulcraft:registry=https://registry.npmjs.org/"
|
||||
rm -rf "$STOREFRONT_TMP"
|
||||
# Brainy is the only PUBLIC @soulcraft package — verify visibility after every publish.
|
||||
npm access get status @soulcraft/brainy "--@soulcraft:registry=https://registry.npmjs.org/" || true
|
||||
# Verify the pair is byte-identical by registry-reported shasum — divergence
|
||||
# here means the storefront leg must be treated as failed, loudly. RETRIED
|
||||
# with raw curl: npmjs metadata propagates with a lag measured in minutes,
|
||||
# and a one-shot npm-view probe fired a false DIVERGENCE on 10.0.0 while a
|
||||
# raw curl of the registry document already confirmed byte-identity. The
|
||||
# probe now reads the registry JSON directly (no npm cache in the path) and
|
||||
# gives propagation up to 5 minutes before calling the pair divergent.
|
||||
NPMJS_VERIFY_ATTEMPTS=20
|
||||
NPMJS_VERIFY_INTERVAL_S=15 # 20 × 15s = 5 minutes of propagation grace
|
||||
SOURCE_SHA=$(npm view "@soulcraft/brainy@${NEW_VERSION}" dist.shasum "--@soulcraft:registry=${SOURCE_NPM_REG}" 2>/dev/null || echo "source-unavailable")
|
||||
PAIR_IDENTICAL=false
|
||||
for ((attempt = 1; attempt <= NPMJS_VERIFY_ATTEMPTS; attempt++)); do
|
||||
NPMJS_SHA=$(curl -fsSL "https://registry.npmjs.org/@soulcraft%2Fbrainy" 2>/dev/null \
|
||||
| node -e "let d='';process.stdin.on('data',c=>d+=c).on('end',()=>{try{const v=JSON.parse(d).versions[process.argv[1]];console.log(v?v.dist.shasum:'')}catch{console.log('')}})" "${NEW_VERSION}" \
|
||||
|| echo "")
|
||||
|
|
@ -255,12 +274,13 @@ for ((attempt = 1; attempt <= NPMJS_VERIFY_ATTEMPTS; attempt++)); do
|
|||
fi
|
||||
echo -e "${YELLOW} … npmjs metadata not settled (attempt ${attempt}/${NPMJS_VERIFY_ATTEMPTS}: '${NPMJS_SHA:-absent}' vs '${SOURCE_SHA}'); retrying in ${NPMJS_VERIFY_INTERVAL_S}s${NC}"
|
||||
sleep "$NPMJS_VERIFY_INTERVAL_S"
|
||||
done
|
||||
if [ "$PAIR_IDENTICAL" = true ]; then
|
||||
done
|
||||
if [ "$PAIR_IDENTICAL" = true ]; then
|
||||
echo -e "${GREEN}✅ Published to npmjs — byte-identical pair (shasum ${NPMJS_SHA})${NC}\n"
|
||||
else
|
||||
else
|
||||
echo -e "${RED}❌ REGISTRY DIVERGENCE: The Source shasum ${SOURCE_SHA} != npmjs shasum ${NPMJS_SHA} after ${NPMJS_VERIFY_ATTEMPTS} attempts — investigate before announcing${NC}\n"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Step 11: Release object on The Source (presentational — the tag, CHANGELOG,
|
||||
|
|
@ -283,16 +303,24 @@ fi
|
|||
# DOCS_INGEST_SECRET is unset; fails loudly (without undoing the publish —
|
||||
# that already happened) when a push errors, so the docs site never
|
||||
# silently trails npm.
|
||||
echo -e "${BLUE}1️⃣2️⃣ Pushing public docs to soulcraft.com/docs...${NC}"
|
||||
if node scripts/push-docs.js; then
|
||||
echo -e "${GREEN}✅ Docs push step done${NC}\n"
|
||||
if [ "$SOURCE_ONLY" = true ]; then
|
||||
echo -e "${YELLOW}1️⃣2️⃣ Docs push SKIPPED — --source-only (a home-only prerelease publishes no public docs)${NC}\n"
|
||||
else
|
||||
echo -e "${BLUE}1️⃣2️⃣ Pushing public docs to soulcraft.com/docs...${NC}"
|
||||
if node scripts/push-docs.js; then
|
||||
echo -e "${GREEN}✅ Docs push step done${NC}\n"
|
||||
else
|
||||
echo -e "${RED}❌ Docs push FAILED — soulcraft.com/docs trails npm until re-run or interim sync${NC}\n"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
echo -e "${GREEN}🎉 Release ${NEW_VERSION} complete!${NC}"
|
||||
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
echo ""
|
||||
echo -e "📦 npm: ${BLUE}https://www.npmjs.com/package/@soulcraft/brainy/v/${NEW_VERSION}${NC}"
|
||||
if [ "$SOURCE_ONLY" = true ]; then
|
||||
echo -e "📦 npmjs: ${YELLOW}not published (--source-only)${NC}"
|
||||
else
|
||||
echo -e "📦 npm: ${BLUE}https://www.npmjs.com/package/@soulcraft/brainy/v/${NEW_VERSION}${NC}"
|
||||
fi
|
||||
echo -e "🏠 The Source: ${BLUE}https://source.soulcraft.com/soulcraft/brainy/releases/tag/v${NEW_VERSION}${NC}"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue