@prisma/cli 3.0.0-dev.82.1 → 3.0.0-dev.85.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 +36 -21
- package/dist/lib/app/preview-build.js +22 -14
- 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 +13 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { getAuthFilePath } from "../lib/auth/client.js";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { CredentialsStore } from "@prisma/credentials-store";
|
|
5
4
|
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { CredentialsStore } from "@prisma/credentials-store";
|
|
6
6
|
//#region src/adapters/token-storage.ts
|
|
7
7
|
function findLatestValidTokens(allCredentials) {
|
|
8
8
|
for (let i = allCredentials.length - 1; i >= 0; i -= 1) {
|
|
@@ -49,7 +49,7 @@ var FileTokenStorage = class {
|
|
|
49
49
|
const all = await this.credentialsStore.getCredentials();
|
|
50
50
|
this.signal?.throwIfAborted();
|
|
51
51
|
return findLatestValidTokens(all);
|
|
52
|
-
} catch (
|
|
52
|
+
} catch (_error) {
|
|
53
53
|
if (this.signal?.aborted) throw this.signal.reason;
|
|
54
54
|
return null;
|
|
55
55
|
}
|
package/dist/cli.js
CHANGED
|
@@ -3,22 +3,22 @@ import { runUpdateDiscoveryWorker } from "./shell/update-check.js";
|
|
|
3
3
|
import { runCli } from "./cli2.js";
|
|
4
4
|
import process from "node:process";
|
|
5
5
|
//#region src/bin.ts
|
|
6
|
-
if (process.env.PRISMA_CLI_RUN_UPDATE_CHECK_WORKER === "1")
|
|
6
|
+
if (process.env.PRISMA_CLI_RUN_UPDATE_CHECK_WORKER === "1") {
|
|
7
|
+
await runUpdateDiscoveryWorker();
|
|
7
8
|
process.exitCode = 0;
|
|
8
|
-
}
|
|
9
|
-
else {
|
|
9
|
+
} else {
|
|
10
10
|
const controller = new AbortController();
|
|
11
11
|
const abortCli = () => {
|
|
12
12
|
if (!controller.signal.aborted) controller.abort(new DOMException("Command canceled", "AbortError"));
|
|
13
13
|
};
|
|
14
14
|
process.once("SIGINT", abortCli);
|
|
15
15
|
process.once("SIGTERM", abortCli);
|
|
16
|
-
|
|
17
|
-
process.exitCode =
|
|
18
|
-
}
|
|
16
|
+
try {
|
|
17
|
+
process.exitCode = await runCli({ signal: controller.signal });
|
|
18
|
+
} finally {
|
|
19
19
|
process.off("SIGINT", abortCli);
|
|
20
20
|
process.off("SIGTERM", abortCli);
|
|
21
|
-
}
|
|
21
|
+
}
|
|
22
22
|
}
|
|
23
23
|
//#endregion
|
|
24
24
|
export {};
|
package/dist/cli2.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { CliError } from "./shell/errors.js";
|
|
2
|
-
import
|
|
3
|
-
import { formatUnexpectedError, writeHumanError, writeJsonError, writeJsonSuccess } from "./shell/output.js";
|
|
2
|
+
import "./shell/prompt.js";
|
|
4
3
|
import { attachCommandDescriptor } from "./shell/command-meta.js";
|
|
5
4
|
import { addCompactGlobalFlags } from "./shell/global-flags.js";
|
|
5
|
+
import { createShellUi } from "./shell/ui.js";
|
|
6
6
|
import { configureRuntimeCommand, createCommandContext } from "./shell/runtime.js";
|
|
7
|
-
import "./shell/
|
|
7
|
+
import { formatUnexpectedError, writeHumanError, writeJsonError, writeJsonSuccess } from "./shell/output.js";
|
|
8
8
|
import { createAppCommand } from "./commands/app/index.js";
|
|
9
9
|
import { createAuthCommand } from "./commands/auth/index.js";
|
|
10
10
|
import { createBranchCommand } from "./commands/branch/index.js";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { CliError, authRequiredError, usageError, workspaceRequiredError } from "../shell/errors.js";
|
|
2
|
-
import {
|
|
2
|
+
import { projectResolutionErrorToCliError, resolveProjectTarget } from "../lib/project/resolution.js";
|
|
3
3
|
import { formatScopeLabel, parseKeyValuePositional, resolveEnvScope } from "../lib/app/env-config.js";
|
|
4
4
|
import { readEnvFileAssignments } from "../lib/app/env-file.js";
|
|
5
|
-
import {
|
|
5
|
+
import { requireComputeAuth } from "../lib/auth/guard.js";
|
|
6
6
|
import { readLocalGitBranch } from "../lib/git/local-branch.js";
|
|
7
7
|
import { requireAuthenticatedAuthState } from "./auth.js";
|
|
8
8
|
import { listRealWorkspaceProjects } from "./project.js";
|
|
@@ -204,12 +204,13 @@ async function requireClientAndProject(context, explicitProject, commandName) {
|
|
|
204
204
|
const authState = await requireAuthenticatedAuthState(context);
|
|
205
205
|
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
206
206
|
if (!client) throw authRequiredError(["prisma-cli auth login"]);
|
|
207
|
-
|
|
207
|
+
const workspace = authState.workspace;
|
|
208
|
+
if (!workspace) throw workspaceRequiredError();
|
|
208
209
|
const targetResult = await resolveProjectTarget({
|
|
209
210
|
context,
|
|
210
|
-
workspace
|
|
211
|
+
workspace,
|
|
211
212
|
explicitProject,
|
|
212
|
-
listProjects: () => listRealWorkspaceProjects(client,
|
|
213
|
+
listProjects: () => listRealWorkspaceProjects(client, workspace, context.runtime.signal),
|
|
213
214
|
commandName
|
|
214
215
|
});
|
|
215
216
|
if (targetResult.isErr()) throw projectResolutionErrorToCliError(targetResult.error);
|
|
@@ -218,7 +219,7 @@ async function requireClientAndProject(context, explicitProject, commandName) {
|
|
|
218
219
|
client,
|
|
219
220
|
projectId: target.project.id,
|
|
220
221
|
verboseContext: {
|
|
221
|
-
workspace
|
|
222
|
+
workspace,
|
|
222
223
|
project: target.project,
|
|
223
224
|
resolution: target.resolution
|
|
224
225
|
}
|
package/dist/controllers/app.js
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
1
|
import { SERVICE_TOKEN_ENV_VAR, getApiBaseUrl } from "../lib/auth/client.js";
|
|
2
2
|
import { FileTokenStorage } from "../adapters/token-storage.js";
|
|
3
3
|
import { CliError, authRequiredError, featureUnavailableError, usageError, workspaceRequiredError } from "../shell/errors.js";
|
|
4
|
+
import { confirmPrompt, selectPrompt, textPrompt } from "../shell/prompt.js";
|
|
4
5
|
import { renderCommandHeader } from "../shell/ui.js";
|
|
5
|
-
import { writeJsonEvent } from "../shell/output.js";
|
|
6
6
|
import { canPrompt } from "../shell/runtime.js";
|
|
7
|
-
import { confirmPrompt, selectPrompt, textPrompt } from "../shell/prompt.js";
|
|
8
|
-
import { requireComputeAuth } from "../lib/auth/guard.js";
|
|
9
|
-
import { readAuthState } from "../lib/auth/auth-ops.js";
|
|
10
|
-
import { envVarNames, parseEnvInputs } from "../lib/app/env-vars.js";
|
|
11
|
-
import { renderDeployOutputRows, renderDeploySettingsPreview } from "../lib/app/deploy-output.js";
|
|
12
|
-
import { readBunPackageEntrypoint, readBunPackageJson } from "../lib/app/bun-project.js";
|
|
13
|
-
import { DEFAULT_LOCAL_DEV_PORT, resolveLocalBuildType, runLocalApp } from "../lib/app/local-dev.js";
|
|
14
|
-
import { formatCommandArgument } from "../shell/command-arguments.js";
|
|
15
7
|
import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, readLocalResolutionPin } from "../lib/project/local-pin.js";
|
|
8
|
+
import { formatCommandArgument } from "../shell/command-arguments.js";
|
|
16
9
|
import { buildProjectSetupNextActions, inferTargetName, projectNotFoundError, projectResolutionErrorToCliError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
|
|
17
10
|
import { bindProjectToDirectory, projectCreateFailedError, projectDirectoryBindingErrorToCliError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
11
|
+
import { maybeSetupBranchDatabase } from "../lib/app/branch-database-deploy.js";
|
|
12
|
+
import { readBunPackageEntrypoint, readBunPackageJson } from "../lib/app/bun-project.js";
|
|
13
|
+
import { renderDeployOutputRows, renderDeploySettingsPreview } from "../lib/app/deploy-output.js";
|
|
14
|
+
import { formatDomainFailureFix } from "../lib/app/domain-guidance.js";
|
|
15
|
+
import { envVarNames, parseEnvInputs } from "../lib/app/env-vars.js";
|
|
16
|
+
import { DEFAULT_LOCAL_DEV_PORT, resolveLocalBuildType, runLocalApp } from "../lib/app/local-dev.js";
|
|
20
17
|
import { resolveOrCreatePreviewBuildSettings } from "../lib/app/preview-build-settings.js";
|
|
21
18
|
import { PREVIEW_BUILD_TYPES, RESOLVED_PREVIEW_BUILD_TYPES, executePreviewBuild } from "../lib/app/preview-build.js";
|
|
22
19
|
import { PREVIEW_DEFAULT_REGION } from "../lib/app/preview-interaction.js";
|
|
23
|
-
import { maybeSetupBranchDatabase } from "../lib/app/branch-database-deploy.js";
|
|
24
20
|
import { createPreviewDeployProgress, createPreviewDeployProgressState, createPreviewPromoteProgress } from "../lib/app/preview-progress.js";
|
|
25
21
|
import { PreviewDomainApiError, createPreviewAppProvider } from "../lib/app/preview-provider.js";
|
|
26
22
|
import { enforceProductionDeployGate } from "../lib/app/production-deploy-gate.js";
|
|
27
|
-
import {
|
|
23
|
+
import { requireComputeAuth } from "../lib/auth/guard.js";
|
|
24
|
+
import { readAuthState } from "../lib/auth/auth-ops.js";
|
|
25
|
+
import { readLocalGitBranch } from "../lib/git/local-branch.js";
|
|
26
|
+
import { promptForProjectSetupChoice } from "../lib/project/interactive-setup.js";
|
|
27
|
+
import { writeJsonEvent } from "../shell/output.js";
|
|
28
28
|
import { createSelectPromptPort } from "./select-prompt-port.js";
|
|
29
29
|
import { requireAuthenticatedAuthState } from "./auth.js";
|
|
30
30
|
import { listRealWorkspaceProjects } from "./project.js";
|
|
31
31
|
import { access, readFile } from "node:fs/promises";
|
|
32
32
|
import path from "node:path";
|
|
33
|
-
import open from "open";
|
|
34
33
|
import { Result, matchError } from "better-result";
|
|
34
|
+
import open from "open";
|
|
35
35
|
//#region src/controllers/app.ts
|
|
36
36
|
const DEPLOY_FRAMEWORKS = [
|
|
37
37
|
"nextjs",
|
|
@@ -910,51 +910,72 @@ async function confirmDomainRemoval(context, target, hostname) {
|
|
|
910
910
|
})) throw usageError("Custom domain removal canceled", "The command was canceled before the domain was detached.", "Rerun the command and confirm removal, or pass --yes.", [`prisma-cli app domain remove ${hostname} --app ${target.app.name} --yes`], "app");
|
|
911
911
|
}
|
|
912
912
|
function domainCommandError(command, error, hostname) {
|
|
913
|
-
if (error instanceof PreviewDomainApiError)
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
913
|
+
if (error instanceof PreviewDomainApiError) return domainApiCommandError(command, error, hostname);
|
|
914
|
+
return new CliError({
|
|
915
|
+
code: "DEPLOY_FAILED",
|
|
916
|
+
domain: "app",
|
|
917
|
+
summary: `Custom domain ${command} failed`,
|
|
918
|
+
why: error instanceof Error ? error.message : String(error),
|
|
919
|
+
fix: "Retry the command, or rerun with --trace for more detailed diagnostics.",
|
|
920
|
+
debug: formatDebugDetails(error),
|
|
921
|
+
exitCode: 1,
|
|
922
|
+
nextSteps: [`prisma-cli app domain show ${hostname}`]
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
function domainApiCommandError(command, error, hostname) {
|
|
926
|
+
if (command === "add") return domainAddCommandError(error, hostname);
|
|
927
|
+
if (error.status === 404) return domainNotFoundError(hostname);
|
|
928
|
+
if (command === "retry" && error.status === 409) return domainRetryNotEligibleError(error, hostname);
|
|
929
|
+
return domainGenericCommandError(command, error, hostname);
|
|
930
|
+
}
|
|
931
|
+
function domainAddCommandError(error, hostname) {
|
|
932
|
+
if ((error.status === 400 || error.status === 422) && isDomainDnsError(error)) return domainDnsNotConfiguredError(hostname, error);
|
|
933
|
+
if (error.status === 400) return new CliError({
|
|
934
|
+
code: "DOMAIN_HOSTNAME_INVALID",
|
|
935
|
+
domain: "app",
|
|
936
|
+
summary: `Invalid custom domain "${hostname}"`,
|
|
937
|
+
why: error.message,
|
|
938
|
+
fix: "Pass a valid hostname like shop.acme.com and make sure DNS can be verified.",
|
|
939
|
+
debug: formatDebugDetails(error),
|
|
940
|
+
exitCode: 2,
|
|
941
|
+
nextSteps: ["prisma-cli app domain add shop.acme.com"]
|
|
942
|
+
});
|
|
943
|
+
if (error.status === 429 || isDomainQuotaError(error)) return new CliError({
|
|
944
|
+
code: "DOMAIN_QUOTA_EXCEEDED",
|
|
945
|
+
domain: "app",
|
|
946
|
+
summary: "Custom domain quota exceeded",
|
|
947
|
+
why: error.message,
|
|
948
|
+
fix: "Remove an existing custom domain before adding another one.",
|
|
949
|
+
debug: formatDebugDetails(error),
|
|
950
|
+
exitCode: 1,
|
|
951
|
+
nextSteps: ["prisma-cli app domain remove <hostname>"]
|
|
952
|
+
});
|
|
953
|
+
if (error.status === 409) return domainAlreadyRegisteredError(hostname, error);
|
|
954
|
+
if (error.status === 422) return new CliError({
|
|
955
|
+
code: "NO_DEPLOYMENTS",
|
|
956
|
+
domain: "app",
|
|
957
|
+
summary: "Custom domain requires a live production deployment",
|
|
958
|
+
why: "The selected production app does not have a promoted version that can receive a custom domain.",
|
|
959
|
+
fix: "Deploy the app to the production branch, then rerun the domain command.",
|
|
960
|
+
debug: formatDebugDetails(error),
|
|
961
|
+
exitCode: 1,
|
|
962
|
+
nextSteps: ["prisma-cli app deploy --branch production", `prisma-cli app domain add ${hostname}`]
|
|
963
|
+
});
|
|
964
|
+
return domainGenericCommandError("add", error, hostname);
|
|
965
|
+
}
|
|
966
|
+
function domainRetryNotEligibleError(error, hostname) {
|
|
967
|
+
return new CliError({
|
|
968
|
+
code: "DOMAIN_RETRY_NOT_ELIGIBLE",
|
|
969
|
+
domain: "app",
|
|
970
|
+
summary: `Custom domain "${hostname}" is not eligible for retry`,
|
|
971
|
+
why: error.message,
|
|
972
|
+
fix: "Wait for the current verification or TLS step to finish, then rerun retry if the domain fails.",
|
|
973
|
+
debug: formatDebugDetails(error),
|
|
974
|
+
exitCode: 1,
|
|
975
|
+
nextSteps: [`prisma-cli app domain show ${hostname}`]
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
function domainGenericCommandError(command, error, hostname) {
|
|
958
979
|
return new CliError({
|
|
959
980
|
code: "DEPLOY_FAILED",
|
|
960
981
|
domain: "app",
|
|
@@ -1356,14 +1377,14 @@ async function requireProviderAndDeployProjectContext(context, explicitProject,
|
|
|
1356
1377
|
};
|
|
1357
1378
|
}
|
|
1358
1379
|
async function resolveProjectContext(context, client, explicitProject, options) {
|
|
1359
|
-
const
|
|
1360
|
-
if (!
|
|
1380
|
+
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
1381
|
+
if (!workspace) throw workspaceRequiredError();
|
|
1361
1382
|
const resolvedResult = await resolveProjectTarget({
|
|
1362
1383
|
context,
|
|
1363
|
-
workspace
|
|
1384
|
+
workspace,
|
|
1364
1385
|
explicitProject,
|
|
1365
1386
|
envProjectId: options?.envProjectId,
|
|
1366
|
-
listProjects: () => listRealWorkspaceProjects(client,
|
|
1387
|
+
listProjects: () => listRealWorkspaceProjects(client, workspace, context.runtime.signal),
|
|
1367
1388
|
commandName: options?.commandName
|
|
1368
1389
|
});
|
|
1369
1390
|
if (resolvedResult.isErr()) throw projectResolutionErrorToCliError(resolvedResult.error);
|
|
@@ -1382,8 +1403,16 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
|
|
|
1382
1403
|
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
1383
1404
|
if (!workspace) throw workspaceRequiredError();
|
|
1384
1405
|
const branch = options.branch ?? await resolveDeployBranch(context, void 0);
|
|
1385
|
-
|
|
1386
|
-
|
|
1406
|
+
return withRemoteDeployBranch(provider, await resolveDeployProjectSetup(context, provider, workspace, await listRealWorkspaceProjects(client, workspace, context.runtime.signal), explicitProject, options), branch, context.runtime.signal);
|
|
1407
|
+
}
|
|
1408
|
+
async function resolveDeployProjectSetup(context, provider, workspace, projects, explicitProject, options) {
|
|
1409
|
+
const selected = await resolveNonInteractiveDeployProjectSetup(context, provider, workspace, projects, explicitProject, options);
|
|
1410
|
+
if (selected) return selected;
|
|
1411
|
+
if (canPrompt(context) && !context.flags.yes) return resolveInteractiveDeployProjectSetup(context, provider, workspace, projects);
|
|
1412
|
+
throw projectSetupRequiredError(projects, await inferTargetName(context.runtime.cwd, context.runtime.signal));
|
|
1413
|
+
}
|
|
1414
|
+
async function resolveNonInteractiveDeployProjectSetup(context, provider, workspace, projects, explicitProject, options) {
|
|
1415
|
+
if (explicitProject) return {
|
|
1387
1416
|
workspace,
|
|
1388
1417
|
project: toProjectSummary(resolveProjectForSetup(explicitProject, projects, workspace)),
|
|
1389
1418
|
resolution: {
|
|
@@ -1392,11 +1421,11 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
|
|
|
1392
1421
|
targetNameSource: "explicit"
|
|
1393
1422
|
},
|
|
1394
1423
|
localPinAction: "linked"
|
|
1395
|
-
}
|
|
1424
|
+
};
|
|
1396
1425
|
if (options.createProjectName) {
|
|
1397
1426
|
const projectName = options.createProjectName.trim();
|
|
1398
1427
|
if (!projectName) throw projectSetupNameRequiredError("app deploy --create-project");
|
|
1399
|
-
return
|
|
1428
|
+
return {
|
|
1400
1429
|
workspace,
|
|
1401
1430
|
project: toProjectSummary(await createProjectForDeploySetup(provider, projectName, workspace, context.runtime.signal)),
|
|
1402
1431
|
resolution: {
|
|
@@ -1405,12 +1434,12 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
|
|
|
1405
1434
|
targetNameSource: "explicit"
|
|
1406
1435
|
},
|
|
1407
1436
|
localPinAction: "created"
|
|
1408
|
-
}
|
|
1437
|
+
};
|
|
1409
1438
|
}
|
|
1410
1439
|
if (options.envProjectId) {
|
|
1411
1440
|
const project = projects.find((candidate) => candidate.id === options.envProjectId);
|
|
1412
1441
|
if (!project) throw projectNotFoundError(options.envProjectId, workspace);
|
|
1413
|
-
return
|
|
1442
|
+
return {
|
|
1414
1443
|
workspace,
|
|
1415
1444
|
project: toProjectSummary(project),
|
|
1416
1445
|
resolution: {
|
|
@@ -1418,14 +1447,14 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
|
|
|
1418
1447
|
targetName: options.envProjectId,
|
|
1419
1448
|
targetNameSource: "env"
|
|
1420
1449
|
}
|
|
1421
|
-
}
|
|
1450
|
+
};
|
|
1422
1451
|
}
|
|
1423
1452
|
const localPin = options.localPin;
|
|
1424
1453
|
if (localPin.kind === "present") {
|
|
1425
1454
|
if (localPin.pin.workspaceId !== workspace.id) throw localResolutionPinStaleError();
|
|
1426
1455
|
const project = projects.find((candidate) => candidate.id === localPin.pin.projectId);
|
|
1427
1456
|
if (!project) throw localResolutionPinStaleError();
|
|
1428
|
-
return
|
|
1457
|
+
return {
|
|
1429
1458
|
workspace,
|
|
1430
1459
|
project: toProjectSummary(project),
|
|
1431
1460
|
resolution: {
|
|
@@ -1433,10 +1462,10 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
|
|
|
1433
1462
|
targetName: project.name,
|
|
1434
1463
|
targetNameSource: "local-pin"
|
|
1435
1464
|
}
|
|
1436
|
-
}
|
|
1465
|
+
};
|
|
1437
1466
|
}
|
|
1438
1467
|
const platformMapping = await resolveDurablePlatformMapping();
|
|
1439
|
-
if (platformMapping && platformMapping.workspace.id === workspace.id) return
|
|
1468
|
+
if (platformMapping && platformMapping.workspace.id === workspace.id) return {
|
|
1440
1469
|
workspace,
|
|
1441
1470
|
project: toProjectSummary(platformMapping),
|
|
1442
1471
|
resolution: {
|
|
@@ -1444,9 +1473,8 @@ async function resolveDeployProjectContext(context, client, provider, explicitPr
|
|
|
1444
1473
|
targetName: platformMapping.name,
|
|
1445
1474
|
targetNameSource: "platform-mapping"
|
|
1446
1475
|
}
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
|
-
throw projectSetupRequiredError(projects, await inferTargetName(context.runtime.cwd, context.runtime.signal));
|
|
1476
|
+
};
|
|
1477
|
+
return null;
|
|
1450
1478
|
}
|
|
1451
1479
|
async function resolveInteractiveDeployProjectSetup(context, provider, workspace, projects) {
|
|
1452
1480
|
const setup = await promptForProjectSetupChoice({
|
|
@@ -1890,41 +1918,7 @@ function deployFailedError(summary, error, nextSteps) {
|
|
|
1890
1918
|
function appDeployFailedError(error, progress) {
|
|
1891
1919
|
const why = error instanceof Error ? error.message : String(error);
|
|
1892
1920
|
const debug = formatDebugDetails(error);
|
|
1893
|
-
if (progress.buildStarted && !progress.buildCompleted)
|
|
1894
|
-
const standaloneOutputFailure = isNextStandaloneOutputFailure(why);
|
|
1895
|
-
const fix = standaloneOutputFailure ? "Add output: \"standalone\" to next.config.*, then rerun deploy." : "Inspect the build output above, fix the error, and redeploy.";
|
|
1896
|
-
const nextSteps = standaloneOutputFailure ? ["Add output: \"standalone\" to next.config.*, then rerun prisma-cli app deploy"] : [];
|
|
1897
|
-
const nextActions = standaloneOutputFailure ? [{
|
|
1898
|
-
kind: "edit-file",
|
|
1899
|
-
journey: "deploy-app",
|
|
1900
|
-
label: "Add Next.js standalone output",
|
|
1901
|
-
reason: "Prisma Compute needs Next.js standalone output to build a deployable server artifact."
|
|
1902
|
-
}, {
|
|
1903
|
-
kind: "run-command",
|
|
1904
|
-
journey: "deploy-app",
|
|
1905
|
-
label: "Rerun deploy",
|
|
1906
|
-
command: "prisma-cli app deploy"
|
|
1907
|
-
}] : [];
|
|
1908
|
-
return new CliError({
|
|
1909
|
-
code: "BUILD_FAILED",
|
|
1910
|
-
domain: "app",
|
|
1911
|
-
summary: "Build failed locally.",
|
|
1912
|
-
why,
|
|
1913
|
-
fix,
|
|
1914
|
-
debug,
|
|
1915
|
-
meta: { phase: "build" },
|
|
1916
|
-
humanLines: [
|
|
1917
|
-
"Build failed locally.",
|
|
1918
|
-
"",
|
|
1919
|
-
`✗ Built ${why}`,
|
|
1920
|
-
"",
|
|
1921
|
-
`Fix: ${fix}`
|
|
1922
|
-
],
|
|
1923
|
-
exitCode: 1,
|
|
1924
|
-
nextSteps,
|
|
1925
|
-
nextActions
|
|
1926
|
-
});
|
|
1927
|
-
}
|
|
1921
|
+
if (progress.buildStarted && !progress.buildCompleted) return appBuildFailedError(why, debug);
|
|
1928
1922
|
if (!progress.buildStarted) return deployFailedError("App deploy failed", error, ["prisma-cli app deploy"]);
|
|
1929
1923
|
const phaseHeadline = progress.containerLive ? "The deployment started, but the app is not ready yet." : "Deploy failed after the build completed.";
|
|
1930
1924
|
const recoveryLines = progress.versionId ? ["See what happened", `prisma-cli app logs --deployment ${progress.versionId}`] : ["Fix", "Retry the command, or rerun with --trace for more detailed diagnostics."];
|
|
@@ -1965,6 +1959,41 @@ function appDeployFailedError(error, progress) {
|
|
|
1965
1959
|
nextSteps: []
|
|
1966
1960
|
});
|
|
1967
1961
|
}
|
|
1962
|
+
function appBuildFailedError(why, debug) {
|
|
1963
|
+
const standaloneOutputFailure = isNextStandaloneOutputFailure(why);
|
|
1964
|
+
const fix = standaloneOutputFailure ? "Add output: \"standalone\" to next.config.*, then rerun deploy." : "Inspect the build output above, fix the error, and redeploy.";
|
|
1965
|
+
const nextSteps = standaloneOutputFailure ? ["Add output: \"standalone\" to next.config.*, then rerun prisma-cli app deploy"] : [];
|
|
1966
|
+
const nextActions = standaloneOutputFailure ? [{
|
|
1967
|
+
kind: "edit-file",
|
|
1968
|
+
journey: "deploy-app",
|
|
1969
|
+
label: "Add Next.js standalone output",
|
|
1970
|
+
reason: "Prisma Compute needs Next.js standalone output to build a deployable server artifact."
|
|
1971
|
+
}, {
|
|
1972
|
+
kind: "run-command",
|
|
1973
|
+
journey: "deploy-app",
|
|
1974
|
+
label: "Rerun deploy",
|
|
1975
|
+
command: "prisma-cli app deploy"
|
|
1976
|
+
}] : [];
|
|
1977
|
+
return new CliError({
|
|
1978
|
+
code: "BUILD_FAILED",
|
|
1979
|
+
domain: "app",
|
|
1980
|
+
summary: "Build failed locally.",
|
|
1981
|
+
why,
|
|
1982
|
+
fix,
|
|
1983
|
+
debug,
|
|
1984
|
+
meta: { phase: "build" },
|
|
1985
|
+
humanLines: [
|
|
1986
|
+
"Build failed locally.",
|
|
1987
|
+
"",
|
|
1988
|
+
`✗ Built ${why}`,
|
|
1989
|
+
"",
|
|
1990
|
+
`Fix: ${fix}`
|
|
1991
|
+
],
|
|
1992
|
+
exitCode: 1,
|
|
1993
|
+
nextSteps,
|
|
1994
|
+
nextActions
|
|
1995
|
+
});
|
|
1996
|
+
}
|
|
1968
1997
|
function localResolutionPinStaleError() {
|
|
1969
1998
|
return new CliError({
|
|
1970
1999
|
code: "LOCAL_STATE_STALE",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CliError, authRequiredError, workspaceRequiredError } from "../shell/errors.js";
|
|
2
|
-
import { requireComputeAuth } from "../lib/auth/guard.js";
|
|
3
2
|
import { projectResolutionErrorToCliError, resolveProjectTarget } from "../lib/project/resolution.js";
|
|
3
|
+
import { requireComputeAuth } from "../lib/auth/guard.js";
|
|
4
4
|
import { createCliUseCaseGateways } from "../use-cases/create-cli-gateways.js";
|
|
5
5
|
import { requireAuthenticatedAuthState } from "./auth.js";
|
|
6
6
|
import { listRealWorkspaceProjects } from "./project.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CliError, authRequiredError, usageError, workspaceRequiredError } from "../shell/errors.js";
|
|
2
|
-
import { requireComputeAuth } from "../lib/auth/guard.js";
|
|
3
2
|
import { projectResolutionErrorToCliError, resolveProjectTarget } from "../lib/project/resolution.js";
|
|
3
|
+
import { requireComputeAuth } from "../lib/auth/guard.js";
|
|
4
4
|
import { requireAuthenticatedAuthState } from "./auth.js";
|
|
5
5
|
import { listFixtureWorkspaceProjects, listRealWorkspaceProjects } from "./project.js";
|
|
6
6
|
import { createManagementDatabaseProvider, normalizeConnection, normalizeDatabase } from "../lib/database/provider.js";
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { CliError, authRequiredError, featureUnavailableError, usageError, workspaceRequiredError } from "../shell/errors.js";
|
|
2
2
|
import { renderSummaryLine } from "../shell/ui.js";
|
|
3
3
|
import { canPrompt } from "../shell/runtime.js";
|
|
4
|
-
import { requireComputeAuth } from "../lib/auth/guard.js";
|
|
5
|
-
import { formatCommandArgument } from "../shell/command-arguments.js";
|
|
6
4
|
import { readLocalResolutionPin } from "../lib/project/local-pin.js";
|
|
5
|
+
import { formatCommandArgument } from "../shell/command-arguments.js";
|
|
7
6
|
import { buildProjectSetupNextActions, inferTargetName, inspectProjectBinding, projectResolutionErrorToCliError, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
|
|
8
7
|
import { bindProjectToDirectory, isValidProjectSetupName, projectCreateFailedError, projectDirectoryBindingErrorToCliError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
|
|
9
|
-
import { promptForProjectSetupChoice } from "../lib/project/interactive-setup.js";
|
|
10
8
|
import { createPreviewAppProvider } from "../lib/app/preview-provider.js";
|
|
9
|
+
import { requireComputeAuth } from "../lib/auth/guard.js";
|
|
10
|
+
import { promptForProjectSetupChoice } from "../lib/project/interactive-setup.js";
|
|
11
11
|
import { createCliUseCaseGateways } from "../use-cases/create-cli-gateways.js";
|
|
12
12
|
import { requireAuthenticatedAuthState } from "./auth.js";
|
|
13
13
|
import { parseGitHubRepositoryUrl, readGitOriginRemote } from "../adapters/git.js";
|
|
14
14
|
import { createProjectUseCases } from "../use-cases/project.js";
|
|
15
|
-
import open from "open";
|
|
16
15
|
import { matchError } from "better-result";
|
|
16
|
+
import open from "open";
|
|
17
17
|
//#region src/controllers/project.ts
|
|
18
18
|
const GITHUB_INSTALL_POLL_INTERVAL_MS = 2e3;
|
|
19
19
|
const GITHUB_INSTALL_POLL_TIMEOUT_MS = 12e4;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { CliError, usageError } from "../../shell/errors.js";
|
|
2
|
+
import { confirmPrompt } from "../../shell/prompt.js";
|
|
2
3
|
import { renderSummaryLine } from "../../shell/ui.js";
|
|
3
4
|
import { canPrompt } from "../../shell/runtime.js";
|
|
4
|
-
import { confirmPrompt } from "../../shell/prompt.js";
|
|
5
5
|
import { formatCommandArgument } from "../../shell/command-arguments.js";
|
|
6
6
|
import "../project/setup.js";
|
|
7
7
|
import { hasBranchDatabaseSignal, inspectBranchDatabaseSignal, runBranchDatabaseSchemaSetup } from "./branch-database.js";
|
|
@@ -9,6 +9,22 @@ import path from "node:path";
|
|
|
9
9
|
//#region src/lib/app/branch-database-deploy.ts
|
|
10
10
|
async function maybeSetupBranchDatabase(context, provider, projectId, branch, options) {
|
|
11
11
|
if (options.db === false) return emptyBranchDatabaseSetupOutcome();
|
|
12
|
+
const preflight = branchDatabasePreflight(branch, options);
|
|
13
|
+
if (preflight) return preflight;
|
|
14
|
+
const envState = await inspectBranchDatabaseEnv(provider, projectId, branch, context.runtime.signal);
|
|
15
|
+
const existingEnvOutcome = existingBranchDatabaseEnvOutcome(context, branch, getTargetDatabaseEnvVarKeys(envState), envState, options.db);
|
|
16
|
+
if (existingEnvOutcome) return existingEnvOutcome;
|
|
17
|
+
const localSignal = await inspectBranchDatabaseSignal(context.runtime.cwd, context.runtime.signal);
|
|
18
|
+
if (localSignal.unsupportedSchema) {
|
|
19
|
+
if (options.db === true) throw unsupportedBranchDatabaseSchemaError(localSignal.unsupportedSchema, branch, context);
|
|
20
|
+
return emptyBranchDatabaseSetupOutcome();
|
|
21
|
+
}
|
|
22
|
+
const promptOutcome = await branchDatabasePromptOutcome(context, branch, localSignal, envState, options.db);
|
|
23
|
+
if (promptOutcome) return promptOutcome;
|
|
24
|
+
if (options.db === true && !canPrompt(context) && !context.flags.yes) throw nonInteractiveDatabaseSetupRequiresYesError(branch);
|
|
25
|
+
return setupBranchDatabase(context, provider, projectId, branch, localSignal, envState);
|
|
26
|
+
}
|
|
27
|
+
function branchDatabasePreflight(branch, options) {
|
|
12
28
|
if (hasProvidedDatabaseEnvVars(options.providedEnvVars)) {
|
|
13
29
|
if (options.db === true) throw usageError("Database setup cannot be combined with provided database env vars", "The deploy command received --db and a DATABASE_URL or DIRECT_URL value from --env.", "Remove the --env database value to let --db create and wire a database, or remove --db to deploy with the provided value.", ["prisma-cli app deploy --db", "prisma-cli app deploy --env DATABASE_URL=postgresql://example"], "app");
|
|
14
30
|
return emptyBranchDatabaseSetupOutcome();
|
|
@@ -17,46 +33,40 @@ async function maybeSetupBranchDatabase(context, provider, projectId, branch, op
|
|
|
17
33
|
if (options.db === true) throw productionDatabaseSetupAfterFirstDeployError();
|
|
18
34
|
return emptyBranchDatabaseSetupOutcome();
|
|
19
35
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
function existingBranchDatabaseEnvOutcome(context, branch, targetEnvVars, envState, requested) {
|
|
39
|
+
if (!hasExistingDatabaseEnvForTarget(branch, envState)) return null;
|
|
40
|
+
const warning = requested === true ? existingDatabaseEnvWarning(branch, targetEnvVars) : null;
|
|
41
|
+
if (warning) emitBranchDatabaseWarning(context, warning);
|
|
42
|
+
return {
|
|
43
|
+
result: requested === true ? {
|
|
44
|
+
status: "skipped",
|
|
45
|
+
reason: existingDatabaseEnvReason(branch),
|
|
46
|
+
envVars: targetEnvVars,
|
|
47
|
+
schema: null
|
|
48
|
+
} : void 0,
|
|
49
|
+
warnings: warning ? [warning] : []
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
async function branchDatabasePromptOutcome(context, branch, localSignal, envState, requested) {
|
|
53
|
+
if (requested === true) return null;
|
|
54
|
+
if (!(hasBranchDatabaseSignal(localSignal) || Boolean(envState.inheritedPreviewDatabaseUrl))) return emptyBranchDatabaseSetupOutcome();
|
|
55
|
+
if (!canPrompt(context) || context.flags.yes) {
|
|
56
|
+
const warning = databasePromptSuppressedWarning(branch);
|
|
57
|
+
emitBranchDatabaseWarning(context, warning);
|
|
25
58
|
return {
|
|
26
|
-
result:
|
|
27
|
-
|
|
28
|
-
reason: existingDatabaseEnvReason(branch),
|
|
29
|
-
envVars: targetEnvVars,
|
|
30
|
-
schema: null
|
|
31
|
-
} : void 0,
|
|
32
|
-
warnings: warning ? [warning] : []
|
|
59
|
+
result: void 0,
|
|
60
|
+
warnings: [warning]
|
|
33
61
|
};
|
|
34
62
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (!hasSignal) return emptyBranchDatabaseSetupOutcome();
|
|
43
|
-
if (!canPrompt(context) || context.flags.yes) {
|
|
44
|
-
const warning = databasePromptSuppressedWarning(branch);
|
|
45
|
-
emitBranchDatabaseWarning(context, warning);
|
|
46
|
-
return {
|
|
47
|
-
result: void 0,
|
|
48
|
-
warnings: [warning]
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
maybeRenderBranchDatabaseSignal(context, branch, localSignal, envState);
|
|
52
|
-
if (!await confirmPrompt({
|
|
53
|
-
input: context.runtime.stdin,
|
|
54
|
-
output: context.output.stderr,
|
|
55
|
-
message: databasePromptMessage(branch),
|
|
56
|
-
initialValue: false
|
|
57
|
-
})) return emptyBranchDatabaseSetupOutcome();
|
|
58
|
-
} else if (!canPrompt(context) && !context.flags.yes) throw nonInteractiveDatabaseSetupRequiresYesError(branch);
|
|
59
|
-
return setupBranchDatabase(context, provider, projectId, branch, localSignal, envState);
|
|
63
|
+
maybeRenderBranchDatabaseSignal(context, branch, localSignal, envState);
|
|
64
|
+
return await confirmPrompt({
|
|
65
|
+
input: context.runtime.stdin,
|
|
66
|
+
output: context.output.stderr,
|
|
67
|
+
message: databasePromptMessage(branch),
|
|
68
|
+
initialValue: false
|
|
69
|
+
}) ? null : emptyBranchDatabaseSetupOutcome();
|
|
60
70
|
}
|
|
61
71
|
async function setupBranchDatabase(context, provider, projectId, branch, signal, envState) {
|
|
62
72
|
emitBranchDatabaseProgress(context, "pending", "Creating database");
|
|
@@ -73,15 +83,16 @@ async function setupBranchDatabase(context, provider, projectId, branch, signal,
|
|
|
73
83
|
let schemaSetup = null;
|
|
74
84
|
const warnings = [];
|
|
75
85
|
let skippedSchemaWarning = null;
|
|
76
|
-
|
|
77
|
-
|
|
86
|
+
const schema = signal.schema;
|
|
87
|
+
if (schema) {
|
|
88
|
+
emitBranchDatabaseProgress(context, "pending", `Applying database schema with ${formatSchemaSetupCommand(schema.command)}`);
|
|
78
89
|
schemaSetup = await runBranchDatabaseSchemaSetup({
|
|
79
90
|
context,
|
|
80
|
-
schema
|
|
91
|
+
schema,
|
|
81
92
|
databaseUrl: database.databaseUrl,
|
|
82
93
|
directUrl: database.directUrl
|
|
83
94
|
}).catch((error) => {
|
|
84
|
-
throw schemaSetupFailedError(error,
|
|
95
|
+
throw schemaSetupFailedError(error, schema, branch, context.runtime.cwd);
|
|
85
96
|
});
|
|
86
97
|
emitBranchDatabaseProgress(context, "success", "Applied database schema");
|
|
87
98
|
} else skippedSchemaWarning = "No supported Prisma schema source was found. Database env vars were created, but schema setup was skipped.";
|
|
@@ -88,29 +88,41 @@ async function runBranchDatabaseSchemaSetup(options) {
|
|
|
88
88
|
async function scanDirectory(cwd, directory, depth, state, signal) {
|
|
89
89
|
signal.throwIfAborted();
|
|
90
90
|
if (depth > MAX_SCAN_DEPTH || state.filesVisited >= MAX_SCAN_FILES) return;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
entries = await readdir(directory, { withFileTypes: true });
|
|
94
|
-
} catch (error) {
|
|
95
|
-
if (error.code === "ENOENT") return;
|
|
96
|
-
throw error;
|
|
97
|
-
}
|
|
91
|
+
const entries = await readDirectoryEntries(directory);
|
|
92
|
+
if (!entries) return;
|
|
98
93
|
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
99
94
|
for (const entry of entries) {
|
|
100
95
|
signal.throwIfAborted();
|
|
101
96
|
if (state.filesVisited >= MAX_SCAN_FILES) return;
|
|
102
|
-
|
|
103
|
-
if (entry.isDirectory()) {
|
|
104
|
-
if (!SKIPPED_DIRECTORIES.has(entry.name)) await scanDirectory(cwd, entryPath, depth + 1, state, signal);
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
107
|
-
if (!entry.isFile()) continue;
|
|
108
|
-
state.filesVisited += 1;
|
|
109
|
-
if (entry.name === "schema.prisma") state.schemaCandidates.push(entryPath);
|
|
110
|
-
if (isPrismaNextConfigFile(entry.name)) state.prismaNextConfigCandidates.push(entryPath);
|
|
111
|
-
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);
|
|
112
98
|
}
|
|
113
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
|
+
}
|
|
114
126
|
async function selectPrismaOrmSchema(cwd, candidates, signal) {
|
|
115
127
|
const sorted = sortByPreferredRelativePath(cwd, candidates, "schema.prisma");
|
|
116
128
|
for (const schemaPath of sorted) {
|
|
@@ -143,12 +155,15 @@ async function selectPrismaOrmSchema(cwd, candidates, signal) {
|
|
|
143
155
|
};
|
|
144
156
|
}
|
|
145
157
|
function selectPrismaNextConfig(cwd, candidates, mode) {
|
|
146
|
-
const matches = candidates.filter(
|
|
147
|
-
const isSupported = candidate.target === "postgresql" || candidate.target === "unknown";
|
|
148
|
-
return mode === "supported" ? isSupported : !isSupported;
|
|
149
|
-
});
|
|
158
|
+
const matches = candidates.filter(mode === "supported" ? isSupportedPrismaNextConfig : isUnsupportedPrismaNextConfig);
|
|
150
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;
|
|
151
160
|
}
|
|
161
|
+
function isSupportedPrismaNextConfig(candidate) {
|
|
162
|
+
return candidate.target === "postgresql" || candidate.target === "unknown";
|
|
163
|
+
}
|
|
164
|
+
function isUnsupportedPrismaNextConfig(candidate) {
|
|
165
|
+
return !isSupportedPrismaNextConfig(candidate);
|
|
166
|
+
}
|
|
152
167
|
function sortByPreferredRelativePath(cwd, candidates, preferredRootFile) {
|
|
153
168
|
return candidates.map((candidate) => ({
|
|
154
169
|
absolute: candidate,
|
|
@@ -401,23 +401,31 @@ async function normalizeArtifactSymlinks(artifactDir, appPath, signal) {
|
|
|
401
401
|
continue;
|
|
402
402
|
}
|
|
403
403
|
if (!entry.isSymbolicLink()) continue;
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
force: true,
|
|
411
|
-
recursive: true
|
|
412
|
-
}));
|
|
413
|
-
await unsupportedFilesystemBoundary(signal, () => cp(resolvedTarget, fullPath, {
|
|
414
|
-
recursive: targetStat.isDirectory(),
|
|
415
|
-
dereference: true
|
|
416
|
-
}));
|
|
417
|
-
if (targetStat.isDirectory()) await walkDirectory(fullPath);
|
|
404
|
+
if (await materializeArtifactSymlink({
|
|
405
|
+
fullPath,
|
|
406
|
+
normalizedArtifactDir,
|
|
407
|
+
normalizedAppPath,
|
|
408
|
+
signal
|
|
409
|
+
}) === "directory") await walkDirectory(fullPath);
|
|
418
410
|
}
|
|
419
411
|
}
|
|
420
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
|
+
}
|
|
421
429
|
function isPathWithin(rootPath, candidatePath) {
|
|
422
430
|
const relativePath = path.relative(rootPath, candidatePath);
|
|
423
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
|
package/dist/shell/ui.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { createColors } from "colorette";
|
|
1
2
|
import stringWidth from "string-width";
|
|
2
3
|
import stripAnsi from "strip-ansi";
|
|
3
4
|
import wrapAnsi from "wrap-ansi";
|
|
4
|
-
import { createColors } from "colorette";
|
|
5
5
|
//#region src/shell/ui.ts
|
|
6
|
+
const URL_CREDENTIALS_PATTERN = /:\/\/[^:@/\s]+:[^@/\s]+@/g;
|
|
6
7
|
const DEFAULT_WIDTH = 80;
|
|
7
8
|
function createShellUi(runtime, flags) {
|
|
8
9
|
const isTTY = Boolean(runtime.stderr.isTTY);
|
|
@@ -73,7 +74,23 @@ function padDisplay(text, width) {
|
|
|
73
74
|
return `${text}${" ".repeat(padding)}`;
|
|
74
75
|
}
|
|
75
76
|
function maskValue(value) {
|
|
76
|
-
return value
|
|
77
|
+
return maskEmailLocalParts(value).replace(URL_CREDENTIALS_PATTERN, "://****:****@");
|
|
78
|
+
}
|
|
79
|
+
function maskEmailLocalParts(value) {
|
|
80
|
+
let masked = "";
|
|
81
|
+
let segmentStart = 0;
|
|
82
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
83
|
+
if (value[index] !== "@") continue;
|
|
84
|
+
let localStart = index;
|
|
85
|
+
while (localStart > segmentStart && isEmailLocalPartChar(value[localStart - 1])) localStart -= 1;
|
|
86
|
+
if (localStart === index) continue;
|
|
87
|
+
masked += `${value.slice(segmentStart, localStart)}****@`;
|
|
88
|
+
segmentStart = index + 1;
|
|
89
|
+
}
|
|
90
|
+
return masked + value.slice(segmentStart);
|
|
91
|
+
}
|
|
92
|
+
function isEmailLocalPartChar(char) {
|
|
93
|
+
return char >= "A" && char <= "Z" || char >= "a" && char <= "z" || char >= "0" && char <= "9" || char === "!" || char === "#" || char === "$" || char === "." || char === "&" || char === "'" || char === "*" || char === "%" || char === "+" || char === "-" || char === "/" || char === "=" || char === "?" || char === "^" || char === "_" || char === "`" || char === "{" || char === "|" || char === "}" || char === "~";
|
|
77
94
|
}
|
|
78
95
|
function resolveColorEnabled(runtime, flags, isTTY) {
|
|
79
96
|
if (flags.color === true) return true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma/cli",
|
|
3
|
-
"version": "3.0.0-dev.
|
|
3
|
+
"version": "3.0.0-dev.85.1",
|
|
4
4
|
"description": "Command-line interface for the Prisma Developer Platform.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -34,6 +34,11 @@
|
|
|
34
34
|
"url": "https://github.com/prisma/prisma-cli/issues"
|
|
35
35
|
},
|
|
36
36
|
"license": "Apache-2.0",
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsdown",
|
|
39
|
+
"prepack": "pnpm run build",
|
|
40
|
+
"test": "vitest run"
|
|
41
|
+
},
|
|
37
42
|
"dependencies": {
|
|
38
43
|
"@clack/prompts": "^1.5.0",
|
|
39
44
|
"@prisma/compute-sdk": "^0.22.0",
|
|
@@ -49,5 +54,12 @@
|
|
|
49
54
|
"string-width": "^8.2.1",
|
|
50
55
|
"strip-ansi": "^7.2.0",
|
|
51
56
|
"wrap-ansi": "^10.0.0"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@types/node": "^22.19.19",
|
|
60
|
+
"tsdown": "^0.21.10",
|
|
61
|
+
"tsx": "^4.22.4",
|
|
62
|
+
"typescript": "^6.0.3",
|
|
63
|
+
"vitest": "^4.1.8"
|
|
52
64
|
}
|
|
53
65
|
}
|