@prisma/cli 3.0.0-beta.9 → 3.0.0-dev.102.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/README.md +0 -13
- package/dist/adapters/token-storage.js +311 -33
- package/dist/cli.js +7 -7
- package/dist/cli2.js +3 -3
- package/dist/commands/app/index.js +65 -52
- package/dist/commands/auth/index.js +55 -2
- package/dist/controllers/app-env.js +10 -7
- package/dist/controllers/app.js +539 -210
- package/dist/controllers/auth.js +211 -2
- package/dist/controllers/branch.js +5 -6
- package/dist/controllers/database.js +7 -5
- package/dist/controllers/project.js +33 -18
- package/dist/lib/app/app-interaction.js +5 -0
- package/dist/lib/app/{preview-provider.js → app-provider.js} +82 -78
- package/dist/lib/app/{preview-branch-database.js → branch-database-api.js} +1 -1
- package/dist/lib/app/branch-database-deploy.js +12 -60
- package/dist/lib/app/branch-database.js +1 -102
- package/dist/lib/app/build-settings.js +93 -0
- package/dist/lib/app/build.js +82 -0
- package/dist/lib/app/bun-project.js +2 -3
- package/dist/lib/app/compute-config.js +147 -0
- package/dist/lib/app/deploy-plan.js +58 -0
- package/dist/lib/app/{preview-progress.js → deploy-progress.js} +12 -12
- package/dist/lib/app/local-dev.js +3 -60
- package/dist/lib/app/production-deploy-gate.js +3 -3
- 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 +32 -25
- package/dist/lib/diagnostics.js +1 -1
- package/dist/lib/fs/home-path.js +24 -0
- package/dist/lib/git/local-branch.js +15 -3
- package/dist/lib/project/local-pin.js +106 -26
- package/dist/lib/project/resolution.js +162 -71
- package/dist/lib/project/setup.js +65 -19
- package/dist/output/patterns.js +1 -1
- package/dist/presenters/app.js +44 -39
- package/dist/presenters/auth.js +98 -1
- package/dist/presenters/branch.js +1 -1
- package/dist/presenters/database.js +2 -2
- package/dist/presenters/project.js +3 -9
- package/dist/shell/command-meta.js +52 -2
- package/dist/shell/command-runner.js +39 -28
- package/dist/shell/diagnostics-output.js +2 -8
- package/dist/shell/errors.js +50 -1
- package/dist/shell/help.js +30 -20
- package/dist/shell/runtime.js +8 -4
- package/dist/shell/ui.js +19 -2
- package/dist/use-cases/auth.js +68 -1
- package/package.json +18 -4
- package/dist/lib/app/preview-build-settings.js +0 -385
- package/dist/lib/app/preview-build.js +0 -475
- package/dist/lib/app/preview-interaction.js +0 -5
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;
|
|
@@ -144,7 +151,7 @@ var LoginState = class {
|
|
|
144
151
|
output;
|
|
145
152
|
constructor(options) {
|
|
146
153
|
this.options = options;
|
|
147
|
-
this.tokenStorage = options.tokenStorage ?? new FileTokenStorage(options.env, options.signal);
|
|
154
|
+
this.tokenStorage = options.tokenStorage ?? new FileTokenStorage(options.env, options.signal, { activateOnSetTokens: true });
|
|
148
155
|
this.sdk = createManagementApiSdk({
|
|
149
156
|
clientId: options.clientId ?? "cmm3lndn701oo0uefvxzo0ivw",
|
|
150
157
|
redirectUri: `http://${options.hostname}:${options.port}/auth/callback`,
|
package/dist/lib/diagnostics.js
CHANGED
|
@@ -3,7 +3,7 @@ import { resolveStateDir } from "../shell/runtime.js";
|
|
|
3
3
|
import { readLocalGitState } from "./git/local-status.js";
|
|
4
4
|
//#region src/lib/diagnostics.ts
|
|
5
5
|
async function collectCommandDiagnostics(context, options = {}) {
|
|
6
|
-
const stateDir = resolveStateDir(context.runtime);
|
|
6
|
+
const stateDir = await resolveStateDir(context.runtime);
|
|
7
7
|
return {
|
|
8
8
|
cwd: context.runtime.cwd,
|
|
9
9
|
stateFilePath: resolveLocalStateFilePath(stateDir),
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
//#region src/lib/fs/home-path.ts
|
|
3
|
+
/**
|
|
4
|
+
* Shortens a path under the user's home directory to `~/...` for display,
|
|
5
|
+
* posix-style on every platform. Falls back to the Windows home variables
|
|
6
|
+
* when `HOME` is unset (native cmd/PowerShell sessions).
|
|
7
|
+
*/
|
|
8
|
+
function shortenHomePath(value, env) {
|
|
9
|
+
const resolved = path.resolve(value);
|
|
10
|
+
const home = resolveHomeDirectory(env);
|
|
11
|
+
if (home && (resolved === home || resolved.startsWith(`${home}${path.sep}`))) {
|
|
12
|
+
const relative = path.relative(home, resolved).split(path.sep).join("/");
|
|
13
|
+
return relative ? `~/${relative}` : "~";
|
|
14
|
+
}
|
|
15
|
+
return resolved;
|
|
16
|
+
}
|
|
17
|
+
function resolveHomeDirectory(env) {
|
|
18
|
+
if (env.HOME) return path.resolve(env.HOME);
|
|
19
|
+
if (env.USERPROFILE) return path.resolve(env.USERPROFILE);
|
|
20
|
+
if (env.HOMEDRIVE && env.HOMEPATH) return path.resolve(`${env.HOMEDRIVE}${env.HOMEPATH}`);
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
export { shortenHomePath };
|
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
import { access, readFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
//#region src/lib/git/local-branch.ts
|
|
4
|
+
/**
|
|
5
|
+
* Resolves the checked-out branch the way git does: the nearest `.git`
|
|
6
|
+
* (directory or worktree file) from `cwd` upward owns the answer, so
|
|
7
|
+
* monorepo commands run from inside a package see the repository branch.
|
|
8
|
+
* Returns null for detached HEAD or when no repository contains `cwd`.
|
|
9
|
+
*/
|
|
4
10
|
async function readLocalGitBranch(cwd, signal) {
|
|
5
|
-
|
|
6
|
-
|
|
11
|
+
for (let directory = path.resolve(cwd);;) {
|
|
12
|
+
const headPath = await resolveGitHeadPath(path.join(directory, ".git"), signal);
|
|
13
|
+
if (headPath) return readBranchFromHead(headPath, signal);
|
|
14
|
+
const parent = path.dirname(directory);
|
|
15
|
+
if (parent === directory) return null;
|
|
16
|
+
directory = parent;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async function readBranchFromHead(headPath, signal) {
|
|
7
20
|
try {
|
|
8
21
|
const head = (await readFile(headPath, {
|
|
9
22
|
encoding: "utf8",
|
|
@@ -12,7 +25,6 @@ async function readLocalGitBranch(cwd, signal) {
|
|
|
12
25
|
if (head.startsWith("ref: refs/heads/")) return head.slice(16);
|
|
13
26
|
} catch (error) {
|
|
14
27
|
if (signal.aborted) throw error;
|
|
15
|
-
return null;
|
|
16
28
|
}
|
|
17
29
|
return null;
|
|
18
30
|
}
|
|
@@ -29,6 +29,53 @@ var LocalResolutionPinReadAbortedError = class extends TaggedError("LocalResolut
|
|
|
29
29
|
});
|
|
30
30
|
}
|
|
31
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"
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
};
|
|
32
79
|
async function readLocalResolutionPin(cwd, signal) {
|
|
33
80
|
return Result.gen(async function* () {
|
|
34
81
|
yield* ensureLocalResolutionPinReadNotAborted(signal);
|
|
@@ -72,41 +119,74 @@ function parseLocalResolutionPin(raw) {
|
|
|
72
119
|
});
|
|
73
120
|
}
|
|
74
121
|
async function writeLocalResolutionPin(cwd, pin, signal) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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);
|
|
83
136
|
});
|
|
84
|
-
signal?.throwIfAborted();
|
|
85
|
-
await rename(tmpPath, pinPath);
|
|
86
137
|
}
|
|
87
138
|
async function ensureLocalResolutionPinGitignore(cwd, signal) {
|
|
88
139
|
const gitignorePath = path.join(cwd, ".gitignore");
|
|
89
140
|
let existing = null;
|
|
90
|
-
signal
|
|
91
|
-
|
|
92
|
-
|
|
141
|
+
const notAborted = ensureLocalResolutionPinGitignoreUpdateNotAborted(signal);
|
|
142
|
+
if (notAborted.isErr()) return Result.err(notAborted.error);
|
|
143
|
+
const existingResult = await Result.tryPromise({
|
|
144
|
+
try: () => readFile(gitignorePath, {
|
|
93
145
|
encoding: "utf8",
|
|
94
146
|
signal
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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, {
|
|
101
186
|
encoding: "utf8",
|
|
102
187
|
signal
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
if (existing.split(/\r?\n/).map((line) => line.trim()).some((line) => line === ".prisma/" || line === ".prisma/local.json")) return;
|
|
107
|
-
await writeFile(gitignorePath, existing.endsWith("\n") ? `${existing}.prisma/\n` : `${existing}\n.prisma/\n`, {
|
|
108
|
-
encoding: "utf8",
|
|
109
|
-
signal
|
|
188
|
+
}),
|
|
189
|
+
catch: (cause) => signal?.aborted ? new LocalResolutionPinGitignoreUpdateAbortedError(cause) : new LocalResolutionPinGitignoreUpdateFailedError("write", cause)
|
|
110
190
|
});
|
|
111
191
|
}
|
|
112
192
|
function isLocalResolutionPin(value) {
|
|
@@ -1,47 +1,100 @@
|
|
|
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
|
-
import { matchError } from "better-result";
|
|
6
|
+
import { Result, TaggedError, matchError } from "better-result";
|
|
7
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
|
+
};
|
|
8
54
|
async function resolveProjectTarget(options) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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({
|
|
64
|
+
cwd: options.context.runtime.cwd,
|
|
65
|
+
projects,
|
|
66
|
+
commandName: options.commandName,
|
|
67
|
+
signal: options.context.runtime.signal
|
|
68
|
+
}));
|
|
21
69
|
});
|
|
22
70
|
}
|
|
23
71
|
async function inspectProjectBinding(options) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
+
});
|
|
29
92
|
});
|
|
30
|
-
if (target) return target;
|
|
31
|
-
return {
|
|
32
|
-
workspace: options.workspace,
|
|
33
|
-
project: null,
|
|
34
|
-
localBinding: { status: "not-linked" },
|
|
35
|
-
resolution: { projectSource: "unbound" },
|
|
36
|
-
...await buildProjectSetupSuggestion({
|
|
37
|
-
cwd: options.context.runtime.cwd,
|
|
38
|
-
projects,
|
|
39
|
-
commandName: options.commandName ?? "project show",
|
|
40
|
-
signal: options.context.runtime.signal
|
|
41
|
-
})
|
|
42
|
-
};
|
|
43
93
|
}
|
|
44
94
|
function projectNotFoundError(projectRef, workspace) {
|
|
95
|
+
return projectResolutionErrorToCliError(new ProjectNotFoundError(projectRef, workspace));
|
|
96
|
+
}
|
|
97
|
+
function projectNotFoundCliError(projectRef, workspace) {
|
|
45
98
|
return new CliError({
|
|
46
99
|
code: "PROJECT_NOT_FOUND",
|
|
47
100
|
domain: "project",
|
|
@@ -53,6 +106,9 @@ function projectNotFoundError(projectRef, workspace) {
|
|
|
53
106
|
});
|
|
54
107
|
}
|
|
55
108
|
function projectAmbiguousError(projectRef, matches) {
|
|
109
|
+
return projectResolutionErrorToCliError(new ProjectAmbiguousError(projectRef, matches));
|
|
110
|
+
}
|
|
111
|
+
function projectAmbiguousCliError(projectRef, matches) {
|
|
56
112
|
const firstMatch = matches[0];
|
|
57
113
|
const nextSteps = ["prisma-cli project list"];
|
|
58
114
|
if (firstMatch) nextSteps.push(`prisma-cli app deploy --project ${firstMatch.id}`);
|
|
@@ -70,7 +126,7 @@ function projectAmbiguousError(projectRef, matches) {
|
|
|
70
126
|
nextSteps
|
|
71
127
|
});
|
|
72
128
|
}
|
|
73
|
-
function
|
|
129
|
+
function localStateStaleCliError() {
|
|
74
130
|
return new CliError({
|
|
75
131
|
code: "LOCAL_STATE_STALE",
|
|
76
132
|
domain: "project",
|
|
@@ -83,12 +139,15 @@ function localStateStaleError() {
|
|
|
83
139
|
});
|
|
84
140
|
}
|
|
85
141
|
function localProjectWorkspaceMismatchError(options) {
|
|
142
|
+
return projectResolutionErrorToCliError(new LocalProjectWorkspaceMismatchError(options));
|
|
143
|
+
}
|
|
144
|
+
function localProjectWorkspaceMismatchCliError(options) {
|
|
86
145
|
return new CliError({
|
|
87
146
|
code: "LOCAL_PROJECT_WORKSPACE_MISMATCH",
|
|
88
147
|
domain: "project",
|
|
89
148
|
summary: "Project link uses another workspace",
|
|
90
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}).`,
|
|
91
|
-
fix: "
|
|
150
|
+
fix: "Switch to the linked workspace, or relink this directory to a project in the current workspace.",
|
|
92
151
|
meta: {
|
|
93
152
|
pinPath: LOCAL_RESOLUTION_PIN_RELATIVE_PATH,
|
|
94
153
|
pinnedWorkspaceId: options.pinnedWorkspaceId,
|
|
@@ -98,12 +157,37 @@ function localProjectWorkspaceMismatchError(options) {
|
|
|
98
157
|
},
|
|
99
158
|
exitCode: 1,
|
|
100
159
|
nextSteps: [
|
|
101
|
-
|
|
160
|
+
`prisma-cli auth workspace use ${options.pinnedWorkspaceId}`,
|
|
102
161
|
"prisma-cli project list",
|
|
103
162
|
"prisma-cli project link <id-or-name>"
|
|
104
163
|
]
|
|
105
164
|
});
|
|
106
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
|
+
}
|
|
107
191
|
async function buildProjectSetupSuggestion(options) {
|
|
108
192
|
const suggestedName = await inferTargetName(options.cwd, options.signal);
|
|
109
193
|
const candidates = sortProjects(options.projects.filter((project) => projectMatchesSuggestedName(project, suggestedName.name))).map(toProjectSummary);
|
|
@@ -116,17 +200,24 @@ async function buildProjectSetupSuggestion(options) {
|
|
|
116
200
|
}
|
|
117
201
|
async function projectSetupRequiredError(options) {
|
|
118
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;
|
|
119
210
|
return new CliError({
|
|
120
211
|
code: "PROJECT_SETUP_REQUIRED",
|
|
121
212
|
domain: "project",
|
|
122
213
|
summary: "Choose a Project before running this command",
|
|
123
|
-
why:
|
|
214
|
+
why: error.message,
|
|
124
215
|
fix: "Link the directory to an existing Project, or pass --project <id-or-name> for this command.",
|
|
125
216
|
meta: { ...suggestion },
|
|
126
217
|
exitCode: 1,
|
|
127
218
|
nextSteps: ["prisma-cli project list", ...suggestion.recoveryCommands],
|
|
128
219
|
nextActions: buildProjectSetupNextActions({
|
|
129
|
-
commandName:
|
|
220
|
+
commandName: error.commandName,
|
|
130
221
|
suggestedProjectName: suggestion.suggestedProjectName
|
|
131
222
|
})
|
|
132
223
|
});
|
|
@@ -200,9 +291,9 @@ function sortProjects(projects) {
|
|
|
200
291
|
}
|
|
201
292
|
function resolveExplicitProject(projectRef, projects, workspace) {
|
|
202
293
|
const matches = projects.filter((project) => project.id === projectRef || project.name === projectRef);
|
|
203
|
-
if (matches.length === 1) return matches[0];
|
|
204
|
-
if (matches.length > 1)
|
|
205
|
-
|
|
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));
|
|
206
297
|
}
|
|
207
298
|
function projectMatchesSuggestedName(project, suggestedName) {
|
|
208
299
|
return project.id === suggestedName || project.name === suggestedName || project.slug === suggestedName;
|
|
@@ -211,62 +302,62 @@ async function resolveDurablePlatformMapping() {
|
|
|
211
302
|
return null;
|
|
212
303
|
}
|
|
213
304
|
async function resolveBoundProjectTarget(options, projects, settings) {
|
|
214
|
-
if (options.explicitProject)
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
+
}
|
|
218
313
|
if (settings.allowEnvProjectId && options.envProjectId) {
|
|
219
314
|
const project = projects.find((candidate) => candidate.id === options.envProjectId);
|
|
220
|
-
if (!project)
|
|
221
|
-
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", {
|
|
222
317
|
targetName: options.envProjectId,
|
|
223
318
|
targetNameSource: "env"
|
|
224
|
-
});
|
|
319
|
+
}));
|
|
225
320
|
}
|
|
226
321
|
const localPin = settings.localPin;
|
|
227
|
-
if (!localPin) return null;
|
|
322
|
+
if (!localPin) return Result.ok(null);
|
|
228
323
|
if (localPin.kind === "present") {
|
|
229
|
-
if (localPin.pin.workspaceId !== options.workspace.id)
|
|
324
|
+
if (localPin.pin.workspaceId !== options.workspace.id) return Result.err(new LocalProjectWorkspaceMismatchError({
|
|
230
325
|
pinnedWorkspaceId: localPin.pin.workspaceId,
|
|
231
326
|
pinnedProjectId: localPin.pin.projectId,
|
|
232
327
|
activeWorkspace: options.workspace
|
|
233
|
-
});
|
|
328
|
+
}));
|
|
234
329
|
const project = projects.find((candidate) => candidate.id === localPin.pin.projectId);
|
|
235
|
-
if (!project)
|
|
236
|
-
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", {
|
|
237
332
|
targetName: project.name,
|
|
238
333
|
targetNameSource: "local-pin"
|
|
239
|
-
});
|
|
334
|
+
}));
|
|
240
335
|
}
|
|
241
336
|
const platformMapping = await resolveDurablePlatformMapping();
|
|
242
|
-
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", {
|
|
243
338
|
targetName: platformMapping.name,
|
|
244
339
|
targetNameSource: "platform-mapping"
|
|
245
|
-
});
|
|
246
|
-
return null;
|
|
340
|
+
}));
|
|
341
|
+
return Result.ok(null);
|
|
247
342
|
}
|
|
248
343
|
async function readImplicitLocalPin(options, settings) {
|
|
249
|
-
if (options.explicitProject || settings.allowEnvProjectId && options.envProjectId) return null;
|
|
250
|
-
const localPinResult = await readLocalResolutionPin(options.context.runtime.cwd, options.context.runtime.signal);
|
|
251
|
-
if (localPinResult.isErr())
|
|
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));
|
|
252
347
|
const localPin = localPinResult.value;
|
|
253
|
-
if (localPin.kind === "present" && localPin.pin.workspaceId !== options.workspace.id)
|
|
348
|
+
if (localPin.kind === "present" && localPin.pin.workspaceId !== options.workspace.id) return Result.err(new LocalProjectWorkspaceMismatchError({
|
|
254
349
|
pinnedWorkspaceId: localPin.pin.workspaceId,
|
|
255
350
|
pinnedProjectId: localPin.pin.projectId,
|
|
256
351
|
activeWorkspace: options.workspace
|
|
257
|
-
});
|
|
258
|
-
return localPin;
|
|
352
|
+
}));
|
|
353
|
+
return Result.ok(localPin);
|
|
259
354
|
}
|
|
260
355
|
function localPinReadErrorToProjectError(error) {
|
|
261
356
|
return matchError(error, {
|
|
262
|
-
LocalResolutionPinInvalidJsonError: () =>
|
|
263
|
-
LocalResolutionPinInvalidShapeError: () =>
|
|
264
|
-
LocalResolutionPinReadAbortedError: (error) =>
|
|
265
|
-
|
|
266
|
-
},
|
|
267
|
-
UnhandledException: (error) => {
|
|
268
|
-
throw error;
|
|
269
|
-
}
|
|
357
|
+
LocalResolutionPinInvalidJsonError: () => new LocalStateStaleError(),
|
|
358
|
+
LocalResolutionPinInvalidShapeError: () => new LocalStateStaleError(),
|
|
359
|
+
LocalResolutionPinReadAbortedError: (error) => error,
|
|
360
|
+
UnhandledException: (error) => error
|
|
270
361
|
});
|
|
271
362
|
}
|
|
272
363
|
function resolvedTarget(workspace, project, projectSource, resolutionDetails) {
|
|
@@ -292,4 +383,4 @@ function toProjectSummary(project) {
|
|
|
292
383
|
};
|
|
293
384
|
}
|
|
294
385
|
//#endregion
|
|
295
|
-
export { buildProjectSetupNextActions, inferTargetName, inspectProjectBinding, projectAmbiguousError, projectNotFoundError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects };
|
|
386
|
+
export { buildProjectSetupNextActions, inferTargetName, inspectProjectBinding, localProjectWorkspaceMismatchError, projectAmbiguousError, projectNotFoundError, projectResolutionErrorToCliError, resolveDurablePlatformMapping, resolveProjectTarget, sortProjects };
|