@rehearsal-db/core 0.1.0-beta.9 → 0.1.0-rc.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 +319 -1
- package/COMPATIBILITY.md +15 -0
- package/README.md +73 -16
- package/dist/package.json +100 -0
- package/dist/scripts/install/create_root_config.d.mts +3 -0
- package/dist/scripts/install/create_root_config.mjs +13 -0
- package/dist/scripts/runtime/manage_database.d.mts +6 -0
- package/{scripts → dist/scripts}/runtime/manage_database.mjs +6 -8
- package/dist/src/application/interactive_task.d.mts +30 -0
- package/dist/src/application/interactive_task.mjs +83 -0
- package/dist/src/application/session.d.mts +97 -0
- package/dist/src/application/session.mjs +380 -0
- package/dist/src/baseline/artifact.d.mts +119 -0
- package/dist/src/baseline/artifact.mjs +522 -0
- package/dist/src/baseline/builder.d.mts +19 -0
- package/dist/src/baseline/builder.mjs +142 -0
- package/dist/src/baseline/input_discovery.d.mts +13 -0
- package/dist/src/baseline/input_discovery.mjs +149 -0
- package/dist/src/baseline/policy_review.d.mts +56 -0
- package/dist/src/baseline/policy_review.mjs +73 -0
- package/dist/src/baseline/preparation.d.mts +63 -0
- package/dist/src/baseline/preparation.mjs +295 -0
- package/dist/src/baseline/privacy_contract.d.mts +147 -0
- package/dist/src/baseline/privacy_contract.mjs +2 -0
- package/dist/src/baseline/privacy_dependency_graph.d.mts +7 -0
- package/dist/src/baseline/privacy_dependency_graph.mjs +110 -0
- package/dist/src/baseline/privacy_engine.d.mts +5 -0
- package/dist/src/baseline/privacy_engine.mjs +4 -0
- package/dist/src/baseline/privacy_key.d.mts +7 -0
- package/dist/src/baseline/privacy_key.mjs +42 -0
- package/dist/src/baseline/privacy_pipeline.d.mts +7 -0
- package/dist/src/baseline/privacy_pipeline.mjs +127 -0
- package/dist/src/baseline/privacy_policy.d.mts +6 -0
- package/dist/src/baseline/privacy_policy.mjs +237 -0
- package/dist/src/baseline/privacy_recipe_validation.d.mts +5 -0
- package/dist/src/baseline/privacy_recipe_validation.mjs +289 -0
- package/dist/src/baseline/privacy_rule_registry.d.mts +18 -0
- package/dist/src/baseline/privacy_rule_registry.mjs +143 -0
- package/dist/src/baseline/privacy_structured_json.d.mts +66 -0
- package/dist/src/baseline/privacy_structured_json.mjs +464 -0
- package/dist/src/baseline/privacy_transforms.d.mts +18 -0
- package/dist/src/baseline/privacy_transforms.mjs +142 -0
- package/dist/src/baseline/privacy_validation_shared.d.mts +31 -0
- package/dist/src/baseline/privacy_validation_shared.mjs +127 -0
- package/dist/src/baseline/sanitization_policy.d.mts +69 -0
- package/dist/src/baseline/sanitization_policy.mjs +227 -0
- package/dist/src/baseline/schema_snapshot.d.mts +19 -0
- package/dist/src/baseline/schema_snapshot.mjs +79 -0
- package/dist/src/cli/application_command_handlers.d.mts +8 -0
- package/dist/src/cli/application_command_handlers.mjs +52 -0
- package/dist/src/cli/arguments.d.mts +38 -0
- package/dist/src/cli/arguments.mjs +130 -0
- package/dist/src/cli/basic_command_handlers.d.mts +4 -0
- package/dist/src/cli/basic_command_handlers.mjs +57 -0
- package/dist/src/cli/cli_commands.d.mts +14 -0
- package/dist/src/cli/cli_commands.mjs +34 -0
- package/dist/src/cli/command_contract.d.mts +57 -0
- package/dist/src/cli/command_contract.mjs +39 -0
- package/dist/src/cli/command_output.d.mts +12 -0
- package/dist/src/cli/command_output.mjs +20 -0
- package/dist/src/cli/command_registry.d.mts +7 -0
- package/dist/src/cli/command_registry.mjs +14 -0
- package/dist/src/cli/guided.d.mts +53 -0
- package/dist/src/cli/guided.mjs +727 -0
- package/dist/src/cli/identity_command_handlers.d.mts +12 -0
- package/dist/src/cli/identity_command_handlers.mjs +239 -0
- package/dist/src/cli/identity_renderer.d.mts +8 -0
- package/dist/src/cli/identity_renderer.mjs +33 -0
- package/dist/src/cli/init_command.d.mts +21 -0
- package/dist/src/cli/init_command.mjs +90 -0
- package/dist/src/cli/inspection_command_handlers.d.mts +3 -0
- package/dist/src/cli/inspection_command_handlers.mjs +79 -0
- package/dist/src/cli/project_command_handlers.d.mts +11 -0
- package/dist/src/cli/project_command_handlers.mjs +92 -0
- package/dist/src/cli/project_commands.d.mts +283 -0
- package/dist/src/cli/project_commands.mjs +192 -0
- package/dist/src/cli/rehearsal.d.mts +7 -0
- package/dist/src/cli/rehearsal.mjs +190 -0
- package/dist/src/cli/renderers.d.mts +299 -0
- package/dist/src/cli/renderers.mjs +554 -0
- package/dist/src/cli/runtime_action_handlers.d.mts +12 -0
- package/dist/src/cli/runtime_action_handlers.mjs +201 -0
- package/dist/src/cli/runtime_commands.d.mts +294 -0
- package/dist/src/cli/runtime_commands.mjs +619 -0
- package/dist/src/cli/source_command_handlers.d.mts +8 -0
- package/dist/src/cli/source_command_handlers.mjs +62 -0
- package/dist/src/cli/source_commands.d.mts +436 -0
- package/dist/src/cli/source_commands.mjs +301 -0
- package/dist/src/cli/terminal.d.mts +59 -0
- package/dist/src/cli/terminal.mjs +230 -0
- package/dist/src/identity/claim.d.mts +5 -0
- package/dist/src/identity/claim.mjs +4 -0
- package/dist/src/identity/claim_contract.d.mts +135 -0
- package/dist/src/identity/claim_contract.mjs +6 -0
- package/dist/src/identity/claim_executor.d.mts +10 -0
- package/dist/src/identity/claim_executor.mjs +358 -0
- package/dist/src/identity/claim_identity_lookup.d.mts +23 -0
- package/dist/src/identity/claim_identity_lookup.mjs +134 -0
- package/dist/src/identity/claim_plan.d.mts +8 -0
- package/dist/src/identity/claim_plan.mjs +61 -0
- package/dist/src/identity/claim_policy.d.mts +3 -0
- package/dist/src/identity/claim_policy.mjs +377 -0
- package/dist/src/identity/claim_sql.d.mts +10 -0
- package/dist/src/identity/claim_sql.mjs +46 -0
- package/dist/src/identity/claim_verification.d.mts +13 -0
- package/dist/src/identity/claim_verification.mjs +35 -0
- package/dist/src/identity/connect.d.mts +30 -0
- package/dist/src/identity/connect.mjs +111 -0
- package/dist/src/identity/provider_configuration.d.mts +37 -0
- package/dist/src/identity/provider_configuration.mjs +132 -0
- package/dist/src/identity/storage.d.mts +21 -0
- package/dist/src/identity/storage.mjs +122 -0
- package/dist/src/index.d.mts +4 -0
- package/dist/src/index.mjs +3 -0
- package/dist/src/project/configuration.d.mts +13 -0
- package/dist/src/project/configuration.mjs +9 -0
- package/dist/src/project/configuration_contract.d.mts +125 -0
- package/dist/src/project/configuration_contract.mjs +27 -0
- package/dist/src/project/configuration_discovery.d.mts +7 -0
- package/dist/src/project/configuration_discovery.mjs +37 -0
- package/dist/src/project/configuration_normalization.d.mts +107 -0
- package/dist/src/project/configuration_normalization.mjs +302 -0
- package/dist/src/project/configuration_rendering.d.mts +14 -0
- package/dist/src/project/configuration_rendering.mjs +228 -0
- package/dist/src/project/configuration_resolution.d.mts +125 -0
- package/dist/src/project/configuration_resolution.mjs +81 -0
- package/dist/src/project/configuration_schema.d.mts +22 -0
- package/dist/src/project/configuration_schema.mjs +160 -0
- package/dist/src/project/configuration_validation.d.mts +22 -0
- package/dist/src/project/configuration_validation.mjs +136 -0
- package/dist/src/project/install_scaffold.d.mts +42 -0
- package/dist/src/project/install_scaffold.mjs +263 -0
- package/dist/src/project/project_detection.d.mts +14 -0
- package/dist/src/project/project_detection.mjs +47 -0
- package/dist/src/project/setup.d.mts +87 -0
- package/dist/src/project/setup.mjs +414 -0
- package/dist/src/project/support_report.d.mts +88 -0
- package/dist/src/project/support_report.mjs +83 -0
- package/dist/src/runtime/cleanup.d.mts +85 -0
- package/dist/src/runtime/cleanup.mjs +311 -0
- package/dist/src/runtime/lifecycle_engine.d.mts +63 -0
- package/dist/src/runtime/lifecycle_engine.mjs +114 -0
- package/dist/src/runtime/migration_history.d.mts +51 -0
- package/dist/src/runtime/migration_history.mjs +170 -0
- package/dist/src/runtime/plan.d.mts +134 -0
- package/dist/src/runtime/plan.mjs +640 -0
- package/dist/src/runtime/policy.d.mts +66 -0
- package/dist/src/runtime/policy.mjs +369 -0
- package/dist/src/runtime/restore.d.mts +22 -0
- package/dist/src/runtime/restore.mjs +276 -0
- package/dist/src/runtime/service_environment.d.mts +11 -0
- package/dist/src/runtime/service_environment.mjs +58 -0
- package/dist/src/runtime/topology.d.mts +51 -0
- package/dist/src/runtime/topology.mjs +128 -0
- package/dist/src/shared/diagnostics.d.mts +97 -0
- package/dist/src/shared/diagnostics.mjs +170 -0
- package/dist/src/shared/human_output.d.mts +2 -0
- package/{src → dist/src}/shared/human_output.mjs +1 -3
- package/dist/src/shared/node_runtime.d.mts +14 -0
- package/dist/src/shared/node_runtime.mjs +25 -0
- package/dist/src/shared/operation_guard.d.mts +22 -0
- package/dist/src/shared/operation_guard.mjs +150 -0
- package/dist/src/shared/process_environment.d.mts +15 -0
- package/dist/src/shared/process_environment.mjs +50 -0
- package/dist/src/source/access.d.mts +165 -0
- package/dist/src/source/access.mjs +602 -0
- package/dist/src/source/asset_transfer.d.mts +27 -0
- package/dist/src/source/asset_transfer.mjs +202 -0
- package/dist/src/source/baseline.d.mts +66 -0
- package/dist/src/source/baseline.mjs +372 -0
- package/dist/src/source/external_reader.d.mts +42 -0
- package/dist/src/source/external_reader.mjs +278 -0
- package/dist/src/source/postgresql_access.d.mts +86 -0
- package/dist/src/source/postgresql_access.mjs +454 -0
- package/dist/src/source/privacy_paths.d.mts +10 -0
- package/dist/src/source/privacy_paths.mjs +27 -0
- package/dist/src/targets/driver.d.mts +2 -0
- package/dist/src/targets/driver.mjs +13 -0
- package/dist/src/targets/postgresql.d.mts +5 -0
- package/dist/src/targets/postgresql.mjs +620 -0
- package/dist/src/targets/supabase.d.mts +8 -0
- package/dist/src/targets/supabase.mjs +629 -0
- package/dist/src/targets/supabase_environment.d.mts +54 -0
- package/dist/src/targets/supabase_environment.mjs +184 -0
- package/dist/src/targets/target.d.mts +33 -0
- package/dist/src/targets/target.mjs +39 -0
- package/docs/README.md +35 -16
- package/docs/architecture.md +135 -10
- package/docs/commands.md +53 -15
- package/docs/configuration.md +186 -31
- package/docs/getting-started.md +66 -9
- package/docs/index.md +67 -0
- package/docs/production-source.md +175 -17
- package/docs/releasing.md +24 -12
- package/docs/roadmap.md +3 -2
- package/docs/runtime-lifecycle.md +99 -0
- package/docs/runtime-policies.md +80 -7
- package/docs/sanitization.md +572 -4
- package/docs/security-model.md +8 -0
- package/docs/stability.md +70 -0
- package/docs/standalone-workflow.md +36 -13
- package/docs/troubleshooting.md +82 -10
- package/docs/tutorial.md +7 -1
- package/docs/typescript.md +134 -0
- package/package.json +44 -29
- package/types/configuration.d.mts +5 -0
- package/src/README.md +0 -17
- package/src/application/session.mjs +0 -313
- package/src/baseline/artifact.mjs +0 -661
- package/src/baseline/builder.mjs +0 -164
- package/src/baseline/input_discovery.mjs +0 -155
- package/src/baseline/policy_review.mjs +0 -92
- package/src/baseline/preparation.mjs +0 -323
- package/src/baseline/privacy_engine.mjs +0 -413
- package/src/baseline/sanitization_policy.mjs +0 -299
- package/src/baseline/schema_snapshot.mjs +0 -113
- package/src/cli/arguments.mjs +0 -120
- package/src/cli/guided.mjs +0 -807
- package/src/cli/rehearsal.mjs +0 -933
- package/src/cli/renderers.mjs +0 -584
- package/src/cli/runtime_commands.mjs +0 -553
- package/src/cli/source_commands.mjs +0 -326
- package/src/cli/terminal.mjs +0 -275
- package/src/identity/claim.mjs +0 -975
- package/src/identity/storage.mjs +0 -165
- package/src/project/configuration.d.mts +0 -153
- package/src/project/configuration.mjs +0 -1197
- package/src/project/setup.mjs +0 -379
- package/src/project/support_report.mjs +0 -105
- package/src/runtime/cleanup.mjs +0 -381
- package/src/runtime/migration_history.mjs +0 -220
- package/src/runtime/plan.mjs +0 -685
- package/src/runtime/policy.mjs +0 -438
- package/src/runtime/restore.mjs +0 -329
- package/src/runtime/service_environment.mjs +0 -82
- package/src/runtime/topology.mjs +0 -177
- package/src/shared/diagnostics.mjs +0 -199
- package/src/shared/operation_guard.mjs +0 -162
- package/src/shared/process_environment.mjs +0 -68
- package/src/source/access.mjs +0 -578
- package/src/source/asset_transfer.mjs +0 -177
- package/src/source/baseline.mjs +0 -446
- package/src/source/postgresql_access.mjs +0 -480
- package/src/targets/postgresql.mjs +0 -808
- package/src/targets/supabase.mjs +0 -830
- package/src/targets/supabase_environment.mjs +0 -229
- package/src/targets/target.mjs +0 -66
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Provider-neutral copied-account planning, application, and guided connection. */
|
|
2
|
+
import type { CommandDefinition } from "./command_contract.mjs";
|
|
3
|
+
import type { createProjectCommands } from "./project_commands.mjs";
|
|
4
|
+
import type { createRuntimeCommands } from "./runtime_commands.mjs";
|
|
5
|
+
type ProjectCommands = ReturnType<typeof createProjectCommands>;
|
|
6
|
+
type RuntimeCommands = ReturnType<typeof createRuntimeCommands>;
|
|
7
|
+
export declare const createIdentityCommandHandlers: ({ loadIdentityClaimPlan, loadIdentityPolicy, startRuntimeApplication, }: {
|
|
8
|
+
loadIdentityClaimPlan: ProjectCommands["loadIdentityClaimPlan"];
|
|
9
|
+
loadIdentityPolicy: ProjectCommands["loadIdentityPolicy"];
|
|
10
|
+
startRuntimeApplication: RuntimeCommands["startRuntimeApplication"];
|
|
11
|
+
}) => readonly CommandDefinition[];
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/** Provider-neutral copied-account planning, application, and guided connection. */
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { parseEnv } from "node:util";
|
|
4
|
+
import * as prompts from "@clack/prompts";
|
|
5
|
+
import { holdApplicationSession } from "../application/session.mjs";
|
|
6
|
+
import { runApplicationSessionTask } from "../application/interactive_task.mjs";
|
|
7
|
+
import { applyIdentityClaim } from "../identity/claim.mjs";
|
|
8
|
+
import { waitForIdentityConnection } from "../identity/connect.mjs";
|
|
9
|
+
import { createSupabaseStorageTransfer } from "../identity/storage.mjs";
|
|
10
|
+
import { exactCommands, renderedCommand, textCommand, } from "./command_contract.mjs";
|
|
11
|
+
import { renderIdentityConnection } from "./identity_renderer.mjs";
|
|
12
|
+
import { renderIdentityPlan } from "./renderers.mjs";
|
|
13
|
+
import { isHumanTerminal, promptForConfirmation, promptForSelection, useStyledPrompts, } from "./terminal.mjs";
|
|
14
|
+
const loadIdentityRuntime = async (loaded) => {
|
|
15
|
+
const environment = parseEnv(await readFile(loaded.paths.applicationEnvironment, "utf8"));
|
|
16
|
+
if (!environment.DATABASE_URL) {
|
|
17
|
+
throw new Error("The local runtime environment has no DATABASE_URL.");
|
|
18
|
+
}
|
|
19
|
+
return environment;
|
|
20
|
+
};
|
|
21
|
+
const storageTransferFor = ({ plan, environment, }) => {
|
|
22
|
+
const needsStorageTransfer = plan.identity.assets.some((asset) => asset.rewritePath);
|
|
23
|
+
if (!needsStorageTransfer)
|
|
24
|
+
return undefined;
|
|
25
|
+
if (!environment.SUPABASE_URL || !environment.SUPABASE_SERVICE_ROLE_KEY) {
|
|
26
|
+
throw new Error("Identity asset transfer requires local Supabase URL and service-role credentials.");
|
|
27
|
+
}
|
|
28
|
+
return createSupabaseStorageTransfer({
|
|
29
|
+
url: environment.SUPABASE_URL,
|
|
30
|
+
serviceRoleKey: environment.SUPABASE_SERVICE_ROLE_KEY,
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
export const createIdentityCommandHandlers = ({ loadIdentityClaimPlan, loadIdentityPolicy, startRuntimeApplication, }) => [
|
|
34
|
+
exactCommands("identity", ["identity plan", "identity claim", "identity connect"], async ({ command, flags, planOptions, guided, state }) => {
|
|
35
|
+
if (command === "identity connect") {
|
|
36
|
+
if (!isHumanTerminal(flags)) {
|
|
37
|
+
throw new Error("identity connect requires an interactive terminal. Scripts should keep using identity plan and identity claim.");
|
|
38
|
+
}
|
|
39
|
+
if (!flags.identityName) {
|
|
40
|
+
const { policy } = await loadIdentityPolicy({
|
|
41
|
+
flags,
|
|
42
|
+
planOptions,
|
|
43
|
+
session: state,
|
|
44
|
+
});
|
|
45
|
+
if (policy.identities.length === 1) {
|
|
46
|
+
flags.identityName = policy.identities[0].name;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
const selected = await promptForSelection({
|
|
50
|
+
message: "Which copied account would you like to connect?",
|
|
51
|
+
flags,
|
|
52
|
+
options: policy.identities.map((identity) => ({
|
|
53
|
+
label: identity.name,
|
|
54
|
+
value: identity.name,
|
|
55
|
+
})),
|
|
56
|
+
});
|
|
57
|
+
if (!selected) {
|
|
58
|
+
return textCommand("COPIED ACCOUNT CONNECTION CANCELLED\nNothing was changed.");
|
|
59
|
+
}
|
|
60
|
+
flags.identityName = selected;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const initial = await loadIdentityClaimPlan({
|
|
64
|
+
flags,
|
|
65
|
+
planOptions,
|
|
66
|
+
session: state,
|
|
67
|
+
});
|
|
68
|
+
const initialEnvironment = await loadIdentityRuntime(initial.loaded);
|
|
69
|
+
let active;
|
|
70
|
+
let reopened;
|
|
71
|
+
try {
|
|
72
|
+
active = await startRuntimeApplication({
|
|
73
|
+
flags,
|
|
74
|
+
planOptions,
|
|
75
|
+
session: state,
|
|
76
|
+
});
|
|
77
|
+
const observed = await runApplicationSessionTask({
|
|
78
|
+
session: active.session,
|
|
79
|
+
onReady: (ready) => {
|
|
80
|
+
const lines = [
|
|
81
|
+
`Application: ${ready.url}`,
|
|
82
|
+
"Sign in normally with the provider allowed by your reviewed identity policy.",
|
|
83
|
+
"Rehearsal will continue when exactly one approved, verified local identity appears.",
|
|
84
|
+
"It never reads your provider password, passkey, MFA secret, browser cookies, or session tokens.",
|
|
85
|
+
"Press Ctrl+C to cancel or Ctrl+Z to exit Rehearsal.",
|
|
86
|
+
];
|
|
87
|
+
if (useStyledPrompts(flags)) {
|
|
88
|
+
prompts.note(lines.join("\n"), "SIGN IN TO THE SANDBOX");
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
console.log(["", "SIGN IN TO THE SANDBOX", ...lines, ""].join("\n"));
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
task: (signal) => waitForIdentityConnection({
|
|
95
|
+
plan: initial.plan,
|
|
96
|
+
connectionString: initialEnvironment.DATABASE_URL,
|
|
97
|
+
signal,
|
|
98
|
+
}),
|
|
99
|
+
});
|
|
100
|
+
if (!observed.completed) {
|
|
101
|
+
if (guided && observed.signal === "SIGTSTP") {
|
|
102
|
+
flags.exitGuidedSession = true;
|
|
103
|
+
}
|
|
104
|
+
return textCommand([
|
|
105
|
+
"COPIED ACCOUNT CONNECTION CANCELLED",
|
|
106
|
+
`Application stopped after ${observed.signal}.`,
|
|
107
|
+
"Nothing was changed. The database runtime was preserved.",
|
|
108
|
+
].join("\n"));
|
|
109
|
+
}
|
|
110
|
+
const preview = observed.value;
|
|
111
|
+
if (preview.associated) {
|
|
112
|
+
const closed = await holdApplicationSession({
|
|
113
|
+
session: active.session,
|
|
114
|
+
onReady: () => {
|
|
115
|
+
const message = renderIdentityConnection({
|
|
116
|
+
mode: "already-connected",
|
|
117
|
+
preview,
|
|
118
|
+
});
|
|
119
|
+
if (useStyledPrompts(flags)) {
|
|
120
|
+
prompts.note(message, "SANDBOX READY");
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
console.log(`\n${message}\n`);
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
if (guided && closed.signal === "SIGTSTP") {
|
|
128
|
+
flags.exitGuidedSession = true;
|
|
129
|
+
}
|
|
130
|
+
return renderedCommand({
|
|
131
|
+
data: {
|
|
132
|
+
mode: "already-connected",
|
|
133
|
+
preview,
|
|
134
|
+
application: { stoppedBy: closed.signal },
|
|
135
|
+
},
|
|
136
|
+
render: ({ mode, preview: renderedPreview }) => renderIdentityConnection({ mode, preview: renderedPreview }),
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
await active.session.stop();
|
|
140
|
+
console.log(`\n${renderIdentityConnection({ mode: "preview", preview })}\n`);
|
|
141
|
+
const accepted = await promptForConfirmation("Connect my copied account using exactly this plan?", flags);
|
|
142
|
+
if (!accepted) {
|
|
143
|
+
return renderedCommand({
|
|
144
|
+
data: { mode: "cancelled", preview },
|
|
145
|
+
render: ({ mode, preview: renderedPreview }) => renderIdentityConnection({ mode, preview: renderedPreview }),
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
const refreshed = await loadIdentityClaimPlan({
|
|
149
|
+
flags,
|
|
150
|
+
planOptions,
|
|
151
|
+
session: state,
|
|
152
|
+
});
|
|
153
|
+
if (refreshed.plan.digest !== preview.planDigest) {
|
|
154
|
+
throw new Error("The reviewed identity plan changed after sign-in. Nothing was changed; start the connection again.");
|
|
155
|
+
}
|
|
156
|
+
const refreshedEnvironment = await loadIdentityRuntime(refreshed.loaded);
|
|
157
|
+
const storageTransfer = storageTransferFor({
|
|
158
|
+
plan: refreshed.plan,
|
|
159
|
+
environment: refreshedEnvironment,
|
|
160
|
+
});
|
|
161
|
+
const result = await applyIdentityClaim({
|
|
162
|
+
plan: refreshed.plan,
|
|
163
|
+
confirmation: preview.planDigest,
|
|
164
|
+
connectionString: refreshedEnvironment.DATABASE_URL,
|
|
165
|
+
...(storageTransfer === undefined ? {} : { storageTransfer }),
|
|
166
|
+
});
|
|
167
|
+
reopened = await startRuntimeApplication({
|
|
168
|
+
flags,
|
|
169
|
+
planOptions,
|
|
170
|
+
session: state,
|
|
171
|
+
});
|
|
172
|
+
const closed = await holdApplicationSession({
|
|
173
|
+
session: reopened.session,
|
|
174
|
+
onReady: (ready) => {
|
|
175
|
+
const lines = [
|
|
176
|
+
renderIdentityConnection({
|
|
177
|
+
mode: "connected",
|
|
178
|
+
preview,
|
|
179
|
+
result,
|
|
180
|
+
}),
|
|
181
|
+
"",
|
|
182
|
+
`Application: ${ready.url}`,
|
|
183
|
+
"The app is running again. Sign out and sign in once for fresh claims.",
|
|
184
|
+
"Press Ctrl+C to close the app or Ctrl+Z to exit Rehearsal.",
|
|
185
|
+
];
|
|
186
|
+
if (useStyledPrompts(flags)) {
|
|
187
|
+
prompts.note(lines.join("\n"), "ACCOUNT CONNECTED");
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
console.log(["", ...lines, ""].join("\n"));
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
if (guided && closed.signal === "SIGTSTP") {
|
|
195
|
+
flags.exitGuidedSession = true;
|
|
196
|
+
}
|
|
197
|
+
return renderedCommand({
|
|
198
|
+
data: {
|
|
199
|
+
mode: "connected",
|
|
200
|
+
preview,
|
|
201
|
+
result,
|
|
202
|
+
application: { stoppedBy: closed.signal },
|
|
203
|
+
},
|
|
204
|
+
render: ({ mode, preview: renderedPreview, result: claimResult }) => renderIdentityConnection({
|
|
205
|
+
mode,
|
|
206
|
+
preview: renderedPreview,
|
|
207
|
+
result: claimResult,
|
|
208
|
+
}),
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
finally {
|
|
212
|
+
await active?.session.stop();
|
|
213
|
+
await reopened?.session.stop();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
const { loaded, plan } = await loadIdentityClaimPlan({
|
|
217
|
+
flags,
|
|
218
|
+
planOptions,
|
|
219
|
+
session: state,
|
|
220
|
+
});
|
|
221
|
+
if (command === "identity plan" || !flags.identityConfirmation) {
|
|
222
|
+
const data = { mode: "preview", plan };
|
|
223
|
+
return renderedCommand({ data, render: renderIdentityPlan });
|
|
224
|
+
}
|
|
225
|
+
const runtimeEnvironment = await loadIdentityRuntime(loaded);
|
|
226
|
+
const storageTransfer = storageTransferFor({
|
|
227
|
+
plan,
|
|
228
|
+
environment: runtimeEnvironment,
|
|
229
|
+
});
|
|
230
|
+
const result = await applyIdentityClaim({
|
|
231
|
+
plan,
|
|
232
|
+
confirmation: flags.identityConfirmation,
|
|
233
|
+
connectionString: runtimeEnvironment.DATABASE_URL,
|
|
234
|
+
...(storageTransfer === undefined ? {} : { storageTransfer }),
|
|
235
|
+
});
|
|
236
|
+
const data = { mode: "written", plan, result };
|
|
237
|
+
return renderedCommand({ data, render: renderIdentityPlan });
|
|
238
|
+
}),
|
|
239
|
+
];
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Redacted terminal output for the interactive copied-account connection. */
|
|
2
|
+
import type { IdentityClaimResult } from "../identity/claim_contract.mjs";
|
|
3
|
+
import type { IdentityConnectionPreview } from "../identity/connect.mjs";
|
|
4
|
+
export declare const renderIdentityConnection: ({ mode, preview, result, }: {
|
|
5
|
+
mode: "preview" | "connected" | "already-connected" | "cancelled";
|
|
6
|
+
preview: IdentityConnectionPreview;
|
|
7
|
+
result?: IdentityClaimResult;
|
|
8
|
+
}) => string;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** Redacted terminal output for the interactive copied-account connection. */
|
|
2
|
+
export const renderIdentityConnection = ({ mode, preview, result, }) => [
|
|
3
|
+
`COPIED ACCOUNT — ${mode.replaceAll("-", " ").toUpperCase()}`,
|
|
4
|
+
`Account label: ${preview.name}`,
|
|
5
|
+
`Verified provider: ${preview.provider}`,
|
|
6
|
+
`Data groups: ${preview.relationalGroups} relational; ${preview.nestedJsonGroups} nested JSON`,
|
|
7
|
+
`Images: ${preview.assetScopes} reviewed Storage scope${preview.assetScopes === 1 ? "" : "s"}; ${preview.storagePathGroups} path reference group${preview.storagePathGroups === 1 ? "" : "s"}`,
|
|
8
|
+
`Permissions: ${preview.claimKeys} reviewed claim${preview.claimKeys === 1 ? "" : "s"}; token hook ${preview.tokenHookConfigured ? "verified during connection" : "not configured"}`,
|
|
9
|
+
`Immutable history: ${preview.preservedAuditGroups} reference group${preview.preservedAuditGroups === 1 ? "" : "s"} stays with the copied audit identity`,
|
|
10
|
+
`Plan: ${preview.planDigest.slice(0, 12)}`,
|
|
11
|
+
"",
|
|
12
|
+
...(mode === "preview"
|
|
13
|
+
? [
|
|
14
|
+
"This changes only the disposable local sandbox.",
|
|
15
|
+
"Edited or independent local account data will make the connection stop safely.",
|
|
16
|
+
"Nothing has changed yet.",
|
|
17
|
+
]
|
|
18
|
+
: mode === "cancelled"
|
|
19
|
+
? [
|
|
20
|
+
"Nothing was changed. Your local database runtime is still available.",
|
|
21
|
+
]
|
|
22
|
+
: mode === "already-connected"
|
|
23
|
+
? [
|
|
24
|
+
"This copied account is already connected in the current local runtime.",
|
|
25
|
+
"No transfer was repeated.",
|
|
26
|
+
]
|
|
27
|
+
: [
|
|
28
|
+
`Transferred images: ${result?.storageObjectsTransferred ?? 0}`,
|
|
29
|
+
`Removed untouched signup defaults: ${result?.removedSignupDefaults ?? 0}`,
|
|
30
|
+
"The copied account is connected and verified.",
|
|
31
|
+
"Sign out and sign in once more so the application receives fresh role claims.",
|
|
32
|
+
]),
|
|
33
|
+
].join("\n");
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Preview or create the project-root Rehearsal configuration. */
|
|
2
|
+
interface InitFlags {
|
|
3
|
+
readonly configPath: string | undefined;
|
|
4
|
+
readonly write: boolean;
|
|
5
|
+
}
|
|
6
|
+
interface InitOptions {
|
|
7
|
+
readonly projectRoot: string;
|
|
8
|
+
readonly flags: InitFlags;
|
|
9
|
+
}
|
|
10
|
+
export declare const runRehearsalInit: ({ projectRoot, flags }: InitOptions) => Promise<{
|
|
11
|
+
mode: string;
|
|
12
|
+
destination: string;
|
|
13
|
+
detected: import("../project/project_detection.mjs").DetectedProject;
|
|
14
|
+
source: string;
|
|
15
|
+
availableOptions: {
|
|
16
|
+
path: string;
|
|
17
|
+
summary: string;
|
|
18
|
+
}[];
|
|
19
|
+
nextAction: string;
|
|
20
|
+
}>;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/** Preview or create the project-root Rehearsal configuration. */
|
|
2
|
+
import { writeFile } from "node:fs/promises";
|
|
3
|
+
import { join, relative } from "node:path";
|
|
4
|
+
import { findRehearsalConfigPath, inspectDetectedProject, loadRehearsalConfig, renderDetectedConfig, renderDetectedPostgresqlConfig, } from "../project/configuration.mjs";
|
|
5
|
+
const findExistingConfig = async ({ projectRoot, configPath, }) => {
|
|
6
|
+
try {
|
|
7
|
+
return await findRehearsalConfigPath({
|
|
8
|
+
projectRoot,
|
|
9
|
+
...(configPath === undefined ? {} : { configPath }),
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
if (String(error instanceof Error ? error.message : error).startsWith("No Rehearsal configuration")) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
export const runRehearsalInit = async ({ projectRoot, flags }) => {
|
|
20
|
+
const detected = await inspectDetectedProject({ projectRoot });
|
|
21
|
+
const destination = join(projectRoot, "rehearsal.config.mjs");
|
|
22
|
+
const existingPath = await findExistingConfig({
|
|
23
|
+
projectRoot,
|
|
24
|
+
configPath: flags.configPath,
|
|
25
|
+
});
|
|
26
|
+
if (existingPath) {
|
|
27
|
+
if (flags.write) {
|
|
28
|
+
throw new Error(`A Rehearsal configuration already exists at ${relative(projectRoot, existingPath)}; Rehearsal will not overwrite it.`);
|
|
29
|
+
}
|
|
30
|
+
const loaded = await loadRehearsalConfig({
|
|
31
|
+
projectRoot,
|
|
32
|
+
...(flags.configPath === undefined
|
|
33
|
+
? {}
|
|
34
|
+
: { configPath: flags.configPath }),
|
|
35
|
+
});
|
|
36
|
+
let source;
|
|
37
|
+
if (loaded.config.supabase) {
|
|
38
|
+
const { api, database, studio } = loaded.config.runtime.ports;
|
|
39
|
+
if (api === undefined || studio === undefined) {
|
|
40
|
+
throw new Error("The normalized Supabase configuration is missing its API or Studio port.");
|
|
41
|
+
}
|
|
42
|
+
source = renderDetectedConfig(detected, {
|
|
43
|
+
applicationUrl: loaded.config.runtime.applicationUrl,
|
|
44
|
+
ports: { api, database, studio },
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
source = renderDetectedPostgresqlConfig(detected, {
|
|
49
|
+
applicationUrl: loaded.config.runtime.applicationUrl,
|
|
50
|
+
databasePort: loaded.config.runtime.ports.database,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
const usesLegacyAuthentication = Boolean(loaded.config.supabase?.serviceEnvironmentFile ||
|
|
54
|
+
loaded.config.safety.authenticationProviders.length);
|
|
55
|
+
const availableOptions = loaded.config.supabase && !loaded.config.supabase.authentication
|
|
56
|
+
? [
|
|
57
|
+
{
|
|
58
|
+
path: "supabase.authentication",
|
|
59
|
+
summary: usesLegacyAuthentication
|
|
60
|
+
? "A declarative Google/GitHub replacement is available; replace the legacy provider fields together after review."
|
|
61
|
+
: "Configure local Google or GitHub sign-in with credentials kept outside tracked files.",
|
|
62
|
+
},
|
|
63
|
+
]
|
|
64
|
+
: [];
|
|
65
|
+
return {
|
|
66
|
+
mode: "current-template",
|
|
67
|
+
destination: relative(projectRoot, existingPath),
|
|
68
|
+
detected,
|
|
69
|
+
source,
|
|
70
|
+
availableOptions,
|
|
71
|
+
nextAction: "Your existing configuration was not changed. Compare it with this installed-release template to discover new optional keys.",
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const source = detected.hasSupabaseConfig
|
|
75
|
+
? renderDetectedConfig(detected)
|
|
76
|
+
: renderDetectedPostgresqlConfig(detected);
|
|
77
|
+
if (flags.write) {
|
|
78
|
+
await writeFile(destination, source, { flag: "wx", mode: 0o600 });
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
mode: flags.write ? "written" : "preview",
|
|
82
|
+
destination: relative(projectRoot, destination),
|
|
83
|
+
detected,
|
|
84
|
+
source,
|
|
85
|
+
availableOptions: [],
|
|
86
|
+
nextAction: flags.write
|
|
87
|
+
? "Review the generated safety settings and run rehearsal doctor."
|
|
88
|
+
: "Review this preview, then rerun rehearsal init --write to create it.",
|
|
89
|
+
};
|
|
90
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/** Read-only readiness, support, planning, and inspection command handlers. */
|
|
2
|
+
import { collectRehearsalSupportReport } from "../project/support_report.mjs";
|
|
3
|
+
import { inspectRehearsalBaseline, inspectRehearsalMigrations, } from "../runtime/plan.mjs";
|
|
4
|
+
import { buildRuntimeStackPlan, runRuntimeStackDoctor, topologyCandidateSummary, } from "./runtime_commands.mjs";
|
|
5
|
+
import { renderBaseline, renderCandidates, renderDoctor, renderMigrations, renderPlan, renderSupportReport, } from "./renderers.mjs";
|
|
6
|
+
import { exactCommands, renderedCommand } from "./command_contract.mjs";
|
|
7
|
+
export const createInspectionCommandHandlers = () => [
|
|
8
|
+
exactCommands("doctor", ["doctor"], async ({ guided, flags, planOptions }) => {
|
|
9
|
+
const data = await runRuntimeStackDoctor(planOptions);
|
|
10
|
+
return renderedCommand({
|
|
11
|
+
data,
|
|
12
|
+
render: (result, verbosity) => renderDoctor(result, verbosity),
|
|
13
|
+
status: data.state === "READY" ? "success" : "not_ready",
|
|
14
|
+
...(data.state !== "READY" && !guided ? { exitCode: 1 } : {}),
|
|
15
|
+
});
|
|
16
|
+
}),
|
|
17
|
+
exactCommands("support", ["support"], async ({ planOptions }) => {
|
|
18
|
+
const data = await collectRehearsalSupportReport(planOptions, {
|
|
19
|
+
runDoctor: async (options) => {
|
|
20
|
+
const report = await runRuntimeStackDoctor(options);
|
|
21
|
+
const targetAliases = new Map();
|
|
22
|
+
const aliasFor = (name) => {
|
|
23
|
+
if (!targetAliases.has(name)) {
|
|
24
|
+
targetAliases.set(name, `runtime-${targetAliases.size + 1}`);
|
|
25
|
+
}
|
|
26
|
+
return targetAliases.get(name);
|
|
27
|
+
};
|
|
28
|
+
return {
|
|
29
|
+
...report,
|
|
30
|
+
checks: report.checks.map((check) => {
|
|
31
|
+
const separator = check.id.indexOf(":");
|
|
32
|
+
if (separator === -1)
|
|
33
|
+
return check;
|
|
34
|
+
const target = check.id.slice(0, separator);
|
|
35
|
+
const alias = aliasFor(target);
|
|
36
|
+
return {
|
|
37
|
+
...check,
|
|
38
|
+
id: `${alias}:${check.id.slice(separator + 1)}`,
|
|
39
|
+
label: check.label.replace(`${target}:`, `${alias}:`),
|
|
40
|
+
};
|
|
41
|
+
}),
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
return renderedCommand({
|
|
46
|
+
data,
|
|
47
|
+
render: (result) => renderSupportReport(result),
|
|
48
|
+
});
|
|
49
|
+
}),
|
|
50
|
+
{
|
|
51
|
+
id: "explain",
|
|
52
|
+
matches: ({ command, flags }) => command === "explain" || (command === "run" && flags.dryRun),
|
|
53
|
+
handle: async ({ planOptions }) => {
|
|
54
|
+
const data = await buildRuntimeStackPlan(planOptions);
|
|
55
|
+
return renderedCommand({
|
|
56
|
+
data,
|
|
57
|
+
render: (result, verbosity) => renderPlan(result, verbosity),
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
exactCommands("inspect-baseline", ["inspect baseline"], async ({ planOptions }) => {
|
|
62
|
+
const data = await inspectRehearsalBaseline(planOptions);
|
|
63
|
+
return renderedCommand({ data, render: renderBaseline });
|
|
64
|
+
}),
|
|
65
|
+
exactCommands("inspect-migrations", ["inspect migrations"], async ({ planOptions }) => {
|
|
66
|
+
const data = await inspectRehearsalMigrations(planOptions);
|
|
67
|
+
return renderedCommand({
|
|
68
|
+
data,
|
|
69
|
+
render: (result, verbosity) => renderMigrations(result, verbosity),
|
|
70
|
+
});
|
|
71
|
+
}),
|
|
72
|
+
exactCommands("candidates", ["candidates"], async ({ planOptions }) => {
|
|
73
|
+
const { topology: _topology, ...data } = await topologyCandidateSummary(planOptions);
|
|
74
|
+
return renderedCommand({
|
|
75
|
+
data,
|
|
76
|
+
render: (result, verbosity) => renderCandidates(result, verbosity),
|
|
77
|
+
});
|
|
78
|
+
}),
|
|
79
|
+
];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Project setup, baseline creation, privacy-key, and cleanup command handlers. */
|
|
2
|
+
import type { CommandDefinition } from "./command_contract.mjs";
|
|
3
|
+
import type { createProjectCommands } from "./project_commands.mjs";
|
|
4
|
+
type ProjectCommands = ReturnType<typeof createProjectCommands>;
|
|
5
|
+
export declare const createProjectCommandHandlers: ({ projectRoot, runSetup, runBaselinePreparation, runCleanup, }: {
|
|
6
|
+
projectRoot: string;
|
|
7
|
+
runSetup: ProjectCommands["runSetup"];
|
|
8
|
+
runBaselinePreparation: ProjectCommands["runBaselinePreparation"];
|
|
9
|
+
runCleanup: ProjectCommands["runCleanup"];
|
|
10
|
+
}) => readonly CommandDefinition[];
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/** Project setup, baseline creation, privacy-key, and cleanup command handlers. */
|
|
2
|
+
import { relative } from "node:path";
|
|
3
|
+
import { createSyntheticBaselineFromFiles } from "../baseline/builder.mjs";
|
|
4
|
+
import { inspectBaselineInputFiles } from "../baseline/preparation.mjs";
|
|
5
|
+
import { createPrivacyKey } from "../baseline/privacy_engine.mjs";
|
|
6
|
+
import { formatCount } from "../shared/human_output.mjs";
|
|
7
|
+
import { runRehearsalInit } from "./init_command.mjs";
|
|
8
|
+
import { loadPreparationContext } from "./source_commands.mjs";
|
|
9
|
+
import { renderBaselinePreparation, renderCleanup, renderInit, renderSetup, } from "./renderers.mjs";
|
|
10
|
+
import { exactCommands, renderedCommand } from "./command_contract.mjs";
|
|
11
|
+
export const createProjectCommandHandlers = ({ projectRoot, runSetup, runBaselinePreparation, runCleanup, }) => [
|
|
12
|
+
exactCommands("setup", ["setup"], async ({ flags, planOptions, state }) => {
|
|
13
|
+
const data = await runSetup({ flags, planOptions, session: state });
|
|
14
|
+
return renderedCommand({
|
|
15
|
+
data,
|
|
16
|
+
render: () => renderSetup(data),
|
|
17
|
+
});
|
|
18
|
+
}),
|
|
19
|
+
exactCommands("init", ["init"], async ({ flags }) => {
|
|
20
|
+
const data = await runRehearsalInit({ projectRoot, flags });
|
|
21
|
+
return renderedCommand({
|
|
22
|
+
data,
|
|
23
|
+
render: (result) => renderInit(result),
|
|
24
|
+
});
|
|
25
|
+
}),
|
|
26
|
+
exactCommands("baseline-prepare", ["baseline prepare"], async ({ flags, planOptions, state }) => {
|
|
27
|
+
const data = await runBaselinePreparation({
|
|
28
|
+
flags,
|
|
29
|
+
planOptions,
|
|
30
|
+
session: state,
|
|
31
|
+
});
|
|
32
|
+
return renderedCommand({
|
|
33
|
+
data,
|
|
34
|
+
render: (result) => renderBaselinePreparation(result, flags),
|
|
35
|
+
});
|
|
36
|
+
}),
|
|
37
|
+
exactCommands("baseline-create", ["baseline create"], async ({ flags, planOptions }) => {
|
|
38
|
+
if (!flags.recordsPath || !flags.ledgerPath) {
|
|
39
|
+
throw new Error("baseline create requires --records and --ledger input paths.");
|
|
40
|
+
}
|
|
41
|
+
const baselineInput = {
|
|
42
|
+
...planOptions,
|
|
43
|
+
recordsPath: flags.recordsPath,
|
|
44
|
+
ledgerPath: flags.ledgerPath,
|
|
45
|
+
...(flags.assetsPath === undefined
|
|
46
|
+
? {}
|
|
47
|
+
: { assetsPath: flags.assetsPath }),
|
|
48
|
+
};
|
|
49
|
+
await inspectBaselineInputFiles(baselineInput);
|
|
50
|
+
const data = await createSyntheticBaselineFromFiles(baselineInput);
|
|
51
|
+
return renderedCommand({
|
|
52
|
+
data,
|
|
53
|
+
render: (baseline) => [
|
|
54
|
+
`Activated synthetic baseline ${baseline.generationId}: ${formatCount(baseline.rowCount, "row")} across ${formatCount(baseline.tableCount, "table")}; ${formatCount(baseline.migrationCount, "migration")} through ${baseline.migrationCutoff}.`,
|
|
55
|
+
"",
|
|
56
|
+
flags.guided
|
|
57
|
+
? "Next: choose Run a rehearsal when you are ready."
|
|
58
|
+
: "Next: run rehearsal doctor to check readiness.",
|
|
59
|
+
].join("\n"),
|
|
60
|
+
});
|
|
61
|
+
}),
|
|
62
|
+
exactCommands("privacy-key", ["privacy key"], async ({ flags, planOptions }) => {
|
|
63
|
+
const { loaded } = await loadPreparationContext(planOptions);
|
|
64
|
+
if (!loaded.paths.privacyKey) {
|
|
65
|
+
throw new Error("No config.preparation.privacyKey is configured.");
|
|
66
|
+
}
|
|
67
|
+
const privacyKeyPath = loaded.paths.privacyKey;
|
|
68
|
+
const data = flags.write
|
|
69
|
+
? {
|
|
70
|
+
mode: "written",
|
|
71
|
+
path: relative(loaded.projectRoot, privacyKeyPath),
|
|
72
|
+
...(await createPrivacyKey(privacyKeyPath)),
|
|
73
|
+
}
|
|
74
|
+
: {
|
|
75
|
+
mode: "preview",
|
|
76
|
+
path: relative(loaded.projectRoot, privacyKeyPath),
|
|
77
|
+
};
|
|
78
|
+
return renderedCommand({
|
|
79
|
+
data,
|
|
80
|
+
render: (result) => result.mode === "preview"
|
|
81
|
+
? `PRIVACY KEY — PREVIEW\n\nWould create ${result.path} with owner-only permissions.\nNothing was written. Rerun with --write after reviewing the path.`
|
|
82
|
+
: `Created owner-only privacy key at ${result.path}.\nFingerprint: ${result.fingerprint}\nThe key is excluded from source control and must not be shared.`,
|
|
83
|
+
});
|
|
84
|
+
}),
|
|
85
|
+
exactCommands("cleanup", ["cleanup"], async ({ flags, planOptions, state }) => {
|
|
86
|
+
const data = await runCleanup({ flags, planOptions, session: state });
|
|
87
|
+
return renderedCommand({
|
|
88
|
+
data,
|
|
89
|
+
render: () => renderCleanup(data),
|
|
90
|
+
});
|
|
91
|
+
}),
|
|
92
|
+
];
|