@prisma/cli 3.0.0-beta.1 → 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 +5 -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 +22 -10
- 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/controllers/app-env-api.js +54 -0
- package/dist/controllers/app-env-file.js +181 -0
- package/dist/controllers/app-env.js +283 -129
- package/dist/controllers/app.js +247 -106
- 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 +153 -82
- 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 +30 -21
- 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 +1 -1
- package/dist/lib/project/local-pin.js +182 -33
- package/dist/lib/project/resolution.js +203 -49
- package/dist/lib/project/setup.js +59 -15
- package/dist/presenters/app-env.js +149 -14
- package/dist/presenters/app.js +170 -20
- package/dist/presenters/branch.js +37 -102
- package/dist/presenters/database.js +274 -0
- package/dist/presenters/project.js +63 -39
- package/dist/presenters/verbose-context.js +64 -0
- package/dist/shell/command-meta.js +103 -13
- package/dist/shell/command-runner.js +34 -6
- package/dist/shell/diagnostics-output.js +63 -0
- package/dist/shell/errors.js +12 -1
- package/dist/shell/output.js +10 -1
- 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/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
|
@@ -4,8 +4,8 @@ import { canPrompt } from "../shell/runtime.js";
|
|
|
4
4
|
import { requireComputeAuth } from "../lib/auth/guard.js";
|
|
5
5
|
import { formatCommandArgument } from "../shell/command-arguments.js";
|
|
6
6
|
import { readLocalResolutionPin } from "../lib/project/local-pin.js";
|
|
7
|
-
import { buildProjectSetupNextActions, inferTargetName, inspectProjectBinding, resolveProjectTarget, sortProjects } from "../lib/project/resolution.js";
|
|
8
|
-
import { bindProjectToDirectory, isValidProjectSetupName, projectCreateFailedError, projectSetupNameRequiredError, resolveProjectForSetup, toProjectSummary } from "../lib/project/setup.js";
|
|
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
9
|
import { promptForProjectSetupChoice } from "../lib/project/interactive-setup.js";
|
|
10
10
|
import { createPreviewAppProvider } from "../lib/app/preview-provider.js";
|
|
11
11
|
import { createCliUseCaseGateways } from "../use-cases/create-cli-gateways.js";
|
|
@@ -13,27 +13,41 @@ import { requireAuthenticatedAuthState } from "./auth.js";
|
|
|
13
13
|
import { parseGitHubRepositoryUrl, readGitOriginRemote } from "../adapters/git.js";
|
|
14
14
|
import { createProjectUseCases } from "../use-cases/project.js";
|
|
15
15
|
import open from "open";
|
|
16
|
+
import { matchError } from "better-result";
|
|
16
17
|
//#region src/controllers/project.ts
|
|
17
18
|
const GITHUB_INSTALL_POLL_INTERVAL_MS = 2e3;
|
|
18
19
|
const GITHUB_INSTALL_POLL_TIMEOUT_MS = 12e4;
|
|
19
20
|
function isRealMode(context) {
|
|
20
21
|
return !context.runtime.fixturePath && !context.runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
|
|
21
22
|
}
|
|
22
|
-
async function readProjectListLocalBinding(cwd, workspace, projects) {
|
|
23
|
-
const
|
|
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;
|
|
24
27
|
if (pin.kind === "present") return pin.pin.workspaceId === workspace.id && projects.some((project) => project.id === pin.pin.projectId) ? { status: "linked" } : { status: "invalid" };
|
|
25
|
-
if (pin.kind === "invalid") return { status: "invalid" };
|
|
26
28
|
return { status: "not-linked" };
|
|
27
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
|
+
}
|
|
28
42
|
async function runProjectList(context) {
|
|
29
43
|
const authState = await requireAuthenticatedAuthState(context);
|
|
30
44
|
const workspace = authState.workspace;
|
|
31
45
|
if (!workspace) throw workspaceRequiredError();
|
|
32
46
|
if (isRealMode(context)) {
|
|
33
|
-
const client = await requireComputeAuth(context.runtime.env);
|
|
47
|
+
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
34
48
|
if (!client) throw authRequiredError();
|
|
35
|
-
const projects = sortProjects(await listRealWorkspaceProjects(client, workspace));
|
|
36
|
-
const localBinding = await readProjectListLocalBinding(context.runtime.cwd, workspace, projects);
|
|
49
|
+
const projects = sortProjects(await listRealWorkspaceProjects(client, workspace, context.runtime.signal));
|
|
50
|
+
const localBinding = await readProjectListLocalBinding(context.runtime.cwd, workspace, projects, context.runtime.signal);
|
|
37
51
|
const nextActions = buildProjectListNextActions(localBinding);
|
|
38
52
|
return {
|
|
39
53
|
command: "project.list",
|
|
@@ -48,7 +62,7 @@ async function runProjectList(context) {
|
|
|
48
62
|
};
|
|
49
63
|
}
|
|
50
64
|
const result = await createProjectUseCases(createCliUseCaseGateways(context)).list(authState);
|
|
51
|
-
const localBinding = await readProjectListLocalBinding(context.runtime.cwd, workspace, result.projects);
|
|
65
|
+
const localBinding = await readProjectListLocalBinding(context.runtime.cwd, workspace, result.projects, context.runtime.signal);
|
|
52
66
|
const nextActions = buildProjectListNextActions(localBinding);
|
|
53
67
|
return {
|
|
54
68
|
command: "project.list",
|
|
@@ -88,23 +102,28 @@ async function runProjectCreate(context, projectName) {
|
|
|
88
102
|
if (!workspace) throw workspaceRequiredError();
|
|
89
103
|
if (!isValidProjectSetupName(projectName)) throw projectSetupNameRequiredError("project create");
|
|
90
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");
|
|
91
|
-
const client = await requireComputeAuth(context.runtime.env);
|
|
105
|
+
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
92
106
|
if (!client) throw authRequiredError();
|
|
93
107
|
const provider = createPreviewAppProvider(client);
|
|
94
108
|
const name = projectName.trim();
|
|
95
|
-
const created = await provider.createProject({
|
|
109
|
+
const created = await provider.createProject({
|
|
110
|
+
name,
|
|
111
|
+
signal: context.runtime.signal
|
|
112
|
+
}).catch((error) => {
|
|
96
113
|
throw projectCreateFailedError(error, name, workspace, {
|
|
97
114
|
nextSteps: ["prisma-cli project list", "prisma-cli project link <id-or-name>"],
|
|
98
115
|
permissionFix: "Grant the token permission to create Projects in this workspace, or link an existing Project.",
|
|
99
116
|
fallbackFix: "Retry the command, or choose an existing Project with prisma-cli project link <id-or-name>."
|
|
100
117
|
});
|
|
101
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);
|
|
102
124
|
return {
|
|
103
125
|
command: "project.create",
|
|
104
|
-
result:
|
|
105
|
-
id: created.id,
|
|
106
|
-
name: created.name
|
|
107
|
-
}, "created"),
|
|
126
|
+
result: bindResult.value,
|
|
108
127
|
warnings: [],
|
|
109
128
|
nextSteps: ["prisma-cli app deploy"]
|
|
110
129
|
};
|
|
@@ -115,13 +134,13 @@ async function runProjectLink(context, projectRef) {
|
|
|
115
134
|
let provider = null;
|
|
116
135
|
let projects;
|
|
117
136
|
if (isRealMode(context)) {
|
|
118
|
-
const client = await requireComputeAuth(context.runtime.env);
|
|
137
|
+
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
119
138
|
if (!client) throw authRequiredError();
|
|
120
139
|
provider = createPreviewAppProvider(client);
|
|
121
|
-
projects = await listRealWorkspaceProjects(client, workspace);
|
|
140
|
+
projects = await listRealWorkspaceProjects(client, workspace, context.runtime.signal);
|
|
122
141
|
} else projects = listFixtureWorkspaceProjects(context, workspace);
|
|
123
142
|
let result;
|
|
124
|
-
if (projectRef?.trim()) result = await
|
|
143
|
+
if (projectRef?.trim()) result = await requireProjectDirectoryBinding(context, workspace, toProjectSummary(resolveProjectForSetup(projectRef.trim(), projects, workspace)), "linked");
|
|
125
144
|
else if (canPrompt(context) && !context.flags.yes) result = await resolveInteractiveProjectLinkSetup(context, workspace, projects, provider);
|
|
126
145
|
else throw await projectLinkTargetRequiredError(context, projects);
|
|
127
146
|
return {
|
|
@@ -137,7 +156,7 @@ async function resolveInteractiveProjectLinkSetup(context, workspace, projects,
|
|
|
137
156
|
projects,
|
|
138
157
|
createProject: (projectName) => {
|
|
139
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");
|
|
140
|
-
return createProjectForLinkSetup(provider, projectName, workspace);
|
|
159
|
+
return createProjectForLinkSetup(provider, projectName, workspace, context.runtime.signal);
|
|
141
160
|
},
|
|
142
161
|
cancel: {
|
|
143
162
|
why: "Project link needs a Project before it can continue.",
|
|
@@ -145,10 +164,18 @@ async function resolveInteractiveProjectLinkSetup(context, workspace, projects,
|
|
|
145
164
|
nextSteps: ["prisma-cli project link <id-or-name>", "prisma-cli project create <name>"]
|
|
146
165
|
}
|
|
147
166
|
});
|
|
148
|
-
return
|
|
149
|
-
}
|
|
150
|
-
async function
|
|
151
|
-
const
|
|
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) => {
|
|
152
179
|
throw projectCreateFailedError(error, projectName, workspace, {
|
|
153
180
|
nextSteps: [
|
|
154
181
|
"prisma-cli project list",
|
|
@@ -166,7 +193,7 @@ async function createProjectForLinkSetup(provider, projectName, workspace) {
|
|
|
166
193
|
};
|
|
167
194
|
}
|
|
168
195
|
async function projectLinkTargetRequiredError(context, projects) {
|
|
169
|
-
const suggestedName = await inferTargetName(context.runtime.cwd);
|
|
196
|
+
const suggestedName = await inferTargetName(context.runtime.cwd, context.runtime.signal);
|
|
170
197
|
const createCommand = `prisma-cli project create ${formatCommandArgument(suggestedName.name)}`;
|
|
171
198
|
const recoveryCommands = ["prisma-cli project link <id-or-name>", createCommand];
|
|
172
199
|
return new CliError({
|
|
@@ -194,12 +221,12 @@ async function runGitConnect(context, gitUrl, options = {}) {
|
|
|
194
221
|
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
195
222
|
if (!workspace) throw workspaceRequiredError();
|
|
196
223
|
if (isRealMode(context)) {
|
|
197
|
-
const client = await requireComputeAuth(context.runtime.env);
|
|
224
|
+
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
198
225
|
if (!client) throw authRequiredError();
|
|
199
226
|
const target = await resolveRequiredProjectInRealMode(context, workspace, options.project, "git connect");
|
|
200
227
|
const repository = await resolveRepositoryForConnect(context, gitUrl);
|
|
201
228
|
const api = client;
|
|
202
|
-
const existing = await readFirstSourceRepository(api, target.project.id);
|
|
229
|
+
const existing = await readFirstSourceRepository(api, target.project.id, context.runtime.signal);
|
|
203
230
|
if (existing) {
|
|
204
231
|
const existingConnection = toRepositoryConnection(existing);
|
|
205
232
|
if (repositoryFullNamesMatch(existingConnection.repository.fullName, repository.fullName)) return {
|
|
@@ -214,12 +241,15 @@ async function runGitConnect(context, gitUrl, options = {}) {
|
|
|
214
241
|
throw repoAlreadyConnectedError(existingConnection.repository.fullName);
|
|
215
242
|
}
|
|
216
243
|
const resolvedRepository = await resolveInstalledRepository(context, api, workspace.id, repository);
|
|
217
|
-
const { data, error, response } = await api.POST("/v1/source-repositories", {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
+
});
|
|
223
253
|
if (error || !data) throw repoConnectionApiError("Failed to connect GitHub repository", response, error);
|
|
224
254
|
return {
|
|
225
255
|
command: "git.connect",
|
|
@@ -262,13 +292,16 @@ async function runGitDisconnect(context, options = {}) {
|
|
|
262
292
|
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
263
293
|
if (!workspace) throw workspaceRequiredError();
|
|
264
294
|
if (isRealMode(context)) {
|
|
265
|
-
const client = await requireComputeAuth(context.runtime.env);
|
|
295
|
+
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
266
296
|
if (!client) throw authRequiredError();
|
|
267
297
|
const target = await resolveRequiredProjectInRealMode(context, workspace, options.project, "git disconnect");
|
|
268
298
|
const api = client;
|
|
269
|
-
const existing = await readFirstSourceRepository(api, target.project.id);
|
|
299
|
+
const existing = await readFirstSourceRepository(api, target.project.id, context.runtime.signal);
|
|
270
300
|
if (!existing) throw repoNotConnectedError();
|
|
271
|
-
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
|
+
});
|
|
272
305
|
if (error) throw repoConnectionApiError("Failed to disconnect GitHub repository", response, error);
|
|
273
306
|
return {
|
|
274
307
|
command: "git.disconnect",
|
|
@@ -295,50 +328,59 @@ async function runGitDisconnect(context, options = {}) {
|
|
|
295
328
|
};
|
|
296
329
|
}
|
|
297
330
|
async function resolveProjectShowInRealMode(context, workspace, explicitProject) {
|
|
298
|
-
const client = await requireComputeAuth(context.runtime.env);
|
|
331
|
+
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
299
332
|
if (!client) throw authRequiredError();
|
|
300
|
-
|
|
333
|
+
const result = await inspectProjectBinding({
|
|
301
334
|
context,
|
|
302
335
|
workspace,
|
|
303
336
|
explicitProject,
|
|
304
|
-
listProjects: () => listRealWorkspaceProjects(client, workspace),
|
|
337
|
+
listProjects: () => listRealWorkspaceProjects(client, workspace, context.runtime.signal),
|
|
305
338
|
commandName: "project show"
|
|
306
339
|
});
|
|
340
|
+
if (result.isErr()) throw projectResolutionErrorToCliError(result.error);
|
|
341
|
+
return result.value;
|
|
307
342
|
}
|
|
308
343
|
async function resolveRequiredProjectInRealMode(context, workspace, explicitProject, commandName) {
|
|
309
|
-
const client = await requireComputeAuth(context.runtime.env);
|
|
344
|
+
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
310
345
|
if (!client) throw authRequiredError();
|
|
311
|
-
|
|
346
|
+
const result = await resolveProjectTarget({
|
|
312
347
|
context,
|
|
313
348
|
workspace,
|
|
314
349
|
explicitProject,
|
|
315
|
-
listProjects: () => listRealWorkspaceProjects(client, workspace),
|
|
350
|
+
listProjects: () => listRealWorkspaceProjects(client, workspace, context.runtime.signal),
|
|
316
351
|
commandName
|
|
317
352
|
});
|
|
353
|
+
if (result.isErr()) throw projectResolutionErrorToCliError(result.error);
|
|
354
|
+
return result.value;
|
|
318
355
|
}
|
|
319
356
|
async function resolveProjectShowInFixtureMode(context, workspace, explicitProject) {
|
|
320
|
-
|
|
357
|
+
const result = await inspectProjectBinding({
|
|
321
358
|
context,
|
|
322
359
|
workspace,
|
|
323
360
|
explicitProject,
|
|
324
361
|
listProjects: async () => listFixtureWorkspaceProjects(context, workspace),
|
|
325
362
|
commandName: "project show"
|
|
326
363
|
});
|
|
364
|
+
if (result.isErr()) throw projectResolutionErrorToCliError(result.error);
|
|
365
|
+
return result.value;
|
|
327
366
|
}
|
|
328
367
|
async function resolveRequiredProjectInFixtureMode(context, workspace, explicitProject, commandName) {
|
|
329
|
-
|
|
368
|
+
const result = await resolveProjectTarget({
|
|
330
369
|
context,
|
|
331
370
|
workspace,
|
|
332
371
|
explicitProject,
|
|
333
372
|
listProjects: async () => listFixtureWorkspaceProjects(context, workspace),
|
|
334
373
|
commandName
|
|
335
374
|
});
|
|
375
|
+
if (result.isErr()) throw projectResolutionErrorToCliError(result.error);
|
|
376
|
+
return result.value;
|
|
336
377
|
}
|
|
337
|
-
async function listRealWorkspaceProjects(client, workspace) {
|
|
338
|
-
const { data } = await client.GET("/v1/projects", {});
|
|
378
|
+
async function listRealWorkspaceProjects(client, workspace, signal) {
|
|
379
|
+
const { data } = await client.GET("/v1/projects", { signal });
|
|
339
380
|
return sortProjects((data?.data ?? []).filter((project) => project.workspace.id === workspace.id).map((project) => ({
|
|
340
381
|
id: project.id,
|
|
341
382
|
name: project.name,
|
|
383
|
+
..."url" in project && typeof project.url === "string" ? { url: project.url } : {},
|
|
342
384
|
slug: "slug" in project && typeof project.slug === "string" ? project.slug : null,
|
|
343
385
|
workspace: {
|
|
344
386
|
id: project.workspace.id,
|
|
@@ -350,21 +392,22 @@ function listFixtureWorkspaceProjects(context, workspace) {
|
|
|
350
392
|
return sortProjects(context.api.listProjectsForWorkspace(workspace.id).map((project) => ({
|
|
351
393
|
id: project.id,
|
|
352
394
|
name: project.name,
|
|
395
|
+
...project.url ? { url: project.url } : {},
|
|
353
396
|
slug: project.slug,
|
|
354
397
|
workspace
|
|
355
398
|
})));
|
|
356
399
|
}
|
|
357
400
|
async function resolveRepositoryForConnect(context, gitUrl) {
|
|
358
|
-
const remoteUrl = gitUrl ?? await readGitOriginRemote(context.runtime.cwd);
|
|
401
|
+
const remoteUrl = gitUrl ?? await readGitOriginRemote(context.runtime.cwd, context.runtime.signal);
|
|
359
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");
|
|
360
403
|
const repository = parseGitHubRepositoryUrl(remoteUrl);
|
|
361
404
|
if (!repository) throw unsupportedRepositoryProviderError();
|
|
362
405
|
return repository;
|
|
363
406
|
}
|
|
364
407
|
async function resolveInstalledRepository(context, api, workspaceId, repository) {
|
|
365
|
-
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);
|
|
366
409
|
if (lookup.match) return lookup.match;
|
|
367
|
-
const installUrl = await createGitHubInstallIntent(api, workspaceId);
|
|
410
|
+
const installUrl = await createGitHubInstallIntent(api, workspaceId, context.runtime.signal);
|
|
368
411
|
const canWait = canPrompt(context);
|
|
369
412
|
const opened = await openInstallUrlIfInteractive(context, installUrl);
|
|
370
413
|
if (!canWait) {
|
|
@@ -377,11 +420,11 @@ async function resolveInstalledRepository(context, api, workspaceId, repository)
|
|
|
377
420
|
if (result.inspectableInstallationCount > 0) throw repoNotAccessibleError(repository, installUrl, opened);
|
|
378
421
|
throw repoInstallationRequiredError(repository, installUrl, opened);
|
|
379
422
|
}
|
|
380
|
-
async function findRepositoryInInstallations(api, installations, repository) {
|
|
423
|
+
async function findRepositoryInInstallations(api, installations, repository, signal) {
|
|
381
424
|
let inspectableInstallationCount = 0;
|
|
382
425
|
for (const installation of installations) {
|
|
383
426
|
if (installation.provider !== "github" || installation.suspended) continue;
|
|
384
|
-
const matchedRepository = await findRepositoryInInstallationIfAvailable(api, installation.id, repository);
|
|
427
|
+
const matchedRepository = await findRepositoryInInstallationIfAvailable(api, installation.id, repository, signal);
|
|
385
428
|
if (matchedRepository === "unavailable") continue;
|
|
386
429
|
inspectableInstallationCount += 1;
|
|
387
430
|
if (matchedRepository) return {
|
|
@@ -403,7 +446,8 @@ async function waitForInstalledRepository(context, api, workspaceId, repository)
|
|
|
403
446
|
const deadline = Date.now() + timeoutMs;
|
|
404
447
|
let inspectableInstallationCount = 0;
|
|
405
448
|
while (Date.now() <= deadline) {
|
|
406
|
-
|
|
449
|
+
context.runtime.signal.throwIfAborted();
|
|
450
|
+
const lookup = await findRepositoryInInstallations(api, await listScmInstallations(api, workspaceId, context.runtime.signal), repository, context.runtime.signal);
|
|
407
451
|
inspectableInstallationCount = lookup.inspectableInstallationCount;
|
|
408
452
|
if (lookup.match) return {
|
|
409
453
|
match: lookup.match,
|
|
@@ -411,7 +455,7 @@ async function waitForInstalledRepository(context, api, workspaceId, repository)
|
|
|
411
455
|
};
|
|
412
456
|
const remainingMs = deadline - Date.now();
|
|
413
457
|
if (remainingMs <= 0) break;
|
|
414
|
-
await sleep(Math.min(intervalMs, remainingMs));
|
|
458
|
+
await sleep(Math.min(intervalMs, remainingMs), context.runtime.signal);
|
|
415
459
|
}
|
|
416
460
|
return {
|
|
417
461
|
match: null,
|
|
@@ -429,37 +473,54 @@ function writeInstallWaitStatus(context, opened, installUrl) {
|
|
|
429
473
|
if (!opened) lines.push(installUrl);
|
|
430
474
|
context.output.stderr.write(`${lines.join("\n")}\n`);
|
|
431
475
|
}
|
|
432
|
-
function sleep(ms) {
|
|
433
|
-
|
|
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
|
+
});
|
|
434
489
|
}
|
|
435
|
-
async function listScmInstallations(api, workspaceId) {
|
|
490
|
+
async function listScmInstallations(api, workspaceId, signal) {
|
|
436
491
|
const installations = [];
|
|
437
492
|
let cursor;
|
|
438
493
|
const seenCursors = /* @__PURE__ */ new Set();
|
|
439
494
|
do {
|
|
440
|
-
const { data, error, response } = await api.GET("/v1/scm-installations", {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
+
});
|
|
445
503
|
if (error || !data) throw repoConnectionApiError("Failed to inspect GitHub App installations", response, error);
|
|
446
504
|
installations.push(...data.data);
|
|
447
505
|
cursor = readNextPaginationCursor(data.pagination, seenCursors, "Failed to inspect GitHub App installations", response);
|
|
448
506
|
} while (cursor);
|
|
449
507
|
return installations;
|
|
450
508
|
}
|
|
451
|
-
async function findRepositoryInInstallation(api, installationId, repository) {
|
|
509
|
+
async function findRepositoryInInstallation(api, installationId, repository, signal) {
|
|
452
510
|
const expectedFullName = repository.fullName.toLowerCase();
|
|
453
511
|
let cursor;
|
|
454
512
|
const seenCursors = /* @__PURE__ */ new Set();
|
|
455
513
|
do {
|
|
456
|
-
const { data, error, response } = await api.GET("/v1/scm-installations/{installationId}/repositories", {
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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
|
+
});
|
|
463
524
|
if (error || !data) throw repoConnectionApiError("Failed to inspect GitHub repositories", response, error);
|
|
464
525
|
const matchedRepository = data.data.find((candidate) => candidate.fullName.toLowerCase() === expectedFullName);
|
|
465
526
|
if (matchedRepository) return matchedRepository;
|
|
@@ -474,10 +535,11 @@ function readNextPaginationCursor(pagination, seenCursors, summary, response) {
|
|
|
474
535
|
seenCursors.add(nextCursor);
|
|
475
536
|
return nextCursor;
|
|
476
537
|
}
|
|
477
|
-
async function findRepositoryInInstallationIfAvailable(api, installationId, repository) {
|
|
538
|
+
async function findRepositoryInInstallationIfAvailable(api, installationId, repository, signal) {
|
|
478
539
|
try {
|
|
479
|
-
return await findRepositoryInInstallation(api, installationId, repository);
|
|
540
|
+
return await findRepositoryInInstallation(api, installationId, repository, signal);
|
|
480
541
|
} catch (error) {
|
|
542
|
+
if (signal.aborted) throw error;
|
|
481
543
|
if (isUnavailableScmInstallationError(error)) return "unavailable";
|
|
482
544
|
throw error;
|
|
483
545
|
}
|
|
@@ -486,28 +548,37 @@ function isUnavailableScmInstallationError(error) {
|
|
|
486
548
|
if (!(error instanceof CliError) || error.code !== "REPO_CONNECTION_FAILED") return false;
|
|
487
549
|
return error.meta.status === 404 || error.meta.status === 422;
|
|
488
550
|
}
|
|
489
|
-
async function createGitHubInstallIntent(api, workspaceId) {
|
|
490
|
-
const { data, error, response } = await api.POST("/v1/scm-installations/install-intents", {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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
|
+
});
|
|
494
559
|
if (error || !data) throw repoConnectionApiError("Failed to create GitHub App installation link", response, error);
|
|
495
560
|
return data.data.installUrl;
|
|
496
561
|
}
|
|
497
562
|
async function openInstallUrlIfInteractive(context, installUrl) {
|
|
498
563
|
if (!canPrompt(context)) return false;
|
|
499
564
|
try {
|
|
565
|
+
context.runtime.signal.throwIfAborted();
|
|
500
566
|
await open(installUrl);
|
|
567
|
+
context.runtime.signal.throwIfAborted();
|
|
501
568
|
return true;
|
|
502
|
-
} catch {
|
|
569
|
+
} catch (error) {
|
|
570
|
+
if (context.runtime.signal.aborted) throw error;
|
|
503
571
|
return false;
|
|
504
572
|
}
|
|
505
573
|
}
|
|
506
|
-
async function readFirstSourceRepository(api, projectId) {
|
|
507
|
-
const { data, error, response } = await api.GET("/v1/source-repositories", {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
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
|
+
});
|
|
511
582
|
if (error || !data) throw repoConnectionApiError("Failed to inspect GitHub repository connection", response, error);
|
|
512
583
|
return data.data[0] ?? null;
|
|
513
584
|
}
|
|
@@ -657,4 +728,4 @@ function repoConnectionFixForStatus(status) {
|
|
|
657
728
|
return "Re-run with --trace for the underlying API response details.";
|
|
658
729
|
}
|
|
659
730
|
//#endregion
|
|
660
|
-
export { listRealWorkspaceProjects, runGitConnect, runGitDisconnect, runProjectCreate, runProjectLink, runProjectList, runProjectShow };
|
|
731
|
+
export { listFixtureWorkspaceProjects, listRealWorkspaceProjects, runGitConnect, runGitDisconnect, runProjectCreate, runProjectLink, runProjectList, runProjectShow };
|