OverviewFilesCommitsIssuesDocsReleasesPeers

godot-keet / test-ci-deps.sh

228 lines · bash ref:
#!/bin/bash
# test-ci-deps.sh — Validates CI dependency resolution locally
#
# Simulates the exact CI steps in a temp directory to prove all imports resolve.
# Run BEFORE pushing CI changes to catch dependency issues.
#
# Usage: bash test-ci-deps.sh

set -e

GODOT_KEET="/storage/projects/code/godot/godot-keet"
KEET_CLI="/storage/projects/code/hypercore-kb/hyper-fleeky/keet-cli"
KEET_DAEMON="/storage/projects/code/hypercore-kb/hyper-fleeky/packages/keet-daemon"

# Use a temp directory to simulate clean CI environment
WORKDIR=$(mktemp -d)
trap "rm -rf $WORKDIR" EXIT

echo "=== CI Dep Resolution Test ==="
echo "Work dir: $WORKDIR"
echo ""

# Step 1: Copy package files (simulating git checkout)
echo "--- Step 1: Copy package files ---"
cp "$GODOT_KEET/package.json" "$WORKDIR/"
cp "$GODOT_KEET/package-lock.json" "$WORKDIR/"
# Daemon bridge file
mkdir -p "$WORKDIR/daemon"
cp "$GODOT_KEET/daemon/daemon.js" "$WORKDIR/daemon/"
# Main entry
cp "$GODOT_KEET/index.js" "$WORKDIR/"
echo "OK"

# Step 2: npm install --production (same as CI)
echo ""
echo "--- Step 2: npm install --production ---"
cd "$WORKDIR"
# Override preinstall script since keet-cli may not be at same path in CI
# The CI yaml handles @holepunchto copy separately
npm install --production --ignore-scripts 2>&1 | tail -5
MODULE_COUNT=$(ls node_modules/ 2>/dev/null | wc -l)
echo "Installed $MODULE_COUNT modules"

# Step 3: Overlay keet-cli deps (rsync --ignore-existing preserves npm-resolved versions)
echo ""
echo "--- Step 3: Overlay keet-cli deps (rsync --ignore-existing) ---"
PRE_COUNT=$(ls node_modules/ | wc -l)
rsync -a --ignore-existing "$KEET_CLI/node_modules/" node_modules/
POST_COUNT=$(ls node_modules/ | wc -l)
ADDED=$((POST_COUNT - PRE_COUNT))
echo "Added $ADDED packages from keet-cli (total: $POST_COUNT)"

# Overwrite keet-daemon with our modified version (rsync may have copied keet-cli's)
rm -rf node_modules/keet-daemon
cp -r "$KEET_DAEMON" node_modules/keet-daemon
echo "Copied keet-daemon (overwrite)"

# Step 4: Patch pear-runtime + clean bare-sidecar (same as CI)
echo ""
echo "--- Step 4: Patch pear-runtime (lazy-load bare-sidecar) ---"
sed -i "s/const Sidecar = require('bare-sidecar')/let Sidecar = null/" node_modules/pear-runtime/index.js
sed -i "/return new Sidecar/i\\    if (!Sidecar) Sidecar = require('bare-sidecar')" node_modules/pear-runtime/index.js
rm -rf node_modules/bare-sidecar node_modules/pear-runtime/node_modules/bare-sidecar
# Verify patch applied
if grep -q "let Sidecar = null" node_modules/pear-runtime/index.js; then
  echo "OK — pear-runtime patched (lazy-load bare-sidecar)"
else
  echo "FAIL — patch did not apply"
  ERRORS=$((ERRORS + 1))
fi

# Step 5: Validate all imports resolve
echo ""
echo "--- Step 5: Validate imports ---"
ERRORS=0

check_module() {
  local mod="$1"
  local desc="$2"
  if [ -d "node_modules/$mod" ] || [ -f "node_modules/$mod" ] || [ -d "node_modules/$mod.js" ]; then
    echo "  OK: $mod ($desc)"
  else
    # Check if it's nested (e.g. in pear-runtime/node_modules/)
    if find node_modules -path "*/$mod/package.json" -print -quit 2>/dev/null | grep -q .; then
      echo "  OK: $mod ($desc) [nested]"
    else
      echo "  FAIL: $mod ($desc)"
      ERRORS=$((ERRORS + 1))
    fi
  fi
}

echo "Checking godot-keet direct imports:"
check_module "bare-subprocess" "index.js"
check_module "bare-pipe" "index.js"
check_module "pear-appdrive" "index.js (legacy path)"
check_module "pear-runtime" "index.js (OTA)"

echo ""
echo "Checking keet-daemon imports:"
check_module "keet-daemon" "daemon.js"
check_module "@holepunchto/keet-core-hyperdb" "keet-daemon/index.js"

