@prisma/cli 3.0.0-dev.97.1 → 8.0.0-rc.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 +3 -14
- package/dist/adapters/local-state.js +14 -3
- package/dist/{lib/auth → auth}/client.js +11 -3
- package/dist/auth/errors.js +50 -0
- package/dist/{lib/auth → auth}/guard.js +7 -6
- package/dist/{lib/auth → auth}/login.js +147 -6
- package/dist/{lib/auth/auth-ops.js → auth/operations.js} +63 -13
- package/dist/auth/recipient.js +42 -0
- package/dist/{adapters → auth}/token-storage.js +28 -7
- package/dist/auth/workspaces.js +171 -0
- package/dist/cli-command.js +14 -0
- package/dist/cli-name.js +12 -0
- package/dist/cli.js +1 -1
- package/dist/cli2.js +25 -8
- package/dist/command-arguments.js +12 -0
- package/dist/commands/agent/index.js +60 -0
- package/dist/commands/app/index.js +11 -6
- package/dist/commands/auth/index.js +2 -3
- package/dist/commands/bucket/index.js +123 -0
- package/dist/commands/build/index.js +29 -0
- package/dist/commands/database/index.js +92 -2
- 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 +3 -2
- package/dist/controllers/app-env-file.js +1 -1
- package/dist/controllers/app-env.js +5 -5
- package/dist/controllers/app.js +423 -358
- package/dist/controllers/auth.js +62 -254
- package/dist/controllers/branch.js +5 -16
- package/dist/controllers/bucket.js +184 -0
- package/dist/controllers/build.js +88 -0
- package/dist/controllers/database.js +230 -85
- package/dist/controllers/feedback.js +90 -0
- package/dist/controllers/init.js +814 -0
- package/dist/controllers/project.js +345 -184
- package/dist/controllers/select-prompt-port.js +1 -0
- package/dist/{shell/errors.js → errors.js} +8 -40
- 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/app-provider.js +168 -95
- package/dist/lib/app/branch-database-deploy.js +70 -42
- package/dist/lib/app/branch-database.js +39 -20
- package/dist/lib/app/build-settings.js +8 -3
- package/dist/lib/app/build.js +16 -14
- package/dist/lib/app/bun-project.js +1 -1
- package/dist/lib/app/compute-config.js +29 -31
- package/dist/lib/app/deploy-plan.js +1 -0
- package/dist/lib/app/deploy-progress.js +7 -7
- package/dist/lib/app/env-config.js +1 -1
- package/dist/lib/app/env-file.js +2 -2
- package/dist/lib/app/env-vars.js +1 -1
- package/dist/lib/app/local-dev.js +1 -1
- package/dist/lib/app/production-deploy-gate.js +3 -2
- package/dist/lib/bucket/provider.js +139 -0
- package/dist/lib/database/provider.js +235 -17
- package/dist/lib/diagnostics.js +2 -1
- package/dist/lib/feedback.js +15 -0
- package/dist/lib/git/local-branch.js +1 -1
- package/dist/lib/project/interactive-setup.js +6 -5
- package/dist/lib/project/local-pin.js +1 -1
- package/dist/lib/project/provider.js +93 -0
- package/dist/lib/project/resolution.js +11 -8
- package/dist/lib/project/setup.js +9 -6
- package/dist/lib/version.js +3 -2
- package/dist/lib/workspace-id.js +18 -0
- package/dist/payload-B88Qvzxk-CrtTXSOe.js +34 -0
- package/dist/presenters/agent.js +74 -0
- package/dist/presenters/app.js +33 -7
- package/dist/presenters/auth.js +3 -2
- package/dist/presenters/bucket.js +174 -0
- package/dist/presenters/database.js +193 -5
- package/dist/presenters/feedback.js +26 -0
- package/dist/presenters/init.js +30 -0
- package/dist/presenters/project.js +98 -4
- package/dist/shell/cli-command.js +2 -0
- package/dist/shell/command-meta.js +303 -4
- package/dist/shell/command-runner.js +13 -6
- package/dist/shell/help.js +6 -5
- package/dist/shell/output.js +65 -3
- package/dist/shell/prompt.js +12 -5
- package/dist/shell/runtime.js +4 -21
- package/dist/state-dir.js +12 -0
- package/dist/{shell/update-check.js → update-check.js} +6 -5
- package/dist/v8/cli.js +14699 -0
- package/dist/v8/sender.js +192 -0
- package/package.json +21 -13
- package/dist/adapters/mock-api.js +0 -136
- package/dist/lib/app/app-interaction.js +0 -5
- package/dist/shell/command-arguments.js +0 -6
- package/dist/use-cases/auth.js +0 -145
- package/dist/use-cases/branch.js +0 -47
- package/dist/use-cases/create-cli-gateways.js +0 -68
- package/dist/use-cases/project.js +0 -30
|
@@ -1,30 +1,33 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { resolvePrismaCliPackageCommandFormatterSync } from "../lib/agent/cli-command.js";
|
|
2
|
+
import { formatCommandArgument } from "../command-arguments.js";
|
|
3
|
+
import { CliError, authRequiredError, usageError, workspaceRequiredError } from "../errors.js";
|
|
3
4
|
import { renderSummaryLine } from "../shell/ui.js";
|
|
5
|
+
import { WorkspaceSelectionError } from "../auth/token-storage.js";
|
|
6
|
+
import { authenticatedManagementApiClient } from "../auth/guard.js";
|
|
4
7
|
import { canPrompt } from "../shell/runtime.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
8
|
+
import { createAppProvider } from "../lib/app/app-provider.js";
|
|
9
|
+
import { LOCAL_RESOLUTION_PIN_RELATIVE_PATH, readLocalResolutionPin, writeLocalResolutionPin } from "../lib/project/local-pin.js";
|
|
7
10
|
import { buildProjectSetupNextActions, inferTargetName, inspectProjectBinding, projectResolutionErrorToCliError, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
|
|
8
11
|
import { bindProjectToDirectory, isValidProjectSetupName, projectCreateFailedError, projectDirectoryBindingErrorToCliError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
|
|
9
|
-
import { requireComputeAuth } from "../lib/auth/guard.js";
|
|
10
12
|
import { promptForProjectSetupChoice } from "../lib/project/interactive-setup.js";
|
|
11
|
-
import {
|
|
13
|
+
import { workspaceAmbiguousError, workspaceNotAuthenticatedError } from "../auth/errors.js";
|
|
12
14
|
import { requireAuthenticatedAuthState } from "./auth.js";
|
|
13
15
|
import { parseGitHubRepositoryUrl, readGitOriginRemote } from "../adapters/git.js";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
+
import { RecipientSessionInvalidError, resolveRecipientWorkspaceSession } from "../auth/recipient.js";
|
|
17
|
+
import { createManagementProjectProvider, projectApiError } from "../lib/project/provider.js";
|
|
18
|
+
import path from "node:path";
|
|
19
|
+
import { unlink } from "node:fs/promises";
|
|
20
|
+
import { SERVICE_TOKEN_ENV_VAR } from "@prisma/cli-engine";
|
|
16
21
|
import open from "open";
|
|
22
|
+
import { matchError } from "better-result";
|
|
17
23
|
//#region src/controllers/project.ts
|
|
18
24
|
const GITHUB_INSTALL_POLL_INTERVAL_MS = 2e3;
|
|
19
25
|
const GITHUB_INSTALL_POLL_TIMEOUT_MS = 12e4;
|
|
20
|
-
function
|
|
21
|
-
return !context.runtime.fixturePath && !context.runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
|
|
22
|
-
}
|
|
23
|
-
async function readProjectListLocalBinding(cwd, workspace, projects, signal) {
|
|
26
|
+
async function readProjectListLocalBinding(cwd, projects, signal) {
|
|
24
27
|
const pinResult = await readLocalResolutionPin(cwd, signal);
|
|
25
28
|
if (pinResult.isErr()) return localPinReadErrorToInvalidLocalBinding(pinResult.error);
|
|
26
29
|
const pin = pinResult.value;
|
|
27
|
-
if (pin.kind === "present") return
|
|
30
|
+
if (pin.kind === "present") return projects.some((project) => project.id === pin.pin.projectId) ? { status: "linked" } : { status: "invalid" };
|
|
28
31
|
return { status: "not-linked" };
|
|
29
32
|
}
|
|
30
33
|
function localPinReadErrorToInvalidLocalBinding(error) {
|
|
@@ -40,34 +43,18 @@ function localPinReadErrorToInvalidLocalBinding(error) {
|
|
|
40
43
|
});
|
|
41
44
|
}
|
|
42
45
|
async function runProjectList(context) {
|
|
43
|
-
const
|
|
44
|
-
const workspace = authState.workspace;
|
|
46
|
+
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
45
47
|
if (!workspace) throw workspaceRequiredError();
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const localBinding = await readProjectListLocalBinding(context.runtime.cwd, workspace, projects, context.runtime.signal);
|
|
51
|
-
const nextActions = buildProjectListNextActions(localBinding);
|
|
52
|
-
return {
|
|
53
|
-
command: "project.list",
|
|
54
|
-
result: {
|
|
55
|
-
workspace,
|
|
56
|
-
projects: projects.map(toProjectSummary),
|
|
57
|
-
localBinding
|
|
58
|
-
},
|
|
59
|
-
warnings: [],
|
|
60
|
-
nextSteps: [],
|
|
61
|
-
nextActions
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
const result = await createProjectUseCases(createCliUseCaseGateways(context)).list(authState);
|
|
65
|
-
const localBinding = await readProjectListLocalBinding(context.runtime.cwd, workspace, result.projects, context.runtime.signal);
|
|
48
|
+
const client = await authenticatedManagementApiClient(context.runtime.env, context.runtime.signal);
|
|
49
|
+
if (!client) throw authRequiredError();
|
|
50
|
+
const projects = sortProjects(await listRealWorkspaceProjects(client, context.runtime.signal));
|
|
51
|
+
const localBinding = await readProjectListLocalBinding(context.runtime.cwd, projects, context.runtime.signal);
|
|
66
52
|
const nextActions = buildProjectListNextActions(localBinding);
|
|
67
53
|
return {
|
|
68
54
|
command: "project.list",
|
|
69
55
|
result: {
|
|
70
|
-
|
|
56
|
+
workspace,
|
|
57
|
+
projects: projects.map(toProjectSummary),
|
|
71
58
|
localBinding
|
|
72
59
|
},
|
|
73
60
|
warnings: [],
|
|
@@ -84,7 +71,7 @@ function buildProjectListNextActions(localBinding) {
|
|
|
84
71
|
async function runProjectShow(context, explicitProject) {
|
|
85
72
|
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
86
73
|
if (!workspace) throw workspaceRequiredError();
|
|
87
|
-
const result =
|
|
74
|
+
const result = await resolveProjectShowInRealMode(context, workspace, explicitProject);
|
|
88
75
|
return {
|
|
89
76
|
command: "project.show",
|
|
90
77
|
result,
|
|
@@ -97,17 +84,17 @@ async function runProjectShow(context, explicitProject) {
|
|
|
97
84
|
}) : []
|
|
98
85
|
};
|
|
99
86
|
}
|
|
100
|
-
async function runProjectCreate(context, projectName) {
|
|
87
|
+
async function runProjectCreate(context, projectName, options) {
|
|
101
88
|
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
102
89
|
if (!workspace) throw workspaceRequiredError();
|
|
103
90
|
if (!isValidProjectSetupName(projectName)) throw projectSetupNameRequiredError("project create");
|
|
104
|
-
|
|
105
|
-
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
91
|
+
const client = await authenticatedManagementApiClient(context.runtime.env, context.runtime.signal);
|
|
106
92
|
if (!client) throw authRequiredError();
|
|
107
93
|
const provider = createAppProvider(client);
|
|
108
94
|
const name = projectName.trim();
|
|
109
95
|
const created = await provider.createProject({
|
|
110
96
|
name,
|
|
97
|
+
region: options?.region,
|
|
111
98
|
signal: context.runtime.signal
|
|
112
99
|
}).catch((error) => {
|
|
113
100
|
throw projectCreateFailedError(error, name, workspace, {
|
|
@@ -118,7 +105,8 @@ async function runProjectCreate(context, projectName) {
|
|
|
118
105
|
});
|
|
119
106
|
const bindResult = await bindProjectToDirectory(context, workspace, {
|
|
120
107
|
id: created.id,
|
|
121
|
-
name: created.name
|
|
108
|
+
name: created.name,
|
|
109
|
+
...created.defaultRegion != null ? { defaultRegion: created.defaultRegion } : {}
|
|
122
110
|
}, "created");
|
|
123
111
|
if (bindResult.isErr()) throw projectDirectoryBindingErrorToCliError(bindResult.error);
|
|
124
112
|
return {
|
|
@@ -131,14 +119,10 @@ async function runProjectCreate(context, projectName) {
|
|
|
131
119
|
async function runProjectLink(context, projectRef) {
|
|
132
120
|
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
133
121
|
if (!workspace) throw workspaceRequiredError();
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (!client) throw authRequiredError();
|
|
139
|
-
provider = createAppProvider(client);
|
|
140
|
-
projects = await listRealWorkspaceProjects(client, workspace, context.runtime.signal);
|
|
141
|
-
} else projects = listFixtureWorkspaceProjects(context, workspace);
|
|
122
|
+
const client = await authenticatedManagementApiClient(context.runtime.env, context.runtime.signal);
|
|
123
|
+
if (!client) throw authRequiredError();
|
|
124
|
+
const provider = createAppProvider(client);
|
|
125
|
+
const projects = await listRealWorkspaceProjects(client, context.runtime.signal);
|
|
142
126
|
let result;
|
|
143
127
|
if (projectRef?.trim()) result = await requireProjectDirectoryBinding(context, workspace, toProjectSummary(resolveProjectForSetup(projectRef.trim(), projects, workspace)), "linked");
|
|
144
128
|
else if (canPrompt(context) && !context.flags.yes) result = await resolveInteractiveProjectLinkSetup(context, workspace, projects, provider);
|
|
@@ -154,10 +138,7 @@ async function resolveInteractiveProjectLinkSetup(context, workspace, projects,
|
|
|
154
138
|
const setup = await promptForProjectSetupChoice({
|
|
155
139
|
context,
|
|
156
140
|
projects,
|
|
157
|
-
createProject: (projectName) =>
|
|
158
|
-
if (!provider) 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");
|
|
159
|
-
return createProjectForLinkSetup(provider, projectName, workspace, context.runtime.signal);
|
|
160
|
-
},
|
|
141
|
+
createProject: (projectName) => createProjectForLinkSetup(provider, projectName, workspace, context.runtime.signal),
|
|
161
142
|
cancel: {
|
|
162
143
|
why: "Project link needs a Project before it can continue.",
|
|
163
144
|
fix: "Choose an existing Project or create a new one, then rerun project link.",
|
|
@@ -217,54 +198,288 @@ async function projectLinkTargetRequiredError(context, projects) {
|
|
|
217
198
|
})
|
|
218
199
|
});
|
|
219
200
|
}
|
|
220
|
-
async function
|
|
201
|
+
async function runProjectRename(context, newName, options) {
|
|
221
202
|
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
222
203
|
if (!workspace) throw workspaceRequiredError();
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
if (repositoryFullNamesMatch(existingConnection.repository.fullName, repository.fullName)) return {
|
|
233
|
-
command: "git.connect",
|
|
234
|
-
result: {
|
|
235
|
-
...target,
|
|
236
|
-
repositoryConnection: existingConnection
|
|
237
|
-
},
|
|
238
|
-
warnings: [],
|
|
239
|
-
nextSteps: []
|
|
240
|
-
};
|
|
241
|
-
throw repoAlreadyConnectedError(existingConnection.repository.fullName);
|
|
242
|
-
}
|
|
243
|
-
const resolvedRepository = await resolveInstalledRepository(context, api, workspace.id, repository);
|
|
244
|
-
const { data, error, response } = await api.POST("/v1/source-repositories", {
|
|
245
|
-
body: {
|
|
204
|
+
const name = newName.trim();
|
|
205
|
+
if (!isValidProjectSetupName(name)) throw projectSetupNameRequiredError("project rename");
|
|
206
|
+
const { provider, target } = await requireProjectCommandContext(context, workspace, options.project, "project rename");
|
|
207
|
+
const previousName = target.project.name;
|
|
208
|
+
return {
|
|
209
|
+
command: "project.rename",
|
|
210
|
+
result: {
|
|
211
|
+
workspace,
|
|
212
|
+
project: await provider.renameProject({
|
|
246
213
|
projectId: target.project.id,
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
214
|
+
name,
|
|
215
|
+
signal: context.runtime.signal
|
|
216
|
+
}),
|
|
217
|
+
previousName
|
|
218
|
+
},
|
|
219
|
+
warnings: [],
|
|
220
|
+
nextSteps: []
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
async function runProjectRemove(context, projectRef, options) {
|
|
224
|
+
const formatCommand = resolvePrismaCliPackageCommandFormatterSync(context.runtime.cwd);
|
|
225
|
+
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
226
|
+
if (!workspace) throw workspaceRequiredError();
|
|
227
|
+
const { provider, projects } = await requireProjectMutationContext(context, workspace);
|
|
228
|
+
const project = toProjectSummary(resolveProjectForSetup(projectRef.trim(), projects, workspace));
|
|
229
|
+
requireProjectExactConfirmation({
|
|
230
|
+
id: project.id,
|
|
231
|
+
confirm: options.confirm,
|
|
232
|
+
summary: "Confirm project removal",
|
|
233
|
+
why: "Removing a project is permanent, deletes its databases, and stops its apps, so it requires the exact project id.",
|
|
234
|
+
nextStep: formatCommand([
|
|
235
|
+
"project",
|
|
236
|
+
"remove",
|
|
237
|
+
project.id,
|
|
238
|
+
"--confirm",
|
|
239
|
+
project.id
|
|
240
|
+
])
|
|
241
|
+
});
|
|
242
|
+
await provider.removeProject({
|
|
243
|
+
projectId: project.id,
|
|
244
|
+
signal: context.runtime.signal
|
|
245
|
+
});
|
|
246
|
+
const warnings = [];
|
|
247
|
+
return {
|
|
248
|
+
command: "project.remove",
|
|
249
|
+
result: {
|
|
250
|
+
workspace,
|
|
251
|
+
project,
|
|
252
|
+
localPin: { cleared: await cleanupLocalPinForProject(context, project.id, { onError: (message) => warnings.push(message) }) }
|
|
253
|
+
},
|
|
254
|
+
warnings,
|
|
255
|
+
nextSteps: []
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
async function runProjectTransfer(context, projectRef, options) {
|
|
259
|
+
const formatCommand = resolvePrismaCliPackageCommandFormatterSync(context.runtime.cwd);
|
|
260
|
+
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
261
|
+
if (!workspace) throw workspaceRequiredError();
|
|
262
|
+
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([
|
|
263
|
+
"project",
|
|
264
|
+
"transfer",
|
|
265
|
+
"<project>",
|
|
266
|
+
"--to-workspace",
|
|
267
|
+
"<id-or-name>",
|
|
268
|
+
"--confirm",
|
|
269
|
+
"<project-id>"
|
|
270
|
+
])], "project");
|
|
271
|
+
if (!options.toWorkspace?.trim() && !options.recipientToken?.trim()) throw transferRecipientRequiredError(formatCommand);
|
|
272
|
+
const { provider, projects } = await requireProjectMutationContext(context, workspace);
|
|
273
|
+
const project = toProjectSummary(resolveProjectForSetup(projectRef.trim(), projects, workspace));
|
|
274
|
+
requireProjectExactConfirmation({
|
|
275
|
+
id: project.id,
|
|
276
|
+
confirm: options.confirm,
|
|
277
|
+
summary: "Confirm project transfer",
|
|
278
|
+
why: "Transferring moves the project to another workspace and this workspace loses access, so it requires the exact project id.",
|
|
279
|
+
nextStep: `${formatCommand([
|
|
280
|
+
"project",
|
|
281
|
+
"transfer",
|
|
282
|
+
project.id
|
|
283
|
+
])} ${options.toWorkspace ? `--to-workspace ${formatCommandArgument(options.toWorkspace)}` : "--recipient-token <token>"} --confirm ${project.id}`
|
|
284
|
+
});
|
|
285
|
+
const recipient = await resolveTransferRecipient(context, options);
|
|
286
|
+
await provider.transferProject({
|
|
287
|
+
projectId: project.id,
|
|
288
|
+
recipientAccessToken: recipient.accessToken,
|
|
289
|
+
signal: context.runtime.signal
|
|
290
|
+
});
|
|
291
|
+
const warnings = [];
|
|
292
|
+
const pinAction = await rewriteOrClearLocalPinForProject(context, project.id, recipient.workspaceId, { onError: (message) => warnings.push(message) });
|
|
293
|
+
return {
|
|
294
|
+
command: "project.transfer",
|
|
295
|
+
result: {
|
|
296
|
+
workspace,
|
|
297
|
+
project,
|
|
298
|
+
recipient: {
|
|
299
|
+
workspaceId: recipient.workspaceId,
|
|
300
|
+
workspaceName: recipient.workspaceName,
|
|
301
|
+
source: recipient.source
|
|
250
302
|
},
|
|
251
|
-
|
|
252
|
-
}
|
|
253
|
-
|
|
303
|
+
localPin: { action: pinAction }
|
|
304
|
+
},
|
|
305
|
+
warnings,
|
|
306
|
+
nextSteps: options.toWorkspace ? [`${formatCommand([
|
|
307
|
+
"auth",
|
|
308
|
+
"workspace",
|
|
309
|
+
"use"
|
|
310
|
+
])} ${formatCommandArgument(options.toWorkspace)}`] : []
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
async function resolveTransferRecipient(context, options) {
|
|
314
|
+
const formatCommand = resolvePrismaCliPackageCommandFormatterSync(context.runtime.cwd);
|
|
315
|
+
const recipientToken = options.recipientToken?.trim();
|
|
316
|
+
if (recipientToken) return {
|
|
317
|
+
accessToken: recipientToken,
|
|
318
|
+
workspaceId: null,
|
|
319
|
+
workspaceName: null,
|
|
320
|
+
source: "recipient-token"
|
|
321
|
+
};
|
|
322
|
+
const workspaceRef = options.toWorkspace?.trim();
|
|
323
|
+
if (!workspaceRef) throw transferRecipientRequiredError(formatCommand);
|
|
324
|
+
if (context.runtime.env[SERVICE_TOKEN_ENV_VAR] !== void 0) throw transferRecipientUnavailableError(formatCommand);
|
|
325
|
+
return resolveTransferRecipientFromWorkspaceSession(context, workspaceRef);
|
|
326
|
+
}
|
|
327
|
+
async function resolveTransferRecipientFromWorkspaceSession(context, workspaceRef) {
|
|
328
|
+
try {
|
|
329
|
+
const session = await resolveRecipientWorkspaceSession(workspaceRef, context.runtime.env, context.runtime.signal);
|
|
254
330
|
return {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
},
|
|
260
|
-
warnings: [],
|
|
261
|
-
nextSteps: []
|
|
331
|
+
accessToken: session.accessToken,
|
|
332
|
+
workspaceId: session.workspace.id,
|
|
333
|
+
workspaceName: session.workspace.name,
|
|
334
|
+
source: "workspace-session"
|
|
262
335
|
};
|
|
336
|
+
} catch (error) {
|
|
337
|
+
throw recipientWorkspaceSessionFailure(error, workspaceRef);
|
|
263
338
|
}
|
|
264
|
-
|
|
339
|
+
}
|
|
340
|
+
function recipientWorkspaceSessionFailure(error, workspaceRef) {
|
|
341
|
+
if (error instanceof WorkspaceSelectionError) {
|
|
342
|
+
if (error.reason === "ambiguous") return workspaceAmbiguousError(error.workspaceRef ?? workspaceRef, error.matches.map((match) => ({
|
|
343
|
+
id: match.id,
|
|
344
|
+
name: match.name,
|
|
345
|
+
credentialWorkspaceId: match.credentialWorkspaceId
|
|
346
|
+
})));
|
|
347
|
+
return workspaceNotAuthenticatedError(error.workspaceRef ?? workspaceRef);
|
|
348
|
+
}
|
|
349
|
+
if (error instanceof RecipientSessionInvalidError) return workspaceNotAuthenticatedError(error.workspaceRef);
|
|
350
|
+
return error;
|
|
351
|
+
}
|
|
352
|
+
async function requireProjectMutationContext(context, _workspace) {
|
|
353
|
+
const client = await requireProjectClient(context);
|
|
354
|
+
return {
|
|
355
|
+
provider: createManagementProjectProvider(client),
|
|
356
|
+
projects: await listRealWorkspaceProjects(client, context.runtime.signal)
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
async function requireProjectCommandContext(context, workspace, explicitProject, commandName) {
|
|
360
|
+
const client = await requireProjectClient(context);
|
|
361
|
+
const listProjects = async () => listRealWorkspaceProjects(client, context.runtime.signal);
|
|
362
|
+
const targetResult = await resolveProjectTarget({
|
|
363
|
+
context,
|
|
364
|
+
workspace,
|
|
365
|
+
explicitProject,
|
|
366
|
+
listProjects,
|
|
367
|
+
commandName
|
|
368
|
+
});
|
|
369
|
+
if (targetResult.isErr()) throw projectResolutionErrorToCliError(targetResult.error);
|
|
370
|
+
return {
|
|
371
|
+
provider: createManagementProjectProvider(client),
|
|
372
|
+
target: targetResult.value
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
async function requireProjectClient(context) {
|
|
376
|
+
const client = await authenticatedManagementApiClient(context.runtime.env, context.runtime.signal);
|
|
377
|
+
if (!client) throw authRequiredError();
|
|
378
|
+
return client;
|
|
379
|
+
}
|
|
380
|
+
function requireProjectExactConfirmation(options) {
|
|
381
|
+
if (options.confirm === options.id) return;
|
|
382
|
+
throw new CliError({
|
|
383
|
+
code: "CONFIRMATION_REQUIRED",
|
|
384
|
+
domain: "project",
|
|
385
|
+
summary: options.summary,
|
|
386
|
+
why: options.why,
|
|
387
|
+
fix: `Rerun with --confirm ${options.id}.`,
|
|
388
|
+
exitCode: 2,
|
|
389
|
+
nextSteps: [options.nextStep],
|
|
390
|
+
meta: {
|
|
391
|
+
expectedConfirm: options.id,
|
|
392
|
+
receivedConfirm: options.confirm ?? null
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
function transferRecipientRequiredError(formatCommand) {
|
|
397
|
+
return new CliError({
|
|
398
|
+
code: "TRANSFER_RECIPIENT_REQUIRED",
|
|
399
|
+
domain: "project",
|
|
400
|
+
summary: "Transfer recipient required",
|
|
401
|
+
why: "Project transfer needs the receiving workspace.",
|
|
402
|
+
fix: "Pass --to-workspace <id-or-name> for a locally authenticated workspace, or --recipient-token <token> for a cross-account transfer.",
|
|
403
|
+
exitCode: 2,
|
|
404
|
+
nextSteps: [formatCommand([
|
|
405
|
+
"auth",
|
|
406
|
+
"workspace",
|
|
407
|
+
"list"
|
|
408
|
+
]), formatCommand([
|
|
409
|
+
"project",
|
|
410
|
+
"transfer",
|
|
411
|
+
"<project>",
|
|
412
|
+
"--to-workspace",
|
|
413
|
+
"<id-or-name>",
|
|
414
|
+
"--confirm",
|
|
415
|
+
"<project-id>"
|
|
416
|
+
])]
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
function transferRecipientUnavailableError(formatCommand) {
|
|
420
|
+
return new CliError({
|
|
421
|
+
code: "TRANSFER_RECIPIENT_UNAVAILABLE",
|
|
422
|
+
domain: "project",
|
|
423
|
+
summary: "Local workspace sessions are unavailable",
|
|
424
|
+
why: `--to-workspace resolves locally stored OAuth sessions, but ${SERVICE_TOKEN_ENV_VAR} is set and service-token mode does not read them.`,
|
|
425
|
+
fix: "Pass --recipient-token <token> with an access token for the receiving workspace, or unset the service token.",
|
|
426
|
+
exitCode: 1,
|
|
427
|
+
nextSteps: [formatCommand([
|
|
428
|
+
"project",
|
|
429
|
+
"transfer",
|
|
430
|
+
"<project>",
|
|
431
|
+
"--recipient-token",
|
|
432
|
+
"<token>",
|
|
433
|
+
"--confirm",
|
|
434
|
+
"<project-id>"
|
|
435
|
+
])]
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
async function cleanupLocalPinForProject(context, projectId, hooks) {
|
|
439
|
+
const pinResult = await readLocalResolutionPin(context.runtime.cwd, context.runtime.signal);
|
|
440
|
+
if (pinResult.isErr()) return false;
|
|
441
|
+
const pin = pinResult.value;
|
|
442
|
+
if (pin.kind !== "present" || pin.pin.projectId !== projectId) return false;
|
|
443
|
+
try {
|
|
444
|
+
await unlink(path.join(context.runtime.cwd, LOCAL_RESOLUTION_PIN_RELATIVE_PATH));
|
|
445
|
+
return true;
|
|
446
|
+
} catch {
|
|
447
|
+
hooks.onError(`The local pin ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} points at the removed project but could not be deleted.`);
|
|
448
|
+
return false;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
async function rewriteOrClearLocalPinForProject(context, projectId, recipientWorkspaceId, hooks) {
|
|
452
|
+
const pinResult = await readLocalResolutionPin(context.runtime.cwd, context.runtime.signal);
|
|
453
|
+
if (pinResult.isErr()) return "none";
|
|
454
|
+
const pin = pinResult.value;
|
|
455
|
+
if (pin.kind !== "present" || pin.pin.projectId !== projectId) return "none";
|
|
456
|
+
if (recipientWorkspaceId) {
|
|
457
|
+
if ((await writeLocalResolutionPin(context.runtime.cwd, {
|
|
458
|
+
workspaceId: recipientWorkspaceId,
|
|
459
|
+
projectId
|
|
460
|
+
}, context.runtime.signal)).isOk()) return "rewritten";
|
|
461
|
+
hooks.onError(`The local pin ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} points at the transferred project but could not be rewritten.`);
|
|
462
|
+
return "none";
|
|
463
|
+
}
|
|
464
|
+
try {
|
|
465
|
+
await unlink(path.join(context.runtime.cwd, LOCAL_RESOLUTION_PIN_RELATIVE_PATH));
|
|
466
|
+
return "cleared";
|
|
467
|
+
} catch {
|
|
468
|
+
hooks.onError(`The local pin ${LOCAL_RESOLUTION_PIN_RELATIVE_PATH} points at the transferred project but could not be cleared.`);
|
|
469
|
+
return "none";
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
async function runGitConnect(context, gitUrl, options = {}) {
|
|
473
|
+
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
474
|
+
if (!workspace) throw workspaceRequiredError();
|
|
475
|
+
const client = await authenticatedManagementApiClient(context.runtime.env, context.runtime.signal);
|
|
476
|
+
if (!client) throw authRequiredError();
|
|
477
|
+
const target = await resolveRequiredProjectInRealMode(context, workspace, options.project, "git connect");
|
|
265
478
|
const repository = await resolveRepositoryForConnect(context, gitUrl);
|
|
266
|
-
const
|
|
267
|
-
|
|
479
|
+
const api = client;
|
|
480
|
+
const existing = await readFirstSourceRepository(api, target.project.id, context.runtime.signal);
|
|
481
|
+
if (existing) {
|
|
482
|
+
const existingConnection = toRepositoryConnection(existing);
|
|
268
483
|
if (repositoryFullNamesMatch(existingConnection.repository.fullName, repository.fullName)) return {
|
|
269
484
|
command: "git.connect",
|
|
270
485
|
result: {
|
|
@@ -276,13 +491,22 @@ async function runGitConnect(context, gitUrl, options = {}) {
|
|
|
276
491
|
};
|
|
277
492
|
throw repoAlreadyConnectedError(existingConnection.repository.fullName);
|
|
278
493
|
}
|
|
279
|
-
const
|
|
280
|
-
await
|
|
494
|
+
const resolvedRepository = await resolveInstalledRepository(context, api, workspace.id, repository);
|
|
495
|
+
const { data, error, response } = await api.POST("/v1/source-repositories", {
|
|
496
|
+
body: {
|
|
497
|
+
projectId: target.project.id,
|
|
498
|
+
provider: "github",
|
|
499
|
+
providerRepositoryId: resolvedRepository.repository.id,
|
|
500
|
+
installationId: resolvedRepository.installation.id
|
|
501
|
+
},
|
|
502
|
+
signal: context.runtime.signal
|
|
503
|
+
});
|
|
504
|
+
if (error || !data) throw repoConnectionApiError("Failed to connect GitHub repository", response, error);
|
|
281
505
|
return {
|
|
282
506
|
command: "git.connect",
|
|
283
507
|
result: {
|
|
284
508
|
...target,
|
|
285
|
-
repositoryConnection:
|
|
509
|
+
repositoryConnection: toRepositoryConnection(data.data)
|
|
286
510
|
},
|
|
287
511
|
warnings: [],
|
|
288
512
|
nextSteps: []
|
|
@@ -291,96 +515,64 @@ async function runGitConnect(context, gitUrl, options = {}) {
|
|
|
291
515
|
async function runGitDisconnect(context, options = {}) {
|
|
292
516
|
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
293
517
|
if (!workspace) throw workspaceRequiredError();
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
if (error) throw repoConnectionApiError("Failed to disconnect GitHub repository", response, error);
|
|
306
|
-
return {
|
|
307
|
-
command: "git.disconnect",
|
|
308
|
-
result: {
|
|
309
|
-
...target,
|
|
310
|
-
repositoryConnection: toRepositoryConnection(existing)
|
|
311
|
-
},
|
|
312
|
-
warnings: [],
|
|
313
|
-
nextSteps: []
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
const target = await resolveRequiredProjectInFixtureMode(context, workspace, options.project, "git disconnect");
|
|
317
|
-
const existingConnection = await context.stateStore.readRepositoryConnection(target.project.id);
|
|
318
|
-
if (!existingConnection) throw repoNotConnectedError();
|
|
319
|
-
await context.stateStore.clearRepositoryConnection(target.project.id);
|
|
518
|
+
const client = await authenticatedManagementApiClient(context.runtime.env, context.runtime.signal);
|
|
519
|
+
if (!client) throw authRequiredError();
|
|
520
|
+
const target = await resolveRequiredProjectInRealMode(context, workspace, options.project, "git disconnect");
|
|
521
|
+
const api = client;
|
|
522
|
+
const existing = await readFirstSourceRepository(api, target.project.id, context.runtime.signal);
|
|
523
|
+
if (!existing) throw repoNotConnectedError();
|
|
524
|
+
const { error, response } = await api.DELETE("/v1/source-repositories/{id}", {
|
|
525
|
+
params: { path: { id: existing.id } },
|
|
526
|
+
signal: context.runtime.signal
|
|
527
|
+
});
|
|
528
|
+
if (error) throw repoConnectionApiError("Failed to disconnect GitHub repository", response, error);
|
|
320
529
|
return {
|
|
321
530
|
command: "git.disconnect",
|
|
322
531
|
result: {
|
|
323
532
|
...target,
|
|
324
|
-
repositoryConnection:
|
|
533
|
+
repositoryConnection: toRepositoryConnection(existing)
|
|
325
534
|
},
|
|
326
535
|
warnings: [],
|
|
327
536
|
nextSteps: []
|
|
328
537
|
};
|
|
329
538
|
}
|
|
330
539
|
async function resolveProjectShowInRealMode(context, workspace, explicitProject) {
|
|
331
|
-
const client = await
|
|
540
|
+
const client = await authenticatedManagementApiClient(context.runtime.env, context.runtime.signal);
|
|
332
541
|
if (!client) throw authRequiredError();
|
|
333
542
|
const result = await inspectProjectBinding({
|
|
334
543
|
context,
|
|
335
544
|
workspace,
|
|
336
545
|
explicitProject,
|
|
337
|
-
listProjects: () => listRealWorkspaceProjects(client,
|
|
546
|
+
listProjects: () => listRealWorkspaceProjects(client, context.runtime.signal),
|
|
338
547
|
commandName: "project show"
|
|
339
548
|
});
|
|
340
549
|
if (result.isErr()) throw projectResolutionErrorToCliError(result.error);
|
|
341
550
|
return result.value;
|
|
342
551
|
}
|
|
343
552
|
async function resolveRequiredProjectInRealMode(context, workspace, explicitProject, commandName) {
|
|
344
|
-
const client = await
|
|
553
|
+
const client = await authenticatedManagementApiClient(context.runtime.env, context.runtime.signal);
|
|
345
554
|
if (!client) throw authRequiredError();
|
|
346
555
|
const result = await resolveProjectTarget({
|
|
347
556
|
context,
|
|
348
557
|
workspace,
|
|
349
558
|
explicitProject,
|
|
350
|
-
listProjects: () => listRealWorkspaceProjects(client,
|
|
559
|
+
listProjects: () => listRealWorkspaceProjects(client, context.runtime.signal),
|
|
351
560
|
commandName
|
|
352
561
|
});
|
|
353
562
|
if (result.isErr()) throw projectResolutionErrorToCliError(result.error);
|
|
354
563
|
return result.value;
|
|
355
564
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
});
|
|
364
|
-
if (result.isErr()) throw projectResolutionErrorToCliError(result.error);
|
|
365
|
-
return result.value;
|
|
366
|
-
}
|
|
367
|
-
async function resolveRequiredProjectInFixtureMode(context, workspace, explicitProject, commandName) {
|
|
368
|
-
const result = await resolveProjectTarget({
|
|
369
|
-
context,
|
|
370
|
-
workspace,
|
|
371
|
-
explicitProject,
|
|
372
|
-
listProjects: async () => listFixtureWorkspaceProjects(context, workspace),
|
|
373
|
-
commandName
|
|
374
|
-
});
|
|
375
|
-
if (result.isErr()) throw projectResolutionErrorToCliError(result.error);
|
|
376
|
-
return result.value;
|
|
377
|
-
}
|
|
378
|
-
async function listRealWorkspaceProjects(client, workspace, signal) {
|
|
379
|
-
const { data } = await client.GET("/v1/projects", { signal });
|
|
380
|
-
return sortProjects((data?.data ?? []).filter((project) => project.workspace.id === workspace.id).map((project) => ({
|
|
565
|
+
/** The projects the API returns for the active credential, sorted.
|
|
566
|
+
* Takes no workspace: the credential names one, and the API answers
|
|
567
|
+
* within it. */
|
|
568
|
+
async function listRealWorkspaceProjects(client, signal) {
|
|
569
|
+
const { data, error, response } = await client.GET("/v1/projects", { signal });
|
|
570
|
+
if (error || !data) throw projectApiError("Failed to list projects", response, error);
|
|
571
|
+
return sortProjects((data.data ?? []).map((project) => ({
|
|
381
572
|
id: project.id,
|
|
382
573
|
name: project.name,
|
|
383
574
|
..."url" in project && typeof project.url === "string" ? { url: project.url } : {},
|
|
575
|
+
..."defaultRegion" in project ? { defaultRegion: project.defaultRegion } : {},
|
|
384
576
|
slug: "slug" in project && typeof project.slug === "string" ? project.slug : null,
|
|
385
577
|
workspace: {
|
|
386
578
|
id: project.workspace.id,
|
|
@@ -388,15 +580,6 @@ async function listRealWorkspaceProjects(client, workspace, signal) {
|
|
|
388
580
|
}
|
|
389
581
|
})));
|
|
390
582
|
}
|
|
391
|
-
function listFixtureWorkspaceProjects(context, workspace) {
|
|
392
|
-
return sortProjects(context.api.listProjectsForWorkspace(workspace.id).map((project) => ({
|
|
393
|
-
id: project.id,
|
|
394
|
-
name: project.name,
|
|
395
|
-
...project.url ? { url: project.url } : {},
|
|
396
|
-
slug: project.slug,
|
|
397
|
-
workspace
|
|
398
|
-
})));
|
|
399
|
-
}
|
|
400
583
|
async function resolveRepositoryForConnect(context, gitUrl) {
|
|
401
584
|
const remoteUrl = gitUrl ?? await readGitOriginRemote(context.runtime.cwd, context.runtime.signal);
|
|
402
585
|
if (!remoteUrl) throw usageError("Repository connection requires a GitHub repository URL", "No git-url was provided and the local repo does not have an origin remote.", "Pass a GitHub repository URL, or add a GitHub origin remote and rerun prisma-cli git connect.", ["prisma-cli git connect git@github.com:prisma/prisma-cli.git"], "project");
|
|
@@ -582,28 +765,6 @@ async function readFirstSourceRepository(api, projectId, signal) {
|
|
|
582
765
|
if (error || !data) throw repoConnectionApiError("Failed to inspect GitHub repository connection", response, error);
|
|
583
766
|
return data.data[0] ?? null;
|
|
584
767
|
}
|
|
585
|
-
function createPendingRepositoryConnection(repository) {
|
|
586
|
-
return {
|
|
587
|
-
id: null,
|
|
588
|
-
provider: "github",
|
|
589
|
-
repoId: null,
|
|
590
|
-
repository,
|
|
591
|
-
defaultBranch: null,
|
|
592
|
-
isPrivate: null,
|
|
593
|
-
status: "pending",
|
|
594
|
-
installation: {
|
|
595
|
-
id: null,
|
|
596
|
-
status: "pending"
|
|
597
|
-
},
|
|
598
|
-
automation: {
|
|
599
|
-
branches: false,
|
|
600
|
-
pullRequests: false,
|
|
601
|
-
comments: false
|
|
602
|
-
},
|
|
603
|
-
connectedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
604
|
-
updatedAt: null
|
|
605
|
-
};
|
|
606
|
-
}
|
|
607
768
|
function toRepositoryConnection(record) {
|
|
608
769
|
const [owner = "", name = ""] = record.repoFullName.split("/");
|
|
609
770
|
return {
|
|
@@ -728,4 +889,4 @@ function repoConnectionFixForStatus(status) {
|
|
|
728
889
|
return "Re-run with --trace for the underlying API response details.";
|
|
729
890
|
}
|
|
730
891
|
//#endregion
|
|
731
|
-
export {
|
|
892
|
+
export { listRealWorkspaceProjects, runGitConnect, runGitDisconnect, runProjectCreate, runProjectLink, runProjectList, runProjectRemove, runProjectRename, runProjectShow, runProjectTransfer };
|