@celilo/cli 2.2.1 → 3.0.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/CELILO_CORE_MODULES.md +2 -1
- package/CELILO_SUBSYSTEMS.md +1 -1
- package/README.md +1 -1
- package/drizzle/0032_module_jail_policies.sql +28 -0
- package/drizzle/0033_build_bus_hook_runs.sql +37 -0
- package/drizzle/meta/_journal.json +15 -1
- package/package.json +3 -3
- package/src/__integration__/container-services-cli.integration.test.ts +1 -1
- package/src/api/serve.ts +13 -1
- package/src/api/sessions.test.ts +3 -3
- package/src/api-clients/proxmox.ts +10 -7
- package/src/cli/backup-rename.test.ts +3 -3
- package/src/cli/cli.test.ts +1 -1
- package/src/cli/commands/events.test.ts +2 -2
- package/src/cli/commands/firewall-interface-list.test.ts +2 -2
- package/src/cli/commands/machine-list.test.ts +57 -0
- package/src/cli/commands/machine-list.ts +35 -4
- package/src/cli/commands/module-health.test.ts +35 -0
- package/src/cli/commands/module-health.ts +12 -3
- package/src/cli/commands/module-import-registry.test.ts +1 -1
- package/src/cli/commands/module-jail.test.ts +242 -0
- package/src/cli/commands/module-jail.ts +227 -0
- package/src/cli/commands/module-list-jail.test.ts +136 -0
- package/src/cli/commands/module-list.ts +30 -3
- package/src/cli/commands/module-publish.test.ts +28 -6
- package/src/cli/commands/module-publish.ts +14 -12
- package/src/cli/commands/module-update.test.ts +22 -7
- package/src/cli/commands/module-update.ts +73 -18
- package/src/cli/commands/module-upgrade-gate.test.ts +154 -0
- package/src/cli/commands/module-upgrade.test.ts +115 -1
- package/src/cli/commands/module-upgrade.ts +66 -4
- package/src/cli/commands/module-verify.test.ts +1 -1
- package/src/cli/commands/publish/execute.ts +4 -1
- package/src/cli/commands/publish/helpers.ts +4 -3
- package/src/cli/commands/publish/index.ts +23 -1
- package/src/cli/commands/publish/module-registry.test.ts +24 -1
- package/src/cli/commands/publish/module-registry.ts +23 -4
- package/src/cli/commands/publish/plan.test.ts +64 -0
- package/src/cli/commands/publish/plan.ts +52 -19
- package/src/cli/commands/publish/types.ts +20 -0
- package/src/cli/commands/registry-owner.test.ts +1 -1
- package/src/cli/commands/registry-token.test.ts +1 -1
- package/src/cli/commands/subscribers-install-daemon.test.ts +44 -0
- package/src/cli/commands/subscribers-install-daemon.ts +107 -0
- package/src/cli/commands/subscribers-serve.test.ts +22 -0
- package/src/cli/commands/subscribers-serve.ts +22 -4
- package/src/cli/commands/system-audit.ts +34 -11
- package/src/cli/commands/system-doctor-remediation-gate.test.ts +295 -0
- package/src/cli/commands/system-doctor.test.ts +76 -10
- package/src/cli/commands/system-doctor.ts +34 -3
- package/src/cli/commands/system-init-deprecation.test.ts +1 -1
- package/src/cli/commands/system-update.ts +16 -6
- package/src/cli/completion.ts +18 -3
- package/src/cli/flag-surface-gate.test.ts +279 -0
- package/src/cli/fuel-gauge.ts +12 -4
- package/src/cli/index.ts +80 -2
- package/src/cli/json-output.test.ts +81 -0
- package/src/cli/parser.test.ts +37 -1
- package/src/cli/restore-command.test.ts +3 -3
- package/src/cli/restore-migration-failure.test.ts +2 -2
- package/src/cli/tui/audit-state.ts +2 -0
- package/src/cli/types.ts +9 -0
- package/src/config/paths.test.ts +19 -19
- package/src/db/client.test.ts +46 -1
- package/src/db/client.ts +26 -0
- package/src/db/schema.ts +63 -0
- package/src/hooks/broker.test.ts +9 -3
- package/src/hooks/capability-loader-firewall.test.ts +9 -1
- package/src/hooks/capability-loader.ts +8 -0
- package/src/hooks/executor.test.ts +91 -14
- package/src/hooks/executor.ts +31 -1
- package/src/hooks/hook-jail-toolchain-reach.test.ts +8 -3
- package/src/hooks/hook-jail-unreachability.test.ts +27 -20
- package/src/hooks/hook-trespass.test.ts +22 -6
- package/src/hooks/jail.test.ts +105 -17
- package/src/hooks/jail.ts +47 -10
- package/src/hooks/run-named-hook.ts +19 -16
- package/src/hooks/test-fixtures/artifact-writing-hook.ts +0 -1
- package/src/hooks/test-fixtures/capability-calling-hook.ts +20 -13
- package/src/hooks/test-fixtures/jail-probe-hook.ts +9 -1
- package/src/hooks/test-fixtures/jail-toolchain-hook.ts +7 -2
- package/src/hooks/test-fixtures/runaway-hook.ts +0 -1
- package/src/hooks/test-fixtures/sigterm-ignoring-hook.ts +0 -1
- package/src/hooks/test-fixtures/silent-hook.ts +0 -1
- package/src/hooks/test-fixtures/store-writing-hook.ts +20 -11
- package/src/hooks/test-fixtures/success-hook.ts +4 -4
- package/src/manifest/contracts/v1.ts +32 -14
- package/src/manifest/json-schema-roundtrip.test.ts +1 -1
- package/src/manifest/schema.ts +66 -16
- package/src/manifest/validate.test.ts +47 -0
- package/src/policy/capability-shape-baseline.ts +63 -21
- package/src/policy/capability-shape-drift.test.ts +53 -1
- package/src/policy/capability-shape.test.ts +105 -0
- package/src/policy/capability-shape.ts +283 -2
- package/src/policy/module-script-scan.ts +52 -68
- package/src/policy/no-hand-built-ssh.test.ts +5 -1
- package/src/policy/no-swallowed-refusal.test.ts +14 -14
- package/src/registry/client.test.ts +67 -2
- package/src/registry/client.ts +7 -7
- package/src/secrets/storage.test.ts +70 -4
- package/src/secrets/storage.ts +71 -1
- package/src/services/alerting/keys.test.ts +4 -0
- package/src/services/alerting/keys.ts +12 -2
- package/src/services/audit/health.test.ts +155 -2
- package/src/services/audit/health.ts +76 -2
- package/src/services/audit/index.test.ts +23 -1
- package/src/services/audit/index.ts +8 -2
- package/src/services/audit/interface-classification.test.ts +16 -5
- package/src/services/audit/interface-classification.ts +25 -2
- package/src/services/audit/jail-exemptions.test.ts +42 -0
- package/src/services/audit/jail-exemptions.ts +44 -0
- package/src/services/audit/module-integrity.test.ts +23 -1
- package/src/services/audit/module-integrity.ts +7 -2
- package/src/services/audit/module-versions.ts +5 -1
- package/src/services/audit/public-dns.test.ts +20 -0
- package/src/services/audit/public-dns.ts +7 -2
- package/src/services/audit/recurrence-gate.test.ts +225 -0
- package/src/services/audit/trusted-sources.test.ts +17 -0
- package/src/services/audit/trusted-sources.ts +21 -0
- package/src/services/audit/types.ts +2 -1
- package/src/services/backup-create.ts +9 -0
- package/src/services/backup-envelope-roundtrip.test.ts +1 -1
- package/src/services/backup-in-flight-refusal.test.ts +1 -1
- package/src/services/build-bus/hook-dispatch-executor.test.ts +269 -0
- package/src/services/build-bus/hook-dispatch-mgmt.test.ts +105 -105
- package/src/services/build-bus/hook-dispatch-path.test.ts +95 -0
- package/src/services/build-bus/hook-dispatch.test.ts +86 -116
- package/src/services/build-bus/hook-dispatch.ts +99 -121
- package/src/services/build-bus/hook-dispatcher.ts +143 -17
- package/src/services/build-bus/receiver-daemon.test.ts +189 -0
- package/src/services/build-bus/receiver-daemon.ts +355 -0
- package/src/services/build-bus/self-update.ts +156 -0
- package/src/services/bus-ensure-flow.test.ts +1 -1
- package/src/services/bus-interview-park.test.ts +2 -2
- package/src/services/bus-interview.test.ts +2 -2
- package/src/services/bus-secret-flow.test.ts +1 -1
- package/src/services/capability-compat.test.ts +90 -0
- package/src/services/capability-compat.ts +128 -0
- package/src/services/celilo-events.test.ts +1 -1
- package/src/services/celilo-mgmt-hooks.test.ts +23 -5
- package/src/services/container-service.test.ts +1 -1
- package/src/services/cross-module-read.test.ts +2 -2
- package/src/services/deploy-preflight.ts +7 -0
- package/src/services/deploy-terraform.ts +38 -1
- package/src/services/deployed-systems.ts +1 -1
- package/src/services/events-daemon.test.ts +57 -0
- package/src/services/events-daemon.ts +76 -0
- package/src/services/firewall-reach.ts +21 -8
- package/src/services/fleet-checks.test.ts +159 -4
- package/src/services/fleet-checks.ts +206 -3
- package/src/services/health-runner.test.ts +87 -2
- package/src/services/health-runner.ts +83 -16
- package/src/services/infrastructure-selector.test.ts +1 -1
- package/src/services/jail-exemptions.test.ts +125 -0
- package/src/services/jail-exemptions.ts +81 -0
- package/src/services/machine-pool.test.ts +1 -1
- package/src/services/module-deploy-prune.test.ts +89 -0
- package/src/services/module-deploy.ts +84 -129
- package/src/services/module-subscriptions.test.ts +2 -2
- package/src/services/module-types-drift.test.ts +1 -1
- package/src/services/module-validator/capability-versions.test.ts +13 -2
- package/src/services/network-discovery.test.ts +64 -1
- package/src/services/network-discovery.ts +30 -5
- package/src/services/responder-probe.test.ts +1 -1
- package/src/services/restore-from-file.test.ts +4 -4
- package/src/services/restore-preflight.test.ts +1 -1
- package/src/services/ssh-key-manager.test.ts +2 -2
- package/src/services/system-state-stage.test.ts +2 -2
- package/src/services/terraform-safety.test.ts +83 -0
- package/src/services/terraform-safety.ts +53 -0
- package/src/services/update/orchestrator.test.ts +3 -1
- package/src/test-utils/bus-responder.ts +1 -1
- package/tsconfig.json +2 -13
package/src/cli/fuel-gauge.ts
CHANGED
|
@@ -56,11 +56,19 @@ export class FuelGauge {
|
|
|
56
56
|
private linesDrawn = 0; // Track exactly how many lines were drawn last frame
|
|
57
57
|
private startTime = Date.now();
|
|
58
58
|
private lastOutputTime = Date.now();
|
|
59
|
+
/**
|
|
60
|
+
* Whether this gauge may draw frames on its output stream. A redirected
|
|
61
|
+
* or piped stream is a data channel, not a terminal (celilo#699), so the
|
|
62
|
+
* gauge tracks state silently there instead of writing frames into
|
|
63
|
+
* whatever file or protocol lies downstream (celilo#1362).
|
|
64
|
+
*/
|
|
65
|
+
private readonly interactive: boolean;
|
|
59
66
|
|
|
60
67
|
constructor(title: string, options: FuelGaugeOptions = {}) {
|
|
61
68
|
this.title = title;
|
|
62
69
|
this.output = options.output || stdout;
|
|
63
70
|
this.skipAnimation = options.skipAnimation || false;
|
|
71
|
+
this.interactive = !this.skipAnimation && Boolean(this.output.isTTY);
|
|
64
72
|
this.onBackground = options.onBackground;
|
|
65
73
|
}
|
|
66
74
|
|
|
@@ -68,8 +76,8 @@ export class FuelGauge {
|
|
|
68
76
|
* Start the fuel-gauge animation
|
|
69
77
|
*/
|
|
70
78
|
start(): void {
|
|
71
|
-
if (this.skipAnimation) {
|
|
72
|
-
// Test mode: just track state
|
|
79
|
+
if (this.skipAnimation || !this.interactive) {
|
|
80
|
+
// Test mode or non-interactive output: just track state
|
|
73
81
|
this.running = true;
|
|
74
82
|
return;
|
|
75
83
|
}
|
|
@@ -188,7 +196,7 @@ export class FuelGauge {
|
|
|
188
196
|
|
|
189
197
|
this.running = false;
|
|
190
198
|
|
|
191
|
-
if (this.
|
|
199
|
+
if (!this.interactive) {
|
|
192
200
|
return;
|
|
193
201
|
}
|
|
194
202
|
|
|
@@ -233,7 +241,7 @@ export class FuelGauge {
|
|
|
233
241
|
stopSilent(): void {
|
|
234
242
|
if (!this.running) return;
|
|
235
243
|
this.running = false;
|
|
236
|
-
if (this.
|
|
244
|
+
if (!this.interactive) return;
|
|
237
245
|
const display = getActiveDisplay();
|
|
238
246
|
if (display) {
|
|
239
247
|
display.abandon();
|
package/src/cli/index.ts
CHANGED
|
@@ -80,6 +80,7 @@ import { handleModuleDeploy } from './commands/module-deploy';
|
|
|
80
80
|
import { handleModuleGenerate } from './commands/module-generate';
|
|
81
81
|
import { handleModuleHealth } from './commands/module-health';
|
|
82
82
|
import { handleModuleImport } from './commands/module-import';
|
|
83
|
+
import { handleModuleJail } from './commands/module-jail';
|
|
83
84
|
import { handleModuleJournal } from './commands/module-journal';
|
|
84
85
|
import { handleModuleList } from './commands/module-list';
|
|
85
86
|
import { handleModuleLogs } from './commands/module-logs';
|
|
@@ -114,6 +115,10 @@ import { handleServiceSetCredentials } from './commands/service-set-credentials'
|
|
|
114
115
|
import { handleServiceVerify } from './commands/service-verify';
|
|
115
116
|
import { handleStatus } from './commands/status';
|
|
116
117
|
import { handleSubscribersAdd } from './commands/subscribers-add';
|
|
118
|
+
import {
|
|
119
|
+
handleSubscribersInstallDaemon,
|
|
120
|
+
handleSubscribersUninstallDaemon,
|
|
121
|
+
} from './commands/subscribers-install-daemon';
|
|
117
122
|
import { handleSubscribersList } from './commands/subscribers-list';
|
|
118
123
|
import { handleSubscribersRemove } from './commands/subscribers-remove';
|
|
119
124
|
import { handleSubscribersServe } from './commands/subscribers-serve';
|
|
@@ -315,6 +320,7 @@ Subcommands:
|
|
|
315
320
|
resync-subscriptions Rebuild subscribers from deployed modules' manifests (after a restore/migration)
|
|
316
321
|
list-pending [--subscriber] List pending deliveries
|
|
317
322
|
list-failed [--subscriber] List failed/abandoned deliveries with a true total
|
|
323
|
+
list-unanswered List interview questions nobody has answered yet
|
|
318
324
|
drain [--concurrency N] Process pending deliveries once and return
|
|
319
325
|
run [--poll-ms N] Run the long-running dispatcher (foreground)
|
|
320
326
|
emit <type> [<payload>] Emit an event (operator/test path)
|
|
@@ -363,6 +369,10 @@ Subcommands:
|
|
|
363
369
|
test <url> Fire a synthetic event at a subscriber; report delivery
|
|
364
370
|
serve --secret <hmac-secret> Run the receiver daemon (verifies + emits to local bus)
|
|
365
371
|
[--port <p>] [--no-dispatch]
|
|
372
|
+
install-daemon --secret <s> Write a supervisor unit for the receiver (celilo#1304:
|
|
373
|
+
[--system] [--port <p>] nothing else installs it). Runs serve --no-dispatch so the
|
|
374
|
+
[--dispatch] [--print] standing dispatcher keeps owning hook delivery.
|
|
375
|
+
uninstall-daemon [--system] Remove the receiver supervisor unit
|
|
366
376
|
status Per-subscriber delivery history (success rate, recent failures)
|
|
367
377
|
--help, -h Show this help
|
|
368
378
|
|
|
@@ -588,6 +598,32 @@ Subcommands:
|
|
|
588
598
|
--auto-generate-secrets Auto-generate all secrets without prompting
|
|
589
599
|
|
|
590
600
|
list List all installed modules
|
|
601
|
+
Options:
|
|
602
|
+
--json Output the module roster as stable JSON
|
|
603
|
+
|
|
604
|
+
status <id> Show detailed module status
|
|
605
|
+
|
|
606
|
+
where <id> Show which host(s) serve a module (host discovery)
|
|
607
|
+
Options:
|
|
608
|
+
--json Output host discovery as stable JSON
|
|
609
|
+
|
|
610
|
+
logs <id> Show deploy logs for a module
|
|
611
|
+
Options:
|
|
612
|
+
--tail <n> Show last N lines
|
|
613
|
+
--last Show only the most recent deploy run
|
|
614
|
+
|
|
615
|
+
journal <id> Tail a deployed module's runtime daemon journal on its host (no SSH)
|
|
616
|
+
Options:
|
|
617
|
+
--unit <glob> systemd unit or glob (default: <module-id>*)
|
|
618
|
+
--lines <n> Lines to return (default 100)
|
|
619
|
+
--since <window> Time window, e.g. '30 min ago'
|
|
620
|
+
--grep <text> Only lines containing this fixed string
|
|
621
|
+
--json Machine-readable JSON output
|
|
622
|
+
|
|
623
|
+
operations list|clear Show or release in-flight module operations (the backup/restore lock)
|
|
624
|
+
Options:
|
|
625
|
+
--all With clear: also release operations that still look alive
|
|
626
|
+
--abandoned With list: also show abandoned rows (hidden by default)
|
|
591
627
|
|
|
592
628
|
remove <id> Remove a module and all its data
|
|
593
629
|
|
|
@@ -605,8 +641,12 @@ Subcommands:
|
|
|
605
641
|
--dry-run Print the ordered plan and change nothing
|
|
606
642
|
--yes Skip the cascade confirmation
|
|
607
643
|
|
|
608
|
-
verify <id> Verify module integrity (signature + checksums)
|
|
609
|
-
|
|
644
|
+
verify <id> Verify module integrity (signature + checksums) (legacy alias: 'audit')
|
|
645
|
+
Options:
|
|
646
|
+
--deep Deep verification (re-check all files)
|
|
647
|
+
--json Machine-readable JSON output
|
|
648
|
+
|
|
649
|
+
validate <id> Pre-flight check: validate module is ready for deployment
|
|
610
650
|
|
|
611
651
|
config set <id> <key> <value> Set module configuration value
|
|
612
652
|
config get <id> [key] Get module configuration value(s)
|
|
@@ -616,6 +656,11 @@ Subcommands:
|
|
|
616
656
|
secret get <id> <key> Get a decrypted module secret value
|
|
617
657
|
secret list <id> List module secrets
|
|
618
658
|
|
|
659
|
+
jail <id> [policy] Get or set one module's hook jail policy (auto|off|required)
|
|
660
|
+
Options:
|
|
661
|
+
--clear Remove the module's policy; follow the system's hooks.jail_policy again
|
|
662
|
+
--force Skip the confirmation when the new policy is weaker than the system's
|
|
663
|
+
|
|
619
664
|
show-config <id> Show all config including auto-derived values
|
|
620
665
|
show-zone <id> Show module's network zone and config
|
|
621
666
|
|
|
@@ -647,6 +692,11 @@ Subcommands:
|
|
|
647
692
|
Options:
|
|
648
693
|
--registry <url> Use a custom registry (sweep mode only)
|
|
649
694
|
|
|
695
|
+
upgrade <id> Upgrade a module to its latest registry version (update, posture-gated backup, deploy, verify)
|
|
696
|
+
Options:
|
|
697
|
+
--poll Run the CD poll: upgrade every auto_upgrade module with a newer version
|
|
698
|
+
--registry <url> Registry URL (defaults to configured registry)
|
|
699
|
+
|
|
650
700
|
Registry:
|
|
651
701
|
search [query] Search the registry for modules
|
|
652
702
|
Options:
|
|
@@ -662,6 +712,19 @@ Registry:
|
|
|
662
712
|
--allow-dirty Permit publishing from a dirty git tree
|
|
663
713
|
--allow-stale Skip the manifest-vs-src stale-check. Use sparingly.
|
|
664
714
|
|
|
715
|
+
changeset <module-dir> Author a version changeset for a module (.changeset/<name>.md)
|
|
716
|
+
Options:
|
|
717
|
+
--bump <type> Version bump type: major, minor or patch (required)
|
|
718
|
+
--message <text> Changeset body text
|
|
719
|
+
--name <stem> Changeset filename stem (default: auto-generated slug)
|
|
720
|
+
|
|
721
|
+
backup <module-id> Create a backup of a module's data (invokes its on_backup hook)
|
|
722
|
+
Options:
|
|
723
|
+
--force Ignore schedule, back up now
|
|
724
|
+
--now Ignore schedule, back up now (alias for --force)
|
|
725
|
+
--storage <id> Storage destination ID
|
|
726
|
+
--no-interactive Non-interactive mode (for cron)
|
|
727
|
+
|
|
665
728
|
check [path] Check a module for drift against the framework (default: .)
|
|
666
729
|
Options:
|
|
667
730
|
--no-build Skip the TypeScript build check
|
|
@@ -836,6 +899,12 @@ Subcommands:
|
|
|
836
899
|
--storage <id> Use specific storage destination
|
|
837
900
|
--yes Skip confirmation prompt
|
|
838
901
|
|
|
902
|
+
pull Download another box's backup artifact from a storage destination
|
|
903
|
+
Options:
|
|
904
|
+
--storage <name> Storage destination name
|
|
905
|
+
--module <id> Module ID whose backup to pull (default: system state)
|
|
906
|
+
--output <path> Local path to write the artifact
|
|
907
|
+
|
|
839
908
|
Description:
|
|
840
909
|
Backups are encrypted with the master key and uploaded to a configured
|
|
841
910
|
storage destination. System state backups capture the Celilo database.
|
|
@@ -1056,6 +1125,7 @@ Usage:
|
|
|
1056
1125
|
Resources:
|
|
1057
1126
|
vmid Manage VMID reservations and allocations
|
|
1058
1127
|
ip Manage IP address reservations
|
|
1128
|
+
show Show comprehensive IPAM summary
|
|
1059
1129
|
list-allocations List all IPAM allocations (VMIDs and IPs)
|
|
1060
1130
|
|
|
1061
1131
|
VMID Commands:
|
|
@@ -1140,6 +1210,8 @@ Subcommands:
|
|
|
1140
1210
|
update [--module <id>] [--no-backup] [--allow-destructive] [--dry-run] [--json]
|
|
1141
1211
|
Bring the system to the audit-determined READY state
|
|
1142
1212
|
|
|
1213
|
+
migrate Apply pending database migrations (idempotent; safe to re-run)
|
|
1214
|
+
|
|
1143
1215
|
doctor [--fix] Diagnose system prerequisites and @celilo/* version drift
|
|
1144
1216
|
|
|
1145
1217
|
Runbook:
|
|
@@ -1609,6 +1681,8 @@ export async function runCli(argv: string[]): Promise<CommandResult> {
|
|
|
1609
1681
|
return handleModuleVersion(parsed.args, parsed.flags);
|
|
1610
1682
|
case 'changeset':
|
|
1611
1683
|
return handleModuleChangeset(parsed.args, parsed.flags);
|
|
1684
|
+
case 'jail':
|
|
1685
|
+
return handleModuleJail(parsed.args, parsed.flags);
|
|
1612
1686
|
case 'audit':
|
|
1613
1687
|
return moduleAudit(parsed.args, parsed.flags);
|
|
1614
1688
|
case 'verify':
|
|
@@ -1843,6 +1917,10 @@ export async function runCli(argv: string[]): Promise<CommandResult> {
|
|
|
1843
1917
|
return handleSubscribersTest(parsed.args);
|
|
1844
1918
|
case 'serve':
|
|
1845
1919
|
return handleSubscribersServe(parsed.args, parsed.flags);
|
|
1920
|
+
case 'install-daemon':
|
|
1921
|
+
return handleSubscribersInstallDaemon(parsed.args, parsed.flags);
|
|
1922
|
+
case 'uninstall-daemon':
|
|
1923
|
+
return handleSubscribersUninstallDaemon(parsed.args, parsed.flags);
|
|
1846
1924
|
case 'status':
|
|
1847
1925
|
return handleSubscribersStatus();
|
|
1848
1926
|
default:
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
* have failed anything. See celilo#703.
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
|
+
import { Database } from 'bun:sqlite';
|
|
30
31
|
import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
|
|
31
32
|
import { spawnSync } from 'node:child_process';
|
|
32
33
|
import { readFileSync, readdirSync } from 'node:fs';
|
|
@@ -49,6 +50,7 @@ const JSON_COMMANDS = [
|
|
|
49
50
|
'module list --json',
|
|
50
51
|
'module health --json',
|
|
51
52
|
'system audit --json',
|
|
53
|
+
'system update --dry-run --json',
|
|
52
54
|
'alerts list --json',
|
|
53
55
|
'commands --json',
|
|
54
56
|
] as const;
|
|
@@ -90,6 +92,85 @@ describe('celilo#698 — JSON commands emit parseable JSON', () => {
|
|
|
90
92
|
}
|
|
91
93
|
});
|
|
92
94
|
|
|
95
|
+
/**
|
|
96
|
+
* celilo#1362 — a --json command must stay parseable while its health checks
|
|
97
|
+
* run. The celilo#698 gates above all fire against an EMPTY fleet, where no
|
|
98
|
+
* health check ever executes and the pollution path never wakes up: the
|
|
99
|
+
* gauge silently animated frames onto stdout (plus plain `[module:hook]`
|
|
100
|
+
* logger lines) for every check, and `system audit --json` redirected to a
|
|
101
|
+
* file carried ~1 MB of `Testing app` frames before the first `{`.
|
|
102
|
+
*
|
|
103
|
+
* This gate seeds a module with a logging, sleeping health_check hook
|
|
104
|
+
* (`test-fixtures/modules/gauge-pollution-test`) so the checks genuinely
|
|
105
|
+
* run, then parses each fleet-wide --json command's stdout verbatim.
|
|
106
|
+
*/
|
|
107
|
+
describe('celilo#1362 — --json output stays parseable while health checks run', () => {
|
|
108
|
+
let ctx: IntegrationTestContext;
|
|
109
|
+
const FIXTURE_PATH = join(import.meta.dir, '../../test-fixtures/modules/gauge-pollution-test');
|
|
110
|
+
const MODULE_ID = 'gauge-pollution-test';
|
|
111
|
+
const SPAWN_ENV = () => ({
|
|
112
|
+
...process.env,
|
|
113
|
+
CELILO_DB_PATH: ctx.dbPath,
|
|
114
|
+
CELILO_DATA_DIR: ctx.dataDir,
|
|
115
|
+
CELILO_SUPPRESS_DEPRECATION: '1',
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
beforeAll(async () => {
|
|
119
|
+
ctx = await setupIntegrationTest();
|
|
120
|
+
|
|
121
|
+
const imported = spawnSync(
|
|
122
|
+
'bun',
|
|
123
|
+
['run', 'src/cli/index.ts', 'module', 'import', FIXTURE_PATH],
|
|
124
|
+
{ encoding: 'utf8', env: SPAWN_ENV(), stdio: ['ignore', 'pipe', 'pipe'], timeout: 120_000 },
|
|
125
|
+
);
|
|
126
|
+
expect(
|
|
127
|
+
imported.status,
|
|
128
|
+
`module import exited ${imported.status}\nstdout: ${imported.stdout}\nstderr: ${imported.stderr}`,
|
|
129
|
+
).toBe(0);
|
|
130
|
+
|
|
131
|
+
// runAllHealthChecks only checks INSTALLED/VERIFIED modules. Seeded
|
|
132
|
+
// the same way capability-abi-mismatch.test.ts seeds providers: the
|
|
133
|
+
// lifecycle under test is output purity, not the deploy flow.
|
|
134
|
+
const db = new Database(ctx.dbPath);
|
|
135
|
+
try {
|
|
136
|
+
db.run(`UPDATE modules SET state = 'VERIFIED' WHERE id = '${MODULE_ID}'`);
|
|
137
|
+
} finally {
|
|
138
|
+
db.close();
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
afterAll(async () => {
|
|
143
|
+
await ctx.cleanup();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
for (const command of [
|
|
147
|
+
'system audit --json',
|
|
148
|
+
'system update --dry-run --json',
|
|
149
|
+
'module health --json',
|
|
150
|
+
]) {
|
|
151
|
+
test(`celilo ${command} emits parseable JSON with a health-checked module deployed`, () => {
|
|
152
|
+
const result = spawnSync('bun', ['run', 'src/cli/index.ts', ...command.split(' ')], {
|
|
153
|
+
encoding: 'utf8',
|
|
154
|
+
env: SPAWN_ENV(),
|
|
155
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
156
|
+
timeout: 120_000,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
expect(
|
|
160
|
+
result.status,
|
|
161
|
+
`celilo ${command} exited ${result.status}\nstderr: ${result.stderr}`,
|
|
162
|
+
).toBe(0);
|
|
163
|
+
|
|
164
|
+
// Verbatim JSON.parse — the gauge frames and plain logger lines both
|
|
165
|
+
// land here pre-fix, and either one alone breaks the parse.
|
|
166
|
+
expect(() => JSON.parse(result.stdout)).not.toThrow();
|
|
167
|
+
|
|
168
|
+
// And the fixture's own log lines must not ride along on stdout.
|
|
169
|
+
expect(result.stdout).not.toContain('gauge-pollution step');
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
93
174
|
/**
|
|
94
175
|
* Walk from the index of a `{` to the index just past its matching `}`,
|
|
95
176
|
* skipping over string literals so a brace inside a string doesn't unbalance
|
package/src/cli/parser.test.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
import {
|
|
2
|
+
import { COMMANDS } from '@celilo/core';
|
|
3
|
+
import {
|
|
4
|
+
getArg,
|
|
5
|
+
getFlag,
|
|
6
|
+
hasFlag,
|
|
7
|
+
parseArguments,
|
|
8
|
+
validateFlags,
|
|
9
|
+
validateRequiredArgs,
|
|
10
|
+
} from './parser';
|
|
3
11
|
|
|
4
12
|
describe('CLI Parser', () => {
|
|
5
13
|
describe('parseArguments', () => {
|
|
@@ -238,3 +246,31 @@ describe('CLI Parser', () => {
|
|
|
238
246
|
});
|
|
239
247
|
});
|
|
240
248
|
});
|
|
249
|
+
|
|
250
|
+
describe('a flag a handler reads is a flag the registry declares', () => {
|
|
251
|
+
// `handleSystemConfigSet` has read `flags.force` since the hooks.jail_policy
|
|
252
|
+
// interview gate landed, and its unit test drove the handler directly with
|
|
253
|
+
// `{ force: true }`. The registry declared no flags for `system config set`,
|
|
254
|
+
// so the parser answered `--force` with "does not accept any flags" and the
|
|
255
|
+
// escape hatch was tested and unreachable from a terminal. The suite could
|
|
256
|
+
// not see it, because nothing in it went through the parser.
|
|
257
|
+
const leaf = (path: readonly string[]) =>
|
|
258
|
+
path.slice(1).reduce<{ name: string; subcommands?: unknown[] } | undefined>(
|
|
259
|
+
(def, name) =>
|
|
260
|
+
(def as { subcommands?: { name: string }[] } | undefined)?.subcommands?.find(
|
|
261
|
+
(s) => s.name === name,
|
|
262
|
+
),
|
|
263
|
+
COMMANDS.find((c) => c.name === path[0]),
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
test('system config set accepts --force through the real registry', () => {
|
|
267
|
+
const def = leaf(['system', 'config', 'set']);
|
|
268
|
+
expect(def).toBeDefined();
|
|
269
|
+
expect(validateFlags({ force: true }, def as never)).toBeNull();
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test('and still refuses a flag nobody declared', () => {
|
|
273
|
+
const def = leaf(['system', 'config', 'set']);
|
|
274
|
+
expect(validateFlags({ frce: true }, def as never)).toContain('Unknown flag');
|
|
275
|
+
});
|
|
276
|
+
});
|
|
@@ -34,9 +34,9 @@ describe('celilo restore', () => {
|
|
|
34
34
|
afterEach(() => {
|
|
35
35
|
closeDb();
|
|
36
36
|
resetTestDbPath();
|
|
37
|
-
process.env.CELILO_DATA_DIR
|
|
38
|
-
process.env.CELILO_MASTER_KEY_PATH
|
|
39
|
-
process.env.CELILO_SUPPRESS_DEPRECATION
|
|
37
|
+
delete process.env.CELILO_DATA_DIR;
|
|
38
|
+
delete process.env.CELILO_MASTER_KEY_PATH;
|
|
39
|
+
delete process.env.CELILO_SUPPRESS_DEPRECATION;
|
|
40
40
|
try {
|
|
41
41
|
rmSync(dir, { recursive: true, force: true });
|
|
42
42
|
} catch {
|
|
@@ -99,8 +99,8 @@ describe('restore migration failure reporting (celilo#1269)', () => {
|
|
|
99
99
|
afterEach(() => {
|
|
100
100
|
closeDb();
|
|
101
101
|
resetTestDbPath();
|
|
102
|
-
process.env.CELILO_DATA_DIR
|
|
103
|
-
process.env.CELILO_MASTER_KEY_PATH
|
|
102
|
+
delete process.env.CELILO_DATA_DIR;
|
|
103
|
+
delete process.env.CELILO_MASTER_KEY_PATH;
|
|
104
104
|
try {
|
|
105
105
|
rmSync(dir, { recursive: true, force: true });
|
|
106
106
|
} catch {
|
|
@@ -92,11 +92,13 @@ export const ALL_CATEGORIES: readonly DriftCategory[] = [
|
|
|
92
92
|
'interface_classification',
|
|
93
93
|
'module_integrity',
|
|
94
94
|
'detect_without_converge',
|
|
95
|
+
'jail_exemptions',
|
|
95
96
|
];
|
|
96
97
|
|
|
97
98
|
export const CATEGORY_LABELS: Record<DriftCategory, string> = {
|
|
98
99
|
module_integrity: 'Module integrity',
|
|
99
100
|
detect_without_converge: 'Drift without converge',
|
|
101
|
+
jail_exemptions: 'Hook jail exemptions',
|
|
100
102
|
interface_classification: 'Firewall interfaces',
|
|
101
103
|
cli_version: 'CLI version',
|
|
102
104
|
schema: 'Schema migrations',
|
package/src/cli/types.ts
CHANGED
|
@@ -35,6 +35,15 @@ export interface CommandError {
|
|
|
35
35
|
success: false;
|
|
36
36
|
error: string;
|
|
37
37
|
details?: unknown;
|
|
38
|
+
/**
|
|
39
|
+
* Marks a refusal that is not a failure: the action was skipped BEFORE any
|
|
40
|
+
* state changed and will be retried when its precondition holds (the
|
|
41
|
+
* registry poll defers a consumer upgrade whose capability requirements the
|
|
42
|
+
* deployed providers cannot serve — celilo#1361). Callers that read only
|
|
43
|
+
* `success` still see a non-deploy; the poll uses this to report deferrals
|
|
44
|
+
* separately from failures instead of counting them as red.
|
|
45
|
+
*/
|
|
46
|
+
deferred?: boolean;
|
|
38
47
|
}
|
|
39
48
|
|
|
40
49
|
export type CommandResult = CommandSuccess | CommandError;
|
package/src/config/paths.test.ts
CHANGED
|
@@ -21,14 +21,14 @@ describe('paths configuration', () => {
|
|
|
21
21
|
describe('getModuleStoragePath', () => {
|
|
22
22
|
it('returns custom path when CELILO_DATA_DIR is set', () => {
|
|
23
23
|
process.env.CELILO_DATA_DIR = '/custom/data';
|
|
24
|
-
process.env.ENVIRONMENT
|
|
24
|
+
delete process.env.ENVIRONMENT;
|
|
25
25
|
|
|
26
26
|
const path = getModuleStoragePath();
|
|
27
27
|
expect(path).toBe('/custom/data/modules');
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
it('returns dev path when ENVIRONMENT=dev', () => {
|
|
31
|
-
process.env.CELILO_DATA_DIR
|
|
31
|
+
delete process.env.CELILO_DATA_DIR;
|
|
32
32
|
process.env.ENVIRONMENT = 'dev';
|
|
33
33
|
|
|
34
34
|
const path = getModuleStoragePath();
|
|
@@ -38,8 +38,8 @@ describe('paths configuration', () => {
|
|
|
38
38
|
it.skipIf(skipIntegration({ platform: 'darwin' }))(
|
|
39
39
|
'returns platform-specific path by default',
|
|
40
40
|
() => {
|
|
41
|
-
process.env.CELILO_DATA_DIR
|
|
42
|
-
process.env.ENVIRONMENT
|
|
41
|
+
delete process.env.CELILO_DATA_DIR;
|
|
42
|
+
delete process.env.ENVIRONMENT;
|
|
43
43
|
|
|
44
44
|
const path = getModuleStoragePath();
|
|
45
45
|
|
|
@@ -63,14 +63,14 @@ describe('paths configuration', () => {
|
|
|
63
63
|
describe('getDataDir', () => {
|
|
64
64
|
it('returns custom path when CELILO_DATA_DIR is set', () => {
|
|
65
65
|
process.env.CELILO_DATA_DIR = '/custom/data';
|
|
66
|
-
process.env.ENVIRONMENT
|
|
66
|
+
delete process.env.ENVIRONMENT;
|
|
67
67
|
|
|
68
68
|
const path = getDataDir();
|
|
69
69
|
expect(path).toBe('/custom/data');
|
|
70
70
|
});
|
|
71
71
|
|
|
72
72
|
it('returns dev path when ENVIRONMENT=dev', () => {
|
|
73
|
-
process.env.CELILO_DATA_DIR
|
|
73
|
+
delete process.env.CELILO_DATA_DIR;
|
|
74
74
|
process.env.ENVIRONMENT = 'dev';
|
|
75
75
|
|
|
76
76
|
const path = getDataDir();
|
|
@@ -80,8 +80,8 @@ describe('paths configuration', () => {
|
|
|
80
80
|
it.skipIf(skipIntegration({ platform: 'darwin' }))(
|
|
81
81
|
'returns platform-specific path by default',
|
|
82
82
|
() => {
|
|
83
|
-
process.env.CELILO_DATA_DIR
|
|
84
|
-
process.env.ENVIRONMENT
|
|
83
|
+
delete process.env.CELILO_DATA_DIR;
|
|
84
|
+
delete process.env.ENVIRONMENT;
|
|
85
85
|
|
|
86
86
|
const path = getDataDir();
|
|
87
87
|
|
|
@@ -103,7 +103,7 @@ describe('paths configuration', () => {
|
|
|
103
103
|
});
|
|
104
104
|
|
|
105
105
|
it('returns data dir + master.key when CELILO_MASTER_KEY_PATH not set', () => {
|
|
106
|
-
process.env.CELILO_MASTER_KEY_PATH
|
|
106
|
+
delete process.env.CELILO_MASTER_KEY_PATH;
|
|
107
107
|
process.env.CELILO_DATA_DIR = '/custom/data';
|
|
108
108
|
|
|
109
109
|
const path = getMasterKeyPath();
|
|
@@ -113,9 +113,9 @@ describe('paths configuration', () => {
|
|
|
113
113
|
it.skipIf(skipIntegration({ platform: 'darwin' }))(
|
|
114
114
|
'uses platform-specific data dir by default',
|
|
115
115
|
() => {
|
|
116
|
-
process.env.CELILO_MASTER_KEY_PATH
|
|
117
|
-
process.env.CELILO_DATA_DIR
|
|
118
|
-
process.env.ENVIRONMENT
|
|
116
|
+
delete process.env.CELILO_MASTER_KEY_PATH;
|
|
117
|
+
delete process.env.CELILO_DATA_DIR;
|
|
118
|
+
delete process.env.ENVIRONMENT;
|
|
119
119
|
|
|
120
120
|
const path = getMasterKeyPath();
|
|
121
121
|
|
|
@@ -131,8 +131,8 @@ describe('paths configuration', () => {
|
|
|
131
131
|
describe('getDbPath', () => {
|
|
132
132
|
it('returns custom path when CELILO_DB_PATH is set', () => {
|
|
133
133
|
process.env.CELILO_DB_PATH = '/custom/path/celilo.db';
|
|
134
|
-
process.env.CELILO_DATA_DIR
|
|
135
|
-
process.env.ENVIRONMENT
|
|
134
|
+
delete process.env.CELILO_DATA_DIR;
|
|
135
|
+
delete process.env.ENVIRONMENT;
|
|
136
136
|
|
|
137
137
|
const path = getDbPath();
|
|
138
138
|
expect(path).toBe('/custom/path/celilo.db');
|
|
@@ -142,8 +142,8 @@ describe('paths configuration', () => {
|
|
|
142
142
|
'returns data dir + celilo.db on macOS in production',
|
|
143
143
|
() => {
|
|
144
144
|
delete process.env.CELILO_DB_PATH;
|
|
145
|
-
process.env.CELILO_DATA_DIR
|
|
146
|
-
process.env.ENVIRONMENT
|
|
145
|
+
delete process.env.CELILO_DATA_DIR;
|
|
146
|
+
delete process.env.ENVIRONMENT;
|
|
147
147
|
|
|
148
148
|
const path = getDbPath();
|
|
149
149
|
|
|
@@ -157,7 +157,7 @@ describe('paths configuration', () => {
|
|
|
157
157
|
|
|
158
158
|
it('returns celilo-data/celilo.db in development mode', () => {
|
|
159
159
|
delete process.env.CELILO_DB_PATH;
|
|
160
|
-
process.env.CELILO_DATA_DIR
|
|
160
|
+
delete process.env.CELILO_DATA_DIR;
|
|
161
161
|
process.env.ENVIRONMENT = 'dev';
|
|
162
162
|
|
|
163
163
|
const path = getDbPath();
|
|
@@ -167,7 +167,7 @@ describe('paths configuration', () => {
|
|
|
167
167
|
it('respects CELILO_DATA_DIR override', () => {
|
|
168
168
|
delete process.env.CELILO_DB_PATH;
|
|
169
169
|
process.env.CELILO_DATA_DIR = '/custom/data';
|
|
170
|
-
process.env.ENVIRONMENT
|
|
170
|
+
delete process.env.ENVIRONMENT;
|
|
171
171
|
|
|
172
172
|
const path = getDbPath();
|
|
173
173
|
expect(path).toBe('/custom/data/celilo.db');
|
|
@@ -176,7 +176,7 @@ describe('paths configuration', () => {
|
|
|
176
176
|
it('prioritizes CELILO_DB_PATH over CELILO_DATA_DIR', () => {
|
|
177
177
|
process.env.CELILO_DB_PATH = '/explicit/db.db';
|
|
178
178
|
process.env.CELILO_DATA_DIR = '/custom/data';
|
|
179
|
-
process.env.ENVIRONMENT
|
|
179
|
+
delete process.env.ENVIRONMENT;
|
|
180
180
|
|
|
181
181
|
const path = getDbPath();
|
|
182
182
|
expect(path).toBe('/explicit/db.db');
|
package/src/db/client.test.ts
CHANGED
|
@@ -3,7 +3,8 @@ import { afterEach, describe, expect, test } from 'bun:test';
|
|
|
3
3
|
import { rmSync } from 'node:fs';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
|
-
import {
|
|
6
|
+
import { resetTestDbPath } from '../test-utils/db-path';
|
|
7
|
+
import { closeDb, createDbClient, getDb } from './client';
|
|
7
8
|
|
|
8
9
|
// #798: a command that opened the db while a deploy still held it died on
|
|
9
10
|
// SQLITE_BUSY immediately, because the connection carried no busy timeout.
|
|
@@ -61,3 +62,47 @@ describe('createDbClient concurrency', () => {
|
|
|
61
62
|
}
|
|
62
63
|
});
|
|
63
64
|
});
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The tripwire from celilo#1343: under bun test, a default-path open must be
|
|
68
|
+
* refused instead of landing on the operator's real database. The scratch-DB
|
|
69
|
+
* preload (apps/celilo/test-preload.ts) arms CELILO_DB_PATH only when bun
|
|
70
|
+
* runs from apps/celilo/, so every one of these tests strips the redirects
|
|
71
|
+
* itself to reproduce the out-of-scope state.
|
|
72
|
+
*/
|
|
73
|
+
describe('createDbClient operator-database tripwire (celilo#1343)', () => {
|
|
74
|
+
afterEach(() => {
|
|
75
|
+
closeDb();
|
|
76
|
+
resetTestDbPath();
|
|
77
|
+
delete process.env.CELILO_DATA_DIR;
|
|
78
|
+
delete process.env.ENVIRONMENT;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('default-path open under bun test throws, naming the fix', () => {
|
|
82
|
+
delete process.env.CELILO_DB_PATH;
|
|
83
|
+
delete process.env.CELILO_DATA_DIR;
|
|
84
|
+
delete process.env.ENVIRONMENT;
|
|
85
|
+
|
|
86
|
+
expect(() => getDb()).toThrow(/CELILO_DB_PATH/);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('CELILO_DATA_DIR redirect keeps the default-path open allowed', () => {
|
|
90
|
+
delete process.env.CELILO_DB_PATH;
|
|
91
|
+
delete process.env.ENVIRONMENT;
|
|
92
|
+
process.env.CELILO_DATA_DIR = join(tmpdir(), 'celilo-tripwire-redirect');
|
|
93
|
+
|
|
94
|
+
const db = createDbClient();
|
|
95
|
+
db.$client.close();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('explicit config path bypasses the tripwire', () => {
|
|
99
|
+
delete process.env.CELILO_DB_PATH;
|
|
100
|
+
delete process.env.CELILO_DATA_DIR;
|
|
101
|
+
delete process.env.ENVIRONMENT;
|
|
102
|
+
|
|
103
|
+
const db = createDbClient({
|
|
104
|
+
path: join(tmpdir(), 'celilo-tripwire-explicit', 'celilo.db'),
|
|
105
|
+
});
|
|
106
|
+
db.$client.close();
|
|
107
|
+
});
|
|
108
|
+
});
|
package/src/db/client.ts
CHANGED
|
@@ -49,6 +49,32 @@ export function createDbClient(config?: Partial<DatabaseConfig>) {
|
|
|
49
49
|
const dbPath = config?.path ?? getDbPath();
|
|
50
50
|
const readonly = config?.readonly ?? false;
|
|
51
51
|
|
|
52
|
+
// Tripwire against opening the operator's real database from a test
|
|
53
|
+
// (celilo#1343). The scratch-DB preload in apps/celilo/test-preload.ts only
|
|
54
|
+
// arms when bun runs from apps/celilo/ — bun reads the bunfig at the working
|
|
55
|
+
// directory — so the same suite run from the repo root reaches this function
|
|
56
|
+
// with every override unset and would open, and migrate, the operator's
|
|
57
|
+
// ~/Library/.../celilo.db. The condition below is exactly that resolution:
|
|
58
|
+
// under bun test, with every redirect unset, getDbPath() falls through to
|
|
59
|
+
// the platform data directory. A run that is properly scoped (preload armed,
|
|
60
|
+
// CELILO_DATA_DIR redirected, or an explicit config.path) never satisfies
|
|
61
|
+
// it and pays one string comparison.
|
|
62
|
+
if (
|
|
63
|
+
!config?.path &&
|
|
64
|
+
process.env.NODE_ENV === 'test' &&
|
|
65
|
+
!process.env.CELILO_DB_PATH &&
|
|
66
|
+
!process.env.CELILO_DATA_DIR &&
|
|
67
|
+
process.env.ENVIRONMENT !== 'dev'
|
|
68
|
+
) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
'Refusing to open the celilo database under bun test with no database path set ' +
|
|
71
|
+
'(celilo#1343): the scratch-DB preload in apps/celilo/test-preload.ts only arms ' +
|
|
72
|
+
"when bun runs from apps/celilo/, so this default path is the operator's real " +
|
|
73
|
+
'celilo.db. Run tests from apps/celilo/, or set CELILO_DB_PATH (or ' +
|
|
74
|
+
'CELILO_DATA_DIR) to a test-scoped path.',
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
52
78
|
// bun:sqlite's `create: true` makes the file but not the parent directory.
|
|
53
79
|
// First-run after a fresh install hits this — without recursive mkdir we
|
|
54
80
|
// get SQLITE_CANTOPEN before init can write its config.
|