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

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 (162) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +1 -0
  3. package/dist/package.json +100 -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_engine.d.mts +162 -0
  21. package/dist/src/baseline/privacy_engine.mjs +1162 -0
  22. package/dist/src/baseline/privacy_structured_json.d.mts +66 -0
  23. package/dist/src/baseline/privacy_structured_json.mjs +464 -0
  24. package/dist/src/baseline/sanitization_policy.d.mts +69 -0
  25. package/dist/src/baseline/sanitization_policy.mjs +227 -0
  26. package/dist/src/baseline/schema_snapshot.d.mts +19 -0
  27. package/dist/src/baseline/schema_snapshot.mjs +79 -0
  28. package/dist/src/cli/arguments.d.mts +43 -0
  29. package/dist/src/cli/arguments.mjs +135 -0
  30. package/dist/src/cli/guided.d.mts +51 -0
  31. package/dist/src/cli/guided.mjs +713 -0
  32. package/dist/src/cli/init_command.d.mts +21 -0
  33. package/dist/src/cli/init_command.mjs +90 -0
  34. package/dist/src/cli/project_commands.d.mts +157 -0
  35. package/dist/src/cli/project_commands.mjs +183 -0
  36. package/dist/src/cli/rehearsal.d.mts +7 -0
  37. package/dist/src/cli/rehearsal.mjs +706 -0
  38. package/dist/src/cli/renderers.d.mts +299 -0
  39. package/dist/src/cli/renderers.mjs +554 -0
  40. package/dist/src/cli/runtime_commands.d.mts +265 -0
  41. package/dist/src/cli/runtime_commands.mjs +599 -0
  42. package/dist/src/cli/source_commands.d.mts +432 -0
  43. package/dist/src/cli/source_commands.mjs +299 -0
  44. package/dist/src/cli/terminal.d.mts +59 -0
  45. package/dist/src/cli/terminal.mjs +230 -0
  46. package/dist/src/identity/claim.d.mts +144 -0
  47. package/dist/src/identity/claim.mjs +947 -0
  48. package/dist/src/identity/provider_configuration.d.mts +37 -0
  49. package/dist/src/identity/provider_configuration.mjs +132 -0
  50. package/dist/src/identity/storage.d.mts +21 -0
  51. package/dist/src/identity/storage.mjs +122 -0
  52. package/dist/src/index.d.mts +4 -0
  53. package/dist/src/index.mjs +3 -0
  54. package/dist/src/project/configuration.d.mts +181 -0
  55. package/dist/src/project/configuration.mjs +958 -0
  56. package/dist/src/project/install_scaffold.d.mts +42 -0
  57. package/dist/src/project/install_scaffold.mjs +263 -0
  58. package/dist/src/project/setup.d.mts +87 -0
  59. package/dist/src/project/setup.mjs +414 -0
  60. package/dist/src/project/support_report.d.mts +88 -0
  61. package/dist/src/project/support_report.mjs +83 -0
  62. package/dist/src/runtime/cleanup.d.mts +85 -0
  63. package/dist/src/runtime/cleanup.mjs +311 -0
  64. package/dist/src/runtime/migration_history.d.mts +51 -0
  65. package/dist/src/runtime/migration_history.mjs +170 -0
  66. package/dist/src/runtime/plan.d.mts +134 -0
  67. package/dist/src/runtime/plan.mjs +636 -0
  68. package/dist/src/runtime/policy.d.mts +66 -0
  69. package/dist/src/runtime/policy.mjs +369 -0
  70. package/dist/src/runtime/restore.d.mts +22 -0
  71. package/dist/src/runtime/restore.mjs +276 -0
  72. package/dist/src/runtime/service_environment.d.mts +11 -0
  73. package/dist/src/runtime/service_environment.mjs +58 -0
  74. package/dist/src/runtime/topology.d.mts +51 -0
  75. package/dist/src/runtime/topology.mjs +128 -0
  76. package/dist/src/shared/diagnostics.d.mts +97 -0
  77. package/dist/src/shared/diagnostics.mjs +170 -0
  78. package/dist/src/shared/human_output.d.mts +2 -0
  79. package/{src → dist/src}/shared/human_output.mjs +1 -3
  80. package/dist/src/shared/node_runtime.d.mts +14 -0
  81. package/dist/src/shared/node_runtime.mjs +25 -0
  82. package/dist/src/shared/operation_guard.d.mts +22 -0
  83. package/dist/src/shared/operation_guard.mjs +150 -0
  84. package/dist/src/shared/process_environment.d.mts +15 -0
  85. package/dist/src/shared/process_environment.mjs +50 -0
  86. package/dist/src/source/access.d.mts +165 -0
  87. package/dist/src/source/access.mjs +602 -0
  88. package/dist/src/source/asset_transfer.d.mts +27 -0
  89. package/dist/src/source/asset_transfer.mjs +202 -0
  90. package/dist/src/source/baseline.d.mts +66 -0
  91. package/dist/src/source/baseline.mjs +372 -0
  92. package/dist/src/source/external_reader.d.mts +42 -0
  93. package/dist/src/source/external_reader.mjs +278 -0
  94. package/dist/src/source/postgresql_access.d.mts +86 -0
  95. package/dist/src/source/postgresql_access.mjs +454 -0
  96. package/dist/src/source/privacy_paths.d.mts +10 -0
  97. package/dist/src/source/privacy_paths.mjs +27 -0
  98. package/dist/src/targets/driver.d.mts +2 -0
  99. package/dist/src/targets/driver.mjs +13 -0
  100. package/dist/src/targets/postgresql.d.mts +5 -0
  101. package/dist/src/targets/postgresql.mjs +641 -0
  102. package/dist/src/targets/supabase.d.mts +8 -0
  103. package/dist/src/targets/supabase.mjs +659 -0
  104. package/dist/src/targets/supabase_environment.d.mts +49 -0
  105. package/dist/src/targets/supabase_environment.mjs +162 -0
  106. package/dist/src/targets/target.d.mts +33 -0
  107. package/dist/src/targets/target.mjs +39 -0
  108. package/docs/README.md +1 -0
  109. package/docs/architecture.md +17 -9
  110. package/docs/configuration.md +9 -3
  111. package/docs/index.md +1 -0
  112. package/docs/typescript.md +87 -0
  113. package/package.json +35 -29
  114. package/scripts/install/create_root_config.mjs +0 -19
  115. package/src/README.md +0 -17
  116. package/src/application/session.mjs +0 -435
  117. package/src/baseline/artifact.mjs +0 -684
  118. package/src/baseline/builder.mjs +0 -164
  119. package/src/baseline/input_discovery.mjs +0 -155
  120. package/src/baseline/policy_review.mjs +0 -92
  121. package/src/baseline/preparation.mjs +0 -323
  122. package/src/baseline/privacy_engine.mjs +0 -1399
  123. package/src/baseline/privacy_structured_json.mjs +0 -554
  124. package/src/baseline/sanitization_policy.mjs +0 -299
  125. package/src/baseline/schema_snapshot.mjs +0 -113
  126. package/src/cli/arguments.mjs +0 -126
  127. package/src/cli/guided.mjs +0 -814
  128. package/src/cli/init_command.mjs +0 -94
  129. package/src/cli/rehearsal.mjs +0 -972
  130. package/src/cli/renderers.mjs +0 -638
  131. package/src/cli/runtime_commands.mjs +0 -707
  132. package/src/cli/source_commands.mjs +0 -355
  133. package/src/cli/terminal.mjs +0 -275
  134. package/src/identity/claim.mjs +0 -1165
  135. package/src/identity/provider_configuration.mjs +0 -177
  136. package/src/identity/storage.mjs +0 -165
  137. package/src/project/configuration.mjs +0 -1337
  138. package/src/project/install_scaffold.mjs +0 -325
  139. package/src/project/setup.mjs +0 -474
  140. package/src/project/support_report.mjs +0 -105
  141. package/src/runtime/cleanup.mjs +0 -381
  142. package/src/runtime/migration_history.mjs +0 -220
  143. package/src/runtime/plan.mjs +0 -722
  144. package/src/runtime/policy.mjs +0 -438
  145. package/src/runtime/restore.mjs +0 -337
  146. package/src/runtime/service_environment.mjs +0 -82
  147. package/src/runtime/topology.mjs +0 -177
  148. package/src/shared/diagnostics.mjs +0 -207
  149. package/src/shared/node_runtime.mjs +0 -31
  150. package/src/shared/operation_guard.mjs +0 -162
  151. package/src/shared/process_environment.mjs +0 -68
  152. package/src/source/access.mjs +0 -798
  153. package/src/source/asset_transfer.mjs +0 -259
  154. package/src/source/baseline.mjs +0 -482
  155. package/src/source/external_reader.mjs +0 -368
  156. package/src/source/postgresql_access.mjs +0 -638
  157. package/src/source/privacy_paths.mjs +0 -38
  158. package/src/targets/postgresql.mjs +0 -808
  159. package/src/targets/supabase.mjs +0 -840
  160. package/src/targets/supabase_environment.mjs +0 -229
  161. package/src/targets/target.mjs +0 -66
  162. /package/{src/project → types}/configuration.d.mts +0 -0
