Automate source resource rebuild for SF

This commit is contained in:
Mikael Hugo 2026-04-30 09:35:59 +02:00
parent 6ccce42c62
commit 50975c19e0
4 changed files with 214 additions and 65 deletions

View file

@ -30,10 +30,14 @@ set -euo pipefail
SCRIPT_DIR=$(cd -- "$(dirname -- "$(readlink -f "${BASH_SOURCE[0]}")")" &>/dev/null && pwd)
SF_SOURCE_ROOT=$(cd -- "$SCRIPT_DIR/.." &>/dev/null && pwd)
NODE_BIN=${SF_NODE_BIN:-node}
export SF_BIN_PATH="$SCRIPT_DIR/sf-from-source"
export SF_CLI_PATH="${SF_CLI_PATH:-$SCRIPT_DIR/sf-from-source}"
exec node \
"$NODE_BIN" "$SF_SOURCE_ROOT/scripts/ensure-source-resources.cjs"
exec "$NODE_BIN" \
--import "$SF_SOURCE_ROOT/src/resources/extensions/sf/tests/resolve-ts.mjs" \
--experimental-strip-types \
--no-warnings \

View file

@ -1,53 +1,70 @@
#!/usr/bin/env node
const { spawnSync } = require('child_process');
const { copyFileSync, mkdirSync, readdirSync, rmSync } = require('fs');
const { dirname, join } = require('path');
const { spawnSync } = require("node:child_process");
const {
copyFileSync,
mkdirSync,
readFileSync,
readdirSync,
rmSync,
writeFileSync,
} = require("node:fs");
const { dirname, join } = require("node:path");
function copyNonTsFiles(srcDir, destDir) {
for (const entry of readdirSync(srcDir, { withFileTypes: true })) {
const srcPath = join(srcDir, entry.name);
const destPath = join(destDir, entry.name);
for (const entry of readdirSync(srcDir, { withFileTypes: true })) {
const srcPath = join(srcDir, entry.name);
const destPath = join(destDir, entry.name);
if (entry.isDirectory()) {
copyNonTsFiles(srcPath, destPath);
continue;
}
if (entry.isDirectory()) {
copyNonTsFiles(srcPath, destPath);
continue;
}
if (entry.name.endsWith('.ts') || entry.name.endsWith('.tsx')) {
continue;
}
if (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) {
continue;
}
mkdirSync(dirname(destPath), { recursive: true });
mkdirSync(dirname(destPath), { recursive: true });
// Rewrite pi.extensions paths from .ts to .js in package.json files
// so they match the compiled output (tsc compiles index.ts → index.js
// but package.json is copied as-is).
if (entry.name === 'package.json') {
try {
const pkg = JSON.parse(require('fs').readFileSync(srcPath, 'utf-8'));
if (Array.isArray(pkg?.pi?.extensions)) {
pkg.pi.extensions = pkg.pi.extensions.map(ext =>
ext.replace(/\.ts$/, '.js').replace(/\.tsx$/, '.js')
);
require('fs').writeFileSync(destPath, JSON.stringify(pkg, null, 2) + '\n');
continue;
}
} catch { /* fall through to plain copy */ }
}
// Rewrite pi.extensions paths from .ts to .js in package.json files
// so they match the compiled output (tsc compiles index.ts → index.js
// but package.json is copied as-is).
if (entry.name === "package.json") {
try {
const pkg = JSON.parse(readFileSync(srcPath, "utf-8"));
if (Array.isArray(pkg?.pi?.extensions)) {
pkg.pi.extensions = pkg.pi.extensions.map((ext) =>
ext.replace(/\.ts$/, ".js").replace(/\.tsx$/, ".js"),
);
writeFileSync(destPath, JSON.stringify(pkg, null, 2) + "\n");
continue;
}
} catch {
/* fall through to plain copy */
}
}
copyFileSync(srcPath, destPath);
}
copyFileSync(srcPath, destPath);
}
}
rmSync('dist/resources', { recursive: true, force: true });
rmSync("dist/resources", { recursive: true, force: true });
const tscBin = require.resolve('typescript/bin/tsc');
const compile = spawnSync(process.execPath, [tscBin, '--project', 'tsconfig.resources.json'], {
stdio: 'inherit',
});
const tscBin = require.resolve("typescript/bin/tsc");
const compile = spawnSync(
process.execPath,
[tscBin, "--project", "tsconfig.resources.json"],
{
stdio: "inherit",
},
);
if (compile.status !== 0) {
process.exit(compile.status ?? 1);
process.exit(compile.status ?? 1);
}
copyNonTsFiles('src/resources', 'dist/resources');
copyNonTsFiles("src/resources", "dist/resources");
writeFileSync(
join("dist", "resources", ".sf-resource-build-stamp"),
`${new Date().toISOString()}\n`,
);

View file

