Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions config/scripts/build-orcad.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ import { tmpdir } from 'node:os'
import { dirname, join, resolve } from 'node:path'
import process from 'node:process'
import { smokeProfileStateWorkers } from './profile-state-worker-smoke.mjs'
import { smokeForeignSqliteReaderWorker } from './foreign-sqlite-reader-worker-smoke.mjs'
import { materializeWatcherPackage } from './orcad-watcher-package.mjs'
import { stageOrcadWindowsProcessTree } from './orcad-windows-process-tree.mjs'
import {
ORCAD_EMOJI_SHORTCODE_DATASET,
ORCAD_FOREIGN_SQLITE_READER_ENTRY,
ORCAD_NODE_PTY_DIR,
ORCAD_NODE_PTY_JS_ARTIFACTS,
ORCAD_NODE_RUNTIME_MARKER_FILENAME,
Expand Down Expand Up @@ -56,6 +58,10 @@ const WATCHER_OUT_FILE = join(OUT_DIR, 'parcel-watcher-process-entry.js')
// orcad restart would SIGKILL every running terminal.
const DAEMON_ENTRY = join(ROOT, ORCAD_CHILD_ENTRY_POINTS.daemon)
const DAEMON_OUT_FILE = join(OUT_DIR, 'daemon-entry.js')
// Why beside orcad.js: the hook server's OpenCode binder and the OpenCode history scanner
// start this worker from the module dir, since orcad has no Electron resources tree.
const FOREIGN_SQLITE_READER_ENTRY = join(ROOT, ORCAD_CHILD_ENTRY_POINTS.foreignSqliteReader)
const FOREIGN_SQLITE_READER_OUT_FILE = join(OUT_DIR, ORCAD_FOREIGN_SQLITE_READER_ENTRY)
const OUT_FILE = join(OUT_DIR, 'orcad.js')
const BUILD_TARGET = process.env.ORCAD_BUILD_TARGET
if (!BUILD_TARGET) {
Expand Down Expand Up @@ -205,6 +211,7 @@ function buildForkedChild(entryPoint, outfile) {
const childResults = await Promise.all([
buildForkedChild(WATCHER_ENTRY, WATCHER_OUT_FILE),
buildForkedChild(DAEMON_ENTRY, DAEMON_OUT_FILE),
buildForkedChild(FOREIGN_SQLITE_READER_ENTRY, FOREIGN_SQLITE_READER_OUT_FILE),
...['writer', 'backup'].map((role) =>
buildForkedChild(
join(ROOT, ORCAD_CHILD_ENTRY_POINTS[role]),
Expand Down Expand Up @@ -340,6 +347,16 @@ try {
process.exitCode = 1
}

try {
smokeForeignSqliteReaderWorker(OUT_DIR)
if (nodeRuntimePath) {
smokeForeignSqliteReaderWorker(OUT_DIR, { runtimePath: nodeRuntimePath })
}
} catch (error) {
console.error('[build-orcad] foreign SQLite reader worker check failed:', error)
process.exitCode = 1
}

// Why a content hash and not ORCAD_VERSION alone: the remote install directory is keyed on
// this string, so two different builds carrying one version would share a directory — and an
// already-`.install-complete` dir is never re-uploaded. The deploy would silently run stale
Expand Down
80 changes: 80 additions & 0 deletions config/scripts/foreign-sqlite-reader-worker-smoke.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join, resolve } from 'node:path'
import { describeProcessFailure, runProcessSync } from './script-child-process.mjs'
import { ORCAD_FOREIGN_SQLITE_READER_ENTRY } from '../../src/shared/orcad-artifacts.ts'

// Why a child process: the read must run under the runtime orcad ships, which may not be
// the Node running the build. The verdict is the exit code, never matched output.
const PROBE = `
const { Worker } = require('node:worker_threads')
const { DatabaseSync } = process.getBuiltinModule('node:sqlite')
const [entry, dbPath, missingPath] = process.argv.slice(2)
const db = new DatabaseSync(dbPath)
db.exec('CREATE TABLE session (id TEXT PRIMARY KEY, directory TEXT NOT NULL, time_created INTEGER NOT NULL, parent_id TEXT)')
db.prepare('INSERT INTO session VALUES (?, ?, ?, ?)').run('ses_smoke', '/smoke', 100, null)
db.close()
const steps = [
{
request: { id: 1, kind: 'openCodeBinderSessions', dbPath, cursor: { ms: 0, id: '' } },
expected: [{ id: 'ses_smoke', directory: '/smoke', createdAtMs: 100, parentId: null }]
},
{ request: { id: 2, kind: 'cursorProfile', dbPath: missingPath }, expected: { status: 'missing' } },
// The OpenCode history scanner's kinds share this entry.
{ request: { id: 3, kind: 'list', dbPaths: [], limit: null }, expected: { candidates: [], issues: [] } }
]
const worker = new Worker(entry, { execArgv: [] })
const fail = (code, message) => {
console.error(message)
process.exit(code)
}
setTimeout(() => fail(3, 'foreign SQLite reader worker did not answer'), 20000).unref()
worker.on('error', (error) => fail(4, String(error && error.stack || error)))
worker.on('exit', (code) => fail(5, 'foreign SQLite reader worker exited with ' + code))
let step = 0
worker.on('message', (response) => {
const { request, expected } = steps[step]
if (!response || response.id !== request.id || response.ok !== true ||
JSON.stringify(response.value) !== JSON.stringify(expected)) {
fail(6, request.kind + ' answered ' + JSON.stringify(response))
}
step += 1
if (step === steps.length) {
process.exit(0)
}
worker.postMessage(steps[step].request)
})
worker.postMessage(steps[0].request)
`

/**
* Load the built foreign SQLite reader entry and run real reads through it.
* @param outDir - orcad output directory holding the entry.
* @param options.runtimePath - Node to run under; the build's own Node when omitted.
*/
export function smokeForeignSqliteReaderWorker(outDir, { runtimePath, timeoutMs = 30_000 } = {}) {
const directory = mkdtempSync(join(tmpdir(), 'orca-foreign-sqlite-smoke-'))
try {
const probe = join(directory, 'probe.cjs')
writeFileSync(probe, PROBE)
const result = runProcessSync({
program: runtimePath ?? process.execPath,
args: [
probe,
resolve(outDir, ORCAD_FOREIGN_SQLITE_READER_ENTRY),
join(directory, 'opencode.db'),
join(directory, 'missing.vscdb')
],
env: { ...process.env, ORCA_BACKGROUND_LAUNCH: '1' },
timeoutMs,
maxOutputBytes: 64 * 1024
})
if (result.code !== 0 || result.timedOut) {
throw new Error(
`Foreign SQLite reader worker smoke failed: ${describeProcessFailure(result)}`
)
}
} finally {
rmSync(directory, { recursive: true, force: true })
}
}
65 changes: 65 additions & 0 deletions config/scripts/foreign-sqlite-reader-worker-smoke.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { build } from 'esbuild'
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join, resolve } from 'node:path'
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import { smokeForeignSqliteReaderWorker } from './foreign-sqlite-reader-worker-smoke.mjs'
import { ORCAD_CHILD_ENTRY_POINTS } from './orcad-entry-build.mjs'

const ENTRY = 'foreign-sqlite-reader-entry.js'
const directories = []
let builtDirectory

function fixtureDirectory() {
const directory = mkdtempSync(join(tmpdir(), 'orca-foreign-sqlite-smoke-test-'))
directories.push(directory)
return directory
}

beforeAll(async () => {
builtDirectory = fixtureDirectory()
await build({
entryPoints: [resolve(ORCAD_CHILD_ENTRY_POINTS.foreignSqliteReader)],
outfile: join(builtDirectory, ENTRY),
bundle: true,
platform: 'node',
target: 'node18',
format: 'cjs',
external: ['electron'],
logLevel: 'silent'
})
}, 60_000)

afterAll(() => {
for (const directory of directories) {
rmSync(directory, { recursive: true, force: true })
}
})

describe('foreign SQLite reader build smoke', () => {
it('passes against the built entry', () => {
expect(() => smokeForeignSqliteReaderWorker(builtDirectory)).not.toThrow()
})

it('fails when the entry is missing', () => {
expect(() => smokeForeignSqliteReaderWorker(fixtureDirectory())).toThrow('smoke failed')
})

it('fails when the worker answers without reading', () => {
const directory = fixtureDirectory()
writeFileSync(
join(directory, ENTRY),
`const { parentPort } = require('node:worker_threads')
parentPort.on('message', ({ id }) => parentPort.postMessage({ id, ok: true, value: [] }))`
)
expect(() => smokeForeignSqliteReaderWorker(directory)).toThrow('smoke failed')
})

it('runs in the orcad build under both runtimes', () => {
const source = readFileSync(resolve('config/scripts/build-orcad.mjs'), 'utf8')
expect(source).toContain('smokeForeignSqliteReaderWorker(OUT_DIR)')
expect(source).toContain(
'smokeForeignSqliteReaderWorker(OUT_DIR, { runtimePath: nodeRuntimePath })'
)
})
})
3 changes: 2 additions & 1 deletion config/scripts/orcad-entry-build.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ export const ORCAD_CHILD_ENTRY_POINTS = {
watcher: 'src/main/ipc/parcel-watcher-process-entry.ts',
daemon: 'src/main/daemon/daemon-entry.ts',
writer: 'src/main/persistence/profile-state/profile-state-writer-worker-entry.ts',
backup: 'src/main/persistence/profile-state/profile-state-backup-worker-entry.ts'
backup: 'src/main/persistence/profile-state/profile-state-backup-worker-entry.ts',
foreignSqliteReader: 'src/main/foreign-sqlite-readers/foreign-sqlite-reader-entry.ts'
}

export const ORCAD_EXTERNAL_MODULES = ['electron', 'node-pty', '@parcel/watcher', 'fsevents']
Expand Down
Loading
Loading