@rehearsal-db/core 0.1.0-beta.1 → 0.1.0-beta.10
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 +244 -1
- package/COMPATIBILITY.md +17 -0
- package/README.md +139 -346
- package/SECURITY.md +2 -1
- package/SUPPORT.md +5 -0
- package/docs/README.md +29 -0
- package/docs/adapters.md +20 -6
- package/docs/architecture.md +58 -0
- package/docs/baselines.md +102 -22
- package/docs/commands.md +196 -33
- package/docs/configuration.md +258 -13
- package/docs/getting-started.md +106 -182
- package/docs/glossary.md +11 -8
- package/docs/production-source.md +176 -33
- package/docs/releasing.md +28 -38
- package/docs/roadmap.md +41 -0
- package/docs/runtime-policies.md +193 -0
- package/docs/sanitization.md +43 -8
- package/docs/security-model.md +34 -9
- package/docs/standalone-workflow.md +107 -0
- package/docs/troubleshooting.md +102 -2
- package/docs/tutorial.md +70 -59
- package/package.json +30 -22
- package/scripts/runtime/manage_database.mjs +18 -0
- package/src/README.md +17 -0
- package/src/application/session.mjs +435 -0
- package/{scripts/lib/rehearsal/baseline_artifact.mjs → src/baseline/artifact.mjs} +142 -17
- package/{scripts/lib/rehearsal/baseline_builder.mjs → src/baseline/builder.mjs} +19 -10
- package/src/baseline/input_discovery.mjs +155 -0
- package/src/baseline/policy_review.mjs +92 -0
- package/src/baseline/preparation.mjs +323 -0
- package/src/baseline/privacy_engine.mjs +413 -0
- package/{scripts/lib/rehearsal → src/baseline}/sanitization_policy.mjs +133 -3
- package/{scripts/lib/rehearsal → src/baseline}/schema_snapshot.mjs +1 -1
- package/src/cli/arguments.mjs +123 -0
- package/src/cli/guided.mjs +812 -0
- package/src/cli/rehearsal.mjs +997 -0
- package/src/cli/renderers.mjs +584 -0
- package/src/cli/runtime_commands.mjs +624 -0
- package/src/cli/source_commands.mjs +326 -0
- package/src/cli/terminal.mjs +275 -0
- package/src/identity/claim.mjs +975 -0
- package/src/identity/storage.mjs +165 -0
- package/{scripts/lib/rehearsal → src/project}/configuration.d.mts +72 -4
- package/src/project/configuration.mjs +1199 -0
- package/src/project/setup.mjs +379 -0
- package/src/project/support_report.mjs +105 -0
- package/src/runtime/cleanup.mjs +381 -0
- package/{scripts/lib/rehearsal → src/runtime}/plan.mjs +118 -20
- package/src/runtime/policy.mjs +438 -0
- package/{scripts/lib/rehearsal/runtime_restore.mjs → src/runtime/restore.mjs} +46 -27
- package/src/runtime/topology.mjs +177 -0
- package/{scripts/lib/rehearsal → src/shared}/diagnostics.mjs +10 -3
- package/src/shared/human_output.mjs +4 -0
- package/src/shared/operation_guard.mjs +162 -0
- package/{scripts/lib/rehearsal → src/shared}/process_environment.mjs +5 -1
- package/src/source/access.mjs +578 -0
- package/src/source/asset_transfer.mjs +177 -0
- package/src/source/baseline.mjs +446 -0
- package/src/source/postgresql_access.mjs +480 -0
- package/src/targets/postgresql.mjs +808 -0
- package/{scripts/operations/database/manage_rehearsal_database.mjs → src/targets/supabase.mjs} +66 -20
- package/{scripts/lib/environment/local_supabase.mjs → src/targets/supabase_environment.mjs} +60 -28
- package/src/targets/target.mjs +66 -0
- package/scripts/lib/rehearsal/configuration.mjs +0 -559
- package/scripts/operations/rehearsal/rehearsal_cli.mjs +0 -565
- /package/{scripts/lib/rehearsal → src/runtime}/migration_history.mjs +0 -0
- /package/{scripts/lib/rehearsal → src/runtime}/service_environment.mjs +0 -0
package/{scripts/operations/database/manage_rehearsal_database.mjs → src/targets/supabase.mjs}
RENAMED
|
@@ -16,11 +16,11 @@ import { pathToFileURL } from "node:url";
|
|
|
16
16
|
import {
|
|
17
17
|
resolveActiveBaselinePaths,
|
|
18
18
|
verifyActiveBaseline,
|
|
19
|
-
} from "
|
|
19
|
+
} from "../baseline/artifact.mjs";
|
|
20
20
|
import {
|
|
21
21
|
createCleanProcessEnvironment,
|
|
22
22
|
assertLoopbackUrl,
|
|
23
|
-
} from "
|
|
23
|
+
} from "../shared/process_environment.mjs";
|
|
24
24
|
import {
|
|
25
25
|
localCommandSucceeds,
|
|
26
26
|
readLocalSupabaseEnvironment,
|
|
@@ -28,30 +28,42 @@ import {
|
|
|
28
28
|
runLocalCommand,
|
|
29
29
|
startLocalSupabase,
|
|
30
30
|
stopLocalSupabase,
|
|
31
|
-
} from "
|
|
31
|
+
} from "./supabase_environment.mjs";
|
|
32
32
|
import {
|
|
33
33
|
buildRestoreSqlPrefix,
|
|
34
34
|
buildRestoreSqlSuffix,
|
|
35
35
|
createCandidateMigrationReceipt,
|
|
36
36
|
encodeBaselineRecordForCopy,
|
|
37
37
|
summarizeRestoreError,
|
|
38
|
-
} from "
|
|
38
|
+
} from "../runtime/restore.mjs";
|
|
39
39
|
import {
|
|
40
40
|
buildMigrationLedgerInventory,
|
|
41
41
|
compareSourceToReplay,
|
|
42
42
|
createMigrationReplayReceipt,
|
|
43
43
|
readMigrationFileInventory,
|
|
44
|
-
} from "
|
|
45
|
-
import { loadRehearsalConfig } from "
|
|
46
|
-
import { readRehearsalServiceEnvironment } from "
|
|
44
|
+
} from "../runtime/migration_history.mjs";
|
|
45
|
+
import { loadRehearsalConfig } from "../project/configuration.mjs";
|
|
46
|
+
import { readRehearsalServiceEnvironment } from "../runtime/service_environment.mjs";
|
|
47
|
+
import { readBoundRuntimeSanitizationPolicy } from "../baseline/sanitization_policy.mjs";
|
|
47
48
|
import {
|
|
48
49
|
captureLocalPublicSchema,
|
|
49
50
|
compareProductionSchemaDumps,
|
|
50
|
-
} from "
|
|
51
|
+
} from "../baseline/schema_snapshot.mjs";
|
|
52
|
+
import { formatCount } from "../shared/human_output.mjs";
|
|
53
|
+
import { parseRuntimeInvocation } from "./target.mjs";
|
|
54
|
+
import {
|
|
55
|
+
buildRuntimeFinalizationSql,
|
|
56
|
+
buildRuntimePostSchemaSql,
|
|
57
|
+
buildRuntimePrerequisiteSql,
|
|
58
|
+
buildRuntimeVerificationSql,
|
|
59
|
+
validateRuntimePolicy,
|
|
60
|
+
} from "../runtime/policy.mjs";
|
|
51
61
|
|
|
52
62
|
const repositoryRoot = process.cwd();
|
|
63
|
+
const invocation = parseRuntimeInvocation();
|
|
53
64
|
const loadedConfiguration = await loadRehearsalConfig({
|
|
54
65
|
projectRoot: repositoryRoot,
|
|
66
|
+
configPath: invocation.configPath,
|
|
55
67
|
});
|
|
56
68
|
const { config, paths: configuredPaths } = loadedConfiguration;
|
|
57
69
|
const artifactRoot = configuredPaths.artifactDirectory;
|
|
@@ -73,8 +85,13 @@ const projectId = config.runtime.projectId;
|
|
|
73
85
|
const runtimeAdapter = configuredPaths.runtimeAdapter
|
|
74
86
|
? await import(pathToFileURL(configuredPaths.runtimeAdapter).href)
|
|
75
87
|
: null;
|
|
88
|
+
const runtimePolicy = configuredPaths.runtimePolicy
|
|
89
|
+
? validateRuntimePolicy(
|
|
90
|
+
JSON.parse(await readFile(configuredPaths.runtimePolicy, "utf8")),
|
|
91
|
+
)
|
|
92
|
+
: null;
|
|
76
93
|
const environmentKeyPattern = /^[A-Z][A-Z0-9_]*$/u;
|
|
77
|
-
const action =
|
|
94
|
+
const action = invocation.action;
|
|
78
95
|
|
|
79
96
|
const readServiceEnvironment = () =>
|
|
80
97
|
readRehearsalServiceEnvironment({
|
|
@@ -329,6 +346,12 @@ const writeRuntimeEnvironment = async ({
|
|
|
329
346
|
const variables = {
|
|
330
347
|
REHEARSAL_RUNTIME_ENVIRONMENT: "rehearsal",
|
|
331
348
|
REHEARSAL_BASELINE_ID: baseline.generationId,
|
|
349
|
+
DATABASE_URL: environment.databaseUrl,
|
|
350
|
+
PGHOST: environment.databaseHost,
|
|
351
|
+
PGPORT: environment.databasePort,
|
|
352
|
+
PGDATABASE: environment.databaseName,
|
|
353
|
+
PGUSER: environment.databaseUser,
|
|
354
|
+
PGPASSWORD: environment.databasePassword,
|
|
332
355
|
SUPABASE_URL: environment.apiUrl,
|
|
333
356
|
SUPABASE_PUBLISHABLE_KEY: environment.publishableKey,
|
|
334
357
|
SUPABASE_SERVICE_ROLE_KEY: environment.serviceRoleKey,
|
|
@@ -370,7 +393,13 @@ const runDatabaseSql = (sql) => {
|
|
|
370
393
|
|
|
371
394
|
const restoreProductionSchema = async ({ baseline, paths }) => {
|
|
372
395
|
const receipt = baseline.files?.["production-schema.sql"];
|
|
373
|
-
if (baseline.formatVersion === 1 && receipt === undefined)
|
|
396
|
+
if (baseline.formatVersion === 1 && receipt === undefined) {
|
|
397
|
+
if (runtimePolicy) {
|
|
398
|
+
runDatabaseSql(buildRuntimePrerequisiteSql(runtimePolicy));
|
|
399
|
+
runDatabaseSql(buildRuntimePostSchemaSql(runtimePolicy));
|
|
400
|
+
}
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
374
403
|
if (
|
|
375
404
|
baseline.formatVersion !== 2 ||
|
|
376
405
|
!/^[a-f0-9]{64}$/u.test(receipt?.sha256 ?? "") ||
|
|
@@ -381,7 +410,12 @@ const restoreProductionSchema = async ({ baseline, paths }) => {
|
|
|
381
410
|
);
|
|
382
411
|
}
|
|
383
412
|
const schemaSql = await readFile(paths.schemaPath, "utf8");
|
|
384
|
-
runDatabaseSql(
|
|
413
|
+
runDatabaseSql(`drop schema if exists public cascade;
|
|
414
|
+
create schema public authorization pg_database_owner;
|
|
415
|
+
grant usage on schema public to public;`);
|
|
416
|
+
if (runtimePolicy) {
|
|
417
|
+
runDatabaseSql(buildRuntimePrerequisiteSql(runtimePolicy));
|
|
418
|
+
}
|
|
385
419
|
if (runtimeAdapter?.prepareSchema) {
|
|
386
420
|
const preparation = runtimeAdapter.prepareSchema({
|
|
387
421
|
runSql: runDatabaseSql,
|
|
@@ -393,6 +427,9 @@ const restoreProductionSchema = async ({ baseline, paths }) => {
|
|
|
393
427
|
}
|
|
394
428
|
}
|
|
395
429
|
runDatabaseSql(schemaSql);
|
|
430
|
+
if (runtimePolicy) {
|
|
431
|
+
runDatabaseSql(buildRuntimePostSchemaSql(runtimePolicy));
|
|
432
|
+
}
|
|
396
433
|
const restoredSchema = await captureLocalPublicSchema({
|
|
397
434
|
repositoryRoot,
|
|
398
435
|
artifactRoot,
|
|
@@ -443,11 +480,15 @@ const configureProjectRuntime = ({ environment, baseline }) => {
|
|
|
443
480
|
};
|
|
444
481
|
|
|
445
482
|
const readActive = async () => {
|
|
446
|
-
const [baseline, paths,
|
|
483
|
+
const [baseline, paths, policyBytes] = await Promise.all([
|
|
447
484
|
verifyActiveBaseline({ artifactRoot }),
|
|
448
485
|
resolveActiveBaselinePaths({ artifactRoot }),
|
|
449
|
-
readFile(manifestPath
|
|
486
|
+
readFile(manifestPath),
|
|
450
487
|
]);
|
|
488
|
+
const manifest = readBoundRuntimeSanitizationPolicy({
|
|
489
|
+
bytes: policyBytes,
|
|
490
|
+
expectedSha256: baseline.sanitizationPolicySha256,
|
|
491
|
+
});
|
|
451
492
|
return { baseline, paths, manifest };
|
|
452
493
|
};
|
|
453
494
|
|
|
@@ -462,6 +503,7 @@ const resetRuntime = async () => {
|
|
|
462
503
|
workdir: runtimeWorkdir,
|
|
463
504
|
exclude: ["edge-runtime", "logflare", "vector", "realtime"],
|
|
464
505
|
environment: serviceEnvironment,
|
|
506
|
+
autoStartColima: config.containerRuntime.autoStartColima,
|
|
465
507
|
});
|
|
466
508
|
const structuralSchemaSha256 = await restoreProductionSchema(active);
|
|
467
509
|
await restoreBaselineRows(active);
|
|
@@ -470,6 +512,10 @@ const resetRuntime = async () => {
|
|
|
470
512
|
paths: active.paths,
|
|
471
513
|
environment,
|
|
472
514
|
});
|
|
515
|
+
if (runtimePolicy && runtimePolicy.localRows.length > 0) {
|
|
516
|
+
runDatabaseSql(buildRuntimeFinalizationSql(runtimePolicy));
|
|
517
|
+
console.log("Applied reviewed local-only recipes.");
|
|
518
|
+
}
|
|
473
519
|
const projectRuntime = configureProjectRuntime({
|
|
474
520
|
environment,
|
|
475
521
|
baseline: active.baseline,
|
|
@@ -496,7 +542,7 @@ const resetRuntime = async () => {
|
|
|
496
542
|
{ mode: 0o600 },
|
|
497
543
|
);
|
|
498
544
|
console.log(
|
|
499
|
-
`Restored Rehearsal baseline ${active.baseline.generationId}: ${active.baseline.rowCount}
|
|
545
|
+
`Restored Rehearsal baseline ${active.baseline.generationId}: ${formatCount(active.baseline.rowCount, "row")} across ${formatCount(Object.keys(active.baseline.tableCounts).length, "table")}.`,
|
|
500
546
|
);
|
|
501
547
|
console.log(projectRuntime.message);
|
|
502
548
|
return environment;
|
|
@@ -526,6 +572,7 @@ const startRuntime = async () => {
|
|
|
526
572
|
workdir: runtimeWorkdir,
|
|
527
573
|
exclude: ["edge-runtime", "logflare", "vector", "realtime"],
|
|
528
574
|
environment: serviceEnvironment,
|
|
575
|
+
autoStartColima: config.containerRuntime.autoStartColima,
|
|
529
576
|
});
|
|
530
577
|
const projectRuntime = configureProjectRuntime({ environment, baseline });
|
|
531
578
|
await writeRuntimeEnvironment({
|
|
@@ -605,6 +652,7 @@ const verifyRuntime = async () => {
|
|
|
605
652
|
currentFiles: candidateReceipt.currentFiles,
|
|
606
653
|
});
|
|
607
654
|
let projectVerification;
|
|
655
|
+
if (runtimePolicy) runDatabaseSql(buildRuntimeVerificationSql(runtimePolicy));
|
|
608
656
|
if (runtimeAdapter?.verifyRuntime) {
|
|
609
657
|
projectVerification = runtimeAdapter.verifyRuntime({
|
|
610
658
|
baseline,
|
|
@@ -621,7 +669,7 @@ const verifyRuntime = async () => {
|
|
|
621
669
|
}
|
|
622
670
|
}
|
|
623
671
|
console.log(
|
|
624
|
-
`Verified Rehearsal runtime ${baseline.generationId}: immutable baseline and migration history are valid with ${migrationComparison.candidates.length
|
|
672
|
+
`Verified Rehearsal runtime ${baseline.generationId}: immutable baseline and migration history are valid with ${formatCount(migrationComparison.candidates.length, "applied candidate migration")}. Runtime rows may differ from the baseline until the next reset.`,
|
|
625
673
|
);
|
|
626
674
|
if (projectVerification) console.log(projectVerification.message);
|
|
627
675
|
return { baseline, candidateReceipt, migrationComparison };
|
|
@@ -652,11 +700,9 @@ const migrateRuntime = async () => {
|
|
|
652
700
|
return receipt;
|
|
653
701
|
}
|
|
654
702
|
console.log(
|
|
655
|
-
|
|
703
|
+
`${receipt.candidates.length === 1 ? "Candidate migration" : "Candidate migrations"} (${receipt.candidateSha256}): ${receipt.candidates.map((candidate) => candidate.filename).join(", ")}`,
|
|
656
704
|
);
|
|
657
|
-
const confirmation =
|
|
658
|
-
.find((argument) => argument.startsWith("--confirm-candidates="))
|
|
659
|
-
?.slice("--confirm-candidates=".length);
|
|
705
|
+
const confirmation = invocation.confirmation;
|
|
660
706
|
if (confirmation !== receipt.candidateSha256) {
|
|
661
707
|
throw new Error(
|
|
662
708
|
"Candidate migration confirmation is missing or does not match the exact SHA-256.",
|
|
@@ -707,7 +753,7 @@ const migrateRuntime = async () => {
|
|
|
707
753
|
{ mode: 0o600 },
|
|
708
754
|
);
|
|
709
755
|
console.log(
|
|
710
|
-
`Applied ${receipt.candidates.length
|
|
756
|
+
`Applied ${formatCount(receipt.candidates.length, "confirmed candidate migration")} to the disposable Rehearsal runtime.`,
|
|
711
757
|
);
|
|
712
758
|
return receipt;
|
|
713
759
|
} catch (error) {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { spawnSync } from "node:child_process";
|
|
7
|
-
import { createCleanProcessEnvironment } from "../
|
|
7
|
+
import { createCleanProcessEnvironment } from "../shared/process_environment.mjs";
|
|
8
8
|
|
|
9
9
|
export const parseSupabaseStatusEnvironment = (output) => {
|
|
10
10
|
const values = {};
|
|
@@ -18,6 +18,56 @@ export const parseSupabaseStatusEnvironment = (output) => {
|
|
|
18
18
|
return values;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
export const parseLocalSupabaseEnvironment = (output) => {
|
|
22
|
+
const values = parseSupabaseStatusEnvironment(output);
|
|
23
|
+
const localEnvironment = {
|
|
24
|
+
apiUrl: values.API_URL,
|
|
25
|
+
databaseUrl: values.DB_URL,
|
|
26
|
+
publishableKey: values.PUBLISHABLE_KEY ?? values.ANON_KEY,
|
|
27
|
+
serviceRoleKey: values.SERVICE_ROLE_KEY ?? values.SECRET_KEY,
|
|
28
|
+
studioUrl: values.STUDIO_URL,
|
|
29
|
+
};
|
|
30
|
+
for (const [label, value] of [
|
|
31
|
+
["API URL", localEnvironment.apiUrl],
|
|
32
|
+
["database URL", localEnvironment.databaseUrl],
|
|
33
|
+
["publishable key", localEnvironment.publishableKey],
|
|
34
|
+
["service-role key", localEnvironment.serviceRoleKey],
|
|
35
|
+
]) {
|
|
36
|
+
if (!value) throw new Error(`Local Supabase status omitted its ${label}.`);
|
|
37
|
+
}
|
|
38
|
+
const api = new URL(localEnvironment.apiUrl);
|
|
39
|
+
const database = new URL(localEnvironment.databaseUrl);
|
|
40
|
+
const loopbackHosts = ["127.0.0.1", "::1", "localhost"];
|
|
41
|
+
if (
|
|
42
|
+
!loopbackHosts.includes(api.hostname) ||
|
|
43
|
+
!loopbackHosts.includes(database.hostname) ||
|
|
44
|
+
!["postgres:", "postgresql:"].includes(database.protocol)
|
|
45
|
+
) {
|
|
46
|
+
throw new Error("Refusing to use a non-local Supabase status target.");
|
|
47
|
+
}
|
|
48
|
+
const databaseName = decodeURIComponent(
|
|
49
|
+
database.pathname.replace(/^\//u, ""),
|
|
50
|
+
);
|
|
51
|
+
if (
|
|
52
|
+
!database.port ||
|
|
53
|
+
!databaseName ||
|
|
54
|
+
!database.username ||
|
|
55
|
+
!database.password
|
|
56
|
+
) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
"Local Supabase status returned an incomplete database URL.",
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
...localEnvironment,
|
|
63
|
+
databaseHost: database.hostname,
|
|
64
|
+
databasePort: database.port,
|
|
65
|
+
databaseName,
|
|
66
|
+
databaseUser: decodeURIComponent(database.username),
|
|
67
|
+
databasePassword: decodeURIComponent(database.password),
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
|
|
21
71
|
export const runLocalCommand = (
|
|
22
72
|
command,
|
|
23
73
|
args,
|
|
@@ -62,14 +112,13 @@ export const localCommandSucceeds = (
|
|
|
62
112
|
stdio: "ignore",
|
|
63
113
|
}).status === 0;
|
|
64
114
|
|
|
65
|
-
export const ensureLocalContainerRuntime = ({
|
|
115
|
+
export const ensureLocalContainerRuntime = ({
|
|
116
|
+
cwd = process.cwd(),
|
|
117
|
+
autoStartColima = true,
|
|
118
|
+
} = {}) => {
|
|
66
119
|
if (localCommandSucceeds("docker", ["info"], { cwd })) return;
|
|
67
|
-
if (localCommandSucceeds("colima", ["version"], { cwd })) {
|
|
68
|
-
runLocalCommand(
|
|
69
|
-
"colima",
|
|
70
|
-
["start", "--cpu", "4", "--memory", "4", "--disk", "40"],
|
|
71
|
-
{ cwd },
|
|
72
|
-
);
|
|
120
|
+
if (autoStartColima && localCommandSucceeds("colima", ["version"], { cwd })) {
|
|
121
|
+
runLocalCommand("colima", ["start"], { cwd });
|
|
73
122
|
}
|
|
74
123
|
if (!localCommandSucceeds("docker", ["info"], { cwd })) {
|
|
75
124
|
throw new Error(
|
|
@@ -91,25 +140,7 @@ export const readLocalSupabaseEnvironment = ({
|
|
|
91
140
|
withWorkdir(["status", "-o", "env"], workdir),
|
|
92
141
|
{ capture: true, cwd, environment },
|
|
93
142
|
);
|
|
94
|
-
|
|
95
|
-
const localEnvironment = {
|
|
96
|
-
apiUrl: values.API_URL,
|
|
97
|
-
publishableKey: values.PUBLISHABLE_KEY ?? values.ANON_KEY,
|
|
98
|
-
serviceRoleKey: values.SERVICE_ROLE_KEY ?? values.SECRET_KEY,
|
|
99
|
-
studioUrl: values.STUDIO_URL,
|
|
100
|
-
};
|
|
101
|
-
for (const [label, value] of [
|
|
102
|
-
["API URL", localEnvironment.apiUrl],
|
|
103
|
-
["publishable key", localEnvironment.publishableKey],
|
|
104
|
-
["service-role key", localEnvironment.serviceRoleKey],
|
|
105
|
-
]) {
|
|
106
|
-
if (!value) throw new Error(`Local Supabase status omitted its ${label}.`);
|
|
107
|
-
}
|
|
108
|
-
const hostname = new URL(localEnvironment.apiUrl).hostname;
|
|
109
|
-
if (!["127.0.0.1", "::1", "localhost"].includes(hostname)) {
|
|
110
|
-
throw new Error("Refusing to use a non-loopback Supabase status target.");
|
|
111
|
-
}
|
|
112
|
-
return localEnvironment;
|
|
143
|
+
return parseLocalSupabaseEnvironment(output);
|
|
113
144
|
};
|
|
114
145
|
|
|
115
146
|
export const startLocalSupabase = ({
|
|
@@ -118,8 +149,9 @@ export const startLocalSupabase = ({
|
|
|
118
149
|
exclude = [],
|
|
119
150
|
environment = {},
|
|
120
151
|
applyMigrations = true,
|
|
152
|
+
autoStartColima = true,
|
|
121
153
|
} = {}) => {
|
|
122
|
-
ensureLocalContainerRuntime({ cwd });
|
|
154
|
+
ensureLocalContainerRuntime({ cwd, autoStartColima });
|
|
123
155
|
const startArguments = withWorkdir(
|
|
124
156
|
["start", ...(exclude.length ? ["--exclude", exclude.join(",")] : [])],
|
|
125
157
|
workdir,
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Purpose: Define the small, closed set of database runtimes Rehearsal can select.
|
|
3
|
+
* Runtime implementations stay behind this boundary so the shared CLI does not
|
|
4
|
+
* need target-specific branches.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const TARGETS = Object.freeze({
|
|
8
|
+
supabase: Object.freeze({
|
|
9
|
+
id: "supabase",
|
|
10
|
+
label: "Supabase",
|
|
11
|
+
}),
|
|
12
|
+
postgresql: Object.freeze({
|
|
13
|
+
id: "postgresql",
|
|
14
|
+
label: "PostgreSQL",
|
|
15
|
+
}),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export const REHEARSAL_RUNTIME_TARGETS = Object.freeze(Object.keys(TARGETS));
|
|
19
|
+
|
|
20
|
+
export const parseRuntimeInvocation = (arguments_ = process.argv.slice(2)) => {
|
|
21
|
+
const action = arguments_.find((argument) => !argument.startsWith("--"));
|
|
22
|
+
const valueAfter = (prefix) =>
|
|
23
|
+
arguments_
|
|
24
|
+
.find((argument) => argument.startsWith(prefix))
|
|
25
|
+
?.slice(prefix.length);
|
|
26
|
+
return Object.freeze({
|
|
27
|
+
action: action ?? "status",
|
|
28
|
+
configPath: valueAfter("--config="),
|
|
29
|
+
confirmation: valueAfter("--confirm-candidates="),
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const buildRuntimeManagerArguments = ({
|
|
34
|
+
managerPath,
|
|
35
|
+
action,
|
|
36
|
+
configPath,
|
|
37
|
+
confirmation,
|
|
38
|
+
}) => [
|
|
39
|
+
managerPath,
|
|
40
|
+
action,
|
|
41
|
+
...(configPath ? [`--config=${configPath}`] : []),
|
|
42
|
+
...(confirmation ? [`--confirm-candidates=${confirmation}`] : []),
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
export const resolveRuntimeTarget = (value = "supabase") => {
|
|
46
|
+
if (typeof value !== "string" || !Object.hasOwn(TARGETS, value)) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Unsupported Rehearsal runtime target: ${String(value)}. Supported targets: ${REHEARSAL_RUNTIME_TARGETS.join(", ")}.`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
return TARGETS[value];
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const runRuntimeTarget = async (target) => {
|
|
55
|
+
const resolved = resolveRuntimeTarget(target);
|
|
56
|
+
switch (resolved.id) {
|
|
57
|
+
case "supabase":
|
|
58
|
+
await import("./supabase.mjs");
|
|
59
|
+
return;
|
|
60
|
+
case "postgresql":
|
|
61
|
+
await import("./postgresql.mjs");
|
|
62
|
+
return;
|
|
63
|
+
default:
|
|
64
|
+
throw new Error(`No runtime driver is installed for ${resolved.id}.`);
|
|
65
|
+
}
|
|
66
|
+
};
|