@rehearsal-db/core 0.1.0-beta.13 → 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 (169) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/README.md +4 -1
  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 +49 -8
  111. package/docs/getting-started.md +28 -0
  112. package/docs/index.md +1 -0
  113. package/docs/production-source.md +43 -7
  114. package/docs/releasing.md +5 -3
  115. package/docs/roadmap.md +3 -2
  116. package/docs/sanitization.md +415 -14
  117. package/docs/standalone-workflow.md +12 -6
  118. package/docs/troubleshooting.md +5 -1
  119. package/docs/tutorial.md +5 -0
  120. package/docs/typescript.md +87 -0
  121. package/package.json +35 -29
  122. package/scripts/install/create_root_config.mjs +0 -36
  123. package/src/README.md +0 -17
  124. package/src/application/session.mjs +0 -435
  125. package/src/baseline/artifact.mjs +0 -684
  126. package/src/baseline/builder.mjs +0 -164
  127. package/src/baseline/input_discovery.mjs +0 -155
  128. package/src/baseline/policy_review.mjs +0 -92
  129. package/src/baseline/preparation.mjs +0 -323
  130. package/src/baseline/privacy_engine.mjs +0 -952
  131. package/src/baseline/sanitization_policy.mjs +0 -299
  132. package/src/baseline/schema_snapshot.mjs +0 -113
  133. package/src/cli/arguments.mjs +0 -126
  134. package/src/cli/guided.mjs +0 -814
  135. package/src/cli/init_command.mjs +0 -94
  136. package/src/cli/rehearsal.mjs +0 -970
  137. package/src/cli/renderers.mjs +0 -626
  138. package/src/cli/runtime_commands.mjs +0 -707
  139. package/src/cli/source_commands.mjs +0 -347
  140. package/src/cli/terminal.mjs +0 -275
  141. package/src/identity/claim.mjs +0 -1163
  142. package/src/identity/provider_configuration.mjs +0 -177
  143. package/src/identity/storage.mjs +0 -165
  144. package/src/project/configuration.mjs +0 -1337
  145. package/src/project/install_scaffold.mjs +0 -291
  146. package/src/project/setup.mjs +0 -466
  147. package/src/project/support_report.mjs +0 -105
  148. package/src/runtime/cleanup.mjs +0 -381
  149. package/src/runtime/migration_history.mjs +0 -220
  150. package/src/runtime/plan.mjs +0 -722
  151. package/src/runtime/policy.mjs +0 -438
  152. package/src/runtime/restore.mjs +0 -337
  153. package/src/runtime/service_environment.mjs +0 -82
  154. package/src/runtime/topology.mjs +0 -177
  155. package/src/shared/diagnostics.mjs +0 -207
  156. package/src/shared/node_runtime.mjs +0 -31
  157. package/src/shared/operation_guard.mjs +0 -162
  158. package/src/shared/process_environment.mjs +0 -68
  159. package/src/source/access.mjs +0 -753
  160. package/src/source/asset_transfer.mjs +0 -207
  161. package/src/source/baseline.mjs +0 -482
  162. package/src/source/external_reader.mjs +0 -255
  163. package/src/source/postgresql_access.mjs +0 -581
  164. package/src/source/privacy_paths.mjs +0 -38
  165. package/src/targets/postgresql.mjs +0 -808
  166. package/src/targets/supabase.mjs +0 -840
  167. package/src/targets/supabase_environment.mjs +0 -229
  168. package/src/targets/target.mjs +0 -66
  169. /package/{src/project → types}/configuration.d.mts +0 -0