@ -1,33 +1,69 @@
#!/usr/bin/env node
import { spawn } from 'node:child_process'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { spawn, spawnSync } from "node:child_process";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url))
const root = resolve(__dirname, '..')
const srcLoaderPath = resolve(root, 'src', 'loader.ts')
const resolveTsPath = resolve(root, 'src', 'resources', 'extensions', 'sf', 'tests', 'resolve-ts.mjs')
const __dirname = dirname(fileURLToPath(import.meta.url));
const root = resolve(__dirname, "..");
const sourceBinPath = resolve(root, "bin", "sf-from-source");
const ensureResourcesPath = resolve(
root,
"scripts",
"ensure-source-resources.cjs",
);
const srcLoaderPath = resolve(root, "src", "loader.ts");
const resolveTsPath = resolve(
root,
"src",
"resources",
"extensions",
"sf",
"tests",
"resolve-ts.mjs",
);
const resourceBuild = spawnSync(process.execPath, [ensureResourcesPath], {
cwd: root,
stdio: "inherit",
env: process.env,
});
if (resourceBuild.status !== 0) {
process.exit(resourceBuild.status ?? 1);
}
const child = spawn(
process.execPath,
['--import', resolveTsPath, '--experimental-strip-types', srcLoaderPath, ...process.argv.slice(2)],
{
cwd: process.cwd(),
stdio: 'inherit',
env: process.env,
},
)
process.execPath,
[
"--import",
resolveTsPath,
"--experimental-strip-types",
srcLoaderPath,
...process.argv.slice(2),
],
{
cwd: process.cwd(),
stdio: "inherit",
env: {
...process.env,
SF_BIN_PATH: process.env.SF_BIN_PATH || sourceBinPath,
SF_CLI_PATH: process.env.SF_CLI_PATH || sourceBinPath,
},
},
);
child.on('error', (error) => {
console.error(`[forge] Failed to launch local dev CLI: ${error instanceof Error ? error.message : String(error)}`)
process.exit(1)
})
child.on("error", (error) => {
console.error(
`[forge] Failed to launch local dev CLI: ${error instanceof Error ? error.message : String(error)}`,
);
process.exit(1);
});
child.on('exit', (code, signal) => {
if (signal) {
process.kill(process.pid, signal)
return
}
process.exit(code ?? 0)
})
child.on("exit", (code, signal) => {
if (signal) {
process.kill(process.pid, signal);
return;
}
process.exit(code ?? 0);
});

View file

@ -0,0 +1,92 @@
#!/usr/bin/env node
const { spawnSync } = require("node:child_process");
const { existsSync, readdirSync, statSync } = require("node:fs");
const { join, resolve } = require("node:path");
const root = resolve(__dirname, "..");
const srcResources = join(root, "src", "resources");
const distResources = join(root, "dist", "resources");
const stampPath = join(distResources, ".sf-resource-build-stamp");
const copyResourcesScript = join(root, "scripts", "copy-resources.cjs");
function latestMtimeMs(path) {
let latest = 0;
const stack = [path];
while (stack.length > 0) {
const current = stack.pop();
if (!current) continue;
let entries;
try {
entries = readdirSync(current, { withFileTypes: true });
} catch {
continue;
}
for (const entry of entries) {
const entryPath = join(current, entry.name);
let stat;
try {
stat = statSync(entryPath);
} catch {
continue;
}
latest = Math.max(latest, stat.mtimeMs);
if (entry.isDirectory()) {
stack.push(entryPath);
}
}
}
return latest;
}
function sourceInputsMtimeMs() {
return Math.max(
latestMtimeMs(srcResources),
existsSync(copyResourcesScript) ? statSync(copyResourcesScript).mtimeMs : 0,
existsSync(join(root, "tsconfig.resources.json"))
? statSync(join(root, "tsconfig.resources.json")).mtimeMs
: 0,
);
}
function hasCompleteResourceBuild() {
return (
existsSync(stampPath) &&
existsSync(join(distResources, "SF-WORKFLOW.md")) &&
existsSync(join(distResources, "agents")) &&
existsSync(join(distResources, "extensions"))
);
}
function shouldRebuild() {
if (process.env.SF_DEV_CLI_SKIP_RESOURCE_BUILD === "1") return false;
if (process.env.SF_SKIP_SOURCE_RESOURCE_BUILD === "1") return false;
if (!hasCompleteResourceBuild()) return true;
let stampMtime = 0;
try {
stampMtime = statSync(stampPath).mtimeMs;
} catch {
return true;
}
return sourceInputsMtimeMs() > stampMtime;
}
if (shouldRebuild()) {
console.error(
"[forge] Source resources changed; rebuilding dist/resources before launch...",
);
const result = spawnSync(process.execPath, [copyResourcesScript], {
cwd: root,
stdio: "inherit",
env: process.env,
});
if (result.status !== 0) {
process.exit(result.status ?? 1);
}
}