@prisma/cli 8.0.0-rc.1 → 8.0.0-rc.2-dev.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +15410 -16
- package/dist/deploy-framework-C7bQM00A.js +23 -0
- package/dist/{v8/sender.js → sender.js} +2 -2
- package/package.json +11 -15
- package/dist/adapters/git.js +0 -54
- package/dist/adapters/local-state.js +0 -155
- package/dist/auth/client.js +0 -30
- package/dist/auth/errors.js +0 -50
- package/dist/auth/guard.js +0 -39
- package/dist/auth/login.js +0 -471
- package/dist/auth/operations.js +0 -208
- package/dist/auth/recipient.js +0 -42
- package/dist/auth/token-storage.js +0 -439
- package/dist/auth/workspaces.js +0 -171
- package/dist/cli-command.js +0 -14
- package/dist/cli-name.js +0 -12
- package/dist/cli2.js +0 -136
- package/dist/command-arguments.js +0 -12
- package/dist/commands/agent/index.js +0 -60
- package/dist/commands/app/index.js +0 -350
- package/dist/commands/auth/index.js +0 -95
- package/dist/commands/branch/index.js +0 -27
- package/dist/commands/bucket/index.js +0 -123
- package/dist/commands/build/index.js +0 -29
- package/dist/commands/database/index.js +0 -249
- package/dist/commands/env.js +0 -99
- package/dist/commands/feedback/index.js +0 -20
- package/dist/commands/git/index.js +0 -36
- package/dist/commands/init/index.js +0 -33
- package/dist/commands/project/index.js +0 -118
- package/dist/commands/version/index.js +0 -18
- package/dist/controllers/agent-setup.js +0 -52
- package/dist/controllers/agent.js +0 -228
- package/dist/controllers/app-env-api.js +0 -55
- package/dist/controllers/app-env-file.js +0 -181
- package/dist/controllers/app-env.js +0 -502
- package/dist/controllers/app.js +0 -2508
- package/dist/controllers/auth.js +0 -124
- package/dist/controllers/branch.js +0 -92
- package/dist/controllers/bucket.js +0 -184
- package/dist/controllers/build.js +0 -88
- package/dist/controllers/database.js +0 -463
- package/dist/controllers/feedback.js +0 -90
- package/dist/controllers/init.js +0 -814
- package/dist/controllers/project.js +0 -892
- package/dist/controllers/select-prompt-port.js +0 -13
- package/dist/controllers/version.js +0 -12
- package/dist/errors.js +0 -102
- package/dist/lib/agent/cli-command.js +0 -20
- package/dist/lib/agent/constants.js +0 -12
- package/dist/lib/agent/package-manager.js +0 -99
- package/dist/lib/agent/setup-status.js +0 -83
- package/dist/lib/app/app-provider.js +0 -617
- package/dist/lib/app/branch-database-api.js +0 -102
- package/dist/lib/app/branch-database-deploy.js +0 -353
- package/dist/lib/app/branch-database.js +0 -234
- package/dist/lib/app/build-settings.js +0 -98
- package/dist/lib/app/build.js +0 -82
- package/dist/lib/app/bun-project.js +0 -45
- package/dist/lib/app/compute-config.js +0 -145
- package/dist/lib/app/deploy-output.js +0 -24
- package/dist/lib/app/deploy-plan.js +0 -59
- package/dist/lib/app/deploy-progress.js +0 -100
- package/dist/lib/app/domain-guidance.js +0 -14
- package/dist/lib/app/env-config.js +0 -67
- package/dist/lib/app/env-file.js +0 -82
- package/dist/lib/app/env-vars.js +0 -50
- package/dist/lib/app/local-dev.js +0 -109
- package/dist/lib/app/production-deploy-gate.js +0 -162
- package/dist/lib/app/read-branch.js +0 -30
- package/dist/lib/bucket/provider.js +0 -139
- package/dist/lib/database/provider.js +0 -385
- package/dist/lib/diagnostics.js +0 -16
- package/dist/lib/feedback.js +0 -15
- package/dist/lib/fs/home-path.js +0 -24
- package/dist/lib/git/local-branch.js +0 -53
- package/dist/lib/git/local-status.js +0 -57
- package/dist/lib/project/interactive-setup.js +0 -57
- package/dist/lib/project/local-pin.js +0 -200
- package/dist/lib/project/provider.js +0 -93
- package/dist/lib/project/resolution.js +0 -389
- package/dist/lib/project/setup.js +0 -138
- package/dist/lib/version.js +0 -56
- package/dist/lib/workspace-id.js +0 -18
- package/dist/output/patterns.js +0 -90
- package/dist/presenters/agent.js +0 -74
- package/dist/presenters/app-env.js +0 -265
- package/dist/presenters/app.js +0 -672
- package/dist/presenters/auth.js +0 -184
- package/dist/presenters/branch.js +0 -46
- package/dist/presenters/bucket.js +0 -174
- package/dist/presenters/database.js +0 -462
- package/dist/presenters/feedback.js +0 -26
- package/dist/presenters/init.js +0 -30
- package/dist/presenters/project.js +0 -268
- package/dist/presenters/verbose-context.js +0 -64
- package/dist/presenters/version.js +0 -29
- package/dist/shell/cli-command.js +0 -2
- package/dist/shell/command-meta.js +0 -923
- package/dist/shell/command-runner.js +0 -119
- package/dist/shell/diagnostics-output.js +0 -57
- package/dist/shell/global-flags.js +0 -37
- package/dist/shell/help.js +0 -90
- package/dist/shell/output.js +0 -144
- package/dist/shell/prompt.js +0 -48
- package/dist/shell/runtime.js +0 -38
- package/dist/shell/ui.js +0 -116
- package/dist/state-dir.js +0 -12
- package/dist/update-check.js +0 -248
- package/dist/v8/cli.js +0 -14699
|
@@ -1,892 +0,0 @@
|
|
|
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";
|
|
4
|
-
import { renderSummaryLine } from "../shell/ui.js";
|
|
5
|
-
import { WorkspaceSelectionError } from "../auth/token-storage.js";
|
|
6
|
-
import { authenticatedManagementApiClient } from "../auth/guard.js";
|
|
7
|
-
import { canPrompt } from "../shell/runtime.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";
|
|
12
|
-
import { promptForProjectSetupChoice } from "../lib/project/interactive-setup.js";
|
|
13
|
-
import { workspaceAmbiguousError, workspaceNotAuthenticatedError } from "../auth/errors.js";
|
|
14
|
-
import { requireAuthenticatedAuthState } from "./auth.js";
|
|
15
|
-
import { parseGitHubRepositoryUrl, readGitOriginRemote } from "../adapters/git.js";
|
|
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";
|
|
21
|
-
import open from "open";
|
|
22
|
-
import { matchError } from "better-result";
|
|
23
|
-
//#region src/controllers/project.ts
|
|
24
|
-
const GITHUB_INSTALL_POLL_INTERVAL_MS = 2e3;
|
|
25
|
-
const GITHUB_INSTALL_POLL_TIMEOUT_MS = 12e4;
|
|
26
|
-
async function readProjectListLocalBinding(cwd, projects, signal) {
|
|
27
|
-
const pinResult = await readLocalResolutionPin(cwd, signal);
|
|
28
|
-
if (pinResult.isErr()) return localPinReadErrorToInvalidLocalBinding(pinResult.error);
|
|
29
|
-
const pin = pinResult.value;
|
|
30
|
-
if (pin.kind === "present") return projects.some((project) => project.id === pin.pin.projectId) ? { status: "linked" } : { status: "invalid" };
|
|
31
|
-
return { status: "not-linked" };
|
|
32
|
-
}
|
|
33
|
-
function localPinReadErrorToInvalidLocalBinding(error) {
|
|
34
|
-
return matchError(error, {
|
|
35
|
-
LocalResolutionPinInvalidJsonError: () => ({ status: "invalid" }),
|
|
36
|
-
LocalResolutionPinInvalidShapeError: () => ({ status: "invalid" }),
|
|
37
|
-
LocalResolutionPinReadAbortedError: (error) => {
|
|
38
|
-
throw error;
|
|
39
|
-
},
|
|
40
|
-
UnhandledException: (error) => {
|
|
41
|
-
throw error;
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
async function runProjectList(context) {
|
|
46
|
-
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
47
|
-
if (!workspace) throw workspaceRequiredError();
|
|
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);
|
|
52
|
-
const nextActions = buildProjectListNextActions(localBinding);
|
|
53
|
-
return {
|
|
54
|
-
command: "project.list",
|
|
55
|
-
result: {
|
|
56
|
-
workspace,
|
|
57
|
-
projects: projects.map(toProjectSummary),
|
|
58
|
-
localBinding
|
|
59
|
-
},
|
|
60
|
-
warnings: [],
|
|
61
|
-
nextSteps: [],
|
|
62
|
-
nextActions
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
function buildProjectListNextActions(localBinding) {
|
|
66
|
-
return localBinding?.status === "linked" ? [] : buildProjectSetupNextActions({
|
|
67
|
-
createCommand: "prisma-cli project create <name>",
|
|
68
|
-
reason: localBinding?.status === "invalid" ? "This directory has an invalid local Project binding. Ask the user which Prisma Project to link before running Project-scoped commands." : "This directory is not linked to a Prisma Project. Project list shows available Projects, but none is selected for this directory."
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
async function runProjectShow(context, explicitProject) {
|
|
72
|
-
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
73
|
-
if (!workspace) throw workspaceRequiredError();
|
|
74
|
-
const result = await resolveProjectShowInRealMode(context, workspace, explicitProject);
|
|
75
|
-
return {
|
|
76
|
-
command: "project.show",
|
|
77
|
-
result,
|
|
78
|
-
warnings: [],
|
|
79
|
-
nextSteps: [],
|
|
80
|
-
nextActions: result.project === null ? buildProjectSetupNextActions({
|
|
81
|
-
commandName: "project show",
|
|
82
|
-
suggestedProjectName: result.suggestedProjectName,
|
|
83
|
-
reason: "This directory is not linked to a Prisma Project. Package and directory names can suggest setup defaults, but they do not select a Project."
|
|
84
|
-
}) : []
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
async function runProjectCreate(context, projectName, options) {
|
|
88
|
-
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
89
|
-
if (!workspace) throw workspaceRequiredError();
|
|
90
|
-
if (!isValidProjectSetupName(projectName)) throw projectSetupNameRequiredError("project create");
|
|
91
|
-
const client = await authenticatedManagementApiClient(context.runtime.env, context.runtime.signal);
|
|
92
|
-
if (!client) throw authRequiredError();
|
|
93
|
-
const provider = createAppProvider(client);
|
|
94
|
-
const name = projectName.trim();
|
|
95
|
-
const created = await provider.createProject({
|
|
96
|
-
name,
|
|
97
|
-
region: options?.region,
|
|
98
|
-
signal: context.runtime.signal
|
|
99
|
-
}).catch((error) => {
|
|
100
|
-
throw projectCreateFailedError(error, name, workspace, {
|
|
101
|
-
nextSteps: ["prisma-cli project list", "prisma-cli project link <id-or-name>"],
|
|
102
|
-
permissionFix: "Grant the token permission to create Projects in this workspace, or link an existing Project.",
|
|
103
|
-
fallbackFix: "Retry the command, or choose an existing Project with prisma-cli project link <id-or-name>."
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
const bindResult = await bindProjectToDirectory(context, workspace, {
|
|
107
|
-
id: created.id,
|
|
108
|
-
name: created.name,
|
|
109
|
-
...created.defaultRegion != null ? { defaultRegion: created.defaultRegion } : {}
|
|
110
|
-
}, "created");
|
|
111
|
-
if (bindResult.isErr()) throw projectDirectoryBindingErrorToCliError(bindResult.error);
|
|
112
|
-
return {
|
|
113
|
-
command: "project.create",
|
|
114
|
-
result: bindResult.value,
|
|
115
|
-
warnings: [],
|
|
116
|
-
nextSteps: ["prisma-cli app deploy"]
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
async function runProjectLink(context, projectRef) {
|
|
120
|
-
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
121
|
-
if (!workspace) throw workspaceRequiredError();
|
|
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);
|
|
126
|
-
let result;
|
|
127
|
-
if (projectRef?.trim()) result = await requireProjectDirectoryBinding(context, workspace, toProjectSummary(resolveProjectForSetup(projectRef.trim(), projects, workspace)), "linked");
|
|
128
|
-
else if (canPrompt(context) && !context.flags.yes) result = await resolveInteractiveProjectLinkSetup(context, workspace, projects, provider);
|
|
129
|
-
else throw await projectLinkTargetRequiredError(context, projects);
|
|
130
|
-
return {
|
|
131
|
-
command: "project.link",
|
|
132
|
-
result,
|
|
133
|
-
warnings: [],
|
|
134
|
-
nextSteps: ["prisma-cli app deploy"]
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
async function resolveInteractiveProjectLinkSetup(context, workspace, projects, provider) {
|
|
138
|
-
const setup = await promptForProjectSetupChoice({
|
|
139
|
-
context,
|
|
140
|
-
projects,
|
|
141
|
-
createProject: (projectName) => createProjectForLinkSetup(provider, projectName, workspace, context.runtime.signal),
|
|
142
|
-
cancel: {
|
|
143
|
-
why: "Project link needs a Project before it can continue.",
|
|
144
|
-
fix: "Choose an existing Project or create a new one, then rerun project link.",
|
|
145
|
-
nextSteps: ["prisma-cli project link <id-or-name>", "prisma-cli project create <name>"]
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
return requireProjectDirectoryBinding(context, workspace, setup.project, setup.action);
|
|
149
|
-
}
|
|
150
|
-
async function requireProjectDirectoryBinding(context, workspace, project, action) {
|
|
151
|
-
const bindResult = await bindProjectToDirectory(context, workspace, project, action);
|
|
152
|
-
if (bindResult.isErr()) throw projectDirectoryBindingErrorToCliError(bindResult.error);
|
|
153
|
-
return bindResult.value;
|
|
154
|
-
}
|
|
155
|
-
async function createProjectForLinkSetup(provider, projectName, workspace, signal) {
|
|
156
|
-
const created = await provider.createProject({
|
|
157
|
-
name: projectName,
|
|
158
|
-
signal
|
|
159
|
-
}).catch((error) => {
|
|
160
|
-
throw projectCreateFailedError(error, projectName, workspace, {
|
|
161
|
-
nextSteps: [
|
|
162
|
-
"prisma-cli project list",
|
|
163
|
-
"prisma-cli project link <id-or-name>",
|
|
164
|
-
`prisma-cli project create ${formatCommandArgument(projectName)}`
|
|
165
|
-
],
|
|
166
|
-
permissionFix: "Grant the token permission to create Projects in this workspace, or link an existing Project.",
|
|
167
|
-
fallbackFix: "Retry the command, or choose an existing Project with prisma-cli project link <id-or-name>."
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
return {
|
|
171
|
-
id: created.id,
|
|
172
|
-
name: created.name,
|
|
173
|
-
workspace
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
async function projectLinkTargetRequiredError(context, projects) {
|
|
177
|
-
const suggestedName = await inferTargetName(context.runtime.cwd, context.runtime.signal);
|
|
178
|
-
const createCommand = `prisma-cli project create ${formatCommandArgument(suggestedName.name)}`;
|
|
179
|
-
const recoveryCommands = ["prisma-cli project link <id-or-name>", createCommand];
|
|
180
|
-
return new CliError({
|
|
181
|
-
code: "PROJECT_LINK_TARGET_REQUIRED",
|
|
182
|
-
domain: "project",
|
|
183
|
-
summary: "Choose a Project to link this directory",
|
|
184
|
-
why: "This directory is not linked to a Prisma Project. Existing Projects are candidates until the user chooses one, and package or directory names are suggestions only.",
|
|
185
|
-
fix: "Run prisma-cli project link in a TTY to choose from the setup list, pass a Project id or name, or create a new Project.",
|
|
186
|
-
meta: {
|
|
187
|
-
suggestedProjectName: suggestedName.name,
|
|
188
|
-
suggestedProjectNameSource: suggestedName.source,
|
|
189
|
-
candidates: sortProjects(projects).map(toProjectSummary),
|
|
190
|
-
recoveryCommands
|
|
191
|
-
},
|
|
192
|
-
exitCode: 2,
|
|
193
|
-
nextSteps: ["prisma-cli project list", ...recoveryCommands],
|
|
194
|
-
nextActions: buildProjectSetupNextActions({
|
|
195
|
-
suggestedProjectName: suggestedName.name,
|
|
196
|
-
createCommand,
|
|
197
|
-
reason: "Project link needs the user to choose an existing Project or create a new one. Existing Projects, package names, and directory names are candidates only, not selections."
|
|
198
|
-
})
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
async function runProjectRename(context, newName, options) {
|
|
202
|
-
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
203
|
-
if (!workspace) throw workspaceRequiredError();
|
|
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({
|
|
213
|
-
projectId: target.project.id,
|
|
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
|
|
302
|
-
},
|
|
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);
|
|
330
|
-
return {
|
|
331
|
-
accessToken: session.accessToken,
|
|
332
|
-
workspaceId: session.workspace.id,
|
|
333
|
-
workspaceName: session.workspace.name,
|
|
334
|
-
source: "workspace-session"
|
|
335
|
-
};
|
|
336
|
-
} catch (error) {
|
|
337
|
-
throw recipientWorkspaceSessionFailure(error, workspaceRef);
|
|
338
|
-
}
|
|
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");
|
|
478
|
-
const repository = await resolveRepositoryForConnect(context, gitUrl);
|
|
479
|
-
const api = client;
|
|
480
|
-
const existing = await readFirstSourceRepository(api, target.project.id, context.runtime.signal);
|
|
481
|
-
if (existing) {
|
|
482
|
-
const existingConnection = toRepositoryConnection(existing);
|
|
483
|
-
if (repositoryFullNamesMatch(existingConnection.repository.fullName, repository.fullName)) return {
|
|
484
|
-
command: "git.connect",
|
|
485
|
-
result: {
|
|
486
|
-
...target,
|
|
487
|
-
repositoryConnection: existingConnection
|
|
488
|
-
},
|
|
489
|
-
warnings: [],
|
|
490
|
-
nextSteps: []
|
|
491
|
-
};
|
|
492
|
-
throw repoAlreadyConnectedError(existingConnection.repository.fullName);
|
|
493
|
-
}
|
|
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);
|
|
505
|
-
return {
|
|
506
|
-
command: "git.connect",
|
|
507
|
-
result: {
|
|
508
|
-
...target,
|
|
509
|
-
repositoryConnection: toRepositoryConnection(data.data)
|
|
510
|
-
},
|
|
511
|
-
warnings: [],
|
|
512
|
-
nextSteps: []
|
|
513
|
-
};
|
|
514
|
-
}
|
|
515
|
-
async function runGitDisconnect(context, options = {}) {
|
|
516
|
-
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
517
|
-
if (!workspace) throw workspaceRequiredError();
|
|
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);
|
|
529
|
-
return {
|
|
530
|
-
command: "git.disconnect",
|
|
531
|
-
result: {
|
|
532
|
-
...target,
|
|
533
|
-
repositoryConnection: toRepositoryConnection(existing)
|
|
534
|
-
},
|
|
535
|
-
warnings: [],
|
|
536
|
-
nextSteps: []
|
|
537
|
-
};
|
|
538
|
-
}
|
|
539
|
-
async function resolveProjectShowInRealMode(context, workspace, explicitProject) {
|
|
540
|
-
const client = await authenticatedManagementApiClient(context.runtime.env, context.runtime.signal);
|
|
541
|
-
if (!client) throw authRequiredError();
|
|
542
|
-
const result = await inspectProjectBinding({
|
|
543
|
-
context,
|
|
544
|
-
workspace,
|
|
545
|
-
explicitProject,
|
|
546
|
-
listProjects: () => listRealWorkspaceProjects(client, context.runtime.signal),
|
|
547
|
-
commandName: "project show"
|
|
548
|
-
});
|
|
549
|
-
if (result.isErr()) throw projectResolutionErrorToCliError(result.error);
|
|
550
|
-
return result.value;
|
|
551
|
-
}
|
|
552
|
-
async function resolveRequiredProjectInRealMode(context, workspace, explicitProject, commandName) {
|
|
553
|
-
const client = await authenticatedManagementApiClient(context.runtime.env, context.runtime.signal);
|
|
554
|
-
if (!client) throw authRequiredError();
|
|
555
|
-
const result = await resolveProjectTarget({
|
|
556
|
-
context,
|
|
557
|
-
workspace,
|
|
558
|
-
explicitProject,
|
|
559
|
-
listProjects: () => listRealWorkspaceProjects(client, context.runtime.signal),
|
|
560
|
-
commandName
|
|
561
|
-
});
|
|
562
|
-
if (result.isErr()) throw projectResolutionErrorToCliError(result.error);
|
|
563
|
-
return result.value;
|
|
564
|
-
}
|
|
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) => ({
|
|
572
|
-
id: project.id,
|
|
573
|
-
name: project.name,
|
|
574
|
-
..."url" in project && typeof project.url === "string" ? { url: project.url } : {},
|
|
575
|
-
..."defaultRegion" in project ? { defaultRegion: project.defaultRegion } : {},
|
|
576
|
-
slug: "slug" in project && typeof project.slug === "string" ? project.slug : null,
|
|
577
|
-
workspace: {
|
|
578
|
-
id: project.workspace.id,
|
|
579
|
-
name: project.workspace.name
|
|
580
|
-
}
|
|
581
|
-
})));
|
|
582
|
-
}
|
|
583
|
-
async function resolveRepositoryForConnect(context, gitUrl) {
|
|
584
|
-
const remoteUrl = gitUrl ?? await readGitOriginRemote(context.runtime.cwd, context.runtime.signal);
|
|
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");
|
|
586
|
-
const repository = parseGitHubRepositoryUrl(remoteUrl);
|
|
587
|
-
if (!repository) throw unsupportedRepositoryProviderError();
|
|
588
|
-
return repository;
|
|
589
|
-
}
|
|
590
|
-
async function resolveInstalledRepository(context, api, workspaceId, repository) {
|
|
591
|
-
const lookup = await findRepositoryInInstallations(api, await listScmInstallations(api, workspaceId, context.runtime.signal), repository, context.runtime.signal);
|
|
592
|
-
if (lookup.match) return lookup.match;
|
|
593
|
-
const installUrl = await createGitHubInstallIntent(api, workspaceId, context.runtime.signal);
|
|
594
|
-
const canWait = canPrompt(context);
|
|
595
|
-
const opened = await openInstallUrlIfInteractive(context, installUrl);
|
|
596
|
-
if (!canWait) {
|
|
597
|
-
if (lookup.inspectableInstallationCount > 0) throw repoNotAccessibleError(repository, installUrl, opened);
|
|
598
|
-
throw repoInstallationRequiredError(repository, installUrl, opened);
|
|
599
|
-
}
|
|
600
|
-
writeInstallWaitStatus(context, opened, installUrl);
|
|
601
|
-
const result = await waitForInstalledRepository(context, api, workspaceId, repository);
|
|
602
|
-
if (result.match) return result.match;
|
|
603
|
-
if (result.inspectableInstallationCount > 0) throw repoNotAccessibleError(repository, installUrl, opened);
|
|
604
|
-
throw repoInstallationRequiredError(repository, installUrl, opened);
|
|
605
|
-
}
|
|
606
|
-
async function findRepositoryInInstallations(api, installations, repository, signal) {
|
|
607
|
-
let inspectableInstallationCount = 0;
|
|
608
|
-
for (const installation of installations) {
|
|
609
|
-
if (installation.provider !== "github" || installation.suspended) continue;
|
|
610
|
-
const matchedRepository = await findRepositoryInInstallationIfAvailable(api, installation.id, repository, signal);
|
|
611
|
-
if (matchedRepository === "unavailable") continue;
|
|
612
|
-
inspectableInstallationCount += 1;
|
|
613
|
-
if (matchedRepository) return {
|
|
614
|
-
match: {
|
|
615
|
-
installation,
|
|
616
|
-
repository: matchedRepository
|
|
617
|
-
},
|
|
618
|
-
inspectableInstallationCount
|
|
619
|
-
};
|
|
620
|
-
}
|
|
621
|
-
return {
|
|
622
|
-
match: null,
|
|
623
|
-
inspectableInstallationCount
|
|
624
|
-
};
|
|
625
|
-
}
|
|
626
|
-
async function waitForInstalledRepository(context, api, workspaceId, repository) {
|
|
627
|
-
const timeoutMs = readPositiveIntegerEnv(context.runtime.env.PRISMA_CLI_GITHUB_INSTALL_TIMEOUT_MS, GITHUB_INSTALL_POLL_TIMEOUT_MS);
|
|
628
|
-
const intervalMs = readPositiveIntegerEnv(context.runtime.env.PRISMA_CLI_GITHUB_INSTALL_POLL_INTERVAL_MS, GITHUB_INSTALL_POLL_INTERVAL_MS);
|
|
629
|
-
const deadline = Date.now() + timeoutMs;
|
|
630
|
-
let inspectableInstallationCount = 0;
|
|
631
|
-
while (Date.now() <= deadline) {
|
|
632
|
-
context.runtime.signal.throwIfAborted();
|
|
633
|
-
const lookup = await findRepositoryInInstallations(api, await listScmInstallations(api, workspaceId, context.runtime.signal), repository, context.runtime.signal);
|
|
634
|
-
inspectableInstallationCount = lookup.inspectableInstallationCount;
|
|
635
|
-
if (lookup.match) return {
|
|
636
|
-
match: lookup.match,
|
|
637
|
-
inspectableInstallationCount
|
|
638
|
-
};
|
|
639
|
-
const remainingMs = deadline - Date.now();
|
|
640
|
-
if (remainingMs <= 0) break;
|
|
641
|
-
await sleep(Math.min(intervalMs, remainingMs), context.runtime.signal);
|
|
642
|
-
}
|
|
643
|
-
return {
|
|
644
|
-
match: null,
|
|
645
|
-
inspectableInstallationCount
|
|
646
|
-
};
|
|
647
|
-
}
|
|
648
|
-
function readPositiveIntegerEnv(value, fallback) {
|
|
649
|
-
if (value === void 0) return fallback;
|
|
650
|
-
const parsed = Number(value);
|
|
651
|
-
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
652
|
-
}
|
|
653
|
-
function writeInstallWaitStatus(context, opened, installUrl) {
|
|
654
|
-
if (context.flags.quiet) return;
|
|
655
|
-
const lines = [renderSummaryLine(context.ui, "info", opened ? "Waiting for GitHub App installation or repository access approval..." : "Waiting for GitHub App installation or repository access approval. Open the install URL in your browser.")];
|
|
656
|
-
if (!opened) lines.push(installUrl);
|
|
657
|
-
context.output.stderr.write(`${lines.join("\n")}\n`);
|
|
658
|
-
}
|
|
659
|
-
function sleep(ms, signal) {
|
|
660
|
-
signal.throwIfAborted();
|
|
661
|
-
return new Promise((resolve, reject) => {
|
|
662
|
-
const onAbort = () => {
|
|
663
|
-
clearTimeout(timeout);
|
|
664
|
-
reject(signal.reason);
|
|
665
|
-
};
|
|
666
|
-
const timeout = setTimeout(() => {
|
|
667
|
-
signal.removeEventListener("abort", onAbort);
|
|
668
|
-
resolve();
|
|
669
|
-
}, ms);
|
|
670
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
671
|
-
});
|
|
672
|
-
}
|
|
673
|
-
async function listScmInstallations(api, workspaceId, signal) {
|
|
674
|
-
const installations = [];
|
|
675
|
-
let cursor;
|
|
676
|
-
const seenCursors = /* @__PURE__ */ new Set();
|
|
677
|
-
do {
|
|
678
|
-
const { data, error, response } = await api.GET("/v1/scm-installations", {
|
|
679
|
-
params: { query: {
|
|
680
|
-
workspaceId,
|
|
681
|
-
limit: 100,
|
|
682
|
-
...cursor ? { cursor } : {}
|
|
683
|
-
} },
|
|
684
|
-
signal
|
|
685
|
-
});
|
|
686
|
-
if (error || !data) throw repoConnectionApiError("Failed to inspect GitHub App installations", response, error);
|
|
687
|
-
installations.push(...data.data);
|
|
688
|
-
cursor = readNextPaginationCursor(data.pagination, seenCursors, "Failed to inspect GitHub App installations", response);
|
|
689
|
-
} while (cursor);
|
|
690
|
-
return installations;
|
|
691
|
-
}
|
|
692
|
-
async function findRepositoryInInstallation(api, installationId, repository, signal) {
|
|
693
|
-
const expectedFullName = repository.fullName.toLowerCase();
|
|
694
|
-
let cursor;
|
|
695
|
-
const seenCursors = /* @__PURE__ */ new Set();
|
|
696
|
-
do {
|
|
697
|
-
const { data, error, response } = await api.GET("/v1/scm-installations/{installationId}/repositories", {
|
|
698
|
-
params: {
|
|
699
|
-
path: { installationId },
|
|
700
|
-
query: {
|
|
701
|
-
limit: 100,
|
|
702
|
-
...cursor ? { cursor } : {}
|
|
703
|
-
}
|
|
704
|
-
},
|
|
705
|
-
signal
|
|
706
|
-
});
|
|
707
|
-
if (error || !data) throw repoConnectionApiError("Failed to inspect GitHub repositories", response, error);
|
|
708
|
-
const matchedRepository = data.data.find((candidate) => candidate.fullName.toLowerCase() === expectedFullName);
|
|
709
|
-
if (matchedRepository) return matchedRepository;
|
|
710
|
-
cursor = readNextPaginationCursor(data.pagination, seenCursors, "Failed to inspect GitHub repositories", response);
|
|
711
|
-
} while (cursor);
|
|
712
|
-
return null;
|
|
713
|
-
}
|
|
714
|
-
function readNextPaginationCursor(pagination, seenCursors, summary, response) {
|
|
715
|
-
const nextCursor = pagination.hasMore && pagination.nextCursor ? pagination.nextCursor : void 0;
|
|
716
|
-
if (!nextCursor) return;
|
|
717
|
-
if (seenCursors.has(nextCursor)) throw repoConnectionApiError(summary, response, { error: { message: "Pagination cursor did not advance." } });
|
|
718
|
-
seenCursors.add(nextCursor);
|
|
719
|
-
return nextCursor;
|
|
720
|
-
}
|
|
721
|
-
async function findRepositoryInInstallationIfAvailable(api, installationId, repository, signal) {
|
|
722
|
-
try {
|
|
723
|
-
return await findRepositoryInInstallation(api, installationId, repository, signal);
|
|
724
|
-
} catch (error) {
|
|
725
|
-
if (signal.aborted) throw error;
|
|
726
|
-
if (isUnavailableScmInstallationError(error)) return "unavailable";
|
|
727
|
-
throw error;
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
function isUnavailableScmInstallationError(error) {
|
|
731
|
-
if (!(error instanceof CliError) || error.code !== "REPO_CONNECTION_FAILED") return false;
|
|
732
|
-
return error.meta.status === 404 || error.meta.status === 422;
|
|
733
|
-
}
|
|
734
|
-
async function createGitHubInstallIntent(api, workspaceId, signal) {
|
|
735
|
-
const { data, error, response } = await api.POST("/v1/scm-installations/install-intents", {
|
|
736
|
-
body: {
|
|
737
|
-
provider: "github",
|
|
738
|
-
workspaceId
|
|
739
|
-
},
|
|
740
|
-
signal
|
|
741
|
-
});
|
|
742
|
-
if (error || !data) throw repoConnectionApiError("Failed to create GitHub App installation link", response, error);
|
|
743
|
-
return data.data.installUrl;
|
|
744
|
-
}
|
|
745
|
-
async function openInstallUrlIfInteractive(context, installUrl) {
|
|
746
|
-
if (!canPrompt(context)) return false;
|
|
747
|
-
try {
|
|
748
|
-
context.runtime.signal.throwIfAborted();
|
|
749
|
-
await open(installUrl);
|
|
750
|
-
context.runtime.signal.throwIfAborted();
|
|
751
|
-
return true;
|
|
752
|
-
} catch (error) {
|
|
753
|
-
if (context.runtime.signal.aborted) throw error;
|
|
754
|
-
return false;
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
async function readFirstSourceRepository(api, projectId, signal) {
|
|
758
|
-
const { data, error, response } = await api.GET("/v1/source-repositories", {
|
|
759
|
-
params: { query: {
|
|
760
|
-
projectId,
|
|
761
|
-
limit: 1
|
|
762
|
-
} },
|
|
763
|
-
signal
|
|
764
|
-
});
|
|
765
|
-
if (error || !data) throw repoConnectionApiError("Failed to inspect GitHub repository connection", response, error);
|
|
766
|
-
return data.data[0] ?? null;
|
|
767
|
-
}
|
|
768
|
-
function toRepositoryConnection(record) {
|
|
769
|
-
const [owner = "", name = ""] = record.repoFullName.split("/");
|
|
770
|
-
return {
|
|
771
|
-
id: record.id,
|
|
772
|
-
provider: "github",
|
|
773
|
-
repoId: record.repoId,
|
|
774
|
-
repository: {
|
|
775
|
-
owner,
|
|
776
|
-
name,
|
|
777
|
-
fullName: record.repoFullName,
|
|
778
|
-
url: `https://github.com/${record.repoFullName}`
|
|
779
|
-
},
|
|
780
|
-
defaultBranch: record.defaultBranch,
|
|
781
|
-
isPrivate: record.isPrivate,
|
|
782
|
-
status: record.status,
|
|
783
|
-
installation: {
|
|
784
|
-
id: record.installationId,
|
|
785
|
-
status: "connected"
|
|
786
|
-
},
|
|
787
|
-
automation: {
|
|
788
|
-
branches: record.status === "active",
|
|
789
|
-
pullRequests: false,
|
|
790
|
-
comments: false
|
|
791
|
-
},
|
|
792
|
-
connectedAt: record.createdAt,
|
|
793
|
-
updatedAt: record.updatedAt
|
|
794
|
-
};
|
|
795
|
-
}
|
|
796
|
-
function unsupportedRepositoryProviderError() {
|
|
797
|
-
return new CliError({
|
|
798
|
-
code: "REPO_PROVIDER_UNSUPPORTED",
|
|
799
|
-
domain: "project",
|
|
800
|
-
summary: "Repository provider is not supported",
|
|
801
|
-
why: "Repository connection supports GitHub repository URLs only.",
|
|
802
|
-
fix: "Pass a GitHub repository URL such as git@github.com:prisma/prisma-cli.git.",
|
|
803
|
-
exitCode: 2,
|
|
804
|
-
nextSteps: ["prisma-cli git connect git@github.com:owner/repo.git"]
|
|
805
|
-
});
|
|
806
|
-
}
|
|
807
|
-
function repoNotConnectedError() {
|
|
808
|
-
return new CliError({
|
|
809
|
-
code: "REPO_NOT_CONNECTED",
|
|
810
|
-
domain: "project",
|
|
811
|
-
summary: "No GitHub repository connected",
|
|
812
|
-
why: "The resolved project does not have an active GitHub repository connection.",
|
|
813
|
-
fix: "Run prisma-cli git connect before disconnecting.",
|
|
814
|
-
exitCode: 1,
|
|
815
|
-
nextSteps: ["prisma-cli git connect"]
|
|
816
|
-
});
|
|
817
|
-
}
|
|
818
|
-
function repoInstallationRequiredError(repository, installUrl, opened) {
|
|
819
|
-
return new CliError({
|
|
820
|
-
code: "REPO_INSTALLATION_REQUIRED",
|
|
821
|
-
domain: "project",
|
|
822
|
-
summary: "GitHub App installation required",
|
|
823
|
-
why: `The selected workspace does not have a GitHub App installation that can be used to link ${repository.fullName}.`,
|
|
824
|
-
fix: opened ? "Finish installing the GitHub App in the browser, then rerun prisma-cli git connect." : "Open the GitHub App installation URL, approve access, then rerun prisma-cli git connect.",
|
|
825
|
-
meta: {
|
|
826
|
-
repository: repository.fullName,
|
|
827
|
-
installUrl,
|
|
828
|
-
opened
|
|
829
|
-
},
|
|
830
|
-
exitCode: 1,
|
|
831
|
-
nextSteps: [installUrl, `prisma-cli git connect ${repository.url}`]
|
|
832
|
-
});
|
|
833
|
-
}
|
|
834
|
-
function repoNotAccessibleError(repository, installUrl, opened) {
|
|
835
|
-
return new CliError({
|
|
836
|
-
code: "REPO_NOT_ACCESSIBLE",
|
|
837
|
-
domain: "project",
|
|
838
|
-
summary: "GitHub repository is not accessible",
|
|
839
|
-
why: `The GitHub App installations connected to this workspace do not expose ${repository.fullName}.`,
|
|
840
|
-
fix: "Open the GitHub App installation URL, grant access to this repository, then rerun prisma-cli git connect.",
|
|
841
|
-
meta: {
|
|
842
|
-
repository: repository.fullName,
|
|
843
|
-
installUrl,
|
|
844
|
-
opened
|
|
845
|
-
},
|
|
846
|
-
exitCode: 1,
|
|
847
|
-
nextSteps: [installUrl, `prisma-cli git connect ${repository.url}`]
|
|
848
|
-
});
|
|
849
|
-
}
|
|
850
|
-
function repoAlreadyConnectedError(repositoryFullName) {
|
|
851
|
-
return new CliError({
|
|
852
|
-
code: "REPO_ALREADY_CONNECTED",
|
|
853
|
-
domain: "project",
|
|
854
|
-
summary: "Project already has a GitHub repository connected",
|
|
855
|
-
why: `The resolved project is already connected to ${repositoryFullName}.`,
|
|
856
|
-
fix: "Disconnect the existing repository before connecting a different one.",
|
|
857
|
-
meta: { repository: repositoryFullName },
|
|
858
|
-
exitCode: 1,
|
|
859
|
-
nextSteps: ["prisma-cli git disconnect"]
|
|
860
|
-
});
|
|
861
|
-
}
|
|
862
|
-
function repositoryFullNamesMatch(left, right) {
|
|
863
|
-
return left.toLowerCase() === right.toLowerCase();
|
|
864
|
-
}
|
|
865
|
-
function repoConnectionApiError(summary, response, error) {
|
|
866
|
-
const status = response?.status ?? 0;
|
|
867
|
-
const apiCode = error?.error?.code;
|
|
868
|
-
const apiMessage = error?.error?.message;
|
|
869
|
-
const apiHint = error?.error?.hint;
|
|
870
|
-
if (status === 401 || status === 403) return authRequiredError(["prisma-cli auth login"]);
|
|
871
|
-
return new CliError({
|
|
872
|
-
code: "REPO_CONNECTION_FAILED",
|
|
873
|
-
domain: "project",
|
|
874
|
-
summary,
|
|
875
|
-
why: apiMessage ?? `The Management API returned status ${status || "unknown"}.`,
|
|
876
|
-
fix: apiHint ?? repoConnectionFixForStatus(status),
|
|
877
|
-
meta: {
|
|
878
|
-
status,
|
|
879
|
-
...apiCode ? { apiCode } : {}
|
|
880
|
-
},
|
|
881
|
-
exitCode: 1,
|
|
882
|
-
nextSteps: ["prisma-cli project show"]
|
|
883
|
-
});
|
|
884
|
-
}
|
|
885
|
-
function repoConnectionFixForStatus(status) {
|
|
886
|
-
if (status === 404) return "Install the GitHub App for this workspace, then rerun prisma-cli git connect.";
|
|
887
|
-
if (status === 409) return "This project or repository is already linked. Disconnect the old link first, then try again.";
|
|
888
|
-
if (status === 422) return "Make sure the GitHub App installation has access to this repository.";
|
|
889
|
-
return "Re-run with --trace for the underlying API response details.";
|
|
890
|
-
}
|
|
891
|
-
//#endregion
|
|
892
|
-
export { listRealWorkspaceProjects, runGitConnect, runGitDisconnect, runProjectCreate, runProjectLink, runProjectList, runProjectRemove, runProjectRename, runProjectShow, runProjectTransfer };
|