@@ -1,207 +0,0 @@
1
- /**
2
- * Purpose: Provide stable Rehearsal result, failure, redaction, and rendering
3
- * contracts for humans and automation. Do not run directly; this module is reusable
4
- * script infrastructure.
5
- */
6
-
7
- import { createHash } from "node:crypto";
8
- import packageManifest from "../../package.json" with { type: "json" };
9
-
10
- export const REHEARSAL_RESULT_VERSION = 1;
11
- export const REHEARSAL_VERSION = packageManifest.version;
12
-
13
- export const REHEARSAL_EXIT_CODES = Object.freeze({
14
- success: 0,
15
- configuration_invalid: 2,
16
- unsafe_environment: 3,
17
- baseline_invalid: 4,
18
- baseline_checksum_mismatch: 5,
19
- migration_candidate_failure: 6,
20
- migration_verification_failure: 7,
21
- application_proof_failure: 8,
22
- runtime_dependency_failure: 9,
23
- internal_failure: 10,
24
- });
25
-
26
- const SECRET_KEY_PATTERN =
27
- /(?:authorization|cookie|credential|database[_-]?url|jwt|key|password|secret|service[_-]?role|token)/iu;
28
- const JWT_PATTERN =
29
- /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/gu;
30
- const CONNECTION_STRING_PATTERN =
31
- /\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?):\/\/[^\s"']+/giu;
32
- const BEARER_PATTERN = /\bBearer\s+[^\s"']+/giu;
33
- const SUPABASE_KEY_PATTERN = /\bsb_(?:publishable|secret)_[A-Za-z0-9_-]+\b/gu;
34
-
35
- const redactString = (value) =>
36
- value
37
- .replace(CONNECTION_STRING_PATTERN, "[REDACTED_CONNECTION_STRING]")
38
- .replace(JWT_PATTERN, "[REDACTED_JWT]")
39
- .replace(BEARER_PATTERN, "Bearer [REDACTED]")
40
- .replace(SUPABASE_KEY_PATTERN, "[REDACTED_KEY]");
41
-
42
- export const redactDiagnosticValue = (value, key = "") => {
43
- if (SECRET_KEY_PATTERN.test(key)) return "[REDACTED]";
44
- if (typeof value === "string") return redactString(value);
45
- if (Array.isArray(value)) {
46
- return value.map((entry) => redactDiagnosticValue(entry));
47
- }
48
- if (value && typeof value === "object") {
49
- return Object.fromEntries(
50
- Object.entries(value).map(([entryKey, entryValue]) => [
51
- entryKey,
52
- redactDiagnosticValue(entryValue, entryKey),
53
- ]),
54
- );
55
- }
56
- return value;
57
- };
58
-
59
- const diagnosticIdFor = ({ category, code, message }) =>
60
- createHash("sha256")
61
- .update(`${category}\0${code}\0${message}`)
62
- .digest("hex")
63
- .slice(0, 12);
64
-
65
- export class RehearsalError extends Error {
66
- constructor({
67
- category,
68
- code,
69
- message,
70
- expected,
71
- actual,
72
- context,
73
- refused,
74
- suggestions = [],
75
- cause,
76
- }) {
77
- super(message, { cause });
78
- this.name = "RehearsalError";
79
- if (!Object.hasOwn(REHEARSAL_EXIT_CODES, category)) {
80
- throw new Error(`Unknown Rehearsal error category: ${category}.`);
81
- }
82
- this.category = category;
83
- this.code = code;
84
- this.expected = expected;
85
- this.actual = actual;
86
- this.context = context;
87
- this.refused = refused;
88
- this.suggestions = suggestions;
89
- this.diagnosticId = diagnosticIdFor({ category, code, message });
90
- this.exitCode = REHEARSAL_EXIT_CODES[category];
91
- }
92
- }
93
-
94
- const inferCategory = (error) => {
95
- const message = String(error?.message ?? error);
96
- if (/checksum/iu.test(message)) return "baseline_checksum_mismatch";
97
- if (
98
- /container\b.*\b(?:is not ready|starting|unhealthy)|health(?:check)?\b.*\b(?:failed|starting|unhealthy)|out of memory|\boom\b|no space left|resource temporarily unavailable/iu.test(
99
- message,
100
- )
101
- ) {
102
- return "runtime_dependency_failure";
103
- }
104
- if (/candidate|migration history diverges/iu.test(message)) {
105
- return "migration_candidate_failure";
106
- }
107
- if (/baseline|artifact|generation|sanitization policy/iu.test(message)) {
108
- return "baseline_invalid";
109
- }
110
- if (/migration|ledger|replay/iu.test(message)) {
111
- return "migration_verification_failure";
112
- }
113
- if (/config|unknown rehearsal|unsupported rehearsal/iu.test(message)) {
114
- return "configuration_invalid";
115
- }
116
- if (/loopback|hosted|unsafe|credential|outbound/iu.test(message)) {
117
- return "unsafe_environment";
118
- }
119
- if (
120
- /docker|postgres(?:ql)?|supabase|runtime|command|enoent|node\.js|ports?\b/iu.test(
121
- message,
122
- )
123
- ) {
124
- return "runtime_dependency_failure";
125
- }
126
- return "internal_failure";
127
- };
128
-
129
- export const normalizeRehearsalError = (error, overrides = {}) => {
130
- if (error instanceof RehearsalError) return error;
131
- const message = String(error?.message ?? error);
132
- const category = overrides.category ?? inferCategory(error);
133
- return new RehearsalError({
134
- category,
135
- code: overrides.code ?? category.toUpperCase(),
136
- message,
137
- expected: overrides.expected,
138
- actual: overrides.actual,
139
- context: overrides.context,
140
- refused:
141
- overrides.refused ??
142
- "Rehearsal stopped before performing the requested operation.",
143
- suggestions: overrides.suggestions ?? [],
144
- cause: error,
145
- });
146
- };
147
-
148
- export const createRehearsalResult = ({
149
- command,
150
- status,
151
- data,
152
- warnings = [],
153
- startedAt = new Date(),
154
- durationMs = 0,
155
- }) =>
156
- redactDiagnosticValue({
157
- schemaVersion: REHEARSAL_RESULT_VERSION,
158
- rehearsalVersion: REHEARSAL_VERSION,
159
- command,
160
- status,
161
- startedAt: startedAt.toISOString(),
162
- durationMs,
163
- warnings,
164
- data,
165
- });
166
-
167
- export const serializeRehearsalError = (error, { debug = false } = {}) => {
168
- const failure = normalizeRehearsalError(error);
169
- return redactDiagnosticValue({
170
- schemaVersion: REHEARSAL_RESULT_VERSION,
171
- rehearsalVersion: REHEARSAL_VERSION,
172
- status: "error",
173
- error: {
174
- category: failure.category,
175
- code: failure.code,
176
- message: failure.message,
177
- expected: failure.expected,
178
- actual: failure.actual,
179
- context: failure.context,
180
- refused: failure.refused,
181
- suggestions: failure.suggestions,
182
- diagnosticId: failure.diagnosticId,
183
- ...(debug && failure.cause
184
- ? { cause: String(failure.cause?.message ?? failure.cause) }
185
- : {}),
186
- },
187
- });
188
- };
189
-
190
- const formatValue = (value) =>
191
- typeof value === "string" ? value : JSON.stringify(value);
192
-
193
- export const renderHumanError = (error, { debug = false } = {}) => {
194
- const { error: failure } = serializeRehearsalError(error, { debug });
195
- return [
196
- `Rehearsal stopped: ${failure.message}`,
197
- failure.expected ? `Expected: ${formatValue(failure.expected)}` : null,
198
- failure.actual ? `Actual: ${formatValue(failure.actual)}` : null,
199
- failure.context ? `Context: ${formatValue(failure.context)}` : null,
200
- failure.refused ? `Refused: ${failure.refused}` : null,
201
- ...(failure.suggestions ?? []).map((value) => `Try: ${value}`),
202
- `Diagnostic: ${failure.diagnosticId} (${failure.category})`,
203
- debug && failure.cause ? `Cause: ${failure.cause}` : null,
204
- ]
205
- .filter(Boolean)
206
- .join("\n");
207
- };
@@ -1,31 +0,0 @@
1
- /**
2
- * Supported Node.js release lines. Keep this aligned with package.json and the
3
- * verification matrix.
4
- */
5
-
6
- export const REHEARSAL_NODE_MAJORS = Object.freeze([22, 24, 26]);
7
- export const REHEARSAL_NODE_LABEL = "Node.js 22, 24, or 26";
8
- export const REHEARSAL_RECOMMENDED_NODE_MAJOR = 24;
9
-
10
- export const inspectRehearsalNodeRuntime = (
11
- version = process.versions.node,
12
- ) => {
13
- const major = Number.parseInt(String(version).split(".")[0], 10);
14
- return {
15
- version: String(version),
16
- major,
17
- supported: REHEARSAL_NODE_MAJORS.includes(major),
18
- };
19
- };
20
-
21
- export const assertSupportedRehearsalNodeRuntime = (
22
- version = process.versions.node,
23
- ) => {
24
- const runtime = inspectRehearsalNodeRuntime(version);
25
- if (!runtime.supported) {
26
- throw new Error(
27
- `Rehearsal requires ${REHEARSAL_NODE_LABEL}; this shell is using Node.js ${runtime.version}. Switch to a supported release, then rerun setup. With nvm: nvm install --lts && nvm use --lts.`,
28
- );
29
- }
30
- return runtime;
31
- };
@@ -1,162 +0,0 @@
1
- /** Project-wide mutation lock and installed-package integrity guard. */
2
-
3
- import { createHash, randomBytes } from "node:crypto";
4
- import {
5
- lstat,
6
- mkdir,
7
- readFile,
8
- readdir,
9
- realpath,
10
- rename,
11
- rm,
12
- stat,
13
- writeFile,
14
- } from "node:fs/promises";
15
- import { readFileSync, readdirSync, rmSync } from "node:fs";
16
- import { tmpdir } from "node:os";
17
- import { join, relative } from "node:path";
18
-
19
- const LOCK_ROOT = join(tmpdir(), "rehearsal-db-operation-locks");
20
-
21
- const digest = (value) => createHash("sha256").update(value).digest("hex");
22
-
23
- const packageFiles = (root, directory = root) =>
24
- readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
25
- const path = join(directory, entry.name);
26
- if (entry.isDirectory()) return packageFiles(root, path);
27
- if (!entry.isFile()) return [];
28
- return [relative(root, path)];
29
- });
30
-
31
- export const createInstalledPackageFingerprint = ({ packageRoot }) => {
32
- const files = [
33
- "package.json",
34
- ...["src", "scripts/runtime"].flatMap((directory) =>
35
- packageFiles(packageRoot, join(packageRoot, directory)),
36
- ),
37
- ].sort();
38
- const hash = createHash("sha256");
39
- for (const file of files) {
40
- hash.update(file);
41
- hash.update("\0");
42
- hash.update(readFileSync(join(packageRoot, file)));
43
- hash.update("\0");
44
- }
45
- return Object.freeze({ files: files.length, sha256: hash.digest("hex") });
46
- };
47
-
48
- export const assertInstalledPackageFingerprint = ({
49
- packageRoot,
50
- expected,
51
- }) => {
52
- const actual = createInstalledPackageFingerprint({ packageRoot });
53
- if (actual.files !== expected?.files || actual.sha256 !== expected?.sha256) {
54
- throw new Error(
55
- "The installed Rehearsal package changed during this operation. Reinstall the intended version, then start a new command.",
56
- );
57
- }
58
- return actual;
59
- };
60
-
61
- const processIsAlive = (pid) => {
62
- if (!Number.isSafeInteger(pid) || pid < 1) return false;
63
- try {
64
- process.kill(pid, 0);
65
- return true;
66
- } catch (error) {
67
- return error?.code === "EPERM";
68
- }
69
- };
70
-
71
- const assertPrivateLockRoot = async () => {
72
- await mkdir(LOCK_ROOT, { recursive: true, mode: 0o700 });
73
- const details = await stat(LOCK_ROOT);
74
- if (
75
- !details.isDirectory() ||
76
- (details.mode & 0o077) !== 0 ||
77
- (typeof process.getuid === "function" && details.uid !== process.getuid())
78
- ) {
79
- throw new Error("The Rehearsal operation-lock directory is not private.");
80
- }
81
- };
82
-
83
- export const acquireProjectOperation = async ({ projectRoot, command }) => {
84
- await assertPrivateLockRoot();
85
- const canonicalRoot = await realpath(projectRoot);
86
- const projectHash = digest(canonicalRoot);
87
- const lockDirectory = join(LOCK_ROOT, projectHash);
88
- const ownerPath = join(lockDirectory, "owner.json");
89
- const token = randomBytes(16).toString("hex");
90
- const owner = {
91
- formatVersion: 1,
92
- pid: process.pid,
93
- command,
94
- projectHash,
95
- token,
96
- startedAt: new Date().toISOString(),
97
- };
98
-
99
- for (let attempt = 0; attempt < 2; attempt += 1) {
100
- try {
101
- await mkdir(lockDirectory, { mode: 0o700 });
102
- await writeFile(ownerPath, `${JSON.stringify(owner)}\n`, {
103
- mode: 0o600,
104
- flag: "wx",
105
- });
106
- const assertOwnership = (current) => {
107
- if (current.token !== token || current.pid !== process.pid) {
108
- throw new Error(
109
- "The Rehearsal operation lock changed ownership unexpectedly.",
110
- );
111
- }
112
- };
113
- return Object.freeze({
114
- command,
115
- async release() {
116
- const current = JSON.parse(await readFile(ownerPath, "utf8"));
117
- assertOwnership(current);
118
- await rm(lockDirectory, { recursive: true });
119
- },
120
- releaseSync() {
121
- const current = JSON.parse(readFileSync(ownerPath, "utf8"));
122
- assertOwnership(current);
123
- rmSync(lockDirectory, { recursive: true });
124
- },
125
- });
126
- } catch (error) {
127
- if (error?.code !== "EEXIST" || attempt > 0) throw error;
128
- const details = await lstat(lockDirectory);
129
- if (!details.isDirectory() || details.isSymbolicLink()) {
130
- throw new Error("The Rehearsal operation lock is unsafe.");
131
- }
132
- let current;
133
- try {
134
- current = JSON.parse(await readFile(ownerPath, "utf8"));
135
- } catch {
136
- throw new Error(
137
- "Another Rehearsal operation owns this project, or its lock is incomplete.",
138
- );
139
- }
140
- if (processIsAlive(current.pid)) {
141
- throw new Error(
142
- `Another Rehearsal operation is already running for this project (PID ${current.pid}).`,
143
- );
144
- }
145
- const staleDirectory = `${lockDirectory}.stale-${token}`;
146
- try {
147
- await rename(lockDirectory, staleDirectory);
148
- } catch (renameError) {
149
- if (renameError?.code === "ENOENT") continue;
150
- throw renameError;
151
- }
152
- await rm(staleDirectory, { recursive: true });
153
- }
154
- }
155
- throw new Error("Rehearsal could not acquire its project operation lock.");
156
- };
157
-
158
- export const inspectProjectOperationLocks = async () =>
159
- readdir(LOCK_ROOT).catch((error) => {
160
- if (error?.code === "ENOENT") return [];
161
- throw error;
162
- });
@@ -1,68 +0,0 @@
1
- /**
2
- * Purpose: Build minimal child-process environments and validate loopback URLs for
3
- * the project-neutral Rehearsal runtime. Do not run directly; this module is reusable
4
- * script infrastructure.
5
- */
6
-
7
- const SAFE_SYSTEM_ENVIRONMENT_KEYS = Object.freeze([
8
- "CI",
9
- "COLORTERM",
10
- "FORCE_COLOR",
11
- "HOME",
12
- "LANG",
13
- "LANGUAGE",
14
- "LC_ALL",
15
- "LOGNAME",
16
- "NO_COLOR",
17
- "PATH",
18
- "SHELL",
19
- "TERM",
20
- "TMP",
21
- "TMPDIR",
22
- "TEMP",
23
- "USER",
24
- ]);
25
-
26
- const LOOPBACK_HOSTNAMES = new Set(["127.0.0.1", "::1", "localhost"]);
27
-
28
- export const isLoopbackUrl = (value) => {
29
- try {
30
- const parsed = new URL(value);
31
- return (
32
- ["http:", "https:"].includes(parsed.protocol) &&
33
- LOOPBACK_HOSTNAMES.has(parsed.hostname)
34
- );
35
- } catch {
36
- return false;
37
- }
38
- };
39
-
40
- export const assertLoopbackUrl = (label, value) => {
41
- if (!value || !isLoopbackUrl(value)) {
42
- throw new Error(
43
- `${label} must use a loopback URL; received a non-local target.`,
44
- );
45
- }
46
- };
47
-
48
- export const pickEnvironmentVariables = (source, keys) =>
49
- Object.fromEntries(
50
- keys.flatMap((key) => {
51
- const value = source[key];
52
- return value === undefined ? [] : [[key, value]];
53
- }),
54
- );
55
-
56
- export const createCleanProcessEnvironment = ({
57
- inheritedEnvironment = process.env,
58
- overrides = {},
59
- passthroughKeys = [],
60
- } = {}) => ({
61
- ...pickEnvironmentVariables(inheritedEnvironment, [
62
- ...SAFE_SYSTEM_ENVIRONMENT_KEYS,
63
- ...passthroughKeys,
64
- ]),
65
- ...Object.fromEntries(
66
- Object.entries(overrides).filter(([, value]) => value !== undefined),
67
- ),
68
- });