@@ -1,474 +0,0 @@
1
- /**
2
- * Purpose: Plan and apply conservative first-run Rehearsal scaffolding without
3
- * contacting hosted services or overwriting project-owned files.
4
- */
5
-
6
- import { randomUUID } from "node:crypto";
7
- import { createConnection, createServer } from "node:net";
8
- import {
9
- access,
10
- mkdir,
11
- open,
12
- readFile,
13
- rename,
14
- rm,
15
- writeFile,
16
- } from "node:fs/promises";
17
- import { basename, dirname, join, relative, resolve } from "node:path";
18
- import {
19
- findRehearsalConfigPath,
20
- inspectDetectedProject,
21
- loadRehearsalConfig,
22
- renderDetectedConfig,
23
- renderDetectedPostgresqlConfig,
24
- } from "./configuration.mjs";
25
- import { resolveRuntimeTarget } from "../targets/target.mjs";
26
- export {
27
- assertSupportedRehearsalNodeRuntime,
28
- inspectRehearsalNodeRuntime,
29
- } from "../shared/node_runtime.mjs";
30
- import { assertSupportedRehearsalNodeRuntime } from "../shared/node_runtime.mjs";
31
-
32
- const CONFIG_PATH = "rehearsal.config.mjs";
33
- const LOCAL_SUPABASE_CONFIG_PATH =
34
- "infrastructure/rehearsal/supabase/config.toml";
35
- const GITIGNORE_PATH = ".gitignore";
36
- const PORT_OFFSETS = Object.freeze({
37
- shadow: 0,
38
- api: 1,
39
- database: 2,
40
- studio: 3,
41
- smtp: 4,
42
- pooler: 9,
43
- });
44
-
45
- const pathExists = (path) =>
46
- access(path)
47
- .then(() => true)
48
- .catch((error) => {
49
- if (error?.code === "ENOENT") return false;
50
- throw error;
51
- });
52
-
53
- const readOptionalFile = async (path) => {
54
- try {
55
- return await readFile(path, "utf8");
56
- } catch (error) {
57
- if (error?.code === "ENOENT") return null;
58
- throw error;
59
- }
60
- };
61
-
62
- const canConnectToPort = (port) =>
63
- new Promise((resolveAvailability) => {
64
- const socket = createConnection({ host: "127.0.0.1", port });
65
- const finish = (result) => {
66
- socket.destroy();
67
- resolveAvailability(result);
68
- };
69
- socket.setTimeout(500);
70
- socket.once("connect", () => finish(true));
71
- socket.once("error", () => finish(false));
72
- socket.once("timeout", () => finish(false));
73
- });
74
-
75
- const canBindToPort = (port) =>
76
- new Promise((resolveAvailability) => {
77
- const server = createServer();
78
- server.unref();
79
- server.once("error", () => resolveAvailability(false));
80
- server.listen({ host: "127.0.0.1", port, exclusive: true }, () => {
81
- server.close(() => resolveAvailability(true));
82
- });
83
- });
84
-
85
- export const isRehearsalPortAvailable = async (port) => {
86
- if (await canConnectToPort(port)) return false;
87
- return canBindToPort(port);
88
- };
89
-
90
- export const findAvailableRehearsalPorts = async ({
91
- isAvailable = isRehearsalPortAvailable,
92
- } = {}) => {
93
- for (let base = 58_320; base <= 65_520; base += 20) {
94
- const entries = Object.entries(PORT_OFFSETS).map(([name, offset]) => [
95
- name,
96
- base + offset,
97
- ]);
98
- const availability = await Promise.all(
99
- entries.map(([, port]) => isAvailable(port)),
100
- );
101
- if (availability.every(Boolean)) return Object.fromEntries(entries);
102
- }
103
- throw new Error(
104
- "Rehearsal could not find an available local port block between 58320 and 65529.",
105
- );
106
- };
107
-
108
- const databaseMajorVersionFrom = (source) => {
109
- const match = /^\s*major_version\s*=\s*(\d+)\s*(?:#.*)?$/imu.exec(
110
- source ?? "",
111
- );
112
- return match ? Number(match[1]) : 17;
113
- };
114
-
115
- export const renderSafeLocalSupabaseConfig = ({
116
- projectId,
117
- applicationUrl,
118
- ports,
119
- databaseMajorVersion = 17,
120
- }) => `# Generated by Rehearsal for a disposable, local-only runtime.
121
- # Review project-specific Auth or Storage additions before enabling them here.
122
- project_id = ${JSON.stringify(projectId)}
123
-
124
- [api]
125
- enabled = true
126
- port = ${ports.api}
127
- schemas = ["public"]
128
- extra_search_path = ["public", "extensions"]
129
-
130
- [db]
131
- port = ${ports.database}
132
- shadow_port = ${ports.shadow}
133
- major_version = ${databaseMajorVersion}
134
-
135
- [db.pooler]
136
- enabled = false
137
- port = ${ports.pooler}
138
-
139
- [db.migrations]
140
- enabled = true
141
- schema_paths = []
142
-
143
- [db.seed]
144
- enabled = false
145
- sql_paths = []
146
-
147
- [studio]
148
- enabled = true
149
- port = ${ports.studio}
150
- api_url = "http://127.0.0.1"
151
-
152
- [local_smtp]
153
- enabled = false
154
- port = ${ports.smtp}
155
-
156
- [auth]
157
- enabled = true
158
- site_url = ${JSON.stringify(applicationUrl)}
159
- enable_signup = false
160
-
161
- [storage]
162
- enabled = true
163
-
164
- [realtime]
165
- enabled = false
166
-
167
- [edge_runtime]
168
- enabled = false
169
-
170
- [analytics]
171
- enabled = false
172
- `;
173
-
174
- export const appendRehearsalGitignoreEntries = (source) => {
175
- const original = source ?? "";
176
- const lines = new Set(
177
- original
178
- .split(/\r?\n/u)
179
- .map((line) => line.trim())
180
- .filter(Boolean),
181
- );
182
- const entries = [".rehearsal/", ".env.rehearsal-service.local"];
183
- const missing = entries.filter(
184
- (entry) =>
185
- !lines.has(entry) &&
186
- !lines.has(`/${entry}`) &&
187
- !lines.has(entry.replace(/\/$/u, "")),
188
- );
189
- if (missing.length === 0) return original;
190
- const prefix =
191
- original && !original.endsWith("\n") ? `${original}\n` : original;
192
- const separator = prefix && !prefix.endsWith("\n\n") ? "\n" : "";
193
- return `${prefix}${separator}# Rehearsal local artifacts\n${missing.join("\n")}\n`;
194
- };
195
-
196
- const findExistingConfigPath = async ({ projectRoot, configPath }) => {
197
- try {
198
- return await findRehearsalConfigPath({ projectRoot, configPath });
199
- } catch (error) {
200
- if (
201
- String(error?.message ?? error).startsWith("No Rehearsal configuration")
202
- ) {
203
- return null;
204
- }
205
- throw error;
206
- }
207
- };
208
-
209
- const deriveSupabasePorts = (runtimePorts) => ({
210
- shadow: runtimePorts.database - 2,
211
- api: runtimePorts.api,
212
- database: runtimePorts.database,
213
- studio: runtimePorts.studio,
214
- smtp: runtimePorts.database + 2,
215
- pooler: runtimePorts.database + 7,
216
- });
217
-
218
- const applyGitignoreChange = async (file) => {
219
- if (file.action === "unchanged") return;
220
- if (file.action === "create") {
221
- await writeFile(file.absolutePath, file.content, {
222
- flag: "wx",
223
- mode: file.mode,
224
- });
225
- return;
226
- }
227
-
228
- const lockPath = `${file.absolutePath}.rehearsal.lock`;
229
- let lock;
230
- let temporaryPath;
231
- try {
232
- lock = await open(lockPath, "wx", 0o600);
233
- const current = await readOptionalFile(file.absolutePath);
234
- if (current !== file.originalContent) {
235
- throw new Error(
236
- ".gitignore changed after the setup preview; rerun rehearsal setup before writing.",
237
- );
238
- }
239
- temporaryPath = join(
240
- dirname(file.absolutePath),
241
- `.${basename(file.absolutePath)}.rehearsal-${process.pid}-${randomUUID()}.tmp`,
242
- );
243
- await writeFile(temporaryPath, file.content, {
244
- flag: "wx",
245
- mode: file.mode,
246
- });
247
- await rename(temporaryPath, file.absolutePath);
248
- temporaryPath = null;
249
- } catch (error) {
250
- if (error?.code === "EEXIST" && !lock) {
251
- throw new Error(
252
- "Another Rehearsal process is updating .gitignore; rerun setup after it finishes.",
253
- );
254
- }
255
- throw error;
256
- } finally {
257
- if (temporaryPath) await rm(temporaryPath, { force: true });
258
- if (lock) {
259
- await lock.close();
260
- await rm(lockPath, { force: true });
261
- }
262
- }
263
- };
264
-
265
- const assertCreateTargetAvailable = async (file) => {
266
- if (file.action === "create" && (await pathExists(file.absolutePath))) {
267
- throw new Error(
268
- `Rehearsal setup will not overwrite the existing file at ${file.path}.`,
269
- );
270
- }
271
- };
272
-
273
- export const planRehearsalSetup = async ({
274
- projectRoot = process.cwd(),
275
- configPath,
276
- isPortAvailable,
277
- target,
278
- } = {}) => {
279
- const root = resolve(projectRoot);
280
- const detected = await inspectDetectedProject({ projectRoot: root });
281
- const existingConfigPath = await findExistingConfigPath({
282
- projectRoot: root,
283
- configPath,
284
- });
285
- const existing = existingConfigPath
286
- ? await loadRehearsalConfig({ projectRoot: root, configPath })
287
- : null;
288
- const requestedTarget = resolveRuntimeTarget(
289
- target ?? existing?.config.runtime.target ?? "supabase",
290
- ).id;
291
- const runtimeTarget = existing?.config.runtime.target ?? requestedTarget;
292
- if (existing && target && runtimeTarget !== requestedTarget) {
293
- throw new Error(
294
- `The existing Rehearsal configuration targets ${runtimeTarget}; setup will not replace it with ${requestedTarget}.`,
295
- );
296
- }
297
- const ports = existing
298
- ? runtimeTarget === "supabase"
299
- ? deriveSupabasePorts(existing.config.runtime.ports)
300
- : { database: existing.config.runtime.ports.database }
301
- : await findAvailableRehearsalPorts({
302
- ...(isPortAvailable ? { isAvailable: isPortAvailable } : {}),
303
- });
304
- const applicationUrl =
305
- existing?.config.runtime.applicationUrl ?? "http://localhost:5175";
306
- const projectId =
307
- existing?.config.runtime.projectId ?? `${detected.projectName}-rehearsal`;
308
- const sourceSupabaseConfig =
309
- runtimeTarget === "supabase"
310
- ? await readOptionalFile(join(root, "supabase/config.toml"))
311
- : null;
312
- const originalGitignore = await readOptionalFile(join(root, GITIGNORE_PATH));
313
- const nextGitignore = appendRehearsalGitignoreEntries(originalGitignore);
314
- const rehearsalConfigPath = existing?.paths.rehearsalConfig;
315
- const localSupabasePath = rehearsalConfigPath
316
- ? relative(root, rehearsalConfigPath)
317
- : LOCAL_SUPABASE_CONFIG_PATH;
318
- const localSupabaseExists = rehearsalConfigPath
319
- ? await pathExists(rehearsalConfigPath)
320
- : false;
321
- const files = [
322
- {
323
- path: existingConfigPath
324
- ? relative(root, existingConfigPath)
325
- : CONFIG_PATH,
326
- absolutePath: existingConfigPath ?? join(root, CONFIG_PATH),
327
- action: existingConfigPath ? "unchanged" : "create",
328
- mode: 0o600,
329
- content: existingConfigPath
330
- ? null
331
- : runtimeTarget === "supabase"
332
- ? renderDetectedConfig(detected, {
333
- applicationUrl,
334
- ports,
335
- })
336
- : renderDetectedPostgresqlConfig(detected, {
337
- applicationUrl,
338
- databasePort: ports.database,
339
- }),
340
- },
341
- ...(runtimeTarget === "supabase"
342
- ? [
343
- {
344
- path: localSupabasePath,
345
- absolutePath:
346
- rehearsalConfigPath ?? join(root, LOCAL_SUPABASE_CONFIG_PATH),
347
- action: localSupabaseExists ? "unchanged" : "create",
348
- mode: 0o600,
349
- content: localSupabaseExists
350
- ? null
351
- : renderSafeLocalSupabaseConfig({
352
- projectId,
353
- applicationUrl,
354
- ports,
355
- databaseMajorVersion:
356
- databaseMajorVersionFrom(sourceSupabaseConfig),
357
- }),
358
- },
359
- ]
360
- : []),
361
- {
362
- path: GITIGNORE_PATH,
363
- absolutePath: join(root, GITIGNORE_PATH),
364
- action:
365
- nextGitignore === (originalGitignore ?? "")
366
- ? "unchanged"
367
- : originalGitignore === null
368
- ? "create"
369
- : "update",
370
- mode: 0o644,
371
- originalContent: originalGitignore,
372
- content: nextGitignore,
373
- },
374
- ];
375
- return {
376
- projectRoot: root,
377
- configurationPath: existingConfigPath
378
- ? relative(root, existingConfigPath)
379
- : CONFIG_PATH,
380
- project: detected.projectName,
381
- projectId,
382
- target: runtimeTarget,
383
- applicationUrl,
384
- detected,
385
- ports: runtimeTarget === "supabase" ? ports : { database: ports.database },
386
- requiresAvailablePorts: files.some(
387
- (file) =>
388
- file.action === "create" &&
389
- [CONFIG_PATH, localSupabasePath].includes(file.path),
390
- ),
391
- isPortAvailable: isPortAvailable ?? isRehearsalPortAvailable,
392
- files,
393
- safety: [
394
- "hosted access remains disabled",
395
- runtimeTarget === "supabase"
396
- ? "the Supabase runtime binds to dedicated local ports"
397
- : "PostgreSQL binds only to a dedicated loopback port",
398
- ...(runtimeTarget === "supabase"
399
- ? [
400
- "external identity providers, realtime, edge functions, and analytics start disabled",
401
- ]
402
- : [
403
- "the runtime uses an already-installed official PostgreSQL image and never pulls implicitly",
404
- ]),
405
- "configuration files are never overwritten and .gitignore updates are concurrency-checked",
406
- ],
407
- };
408
- };
409
-
410
- export const applyRehearsalSetup = async (plan) => {
411
- assertSupportedRehearsalNodeRuntime();
412
- const unavailablePorts = plan.requiresAvailablePorts
413
- ? (
414
- await Promise.all(
415
- Object.values(plan.ports).map(async (port) => ({
416
- port,
417
- available: await plan.isPortAvailable(port),
418
- })),
419
- )
420
- ).filter(({ available }) => !available)
421
- : [];
422
- if (unavailablePorts.length) {
423
- throw new Error(
424
- `Rehearsal setup ports became occupied after the preview (${unavailablePorts.map(({ port }) => port).join(", ")}); rerun setup to choose another local port block.`,
425
- );
426
- }
427
- for (const file of plan.files) await assertCreateTargetAvailable(file);
428
- const gitignore = plan.files.find((file) => file.path === GITIGNORE_PATH);
429
- if (gitignore) await applyGitignoreChange(gitignore);
430
- for (const file of plan.files.filter(
431
- (entry) => entry.action !== "unchanged" && entry.path !== GITIGNORE_PATH,
432
- )) {
433
- await mkdir(dirname(file.absolutePath), { recursive: true, mode: 0o700 });
434
- await writeFile(file.absolutePath, file.content, {
435
- flag: file.action === "create" ? "wx" : "w",
436
- mode: file.mode,
437
- });
438
- }
439
- return {
440
- projectRoot: plan.projectRoot,
441
- files: plan.files.map((file) => ({
442
- path: relative(plan.projectRoot, file.absolutePath),
443
- action: file.action,
444
- })),
445
- };
446
- };
447
-
448
- export const summarizeRehearsalSetup = (plan, { mode }) => ({
449
- mode,
450
- configurationPath: plan.configurationPath,
451
- configurationAction:
452
- plan.files.find((file) => file.path === plan.configurationPath)?.action ??
453
- "unchanged",
454
- rootConfigGenerationSkipped: plan.configurationPath !== CONFIG_PATH,
455
- project: plan.project,
456
- projectId: plan.projectId,
457
- target: plan.target,
458
- applicationUrl: plan.applicationUrl,
459
- detected: plan.detected,
460
- ports:
461
- plan.target === "supabase"
462
- ? {
463
- api: plan.ports.api,
464
- database: plan.ports.database,
465
- studio: plan.ports.studio,
466
- }
467
- : { database: plan.ports.database },
468
- files: plan.files.map((file) => ({ path: file.path, action: file.action })),
469
- safety: plan.safety,
470
- nextAction:
471
- mode === "written"
472
- ? `Review ${plan.configurationPath}, including its commands and sanitization policy path, then run rehearsal doctor.`
473
- : "Review this plan, then rerun rehearsal setup --write to create it.",
474
- });
@@ -1,105 +0,0 @@
1
- /**
2
- * Build a copy-ready, data-free support report. The report intentionally omits
3
- * project names, paths, command output, environment-variable names, row data,
4
- * migration contents, and baseline identifiers.
5
- */
6
-
7
- import { spawnSync } from "node:child_process";
8
- import { arch, platform, release } from "node:os";
9
- import {
10
- REHEARSAL_VERSION,
11
- redactDiagnosticValue,
12
- } from "../shared/diagnostics.mjs";
13
- import { runRehearsalDoctor } from "../runtime/plan.mjs";
14
-
15
- export const REHEARSAL_SUPPORT_URL =
16
- "https://github.com/Ddupasquier/rehearsal-db/issues/new?template=bug.yml";
17
-
18
- const safeEnvironment = () =>
19
- Object.fromEntries(
20
- ["HOME", "LANG", "LC_ALL", "PATH", "SHELL", "TMPDIR"].flatMap((key) =>
21
- process.env[key] === undefined ? [] : [[key, process.env[key]]],
22
- ),
23
- );
24
-
25
- const runVersionCommand = (command, args) =>
26
- spawnSync(command, args, {
27
- encoding: "utf8",
28
- env: safeEnvironment(),
29
- stdio: ["ignore", "pipe", "pipe"],
30
- timeout: 5_000,
31
- });
32
-
33
- const inspectTool = ({ command, args, runCommand }) => {
34
- const result = runCommand(command, args);
35
- if (result.error?.code === "ENOENT") return { status: "not_installed" };
36
- if (result.status !== 0) return { status: "unavailable" };
37
- const version = String(result.stdout || result.stderr)
38
- .trim()
39
- .split(/\r?\n/u)[0]
40
- .slice(0, 200);
41
- return {
42
- status: "available",
43
- version: redactDiagnosticValue(version || "version not reported"),
44
- };
45
- };
46
-
47
- export const collectRehearsalSupportReport = async (
48
- options = {},
49
- {
50
- runDoctor = runRehearsalDoctor,
51
- runCommand = runVersionCommand,
52
- system = {
53
- platform: platform(),
54
- release: release(),
55
- architecture: arch(),
56
- node: process.versions.node,
57
- },
58
- } = {},
59
- ) => {
60
- const doctor = await runDoctor(options);
61
- return {
62
- rehearsalVersion: REHEARSAL_VERSION,
63
- system,
64
- tools: {
65
- npm: inspectTool({ command: "npm", args: ["--version"], runCommand }),
66
- supabase: inspectTool({
67
- command: "supabase",
68
- args: ["--version"],
69
- runCommand,
70
- }),
71
- dockerClient: inspectTool({
72
- command: "docker",
73
- args: ["--version"],
74
- runCommand,
75
- }),
76
- dockerServer: inspectTool({
77
- command: "docker",
78
- args: ["info", "--format", "{{.ServerVersion}}"],
79
- runCommand,
80
- }),
81
- },
82
- readiness: {
83
- state: doctor.state,
84
- checks: doctor.checks.map(({ id, label, status, remediation }) => ({
85
- id,
86
- label,
87
- status,
88
- ...(status === "fail" && remediation ? { remediation } : {}),
89
- })),
90
- quarantinedHostedVariableCount:
91
- doctor.ambientHostedVariables?.presentButQuarantined.length ?? 0,
92
- },
93
- privacy: {
94
- omits: [
95
- "row values",
96
- "credentials",
97
- "project paths",
98
- "migration SQL",
99
- "baseline identifiers",
100
- ],
101
- reviewBeforeSharing: true,
102
- },
103
- supportUrl: REHEARSAL_SUPPORT_URL,
104
- };
105
- };