236 lines
7.4 KiB
Bash
Executable File
236 lines
7.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# prep-consumer.sh — Pack @bytelyst/* packages as tarballs and rewrite
|
||
# a consumer's package.json file: refs so builds are fully self-contained.
|
||
#
|
||
# This script lives in learning_ai_common_plat and can be called by any
|
||
# consumer repo that uses file: refs to @bytelyst/* packages.
|
||
#
|
||
# Usage:
|
||
# ../learning_ai_common_plat/scripts/prep-consumer.sh <target-dir>
|
||
# ../learning_ai_common_plat/scripts/prep-consumer.sh <target-dir> --restore
|
||
#
|
||
# Prerequisites:
|
||
# cd learning_ai_common_plat && pnpm build
|
||
#
|
||
# Example:
|
||
# # From learning_voice_ai_agent:
|
||
# ../learning_ai_common_plat/scripts/prep-consumer.sh user-dashboard-web
|
||
#
|
||
# # From learning_ai_clock:
|
||
# ../learning_ai_common_plat/scripts/prep-consumer.sh web
|
||
#
|
||
# # Restore original package.json:
|
||
# ../learning_ai_common_plat/scripts/prep-consumer.sh user-dashboard-web --restore
|
||
set -euo pipefail
|
||
|
||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||
COMMON_PLAT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||
|
||
if [ $# -lt 1 ]; then
|
||
echo "Usage: $0 <target-dir> [--restore]"
|
||
echo " target-dir: path to consumer directory containing package.json"
|
||
exit 1
|
||
fi
|
||
|
||
TARGET_DIR="$1"
|
||
|
||
# Resolve relative paths
|
||
if [[ ! "$TARGET_DIR" = /* ]]; then
|
||
TARGET_DIR="$(pwd)/$TARGET_DIR"
|
||
fi
|
||
|
||
if [ ! -f "$TARGET_DIR/package.json" ]; then
|
||
echo "❌ No package.json found at $TARGET_DIR"
|
||
exit 1
|
||
fi
|
||
|
||
# ── Restore mode ──────────────────────────────────────────────
|
||
if [[ "${2:-}" == "--restore" ]]; then
|
||
BACKUP="$TARGET_DIR/package.json.bak"
|
||
if [ -f "$BACKUP" ]; then
|
||
mv "$BACKUP" "$TARGET_DIR/package.json"
|
||
rm -rf "$TARGET_DIR/.docker-deps"
|
||
echo "✅ Restored $TARGET_DIR/package.json"
|
||
else
|
||
echo "⚠️ No backup found at $BACKUP"
|
||
fi
|
||
exit 0
|
||
fi
|
||
|
||
# ── Validate packages are built ──────────────────────────────
|
||
if [ ! -d "$COMMON_PLAT/packages" ]; then
|
||
echo "❌ Cannot find packages/ in $COMMON_PLAT"
|
||
exit 1
|
||
fi
|
||
|
||
# ── Pack + rewrite ────────────────────────────────────────────
|
||
DEPS_DIR="$TARGET_DIR/.docker-deps"
|
||
PACK_SRC_DIR="$DEPS_DIR/.pack-src"
|
||
PLAN_FILE="$DEPS_DIR/.pack-plan.json"
|
||
rm -rf "$DEPS_DIR"
|
||
mkdir -p "$DEPS_DIR"
|
||
|
||
# Back up original package.json
|
||
cp "$TARGET_DIR/package.json" "$TARGET_DIR/package.json.bak"
|
||
|
||
DIRNAME="$(basename "$TARGET_DIR")"
|
||
echo "📦 Prepping $DIRNAME..."
|
||
|
||
# Find all @bytelyst/* file: refs in package.json
|
||
PKGS=$(grep -oE '"@bytelyst/[^"]+": *"file:[^"]+"' "$TARGET_DIR/package.json" | grep -oE '@bytelyst/[^"]+' || true)
|
||
|
||
if [ -z "$PKGS" ]; then
|
||
echo " ℹ️ No @bytelyst/* file: refs found — nothing to do"
|
||
rm -f "$TARGET_DIR/package.json.bak"
|
||
rm -rf "$DEPS_DIR"
|
||
exit 0
|
||
fi
|
||
|
||
node - "$TARGET_DIR/package.json" "$COMMON_PLAT" "$PLAN_FILE" <<'NODE'
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
const consumerPath = process.argv[2];
|
||
const commonPlat = process.argv[3];
|
||
const planPath = process.argv[4];
|
||
const packageRoot = path.join(commonPlat, 'packages');
|
||
const consumer = JSON.parse(fs.readFileSync(consumerPath, 'utf8'));
|
||
|
||
const directEntries = [];
|
||
for (const depType of ['dependencies', 'devDependencies']) {
|
||
for (const [name, version] of Object.entries(consumer[depType] ?? {})) {
|
||
if (name.startsWith('@bytelyst/') && typeof version === 'string' && version.startsWith('file:')) {
|
||
directEntries.push({ name, depType });
|
||
}
|
||
}
|
||
}
|
||
|
||
const queue = [...new Set(directEntries.map((entry) => entry.name))];
|
||
const closure = new Set(queue);
|
||
const versions = {};
|
||
|
||
while (queue.length > 0) {
|
||
const scopedName = queue.shift();
|
||
const shortName = scopedName.replace('@bytelyst/', '');
|
||
const pkgPath = path.join(packageRoot, shortName, 'package.json');
|
||
if (!fs.existsSync(pkgPath)) {
|
||
throw new Error(`Package ${scopedName} not found at ${pkgPath}`);
|
||
}
|
||
|
||
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
||
versions[scopedName] = pkg.version;
|
||
|
||
for (const depField of ['dependencies', 'optionalDependencies', 'peerDependencies']) {
|
||
for (const depName of Object.keys(pkg[depField] ?? {})) {
|
||
if (!depName.startsWith('@bytelyst/')) {
|
||
continue;
|
||
}
|
||
|
||
if (!closure.has(depName)) {
|
||
closure.add(depName);
|
||
queue.push(depName);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
fs.writeFileSync(
|
||
planPath,
|
||
JSON.stringify(
|
||
{
|
||
directEntries,
|
||
packages: [...closure],
|
||
versions,
|
||
},
|
||
null,
|
||
2,
|
||
) + '\n',
|
||
);
|
||
NODE
|
||
|
||
mkdir -p "$PACK_SRC_DIR"
|
||
|
||
COUNT=0
|
||
while IFS= read -r scoped_name; do
|
||
pkg_short="${scoped_name#@bytelyst/}"
|
||
PKG_DIR="$COMMON_PLAT/packages/$pkg_short"
|
||
TMP_PKG_DIR="$PACK_SRC_DIR/$pkg_short"
|
||
|
||
if [ ! -d "$PKG_DIR" ]; then
|
||
echo " ⚠️ Package $pkg_short not found in $COMMON_PLAT/packages/"
|
||
continue
|
||
fi
|
||
|
||
if [ ! -d "$PKG_DIR/dist" ]; then
|
||
echo " ⚠️ $pkg_short has no dist/ — run 'pnpm build' in common plat first"
|
||
continue
|
||
fi
|
||
|
||
rm -rf "$TMP_PKG_DIR"
|
||
cp -R "$PKG_DIR" "$TMP_PKG_DIR"
|
||
rm -rf "$TMP_PKG_DIR/node_modules"
|
||
|
||
node - "$TMP_PKG_DIR/package.json" "$PLAN_FILE" <<'NODE'
|
||
const fs = require('fs');
|
||
|
||
const packageJsonPath = process.argv[2];
|
||
const planPath = process.argv[3];
|
||
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
||
const plan = JSON.parse(fs.readFileSync(planPath, 'utf8'));
|
||
|
||
for (const depField of ['dependencies', 'optionalDependencies', 'peerDependencies']) {
|
||
for (const [depName, depVersion] of Object.entries(pkg[depField] ?? {})) {
|
||
if (!depName.startsWith('@bytelyst/')) {
|
||
continue;
|
||
}
|
||
|
||
if (typeof depVersion === 'string' && depVersion.startsWith('workspace:') && plan.versions[depName]) {
|
||
pkg[depField][depName] = plan.versions[depName];
|
||
}
|
||
}
|
||
}
|
||
|
||
fs.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2) + '\n');
|
||
NODE
|
||
|
||
TARBALL=$(cd "$TMP_PKG_DIR" && npm pack --pack-destination "$DEPS_DIR" 2>/dev/null | tail -1)
|
||
echo " 📦 $pkg_short → $TARBALL"
|
||
COUNT=$((COUNT + 1))
|
||
done < <(node -e "const fs=require('fs'); const plan=JSON.parse(fs.readFileSync(process.argv[1], 'utf8')); for (const name of plan.packages) console.log(name);" "$PLAN_FILE")
|
||
|
||
node - "$TARGET_DIR/package.json" "$PLAN_FILE" <<'NODE'
|
||
const fs = require('fs');
|
||
|
||
const consumerPath = process.argv[2];
|
||
const planPath = process.argv[3];
|
||
const consumer = JSON.parse(fs.readFileSync(consumerPath, 'utf8'));
|
||
const plan = JSON.parse(fs.readFileSync(planPath, 'utf8'));
|
||
|
||
const tarballRefFor = (scopedName) => `file:.docker-deps/${scopedName.replace('@bytelyst/', 'bytelyst-')}-${plan.versions[scopedName]}.tgz`;
|
||
|
||
for (const depType of ['dependencies', 'devDependencies']) {
|
||
for (const [name, version] of Object.entries(consumer[depType] ?? {})) {
|
||
if (name.startsWith('@bytelyst/') && typeof version === 'string' && version.startsWith('file:') && plan.versions[name]) {
|
||
consumer[depType][name] = tarballRefFor(name);
|
||
}
|
||
}
|
||
}
|
||
|
||
consumer.dependencies ??= {};
|
||
|
||
for (const scopedName of plan.packages) {
|
||
const alreadyDeclared = (consumer.dependencies && consumer.dependencies[scopedName]) || (consumer.devDependencies && consumer.devDependencies[scopedName]);
|
||
if (!alreadyDeclared) {
|
||
consumer.dependencies[scopedName] = tarballRefFor(scopedName);
|
||
}
|
||
}
|
||
|
||
fs.writeFileSync(consumerPath, JSON.stringify(consumer, null, 2) + '\n');
|
||
NODE
|
||
|
||
rm -rf "$PACK_SRC_DIR" "$PLAN_FILE"
|
||
|
||
echo " ✅ $DIRNAME ready ($COUNT tarballs in .docker-deps/)"
|
||
echo ""
|
||
echo " Restore with: $0 $1 --restore"
|