prisma-sharding 0.0.5 → 0.0.7
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.
- package/README.md +237 -59
- package/dist/cli/migrate.js +327 -70
- package/dist/cli/studio.js +668 -59
- package/dist/cli/test.js +169 -59
- package/dist/cli/update.js +340 -75
- package/dist/cli/utils/command.js +232 -0
- package/dist/cli/utils/postgres.js +55 -0
- package/dist/index.d.mts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +414 -90
- package/dist/index.mjs +414 -90
- package/package.json +4 -3
package/dist/cli/update.js
CHANGED
|
@@ -25,109 +25,374 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
25
25
|
|
|
26
26
|
// src/cli/update.ts
|
|
27
27
|
var import_config = require("dotenv/config");
|
|
28
|
+
|
|
29
|
+
// src/utils/env.ts
|
|
30
|
+
var parseBooleanEnv = (name, defaultValue, env = process.env) => {
|
|
31
|
+
const value = env[name]?.trim();
|
|
32
|
+
if (value === void 0 || value === "") {
|
|
33
|
+
return defaultValue;
|
|
34
|
+
}
|
|
35
|
+
return ["1", "true", "yes", "on"].includes(value.toLowerCase());
|
|
36
|
+
};
|
|
37
|
+
var isVerboseEnv = (names, env = process.env) => names.some((name) => parseBooleanEnv(name, false, env));
|
|
38
|
+
|
|
39
|
+
// src/cli/utils/command.ts
|
|
28
40
|
var import_child_process = require("child_process");
|
|
29
41
|
var import_path = __toESM(require("path"));
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
42
|
+
|
|
43
|
+
// src/constants/internal.ts
|
|
44
|
+
var INTERNAL_DEFAULTS = {
|
|
45
|
+
HEALTH_CHECK_TIMEOUT_MS: 5e3,
|
|
46
|
+
CROSS_SHARD_CONCURRENCY: 4,
|
|
47
|
+
CROSS_SHARD_TIMEOUT_MS: 1e4,
|
|
48
|
+
CLI_COMMAND_TIMEOUT_MS: 12e4,
|
|
49
|
+
CLI_FORCE_KILL_GRACE_MS: 5e3,
|
|
50
|
+
CLI_MAX_OUTPUT_LENGTH: 1e6,
|
|
51
|
+
CLI_TEST_TCP_TIMEOUT_MS: 5e3,
|
|
52
|
+
CLI_TEST_COMMAND_TIMEOUT_MS: 15e3,
|
|
53
|
+
STUDIO_BASE_PORT: 51212,
|
|
54
|
+
STUDIO_REUSE_EXISTING: true,
|
|
55
|
+
STUDIO_STRICT_PORT_CHECK: false,
|
|
56
|
+
STUDIO_START_TIMEOUT_MS: 15e3,
|
|
57
|
+
STUDIO_STABILITY_MS: 500,
|
|
58
|
+
STUDIO_SHUTDOWN_TIMEOUT_MS: 5e3
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// src/utils/sanitize.ts
|
|
62
|
+
var escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
63
|
+
var maskDatabaseUrl = (url) => {
|
|
64
|
+
try {
|
|
65
|
+
const parsed = new URL(url);
|
|
66
|
+
if (parsed.password) {
|
|
67
|
+
parsed.password = "***";
|
|
68
|
+
}
|
|
69
|
+
return parsed.toString();
|
|
70
|
+
} catch {
|
|
71
|
+
return url.replace(/:[^:@]+@/, ":***@");
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
var sanitizeDatabaseText = (output, databaseUrls) => {
|
|
75
|
+
let sanitized = output;
|
|
76
|
+
for (const url of databaseUrls) {
|
|
77
|
+
sanitized = sanitized.replace(new RegExp(escapeRegExp(url), "g"), maskDatabaseUrl(url));
|
|
78
|
+
try {
|
|
79
|
+
const password = new URL(url).password;
|
|
80
|
+
if (password.length >= 4) {
|
|
81
|
+
sanitized = sanitized.replace(
|
|
82
|
+
new RegExp(escapeRegExp(decodeURIComponent(password)), "g"),
|
|
83
|
+
"***"
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
} catch {
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return sanitized.replace(
|
|
90
|
+
/(postgres(?:ql)?:\/\/[^:/\s]+:)[^@\s]+(@)/gi,
|
|
91
|
+
"$1***$2"
|
|
92
|
+
);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// src/cli/utils/process.ts
|
|
96
|
+
var isChildProcessRunning = (childProcess) => {
|
|
97
|
+
return childProcess.exitCode === null && childProcess.signalCode === null;
|
|
98
|
+
};
|
|
99
|
+
var terminateChildProcess = (childProcess, options = {}) => {
|
|
100
|
+
if (!isChildProcessRunning(childProcess)) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
const signal = options.signal || "SIGTERM";
|
|
104
|
+
try {
|
|
105
|
+
if (options.processGroup && childProcess.pid && process.platform !== "win32") {
|
|
106
|
+
try {
|
|
107
|
+
process.kill(-childProcess.pid, signal);
|
|
108
|
+
return true;
|
|
109
|
+
} catch {
|
|
110
|
+
}
|
|
37
111
|
}
|
|
112
|
+
return childProcess.kill(signal);
|
|
113
|
+
} catch {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// src/cli/utils/command.ts
|
|
119
|
+
var getNpxCommand = () => process.platform === "win32" ? "npx.cmd" : "npx";
|
|
120
|
+
var OUTPUT_TRUNCATED_MARKER = "\n[output truncated]\n";
|
|
121
|
+
var appendBoundedOutput = (current, chunk, limit) => {
|
|
122
|
+
const combined = current + chunk;
|
|
123
|
+
if (combined.length <= limit) {
|
|
124
|
+
return combined;
|
|
38
125
|
}
|
|
39
|
-
if (
|
|
40
|
-
|
|
126
|
+
if (limit <= OUTPUT_TRUNCATED_MARKER.length) {
|
|
127
|
+
return combined.slice(-limit);
|
|
41
128
|
}
|
|
42
|
-
return
|
|
129
|
+
return OUTPUT_TRUNCATED_MARKER + combined.slice(-(limit - OUTPUT_TRUNCATED_MARKER.length));
|
|
43
130
|
};
|
|
44
|
-
var
|
|
131
|
+
var sanitizeCommandOutput = (output, env = process.env) => {
|
|
132
|
+
const databaseUrls = Object.entries(env).filter(
|
|
133
|
+
([name, value]) => Boolean(value) && (name === "DATABASE_URL" || /^SHARD_\d+_URL$/.test(name))
|
|
134
|
+
).map(([, value]) => value);
|
|
135
|
+
return sanitizeDatabaseText(output, databaseUrls).replace(/(\bpassword\s*[=:]\s*)[^\s,;]+/gi, "$1***");
|
|
136
|
+
};
|
|
137
|
+
var runCommand = (command, args, options = {}) => {
|
|
45
138
|
return new Promise((resolve) => {
|
|
46
|
-
|
|
139
|
+
let stdout = "";
|
|
140
|
+
let stderr = "";
|
|
141
|
+
let settled = false;
|
|
142
|
+
let timedOut = false;
|
|
143
|
+
let forceKillTimeout;
|
|
144
|
+
const env = options.env || process.env;
|
|
145
|
+
const timeoutMs = options.timeoutMs ?? INTERNAL_DEFAULTS.CLI_COMMAND_TIMEOUT_MS;
|
|
146
|
+
const forceKillGraceMs = options.forceKillGraceMs ?? INTERNAL_DEFAULTS.CLI_FORCE_KILL_GRACE_MS;
|
|
147
|
+
const maxOutputLength = options.maxOutputLength ?? INTERNAL_DEFAULTS.CLI_MAX_OUTPUT_LENGTH;
|
|
148
|
+
const processGroup = process.platform !== "win32";
|
|
149
|
+
const child = (0, import_child_process.spawn)(command, args, {
|
|
47
150
|
env,
|
|
48
|
-
cwd: import_path.default.resolve(cwd),
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
let errorOutput = "";
|
|
53
|
-
proc.stdout.on("data", (data) => {
|
|
54
|
-
process.stdout.write(data);
|
|
55
|
-
output += data.toString();
|
|
151
|
+
cwd: import_path.default.resolve(options.cwd || process.cwd()),
|
|
152
|
+
detached: processGroup,
|
|
153
|
+
shell: false,
|
|
154
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
56
155
|
});
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
156
|
+
const settle = (result) => {
|
|
157
|
+
if (!settled) {
|
|
158
|
+
settled = true;
|
|
159
|
+
clearTimeout(timeout);
|
|
160
|
+
if (forceKillTimeout) {
|
|
161
|
+
clearTimeout(forceKillTimeout);
|
|
162
|
+
}
|
|
163
|
+
const sanitizedResult = {
|
|
164
|
+
...result,
|
|
165
|
+
stdout: sanitizeCommandOutput(result.stdout, env),
|
|
166
|
+
stderr: sanitizeCommandOutput(result.stderr, env),
|
|
167
|
+
error: result.error ? sanitizeCommandOutput(result.error, env) : void 0
|
|
168
|
+
};
|
|
169
|
+
if (options.verbose) {
|
|
170
|
+
if (sanitizedResult.stdout) {
|
|
171
|
+
process.stdout.write(sanitizedResult.stdout);
|
|
172
|
+
}
|
|
173
|
+
if (sanitizedResult.stderr) {
|
|
174
|
+
process.stderr.write(sanitizedResult.stderr);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
resolve(sanitizedResult);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
child.stdout?.on("data", (data) => {
|
|
181
|
+
if (!settled) {
|
|
182
|
+
stdout = appendBoundedOutput(stdout, data.toString(), maxOutputLength);
|
|
183
|
+
}
|
|
60
184
|
});
|
|
61
|
-
|
|
62
|
-
if (
|
|
63
|
-
|
|
64
|
-
} else {
|
|
65
|
-
resolve({ success: false, output, error: errorOutput || `Exit code: ${code}` });
|
|
185
|
+
child.stderr?.on("data", (data) => {
|
|
186
|
+
if (!settled) {
|
|
187
|
+
stderr = appendBoundedOutput(stderr, data.toString(), maxOutputLength);
|
|
66
188
|
}
|
|
67
189
|
});
|
|
68
|
-
|
|
69
|
-
|
|
190
|
+
child.once("error", (error) => {
|
|
191
|
+
settle({
|
|
192
|
+
success: false,
|
|
193
|
+
stdout,
|
|
194
|
+
stderr,
|
|
195
|
+
exitCode: null,
|
|
196
|
+
error: timedOut ? `Command timed out after ${timeoutMs}ms` : error.message
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
child.once("close", (exitCode) => {
|
|
200
|
+
settle({
|
|
201
|
+
success: !timedOut && exitCode === 0,
|
|
202
|
+
stdout,
|
|
203
|
+
stderr,
|
|
204
|
+
exitCode,
|
|
205
|
+
error: timedOut ? `Command timed out after ${timeoutMs}ms` : exitCode === 0 ? void 0 : stderr.trim() || stdout.trim() || `Command exited with code ${exitCode}`
|
|
206
|
+
});
|
|
70
207
|
});
|
|
208
|
+
const timeout = setTimeout(() => {
|
|
209
|
+
timedOut = true;
|
|
210
|
+
terminateChildProcess(child, { signal: "SIGTERM", processGroup });
|
|
211
|
+
forceKillTimeout = setTimeout(() => {
|
|
212
|
+
terminateChildProcess(child, { signal: "SIGKILL", processGroup });
|
|
213
|
+
settle({
|
|
214
|
+
success: false,
|
|
215
|
+
stdout,
|
|
216
|
+
stderr,
|
|
217
|
+
exitCode: null,
|
|
218
|
+
error: `Command timed out after ${timeoutMs}ms`
|
|
219
|
+
});
|
|
220
|
+
}, forceKillGraceMs);
|
|
221
|
+
forceKillTimeout.unref?.();
|
|
222
|
+
}, timeoutMs);
|
|
223
|
+
timeout.unref?.();
|
|
71
224
|
});
|
|
72
225
|
};
|
|
73
|
-
var
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
226
|
+
var runPrismaCommand = (args, options = {}) => runCommand(getNpxCommand(), ["prisma", ...args], options);
|
|
227
|
+
|
|
228
|
+
// src/cli/utils/output.ts
|
|
229
|
+
var import_readline = __toESM(require("readline"));
|
|
230
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
231
|
+
var printCliHeader = (icon, title) => {
|
|
232
|
+
console.log(`${icon} ${title}
|
|
233
|
+
`);
|
|
234
|
+
};
|
|
235
|
+
var printCliRow = (icon, label, message) => {
|
|
236
|
+
console.log(`${icon} ${label} ${message}`);
|
|
237
|
+
};
|
|
238
|
+
var printVerboseHint = () => {
|
|
239
|
+
console.log("\nRun with SHARD_CLI_VERBOSE=true for details.");
|
|
240
|
+
};
|
|
241
|
+
var createCliLoader = (label, message, enabled = true) => {
|
|
242
|
+
const animated = enabled && Boolean(process.stdout.isTTY);
|
|
243
|
+
let frameIndex = 0;
|
|
244
|
+
let timer;
|
|
245
|
+
let finished = false;
|
|
246
|
+
const clear = () => {
|
|
247
|
+
if (animated) {
|
|
248
|
+
import_readline.default.clearLine(process.stdout, 0);
|
|
249
|
+
import_readline.default.cursorTo(process.stdout, 0);
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
const render = () => {
|
|
253
|
+
clear();
|
|
254
|
+
process.stdout.write(`${SPINNER_FRAMES[frameIndex]} ${label} ${message}`);
|
|
255
|
+
frameIndex = (frameIndex + 1) % SPINNER_FRAMES.length;
|
|
256
|
+
};
|
|
257
|
+
if (animated) {
|
|
258
|
+
render();
|
|
259
|
+
timer = setInterval(render, 80);
|
|
82
260
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
261
|
+
const finish = (icon, finalMessage) => {
|
|
262
|
+
if (finished) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
finished = true;
|
|
266
|
+
if (timer) {
|
|
267
|
+
clearInterval(timer);
|
|
268
|
+
}
|
|
269
|
+
clear();
|
|
270
|
+
printCliRow(icon, label, finalMessage);
|
|
271
|
+
};
|
|
272
|
+
return {
|
|
273
|
+
succeed: (finalMessage) => finish("\u2705", finalMessage),
|
|
274
|
+
fail: (finalMessage) => finish("\u274C", finalMessage)
|
|
275
|
+
};
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
// src/cli/utils/shards.ts
|
|
279
|
+
var NO_SHARDS_CONFIGURED_MESSAGE = "No shards configured. Set SHARD_COUNT and SHARD_N_URL environment variables.";
|
|
280
|
+
var getShardConfigResult = (env = process.env) => {
|
|
281
|
+
const shards = [];
|
|
282
|
+
const missingShardIds = [];
|
|
283
|
+
const shardCount = parseInt(env.SHARD_COUNT || "0", 10);
|
|
284
|
+
for (let i = 1; i <= shardCount; i++) {
|
|
285
|
+
const url = env[`SHARD_${i}_URL`];
|
|
286
|
+
if (url) {
|
|
287
|
+
shards.push({ id: `shard_${i}`, index: i - 1, url });
|
|
288
|
+
} else {
|
|
289
|
+
missingShardIds.push(`shard_${i}`);
|
|
290
|
+
}
|
|
90
291
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
292
|
+
if (shards.length === 0 && env.DATABASE_URL) {
|
|
293
|
+
shards.push({ id: "shard_1", index: 0, url: env.DATABASE_URL });
|
|
294
|
+
}
|
|
295
|
+
return { shards, missingShardIds };
|
|
296
|
+
};
|
|
297
|
+
var maskShardUrl = maskDatabaseUrl;
|
|
298
|
+
|
|
299
|
+
// src/cli/utils/prisma.ts
|
|
300
|
+
var executeShardCommand = async (shard, commandArgs, verbose, action) => {
|
|
301
|
+
const commandEnv = { ...process.env, DATABASE_URL: shard.url };
|
|
302
|
+
if (verbose) {
|
|
303
|
+
console.log(`
|
|
304
|
+
${action} ${shard.id}...`);
|
|
305
|
+
console.log(`Database: ${maskShardUrl(shard.url)}
|
|
306
|
+
`);
|
|
307
|
+
console.log(
|
|
308
|
+
`Command: ${sanitizeCommandOutput(`prisma ${commandArgs.join(" ")}`, commandEnv)}`
|
|
95
309
|
);
|
|
96
|
-
process.exit(1);
|
|
97
310
|
}
|
|
311
|
+
const result = await runPrismaCommand(commandArgs, {
|
|
312
|
+
env: commandEnv,
|
|
313
|
+
verbose
|
|
314
|
+
});
|
|
315
|
+
if (verbose) {
|
|
316
|
+
console.log(`Exit code: ${result.exitCode ?? "unavailable"}`);
|
|
317
|
+
}
|
|
318
|
+
if (verbose && result.error && !result.stderr.trim() && !result.stdout.trim()) {
|
|
319
|
+
console.error(result.error);
|
|
320
|
+
}
|
|
321
|
+
if (verbose && !result.success) {
|
|
322
|
+
console.error(`Next: verify ${shard.id} connectivity and migration state, then retry.`);
|
|
323
|
+
}
|
|
324
|
+
return result;
|
|
325
|
+
};
|
|
326
|
+
var runShardCommands = async ({
|
|
327
|
+
shards,
|
|
328
|
+
args,
|
|
329
|
+
extraArgs,
|
|
330
|
+
verbose,
|
|
331
|
+
action
|
|
332
|
+
}) => {
|
|
98
333
|
const results = [];
|
|
99
334
|
for (const shard of shards) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
console.error(` \u274C Failed`);
|
|
107
|
-
results.push({ shardId: shard.id, success: false, output, error });
|
|
335
|
+
const loader = createCliLoader(shard.id, action, !verbose);
|
|
336
|
+
const commandArgs = [...args, ...extraArgs];
|
|
337
|
+
const result = await executeShardCommand(shard, commandArgs, verbose, action);
|
|
338
|
+
results.push({ shardId: shard.id, success: result.success });
|
|
339
|
+
if (result.success) {
|
|
340
|
+
loader.succeed("Synced");
|
|
108
341
|
} else {
|
|
109
|
-
|
|
110
|
-
results.push({ shardId: shard.id, success: true, output });
|
|
342
|
+
loader.fail("Failed");
|
|
111
343
|
}
|
|
112
344
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
345
|
+
return results;
|
|
346
|
+
};
|
|
347
|
+
var syncShardSchemas = (shards, extraArgs, verbose) => runShardCommands({
|
|
348
|
+
shards,
|
|
349
|
+
args: ["db", "push"],
|
|
350
|
+
extraArgs,
|
|
351
|
+
verbose,
|
|
352
|
+
action: "Syncing"
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
// src/cli/update.ts
|
|
356
|
+
var updateAll = async () => {
|
|
357
|
+
const verbose = isVerboseEnv(["SHARD_UPDATE_VERBOSE", "SHARD_CLI_VERBOSE"]);
|
|
358
|
+
const { shards, missingShardIds } = getShardConfigResult();
|
|
359
|
+
const extraArgs = process.argv.slice(2);
|
|
360
|
+
printCliHeader("\u{1F504}", "Prisma Sharding Update");
|
|
361
|
+
if (missingShardIds.length > 0) {
|
|
362
|
+
printCliRow("\u274C", "config", `Missing shard URLs: ${missingShardIds.join(", ")}`);
|
|
363
|
+
process.exit(1);
|
|
364
|
+
}
|
|
365
|
+
if (shards.length === 0) {
|
|
366
|
+
printCliRow("\u274C", "config", NO_SHARDS_CONFIGURED_MESSAGE);
|
|
367
|
+
process.exit(1);
|
|
368
|
+
}
|
|
369
|
+
if (verbose) {
|
|
370
|
+
console.log("Generating Prisma Client...\n");
|
|
371
|
+
}
|
|
372
|
+
const generateLoader = createCliLoader("client", "Generating", !verbose);
|
|
373
|
+
const generateResult = await runPrismaCommand(["generate"], { verbose });
|
|
374
|
+
if (verbose && generateResult.error && !generateResult.stderr.trim() && !generateResult.stdout.trim()) {
|
|
375
|
+
console.error(generateResult.error);
|
|
376
|
+
}
|
|
377
|
+
if (!generateResult.success) {
|
|
378
|
+
generateLoader.fail("Generation failed");
|
|
379
|
+
if (!verbose) {
|
|
380
|
+
printVerboseHint();
|
|
381
|
+
}
|
|
382
|
+
process.exit(1);
|
|
383
|
+
}
|
|
384
|
+
generateLoader.succeed("Generated");
|
|
385
|
+
const results = await syncShardSchemas(shards, extraArgs, verbose);
|
|
386
|
+
const successful = results.filter((result) => result.success).length;
|
|
387
|
+
const failed = results.length - successful;
|
|
124
388
|
if (failed > 0) {
|
|
125
|
-
|
|
389
|
+
if (!verbose) {
|
|
390
|
+
printVerboseHint();
|
|
391
|
+
}
|
|
126
392
|
process.exit(1);
|
|
127
393
|
}
|
|
128
|
-
console.log("\n\u2728 All updates completed successfully!");
|
|
129
394
|
};
|
|
130
395
|
updateAll().catch((error) => {
|
|
131
|
-
console.error(
|
|
396
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
132
397
|
process.exit(1);
|
|
133
398
|
});
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/cli/utils/command.ts
|
|
31
|
+
var command_exports = {};
|
|
32
|
+
__export(command_exports, {
|
|
33
|
+
getNpxCommand: () => getNpxCommand,
|
|
34
|
+
runCommand: () => runCommand,
|
|
35
|
+
runPrismaCommand: () => runPrismaCommand,
|
|
36
|
+
sanitizeCommandOutput: () => sanitizeCommandOutput
|
|
37
|
+
});
|
|
38
|
+
module.exports = __toCommonJS(command_exports);
|
|
39
|
+
var import_child_process = require("child_process");
|
|
40
|
+
var import_path = __toESM(require("path"));
|
|
41
|
+
|
|
42
|
+
// src/constants/internal.ts
|
|
43
|
+
var INTERNAL_DEFAULTS = {
|
|
44
|
+
HEALTH_CHECK_TIMEOUT_MS: 5e3,
|
|
45
|
+
CROSS_SHARD_CONCURRENCY: 4,
|
|
46
|
+
CROSS_SHARD_TIMEOUT_MS: 1e4,
|
|
47
|
+
CLI_COMMAND_TIMEOUT_MS: 12e4,
|
|
48
|
+
CLI_FORCE_KILL_GRACE_MS: 5e3,
|
|
49
|
+
CLI_MAX_OUTPUT_LENGTH: 1e6,
|
|
50
|
+
CLI_TEST_TCP_TIMEOUT_MS: 5e3,
|
|
51
|
+
CLI_TEST_COMMAND_TIMEOUT_MS: 15e3,
|
|
52
|
+
STUDIO_BASE_PORT: 51212,
|
|
53
|
+
STUDIO_REUSE_EXISTING: true,
|
|
54
|
+
STUDIO_STRICT_PORT_CHECK: false,
|
|
55
|
+
STUDIO_START_TIMEOUT_MS: 15e3,
|
|
56
|
+
STUDIO_STABILITY_MS: 500,
|
|
57
|
+
STUDIO_SHUTDOWN_TIMEOUT_MS: 5e3
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// src/utils/sanitize.ts
|
|
61
|
+
var escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
62
|
+
var maskDatabaseUrl = (url) => {
|
|
63
|
+
try {
|
|
64
|
+
const parsed = new URL(url);
|
|
65
|
+
if (parsed.password) {
|
|
66
|
+
parsed.password = "***";
|
|
67
|
+
}
|
|
68
|
+
return parsed.toString();
|
|
69
|
+
} catch {
|
|
70
|
+
return url.replace(/:[^:@]+@/, ":***@");
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
var sanitizeDatabaseText = (output, databaseUrls) => {
|
|
74
|
+
let sanitized = output;
|
|
75
|
+
for (const url of databaseUrls) {
|
|
76
|
+
sanitized = sanitized.replace(new RegExp(escapeRegExp(url), "g"), maskDatabaseUrl(url));
|
|
77
|
+
try {
|
|
78
|
+
const password = new URL(url).password;
|
|
79
|
+
if (password.length >= 4) {
|
|
80
|
+
sanitized = sanitized.replace(
|
|
81
|
+
new RegExp(escapeRegExp(decodeURIComponent(password)), "g"),
|
|
82
|
+
"***"
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
} catch {
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return sanitized.replace(
|
|
89
|
+
/(postgres(?:ql)?:\/\/[^:/\s]+:)[^@\s]+(@)/gi,
|
|
90
|
+
"$1***$2"
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// src/cli/utils/process.ts
|
|
95
|
+
var isChildProcessRunning = (childProcess) => {
|
|
96
|
+
return childProcess.exitCode === null && childProcess.signalCode === null;
|
|
97
|
+
};
|
|
98
|
+
var terminateChildProcess = (childProcess, options = {}) => {
|
|
99
|
+
if (!isChildProcessRunning(childProcess)) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
const signal = options.signal || "SIGTERM";
|
|
103
|
+
try {
|
|
104
|
+
if (options.processGroup && childProcess.pid && process.platform !== "win32") {
|
|
105
|
+
try {
|
|
106
|
+
process.kill(-childProcess.pid, signal);
|
|
107
|
+
return true;
|
|
108
|
+
} catch {
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return childProcess.kill(signal);
|
|
112
|
+
} catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// src/cli/utils/command.ts
|
|
118
|
+
var getNpxCommand = () => process.platform === "win32" ? "npx.cmd" : "npx";
|
|
119
|
+
var OUTPUT_TRUNCATED_MARKER = "\n[output truncated]\n";
|
|
120
|
+
var appendBoundedOutput = (current, chunk, limit) => {
|
|
121
|
+
const combined = current + chunk;
|
|
122
|
+
if (combined.length <= limit) {
|
|
123
|
+
return combined;
|
|
124
|
+
}
|
|
125
|
+
if (limit <= OUTPUT_TRUNCATED_MARKER.length) {
|
|
126
|
+
return combined.slice(-limit);
|
|
127
|
+
}
|
|
128
|
+
return OUTPUT_TRUNCATED_MARKER + combined.slice(-(limit - OUTPUT_TRUNCATED_MARKER.length));
|
|
129
|
+
};
|
|
130
|
+
var sanitizeCommandOutput = (output, env = process.env) => {
|
|
131
|
+
const databaseUrls = Object.entries(env).filter(
|
|
132
|
+
([name, value]) => Boolean(value) && (name === "DATABASE_URL" || /^SHARD_\d+_URL$/.test(name))
|
|
133
|
+
).map(([, value]) => value);
|
|
134
|
+
return sanitizeDatabaseText(output, databaseUrls).replace(/(\bpassword\s*[=:]\s*)[^\s,;]+/gi, "$1***");
|
|
135
|
+
};
|
|
136
|
+
var runCommand = (command, args, options = {}) => {
|
|
137
|
+
return new Promise((resolve) => {
|
|
138
|
+
let stdout = "";
|
|
139
|
+
let stderr = "";
|
|
140
|
+
let settled = false;
|
|
141
|
+
let timedOut = false;
|
|
142
|
+
let forceKillTimeout;
|
|
143
|
+
const env = options.env || process.env;
|
|
144
|
+
const timeoutMs = options.timeoutMs ?? INTERNAL_DEFAULTS.CLI_COMMAND_TIMEOUT_MS;
|
|
145
|
+
const forceKillGraceMs = options.forceKillGraceMs ?? INTERNAL_DEFAULTS.CLI_FORCE_KILL_GRACE_MS;
|
|
146
|
+
const maxOutputLength = options.maxOutputLength ?? INTERNAL_DEFAULTS.CLI_MAX_OUTPUT_LENGTH;
|
|
147
|
+
const processGroup = process.platform !== "win32";
|
|
148
|
+
const child = (0, import_child_process.spawn)(command, args, {
|
|
149
|
+
env,
|
|
150
|
+
cwd: import_path.default.resolve(options.cwd || process.cwd()),
|
|
151
|
+
detached: processGroup,
|
|
152
|
+
shell: false,
|
|
153
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
154
|
+
});
|
|
155
|
+
const settle = (result) => {
|
|
156
|
+
if (!settled) {
|
|
157
|
+
settled = true;
|
|
158
|
+
clearTimeout(timeout);
|
|
159
|
+
if (forceKillTimeout) {
|
|
160
|
+
clearTimeout(forceKillTimeout);
|
|
161
|
+
}
|
|
162
|
+
const sanitizedResult = {
|
|
163
|
+
...result,
|
|
164
|
+
stdout: sanitizeCommandOutput(result.stdout, env),
|
|
165
|
+
stderr: sanitizeCommandOutput(result.stderr, env),
|
|
166
|
+
error: result.error ? sanitizeCommandOutput(result.error, env) : void 0
|
|
167
|
+
};
|
|
168
|
+
if (options.verbose) {
|
|
169
|
+
if (sanitizedResult.stdout) {
|
|
170
|
+
process.stdout.write(sanitizedResult.stdout);
|
|
171
|
+
}
|
|
172
|
+
if (sanitizedResult.stderr) {
|
|
173
|
+
process.stderr.write(sanitizedResult.stderr);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
resolve(sanitizedResult);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
child.stdout?.on("data", (data) => {
|
|
180
|
+
if (!settled) {
|
|
181
|
+
stdout = appendBoundedOutput(stdout, data.toString(), maxOutputLength);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
child.stderr?.on("data", (data) => {
|
|
185
|
+
if (!settled) {
|
|
186
|
+
stderr = appendBoundedOutput(stderr, data.toString(), maxOutputLength);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
child.once("error", (error) => {
|
|
190
|
+
settle({
|
|
191
|
+
success: false,
|
|
192
|
+
stdout,
|
|
193
|
+
stderr,
|
|
194
|
+
exitCode: null,
|
|
195
|
+
error: timedOut ? `Command timed out after ${timeoutMs}ms` : error.message
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
child.once("close", (exitCode) => {
|
|
199
|
+
settle({
|
|
200
|
+
success: !timedOut && exitCode === 0,
|
|
201
|
+
stdout,
|
|
202
|
+
stderr,
|
|
203
|
+
exitCode,
|
|
204
|
+
error: timedOut ? `Command timed out after ${timeoutMs}ms` : exitCode === 0 ? void 0 : stderr.trim() || stdout.trim() || `Command exited with code ${exitCode}`
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
const timeout = setTimeout(() => {
|
|
208
|
+
timedOut = true;
|
|
209
|
+
terminateChildProcess(child, { signal: "SIGTERM", processGroup });
|
|
210
|
+
forceKillTimeout = setTimeout(() => {
|
|
211
|
+
terminateChildProcess(child, { signal: "SIGKILL", processGroup });
|
|
212
|
+
settle({
|
|
213
|
+
success: false,
|
|
214
|
+
stdout,
|
|
215
|
+
stderr,
|
|
216
|
+
exitCode: null,
|
|
217
|
+
error: `Command timed out after ${timeoutMs}ms`
|
|
218
|
+
});
|
|
219
|
+
}, forceKillGraceMs);
|
|
220
|
+
forceKillTimeout.unref?.();
|
|
221
|
+
}, timeoutMs);
|
|
222
|
+
timeout.unref?.();
|
|
223
|
+
});
|
|
224
|
+
};
|
|
225
|
+
var runPrismaCommand = (args, options = {}) => runCommand(getNpxCommand(), ["prisma", ...args], options);
|
|
226
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
227
|
+
0 && (module.exports = {
|
|
228
|
+
getNpxCommand,
|
|
229
|
+
runCommand,
|
|
230
|
+
runPrismaCommand,
|
|
231
|
+
sanitizeCommandOutput
|
|
232
|
+
});
|