@rehearsal-db/core 0.1.0-beta.14 → 0.1.0-beta.16

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.
Files changed (236) hide show
  1. package/CHANGELOG.md +51 -1
  2. package/README.md +1 -0
  3. package/dist/package.json +101 -0
  4. package/dist/scripts/install/create_root_config.d.mts +3 -0
  5. package/dist/scripts/install/create_root_config.mjs +13 -0
  6. package/dist/scripts/runtime/manage_database.d.mts +6 -0
  7. package/{scripts → dist/scripts}/runtime/manage_database.mjs +6 -8
  8. package/dist/src/application/session.d.mts +96 -0
  9. package/dist/src/application/session.mjs +380 -0
  10. package/dist/src/baseline/artifact.d.mts +119 -0
  11. package/dist/src/baseline/artifact.mjs +522 -0
  12. package/dist/src/baseline/builder.d.mts +19 -0
  13. package/dist/src/baseline/builder.mjs +142 -0
  14. package/dist/src/baseline/input_discovery.d.mts +13 -0
  15. package/dist/src/baseline/input_discovery.mjs +149 -0
  16. package/dist/src/baseline/policy_review.d.mts +56 -0
  17. package/dist/src/baseline/policy_review.mjs +73 -0
  18. package/dist/src/baseline/preparation.d.mts +63 -0
  19. package/dist/src/baseline/preparation.mjs +295 -0
  20. package/dist/src/baseline/privacy_contract.d.mts +147 -0
  21. package/dist/src/baseline/privacy_contract.mjs +2 -0
  22. package/dist/src/baseline/privacy_dependency_graph.d.mts +7 -0
  23. package/dist/src/baseline/privacy_dependency_graph.mjs +110 -0
  24. package/dist/src/baseline/privacy_engine.d.mts +5 -0
  25. package/dist/src/baseline/privacy_engine.mjs +4 -0
  26. package/dist/src/baseline/privacy_key.d.mts +7 -0
  27. package/dist/src/baseline/privacy_key.mjs +42 -0
  28. package/dist/src/baseline/privacy_pipeline.d.mts +7 -0
  29. package/dist/src/baseline/privacy_pipeline.mjs +127 -0
  30. package/dist/src/baseline/privacy_policy.d.mts +6 -0
  31. package/dist/src/baseline/privacy_policy.mjs +237 -0
  32. package/dist/src/baseline/privacy_recipe_validation.d.mts +5 -0
  33. package/dist/src/baseline/privacy_recipe_validation.mjs +289 -0
  34. package/dist/src/baseline/privacy_rule_registry.d.mts +18 -0
  35. package/dist/src/baseline/privacy_rule_registry.mjs +143 -0
  36. package/dist/src/baseline/privacy_structured_json.d.mts +66 -0
  37. package/dist/src/baseline/privacy_structured_json.mjs +464 -0
  38. package/dist/src/baseline/privacy_transforms.d.mts +18 -0
  39. package/dist/src/baseline/privacy_transforms.mjs +142 -0
  40. package/dist/src/baseline/privacy_validation_shared.d.mts +31 -0
  41. package/dist/src/baseline/privacy_validation_shared.mjs +127 -0
  42. package/dist/src/baseline/sanitization_policy.d.mts +69 -0
  43. package/dist/src/baseline/sanitization_policy.mjs +227 -0
  44. package/dist/src/baseline/schema_snapshot.d.mts +19 -0
  45. package/dist/src/baseline/schema_snapshot.mjs +79 -0
  46. package/dist/src/cli/application_command_handlers.d.mts +8 -0
  47. package/dist/src/cli/application_command_handlers.mjs +52 -0
  48. package/dist/src/cli/arguments.d.mts +38 -0
  49. package/dist/src/cli/arguments.mjs +128 -0
  50. package/dist/src/cli/basic_command_handlers.d.mts +4 -0
  51. package/dist/src/cli/basic_command_handlers.mjs +56 -0
  52. package/dist/src/cli/cli_commands.d.mts +14 -0
  53. package/dist/src/cli/cli_commands.mjs +32 -0
  54. package/dist/src/cli/command_contract.d.mts +57 -0
  55. package/dist/src/cli/command_contract.mjs +39 -0
  56. package/dist/src/cli/command_output.d.mts +12 -0
  57. package/dist/src/cli/command_output.mjs +20 -0
  58. package/dist/src/cli/command_registry.d.mts +7 -0
  59. package/dist/src/cli/command_registry.mjs +14 -0
  60. package/dist/src/cli/guided.d.mts +53 -0
  61. package/dist/src/cli/guided.mjs +719 -0
  62. package/dist/src/cli/identity_command_handlers.d.mts +8 -0
  63. package/dist/src/cli/identity_command_handlers.mjs +43 -0
  64. package/dist/src/cli/init_command.d.mts +21 -0
  65. package/dist/src/cli/init_command.mjs +90 -0
  66. package/dist/src/cli/inspection_command_handlers.d.mts +3 -0
  67. package/dist/src/cli/inspection_command_handlers.mjs +79 -0
  68. package/dist/src/cli/project_command_handlers.d.mts +11 -0
  69. package/dist/src/cli/project_command_handlers.mjs +92 -0
  70. package/dist/src/cli/project_commands.d.mts +159 -0
  71. package/dist/src/cli/project_commands.mjs +183 -0
  72. package/dist/src/cli/rehearsal.d.mts +7 -0
  73. package/dist/src/cli/rehearsal.mjs +190 -0
  74. package/dist/src/cli/renderers.d.mts +299 -0
  75. package/dist/src/cli/renderers.mjs +554 -0
  76. package/dist/src/cli/runtime_action_handlers.d.mts +12 -0
  77. package/dist/src/cli/runtime_action_handlers.mjs +201 -0
  78. package/dist/src/cli/runtime_commands.d.mts +269 -0
  79. package/dist/src/cli/runtime_commands.mjs +601 -0
  80. package/dist/src/cli/source_command_handlers.d.mts +8 -0
  81. package/dist/src/cli/source_command_handlers.mjs +62 -0
  82. package/dist/src/cli/source_commands.d.mts +436 -0
  83. package/dist/src/cli/source_commands.mjs +301 -0
  84. package/dist/src/cli/terminal.d.mts +59 -0
  85. package/dist/src/cli/terminal.mjs +230 -0
  86. package/dist/src/identity/claim.d.mts +5 -0
  87. package/dist/src/identity/claim.mjs +4 -0
  88. package/dist/src/identity/claim_contract.d.mts +135 -0
  89. package/dist/src/identity/claim_contract.mjs +6 -0
  90. package/dist/src/identity/claim_executor.d.mts +10 -0
  91. package/dist/src/identity/claim_executor.mjs +358 -0
  92. package/dist/src/identity/claim_identity_lookup.d.mts +17 -0
  93. package/dist/src/identity/claim_identity_lookup.mjs +125 -0
  94. package/dist/src/identity/claim_plan.d.mts +8 -0
  95. package/dist/src/identity/claim_plan.mjs +61 -0
  96. package/dist/src/identity/claim_policy.d.mts +3 -0
  97. package/dist/src/identity/claim_policy.mjs +377 -0
  98. package/dist/src/identity/claim_sql.d.mts +10 -0
  99. package/dist/src/identity/claim_sql.mjs +46 -0
  100. package/dist/src/identity/claim_verification.d.mts +13 -0
  101. package/dist/src/identity/claim_verification.mjs +35 -0
  102. package/dist/src/identity/provider_configuration.d.mts +37 -0
  103. package/dist/src/identity/provider_configuration.mjs +132 -0
  104. package/dist/src/identity/storage.d.mts +21 -0
  105. package/dist/src/identity/storage.mjs +122 -0
  106. package/dist/src/index.d.mts +4 -0
  107. package/dist/src/index.mjs +3 -0
  108. package/dist/src/project/configuration.d.mts +13 -0
  109. package/dist/src/project/configuration.mjs +9 -0
  110. package/dist/src/project/configuration_contract.d.mts +125 -0
  111. package/dist/src/project/configuration_contract.mjs +27 -0
  112. package/dist/src/project/configuration_discovery.d.mts +7 -0
  113. package/dist/src/project/configuration_discovery.mjs +37 -0
  114. package/dist/src/project/configuration_normalization.d.mts +107 -0
  115. package/dist/src/project/configuration_normalization.mjs +302 -0
  116. package/dist/src/project/configuration_rendering.d.mts +14 -0
  117. package/dist/src/project/configuration_rendering.mjs +224 -0
  118. package/dist/src/project/configuration_resolution.d.mts +125 -0
  119. package/dist/src/project/configuration_resolution.mjs +81 -0
  120. package/dist/src/project/configuration_schema.d.mts +22 -0
  121. package/dist/src/project/configuration_schema.mjs +160 -0
  122. package/dist/src/project/configuration_validation.d.mts +22 -0
  123. package/dist/src/project/configuration_validation.mjs +136 -0
  124. package/dist/src/project/install_scaffold.d.mts +42 -0
  125. package/dist/src/project/install_scaffold.mjs +263 -0
  126. package/dist/src/project/project_detection.d.mts +14 -0
  127. package/dist/src/project/project_detection.mjs +47 -0
  128. package/dist/src/project/setup.d.mts +87 -0
  129. package/dist/src/project/setup.mjs +414 -0
  130. package/dist/src/project/support_report.d.mts +88 -0
  131. package/dist/src/project/support_report.mjs +83 -0
  132. package/dist/src/runtime/cleanup.d.mts +85 -0
  133. package/dist/src/runtime/cleanup.mjs +311 -0
  134. package/dist/src/runtime/lifecycle_engine.d.mts +63 -0
  135. package/dist/src/runtime/lifecycle_engine.mjs +114 -0
  136. package/dist/src/runtime/migration_history.d.mts +51 -0
  137. package/dist/src/runtime/migration_history.mjs +170 -0
  138. package/dist/src/runtime/plan.d.mts +134 -0
  139. package/dist/src/runtime/plan.mjs +637 -0
  140. package/dist/src/runtime/policy.d.mts +66 -0
  141. package/dist/src/runtime/policy.mjs +369 -0
  142. package/dist/src/runtime/restore.d.mts +22 -0
  143. package/dist/src/runtime/restore.mjs +276 -0
  144. package/dist/src/runtime/service_environment.d.mts +11 -0
  145. package/dist/src/runtime/service_environment.mjs +58 -0
  146. package/dist/src/runtime/topology.d.mts +51 -0
  147. package/dist/src/runtime/topology.mjs +128 -0
  148. package/dist/src/shared/diagnostics.d.mts +97 -0
  149. package/dist/src/shared/diagnostics.mjs +170 -0
  150. package/dist/src/shared/human_output.d.mts +2 -0
  151. package/{src → dist/src}/shared/human_output.mjs +1 -3
  152. package/dist/src/shared/node_runtime.d.mts +14 -0
  153. package/dist/src/shared/node_runtime.mjs +25 -0
  154. package/dist/src/shared/operation_guard.d.mts +22 -0
  155. package/dist/src/shared/operation_guard.mjs +150 -0
  156. package/dist/src/shared/process_environment.d.mts +15 -0
  157. package/dist/src/shared/process_environment.mjs +50 -0
  158. package/dist/src/source/access.d.mts +165 -0
  159. package/dist/src/source/access.mjs +602 -0
  160. package/dist/src/source/asset_transfer.d.mts +27 -0
  161. package/dist/src/source/asset_transfer.mjs +202 -0
  162. package/dist/src/source/baseline.d.mts +66 -0
  163. package/dist/src/source/baseline.mjs +372 -0
  164. package/dist/src/source/external_reader.d.mts +42 -0
  165. package/dist/src/source/external_reader.mjs +278 -0
  166. package/dist/src/source/postgresql_access.d.mts +86 -0
  167. package/dist/src/source/postgresql_access.mjs +454 -0
  168. package/dist/src/source/privacy_paths.d.mts +10 -0
  169. package/dist/src/source/privacy_paths.mjs +27 -0
  170. package/dist/src/targets/driver.d.mts +2 -0
  171. package/dist/src/targets/driver.mjs +13 -0
  172. package/dist/src/targets/postgresql.d.mts +5 -0
  173. package/dist/src/targets/postgresql.mjs +620 -0
  174. package/dist/src/targets/supabase.d.mts +8 -0
  175. package/dist/src/targets/supabase.mjs +627 -0
  176. package/dist/src/targets/supabase_environment.d.mts +49 -0
  177. package/dist/src/targets/supabase_environment.mjs +162 -0
  178. package/dist/src/targets/target.d.mts +33 -0
  179. package/dist/src/targets/target.mjs +39 -0
  180. package/docs/README.md +1 -0
  181. package/docs/architecture.md +133 -10
  182. package/docs/configuration.md +9 -3
  183. package/docs/index.md +1 -0
  184. package/docs/releasing.md +8 -3
  185. package/docs/typescript.md +134 -0
  186. package/package.json +36 -29
  187. package/types/configuration.d.mts +5 -0
  188. package/scripts/install/create_root_config.mjs +0 -19
  189. package/src/README.md +0 -17
  190. package/src/application/session.mjs +0 -435
  191. package/src/baseline/artifact.mjs +0 -684
  192. package/src/baseline/builder.mjs +0 -164
  193. package/src/baseline/input_discovery.mjs +0 -155
  194. package/src/baseline/policy_review.mjs +0 -92
  195. package/src/baseline/preparation.mjs +0 -323
  196. package/src/baseline/privacy_engine.mjs +0 -1399
  197. package/src/baseline/privacy_structured_json.mjs +0 -554
  198. package/src/baseline/sanitization_policy.mjs +0 -299
  199. package/src/baseline/schema_snapshot.mjs +0 -113
  200. package/src/cli/arguments.mjs +0 -126
  201. package/src/cli/guided.mjs +0 -814
  202. package/src/cli/init_command.mjs +0 -94
  203. package/src/cli/rehearsal.mjs +0 -972
  204. package/src/cli/renderers.mjs +0 -638
  205. package/src/cli/runtime_commands.mjs +0 -707
  206. package/src/cli/source_commands.mjs +0 -355
  207. package/src/cli/terminal.mjs +0 -275
  208. package/src/identity/claim.mjs +0 -1165
  209. package/src/identity/provider_configuration.mjs +0 -177
  210. package/src/identity/storage.mjs +0 -165
  211. package/src/project/configuration.d.mts +0 -164
  212. package/src/project/configuration.mjs +0 -1337
  213. package/src/project/install_scaffold.mjs +0 -325
  214. package/src/project/setup.mjs +0 -474
  215. package/src/project/support_report.mjs +0 -105
  216. package/src/runtime/cleanup.mjs +0 -381
  217. package/src/runtime/migration_history.mjs +0 -220
  218. package/src/runtime/plan.mjs +0 -722
  219. package/src/runtime/policy.mjs +0 -438
  220. package/src/runtime/restore.mjs +0 -337
  221. package/src/runtime/service_environment.mjs +0 -82
  222. package/src/runtime/topology.mjs +0 -177
  223. package/src/shared/diagnostics.mjs +0 -207
  224. package/src/shared/node_runtime.mjs +0 -31
  225. package/src/shared/operation_guard.mjs +0 -162
  226. package/src/shared/process_environment.mjs +0 -68
  227. package/src/source/access.mjs +0 -798
  228. package/src/source/asset_transfer.mjs +0 -259
  229. package/src/source/baseline.mjs +0 -482
  230. package/src/source/external_reader.mjs +0 -368
  231. package/src/source/postgresql_access.mjs +0 -638
  232. package/src/source/privacy_paths.mjs +0 -38
  233. package/src/targets/postgresql.mjs +0 -808
  234. package/src/targets/supabase.mjs +0 -840
  235. package/src/targets/supabase_environment.mjs +0 -229
  236. package/src/targets/target.mjs +0 -66
