feat(installer): add next integration lane (#686)
All checks were successful
ci/woodpecker/push/ci Pipeline was successful

Add --next installer flag (build-from-source at the next integration branch; MOSAIC_NEXT=1 env equiv; explicit --ref wins). Three-lane install docs (stable @latest / --next prerelease / --dev source) + @next dist-tag pipeline design doc. Green PR-event CI 1626 + review-of-record APPROVE (head 3a5c12a5).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit was merged in pull request #686.
This commit is contained in:
2026-06-25 05:14:32 +00:00
parent a3c1ab923c
commit 94d6538061
6 changed files with 174 additions and 15 deletions

View File

@@ -16,6 +16,9 @@
# --framework Install/upgrade framework only (skip npm CLI)
# --cli Install/upgrade npm CLI only (skip framework)
# --ref <branch> Git ref for framework archive (default: main)
# --next Prerelease lane: build CLI + gateway FROM SOURCE at the
# permanent next integration branch. Shorthand for --dev
# with ref=next; explicit --ref/MOSAIC_REF wins.
# --dev Build CLI + gateway FROM SOURCE at --ref instead of the
# registry @latest. Zero registry writes — packs local
# tarballs and installs them globally. Use to test a branch
@@ -31,6 +34,7 @@
# MOSAIC_PREFIX — npm global prefix (default: ~/.npm-global)
# MOSAIC_NO_COLOR — disable colour (set to 1)
# MOSAIC_REF — git ref for framework (default: main)
# MOSAIC_NEXT — equivalent to --next (set to 1)
# MOSAIC_DEV — equivalent to --dev (set to 1)
# MOSAIC_ASSUME_YES — equivalent to --yes (set to 1)
# ──────────────────────────────────────────────────────────────────────────────
@@ -49,7 +53,12 @@ FLAG_NO_AUTO_LAUNCH=false
FLAG_YES=false
FLAG_UNINSTALL=false
FLAG_DEV=false
FLAG_NEXT=false
GIT_REF="${MOSAIC_REF:-main}"
GIT_REF_EXPLICIT=false
if [[ -n "${MOSAIC_REF:-}" ]]; then
GIT_REF_EXPLICIT=true
fi
# MOSAIC_ASSUME_YES env var acts the same as --yes
if [[ "${MOSAIC_ASSUME_YES:-0}" == "1" ]]; then
@@ -61,13 +70,24 @@ if [[ "${MOSAIC_DEV:-0}" == "1" ]]; then
FLAG_DEV=true
fi
# MOSAIC_NEXT env var acts the same as --next: source build from the
# permanent next integration branch unless MOSAIC_REF/--ref explicitly wins.
if [[ "${MOSAIC_NEXT:-0}" == "1" ]]; then
FLAG_DEV=true
FLAG_NEXT=true
if [[ "$GIT_REF_EXPLICIT" == "false" ]]; then
GIT_REF="next"
fi
fi
while [[ $# -gt 0 ]]; do
case "$1" in
--check) FLAG_CHECK=true; shift ;;
--framework) FLAG_CLI=false; shift ;;
--cli) FLAG_FRAMEWORK=false; shift ;;
--ref) GIT_REF="${2:-main}"; shift 2 ;;
--ref) GIT_REF="${2:-main}"; GIT_REF_EXPLICIT=true; shift 2 ;;
--dev) FLAG_DEV=true; shift ;;
--next) FLAG_DEV=true; FLAG_NEXT=true; if [[ "$GIT_REF_EXPLICIT" == "false" ]]; then GIT_REF="next"; fi; shift ;;
--yes|-y) FLAG_YES=true; shift ;;
--no-auto-launch) FLAG_NO_AUTO_LAUNCH=true; shift ;;
--uninstall) FLAG_UNINSTALL=true; shift ;;
@@ -75,6 +95,10 @@ while [[ $# -gt 0 ]]; do
esac
done
if [[ "$FLAG_YES" == "true" ]]; then
export MOSAIC_ASSUME_YES=1
fi
# ─── constants ────────────────────────────────────────────────────────────────
MOSAIC_HOME="${MOSAIC_HOME:-$HOME/.config/mosaic}"
REGISTRY="${MOSAIC_REGISTRY:-https://git.mosaicstack.dev/api/packages/mosaicstack/npm/}"
@@ -95,6 +119,20 @@ fi
WORK_DIR=""
EXTRACTED_DIR=""
newest_matching_file() {
local dir="$1"
local pattern="$2"
local matches=()
[[ -d "$dir" ]] || return 0
shopt -s nullglob
# shellcheck disable=SC2206 # Intentional glob expansion for caller-provided file pattern.
matches=("$dir"/$pattern)
shopt -u nullglob
[[ "${#matches[@]}" -gt 0 ]] || return 0
# shellcheck disable=SC2012 # Need portable mtime sorting across Linux/macOS.
ls -1t "${matches[@]}" 2>/dev/null | head -1
}
# ─── uninstall path ───────────────────────────────────────────────────────────
# Shell-level uninstall for when the CLI is broken or not available.
# Handles: framework directory, npm CLI package, npmrc scope line.
@@ -158,7 +196,7 @@ if [[ "$FLAG_UNINSTALL" == "true" ]]; then
# Find most recent backup
backup=""
if [[ -d "$dir" ]]; then
backup="$(ls -1t "$dir/${base}.mosaic-bak-"* 2>/dev/null | head -1 || true)"
backup="$(newest_matching_file "$dir" "${base}.mosaic-bak-*")"
fi
if [[ -n "$backup" ]] && [[ -f "$backup" ]]; then
cp "$backup" "$dest"
@@ -214,6 +252,16 @@ fail() { echo "${R}✖${RESET} $*" >&2; }
dim() { echo "${DIM}$*${RESET}"; }
step() { echo ""; echo "${BOLD}$*${RESET}"; }
source_ref_details() {
if [[ "$FLAG_NEXT" == "true" && "$GIT_REF" == "next" ]]; then
echo "ref: next, --next prerelease lane"
elif [[ "$FLAG_NEXT" == "true" ]]; then
echo "ref: ${GIT_REF}, --next requested, explicit ref wins"
else
echo "ref: ${GIT_REF}"
fi
}
# ─── helpers ──────────────────────────────────────────────────────────────────
require_cmd() {
@@ -332,8 +380,8 @@ install_cli_from_source() {
( cd "$src/apps/gateway" && pnpm pack --pack-destination "$out_dir" ) 2>&1 | sed 's/^/ /'
local cli_tgz gw_tgz
cli_tgz="$(ls -1t "$out_dir"/mosaicstack-mosaic-*.tgz 2>/dev/null | head -1)"
gw_tgz="$(ls -1t "$out_dir"/mosaicstack-gateway-*.tgz 2>/dev/null | head -1)"
cli_tgz="$(newest_matching_file "$out_dir" 'mosaicstack-mosaic-*.tgz')"
gw_tgz="$(newest_matching_file "$out_dir" 'mosaicstack-gateway-*.tgz')"
if [[ ! -f "$cli_tgz" ]]; then
fail "CLI tarball was not produced by pnpm pack."
@@ -388,7 +436,7 @@ if [[ "$FLAG_FRAMEWORK" == "true" ]]; then
else
dim " Installed: (none)"
fi
dim " Source: ${REPO_BASE} (ref: ${GIT_REF})"
dim " Source: ${REPO_BASE} ($(source_ref_details))"
echo ""
if [[ "$FLAG_CHECK" == "true" ]]; then
@@ -468,7 +516,7 @@ if [[ "$FLAG_CLI" == "true" ]]; then
fi
if [[ "$FLAG_DEV" == "true" ]]; then
dim " Source: ${REPO_BASE} (ref: ${GIT_REF}, build-from-source)"
dim " Source: ${REPO_BASE} ($(source_ref_details), build-from-source)"
elif [[ -n "$LATEST" ]]; then
dim " Latest: ${CLI_PKG}@${LATEST}"
else
@@ -603,7 +651,7 @@ if [[ "$FLAG_CHECK" == "false" ]]; then
local base dir backup_path backup_val
base="$(basename "$dest")"
dir="$(dirname "$dest")"
backup_path="$(ls -1t "$dir/${base}.mosaic-bak-"* 2>/dev/null | head -1 || true)"
backup_path="$(newest_matching_file "$dir" "${base}.mosaic-bak-*")"
if [[ -n "$backup_path" ]]; then
backup_val="\"$backup_path\""
else
@@ -628,7 +676,7 @@ if [[ "$FLAG_CHECK" == "false" ]]; then
NPMRC_LINES_JSON="[\"$MANIFEST_SCOPE_LINE\"]"
fi
node -e "
if node -e "
const fs = require('fs');
const path = require('path');
const p = process.argv[1];
@@ -653,9 +701,11 @@ if [[ "$FLAG_CHECK" == "false" ]]; then
"$MANIFEST_CLI_VERSION" \
"$MANIFEST_FW_VERSION" \
"$NPMRC_LINES_JSON" \
"$RUNTIME_COPIES" 2>/dev/null \
&& ok "Install manifest written: $MANIFEST_PATH" \
|| warn "Could not write install manifest (non-fatal)"
"$RUNTIME_COPIES" 2>/dev/null; then
ok "Install manifest written: $MANIFEST_PATH"
else
warn "Could not write install manifest (non-fatal)"
fi
echo ""
ok "Done."