From 745b9c49675336c8d294634fcb4407d159869c5e Mon Sep 17 00:00:00 2001 From: Jarvis Date: Wed, 24 Jun 2026 16:54:10 -0500 Subject: [PATCH 1/2] feat(installer): --dev flag builds CLI + gateway from source MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds `install.sh --dev` (and MOSAIC_DEV=1): instead of installing @mosaicstack/mosaic from the registry @latest, download the monorepo archive at --ref, `pnpm install` + build, `pnpm pack` both @mosaicstack/mosaic and @mosaicstack/gateway, and `npm install -g` both local tarballs. ZERO registry writes — no dev dist-tags. Workspace deps resolve from the registry at the versions `pnpm pack` rewrites `workspace:*` into. Exports MOSAIC_GATEWAY_SKIP_NPM_INSTALL=1 so the auto-launched wizard keeps the source-built global gateway instead of overwriting it with the registry build. Refactors the archive download into a shared ensure_monorepo helper reused by both the framework install (Part 1) and the dev build (Part 2). Lets a branch be tested end-to-end on a clean machine before cutting a release, reducing semver churn. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01RMoEx7hfdFGjUiCHuN1RRi --- tools/install.sh | 149 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 128 insertions(+), 21 deletions(-) diff --git a/tools/install.sh b/tools/install.sh index 49f8874..00e306b 100755 --- a/tools/install.sh +++ b/tools/install.sh @@ -16,6 +16,10 @@ # --framework Install/upgrade framework only (skip npm CLI) # --cli Install/upgrade npm CLI only (skip framework) # --ref Git ref for framework archive (default: main) +# --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 +# end-to-end before cutting a release. # --yes Accept all defaults; headless/non-interactive install # --no-auto-launch Skip automatic mosaic wizard + gateway install on first install # --uninstall Reverse the install: remove framework dir, CLI package, and npmrc line @@ -27,6 +31,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_DEV — equivalent to --dev (set to 1) # MOSAIC_ASSUME_YES — equivalent to --yes (set to 1) # ────────────────────────────────────────────────────────────────────────────── # @@ -43,6 +48,7 @@ FLAG_CLI=true FLAG_NO_AUTO_LAUNCH=false FLAG_YES=false FLAG_UNINSTALL=false +FLAG_DEV=false GIT_REF="${MOSAIC_REF:-main}" # MOSAIC_ASSUME_YES env var acts the same as --yes @@ -50,12 +56,18 @@ if [[ "${MOSAIC_ASSUME_YES:-0}" == "1" ]]; then FLAG_YES=true fi +# MOSAIC_DEV env var acts the same as --dev +if [[ "${MOSAIC_DEV:-0}" == "1" ]]; then + FLAG_DEV=true +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 ;; + --dev) FLAG_DEV=true; shift ;; --yes|-y) FLAG_YES=true; shift ;; --no-auto-launch) FLAG_NO_AUTO_LAUNCH=true; shift ;; --uninstall) FLAG_UNINSTALL=true; shift ;; @@ -72,6 +84,17 @@ CLI_PKG="${SCOPE}/mosaic" REPO_BASE="https://git.mosaicstack.dev/mosaicstack/stack" ARCHIVE_URL="${REPO_BASE}/archive/${GIT_REF}.tar.gz" +# In dev (build-from-source) mode the gateway is installed globally from a +# locally-built tarball. Tell the wizard / gateway-config stage NOT to overwrite +# it with the registry @latest build (honored by gatewayConfigStage). +if [[ "$FLAG_DEV" == "true" ]]; then + export MOSAIC_GATEWAY_SKIP_NPM_INSTALL=1 +fi + +# Shared monorepo checkout (populated on demand by ensure_monorepo). +WORK_DIR="" +EXTRACTED_DIR="" + # ─── uninstall path ─────────────────────────────────────────────────────────── # Shell-level uninstall for when the CLI is broken or not available. # Handles: framework directory, npm CLI package, npmrc scope line. @@ -239,6 +262,91 @@ framework_version() { fi } +# Download + extract the monorepo archive at $GIT_REF exactly once per run. +# Sets the script-level EXTRACTED_DIR to the repo root. Reused by both the +# framework install (Part 1) and the dev build-from-source path (Part 2). +ensure_monorepo() { + if [[ -n "$EXTRACTED_DIR" ]] && [[ -d "$EXTRACTED_DIR" ]]; then + return 0 + fi + + require_cmd tar + + WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/mosaic-install-XXXXXX")" + # shellcheck disable=SC2317 + cleanup_work() { [[ -n "$WORK_DIR" ]] && rm -rf "$WORK_DIR"; } + trap cleanup_work EXIT + + info "Downloading source from ${GIT_REF}…" + if command -v curl &>/dev/null; then + curl -fsSL "$ARCHIVE_URL" | tar xz -C "$WORK_DIR" + elif command -v wget &>/dev/null; then + wget -qO- "$ARCHIVE_URL" | tar xz -C "$WORK_DIR" + else + fail "curl or wget required to download source." + exit 1 + fi + + # Gitea archives extract to / inside the work dir + EXTRACTED_DIR="$(find "$WORK_DIR" -maxdepth 1 -mindepth 1 -type d | head -1)" + if [[ -z "$EXTRACTED_DIR" ]] || [[ ! -d "$EXTRACTED_DIR" ]]; then + fail "Could not locate extracted source in archive." + ls -la "$WORK_DIR" >&2 + exit 1 + fi +} + +# Build @mosaicstack/mosaic + @mosaicstack/gateway from source and install both +# globally from locally-packed tarballs. ZERO registry writes. Workspace deps +# (brain/config/db/…) are pulled from the registry at the versions pinned in +# each package.json — `pnpm pack` rewrites `workspace:*` to those versions. +install_cli_from_source() { + local src="$EXTRACTED_DIR" + local out_dir="$WORK_DIR/dist-tarballs" + mkdir -p "$out_dir" + + # pnpm via corepack (ships with Node >= 16.9; required by Node >= 20 preflight) + if ! command -v pnpm &>/dev/null; then + info "Activating pnpm via corepack…" + corepack enable >/dev/null 2>&1 || true + corepack prepare pnpm@latest --activate >/dev/null 2>&1 || true + fi + require_cmd pnpm + + info "Installing workspace dependencies (pnpm install)…" + ( cd "$src" && pnpm install ) 2>&1 | sed 's/^/ /' + + info "Building CLI + gateway from source…" + ( cd "$src" && pnpm --filter "@mosaicstack/mosaic..." --filter "@mosaicstack/gateway..." run build ) 2>&1 | sed 's/^/ /' + + info "Packing local tarballs…" + ( cd "$src/packages/mosaic" && pnpm pack --pack-destination "$out_dir" ) 2>&1 | sed 's/^/ /' + ( 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)" + + if [[ ! -f "$cli_tgz" ]]; then + fail "CLI tarball was not produced by pnpm pack." + exit 1 + fi + if [[ ! -f "$gw_tgz" ]]; then + fail "Gateway tarball was not produced by pnpm pack." + exit 1 + fi + + # Gateway first so it is present globally before the CLI's wizard runs (which + # skips its own gateway install via MOSAIC_GATEWAY_SKIP_NPM_INSTALL=1). + info "Installing gateway from source tarball (global)…" + npm install -g "$gw_tgz" --prefix="$PREFIX" 2>&1 | sed 's/^/ /' + + info "Installing CLI from source tarball (global)…" + npm install -g "$cli_tgz" --prefix="$PREFIX" 2>&1 | sed 's/^/ /' + + ok "Installed from source: CLI $(installed_cli_version)" +} + # ─── preflight ──────────────────────────────────────────────────────────────── require_cmd node @@ -282,25 +390,8 @@ if [[ "$FLAG_FRAMEWORK" == "true" ]]; then warn "Framework not installed." fi else - # Download repo archive and extract framework - require_cmd tar - - WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/mosaic-install-XXXXXX")" - cleanup_work() { rm -rf "$WORK_DIR"; } - trap cleanup_work EXIT - - info "Downloading framework from ${GIT_REF}…" - if command -v curl &>/dev/null; then - curl -fsSL "$ARCHIVE_URL" | tar xz -C "$WORK_DIR" - elif command -v wget &>/dev/null; then - wget -qO- "$ARCHIVE_URL" | tar xz -C "$WORK_DIR" - else - fail "curl or wget required to download framework." - exit 1 - fi - - # Gitea archives extract to / inside the work dir - EXTRACTED_DIR="$(find "$WORK_DIR" -maxdepth 1 -mindepth 1 -type d | head -1)" + # Download repo archive and extract framework (shared with the dev build) + ensure_monorepo FRAMEWORK_SRC="$EXTRACTED_DIR/packages/mosaic/framework" if [[ ! -d "$FRAMEWORK_SRC" ]]; then @@ -356,7 +447,11 @@ if [[ "$FLAG_CLI" == "true" ]]; then fi CURRENT="$(installed_cli_version)" - LATEST="$(latest_cli_version)" + if [[ "$FLAG_DEV" == "true" ]]; then + LATEST="" + else + LATEST="$(latest_cli_version)" + fi if [[ -n "$CURRENT" ]]; then dim " Installed: ${CLI_PKG}@${CURRENT}" @@ -364,7 +459,9 @@ if [[ "$FLAG_CLI" == "true" ]]; then dim " Installed: (none)" fi - if [[ -n "$LATEST" ]]; then + if [[ "$FLAG_DEV" == "true" ]]; then + dim " Source: ${REPO_BASE} (ref: ${GIT_REF}, build-from-source)" + elif [[ -n "$LATEST" ]]; then dim " Latest: ${CLI_PKG}@${LATEST}" else dim " Latest: (registry unreachable)" @@ -383,6 +480,16 @@ if [[ "$FLAG_CLI" == "true" ]]; then else ok "Up to date (or ahead of registry)." fi + elif [[ "$FLAG_DEV" == "true" ]]; then + info "Dev mode — building CLI + gateway from source at ref ${GIT_REF}…" + ensure_monorepo + install_cli_from_source + + # PATH check for npm prefix + if [[ ":$PATH:" != *":$PREFIX/bin:"* ]]; then + warn "$PREFIX/bin is not on your PATH" + dim " Add to your shell rc: export PATH=\"$PREFIX/bin:\$PATH\"" + fi else if [[ -z "$LATEST" ]]; then warn "Could not reach registry at $REGISTRY — skipping npm CLI." -- 2.49.1 From 4dd9806af17de63dfaf5bf9225f5ba64010bdd61 Mon Sep 17 00:00:00 2001 From: Jarvis Date: Wed, 24 Jun 2026 16:56:48 -0500 Subject: [PATCH 2/2] fix(installer): clearer pnpm/corepack errors + dev-aware --check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review follow-ups on the --dev feature: - Pin corepack to pnpm@10.6.2 (the repo packageManager) and surface corepack enable/prepare failures, with an actionable hard-fail when pnpm is still absent — the fresh-machine path no longer dies on a bare "command not found". - `--check --dev` now reports the installed version instead of a misleading "Could not reach registry" warning (dev mode skips the registry lookup). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01RMoEx7hfdFGjUiCHuN1RRi --- tools/install.sh | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/tools/install.sh b/tools/install.sh index 00e306b..a1c509d 100755 --- a/tools/install.sh +++ b/tools/install.sh @@ -305,13 +305,21 @@ install_cli_from_source() { local out_dir="$WORK_DIR/dist-tarballs" mkdir -p "$out_dir" - # pnpm via corepack (ships with Node >= 16.9; required by Node >= 20 preflight) + # pnpm via corepack (ships with Node >= 16.9; required by Node >= 20 preflight). + # Pin to the repo's packageManager version so the build matches CI. Surface + # corepack failures so the fresh-machine case gives an actionable error + # instead of a bare "command not found". if ! command -v pnpm &>/dev/null; then info "Activating pnpm via corepack…" - corepack enable >/dev/null 2>&1 || true - corepack prepare pnpm@latest --activate >/dev/null 2>&1 || true + corepack enable 2>&1 | sed 's/^/ /' || warn "corepack enable failed — pnpm may need manual install." + corepack prepare pnpm@10.6.2 --activate 2>&1 | sed 's/^/ /' \ + || warn "corepack prepare failed — pnpm may need manual install." + fi + if ! command -v pnpm &>/dev/null; then + fail "pnpm not available after corepack activation." + echo " Install pnpm manually (https://pnpm.io/installation) and re-run with --dev." + exit 1 fi - require_cmd pnpm info "Installing workspace dependencies (pnpm install)…" ( cd "$src" && pnpm install ) 2>&1 | sed 's/^/ /' @@ -469,7 +477,9 @@ if [[ "$FLAG_CLI" == "true" ]]; then echo "" if [[ "$FLAG_CHECK" == "true" ]]; then - if [[ -z "$LATEST" ]]; then + if [[ "$FLAG_DEV" == "true" ]]; then + info "Dev mode: installed version is ${CURRENT:-(none)} (no registry comparison)." + elif [[ -z "$LATEST" ]]; then warn "Could not reach registry." elif [[ -z "$CURRENT" ]]; then warn "Not installed." -- 2.49.1