singularity-forge/scripts/build-web-if-stale.cjs
ace-pm 172753c3b2 refactor(forge): complete gsd → forge rebrand across native, logging, and build system
- Rename native Rust crates: gsd-engine → forge-engine, gsd-ast → forge-ast, gsd-grep → forge-grep
- Update all crate dependencies (Cargo.toml, .rs source) and N-API artifacts
- Mass rename log prefix [gsd] → [forge] across 81 files (scripts, src/, extensions, tests)
- Rename log prefix "gsd-db:" → "forge-db:" in template literals
- Update nix flake: add sf-run-native devShell with Rust toolchain for native addon builds
- Update CI workflow artifact names (build-native.yml)
- Verify only packages/native/* touched (no upstream pi-* packages renamed)

Rationale: Complete gsd-2 → singularity-forge rebrand (2026-04-15). Native addon is
sf-run-specific; all gsd-prefixed logging and crate names must align with new identity.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 14:11:45 +02:00

110 lines
3.1 KiB
JavaScript

#!/usr/bin/env node
/**
* Rebuild the Next.js web host only when web source files are newer than the
* staged standalone build. Skips the build when nothing has changed.
*
* Also self-heals a missing/incomplete web dependency install so `npm run gsd:web`
* doesn't fail with bare `next` command-not-found errors.
*
* Exit codes:
* 0 — build was up-to-date or successfully rebuilt
* 1 — build failed
*/
'use strict'
const { execSync } = require('node:child_process')
const { existsSync, readdirSync, statSync } = require('node:fs')
const { join, resolve } = require('node:path')
// Skip on Windows — Next.js webpack build hits EPERM scanning system dirs
if (process.platform === 'win32') {
console.log('[forge] Web build skipped on Windows.')
process.exit(0)
}
const root = resolve(__dirname, '..')
const webRoot = join(root, 'web')
// Also watch src/ because api routes import directly from src/web/* and src/resources/*
const srcRoot = join(root, 'src')
const stagedSentinel = join(root, 'dist', 'web', 'standalone', 'server.js')
// Directories inside web/ that are not source and should be ignored for
// staleness comparison.
const IGNORED_DIRS = new Set(['node_modules', '.next', '.turbo', 'dist', 'out', '.cache'])
/**
* Walk a directory tree, yield the mtime of every file, skipping ignored dirs.
* Returns the maximum mtime found (ms since epoch), or 0 if nothing found.
*/
function newestMtime(dir) {
let max = 0
let stack = [dir]
while (stack.length > 0) {
const current = stack.pop()
let entries
try {
entries = readdirSync(current, { withFileTypes: true })
} catch {
continue
}
for (const entry of entries) {
if (entry.isDirectory()) {
if (!IGNORED_DIRS.has(entry.name)) {
stack.push(join(current, entry.name))
}
continue
}
try {
const mt = statSync(join(current, entry.name)).mtimeMs
if (mt > max) max = mt
} catch {
// skip unreadable files
}
}
}
return max
}
function sentinelMtime() {
try {
return statSync(stagedSentinel).mtimeMs
} catch {
return 0
}
}
function hasWebBuildDependencies() {
return existsSync(join(webRoot, 'node_modules', '.bin', 'next'))
}
function ensureWebBuildDependencies() {
if (hasWebBuildDependencies()) {
return
}
console.log('[forge] Web build dependencies are missing or incomplete — running npm --prefix web ci...')
execSync('npm --prefix web ci', { cwd: root, stdio: 'inherit' })
}
const sourceMtime = Math.max(newestMtime(webRoot), newestMtime(srcRoot))
const builtMtime = sentinelMtime()
if (builtMtime > 0 && builtMtime >= sourceMtime) {
console.log('[forge] Web build is up-to-date, skipping rebuild.')
process.exit(0)
}
if (builtMtime === 0) {
console.log('[forge] No staged web build found — building now...')
} else {
console.log('[forge] Web/src source has changed since last build — rebuilding...')
}
try {
ensureWebBuildDependencies()
execSync('npm run build:web-host', { cwd: root, stdio: 'inherit' })
} catch (err) {
console.error('[forge] Web build failed:', err.message)
process.exit(1)
}