@rehearsal-db/core 0.1.0-beta.9 → 0.1.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (247) hide show
  1. package/CHANGELOG.md +319 -1
  2. package/COMPATIBILITY.md +15 -0
  3. package/README.md +73 -16
  4. package/dist/package.json +100 -0
  5. package/dist/scripts/install/create_root_config.d.mts +3 -0
  6. package/dist/scripts/install/create_root_config.mjs +13 -0
  7. package/dist/scripts/runtime/manage_database.d.mts +6 -0
  8. package/{scripts → dist/scripts}/runtime/manage_database.mjs +6 -8
  9. package/dist/src/application/interactive_task.d.mts +30 -0
  10. package/dist/src/application/interactive_task.mjs +83 -0
  11. package/dist/src/application/session.d.mts +97 -0
  12. package/dist/src/application/session.mjs +380 -0
  13. package/dist/src/baseline/artifact.d.mts +119 -0
  14. package/dist/src/baseline/artifact.mjs +522 -0
  15. package/dist/src/baseline/builder.d.mts +19 -0
  16. package/dist/src/baseline/builder.mjs +142 -0
  17. package/dist/src/baseline/input_discovery.d.mts +13 -0
  18. package/dist/src/baseline/input_discovery.mjs +149 -0
  19. package/dist/src/baseline/policy_review.d.mts +56 -0
  20. package/dist/src/baseline/policy_review.mjs +73 -0
  21. package/dist/src/baseline/preparation.d.mts +63 -0
  22. package/dist/src/baseline/preparation.mjs +295 -0
  23. package/dist/src/baseline/privacy_contract.d.mts +147 -0
  24. package/dist/src/baseline/privacy_contract.mjs +2 -0
  25. package/dist/src/baseline/privacy_dependency_graph.d.mts +7 -0
  26. package/dist/src/baseline/privacy_dependency_graph.mjs +110 -0
  27. package/dist/src/baseline/privacy_engine.d.mts +5 -0
  28. package/dist/src/baseline/privacy_engine.mjs +4 -0
  29. package/dist/src/baseline/privacy_key.d.mts +7 -0
  30. package/dist/src/baseline/privacy_key.mjs +42 -0
  31. package/dist/src/baseline/privacy_pipeline.d.mts +7 -0
  32. package/dist/src/baseline/privacy_pipeline.mjs +127 -0
  33. package/dist/src/baseline/privacy_policy.d.mts +6 -0
  34. package/dist/src/baseline/privacy_policy.mjs +237 -0
  35. package/dist/src/baseline/privacy_recipe_validation.d.mts +5 -0
  36. package/dist/src/baseline/privacy_recipe_validation.mjs +289 -0
  37. package/dist/src/baseline/privacy_rule_registry.d.mts +18 -0
  38. package/dist/src/baseline/privacy_rule_registry.mjs +143 -0
  39. package/dist/src/baseline/privacy_structured_json.d.mts +66 -0
  40. package/dist/src/baseline/privacy_structured_json.mjs +464 -0
  41. package/dist/src/baseline/privacy_transforms.d.mts +18 -0
  42. package/dist/src/baseline/privacy_transforms.mjs +142 -0
  43. package/dist/src/baseline/privacy_validation_shared.d.mts +31 -0
  44. package/dist/src/baseline/privacy_validation_shared.mjs +127 -0
  45. package/dist/src/baseline/sanitization_policy.d.mts +69 -0
  46. package/dist/src/baseline/sanitization_policy.mjs +227 -0
  47. package/dist/src/baseline/schema_snapshot.d.mts +19 -0
  48. package/dist/src/baseline/schema_snapshot.mjs +79 -0
  49. package/dist/src/cli/application_command_handlers.d.mts +8 -0
  50. package/dist/src/cli/application_command_handlers.mjs +52 -0
  51. package/dist/src/cli/arguments.d.mts +38 -0
  52. package/dist/src/cli/arguments.mjs +130 -0
  53. package/dist/src/cli/basic_command_handlers.d.mts +4 -0
  54. package/dist/src/cli/basic_command_handlers.mjs +57 -0
  55. package/dist/src/cli/cli_commands.d.mts +14 -0
  56. package/dist/src/cli/cli_commands.mjs +34 -0
  57. package/dist/src/cli/command_contract.d.mts +57 -0
  58. package/dist/src/cli/command_contract.mjs +39 -0
  59. package/dist/src/cli/command_output.d.mts +12 -0
  60. package/dist/src/cli/command_output.mjs +20 -0
  61. package/dist/src/cli/command_registry.d.mts +7 -0
  62. package/dist/src/cli/command_registry.mjs +14 -0
  63. package/dist/src/cli/guided.d.mts +53 -0
  64. package/dist/src/cli/guided.mjs +727 -0
  65. package/dist/src/cli/identity_command_handlers.d.mts +12 -0
  66. package/dist/src/cli/identity_command_handlers.mjs +239 -0
  67. package/dist/src/cli/identity_renderer.d.mts +8 -0
  68. package/dist/src/cli/identity_renderer.mjs +33 -0
  69. package/dist/src/cli/init_command.d.mts +21 -0
  70. package/dist/src/cli/init_command.mjs +90 -0
  71. package/dist/src/cli/inspection_command_handlers.d.mts +3 -0
  72. package/dist/src/cli/inspection_command_handlers.mjs +79 -0
  73. package/dist/src/cli/project_command_handlers.d.mts +11 -0
  74. package/dist/src/cli/project_command_handlers.mjs +92 -0
  75. package/dist/src/cli/project_commands.d.mts +283 -0
  76. package/dist/src/cli/project_commands.mjs +192 -0
  77. package/dist/src/cli/rehearsal.d.mts +7 -0
  78. package/dist/src/cli/rehearsal.mjs +190 -0
  79. package/dist/src/cli/renderers.d.mts +299 -0
  80. package/dist/src/cli/renderers.mjs +554 -0
  81. package/dist/src/cli/runtime_action_handlers.d.mts +12 -0
  82. package/dist/src/cli/runtime_action_handlers.mjs +201 -0
  83. package/dist/src/cli/runtime_commands.d.mts +294 -0
  84. package/dist/src/cli/runtime_commands.mjs +619 -0
  85. package/dist/src/cli/source_command_handlers.d.mts +8 -0
  86. package/dist/src/cli/source_command_handlers.mjs +62 -0
  87. package/dist/src/cli/source_commands.d.mts +436 -0
  88. package/dist/src/cli/source_commands.mjs +301 -0
  89. package/dist/src/cli/terminal.d.mts +59 -0
  90. package/dist/src/cli/terminal.mjs +230 -0
  91. package/dist/src/identity/claim.d.mts +5 -0
  92. package/dist/src/identity/claim.mjs +4 -0
  93. package/dist/src/identity/claim_contract.d.mts +135 -0
  94. package/dist/src/identity/claim_contract.mjs +6 -0
  95. package/dist/src/identity/claim_executor.d.mts +10 -0
  96. package/dist/src/identity/claim_executor.mjs +358 -0
  97. package/dist/src/identity/claim_identity_lookup.d.mts +23 -0
  98. package/dist/src/identity/claim_identity_lookup.mjs +134 -0
  99. package/dist/src/identity/claim_plan.d.mts +8 -0
  100. package/dist/src/identity/claim_plan.mjs +61 -0
  101. package/dist/src/identity/claim_policy.d.mts +3 -0
  102. package/dist/src/identity/claim_policy.mjs +377 -0
  103. package/dist/src/identity/claim_sql.d.mts +10 -0
  104. package/dist/src/identity/claim_sql.mjs +46 -0
  105. package/dist/src/identity/claim_verification.d.mts +13 -0
  106. package/dist/src/identity/claim_verification.mjs +35 -0
  107. package/dist/src/identity/connect.d.mts +30 -0
  108. package/dist/src/identity/connect.mjs +111 -0
  109. package/dist/src/identity/provider_configuration.d.mts +37 -0
  110. package/dist/src/identity/provider_configuration.mjs +132 -0
  111. package/dist/src/identity/storage.d.mts +21 -0
  112. package/dist/src/identity/storage.mjs +122 -0
  113. package/dist/src/index.d.mts +4 -0
  114. package/dist/src/index.mjs +3 -0
  115. package/dist/src/project/configuration.d.mts +13 -0
  116. package/dist/src/project/configuration.mjs +9 -0
  117. package/dist/src/project/configuration_contract.d.mts +125 -0
  118. package/dist/src/project/configuration_contract.mjs +27 -0
  119. package/dist/src/project/configuration_discovery.d.mts +7 -0
  120. package/dist/src/project/configuration_discovery.mjs +37 -0
  121. package/dist/src/project/configuration_normalization.d.mts +107 -0
  122. package/dist/src/project/configuration_normalization.mjs +302 -0
  123. package/dist/src/project/configuration_rendering.d.mts +14 -0
  124. package/dist/src/project/configuration_rendering.mjs +228 -0
  125. package/dist/src/project/configuration_resolution.d.mts +125 -0
  126. package/dist/src/project/configuration_resolution.mjs +81 -0
  127. package/dist/src/project/configuration_schema.d.mts +22 -0
  128. package/dist/src/project/configuration_schema.mjs +160 -0
  129. package/dist/src/project/configuration_validation.d.mts +22 -0
  130. package/dist/src/project/configuration_validation.mjs +136 -0
  131. package/dist/src/project/install_scaffold.d.mts +42 -0
  132. package/dist/src/project/install_scaffold.mjs +263 -0
  133. package/dist/src/project/project_detection.d.mts +14 -0
  134. package/dist/src/project/project_detection.mjs +47 -0
  135. package/dist/src/project/setup.d.mts +87 -0
  136. package/dist/src/project/setup.mjs +414 -0
  137. package/dist/src/project/support_report.d.mts +88 -0
  138. package/dist/src/project/support_report.mjs +83 -0
  139. package/dist/src/runtime/cleanup.d.mts +85 -0
  140. package/dist/src/runtime/cleanup.mjs +311 -0
  141. package/dist/src/runtime/lifecycle_engine.d.mts +63 -0
  142. package/dist/src/runtime/lifecycle_engine.mjs +114 -0
  143. package/dist/src/runtime/migration_history.d.mts +51 -0
  144. package/dist/src/runtime/migration_history.mjs +170 -0
  145. package/dist/src/runtime/plan.d.mts +134 -0
  146. package/dist/src/runtime/plan.mjs +640 -0
  147. package/dist/src/runtime/policy.d.mts +66 -0
  148. package/dist/src/runtime/policy.mjs +369 -0
  149. package/dist/src/runtime/restore.d.mts +22 -0
  150. package/dist/src/runtime/restore.mjs +276 -0
  151. package/dist/src/runtime/service_environment.d.mts +11 -0
  152. package/dist/src/runtime/service_environment.mjs +58 -0
  153. package/dist/src/runtime/topology.d.mts +51 -0
  154. package/dist/src/runtime/topology.mjs +128 -0
  155. package/dist/src/shared/diagnostics.d.mts +97 -0
  156. package/dist/src/shared/diagnostics.mjs +170 -0
  157. package/dist/src/shared/human_output.d.mts +2 -0
  158. package/{src → dist/src}/shared/human_output.mjs +1 -3
  159. package/dist/src/shared/node_runtime.d.mts +14 -0
  160. package/dist/src/shared/node_runtime.mjs +25 -0
  161. package/dist/src/shared/operation_guard.d.mts +22 -0
  162. package/dist/src/shared/operation_guard.mjs +150 -0
  163. package/dist/src/shared/process_environment.d.mts +15 -0
  164. package/dist/src/shared/process_environment.mjs +50 -0
  165. package/dist/src/source/access.d.mts +165 -0
  166. package/dist/src/source/access.mjs +602 -0
  167. package/dist/src/source/asset_transfer.d.mts +27 -0
  168. package/dist/src/source/asset_transfer.mjs +202 -0
  169. package/dist/src/source/baseline.d.mts +66 -0
  170. package/dist/src/source/baseline.mjs +372 -0
  171. package/dist/src/source/external_reader.d.mts +42 -0
  172. package/dist/src/source/external_reader.mjs +278 -0
  173. package/dist/src/source/postgresql_access.d.mts +86 -0
  174. package/dist/src/source/postgresql_access.mjs +454 -0
  175. package/dist/src/source/privacy_paths.d.mts +10 -0
  176. package/dist/src/source/privacy_paths.mjs +27 -0
  177. package/dist/src/targets/driver.d.mts +2 -0
  178. package/dist/src/targets/driver.mjs +13 -0
  179. package/dist/src/targets/postgresql.d.mts +5 -0
  180. package/dist/src/targets/postgresql.mjs +620 -0
  181. package/dist/src/targets/supabase.d.mts +8 -0
  182. package/dist/src/targets/supabase.mjs +629 -0
  183. package/dist/src/targets/supabase_environment.d.mts +54 -0
  184. package/dist/src/targets/supabase_environment.mjs +184 -0
  185. package/dist/src/targets/target.d.mts +33 -0
  186. package/dist/src/targets/target.mjs +39 -0
  187. package/docs/README.md +35 -16
  188. package/docs/architecture.md +135 -10
  189. package/docs/commands.md +53 -15
  190. package/docs/configuration.md +186 -31
  191. package/docs/getting-started.md +66 -9
  192. package/docs/index.md +67 -0
  193. package/docs/production-source.md +175 -17
  194. package/docs/releasing.md +24 -12
  195. package/docs/roadmap.md +3 -2
  196. package/docs/runtime-lifecycle.md +99 -0
  197. package/docs/runtime-policies.md +80 -7
  198. package/docs/sanitization.md +572 -4
  199. package/docs/security-model.md +8 -0
  200. package/docs/stability.md +70 -0
  201. package/docs/standalone-workflow.md +36 -13
  202. package/docs/troubleshooting.md +82 -10
  203. package/docs/tutorial.md +7 -1
  204. package/docs/typescript.md +134 -0
  205. package/package.json +44 -29
  206. package/types/configuration.d.mts +5 -0
  207. package/src/README.md +0 -17
  208. package/src/application/session.mjs +0 -313
  209. package/src/baseline/artifact.mjs +0 -661
  210. package/src/baseline/builder.mjs +0 -164
  211. package/src/baseline/input_discovery.mjs +0 -155
  212. package/src/baseline/policy_review.mjs +0 -92
  213. package/src/baseline/preparation.mjs +0 -323
  214. package/src/baseline/privacy_engine.mjs +0 -413
  215. package/src/baseline/sanitization_policy.mjs +0 -299
  216. package/src/baseline/schema_snapshot.mjs +0 -113
  217. package/src/cli/arguments.mjs +0 -120
  218. package/src/cli/guided.mjs +0 -807
  219. package/src/cli/rehearsal.mjs +0 -933
  220. package/src/cli/renderers.mjs +0 -584
  221. package/src/cli/runtime_commands.mjs +0 -553
  222. package/src/cli/source_commands.mjs +0 -326
  223. package/src/cli/terminal.mjs +0 -275
  224. package/src/identity/claim.mjs +0 -975
  225. package/src/identity/storage.mjs +0 -165
  226. package/src/project/configuration.d.mts +0 -153
  227. package/src/project/configuration.mjs +0 -1197
  228. package/src/project/setup.mjs +0 -379
  229. package/src/project/support_report.mjs +0 -105
  230. package/src/runtime/cleanup.mjs +0 -381
  231. package/src/runtime/migration_history.mjs +0 -220
  232. package/src/runtime/plan.mjs +0 -685
  233. package/src/runtime/policy.mjs +0 -438
  234. package/src/runtime/restore.mjs +0 -329
  235. package/src/runtime/service_environment.mjs +0 -82
  236. package/src/runtime/topology.mjs +0 -177
  237. package/src/shared/diagnostics.mjs +0 -199
  238. package/src/shared/operation_guard.mjs +0 -162
  239. package/src/shared/process_environment.mjs +0 -68
  240. package/src/source/access.mjs +0 -578
  241. package/src/source/asset_transfer.mjs +0 -177
  242. package/src/source/baseline.mjs +0 -446
  243. package/src/source/postgresql_access.mjs +0 -480
  244. package/src/targets/postgresql.mjs +0 -808
  245. package/src/targets/supabase.mjs +0 -830
  246. package/src/targets/supabase_environment.mjs +0 -229
  247. package/src/targets/target.mjs +0 -66
