#!/usr/bin/env node const { spawnSync } = require("node:child_process"); const { copyFileSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync, } = require("node:fs"); const { dirname, join, resolve } = require("node:path"); const root = resolve(__dirname, ".."); const srcResources = join(root, "src", "resources"); const distResources = join(root, "dist", "resources"); const resourcesTsconfig = join(root, "tsconfig.resources.json"); function copyNonTsFiles(srcDir, destDir) { 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.name.endsWith(".ts") || entry.name.endsWith(".tsx") || entry.name.endsWith(".js") || entry.name.endsWith(".jsx") ) { continue; } 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(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); } } rmSync(distResources, { recursive: true, force: true }); // Check if there are any .ts files to compile function hasTsFilesRecursive(dir) { for (const entry of readdirSync(dir, { withFileTypes: true })) { const fullPath = join(dir, entry.name); if (entry.isDirectory()) { if (hasTsFilesRecursive(fullPath)) return true; } else if (entry.name.endsWith(".ts") && !entry.name.endsWith(".d.ts")) { return true; } } return false; } const hasTsFiles = hasTsFilesRecursive(srcResources); if (hasTsFiles) { const tsgoBin = join( dirname(require.resolve("@typescript/native-preview/package.json")), "bin", "tsgo.js", ); const compileWithTsgo = () => spawnSync(process.execPath, [tsgoBin, "--project", resourcesTsconfig], { cwd: root, stdio: "inherit", }); const compileWithTsc = () => { const tscBin = require.resolve("typescript/bin/tsc"); return spawnSync( process.execPath, [tscBin, "--project", resourcesTsconfig], { cwd: root, stdio: "inherit", }, ); }; let compile = compileWithTsgo(); if (compile.status !== 0) { process.exit(compile.status ?? 1); } // Native tsgo has reported successful emits while leaving .ts resources copied // without their .js runtime output. Verify a known TS-only resource before the // launcher tries to import it, and fall back to local tsc if needed. const requiredJs = join( distResources, "extensions", "sf", "model-registry.js", ); try { readFileSync(requiredJs); } catch { console.warn( "[copy-resources] tsgo did not emit model-registry.js; retrying resources build with tsc", ); compile = compileWithTsc(); if (compile.status !== 0) { process.exit(compile.status ?? 1); } } } else { // No .ts files — just create the dist/resources directory and copy .js files mkdirSync(distResources, { recursive: true }); } copyNonTsFiles(srcResources, distResources); // Also copy .js files since they're not compiled from .ts function copyJsFiles(srcDir, destDir) { for (const entry of readdirSync(srcDir, { withFileTypes: true })) { const srcPath = join(srcDir, entry.name); const destPath = join(destDir, entry.name); if (entry.isDirectory()) { copyJsFiles(srcPath, destPath); continue; } if (entry.name.endsWith(".js")) { mkdirSync(dirname(destPath), { recursive: true }); copyFileSync(srcPath, destPath); } } } copyJsFiles(srcResources, distResources); writeFileSync( join(distResources, ".sf-resource-build-stamp"), `${new Date().toISOString()}\n`, );