echo ""
echo "Checking keet-daemon lib imports:"
check_module "framed-stream" "uds-transport.js"
check_module "b4a" "uds-transport.js"
check_module "hypercore-crypto" "topic-announce.js"
check_module "bare-fs" "identity-registry.js"
check_module "bare-path" "identity-registry.js"
check_module "bare-os" "identity-registry.js"

echo ""
echo "Checking pear-runtime transitive deps:"
check_module "corestore" "pear-runtime → corestore"
check_module "hyperdrive" "pear-runtime → hyperdrive"
check_module "hyperswarm" "pear-runtime → hyperswarm"
check_module "hypercore" "corestore → hypercore"
check_module "hypercore-storage" "hypercore → hypercore-storage"
check_module "rocksdb-native" "hypercore-storage → rocksdb-native"

echo ""
echo "Checking @holepunchto packages:"
HOLEPUNCH_COUNT=$(ls node_modules/@holepunchto/ 2>/dev/null | wc -l)
echo "  $HOLEPUNCH_COUNT @holepunchto packages present"

echo ""
echo "Checking keet-core transitive deps (unscoped private):"
check_module "keet-identity-key" "keet-core-hyperdb dep"
check_module "bip39-mnemonic" "keet-daemon/router.js dep"
check_module "blind-pairing-core" "keet-core dep"
check_module "autobase" "keet-core dep"

echo ""
echo "Checking NO stale deps (should NOT exist):"
if [ -d "node_modules/random-access-file" ]; then
  echo "  WARN: random-access-file exists (stale, no longer needed)"
else
  echo "  OK: random-access-file absent (expected)"
fi
if [ -d "node_modules/bare-sidecar" ]; then
  echo "  FAIL: bare-sidecar exists (should be cleaned)"
  ERRORS=$((ERRORS + 1))
else
  echo "  OK: bare-sidecar absent (expected)"
fi

# Step 6: Try resolving the actual import chain with bare
echo ""
echo "--- Step 6: Smoke test with bare ---"
cat > "$WORKDIR/test-ci-imports.mjs" << 'TESTEOF'
// Smoke test: verify all top-level imports resolve
const checks = []

async function check(name, fn) {
  try {
    await fn()
    checks.push({ name, ok: true })
  } catch (err) {
    checks.push({ name, ok: false, err: err.message })
  }
}

await check('bare-pipe', () => import('bare-pipe'))
await check('bare-subprocess', () => import('bare-subprocess'))
await check('framed-stream', () => import('framed-stream'))
await check('b4a', () => import('b4a'))
await check('bare-fs', () => import('bare-fs'))
await check('bare-path', () => import('bare-path'))
await check('bare-os', () => import('bare-os'))
await check('hypercore-crypto', () => import('hypercore-crypto'))
// pear-appdrive only works under pear run (guarded by isPear in index.js)
await check('pear-appdrive [expected fail: pear-only]', async () => {
  try { await import('pear-appdrive') } catch (e) {
    if (e.message.includes('pear-ref')) { console.log('    (expected: pear-only module)'); return }
    throw e
  }
})

// pear-runtime: bare-sidecar lazy-loaded (patched in CI), so this should now succeed
await check('pear-runtime', () => import('pear-runtime'))

// keet-daemon imports keet-core-hyperdb
await check('@holepunchto/keet-core-hyperdb', () => import('@holepunchto/keet-core-hyperdb'))

// keet-daemon full import
await check('keet-daemon', () => import('keet-daemon'))

let failed = 0
for (const c of checks) {
  if (c.ok) {
    console.log(`  OK: ${c.name}`)
  } else {
    console.log(`  FAIL: ${c.name} — ${c.err}`)
    failed++
  }
}

if (failed > 0) {
  console.log(`\n${failed} import(s) FAILED`)
  Bare.exit(1)
} else {
  console.log(`\nAll ${checks.length} imports OK`)
}
TESTEOF

cd "$WORKDIR"
if command -v bare &>/dev/null; then
  cd "$WORKDIR" && bare "$WORKDIR/test-ci-imports.mjs" 2>&1 || ERRORS=$((ERRORS + 1))
else
  echo "  SKIP: bare not available (install bare-runtime for full validation)"
fi

# Summary
echo ""
echo "=== RESULT ==="
TOTAL_MODULES=$(ls node_modules/ 2>/dev/null | wc -l)
echo "Total modules: $TOTAL_MODULES"
echo "Public (npm): $MODULE_COUNT"
echo "@holepunchto (private): $HOLEPUNCH_COUNT"
if [ $ERRORS -eq 0 ]; then
  echo "STATUS: PASS — all deps resolved"
  exit 0
else
  echo "STATUS: FAIL — $ERRORS error(s)"
  exit 1
fi