@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
@@ -0,0 +1,263 @@
1
+ /**
2
+ * Purpose: Resolve an npm consumer root conservatively and create the same
3
+ * project-owned scaffolding used by the guided setup command.
4
+ */
5
+ import { access, readFile, readdir, stat } from "node:fs/promises";
6
+ import { isAbsolute, join, relative, resolve, sep } from "node:path";
7
+ import { applyRehearsalSetup, planRehearsalSetup } from "./setup.mjs";
8
+ import { findRehearsalConfigPath, inspectDetectedProject, loadRehearsalConfig, } from "./configuration.mjs";
9
+ const PACKAGE_NAME = "@rehearsal-db/core";
10
+ const DEPENDENCY_GROUPS = Object.freeze([
11
+ "dependencies",
12
+ "devDependencies",
13
+ "optionalDependencies",
14
+ "peerDependencies",
15
+ ]);
16
+ const CACHE_SEGMENTS = new Set(["_cacache", "_npx", ".cache"]);
17
+ const hasErrorCode = (error, code) => typeof error === "object" &&
18
+ error !== null &&
19
+ "code" in error &&
20
+ error.code === code;
21
+ const pathExists = (path) => access(path)
22
+ .then(() => true)
23
+ .catch((error) => {
24
+ if (hasErrorCode(error, "ENOENT"))
25
+ return false;
26
+ throw error;
27
+ });
28
+ const readJson = async (path) => {
29
+ const value = JSON.parse(await readFile(path, "utf8"));
30
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
31
+ throw new Error(`Expected a JSON object in ${path}.`);
32
+ }
33
+ return value;
34
+ };
35
+ const isInside = (parent, child) => {
36
+ const path = relative(resolve(parent), resolve(child));
37
+ return path === "" || (!path.startsWith(`..${sep}`) && path !== "..");
38
+ };
39
+ const pathSegments = (path) => resolve(path).split(sep).filter(Boolean);
40
+ const isCachePath = (path) => pathSegments(path).some((segment) => CACHE_SEGMENTS.has(segment));
41
+ const isInstalledPackagePath = (packageRoot) => {
42
+ const segments = pathSegments(packageRoot);
43
+ return (segments.at(-3) === "node_modules" &&
44
+ segments.at(-2) === "@rehearsal-db" &&
45
+ segments.at(-1) === "core");
46
+ };
47
+ const declaresRehearsal = (manifest) => DEPENDENCY_GROUPS.some((group) => Object.hasOwn(typeof manifest[group] === "object" && manifest[group] !== null
48
+ ? manifest[group]
49
+ : {}, PACKAGE_NAME));
50
+ const readProject = async (root) => {
51
+ const path = join(root, "package.json");
52
+ if (!(await pathExists(path)))
53
+ return null;
54
+ const metadata = await stat(path);
55
+ if (!metadata.isFile())
56
+ return null;
57
+ return { root: resolve(root), manifest: await readJson(path) };
58
+ };
59
+ const workspacePatterns = (manifest) => {
60
+ const value = manifest.workspaces;
61
+ if (Array.isArray(value))
62
+ return value;
63
+ if (value && Array.isArray(value.packages))
64
+ return value.packages;
65
+ return [];
66
+ };
67
+ const expandWorkspacePattern = async (root, pattern) => {
68
+ if (typeof pattern !== "string" ||
69
+ !pattern ||
70
+ isAbsolute(pattern) ||
71
+ pattern.split(/[\\/]/u).includes("..")) {
72
+ return [];
73
+ }
74
+ if (!pattern.includes("*"))
75
+ return [resolve(root, pattern)];
76
+ if (!pattern.endsWith("/*") || pattern.slice(0, -2).includes("*")) {
77
+ return [];
78
+ }
79
+ const parent = resolve(root, pattern.slice(0, -2));
80
+ const entries = await readdir(parent, { withFileTypes: true }).catch((error) => {
81
+ if (hasErrorCode(error, "ENOENT"))
82
+ return [];
83
+ throw error;
84
+ });
85
+ return entries
86
+ .filter((entry) => entry.isDirectory() && entry.name !== "node_modules")
87
+ .map((entry) => join(parent, entry.name));
88
+ };
89
+ const workspaceProjectsFromManifest = async (project) => {
90
+ const roots = (await Promise.all(workspacePatterns(project.manifest).map((pattern) => expandWorkspacePattern(project.root, pattern)))).flat();
91
+ return (await Promise.all(roots.map((root) => isInside(project.root, root) ? readProject(root) : null))).filter((entry) => entry !== null);
92
+ };
93
+ const workspaceProjectsFromLock = async (root) => {
94
+ const path = join(root, "package-lock.json");
95
+ if (!(await pathExists(path)))
96
+ return [];
97
+ const lock = await readJson(path);
98
+ const packages = typeof lock.packages === "object" && lock.packages !== null
99
+ ? lock.packages
100
+ : {};
101
+ return (await Promise.all(Object.entries(packages)
102
+ .filter(([name, manifest]) => name &&
103
+ !name.split("/").includes("node_modules") &&
104
+ declaresRehearsal(manifest))
105
+ .map(([name]) => readProject(resolve(root, name))))).filter((entry) => entry !== null);
106
+ };
107
+ const uniqueProjects = (projects) => [
108
+ ...new Map(projects
109
+ .filter((entry) => entry !== null)
110
+ .map((entry) => [entry.root, entry])).values(),
111
+ ];
112
+ const skip = (reason) => ({
113
+ status: "skipped",
114
+ reason,
115
+ });
116
+ export const renderInstalledRehearsalScaffold = (result) => {
117
+ if (result.status !== "ready") {
118
+ return `Rehearsal did not create project files: ${result.reason}.\nNext: run npx rehearsal from the application directory.`;
119
+ }
120
+ const changed = result.files.filter((file) => file.action !== "unchanged");
121
+ return [
122
+ "Rehearsal is ready in this project.",
123
+ `Configuration: ${result.configurationPath} (${result.configurationAction === "create" ? "created" : "existing"})`,
124
+ ...(result.rootConfigGenerationSkipped
125
+ ? [
126
+ ` Existing supported configuration detected at ${result.configurationPath}.`,
127
+ " rehearsal.config.mjs was not created because Rehearsal preserves one active configuration and never creates a competing file.",
128
+ ]
129
+ : []),
130
+ ...(changed.length
131
+ ? [
132
+ ...changed.map((file) => ` ${file.action === "create" ? "+" : "~"} ${file.path}`),
133
+ ]
134
+ : [" Existing Rehearsal files preserved."]),
135
+ `Next: review ${result.configurationPath}, then run npx rehearsal.`,
136
+ ].join("\n");
137
+ };
138
+ export const resolveRehearsalConsumerRoot = async ({ packageRoot, initCwd, localPrefix, globalInstall = false, workspace, } = {}) => {
139
+ if (globalInstall === true ||
140
+ (typeof globalInstall === "string" && ["1", "true"].includes(globalInstall))) {
141
+ return skip("global installations do not own an application project");
142
+ }
143
+ if (!packageRoot || !isInstalledPackagePath(packageRoot)) {
144
+ return skip("the package is not running from a consumer node_modules tree");
145
+ }
146
+ if (isCachePath(packageRoot)) {
147
+ return skip("package-cache and temporary npx installs are not project roots");
148
+ }
149
+ const anchorRoots = [
150
+ ...new Set([initCwd, localPrefix].filter((path) => typeof path === "string")),
151
+ ]
152
+ .map((path) => resolve(path))
153
+ .filter((path) => !pathSegments(path).includes("node_modules") && !isCachePath(path));
154
+ const anchors = uniqueProjects(await Promise.all(anchorRoots.map((root) => readProject(root))));
155
+ if (anchors.length === 0) {
156
+ return skip("no application package.json was found for this installation");
157
+ }
158
+ const installationAnchors = anchors.filter((project) => isInside(project.root, packageRoot));
159
+ if (installationAnchors.length === 0) {
160
+ return skip("the installed package is outside the candidate application root");
161
+ }
162
+ const declared = [];
163
+ const workspaces = [];
164
+ for (const anchor of installationAnchors) {
165
+ if (declaresRehearsal(anchor.manifest))
166
+ declared.push(anchor);
167
+ const [manifestWorkspaces, lockWorkspaces] = await Promise.all([
168
+ workspaceProjectsFromManifest(anchor),
169
+ workspaceProjectsFromLock(anchor.root),
170
+ ]);
171
+ workspaces.push(...manifestWorkspaces, ...lockWorkspaces);
172
+ declared.push(...manifestWorkspaces.filter((project) => declaresRehearsal(project.manifest)), ...lockWorkspaces);
173
+ }
174
+ if (workspace) {
175
+ const requested = String(workspace).trim().replace(/\\/gu, "/");
176
+ const selected = uniqueProjects(workspaces).filter((project) => project.manifest.name === requested ||
177
+ installationAnchors.some((anchor) => relative(anchor.root, project.root).replace(/\\/gu, "/") ===
178
+ requested.replace(/^\.\//u, "")));
179
+ if (selected.length === 1) {
180
+ return { status: "resolved", projectRoot: selected[0].root };
181
+ }
182
+ if (selected.length > 1) {
183
+ return skip("the npm workspace selector matched multiple applications");
184
+ }
185
+ }
186
+ const candidates = uniqueProjects(declared);
187
+ if (candidates.length === 1) {
188
+ return { status: "resolved", projectRoot: candidates[0].root };
189
+ }
190
+ if (candidates.length > 1) {
191
+ return skip(`multiple workspace applications declare ${PACKAGE_NAME}; run Rehearsal inside each application`);
192
+ }
193
+ const exact = initCwd ? await readProject(resolve(initCwd)) : null;
194
+ const primaryInstallationAnchor = installationAnchors[0];
195
+ const workspaceRoot = installationAnchors.some((project) => workspacePatterns(project.manifest).length > 0);
196
+ if (exact &&
197
+ installationAnchors.some((anchor) => isInside(anchor.root, exact.root)) &&
198
+ (!workspaceRoot || exact.root !== primaryInstallationAnchor.root)) {
199
+ return { status: "resolved", projectRoot: exact.root };
200
+ }
201
+ if (!workspaceRoot && installationAnchors.length === 1) {
202
+ return {
203
+ status: "resolved",
204
+ projectRoot: primaryInstallationAnchor.root,
205
+ };
206
+ }
207
+ return skip("the workspace application root is ambiguous");
208
+ };
209
+ export const createInstalledRehearsalScaffold = async ({ packageRoot, environment = process.env, isPortAvailable, } = {}) => {
210
+ const resolution = await resolveRehearsalConsumerRoot({
211
+ ...(packageRoot === undefined ? {} : { packageRoot }),
212
+ ...(environment.INIT_CWD === undefined
213
+ ? {}
214
+ : { initCwd: environment.INIT_CWD }),
215
+ ...(environment.npm_config_local_prefix === undefined
216
+ ? {}
217
+ : { localPrefix: environment.npm_config_local_prefix }),
218
+ ...(environment.npm_config_global === undefined
219
+ ? {}
220
+ : { globalInstall: environment.npm_config_global }),
221
+ ...(environment.npm_config_workspace === undefined
222
+ ? {}
223
+ : { workspace: environment.npm_config_workspace }),
224
+ });
225
+ if (resolution.status !== "resolved")
226
+ return resolution;
227
+ const detected = await inspectDetectedProject({
228
+ projectRoot: resolution.projectRoot,
229
+ });
230
+ const hasExistingConfig = await findRehearsalConfigPath({
231
+ projectRoot: resolution.projectRoot,
232
+ })
233
+ .then(() => true)
234
+ .catch((error) => {
235
+ if ((error instanceof Error ? error.message : String(error)).startsWith("No Rehearsal configuration")) {
236
+ return false;
237
+ }
238
+ throw error;
239
+ });
240
+ const plan = await planRehearsalSetup({
241
+ projectRoot: resolution.projectRoot,
242
+ ...(hasExistingConfig
243
+ ? {}
244
+ : { target: detected.hasSupabaseConfig ? "supabase" : "postgresql" }),
245
+ ...(isPortAvailable === undefined ? {} : { isPortAvailable }),
246
+ });
247
+ const result = await applyRehearsalSetup(plan);
248
+ const loaded = await loadRehearsalConfig({
249
+ projectRoot: resolution.projectRoot,
250
+ });
251
+ const configurationPath = relative(resolution.projectRoot, loaded.configPath);
252
+ const configurationAction = result.files.find(({ path }) => path === configurationPath)?.action ??
253
+ "unchanged";
254
+ return {
255
+ status: "ready",
256
+ projectRoot: resolution.projectRoot,
257
+ target: plan.target,
258
+ files: result.files,
259
+ configurationPath,
260
+ configurationAction,
261
+ rootConfigGenerationSkipped: configurationPath !== "rehearsal.config.mjs",
262
+ };
263
+ };
@@ -0,0 +1,14 @@
1
+ /** Inspect project-local files used to render a conservative starter config. */
2
+ export interface DetectedProject {
3
+ projectName: string;
4
+ packageManager: "npm" | "pnpm" | "yarn";
5
+ hasSupabaseConfig: boolean;
6
+ hasMigrations: boolean;
7
+ hasPostgresqlMigrations: boolean;
8
+ postgresqlMigrationDirectory: string;
9
+ applicationCommand: string;
10
+ verificationCommand: string;
11
+ }
12
+ export declare const inspectDetectedProject: ({ projectRoot, }?: {
13
+ projectRoot?: string;
14
+ }) => Promise<DetectedProject>;
@@ -0,0 +1,47 @@
1
+ /** Inspect project-local files used to render a conservative starter config. */
2
+ import { readFile, stat } from "node:fs/promises";
3
+ import { basename, join, resolve } from "node:path";
4
+ const hasErrorCode = (error, code) => typeof error === "object" &&
5
+ error !== null &&
6
+ "code" in error &&
7
+ error.code === code;
8
+ export const inspectDetectedProject = async ({ projectRoot = process.cwd(), } = {}) => {
9
+ const root = resolve(projectRoot);
10
+ const packageJsonPath = join(root, "package.json");
11
+ const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
12
+ const hasPath = async (path) => stat(join(root, path))
13
+ .then(() => true)
14
+ .catch((error) => {
15
+ if (hasErrorCode(error, "ENOENT"))
16
+ return false;
17
+ throw error;
18
+ });
19
+ const packageManager = (await hasPath("pnpm-lock.yaml"))
20
+ ? "pnpm"
21
+ : (await hasPath("yarn.lock"))
22
+ ? "yarn"
23
+ : "npm";
24
+ const scripts = packageJson.scripts ?? {};
25
+ const postgresqlMigrationDirectory = (await Promise.all(["database/migrations", "db/migrations", "migrations"].map(async (path) => ({ path, exists: await hasPath(path) })))).find(({ exists }) => exists)?.path;
26
+ const normalizedProjectName = String(packageJson.name ?? basename(root))
27
+ .toLowerCase()
28
+ .replace(/[^a-z0-9-]+/gu, "-")
29
+ .replace(/^-|-$/gu, "");
30
+ const projectName = normalizedProjectName.slice(0, 52).replace(/-$/u, "") ||
31
+ "rehearsal-project";
32
+ return {
33
+ projectName,
34
+ packageManager,
35
+ hasSupabaseConfig: await hasPath("supabase/config.toml"),
36
+ hasMigrations: await hasPath("supabase/migrations"),
37
+ hasPostgresqlMigrations: Boolean(postgresqlMigrationDirectory),
38
+ postgresqlMigrationDirectory: postgresqlMigrationDirectory ?? "database/migrations",
39
+ applicationCommand: ["dev:rehearsal", "dev", "start"]
40
+ .find((name) => typeof scripts[name] === "string")
41
+ ?.replace(/^/u, `${packageManager} run `) ??
42
+ `${packageManager} run dev`,
43
+ verificationCommand: ["verify:feature", "test", "check"]
44
+ .find((name) => typeof scripts[name] === "string")
45
+ ?.replace(/^/u, `${packageManager} run `) ?? `${packageManager} test`,
46
+ };
47
+ };
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Purpose: Plan and apply conservative first-run Rehearsal scaffolding without
3
+ * contacting hosted services or overwriting project-owned files.
4
+ */
5
+ export { assertSupportedRehearsalNodeRuntime, inspectRehearsalNodeRuntime, } from "../shared/node_runtime.mjs";
6
+ import type { DetectedProject } from "./configuration.mjs";
7
+ import type { RehearsalRuntimeTargetId } from "../targets/target.mjs";
8
+ type PortAvailability = (port: number) => boolean | Promise<boolean>;
9
+ interface SupabasePorts {
10
+ shadow: number;
11
+ api: number;
12
+ database: number;
13
+ studio: number;
14
+ smtp: number;
15
+ pooler: number;
16
+ }
17
+ interface SetupFile {
18
+ path: string;
19
+ absolutePath: string;
20
+ action: "create" | "update" | "unchanged";
21
+ mode: number;
22
+ content: string | null;
23
+ originalContent?: string | null;
24
+ }
25
+ export interface PlanRehearsalSetupOptions {
26
+ projectRoot?: string;
27
+ configPath?: string;
28
+ isPortAvailable?: PortAvailability;
29
+ target?: RehearsalRuntimeTargetId;
30
+ }
31
+ export interface RehearsalSetupPlan {
32
+ projectRoot: string;
33
+ configurationPath: string;
34
+ project: string;
35
+ projectId: string;
36
+ target: RehearsalRuntimeTargetId;
37
+ applicationUrl: string;
38
+ detected: DetectedProject;
39
+ ports: Record<string, number> & {
40
+ database: number;
41
+ };
42
+ requiresAvailablePorts: boolean;
43
+ isPortAvailable: PortAvailability;
44
+ files: SetupFile[];
45
+ safety: string[];
46
+ }
47
+ export declare const isRehearsalPortAvailable: (port: number) => Promise<boolean>;
48
+ export declare const findAvailableRehearsalPorts: ({ isAvailable, }?: {
49
+ isAvailable?: PortAvailability;
50
+ }) => Promise<SupabasePorts>;
51
+ export declare const renderSafeLocalSupabaseConfig: ({ projectId, applicationUrl, ports, databaseMajorVersion, }: {
52
+ projectId: string;
53
+ applicationUrl: string;
54
+ ports: SupabasePorts;
55
+ databaseMajorVersion?: number;
56
+ }) => string;
57
+ export declare const appendRehearsalGitignoreEntries: (source: string | null) => string;
58
+ export declare const planRehearsalSetup: ({ projectRoot, configPath, isPortAvailable, target, }?: PlanRehearsalSetupOptions) => Promise<RehearsalSetupPlan>;
59
+ export declare const applyRehearsalSetup: (plan: RehearsalSetupPlan) => Promise<{
60
+ projectRoot: string;
61
+ files: {
62
+ path: string;
63
+ action: "create" | "unchanged" | "update";
64
+ }[];
65
+ }>;
66
+ export declare const summarizeRehearsalSetup: (plan: RehearsalSetupPlan, { mode }: {
67
+ mode: "preview" | "written";
68
+ }) => {
69
+ mode: "preview" | "written";
70
+ configurationPath: string;
71
+ configurationAction: "create" | "update" | "unchanged";
72
+ rootConfigGenerationSkipped: boolean;
73
+ project: string;
74
+ projectId: string;
75
+ target: "supabase" | "postgresql";
76
+ applicationUrl: string;
77
+ detected: RehearsalSetupPlan["detected"];
78
+ ports: Readonly<Record<string, number>> & {
79
+ readonly database: number;
80
+ };
81
+ files: readonly {
82
+ path: string;
83
+ action: "create" | "update" | "unchanged";
84
+ }[];
85
+ safety: readonly string[];
86
+ nextAction: string;
87
+ };