@open-code-review/cli 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/dashboard/server.js +147 -27
- package/dist/index.js +244 -51
- package/dist/lib/db/index.js +186 -29
- package/package.json +3 -5
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@ Command-line interface for [Open Code Review](https://github.com/spencermarx/ope
|
|
|
4
4
|
|
|
5
5
|
## Quick Start
|
|
6
6
|
|
|
7
|
+
**Requires Node.js >= 22.5** — OCR's storage engine is Node's built-in SQLite (`node:sqlite`), so there's no native module to compile and it installs cleanly under any package manager (npm, **pnpm 10+**, yarn).
|
|
8
|
+
|
|
7
9
|
```bash
|
|
8
10
|
# 1. Install globally
|
|
9
11
|
npm install -g @open-code-review/cli
|
package/dist/dashboard/server.js
CHANGED
|
@@ -30510,37 +30510,98 @@ function resolveOcrDir(startDir) {
|
|
|
30510
30510
|
}
|
|
30511
30511
|
|
|
30512
30512
|
// ../cli/src/lib/db/index.ts
|
|
30513
|
-
import {
|
|
30513
|
+
import {
|
|
30514
|
+
existsSync as existsSync4,
|
|
30515
|
+
mkdirSync as mkdirSync2,
|
|
30516
|
+
copyFileSync,
|
|
30517
|
+
statSync,
|
|
30518
|
+
mkdtempSync,
|
|
30519
|
+
rmSync
|
|
30520
|
+
} from "node:fs";
|
|
30514
30521
|
import { dirname as dirname4, join as join4 } from "node:path";
|
|
30515
30522
|
|
|
30516
30523
|
// ../cli/src/lib/db/engine.ts
|
|
30517
|
-
import
|
|
30524
|
+
import { createRequire } from "node:module";
|
|
30525
|
+
|
|
30526
|
+
// ../cli/src/lib/runtime-checks.ts
|
|
30527
|
+
var NODE_FLOOR = { major: 22, minor: 5 };
|
|
30528
|
+
function isSupportedNode(version) {
|
|
30529
|
+
const [major = 0, minor = 0] = version.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
30530
|
+
return major > NODE_FLOOR.major || major === NODE_FLOOR.major && minor >= NODE_FLOOR.minor;
|
|
30531
|
+
}
|
|
30532
|
+
function nodeVersionGuardMessage(version) {
|
|
30533
|
+
return `
|
|
30534
|
+
Open Code Review requires Node.js >= ${NODE_FLOOR.major}.${NODE_FLOOR.minor} (it uses Node's built-in SQLite, \`node:sqlite\`).
|
|
30535
|
+
You have Node ${version}. Upgrade Node (e.g. \`nvm install 22 && nvm use 22\`) and re-run.
|
|
30536
|
+
|
|
30537
|
+
`;
|
|
30538
|
+
}
|
|
30539
|
+
function isSuppressibleSqliteWarning(warning) {
|
|
30540
|
+
const message = typeof warning === "string" ? warning : warning?.message;
|
|
30541
|
+
return typeof message === "string" && message.includes("SQLite is an experimental feature");
|
|
30542
|
+
}
|
|
30543
|
+
|
|
30544
|
+
// ../cli/src/lib/db/engine.ts
|
|
30545
|
+
var SQLITE_BUSY = 5;
|
|
30546
|
+
var SQLITE_BUSY_SNAPSHOT = 261;
|
|
30518
30547
|
var BUSY_RETRY_ATTEMPTS = 5;
|
|
30519
30548
|
var BUSY_RETRY_BACKOFF_MS = 50;
|
|
30520
|
-
|
|
30521
|
-
|
|
30522
|
-
|
|
30549
|
+
var savepointName = (depth) => `ocr_sp_${depth}`;
|
|
30550
|
+
var nodeRequire = createRequire(import.meta.url);
|
|
30551
|
+
var _preconditionsApplied = false;
|
|
30552
|
+
function applyEnginePreconditions() {
|
|
30553
|
+
if (_preconditionsApplied) return;
|
|
30554
|
+
_preconditionsApplied = true;
|
|
30555
|
+
const originalEmitWarning = process.emitWarning.bind(process);
|
|
30556
|
+
process.emitWarning = (warning, ...args) => {
|
|
30557
|
+
if (isSuppressibleSqliteWarning(warning)) return;
|
|
30558
|
+
originalEmitWarning(warning, ...args);
|
|
30559
|
+
};
|
|
30560
|
+
}
|
|
30561
|
+
var _DatabaseSyncCtor;
|
|
30562
|
+
function newDatabase(path2) {
|
|
30563
|
+
if (!_DatabaseSyncCtor) {
|
|
30564
|
+
applyEnginePreconditions();
|
|
30565
|
+
try {
|
|
30566
|
+
_DatabaseSyncCtor = nodeRequire("node:sqlite").DatabaseSync;
|
|
30567
|
+
} catch (e) {
|
|
30568
|
+
if (!isSupportedNode(process.versions.node)) {
|
|
30569
|
+
throw new Error(nodeVersionGuardMessage(process.versions.node).trim());
|
|
30570
|
+
}
|
|
30571
|
+
throw e;
|
|
30572
|
+
}
|
|
30523
30573
|
}
|
|
30524
|
-
|
|
30525
|
-
return code === "SQLITE_BUSY" || code === "SQLITE_BUSY_SNAPSHOT";
|
|
30574
|
+
return new _DatabaseSyncCtor(path2);
|
|
30526
30575
|
}
|
|
30576
|
+
function isBusyError(e) {
|
|
30577
|
+
const errcode = e?.errcode;
|
|
30578
|
+
return errcode === SQLITE_BUSY || errcode === SQLITE_BUSY_SNAPSHOT;
|
|
30579
|
+
}
|
|
30580
|
+
var SLEEP_BUF = new Int32Array(new SharedArrayBuffer(4));
|
|
30527
30581
|
function sleepSync(ms) {
|
|
30528
|
-
Atomics.wait(
|
|
30582
|
+
Atomics.wait(SLEEP_BUF, 0, 0, ms);
|
|
30529
30583
|
}
|
|
30530
|
-
var
|
|
30584
|
+
var NodeSqliteAdapter = class {
|
|
30531
30585
|
raw;
|
|
30586
|
+
/**
|
|
30587
|
+
* Transaction nesting depth. `node:sqlite` has no transaction helper, so we
|
|
30588
|
+
* drive `BEGIN IMMEDIATE` ourselves and use SAVEPOINTs for nested calls
|
|
30589
|
+
* (better-sqlite3 did this automatically). 0 = no transaction open.
|
|
30590
|
+
*/
|
|
30591
|
+
txnDepth = 0;
|
|
30532
30592
|
constructor(db) {
|
|
30533
30593
|
this.raw = db;
|
|
30534
30594
|
}
|
|
30535
30595
|
exec(sql, params) {
|
|
30536
30596
|
const stmt = this.raw.prepare(sql);
|
|
30537
|
-
|
|
30597
|
+
const cols = stmt.columns();
|
|
30598
|
+
if (cols.length === 0) {
|
|
30538
30599
|
stmt.run(...params ?? []);
|
|
30539
30600
|
return [];
|
|
30540
30601
|
}
|
|
30541
|
-
|
|
30542
|
-
const values = stmt.
|
|
30543
|
-
return values.length > 0 ? [{ columns, values }] : [];
|
|
30602
|
+
stmt.setReturnArrays(true);
|
|
30603
|
+
const values = stmt.all(...params ?? []);
|
|
30604
|
+
return values.length > 0 ? [{ columns: cols.map((c) => c.name), values }] : [];
|
|
30544
30605
|
}
|
|
30545
30606
|
run(sql, params) {
|
|
30546
30607
|
if (params !== void 0) {
|
|
@@ -30553,34 +30614,93 @@ var BetterSqliteAdapter = class {
|
|
|
30553
30614
|
return this.raw.prepare(sql);
|
|
30554
30615
|
}
|
|
30555
30616
|
transaction(fn) {
|
|
30556
|
-
|
|
30557
|
-
|
|
30617
|
+
return this.txnDepth > 0 ? this.runNested(fn) : this.runOuter(fn);
|
|
30618
|
+
}
|
|
30619
|
+
/**
|
|
30620
|
+
* Nested call: a SAVEPOINT within the outer transaction's write lock. No
|
|
30621
|
+
* busy-retry — the outer transaction already holds the lock. The savepoint
|
|
30622
|
+
* lets the inner block roll back independently while the outer continues.
|
|
30623
|
+
*/
|
|
30624
|
+
runNested(fn) {
|
|
30625
|
+
const name = savepointName(this.txnDepth);
|
|
30626
|
+
this.raw.exec(`SAVEPOINT ${name}`);
|
|
30627
|
+
this.txnDepth++;
|
|
30628
|
+
try {
|
|
30629
|
+
const result = fn();
|
|
30630
|
+
this.raw.exec(`RELEASE ${name}`);
|
|
30631
|
+
return result;
|
|
30632
|
+
} catch (e) {
|
|
30633
|
+
try {
|
|
30634
|
+
this.raw.exec(`ROLLBACK TO ${name}`);
|
|
30635
|
+
this.raw.exec(`RELEASE ${name}`);
|
|
30636
|
+
} catch {
|
|
30637
|
+
}
|
|
30638
|
+
throw e;
|
|
30639
|
+
} finally {
|
|
30640
|
+
this.txnDepth--;
|
|
30641
|
+
}
|
|
30642
|
+
}
|
|
30643
|
+
/**
|
|
30644
|
+
* Outer transaction: `BEGIN IMMEDIATE` acquires the write lock up front so
|
|
30645
|
+
* cross-process writers serialize cleanly under WAL instead of failing late
|
|
30646
|
+
* on upgrade. `busy_timeout` covers most contention; a bounded synchronous
|
|
30647
|
+
* retry absorbs the residual SQLITE_BUSY (another connection holds the lock
|
|
30648
|
+
* past the timeout, or BUSY_SNAPSHOT). Non-busy errors and the final attempt
|
|
30649
|
+
* re-throw so genuine failures propagate.
|
|
30650
|
+
*/
|
|
30651
|
+
runOuter(fn) {
|
|
30652
|
+
for (let attempt = 0; attempt < BUSY_RETRY_ATTEMPTS; attempt++) {
|
|
30558
30653
|
try {
|
|
30559
|
-
return
|
|
30654
|
+
return this.runOnce(fn);
|
|
30560
30655
|
} catch (e) {
|
|
30561
|
-
if (!isBusyError(e) || attempt
|
|
30656
|
+
if (!isBusyError(e) || attempt === BUSY_RETRY_ATTEMPTS - 1) throw e;
|
|
30562
30657
|
sleepSync(BUSY_RETRY_BACKOFF_MS);
|
|
30563
30658
|
}
|
|
30564
30659
|
}
|
|
30660
|
+
throw new Error("transaction retry budget exhausted");
|
|
30661
|
+
}
|
|
30662
|
+
/** One `BEGIN IMMEDIATE` / `COMMIT` / `ROLLBACK` lifecycle. */
|
|
30663
|
+
runOnce(fn) {
|
|
30664
|
+
this.raw.exec("BEGIN IMMEDIATE");
|
|
30665
|
+
this.txnDepth = 1;
|
|
30666
|
+
try {
|
|
30667
|
+
const result = fn();
|
|
30668
|
+
this.raw.exec("COMMIT");
|
|
30669
|
+
return result;
|
|
30670
|
+
} catch (e) {
|
|
30671
|
+
try {
|
|
30672
|
+
this.raw.exec("ROLLBACK");
|
|
30673
|
+
} catch {
|
|
30674
|
+
}
|
|
30675
|
+
throw e;
|
|
30676
|
+
} finally {
|
|
30677
|
+
this.txnDepth = 0;
|
|
30678
|
+
}
|
|
30565
30679
|
}
|
|
30566
30680
|
pragma(source) {
|
|
30567
|
-
|
|
30681
|
+
this.raw.exec(`PRAGMA ${source}`);
|
|
30682
|
+
return void 0;
|
|
30568
30683
|
}
|
|
30569
30684
|
close() {
|
|
30570
30685
|
try {
|
|
30571
|
-
this.raw.
|
|
30686
|
+
this.raw.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
30572
30687
|
} catch {
|
|
30573
30688
|
}
|
|
30574
|
-
|
|
30689
|
+
try {
|
|
30690
|
+
this.raw.close();
|
|
30691
|
+
} catch (e) {
|
|
30692
|
+
const message = e?.message ?? "";
|
|
30693
|
+
if (!/database is not open/i.test(message)) throw e;
|
|
30694
|
+
}
|
|
30575
30695
|
}
|
|
30576
30696
|
};
|
|
30577
30697
|
function openEngine(dbPath) {
|
|
30578
|
-
const native =
|
|
30579
|
-
native.
|
|
30580
|
-
native.
|
|
30581
|
-
native.
|
|
30582
|
-
native.
|
|
30583
|
-
return new
|
|
30698
|
+
const native = newDatabase(dbPath);
|
|
30699
|
+
native.exec("PRAGMA journal_mode = WAL");
|
|
30700
|
+
native.exec("PRAGMA foreign_keys = ON");
|
|
30701
|
+
native.exec("PRAGMA busy_timeout = 5000");
|
|
30702
|
+
native.exec("PRAGMA synchronous = NORMAL");
|
|
30703
|
+
return new NodeSqliteAdapter(native);
|
|
30584
30704
|
}
|
|
30585
30705
|
|
|
30586
30706
|
// ../cli/src/lib/db/migrations.ts
|
|
@@ -31760,7 +31880,7 @@ function walCheckpointTruncate(dbPath) {
|
|
|
31760
31880
|
return "failed";
|
|
31761
31881
|
} finally {
|
|
31762
31882
|
try {
|
|
31763
|
-
transient?.
|
|
31883
|
+
transient?.close();
|
|
31764
31884
|
} catch {
|
|
31765
31885
|
}
|
|
31766
31886
|
}
|
package/dist/index.js
CHANGED
|
@@ -39,6 +39,30 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
39
39
|
mod
|
|
40
40
|
));
|
|
41
41
|
|
|
42
|
+
// src/lib/runtime-checks.ts
|
|
43
|
+
function isSupportedNode(version) {
|
|
44
|
+
const [major = 0, minor = 0] = version.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
45
|
+
return major > NODE_FLOOR.major || major === NODE_FLOOR.major && minor >= NODE_FLOOR.minor;
|
|
46
|
+
}
|
|
47
|
+
function nodeVersionGuardMessage(version) {
|
|
48
|
+
return `
|
|
49
|
+
Open Code Review requires Node.js >= ${NODE_FLOOR.major}.${NODE_FLOOR.minor} (it uses Node's built-in SQLite, \`node:sqlite\`).
|
|
50
|
+
You have Node ${version}. Upgrade Node (e.g. \`nvm install 22 && nvm use 22\`) and re-run.
|
|
51
|
+
|
|
52
|
+
`;
|
|
53
|
+
}
|
|
54
|
+
function isSuppressibleSqliteWarning(warning) {
|
|
55
|
+
const message = typeof warning === "string" ? warning : warning?.message;
|
|
56
|
+
return typeof message === "string" && message.includes("SQLite is an experimental feature");
|
|
57
|
+
}
|
|
58
|
+
var NODE_FLOOR;
|
|
59
|
+
var init_runtime_checks = __esm({
|
|
60
|
+
"src/lib/runtime-checks.ts"() {
|
|
61
|
+
"use strict";
|
|
62
|
+
NODE_FLOOR = { major: 22, minor: 5 };
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
42
66
|
// ../../node_modules/.pnpm/commander@13.1.0/node_modules/commander/lib/error.js
|
|
43
67
|
var require_error = __commonJS({
|
|
44
68
|
"../../node_modules/.pnpm/commander@13.1.0/node_modules/commander/lib/error.js"(exports) {
|
|
@@ -23320,21 +23344,41 @@ var init_result_mapper = __esm({
|
|
|
23320
23344
|
});
|
|
23321
23345
|
|
|
23322
23346
|
// src/lib/db/engine.ts
|
|
23323
|
-
import
|
|
23324
|
-
function
|
|
23325
|
-
if (
|
|
23326
|
-
|
|
23347
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
23348
|
+
function applyEnginePreconditions() {
|
|
23349
|
+
if (_preconditionsApplied) return;
|
|
23350
|
+
_preconditionsApplied = true;
|
|
23351
|
+
const originalEmitWarning = process.emitWarning.bind(process);
|
|
23352
|
+
process.emitWarning = (warning, ...args) => {
|
|
23353
|
+
if (isSuppressibleSqliteWarning(warning)) return;
|
|
23354
|
+
originalEmitWarning(warning, ...args);
|
|
23355
|
+
};
|
|
23356
|
+
}
|
|
23357
|
+
function newDatabase(path2) {
|
|
23358
|
+
if (!_DatabaseSyncCtor) {
|
|
23359
|
+
applyEnginePreconditions();
|
|
23360
|
+
try {
|
|
23361
|
+
_DatabaseSyncCtor = nodeRequire("node:sqlite").DatabaseSync;
|
|
23362
|
+
} catch (e) {
|
|
23363
|
+
if (!isSupportedNode(process.versions.node)) {
|
|
23364
|
+
throw new Error(nodeVersionGuardMessage(process.versions.node).trim());
|
|
23365
|
+
}
|
|
23366
|
+
throw e;
|
|
23367
|
+
}
|
|
23327
23368
|
}
|
|
23328
|
-
|
|
23329
|
-
|
|
23369
|
+
return new _DatabaseSyncCtor(path2);
|
|
23370
|
+
}
|
|
23371
|
+
function isBusyError(e) {
|
|
23372
|
+
const errcode = e?.errcode;
|
|
23373
|
+
return errcode === SQLITE_BUSY || errcode === SQLITE_BUSY_SNAPSHOT;
|
|
23330
23374
|
}
|
|
23331
23375
|
function sleepSync(ms) {
|
|
23332
|
-
Atomics.wait(
|
|
23376
|
+
Atomics.wait(SLEEP_BUF, 0, 0, ms);
|
|
23333
23377
|
}
|
|
23334
23378
|
function probeEngine() {
|
|
23335
23379
|
try {
|
|
23336
|
-
const db =
|
|
23337
|
-
db.
|
|
23380
|
+
const db = newDatabase(":memory:");
|
|
23381
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
23338
23382
|
db.exec("CREATE TABLE _probe(x); INSERT INTO _probe VALUES (1);");
|
|
23339
23383
|
const row = db.prepare("SELECT sqlite_version() AS v").get();
|
|
23340
23384
|
db.close();
|
|
@@ -23344,33 +23388,47 @@ function probeEngine() {
|
|
|
23344
23388
|
}
|
|
23345
23389
|
}
|
|
23346
23390
|
function openEngine(dbPath) {
|
|
23347
|
-
const native =
|
|
23348
|
-
native.
|
|
23349
|
-
native.
|
|
23350
|
-
native.
|
|
23351
|
-
native.
|
|
23352
|
-
return new
|
|
23353
|
-
}
|
|
23354
|
-
var BUSY_RETRY_ATTEMPTS, BUSY_RETRY_BACKOFF_MS,
|
|
23391
|
+
const native = newDatabase(dbPath);
|
|
23392
|
+
native.exec("PRAGMA journal_mode = WAL");
|
|
23393
|
+
native.exec("PRAGMA foreign_keys = ON");
|
|
23394
|
+
native.exec("PRAGMA busy_timeout = 5000");
|
|
23395
|
+
native.exec("PRAGMA synchronous = NORMAL");
|
|
23396
|
+
return new NodeSqliteAdapter(native);
|
|
23397
|
+
}
|
|
23398
|
+
var SQLITE_BUSY, SQLITE_BUSY_SNAPSHOT, BUSY_RETRY_ATTEMPTS, BUSY_RETRY_BACKOFF_MS, savepointName, nodeRequire, _preconditionsApplied, _DatabaseSyncCtor, SLEEP_BUF, NodeSqliteAdapter;
|
|
23355
23399
|
var init_engine = __esm({
|
|
23356
23400
|
"src/lib/db/engine.ts"() {
|
|
23357
23401
|
"use strict";
|
|
23402
|
+
init_runtime_checks();
|
|
23403
|
+
SQLITE_BUSY = 5;
|
|
23404
|
+
SQLITE_BUSY_SNAPSHOT = 261;
|
|
23358
23405
|
BUSY_RETRY_ATTEMPTS = 5;
|
|
23359
23406
|
BUSY_RETRY_BACKOFF_MS = 50;
|
|
23360
|
-
|
|
23407
|
+
savepointName = (depth) => `ocr_sp_${depth}`;
|
|
23408
|
+
nodeRequire = createRequire2(import.meta.url);
|
|
23409
|
+
_preconditionsApplied = false;
|
|
23410
|
+
SLEEP_BUF = new Int32Array(new SharedArrayBuffer(4));
|
|
23411
|
+
NodeSqliteAdapter = class {
|
|
23361
23412
|
raw;
|
|
23413
|
+
/**
|
|
23414
|
+
* Transaction nesting depth. `node:sqlite` has no transaction helper, so we
|
|
23415
|
+
* drive `BEGIN IMMEDIATE` ourselves and use SAVEPOINTs for nested calls
|
|
23416
|
+
* (better-sqlite3 did this automatically). 0 = no transaction open.
|
|
23417
|
+
*/
|
|
23418
|
+
txnDepth = 0;
|
|
23362
23419
|
constructor(db) {
|
|
23363
23420
|
this.raw = db;
|
|
23364
23421
|
}
|
|
23365
23422
|
exec(sql, params) {
|
|
23366
23423
|
const stmt = this.raw.prepare(sql);
|
|
23367
|
-
|
|
23424
|
+
const cols = stmt.columns();
|
|
23425
|
+
if (cols.length === 0) {
|
|
23368
23426
|
stmt.run(...params ?? []);
|
|
23369
23427
|
return [];
|
|
23370
23428
|
}
|
|
23371
|
-
|
|
23372
|
-
const values = stmt.
|
|
23373
|
-
return values.length > 0 ? [{ columns, values }] : [];
|
|
23429
|
+
stmt.setReturnArrays(true);
|
|
23430
|
+
const values = stmt.all(...params ?? []);
|
|
23431
|
+
return values.length > 0 ? [{ columns: cols.map((c) => c.name), values }] : [];
|
|
23374
23432
|
}
|
|
23375
23433
|
run(sql, params) {
|
|
23376
23434
|
if (params !== void 0) {
|
|
@@ -23383,25 +23441,84 @@ var init_engine = __esm({
|
|
|
23383
23441
|
return this.raw.prepare(sql);
|
|
23384
23442
|
}
|
|
23385
23443
|
transaction(fn) {
|
|
23386
|
-
|
|
23387
|
-
|
|
23444
|
+
return this.txnDepth > 0 ? this.runNested(fn) : this.runOuter(fn);
|
|
23445
|
+
}
|
|
23446
|
+
/**
|
|
23447
|
+
* Nested call: a SAVEPOINT within the outer transaction's write lock. No
|
|
23448
|
+
* busy-retry — the outer transaction already holds the lock. The savepoint
|
|
23449
|
+
* lets the inner block roll back independently while the outer continues.
|
|
23450
|
+
*/
|
|
23451
|
+
runNested(fn) {
|
|
23452
|
+
const name = savepointName(this.txnDepth);
|
|
23453
|
+
this.raw.exec(`SAVEPOINT ${name}`);
|
|
23454
|
+
this.txnDepth++;
|
|
23455
|
+
try {
|
|
23456
|
+
const result = fn();
|
|
23457
|
+
this.raw.exec(`RELEASE ${name}`);
|
|
23458
|
+
return result;
|
|
23459
|
+
} catch (e) {
|
|
23388
23460
|
try {
|
|
23389
|
-
|
|
23461
|
+
this.raw.exec(`ROLLBACK TO ${name}`);
|
|
23462
|
+
this.raw.exec(`RELEASE ${name}`);
|
|
23463
|
+
} catch {
|
|
23464
|
+
}
|
|
23465
|
+
throw e;
|
|
23466
|
+
} finally {
|
|
23467
|
+
this.txnDepth--;
|
|
23468
|
+
}
|
|
23469
|
+
}
|
|
23470
|
+
/**
|
|
23471
|
+
* Outer transaction: `BEGIN IMMEDIATE` acquires the write lock up front so
|
|
23472
|
+
* cross-process writers serialize cleanly under WAL instead of failing late
|
|
23473
|
+
* on upgrade. `busy_timeout` covers most contention; a bounded synchronous
|
|
23474
|
+
* retry absorbs the residual SQLITE_BUSY (another connection holds the lock
|
|
23475
|
+
* past the timeout, or BUSY_SNAPSHOT). Non-busy errors and the final attempt
|
|
23476
|
+
* re-throw so genuine failures propagate.
|
|
23477
|
+
*/
|
|
23478
|
+
runOuter(fn) {
|
|
23479
|
+
for (let attempt = 0; attempt < BUSY_RETRY_ATTEMPTS; attempt++) {
|
|
23480
|
+
try {
|
|
23481
|
+
return this.runOnce(fn);
|
|
23390
23482
|
} catch (e) {
|
|
23391
|
-
if (!isBusyError(e) || attempt
|
|
23483
|
+
if (!isBusyError(e) || attempt === BUSY_RETRY_ATTEMPTS - 1) throw e;
|
|
23392
23484
|
sleepSync(BUSY_RETRY_BACKOFF_MS);
|
|
23393
23485
|
}
|
|
23394
23486
|
}
|
|
23487
|
+
throw new Error("transaction retry budget exhausted");
|
|
23488
|
+
}
|
|
23489
|
+
/** One `BEGIN IMMEDIATE` / `COMMIT` / `ROLLBACK` lifecycle. */
|
|
23490
|
+
runOnce(fn) {
|
|
23491
|
+
this.raw.exec("BEGIN IMMEDIATE");
|
|
23492
|
+
this.txnDepth = 1;
|
|
23493
|
+
try {
|
|
23494
|
+
const result = fn();
|
|
23495
|
+
this.raw.exec("COMMIT");
|
|
23496
|
+
return result;
|
|
23497
|
+
} catch (e) {
|
|
23498
|
+
try {
|
|
23499
|
+
this.raw.exec("ROLLBACK");
|
|
23500
|
+
} catch {
|
|
23501
|
+
}
|
|
23502
|
+
throw e;
|
|
23503
|
+
} finally {
|
|
23504
|
+
this.txnDepth = 0;
|
|
23505
|
+
}
|
|
23395
23506
|
}
|
|
23396
23507
|
pragma(source) {
|
|
23397
|
-
|
|
23508
|
+
this.raw.exec(`PRAGMA ${source}`);
|
|
23509
|
+
return void 0;
|
|
23398
23510
|
}
|
|
23399
23511
|
close() {
|
|
23400
23512
|
try {
|
|
23401
|
-
this.raw.
|
|
23513
|
+
this.raw.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
23402
23514
|
} catch {
|
|
23403
23515
|
}
|
|
23404
|
-
|
|
23516
|
+
try {
|
|
23517
|
+
this.raw.close();
|
|
23518
|
+
} catch (e) {
|
|
23519
|
+
const message = e?.message ?? "";
|
|
23520
|
+
if (!/database is not open/i.test(message)) throw e;
|
|
23521
|
+
}
|
|
23405
23522
|
}
|
|
23406
23523
|
};
|
|
23407
23524
|
}
|
|
@@ -24764,6 +24881,7 @@ __export(db_exports, {
|
|
|
24764
24881
|
listAgentSessionsForWorkflow: () => listAgentSessionsForWorkflow,
|
|
24765
24882
|
openDatabase: () => openDatabase,
|
|
24766
24883
|
probeEngine: () => probeEngine,
|
|
24884
|
+
probeWrite: () => probeWrite,
|
|
24767
24885
|
readCommandLog: () => readCommandLog,
|
|
24768
24886
|
reconcileLegacyState: () => reconcileLegacyState,
|
|
24769
24887
|
recordVendorSessionIdForExecution: () => recordVendorSessionIdForExecution,
|
|
@@ -24781,7 +24899,15 @@ __export(db_exports, {
|
|
|
24781
24899
|
updateSession: () => updateSession,
|
|
24782
24900
|
walCheckpointTruncate: () => walCheckpointTruncate
|
|
24783
24901
|
});
|
|
24784
|
-
import {
|
|
24902
|
+
import {
|
|
24903
|
+
existsSync as existsSync12,
|
|
24904
|
+
mkdirSync as mkdirSync4,
|
|
24905
|
+
copyFileSync,
|
|
24906
|
+
statSync,
|
|
24907
|
+
mkdtempSync,
|
|
24908
|
+
rmSync
|
|
24909
|
+
} from "node:fs";
|
|
24910
|
+
import { tmpdir } from "node:os";
|
|
24785
24911
|
import { dirname as dirname6, join as join14 } from "node:path";
|
|
24786
24912
|
function maybeSnapshotBeforeUpgrade(db, dbPath, fromVersion) {
|
|
24787
24913
|
if (fromVersion < 1 || fromVersion >= V2_SCHEMA_VERSION) return null;
|
|
@@ -24889,7 +25015,7 @@ function walCheckpointTruncate(dbPath) {
|
|
|
24889
25015
|
return "failed";
|
|
24890
25016
|
} finally {
|
|
24891
25017
|
try {
|
|
24892
|
-
transient?.
|
|
25018
|
+
transient?.close();
|
|
24893
25019
|
} catch {
|
|
24894
25020
|
}
|
|
24895
25021
|
}
|
|
@@ -24907,6 +25033,41 @@ function closeAllDatabases() {
|
|
|
24907
25033
|
connections.delete(path2);
|
|
24908
25034
|
}
|
|
24909
25035
|
}
|
|
25036
|
+
function probeWrite() {
|
|
25037
|
+
let dir;
|
|
25038
|
+
try {
|
|
25039
|
+
dir = mkdtempSync(join14(tmpdir(), "ocr-probe-"));
|
|
25040
|
+
const db = openEngine(join14(dir, "probe.db"));
|
|
25041
|
+
try {
|
|
25042
|
+
db.run("CREATE TABLE _probe_write (id INTEGER PRIMARY KEY, v TEXT)");
|
|
25043
|
+
db.transaction(() => {
|
|
25044
|
+
db.run("INSERT INTO _probe_write (v) VALUES (?)", ["written-in-txn"]);
|
|
25045
|
+
});
|
|
25046
|
+
const value = db.exec("SELECT v FROM _probe_write")[0]?.values[0]?.[0];
|
|
25047
|
+
if (value !== "written-in-txn") {
|
|
25048
|
+
return { ok: false, error: `unexpected probe value: ${String(value)}` };
|
|
25049
|
+
}
|
|
25050
|
+
return { ok: true };
|
|
25051
|
+
} finally {
|
|
25052
|
+
db.close();
|
|
25053
|
+
}
|
|
25054
|
+
} catch (e) {
|
|
25055
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
25056
|
+
} finally {
|
|
25057
|
+
if (dir) rmDirBestEffort(dir);
|
|
25058
|
+
}
|
|
25059
|
+
}
|
|
25060
|
+
function rmDirBestEffort(dir) {
|
|
25061
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
25062
|
+
try {
|
|
25063
|
+
rmSync(dir, { recursive: true, force: true });
|
|
25064
|
+
return;
|
|
25065
|
+
} catch {
|
|
25066
|
+
if (attempt === 2) return;
|
|
25067
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
|
|
25068
|
+
}
|
|
25069
|
+
}
|
|
25070
|
+
}
|
|
24910
25071
|
var V2_SCHEMA_VERSION, connections;
|
|
24911
25072
|
var init_db = __esm({
|
|
24912
25073
|
"src/lib/db/index.ts"() {
|
|
@@ -24929,6 +25090,13 @@ var init_db = __esm({
|
|
|
24929
25090
|
}
|
|
24930
25091
|
});
|
|
24931
25092
|
|
|
25093
|
+
// src/lib/runtime-guard.ts
|
|
25094
|
+
init_runtime_checks();
|
|
25095
|
+
if (!isSupportedNode(process.versions.node)) {
|
|
25096
|
+
process.stderr.write(nodeVersionGuardMessage(process.versions.node));
|
|
25097
|
+
process.exit(1);
|
|
25098
|
+
}
|
|
25099
|
+
|
|
24932
25100
|
// ../../node_modules/.pnpm/commander@13.1.0/node_modules/commander/esm.mjs
|
|
24933
25101
|
var import_index = __toESM(require_commander(), 1);
|
|
24934
25102
|
var {
|
|
@@ -29333,7 +29501,7 @@ ${hint}
|
|
|
29333
29501
|
}
|
|
29334
29502
|
|
|
29335
29503
|
// src/lib/version.ts
|
|
29336
|
-
var CLI_VERSION = true ? "2.
|
|
29504
|
+
var CLI_VERSION = true ? "2.1.0" : createRequire(import.meta.url)("../../package.json").version;
|
|
29337
29505
|
|
|
29338
29506
|
// ../shared/platform/src/index.ts
|
|
29339
29507
|
import { pathToFileURL } from "node:url";
|
|
@@ -35412,8 +35580,50 @@ var dashboardCommand = new Command("dashboard").description("Start the OCR dashb
|
|
|
35412
35580
|
import { existsSync as existsSync20 } from "node:fs";
|
|
35413
35581
|
import { join as join24 } from "node:path";
|
|
35414
35582
|
init_db();
|
|
35415
|
-
|
|
35583
|
+
function printStorageEngine(probeWriteEnabled) {
|
|
35584
|
+
console.log();
|
|
35585
|
+
console.log(source_default.bold(" Storage Engine"));
|
|
35586
|
+
console.log();
|
|
35587
|
+
const engine = probeEngine();
|
|
35588
|
+
if (!engine.ok) {
|
|
35589
|
+
console.log(` ${source_default.red("\u2717")} node:sqlite unavailable`);
|
|
35590
|
+
console.log(` ${source_default.dim(engine.error)}`);
|
|
35591
|
+
console.log(
|
|
35592
|
+
` ${source_default.dim(
|
|
35593
|
+
"OCR requires Node >= 22.5 (node:sqlite). Upgrade Node, then re-run `ocr doctor`."
|
|
35594
|
+
)}`
|
|
35595
|
+
);
|
|
35596
|
+
return false;
|
|
35597
|
+
}
|
|
35598
|
+
console.log(
|
|
35599
|
+
` ${source_default.green("\u2713")} node:sqlite (SQLite ${engine.version}, WAL)`
|
|
35600
|
+
);
|
|
35601
|
+
if (probeWriteEnabled) {
|
|
35602
|
+
const write = probeWrite();
|
|
35603
|
+
if (!write.ok) {
|
|
35604
|
+
console.log(` ${source_default.red("\u2717")} write probe failed`);
|
|
35605
|
+
console.log(` ${source_default.dim(write.error)}`);
|
|
35606
|
+
return false;
|
|
35607
|
+
}
|
|
35608
|
+
console.log(
|
|
35609
|
+
` ${source_default.green("\u2713")} write probe (on-disk WAL transaction round-trip)`
|
|
35610
|
+
);
|
|
35611
|
+
}
|
|
35612
|
+
return true;
|
|
35613
|
+
}
|
|
35614
|
+
var doctorCommand = new Command("doctor").description("Check OCR installation and verify all dependencies").option(
|
|
35615
|
+
"--probe-write",
|
|
35616
|
+
"additionally exercise an on-disk WAL transaction round-trip (used by the release install gate)"
|
|
35617
|
+
).option(
|
|
35618
|
+
"--engine-only",
|
|
35619
|
+
"check ONLY the storage engine and exit on its result \u2014 skips project/tool checks (used by the release install gate, which runs from a non-initialized dir with no AI tools)"
|
|
35620
|
+
).action((options) => {
|
|
35416
35621
|
printHeader();
|
|
35622
|
+
if (options.engineOnly) {
|
|
35623
|
+
const ok = printStorageEngine(options.probeWrite ?? false);
|
|
35624
|
+
console.log();
|
|
35625
|
+
process.exit(ok ? 0 : 1);
|
|
35626
|
+
}
|
|
35417
35627
|
const targetDir = process.cwd();
|
|
35418
35628
|
let hasIssues = false;
|
|
35419
35629
|
const depResult = checkDependencies();
|
|
@@ -35450,25 +35660,8 @@ var doctorCommand = new Command("doctor").description("Check OCR installation an
|
|
|
35450
35660
|
if (!ocrStatus.valid) {
|
|
35451
35661
|
hasIssues = true;
|
|
35452
35662
|
}
|
|
35453
|
-
|
|
35454
|
-
console.log(source_default.bold(" Storage Engine"));
|
|
35455
|
-
console.log();
|
|
35456
|
-
const engine = probeEngine();
|
|
35457
|
-
if (engine.ok) {
|
|
35458
|
-
console.log(
|
|
35459
|
-
` ${source_default.green("\u2713")} better-sqlite3 (SQLite ${engine.version}, WAL)`
|
|
35460
|
-
);
|
|
35461
|
-
} else {
|
|
35663
|
+
if (!printStorageEngine(options.probeWrite ?? false)) {
|
|
35462
35664
|
hasIssues = true;
|
|
35463
|
-
console.log(
|
|
35464
|
-
` ${source_default.red("\u2717")} better-sqlite3 failed to load`
|
|
35465
|
-
);
|
|
35466
|
-
console.log(` ${source_default.dim(engine.error)}`);
|
|
35467
|
-
console.log(
|
|
35468
|
-
` ${source_default.dim(
|
|
35469
|
-
"Reinstall the CLI; if it persists your platform may need build tools (python3 + a C++ toolchain) or lacks a prebuilt binary."
|
|
35470
|
-
)}`
|
|
35471
|
-
);
|
|
35472
35665
|
}
|
|
35473
35666
|
console.log();
|
|
35474
35667
|
printCapabilities(depResult);
|
package/dist/lib/db/index.js
CHANGED
|
@@ -1,35 +1,97 @@
|
|
|
1
1
|
// src/lib/db/index.ts
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
existsSync as existsSync3,
|
|
4
|
+
mkdirSync as mkdirSync2,
|
|
5
|
+
copyFileSync,
|
|
6
|
+
statSync,
|
|
7
|
+
mkdtempSync,
|
|
8
|
+
rmSync
|
|
9
|
+
} from "node:fs";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
3
11
|
import { dirname as dirname3, join as join3 } from "node:path";
|
|
4
12
|
|
|
5
13
|
// src/lib/db/engine.ts
|
|
6
|
-
import
|
|
14
|
+
import { createRequire } from "node:module";
|
|
15
|
+
|
|
16
|
+
// src/lib/runtime-checks.ts
|
|
17
|
+
var NODE_FLOOR = { major: 22, minor: 5 };
|
|
18
|
+
function isSupportedNode(version) {
|
|
19
|
+
const [major = 0, minor = 0] = version.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
20
|
+
return major > NODE_FLOOR.major || major === NODE_FLOOR.major && minor >= NODE_FLOOR.minor;
|
|
21
|
+
}
|
|
22
|
+
function nodeVersionGuardMessage(version) {
|
|
23
|
+
return `
|
|
24
|
+
Open Code Review requires Node.js >= ${NODE_FLOOR.major}.${NODE_FLOOR.minor} (it uses Node's built-in SQLite, \`node:sqlite\`).
|
|
25
|
+
You have Node ${version}. Upgrade Node (e.g. \`nvm install 22 && nvm use 22\`) and re-run.
|
|
26
|
+
|
|
27
|
+
`;
|
|
28
|
+
}
|
|
29
|
+
function isSuppressibleSqliteWarning(warning) {
|
|
30
|
+
const message = typeof warning === "string" ? warning : warning?.message;
|
|
31
|
+
return typeof message === "string" && message.includes("SQLite is an experimental feature");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/lib/db/engine.ts
|
|
35
|
+
var SQLITE_BUSY = 5;
|
|
36
|
+
var SQLITE_BUSY_SNAPSHOT = 261;
|
|
7
37
|
var BUSY_RETRY_ATTEMPTS = 5;
|
|
8
38
|
var BUSY_RETRY_BACKOFF_MS = 50;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
39
|
+
var savepointName = (depth) => `ocr_sp_${depth}`;
|
|
40
|
+
var nodeRequire = createRequire(import.meta.url);
|
|
41
|
+
var _preconditionsApplied = false;
|
|
42
|
+
function applyEnginePreconditions() {
|
|
43
|
+
if (_preconditionsApplied) return;
|
|
44
|
+
_preconditionsApplied = true;
|
|
45
|
+
const originalEmitWarning = process.emitWarning.bind(process);
|
|
46
|
+
process.emitWarning = (warning, ...args) => {
|
|
47
|
+
if (isSuppressibleSqliteWarning(warning)) return;
|
|
48
|
+
originalEmitWarning(warning, ...args);
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
var _DatabaseSyncCtor;
|
|
52
|
+
function newDatabase(path) {
|
|
53
|
+
if (!_DatabaseSyncCtor) {
|
|
54
|
+
applyEnginePreconditions();
|
|
55
|
+
try {
|
|
56
|
+
_DatabaseSyncCtor = nodeRequire("node:sqlite").DatabaseSync;
|
|
57
|
+
} catch (e) {
|
|
58
|
+
if (!isSupportedNode(process.versions.node)) {
|
|
59
|
+
throw new Error(nodeVersionGuardMessage(process.versions.node).trim());
|
|
60
|
+
}
|
|
61
|
+
throw e;
|
|
62
|
+
}
|
|
12
63
|
}
|
|
13
|
-
|
|
14
|
-
|
|
64
|
+
return new _DatabaseSyncCtor(path);
|
|
65
|
+
}
|
|
66
|
+
function isBusyError(e) {
|
|
67
|
+
const errcode = e?.errcode;
|
|
68
|
+
return errcode === SQLITE_BUSY || errcode === SQLITE_BUSY_SNAPSHOT;
|
|
15
69
|
}
|
|
70
|
+
var SLEEP_BUF = new Int32Array(new SharedArrayBuffer(4));
|
|
16
71
|
function sleepSync(ms) {
|
|
17
|
-
Atomics.wait(
|
|
72
|
+
Atomics.wait(SLEEP_BUF, 0, 0, ms);
|
|
18
73
|
}
|
|
19
|
-
var
|
|
74
|
+
var NodeSqliteAdapter = class {
|
|
20
75
|
raw;
|
|
76
|
+
/**
|
|
77
|
+
* Transaction nesting depth. `node:sqlite` has no transaction helper, so we
|
|
78
|
+
* drive `BEGIN IMMEDIATE` ourselves and use SAVEPOINTs for nested calls
|
|
79
|
+
* (better-sqlite3 did this automatically). 0 = no transaction open.
|
|
80
|
+
*/
|
|
81
|
+
txnDepth = 0;
|
|
21
82
|
constructor(db) {
|
|
22
83
|
this.raw = db;
|
|
23
84
|
}
|
|
24
85
|
exec(sql, params) {
|
|
25
86
|
const stmt = this.raw.prepare(sql);
|
|
26
|
-
|
|
87
|
+
const cols = stmt.columns();
|
|
88
|
+
if (cols.length === 0) {
|
|
27
89
|
stmt.run(...params ?? []);
|
|
28
90
|
return [];
|
|
29
91
|
}
|
|
30
|
-
|
|
31
|
-
const values = stmt.
|
|
32
|
-
return values.length > 0 ? [{ columns, values }] : [];
|
|
92
|
+
stmt.setReturnArrays(true);
|
|
93
|
+
const values = stmt.all(...params ?? []);
|
|
94
|
+
return values.length > 0 ? [{ columns: cols.map((c) => c.name), values }] : [];
|
|
33
95
|
}
|
|
34
96
|
run(sql, params) {
|
|
35
97
|
if (params !== void 0) {
|
|
@@ -42,31 +104,90 @@ var BetterSqliteAdapter = class {
|
|
|
42
104
|
return this.raw.prepare(sql);
|
|
43
105
|
}
|
|
44
106
|
transaction(fn) {
|
|
45
|
-
|
|
46
|
-
|
|
107
|
+
return this.txnDepth > 0 ? this.runNested(fn) : this.runOuter(fn);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Nested call: a SAVEPOINT within the outer transaction's write lock. No
|
|
111
|
+
* busy-retry — the outer transaction already holds the lock. The savepoint
|
|
112
|
+
* lets the inner block roll back independently while the outer continues.
|
|
113
|
+
*/
|
|
114
|
+
runNested(fn) {
|
|
115
|
+
const name = savepointName(this.txnDepth);
|
|
116
|
+
this.raw.exec(`SAVEPOINT ${name}`);
|
|
117
|
+
this.txnDepth++;
|
|
118
|
+
try {
|
|
119
|
+
const result = fn();
|
|
120
|
+
this.raw.exec(`RELEASE ${name}`);
|
|
121
|
+
return result;
|
|
122
|
+
} catch (e) {
|
|
47
123
|
try {
|
|
48
|
-
|
|
124
|
+
this.raw.exec(`ROLLBACK TO ${name}`);
|
|
125
|
+
this.raw.exec(`RELEASE ${name}`);
|
|
126
|
+
} catch {
|
|
127
|
+
}
|
|
128
|
+
throw e;
|
|
129
|
+
} finally {
|
|
130
|
+
this.txnDepth--;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Outer transaction: `BEGIN IMMEDIATE` acquires the write lock up front so
|
|
135
|
+
* cross-process writers serialize cleanly under WAL instead of failing late
|
|
136
|
+
* on upgrade. `busy_timeout` covers most contention; a bounded synchronous
|
|
137
|
+
* retry absorbs the residual SQLITE_BUSY (another connection holds the lock
|
|
138
|
+
* past the timeout, or BUSY_SNAPSHOT). Non-busy errors and the final attempt
|
|
139
|
+
* re-throw so genuine failures propagate.
|
|
140
|
+
*/
|
|
141
|
+
runOuter(fn) {
|
|
142
|
+
for (let attempt = 0; attempt < BUSY_RETRY_ATTEMPTS; attempt++) {
|
|
143
|
+
try {
|
|
144
|
+
return this.runOnce(fn);
|
|
49
145
|
} catch (e) {
|
|
50
|
-
if (!isBusyError(e) || attempt
|
|
146
|
+
if (!isBusyError(e) || attempt === BUSY_RETRY_ATTEMPTS - 1) throw e;
|
|
51
147
|
sleepSync(BUSY_RETRY_BACKOFF_MS);
|
|
52
148
|
}
|
|
53
149
|
}
|
|
150
|
+
throw new Error("transaction retry budget exhausted");
|
|
151
|
+
}
|
|
152
|
+
/** One `BEGIN IMMEDIATE` / `COMMIT` / `ROLLBACK` lifecycle. */
|
|
153
|
+
runOnce(fn) {
|
|
154
|
+
this.raw.exec("BEGIN IMMEDIATE");
|
|
155
|
+
this.txnDepth = 1;
|
|
156
|
+
try {
|
|
157
|
+
const result = fn();
|
|
158
|
+
this.raw.exec("COMMIT");
|
|
159
|
+
return result;
|
|
160
|
+
} catch (e) {
|
|
161
|
+
try {
|
|
162
|
+
this.raw.exec("ROLLBACK");
|
|
163
|
+
} catch {
|
|
164
|
+
}
|
|
165
|
+
throw e;
|
|
166
|
+
} finally {
|
|
167
|
+
this.txnDepth = 0;
|
|
168
|
+
}
|
|
54
169
|
}
|
|
55
170
|
pragma(source) {
|
|
56
|
-
|
|
171
|
+
this.raw.exec(`PRAGMA ${source}`);
|
|
172
|
+
return void 0;
|
|
57
173
|
}
|
|
58
174
|
close() {
|
|
59
175
|
try {
|
|
60
|
-
this.raw.
|
|
176
|
+
this.raw.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
61
177
|
} catch {
|
|
62
178
|
}
|
|
63
|
-
|
|
179
|
+
try {
|
|
180
|
+
this.raw.close();
|
|
181
|
+
} catch (e) {
|
|
182
|
+
const message = e?.message ?? "";
|
|
183
|
+
if (!/database is not open/i.test(message)) throw e;
|
|
184
|
+
}
|
|
64
185
|
}
|
|
65
186
|
};
|
|
66
187
|
function probeEngine() {
|
|
67
188
|
try {
|
|
68
|
-
const db =
|
|
69
|
-
db.
|
|
189
|
+
const db = newDatabase(":memory:");
|
|
190
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
70
191
|
db.exec("CREATE TABLE _probe(x); INSERT INTO _probe VALUES (1);");
|
|
71
192
|
const row = db.prepare("SELECT sqlite_version() AS v").get();
|
|
72
193
|
db.close();
|
|
@@ -76,12 +197,12 @@ function probeEngine() {
|
|
|
76
197
|
}
|
|
77
198
|
}
|
|
78
199
|
function openEngine(dbPath) {
|
|
79
|
-
const native =
|
|
80
|
-
native.
|
|
81
|
-
native.
|
|
82
|
-
native.
|
|
83
|
-
native.
|
|
84
|
-
return new
|
|
200
|
+
const native = newDatabase(dbPath);
|
|
201
|
+
native.exec("PRAGMA journal_mode = WAL");
|
|
202
|
+
native.exec("PRAGMA foreign_keys = ON");
|
|
203
|
+
native.exec("PRAGMA busy_timeout = 5000");
|
|
204
|
+
native.exec("PRAGMA synchronous = NORMAL");
|
|
205
|
+
return new NodeSqliteAdapter(native);
|
|
85
206
|
}
|
|
86
207
|
|
|
87
208
|
// src/lib/db/migrations.ts
|
|
@@ -1484,7 +1605,7 @@ function walCheckpointTruncate(dbPath) {
|
|
|
1484
1605
|
return "failed";
|
|
1485
1606
|
} finally {
|
|
1486
1607
|
try {
|
|
1487
|
-
transient?.
|
|
1608
|
+
transient?.close();
|
|
1488
1609
|
} catch {
|
|
1489
1610
|
}
|
|
1490
1611
|
}
|
|
@@ -1502,6 +1623,41 @@ function closeAllDatabases() {
|
|
|
1502
1623
|
connections.delete(path);
|
|
1503
1624
|
}
|
|
1504
1625
|
}
|
|
1626
|
+
function probeWrite() {
|
|
1627
|
+
let dir;
|
|
1628
|
+
try {
|
|
1629
|
+
dir = mkdtempSync(join3(tmpdir(), "ocr-probe-"));
|
|
1630
|
+
const db = openEngine(join3(dir, "probe.db"));
|
|
1631
|
+
try {
|
|
1632
|
+
db.run("CREATE TABLE _probe_write (id INTEGER PRIMARY KEY, v TEXT)");
|
|
1633
|
+
db.transaction(() => {
|
|
1634
|
+
db.run("INSERT INTO _probe_write (v) VALUES (?)", ["written-in-txn"]);
|
|
1635
|
+
});
|
|
1636
|
+
const value = db.exec("SELECT v FROM _probe_write")[0]?.values[0]?.[0];
|
|
1637
|
+
if (value !== "written-in-txn") {
|
|
1638
|
+
return { ok: false, error: `unexpected probe value: ${String(value)}` };
|
|
1639
|
+
}
|
|
1640
|
+
return { ok: true };
|
|
1641
|
+
} finally {
|
|
1642
|
+
db.close();
|
|
1643
|
+
}
|
|
1644
|
+
} catch (e) {
|
|
1645
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
1646
|
+
} finally {
|
|
1647
|
+
if (dir) rmDirBestEffort(dir);
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
function rmDirBestEffort(dir) {
|
|
1651
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
1652
|
+
try {
|
|
1653
|
+
rmSync(dir, { recursive: true, force: true });
|
|
1654
|
+
return;
|
|
1655
|
+
} catch {
|
|
1656
|
+
if (attempt === 2) return;
|
|
1657
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1505
1661
|
export {
|
|
1506
1662
|
CANCELLED_EXIT_CODE,
|
|
1507
1663
|
CASCADE_CLOSE_EXIT_CODE,
|
|
@@ -1540,6 +1696,7 @@ export {
|
|
|
1540
1696
|
listAgentSessionsForWorkflow,
|
|
1541
1697
|
openDatabase,
|
|
1542
1698
|
probeEngine,
|
|
1699
|
+
probeWrite,
|
|
1543
1700
|
readCommandLog,
|
|
1544
1701
|
reconcileLegacyState,
|
|
1545
1702
|
recordVendorSessionIdForExecution,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-code-review/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "CLI for Open Code Review - Multi-environment setup and progress tracking",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"license": "Apache-2.0",
|
|
64
64
|
"author": "Spencer Marx",
|
|
65
65
|
"engines": {
|
|
66
|
-
"node": ">=
|
|
66
|
+
"node": ">=22.5.0"
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
69
|
"@inquirer/prompts": "^7.2.0",
|
|
@@ -73,15 +73,13 @@
|
|
|
73
73
|
"log-update": "^7.0.2",
|
|
74
74
|
"ora": "^8.1.1",
|
|
75
75
|
"socket.io": "^4.8",
|
|
76
|
-
"better-sqlite3": "^11.8.1",
|
|
77
76
|
"yaml": "^2.8.3",
|
|
78
|
-
"@open-code-review/agents": "2.
|
|
77
|
+
"@open-code-review/agents": "2.1.0"
|
|
79
78
|
},
|
|
80
79
|
"publishConfig": {
|
|
81
80
|
"access": "public"
|
|
82
81
|
},
|
|
83
82
|
"devDependencies": {
|
|
84
|
-
"@types/better-sqlite3": "^7.6.12",
|
|
85
83
|
"@open-code-review/platform": "0.0.0"
|
|
86
84
|
}
|
|
87
85
|
}
|