@prisma/cli 3.0.0-dev.81.1 → 3.0.0-dev.83.1
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/dist/adapters/token-storage.js +2 -2
- package/dist/cli.js +7 -7
- package/dist/cli2.js +3 -3
- package/dist/controllers/app-env.js +7 -6
- package/dist/controllers/app.js +140 -111
- package/dist/controllers/branch.js +1 -1
- package/dist/controllers/database.js +1 -1
- package/dist/controllers/project.js +4 -4
- package/dist/lib/app/branch-database-deploy.js +53 -42
- package/dist/lib/app/branch-database.js +90 -29
- package/dist/lib/app/preview-build.js +63 -16
- package/dist/lib/app/preview-provider.js +23 -19
- package/dist/lib/app/production-deploy-gate.js +1 -1
- package/dist/lib/auth/login.js +31 -24
- package/dist/lib/project/resolution.js +1 -1
- package/dist/lib/project/setup.js +3 -2
- package/dist/output/patterns.js +1 -1
- package/dist/presenters/app.js +1 -1
- package/dist/presenters/branch.js +1 -1
- package/dist/presenters/database.js +2 -2
- package/dist/presenters/project.js +1 -1
- package/dist/shell/command-runner.js +37 -27
- package/dist/shell/help.js +30 -20
- package/dist/shell/runtime.js +1 -1
- package/dist/shell/ui.js +19 -2
- package/package.json +1 -1
|
@@ -69,7 +69,8 @@ function hasBranchDatabaseSignal(signal) {
|
|
|
69
69
|
}
|
|
70
70
|
async function runBranchDatabaseSchemaSetup(options) {
|
|
71
71
|
const schemaPath = path.relative(options.context.runtime.cwd, options.schema.path) || defaultSchemaSourcePath(options.schema);
|
|
72
|
-
const
|
|
72
|
+
const prisma = await resolvePrismaInvocation(options.context.runtime.cwd);
|
|
73
|
+
const commands = buildSchemaSetupCommands(options.schema, schemaPath, options.databaseUrl, prisma);
|
|
73
74
|
for (const command of commands) await runPrismaCommand({
|
|
74
75
|
context: options.context,
|
|
75
76
|
...command,
|
|
@@ -87,29 +88,41 @@ async function runBranchDatabaseSchemaSetup(options) {
|
|
|
87
88
|
async function scanDirectory(cwd, directory, depth, state, signal) {
|
|
88
89
|
signal.throwIfAborted();
|
|
89
90
|
if (depth > MAX_SCAN_DEPTH || state.filesVisited >= MAX_SCAN_FILES) return;
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
entries = await readdir(directory, { withFileTypes: true });
|
|
93
|
-
} catch (error) {
|
|
94
|
-
if (error.code === "ENOENT") return;
|
|
95
|
-
throw error;
|
|
96
|
-
}
|
|
91
|
+
const entries = await readDirectoryEntries(directory);
|
|
92
|
+
if (!entries) return;
|
|
97
93
|
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
98
94
|
for (const entry of entries) {
|
|
99
95
|
signal.throwIfAborted();
|
|
100
96
|
if (state.filesVisited >= MAX_SCAN_FILES) return;
|
|
101
|
-
|
|
102
|
-
if (entry.isDirectory()) {
|
|
103
|
-
if (!SKIPPED_DIRECTORIES.has(entry.name)) await scanDirectory(cwd, entryPath, depth + 1, state, signal);
|
|
104
|
-
continue;
|
|
105
|
-
}
|
|
106
|
-
if (!entry.isFile()) continue;
|
|
107
|
-
state.filesVisited += 1;
|
|
108
|
-
if (entry.name === "schema.prisma") state.schemaCandidates.push(entryPath);
|
|
109
|
-
if (isPrismaNextConfigFile(entry.name)) state.prismaNextConfigCandidates.push(entryPath);
|
|
110
|
-
if (state.databaseUrlReferences.length < MAX_DATABASE_URL_REFERENCE_FILES && shouldScanForDatabaseUrl(entry.name) && await fileContainsDatabaseUrl(entryPath, signal)) state.databaseUrlReferences.push(path.relative(cwd, entryPath) || entry.name);
|
|
97
|
+
await scanDirectoryEntry(cwd, directory, entry, depth, state, signal);
|
|
111
98
|
}
|
|
112
99
|
}
|
|
100
|
+
async function readDirectoryEntries(directory) {
|
|
101
|
+
try {
|
|
102
|
+
return await readdir(directory, { withFileTypes: true });
|
|
103
|
+
} catch (error) {
|
|
104
|
+
if (error.code === "ENOENT") return null;
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async function scanDirectoryEntry(cwd, directory, entry, depth, state, signal) {
|
|
109
|
+
const entryPath = path.join(directory, entry.name);
|
|
110
|
+
if (entry.isDirectory()) {
|
|
111
|
+
if (!SKIPPED_DIRECTORIES.has(entry.name)) await scanDirectory(cwd, entryPath, depth + 1, state, signal);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (!entry.isFile()) return;
|
|
115
|
+
state.filesVisited += 1;
|
|
116
|
+
collectBranchDatabaseCandidate(entryPath, entry.name, state);
|
|
117
|
+
if (await shouldRecordDatabaseUrlReference(entryPath, entry.name, state, signal)) state.databaseUrlReferences.push(path.relative(cwd, entryPath) || entry.name);
|
|
118
|
+
}
|
|
119
|
+
function collectBranchDatabaseCandidate(entryPath, entryName, state) {
|
|
120
|
+
if (entryName === "schema.prisma") state.schemaCandidates.push(entryPath);
|
|
121
|
+
if (isPrismaNextConfigFile(entryName)) state.prismaNextConfigCandidates.push(entryPath);
|
|
122
|
+
}
|
|
123
|
+
async function shouldRecordDatabaseUrlReference(entryPath, entryName, state, signal) {
|
|
124
|
+
return state.databaseUrlReferences.length < MAX_DATABASE_URL_REFERENCE_FILES && shouldScanForDatabaseUrl(entryName) && await fileContainsDatabaseUrl(entryPath, signal);
|
|
125
|
+
}
|
|
113
126
|
async function selectPrismaOrmSchema(cwd, candidates, signal) {
|
|
114
127
|
const sorted = sortByPreferredRelativePath(cwd, candidates, "schema.prisma");
|
|
115
128
|
for (const schemaPath of sorted) {
|
|
@@ -142,12 +155,15 @@ async function selectPrismaOrmSchema(cwd, candidates, signal) {
|
|
|
142
155
|
};
|
|
143
156
|
}
|
|
144
157
|
function selectPrismaNextConfig(cwd, candidates, mode) {
|
|
145
|
-
const matches = candidates.filter(
|
|
146
|
-
const isSupported = candidate.target === "postgresql" || candidate.target === "unknown";
|
|
147
|
-
return mode === "supported" ? isSupported : !isSupported;
|
|
148
|
-
});
|
|
158
|
+
const matches = candidates.filter(mode === "supported" ? isSupportedPrismaNextConfig : isUnsupportedPrismaNextConfig);
|
|
149
159
|
return sortByPreferredRelativePath(cwd, matches.map((candidate) => candidate.path), "prisma-next.config.ts").map((candidatePath) => matches.find((candidate) => candidate.path === candidatePath)).find((candidate) => Boolean(candidate)) ?? null;
|
|
150
160
|
}
|
|
161
|
+
function isSupportedPrismaNextConfig(candidate) {
|
|
162
|
+
return candidate.target === "postgresql" || candidate.target === "unknown";
|
|
163
|
+
}
|
|
164
|
+
function isUnsupportedPrismaNextConfig(candidate) {
|
|
165
|
+
return !isSupportedPrismaNextConfig(candidate);
|
|
166
|
+
}
|
|
151
167
|
function sortByPreferredRelativePath(cwd, candidates, preferredRootFile) {
|
|
152
168
|
return candidates.map((candidate) => ({
|
|
153
169
|
absolute: candidate,
|
|
@@ -229,28 +245,73 @@ async function readTextFileIfSmall(filePath, signal) {
|
|
|
229
245
|
signal
|
|
230
246
|
});
|
|
231
247
|
}
|
|
232
|
-
|
|
248
|
+
const FALLBACK_PRISMA_CLI_VERSION = "6.19.3";
|
|
249
|
+
/**
|
|
250
|
+
* Picks how `prisma` CLI commands are invoked for schema setup. Projects
|
|
251
|
+
* with the CLI installed run their own binary (version-exact). Projects
|
|
252
|
+
* without it fall back to a versioned `npx prisma@<x>` pinned to the
|
|
253
|
+
* installed `@prisma/client` — never bare `npx prisma`, which resolves to
|
|
254
|
+
* latest and can be a major version ahead of the project's schema.
|
|
255
|
+
*/
|
|
256
|
+
async function resolvePrismaInvocation(cwd) {
|
|
257
|
+
if (await localPrismaBinExists(cwd)) return {
|
|
258
|
+
argsPrefix: ["--no-install", "prisma"],
|
|
259
|
+
displayPrefix: "npx --no-install prisma"
|
|
260
|
+
};
|
|
261
|
+
const pinned = await readInstalledPrismaClientVersion(cwd) ?? FALLBACK_PRISMA_CLI_VERSION;
|
|
262
|
+
return {
|
|
263
|
+
argsPrefix: ["--yes", `prisma@${pinned}`],
|
|
264
|
+
displayPrefix: `npx prisma@${pinned}`
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
/** npm/pnpm name the local CLI shim `prisma` on POSIX and `prisma.cmd`/`prisma.ps1` on Windows. */
|
|
268
|
+
async function localPrismaBinExists(cwd) {
|
|
269
|
+
const binDir = path.join(cwd, "node_modules", ".bin");
|
|
270
|
+
return (await Promise.all([
|
|
271
|
+
"prisma",
|
|
272
|
+
"prisma.cmd",
|
|
273
|
+
"prisma.ps1"
|
|
274
|
+
].map((name) => fileExists(path.join(binDir, name))))).some(Boolean);
|
|
275
|
+
}
|
|
276
|
+
async function fileExists(filePath) {
|
|
277
|
+
try {
|
|
278
|
+
await access(filePath);
|
|
279
|
+
return true;
|
|
280
|
+
} catch {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
async function readInstalledPrismaClientVersion(cwd) {
|
|
285
|
+
try {
|
|
286
|
+
const raw = await readFile(path.join(cwd, "node_modules", "@prisma", "client", "package.json"), { encoding: "utf8" });
|
|
287
|
+
const parsed = JSON.parse(raw);
|
|
288
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
289
|
+
const version = parsed.version;
|
|
290
|
+
return typeof version === "string" && version.length > 0 ? version : null;
|
|
291
|
+
} catch {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function buildSchemaSetupCommands(schema, schemaPath, databaseUrl, prisma) {
|
|
233
296
|
if (schema.command === "migrate-deploy") return [{
|
|
234
297
|
args: [
|
|
235
|
-
|
|
236
|
-
"prisma",
|
|
298
|
+
...prisma.argsPrefix,
|
|
237
299
|
"migrate",
|
|
238
300
|
"deploy",
|
|
239
301
|
"--schema",
|
|
240
302
|
schemaPath
|
|
241
303
|
],
|
|
242
|
-
displayCommand:
|
|
304
|
+
displayCommand: `${prisma.displayPrefix} migrate deploy`
|
|
243
305
|
}];
|
|
244
306
|
if (schema.command === "db-push") return [{
|
|
245
307
|
args: [
|
|
246
|
-
|
|
247
|
-
"prisma",
|
|
308
|
+
...prisma.argsPrefix,
|
|
248
309
|
"db",
|
|
249
310
|
"push",
|
|
250
311
|
"--schema",
|
|
251
312
|
schemaPath
|
|
252
313
|
],
|
|
253
|
-
displayCommand:
|
|
314
|
+
displayCommand: `${prisma.displayPrefix} db push`
|
|
254
315
|
}];
|
|
255
316
|
return [{
|
|
256
317
|
args: [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readBunPackageJson, resolveBunEntrypoint } from "./bun-project.js";
|
|
2
2
|
import { PRISMA_APP_CONFIG_FILENAME, hasAnyPackageDependency, hasPackageDependency, hasRootFile, joinPosix, nextOutputRootFromStandaloneDirectory, resolvePreviewBuildSettings, runResolvedBuildCommand } from "./preview-build-settings.js";
|
|
3
|
-
import { chmod, copyFile, cp, lstat, mkdir, mkdtemp, readFile, readdir, readlink, rm, stat } from "node:fs/promises";
|
|
3
|
+
import { chmod, copyFile, cp, lstat, mkdir, mkdtemp, readFile, readdir, readlink, rm, stat, writeFile } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import os from "node:os";
|
|
6
6
|
import { AstroBuild, BunBuild, NuxtBuild } from "@prisma/compute-sdk";
|
|
@@ -151,7 +151,7 @@ var PreviewNextjsBuild = class {
|
|
|
151
151
|
});
|
|
152
152
|
await runResolvedBuildCommand(this.#appPath, settings, "Next.js", signal);
|
|
153
153
|
const standaloneDir = path.join(this.#appPath, settings.outputDirectory);
|
|
154
|
-
if (!await directoryExists(standaloneDir, signal))
|
|
154
|
+
if (!await directoryExists(standaloneDir, signal)) return stageNextjsFullTreeFallbackArtifact(this.#appPath, signal);
|
|
155
155
|
const outDir = await unsupportedFilesystemBoundary(signal, () => mkdtemp(path.join(os.tmpdir(), "compute-build-")));
|
|
156
156
|
try {
|
|
157
157
|
const artifactDir = path.join(outDir, "app");
|
|
@@ -187,6 +187,45 @@ var PreviewNextjsBuild = class {
|
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
189
|
};
|
|
190
|
+
const FULL_TREE_NEXT_START_ENTRYPOINT = "prisma-next-start.cjs";
|
|
191
|
+
const FULL_TREE_NEXT_START_SOURCE = [
|
|
192
|
+
"process.chdir(__dirname);",
|
|
193
|
+
"process.env.NODE_ENV = \"production\";",
|
|
194
|
+
"process.argv.push(\"start\", \"-p\", process.env.PORT ?? \"3000\");",
|
|
195
|
+
"require(\"next/dist/bin/next\");",
|
|
196
|
+
""
|
|
197
|
+
].join("\n");
|
|
198
|
+
async function stageNextjsFullTreeFallbackArtifact(appPath, signal) {
|
|
199
|
+
const outDir = await unsupportedFilesystemBoundary(signal, () => mkdtemp(path.join(os.tmpdir(), "compute-build-")));
|
|
200
|
+
try {
|
|
201
|
+
const artifactDir = path.join(outDir, "app");
|
|
202
|
+
await unsupportedFilesystemBoundary(signal, () => cp(appPath, artifactDir, {
|
|
203
|
+
recursive: true,
|
|
204
|
+
verbatimSymlinks: true,
|
|
205
|
+
filter: (source) => !isExcludedFromFullTreeArtifact(path.basename(source))
|
|
206
|
+
}));
|
|
207
|
+
await unsupportedFilesystemBoundary(signal, () => writeFile(path.join(artifactDir, FULL_TREE_NEXT_START_ENTRYPOINT), FULL_TREE_NEXT_START_SOURCE));
|
|
208
|
+
return {
|
|
209
|
+
directory: artifactDir,
|
|
210
|
+
entrypoint: FULL_TREE_NEXT_START_ENTRYPOINT,
|
|
211
|
+
defaultPortMapping: { http: 3e3 },
|
|
212
|
+
cleanup: () => rm(outDir, {
|
|
213
|
+
recursive: true,
|
|
214
|
+
force: true
|
|
215
|
+
})
|
|
216
|
+
};
|
|
217
|
+
} catch (error) {
|
|
218
|
+
await rm(outDir, {
|
|
219
|
+
recursive: true,
|
|
220
|
+
force: true
|
|
221
|
+
});
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/** Excludes VCS internals and dotenv files (local secrets, superseded by the deploy env). */
|
|
226
|
+
function isExcludedFromFullTreeArtifact(basename) {
|
|
227
|
+
return basename === ".git" || basename === ".env" || basename.startsWith(".env.");
|
|
228
|
+
}
|
|
190
229
|
var PreviewTanstackStartBuild = class {
|
|
191
230
|
#appPath;
|
|
192
231
|
#buildSettings;
|
|
@@ -362,23 +401,31 @@ async function normalizeArtifactSymlinks(artifactDir, appPath, signal) {
|
|
|
362
401
|
continue;
|
|
363
402
|
}
|
|
364
403
|
if (!entry.isSymbolicLink()) continue;
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
force: true,
|
|
372
|
-
recursive: true
|
|
373
|
-
}));
|
|
374
|
-
await unsupportedFilesystemBoundary(signal, () => cp(resolvedTarget, fullPath, {
|
|
375
|
-
recursive: targetStat.isDirectory(),
|
|
376
|
-
dereference: true
|
|
377
|
-
}));
|
|
378
|
-
if (targetStat.isDirectory()) await walkDirectory(fullPath);
|
|
404
|
+
if (await materializeArtifactSymlink({
|
|
405
|
+
fullPath,
|
|
406
|
+
normalizedArtifactDir,
|
|
407
|
+
normalizedAppPath,
|
|
408
|
+
signal
|
|
409
|
+
}) === "directory") await walkDirectory(fullPath);
|
|
379
410
|
}
|
|
380
411
|
}
|
|
381
412
|
}
|
|
413
|
+
async function materializeArtifactSymlink(options) {
|
|
414
|
+
const target = await unsupportedFilesystemBoundary(options.signal, () => readlink(options.fullPath));
|
|
415
|
+
const resolvedTarget = path.resolve(path.dirname(options.fullPath), target);
|
|
416
|
+
if (isPathWithin(options.normalizedArtifactDir, resolvedTarget)) return "internal";
|
|
417
|
+
if (!isPathWithin(options.normalizedAppPath, resolvedTarget)) throw new Error(`Build artifact symlink escapes the app directory: ${resolvedTarget}`);
|
|
418
|
+
const targetStat = await unsupportedFilesystemBoundary(options.signal, () => stat(resolvedTarget));
|
|
419
|
+
await unsupportedFilesystemBoundary(options.signal, () => rm(options.fullPath, {
|
|
420
|
+
force: true,
|
|
421
|
+
recursive: true
|
|
422
|
+
}));
|
|
423
|
+
await unsupportedFilesystemBoundary(options.signal, () => cp(resolvedTarget, options.fullPath, {
|
|
424
|
+
recursive: targetStat.isDirectory(),
|
|
425
|
+
dereference: true
|
|
426
|
+
}));
|
|
427
|
+
return targetStat.isDirectory() ? "directory" : "file";
|
|
428
|
+
}
|
|
382
429
|
function isPathWithin(rootPath, candidatePath) {
|
|
383
430
|
const relativePath = path.relative(rootPath, candidatePath);
|
|
384
431
|
return relativePath === "" || !relativePath.startsWith(`..${path.sep}`) && relativePath !== ".." && !path.isAbsolute(relativePath);
|
|
@@ -509,29 +509,33 @@ async function findAppForDeployment(sdk, deploymentId, signal) {
|
|
|
509
509
|
});
|
|
510
510
|
if (servicesResult.isErr()) throw new Error(servicesResult.error.message);
|
|
511
511
|
for (const service of servicesResult.value) {
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
signal
|
|
515
|
-
});
|
|
516
|
-
if (detailResult.isErr()) throw new Error(detailResult.error.message);
|
|
517
|
-
const app = {
|
|
518
|
-
id: detailResult.value.id,
|
|
519
|
-
name: detailResult.value.name,
|
|
520
|
-
region: detailResult.value.region ?? null,
|
|
521
|
-
liveDeploymentId: detailResult.value.latestVersionId ?? null,
|
|
522
|
-
liveUrl: toAbsoluteUrl(detailResult.value.serviceEndpointDomain ?? null)
|
|
523
|
-
};
|
|
524
|
-
if (app.liveDeploymentId === deploymentId) return app;
|
|
525
|
-
const versionsResult = await sdk.listVersions({
|
|
526
|
-
serviceId: service.id,
|
|
527
|
-
signal
|
|
528
|
-
});
|
|
529
|
-
if (versionsResult.isErr()) throw new Error(versionsResult.error.message);
|
|
530
|
-
if (versionsResult.value.some((version) => version.id === deploymentId)) return app;
|
|
512
|
+
const app = await findServiceAppForDeployment(sdk, service.id, deploymentId, signal);
|
|
513
|
+
if (app) return app;
|
|
531
514
|
}
|
|
532
515
|
}
|
|
533
516
|
return null;
|
|
534
517
|
}
|
|
518
|
+
async function findServiceAppForDeployment(sdk, serviceId, deploymentId, signal) {
|
|
519
|
+
const detailResult = await sdk.showService({
|
|
520
|
+
serviceId,
|
|
521
|
+
signal
|
|
522
|
+
});
|
|
523
|
+
if (detailResult.isErr()) throw new Error(detailResult.error.message);
|
|
524
|
+
const app = {
|
|
525
|
+
id: detailResult.value.id,
|
|
526
|
+
name: detailResult.value.name,
|
|
527
|
+
region: detailResult.value.region ?? null,
|
|
528
|
+
liveDeploymentId: detailResult.value.latestVersionId ?? null,
|
|
529
|
+
liveUrl: toAbsoluteUrl(detailResult.value.serviceEndpointDomain ?? null)
|
|
530
|
+
};
|
|
531
|
+
if (app.liveDeploymentId === deploymentId) return app;
|
|
532
|
+
const versionsResult = await sdk.listVersions({
|
|
533
|
+
serviceId,
|
|
534
|
+
signal
|
|
535
|
+
});
|
|
536
|
+
if (versionsResult.isErr()) throw new Error(versionsResult.error.message);
|
|
537
|
+
return versionsResult.value.some((version) => version.id === deploymentId) ? app : null;
|
|
538
|
+
}
|
|
535
539
|
function toAbsoluteUrl(url) {
|
|
536
540
|
if (!url) return null;
|
|
537
541
|
return url.startsWith("https://") || url.startsWith("http://") ? url : `https://${url}`;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CliError } from "../../shell/errors.js";
|
|
2
|
-
import { canPrompt } from "../../shell/runtime.js";
|
|
3
2
|
import { confirmPrompt } from "../../shell/prompt.js";
|
|
3
|
+
import { canPrompt } from "../../shell/runtime.js";
|
|
4
4
|
//#region src/lib/app/production-deploy-gate.ts
|
|
5
5
|
async function enforceProductionDeployGate(context, provider, options) {
|
|
6
6
|
if (options.branchKind !== "production") return { firstProductionDeploy: false };
|
package/dist/lib/auth/login.js
CHANGED
|
@@ -106,35 +106,42 @@ async function consumePastedCallback(options) {
|
|
|
106
106
|
});
|
|
107
107
|
try {
|
|
108
108
|
for (;;) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (error?.name === "AbortError") return;
|
|
114
|
-
throw error;
|
|
115
|
-
}
|
|
116
|
-
const trimmed = answer.trim().replace(/^["']|["']$/g, "");
|
|
117
|
-
let url;
|
|
118
|
-
try {
|
|
119
|
-
if (!trimmed) throw new Error("empty input");
|
|
120
|
-
url = new URL(trimmed);
|
|
121
|
-
} catch {
|
|
122
|
-
options.output.write("That didn't look like a URL. Paste the full localhost callback URL and try again.\n");
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
125
|
-
try {
|
|
126
|
-
await options.complete(url);
|
|
127
|
-
return;
|
|
128
|
-
} catch (error) {
|
|
129
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
130
|
-
options.output.write(`Sign-in didn't complete (${message}). Paste the callback URL to try again.\n`);
|
|
131
|
-
continue;
|
|
132
|
-
}
|
|
109
|
+
const url = await readPastedCallbackUrl(rl, options);
|
|
110
|
+
if (url === null) return;
|
|
111
|
+
if (url === void 0) continue;
|
|
112
|
+
if (await tryCompletePastedCallback(url, options)) return;
|
|
133
113
|
}
|
|
134
114
|
} finally {
|
|
135
115
|
rl.close();
|
|
136
116
|
}
|
|
137
117
|
}
|
|
118
|
+
async function readPastedCallbackUrl(rl, options) {
|
|
119
|
+
let answer;
|
|
120
|
+
try {
|
|
121
|
+
answer = await rl.question("Paste the callback URL here: ", { signal: options.signal });
|
|
122
|
+
} catch (error) {
|
|
123
|
+
if (error?.name === "AbortError") return null;
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
const trimmed = answer.trim().replace(/^["']|["']$/g, "");
|
|
127
|
+
try {
|
|
128
|
+
if (!trimmed) throw new Error("empty input");
|
|
129
|
+
return new URL(trimmed);
|
|
130
|
+
} catch {
|
|
131
|
+
options.output.write("That didn't look like a URL. Paste the full localhost callback URL and try again.\n");
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async function tryCompletePastedCallback(url, options) {
|
|
136
|
+
try {
|
|
137
|
+
await options.complete(url);
|
|
138
|
+
return true;
|
|
139
|
+
} catch (error) {
|
|
140
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
141
|
+
options.output.write(`Sign-in didn't complete (${message}). Paste the callback URL to try again.\n`);
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
138
145
|
var LoginState = class {
|
|
139
146
|
latestVerifier;
|
|
140
147
|
latestState;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CliError } from "../../shell/errors.js";
|
|
2
|
-
import { formatCommandArgument } from "../../shell/command-arguments.js";
|
|
3
2
|
import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, readLocalResolutionPin } from "./local-pin.js";
|
|
3
|
+
import { formatCommandArgument } from "../../shell/command-arguments.js";
|
|
4
4
|
import { readFile } from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { Result, TaggedError, matchError } from "better-result";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CliError, usageError } from "../../shell/errors.js";
|
|
2
|
-
import "../../shell/command-arguments.js";
|
|
3
2
|
import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, ensureLocalResolutionPinGitignore, writeLocalResolutionPin } from "./local-pin.js";
|
|
3
|
+
import "../../shell/command-arguments.js";
|
|
4
4
|
import { projectAmbiguousError, projectNotFoundError } from "./resolution.js";
|
|
5
5
|
import { Result, matchError } from "better-result";
|
|
6
6
|
//#region src/lib/project/setup.ts
|
|
@@ -13,7 +13,8 @@ function validateProjectSetupNameText(value, fallback) {
|
|
|
13
13
|
}
|
|
14
14
|
function resolveProjectForSetup(projectRef, projects, workspace) {
|
|
15
15
|
const matches = projects.filter((project) => project.id === projectRef || project.name === projectRef);
|
|
16
|
-
|
|
16
|
+
const match = matches[0];
|
|
17
|
+
if (matches.length === 1 && match) return match;
|
|
17
18
|
if (matches.length > 1) throw projectAmbiguousError(projectRef, matches);
|
|
18
19
|
throw projectNotFoundError(projectRef, workspace);
|
|
19
20
|
}
|
package/dist/output/patterns.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { maskValue, padDisplay, renderSummaryLine } from "../shell/ui.js";
|
|
2
1
|
import { formatDescriptorLabel } from "../shell/command-meta.js";
|
|
2
|
+
import { maskValue, padDisplay, renderSummaryLine } from "../shell/ui.js";
|
|
3
3
|
import stringWidth from "string-width";
|
|
4
4
|
//#region src/output/patterns.ts
|
|
5
5
|
function renderList(input, ui) {
|
package/dist/presenters/app.js
CHANGED
|
@@ -56,7 +56,7 @@ function serializeAppDeploy(result) {
|
|
|
56
56
|
};
|
|
57
57
|
}
|
|
58
58
|
function renderBranchDatabaseDeploySummary(context, result) {
|
|
59
|
-
if (
|
|
59
|
+
if (result.branchDatabase?.status !== "created") return [];
|
|
60
60
|
return ["", ...renderDeployOutputRows(context.ui, [
|
|
61
61
|
{
|
|
62
62
|
label: "Database",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { formatColumns } from "../shell/ui.js";
|
|
2
1
|
import { formatDescriptorLabel } from "../shell/command-meta.js";
|
|
2
|
+
import { formatColumns } from "../shell/ui.js";
|
|
3
3
|
import { renderResolvedProjectContextBlock } from "./verbose-context.js";
|
|
4
4
|
//#region src/presenters/branch.ts
|
|
5
5
|
function renderBranchList(context, descriptor, result) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { formatColumns, renderSummaryLine } from "../shell/ui.js";
|
|
2
1
|
import { formatDescriptorLabel } from "../shell/command-meta.js";
|
|
2
|
+
import { formatColumns, renderSummaryLine } from "../shell/ui.js";
|
|
3
3
|
import { renderMutate, renderShow, serializeList } from "../output/patterns.js";
|
|
4
4
|
import { renderResolvedProjectContextBlock, stripVerboseContext } from "./verbose-context.js";
|
|
5
5
|
//#region src/presenters/database.ts
|
|
@@ -234,7 +234,7 @@ function renderDatabaseConnectionRemove(context, descriptor, result) {
|
|
|
234
234
|
}, context.ui);
|
|
235
235
|
}
|
|
236
236
|
function serializeDatabaseConnectionRemove(result) {
|
|
237
|
-
return result;
|
|
237
|
+
return { connection: result.connection };
|
|
238
238
|
}
|
|
239
239
|
function formatStatus(database) {
|
|
240
240
|
return database.status ?? (database.isDefault ? "default" : "unknown");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { padDisplay, renderNextSteps, renderSummaryLine, renderVerboseBlock } from "../shell/ui.js";
|
|
2
1
|
import { formatDescriptorLabel } from "../shell/command-meta.js";
|
|
2
|
+
import { padDisplay, renderNextSteps, renderSummaryLine, renderVerboseBlock } from "../shell/ui.js";
|
|
3
3
|
import { formatCommandArgument } from "../shell/command-arguments.js";
|
|
4
4
|
import { renderMutate, renderShow, serializeList } from "../output/patterns.js";
|
|
5
5
|
import { renderResolvedProjectContextBlock } from "./verbose-context.js";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { CliError, authRequiredError, commandCanceledError } from "./errors.js";
|
|
2
|
-
import { cliErrorToJson, writeHumanError, writeHumanLines, writeJsonError, writeJsonEvent, writeJsonSuccess } from "./output.js";
|
|
3
2
|
import { getCommandDescriptor } from "./command-meta.js";
|
|
4
3
|
import { resolveGlobalFlags } from "./global-flags.js";
|
|
5
4
|
import { createCommandContext } from "./runtime.js";
|
|
5
|
+
import { cliErrorToJson, writeHumanError, writeHumanLines, writeJsonError, writeJsonEvent, writeJsonSuccess } from "./output.js";
|
|
6
6
|
import { collectCommandDiagnostics } from "../lib/diagnostics.js";
|
|
7
7
|
import { renderCommandDiagnostics } from "./diagnostics-output.js";
|
|
8
8
|
import { AuthError } from "@prisma/management-api-sdk";
|
|
@@ -18,44 +18,54 @@ function isCancellationError(error) {
|
|
|
18
18
|
return error.name === "AbortError" || error.name === "CancelledError";
|
|
19
19
|
}
|
|
20
20
|
async function runCommand(runtime, commandName, options, handler, presenter) {
|
|
21
|
-
const
|
|
22
|
-
const context = await createCommandContext(runtime, flags);
|
|
21
|
+
const context = await createCommandContext(runtime, resolveGlobalFlags(runtime.argv, options));
|
|
23
22
|
const descriptor = getCommandDescriptor(commandName);
|
|
24
23
|
const startedAt = Date.now();
|
|
25
24
|
try {
|
|
26
|
-
|
|
27
|
-
if (flags.json) {
|
|
28
|
-
writeJsonSuccess(context.output, {
|
|
29
|
-
...success,
|
|
30
|
-
result: presenter.renderJson ? presenter.renderJson(success.result) : success.result
|
|
31
|
-
});
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
const stdout = presenter.renderStdout?.(context, descriptor, success.result) ?? [];
|
|
35
|
-
if (flags.quiet) {
|
|
36
|
-
if (stdout.length > 0) context.output.stdout.write(`${stdout.join("\n")}\n`);
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
const rendered = presenter.renderHuman(context, descriptor, success.result);
|
|
40
|
-
const diagnostics = await renderBestEffortCommandDiagnostics(context, {
|
|
41
|
-
enabled: flags.verbose && rendered.length > 0,
|
|
42
|
-
durationMs: Date.now() - startedAt
|
|
43
|
-
});
|
|
44
|
-
const humanLines = [...rendered, ...diagnostics];
|
|
45
|
-
if (stdout.length > 0 && humanLines.length > 0) humanLines.push("");
|
|
46
|
-
writeHumanLines(context.output, humanLines);
|
|
47
|
-
if (stdout.length > 0) context.output.stdout.write(`${stdout.join("\n")}\n`);
|
|
25
|
+
await writeCommandSuccess(context, descriptor, await handler(context), presenter, Date.now() - startedAt);
|
|
48
26
|
} catch (error) {
|
|
49
27
|
const cliError = toCliError(error, runtime);
|
|
50
28
|
if (cliError) {
|
|
51
|
-
|
|
52
|
-
else writeHumanError(context.output, context.ui, cliError, { trace: flags.trace });
|
|
29
|
+
writeCommandError(context, commandName, cliError);
|
|
53
30
|
process.exitCode = cliError.exitCode;
|
|
54
31
|
return;
|
|
55
32
|
}
|
|
56
33
|
throw error;
|
|
57
34
|
}
|
|
58
35
|
}
|
|
36
|
+
async function writeCommandSuccess(context, descriptor, success, presenter, durationMs) {
|
|
37
|
+
if (context.flags.json) {
|
|
38
|
+
writeJsonSuccess(context.output, {
|
|
39
|
+
...success,
|
|
40
|
+
result: presenter.renderJson ? presenter.renderJson(success.result) : success.result
|
|
41
|
+
});
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const stdout = presenter.renderStdout?.(context, descriptor, success.result) ?? [];
|
|
45
|
+
if (context.flags.quiet) {
|
|
46
|
+
writeStdoutLines(context, stdout);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const rendered = presenter.renderHuman(context, descriptor, success.result);
|
|
50
|
+
const diagnostics = await renderBestEffortCommandDiagnostics(context, {
|
|
51
|
+
enabled: context.flags.verbose && rendered.length > 0,
|
|
52
|
+
durationMs
|
|
53
|
+
});
|
|
54
|
+
const humanLines = [...rendered, ...diagnostics];
|
|
55
|
+
if (stdout.length > 0 && humanLines.length > 0) humanLines.push("");
|
|
56
|
+
writeHumanLines(context.output, humanLines);
|
|
57
|
+
writeStdoutLines(context, stdout);
|
|
58
|
+
}
|
|
59
|
+
function writeCommandError(context, commandName, cliError) {
|
|
60
|
+
if (context.flags.json) {
|
|
61
|
+
writeJsonError(context.output, commandName, cliError);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
writeHumanError(context.output, context.ui, cliError, { trace: context.flags.trace });
|
|
65
|
+
}
|
|
66
|
+
function writeStdoutLines(context, lines) {
|
|
67
|
+
if (lines.length > 0) context.output.stdout.write(`${lines.join("\n")}\n`);
|
|
68
|
+
}
|
|
59
69
|
async function renderBestEffortCommandDiagnostics(context, options) {
|
|
60
70
|
if (!options.enabled) return [];
|
|
61
71
|
try {
|
package/dist/shell/help.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { createShellUi, padDisplay, wrapText } from "./ui.js";
|
|
2
1
|
import { formatDescriptorLabel, getDescriptorForCommand } from "./command-meta.js";
|
|
3
2
|
import { COMPACT_GLOBAL_OPTION_FLAGS, resolveGlobalFlags } from "./global-flags.js";
|
|
3
|
+
import { createShellUi, padDisplay, wrapText } from "./ui.js";
|
|
4
4
|
//#region src/shell/help.ts
|
|
5
5
|
function renderHelp(command, runtime) {
|
|
6
6
|
const descriptor = getDescriptorForCommand(command);
|
|
@@ -10,28 +10,38 @@ function renderHelp(command, runtime) {
|
|
|
10
10
|
const visibleCommands = command.commands.filter((candidate) => candidate.name() !== "help" && !candidate.hidden);
|
|
11
11
|
const visibleOptions = command.options.filter((candidate) => !candidate.hidden);
|
|
12
12
|
if (visibleCommands.length > 0) lines.push(...renderCommandRows(rail, ui, visibleCommands));
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
if (visibleOptions.length > 0) {
|
|
19
|
-
if (visibleCommands.length > 0) lines.push(`${rail}`);
|
|
20
|
-
if (visibleCommands.length > 0 && visibleOptions.every((option) => COMPACT_GLOBAL_OPTION_FLAGS.includes(option.flags))) lines.push(`${rail} Global options:`);
|
|
21
|
-
lines.push(...renderOptionRows(rail, ui, visibleOptions));
|
|
22
|
-
}
|
|
23
|
-
if (descriptor.examples && descriptor.examples.length > 0) {
|
|
24
|
-
lines.push(`${rail}`);
|
|
25
|
-
lines.push(`${rail} Examples:`);
|
|
26
|
-
for (const example of descriptor.examples) lines.push(`${rail} $ ${example}`);
|
|
27
|
-
}
|
|
28
|
-
if (descriptor.docsPath) {
|
|
29
|
-
lines.push(`${rail}`);
|
|
30
|
-
lines.push(`${rail} ${ui.accent(padDisplay("Read more", 16))} ${ui.link(descriptor.docsPath)}`);
|
|
31
|
-
}
|
|
13
|
+
lines.push(...renderLongDescription(rail, ui, descriptor.longDescription));
|
|
14
|
+
lines.push(...renderVisibleOptions(rail, ui, visibleCommands, visibleOptions));
|
|
15
|
+
lines.push(...renderExamples(rail, descriptor.examples));
|
|
16
|
+
lines.push(...renderDocsPath(rail, ui, descriptor.docsPath));
|
|
32
17
|
lines.push("");
|
|
33
18
|
return `${lines.join("\n")}`;
|
|
34
19
|
}
|
|
20
|
+
function renderLongDescription(rail, ui, longDescription) {
|
|
21
|
+
if (!longDescription) return [];
|
|
22
|
+
return [`${rail}`, ...wrapText(longDescription, Math.max(ui.width - 3, 40)).map((line) => `${rail} ${line}`)];
|
|
23
|
+
}
|
|
24
|
+
function renderVisibleOptions(rail, ui, visibleCommands, visibleOptions) {
|
|
25
|
+
if (visibleOptions.length === 0) return [];
|
|
26
|
+
const lines = visibleCommands.length > 0 ? [`${rail}`] : [];
|
|
27
|
+
if (shouldLabelGlobalOptions(visibleCommands, visibleOptions)) lines.push(`${rail} Global options:`);
|
|
28
|
+
return [...lines, ...renderOptionRows(rail, ui, visibleOptions)];
|
|
29
|
+
}
|
|
30
|
+
function shouldLabelGlobalOptions(visibleCommands, visibleOptions) {
|
|
31
|
+
return visibleCommands.length > 0 && visibleOptions.every((option) => COMPACT_GLOBAL_OPTION_FLAGS.includes(option.flags));
|
|
32
|
+
}
|
|
33
|
+
function renderExamples(rail, examples) {
|
|
34
|
+
if (!examples || examples.length === 0) return [];
|
|
35
|
+
return [
|
|
36
|
+
`${rail}`,
|
|
37
|
+
`${rail} Examples:`,
|
|
38
|
+
...examples.map((example) => `${rail} $ ${example}`)
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
function renderDocsPath(rail, ui, docsPath) {
|
|
42
|
+
if (!docsPath) return [];
|
|
43
|
+
return [`${rail}`, `${rail} ${ui.accent(padDisplay("Read more", 16))} ${ui.link(docsPath)}`];
|
|
44
|
+
}
|
|
35
45
|
function renderCommandRows(rail, ui, commands) {
|
|
36
46
|
return renderAlignedRows(rail, ui, commands.map((command) => {
|
|
37
47
|
const descriptor = getDescriptorForCommand(command);
|
package/dist/shell/runtime.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { createShellUi } from "./ui.js";
|
|
2
1
|
import { LocalStateStore } from "../adapters/local-state.js";
|
|
3
2
|
import { MockApi } from "../adapters/mock-api.js";
|
|
3
|
+
import { createShellUi } from "./ui.js";
|
|
4
4
|
import { renderHelp } from "./help.js";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
//#region src/shell/runtime.ts
|