#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$ROOT_DIR" BUILD_ROOT="${STEPFORGE_BUILD_DIR:-$ROOT_DIR/build}" EXAMPLES_ROOT="${STEPFORGE_EXAMPLES_DIR:-$ROOT_DIR/examples}" ARTIFACT_DIR="$BUILD_ROOT/artifacts" REPORT_FILE="$BUILD_ROOT/build_report.md" MANIFEST_FILE="$BUILD_ROOT/artifacts_manifest.json" mkdir -p "$BUILD_ROOT" bash "$ROOT_DIR/scripts/bootstrap-offline.sh" node "$ROOT_DIR/scripts/make-sample-guide.js" --root "$EXAMPLES_ROOT" STEPFORGE_PACKAGE_DIR="$ARTIFACT_DIR" bash "$ROOT_DIR/scripts/package-linux.sh" >/dev/null BUILD_ROOT="$BUILD_ROOT" \ ARTIFACT_DIR="$ARTIFACT_DIR" \ EXAMPLES_ROOT="$EXAMPLES_ROOT" \ REPORT_FILE="$REPORT_FILE" \ MANIFEST_FILE="$MANIFEST_FILE" \ ROOT_DIR="$ROOT_DIR" \ node - <<'NODE' const fs = require('node:fs'); const path = require('node:path'); const crypto = require('node:crypto'); const buildRoot = process.env.BUILD_ROOT; const artifactDir = process.env.ARTIFACT_DIR; const examplesRoot = process.env.EXAMPLES_ROOT; const reportFile = process.env.REPORT_FILE; const manifestFile = process.env.MANIFEST_FILE; const rootDir = process.env.ROOT_DIR; function walk(dir, base = dir, out = []) { if (!fs.existsSync(dir)) return out; for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { const abs = path.join(dir, entry.name); if (entry.isDirectory()) walk(abs, base, out); else out.push(path.relative(base, abs)); } return out; } function sha256(file) { return crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex'); } const files = []; for (const rel of walk(artifactDir, artifactDir)) { const abs = path.join(artifactDir, rel); files.push({ kind: 'artifact', path: path.relative(buildRoot, abs), size: fs.statSync(abs).size, sha256: sha256(abs), }); } for (const rel of walk(examplesRoot, examplesRoot)) { if (!rel.startsWith('sample-')) continue; const abs = path.join(examplesRoot, rel); files.push({ kind: 'sample', path: path.relative(buildRoot, abs), size: fs.statSync(abs).size, sha256: sha256(abs), }); } const pkg = require(path.join(rootDir, 'package.json')); const { execSync } = require('node:child_process'); function toolAvailable(cmd) { try { execSync(`command -v ${cmd}`, { stdio: 'pipe', shell: '/bin/bash' }); return true; } catch { return false; } } const tools = { 'dpkg-deb (Linux .deb)': toolAvailable('dpkg-deb'), 'rpmbuild (Linux .rpm)': toolAvailable('rpmbuild'), 'appimagetool (Linux AppImage)': toolAvailable('appimagetool'), 'makensis (Windows installer .exe)': toolAvailable('makensis'), 'wixl / WiX (Windows .msi)': toolAvailable('wixl'), }; const toolRows = Object.entries(tools) .map(([name, ok]) => `| ${name} | ${ok ? 'available' : '**missing**'} |`) .join('\n'); const report = `# StepForge Build Report Version: ${pkg.version} Generated: ${new Date().toISOString()} Host: ${process.platform} ${process.arch} (node ${process.version}) ## Outputs - Portable tarball: ${files.find((f) => f.path.endsWith('.tar.gz'))?.path || 'not generated'} - Debian package: ${files.find((f) => f.path.endsWith('.deb'))?.path || 'not generated'} - Sample guide archive: ${files.find((f) => f.path.endsWith('sample-guide.sfgz'))?.path || 'not generated'} - Sample exports (9 formats): see examples/sample-exports/ - Full artifact list with sha256 checksums: artifacts_manifest.json ## Packaging tool availability | Tool | Status | |---|---| ${toolRows} Fallback policy: when a packaging tool is missing the build still produces the runnable app (portable tarball with launcher) plus whatever package formats the available tools allow. Windows artifacts are produced by \`npm run package:windows\` (electron-builder, portable .exe); .msi/.rpm/ AppImage require the tools listed above and are skipped on this host. ## Offline guarantee - The shipped app opens no sockets: no telemetry, update checks, license checks, cloud sync, or remote AI. See docs/SECURITY.md. - All exporters (PNG/GIF/PDF/DOCX/PPTX/ZIP) are implemented in-repo with Node built-ins; Electron is the only third-party dependency (dev-time fetch recorded in build/agent_audit.md). ## Verification - \`bash tests/run_test.sh\` runs the workflow suites (node --test), a startup smoke test of the Electron launcher, the sample-artifact pipeline, and this release build. `; fs.writeFileSync(reportFile, report); fs.writeFileSync(manifestFile, JSON.stringify({ format: 'stepforge-artifacts-manifest', version: 1, generatedAt: new Date().toISOString(), packageVersion: pkg.version, files, }, null, 2) + '\n'); NODE echo "Build artifacts written to $BUILD_ROOT"