@deksden-com/dd-flow-cli 0.4.0 → 0.4.2
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/CHANGELOG.md +22 -0
- package/README.md +25 -9
- package/dist/build-info.json +5 -5
- package/dist/cli/help.js +45 -29
- package/dist/cli/run-cli.js +229 -49
- package/dist/domain/entity-ids.js +4 -4
- package/dist/domain/flow-contract.js +502 -36
- package/dist/domain/validation.js +34 -0
- package/dist/protocol/local-files.js +8 -6
- package/dist/schemas/code-stage-report.schema.json +197 -2
- package/dist/schemas/flow-contract.schema.json +126 -0
- package/dist/schemas/flow-run-index-v3.schema.json +203 -0
- package/dist/schemas/flow-run-index.schema.json +22 -2
- package/dist/schemas/flow-run.schema.json +36 -0
- package/dist/schemas/merge-stage-report.schema.json +213 -2
- package/dist/schemas/plan-stage-report.schema.json +156 -2
- package/dist/schemas/release-impact.schema.json +16 -0
- package/dist/services/audit.js +3 -3
- package/dist/services/branch-context.js +17 -5
- package/dist/services/canon.js +0 -1
- package/dist/services/cleanup.js +6 -6
- package/dist/services/cli-operation-classifier.js +1 -1
- package/dist/services/compatibility-preflight.js +3 -75
- package/dist/services/dashboard.js +48 -11
- package/dist/services/engines.js +124 -13
- package/dist/services/hooks.js +6 -6
- package/dist/services/ids.js +40 -49
- package/dist/services/merge-queue.js +33 -26
- package/dist/services/merge-worker.js +2 -1
- package/dist/services/migrations.js +64 -0
- package/dist/services/plans.js +23 -16
- package/dist/services/projects.js +2 -2
- package/dist/services/prompts.js +322 -0
- package/dist/services/protocols.js +77 -46
- package/dist/services/run-projection.js +80 -0
- package/dist/services/runs.js +360 -22
- package/dist/services/schema-validation.js +35 -12
- package/dist/services/sessions.js +81 -3
- package/dist/services/status.js +32 -1
- package/dist/services/usage.js +233 -0
- package/dist/services/worktrees.js +24 -19
- package/dist/storage/database.js +223 -9
- package/package.json +1 -1
package/dist/storage/database.js
CHANGED
|
@@ -10,8 +10,8 @@ export function getDatabase(ddFlowHome) {
|
|
|
10
10
|
const dbPath = path.join(ddFlowHome, "db.sqlite");
|
|
11
11
|
const db = new DatabaseSync(dbPath);
|
|
12
12
|
configureDatabase(db);
|
|
13
|
-
db.exec("PRAGMA foreign_keys = ON");
|
|
14
13
|
migrate(db);
|
|
14
|
+
db.exec("PRAGMA foreign_keys = ON");
|
|
15
15
|
return {
|
|
16
16
|
path: dbPath,
|
|
17
17
|
exec: (sql) => db.exec(sql),
|
|
@@ -41,7 +41,7 @@ function migrate(db) {
|
|
|
41
41
|
);
|
|
42
42
|
|
|
43
43
|
CREATE TABLE IF NOT EXISTS protocols (
|
|
44
|
-
id TEXT
|
|
44
|
+
id TEXT NOT NULL,
|
|
45
45
|
handshake_id TEXT NOT NULL,
|
|
46
46
|
project_id TEXT NOT NULL,
|
|
47
47
|
project_root TEXT NOT NULL,
|
|
@@ -56,14 +56,17 @@ function migrate(db) {
|
|
|
56
56
|
plan_path TEXT NOT NULL,
|
|
57
57
|
created_at TEXT NOT NULL,
|
|
58
58
|
updated_at TEXT NOT NULL,
|
|
59
|
+
PRIMARY KEY(project_id, id),
|
|
59
60
|
FOREIGN KEY(project_id) REFERENCES projects(id)
|
|
60
61
|
);
|
|
61
62
|
|
|
62
63
|
CREATE TABLE IF NOT EXISTS plans (
|
|
63
|
-
|
|
64
|
+
project_id TEXT NOT NULL,
|
|
65
|
+
protocol_id TEXT NOT NULL,
|
|
64
66
|
plan_json TEXT NOT NULL,
|
|
65
67
|
updated_at TEXT NOT NULL,
|
|
66
|
-
|
|
68
|
+
PRIMARY KEY(project_id, protocol_id),
|
|
69
|
+
FOREIGN KEY(project_id, protocol_id) REFERENCES protocols(project_id, id)
|
|
67
70
|
);
|
|
68
71
|
|
|
69
72
|
CREATE TABLE IF NOT EXISTS audit_events (
|
|
@@ -79,7 +82,7 @@ function migrate(db) {
|
|
|
79
82
|
|
|
80
83
|
CREATE TABLE IF NOT EXISTS merge_queue (
|
|
81
84
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
82
|
-
protocol_id TEXT NOT NULL
|
|
85
|
+
protocol_id TEXT NOT NULL,
|
|
83
86
|
project_id TEXT NOT NULL,
|
|
84
87
|
status TEXT NOT NULL,
|
|
85
88
|
claimed_by_session_id TEXT,
|
|
@@ -88,7 +91,8 @@ function migrate(db) {
|
|
|
88
91
|
last_reason TEXT,
|
|
89
92
|
completed_at TEXT,
|
|
90
93
|
created_at TEXT NOT NULL,
|
|
91
|
-
updated_at TEXT NOT NULL
|
|
94
|
+
updated_at TEXT NOT NULL,
|
|
95
|
+
UNIQUE(project_id, protocol_id)
|
|
92
96
|
);
|
|
93
97
|
|
|
94
98
|
CREATE TABLE IF NOT EXISTS merge_sessions (
|
|
@@ -244,6 +248,7 @@ function migrate(db) {
|
|
|
244
248
|
transcript_path TEXT,
|
|
245
249
|
cwd TEXT,
|
|
246
250
|
metadata_json TEXT NOT NULL,
|
|
251
|
+
coverage_units_json TEXT NOT NULL DEFAULT '[]',
|
|
247
252
|
created_at TEXT NOT NULL,
|
|
248
253
|
updated_at TEXT NOT NULL,
|
|
249
254
|
stopped_at TEXT,
|
|
@@ -252,7 +257,7 @@ function migrate(db) {
|
|
|
252
257
|
);
|
|
253
258
|
|
|
254
259
|
CREATE TABLE IF NOT EXISTS flow_runs (
|
|
255
|
-
id TEXT
|
|
260
|
+
id TEXT NOT NULL,
|
|
256
261
|
short_id TEXT NOT NULL,
|
|
257
262
|
slug TEXT NOT NULL,
|
|
258
263
|
project_id TEXT NOT NULL,
|
|
@@ -274,6 +279,7 @@ function migrate(db) {
|
|
|
274
279
|
created_at TEXT NOT NULL,
|
|
275
280
|
updated_at TEXT NOT NULL,
|
|
276
281
|
completed_at TEXT,
|
|
282
|
+
PRIMARY KEY(project_id, id),
|
|
277
283
|
FOREIGN KEY(project_id) REFERENCES projects(id)
|
|
278
284
|
);
|
|
279
285
|
|
|
@@ -283,6 +289,46 @@ function migrate(db) {
|
|
|
283
289
|
CREATE INDEX IF NOT EXISTS idx_flow_runs_short_id
|
|
284
290
|
ON flow_runs(short_id);
|
|
285
291
|
|
|
292
|
+
CREATE TABLE IF NOT EXISTS flow_run_flag_mutations (
|
|
293
|
+
project_id TEXT NOT NULL,
|
|
294
|
+
run_id TEXT NOT NULL,
|
|
295
|
+
idempotency_key TEXT NOT NULL,
|
|
296
|
+
expected_revision INTEGER NOT NULL,
|
|
297
|
+
resulting_revision INTEGER NOT NULL,
|
|
298
|
+
result_json TEXT NOT NULL,
|
|
299
|
+
created_at TEXT NOT NULL,
|
|
300
|
+
PRIMARY KEY(project_id, run_id, idempotency_key),
|
|
301
|
+
FOREIGN KEY(project_id) REFERENCES projects(id)
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
CREATE TABLE IF NOT EXISTS flow_run_usage_snapshots (
|
|
305
|
+
id TEXT PRIMARY KEY,
|
|
306
|
+
project_id TEXT NOT NULL,
|
|
307
|
+
run_id TEXT NOT NULL,
|
|
308
|
+
session_id TEXT NOT NULL,
|
|
309
|
+
checkpoint TEXT NOT NULL,
|
|
310
|
+
stage TEXT,
|
|
311
|
+
stage_attempt TEXT,
|
|
312
|
+
observed_at TEXT NOT NULL,
|
|
313
|
+
total_tokens INTEGER,
|
|
314
|
+
input_tokens INTEGER,
|
|
315
|
+
cached_input_tokens INTEGER,
|
|
316
|
+
output_tokens INTEGER,
|
|
317
|
+
reasoning_output_tokens INTEGER,
|
|
318
|
+
source_kind TEXT NOT NULL,
|
|
319
|
+
token_event_at TEXT,
|
|
320
|
+
turn_id TEXT,
|
|
321
|
+
turn_attribution TEXT NOT NULL,
|
|
322
|
+
parser_version INTEGER NOT NULL,
|
|
323
|
+
extraction_status TEXT NOT NULL,
|
|
324
|
+
diagnostic_code TEXT,
|
|
325
|
+
created_at TEXT NOT NULL,
|
|
326
|
+
FOREIGN KEY(project_id) REFERENCES projects(id)
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
CREATE INDEX IF NOT EXISTS idx_flow_run_usage_snapshots_run
|
|
330
|
+
ON flow_run_usage_snapshots(project_id, run_id, session_id, observed_at, id);
|
|
331
|
+
|
|
286
332
|
CREATE TABLE IF NOT EXISTS pending_flow_session_bindings (
|
|
287
333
|
session_id TEXT NOT NULL,
|
|
288
334
|
project_id TEXT NOT NULL,
|
|
@@ -312,7 +358,7 @@ function migrate(db) {
|
|
|
312
358
|
);
|
|
313
359
|
|
|
314
360
|
CREATE TABLE IF NOT EXISTS worktree_records (
|
|
315
|
-
protocol_id TEXT
|
|
361
|
+
protocol_id TEXT NOT NULL,
|
|
316
362
|
project_id TEXT NOT NULL,
|
|
317
363
|
integration_branch TEXT,
|
|
318
364
|
feature_branch TEXT NOT NULL,
|
|
@@ -325,10 +371,12 @@ function migrate(db) {
|
|
|
325
371
|
created_at TEXT NOT NULL,
|
|
326
372
|
updated_at TEXT NOT NULL,
|
|
327
373
|
closed_at TEXT,
|
|
328
|
-
|
|
374
|
+
PRIMARY KEY(project_id, protocol_id),
|
|
375
|
+
FOREIGN KEY(project_id, protocol_id) REFERENCES protocols(project_id, id),
|
|
329
376
|
FOREIGN KEY(project_id) REFERENCES projects(id)
|
|
330
377
|
);
|
|
331
378
|
`);
|
|
379
|
+
migrateProjectScopedIdentity(db);
|
|
332
380
|
ensureColumn(db, "projects", "short_id", "ALTER TABLE projects ADD COLUMN short_id TEXT");
|
|
333
381
|
ensureColumn(db, "projects", "slug", "ALTER TABLE projects ADD COLUMN slug TEXT");
|
|
334
382
|
ensureColumn(db, "projects", "status", "ALTER TABLE projects ADD COLUMN status TEXT NOT NULL DEFAULT 'active'");
|
|
@@ -345,10 +393,176 @@ function migrate(db) {
|
|
|
345
393
|
ensureColumn(db, "flow_sessions", "next_action", "ALTER TABLE flow_sessions ADD COLUMN next_action TEXT");
|
|
346
394
|
ensureColumn(db, "flow_sessions", "metadata_json", "ALTER TABLE flow_sessions ADD COLUMN metadata_json TEXT NOT NULL DEFAULT '{}'");
|
|
347
395
|
ensureColumn(db, "flow_sessions", "run_id", "ALTER TABLE flow_sessions ADD COLUMN run_id TEXT");
|
|
396
|
+
ensureColumn(db, "flow_sessions", "parent_session_id", "ALTER TABLE flow_sessions ADD COLUMN parent_session_id TEXT");
|
|
397
|
+
ensureColumn(db, "flow_sessions", "role", "ALTER TABLE flow_sessions ADD COLUMN role TEXT");
|
|
398
|
+
ensureColumn(db, "flow_sessions", "aspect_id", "ALTER TABLE flow_sessions ADD COLUMN aspect_id TEXT");
|
|
399
|
+
ensureColumn(db, "flow_sessions", "plan_item_id", "ALTER TABLE flow_sessions ADD COLUMN plan_item_id TEXT");
|
|
400
|
+
ensureColumn(db, "flow_sessions", "session_kind", "ALTER TABLE flow_sessions ADD COLUMN session_kind TEXT");
|
|
401
|
+
ensureColumn(db, "flow_sessions", "coverage_units_json", "ALTER TABLE flow_sessions ADD COLUMN coverage_units_json TEXT NOT NULL DEFAULT '[]'");
|
|
348
402
|
ensureColumn(db, "flow_runs", "run_home_path", "ALTER TABLE flow_runs ADD COLUMN run_home_path TEXT");
|
|
349
403
|
ensureColumn(db, "flow_runs", "layout_version", "ALTER TABLE flow_runs ADD COLUMN layout_version TEXT");
|
|
350
404
|
ensureColumn(db, "flow_runs", "artifact_root_kind", "ALTER TABLE flow_runs ADD COLUMN artifact_root_kind TEXT");
|
|
351
405
|
ensureColumn(db, "lane_waiters", "expires_at", "ALTER TABLE lane_waiters ADD COLUMN expires_at TEXT");
|
|
406
|
+
db.exec(`
|
|
407
|
+
CREATE INDEX IF NOT EXISTS idx_flow_runs_project_updated
|
|
408
|
+
ON flow_runs(project_id, updated_at DESC);
|
|
409
|
+
CREATE INDEX IF NOT EXISTS idx_flow_runs_short_id
|
|
410
|
+
ON flow_runs(short_id);
|
|
411
|
+
`);
|
|
412
|
+
}
|
|
413
|
+
function migrateProjectScopedIdentity(db) {
|
|
414
|
+
const protocolColumns = db.prepare("PRAGMA table_info(protocols)").all();
|
|
415
|
+
const legacyGlobalPrimaryKey = protocolColumns.some((column) => column.name === "id" && column.pk === 1)
|
|
416
|
+
&& protocolColumns.some((column) => column.name === "project_id" && column.pk === 0);
|
|
417
|
+
if (!legacyGlobalPrimaryKey)
|
|
418
|
+
return;
|
|
419
|
+
db.exec(`
|
|
420
|
+
PRAGMA foreign_keys = OFF;
|
|
421
|
+
BEGIN IMMEDIATE;
|
|
422
|
+
|
|
423
|
+
ALTER TABLE plans RENAME TO plans_legacy_global_identity;
|
|
424
|
+
ALTER TABLE merge_queue RENAME TO merge_queue_legacy_global_identity;
|
|
425
|
+
ALTER TABLE worktree_records RENAME TO worktree_records_legacy_global_identity;
|
|
426
|
+
ALTER TABLE flow_runs RENAME TO flow_runs_legacy_global_identity;
|
|
427
|
+
ALTER TABLE protocols RENAME TO protocols_legacy_global_identity;
|
|
428
|
+
|
|
429
|
+
CREATE TABLE protocols (
|
|
430
|
+
id TEXT NOT NULL,
|
|
431
|
+
handshake_id TEXT NOT NULL,
|
|
432
|
+
project_id TEXT NOT NULL,
|
|
433
|
+
project_root TEXT NOT NULL,
|
|
434
|
+
status TEXT NOT NULL,
|
|
435
|
+
stage TEXT NOT NULL,
|
|
436
|
+
next_action TEXT,
|
|
437
|
+
route_json TEXT NOT NULL,
|
|
438
|
+
workspace_json TEXT NOT NULL,
|
|
439
|
+
blockers_json TEXT NOT NULL,
|
|
440
|
+
active_def_json TEXT NOT NULL,
|
|
441
|
+
state_path TEXT NOT NULL,
|
|
442
|
+
plan_path TEXT NOT NULL,
|
|
443
|
+
created_at TEXT NOT NULL,
|
|
444
|
+
updated_at TEXT NOT NULL,
|
|
445
|
+
PRIMARY KEY(project_id, id),
|
|
446
|
+
FOREIGN KEY(project_id) REFERENCES projects(id)
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
INSERT INTO protocols
|
|
450
|
+
(id, handshake_id, project_id, project_root, status, stage, next_action,
|
|
451
|
+
route_json, workspace_json, blockers_json, active_def_json, state_path,
|
|
452
|
+
plan_path, created_at, updated_at)
|
|
453
|
+
SELECT id, handshake_id, project_id, project_root, status, stage,
|
|
454
|
+
next_action, route_json, workspace_json, blockers_json, active_def_json,
|
|
455
|
+
state_path, plan_path, created_at, updated_at
|
|
456
|
+
FROM protocols_legacy_global_identity;
|
|
457
|
+
|
|
458
|
+
CREATE TABLE plans (
|
|
459
|
+
project_id TEXT NOT NULL,
|
|
460
|
+
protocol_id TEXT NOT NULL,
|
|
461
|
+
plan_json TEXT NOT NULL,
|
|
462
|
+
updated_at TEXT NOT NULL,
|
|
463
|
+
PRIMARY KEY(project_id, protocol_id),
|
|
464
|
+
FOREIGN KEY(project_id, protocol_id) REFERENCES protocols(project_id, id)
|
|
465
|
+
);
|
|
466
|
+
INSERT INTO plans (project_id, protocol_id, plan_json, updated_at)
|
|
467
|
+
SELECT p.project_id, legacy.protocol_id, legacy.plan_json, legacy.updated_at
|
|
468
|
+
FROM plans_legacy_global_identity legacy
|
|
469
|
+
JOIN protocols p ON p.id = legacy.protocol_id;
|
|
470
|
+
|
|
471
|
+
CREATE TABLE merge_queue (
|
|
472
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
473
|
+
protocol_id TEXT NOT NULL,
|
|
474
|
+
project_id TEXT NOT NULL,
|
|
475
|
+
status TEXT NOT NULL,
|
|
476
|
+
claimed_by_session_id TEXT,
|
|
477
|
+
claimed_at TEXT,
|
|
478
|
+
attempts_count INTEGER NOT NULL DEFAULT 0,
|
|
479
|
+
last_reason TEXT,
|
|
480
|
+
completed_at TEXT,
|
|
481
|
+
created_at TEXT NOT NULL,
|
|
482
|
+
updated_at TEXT NOT NULL,
|
|
483
|
+
UNIQUE(project_id, protocol_id)
|
|
484
|
+
);
|
|
485
|
+
INSERT INTO merge_queue
|
|
486
|
+
(id, protocol_id, project_id, status, claimed_by_session_id, claimed_at,
|
|
487
|
+
attempts_count, last_reason, completed_at, created_at, updated_at)
|
|
488
|
+
SELECT id, protocol_id, project_id, status, claimed_by_session_id,
|
|
489
|
+
claimed_at, COALESCE(attempts_count, 0), last_reason, completed_at,
|
|
490
|
+
created_at, updated_at
|
|
491
|
+
FROM merge_queue_legacy_global_identity;
|
|
492
|
+
|
|
493
|
+
CREATE TABLE worktree_records (
|
|
494
|
+
protocol_id TEXT NOT NULL,
|
|
495
|
+
project_id TEXT NOT NULL,
|
|
496
|
+
integration_branch TEXT,
|
|
497
|
+
feature_branch TEXT NOT NULL,
|
|
498
|
+
base_ref TEXT NOT NULL,
|
|
499
|
+
worktree_path TEXT NOT NULL,
|
|
500
|
+
worktrunk_metadata_json TEXT NOT NULL,
|
|
501
|
+
bootstrap_status TEXT NOT NULL,
|
|
502
|
+
status TEXT NOT NULL,
|
|
503
|
+
last_command_result_json TEXT NOT NULL,
|
|
504
|
+
created_at TEXT NOT NULL,
|
|
505
|
+
updated_at TEXT NOT NULL,
|
|
506
|
+
closed_at TEXT,
|
|
507
|
+
PRIMARY KEY(project_id, protocol_id),
|
|
508
|
+
FOREIGN KEY(project_id, protocol_id) REFERENCES protocols(project_id, id),
|
|
509
|
+
FOREIGN KEY(project_id) REFERENCES projects(id)
|
|
510
|
+
);
|
|
511
|
+
INSERT INTO worktree_records
|
|
512
|
+
(protocol_id, project_id, integration_branch, feature_branch, base_ref,
|
|
513
|
+
worktree_path, worktrunk_metadata_json, bootstrap_status, status,
|
|
514
|
+
last_command_result_json, created_at, updated_at, closed_at)
|
|
515
|
+
SELECT protocol_id, project_id, integration_branch, feature_branch,
|
|
516
|
+
base_ref, worktree_path, worktrunk_metadata_json, bootstrap_status,
|
|
517
|
+
status, last_command_result_json, created_at, updated_at, closed_at
|
|
518
|
+
FROM worktree_records_legacy_global_identity;
|
|
519
|
+
|
|
520
|
+
CREATE TABLE flow_runs (
|
|
521
|
+
id TEXT NOT NULL,
|
|
522
|
+
short_id TEXT NOT NULL,
|
|
523
|
+
slug TEXT NOT NULL,
|
|
524
|
+
project_id TEXT NOT NULL,
|
|
525
|
+
project_root TEXT NOT NULL,
|
|
526
|
+
workspace_root TEXT NOT NULL,
|
|
527
|
+
flow_kind TEXT NOT NULL,
|
|
528
|
+
subject_type TEXT NOT NULL,
|
|
529
|
+
subject_id TEXT NOT NULL,
|
|
530
|
+
status TEXT NOT NULL,
|
|
531
|
+
verdict TEXT NOT NULL,
|
|
532
|
+
next_action TEXT,
|
|
533
|
+
runtime_path TEXT NOT NULL,
|
|
534
|
+
run_dir TEXT NOT NULL,
|
|
535
|
+
run_index_path TEXT NOT NULL,
|
|
536
|
+
run_home_path TEXT,
|
|
537
|
+
layout_version TEXT,
|
|
538
|
+
artifact_root_kind TEXT,
|
|
539
|
+
index_json TEXT NOT NULL,
|
|
540
|
+
created_at TEXT NOT NULL,
|
|
541
|
+
updated_at TEXT NOT NULL,
|
|
542
|
+
completed_at TEXT,
|
|
543
|
+
PRIMARY KEY(project_id, id),
|
|
544
|
+
FOREIGN KEY(project_id) REFERENCES projects(id)
|
|
545
|
+
);
|
|
546
|
+
INSERT INTO flow_runs
|
|
547
|
+
(id, short_id, slug, project_id, project_root, workspace_root, flow_kind,
|
|
548
|
+
subject_type, subject_id, status, verdict, next_action, runtime_path,
|
|
549
|
+
run_dir, run_index_path, run_home_path, layout_version,
|
|
550
|
+
artifact_root_kind, index_json, created_at, updated_at, completed_at)
|
|
551
|
+
SELECT id, short_id, slug, project_id, project_root, workspace_root,
|
|
552
|
+
flow_kind, subject_type, subject_id, status, verdict, next_action,
|
|
553
|
+
runtime_path, run_dir, run_index_path, run_home_path, layout_version,
|
|
554
|
+
artifact_root_kind, index_json, created_at, updated_at, completed_at
|
|
555
|
+
FROM flow_runs_legacy_global_identity;
|
|
556
|
+
|
|
557
|
+
DROP TABLE plans_legacy_global_identity;
|
|
558
|
+
DROP TABLE merge_queue_legacy_global_identity;
|
|
559
|
+
DROP TABLE worktree_records_legacy_global_identity;
|
|
560
|
+
DROP TABLE flow_runs_legacy_global_identity;
|
|
561
|
+
DROP TABLE protocols_legacy_global_identity;
|
|
562
|
+
|
|
563
|
+
COMMIT;
|
|
564
|
+
PRAGMA foreign_keys = ON;
|
|
565
|
+
`);
|
|
352
566
|
}
|
|
353
567
|
function ensureColumn(db, table, column, sql) {
|
|
354
568
|
const columns = db.prepare(`PRAGMA table_info(${table})`).all();
|