Files
CASAN/scripts/package-release.sh
T

162 lines
6.0 KiB
Bash
Executable File

#!/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="${CASAN_DIST_DIR:-$ROOT/dist}"
BUNDLE="${1:-}"
if [[ -z "$BUNDLE" ]]; then
echo "Usage: package-release.sh <core|devkit|platform|all-in-one-demo|enterprise>" >&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 <<EOF
package-release: '$BUNDLE' is a FUTURE / structure-only level — refusing to build.
Level 4 (enterprise / governed console) is documented + scaffolded but NOT implemented
in this task. Some building blocks already exist inside core (RBAC, tenant isolation,
approval workflow, KMS, WORM) — see packages/casan-enterprise/README.md — but the
Governed Chat Console / Prompt Router / Model-Provider Management do not exist yet, so
no 'complete' enterprise artifact can be produced. This is intentional (no fake-complete
packages). Build 'core', 'devkit', 'platform', or 'all-in-one-demo' instead.
EOF
exit 3
fi
PREVIEW=0
SUFFIX=""
[[ "$BUNDLE_STATUS" == "preview" ]] && { PREVIEW=1; SUFFIX="-preview"; }
# --- gather include globs for the levels this bundle builds -------------------
gather_includes() { # <level>
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() { # <relpath>
local src="$ROOT/$1"
if [[ ! -e "$src" ]]; then
echo " ! skip (missing): $1" >&2
return 0
fi
local dest="$PKGDIR/$1"
mkdir -p "$(dirname "$dest")"
case "$1" in
packages/casan-harness)
python3 "$ROOT/scripts/copy-runtime.py" \
--source-root "$ROOT" --destination-root "$PKGDIR" --component harness
return
;;
packages/casan-devkit)
python3 "$ROOT/scripts/copy-runtime.py" \
--source-root "$ROOT" --destination-root "$PKGDIR" --component devkit
return
;;
esac
# 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" <<EOF
⚠️ PREVIEW / INCOMPLETE BUNDLE — NOT production-complete.
This '$BUNDLE' bundle is a PREVIEW. Only the components that exist today are included
(see packaging/levels.json -> 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")"