#!/usr/bin/env bash # CASAN level-based release packager. # # scripts/package-release.sh core # Level 1 — always buildable # scripts/package-release.sh devkit # Level 2 — always buildable # scripts/package-release.sh platform # Level 3 — PREVIEW (dashboard only today) # scripts/package-release.sh all-in-one-demo # everything runnable + OKR demo domain # scripts/package-release.sh enterprise # Level 4 — FUTURE → fails clearly # # Contents + maturity are driven by packaging/levels.json (single source of truth). # A bundle whose level is `future` is REFUSED (no fake-complete package). A `preview` # bundle is built but clearly stamped PREVIEW/INCOMPLETE and named *-preview. set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$ROOT" MANIFEST="packaging/levels.json" VERSION="$(cat VERSION 2>/dev/null || echo 0.0.0)" DIST="$ROOT/dist" BUNDLE="${1:-}" if [[ -z "$BUNDLE" ]]; then echo "Usage: package-release.sh " >&2 exit 64 fi [[ -f "$MANIFEST" ]] || { echo "package-release: missing $MANIFEST" >&2; exit 1; } # --- read bundle definition from the manifest via python3 --------------------- read -r BUNDLE_STATUS BUILDS < <(python3 - "$MANIFEST" "$BUNDLE" <<'PY' import json, sys m = json.load(open(sys.argv[1])); b = sys.argv[2] bundles = m["bundles"] if b not in bundles: print("UNKNOWN -"); sys.exit(0) spec = bundles[b] print(spec.get("status", "unknown"), ",".join(spec.get("builds", []))) PY ) if [[ "$BUNDLE_STATUS" == "UNKNOWN" ]]; then echo "package-release: unknown bundle '$BUNDLE'. Valid: core devkit platform all-in-one-demo enterprise" >&2 exit 64 fi # --- refuse to build a fake-complete package for a not-implemented level ------- if [[ "$BUNDLE_STATUS" == "future" ]]; then cat >&2 < python3 - "$MANIFEST" "$1" <<'PY' import json, sys m = json.load(open(sys.argv[1])); lvl = sys.argv[2] # a "level" here is a levels.* key; the demo pseudo-paths are handled by the caller node = m["levels"].get(lvl) if node: for inc in node.get("includes", []): print(inc) PY } STAGE="$(mktemp -d)" PKGDIR="$STAGE/casan-$BUNDLE$SUFFIX-v$VERSION" mkdir -p "$PKGDIR" trap 'rm -rf "$STAGE"' EXIT copy_path() { # local src="$ROOT/$1" if [[ ! -e "$src" ]]; then echo " ! skip (missing): $1" >&2 return 0 fi local dest="$PKGDIR/$1" mkdir -p "$(dirname "$dest")" # copy, dropping caches/artifacts if [[ -d "$src" ]]; then rsync -a --exclude='__pycache__' --exclude='*.pyc' --exclude='.DS_Store' "$src/" "$dest/" 2>/dev/null \ || cp -R "$src/" "$dest/" else cp "$src" "$dest" fi } echo "==> building bundle '$BUNDLE' (status=$BUNDLE_STATUS) version=$VERSION" SEEN=" " # space-delimited dedup (bash 3.2 compatible — no associative arrays) seen() { case "$SEEN" in *" $1 "*) return 0;; *) SEEN="$SEEN$1 "; return 1;; esac; } IFS=',' read -ra LEVELS <<< "$BUILDS" for lvl in "${LEVELS[@]}"; do # Entries that are level keys (core/devkit/platform/enterprise) expand via the manifest; # anything else is a literal path (demo snapshot: backend, frontend, apps/okr/domain, ...). case "$lvl" in core|devkit|platform|enterprise) ;; *) seen "$lvl" && continue echo " + $lvl"; copy_path "$lvl"; continue ;; esac echo " level: $lvl" while IFS= read -r inc; do [[ -z "$inc" ]] && continue seen "$inc" && continue echo " + $inc"; copy_path "$inc" done < <(gather_includes "$lvl") done # --- bundle manifest + preview notice ---------------------------------------- { echo "CASAN release bundle" echo "bundle: $BUNDLE" echo "version: $VERSION" echo "status: $BUNDLE_STATUS" echo "built: $(date -u +%Y-%m-%dT%H:%M:%SZ)" echo "builds: $BUILDS" } > "$PKGDIR/BUNDLE-MANIFEST.txt" if [[ "$PREVIEW" == 1 ]]; then cat > "$PKGDIR/PREVIEW-INCOMPLETE.txt" < levels.$BUNDLE.implemented_components). Pending components are listed under pending_components. Do not treat this as a finished product. EOF fi # --- checksums + tar ---------------------------------------------------------- ( cd "$PKGDIR" && find . -type f ! -name SHA256SUMS -print0 | sort -z | xargs -0 shasum -a 256 > SHA256SUMS 2>/dev/null || true ) mkdir -p "$DIST" ARTIFACT="casan-$BUNDLE$SUFFIX-v$VERSION.tar.gz" tar -czf "$DIST/$ARTIFACT" -C "$STAGE" "casan-$BUNDLE$SUFFIX-v$VERSION" SIZE="$(du -h "$DIST/$ARTIFACT" | cut -f1)" ( cd "$DIST" && shasum -a 256 "$ARTIFACT" > "$ARTIFACT.sha256" ) echo "==> BUILT $DIST/$ARTIFACT ($SIZE)" [[ "$PREVIEW" == 1 ]] && echo " (PREVIEW — incomplete; see PREVIEW-INCOMPLETE.txt inside)" echo " sha256: $(cut -d' ' -f1 "$DIST/$ARTIFACT.sha256")"