@@ -0,0 +1,629 @@
1
+ /**
2
+ * Purpose: Restore and manage the disposable local Rehearsal Supabase runtime from
3
+ * the active verified sanitized baseline.
4
+ * Run: `npm run db:rehearsal:reset`, `npm run db:rehearsal:start`,
5
+ * `npm run db:rehearsal:status`, or `npm run db:rehearsal:stop`.
6
+ * Reset deletes only the dedicated local Rehearsal runtime and its Docker volumes.
7
+ */
8
+ import { spawn } from "node:child_process";
9
+ import { cp, mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
10
+ import { createReadStream } from "node:fs";
11
+ import { once } from "node:events";
12
+ import { join, resolve } from "node:path";
13
+ import { createInterface } from "node:readline";
14
+ import { pathToFileURL } from "node:url";
15
+ import { verifyActiveBaseline } from "../baseline/artifact.mjs";
16
+ import { createCleanProcessEnvironment, assertLoopbackUrl, } from "../shared/process_environment.mjs";
17
+ import { localCommandSucceeds, readLocalSupabaseEnvironment, removeLocalSupabaseProjectResources, runLocalCommand, startLocalSupabase, stopLocalSupabase, } from "./supabase_environment.mjs";
18
+ import { buildRestoreSqlPrefix, buildRestoreSqlSuffix, encodeBaselineRecordForCopy, summarizeRestoreError, } from "../runtime/restore.mjs";
19
+ import { buildMigrationLedgerInventory, compareSourceToReplay, createMigrationReplayReceipt, readMigrationFileInventory, } from "../runtime/migration_history.mjs";
20
+ import { loadRehearsalConfig } from "../project/configuration.mjs";
21
+ import { readRehearsalServiceEnvironment } from "../runtime/service_environment.mjs";
22
+ import { applySupabaseAuthenticationProviders } from "../identity/provider_configuration.mjs";
23
+ import { captureLocalPublicSchema, compareProductionSchemaDumps, } from "../baseline/schema_snapshot.mjs";
24
+ import { formatCount } from "../shared/human_output.mjs";
25
+ import { parseRuntimeInvocation } from "./target.mjs";
26
+ import { buildRuntimeFinalizationSql, buildRuntimePostSchemaSql, buildRuntimePrerequisiteSql, buildRuntimeVerificationSql, validateRuntimePolicy, } from "../runtime/policy.mjs";
27
+ import { assertCandidateConfirmation, readActiveRuntimeInput, readCandidateMigrationPlan, readMatchingRuntimeMarker, runRuntimeLifecycle, withOwnedRuntimeRollback, writeCandidateMigrationReceipt, writeRuntimeMarker, } from "../runtime/lifecycle_engine.mjs";
28
+ const repositoryRoot = process.cwd();
29
+ const invocation = parseRuntimeInvocation();
30
+ const loadedConfiguration = await loadRehearsalConfig({
31
+ projectRoot: repositoryRoot,
32
+ ...(invocation.configPath === undefined
33
+ ? {}
34
+ : { configPath: invocation.configPath }),
35
+ });
36
+ const { config, paths: configuredPaths } = loadedConfiguration;
37
+ if (!config.supabase) {
38
+ throw new Error("The Supabase runtime requires config.supabase.");
39
+ }
40
+ if (!configuredPaths.rehearsalConfig) {
41
+ throw new Error("The Supabase runtime requires a Rehearsal config template.");
42
+ }
43
+ const supabaseConfig = config.supabase;
44
+ const artifactRoot = configuredPaths.artifactDirectory;
45
+ const runtimeWorkdir = configuredPaths.runtimeWorkdir;
46
+ const runtimeSupabaseDirectory = join(runtimeWorkdir, "supabase");
47
+ const runtimeMarkerPath = join(runtimeWorkdir, "baseline.json");
48
+ const runtimeCandidateReceiptPath = join(runtimeWorkdir, "candidate-receipt.json");
49
+ const environmentPath = configuredPaths.applicationEnvironment;
50
+ const configTemplatePath = configuredPaths.rehearsalConfig;
51
+ const manifestPath = configuredPaths.sanitizationPolicy;
52
+ const applicationMigrations = new URL("./", pathToFileURL(`${configuredPaths.migrationDirectory}/`));
53
+ const projectId = config.runtime.projectId;
54
+ const runtimeAdapter = configuredPaths.runtimeAdapter
55
+ ? (await import(pathToFileURL(configuredPaths.runtimeAdapter).href))
56
+ : null;
57
+ const runtimePolicy = configuredPaths.runtimePolicy
58
+ ? validateRuntimePolicy(JSON.parse(await readFile(configuredPaths.runtimePolicy, "utf8")))
59
+ : null;
60
+ const environmentKeyPattern = /^[A-Z][A-Z0-9_]*$/u;
61
+ const action = invocation.action;
62
+ const readBaselineDataSha256 = (baseline) => {
63
+ const receipt = baseline.files["sanitized-data.ndjson"];
64
+ if (!receipt) {
65
+ throw new Error("The active baseline is missing its sanitized data receipt.");
66
+ }
67
+ return receipt.sha256;
68
+ };
69
+ const readServiceEnvironment = () => readRehearsalServiceEnvironment({
70
+ path: configuredPaths.serviceEnvironment,
71
+ keys: supabaseConfig.serviceEnvironmentVariables,
72
+ blockedKeys: config.safety.blockedEnvironmentVariables,
73
+ });
74
+ const assertRuntimePath = () => {
75
+ const resolvedRoot = resolve(artifactRoot);
76
+ const resolvedRuntime = resolve(runtimeWorkdir);
77
+ if (resolvedRuntime !== join(resolvedRoot, "runtime")) {
78
+ throw new Error("Refusing an unsafe Rehearsal runtime path.");
79
+ }
80
+ };
81
+ const pathExists = async (path) => stat(path)
82
+ .then(() => true)
83
+ .catch((error) => {
84
+ if (typeof error === "object" &&
85
+ error !== null &&
86
+ "code" in error &&
87
+ error.code === "ENOENT")
88
+ return false;
89
+ throw error;
90
+ });
91
+ const hasProjectRuntimeResources = () => {
92
+ const containers = runLocalCommand("docker", [
93
+ "ps",
94
+ "--all",
95
+ "--filter",
96
+ `label=com.supabase.cli.project=${projectId}`,
97
+ "--format",
98
+ "{{.ID}}",
99
+ ], { capture: true, cwd: repositoryRoot }).trim();
100
+ const volumes = runLocalCommand("docker", ["volume", "ls", "--format", "{{.Name}}"], { capture: true, cwd: repositoryRoot })
101
+ .split("\n")
102
+ .some((name) => name.trim().endsWith(`_${projectId}`));
103
+ return Boolean(containers) || volumes;
104
+ };
105
+ const removeRuntime = async () => {
106
+ assertRuntimePath();
107
+ const hasRuntimeConfig = await pathExists(join(runtimeSupabaseDirectory, "config.toml"));
108
+ if (hasRuntimeConfig || hasProjectRuntimeResources()) {
109
+ try {
110
+ const serviceEnvironment = await readServiceEnvironment().catch(() => ({}));
111
+ runLocalCommand("supabase", hasRuntimeConfig
112
+ ? ["stop", "--no-backup", "--workdir", runtimeWorkdir]
113
+ : ["stop", "--no-backup", "--project-id", projectId], { cwd: repositoryRoot, environment: serviceEnvironment });
114
+ }
115
+ catch {
116
+ removeLocalSupabaseProjectResources({
117
+ cwd: repositoryRoot,
118
+ projectId,
119
+ });
120
+ }
121
+ }
122
+ await rm(runtimeWorkdir, { recursive: true, force: true });
123
+ await rm(environmentPath, { force: true });
124
+ };
125
+ const findDatabaseContainer = () => {
126
+ const output = runLocalCommand("docker", [
127
+ "ps",
128
+ "--filter",
129
+ `label=com.supabase.cli.project=${projectId}`,
130
+ "--format",
131
+ "{{.Names}}",
132
+ ], { capture: true, cwd: repositoryRoot });
133
+ const names = output
134
+ .split("\n")
135
+ .map((value) => value.trim())
136
+ .filter((value) => value.startsWith("supabase_db_"));
137
+ if (names.length !== 1) {
138
+ throw new Error(`Expected one local Rehearsal database container; found ${names.length}.`);
139
+ }
140
+ return names[0];
141
+ };
142
+ const writeChunk = async (stream, chunk, completion) => {
143
+ if (stream.destroyed || stream.writableEnded) {
144
+ throw new Error("The Rehearsal restore connection closed early.");
145
+ }
146
+ if (!stream.write(chunk)) {
147
+ await Promise.race([once(stream, "drain"), completion]);
148
+ if (stream.destroyed || stream.writableEnded) {
149
+ throw new Error("The Rehearsal restore connection closed early.");
150
+ }
151
+ }
152
+ };
153
+ const restoreBaselineRows = async ({ baseline, paths, manifest, }) => {
154
+ const child = spawn("docker", [
155
+ "exec",
156
+ "--interactive",
157
+ findDatabaseContainer(),
158
+ "psql",
159
+ "--quiet",
160
+ "--set",
161
+ "ON_ERROR_STOP=1",
162
+ "--username",
163
+ "postgres",
164
+ "--dbname",
165
+ "postgres",
166
+ ], {
167
+ cwd: repositoryRoot,
168
+ env: createCleanProcessEnvironment(),
169
+ stdio: ["pipe", "ignore", "pipe"],
170
+ });
171
+ let stderr = "";
172
+ child.stderr.setEncoding("utf8");
173
+ child.stderr.on("data", (chunk) => {
174
+ stderr = `${stderr}${chunk}`.slice(-16_384);
175
+ });
176
+ const completion = new Promise((complete, reject) => {
177
+ child.once("error", reject);
178
+ child.once("close", complete);
179
+ });
180
+ child.stdin.on("error", () => undefined);
181
+ try {
182
+ await writeChunk(child.stdin, buildRestoreSqlPrefix(), completion);
183
+ const lines = createInterface({
184
+ input: createReadStream(paths.dataPath, { encoding: "utf8" }),
185
+ crlfDelay: Infinity,
186
+ });
187
+ for await (const line of lines) {
188
+ if (line) {
189
+ await writeChunk(child.stdin, `${encodeBaselineRecordForCopy(line)}\n`, completion);
190
+ }
191
+ }
192
+ await writeChunk(child.stdin, buildRestoreSqlSuffix({
193
+ manifest,
194
+ tableCounts: baseline.tableCounts,
195
+ }), completion);
196
+ child.stdin.end();
197
+ const exitCode = await completion;
198
+ if (exitCode !== 0) {
199
+ throw new Error(`The local Rehearsal restore failed. ${summarizeRestoreError(stderr)}`);
200
+ }
201
+ }
202
+ catch (error) {
203
+ if (child.exitCode === null)
204
+ child.kill("SIGTERM");
205
+ await completion.catch(() => undefined);
206
+ throw new Error(`The local Rehearsal restore failed. ${summarizeRestoreError(stderr)}`, { cause: error });
207
+ }
208
+ };
209
+ const restoreBaselineAssets = async ({ baseline, paths, environment, }) => {
210
+ const assets = Object.entries(baseline.storageAssets ?? {});
211
+ if (assets.length === 0)
212
+ return;
213
+ assertLoopbackUrl("Rehearsal Supabase API", environment.apiUrl);
214
+ for (const [relativePath, receipt] of assets) {
215
+ const content = await readFile(join(paths.generationDirectory, relativePath));
216
+ const objectUrl = new URL(`/storage/v1/object/${encodeURIComponent(receipt.bucket)}/${receipt.objectPath
217
+ .split("/")
218
+ .map(encodeURIComponent)
219
+ .join("/")}`, environment.apiUrl);
220
+ const response = await fetch(objectUrl, {
221
+ method: "POST",
222
+ headers: {
223
+ apikey: environment.serviceRoleKey,
224
+ authorization: `Bearer ${environment.serviceRoleKey}`,
225
+ "content-type": receipt.contentType,
226
+ "x-upsert": "true",
227
+ },
228
+ body: content,
229
+ signal: AbortSignal.timeout(30_000),
230
+ });
231
+ if (!response.ok) {
232
+ throw new Error(`The local Rehearsal Storage restore failed for ${receipt.bucket} with status ${response.status}.`);
233
+ }
234
+ }
235
+ };
236
+ const prepareRuntimeWorkdir = async ({ baseline, paths, }) => {
237
+ assertRuntimePath();
238
+ await mkdir(join(runtimeSupabaseDirectory, "migrations"), {
239
+ recursive: true,
240
+ mode: 0o700,
241
+ });
242
+ const configTemplate = await readFile(configTemplatePath, "utf8");
243
+ await writeFile(join(runtimeSupabaseDirectory, "config.toml"), applySupabaseAuthenticationProviders({
244
+ source: configTemplate,
245
+ authentication: supabaseConfig.authentication,
246
+ apiPort: config.runtime.ports.api ??
247
+ (() => {
248
+ throw new Error("The Supabase runtime requires runtime.ports.api.");
249
+ })(),
250
+ }), { mode: 0o600 });
251
+ for (const filename of Object.keys(baseline.migrations).sort()) {
252
+ await cp(join(paths.migrationsDirectory, filename), join(runtimeSupabaseDirectory, "migrations", filename));
253
+ }
254
+ await writeFile(runtimeMarkerPath, `${JSON.stringify({
255
+ formatVersion: 1,
256
+ generationId: baseline.generationId,
257
+ dataSha256: readBaselineDataSha256(baseline),
258
+ }, null, "\t")}\n`, { mode: 0o600 });
259
+ };
260
+ const writeRuntimeEnvironment = async ({ environment, baseline, projectRuntime, }) => {
261
+ assertLoopbackUrl("Rehearsal Supabase API", environment.apiUrl);
262
+ const variables = {
263
+ REHEARSAL_RUNTIME_ENVIRONMENT: "rehearsal",
264
+ REHEARSAL_BASELINE_ID: baseline.generationId,
265
+ DATABASE_URL: environment.databaseUrl,
266
+ PGHOST: environment.databaseHost,
267
+ PGPORT: environment.databasePort,
268
+ PGDATABASE: environment.databaseName,
269
+ PGUSER: environment.databaseUser,
270
+ PGPASSWORD: environment.databasePassword,
271
+ SUPABASE_URL: environment.apiUrl,
272
+ SUPABASE_PUBLISHABLE_KEY: environment.publishableKey,
273
+ SUPABASE_SERVICE_ROLE_KEY: environment.serviceRoleKey,
274
+ ...projectRuntime.environmentVariables,
275
+ };
276
+ await writeFile(environmentPath, [
277
+ "# Generated local Rehearsal runtime credentials. Do not commit.",
278
+ ...Object.entries(variables).map(([key, value]) => `${key}=${value}`),
279
+ "",
280
+ ].join("\n"), { mode: 0o600 });
281
+ };
282
+ function runDatabaseSql(sql) {
283
+ const databaseArguments = [
284
+ "exec",
285
+ "--interactive",
286
+ findDatabaseContainer(),
287
+ "psql",
288
+ "--quiet",
289
+ "--no-align",
290
+ "--tuples-only",
291
+ "--set",
292
+ "ON_ERROR_STOP=1",
293
+ "--username",
294
+ "postgres",
295
+ "--dbname",
296
+ "postgres",
297
+ ];
298
+ return runLocalCommand("docker", databaseArguments, {
299
+ capture: true,
300
+ cwd: repositoryRoot,
301
+ input: sql,
302
+ });
303
+ }
304
+ const restoreProductionSchema = async ({ baseline, paths, }) => {
305
+ const receipt = baseline.files?.["production-schema.sql"];
306
+ if (baseline.formatVersion === 1 && receipt === undefined) {
307
+ if (runtimePolicy) {
308
+ runDatabaseSql(buildRuntimePrerequisiteSql(runtimePolicy));
309
+ runDatabaseSql(buildRuntimePostSchemaSql(runtimePolicy));
310
+ }
311
+ return null;
312
+ }
313
+ if (baseline.formatVersion !== 2 ||
314
+ !/^[a-f0-9]{64}$/u.test(receipt?.sha256 ?? "") ||
315
+ typeof receipt?.bytes !== "number" ||
316
+ !Number.isSafeInteger(receipt.bytes)) {
317
+ throw new Error("The active baseline predates exact production-schema restoration; refresh it first.");
318
+ }
319
+ const schemaSql = await readFile(paths.schemaPath, "utf8");
320
+ runDatabaseSql(`drop schema if exists public cascade;
321
+ create schema public authorization pg_database_owner;
322
+ grant usage on schema public to public;`);
323
+ if (runtimePolicy) {
324
+ runDatabaseSql(buildRuntimePrerequisiteSql(runtimePolicy));
325
+ }
326
+ if (runtimeAdapter?.prepareSchema) {
327
+ const preparation = runtimeAdapter.prepareSchema({
328
+ runSql: runDatabaseSql,
329
+ });
330
+ if (!preparation || typeof preparation.message !== "string") {
331
+ throw new Error("The project runtime adapter returned an invalid schema preparation result.");
332
+ }
333
+ }
334
+ runDatabaseSql(schemaSql);
335
+ if (runtimePolicy) {
336
+ runDatabaseSql(buildRuntimePostSchemaSql(runtimePolicy));
337
+ }
338
+ const restoredSchema = await captureLocalPublicSchema({
339
+ repositoryRoot,
340
+ artifactRoot,
341
+ workdir: runtimeWorkdir,
342
+ });
343
+ return compareProductionSchemaDumps({
344
+ expected: schemaSql,
345
+ actual: restoredSchema,
346
+ });
347
+ };
348
+ const configureProjectRuntime = ({ environment, baseline, }) => {
349
+ assertLoopbackUrl("Rehearsal Supabase API", environment.apiUrl);
350
+ if (!runtimeAdapter?.configureRuntime) {
351
+ return {
352
+ message: "Project-neutral local runtime is configured.",
353
+ environmentVariables: {},
354
+ };
355
+ }
356
+ const projectRuntime = runtimeAdapter.configureRuntime({
357
+ baseline,
358
+ environment,
359
+ runSql: runDatabaseSql,
360
+ });
361
+ if (!projectRuntime ||
362
+ typeof projectRuntime.message !== "string" ||
363
+ !projectRuntime.environmentVariables ||
364
+ typeof projectRuntime.environmentVariables !== "object" ||
365
+ Array.isArray(projectRuntime.environmentVariables)) {
366
+ throw new Error("The project runtime adapter returned an invalid result.");
367
+ }
368
+ for (const [key, value] of Object.entries(projectRuntime.environmentVariables)) {
369
+ if (!environmentKeyPattern.test(key) ||
370
+ typeof value !== "string" ||
371
+ /[\n\r\0]/u.test(value)) {
372
+ throw new Error("The project runtime adapter returned an unsafe environment variable.");
373
+ }
374
+ }
375
+ return projectRuntime;
376
+ };
377
+ const readActive = () => readActiveRuntimeInput({ artifactRoot, manifestPath });
378
+ const resetRuntime = async () => {
379
+ const active = await readActive();
380
+ const serviceEnvironment = await readServiceEnvironment();
381
+ await removeRuntime();
382
+ return withOwnedRuntimeRollback({
383
+ operation: async function resetRuntimeOperation() {
384
+ await prepareRuntimeWorkdir(active);
385
+ const environment = startLocalSupabase({
386
+ cwd: repositoryRoot,
387
+ workdir: runtimeWorkdir,
388
+ exclude: ["edge-runtime", "logflare", "vector", "realtime"],
389
+ environment: serviceEnvironment,
390
+ autoStartColima: config.containerRuntime.autoStartColima,
391
+ projectId: config.runtime.projectId,
392
+ });
393
+ const structuralSchemaSha256 = await restoreProductionSchema(active);
394
+ await restoreBaselineRows(active);
395
+ await restoreBaselineAssets({
396
+ baseline: active.baseline,
397
+ paths: active.paths,
398
+ environment,
399
+ });
400
+ if (runtimePolicy && runtimePolicy.localRows.length > 0) {
401
+ runDatabaseSql(buildRuntimeFinalizationSql(runtimePolicy));
402
+ console.log("Applied reviewed local-only recipes.");
403
+ }
404
+ const projectRuntime = configureProjectRuntime({
405
+ environment,
406
+ baseline: active.baseline,
407
+ });
408
+ await writeRuntimeEnvironment({
409
+ environment,
410
+ baseline: active.baseline,
411
+ projectRuntime,
412
+ });
413
+ await writeRuntimeMarker({
414
+ path: runtimeMarkerPath,
415
+ marker: {
416
+ formatVersion: active.baseline.formatVersion,
417
+ generationId: active.baseline.generationId,
418
+ dataSha256: readBaselineDataSha256(active.baseline),
419
+ schemaSha256: active.baseline.files?.["production-schema.sql"]?.sha256 ?? null,
420
+ structuralSchemaSha256,
421
+ },
422
+ });
423
+ console.log(`Restored Rehearsal baseline ${active.baseline.generationId}: ${formatCount(active.baseline.rowCount, "row")} across ${formatCount(Object.keys(active.baseline.tableCounts).length, "table")}.`);
424
+ console.log(projectRuntime.message);
425
+ return environment;
426
+ },
427
+ rollback: removeRuntime,
428
+ });
429
+ };
430
+ const startRuntime = async () => {
431
+ const { baseline } = await readActive();
432
+ if (!(await pathExists(runtimeMarkerPath)))
433
+ return resetRuntime();
434
+ const serviceEnvironment = await readServiceEnvironment();
435
+ await readMatchingRuntimeMarker({
436
+ path: runtimeMarkerPath,
437
+ expected: {
438
+ generationId: baseline.generationId,
439
+ dataSha256: readBaselineDataSha256(baseline),
440
+ schemaSha256: baseline.files?.["production-schema.sql"]?.sha256 ?? null,
441
+ },
442
+ mismatchMessage: "The local Rehearsal runtime does not match the active baseline; run db:rehearsal:reset.",
443
+ });
444
+ const environment = startLocalSupabase({
445
+ cwd: repositoryRoot,
446
+ workdir: runtimeWorkdir,
447
+ exclude: ["edge-runtime", "logflare", "vector", "realtime"],
448
+ environment: serviceEnvironment,
449
+ autoStartColima: config.containerRuntime.autoStartColima,
450
+ projectId: config.runtime.projectId,
451
+ });
452
+ const projectRuntime = configureProjectRuntime({ environment, baseline });
453
+ await writeRuntimeEnvironment({
454
+ environment,
455
+ baseline,
456
+ projectRuntime,
457
+ });
458
+ return environment;
459
+ };
460
+ const readCandidateReceipt = (baseline) => readCandidateMigrationPlan({
461
+ baseline,
462
+ migrationDirectory: applicationMigrations,
463
+ });
464
+ const readRuntimeMigrationLedger = () => {
465
+ const output = runLocalCommand("docker", [
466
+ "exec",
467
+ "--interactive",
468
+ findDatabaseContainer(),
469
+ "psql",
470
+ "--quiet",
471
+ "--no-align",
472
+ "--tuples-only",
473
+ "--set",
474
+ "ON_ERROR_STOP=1",
475
+ "--username",
476
+ "postgres",
477
+ "--dbname",
478
+ "postgres",
479
+ ], {
480
+ capture: true,
481
+ cwd: repositoryRoot,
482
+ input: `select coalesce(json_agg(json_build_object(
483
+ 'version', version,
484
+ 'name', name,
485
+ 'statements', statements
486
+ ) order by version), '[]'::json)::text
487
+ from supabase_migrations.schema_migrations;`,
488
+ });
489
+ return buildMigrationLedgerInventory(JSON.parse(output.trim()), "Rehearsal runtime migration ledger");
490
+ };
491
+ const verifyRuntimeMigrationHistory = async ({ baseline, currentFiles, }) => {
492
+ const ledger = readRuntimeMigrationLedger();
493
+ const appliedFiles = currentFiles.slice(0, ledger.length);
494
+ const replayReceipt = createMigrationReplayReceipt({
495
+ files: appliedFiles,
496
+ ledger,
497
+ });
498
+ if (!baseline.sourceMigrationHistory) {
499
+ throw new Error("The active baseline is missing source migration history.");
500
+ }
501
+ return compareSourceToReplay({
502
+ sourceLedger: baseline.sourceMigrationHistory,
503
+ replayReceipt,
504
+ ...(baseline.files["production-schema.sql"]?.sha256 === undefined
505
+ ? {}
506
+ : {
507
+ snapshotSchemaSha256: baseline.files["production-schema.sql"].sha256,
508
+ }),
509
+ });
510
+ };
511
+ const verifyRuntime = async () => {
512
+ const { baseline } = await readActive();
513
+ const environment = await startRuntime();
514
+ const candidateReceipt = await readCandidateReceipt(baseline);
515
+ const migrationComparison = await verifyRuntimeMigrationHistory({
516
+ baseline,
517
+ currentFiles: candidateReceipt.currentFiles,
518
+ });
519
+ let projectVerification;
520
+ if (runtimePolicy)
521
+ runDatabaseSql(buildRuntimeVerificationSql(runtimePolicy));
522
+ if (runtimeAdapter?.verifyRuntime) {
523
+ projectVerification = runtimeAdapter.verifyRuntime({
524
+ baseline,
525
+ environment,
526
+ runSql: runDatabaseSql,
527
+ });
528
+ if (!projectVerification ||
529
+ typeof projectVerification.message !== "string") {
530
+ throw new Error("The project runtime adapter returned an invalid verification result.");
531
+ }
532
+ }
533
+ console.log(`Verified Rehearsal runtime ${baseline.generationId}: immutable baseline and migration history are valid with ${formatCount(migrationComparison.candidates.length, "applied candidate migration")}. Runtime rows may differ from the baseline until the next reset.`);
534
+ if (projectVerification)
535
+ console.log(projectVerification.message);
536
+ return { baseline, candidateReceipt, migrationComparison };
537
+ };
538
+ const migrateRuntime = async () => {
539
+ const { baseline } = await readActive();
540
+ await startRuntime();
541
+ const receipt = await readCandidateReceipt(baseline);
542
+ if (receipt.candidates.length === 0) {
543
+ await writeCandidateMigrationReceipt({
544
+ path: runtimeCandidateReceiptPath,
545
+ target: "supabase",
546
+ baseline,
547
+ plan: receipt,
548
+ });
549
+ console.log(`No candidate migrations follow baseline ${baseline.migrationCutoff}.`);
550
+ return receipt;
551
+ }
552
+ console.log(`${receipt.candidates.length === 1 ? "Candidate migration" : "Candidate migrations"} (${receipt.candidateSha256}): ${receipt.candidates.map((candidate) => candidate.filename).join(", ")}`);
553
+ assertCandidateConfirmation({
554
+ plan: receipt,
555
+ confirmation: invocation.confirmation,
556
+ });
557
+ return withOwnedRuntimeRollback({
558
+ operation: async function migrateRuntimeOperation() {
559
+ const serviceEnvironment = await readServiceEnvironment();
560
+ for (const candidate of receipt.candidates) {
561
+ await cp(join(configuredPaths.migrationDirectory, candidate.filename), join(runtimeSupabaseDirectory, "migrations", candidate.filename));
562
+ }
563
+ runLocalCommand("supabase", ["migration", "up", "--local", "--workdir", runtimeWorkdir], { cwd: repositoryRoot, environment: serviceEnvironment });
564
+ const comparison = await verifyRuntimeMigrationHistory({
565
+ baseline,
566
+ currentFiles: receipt.currentFiles,
567
+ });
568
+ if (comparison.candidates
569
+ .map((candidate) => candidate.filename)
570
+ .join("\0") !==
571
+ receipt.candidates.map((candidate) => candidate.filename).join("\0")) {
572
+ throw new Error("Applied Rehearsal migration history does not match the confirmed candidate suffix.");
573
+ }
574
+ await writeCandidateMigrationReceipt({
575
+ path: runtimeCandidateReceiptPath,
576
+ target: "supabase",
577
+ baseline,
578
+ plan: receipt,
579
+ });
580
+ console.log(`Applied ${formatCount(receipt.candidates.length, "confirmed candidate migration")} to the disposable Rehearsal runtime.`);
581
+ return receipt;
582
+ },
583
+ rollback: removeRuntime,
584
+ });
585
+ };
586
+ await runRuntimeLifecycle({
587
+ action,
588
+ operations: {
589
+ reset: resetRuntime,
590
+ start: async () => {
591
+ const environment = await startRuntime();
592
+ console.log(`Local Rehearsal Supabase: ${environment.apiUrl}`);
593
+ },
594
+ status: async () => {
595
+ const baseline = await verifyActiveBaseline({ artifactRoot });
596
+ const receipt = await readCandidateReceipt(baseline);
597
+ const serviceEnvironment = await readServiceEnvironment();
598
+ const runtimeIsRunning = localCommandSucceeds("supabase", ["status", "--workdir", runtimeWorkdir], { cwd: repositoryRoot, environment: serviceEnvironment });
599
+ if (runtimeIsRunning) {
600
+ const environment = readLocalSupabaseEnvironment({
601
+ cwd: repositoryRoot,
602
+ workdir: runtimeWorkdir,
603
+ environment: serviceEnvironment,
604
+ });
605
+ console.log(`Local Rehearsal Supabase: running at ${environment.apiUrl}`);
606
+ }
607
+ else {
608
+ console.log("Local Rehearsal Supabase: stopped");
609
+ }
610
+ console.log(`Active sanitized baseline: ${baseline.generationId}`);
611
+ console.log(`Candidate migrations (${receipt.candidateSha256}): ${receipt.candidates.map((candidate) => candidate.filename).join(", ") || "none"}`);
612
+ },
613
+ candidates: async () => {
614
+ const baseline = await verifyActiveBaseline({ artifactRoot });
615
+ const receipt = await readCandidateReceipt(baseline);
616
+ console.log(`Candidate migrations (${receipt.candidateSha256}): ${receipt.candidates.map((candidate) => candidate.filename).join(", ") || "none"}`);
617
+ },
618
+ migrate: migrateRuntime,
619
+ verify: verifyRuntime,
620
+ stop: async () => {
621
+ stopLocalSupabase({
622
+ cwd: repositoryRoot,
623
+ workdir: runtimeWorkdir,
624
+ environment: await readServiceEnvironment(),
625
+ });
626
+ },
627
+ discard: removeRuntime,
628
+ },
629
+ });
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Purpose: Start and inspect local-only Supabase workdirs without inheriting hosted
3
+ * credentials. Do not run directly; this module is reusable script infrastructure.
4
+ */
5
+ import { type ProcessEnvironment } from "../shared/process_environment.mjs";
6
+ export interface LocalSupabaseEnvironment {
7
+ readonly apiUrl: string;
8
+ readonly databaseUrl: string;
9
+ readonly publishableKey: string;
10
+ readonly serviceRoleKey: string;
11
+ readonly studioUrl: string | undefined;
12
+ readonly databaseHost: string;
13
+ readonly databasePort: string;
14
+ readonly databaseName: string;
15
+ readonly databaseUser: string;
16
+ readonly databasePassword: string;
17
+ }
18
+ interface LocalCommandOptions {
19
+ readonly capture?: boolean;
20
+ readonly cwd?: string | undefined;
21
+ readonly input?: string | Uint8Array;
22
+ readonly environment?: ProcessEnvironment;
23
+ readonly maxBuffer?: number;
24
+ }
25
+ interface SupabaseCommandOptions {
26
+ readonly cwd?: string | undefined;
27
+ readonly workdir?: string | undefined;
28
+ readonly environment?: ProcessEnvironment;
29
+ }
30
+ export declare const parseSupabaseStatusEnvironment: (output: string) => Record<string, string>;
31
+ export declare const parseLocalSupabaseEnvironment: (output: string) => LocalSupabaseEnvironment;
32
+ export declare const runLocalCommand: (command: string, args: readonly string[], { capture, cwd, input, environment, maxBuffer, }?: LocalCommandOptions) => string;
33
+ export declare const localCommandSucceeds: (command: string, args: readonly string[], { cwd, environment, }?: Pick<LocalCommandOptions, "cwd" | "environment">) => boolean;
34
+ export declare const ensureLocalContainerRuntime: ({ cwd, autoStartColima, }?: {
35
+ cwd?: string | undefined;
36
+ autoStartColima?: boolean;
37
+ }) => void;
38
+ export declare const preventOwnedSupabaseAutoRestart: ({ cwd, projectId, }: {
39
+ cwd?: string | undefined;
40
+ projectId: string;
41
+ }) => readonly string[];
42
+ export declare const readLocalSupabaseEnvironment: ({ cwd, workdir, environment, }?: SupabaseCommandOptions) => LocalSupabaseEnvironment;
43
+ export declare const startLocalSupabase: ({ cwd, workdir, exclude, environment, applyMigrations, autoStartColima, projectId, }?: SupabaseCommandOptions & {
44
+ exclude?: readonly string[];
45
+ applyMigrations?: boolean;
46
+ autoStartColima?: boolean;
47
+ projectId?: string | undefined;
48
+ }) => LocalSupabaseEnvironment;
49
+ export declare const stopLocalSupabase: ({ cwd, workdir, environment, }?: SupabaseCommandOptions) => void;
50
+ export declare const removeLocalSupabaseProjectResources: ({ cwd, projectId, }?: {
51
+ cwd?: string;
52
+ projectId?: string;
53
+ }) => void;
54
+ export {};