@prisma/cli 3.0.0-beta.3 → 3.0.0-beta.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -15
- package/dist/adapters/local-state.js +15 -4
- package/dist/adapters/mock-api.js +244 -0
- package/dist/adapters/token-storage.js +335 -34
- package/dist/cli.js +7 -7
- package/dist/cli2.js +24 -5
- package/dist/commands/agent/index.js +60 -0
- package/dist/commands/app/index.js +91 -61
- package/dist/commands/auth/index.js +55 -2
- package/dist/commands/branch/index.js +2 -27
- package/dist/commands/bucket/index.js +123 -0
- package/dist/commands/build/index.js +29 -0
- package/dist/commands/database/index.js +249 -0
- package/dist/commands/env.js +8 -4
- package/dist/commands/feedback/index.js +20 -0
- package/dist/commands/git/index.js +1 -1
- package/dist/commands/init/index.js +33 -0
- package/dist/commands/project/index.js +54 -5
- package/dist/controllers/agent-setup.js +52 -0
- package/dist/controllers/agent.js +228 -0
- package/dist/controllers/app-env-api.js +55 -0
- package/dist/controllers/app-env-file.js +181 -0
- package/dist/controllers/app-env.js +227 -104
- package/dist/controllers/app.js +746 -306
- package/dist/controllers/auth.js +247 -3
- package/dist/controllers/branch.js +78 -48
- package/dist/controllers/bucket.js +278 -0
- package/dist/controllers/build.js +88 -0
- package/dist/controllers/database.js +567 -0
- package/dist/controllers/feedback.js +86 -0
- package/dist/controllers/init.js +753 -0
- package/dist/controllers/project.js +377 -22
- package/dist/controllers/select-prompt-port.js +1 -0
- package/dist/lib/agent/cli-command.js +20 -0
- package/dist/lib/agent/constants.js +12 -0
- package/dist/lib/agent/package-manager.js +99 -0
- package/dist/lib/agent/setup-status.js +83 -0
- package/dist/lib/app/{preview-provider.js → app-provider.js} +137 -88
- package/dist/lib/app/branch-database-api.js +102 -0
- package/dist/lib/app/branch-database-deploy.js +326 -0
- package/dist/lib/app/branch-database.js +216 -0
- package/dist/lib/app/build-settings.js +93 -0
- package/dist/lib/app/build.js +83 -0
- package/dist/lib/app/bun-project.js +3 -4
- package/dist/lib/app/compute-config.js +145 -0
- package/dist/lib/app/deploy-plan.js +59 -0
- package/dist/lib/app/{preview-progress.js → deploy-progress.js} +12 -12
- package/dist/lib/app/env-config.js +1 -1
- package/dist/lib/app/env-file.js +82 -0
- package/dist/lib/app/env-vars.js +28 -2
- package/dist/lib/app/local-dev.js +3 -60
- package/dist/lib/app/production-deploy-gate.js +162 -0
- package/dist/lib/app/read-branch.js +30 -0
- package/dist/lib/auth/auth-ops.js +10 -4
- package/dist/lib/auth/guard.js +4 -1
- package/dist/lib/auth/login.js +33 -26
- package/dist/lib/auth/recipient.js +42 -0
- package/dist/lib/bucket/provider.js +139 -0
- package/dist/lib/database/provider.js +378 -0
- package/dist/lib/diagnostics.js +15 -0
- package/dist/lib/fs/home-path.js +24 -0
- package/dist/lib/git/local-branch.js +53 -0
- package/dist/lib/git/local-status.js +57 -0
- package/dist/lib/project/interactive-setup.js +5 -4
- package/dist/lib/project/local-pin.js +171 -41
- package/dist/lib/project/provider.js +92 -0
- package/dist/lib/project/resolution.js +199 -48
- package/dist/lib/project/setup.js +67 -20
- package/dist/output/patterns.js +1 -1
- package/dist/presenters/agent.js +74 -0
- package/dist/presenters/app-env.js +149 -14
- package/dist/presenters/app.js +208 -27
- package/dist/presenters/auth.js +99 -2
- package/dist/presenters/branch.js +37 -102
- package/dist/presenters/bucket.js +174 -0
- package/dist/presenters/database.js +448 -0
- package/dist/presenters/feedback.js +26 -0
- package/dist/presenters/init.js +30 -0
- package/dist/presenters/project.js +139 -27
- package/dist/presenters/verbose-context.js +64 -0
- package/dist/shell/cli-command.js +12 -0
- package/dist/shell/command-arguments.js +7 -1
- package/dist/shell/command-meta.js +458 -17
- package/dist/shell/command-runner.js +58 -18
- package/dist/shell/diagnostics-output.js +57 -0
- package/dist/shell/errors.js +56 -1
- package/dist/shell/help.js +31 -20
- package/dist/shell/output.js +72 -1
- package/dist/shell/prompt.js +12 -5
- package/dist/shell/runtime.js +8 -4
- package/dist/shell/ui.js +42 -3
- package/dist/shell/update-check.js +2 -2
- package/dist/use-cases/auth.js +68 -1
- package/dist/use-cases/branch.js +20 -68
- package/dist/use-cases/create-cli-gateways.js +2 -17
- package/dist/use-cases/project.js +2 -1
- package/package.json +21 -4
- package/dist/lib/app/preview-build.js +0 -312
- package/dist/lib/app/preview-interaction.js +0 -5
|
@@ -1,62 +1,192 @@
|
|
|
1
|
-
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
1
|
import path from "node:path";
|
|
2
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
3
|
+
import { Result, TaggedError, UnhandledException } from "better-result";
|
|
3
4
|
//#region src/lib/project/local-pin.ts
|
|
4
5
|
const LOCAL_RESOLUTION_PIN_RELATIVE_PATH = ".prisma/local.json";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
var LocalResolutionPinInvalidJsonError = class extends TaggedError("LocalResolutionPinInvalidJsonError")() {
|
|
7
|
+
constructor(cause) {
|
|
8
|
+
super({
|
|
9
|
+
message: `${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} contains invalid JSON.`,
|
|
10
|
+
cause,
|
|
11
|
+
pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
var LocalResolutionPinInvalidShapeError = class extends TaggedError("LocalResolutionPinInvalidShapeError")() {
|
|
16
|
+
constructor() {
|
|
17
|
+
super({
|
|
18
|
+
message: `${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} must contain workspaceId and projectId string fields only.`,
|
|
19
|
+
pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
var LocalResolutionPinReadAbortedError = class extends TaggedError("LocalResolutionPinReadAbortedError")() {
|
|
24
|
+
constructor(cause) {
|
|
25
|
+
super({
|
|
26
|
+
message: `Reading ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} was aborted.`,
|
|
27
|
+
cause,
|
|
28
|
+
pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
var LocalResolutionPinSerializationError = class extends TaggedError("LocalResolutionPinSerializationError")() {
|
|
33
|
+
constructor(cause) {
|
|
34
|
+
super({
|
|
35
|
+
message: `Could not serialize ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH}.`,
|
|
36
|
+
cause,
|
|
37
|
+
pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
var LocalResolutionPinWriteAbortedError = class extends TaggedError("LocalResolutionPinWriteAbortedError")() {
|
|
42
|
+
constructor(cause) {
|
|
43
|
+
super({
|
|
44
|
+
message: `Writing ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} was aborted.`,
|
|
45
|
+
cause,
|
|
46
|
+
pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
var LocalResolutionPinWriteFailedError = class extends TaggedError("LocalResolutionPinWriteFailedError")() {
|
|
51
|
+
constructor(operation, cause) {
|
|
52
|
+
super({
|
|
53
|
+
message: `Could not write ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH}.`,
|
|
54
|
+
cause,
|
|
55
|
+
operation,
|
|
56
|
+
pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
var LocalResolutionPinGitignoreUpdateAbortedError = class extends TaggedError("LocalResolutionPinGitignoreUpdateAbortedError")() {
|
|
61
|
+
constructor(cause) {
|
|
62
|
+
super({
|
|
63
|
+
message: "Updating .gitignore for the local Project binding was aborted.",
|
|
64
|
+
cause,
|
|
65
|
+
gitignorePath: ".gitignore"
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
var LocalResolutionPinGitignoreUpdateFailedError = class extends TaggedError("LocalResolutionPinGitignoreUpdateFailedError")() {
|
|
70
|
+
constructor(operation, cause) {
|
|
71
|
+
super({
|
|
72
|
+
message: "Could not update .gitignore for the local Project binding.",
|
|
73
|
+
cause,
|
|
74
|
+
operation,
|
|
75
|
+
gitignorePath: ".gitignore"
|
|
11
76
|
});
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
async function readLocalResolutionPin(cwd, signal) {
|
|
80
|
+
return Result.gen(async function* () {
|
|
81
|
+
yield* ensureLocalResolutionPinReadNotAborted(signal);
|
|
82
|
+
const file = yield* Result.await(readLocalResolutionPinFile(cwd, signal));
|
|
83
|
+
if (file.kind === "missing") return Result.ok({ kind: "missing" });
|
|
84
|
+
const parsed = yield* parseLocalResolutionPin(file.raw);
|
|
85
|
+
if (!isLocalResolutionPin(parsed)) return Result.err(new LocalResolutionPinInvalidShapeError());
|
|
86
|
+
return Result.ok({
|
|
15
87
|
kind: "present",
|
|
16
88
|
pin: parsed
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
function ensureLocalResolutionPinReadNotAborted(signal) {
|
|
93
|
+
return Result.try({
|
|
94
|
+
try: () => signal?.throwIfAborted(),
|
|
95
|
+
catch: (cause) => new LocalResolutionPinReadAbortedError(cause)
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
async function readLocalResolutionPinFile(cwd, signal) {
|
|
99
|
+
const readResult = await Result.tryPromise({
|
|
100
|
+
try: () => readFile(path.join(cwd, LOCAL_RESOLUTION_PIN_RELATIVE_PATH), {
|
|
101
|
+
encoding: "utf8",
|
|
102
|
+
signal
|
|
103
|
+
}),
|
|
104
|
+
catch: (cause) => signal?.aborted ? new LocalResolutionPinReadAbortedError(cause) : new UnhandledException({ cause })
|
|
105
|
+
});
|
|
106
|
+
if (readResult.isErr()) {
|
|
107
|
+
if (readResult.error instanceof UnhandledException && readResult.error.cause.code === "ENOENT") return Result.ok({ kind: "missing" });
|
|
108
|
+
return Result.err(readResult.error);
|
|
22
109
|
}
|
|
110
|
+
return Result.ok({
|
|
111
|
+
kind: "present",
|
|
112
|
+
raw: readResult.value
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
function parseLocalResolutionPin(raw) {
|
|
116
|
+
return Result.try({
|
|
117
|
+
try: () => JSON.parse(raw),
|
|
118
|
+
catch: (cause) => cause instanceof SyntaxError ? new LocalResolutionPinInvalidJsonError(cause) : new UnhandledException({ cause })
|
|
119
|
+
});
|
|
23
120
|
}
|
|
24
121
|
async function writeLocalResolutionPin(cwd, pin, signal) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
122
|
+
return Result.gen(async function* () {
|
|
123
|
+
const prismaDir = path.join(cwd, ".prisma");
|
|
124
|
+
yield* ensureLocalResolutionPinWriteNotAborted(signal);
|
|
125
|
+
yield* Result.await(writeLocalResolutionPinBoundary(() => mkdir(prismaDir, { recursive: true }), "create-directory", signal));
|
|
126
|
+
const pinPath = path.join(cwd, LOCAL_RESOLUTION_PIN_RELATIVE_PATH);
|
|
127
|
+
const tmpPath = path.join(prismaDir, `local.${process.pid}.${Date.now()}.tmp`);
|
|
128
|
+
const serialized = yield* serializeLocalResolutionPin(pin);
|
|
129
|
+
yield* Result.await(writeLocalResolutionPinBoundary(() => writeFile(tmpPath, serialized, {
|
|
130
|
+
encoding: "utf8",
|
|
131
|
+
signal
|
|
132
|
+
}), "write-temp-file", signal));
|
|
133
|
+
yield* ensureLocalResolutionPinWriteNotAborted(signal);
|
|
134
|
+
yield* Result.await(writeLocalResolutionPinBoundary(() => rename(tmpPath, pinPath), "rename-temp-file", signal));
|
|
135
|
+
return Result.ok(void 0);
|
|
33
136
|
});
|
|
34
|
-
signal?.throwIfAborted();
|
|
35
|
-
await rename(tmpPath, pinPath);
|
|
36
137
|
}
|
|
37
138
|
async function ensureLocalResolutionPinGitignore(cwd, signal) {
|
|
38
139
|
const gitignorePath = path.join(cwd, ".gitignore");
|
|
39
140
|
let existing = null;
|
|
40
|
-
signal
|
|
41
|
-
|
|
42
|
-
|
|
141
|
+
const notAborted = ensureLocalResolutionPinGitignoreUpdateNotAborted(signal);
|
|
142
|
+
if (notAborted.isErr()) return Result.err(notAborted.error);
|
|
143
|
+
const existingResult = await Result.tryPromise({
|
|
144
|
+
try: () => readFile(gitignorePath, {
|
|
43
145
|
encoding: "utf8",
|
|
44
146
|
signal
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
147
|
+
}),
|
|
148
|
+
catch: (cause) => signal?.aborted ? new LocalResolutionPinGitignoreUpdateAbortedError(cause) : new LocalResolutionPinGitignoreUpdateFailedError("read", cause)
|
|
149
|
+
});
|
|
150
|
+
if (existingResult.isErr()) if (existingResult.error instanceof LocalResolutionPinGitignoreUpdateFailedError && existingResult.error.cause.code === "ENOENT") existing = null;
|
|
151
|
+
else return Result.err(existingResult.error);
|
|
152
|
+
else existing = existingResult.value;
|
|
153
|
+
if (existing === null) return writeLocalResolutionPinGitignore(gitignorePath, ".prisma/\n", signal);
|
|
154
|
+
if (existing.split(/\r?\n/).map((line) => line.trim()).some((line) => line === ".prisma/" || line === ".prisma/local.json")) return Result.ok(void 0);
|
|
155
|
+
return writeLocalResolutionPinGitignore(gitignorePath, existing.endsWith("\n") ? `${existing}.prisma/\n` : `${existing}\n.prisma/\n`, signal);
|
|
156
|
+
}
|
|
157
|
+
function ensureLocalResolutionPinWriteNotAborted(signal) {
|
|
158
|
+
return Result.try({
|
|
159
|
+
try: () => signal?.throwIfAborted(),
|
|
160
|
+
catch: (cause) => new LocalResolutionPinWriteAbortedError(cause)
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
function serializeLocalResolutionPin(pin) {
|
|
164
|
+
return Result.try({
|
|
165
|
+
try: () => `${JSON.stringify(pin, null, 2)}\n`,
|
|
166
|
+
catch: (cause) => new LocalResolutionPinSerializationError(cause)
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
function writeLocalResolutionPinBoundary(run, operation, signal) {
|
|
170
|
+
return Result.tryPromise({
|
|
171
|
+
try: async () => {
|
|
172
|
+
await run();
|
|
173
|
+
},
|
|
174
|
+
catch: (cause) => signal?.aborted ? new LocalResolutionPinWriteAbortedError(cause) : new LocalResolutionPinWriteFailedError(operation, cause)
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
function ensureLocalResolutionPinGitignoreUpdateNotAborted(signal) {
|
|
178
|
+
return Result.try({
|
|
179
|
+
try: () => signal?.throwIfAborted(),
|
|
180
|
+
catch: (cause) => new LocalResolutionPinGitignoreUpdateAbortedError(cause)
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
function writeLocalResolutionPinGitignore(gitignorePath, contents, signal) {
|
|
184
|
+
return Result.tryPromise({
|
|
185
|
+
try: () => writeFile(gitignorePath, contents, {
|
|
51
186
|
encoding: "utf8",
|
|
52
187
|
signal
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
if (existing.split(/\r?\n/).map((line) => line.trim()).some((line) => line === ".prisma/" || line === ".prisma/local.json")) return;
|
|
57
|
-
await writeFile(gitignorePath, existing.endsWith("\n") ? `${existing}.prisma/\n` : `${existing}\n.prisma/\n`, {
|
|
58
|
-
encoding: "utf8",
|
|
59
|
-
signal
|
|
188
|
+
}),
|
|
189
|
+
catch: (cause) => signal?.aborted ? new LocalResolutionPinGitignoreUpdateAbortedError(cause) : new LocalResolutionPinGitignoreUpdateFailedError("write", cause)
|
|
60
190
|
});
|
|
61
191
|
}
|
|
62
192
|
function isLocalResolutionPin(value) {
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { formatPrismaCliCommand } from "../../shell/cli-command.js";
|
|
2
|
+
import { CliError } from "../../shell/errors.js";
|
|
3
|
+
//#region src/lib/project/provider.ts
|
|
4
|
+
function createManagementProjectProvider(client) {
|
|
5
|
+
return {
|
|
6
|
+
async renameProject(options) {
|
|
7
|
+
const result = await client.PATCH("/v1/projects/{id}", {
|
|
8
|
+
params: { path: { id: options.projectId } },
|
|
9
|
+
body: { name: options.name },
|
|
10
|
+
signal: options.signal
|
|
11
|
+
});
|
|
12
|
+
const status = result.response?.status ?? 0;
|
|
13
|
+
if (status === 400 || status === 422) throw projectRenameFailedError(options.name, result.error);
|
|
14
|
+
if (result.error || !result.data) throw projectApiError("Failed to rename project", result.response, result.error);
|
|
15
|
+
const project = result.data.data;
|
|
16
|
+
return {
|
|
17
|
+
id: project.id,
|
|
18
|
+
name: project.name,
|
|
19
|
+
...project.url ? { url: project.url } : {}
|
|
20
|
+
};
|
|
21
|
+
},
|
|
22
|
+
async removeProject(options) {
|
|
23
|
+
const result = await client.DELETE("/v1/projects/{id}", {
|
|
24
|
+
params: { path: { id: options.projectId } },
|
|
25
|
+
signal: options.signal
|
|
26
|
+
});
|
|
27
|
+
if (result.response?.status === 400) throw projectRemoveBlockedError(options.projectId, result.error);
|
|
28
|
+
if (result.error) throw projectApiError("Failed to remove project", result.response, result.error);
|
|
29
|
+
},
|
|
30
|
+
async transferProject(options) {
|
|
31
|
+
const result = await client.POST("/v1/projects/{id}/transfer", {
|
|
32
|
+
params: { path: { id: options.projectId } },
|
|
33
|
+
body: { recipientAccessToken: options.recipientAccessToken },
|
|
34
|
+
signal: options.signal
|
|
35
|
+
});
|
|
36
|
+
if (result.response?.status === 400) throw projectTransferRejectedError(options.projectId, result.error);
|
|
37
|
+
if (result.error) throw projectApiError("Failed to transfer project", result.response, result.error);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function projectRenameFailedError(name, error) {
|
|
42
|
+
return new CliError({
|
|
43
|
+
code: "PROJECT_RENAME_FAILED",
|
|
44
|
+
domain: "project",
|
|
45
|
+
summary: "Project rename failed",
|
|
46
|
+
why: error?.error?.message ?? `The platform rejected the name "${name}".`,
|
|
47
|
+
fix: error?.error?.hint ?? "Pass a different project name and retry the rename.",
|
|
48
|
+
exitCode: 1,
|
|
49
|
+
nextSteps: []
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function projectRemoveBlockedError(projectId, error) {
|
|
53
|
+
return new CliError({
|
|
54
|
+
code: "PROJECT_REMOVE_BLOCKED",
|
|
55
|
+
domain: "project",
|
|
56
|
+
summary: "Project cannot be removed yet",
|
|
57
|
+
why: error?.error?.message ?? `Project "${projectId}" still has active deployments.`,
|
|
58
|
+
fix: "Remove the project's apps first, then retry the removal.",
|
|
59
|
+
exitCode: 1,
|
|
60
|
+
nextSteps: [formatPrismaCliCommand([
|
|
61
|
+
"app",
|
|
62
|
+
"remove",
|
|
63
|
+
"--app",
|
|
64
|
+
"<name>"
|
|
65
|
+
])]
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function projectTransferRejectedError(projectId, error) {
|
|
69
|
+
return new CliError({
|
|
70
|
+
code: "PROJECT_TRANSFER_REJECTED",
|
|
71
|
+
domain: "project",
|
|
72
|
+
summary: "Project transfer was rejected",
|
|
73
|
+
why: error?.error?.message ?? `The platform rejected the transfer of project "${projectId}", for example because the recipient token is invalid or expired.`,
|
|
74
|
+
fix: "Check the recipient workspace session or token and retry the transfer.",
|
|
75
|
+
exitCode: 1,
|
|
76
|
+
nextSteps: []
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function projectApiError(summary, response, error) {
|
|
80
|
+
const status = response?.status ?? 0;
|
|
81
|
+
return new CliError({
|
|
82
|
+
code: error?.error?.code ?? "PROJECT_API_ERROR",
|
|
83
|
+
domain: "project",
|
|
84
|
+
summary,
|
|
85
|
+
why: error?.error?.message ?? `The Management API returned status ${status || "unknown"}.`,
|
|
86
|
+
fix: error?.error?.hint ?? "Re-run with --trace for the underlying API response details.",
|
|
87
|
+
exitCode: 1,
|
|
88
|
+
nextSteps: []
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
92
|
+
export { createManagementProjectProvider, projectRemoveBlockedError, projectRenameFailedError, projectTransferRejectedError };
|
|
@@ -1,38 +1,100 @@
|
|
|
1
|
-
import { CliError } from "../../shell/errors.js";
|
|
2
1
|
import { formatCommandArgument } from "../../shell/command-arguments.js";
|
|
2
|
+
import { CliError } from "../../shell/errors.js";
|
|
3
3
|
import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, readLocalResolutionPin } from "./local-pin.js";
|
|
4
|
-
import { readFile } from "node:fs/promises";
|
|
5
4
|
import path from "node:path";
|
|
5
|
+
import { readFile } from "node:fs/promises";
|
|
6
|
+
import { Result, TaggedError, matchError } from "better-result";
|
|
6
7
|
//#region src/lib/project/resolution.ts
|
|
8
|
+
var ProjectNotFoundError = class extends TaggedError("ProjectNotFoundError")() {
|
|
9
|
+
constructor(projectRef, workspace) {
|
|
10
|
+
super({
|
|
11
|
+
message: `Project "${projectRef}" was not found in workspace "${workspace.name}".`,
|
|
12
|
+
projectRef,
|
|
13
|
+
workspace
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
var ProjectAmbiguousError = class extends TaggedError("ProjectAmbiguousError")() {
|
|
18
|
+
constructor(projectRef, matches) {
|
|
19
|
+
super({
|
|
20
|
+
message: projectRef ? `Multiple projects matched "${projectRef}".` : "Multiple projects matched the current directory context.",
|
|
21
|
+
projectRef,
|
|
22
|
+
matches
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
var LocalStateStaleError = class extends TaggedError("LocalStateStaleError")() {
|
|
27
|
+
constructor() {
|
|
28
|
+
super({
|
|
29
|
+
message: `The target recorded in ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} is no longer available in the selected workspace.`,
|
|
30
|
+
pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
var LocalProjectWorkspaceMismatchError = class extends TaggedError("LocalProjectWorkspaceMismatchError")() {
|
|
35
|
+
constructor(options) {
|
|
36
|
+
super({
|
|
37
|
+
message: `${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} links this directory to project ${options.pinnedProjectId} in workspace ${options.pinnedWorkspaceId}, but the active workspace is "${options.activeWorkspace.name}" (${options.activeWorkspace.id}).`,
|
|
38
|
+
pinnedWorkspaceId: options.pinnedWorkspaceId,
|
|
39
|
+
pinnedProjectId: options.pinnedProjectId,
|
|
40
|
+
activeWorkspace: options.activeWorkspace
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
var ProjectSetupRequiredError = class extends TaggedError("ProjectSetupRequiredError")() {
|
|
45
|
+
constructor(options) {
|
|
46
|
+
const commandLabel = options.commandName ? `prisma-cli ${options.commandName}` : "this command";
|
|
47
|
+
super({
|
|
48
|
+
message: `This directory is not linked to a Prisma Project, and ${commandLabel} will not choose one from package or directory names.`,
|
|
49
|
+
commandName: options.commandName,
|
|
50
|
+
suggestion: options.suggestion
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
};
|
|
7
54
|
async function resolveProjectTarget(options) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
async function inspectProjectBinding(options) {
|
|
19
|
-
const projects = await options.listProjects();
|
|
20
|
-
const target = await resolveBoundProjectTarget(options, projects, { allowEnvProjectId: false });
|
|
21
|
-
if (target) return target;
|
|
22
|
-
return {
|
|
23
|
-
workspace: options.workspace,
|
|
24
|
-
project: null,
|
|
25
|
-
localBinding: { status: "not-linked" },
|
|
26
|
-
resolution: { projectSource: "unbound" },
|
|
27
|
-
...await buildProjectSetupSuggestion({
|
|
55
|
+
return Result.gen(async function* () {
|
|
56
|
+
const localPin = yield* Result.await(readImplicitLocalPin(options, { allowEnvProjectId: true }));
|
|
57
|
+
const projects = await options.listProjects();
|
|
58
|
+
const target = yield* Result.await(resolveBoundProjectTarget(options, projects, {
|
|
59
|
+
allowEnvProjectId: true,
|
|
60
|
+
localPin
|
|
61
|
+
}));
|
|
62
|
+
if (target) return Result.ok(target);
|
|
63
|
+
return Result.err(await projectSetupRequiredError({
|
|
28
64
|
cwd: options.context.runtime.cwd,
|
|
29
65
|
projects,
|
|
30
|
-
commandName: options.commandName
|
|
66
|
+
commandName: options.commandName,
|
|
31
67
|
signal: options.context.runtime.signal
|
|
32
|
-
})
|
|
33
|
-
};
|
|
68
|
+
}));
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
async function inspectProjectBinding(options) {
|
|
72
|
+
return Result.gen(async function* () {
|
|
73
|
+
const localPin = yield* Result.await(readImplicitLocalPin(options, { allowEnvProjectId: false }));
|
|
74
|
+
const projects = await options.listProjects();
|
|
75
|
+
const target = yield* Result.await(resolveBoundProjectTarget(options, projects, {
|
|
76
|
+
allowEnvProjectId: false,
|
|
77
|
+
localPin
|
|
78
|
+
}));
|
|
79
|
+
if (target) return Result.ok(target);
|
|
80
|
+
return Result.ok({
|
|
81
|
+
workspace: options.workspace,
|
|
82
|
+
project: null,
|
|
83
|
+
localBinding: { status: "not-linked" },
|
|
84
|
+
resolution: { projectSource: "unbound" },
|
|
85
|
+
...await buildProjectSetupSuggestion({
|
|
86
|
+
cwd: options.context.runtime.cwd,
|
|
87
|
+
projects,
|
|
88
|
+
commandName: options.commandName ?? "project show",
|
|
89
|
+
signal: options.context.runtime.signal
|
|
90
|
+
})
|
|
91
|
+
});
|
|
92
|
+
});
|
|
34
93
|
}
|
|
35
94
|
function projectNotFoundError(projectRef, workspace) {
|
|
95
|
+
return projectResolutionErrorToCliError(new ProjectNotFoundError(projectRef, workspace));
|
|
96
|
+
}
|
|
97
|
+
function projectNotFoundCliError(projectRef, workspace) {
|
|
36
98
|
return new CliError({
|
|
37
99
|
code: "PROJECT_NOT_FOUND",
|
|
38
100
|
domain: "project",
|
|
@@ -44,6 +106,9 @@ function projectNotFoundError(projectRef, workspace) {
|
|
|
44
106
|
});
|
|
45
107
|
}
|
|
46
108
|
function projectAmbiguousError(projectRef, matches) {
|
|
109
|
+
return projectResolutionErrorToCliError(new ProjectAmbiguousError(projectRef, matches));
|
|
110
|
+
}
|
|
111
|
+
function projectAmbiguousCliError(projectRef, matches) {
|
|
47
112
|
const firstMatch = matches[0];
|
|
48
113
|
const nextSteps = ["prisma-cli project list"];
|
|
49
114
|
if (firstMatch) nextSteps.push(`prisma-cli app deploy --project ${firstMatch.id}`);
|
|
@@ -61,7 +126,7 @@ function projectAmbiguousError(projectRef, matches) {
|
|
|
61
126
|
nextSteps
|
|
62
127
|
});
|
|
63
128
|
}
|
|
64
|
-
function
|
|
129
|
+
function localStateStaleCliError() {
|
|
65
130
|
return new CliError({
|
|
66
131
|
code: "LOCAL_STATE_STALE",
|
|
67
132
|
domain: "project",
|
|
@@ -73,6 +138,56 @@ function localStateStaleError() {
|
|
|
73
138
|
nextSteps: ["prisma-cli project list", "prisma-cli project link <id-or-name>"]
|
|
74
139
|
});
|
|
75
140
|
}
|
|
141
|
+
function localProjectWorkspaceMismatchError(options) {
|
|
142
|
+
return projectResolutionErrorToCliError(new LocalProjectWorkspaceMismatchError(options));
|
|
143
|
+
}
|
|
144
|
+
function localProjectWorkspaceMismatchCliError(options) {
|
|
145
|
+
return new CliError({
|
|
146
|
+
code: "LOCAL_PROJECT_WORKSPACE_MISMATCH",
|
|
147
|
+
domain: "project",
|
|
148
|
+
summary: "Project link uses another workspace",
|
|
149
|
+
why: `${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} links this directory to project ${options.pinnedProjectId} in workspace ${options.pinnedWorkspaceId}, but your current CLI session is workspace "${options.activeWorkspace.name}" (${options.activeWorkspace.id}).`,
|
|
150
|
+
fix: "Switch to the linked workspace, or relink this directory to a project in the current workspace.",
|
|
151
|
+
meta: {
|
|
152
|
+
pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH,
|
|
153
|
+
pinnedWorkspaceId: options.pinnedWorkspaceId,
|
|
154
|
+
pinnedProjectId: options.pinnedProjectId,
|
|
155
|
+
activeWorkspaceId: options.activeWorkspace.id,
|
|
156
|
+
activeWorkspaceName: options.activeWorkspace.name
|
|
157
|
+
},
|
|
158
|
+
exitCode: 1,
|
|
159
|
+
nextSteps: [
|
|
160
|
+
`prisma-cli auth workspace use ${options.pinnedWorkspaceId}`,
|
|
161
|
+
"prisma-cli project list",
|
|
162
|
+
"prisma-cli project link <id-or-name>"
|
|
163
|
+
]
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Converts expected project-resolution variants to command-boundary CliErrors.
|
|
168
|
+
* `LocalResolutionPinReadAbortedError` and `UnhandledException` intentionally
|
|
169
|
+
* propagate as exceptions; callers such as `resolveProjectShowInRealMode`
|
|
170
|
+
* throw this helper's result, so passthrough variants should keep bubbling.
|
|
171
|
+
*/
|
|
172
|
+
function projectResolutionErrorToCliError(error) {
|
|
173
|
+
return matchError(error, {
|
|
174
|
+
ProjectNotFoundError: (error) => projectNotFoundCliError(error.projectRef, error.workspace),
|
|
175
|
+
ProjectAmbiguousError: (error) => projectAmbiguousCliError(error.projectRef, error.matches),
|
|
176
|
+
ProjectSetupRequiredError: (error) => projectSetupRequiredCliError(error),
|
|
177
|
+
LocalStateStaleError: () => localStateStaleCliError(),
|
|
178
|
+
LocalProjectWorkspaceMismatchError: (error) => localProjectWorkspaceMismatchCliError({
|
|
179
|
+
pinnedWorkspaceId: error.pinnedWorkspaceId,
|
|
180
|
+
pinnedProjectId: error.pinnedProjectId,
|
|
181
|
+
activeWorkspace: error.activeWorkspace
|
|
182
|
+
}),
|
|
183
|
+
LocalResolutionPinReadAbortedError: (error) => {
|
|
184
|
+
throw error;
|
|
185
|
+
},
|
|
186
|
+
UnhandledException: (error) => {
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
}
|
|
76
191
|
async function buildProjectSetupSuggestion(options) {
|
|
77
192
|
const suggestedName = await inferTargetName(options.cwd, options.signal);
|
|
78
193
|
const candidates = sortProjects(options.projects.filter((project) => projectMatchesSuggestedName(project, suggestedName.name))).map(toProjectSummary);
|
|
@@ -85,17 +200,24 @@ async function buildProjectSetupSuggestion(options) {
|
|
|
85
200
|
}
|
|
86
201
|
async function projectSetupRequiredError(options) {
|
|
87
202
|
const suggestion = await buildProjectSetupSuggestion(options);
|
|
203
|
+
return new ProjectSetupRequiredError({
|
|
204
|
+
commandName: options.commandName,
|
|
205
|
+
suggestion
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
function projectSetupRequiredCliError(error) {
|
|
209
|
+
const suggestion = error.suggestion;
|
|
88
210
|
return new CliError({
|
|
89
211
|
code: "PROJECT_SETUP_REQUIRED",
|
|
90
212
|
domain: "project",
|
|
91
213
|
summary: "Choose a Project before running this command",
|
|
92
|
-
why:
|
|
214
|
+
why: error.message,
|
|
93
215
|
fix: "Link the directory to an existing Project, or pass --project <id-or-name> for this command.",
|
|
94
216
|
meta: { ...suggestion },
|
|
95
217
|
exitCode: 1,
|
|
96
218
|
nextSteps: ["prisma-cli project list", ...suggestion.recoveryCommands],
|
|
97
219
|
nextActions: buildProjectSetupNextActions({
|
|
98
|
-
commandName:
|
|
220
|
+
commandName: error.commandName,
|
|
99
221
|
suggestedProjectName: suggestion.suggestedProjectName
|
|
100
222
|
})
|
|
101
223
|
});
|
|
@@ -169,9 +291,9 @@ function sortProjects(projects) {
|
|
|
169
291
|
}
|
|
170
292
|
function resolveExplicitProject(projectRef, projects, workspace) {
|
|
171
293
|
const matches = projects.filter((project) => project.id === projectRef || project.name === projectRef);
|
|
172
|
-
if (matches.length === 1) return matches[0];
|
|
173
|
-
if (matches.length > 1)
|
|
174
|
-
|
|
294
|
+
if (matches.length === 1) return Result.ok(matches[0]);
|
|
295
|
+
if (matches.length > 1) return Result.err(new ProjectAmbiguousError(projectRef, matches));
|
|
296
|
+
return Result.err(new ProjectNotFoundError(projectRef, workspace));
|
|
175
297
|
}
|
|
176
298
|
function projectMatchesSuggestedName(project, suggestedName) {
|
|
177
299
|
return project.id === suggestedName || project.name === suggestedName || project.slug === suggestedName;
|
|
@@ -180,35 +302,63 @@ async function resolveDurablePlatformMapping() {
|
|
|
180
302
|
return null;
|
|
181
303
|
}
|
|
182
304
|
async function resolveBoundProjectTarget(options, projects, settings) {
|
|
183
|
-
if (options.explicitProject)
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
305
|
+
if (options.explicitProject) {
|
|
306
|
+
const projectResult = resolveExplicitProject(options.explicitProject, projects, options.workspace);
|
|
307
|
+
if (projectResult.isErr()) return Result.err(projectResult.error);
|
|
308
|
+
return Result.ok(resolvedTarget(options.workspace, projectResult.value, "explicit", {
|
|
309
|
+
targetName: options.explicitProject,
|
|
310
|
+
targetNameSource: "explicit"
|
|
311
|
+
}));
|
|
312
|
+
}
|
|
187
313
|
if (settings.allowEnvProjectId && options.envProjectId) {
|
|
188
314
|
const project = projects.find((candidate) => candidate.id === options.envProjectId);
|
|
189
|
-
if (!project)
|
|
190
|
-
return resolvedTarget(options.workspace, project, "env", {
|
|
315
|
+
if (!project) return Result.err(new ProjectNotFoundError(options.envProjectId, options.workspace));
|
|
316
|
+
return Result.ok(resolvedTarget(options.workspace, project, "env", {
|
|
191
317
|
targetName: options.envProjectId,
|
|
192
318
|
targetNameSource: "env"
|
|
193
|
-
});
|
|
319
|
+
}));
|
|
194
320
|
}
|
|
195
|
-
const localPin =
|
|
196
|
-
if (localPin
|
|
321
|
+
const localPin = settings.localPin;
|
|
322
|
+
if (!localPin) return Result.ok(null);
|
|
197
323
|
if (localPin.kind === "present") {
|
|
198
|
-
if (localPin.pin.workspaceId !== options.workspace.id)
|
|
324
|
+
if (localPin.pin.workspaceId !== options.workspace.id) return Result.err(new LocalProjectWorkspaceMismatchError({
|
|
325
|
+
pinnedWorkspaceId: localPin.pin.workspaceId,
|
|
326
|
+
pinnedProjectId: localPin.pin.projectId,
|
|
327
|
+
activeWorkspace: options.workspace
|
|
328
|
+
}));
|
|
199
329
|
const project = projects.find((candidate) => candidate.id === localPin.pin.projectId);
|
|
200
|
-
if (!project)
|
|
201
|
-
return resolvedTarget(options.workspace, project, "local-pin", {
|
|
330
|
+
if (!project) return Result.err(new LocalStateStaleError());
|
|
331
|
+
return Result.ok(resolvedTarget(options.workspace, project, "local-pin", {
|
|
202
332
|
targetName: project.name,
|
|
203
333
|
targetNameSource: "local-pin"
|
|
204
|
-
});
|
|
334
|
+
}));
|
|
205
335
|
}
|
|
206
336
|
const platformMapping = await resolveDurablePlatformMapping();
|
|
207
|
-
if (platformMapping && platformMapping.workspace.id === options.workspace.id) return resolvedTarget(options.workspace, platformMapping, "platform-mapping", {
|
|
337
|
+
if (platformMapping && platformMapping.workspace.id === options.workspace.id) return Result.ok(resolvedTarget(options.workspace, platformMapping, "platform-mapping", {
|
|
208
338
|
targetName: platformMapping.name,
|
|
209
339
|
targetNameSource: "platform-mapping"
|
|
340
|
+
}));
|
|
341
|
+
return Result.ok(null);
|
|
342
|
+
}
|
|
343
|
+
async function readImplicitLocalPin(options, settings) {
|
|
344
|
+
if (options.explicitProject || settings.allowEnvProjectId && options.envProjectId) return Result.ok(null);
|
|
345
|
+
const localPinResult = await readLocalResolutionPin(options.projectDir ?? options.context.runtime.cwd, options.context.runtime.signal);
|
|
346
|
+
if (localPinResult.isErr()) return Result.err(localPinReadErrorToProjectError(localPinResult.error));
|
|
347
|
+
const localPin = localPinResult.value;
|
|
348
|
+
if (localPin.kind === "present" && localPin.pin.workspaceId !== options.workspace.id) return Result.err(new LocalProjectWorkspaceMismatchError({
|
|
349
|
+
pinnedWorkspaceId: localPin.pin.workspaceId,
|
|
350
|
+
pinnedProjectId: localPin.pin.projectId,
|
|
351
|
+
activeWorkspace: options.workspace
|
|
352
|
+
}));
|
|
353
|
+
return Result.ok(localPin);
|
|
354
|
+
}
|
|
355
|
+
function localPinReadErrorToProjectError(error) {
|
|
356
|
+
return matchError(error, {
|
|
357
|
+
LocalResolutionPinInvalidJsonError: () => new LocalStateStaleError(),
|
|
358
|
+
LocalResolutionPinInvalidShapeError: () => new LocalStateStaleError(),
|
|
359
|
+
LocalResolutionPinReadAbortedError: (error) => error,
|
|
360
|
+
UnhandledException: (error) => error
|
|
210
361
|
});
|
|
211
|
-
return null;
|
|
212
362
|
}
|
|
213
363
|
function resolvedTarget(workspace, project, projectSource, resolutionDetails) {
|
|
214
364
|
return {
|
|
@@ -229,8 +379,9 @@ function toProjectSummary(project) {
|
|
|
229
379
|
return {
|
|
230
380
|
id: project.id,
|
|
231
381
|
name: project.name,
|
|
232
|
-
...project.url ? { url: project.url } : {}
|
|
382
|
+
...project.url ? { url: project.url } : {},
|
|
383
|
+
...project.defaultRegion != null ? { defaultRegion: project.defaultRegion } : {}
|
|
233
384
|
};
|
|
234
385
|
}
|
|
235
386
|
//#endregion
|
|
236
|
-
export { buildProjectSetupNextActions, inferTargetName, inspectProjectBinding, projectAmbiguousError, projectNotFoundError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects };
|
|
387
|
+
export { buildProjectSetupNextActions, inferTargetName, inspectProjectBinding, localProjectWorkspaceMismatchError, projectAmbiguousError, projectNotFoundError, projectResolutionErrorToCliError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects };
|