@prisma/cli 3.0.0-dev.53.1 → 3.0.0-dev.54.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/git.js +8 -3
- package/dist/adapters/local-state.js +11 -3
- package/dist/adapters/mock-api.js +6 -2
- package/dist/adapters/token-storage.js +63 -22
- package/dist/cli.js +14 -3
- package/dist/cli2.js +1 -0
- package/dist/controllers/app-env.js +78 -46
- package/dist/controllers/app.js +111 -62
- package/dist/controllers/auth.js +8 -8
- package/dist/controllers/project.js +107 -67
- package/dist/lib/app/bun-project.js +12 -5
- package/dist/lib/app/local-dev.js +34 -18
- package/dist/lib/app/preview-build.js +90 -62
- package/dist/lib/app/preview-provider.js +118 -49
- package/dist/lib/auth/auth-ops.js +30 -21
- package/dist/lib/auth/guard.js +3 -2
- package/dist/lib/auth/login.js +31 -17
- package/dist/lib/project/interactive-setup.js +1 -1
- package/dist/lib/project/local-pin.js +27 -8
- package/dist/lib/project/resolution.js +14 -8
- package/dist/lib/project/setup.js +2 -2
- package/dist/shell/command-runner.js +9 -4
- package/dist/shell/errors.js +12 -1
- package/dist/shell/runtime.js +2 -2
- package/package.json +1 -1
|
@@ -19,8 +19,8 @@ const GITHUB_INSTALL_POLL_TIMEOUT_MS = 12e4;
|
|
|
19
19
|
function isRealMode(context) {
|
|
20
20
|
return !context.runtime.fixturePath && !context.runtime.env.PRISMA_CLI_MOCK_FIXTURE_PATH;
|
|
21
21
|
}
|
|
22
|
-
async function readProjectListLocalBinding(cwd, workspace, projects) {
|
|
23
|
-
const pin = await readLocalResolutionPin(cwd);
|
|
22
|
+
async function readProjectListLocalBinding(cwd, workspace, projects, signal) {
|
|
23
|
+
const pin = await readLocalResolutionPin(cwd, signal);
|
|
24
24
|
if (pin.kind === "present") return pin.pin.workspaceId === workspace.id && projects.some((project) => project.id === pin.pin.projectId) ? { status: "linked" } : { status: "invalid" };
|
|
25
25
|
if (pin.kind === "invalid") return { status: "invalid" };
|
|
26
26
|
return { status: "not-linked" };
|
|
@@ -30,10 +30,10 @@ async function runProjectList(context) {
|
|
|
30
30
|
const workspace = authState.workspace;
|
|
31
31
|
if (!workspace) throw workspaceRequiredError();
|
|
32
32
|
if (isRealMode(context)) {
|
|
33
|
-
const client = await requireComputeAuth(context.runtime.env);
|
|
33
|
+
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
34
34
|
if (!client) throw authRequiredError();
|
|
35
|
-
const projects = sortProjects(await listRealWorkspaceProjects(client, workspace));
|
|
36
|
-
const localBinding = await readProjectListLocalBinding(context.runtime.cwd, workspace, projects);
|
|
35
|
+
const projects = sortProjects(await listRealWorkspaceProjects(client, workspace, context.runtime.signal));
|
|
36
|
+
const localBinding = await readProjectListLocalBinding(context.runtime.cwd, workspace, projects, context.runtime.signal);
|
|
37
37
|
const nextActions = buildProjectListNextActions(localBinding);
|
|
38
38
|
return {
|
|
39
39
|
command: "project.list",
|
|
@@ -48,7 +48,7 @@ async function runProjectList(context) {
|
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
50
|
const result = await createProjectUseCases(createCliUseCaseGateways(context)).list(authState);
|
|
51
|
-
const localBinding = await readProjectListLocalBinding(context.runtime.cwd, workspace, result.projects);
|
|
51
|
+
const localBinding = await readProjectListLocalBinding(context.runtime.cwd, workspace, result.projects, context.runtime.signal);
|
|
52
52
|
const nextActions = buildProjectListNextActions(localBinding);
|
|
53
53
|
return {
|
|
54
54
|
command: "project.list",
|
|
@@ -88,11 +88,14 @@ async function runProjectCreate(context, projectName) {
|
|
|
88
88
|
if (!workspace) throw workspaceRequiredError();
|
|
89
89
|
if (!isValidProjectSetupName(projectName)) throw projectSetupNameRequiredError("project create");
|
|
90
90
|
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);
|
|
91
|
+
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
92
92
|
if (!client) throw authRequiredError();
|
|
93
93
|
const provider = createPreviewAppProvider(client);
|
|
94
94
|
const name = projectName.trim();
|
|
95
|
-
const created = await provider.createProject({
|
|
95
|
+
const created = await provider.createProject({
|
|
96
|
+
name,
|
|
97
|
+
signal: context.runtime.signal
|
|
98
|
+
}).catch((error) => {
|
|
96
99
|
throw projectCreateFailedError(error, name, workspace, {
|
|
97
100
|
nextSteps: ["prisma-cli project list", "prisma-cli project link <id-or-name>"],
|
|
98
101
|
permissionFix: "Grant the token permission to create Projects in this workspace, or link an existing Project.",
|
|
@@ -115,10 +118,10 @@ async function runProjectLink(context, projectRef) {
|
|
|
115
118
|
let provider = null;
|
|
116
119
|
let projects;
|
|
117
120
|
if (isRealMode(context)) {
|
|
118
|
-
const client = await requireComputeAuth(context.runtime.env);
|
|
121
|
+
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
119
122
|
if (!client) throw authRequiredError();
|
|
120
123
|
provider = createPreviewAppProvider(client);
|
|
121
|
-
projects = await listRealWorkspaceProjects(client, workspace);
|
|
124
|
+
projects = await listRealWorkspaceProjects(client, workspace, context.runtime.signal);
|
|
122
125
|
} else projects = listFixtureWorkspaceProjects(context, workspace);
|
|
123
126
|
let result;
|
|
124
127
|
if (projectRef?.trim()) result = await bindProjectToDirectory(context, workspace, toProjectSummary(resolveProjectForSetup(projectRef.trim(), projects, workspace)), "linked");
|
|
@@ -137,7 +140,7 @@ async function resolveInteractiveProjectLinkSetup(context, workspace, projects,
|
|
|
137
140
|
projects,
|
|
138
141
|
createProject: (projectName) => {
|
|
139
142
|
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);
|
|
143
|
+
return createProjectForLinkSetup(provider, projectName, workspace, context.runtime.signal);
|
|
141
144
|
},
|
|
142
145
|
cancel: {
|
|
143
146
|
why: "Project link needs a Project before it can continue.",
|
|
@@ -147,8 +150,11 @@ async function resolveInteractiveProjectLinkSetup(context, workspace, projects,
|
|
|
147
150
|
});
|
|
148
151
|
return bindProjectToDirectory(context, workspace, setup.project, setup.action);
|
|
149
152
|
}
|
|
150
|
-
async function createProjectForLinkSetup(provider, projectName, workspace) {
|
|
151
|
-
const created = await provider.createProject({
|
|
153
|
+
async function createProjectForLinkSetup(provider, projectName, workspace, signal) {
|
|
154
|
+
const created = await provider.createProject({
|
|
155
|
+
name: projectName,
|
|
156
|
+
signal
|
|
157
|
+
}).catch((error) => {
|
|
152
158
|
throw projectCreateFailedError(error, projectName, workspace, {
|
|
153
159
|
nextSteps: [
|
|
154
160
|
"prisma-cli project list",
|
|
@@ -166,7 +172,7 @@ async function createProjectForLinkSetup(provider, projectName, workspace) {
|
|
|
166
172
|
};
|
|
167
173
|
}
|
|
168
174
|
async function projectLinkTargetRequiredError(context, projects) {
|
|
169
|
-
const suggestedName = await inferTargetName(context.runtime.cwd);
|
|
175
|
+
const suggestedName = await inferTargetName(context.runtime.cwd, context.runtime.signal);
|
|
170
176
|
const createCommand = `prisma-cli project create ${formatCommandArgument(suggestedName.name)}`;
|
|
171
177
|
const recoveryCommands = ["prisma-cli project link <id-or-name>", createCommand];
|
|
172
178
|
return new CliError({
|
|
@@ -194,12 +200,12 @@ async function runGitConnect(context, gitUrl, options = {}) {
|
|
|
194
200
|
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
195
201
|
if (!workspace) throw workspaceRequiredError();
|
|
196
202
|
if (isRealMode(context)) {
|
|
197
|
-
const client = await requireComputeAuth(context.runtime.env);
|
|
203
|
+
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
198
204
|
if (!client) throw authRequiredError();
|
|
199
205
|
const target = await resolveRequiredProjectInRealMode(context, workspace, options.project, "git connect");
|
|
200
206
|
const repository = await resolveRepositoryForConnect(context, gitUrl);
|
|
201
207
|
const api = client;
|
|
202
|
-
const existing = await readFirstSourceRepository(api, target.project.id);
|
|
208
|
+
const existing = await readFirstSourceRepository(api, target.project.id, context.runtime.signal);
|
|
203
209
|
if (existing) {
|
|
204
210
|
const existingConnection = toRepositoryConnection(existing);
|
|
205
211
|
if (repositoryFullNamesMatch(existingConnection.repository.fullName, repository.fullName)) return {
|
|
@@ -214,12 +220,15 @@ async function runGitConnect(context, gitUrl, options = {}) {
|
|
|
214
220
|
throw repoAlreadyConnectedError(existingConnection.repository.fullName);
|
|
215
221
|
}
|
|
216
222
|
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
|
-
|
|
223
|
+
const { data, error, response } = await api.POST("/v1/source-repositories", {
|
|
224
|
+
body: {
|
|
225
|
+
projectId: target.project.id,
|
|
226
|
+
provider: "github",
|
|
227
|
+
providerRepositoryId: resolvedRepository.repository.id,
|
|
228
|
+
installationId: resolvedRepository.installation.id
|
|
229
|
+
},
|
|
230
|
+
signal: context.runtime.signal
|
|
231
|
+
});
|
|
223
232
|
if (error || !data) throw repoConnectionApiError("Failed to connect GitHub repository", response, error);
|
|
224
233
|
return {
|
|
225
234
|
command: "git.connect",
|
|
@@ -262,13 +271,16 @@ async function runGitDisconnect(context, options = {}) {
|
|
|
262
271
|
const workspace = (await requireAuthenticatedAuthState(context)).workspace;
|
|
263
272
|
if (!workspace) throw workspaceRequiredError();
|
|
264
273
|
if (isRealMode(context)) {
|
|
265
|
-
const client = await requireComputeAuth(context.runtime.env);
|
|
274
|
+
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
266
275
|
if (!client) throw authRequiredError();
|
|
267
276
|
const target = await resolveRequiredProjectInRealMode(context, workspace, options.project, "git disconnect");
|
|
268
277
|
const api = client;
|
|
269
|
-
const existing = await readFirstSourceRepository(api, target.project.id);
|
|
278
|
+
const existing = await readFirstSourceRepository(api, target.project.id, context.runtime.signal);
|
|
270
279
|
if (!existing) throw repoNotConnectedError();
|
|
271
|
-
const { error, response } = await api.DELETE("/v1/source-repositories/{id}", {
|
|
280
|
+
const { error, response } = await api.DELETE("/v1/source-repositories/{id}", {
|
|
281
|
+
params: { path: { id: existing.id } },
|
|
282
|
+
signal: context.runtime.signal
|
|
283
|
+
});
|
|
272
284
|
if (error) throw repoConnectionApiError("Failed to disconnect GitHub repository", response, error);
|
|
273
285
|
return {
|
|
274
286
|
command: "git.disconnect",
|
|
@@ -295,24 +307,24 @@ async function runGitDisconnect(context, options = {}) {
|
|
|
295
307
|
};
|
|
296
308
|
}
|
|
297
309
|
async function resolveProjectShowInRealMode(context, workspace, explicitProject) {
|
|
298
|
-
const client = await requireComputeAuth(context.runtime.env);
|
|
310
|
+
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
299
311
|
if (!client) throw authRequiredError();
|
|
300
312
|
return inspectProjectBinding({
|
|
301
313
|
context,
|
|
302
314
|
workspace,
|
|
303
315
|
explicitProject,
|
|
304
|
-
listProjects: () => listRealWorkspaceProjects(client, workspace),
|
|
316
|
+
listProjects: () => listRealWorkspaceProjects(client, workspace, context.runtime.signal),
|
|
305
317
|
commandName: "project show"
|
|
306
318
|
});
|
|
307
319
|
}
|
|
308
320
|
async function resolveRequiredProjectInRealMode(context, workspace, explicitProject, commandName) {
|
|
309
|
-
const client = await requireComputeAuth(context.runtime.env);
|
|
321
|
+
const client = await requireComputeAuth(context.runtime.env, context.runtime.signal);
|
|
310
322
|
if (!client) throw authRequiredError();
|
|
311
323
|
return resolveProjectTarget({
|
|
312
324
|
context,
|
|
313
325
|
workspace,
|
|
314
326
|
explicitProject,
|
|
315
|
-
listProjects: () => listRealWorkspaceProjects(client, workspace),
|
|
327
|
+
listProjects: () => listRealWorkspaceProjects(client, workspace, context.runtime.signal),
|
|
316
328
|
commandName
|
|
317
329
|
});
|
|
318
330
|
}
|
|
@@ -334,8 +346,8 @@ async function resolveRequiredProjectInFixtureMode(context, workspace, explicitP
|
|
|
334
346
|
commandName
|
|
335
347
|
});
|
|
336
348
|
}
|
|
337
|
-
async function listRealWorkspaceProjects(client, workspace) {
|
|
338
|
-
const { data } = await client.GET("/v1/projects", {});
|
|
349
|
+
async function listRealWorkspaceProjects(client, workspace, signal) {
|
|
350
|
+
const { data } = await client.GET("/v1/projects", { signal });
|
|
339
351
|
return sortProjects((data?.data ?? []).filter((project) => project.workspace.id === workspace.id).map((project) => ({
|
|
340
352
|
id: project.id,
|
|
341
353
|
name: project.name,
|
|
@@ -357,16 +369,16 @@ function listFixtureWorkspaceProjects(context, workspace) {
|
|
|
357
369
|
})));
|
|
358
370
|
}
|
|
359
371
|
async function resolveRepositoryForConnect(context, gitUrl) {
|
|
360
|
-
const remoteUrl = gitUrl ?? await readGitOriginRemote(context.runtime.cwd);
|
|
372
|
+
const remoteUrl = gitUrl ?? await readGitOriginRemote(context.runtime.cwd, context.runtime.signal);
|
|
361
373
|
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");
|
|
362
374
|
const repository = parseGitHubRepositoryUrl(remoteUrl);
|
|
363
375
|
if (!repository) throw unsupportedRepositoryProviderError();
|
|
364
376
|
return repository;
|
|
365
377
|
}
|
|
366
378
|
async function resolveInstalledRepository(context, api, workspaceId, repository) {
|
|
367
|
-
const lookup = await findRepositoryInInstallations(api, await listScmInstallations(api, workspaceId), repository);
|
|
379
|
+
const lookup = await findRepositoryInInstallations(api, await listScmInstallations(api, workspaceId, context.runtime.signal), repository, context.runtime.signal);
|
|
368
380
|
if (lookup.match) return lookup.match;
|
|
369
|
-
const installUrl = await createGitHubInstallIntent(api, workspaceId);
|
|
381
|
+
const installUrl = await createGitHubInstallIntent(api, workspaceId, context.runtime.signal);
|
|
370
382
|
const canWait = canPrompt(context);
|
|
371
383
|
const opened = await openInstallUrlIfInteractive(context, installUrl);
|
|
372
384
|
if (!canWait) {
|
|
@@ -379,11 +391,11 @@ async function resolveInstalledRepository(context, api, workspaceId, repository)
|
|
|
379
391
|
if (result.inspectableInstallationCount > 0) throw repoNotAccessibleError(repository, installUrl, opened);
|
|
380
392
|
throw repoInstallationRequiredError(repository, installUrl, opened);
|
|
381
393
|
}
|
|
382
|
-
async function findRepositoryInInstallations(api, installations, repository) {
|
|
394
|
+
async function findRepositoryInInstallations(api, installations, repository, signal) {
|
|
383
395
|
let inspectableInstallationCount = 0;
|
|
384
396
|
for (const installation of installations) {
|
|
385
397
|
if (installation.provider !== "github" || installation.suspended) continue;
|
|
386
|
-
const matchedRepository = await findRepositoryInInstallationIfAvailable(api, installation.id, repository);
|
|
398
|
+
const matchedRepository = await findRepositoryInInstallationIfAvailable(api, installation.id, repository, signal);
|
|
387
399
|
if (matchedRepository === "unavailable") continue;
|
|
388
400
|
inspectableInstallationCount += 1;
|
|
389
401
|
if (matchedRepository) return {
|
|
@@ -405,7 +417,8 @@ async function waitForInstalledRepository(context, api, workspaceId, repository)
|
|
|
405
417
|
const deadline = Date.now() + timeoutMs;
|
|
406
418
|
let inspectableInstallationCount = 0;
|
|
407
419
|
while (Date.now() <= deadline) {
|
|
408
|
-
|
|
420
|
+
context.runtime.signal.throwIfAborted();
|
|
421
|
+
const lookup = await findRepositoryInInstallations(api, await listScmInstallations(api, workspaceId, context.runtime.signal), repository, context.runtime.signal);
|
|
409
422
|
inspectableInstallationCount = lookup.inspectableInstallationCount;
|
|
410
423
|
if (lookup.match) return {
|
|
411
424
|
match: lookup.match,
|
|
@@ -413,7 +426,7 @@ async function waitForInstalledRepository(context, api, workspaceId, repository)
|
|
|
413
426
|
};
|
|
414
427
|
const remainingMs = deadline - Date.now();
|
|
415
428
|
if (remainingMs <= 0) break;
|
|
416
|
-
await sleep(Math.min(intervalMs, remainingMs));
|
|
429
|
+
await sleep(Math.min(intervalMs, remainingMs), context.runtime.signal);
|
|
417
430
|
}
|
|
418
431
|
return {
|
|
419
432
|
match: null,
|
|
@@ -431,37 +444,54 @@ function writeInstallWaitStatus(context, opened, installUrl) {
|
|
|
431
444
|
if (!opened) lines.push(installUrl);
|
|
432
445
|
context.output.stderr.write(`${lines.join("\n")}\n`);
|
|
433
446
|
}
|
|
434
|
-
function sleep(ms) {
|
|
435
|
-
|
|
447
|
+
function sleep(ms, signal) {
|
|
448
|
+
signal.throwIfAborted();
|
|
449
|
+
return new Promise((resolve, reject) => {
|
|
450
|
+
const onAbort = () => {
|
|
451
|
+
clearTimeout(timeout);
|
|
452
|
+
reject(signal.reason);
|
|
453
|
+
};
|
|
454
|
+
const timeout = setTimeout(() => {
|
|
455
|
+
signal.removeEventListener("abort", onAbort);
|
|
456
|
+
resolve();
|
|
457
|
+
}, ms);
|
|
458
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
459
|
+
});
|
|
436
460
|
}
|
|
437
|
-
async function listScmInstallations(api, workspaceId) {
|
|
461
|
+
async function listScmInstallations(api, workspaceId, signal) {
|
|
438
462
|
const installations = [];
|
|
439
463
|
let cursor;
|
|
440
464
|
const seenCursors = /* @__PURE__ */ new Set();
|
|
441
465
|
do {
|
|
442
|
-
const { data, error, response } = await api.GET("/v1/scm-installations", {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
466
|
+
const { data, error, response } = await api.GET("/v1/scm-installations", {
|
|
467
|
+
params: { query: {
|
|
468
|
+
workspaceId,
|
|
469
|
+
limit: 100,
|
|
470
|
+
...cursor ? { cursor } : {}
|
|
471
|
+
} },
|
|
472
|
+
signal
|
|
473
|
+
});
|
|
447
474
|
if (error || !data) throw repoConnectionApiError("Failed to inspect GitHub App installations", response, error);
|
|
448
475
|
installations.push(...data.data);
|
|
449
476
|
cursor = readNextPaginationCursor(data.pagination, seenCursors, "Failed to inspect GitHub App installations", response);
|
|
450
477
|
} while (cursor);
|
|
451
478
|
return installations;
|
|
452
479
|
}
|
|
453
|
-
async function findRepositoryInInstallation(api, installationId, repository) {
|
|
480
|
+
async function findRepositoryInInstallation(api, installationId, repository, signal) {
|
|
454
481
|
const expectedFullName = repository.fullName.toLowerCase();
|
|
455
482
|
let cursor;
|
|
456
483
|
const seenCursors = /* @__PURE__ */ new Set();
|
|
457
484
|
do {
|
|
458
|
-
const { data, error, response } = await api.GET("/v1/scm-installations/{installationId}/repositories", {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
485
|
+
const { data, error, response } = await api.GET("/v1/scm-installations/{installationId}/repositories", {
|
|
486
|
+
params: {
|
|
487
|
+
path: { installationId },
|
|
488
|
+
query: {
|
|
489
|
+
limit: 100,
|
|
490
|
+
...cursor ? { cursor } : {}
|
|
491
|
+
}
|
|
492
|
+
},
|
|
493
|
+
signal
|
|
494
|
+
});
|
|
465
495
|
if (error || !data) throw repoConnectionApiError("Failed to inspect GitHub repositories", response, error);
|
|
466
496
|
const matchedRepository = data.data.find((candidate) => candidate.fullName.toLowerCase() === expectedFullName);
|
|
467
497
|
if (matchedRepository) return matchedRepository;
|
|
@@ -476,10 +506,11 @@ function readNextPaginationCursor(pagination, seenCursors, summary, response) {
|
|
|
476
506
|
seenCursors.add(nextCursor);
|
|
477
507
|
return nextCursor;
|
|
478
508
|
}
|
|
479
|
-
async function findRepositoryInInstallationIfAvailable(api, installationId, repository) {
|
|
509
|
+
async function findRepositoryInInstallationIfAvailable(api, installationId, repository, signal) {
|
|
480
510
|
try {
|
|
481
|
-
return await findRepositoryInInstallation(api, installationId, repository);
|
|
511
|
+
return await findRepositoryInInstallation(api, installationId, repository, signal);
|
|
482
512
|
} catch (error) {
|
|
513
|
+
if (signal.aborted) throw error;
|
|
483
514
|
if (isUnavailableScmInstallationError(error)) return "unavailable";
|
|
484
515
|
throw error;
|
|
485
516
|
}
|
|
@@ -488,28 +519,37 @@ function isUnavailableScmInstallationError(error) {
|
|
|
488
519
|
if (!(error instanceof CliError) || error.code !== "REPO_CONNECTION_FAILED") return false;
|
|
489
520
|
return error.meta.status === 404 || error.meta.status === 422;
|
|
490
521
|
}
|
|
491
|
-
async function createGitHubInstallIntent(api, workspaceId) {
|
|
492
|
-
const { data, error, response } = await api.POST("/v1/scm-installations/install-intents", {
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
522
|
+
async function createGitHubInstallIntent(api, workspaceId, signal) {
|
|
523
|
+
const { data, error, response } = await api.POST("/v1/scm-installations/install-intents", {
|
|
524
|
+
body: {
|
|
525
|
+
provider: "github",
|
|
526
|
+
workspaceId
|
|
527
|
+
},
|
|
528
|
+
signal
|
|
529
|
+
});
|
|
496
530
|
if (error || !data) throw repoConnectionApiError("Failed to create GitHub App installation link", response, error);
|
|
497
531
|
return data.data.installUrl;
|
|
498
532
|
}
|
|
499
533
|
async function openInstallUrlIfInteractive(context, installUrl) {
|
|
500
534
|
if (!canPrompt(context)) return false;
|
|
501
535
|
try {
|
|
536
|
+
context.runtime.signal.throwIfAborted();
|
|
502
537
|
await open(installUrl);
|
|
538
|
+
context.runtime.signal.throwIfAborted();
|
|
503
539
|
return true;
|
|
504
|
-
} catch {
|
|
540
|
+
} catch (error) {
|
|
541
|
+
if (context.runtime.signal.aborted) throw error;
|
|
505
542
|
return false;
|
|
506
543
|
}
|
|
507
544
|
}
|
|
508
|
-
async function readFirstSourceRepository(api, projectId) {
|
|
509
|
-
const { data, error, response } = await api.GET("/v1/source-repositories", {
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
545
|
+
async function readFirstSourceRepository(api, projectId, signal) {
|
|
546
|
+
const { data, error, response } = await api.GET("/v1/source-repositories", {
|
|
547
|
+
params: { query: {
|
|
548
|
+
projectId,
|
|
549
|
+
limit: 1
|
|
550
|
+
} },
|
|
551
|
+
signal
|
|
552
|
+
});
|
|
513
553
|
if (error || !data) throw repoConnectionApiError("Failed to inspect GitHub repository connection", response, error);
|
|
514
554
|
return data.data[0] ?? null;
|
|
515
555
|
}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { access, readFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
//#region src/lib/app/bun-project.ts
|
|
4
|
-
async function readBunPackageJson(appPath) {
|
|
4
|
+
async function readBunPackageJson(appPath, signal) {
|
|
5
5
|
const packageJsonPath = path.join(appPath, "package.json");
|
|
6
6
|
let content;
|
|
7
|
+
signal?.throwIfAborted();
|
|
7
8
|
try {
|
|
8
|
-
content = await readFile(packageJsonPath,
|
|
9
|
+
content = await readFile(packageJsonPath, {
|
|
10
|
+
encoding: "utf8",
|
|
11
|
+
signal
|
|
12
|
+
});
|
|
9
13
|
} catch (error) {
|
|
10
14
|
if (error.code === "ENOENT") return null;
|
|
11
15
|
throw new Error(`Failed to read ${packageJsonPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -20,17 +24,20 @@ function readBunPackageEntrypoint(packageJson) {
|
|
|
20
24
|
if (typeof packageJson?.main === "string") return packageJson.main;
|
|
21
25
|
if (typeof packageJson?.module === "string") return packageJson.module;
|
|
22
26
|
}
|
|
23
|
-
async function resolveBunEntrypoint(appPath, explicitEntrypoint) {
|
|
24
|
-
const packageJson = await readBunPackageJson(appPath);
|
|
27
|
+
async function resolveBunEntrypoint(appPath, explicitEntrypoint, signal) {
|
|
28
|
+
const packageJson = await readBunPackageJson(appPath, signal);
|
|
25
29
|
const candidate = explicitEntrypoint ?? readBunPackageEntrypoint(packageJson);
|
|
26
30
|
if (!candidate) throw new Error("Entrypoint is required. Pass --entry or define package.json main or module.");
|
|
27
31
|
if (path.isAbsolute(candidate)) throw new Error("Entrypoint must be a relative path.");
|
|
28
32
|
const normalized = path.normalize(candidate);
|
|
29
33
|
if (normalized.startsWith("..") || path.isAbsolute(normalized) || normalized.includes(`${path.sep}..${path.sep}`)) throw new Error("Entrypoint must not escape the app directory.");
|
|
30
34
|
const entrypointPath = path.join(appPath, normalized);
|
|
35
|
+
signal?.throwIfAborted();
|
|
31
36
|
try {
|
|
32
37
|
await access(entrypointPath);
|
|
33
|
-
|
|
38
|
+
signal?.throwIfAborted();
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if (signal?.aborted) throw error;
|
|
34
41
|
throw new Error(`Entrypoint file does not exist: ${entrypointPath}`);
|
|
35
42
|
}
|
|
36
43
|
return normalized.split(path.sep).join("/");
|
|
@@ -10,14 +10,14 @@ const NEXT_CONFIG_FILENAMES = [
|
|
|
10
10
|
"next.config.mts"
|
|
11
11
|
];
|
|
12
12
|
const DEFAULT_LOCAL_DEV_PORT = 3e3;
|
|
13
|
-
async function resolveLocalBuildType(appPath, buildType) {
|
|
13
|
+
async function resolveLocalBuildType(appPath, buildType, signal) {
|
|
14
14
|
if (buildType === "bun" || buildType === "nextjs") return buildType;
|
|
15
15
|
if (buildType !== "auto") return null;
|
|
16
|
-
return detectLocalBuildType(appPath);
|
|
16
|
+
return detectLocalBuildType(appPath, signal);
|
|
17
17
|
}
|
|
18
|
-
async function detectLocalBuildType(appPath) {
|
|
19
|
-
if (await isNextProject(appPath)) return "nextjs";
|
|
20
|
-
if (await isBunProject(appPath)) return "bun";
|
|
18
|
+
async function detectLocalBuildType(appPath, signal) {
|
|
19
|
+
if (await isNextProject(appPath, signal)) return "nextjs";
|
|
20
|
+
if (await isBunProject(appPath, signal)) return "bun";
|
|
21
21
|
return null;
|
|
22
22
|
}
|
|
23
23
|
async function runLocalApp(options) {
|
|
@@ -58,7 +58,8 @@ async function runLocalApp(options) {
|
|
|
58
58
|
env: {
|
|
59
59
|
...options.env,
|
|
60
60
|
PORT: String(options.port)
|
|
61
|
-
}
|
|
61
|
+
},
|
|
62
|
+
signal: options.signal
|
|
62
63
|
}, spawnImpl, "Could not find the Next.js CLI. Install it with `npm install next` or ensure npx/bunx is available.");
|
|
63
64
|
return {
|
|
64
65
|
framework: "nextjs",
|
|
@@ -69,7 +70,7 @@ async function runLocalApp(options) {
|
|
|
69
70
|
signal: command.signal
|
|
70
71
|
};
|
|
71
72
|
}
|
|
72
|
-
const entrypoint = await resolveBunEntrypoint(options.appPath, options.entrypoint);
|
|
73
|
+
const entrypoint = await resolveBunEntrypoint(options.appPath, options.entrypoint, options.signal);
|
|
73
74
|
const command = await runWithFallback([{
|
|
74
75
|
command: "bun",
|
|
75
76
|
args: ["--watch", entrypoint],
|
|
@@ -79,7 +80,8 @@ async function runLocalApp(options) {
|
|
|
79
80
|
env: {
|
|
80
81
|
...options.env,
|
|
81
82
|
PORT: String(options.port)
|
|
82
|
-
}
|
|
83
|
+
},
|
|
84
|
+
signal: options.signal
|
|
83
85
|
}, spawnImpl, "Bun is required to run this app locally. Install it from https://bun.sh.");
|
|
84
86
|
return {
|
|
85
87
|
framework: "bun",
|
|
@@ -90,23 +92,37 @@ async function runLocalApp(options) {
|
|
|
90
92
|
signal: command.signal
|
|
91
93
|
};
|
|
92
94
|
}
|
|
93
|
-
async function isNextProject(appPath) {
|
|
94
|
-
for (const fileName of NEXT_CONFIG_FILENAMES)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
async function isNextProject(appPath, signal) {
|
|
96
|
+
for (const fileName of NEXT_CONFIG_FILENAMES) {
|
|
97
|
+
signal?.throwIfAborted();
|
|
98
|
+
try {
|
|
99
|
+
await access(path.join(appPath, fileName));
|
|
100
|
+
signal?.throwIfAborted();
|
|
101
|
+
return true;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (signal?.aborted) throw error;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return hasDependency(await readBunPackageJson(appPath, signal), "next");
|
|
99
107
|
}
|
|
100
|
-
async function isBunProject(appPath) {
|
|
108
|
+
async function isBunProject(appPath, signal) {
|
|
109
|
+
signal?.throwIfAborted();
|
|
101
110
|
try {
|
|
102
111
|
await access(path.join(appPath, "bun.lock"));
|
|
112
|
+
signal?.throwIfAborted();
|
|
103
113
|
return true;
|
|
104
|
-
} catch {
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if (signal?.aborted) throw error;
|
|
116
|
+
}
|
|
117
|
+
signal?.throwIfAborted();
|
|
105
118
|
try {
|
|
106
119
|
await access(path.join(appPath, "bun.lockb"));
|
|
120
|
+
signal?.throwIfAborted();
|
|
107
121
|
return true;
|
|
108
|
-
} catch {
|
|
109
|
-
|
|
122
|
+
} catch (error) {
|
|
123
|
+
if (signal?.aborted) throw error;
|
|
124
|
+
}
|
|
125
|
+
const packageJson = await readBunPackageJson(appPath, signal);
|
|
110
126
|
if (!packageJson) return false;
|
|
111
127
|
const hasEntrypoint = typeof readBunPackageEntrypoint(packageJson) === "string";
|
|
112
128
|
const hasBunDependency = hasDependency(packageJson, "@types/bun") || hasDependency(packageJson, "bun");
|