@@ -1,814 +0,0 @@
1
- /**
2
- * State-aware interactive home screen and first-run flows.
3
- */
4
-
5
- import { access, readFile } from "node:fs/promises";
6
- import { join, relative } from "node:path";
7
- import * as prompts from "@clack/prompts";
8
- import {
9
- findRehearsalConfigPath,
10
- inspectDetectedProject,
11
- loadRehearsalConfig,
12
- validateRuntimeSanitizationPolicy,
13
- } from "../project/configuration.mjs";
14
- import {
15
- inspectRehearsalNodeRuntime,
16
- planRehearsalSetup,
17
- summarizeRehearsalSetup,
18
- } from "../project/setup.mjs";
19
- import {
20
- inspectBaselineInputFiles,
21
- planBaselinePreparation,
22
- summarizeBaselinePreparation,
23
- } from "../baseline/preparation.mjs";
24
- import { discoverBaselineInputFiles } from "../baseline/input_discovery.mjs";
25
- import {
26
- applyReviewedPolicy,
27
- completePolicyDraft,
28
- createSafeTablePreset,
29
- readReviewablePolicyDraft,
30
- suggestPolicyExceptionColumns,
31
- } from "../baseline/policy_review.mjs";
32
- import { formatCount } from "../shared/human_output.mjs";
33
- import { redactDiagnosticValue } from "../shared/diagnostics.mjs";
34
- import { REHEARSAL_NODE_LABEL } from "../shared/node_runtime.mjs";
35
- import {
36
- renderBaselineInputInspection,
37
- renderBaselinePreparation,
38
- renderCleanup,
39
- renderRefreshWorkflow,
40
- renderSetup,
41
- } from "./renderers.mjs";
42
- import {
43
- promptForChoice,
44
- promptForConfirmation,
45
- promptForDiscoveredPath,
46
- promptForMultipleChoice,
47
- promptForPath,
48
- promptForSelection,
49
- terminalStyle,
50
- useStyledPrompts,
51
- } from "./terminal.mjs";
52
-
53
- const pathExists = (path) =>
54
- access(path)
55
- .then(() => true)
56
- .catch((error) => {
57
- if (error?.code === "ENOENT") return false;
58
- throw error;
59
- });
60
-
61
- const inspectGuidedProject = async ({
62
- projectRoot,
63
- planOptions,
64
- topologyCandidateSummary,
65
- }) => {
66
- const detected = await inspectDetectedProject({ projectRoot });
67
- const node = inspectRehearsalNodeRuntime();
68
- let configPath;
69
- try {
70
- configPath = await findRehearsalConfigPath(planOptions);
71
- } catch (error) {
72
- if (
73
- !String(error?.message ?? error).startsWith("No Rehearsal configuration")
74
- ) {
75
- throw error;
76
- }
77
- }
78
- if (!configPath) {
79
- return {
80
- detected,
81
- node,
82
- config: "missing",
83
- target: detected.hasSupabaseConfig ? "supabase" : "postgresql",
84
- baseline: false,
85
- runtime: false,
86
- };
87
- }
88
- let loaded;
89
- try {
90
- loaded = await loadRehearsalConfig(planOptions);
91
- } catch (error) {
92
- return {
93
- detected,
94
- node,
95
- config: "invalid",
96
- target: undefined,
97
- configPath,
98
- configError: redactDiagnosticValue(String(error?.message ?? error)),
99
- baseline: false,
100
- runtime: false,
101
- };
102
- }
103
- let migrationSummary;
104
- let topology;
105
- try {
106
- const summary = await topologyCandidateSummary(planOptions);
107
- migrationSummary = summary;
108
- topology = summary.topology;
109
- } catch {
110
- // The doctor command owns the detailed, safely redacted explanation.
111
- }
112
- let policy = "missing";
113
- if (await pathExists(loaded.paths.sanitizationPolicy)) {
114
- try {
115
- validateRuntimeSanitizationPolicy(
116
- JSON.parse(await readFile(loaded.paths.sanitizationPolicy, "utf8")),
117
- );
118
- policy = "reviewed";
119
- } catch {
120
- policy = "needs-review";
121
- }
122
- }
123
- return {
124
- detected,
125
- node,
126
- config: "valid",
127
- target: loaded.config.runtime.target,
128
- configPath,
129
- baseline: Boolean(migrationSummary),
130
- policy,
131
- policyPath: relative(projectRoot, loaded.paths.sanitizationPolicy),
132
- policyAbsolutePath: loaded.paths.sanitizationPolicy,
133
- migrationSummary,
134
- refreshConfigured: Boolean(loaded.config.preparation),
135
- dependentCount: topology?.dependents.length ?? 0,
136
- runtime: topology
137
- ? (
138
- await Promise.all(
139
- topology.targets.map((target) =>
140
- pathExists(join(target.paths.runtimeWorkdir, "baseline.json")),
141
- ),
142
- )
143
- ).every(Boolean)
144
- : await pathExists(join(loaded.paths.runtimeWorkdir, "baseline.json")),
145
- };
146
- };
147
-
148
- const reviewColumn = async ({ table, column, flags }) => {
149
- const label = `${table}.${column}`;
150
- const action = await promptForSelection({
151
- message: `${label}: how should this value be handled?`,
152
- flags,
153
- options: [
154
- {
155
- label: "Replace with synthetic data",
156
- hint: "Safest default",
157
- value: "REPLACE WITH SYNTHETIC",
158
- },
159
- {
160
- label: "Pseudonymize consistently",
161
- hint: "Stable links without original values",
162
- value: "PSEUDONYMIZE",
163
- },
164
- { label: "Derive a safe value", value: "DERIVE" },
165
- { label: "Exclude this field", value: "EXCLUDE" },
166
- {
167
- label: "Keep exactly",
168
- hint: "Only for values explicitly safe to retain",
169
- value: "KEEP EXACTLY",
170
- },
171
- ],
172
- });
173
- if (!action) throw new Error("Policy review was cancelled.");
174
- const generated = await promptForSelection({
175
- message: `${label}: is this column always database-generated?`,
176
- flags,
177
- options: [
178
- { label: "No", value: "NEVER" },
179
- { label: "Yes", value: "ALWAYS" },
180
- ],
181
- });
182
- if (!generated) throw new Error("Policy review was cancelled.");
183
- const identity = await promptForSelection({
184
- message: `${label}: is this an identity column?`,
185
- flags,
186
- options: [
187
- { label: "No", value: "NO" },
188
- { label: "Yes", value: "YES" },
189
- ],
190
- });
191
- if (!identity) throw new Error("Policy review was cancelled.");
192
- const hasForeignKey = await promptForConfirmation(
193
- `${label}: does this column reference another table?`,
194
- flags,
195
- { cancelValue: null },
196
- );
197
- if (hasForeignKey === null) throw new Error("Policy review was cancelled.");
198
- let foreignKey = null;
199
- if (hasForeignKey) {
200
- const identifier = (value) =>
201
- /^[a-z][a-z0-9_]{0,62}$/u.test(value)
202
- ? undefined
203
- : "Use a lowercase PostgreSQL identifier.";
204
- const schema = await promptForPath({
205
- message: `${label}: referenced schema`,
206
- defaultValue: "public",
207
- flags,
208
- validate: identifier,
209
- });
210
- const referencedTable = await promptForPath({
211
- message: `${label}: referenced table`,
212
- flags,
213
- validate: identifier,
214
- });
215
- const referencedColumn = await promptForPath({
216
- message: `${label}: referenced column`,
217
- flags,
218
- validate: identifier,
219
- });
220
- if (!schema || !referencedTable || !referencedColumn) {
221
- throw new Error("Policy review was cancelled.");
222
- }
223
- foreignKey = {
224
- schema,
225
- table: referencedTable,
226
- column: referencedColumn,
227
- };
228
- }
229
- return { action, generated, identity, foreignKey };
230
- };
231
-
232
- const reviewPolicyInteractively = async ({ state, flags }) => {
233
- const { source, draft } = await readReviewablePolicyDraft(
234
- state.policyAbsolutePath,
235
- );
236
- const totalColumns = draft.tables.reduce(
237
- (total, table) => total + (table.columns?.length ?? 0),
238
- 0,
239
- );
240
- const tableSummaries = [];
241
- let individuallyReviewed = 0;
242
- if (useStyledPrompts(flags)) {
243
- prompts.note(
244
- [
245
- `${formatCount(totalColumns, "column")} across ${formatCount(draft.tables.length, "table")} require explicit review.`,
246
- "No row values are displayed or changed by this step.",
247
- "Safe defaults replace values, mark generated NEVER and identity NO, and set no foreign key.",
248
- "Likely structural columns start selected for individual review.",
249
- ].join("\n"),
250
- "Review the script",
251
- );
252
- }
253
- const policy = await completePolicyDraft({
254
- draft,
255
- reviewColumn: async (column) => {
256
- individuallyReviewed += 1;
257
- return reviewColumn({ ...column, flags });
258
- },
259
- reviewTable: async ({ table, columns }) => {
260
- if (columns.length === 1) {
261
- tableSummaries.push({ table, defaulted: 0, individual: 1 });
262
- return {};
263
- }
264
- const mode = await promptForSelection({
265
- message: `${table}: how would you like to review ${formatCount(columns.length, "column")}?`,
266
- flags,
267
- options: [
268
- {
269
- label: "Use safe defaults, then review exceptions",
270
- hint: "Best for larger tables",
271
- value: "preset",
272
- },
273
- { label: "Review every column individually", value: "individual" },
274
- ],
275
- });
276
- if (!mode) throw new Error("Policy review was cancelled.");
277
- if (mode === "individual") {
278
- tableSummaries.push({
279
- table,
280
- defaulted: 0,
281
- individual: columns.length,
282
- });
283
- return {};
284
- }
285
- const exceptions = await promptForMultipleChoice({
286
- message: `${table}: select columns that need individual review`,
287
- flags,
288
- options: columns.map((column) => ({ label: column, value: column })),
289
- initialValues: suggestPolicyExceptionColumns(columns),
290
- });
291
- if (!exceptions) throw new Error("Policy review was cancelled.");
292
- const tableDecisions = createSafeTablePreset(columns);
293
- for (const column of exceptions) delete tableDecisions[column];
294
- tableSummaries.push({
295
- table,
296
- defaulted: columns.length - exceptions.length,
297
- individual: exceptions.length,
298
- });
299
- if (useStyledPrompts(flags)) {
300
- prompts.log.info(
301
- `${table}: ${formatCount(columns.length - exceptions.length, "safe default")}; ${formatCount(exceptions.length, "exception")}.`,
302
- );
303
- }
304
- return tableDecisions;
305
- },
306
- });
307
- const defaultedColumns = totalColumns - individuallyReviewed;
308
- const summaryLines = tableSummaries.map(
309
- (summary) =>
310
- `${summary.table}: ${formatCount(summary.defaulted, "safe default")}; ${formatCount(summary.individual, "individual review")}`,
311
- );
312
- if (useStyledPrompts(flags)) {
313
- prompts.note(summaryLines.join("\n"), "Review summary");
314
- } else {
315
- console.log(["", "Review summary", ...summaryLines, ""].join("\n"));
316
- }
317
- const accepted = await promptForConfirmation(
318
- `Save ${formatCount(totalColumns, "classified column")} and activate this policy?`,
319
- flags,
320
- );
321
- if (!accepted) return false;
322
- await applyReviewedPolicy({
323
- path: state.policyAbsolutePath,
324
- originalSource: source,
325
- policy,
326
- });
327
- const result = `${formatCount(totalColumns, "column")} classified (${formatCount(defaultedColumns, "safe default")}; ${formatCount(individuallyReviewed, "individual review")}).`;
328
- if (useStyledPrompts(flags)) {
329
- prompts.log.success(`${result} The policy is ready for baseline creation.`);
330
- } else {
331
- console.log(`Reviewed policy saved: ${result}`);
332
- }
333
- return true;
334
- };
335
-
336
- const guidedOptions = (state, hasLastDetails) => {
337
- const options = [];
338
- if (!state.node.supported) {
339
- options.push({
340
- label: "Show supported Node.js versions",
341
- hint: "Required before Rehearsal can change this project",
342
- command: "node-help",
343
- });
344
- } else if (state.config === "missing") {
345
- options.push({
346
- label: "Set the stage",
347
- hint: "Create safe local configuration",
348
- command: "setup-write",
349
- });
350
- } else if (state.baseline) {
351
- options.push(
352
- { label: "Run a rehearsal", command: "run" },
353
- ...(state.refreshConfigured
354
- ? [
355
- {
356
- label: "Refresh the database copy",
357
- hint: "Replace safely, reset locally, then remove old copies",
358
- command: "refresh-guide",
359
- },
360
- ]
361
- : []),
362
- { label: "Review the migration plan", command: "explain" },
363
- { label: "Show candidate migrations", command: "candidates" },
364
- { label: "Check readiness", command: "doctor" },
365
- );
366
- } else if (state.node.supported && state.config === "valid") {
367
- if (state.policy === "missing") {
368
- options.push({
369
- label: "Prepare the script",
370
- hint: "Create a reviewable policy draft",
371
- command: "baseline-prepare",
372
- });
373
- } else if (state.policy === "needs-review") {
374
- options.push({
375
- label: "Review the script",
376
- hint: "Apply table presets and review exceptions",
377
- command: "policy-review",
378
- });
379
- } else {
380
- options.push({
381
- label: "Create the baseline",
382
- hint: "Activate reviewed local inputs",
383
- command: "baseline-guide",
384
- });
385
- }
386
- options.push({ label: "Check readiness", command: "doctor" });
387
- } else {
388
- options.push({ label: "Check readiness", command: "doctor" });
389
- }
390
- if (state.node.supported && state.runtime) {
391
- options.push({
392
- label: "Open the sandbox app",
393
- hint: "Keep it running for hands-on testing",
394
- command: "open",
395
- });
396
- options.push({
397
- label: "Manage the local runtime",
398
- hint: "Status, verification, and cleanup",
399
- command: "manage-runtime",
400
- });
401
- }
402
- if (state.node.supported && state.config === "valid") {
403
- options.push({
404
- label: "Clean up disk space",
405
- hint: "Preview old baselines, the runtime, and unused images",
406
- command: "cleanup-guide",
407
- });
408
- }
409
- if (hasLastDetails) {
410
- options.push({
411
- label: "Show details from the last action",
412
- command: "last-details",
413
- });
414
- }
415
- options.push(
416
- {
417
- label: "Get help",
418
- hint: "Create a safe, copy-ready support report",
419
- command: "support",
420
- },
421
- { label: "Show all commands", command: "help" },
422
- { label: "Exit", command: "exit" },
423
- );
424
- return options;
425
- };
426
-
427
- const showGuidedStatus = ({ state, options, flags }) => {
428
- const marker = (complete) =>
429
- complete ? terminalStyle(flags, "32", "✓") : terminalStyle(flags, "2", "○");
430
- const statusLines = [
431
- `${marker(state.node.supported)} Node.js ${state.node.version}${state.node.supported ? "" : ` (${REHEARSAL_NODE_LABEL} required)`}`,
432
- `${marker(state.target === "supabase" ? state.detected.hasSupabaseConfig : state.detected.hasPostgresqlMigrations)} ${state.target === "supabase" ? "Supabase" : "PostgreSQL"} project detected`,
433
- `${marker(state.target === "supabase" ? state.detected.hasMigrations : state.detected.hasPostgresqlMigrations)} Migration history detected`,
434
- `${marker(state.config === "valid")} Rehearsal configuration${state.config === "invalid" ? " needs attention" : ""}`,
435
- ...(state.dependentCount
436
- ? [
437
- `${marker(true)} ${formatCount(state.dependentCount, "dependent database")} included`,
438
- ]
439
- : []),
440
- ...(state.config === "valid"
441
- ? [
442
- `${marker(state.policy === "reviewed")} ${state.policy === "needs-review" ? "Baseline policy needs review" : state.policy === "reviewed" ? "Baseline policy reviewed" : "Baseline policy needed"}`,
443
- ]
444
- : []),
445
- `${marker(state.baseline)} Verified baseline`,
446
- ...(state.runtime ? [`${marker(true)} Disposable runtime created`] : []),
447
- ...(state.configError
448
- ? ["", terminalStyle(flags, "33", `! ${state.configError}`)]
449
- : []),
450
- ];
451
- const stage =
452
- state.config !== "valid"
453
- ? 1
454
- : state.policy !== "reviewed"
455
- ? 2
456
- : !state.baseline
457
- ? 3
458
- : 4;
459
- if (useStyledPrompts(flags)) {
460
- prompts.note(
461
- statusLines.join("\n"),
462
- `${state.detected.projectName} · Stage ${stage} of 4`,
463
- );
464
- } else {
465
- console.log(
466
- [
467
- "",
468
- terminalStyle(flags, "1;36", "REHEARSAL"),
469
- "Safe local migration testing",
470
- "",
471
- terminalStyle(flags, "1", state.detected.projectName),
472
- `Stage ${stage} of 4`,
473
- ...statusLines,
474
- "",
475
- "What would you like to do?",
476
- "",
477
- ...options.map(
478
- (option, index) =>
479
- ` ${index === 0 ? terminalStyle(flags, "36", "›") : " "} ${index + 1}. ${option.label}`,
480
- ),
481
- "",
482
- ].join("\n"),
483
- );
484
- }
485
- };
486
-
487
- const guideCleanup = async ({
488
- flags,
489
- planOptions,
490
- planRuntimeStackCleanup,
491
- }) => {
492
- const cleanupScope = await promptForChoice({
493
- message: "What may Rehearsal include in the cleanup preview?",
494
- flags,
495
- options: [
496
- {
497
- label: "Old baselines only",
498
- hint: "Keep the configured number of recent generations",
499
- command: "baselines",
500
- },
501
- {
502
- label: "Old baselines and this project's runtime",
503
- hint: "Also remove the disposable database and its volumes",
504
- command: "runtime",
505
- },
506
- {
507
- label: "Old baselines and older unused Supabase images",
508
- hint: "Images may be shared and can be downloaded again",
509
- command: "images",
510
- },
511
- { label: "Everything above", command: "all" },
512
- { label: "Back", command: "exit" },
513
- ],
514
- });
515
- if (!cleanupScope || cleanupScope.command === "exit") return "home";
516
- flags.includeRuntime = ["runtime", "all"].includes(cleanupScope.command);
517
- flags.includeImages = ["images", "all"].includes(cleanupScope.command);
518
- flags.cleanupPlan = await planRuntimeStackCleanup({ flags, planOptions });
519
- console.log(
520
- `\n${renderCleanup({ mode: "preview", plan: flags.cleanupPlan })}`,
521
- );
522
- const selectedCount = flags.cleanupPlan.targets
523
- ? flags.cleanupPlan.targets.reduce(
524
- (total, target) =>
525
- total +
526
- target.plan.baselines.removed.length +
527
- (target.plan.runtime.included && target.plan.runtime.detected
528
- ? 1
529
- : 0) +
530
- target.plan.images.candidates.length,
531
- 0,
532
- )
533
- : flags.cleanupPlan.baselines.removed.length +
534
- (flags.cleanupPlan.runtime.included && flags.cleanupPlan.runtime.detected
535
- ? 1
536
- : 0) +
537
- flags.cleanupPlan.images.candidates.length;
538
- if (selectedCount === 0) return "home";
539
- const accepted = await promptForConfirmation(
540
- "Remove exactly the resources shown above?",
541
- flags,
542
- );
543
- if (!accepted) return "home";
544
- flags.write = true;
545
- flags.cleanupConfirmation = flags.cleanupPlan.digest;
546
- return "cleanup";
547
- };
548
-
549
- const guideBaseline = async ({
550
- state,
551
- selected,
552
- flags,
553
- planOptions,
554
- projectRoot,
555
- }) => {
556
- console.log(
557
- [
558
- "",
559
- terminalStyle(flags, "1", "CREATE A SAFE BASELINE"),
560
- "Rehearsal only reads the local files you name here.",
561
- "It does not extract data or contact a hosted database.",
562
- "",
563
- ].join("\n"),
564
- );
565
- const discovered = await discoverBaselineInputFiles({ projectRoot });
566
- const detectedLines = [
567
- discovered.records.length
568
- ? `Records: ${discovered.records.join(", ")}`
569
- : "Records: none detected",
570
- discovered.ledgers.length
571
- ? `Migration ledgers: ${discovered.ledgers.join(", ")}`
572
- : "Migration ledgers: none detected",
573
- ...(state.target === "supabase"
574
- ? [
575
- discovered.assetManifests.length
576
- ? `Storage manifests: ${discovered.assetManifests.join(", ")}`
577
- : "Storage manifests: none detected",
578
- ]
579
- : []),
580
- "Only paths and file structure were inspected; row values stay hidden.",
581
- ];
582
- if (useStyledPrompts(flags)) {
583
- prompts.note(detectedLines.join("\n"), "Detected safe local inputs");
584
- } else {
585
- console.log(
586
- ["Detected safe local inputs", ...detectedLines, ""].join("\n"),
587
- );
588
- }
589
- flags.recordsPath = await promptForDiscoveredPath({
590
- message: "Sanitized NDJSON records",
591
- candidates: discovered.records,
592
- flags,
593
- });
594
- flags.ledgerPath = await promptForDiscoveredPath({
595
- message: "Migration ledger",
596
- candidates: discovered.ledgers,
597
- flags,
598
- });
599
- try {
600
- if (selected.command === "baseline-prepare") {
601
- const preparationPlan = await planBaselinePreparation({
602
- ...planOptions,
603
- recordsPath: flags.recordsPath,
604
- ledgerPath: flags.ledgerPath,
605
- });
606
- const preview = {
607
- ...summarizeBaselinePreparation(preparationPlan, { mode: "preview" }),
608
- nextAction:
609
- "Review this schema-only summary. No file is written unless you approve below.",
610
- };
611
- console.log(`\n${renderBaselinePreparation(preview, flags)}\n`);
612
- const accepted = await promptForConfirmation(
613
- `Create the REVIEW REQUIRED draft at ${preview.destination}?`,
614
- flags,
615
- );
616
- if (!accepted) {
617
- console.log("No changes made.");
618
- return "home";
619
- }
620
- flags.preparationPlan = preparationPlan;
621
- flags.write = true;
622
- return "baseline prepare";
623
- }
624
- flags.assetsPath =
625
- state.target === "supabase"
626
- ? await promptForDiscoveredPath({
627
- message: "Storage assets",
628
- candidates: discovered.assetManifests,
629
- optional: true,
630
- flags,
631
- })
632
- : undefined;
633
- const inspection = await inspectBaselineInputFiles({
634
- ...planOptions,
635
- recordsPath: flags.recordsPath,
636
- ledgerPath: flags.ledgerPath,
637
- assetsPath: flags.assetsPath,
638
- });
639
- console.log(`\n${renderBaselineInputInspection(inspection, flags)}\n`);
640
- const accepted = await promptForConfirmation(
641
- "Create and activate this immutable local baseline?",
642
- flags,
643
- );
644
- if (!accepted) {
645
- console.log("No changes made.");
646
- return "home";
647
- }
648
- return "baseline create";
649
- } catch (error) {
650
- const message = redactDiagnosticValue(String(error?.message ?? error));
651
- if (useStyledPrompts(flags)) {
652
- prompts.cancel("These baseline inputs need attention.");
653
- prompts.note(message, "How to continue");
654
- } else {
655
- console.log(`\nBASELINE INPUTS NEED ATTENTION\n${message}\n`);
656
- }
657
- return "home";
658
- }
659
- };
660
-
661
- export const createGuidedHome =
662
- ({
663
- projectRoot,
664
- topologyCandidateSummary,
665
- buildRefreshWorkflowPlan,
666
- planRuntimeStackCleanup,
667
- getLastGuidedDetails,
668
- }) =>
669
- async ({ flags, planOptions }) => {
670
- const state = await inspectGuidedProject({
671
- projectRoot,
672
- planOptions,
673
- topologyCandidateSummary,
674
- });
675
- const lastDetails = getLastGuidedDetails();
676
- const options = guidedOptions(state, Boolean(lastDetails));
677
- showGuidedStatus({ state, options, flags });
678
- const selected = await promptForChoice({
679
- message: "What would you like to do?",
680
- options,
681
- flags,
682
- optionsAlreadyShown: !useStyledPrompts(flags),
683
- });
684
- if (!selected || selected.command === "exit") return null;
685
- if (selected.command === "node-help") {
686
- console.log(
687
- [
688
- "",
689
- terminalStyle(flags, "1", "SUPPORTED NODE.JS REQUIRED"),
690
- `This shell is using Node.js ${state.node.version}. Rehearsal has not changed your project.`,
691
- `Supported releases: ${REHEARSAL_NODE_LABEL}.`,
692
- "",
693
- "Recommended with nvm:",
694
- " nvm install --lts",
695
- " nvm use --lts",
696
- "",
697
- "Then reinstall Rehearsal in your scratch project and run npx rehearsal again.",
698
- ].join("\n"),
699
- );
700
- return "home";
701
- }
702
- if (selected.command === "policy-review") {
703
- try {
704
- await reviewPolicyInteractively({ state, flags });
705
- } catch (error) {
706
- if (
707
- String(error?.message ?? error) !== "Policy review was cancelled."
708
- ) {
709
- throw error;
710
- }
711
- if (useStyledPrompts(flags)) {
712
- prompts.cancel("Policy review cancelled; the draft was not changed.");
713
- } else {
714
- console.log("Policy review cancelled; the draft was not changed.");
715
- }
716
- }
717
- return "home";
718
- }
719
- if (selected.command === "last-details") {
720
- if (useStyledPrompts(flags)) {
721
- prompts.note(lastDetails, "Technical details");
722
- } else {
723
- console.log(`\nTECHNICAL DETAILS\n${lastDetails}`);
724
- }
725
- return "home";
726
- }
727
- if (selected.command === "refresh-guide") {
728
- const { plan } = await buildRefreshWorkflowPlan(planOptions);
729
- console.log(`\n${renderRefreshWorkflow({ mode: "preview", plan })}\n`);
730
- const accepted = await promptForConfirmation(
731
- "Create this replacement, discard current runtime edits, and remove exactly the listed old copies after success?",
732
- flags,
733
- );
734
- if (!accepted) return "home";
735
- flags.refreshConfirmation = plan.digest;
736
- return "refresh";
737
- }
738
- if (selected.command === "manage-runtime") {
739
- const runtimeAction = await promptForChoice({
740
- message: "Manage the disposable local runtime",
741
- flags,
742
- options: [
743
- { label: "Show status", command: "status" },
744
- { label: "Verify runtime", command: "verify" },
745
- { label: "Reset to the immutable baseline", command: "reset" },
746
- { label: "Stop runtime", command: "stop" },
747
- { label: "Discard runtime", command: "discard" },
748
- { label: "Back", command: "exit" },
749
- ],
750
- });
751
- if (!runtimeAction || runtimeAction.command === "exit") return "home";
752
- if (["reset", "discard"].includes(runtimeAction.command)) {
753
- const accepted = await promptForConfirmation(
754
- runtimeAction.command === "reset"
755
- ? "Reset the disposable runtime and remove its current changes?"
756
- : "Discard the disposable runtime? The immutable baseline will remain.",
757
- flags,
758
- );
759
- if (!accepted) return "home";
760
- }
761
- return runtimeAction.command;
762
- }
763
- if (selected.command === "cleanup-guide") {
764
- return guideCleanup({ flags, planOptions, planRuntimeStackCleanup });
765
- }
766
- if (["baseline-guide", "baseline-prepare"].includes(selected.command)) {
767
- return guideBaseline({
768
- state,
769
- selected,
770
- flags,
771
- planOptions,
772
- projectRoot,
773
- });
774
- }
775
- if (selected.command !== "setup-write") return selected.command;
776
-
777
- const targetChoice = await promptForChoice({
778
- message: "Which database should Rehearsal use?",
779
- flags,
780
- options:
781
- state.target === "supabase"
782
- ? [
783
- { label: "Supabase", command: "supabase" },
784
- { label: "PostgreSQL", command: "postgresql" },
785
- ]
786
- : [
787
- { label: "PostgreSQL", command: "postgresql" },
788
- { label: "Supabase", command: "supabase" },
789
- ],
790
- });
791
- if (!targetChoice) return "home";
792
- flags.target = targetChoice.command;
793
- const setupPlan = await planRehearsalSetup({
794
- projectRoot,
795
- target: flags.target,
796
- });
797
- const preview = {
798
- ...summarizeRehearsalSetup(setupPlan, { mode: "preview" }),
799
- nextAction:
800
- "Review this plan. No files are written unless you approve below.",
801
- };
802
- console.log(`\n${renderSetup(preview)}\n`);
803
- const accepted = await promptForConfirmation(
804
- "Create these project-local files?",
805
- flags,
806
- );
807
- if (!accepted) {
808
- console.log("No changes made.");
809
- return "home";
810
- }
811
- flags.setupPlan = setupPlan;
812
- flags.write = true;
813
- return "setup";
814
- };