@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,17 +1,25 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolvePrismaCliPackageCommandFormatterSync } from "../lib/agent/cli-command.js";
|
|
2
|
+
import { formatCommandArgument } from "../shell/command-arguments.js";
|
|
3
|
+
import { CliError, authRequiredError, featureUnavailableError, usageError, workspaceAmbiguousError, workspaceNotAuthenticatedError, workspaceRequiredError } from "../shell/errors.js";
|
|
2
4
|
import { renderSummaryLine } from "../shell/ui.js";
|
|
3
5
|
import { canPrompt } from "../shell/runtime.js";
|
|
6
|
+
import { SERVICE_TOKEN_ENV_VAR } from "../lib/auth/client.js";
|
|
7
|
+
import { WorkspaceSelectionError } from "../adapters/token-storage.js";
|
|
8
|
+
import { createAppProvider } from "../lib/app/app-provider.js";
|
|
9
|
+
import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, readLocalResolutionPin, writeLocalResolutionPin } from "../lib/project/local-pin.js";
|
|
10
|
+
import { buildProjectSetupNextActions, inferTargetName, inspectProjectBinding, projectResolutionErrorToCliError, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
|
|
11
|
+
import { bindProjectToDirectory, isValidProjectSetupName, projectCreateFailedError, projectDirectoryBindingErrorToCliError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
|
|
4
12
|
import { requireComputeAuth } from "../lib/auth/guard.js";
|
|
5
|
-
import { formatCommandArgument } from "../shell/command-arguments.js";
|
|
6
|
-
import { readLocalResolutionPin } from "../lib/project/local-pin.js";
|
|
7
|
-
import { buildProjectSetupNextActions, inferTargetName, inspectProjectBinding, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
|
|
8
|
-
import { bindProjectToDirectory, isValidProjectSetupName, projectCreateFailedError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
|
|
9
13
|
import { promptForProjectSetupChoice } from "../lib/project/interactive-setup.js";
|
|
10
|
-
import { createPreviewAppProvider } from "../lib/app/preview-provider.js";
|
|
11
14
|
import { createCliUseCaseGateways } from "../use-cases/create-cli-gateways.js";
|
|
12
15
|
import { requireAuthenticatedAuthState } from "./auth.js";
|
|
13
16
|
import { parseGitHubRepositoryUrl, readGitOriginRemote } from "../adapters/git.js";
|
|
17
|
+
import { RecipientSessionInvalidError, resolveRecipientWorkspaceSession } from "../lib/auth/recipient.js";
|
|
18
|
+
import { createManagementProjectProvider, projectRemoveBlockedError, projectRenameFailedError, projectTransferRejectedError } from "../lib/project/provider.js";
|
|
14
19
|
import { createProjectUseCases } from "../use-cases/project.js";
|
|
20
|
+
import path from "node:path";
|
|
21
|
+
import { unlink } from "node:fs/promises";
|
|
22
|
+
import { matchError } from "better-result";
|
|
15
23
|
import open from "open";
|
|
16
24
|
//#region src/controllers/project.ts
|
|
17
25
|
const GITHUB_INSTALL_POLL_INTERVAL_MS = 2e3;
|
|
@@ -20,11 +28,24 @@ function isRealMode(context) {
|
|
|
20
28
|
return !context.runtime.fixturePath && !context.runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
|
|
21
29
|
}
|
|
22
30
|
async function readProjectListLocalBinding(cwd, workspace, projects, signal) {
|
|
23
|
-
const
|
|
31
|
+
const pinResult = await readLocalResolutionPin(cwd, signal);
|
|
32
|
+
if (pinResult.isErr()) return localPinReadErrorToInvalidLocalBinding(pinResult.error);
|
|
33
|
+
const pin = pinResult.value;
|
|
24
34
|
if (pin.kind === "present") return pin.pin.workspaceId === workspace.id && projects.some((project) => project.id === pin.pin.projectId) ? { status: "linked" } : { status: "invalid" };
|
|
25
|
-
if (pin.kind === "invalid") return { status: "invalid" };
|
|
26
35
|
return { status: "not-linked" };
|
|
27
36
|
}
|
|
37
|
+
function localPinReadErrorToInvalidLocalBinding(error) {
|
|
38
|
+
return matchError(error, {
|
|
39
|
+
LocalResolutionPinInvalidJsonError: () => ({ status: "invalid" }),
|
|
40
|
+
LocalResolutionPinInvalidShapeError: () => ({ status: "invalid" }),
|
|
41
|
+
LocalResolutionPinReadAbortedError: (error) => {
|
|
42
|
+
throw error;
|
|
43
|
+
},
|
|
44
|
+
UnhandledException: (error) => {
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
28
49
|
async function runProjectList(context) {
|
|
29
50
|
const authState = await requireAuthenticatedAuthState(context);
|
|
30
51
|
const workspace = authState.workspace;
|
|
@@ -83,17 +104,18 @@ async function runProjectShow(context, explicitProject) {
|
|
|
83
104
|
}) : []
|
|
84
105
|
};
|
|
85
106
|
}
|
|
86
|
-
async function runProjectCreate(context, projectName) {
|
|
107
|
+
async function runProjectCreate(context, projectName, options) {
|
|
87
108
|
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
88
109
|
if (!workspace) throw workspaceRequiredError();
|
|
89
110
|
if (!isValidProjectSetupName(projectName)) throw projectSetupNameRequiredError("project create");
|
|
90
111
|
if (!isRealMode(context)) throw featureUnavailableError("Project create is not available in fixture mode", "Creating Projects requires live platform integration.", "Rerun without fixture mode enabled to create a Project.", ["prisma-cli auth login"], "project");
|
|
91
112
|
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
92
113
|
if (!client) throw authRequiredError();
|
|
93
|
-
const provider =
|
|
114
|
+
const provider = createAppProvider(client);
|
|
94
115
|
const name = projectName.trim();
|
|
95
116
|
const created = await provider.createProject({
|
|
96
117
|
name,
|
|
118
|
+
region: options?.region,
|
|
97
119
|
signal: context.runtime.signal
|
|
98
120
|
}).catch((error) => {
|
|
99
121
|
throw projectCreateFailedError(error, name, workspace, {
|
|
@@ -102,12 +124,15 @@ async function runProjectCreate(context, projectName) {
|
|
|
102
124
|
fallbackFix: "Retry the command, or choose an existing Project with prisma-cli project link <id-or-name>."
|
|
103
125
|
});
|
|
104
126
|
});
|
|
127
|
+
const bindResult = await bindProjectToDirectory(context, workspace, {
|
|
128
|
+
id: created.id,
|
|
129
|
+
name: created.name,
|
|
130
|
+
...created.defaultRegion != null ? { defaultRegion: created.defaultRegion } : {}
|
|
131
|
+
}, "created");
|
|
132
|
+
if (bindResult.isErr()) throw projectDirectoryBindingErrorToCliError(bindResult.error);
|
|
105
133
|
return {
|
|
106
134
|
command: "project.create",
|
|
107
|
-
result:
|
|
108
|
-
id: created.id,
|
|
109
|
-
name: created.name
|
|
110
|
-
}, "created"),
|
|
135
|
+
result: bindResult.value,
|
|
111
136
|
warnings: [],
|
|
112
137
|
nextSteps: ["prisma-cli app deploy"]
|
|
113
138
|
};
|
|
@@ -120,11 +145,11 @@ async function runProjectLink(context, projectRef) {
|
|
|
120
145
|
if (isRealMode(context)) {
|
|
121
146
|
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
122
147
|
if (!client) throw authRequiredError();
|
|
123
|
-
provider =
|
|
148
|
+
provider = createAppProvider(client);
|
|
124
149
|
projects = await listRealWorkspaceProjects(client, workspace, context.runtime.signal);
|
|
125
150
|
} else projects = listFixtureWorkspaceProjects(context, workspace);
|
|
126
151
|
let result;
|
|
127
|
-
if (projectRef?.trim()) result = await
|
|
152
|
+
if (projectRef?.trim()) result = await requireProjectDirectoryBinding(context, workspace, toProjectSummary(resolveProjectForSetup(projectRef.trim(), projects, workspace)), "linked");
|
|
128
153
|
else if (canPrompt(context) && !context.flags.yes) result = await resolveInteractiveProjectLinkSetup(context, workspace, projects, provider);
|
|
129
154
|
else throw await projectLinkTargetRequiredError(context, projects);
|
|
130
155
|
return {
|
|
@@ -148,7 +173,12 @@ async function resolveInteractiveProjectLinkSetup(context, workspace, projects,
|
|
|
148
173
|
nextSteps: ["prisma-cli project link <id-or-name>", "prisma-cli project create <name>"]
|
|
149
174
|
}
|
|
150
175
|
});
|
|
151
|
-
return
|
|
176
|
+
return requireProjectDirectoryBinding(context, workspace, setup.project, setup.action);
|
|
177
|
+
}
|
|
178
|
+
async function requireProjectDirectoryBinding(context, workspace, project, action) {
|
|
179
|
+
const bindResult = await bindProjectToDirectory(context, workspace, project, action);
|
|
180
|
+
if (bindResult.isErr()) throw projectDirectoryBindingErrorToCliError(bindResult.error);
|
|
181
|
+
return bindResult.value;
|
|
152
182
|
}
|
|
153
183
|
async function createProjectForLinkSetup(provider, projectName, workspace, signal) {
|
|
154
184
|
const created = await provider.createProject({
|
|
@@ -196,6 +226,322 @@ async function projectLinkTargetRequiredError(context, projects) {
|
|
|
196
226
|
})
|
|
197
227
|
});
|
|
198
228
|
}
|
|
229
|
+
async function runProjectRename(context, newName, options) {
|
|
230
|
+
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
231
|
+
if (!workspace) throw workspaceRequiredError();
|
|
232
|
+
const name = newName.trim();
|
|
233
|
+
if (!isValidProjectSetupName(name)) throw projectSetupNameRequiredError("project rename");
|
|
234
|
+
const { provider, target } = await requireProjectCommandContext(context, workspace, options.project, "project rename");
|
|
235
|
+
const previousName = target.project.name;
|
|
236
|
+
return {
|
|
237
|
+
command: "project.rename",
|
|
238
|
+
result: {
|
|
239
|
+
workspace,
|
|
240
|
+
project: await provider.renameProject({
|
|
241
|
+
projectId: target.project.id,
|
|
242
|
+
name,
|
|
243
|
+
signal: context.runtime.signal
|
|
244
|
+
}),
|
|
245
|
+
previousName
|
|
246
|
+
},
|
|
247
|
+
warnings: [],
|
|
248
|
+
nextSteps: []
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
async function runProjectRemove(context, projectRef, options) {
|
|
252
|
+
const formatCommand = resolvePrismaCliPackageCommandFormatterSync(context.runtime.cwd);
|
|
253
|
+
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
254
|
+
if (!workspace) throw workspaceRequiredError();
|
|
255
|
+
const { provider, projects } = await requireProjectMutationContext(context, workspace);
|
|
256
|
+
const project = toProjectSummary(resolveProjectForSetup(projectRef.trim(), projects, workspace));
|
|
257
|
+
requireProjectExactConfirmation({
|
|
258
|
+
id: project.id,
|
|
259
|
+
confirm: options.confirm,
|
|
260
|
+
summary: "Confirm project removal",
|
|
261
|
+
why: "Removing a project is permanent, deletes its databases, and stops its apps, so it requires the exact project id.",
|
|
262
|
+
nextStep: formatCommand([
|
|
263
|
+
"project",
|
|
264
|
+
"remove",
|
|
265
|
+
project.id,
|
|
266
|
+
"--confirm",
|
|
267
|
+
project.id
|
|
268
|
+
])
|
|
269
|
+
});
|
|
270
|
+
await provider.removeProject({
|
|
271
|
+
projectId: project.id,
|
|
272
|
+
signal: context.runtime.signal
|
|
273
|
+
});
|
|
274
|
+
const warnings = [];
|
|
275
|
+
return {
|
|
276
|
+
command: "project.remove",
|
|
277
|
+
result: {
|
|
278
|
+
workspace,
|
|
279
|
+
project,
|
|
280
|
+
localPin: { cleared: await cleanupLocalPinForProject(context, project.id, { onError: (message) => warnings.push(message) }) }
|
|
281
|
+
},
|
|
282
|
+
warnings,
|
|
283
|
+
nextSteps: []
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
async function runProjectTransfer(context, projectRef, options) {
|
|
287
|
+
const formatCommand = resolvePrismaCliPackageCommandFormatterSync(context.runtime.cwd);
|
|
288
|
+
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
289
|
+
if (!workspace) throw workspaceRequiredError();
|
|
290
|
+
if (options.toWorkspace && options.recipientToken) throw usageError("Choose one transfer recipient source", "--to-workspace and --recipient-token are mutually exclusive.", "Pass either --to-workspace <id-or-name> or --recipient-token <token>.", [formatCommand([
|
|
291
|
+
"project",
|
|
292
|
+
"transfer",
|
|
293
|
+
"<project>",
|
|
294
|
+
"--to-workspace",
|
|
295
|
+
"<id-or-name>",
|
|
296
|
+
"--confirm",
|
|
297
|
+
"<project-id>"
|
|
298
|
+
])], "project");
|
|
299
|
+
if (!options.toWorkspace?.trim() && !options.recipientToken?.trim()) throw transferRecipientRequiredError(formatCommand);
|
|
300
|
+
const { provider, projects } = await requireProjectMutationContext(context, workspace);
|
|
301
|
+
const project = toProjectSummary(resolveProjectForSetup(projectRef.trim(), projects, workspace));
|
|
302
|
+
requireProjectExactConfirmation({
|
|
303
|
+
id: project.id,
|
|
304
|
+
confirm: options.confirm,
|
|
305
|
+
summary: "Confirm project transfer",
|
|
306
|
+
why: "Transferring moves the project to another workspace and this workspace loses access, so it requires the exact project id.",
|
|
307
|
+
nextStep: `${formatCommand([
|
|
308
|
+
"project",
|
|
309
|
+
"transfer",
|
|
310
|
+
project.id
|
|
311
|
+
])} ${options.toWorkspace ? `--to-workspace ${formatCommandArgument(options.toWorkspace)}` : "--recipient-token <token>"} --confirm ${project.id}`
|
|
312
|
+
});
|
|
313
|
+
const recipient = await resolveTransferRecipient(context, options);
|
|
314
|
+
await provider.transferProject({
|
|
315
|
+
projectId: project.id,
|
|
316
|
+
recipientAccessToken: recipient.accessToken,
|
|
317
|
+
signal: context.runtime.signal
|
|
318
|
+
});
|
|
319
|
+
const warnings = [];
|
|
320
|
+
const pinAction = await rewriteOrClearLocalPinForProject(context, project.id, recipient.workspaceId, { onError: (message) => warnings.push(message) });
|
|
321
|
+
return {
|
|
322
|
+
command: "project.transfer",
|
|
323
|
+
result: {
|
|
324
|
+
workspace,
|
|
325
|
+
project,
|
|
326
|
+
recipient: {
|
|
327
|
+
workspaceId: recipient.workspaceId,
|
|
328
|
+
workspaceName: recipient.workspaceName,
|
|
329
|
+
source: recipient.source
|
|
330
|
+
},
|
|
331
|
+
localPin: { action: pinAction }
|
|
332
|
+
},
|
|
333
|
+
warnings,
|
|
334
|
+
nextSteps: options.toWorkspace ? [`${formatCommand([
|
|
335
|
+
"auth",
|
|
336
|
+
"workspace",
|
|
337
|
+
"use"
|
|
338
|
+
])} ${formatCommandArgument(options.toWorkspace)}`] : []
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
async function resolveTransferRecipient(context, options) {
|
|
342
|
+
const formatCommand = resolvePrismaCliPackageCommandFormatterSync(context.runtime.cwd);
|
|
343
|
+
const recipientToken = options.recipientToken?.trim();
|
|
344
|
+
if (recipientToken) return {
|
|
345
|
+
accessToken: recipientToken,
|
|
346
|
+
workspaceId: isRealMode(context) ? null : recipientToken,
|
|
347
|
+
workspaceName: null,
|
|
348
|
+
source: "recipient-token"
|
|
349
|
+
};
|
|
350
|
+
const workspaceRef = options.toWorkspace?.trim();
|
|
351
|
+
if (!workspaceRef) throw transferRecipientRequiredError(formatCommand);
|
|
352
|
+
if (!isRealMode(context)) {
|
|
353
|
+
const matches = context.api.listWorkspaces().filter((candidate) => candidate.id === workspaceRef || candidate.name.toLowerCase() === workspaceRef.toLowerCase());
|
|
354
|
+
if (matches.length === 0) throw workspaceNotAuthenticatedError(workspaceRef);
|
|
355
|
+
if (matches.length > 1) throw workspaceAmbiguousError(workspaceRef, matches.map((match) => ({
|
|
356
|
+
id: match.id,
|
|
357
|
+
name: match.name,
|
|
358
|
+
credentialWorkspaceId: match.id
|
|
359
|
+
})));
|
|
360
|
+
return {
|
|
361
|
+
accessToken: matches[0].id,
|
|
362
|
+
workspaceId: matches[0].id,
|
|
363
|
+
workspaceName: matches[0].name,
|
|
364
|
+
source: "workspace-session"
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
if (context.runtime.env["PRISMA_SERVICE_TOKEN"] !== void 0) throw transferRecipientUnavailableError(formatCommand);
|
|
368
|
+
try {
|
|
369
|
+
const session = await resolveRecipientWorkspaceSession(workspaceRef, context.runtime.env, context.runtime.signal);
|
|
370
|
+
return {
|
|
371
|
+
accessToken: session.accessToken,
|
|
372
|
+
workspaceId: session.workspace.id,
|
|
373
|
+
workspaceName: session.workspace.name,
|
|
374
|
+
source: "workspace-session"
|
|
375
|
+
};
|
|
376
|
+
} catch (error) {
|
|
377
|
+
if (error instanceof WorkspaceSelectionError) {
|
|
378
|
+
if (error.reason === "ambiguous") throw workspaceAmbiguousError(error.workspaceRef ?? workspaceRef, error.matches.map((match) => ({
|
|
379
|
+
id: match.id,
|
|
380
|
+
name: match.name,
|
|
381
|
+
credentialWorkspaceId: match.credentialWorkspaceId
|
|
382
|
+
})));
|
|
383
|
+
throw workspaceNotAuthenticatedError(error.workspaceRef ?? workspaceRef);
|
|
384
|
+
}
|
|
385
|
+
if (error instanceof RecipientSessionInvalidError) throw workspaceNotAuthenticatedError(error.workspaceRef);
|
|
386
|
+
throw error;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
async function requireProjectMutationContext(context, workspace) {
|
|
390
|
+
if (isRealMode(context)) {
|
|
391
|
+
const client = await requireProjectClient(context);
|
|
392
|
+
return {
|
|
393
|
+
provider: createManagementProjectProvider(client),
|
|
394
|
+
projects: await listRealWorkspaceProjects(client, workspace, context.runtime.signal)
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
return {
|
|
398
|
+
provider: createFixtureProjectProvider(context),
|
|
399
|
+
projects: listFixtureWorkspaceProjects(context, workspace)
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
async function requireProjectCommandContext(context, workspace, explicitProject, commandName) {
|
|
403
|
+
const client = isRealMode(context) ? await requireProjectClient(context) : null;
|
|
404
|
+
const listProjects = async () => client ? listRealWorkspaceProjects(client, workspace, context.runtime.signal) : listFixtureWorkspaceProjects(context, workspace);
|
|
405
|
+
const targetResult = await resolveProjectTarget({
|
|
406
|
+
context,
|
|
407
|
+
workspace,
|
|
408
|
+
explicitProject,
|
|
409
|
+
listProjects,
|
|
410
|
+
commandName
|
|
411
|
+
});
|
|
412
|
+
if (targetResult.isErr()) throw projectResolutionErrorToCliError(targetResult.error);
|
|
413
|
+
return {
|
|
414
|
+
provider: client ? createManagementProjectProvider(client) : createFixtureProjectProvider(context),
|
|
415
|
+
target: targetResult.value
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
async function requireProjectClient(context) {
|
|
419
|
+
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
420
|
+
if (!client) throw authRequiredError();
|
|
421
|
+
return client;
|
|
422
|
+
}
|
|
423
|
+
function createFixtureProjectProvider(context) {
|
|
424
|
+
const fixtureFormatCommand = resolvePrismaCliPackageCommandFormatterSync(context.runtime.cwd);
|
|
425
|
+
return {
|
|
426
|
+
async renameProject(options) {
|
|
427
|
+
const renamed = context.api.renameProject(options.projectId, options.name);
|
|
428
|
+
if (!renamed) throw projectRenameFailedError(options.name, void 0);
|
|
429
|
+
return {
|
|
430
|
+
id: renamed.id,
|
|
431
|
+
name: renamed.name,
|
|
432
|
+
...renamed.url ? { url: renamed.url } : {}
|
|
433
|
+
};
|
|
434
|
+
},
|
|
435
|
+
async removeProject(options) {
|
|
436
|
+
const removed = context.api.removeProject(options.projectId);
|
|
437
|
+
if (removed.outcome === "blocked") throw projectRemoveBlockedError(options.projectId, void 0);
|
|
438
|
+
if (removed.outcome === "not-found") throw new CliError({
|
|
439
|
+
code: "PROJECT_NOT_FOUND",
|
|
440
|
+
domain: "project",
|
|
441
|
+
summary: "Project not found",
|
|
442
|
+
why: `No project matched "${options.projectId}".`,
|
|
443
|
+
fix: `Pass a project id or name from ${fixtureFormatCommand(["project", "list"])}.`,
|
|
444
|
+
exitCode: 1,
|
|
445
|
+
nextSteps: [fixtureFormatCommand(["project", "list"])]
|
|
446
|
+
});
|
|
447
|
+
},
|
|
448
|
+
async transferProject(options) {
|
|
449
|
+
if (context.api.transferProject(options.projectId, options.recipientAccessToken).outcome !== "transferred") throw projectTransferRejectedError(options.projectId, void 0);
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
function requireProjectExactConfirmation(options) {
|
|
454
|
+
if (options.confirm === options.id) return;
|
|
455
|
+
throw new CliError({
|
|
456
|
+
code: "CONFIRMATION_REQUIRED",
|
|
457
|
+
domain: "project",
|
|
458
|
+
summary: options.summary,
|
|
459
|
+
why: options.why,
|
|
460
|
+
fix: `Rerun with --confirm ${options.id}.`,
|
|
461
|
+
exitCode: 2,
|
|
462
|
+
nextSteps: [options.nextStep],
|
|
463
|
+
meta: {
|
|
464
|
+
expectedConfirm: options.id,
|
|
465
|
+
receivedConfirm: options.confirm ?? null
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
function transferRecipientRequiredError(formatCommand) {
|
|
470
|
+
return new CliError({
|
|
471
|
+
code: "TRANSFER_RECIPIENT_REQUIRED",
|
|
472
|
+
domain: "project",
|
|
473
|
+
summary: "Transfer recipient required",
|
|
474
|
+
why: "Project transfer needs the receiving workspace.",
|
|
475
|
+
fix: "Pass --to-workspace <id-or-name> for a locally authenticated workspace, or --recipient-token <token> for a cross-account transfer.",
|
|
476
|
+
exitCode: 2,
|
|
477
|
+
nextSteps: [formatCommand([
|
|
478
|
+
"auth",
|
|
479
|
+
"workspace",
|
|
480
|
+
"list"
|
|
481
|
+
]), formatCommand([
|
|
482
|
+
"project",
|
|
483
|
+
"transfer",
|
|
484
|
+
"<project>",
|
|
485
|
+
"--to-workspace",
|
|
486
|
+
"<id-or-name>",
|
|
487
|
+
"--confirm",
|
|
488
|
+
"<project-id>"
|
|
489
|
+
])]
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
function transferRecipientUnavailableError(formatCommand) {
|
|
493
|
+
return new CliError({
|
|
494
|
+
code: "TRANSFER_RECIPIENT_UNAVAILABLE",
|
|
495
|
+
domain: "project",
|
|
496
|
+
summary: "Local workspace sessions are unavailable",
|
|
497
|
+
why: `--to-workspace resolves locally stored OAuth sessions, but ${SERVICE_TOKEN_ENV_VAR} is set and service-token mode does not read them.`,
|
|
498
|
+
fix: "Pass --recipient-token <token> with an access token for the receiving workspace, or unset the service token.",
|
|
499
|
+
exitCode: 1,
|
|
500
|
+
nextSteps: [formatCommand([
|
|
501
|
+
"project",
|
|
502
|
+
"transfer",
|
|
503
|
+
"<project>",
|
|
504
|
+
"--recipient-token",
|
|
505
|
+
"<token>",
|
|
506
|
+
"--confirm",
|
|
507
|
+
"<project-id>"
|
|
508
|
+
])]
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
async function cleanupLocalPinForProject(context, projectId, hooks) {
|
|
512
|
+
const pinResult = await readLocalResolutionPin(context.runtime.cwd, context.runtime.signal);
|
|
513
|
+
if (pinResult.isErr()) return false;
|
|
514
|
+
const pin = pinResult.value;
|
|
515
|
+
if (pin.kind !== "present" || pin.pin.projectId !== projectId) return false;
|
|
516
|
+
try {
|
|
517
|
+
await unlink(path.join(context.runtime.cwd, LOCAL_RESOLUTION_PIN_RELATIVE_PATH));
|
|
518
|
+
return true;
|
|
519
|
+
} catch {
|
|
520
|
+
hooks.onError(`The local pin ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} points at the removed project but could not be deleted.`);
|
|
521
|
+
return false;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
async function rewriteOrClearLocalPinForProject(context, projectId, recipientWorkspaceId, hooks) {
|
|
525
|
+
const pinResult = await readLocalResolutionPin(context.runtime.cwd, context.runtime.signal);
|
|
526
|
+
if (pinResult.isErr()) return "none";
|
|
527
|
+
const pin = pinResult.value;
|
|
528
|
+
if (pin.kind !== "present" || pin.pin.projectId !== projectId) return "none";
|
|
529
|
+
if (recipientWorkspaceId) {
|
|
530
|
+
if ((await writeLocalResolutionPin(context.runtime.cwd, {
|
|
531
|
+
workspaceId: recipientWorkspaceId,
|
|
532
|
+
projectId
|
|
533
|
+
}, context.runtime.signal)).isOk()) return "rewritten";
|
|
534
|
+
hooks.onError(`The local pin ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} points at the transferred project but could not be rewritten.`);
|
|
535
|
+
return "none";
|
|
536
|
+
}
|
|
537
|
+
try {
|
|
538
|
+
await unlink(path.join(context.runtime.cwd, LOCAL_RESOLUTION_PIN_RELATIVE_PATH));
|
|
539
|
+
return "cleared";
|
|
540
|
+
} catch {
|
|
541
|
+
hooks.onError(`The local pin ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} points at the transferred project but could not be cleared.`);
|
|
542
|
+
return "none";
|
|
543
|
+
}
|
|
544
|
+
}
|
|
199
545
|
async function runGitConnect(context, gitUrl, options = {}) {
|
|
200
546
|
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
201
547
|
if (!workspace) throw workspaceRequiredError();
|
|
@@ -309,42 +655,50 @@ async function runGitDisconnect(context, options = {}) {
|
|
|
309
655
|
async function resolveProjectShowInRealMode(context, workspace, explicitProject) {
|
|
310
656
|
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
311
657
|
if (!client) throw authRequiredError();
|
|
312
|
-
|
|
658
|
+
const result = await inspectProjectBinding({
|
|
313
659
|
context,
|
|
314
660
|
workspace,
|
|
315
661
|
explicitProject,
|
|
316
662
|
listProjects: () => listRealWorkspaceProjects(client, workspace, context.runtime.signal),
|
|
317
663
|
commandName: "project show"
|
|
318
664
|
});
|
|
665
|
+
if (result.isErr()) throw projectResolutionErrorToCliError(result.error);
|
|
666
|
+
return result.value;
|
|
319
667
|
}
|
|
320
668
|
async function resolveRequiredProjectInRealMode(context, workspace, explicitProject, commandName) {
|
|
321
669
|
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
322
670
|
if (!client) throw authRequiredError();
|
|
323
|
-
|
|
671
|
+
const result = await resolveProjectTarget({
|
|
324
672
|
context,
|
|
325
673
|
workspace,
|
|
326
674
|
explicitProject,
|
|
327
675
|
listProjects: () => listRealWorkspaceProjects(client, workspace, context.runtime.signal),
|
|
328
676
|
commandName
|
|
329
677
|
});
|
|
678
|
+
if (result.isErr()) throw projectResolutionErrorToCliError(result.error);
|
|
679
|
+
return result.value;
|
|
330
680
|
}
|
|
331
681
|
async function resolveProjectShowInFixtureMode(context, workspace, explicitProject) {
|
|
332
|
-
|
|
682
|
+
const result = await inspectProjectBinding({
|
|
333
683
|
context,
|
|
334
684
|
workspace,
|
|
335
685
|
explicitProject,
|
|
336
686
|
listProjects: async () => listFixtureWorkspaceProjects(context, workspace),
|
|
337
687
|
commandName: "project show"
|
|
338
688
|
});
|
|
689
|
+
if (result.isErr()) throw projectResolutionErrorToCliError(result.error);
|
|
690
|
+
return result.value;
|
|
339
691
|
}
|
|
340
692
|
async function resolveRequiredProjectInFixtureMode(context, workspace, explicitProject, commandName) {
|
|
341
|
-
|
|
693
|
+
const result = await resolveProjectTarget({
|
|
342
694
|
context,
|
|
343
695
|
workspace,
|
|
344
696
|
explicitProject,
|
|
345
697
|
listProjects: async () => listFixtureWorkspaceProjects(context, workspace),
|
|
346
698
|
commandName
|
|
347
699
|
});
|
|
700
|
+
if (result.isErr()) throw projectResolutionErrorToCliError(result.error);
|
|
701
|
+
return result.value;
|
|
348
702
|
}
|
|
349
703
|
async function listRealWorkspaceProjects(client, workspace, signal) {
|
|
350
704
|
const { data } = await client.GET("/v1/projects", { signal });
|
|
@@ -352,6 +706,7 @@ async function listRealWorkspaceProjects(client, workspace, signal) {
|
|
|
352
706
|
id: project.id,
|
|
353
707
|
name: project.name,
|
|
354
708
|
..."url" in project && typeof project.url === "string" ? { url: project.url } : {},
|
|
709
|
+
..."defaultRegion" in project ? { defaultRegion: project.defaultRegion } : {},
|
|
355
710
|
slug: "slug" in project && typeof project.slug === "string" ? project.slug : null,
|
|
356
711
|
workspace: {
|
|
357
712
|
id: project.workspace.id,
|
|
@@ -699,4 +1054,4 @@ function repoConnectionFixForStatus(status) {
|
|
|
699
1054
|
return "Re-run with --trace for the underlying API response details.";
|
|
700
1055
|
}
|
|
701
1056
|
//#endregion
|
|
702
|
-
export { listRealWorkspaceProjects, runGitConnect, runGitDisconnect, runProjectCreate, runProjectLink, runProjectList, runProjectShow };
|
|
1057
|
+
export { listFixtureWorkspaceProjects, listRealWorkspaceProjects, runGitConnect, runGitDisconnect, runProjectCreate, runProjectLink, runProjectList, runProjectRemove, runProjectRename, runProjectShow, runProjectTransfer };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { formatPrismaCliCommand } from "../../shell/cli-command.js";
|
|
2
|
+
import { resolvePackageRunner, resolvePackageRunnerSync } from "./package-manager.js";
|
|
3
|
+
//#region src/lib/agent/cli-command.ts
|
|
4
|
+
async function resolvePrismaCliPackageCommandFormatter(options) {
|
|
5
|
+
return createPrismaCliPackageCommandFormatter(await resolvePackageRunner(options));
|
|
6
|
+
}
|
|
7
|
+
async function resolvePrismaCliPackageCommand(options) {
|
|
8
|
+
return (await resolvePrismaCliPackageCommandFormatter(options))(options.args);
|
|
9
|
+
}
|
|
10
|
+
function resolvePrismaCliPackageCommandFormatterSync(cwd) {
|
|
11
|
+
return createPrismaCliPackageCommandFormatter(resolvePackageRunnerSync(cwd));
|
|
12
|
+
}
|
|
13
|
+
function resolvePrismaCliPackageCommandSync(cwd, args) {
|
|
14
|
+
return resolvePrismaCliPackageCommandFormatterSync(cwd)(args);
|
|
15
|
+
}
|
|
16
|
+
function createPrismaCliPackageCommandFormatter(packageRunner) {
|
|
17
|
+
return (args) => formatPrismaCliCommand(args, { packageRunner });
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
export { resolvePrismaCliPackageCommand, resolvePrismaCliPackageCommandFormatterSync, resolvePrismaCliPackageCommandSync };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//#region src/lib/agent/constants.ts
|
|
2
|
+
const PRISMA_SKILLS_SOURCE = "prisma/skills";
|
|
3
|
+
const PRISMA_SKILLS_LOCK_FILENAME = "skills-lock.json";
|
|
4
|
+
const SKILLS_CLI_PACKAGE = "skills@latest";
|
|
5
|
+
const DEFAULT_PRISMA_AGENT_SKILLS = ["*"];
|
|
6
|
+
const PRISMA_COMPUTE_AGENT_SKILL = "prisma-compute";
|
|
7
|
+
const DEFAULT_PRISMA_AGENT_TARGETS = ["codex", "claude-code"];
|
|
8
|
+
const PRISMA_AGENT_INSTALL_ARGS = ["agent", "install"];
|
|
9
|
+
const PRISMA_AGENT_UPDATE_ARGS = ["agent", "update"];
|
|
10
|
+
const PRISMA_AGENT_STATUS_ARGS = ["agent", "status"];
|
|
11
|
+
//#endregion
|
|
12
|
+
export { DEFAULT_PRISMA_AGENT_SKILLS, DEFAULT_PRISMA_AGENT_TARGETS, PRISMA_AGENT_INSTALL_ARGS, PRISMA_AGENT_STATUS_ARGS, PRISMA_AGENT_UPDATE_ARGS, PRISMA_COMPUTE_AGENT_SKILL, PRISMA_SKILLS_LOCK_FILENAME, PRISMA_SKILLS_SOURCE, SKILLS_CLI_PACKAGE };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { readFileSync, statSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
//#region src/lib/agent/package-manager.ts
|
|
4
|
+
const LOCKFILE_PACKAGE_MANAGERS = [
|
|
5
|
+
{
|
|
6
|
+
packageManager: "bun",
|
|
7
|
+
fileNames: ["bun.lock", "bun.lockb"]
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
packageManager: "pnpm",
|
|
11
|
+
fileNames: ["pnpm-lock.yaml", "pnpm-workspace.yaml"]
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
packageManager: "yarn",
|
|
15
|
+
fileNames: ["yarn.lock"]
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
packageManager: "npm",
|
|
19
|
+
fileNames: ["package-lock.json", "npm-shrinkwrap.json"]
|
|
20
|
+
}
|
|
21
|
+
];
|
|
22
|
+
async function resolveSkillsPackageRunner(options) {
|
|
23
|
+
return resolvePackageRunner(options);
|
|
24
|
+
}
|
|
25
|
+
async function resolvePackageRunner(options) {
|
|
26
|
+
options.signal.throwIfAborted();
|
|
27
|
+
const packageManager = detectPackageManagerSync(options.cwd, options.signal) ?? "npm";
|
|
28
|
+
options.signal.throwIfAborted();
|
|
29
|
+
return packageRunnerForPackageManager(packageManager);
|
|
30
|
+
}
|
|
31
|
+
function resolvePackageRunnerSync(cwd) {
|
|
32
|
+
return packageRunnerForPackageManager(detectPackageManagerSync(cwd) ?? "npm");
|
|
33
|
+
}
|
|
34
|
+
function detectPackageManagerSync(cwd, signal) {
|
|
35
|
+
let directory = path.resolve(cwd);
|
|
36
|
+
while (true) {
|
|
37
|
+
signal?.throwIfAborted();
|
|
38
|
+
const packageJsonManager = readPackageJsonPackageManager(directory);
|
|
39
|
+
if (packageJsonManager) return packageJsonManager;
|
|
40
|
+
const lockfileManager = readLockfilePackageManager(directory, signal);
|
|
41
|
+
if (lockfileManager) return lockfileManager;
|
|
42
|
+
const parent = path.dirname(directory);
|
|
43
|
+
if (parent === directory) return null;
|
|
44
|
+
directory = parent;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function readPackageJsonPackageManager(directory) {
|
|
48
|
+
const packageJsonPath = path.join(directory, "package.json");
|
|
49
|
+
let content;
|
|
50
|
+
try {
|
|
51
|
+
content = readFileSync(packageJsonPath, "utf8");
|
|
52
|
+
} catch (error) {
|
|
53
|
+
if (isMissingFileError(error)) return null;
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
return parsePackageManager(JSON.parse(content).packageManager);
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function readLockfilePackageManager(directory, signal) {
|
|
63
|
+
for (const candidate of LOCKFILE_PACKAGE_MANAGERS) for (const fileName of candidate.fileNames) {
|
|
64
|
+
signal?.throwIfAborted();
|
|
65
|
+
if (fileExists(path.join(directory, fileName))) return candidate.packageManager;
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
function fileExists(filePath) {
|
|
70
|
+
try {
|
|
71
|
+
return statSync(filePath).isFile();
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (isMissingFileError(error)) return false;
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function parsePackageManager(value) {
|
|
78
|
+
if (typeof value !== "string") return null;
|
|
79
|
+
const normalized = value.trim().toLowerCase();
|
|
80
|
+
if (normalized === "bun" || normalized.startsWith("bun@")) return "bun";
|
|
81
|
+
if (normalized === "pnpm" || normalized.startsWith("pnpm@")) return "pnpm";
|
|
82
|
+
if (normalized === "yarn" || normalized.startsWith("yarn@")) return "yarn";
|
|
83
|
+
if (normalized === "npm" || normalized.startsWith("npm@")) return "npm";
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
function packageRunnerForPackageManager(packageManager) {
|
|
87
|
+
switch (packageManager) {
|
|
88
|
+
case "bun": return ["bunx"];
|
|
89
|
+
case "pnpm": return ["pnpm", "dlx"];
|
|
90
|
+
case "yarn": return ["yarn", "dlx"];
|
|
91
|
+
case "npm": return ["npx", "-y"];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function isMissingFileError(error) {
|
|
95
|
+
const code = error.code;
|
|
96
|
+
return code === "ENOENT" || code === "ENOTDIR";
|
|
97
|
+
}
|
|
98
|
+
//#endregion
|
|
99
|
+
export { detectPackageManagerSync, resolvePackageRunner, resolvePackageRunnerSync, resolveSkillsPackageRunner };
|