@rehearsal-db/core 0.1.0-beta.15 → 0.1.0-beta.17
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 +47 -1
- package/dist/package.json +10 -9
- 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 +1 -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 -162
- package/dist/src/baseline/privacy_engine.mjs +4 -1162
- 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_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/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 +0 -5
- package/dist/src/cli/arguments.mjs +2 -7
- 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 +3 -1
- package/dist/src/cli/guided.mjs +21 -7
- 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 +1 -1
- 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 +131 -5
- package/dist/src/cli/project_commands.mjs +17 -8
- package/dist/src/cli/rehearsal.d.mts +2 -2
- package/dist/src/cli/rehearsal.mjs +51 -567
- 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 +32 -3
- package/dist/src/cli/runtime_commands.mjs +33 -13
- 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 +5 -1
- package/dist/src/cli/source_commands.mjs +3 -1
- package/dist/src/identity/claim.d.mts +5 -144
- package/dist/src/identity/claim.mjs +4 -947
- 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/index.d.mts +1 -1
- package/dist/src/project/configuration.d.mts +11 -179
- package/dist/src/project/configuration.mjs +7 -956
- 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 +226 -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/project_detection.d.mts +14 -0
- package/dist/src/project/project_detection.mjs +47 -0
- package/dist/src/runtime/lifecycle_engine.d.mts +63 -0
- package/dist/src/runtime/lifecycle_engine.mjs +114 -0
- package/dist/src/runtime/plan.mjs +2 -1
- package/dist/src/targets/postgresql.mjs +142 -163
- package/dist/src/targets/supabase.mjs +142 -174
- package/docs/architecture.md +120 -3
- package/docs/commands.md +18 -15
- package/docs/configuration.md +1 -0
- package/docs/releasing.md +8 -3
- package/docs/runtime-policies.md +12 -3
- package/docs/troubleshooting.md +17 -0
- package/docs/typescript.md +57 -10
- package/package.json +10 -9
- package/types/configuration.d.mts +5 -164
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/** Redacted primitive validators and closed vocabulary for privacy policy v2. */
|
|
2
|
+
export const IDENTIFIER = /^[a-z][a-z0-9_]{0,62}$/u;
|
|
3
|
+
export const BINDING_NAME = /^[a-z][a-z0-9-]{0,62}$/u;
|
|
4
|
+
export const ENVIRONMENT_KEY = /^[A-Z][A-Z0-9_]*$/u;
|
|
5
|
+
export const HEX_64 = /^[a-f0-9]{64}$/u;
|
|
6
|
+
export const UUID = /^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/iu;
|
|
7
|
+
export const PORTABLE_CODE = /^[A-Za-z0-9][A-Za-z0-9_-]*$/u;
|
|
8
|
+
export const ACTIONS = new Set([
|
|
9
|
+
"KEEP",
|
|
10
|
+
"PSEUDONYMIZE",
|
|
11
|
+
"REPLACE",
|
|
12
|
+
"EXCLUDE",
|
|
13
|
+
"DERIVE",
|
|
14
|
+
]);
|
|
15
|
+
export const PSEUDONYM_FORMATS = new Set([
|
|
16
|
+
"uuid",
|
|
17
|
+
"email",
|
|
18
|
+
"text",
|
|
19
|
+
"integer",
|
|
20
|
+
"hex",
|
|
21
|
+
"gtin",
|
|
22
|
+
"url",
|
|
23
|
+
]);
|
|
24
|
+
export const HEX_LENGTHS = new Set([32, 64]);
|
|
25
|
+
export const GTIN_LENGTHS = new Set([8, 12, 13, 14]);
|
|
26
|
+
export const DATE_REPRESENTATIONS = new Set([
|
|
27
|
+
"iso-string",
|
|
28
|
+
"epoch-milliseconds",
|
|
29
|
+
]);
|
|
30
|
+
export const DERIVATION_KINDS = new Set([
|
|
31
|
+
"date-shift",
|
|
32
|
+
"digest",
|
|
33
|
+
"enum",
|
|
34
|
+
"validated-string",
|
|
35
|
+
"path-map",
|
|
36
|
+
"json-object",
|
|
37
|
+
"json-array",
|
|
38
|
+
"json-union",
|
|
39
|
+
"json-dictionary",
|
|
40
|
+
"approved-owner",
|
|
41
|
+
"binding-substitute",
|
|
42
|
+
]);
|
|
43
|
+
export const URL_PATH_PREFIX = "/rehearsal/";
|
|
44
|
+
export const URL_TOKEN_LENGTH = 32;
|
|
45
|
+
export const MAXIMUM_URL_LENGTH = 2_048;
|
|
46
|
+
export const MAXIMUM_TEXT_SUBSTITUTION_BYTES = 65_536;
|
|
47
|
+
export const LOOPBACK_HOSTNAMES = new Set([
|
|
48
|
+
"127.0.0.1",
|
|
49
|
+
"::1",
|
|
50
|
+
"[::1]",
|
|
51
|
+
"localhost",
|
|
52
|
+
]);
|
|
53
|
+
export const DEFAULT_PATH_MAXIMUM_BYTES = 2_048;
|
|
54
|
+
export const DEFAULT_PATH_MAXIMUM_SEGMENTS = 32;
|
|
55
|
+
export const MAXIMUM_POLICY_DEPTH = 16;
|
|
56
|
+
export const MAXIMUM_ENUM_VALUES = 128;
|
|
57
|
+
export const MAXIMUM_ENUM_STRING_BYTES = 256;
|
|
58
|
+
export const isPrivacyObject = (value) => value !== null &&
|
|
59
|
+
typeof value === "object" &&
|
|
60
|
+
!Array.isArray(value) &&
|
|
61
|
+
Object.getPrototypeOf(value) === Object.prototype;
|
|
62
|
+
export const assertKnownKeys = (value, allowed, label) => {
|
|
63
|
+
for (const key of Object.keys(value)) {
|
|
64
|
+
if (!allowed.includes(key))
|
|
65
|
+
throw new Error(`${label}.${key} is unknown.`);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
export const validateIdentifier = (value, label) => {
|
|
69
|
+
if (typeof value !== "string" || !IDENTIFIER.test(value)) {
|
|
70
|
+
throw new Error(`${label} must be a lowercase PostgreSQL identifier.`);
|
|
71
|
+
}
|
|
72
|
+
return value;
|
|
73
|
+
};
|
|
74
|
+
export const validateNonEmpty = (value, label) => {
|
|
75
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
76
|
+
throw new Error(`${label} must be a non-empty string.`);
|
|
77
|
+
}
|
|
78
|
+
return value.trim();
|
|
79
|
+
};
|
|
80
|
+
export const validatePositiveInteger = (value, label, maximum = Number.MAX_SAFE_INTEGER) => {
|
|
81
|
+
if (typeof value !== "number" ||
|
|
82
|
+
!Number.isSafeInteger(value) ||
|
|
83
|
+
value < 1 ||
|
|
84
|
+
value > maximum) {
|
|
85
|
+
throw new Error(`${label} must be an integer from 1 through ${maximum}.`);
|
|
86
|
+
}
|
|
87
|
+
return value;
|
|
88
|
+
};
|
|
89
|
+
export const validateReviewedScalar = (value, label) => {
|
|
90
|
+
if (typeof value === "string") {
|
|
91
|
+
if (Buffer.byteLength(value) > MAXIMUM_ENUM_STRING_BYTES) {
|
|
92
|
+
throw new Error(`${label} must contain at most ${MAXIMUM_ENUM_STRING_BYTES} UTF-8 bytes.`);
|
|
93
|
+
}
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
if (typeof value === "boolean")
|
|
97
|
+
return value;
|
|
98
|
+
if (typeof value === "number" &&
|
|
99
|
+
Number.isFinite(value) &&
|
|
100
|
+
!Object.is(value, -0)) {
|
|
101
|
+
return value;
|
|
102
|
+
}
|
|
103
|
+
throw new Error(`${label} must be a JSON string, finite number, or boolean.`);
|
|
104
|
+
};
|
|
105
|
+
export const validateHttpsLoopbackOrigin = (value, label) => {
|
|
106
|
+
if (typeof value !== "string" || value.trim() !== value || !value) {
|
|
107
|
+
throw new Error(`${label} must be a safe HTTPS loopback origin.`);
|
|
108
|
+
}
|
|
109
|
+
let parsed;
|
|
110
|
+
try {
|
|
111
|
+
parsed = new URL(value);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
throw new Error(`${label} must be a safe HTTPS loopback origin.`);
|
|
115
|
+
}
|
|
116
|
+
if (parsed.protocol !== "https:" ||
|
|
117
|
+
!LOOPBACK_HOSTNAMES.has(parsed.hostname) ||
|
|
118
|
+
![parsed.origin, `${parsed.origin}/`].includes(value) ||
|
|
119
|
+
parsed.username ||
|
|
120
|
+
parsed.password ||
|
|
121
|
+
(parsed.pathname !== "/" && parsed.pathname !== "") ||
|
|
122
|
+
parsed.search ||
|
|
123
|
+
parsed.hash) {
|
|
124
|
+
throw new Error(`${label} must be a safe HTTPS loopback origin.`);
|
|
125
|
+
}
|
|
126
|
+
return parsed.origin;
|
|
127
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Persistent application-session command handler. */
|
|
2
|
+
import type { CommandDefinition } from "./command_contract.mjs";
|
|
3
|
+
import type { createRuntimeCommands } from "./runtime_commands.mjs";
|
|
4
|
+
type RuntimeCommands = ReturnType<typeof createRuntimeCommands>;
|
|
5
|
+
export declare const createApplicationCommandHandlers: ({ openRuntimeApplication, }: {
|
|
6
|
+
openRuntimeApplication: RuntimeCommands["openRuntimeApplication"];
|
|
7
|
+
}) => readonly CommandDefinition[];
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** Persistent application-session command handler. */
|
|
2
|
+
import * as prompts from "@clack/prompts";
|
|
3
|
+
import { exactCommands, renderedCommand } from "./command_contract.mjs";
|
|
4
|
+
import { useStyledPrompts } from "./terminal.mjs";
|
|
5
|
+
export const createApplicationCommandHandlers = ({ openRuntimeApplication, }) => [
|
|
6
|
+
exactCommands("open", ["open"], async ({ flags, planOptions, guided, state }) => {
|
|
7
|
+
if (flags.dryRun) {
|
|
8
|
+
throw new Error("--dry-run is supported only by rehearsal run.");
|
|
9
|
+
}
|
|
10
|
+
const data = await openRuntimeApplication({
|
|
11
|
+
flags,
|
|
12
|
+
planOptions,
|
|
13
|
+
session: state,
|
|
14
|
+
onReady: (ready) => {
|
|
15
|
+
const lines = [
|
|
16
|
+
`Application: ${ready.url}`,
|
|
17
|
+
"The verified database runtime stays running and keeps your local changes.",
|
|
18
|
+
guided
|
|
19
|
+
? "Press Ctrl+C to close the app and return here, or Ctrl+Z to exit Rehearsal."
|
|
20
|
+
: "Press Ctrl+C or Ctrl+Z to close the app. Rehearsal will leave the database runtime running.",
|
|
21
|
+
];
|
|
22
|
+
if (useStyledPrompts(flags)) {
|
|
23
|
+
prompts.note(lines.join("\n"), "SANDBOX READY");
|
|
24
|
+
}
|
|
25
|
+
else if (!flags.json) {
|
|
26
|
+
console.log(["", "SANDBOX READY", ...lines, ""].join("\n"));
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
if (guided && data.application.stoppedBy === "SIGTSTP") {
|
|
31
|
+
flags.exitGuidedSession = true;
|
|
32
|
+
}
|
|
33
|
+
if (guided) {
|
|
34
|
+
state.lastGuidedDetails = [
|
|
35
|
+
data.started.output,
|
|
36
|
+
data.verified.output,
|
|
37
|
+
...data.preparations.map((preparation) => preparation.output),
|
|
38
|
+
]
|
|
39
|
+
.filter(Boolean)
|
|
40
|
+
.join("\n");
|
|
41
|
+
}
|
|
42
|
+
return renderedCommand({
|
|
43
|
+
data,
|
|
44
|
+
render: (result) => [
|
|
45
|
+
"SANDBOX APP CLOSED",
|
|
46
|
+
`Application stopped after ${result.application.stoppedBy}.`,
|
|
47
|
+
"The database runtime and its local database and Storage changes were preserved.",
|
|
48
|
+
"Run rehearsal open to return, rehearsal verify to check it, or rehearsal stop to stop the databases.",
|
|
49
|
+
].join("\n"),
|
|
50
|
+
});
|
|
51
|
+
}),
|
|
52
|
+
];
|
|
@@ -22,13 +22,8 @@ export interface RehearsalCliFlags {
|
|
|
22
22
|
refreshConfirmation: string | undefined;
|
|
23
23
|
identityConfirmation: string | undefined;
|
|
24
24
|
identityName: string | undefined;
|
|
25
|
-
runtimeTopology: unknown | undefined;
|
|
26
|
-
targetConfirmations: unknown | undefined;
|
|
27
25
|
exitGuidedSession: boolean;
|
|
28
26
|
guided: boolean;
|
|
29
|
-
setupPlan?: unknown;
|
|
30
|
-
preparationPlan?: unknown;
|
|
31
|
-
cleanupPlan?: unknown;
|
|
32
27
|
}
|
|
33
28
|
export interface ParsedArguments {
|
|
34
29
|
flags: RehearsalCliFlags;
|
|
@@ -22,8 +22,6 @@ export const parseArguments = (arguments_) => {
|
|
|
22
22
|
refreshConfirmation: undefined,
|
|
23
23
|
identityConfirmation: undefined,
|
|
24
24
|
identityName: undefined,
|
|
25
|
-
runtimeTopology: undefined,
|
|
26
|
-
targetConfirmations: undefined,
|
|
27
25
|
exitGuidedSession: false,
|
|
28
26
|
guided: false,
|
|
29
27
|
};
|
|
@@ -97,12 +95,7 @@ export const resetGuidedFlags = (flags) => {
|
|
|
97
95
|
refreshConfirmation: undefined,
|
|
98
96
|
identityConfirmation: undefined,
|
|
99
97
|
identityName: undefined,
|
|
100
|
-
runtimeTopology: undefined,
|
|
101
|
-
targetConfirmations: undefined,
|
|
102
98
|
exitGuidedSession: false,
|
|
103
|
-
setupPlan: undefined,
|
|
104
|
-
preparationPlan: undefined,
|
|
105
|
-
cleanupPlan: undefined,
|
|
106
99
|
});
|
|
107
100
|
};
|
|
108
101
|
export const commandMutatesProject = ({ command, flags, guided, }) => {
|
|
@@ -120,6 +113,8 @@ export const commandMutatesProject = ({ command, flags, guided, }) => {
|
|
|
120
113
|
return Boolean(flags.refreshConfirmation);
|
|
121
114
|
if (command === "identity claim")
|
|
122
115
|
return Boolean(flags.identityConfirmation);
|
|
116
|
+
if (command === "identity connect")
|
|
117
|
+
return true;
|
|
123
118
|
if (command === "run")
|
|
124
119
|
return !flags.dryRun;
|
|
125
120
|
return [
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** Stable version and help handlers with no project-side effects. */
|
|
2
|
+
import { REHEARSAL_VERSION } from "../shared/diagnostics.mjs";
|
|
3
|
+
import { exactCommands, textCommand } from "./command_contract.mjs";
|
|
4
|
+
export const usage = () => `Usage: rehearsal <command> [options]
|
|
5
|
+
|
|
6
|
+
Commands:
|
|
7
|
+
version Print the installed Rehearsal version
|
|
8
|
+
guide Open the interactive, state-aware home screen
|
|
9
|
+
setup [--target=] [--write] Preview or create safe first-run scaffolding
|
|
10
|
+
init [--write] Preview or explicitly write safe starter config
|
|
11
|
+
baseline prepare --records= --ledger= [--write] Create a fail-closed policy draft
|
|
12
|
+
baseline create --records= --ledger= [--assets=] Create a baseline from safe local inputs
|
|
13
|
+
privacy key [--write] Preview or create the owner-only pseudonym key
|
|
14
|
+
source plan Preview exact, temporary source access
|
|
15
|
+
source apply --confirm-source-access= Apply the reviewed source-access plan
|
|
16
|
+
source retire [--confirm-source-retirement=] Preview or retire exact source access
|
|
17
|
+
baseline refresh Replace only the baseline from a reviewed source
|
|
18
|
+
refresh [--confirm-refresh=] Replace the database copy, reset locally, and remove old copies
|
|
19
|
+
identity plan --identity= Preview an approved local identity association
|
|
20
|
+
identity claim --identity= --confirm-identity= Apply the exact local association
|
|
21
|
+
identity connect [--identity=] Keep the app open, detect sign-in, and connect once
|
|
22
|
+
doctor Check whether Rehearsal is safe and ready
|
|
23
|
+
support Print a safe, copy-ready support report
|
|
24
|
+
explain Show the immutable execution plan
|
|
25
|
+
run --dry-run Alias the exact explain plan without mutations
|
|
26
|
+
run --confirm-candidates= Execute reset, migration, and verification locally
|
|
27
|
+
open Start the runtime and keep the application open
|
|
28
|
+
candidates Show the exact pending migration digest
|
|
29
|
+
inspect baseline Show verified baseline provenance
|
|
30
|
+
inspect migrations Classify represented, applied, and candidate migrations
|
|
31
|
+
start Start an existing verified local runtime
|
|
32
|
+
migrate --confirm-candidates= Apply the exact candidate suffix without resetting
|
|
33
|
+
reset Restore and verify the immutable local baseline
|
|
34
|
+
status Report the disposable local runtime state
|
|
35
|
+
stop Stop only this project's local runtime
|
|
36
|
+
discard Remove only this project's disposable runtime
|
|
37
|
+
cleanup [--include-runtime] [--include-images] [--write] [--confirm-cleanup=]
|
|
38
|
+
Preview or apply conservative disk cleanup
|
|
39
|
+
verify Verify the current local Rehearsal runtime
|
|
40
|
+
|
|
41
|
+
Options: --help, -h --version, -V --json --verbose --debug --plain
|
|
42
|
+
--config=<path> --target=supabase|postgresql
|
|
43
|
+
--confirm-source-access=<digest> --confirm-source-retirement=<digest>
|
|
44
|
+
--confirm-refresh=<digest>`;
|
|
45
|
+
export const createBasicCommandHandlers = () => [
|
|
46
|
+
{
|
|
47
|
+
id: "version",
|
|
48
|
+
matches: ({ command, flags }) => command === "version" || flags.version,
|
|
49
|
+
handle: async ({ flags }) => textCommand(flags.json
|
|
50
|
+
? JSON.stringify({ name: "@rehearsal-db/core", version: REHEARSAL_VERSION }, null, 2)
|
|
51
|
+
: REHEARSAL_VERSION),
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
...exactCommands("help", ["help"], async () => textCommand(usage())),
|
|
55
|
+
matches: ({ command, flags }) => command === "help" || flags.help,
|
|
56
|
+
},
|
|
57
|
+
];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Assemble the CLI command families into one ordered typed registry. */
|
|
2
|
+
import type { createProjectCommands } from "./project_commands.mjs";
|
|
3
|
+
import type { createRuntimeCommands } from "./runtime_commands.mjs";
|
|
4
|
+
import type { createRefreshWorkflow } from "./source_commands.mjs";
|
|
5
|
+
type ProjectCommands = ReturnType<typeof createProjectCommands>;
|
|
6
|
+
type RuntimeCommands = ReturnType<typeof createRuntimeCommands>;
|
|
7
|
+
type RefreshWorkflow = ReturnType<typeof createRefreshWorkflow>;
|
|
8
|
+
export declare const createCliCommandRegistry: ({ projectRoot, projectCommands, runtimeCommands, runRefreshWorkflow, }: {
|
|
9
|
+
projectRoot: string;
|
|
10
|
+
projectCommands: ProjectCommands;
|
|
11
|
+
runtimeCommands: RuntimeCommands;
|
|
12
|
+
runRefreshWorkflow: RefreshWorkflow;
|
|
13
|
+
}) => import("./command_registry.mjs").CommandRegistry;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Assemble the CLI command families into one ordered typed registry. */
|
|
2
|
+
import { createApplicationCommandHandlers } from "./application_command_handlers.mjs";
|
|
3
|
+
import { createBasicCommandHandlers } from "./basic_command_handlers.mjs";
|
|
4
|
+
import { createCommandRegistry } from "./command_registry.mjs";
|
|
5
|
+
import { createIdentityCommandHandlers } from "./identity_command_handlers.mjs";
|
|
6
|
+
import { createInspectionCommandHandlers } from "./inspection_command_handlers.mjs";
|
|
7
|
+
import { createProjectCommandHandlers } from "./project_command_handlers.mjs";
|
|
8
|
+
import { createRuntimeActionHandlers } from "./runtime_action_handlers.mjs";
|
|
9
|
+
import { createSourceCommandHandlers } from "./source_command_handlers.mjs";
|
|
10
|
+
export const createCliCommandRegistry = ({ projectRoot, projectCommands, runtimeCommands, runRefreshWorkflow, }) => createCommandRegistry([
|
|
11
|
+
...createBasicCommandHandlers(),
|
|
12
|
+
...createProjectCommandHandlers({
|
|
13
|
+
projectRoot,
|
|
14
|
+
runSetup: projectCommands.runSetup,
|
|
15
|
+
runBaselinePreparation: projectCommands.runBaselinePreparation,
|
|
16
|
+
runCleanup: projectCommands.runCleanup,
|
|
17
|
+
}),
|
|
18
|
+
...createSourceCommandHandlers({ runRefreshWorkflow }),
|
|
19
|
+
...createIdentityCommandHandlers({
|
|
20
|
+
loadIdentityClaimPlan: projectCommands.loadIdentityClaimPlan,
|
|
21
|
+
loadIdentityPolicy: projectCommands.loadIdentityPolicy,
|
|
22
|
+
startRuntimeApplication: runtimeCommands.startRuntimeApplication,
|
|
23
|
+
}),
|
|
24
|
+
...createInspectionCommandHandlers(),
|
|
25
|
+
...createApplicationCommandHandlers({
|
|
26
|
+
openRuntimeApplication: runtimeCommands.openRuntimeApplication,
|
|
27
|
+
}),
|
|
28
|
+
...createRuntimeActionHandlers({
|
|
29
|
+
prepareCandidateConfirmation: runtimeCommands.prepareCandidateConfirmation,
|
|
30
|
+
prepareDependentTargets: runtimeCommands.prepareDependentTargets,
|
|
31
|
+
proveRuntimeStack: runtimeCommands.proveRuntimeStack,
|
|
32
|
+
runRuntimeStack: runtimeCommands.runRuntimeStack,
|
|
33
|
+
}),
|
|
34
|
+
]);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** Typed contracts shared by CLI command handlers and the process entry point. */
|
|
2
|
+
import type { RehearsalConfigPathOptions } from "../project/configuration.mjs";
|
|
3
|
+
import type { RehearsalStatus } from "../shared/diagnostics.mjs";
|
|
4
|
+
import type { RehearsalCliFlags, RehearsalVerbosity } from "./arguments.mjs";
|
|
5
|
+
export interface CliSessionState {
|
|
6
|
+
lastGuidedDetails: string | undefined;
|
|
7
|
+
runtimeTopology: unknown | undefined;
|
|
8
|
+
targetConfirmations: unknown | undefined;
|
|
9
|
+
setupPlan: unknown | undefined;
|
|
10
|
+
preparationPlan: unknown | undefined;
|
|
11
|
+
cleanupPlan: unknown | undefined;
|
|
12
|
+
}
|
|
13
|
+
export declare const createCliSessionState: () => CliSessionState;
|
|
14
|
+
export declare const resetCommandSessionState: (state: CliSessionState) => void;
|
|
15
|
+
export interface CommandContext {
|
|
16
|
+
readonly command: string;
|
|
17
|
+
readonly flags: RehearsalCliFlags;
|
|
18
|
+
readonly planOptions: RehearsalConfigPathOptions;
|
|
19
|
+
readonly guided: boolean;
|
|
20
|
+
readonly state: CliSessionState;
|
|
21
|
+
}
|
|
22
|
+
export interface RenderedCommandResult {
|
|
23
|
+
readonly kind: "rendered";
|
|
24
|
+
readonly data: unknown;
|
|
25
|
+
readonly status: RehearsalStatus;
|
|
26
|
+
readonly render: (verbosity: RehearsalVerbosity) => string;
|
|
27
|
+
readonly exitCode?: number;
|
|
28
|
+
}
|
|
29
|
+
export interface TextCommandResult {
|
|
30
|
+
readonly kind: "text";
|
|
31
|
+
readonly output: string;
|
|
32
|
+
readonly exitCode?: number;
|
|
33
|
+
}
|
|
34
|
+
export interface SilentCommandResult {
|
|
35
|
+
readonly kind: "silent";
|
|
36
|
+
readonly exitCode?: number;
|
|
37
|
+
}
|
|
38
|
+
export type CommandResult = RenderedCommandResult | TextCommandResult | SilentCommandResult;
|
|
39
|
+
export type CommandHandler = (context: CommandContext) => Promise<CommandResult>;
|
|
40
|
+
export interface CommandDefinition {
|
|
41
|
+
readonly id: string;
|
|
42
|
+
readonly matches: (context: CommandContext) => boolean;
|
|
43
|
+
readonly handle: CommandHandler;
|
|
44
|
+
}
|
|
45
|
+
export declare const renderedCommand: <Data>({ data, render, status, exitCode, }: {
|
|
46
|
+
data: Data;
|
|
47
|
+
render: (data: Data, verbosity: RehearsalVerbosity) => string;
|
|
48
|
+
status?: RehearsalStatus;
|
|
49
|
+
exitCode?: number;
|
|
50
|
+
}) => RenderedCommandResult;
|
|
51
|
+
export declare const textCommand: (output: string, options?: {
|
|
52
|
+
exitCode?: number;
|
|
53
|
+
}) => TextCommandResult;
|
|
54
|
+
export declare const silentCommand: (options?: {
|
|
55
|
+
exitCode?: number;
|
|
56
|
+
}) => SilentCommandResult;
|
|
57
|
+
export declare const exactCommands: (id: string, commands: readonly string[], handle: CommandHandler) => CommandDefinition;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Typed contracts shared by CLI command handlers and the process entry point. */
|
|
2
|
+
export const createCliSessionState = () => ({
|
|
3
|
+
lastGuidedDetails: undefined,
|
|
4
|
+
runtimeTopology: undefined,
|
|
5
|
+
targetConfirmations: undefined,
|
|
6
|
+
setupPlan: undefined,
|
|
7
|
+
preparationPlan: undefined,
|
|
8
|
+
cleanupPlan: undefined,
|
|
9
|
+
});
|
|
10
|
+
export const resetCommandSessionState = (state) => {
|
|
11
|
+
Object.assign(state, {
|
|
12
|
+
runtimeTopology: undefined,
|
|
13
|
+
targetConfirmations: undefined,
|
|
14
|
+
setupPlan: undefined,
|
|
15
|
+
preparationPlan: undefined,
|
|
16
|
+
cleanupPlan: undefined,
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
export const renderedCommand = ({ data, render, status = "success", exitCode, }) => ({
|
|
20
|
+
kind: "rendered",
|
|
21
|
+
data,
|
|
22
|
+
status,
|
|
23
|
+
render: (verbosity) => render(data, verbosity),
|
|
24
|
+
...(exitCode === undefined ? {} : { exitCode }),
|
|
25
|
+
});
|
|
26
|
+
export const textCommand = (output, options = {}) => ({
|
|
27
|
+
kind: "text",
|
|
28
|
+
output,
|
|
29
|
+
...(options.exitCode === undefined ? {} : { exitCode: options.exitCode }),
|
|
30
|
+
});
|
|
31
|
+
export const silentCommand = (options = {}) => ({
|
|
32
|
+
kind: "silent",
|
|
33
|
+
...(options.exitCode === undefined ? {} : { exitCode: options.exitCode }),
|
|
34
|
+
});
|
|
35
|
+
export const exactCommands = (id, commands, handle) => ({
|
|
36
|
+
id,
|
|
37
|
+
matches: ({ command }) => commands.includes(command),
|
|
38
|
+
handle,
|
|
39
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Emit the stable JSON envelope or human result for a typed command result. */
|
|
2
|
+
import type { RehearsalCliFlags } from "./arguments.mjs";
|
|
3
|
+
import type { CommandResult } from "./command_contract.mjs";
|
|
4
|
+
export declare const createCommandEmitter: ({ startedAt, startedMs, write, }: {
|
|
5
|
+
startedAt: Date;
|
|
6
|
+
startedMs: number;
|
|
7
|
+
write?: (output: string) => void;
|
|
8
|
+
}) => ({ command, flags, result, }: {
|
|
9
|
+
command: string;
|
|
10
|
+
flags: RehearsalCliFlags;
|
|
11
|
+
result: CommandResult;
|
|
12
|
+
}) => number | undefined;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Emit the stable JSON envelope or human result for a typed command result. */
|
|
2
|
+
import { performance } from "node:perf_hooks";
|
|
3
|
+
import { createRehearsalResult } from "../shared/diagnostics.mjs";
|
|
4
|
+
export const createCommandEmitter = ({ startedAt, startedMs, write = console.log, }) => ({ command, flags, result, }) => {
|
|
5
|
+
if (result.kind === "text")
|
|
6
|
+
write(result.output);
|
|
7
|
+
if (result.kind === "rendered") {
|
|
8
|
+
const envelope = createRehearsalResult({
|
|
9
|
+
command,
|
|
10
|
+
status: result.status,
|
|
11
|
+
data: result.data,
|
|
12
|
+
startedAt,
|
|
13
|
+
durationMs: Math.round((performance.now() - startedMs) * 100) / 100,
|
|
14
|
+
});
|
|
15
|
+
write(flags.json
|
|
16
|
+
? JSON.stringify(envelope, null, 2)
|
|
17
|
+
: result.render(flags.verbosity));
|
|
18
|
+
}
|
|
19
|
+
return result.exitCode;
|
|
20
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Resolve one CLI request to exactly one typed command handler. */
|
|
2
|
+
import type { CommandContext, CommandDefinition, CommandResult } from "./command_contract.mjs";
|
|
3
|
+
export interface CommandRegistry {
|
|
4
|
+
readonly ids: readonly string[];
|
|
5
|
+
execute(context: CommandContext): Promise<CommandResult | null>;
|
|
6
|
+
}
|
|
7
|
+
export declare const createCommandRegistry: (definitions: readonly CommandDefinition[]) => CommandRegistry;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Resolve one CLI request to exactly one typed command handler. */
|
|
2
|
+
export const createCommandRegistry = (definitions) => {
|
|
3
|
+
const ids = definitions.map(({ id }) => id);
|
|
4
|
+
if (new Set(ids).size !== ids.length) {
|
|
5
|
+
throw new Error("CLI command registry IDs must be unique.");
|
|
6
|
+
}
|
|
7
|
+
return Object.freeze({
|
|
8
|
+
ids: Object.freeze(ids),
|
|
9
|
+
async execute(context) {
|
|
10
|
+
const definition = definitions.find((candidate) => candidate.matches(context));
|
|
11
|
+
return definition ? definition.handle(context) : null;
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
};
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type { RehearsalConfigPathOptions } from "../project/configuration.mjs";
|
|
5
5
|
import type { RehearsalCliFlags } from "./arguments.mjs";
|
|
6
|
+
import type { CliSessionState } from "./command_contract.mjs";
|
|
6
7
|
import type { RuntimeTopologyTarget } from "../runtime/topology.mjs";
|
|
7
8
|
import type { RehearsalCleanupPlan } from "../runtime/cleanup.mjs";
|
|
8
9
|
interface TopologySummary {
|
|
@@ -33,7 +34,7 @@ interface GuidedRefreshPlan {
|
|
|
33
34
|
readonly steps: readonly string[];
|
|
34
35
|
};
|
|
35
36
|
}
|
|
36
|
-
export declare const createGuidedHome: ({ projectRoot, topologyCandidateSummary, buildRefreshWorkflowPlan, planRuntimeStackCleanup, getLastGuidedDetails, }: {
|
|
37
|
+
export declare const createGuidedHome: ({ projectRoot, topologyCandidateSummary, buildRefreshWorkflowPlan, planRuntimeStackCleanup, getLastGuidedDetails, session, }: {
|
|
37
38
|
projectRoot: string;
|
|
38
39
|
topologyCandidateSummary: (options: RehearsalConfigPathOptions) => Promise<TopologySummary>;
|
|
39
40
|
buildRefreshWorkflowPlan: (options: RehearsalConfigPathOptions) => Promise<{
|
|
@@ -44,6 +45,7 @@ export declare const createGuidedHome: ({ projectRoot, topologyCandidateSummary,
|
|
|
44
45
|
planOptions: RehearsalConfigPathOptions;
|
|
45
46
|
}) => Promise<GuidedCleanupPlan>;
|
|
46
47
|
getLastGuidedDetails: () => string | undefined;
|
|
48
|
+
session: CliSessionState;
|
|
47
49
|
}) => ({ flags, planOptions, }: {
|
|
48
50
|
flags: RehearsalCliFlags;
|
|
49
51
|
planOptions: RehearsalConfigPathOptions;
|
package/dist/src/cli/guided.mjs
CHANGED
|
@@ -95,6 +95,7 @@ const inspectGuidedProject = async ({ projectRoot, planOptions, topologyCandidat
|
|
|
95
95
|
policyAbsolutePath: loaded.paths.sanitizationPolicy,
|
|
96
96
|
...(migrationSummary === undefined ? {} : { migrationSummary }),
|
|
97
97
|
refreshConfigured: Boolean(loaded.config.preparation),
|
|
98
|
+
identityConfigured: Boolean(loaded.paths.identityPolicy),
|
|
98
99
|
dependentCount: topology?.dependents.length ?? 0,
|
|
99
100
|
runtime: topology
|
|
100
101
|
? (await Promise.all(topology.targets.map((target) => pathExists(join(target.paths.runtimeWorkdir, "baseline.json"))))).every(Boolean)
|
|
@@ -334,6 +335,13 @@ const guidedOptions = (state, hasLastDetails) => {
|
|
|
334
335
|
options.push({ label: "Check readiness", command: "doctor" });
|
|
335
336
|
}
|
|
336
337
|
if (state.node.supported && state.runtime) {
|
|
338
|
+
if (state.identityConfigured) {
|
|
339
|
+
options.push({
|
|
340
|
+
label: "Connect my copied account",
|
|
341
|
+
hint: "Sign in normally, review once, then open the copied profile",
|
|
342
|
+
command: "identity connect",
|
|
343
|
+
});
|
|
344
|
+
}
|
|
337
345
|
options.push({
|
|
338
346
|
label: "Open the sandbox app",
|
|
339
347
|
hint: "Keep it running for hands-on testing",
|
|
@@ -415,7 +423,7 @@ const showGuidedStatus = ({ state, options, flags, }) => {
|
|
|
415
423
|
].join("\n"));
|
|
416
424
|
}
|
|
417
425
|
};
|
|
418
|
-
const guideCleanup = async ({ flags, planOptions, planRuntimeStackCleanup, }) => {
|
|
426
|
+
const guideCleanup = async ({ flags, planOptions, planRuntimeStackCleanup, session, }) => {
|
|
419
427
|
const cleanupScope = await promptForChoice({
|
|
420
428
|
message: "What may Rehearsal include in the cleanup preview?",
|
|
421
429
|
flags,
|
|
@@ -444,7 +452,7 @@ const guideCleanup = async ({ flags, planOptions, planRuntimeStackCleanup, }) =>
|
|
|
444
452
|
flags.includeRuntime = ["runtime", "all"].includes(cleanupScope.command);
|
|
445
453
|
flags.includeImages = ["images", "all"].includes(cleanupScope.command);
|
|
446
454
|
const cleanupPlan = await planRuntimeStackCleanup({ flags, planOptions });
|
|
447
|
-
|
|
455
|
+
session.cleanupPlan = cleanupPlan;
|
|
448
456
|
console.log(`\n${renderCleanup({ mode: "preview", plan: cleanupPlan })}`);
|
|
449
457
|
const selectedCount = "targets" in cleanupPlan
|
|
450
458
|
? cleanupPlan.targets.reduce((total, target) => total +
|
|
@@ -465,7 +473,7 @@ const guideCleanup = async ({ flags, planOptions, planRuntimeStackCleanup, }) =>
|
|
|
465
473
|
flags.cleanupConfirmation = cleanupPlan.digest;
|
|
466
474
|
return "cleanup";
|
|
467
475
|
};
|
|
468
|
-
const guideBaseline = async ({ state, selected, flags, planOptions, projectRoot, }) => {
|
|
476
|
+
const guideBaseline = async ({ state, selected, flags, planOptions, projectRoot, session, }) => {
|
|
469
477
|
console.log([
|
|
470
478
|
"",
|
|
471
479
|
terminalStyle(flags, "1", "CREATE A SAFE BASELINE"),
|
|
@@ -526,7 +534,7 @@ const guideBaseline = async ({ state, selected, flags, planOptions, projectRoot,
|
|
|
526
534
|
console.log("No changes made.");
|
|
527
535
|
return "home";
|
|
528
536
|
}
|
|
529
|
-
|
|
537
|
+
session.preparationPlan = preparationPlan;
|
|
530
538
|
flags.write = true;
|
|
531
539
|
return "baseline prepare";
|
|
532
540
|
}
|
|
@@ -567,7 +575,7 @@ const guideBaseline = async ({ state, selected, flags, planOptions, projectRoot,
|
|
|
567
575
|
return "home";
|
|
568
576
|
}
|
|
569
577
|
};
|
|
570
|
-
export const createGuidedHome = ({ projectRoot, topologyCandidateSummary, buildRefreshWorkflowPlan, planRuntimeStackCleanup, getLastGuidedDetails, }) => async ({ flags, planOptions, }) => {
|
|
578
|
+
export const createGuidedHome = ({ projectRoot, topologyCandidateSummary, buildRefreshWorkflowPlan, planRuntimeStackCleanup, getLastGuidedDetails, session, }) => async ({ flags, planOptions, }) => {
|
|
571
579
|
const state = await inspectGuidedProject({
|
|
572
580
|
projectRoot,
|
|
573
581
|
planOptions,
|
|
@@ -659,7 +667,12 @@ export const createGuidedHome = ({ projectRoot, topologyCandidateSummary, buildR
|
|
|
659
667
|
return runtimeAction.command;
|
|
660
668
|
}
|
|
661
669
|
if (selected.command === "cleanup-guide") {
|
|
662
|
-
return guideCleanup({
|
|
670
|
+
return guideCleanup({
|
|
671
|
+
flags,
|
|
672
|
+
planOptions,
|
|
673
|
+
planRuntimeStackCleanup,
|
|
674
|
+
session,
|
|
675
|
+
});
|
|
663
676
|
}
|
|
664
677
|
if (["baseline-guide", "baseline-prepare"].includes(selected.command)) {
|
|
665
678
|
return guideBaseline({
|
|
@@ -668,6 +681,7 @@ export const createGuidedHome = ({ projectRoot, topologyCandidateSummary, buildR
|
|
|
668
681
|
flags,
|
|
669
682
|
planOptions,
|
|
670
683
|
projectRoot,
|
|
684
|
+
session,
|
|
671
685
|
});
|
|
672
686
|
}
|
|
673
687
|
if (selected.command !== "setup-write")
|
|
@@ -707,7 +721,7 @@ export const createGuidedHome = ({ projectRoot, topologyCandidateSummary, buildR
|
|
|
707
721
|
console.log("No changes made.");
|
|
708
722
|
return "home";
|
|
709
723
|
}
|
|
710
|
-
|
|
724
|
+
session.setupPlan = setupPlan;
|
|
711
725
|
flags.write = true;
|
|
712
726
|
return "setup";
|
|
713
727
|
};
|
|
@@ -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 {};
|