@prisma/cli 3.0.0-beta.0 → 3.0.0-beta.10
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 +18 -3
- package/dist/adapters/git.js +8 -3
- package/dist/adapters/local-state.js +12 -4
- package/dist/adapters/mock-api.js +81 -2
- package/dist/adapters/token-storage.js +63 -22
- package/dist/cli.js +17 -2
- package/dist/cli2.js +7 -3
- package/dist/commands/app/index.js +26 -13
- package/dist/commands/branch/index.js +2 -27
- package/dist/commands/database/index.js +159 -0
- package/dist/commands/env.js +8 -4
- package/dist/commands/project/index.js +28 -2
- package/dist/controllers/app-env-api.js +54 -0
- package/dist/controllers/app-env-file.js +181 -0
- package/dist/controllers/app-env.js +284 -132
- package/dist/controllers/app.js +493 -344
- package/dist/controllers/auth.js +8 -8
- package/dist/controllers/branch.js +78 -48
- package/dist/controllers/database.js +318 -0
- package/dist/controllers/project.js +302 -75
- package/dist/lib/app/branch-database-deploy.js +373 -0
- package/dist/lib/app/branch-database.js +316 -0
- package/dist/lib/app/bun-project.js +12 -5
- package/dist/lib/app/deploy-output.js +10 -1
- 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 +34 -18
- package/dist/lib/app/preview-branch-database.js +102 -0
- package/dist/lib/app/preview-build-settings.js +385 -0
- package/dist/lib/app/preview-build.js +272 -81
- package/dist/lib/app/preview-provider.js +163 -54
- package/dist/lib/app/production-deploy-gate.js +161 -0
- package/dist/lib/auth/auth-ops.js +69 -19
- package/dist/lib/auth/guard.js +3 -2
- package/dist/lib/auth/login.js +109 -14
- package/dist/lib/database/provider.js +167 -0
- package/dist/lib/diagnostics.js +15 -0
- package/dist/lib/git/local-branch.js +41 -0
- package/dist/lib/git/local-status.js +57 -0
- package/dist/lib/project/interactive-setup.js +56 -0
- package/dist/lib/project/local-pin.js +182 -33
- package/dist/lib/project/resolution.js +287 -105
- package/dist/lib/project/setup.js +132 -0
- package/dist/presenters/app-env.js +149 -14
- package/dist/presenters/app.js +170 -20
- package/dist/presenters/auth.js +19 -6
- package/dist/presenters/branch.js +37 -102
- package/dist/presenters/database.js +274 -0
- package/dist/presenters/project.js +100 -47
- package/dist/presenters/verbose-context.js +64 -0
- package/dist/shell/command-arguments.js +6 -0
- package/dist/shell/command-meta.js +139 -16
- package/dist/shell/command-runner.js +38 -8
- package/dist/shell/diagnostics-output.js +63 -0
- package/dist/shell/errors.js +14 -1
- package/dist/shell/output.js +13 -2
- package/dist/shell/runtime.js +3 -3
- package/dist/shell/ui.js +23 -1
- package/dist/shell/update-check.js +247 -0
- package/dist/use-cases/auth.js +15 -4
- 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 +12 -10
|
@@ -1,63 +1,232 @@
|
|
|
1
|
-
import { CliError, authRequiredError, usageError, workspaceRequiredError } from "../shell/errors.js";
|
|
1
|
+
import { CliError, authRequiredError, featureUnavailableError, usageError, workspaceRequiredError } from "../shell/errors.js";
|
|
2
2
|
import { renderSummaryLine } from "../shell/ui.js";
|
|
3
3
|
import { canPrompt } from "../shell/runtime.js";
|
|
4
4
|
import { requireComputeAuth } from "../lib/auth/guard.js";
|
|
5
|
-
import {
|
|
5
|
+
import { formatCommandArgument } from "../shell/command-arguments.js";
|
|
6
|
+
import { readLocalResolutionPin } from "../lib/project/local-pin.js";
|
|
7
|
+
import { buildProjectSetupNextActions, inferTargetName, inspectProjectBinding, projectResolutionErrorToCliError, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
|
|
8
|
+
import { bindProjectToDirectory, isValidProjectSetupName, projectCreateFailedError, projectDirectoryBindingErrorToCliError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
|
|
9
|
+
import { promptForProjectSetupChoice } from "../lib/project/interactive-setup.js";
|
|
10
|
+
import { createPreviewAppProvider } from "../lib/app/preview-provider.js";
|
|
6
11
|
import { createCliUseCaseGateways } from "../use-cases/create-cli-gateways.js";
|
|
7
12
|
import { requireAuthenticatedAuthState } from "./auth.js";
|
|
8
13
|
import { parseGitHubRepositoryUrl, readGitOriginRemote } from "../adapters/git.js";
|
|
9
14
|
import { createProjectUseCases } from "../use-cases/project.js";
|
|
10
15
|
import open from "open";
|
|
16
|
+
import { matchError } from "better-result";
|
|
11
17
|
//#region src/controllers/project.ts
|
|
12
18
|
const GITHUB_INSTALL_POLL_INTERVAL_MS = 2e3;
|
|
13
19
|
const GITHUB_INSTALL_POLL_TIMEOUT_MS = 12e4;
|
|
14
20
|
function isRealMode(context) {
|
|
15
21
|
return !context.runtime.fixturePath && !context.runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
|
|
16
22
|
}
|
|
23
|
+
async function readProjectListLocalBinding(cwd, workspace, projects, signal) {
|
|
24
|
+
const pinResult = await readLocalResolutionPin(cwd, signal);
|
|
25
|
+
if (pinResult.isErr()) return localPinReadErrorToInvalidLocalBinding(pinResult.error);
|
|
26
|
+
const pin = pinResult.value;
|
|
27
|
+
if (pin.kind === "present") return pin.pin.workspaceId === workspace.id && projects.some((project) => project.id === pin.pin.projectId) ? { status: "linked" } : { status: "invalid" };
|
|
28
|
+
return { status: "not-linked" };
|
|
29
|
+
}
|
|
30
|
+
function localPinReadErrorToInvalidLocalBinding(error) {
|
|
31
|
+
return matchError(error, {
|
|
32
|
+
LocalResolutionPinInvalidJsonError: () => ({ status: "invalid" }),
|
|
33
|
+
LocalResolutionPinInvalidShapeError: () => ({ status: "invalid" }),
|
|
34
|
+
LocalResolutionPinReadAbortedError: (error) => {
|
|
35
|
+
throw error;
|
|
36
|
+
},
|
|
37
|
+
UnhandledException: (error) => {
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
17
42
|
async function runProjectList(context) {
|
|
18
43
|
const authState = await requireAuthenticatedAuthState(context);
|
|
19
44
|
const workspace = authState.workspace;
|
|
20
45
|
if (!workspace) throw workspaceRequiredError();
|
|
21
46
|
if (isRealMode(context)) {
|
|
22
|
-
const client = await requireComputeAuth(context.runtime.env);
|
|
47
|
+
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
23
48
|
if (!client) throw authRequiredError();
|
|
49
|
+
const projects = sortProjects(await listRealWorkspaceProjects(client, workspace, context.runtime.signal));
|
|
50
|
+
const localBinding = await readProjectListLocalBinding(context.runtime.cwd, workspace, projects, context.runtime.signal);
|
|
51
|
+
const nextActions = buildProjectListNextActions(localBinding);
|
|
24
52
|
return {
|
|
25
53
|
command: "project.list",
|
|
26
54
|
result: {
|
|
27
55
|
workspace,
|
|
28
|
-
projects:
|
|
56
|
+
projects: projects.map(toProjectSummary),
|
|
57
|
+
localBinding
|
|
29
58
|
},
|
|
30
59
|
warnings: [],
|
|
31
|
-
nextSteps: []
|
|
60
|
+
nextSteps: [],
|
|
61
|
+
nextActions
|
|
32
62
|
};
|
|
33
63
|
}
|
|
64
|
+
const result = await createProjectUseCases(createCliUseCaseGateways(context)).list(authState);
|
|
65
|
+
const localBinding = await readProjectListLocalBinding(context.runtime.cwd, workspace, result.projects, context.runtime.signal);
|
|
66
|
+
const nextActions = buildProjectListNextActions(localBinding);
|
|
34
67
|
return {
|
|
35
68
|
command: "project.list",
|
|
36
|
-
result:
|
|
69
|
+
result: {
|
|
70
|
+
...result,
|
|
71
|
+
localBinding
|
|
72
|
+
},
|
|
37
73
|
warnings: [],
|
|
38
|
-
nextSteps: []
|
|
74
|
+
nextSteps: [],
|
|
75
|
+
nextActions
|
|
39
76
|
};
|
|
40
77
|
}
|
|
78
|
+
function buildProjectListNextActions(localBinding) {
|
|
79
|
+
return localBinding?.status === "linked" ? [] : buildProjectSetupNextActions({
|
|
80
|
+
createCommand: "prisma-cli project create <name>",
|
|
81
|
+
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."
|
|
82
|
+
});
|
|
83
|
+
}
|
|
41
84
|
async function runProjectShow(context, explicitProject) {
|
|
42
85
|
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
43
86
|
if (!workspace) throw workspaceRequiredError();
|
|
87
|
+
const result = isRealMode(context) ? await resolveProjectShowInRealMode(context, workspace, explicitProject) : await resolveProjectShowInFixtureMode(context, workspace, explicitProject);
|
|
44
88
|
return {
|
|
45
89
|
command: "project.show",
|
|
46
|
-
result
|
|
90
|
+
result,
|
|
47
91
|
warnings: [],
|
|
48
|
-
nextSteps: []
|
|
92
|
+
nextSteps: [],
|
|
93
|
+
nextActions: result.project === null ? buildProjectSetupNextActions({
|
|
94
|
+
commandName: "project show",
|
|
95
|
+
suggestedProjectName: result.suggestedProjectName,
|
|
96
|
+
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."
|
|
97
|
+
}) : []
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
async function runProjectCreate(context, projectName) {
|
|
101
|
+
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
102
|
+
if (!workspace) throw workspaceRequiredError();
|
|
103
|
+
if (!isValidProjectSetupName(projectName)) throw projectSetupNameRequiredError("project create");
|
|
104
|
+
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");
|
|
105
|
+
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
106
|
+
if (!client) throw authRequiredError();
|
|
107
|
+
const provider = createPreviewAppProvider(client);
|
|
108
|
+
const name = projectName.trim();
|
|
109
|
+
const created = await provider.createProject({
|
|
110
|
+
name,
|
|
111
|
+
signal: context.runtime.signal
|
|
112
|
+
}).catch((error) => {
|
|
113
|
+
throw projectCreateFailedError(error, name, workspace, {
|
|
114
|
+
nextSteps: ["prisma-cli project list", "prisma-cli project link <id-or-name>"],
|
|
115
|
+
permissionFix: "Grant the token permission to create Projects in this workspace, or link an existing Project.",
|
|
116
|
+
fallbackFix: "Retry the command, or choose an existing Project with prisma-cli project link <id-or-name>."
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
const bindResult = await bindProjectToDirectory(context, workspace, {
|
|
120
|
+
id: created.id,
|
|
121
|
+
name: created.name
|
|
122
|
+
}, "created");
|
|
123
|
+
if (bindResult.isErr()) throw projectDirectoryBindingErrorToCliError(bindResult.error);
|
|
124
|
+
return {
|
|
125
|
+
command: "project.create",
|
|
126
|
+
result: bindResult.value,
|
|
127
|
+
warnings: [],
|
|
128
|
+
nextSteps: ["prisma-cli app deploy"]
|
|
49
129
|
};
|
|
50
130
|
}
|
|
131
|
+
async function runProjectLink(context, projectRef) {
|
|
132
|
+
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
133
|
+
if (!workspace) throw workspaceRequiredError();
|
|
134
|
+
let provider = null;
|
|
135
|
+
let projects;
|
|
136
|
+
if (isRealMode(context)) {
|
|
137
|
+
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
138
|
+
if (!client) throw authRequiredError();
|
|
139
|
+
provider = createPreviewAppProvider(client);
|
|
140
|
+
projects = await listRealWorkspaceProjects(client, workspace, context.runtime.signal);
|
|
141
|
+
} else projects = listFixtureWorkspaceProjects(context, workspace);
|
|
142
|
+
let result;
|
|
143
|
+
if (projectRef?.trim()) result = await requireProjectDirectoryBinding(context, workspace, toProjectSummary(resolveProjectForSetup(projectRef.trim(), projects, workspace)), "linked");
|
|
144
|
+
else if (canPrompt(context) && !context.flags.yes) result = await resolveInteractiveProjectLinkSetup(context, workspace, projects, provider);
|
|
145
|
+
else throw await projectLinkTargetRequiredError(context, projects);
|
|
146
|
+
return {
|
|
147
|
+
command: "project.link",
|
|
148
|
+
result,
|
|
149
|
+
warnings: [],
|
|
150
|
+
nextSteps: ["prisma-cli app deploy"]
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
async function resolveInteractiveProjectLinkSetup(context, workspace, projects, provider) {
|
|
154
|
+
const setup = await promptForProjectSetupChoice({
|
|
155
|
+
context,
|
|
156
|
+
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
|
+
},
|
|
161
|
+
cancel: {
|
|
162
|
+
why: "Project link needs a Project before it can continue.",
|
|
163
|
+
fix: "Choose an existing Project or create a new one, then rerun project link.",
|
|
164
|
+
nextSteps: ["prisma-cli project link <id-or-name>", "prisma-cli project create <name>"]
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
return requireProjectDirectoryBinding(context, workspace, setup.project, setup.action);
|
|
168
|
+
}
|
|
169
|
+
async function requireProjectDirectoryBinding(context, workspace, project, action) {
|
|
170
|
+
const bindResult = await bindProjectToDirectory(context, workspace, project, action);
|
|
171
|
+
if (bindResult.isErr()) throw projectDirectoryBindingErrorToCliError(bindResult.error);
|
|
172
|
+
return bindResult.value;
|
|
173
|
+
}
|
|
174
|
+
async function createProjectForLinkSetup(provider, projectName, workspace, signal) {
|
|
175
|
+
const created = await provider.createProject({
|
|
176
|
+
name: projectName,
|
|
177
|
+
signal
|
|
178
|
+
}).catch((error) => {
|
|
179
|
+
throw projectCreateFailedError(error, projectName, workspace, {
|
|
180
|
+
nextSteps: [
|
|
181
|
+
"prisma-cli project list",
|
|
182
|
+
"prisma-cli project link <id-or-name>",
|
|
183
|
+
`prisma-cli project create ${formatCommandArgument(projectName)}`
|
|
184
|
+
],
|
|
185
|
+
permissionFix: "Grant the token permission to create Projects in this workspace, or link an existing Project.",
|
|
186
|
+
fallbackFix: "Retry the command, or choose an existing Project with prisma-cli project link <id-or-name>."
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
return {
|
|
190
|
+
id: created.id,
|
|
191
|
+
name: created.name,
|
|
192
|
+
workspace
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
async function projectLinkTargetRequiredError(context, projects) {
|
|
196
|
+
const suggestedName = await inferTargetName(context.runtime.cwd, context.runtime.signal);
|
|
197
|
+
const createCommand = `prisma-cli project create ${formatCommandArgument(suggestedName.name)}`;
|
|
198
|
+
const recoveryCommands = ["prisma-cli project link <id-or-name>", createCommand];
|
|
199
|
+
return new CliError({
|
|
200
|
+
code: "PROJECT_LINK_TARGET_REQUIRED",
|
|
201
|
+
domain: "project",
|
|
202
|
+
summary: "Choose a Project to link this directory",
|
|
203
|
+
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.",
|
|
204
|
+
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.",
|
|
205
|
+
meta: {
|
|
206
|
+
suggestedProjectName: suggestedName.name,
|
|
207
|
+
suggestedProjectNameSource: suggestedName.source,
|
|
208
|
+
candidates: sortProjects(projects).map(toProjectSummary),
|
|
209
|
+
recoveryCommands
|
|
210
|
+
},
|
|
211
|
+
exitCode: 2,
|
|
212
|
+
nextSteps: ["prisma-cli project list", ...recoveryCommands],
|
|
213
|
+
nextActions: buildProjectSetupNextActions({
|
|
214
|
+
suggestedProjectName: suggestedName.name,
|
|
215
|
+
createCommand,
|
|
216
|
+
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."
|
|
217
|
+
})
|
|
218
|
+
});
|
|
219
|
+
}
|
|
51
220
|
async function runGitConnect(context, gitUrl, options = {}) {
|
|
52
221
|
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
53
222
|
if (!workspace) throw workspaceRequiredError();
|
|
54
223
|
if (isRealMode(context)) {
|
|
55
|
-
const client = await requireComputeAuth(context.runtime.env);
|
|
224
|
+
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
56
225
|
if (!client) throw authRequiredError();
|
|
57
|
-
const target = await
|
|
226
|
+
const target = await resolveRequiredProjectInRealMode(context, workspace, options.project, "git connect");
|
|
58
227
|
const repository = await resolveRepositoryForConnect(context, gitUrl);
|
|
59
228
|
const api = client;
|
|
60
|
-
const existing = await readFirstSourceRepository(api, target.project.id);
|
|
229
|
+
const existing = await readFirstSourceRepository(api, target.project.id, context.runtime.signal);
|
|
61
230
|
if (existing) {
|
|
62
231
|
const existingConnection = toRepositoryConnection(existing);
|
|
63
232
|
if (repositoryFullNamesMatch(existingConnection.repository.fullName, repository.fullName)) return {
|
|
@@ -72,12 +241,15 @@ async function runGitConnect(context, gitUrl, options = {}) {
|
|
|
72
241
|
throw repoAlreadyConnectedError(existingConnection.repository.fullName);
|
|
73
242
|
}
|
|
74
243
|
const resolvedRepository = await resolveInstalledRepository(context, api, workspace.id, repository);
|
|
75
|
-
const { data, error, response } = await api.POST("/v1/source-repositories", {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
244
|
+
const { data, error, response } = await api.POST("/v1/source-repositories", {
|
|
245
|
+
body: {
|
|
246
|
+
projectId: target.project.id,
|
|
247
|
+
provider: "github",
|
|
248
|
+
providerRepositoryId: resolvedRepository.repository.id,
|
|
249
|
+
installationId: resolvedRepository.installation.id
|
|
250
|
+
},
|
|
251
|
+
signal: context.runtime.signal
|
|
252
|
+
});
|
|
81
253
|
if (error || !data) throw repoConnectionApiError("Failed to connect GitHub repository", response, error);
|
|
82
254
|
return {
|
|
83
255
|
command: "git.connect",
|
|
@@ -89,7 +261,7 @@ async function runGitConnect(context, gitUrl, options = {}) {
|
|
|
89
261
|
nextSteps: []
|
|
90
262
|
};
|
|
91
263
|
}
|
|
92
|
-
const target = await
|
|
264
|
+
const target = await resolveRequiredProjectInFixtureMode(context, workspace, options.project, "git connect");
|
|
93
265
|
const repository = await resolveRepositoryForConnect(context, gitUrl);
|
|
94
266
|
const existingConnection = await context.stateStore.readRepositoryConnection(target.project.id);
|
|
95
267
|
if (existingConnection) {
|
|
@@ -120,13 +292,16 @@ async function runGitDisconnect(context, options = {}) {
|
|
|
120
292
|
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
121
293
|
if (!workspace) throw workspaceRequiredError();
|
|
122
294
|
if (isRealMode(context)) {
|
|
123
|
-
const client = await requireComputeAuth(context.runtime.env);
|
|
295
|
+
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
124
296
|
if (!client) throw authRequiredError();
|
|
125
|
-
const target = await
|
|
297
|
+
const target = await resolveRequiredProjectInRealMode(context, workspace, options.project, "git disconnect");
|
|
126
298
|
const api = client;
|
|
127
|
-
const existing = await readFirstSourceRepository(api, target.project.id);
|
|
299
|
+
const existing = await readFirstSourceRepository(api, target.project.id, context.runtime.signal);
|
|
128
300
|
if (!existing) throw repoNotConnectedError();
|
|
129
|
-
const { error, response } = await api.DELETE("/v1/source-repositories/{id}", {
|
|
301
|
+
const { error, response } = await api.DELETE("/v1/source-repositories/{id}", {
|
|
302
|
+
params: { path: { id: existing.id } },
|
|
303
|
+
signal: context.runtime.signal
|
|
304
|
+
});
|
|
130
305
|
if (error) throw repoConnectionApiError("Failed to disconnect GitHub repository", response, error);
|
|
131
306
|
return {
|
|
132
307
|
command: "git.disconnect",
|
|
@@ -138,7 +313,7 @@ async function runGitDisconnect(context, options = {}) {
|
|
|
138
313
|
nextSteps: []
|
|
139
314
|
};
|
|
140
315
|
}
|
|
141
|
-
const target = await
|
|
316
|
+
const target = await resolveRequiredProjectInFixtureMode(context, workspace, options.project, "git disconnect");
|
|
142
317
|
const existingConnection = await context.stateStore.readRepositoryConnection(target.project.id);
|
|
143
318
|
if (!existingConnection) throw repoNotConnectedError();
|
|
144
319
|
await context.stateStore.clearRepositoryConnection(target.project.id);
|
|
@@ -153,30 +328,59 @@ async function runGitDisconnect(context, options = {}) {
|
|
|
153
328
|
};
|
|
154
329
|
}
|
|
155
330
|
async function resolveProjectShowInRealMode(context, workspace, explicitProject) {
|
|
156
|
-
const client = await requireComputeAuth(context.runtime.env);
|
|
331
|
+
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
157
332
|
if (!client) throw authRequiredError();
|
|
158
|
-
|
|
333
|
+
const result = await inspectProjectBinding({
|
|
159
334
|
context,
|
|
160
335
|
workspace,
|
|
161
336
|
explicitProject,
|
|
162
|
-
listProjects: () => listRealWorkspaceProjects(client, workspace),
|
|
163
|
-
|
|
337
|
+
listProjects: () => listRealWorkspaceProjects(client, workspace, context.runtime.signal),
|
|
338
|
+
commandName: "project show"
|
|
164
339
|
});
|
|
340
|
+
if (result.isErr()) throw projectResolutionErrorToCliError(result.error);
|
|
341
|
+
return result.value;
|
|
342
|
+
}
|
|
343
|
+
async function resolveRequiredProjectInRealMode(context, workspace, explicitProject, commandName) {
|
|
344
|
+
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
345
|
+
if (!client) throw authRequiredError();
|
|
346
|
+
const result = await resolveProjectTarget({
|
|
347
|
+
context,
|
|
348
|
+
workspace,
|
|
349
|
+
explicitProject,
|
|
350
|
+
listProjects: () => listRealWorkspaceProjects(client, workspace, context.runtime.signal),
|
|
351
|
+
commandName
|
|
352
|
+
});
|
|
353
|
+
if (result.isErr()) throw projectResolutionErrorToCliError(result.error);
|
|
354
|
+
return result.value;
|
|
165
355
|
}
|
|
166
356
|
async function resolveProjectShowInFixtureMode(context, workspace, explicitProject) {
|
|
167
|
-
|
|
357
|
+
const result = await inspectProjectBinding({
|
|
168
358
|
context,
|
|
169
359
|
workspace,
|
|
170
360
|
explicitProject,
|
|
171
361
|
listProjects: async () => listFixtureWorkspaceProjects(context, workspace),
|
|
172
|
-
|
|
362
|
+
commandName: "project show"
|
|
173
363
|
});
|
|
364
|
+
if (result.isErr()) throw projectResolutionErrorToCliError(result.error);
|
|
365
|
+
return result.value;
|
|
174
366
|
}
|
|
175
|
-
async function
|
|
176
|
-
const
|
|
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 });
|
|
177
380
|
return sortProjects((data?.data ?? []).filter((project) => project.workspace.id === workspace.id).map((project) => ({
|
|
178
381
|
id: project.id,
|
|
179
382
|
name: project.name,
|
|
383
|
+
..."url" in project && typeof project.url === "string" ? { url: project.url } : {},
|
|
180
384
|
slug: "slug" in project && typeof project.slug === "string" ? project.slug : null,
|
|
181
385
|
workspace: {
|
|
182
386
|
id: project.workspace.id,
|
|
@@ -188,21 +392,22 @@ function listFixtureWorkspaceProjects(context, workspace) {
|
|
|
188
392
|
return sortProjects(context.api.listProjectsForWorkspace(workspace.id).map((project) => ({
|
|
189
393
|
id: project.id,
|
|
190
394
|
name: project.name,
|
|
395
|
+
...project.url ? { url: project.url } : {},
|
|
191
396
|
slug: project.slug,
|
|
192
397
|
workspace
|
|
193
398
|
})));
|
|
194
399
|
}
|
|
195
400
|
async function resolveRepositoryForConnect(context, gitUrl) {
|
|
196
|
-
const remoteUrl = gitUrl ?? await readGitOriginRemote(context.runtime.cwd);
|
|
401
|
+
const remoteUrl = gitUrl ?? await readGitOriginRemote(context.runtime.cwd, context.runtime.signal);
|
|
197
402
|
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");
|
|
198
403
|
const repository = parseGitHubRepositoryUrl(remoteUrl);
|
|
199
404
|
if (!repository) throw unsupportedRepositoryProviderError();
|
|
200
405
|
return repository;
|
|
201
406
|
}
|
|
202
407
|
async function resolveInstalledRepository(context, api, workspaceId, repository) {
|
|
203
|
-
const lookup = await findRepositoryInInstallations(api, await listScmInstallations(api, workspaceId), repository);
|
|
408
|
+
const lookup = await findRepositoryInInstallations(api, await listScmInstallations(api, workspaceId, context.runtime.signal), repository, context.runtime.signal);
|
|
204
409
|
if (lookup.match) return lookup.match;
|
|
205
|
-
const installUrl = await createGitHubInstallIntent(api, workspaceId);
|
|
410
|
+
const installUrl = await createGitHubInstallIntent(api, workspaceId, context.runtime.signal);
|
|
206
411
|
const canWait = canPrompt(context);
|
|
207
412
|
const opened = await openInstallUrlIfInteractive(context, installUrl);
|
|
208
413
|
if (!canWait) {
|
|
@@ -215,11 +420,11 @@ async function resolveInstalledRepository(context, api, workspaceId, repository)
|
|
|
215
420
|
if (result.inspectableInstallationCount > 0) throw repoNotAccessibleError(repository, installUrl, opened);
|
|
216
421
|
throw repoInstallationRequiredError(repository, installUrl, opened);
|
|
217
422
|
}
|
|
218
|
-
async function findRepositoryInInstallations(api, installations, repository) {
|
|
423
|
+
async function findRepositoryInInstallations(api, installations, repository, signal) {
|
|
219
424
|
let inspectableInstallationCount = 0;
|
|
220
425
|
for (const installation of installations) {
|
|
221
426
|
if (installation.provider !== "github" || installation.suspended) continue;
|
|
222
|
-
const matchedRepository = await findRepositoryInInstallationIfAvailable(api, installation.id, repository);
|
|
427
|
+
const matchedRepository = await findRepositoryInInstallationIfAvailable(api, installation.id, repository, signal);
|
|
223
428
|
if (matchedRepository === "unavailable") continue;
|
|
224
429
|
inspectableInstallationCount += 1;
|
|
225
430
|
if (matchedRepository) return {
|
|
@@ -241,7 +446,8 @@ async function waitForInstalledRepository(context, api, workspaceId, repository)
|
|
|
241
446
|
const deadline = Date.now() + timeoutMs;
|
|
242
447
|
let inspectableInstallationCount = 0;
|
|
243
448
|
while (Date.now() <= deadline) {
|
|
244
|
-
|
|
449
|
+
context.runtime.signal.throwIfAborted();
|
|
450
|
+
const lookup = await findRepositoryInInstallations(api, await listScmInstallations(api, workspaceId, context.runtime.signal), repository, context.runtime.signal);
|
|
245
451
|
inspectableInstallationCount = lookup.inspectableInstallationCount;
|
|
246
452
|
if (lookup.match) return {
|
|
247
453
|
match: lookup.match,
|
|
@@ -249,7 +455,7 @@ async function waitForInstalledRepository(context, api, workspaceId, repository)
|
|
|
249
455
|
};
|
|
250
456
|
const remainingMs = deadline - Date.now();
|
|
251
457
|
if (remainingMs <= 0) break;
|
|
252
|
-
await sleep(Math.min(intervalMs, remainingMs));
|
|
458
|
+
await sleep(Math.min(intervalMs, remainingMs), context.runtime.signal);
|
|
253
459
|
}
|
|
254
460
|
return {
|
|
255
461
|
match: null,
|
|
@@ -267,37 +473,54 @@ function writeInstallWaitStatus(context, opened, installUrl) {
|
|
|
267
473
|
if (!opened) lines.push(installUrl);
|
|
268
474
|
context.output.stderr.write(`${lines.join("\n")}\n`);
|
|
269
475
|
}
|
|
270
|
-
function sleep(ms) {
|
|
271
|
-
|
|
476
|
+
function sleep(ms, signal) {
|
|
477
|
+
signal.throwIfAborted();
|
|
478
|
+
return new Promise((resolve, reject) => {
|
|
479
|
+
const onAbort = () => {
|
|
480
|
+
clearTimeout(timeout);
|
|
481
|
+
reject(signal.reason);
|
|
482
|
+
};
|
|
483
|
+
const timeout = setTimeout(() => {
|
|
484
|
+
signal.removeEventListener("abort", onAbort);
|
|
485
|
+
resolve();
|
|
486
|
+
}, ms);
|
|
487
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
488
|
+
});
|
|
272
489
|
}
|
|
273
|
-
async function listScmInstallations(api, workspaceId) {
|
|
490
|
+
async function listScmInstallations(api, workspaceId, signal) {
|
|
274
491
|
const installations = [];
|
|
275
492
|
let cursor;
|
|
276
493
|
const seenCursors = /* @__PURE__ */ new Set();
|
|
277
494
|
do {
|
|
278
|
-
const { data, error, response } = await api.GET("/v1/scm-installations", {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
495
|
+
const { data, error, response } = await api.GET("/v1/scm-installations", {
|
|
496
|
+
params: { query: {
|
|
497
|
+
workspaceId,
|
|
498
|
+
limit: 100,
|
|
499
|
+
...cursor ? { cursor } : {}
|
|
500
|
+
} },
|
|
501
|
+
signal
|
|
502
|
+
});
|
|
283
503
|
if (error || !data) throw repoConnectionApiError("Failed to inspect GitHub App installations", response, error);
|
|
284
504
|
installations.push(...data.data);
|
|
285
505
|
cursor = readNextPaginationCursor(data.pagination, seenCursors, "Failed to inspect GitHub App installations", response);
|
|
286
506
|
} while (cursor);
|
|
287
507
|
return installations;
|
|
288
508
|
}
|
|
289
|
-
async function findRepositoryInInstallation(api, installationId, repository) {
|
|
509
|
+
async function findRepositoryInInstallation(api, installationId, repository, signal) {
|
|
290
510
|
const expectedFullName = repository.fullName.toLowerCase();
|
|
291
511
|
let cursor;
|
|
292
512
|
const seenCursors = /* @__PURE__ */ new Set();
|
|
293
513
|
do {
|
|
294
|
-
const { data, error, response } = await api.GET("/v1/scm-installations/{installationId}/repositories", {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
514
|
+
const { data, error, response } = await api.GET("/v1/scm-installations/{installationId}/repositories", {
|
|
515
|
+
params: {
|
|
516
|
+
path: { installationId },
|
|
517
|
+
query: {
|
|
518
|
+
limit: 100,
|
|
519
|
+
...cursor ? { cursor } : {}
|
|
520
|
+
}
|
|
521
|
+
},
|
|
522
|
+
signal
|
|
523
|
+
});
|
|
301
524
|
if (error || !data) throw repoConnectionApiError("Failed to inspect GitHub repositories", response, error);
|
|
302
525
|
const matchedRepository = data.data.find((candidate) => candidate.fullName.toLowerCase() === expectedFullName);
|
|
303
526
|
if (matchedRepository) return matchedRepository;
|
|
@@ -312,10 +535,11 @@ function readNextPaginationCursor(pagination, seenCursors, summary, response) {
|
|
|
312
535
|
seenCursors.add(nextCursor);
|
|
313
536
|
return nextCursor;
|
|
314
537
|
}
|
|
315
|
-
async function findRepositoryInInstallationIfAvailable(api, installationId, repository) {
|
|
538
|
+
async function findRepositoryInInstallationIfAvailable(api, installationId, repository, signal) {
|
|
316
539
|
try {
|
|
317
|
-
return await findRepositoryInInstallation(api, installationId, repository);
|
|
540
|
+
return await findRepositoryInInstallation(api, installationId, repository, signal);
|
|
318
541
|
} catch (error) {
|
|
542
|
+
if (signal.aborted) throw error;
|
|
319
543
|
if (isUnavailableScmInstallationError(error)) return "unavailable";
|
|
320
544
|
throw error;
|
|
321
545
|
}
|
|
@@ -324,28 +548,37 @@ function isUnavailableScmInstallationError(error) {
|
|
|
324
548
|
if (!(error instanceof CliError) || error.code !== "REPO_CONNECTION_FAILED") return false;
|
|
325
549
|
return error.meta.status === 404 || error.meta.status === 422;
|
|
326
550
|
}
|
|
327
|
-
async function createGitHubInstallIntent(api, workspaceId) {
|
|
328
|
-
const { data, error, response } = await api.POST("/v1/scm-installations/install-intents", {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
551
|
+
async function createGitHubInstallIntent(api, workspaceId, signal) {
|
|
552
|
+
const { data, error, response } = await api.POST("/v1/scm-installations/install-intents", {
|
|
553
|
+
body: {
|
|
554
|
+
provider: "github",
|
|
555
|
+
workspaceId
|
|
556
|
+
},
|
|
557
|
+
signal
|
|
558
|
+
});
|
|
332
559
|
if (error || !data) throw repoConnectionApiError("Failed to create GitHub App installation link", response, error);
|
|
333
560
|
return data.data.installUrl;
|
|
334
561
|
}
|
|
335
562
|
async function openInstallUrlIfInteractive(context, installUrl) {
|
|
336
563
|
if (!canPrompt(context)) return false;
|
|
337
564
|
try {
|
|
565
|
+
context.runtime.signal.throwIfAborted();
|
|
338
566
|
await open(installUrl);
|
|
567
|
+
context.runtime.signal.throwIfAborted();
|
|
339
568
|
return true;
|
|
340
|
-
} catch {
|
|
569
|
+
} catch (error) {
|
|
570
|
+
if (context.runtime.signal.aborted) throw error;
|
|
341
571
|
return false;
|
|
342
572
|
}
|
|
343
573
|
}
|
|
344
|
-
async function readFirstSourceRepository(api, projectId) {
|
|
345
|
-
const { data, error, response } = await api.GET("/v1/source-repositories", {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
574
|
+
async function readFirstSourceRepository(api, projectId, signal) {
|
|
575
|
+
const { data, error, response } = await api.GET("/v1/source-repositories", {
|
|
576
|
+
params: { query: {
|
|
577
|
+
projectId,
|
|
578
|
+
limit: 1
|
|
579
|
+
} },
|
|
580
|
+
signal
|
|
581
|
+
});
|
|
349
582
|
if (error || !data) throw repoConnectionApiError("Failed to inspect GitHub repository connection", response, error);
|
|
350
583
|
return data.data[0] ?? null;
|
|
351
584
|
}
|
|
@@ -494,11 +727,5 @@ function repoConnectionFixForStatus(status) {
|
|
|
494
727
|
if (status === 422) return "Make sure the GitHub App installation has access to this repository.";
|
|
495
728
|
return "Re-run with --trace for the underlying API response details.";
|
|
496
729
|
}
|
|
497
|
-
function toProjectSummary(project) {
|
|
498
|
-
return {
|
|
499
|
-
id: project.id,
|
|
500
|
-
name: project.name
|
|
501
|
-
};
|
|
502
|
-
}
|
|
503
730
|
//#endregion
|
|
504
|
-
export { listRealWorkspaceProjects, runGitConnect, runGitDisconnect, runProjectList, runProjectShow };
|
|
731
|
+
export { listFixtureWorkspaceProjects, listRealWorkspaceProjects, runGitConnect, runGitDisconnect, runProjectCreate, runProjectLink, runProjectList, runProjectShow };
|