From 94d6538061628c3be4f94d2fd5abd69367e09232 Mon Sep 17 00:00:00 2001 From: "jason.woltje" Date: Thu, 25 Jun 2026 05:14:32 +0000 Subject: [PATCH] feat(installer): add next integration lane (#686) 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 --- README.md | 14 +++- .../prerelease-next-dist-tag-pipeline.md | 40 +++++++++++ docs/guides/user-guide.md | 14 +++- .../installer-next-lane-20260624.md | 35 +++++++++ packages/mosaic/framework/defaults/README.md | 14 +++- tools/install.sh | 72 ++++++++++++++++--- 6 files changed, 174 insertions(+), 15 deletions(-) create mode 100644 docs/design/prerelease-next-dist-tag-pipeline.md create mode 100644 docs/scratchpads/installer-next-lane-20260624.md diff --git a/README.md b/README.md index dca4667..1582d83 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,16 @@ This installs both components: | **Framework** | Bash launcher, guides, runtime configs, tools, skills | `~/.config/mosaic/` | | **@mosaicstack/mosaic** | Unified `mosaic` CLI — TUI, gateway client, wizard, auto-updater | `~/.npm-global/bin/` | +### Install lanes + +| Lane | Command | Use when | Source | +| ------------------------ | ------------------------------------- | ----------------------------------------------------- | ----------------------------------------------------------------------- | +| Stable | `bash tools/install.sh` | You want the released Mosaic CLI/framework | npm registry `@mosaicstack/mosaic@latest` + framework archive at `main` | +| Prerelease integration | `bash tools/install.sh --next` | You want the current `next` integration branch | Build-from-source at `next` | +| Contributor/source build | `bash tools/install.sh --dev --ref X` | You are testing a branch before release; `--ref` wins | Build-from-source at the requested ref | + +`--next` is shorthand for the prerelease integration lane: it enables source-build mode and uses `next` unless an explicit `--ref` or `MOSAIC_REF` is provided. + After install, the wizard runs automatically or you can invoke it manually: ```bash @@ -336,7 +346,9 @@ The CLI also performs a background update check on every invocation (cached for bash tools/install.sh --check # Version check only bash tools/install.sh --framework # Framework only (skip npm CLI) bash tools/install.sh --cli # npm CLI only (skip framework) -bash tools/install.sh --ref v1.0 # Install from a specific git ref +bash tools/install.sh --next # Prerelease lane: source build from next +bash tools/install.sh --dev # Contributor lane: source build at --ref/main +bash tools/install.sh --ref v1.0 # Install from a specific git ref (--ref wins over --next) bash tools/install.sh --yes # Non-interactive, accept all defaults bash tools/install.sh --no-auto-launch # Skip auto-launch of wizard ``` diff --git a/docs/design/prerelease-next-dist-tag-pipeline.md b/docs/design/prerelease-next-dist-tag-pipeline.md new file mode 100644 index 0000000..5cf8d8d --- /dev/null +++ b/docs/design/prerelease-next-dist-tag-pipeline.md @@ -0,0 +1,40 @@ +# Planned Design — npm `@next` prerelease lane + +Status: **PLANNED / not yet built** + +## Current state + +`tools/install.sh --next` provides a prerelease integration lane by building the Mosaic CLI and gateway from source at the permanent `next` branch. This is correct for validating integration-branch source, but it is slower than the stable npm lane because it downloads the archive, installs workspace dependencies, builds packages, packs local tarballs, and installs those tarballs globally. + +## Medium-term target + +Publish every accepted `next` integration build to the npm registry under the `@next` dist-tag, for example: + +```text +@mosaicstack/mosaic@0.0.49-next.1 +@mosaicstack/mosaic@0.0.49-next.2 +``` + +Then move `tools/install.sh --next` from source-build behavior to a fast npm install: + +```bash +npm install -g @mosaicstack/mosaic@next +``` + +The framework archive should still resolve from the matching `next` source/ref until framework packaging has a registry-backed equivalent. + +## Pipeline shape + +1. Trigger on successful CI for `next`. +2. Compute the next prerelease version from the upcoming stable version plus a monotonic prerelease counter (`0.0.49-next.N`). +3. Build and pack publishable packages in CI. +4. Publish to the Mosaic Gitea npm registry with dist-tag `next`. +5. Keep `latest` untouched; only main/release promotion can update `latest`. +6. Teach the installer to prefer `@next` for the CLI/gateway prerelease lane once the registry tag is reliable. + +## Guardrails + +- `@next` is mutable prerelease convenience, not a deployment pin. +- Stable installs continue to use `@latest`. +- Contributor validation remains available through `--dev --ref `. +- Pipeline must be reproducible and trace every prerelease package back to the source commit on `next`. diff --git a/docs/guides/user-guide.md b/docs/guides/user-guide.md index c149edd..0c12580 100644 --- a/docs/guides/user-guide.md +++ b/docs/guides/user-guide.md @@ -175,8 +175,18 @@ Or use the direct URL: bash <(curl -fsSL https://git.mosaicstack.dev/mosaicstack/stack/raw/branch/main/tools/install.sh) ``` -The installer places the `mosaic` binary at `~/.npm-global/bin/mosaic`. Flags for -non-interactive use: +The installer places the `mosaic` binary at `~/.npm-global/bin/mosaic`. + +Install lanes: + +| Lane | Command | Source | +| ------------------------ | ------------------------------------- | -------------------------------------------- | +| Stable | `bash tools/install.sh` | npm `@mosaicstack/mosaic@latest` + `main` | +| Prerelease integration | `bash tools/install.sh --next` | Build-from-source at permanent branch `next` | +| Contributor/source build | `bash tools/install.sh --dev --ref X` | Build-from-source at the requested ref | + +`--next` implies source-build mode at `next`; explicit `--ref` or `MOSAIC_REF` wins. +Flags for non-interactive use: ```bash --yes # Accept all defaults diff --git a/docs/scratchpads/installer-next-lane-20260624.md b/docs/scratchpads/installer-next-lane-20260624.md new file mode 100644 index 0000000..d46c2fa --- /dev/null +++ b/docs/scratchpads/installer-next-lane-20260624.md @@ -0,0 +1,35 @@ +# Scratchpad — installer `--next` lane + +## Objective + +Add a prerelease installer lane for the permanent `next` integration branch. + +## Scope + +- `tools/install.sh` +- README/install documentation +- Follow-up design note for future npm `@next` prerelease publishing + +## Plan + +1. Add `--next` and `MOSAIC_NEXT=1` as source-build shorthand for `next`. +2. Preserve explicit ref precedence: `MOSAIC_REF` and `--ref` win over `--next`. +3. Update installer source display/help text. +4. Document three lanes: + - stable npm `@latest` + - prerelease `--next` + - contributor `--dev --ref X` +5. Run shell and repo gates locally, then hold before push/PR until runner serialization greenlight. + +## Verification + +- `bash -n tools/install.sh` — pass. +- `docker run --rm -v "$PWD:/mnt" -w /mnt koalaman/shellcheck:stable tools/install.sh` — pass. +- `bash tools/install.sh --check --framework --next` — source display shows `ref: next, --next prerelease lane`. +- `bash tools/install.sh --check --cli --next --ref feature-x` — source display shows explicit ref wins. +- `MOSAIC_NEXT=1 MOSAIC_REF=feature-env bash tools/install.sh --check --cli` — source display shows explicit env ref wins. +- `pnpm install --frozen-lockfile --prefer-offline --store-dir /home/jarvis/.local/share/pnpm/store` — pass (local override for repo `.npmrc` CI store path). +- `pnpm typecheck` — pass (41 successful tasks). +- `pnpm lint` — pass (23 successful tasks). +- `pnpm format:check` — pass. +- `bash tools/e2e-install-test.sh` — attempted; current baseline fails during gateway health after stable registry install because Valkey is unavailable in the clean container. The `tools/install.sh --yes --no-auto-launch` stage itself completed before the downstream gateway verification failure. diff --git a/packages/mosaic/framework/defaults/README.md b/packages/mosaic/framework/defaults/README.md index 629c0bf..1baaaec 100644 --- a/packages/mosaic/framework/defaults/README.md +++ b/packages/mosaic/framework/defaults/README.md @@ -43,6 +43,16 @@ The installer: - Runs a health audit - Detects existing installs and preserves local files (SOUL.md, USER.md, etc.) +### Install lanes + +| Lane | Command | Use when | Source | +| ------------------------ | ------------------------------------- | ---------------------------------------------- | ------------------------------------------ | +| Stable | `bash tools/install.sh` | You want the released framework and CLI | npm `@mosaicstack/mosaic@latest` + `main` | +| Prerelease integration | `bash tools/install.sh --next` | You want the permanent `next` integration lane | Build-from-source at `next` | +| Contributor/source build | `bash tools/install.sh --dev --ref X` | You are validating a branch before release | Build-from-source at the requested git ref | + +`--next` is shorthand for source-build mode at `next`; explicit `--ref` or `MOSAIC_REF` wins when both are present. + ## First Run After install, open a new terminal (or `source ~/.bashrc`) and run: @@ -174,7 +184,9 @@ The installer preserves local `SOUL.md`, `USER.md`, `TOOLS.md`, and `memory/` by bash tools/install.sh --check # Version check only bash tools/install.sh --framework # Framework only (skip npm CLI) bash tools/install.sh --cli # npm CLI only (skip framework) -bash tools/install.sh --ref v1.0 # Install from a specific git ref +bash tools/install.sh --next # Prerelease lane: source build from next +bash tools/install.sh --dev # Contributor lane: source build at --ref/main +bash tools/install.sh --ref v1.0 # Install from a specific git ref (--ref wins over --next) ``` ## Universal Skills diff --git a/tools/install.sh b/tools/install.sh index a1c509d..79dfc4d 100755 --- a/tools/install.sh +++ b/tools/install.sh @@ -16,6 +16,9 @@ # --framework Install/upgrade framework only (skip npm CLI) # --cli Install/upgrade npm CLI only (skip framework) # --ref 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."