mirror of
https://github.com/stablyai/orca.git
synced 2026-09-30 08:03:12 +00:00
* refactor(host): resolve the app root through the port in fork-reachable modules
`parcel-watcher-entry-path.ts` and `session-scanner-service-entry-path.ts` read the
app root via `require('electron').app` inside a try/catch that already returns null
when Electron is absent. They were therefore correct under plain Node at runtime and
only failed the *static* text check — which is real, not pedantic: the comment in
`ports/port-scan-command-client.ts:19` records that the plain-node-entry-guard fails
on that literal text, try/catch or not.
`hasAppEnvironment() ? getAppEnvironment() : null` gives the identical "no app root
here" answer without the text. That restores `hasAppEnvironment`, which an earlier
commit in this stack deleted as unused — it now has the caller it was waiting for.
Ratchet baseline 27 → 25.
Verified: 74 files / 458 tests; `pnpm typecheck` clean; `oxlint` clean.
* feat(orcad): boot the Orca runtime on plain Node
Closes the last two Electron couplings and makes `orcad` a working artifact:
a 4.43 MB Node bundle that boots, pairs, registers a repo, creates a real git
worktree and round-trips a PTY — with zero `require("electron")`.
Ratchet 2 -> 0, so `config/runtime-electron-baseline.txt` is now empty and its
test asserts exactly that: any reachable electron import is a regression.
- speech: inject the service factories, so importing ModelManager for its type
no longer drags Electron's streaming net.request into the graph
- filesystem-watcher: add a WorktreeWatcherRemoval port. Every entry in those
maps arrives through an ipcMain handler carrying a renderer sender, so a host
with no renderer has nothing to close, restore or forget — the inert default
is what the desktop code does against empty maps, not a stub hiding work
- user-data-path / profile-storage-paths: resolve userData through
AppEnvironment. These surfaced only once orcad pulled the store in
Both host ports now anchor to a realm-global symbol. `vi.resetModules()` gives
the re-imported graph a fresh module copy, so a binding installed before the
reset silently read back as uninstalled.
The acceptance smoke drives both hosts through one code path (`--target
orcad|electron`) and seeds its own git repo, so it is hermetic and asserts the
same contract of each. Wired into PR CI.
* test(smoke): remove the seeded workspace container, not just the worktree
* test(smoke): surface the server's stderr when it dies before ready
* fix(smoke): build node-pty for Node before booting orcad in CI
* fix(smoke): drive the CLI built from this checkout, not one on PATH
* docs(ratchet): say the baseline must stay empty, not merely shrink
* build(orcad): externalize only the native modules actually in the graph
97 lines
3.8 KiB
JavaScript
97 lines
3.8 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Bundle `orcad` — the Orca runtime served from plain Node, no Electron.
|
|
*
|
|
* Variant B (see docs/design/node-only-runtime-backend.html): the browser-pane and
|
|
* speech clusters are excluded. That is not a size optimisation — those modules are
|
|
* the only ones that statically import `node:sqlite`, so dropping them is what keeps
|
|
* the host Node floor at 18 instead of 22.5+.
|
|
*/
|
|
import { build } from 'esbuild'
|
|
import { mkdirSync, rmSync } from 'node:fs'
|
|
import { join } from 'node:path'
|
|
import process from 'node:process'
|
|
|
|
const ROOT = join(import.meta.dirname, '..', '..')
|
|
const OUT_DIR = join(ROOT, 'out', 'orcad')
|
|
const ENTRY = join(ROOT, 'src/main/orcad/main.ts')
|
|
|
|
// Native addons must exist on the host; they cannot be bundled.
|
|
// `electron` is external so a residual import fails loudly at require() time rather
|
|
// than silently bundling the npm package's installer shim, which is what happened the
|
|
// first time and made the bundle look clean while it was not.
|
|
// Why only these: measured, not guessed. `node-pty` is a hard `require.resolve` — orcad
|
|
// exits at startup without it. `@parcel/watcher` is a guarded dynamic import, so the
|
|
// server boots without it but every watch install fails. `fsevents` is macOS-only and
|
|
// optional upstream. better-sqlite3 / keytar / cpu-features were externalized here
|
|
// defensively and appear nowhere in the graph; listing them implied a shipping burden
|
|
// that does not exist.
|
|
const EXTERNAL = ['electron', 'node-pty', '@parcel/watcher', 'fsevents']
|
|
|
|
/** Why: the UMD build's relative dynamic requires do not bundle. Same fix build-relay.mjs uses. */
|
|
const jsoncParserEsm = {
|
|
name: 'jsonc-parser-esm',
|
|
setup(pluginBuild) {
|
|
pluginBuild.onResolve({ filter: /^jsonc-parser$/ }, () => ({
|
|
path: join(ROOT, 'node_modules', 'jsonc-parser', 'lib', 'esm', 'main.js')
|
|
}))
|
|
}
|
|
}
|
|
|
|
/** Why: optional native deps reference prebuilt .node files that may not exist here. */
|
|
const externalNativeAddons = {
|
|
name: 'external-native-addons',
|
|
setup(pluginBuild) {
|
|
pluginBuild.onResolve({ filter: /\.node$/ }, (args) => ({ path: args.path, external: true }))
|
|
}
|
|
}
|
|
|
|
rmSync(OUT_DIR, { recursive: true, force: true })
|
|
mkdirSync(OUT_DIR, { recursive: true })
|
|
|
|
const result = await build({
|
|
entryPoints: [ENTRY],
|
|
bundle: true,
|
|
platform: 'node',
|
|
target: 'node18',
|
|
format: 'cjs',
|
|
outfile: join(OUT_DIR, 'orcad.js'),
|
|
external: EXTERNAL,
|
|
plugins: [jsoncParserEsm, externalNativeAddons],
|
|
metafile: true,
|
|
minify: true,
|
|
sourcemap: false,
|
|
define: { 'process.env.NODE_ENV': '"production"' },
|
|
logLevel: 'error'
|
|
})
|
|
|
|
const output = Object.values(result.metafile.outputs).find((o) => o.entryPoint)
|
|
// Why check `original` and not just `path`: when electron is bundleable, esbuild
|
|
// rewrites `path` to the resolved file under node_modules and the naive check passes
|
|
// while the package is very much in the bundle.
|
|
const electronImporters = new Set()
|
|
for (const [file, info] of Object.entries(result.metafile.inputs)) {
|
|
for (const imported of info.imports ?? []) {
|
|
const specifier = imported.original ?? imported.path
|
|
if (specifier === 'electron' || specifier.startsWith('electron/')) {
|
|
electronImporters.add(file)
|
|
}
|
|
}
|
|
}
|
|
|
|
if (electronImporters.size > 0) {
|
|
console.error(
|
|
`[build-orcad] ${electronImporters.size} module(s) in the bundle import electron:
|
|
${[...electronImporters].map((f) => ` - ${f}`).join('\n')}`
|
|
)
|
|
// Why this can exceed the ratchet baseline: the ratchet measures the graph reachable
|
|
// from orca-runtime + runtime-rpc, but this entry also imports ipc/pty directly to
|
|
// install the PTY controller. Once orcad ships, it should become a ratchet entry
|
|
// point so the two numbers cannot drift.
|
|
process.exitCode = 1
|
|
} else {
|
|
console.log(
|
|
`[build-orcad] ok — ${(output.bytes / 1024 / 1024).toFixed(2)} MB, ${Object.keys(output.inputs).length} modules, zero electron imports.`
|
|
)
|
|
}
|