mirror of
https://github.com/stablyai/orca.git
synced 2026-10-01 08:01:56 +00:00
Why: CLI's offline settings writes need AGENT_CATALOG_SCHEMA_VERSION but cannot import from out/main (electron-vite removes it during rebuild). Extract pure schema logic to src/shared, FS-bound backup operations to src/main. Add CLI require-resolution verification to catch MODULE_NOT_FOUND at build time instead of runtime. Fix hydration-time purge to defer incomplete repos: was deleting all local owners on remote-only hydration (incident 2026-07-19).
109 lines
3.8 KiB
JavaScript
109 lines
3.8 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'
|
|
import path from 'node:path'
|
|
import { pathToFileURL } from 'node:url'
|
|
|
|
// Why: build:electron-vite cleans out/main and re-emits only its rollup input
|
|
// entries, so a CLI import of a main module that lacks a matching entry ships
|
|
// as a MODULE_NOT_FOUND crash (1.4.150-rc.1.perf shipped a dead CLI this way).
|
|
// This walks the compiled CLI's relative require() graph against the final
|
|
// out/ tree so the break fails the build instead of the user's first command.
|
|
|
|
const RELATIVE_REQUIRE_PATTERN = /\brequire\(\s*(["'])(\.{1,2}\/[^"']+)\1\s*\)/g
|
|
|
|
function listJsFilesRecursively(dir) {
|
|
const files = []
|
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
const entryPath = path.join(dir, entry.name)
|
|
if (entry.isDirectory()) {
|
|
files.push(...listJsFilesRecursively(entryPath))
|
|
} else if (entry.isFile() && entry.name.endsWith('.js')) {
|
|
files.push(entryPath)
|
|
}
|
|
}
|
|
return files
|
|
}
|
|
|
|
/** Node CJS resolution for a relative specifier, limited to on-disk shapes tsc
|
|
* and rollup emit: exact file, `.js`/`.json` extension, or directory index. */
|
|
function resolveRelativeRequire(fromFile, specifier) {
|
|
const base = path.resolve(path.dirname(fromFile), specifier)
|
|
const candidates = [base, `${base}.js`, `${base}.json`, path.join(base, 'index.js')]
|
|
for (const candidate of candidates) {
|
|
if (existsSync(candidate) && statSync(candidate).isFile()) {
|
|
return candidate
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
/**
|
|
* Walks every relative require() reachable from out/cli and asserts each target
|
|
* resolves inside the final out/ tree. Returns the list of missing edges.
|
|
*/
|
|
export function verifyCliRequireResolution({
|
|
projectDir = path.resolve(import.meta.dirname, '..', '..')
|
|
} = {}) {
|
|
const outDir = path.join(projectDir, 'out')
|
|
const cliDir = path.join(outDir, 'cli')
|
|
if (!existsSync(cliDir)) {
|
|
return { checkedFiles: 0, missing: [], skipped: true }
|
|
}
|
|
|
|
const queue = listJsFilesRecursively(cliDir)
|
|
const visited = new Set(queue)
|
|
const missing = []
|
|
while (queue.length > 0) {
|
|
const file = queue.pop()
|
|
const source = readFileSync(file, 'utf8')
|
|
for (const match of source.matchAll(RELATIVE_REQUIRE_PATTERN)) {
|
|
const specifier = match[2]
|
|
const resolved = resolveRelativeRequire(file, specifier)
|
|
if (resolved === null) {
|
|
missing.push({ from: path.relative(projectDir, file), specifier })
|
|
continue
|
|
}
|
|
// Why: transitive edges matter — out/cli requires out/shared modules that
|
|
// may themselves require paths electron-vite's clean step removed.
|
|
if (resolved.endsWith('.js') && !visited.has(resolved)) {
|
|
visited.add(resolved)
|
|
queue.push(resolved)
|
|
}
|
|
}
|
|
}
|
|
return { checkedFiles: visited.size, missing, skipped: false }
|
|
}
|
|
|
|
function main() {
|
|
const result = verifyCliRequireResolution()
|
|
if (result.skipped) {
|
|
console.log('[cli-requires] out/cli not present; skipping (run build:cli first)')
|
|
return
|
|
}
|
|
if (result.missing.length > 0) {
|
|
console.error(
|
|
`[cli-requires] ${result.missing.length} relative require(s) in the compiled CLI graph do not resolve in out/:`
|
|
)
|
|
for (const edge of result.missing) {
|
|
console.error(` ${edge.from} -> require('${edge.specifier}')`)
|
|
}
|
|
console.error(
|
|
'[cli-requires] A CLI import of an out/main module needs a matching rollup input entry in electron.vite.config.ts, or the module belongs in src/shared.'
|
|
)
|
|
process.exit(1)
|
|
}
|
|
console.log(
|
|
`[cli-requires] verified ${result.checkedFiles} compiled module(s), all requires resolve`
|
|
)
|
|
}
|
|
|
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
try {
|
|
main()
|
|
} catch (error) {
|
|
console.error(error instanceof Error ? error.message : String(error))
|
|
process.exit(1)
|
|
}
|
|
}
|