diff --git a/native/scripts/build.js b/native/scripts/build.js index b436871ac..f7841f717 100644 --- a/native/scripts/build.js +++ b/native/scripts/build.js @@ -98,4 +98,26 @@ const destPath = path.join(addonDir, destFilename); fs.copyFileSync(sourcePath, destPath); console.log(`Installed: ${destPath}`); + +// Also copy into the npm platform package so `require('@singularity-forge/engine-')` +// works when the package is symlinked into node_modules during local development. +if (!isDev) { + const platformPackageMap = { + "darwin-arm64": "darwin-arm64", + "darwin-x64": "darwin-x64", + "linux-x64": "linux-x64-gnu", + "linux-arm64": "linux-arm64-gnu", + "win32-x64": "win32-x64-msvc", + }; + const npmSuffix = platformPackageMap[platformTag]; + if (npmSuffix) { + const npmPackageDir = path.join(nativeRoot, "npm", npmSuffix); + const npmDest = path.join(npmPackageDir, "forge_engine.node"); + if (fs.existsSync(npmPackageDir)) { + fs.copyFileSync(sourcePath, npmDest); + console.log(`Installed npm package binary: ${npmDest}`); + } + } +} + console.log("Build complete."); diff --git a/packages/native/package.json b/packages/native/package.json index faa52c435..6805f8409 100644 --- a/packages/native/package.json +++ b/packages/native/package.json @@ -88,5 +88,12 @@ "files": [ "dist" ], + "optionalDependencies": { + "@singularity-forge/engine-darwin-arm64": ">=2.75.0", + "@singularity-forge/engine-darwin-x64": ">=2.75.0", + "@singularity-forge/engine-linux-x64-gnu": ">=2.75.0", + "@singularity-forge/engine-linux-arm64-gnu": ">=2.75.0", + "@singularity-forge/engine-win32-x64-msvc": ">=2.75.0" + }, "license": "MIT" } diff --git a/scripts/link-workspace-packages.cjs b/scripts/link-workspace-packages.cjs index e84c7eacc..fe735146e 100644 --- a/scripts/link-workspace-packages.cjs +++ b/scripts/link-workspace-packages.cjs @@ -87,3 +87,37 @@ for (const dir of packageDirs) { if (linked > 0) process.stderr.write(` Linked ${linked} workspace package${linked !== 1 ? 's' : ''}\n`) if (copied > 0) process.stderr.write(` Copied ${copied} workspace package${copied !== 1 ? 's' : ''} (symlinks unavailable)\n`) + +// Platform-specific native engine packages live under native/npm//, not packages/. +// Wire them into node_modules/@singularity-forge/ so native.ts can require() them without +// a registry install. Only link platforms where the binary (forge_engine.node) is present. +const nativeNpmDir = join(root, 'native', 'npm') +const engineSuffixes = ['darwin-arm64', 'darwin-x64', 'linux-x64-gnu', 'linux-arm64-gnu', 'win32-x64-msvc'] +for (const suffix of engineSuffixes) { + const source = join(nativeNpmDir, suffix) + const binaryPath = join(source, 'forge_engine.node') + if (!existsSync(source) || !existsSync(binaryPath)) continue + + const target = join(scopeDir, `engine-${suffix}`) + if (existsSync(target)) { + try { + const stat = lstatSync(target) + if (stat.isSymbolicLink()) { + const linkTarget = readlinkSync(target) + if (resolve(join(scopeDir, linkTarget)) === source || linkTarget === source) continue + unlinkSync(target) + } else { + continue + } + } catch { + continue + } + } + + try { + symlinkSync(source, target, 'junction') + process.stderr.write(` Linked native engine: @singularity-forge/engine-${suffix}\n`) + } catch { + try { cpSync(source, target, { recursive: true }) } catch { /* non-fatal */ } + } +} diff --git a/src/resources/extensions/sf/sf-db.ts b/src/resources/extensions/sf/sf-db.ts index 8c42ca7e7..2ead0f580 100644 --- a/src/resources/extensions/sf/sf-db.ts +++ b/src/resources/extensions/sf/sf-db.ts @@ -1860,7 +1860,7 @@ function rowToSlice(row: Record): SliceRow { title: row["title"] as string, status: row["status"] as string, risk: row["risk"] as string, - depends: JSON.parse((row["depends"] as string) || "[]"), + depends: safeParseJsonArray(row["depends"]), demo: (row["demo"] as string) ?? "", created_at: row["created_at"] as string, completed_at: (row["completed_at"] as string) ?? null, @@ -1943,6 +1943,16 @@ export interface TaskRow { verification_status?: string; } +function safeParseJsonArray(raw: unknown, fallback: T[] = []): T[] { + if (typeof raw !== "string" || raw.trim() === "") return fallback; + try { + const parsed = JSON.parse(raw); + return Array.isArray(parsed) ? (parsed as T[]) : fallback; + } catch { + return fallback; + } +} + function parseTaskArrayColumn(raw: unknown): string[] { if (typeof raw !== "string" || raw.trim() === "") return []; @@ -2112,18 +2122,18 @@ function rowToMilestone(row: Record): MilestoneRow { id: row["id"] as string, title: row["title"] as string, status: row["status"] as string, - depends_on: JSON.parse((row["depends_on"] as string) || "[]"), + depends_on: safeParseJsonArray(row["depends_on"]), created_at: row["created_at"] as string, completed_at: (row["completed_at"] as string) ?? null, vision: (row["vision"] as string) ?? "", - success_criteria: JSON.parse((row["success_criteria"] as string) || "[]"), - key_risks: JSON.parse((row["key_risks"] as string) || "[]"), - proof_strategy: JSON.parse((row["proof_strategy"] as string) || "[]"), + success_criteria: safeParseJsonArray(row["success_criteria"]), + key_risks: safeParseJsonArray<{ risk: string; whyItMatters: string }>(row["key_risks"]), + proof_strategy: safeParseJsonArray<{ riskOrUnknown: string; retireIn: string; whatWillBeProven: string }>(row["proof_strategy"]), verification_contract: (row["verification_contract"] as string) ?? "", verification_integration: (row["verification_integration"] as string) ?? "", verification_operational: (row["verification_operational"] as string) ?? "", verification_uat: (row["verification_uat"] as string) ?? "", - definition_of_done: JSON.parse((row["definition_of_done"] as string) || "[]"), + definition_of_done: safeParseJsonArray(row["definition_of_done"]), requirement_coverage: (row["requirement_coverage"] as string) ?? "", boundary_map_markdown: (row["boundary_map_markdown"] as string) ?? "", vision_meeting: parseVisionMeeting(row["vision_meeting_json"]),