vercel 54.21.1 → 56.0.0

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.
Files changed (74) hide show
  1. package/dist/chunks/{add-UMB55BRR.js → add-MD247HM7.js} +6 -6
  2. package/dist/chunks/{add-NKDLTZ3B.js → add-YN76XNHI.js} +6 -6
  3. package/dist/chunks/{chunk-MLY3KGVY.js → chunk-2BZVY6SF.js} +4 -4
  4. package/dist/chunks/{chunk-TLKTTWDA.js → chunk-36OUS2KE.js} +1 -1
  5. package/dist/chunks/{chunk-43I5EAPY.js → chunk-3JOLICSO.js} +1 -1
  6. package/dist/chunks/{chunk-3EMRC44D.js → chunk-5OT26JZN.js} +32 -0
  7. package/dist/chunks/{chunk-TRQJQXKD.js → chunk-75HDPY6N.js} +82 -19
  8. package/dist/chunks/chunk-7PZWVZN6.js +105 -0
  9. package/dist/chunks/{chunk-DKE73NGN.js → chunk-C6E2RJS7.js} +29 -2
  10. package/dist/chunks/{chunk-2QBF3ZL3.js → chunk-CB3I3QIT.js} +3 -4
  11. package/dist/chunks/{chunk-I3HGCZ4E.js → chunk-CMJMYVQM.js} +1 -0
  12. package/dist/chunks/{chunk-IMTOXXTF.js → chunk-DO3V6PZD.js} +6 -4
  13. package/dist/chunks/{chunk-QKR7GNAK.js → chunk-DREO3DFB.js} +6 -6
  14. package/dist/chunks/{chunk-GAJ2ESDD.js → chunk-FN36GZDS.js} +100 -36
  15. package/dist/chunks/{chunk-4UZF3U4N.js → chunk-HA7C7SDO.js} +2 -0
  16. package/dist/chunks/{chunk-QH7WYDEP.js → chunk-IC4YEIGW.js} +7 -0
  17. package/dist/chunks/{chunk-H3KWN2KA.js → chunk-JHZG2MPW.js} +4 -4
  18. package/dist/chunks/{chunk-QMZO4CEP.js → chunk-KODCUW77.js} +353 -403
  19. package/dist/chunks/{chunk-RO5JIUOI.js → chunk-KSJ4FNUG.js} +3 -3
  20. package/dist/chunks/{chunk-KNSDQCVQ.js → chunk-MBH4DFF7.js} +51 -9
  21. package/dist/chunks/{chunk-MV65GD4L.js → chunk-N4VWB5RV.js} +1 -1
  22. package/dist/chunks/{chunk-AUYY7RD3.js → chunk-NZ73KKYG.js} +550 -191
  23. package/dist/chunks/{chunk-ADTCIFMD.js → chunk-ORKY33ZK.js} +69 -16
  24. package/dist/chunks/{chunk-XMJAKLQQ.js → chunk-T7EIXPC5.js} +1 -1
  25. package/dist/chunks/{chunk-I4NRKN2Z.js → chunk-THMFJODG.js} +480 -277
  26. package/dist/chunks/{chunk-2ZXLLEEZ.js → chunk-TNDFVAZV.js} +4 -4
  27. package/dist/chunks/{chunk-OVE2GAHC.js → chunk-VPDJOK2C.js} +3 -3
  28. package/dist/chunks/{chunk-HMFUXSD7.js → chunk-VSD5BID5.js} +1 -1
  29. package/dist/chunks/{chunk-ZZIAWRCX.js → chunk-W346YNDN.js} +18 -0
  30. package/dist/chunks/chunk-WGGABYO5.js +31 -0
  31. package/dist/chunks/{chunk-XCL7I6W5.js → chunk-ZHDXN5V3.js} +3 -3
  32. package/dist/chunks/{compile-vercel-config-OKONVGZI.js → compile-vercel-config-65QWHKPO.js} +2 -2
  33. package/dist/chunks/{delete-L7TN5GNZ.js → delete-NPEJGNTV.js} +5 -5
  34. package/dist/chunks/{disable-7NVU4H2N.js → disable-RH3MH7PH.js} +5 -5
  35. package/dist/chunks/{discard-SJ2TDPS2.js → discard-IJBFO24S.js} +5 -5
  36. package/dist/chunks/{edit-SW4MF4Q3.js → edit-GMLOKGHO.js} +6 -6
  37. package/dist/chunks/{enable-FOKEO6J2.js → enable-QCTL7ISO.js} +5 -5
  38. package/dist/chunks/{exec-UKMIYIF6.js → exec-G4AUF3KG.js} +4 -90
  39. package/dist/chunks/{export-3DSHT2FD.js → export-6ERWU54A.js} +9 -8
  40. package/dist/chunks/{image-T6I7BZSC.js → image-XUMTOUQS.js} +5 -5
  41. package/dist/chunks/{inspect-RV444SBC.js → inspect-7UPFOAJO.js} +5 -5
  42. package/dist/chunks/{inspect-EP24WN4I.js → inspect-F6LEST4Y.js} +10 -10
  43. package/dist/chunks/{inspect-2MY25CMN.js → inspect-OKOUOOWW.js} +10 -10
  44. package/dist/chunks/{inspect-DJ7OGHBK.js → inspect-ZS4U24NA.js} +9 -9
  45. package/dist/chunks/{list-CCG2NVLL.js → list-BTMO3CXU.js} +5 -5
  46. package/dist/chunks/{list-OMJ7EDKA.js → list-ZK7Q3UHB.js} +3 -3
  47. package/dist/chunks/login-POFRYQZ6.js +247 -0
  48. package/dist/chunks/{ls-VXZGEJJD.js → ls-D5Z4IWMU.js} +6 -6
  49. package/dist/chunks/{ls-VCTHIVI7.js → ls-EXRMNZUT.js} +9 -9
  50. package/dist/chunks/{ls-33Z3R2KY.js → ls-JGNNPYUA.js} +9 -9
  51. package/dist/chunks/{ls-5KN5FKAQ.js → ls-Z2TCQW5W.js} +9 -9
  52. package/dist/chunks/{publish-RZ2DIYRU.js → publish-ED2XVYSE.js} +5 -5
  53. package/dist/chunks/{query-2MIBB7C5.js → query-6XMBJTPX.js} +3 -3
  54. package/dist/chunks/{reorder-FEZIPHNW.js → reorder-7B6HZCOZ.js} +5 -5
  55. package/dist/chunks/{restore-JHH6CW2S.js → restore-47ZDRYQT.js} +5 -5
  56. package/dist/chunks/{rm-EKPVQYGH.js → rm-4IY6QGHF.js} +6 -6
  57. package/dist/chunks/{rm-3KAXICHP.js → rm-AA7WYBZ5.js} +5 -5
  58. package/dist/chunks/{rm-ULQHII4K.js → rm-NGTPTPZK.js} +6 -6
  59. package/dist/chunks/{rule-inspect-FOZOCOUO.js → rule-inspect-FNX6P4LZ.js} +6 -6
  60. package/dist/chunks/{rules-RJD4S7JA.js → rules-MMCCBGQJ.js} +10 -10
  61. package/dist/chunks/{schema-CDXFJU72.js → schema-JSK7S7HL.js} +3 -3
  62. package/dist/chunks/{tags-BLBAPEWV.js → tags-KNCSBTIH.js} +4 -4
  63. package/dist/chunks/{update-2NW6NKG6.js → update-JEFLJ3TH.js} +6 -6
  64. package/dist/commands/build/index.js +47 -16
  65. package/dist/commands/deploy/index.js +24 -46
  66. package/dist/commands/dev/index.js +202 -59
  67. package/dist/commands/env/index.js +10 -20
  68. package/dist/commands/link/index.js +33 -16
  69. package/dist/commands/list/index.js +26 -11
  70. package/dist/commands-bulk.js +13722 -10599
  71. package/dist/index.js +14 -24
  72. package/dist/version.mjs +1 -1
  73. package/package.json +24 -23
  74. package/dist/chunks/chunk-Y4JJYHUG.js +0 -16
@@ -7,17 +7,20 @@ const __dirname = __dirname_(__filename);
7
7
  import {
8
8
  AGENT_REASON,
9
9
  AGENT_STATUS
10
- } from "./chunk-QH7WYDEP.js";
10
+ } from "./chunk-IC4YEIGW.js";
11
11
  import {
12
- getLocalPathConfig
13
- } from "./chunk-DKE73NGN.js";
12
+ getLocalPathConfig,
13
+ isPromptBackError,
14
+ isPromptCanceledError
15
+ } from "./chunk-C6E2RJS7.js";
14
16
  import {
15
17
  getScope
16
- } from "./chunk-HMFUXSD7.js";
18
+ } from "./chunk-VSD5BID5.js";
17
19
  import {
18
20
  table
19
21
  } from "./chunk-QEEGXNFK.js";
20
22
  import {
23
+ Separator,
21
24
  VERCEL_DIR_PROJECT,
22
25
  VERCEL_DIR_README,
23
26
  checkExistsAndConnect,
@@ -32,7 +35,6 @@ import {
32
35
  getLinkedProject,
33
36
  getProjectByNameOrId,
34
37
  getServicesConfigWriteBlocker,
35
- getTeams,
36
38
  getVercelDirectory,
37
39
  humanizePath,
38
40
  isDirectory,
@@ -51,12 +53,12 @@ import {
51
53
  selectAndParseRemoteUrl,
52
54
  selectOrg,
53
55
  writeServicesConfig
54
- } from "./chunk-I4NRKN2Z.js";
56
+ } from "./chunk-THMFJODG.js";
55
57
  import {
56
58
  buildCommandWithGlobalFlags,
57
59
  outputAgentError,
58
60
  shouldEmitNonInteractiveCommandError
59
- } from "./chunk-2QBF3ZL3.js";
61
+ } from "./chunk-CB3I3QIT.js";
60
62
  import {
61
63
  printError
62
64
  } from "./chunk-EHTPDXTS.js";
@@ -357,14 +359,19 @@ function displayServiceErrors(errors) {
357
359
  }
358
360
 
359
361
  // src/util/link/setup-and-link.ts
360
- var import_chalk6 = __toESM(require_source(), 1);
361
362
  var import_fs_extra2 = __toESM(require_lib(), 1);
362
363
  var import_fs_detectors2 = __toESM(require_dist(), 1);
363
364
  import { join as join2, basename } from "path";
365
+ import { getPlatformEnv } from "@vercel/build-utils";
364
366
 
365
367
  // src/util/input/input-project.ts
366
368
  var import_chalk2 = __toESM(require_source(), 1);
367
369
  var import_slugify = __toESM(require_slugify(), 1);
370
+ var SEARCH_ALL_PROJECTS = "search-all-projects";
371
+ var CREATE_NEW_PROJECT = "create-new-project";
372
+ var BACK_TO_PROJECT_SELECTION = Symbol("back-to-project-selection");
373
+ var BACK_TO_TEAM_SELECTION = Symbol("back-to-team-selection");
374
+ var NO_EXISTING_PROJECTS = Symbol("no-existing-projects");
368
375
  async function inputProjectDecision(client, defaultDecision) {
369
376
  const createChoice = {
370
377
  name: "Create new project",
@@ -379,10 +386,119 @@ async function inputProjectDecision(client, defaultDecision) {
379
386
  choices: defaultDecision === "existing" ? [existingChoice, createChoice] : [createChoice, existingChoice]
380
387
  });
381
388
  }
382
- async function inputProject(client, org, detectedProjectName, autoConfirm = false, skipAutoDetect = false) {
389
+ function randomNameSuffix() {
390
+ return Math.random().toString(36).slice(2, 6).padEnd(4, "0");
391
+ }
392
+ async function suggestNewProjectName(client, org, slugifiedName, knownTaken) {
393
+ let taken = knownTaken;
394
+ if (!taken) {
395
+ try {
396
+ const existing = await getProjectByNameOrId(
397
+ client,
398
+ slugifiedName,
399
+ org.id
400
+ );
401
+ taken = !(existing instanceof ProjectNotFound);
402
+ } catch {
403
+ taken = false;
404
+ }
405
+ }
406
+ return taken ? `${slugifiedName}-${randomNameSuffix()}` : slugifiedName;
407
+ }
408
+ function promptForProjectName(client, org, defaultName, message = "Name?") {
409
+ return client.input.text({
410
+ message,
411
+ default: defaultName,
412
+ validate: async (val) => {
413
+ if (!val) {
414
+ return "Project name cannot be empty";
415
+ }
416
+ const project = await getProjectByNameOrId(client, val, org.id);
417
+ if (!(project instanceof ProjectNotFound)) {
418
+ return "Project already exists";
419
+ }
420
+ return true;
421
+ }
422
+ });
423
+ }
424
+ async function promptForProjectNameWithBack(client, org, defaultName) {
425
+ try {
426
+ return await client.withPromptBackNavigation(
427
+ () => promptForProjectName(
428
+ client,
429
+ org,
430
+ defaultName,
431
+ `Name? ${import_chalk2.default.dim("Press \u2191 to return to project options")}`
432
+ )
433
+ );
434
+ } catch (error) {
435
+ if (isPromptBackError(error)) {
436
+ return BACK_TO_PROJECT_SELECTION;
437
+ }
438
+ throw error;
439
+ }
440
+ }
441
+ async function searchExistingProjects(client, org, allowBack) {
442
+ const firstPage = await client.fetch(`/v9/projects?limit=100`, { accountId: org.id });
443
+ const projects = firstPage.projects;
444
+ const hasMoreProjects = firstPage.pagination.next != null;
445
+ if (projects.length === 0) {
446
+ output_manager_default.log(
447
+ `No existing projects found under ${import_chalk2.default.bold(org.slug)}. Creating new project.`
448
+ );
449
+ return NO_EXISTING_PROJECTS;
450
+ }
451
+ const pageSize = 15;
452
+ const countHint = projects.length > pageSize ? ` ${import_chalk2.default.dim(
453
+ `(${hasMoreProjects ? "100+" : projects.length} projects)`
454
+ )}` : "";
455
+ return await client.input.search({
456
+ message: `Which project?${countHint}`,
457
+ pageSize,
458
+ source: async (term, { signal }) => {
459
+ const searchTerm = term?.trim();
460
+ let matchingProjects = projects;
461
+ if (searchTerm) {
462
+ if (hasMoreProjects) {
463
+ matchingProjects = (await client.fetch(
464
+ `/v9/projects?search=${encodeURIComponent(searchTerm)}&limit=20`,
465
+ {
466
+ accountId: org.id,
467
+ signal
468
+ }
469
+ )).projects;
470
+ } else {
471
+ const normalizedSearchTerm = searchTerm.toLowerCase();
472
+ matchingProjects = projects.filter(
473
+ (project) => project.name.toLowerCase().includes(normalizedSearchTerm) || project.id === searchTerm
474
+ );
475
+ }
476
+ }
477
+ const choices = matchingProjects.slice().sort((a, b) => b.updatedAt - a.updatedAt).map((project) => ({
478
+ name: project.name,
479
+ value: project
480
+ }));
481
+ if (!allowBack) {
482
+ return choices;
483
+ }
484
+ const backChoice = {
485
+ name: "Back to project options",
486
+ value: BACK_TO_PROJECT_SELECTION
487
+ };
488
+ if (choices.length === 0) {
489
+ return [backChoice];
490
+ }
491
+ if (searchTerm) {
492
+ return choices;
493
+ }
494
+ return [...choices, new Separator(), backChoice];
495
+ }
496
+ });
497
+ }
498
+ async function inputProject(client, org, detectedProjectName, autoConfirm = false, skipAutoDetect = false, showProjectSuggestions = false, allowTeamSelectionBack = false, repoMatches = []) {
383
499
  const slugifiedName = (0, import_slugify.default)(detectedProjectName);
384
500
  let detectedProject = null;
385
- if (!skipAutoDetect) {
501
+ if (!skipAutoDetect && repoMatches.length === 0) {
386
502
  output_manager_default.spinner("Searching for existing projects\u2026", 1e3);
387
503
  const [project, slugifiedProject] = await Promise.all([
388
504
  getProjectByNameOrId(client, detectedProjectName, org.id),
@@ -395,6 +511,9 @@ async function inputProject(client, org, detectedProjectName, autoConfirm = fals
395
511
  output_manager_default.stopSpinner();
396
512
  }
397
513
  if (autoConfirm) {
514
+ if (repoMatches.length === 1) {
515
+ return repoMatches[0];
516
+ }
398
517
  return detectedProject || detectedProjectName;
399
518
  }
400
519
  if (client.nonInteractive) {
@@ -405,8 +524,98 @@ async function inputProject(client, org, detectedProjectName, autoConfirm = fals
405
524
  err.code = "HEADLESS";
406
525
  throw err;
407
526
  }
527
+ const slugifiedNameKnownFree = !skipAutoDetect && repoMatches.length === 0 && !detectedProject;
528
+ let memoizedDefaultName;
529
+ async function defaultNewProjectName() {
530
+ if (memoizedDefaultName === void 0) {
531
+ memoizedDefaultName = slugifiedNameKnownFree ? slugifiedName : await suggestNewProjectName(
532
+ client,
533
+ org,
534
+ slugifiedName,
535
+ detectedProject?.name === slugifiedName
536
+ );
537
+ }
538
+ return memoizedDefaultName;
539
+ }
408
540
  let shouldLinkProject;
409
- if (!detectedProject) {
541
+ if (showProjectSuggestions && !skipAutoDetect) {
542
+ for (; ; ) {
543
+ const choices = [];
544
+ if (repoMatches.length > 0) {
545
+ choices.push(
546
+ ...repoMatches.map((match) => ({
547
+ name: `${match.project.name} ${import_chalk2.default.gray("(linked by git)")}`,
548
+ value: match
549
+ })),
550
+ new Separator()
551
+ );
552
+ } else if (detectedProject) {
553
+ choices.push(
554
+ {
555
+ name: `${detectedProject.name} ${import_chalk2.default.gray("(folder name)")}`,
556
+ value: detectedProject
557
+ },
558
+ new Separator()
559
+ );
560
+ }
561
+ choices.push(
562
+ {
563
+ name: "Search all projects",
564
+ value: SEARCH_ALL_PROJECTS,
565
+ description: "Browse or search every project in this team"
566
+ },
567
+ {
568
+ name: "Create a new project",
569
+ value: CREATE_NEW_PROJECT,
570
+ description: `Create it under ${org.slug}`
571
+ }
572
+ );
573
+ if (allowTeamSelectionBack) {
574
+ choices.push({
575
+ name: "Choose a different team",
576
+ value: BACK_TO_TEAM_SELECTION,
577
+ description: "Return to team selection"
578
+ });
579
+ }
580
+ const selected = await client.input.select({
581
+ message: "Which project?",
582
+ choices
583
+ });
584
+ if (selected === BACK_TO_TEAM_SELECTION) {
585
+ return BACK_TO_TEAM_SELECTION;
586
+ }
587
+ if (selected === CREATE_NEW_PROJECT) {
588
+ const projectName = await promptForProjectNameWithBack(
589
+ client,
590
+ org,
591
+ await defaultNewProjectName()
592
+ );
593
+ if (projectName === BACK_TO_PROJECT_SELECTION) {
594
+ continue;
595
+ }
596
+ return projectName;
597
+ }
598
+ if (selected !== SEARCH_ALL_PROJECTS) {
599
+ return selected;
600
+ }
601
+ const existingProject = await searchExistingProjects(client, org, true);
602
+ if (existingProject === BACK_TO_PROJECT_SELECTION) {
603
+ continue;
604
+ }
605
+ if (existingProject === NO_EXISTING_PROJECTS) {
606
+ const projectName = await promptForProjectNameWithBack(
607
+ client,
608
+ org,
609
+ slugifiedName
610
+ );
611
+ if (projectName === BACK_TO_PROJECT_SELECTION) {
612
+ continue;
613
+ }
614
+ return projectName;
615
+ }
616
+ return existingProject;
617
+ }
618
+ } else if (!detectedProject) {
410
619
  const decision = await inputProjectDecision(
411
620
  client,
412
621
  skipAutoDetect ? "existing" : "create"
@@ -423,56 +632,12 @@ async function inputProject(client, org, detectedProjectName, autoConfirm = fals
423
632
  shouldLinkProject = decision === "existing";
424
633
  }
425
634
  if (shouldLinkProject) {
426
- const firstPage = await client.fetch(`/v9/projects?limit=100`, { accountId: org.id });
427
- const projects = firstPage.projects;
428
- const hasMoreProjects = firstPage.pagination.next !== null;
429
- if (projects.length === 0) {
430
- output_manager_default.log(
431
- `No existing projects found under ${import_chalk2.default.bold(org.slug)}. Creating new project.`
432
- );
433
- } else if (hasMoreProjects) {
434
- let toLink;
435
- await client.input.text({
436
- message: "Existing project name?",
437
- validate: async (val) => {
438
- if (!val) {
439
- return "Project name cannot be empty";
440
- }
441
- const project = await getProjectByNameOrId(client, val, org.id);
442
- if (project instanceof ProjectNotFound) {
443
- return "Project not found";
444
- }
445
- toLink = project;
446
- return true;
447
- }
448
- });
449
- return toLink;
450
- } else {
451
- const choices = projects.sort((a, b) => b.updatedAt - a.updatedAt).map((project) => ({
452
- name: project.name,
453
- value: project
454
- }));
455
- const toLink = await client.input.select({
456
- message: "Which project?",
457
- choices
458
- });
459
- return toLink;
635
+ const existingProject = await searchExistingProjects(client, org, false);
636
+ if (existingProject !== NO_EXISTING_PROJECTS && existingProject !== BACK_TO_PROJECT_SELECTION) {
637
+ return existingProject;
460
638
  }
461
639
  }
462
- return await client.input.text({
463
- message: `Name?`,
464
- default: !detectedProject ? slugifiedName : void 0,
465
- validate: async (val) => {
466
- if (!val) {
467
- return "Project name cannot be empty";
468
- }
469
- const project = await getProjectByNameOrId(client, val, org.id);
470
- if (!(project instanceof ProjectNotFound)) {
471
- return "Project already exists";
472
- }
473
- return true;
474
- }
475
- });
640
+ return await promptForProjectName(client, org, await defaultNewProjectName());
476
641
  }
477
642
 
478
643
  // src/util/input/input-root-directory.ts
@@ -800,77 +965,6 @@ async function promptForInferredServicesSetup({
800
965
  // src/util/projects/search-project-across-teams.ts
801
966
  var import_slugify2 = __toESM(require_slugify(), 1);
802
967
  import { relative as relative2 } from "path";
803
- async function searchProjectAcrossTeams(client, projectName, cwd, {
804
- autoConfirm = false,
805
- nonInteractive = false,
806
- teams,
807
- skipLimited,
808
- gitProjectName
809
- } = {}) {
810
- const teamsToSearch = teams ?? await getTeams(client);
811
- const shouldSkipLimited = skipLimited ?? true;
812
- const accessibleTeams = [];
813
- const skippedTeams = [];
814
- const skippedSlugs = [];
815
- for (const t of teamsToSearch) {
816
- if (shouldSkipLimited && t.limited) {
817
- skippedTeams.push(t);
818
- skippedSlugs.push(t.slug);
819
- } else {
820
- accessibleTeams.push(t);
821
- }
822
- }
823
- if (skippedSlugs.length > 0) {
824
- output_manager_default.debug(
825
- `Skipping limited teams during cross-team project search: ${skippedSlugs.join(", ")}`
826
- );
827
- }
828
- const searchedTeamSlugs = accessibleTeams.map((team) => team.slug);
829
- const orgs = accessibleTeams.map((t) => ({
830
- type: "team",
831
- id: t.id,
832
- slug: t.slug
833
- }));
834
- const repoMatchesPromise = searchProjectsByRepoRoot({
835
- client,
836
- cwd,
837
- gitProjectName,
838
- orgs,
839
- autoConfirm,
840
- nonInteractive
841
- });
842
- const slugifiedName = (0, import_slugify2.default)(projectName);
843
- const searchNames = [projectName];
844
- if (slugifiedName !== projectName) {
845
- searchNames.push(slugifiedName);
846
- }
847
- const folderNameSearchPromises = orgs.flatMap(
848
- (org) => searchNames.map(
849
- (name) => getProjectByNameOrId(client, name, org.id).then(
850
- (result) => result instanceof ProjectNotFound ? null : { project: result, org, reason: "folder-name" }
851
- ).catch(() => null)
852
- )
853
- );
854
- const [repoMatches, folderNameMatches] = await Promise.all([
855
- repoMatchesPromise,
856
- Promise.all(folderNameSearchPromises)
857
- ]);
858
- const results = [...repoMatches, ...folderNameMatches];
859
- const seen = /* @__PURE__ */ new Set();
860
- const matches = [];
861
- for (const r of results) {
862
- if (r && r.project.id && !seen.has(r.project.id)) {
863
- seen.add(r.project.id);
864
- matches.push(r);
865
- }
866
- }
867
- return {
868
- matches,
869
- searchedTeamSlugs,
870
- skippedLimitedTeamSlugs: skippedSlugs,
871
- skippedLimitedTeams: skippedTeams
872
- };
873
- }
874
968
  async function searchProjectsByRepoRoot({
875
969
  client,
876
970
  cwd,
@@ -889,9 +983,10 @@ async function searchProjectsByRepoRoot({
889
983
  yes: autoConfirm || nonInteractive
890
984
  });
891
985
  } catch (error) {
892
- output_manager_default.debug(
893
- `Failed to resolve Git remote for cross-team search: ${error}`
894
- );
986
+ if (isPromptCanceledError(error)) {
987
+ throw error;
988
+ }
989
+ output_manager_default.debug(`Failed to resolve Git remote for project search: ${error}`);
895
990
  return [];
896
991
  }
897
992
  if (!remote) {
@@ -934,6 +1029,9 @@ async function searchProjectsByRepoRoot({
934
1029
  };
935
1030
  }).filter(Boolean);
936
1031
  } catch (error) {
1032
+ if (isPromptCanceledError(error)) {
1033
+ throw error;
1034
+ }
937
1035
  output_manager_default.debug(
938
1036
  `Failed to search Git-linked projects under ${org.slug}: ${error}`
939
1037
  );
@@ -945,42 +1043,30 @@ async function searchProjectsByRepoRoot({
945
1043
  }
946
1044
 
947
1045
  // src/util/link/setup-and-link.ts
948
- function formatMatchReason(match) {
949
- if (match.reason === "repo-root") {
950
- return import_chalk6.default.gray("(linked by git)");
951
- }
952
- return import_chalk6.default.gray("(folder name)");
953
- }
954
- function formatCrossTeamMatch(match) {
955
- return `${import_chalk6.default.bold(match.org.slug)}/${match.project.name} ${formatMatchReason(
956
- match
957
- )}`;
958
- }
959
- function formatMatchSource(match) {
960
- return match.reason === "repo-root" ? "Git repository" : "Folder name";
1046
+ function isCrossTeamMatch(value) {
1047
+ return typeof value === "object" && value !== null && "project" in value && "org" in value && "reason" in value;
961
1048
  }
962
- var CHECKBOX_INSTRUCTIONS = [
963
- "\n ",
964
- import_chalk6.default.dim("("),
965
- import_chalk6.default.cyan("<space>"),
966
- import_chalk6.default.dim(" select, "),
967
- import_chalk6.default.cyan("<enter>"),
968
- import_chalk6.default.dim(" confirm, "),
969
- import_chalk6.default.cyan("<a>"),
970
- import_chalk6.default.dim(" toggle all, "),
971
- import_chalk6.default.cyan("<i>"),
972
- import_chalk6.default.dim(" invert)")
973
- ].join("");
974
- function printCrossTeamSearchScope({
975
- searchedTeamSlugs
976
- }) {
977
- if (searchedTeamSlugs.length > 0) {
978
- output_manager_default.print(
979
- ` ${import_chalk6.default.dim(
980
- `Searched ${searchedTeamSlugs.length} ${searchedTeamSlugs.length === 1 ? "team" : "teams"}`
981
- )}
982
- `
1049
+ async function selectOrgForLink(client, autoConfirm, searchable = false, meta) {
1050
+ try {
1051
+ return await selectOrg(
1052
+ client,
1053
+ "Which team?",
1054
+ autoConfirm,
1055
+ searchable,
1056
+ meta
983
1057
  );
1058
+ } catch (err) {
1059
+ if (isAPIError(err)) {
1060
+ if (err.code === "NOT_AUTHORIZED") {
1061
+ output_manager_default.prettyError(err);
1062
+ return { status: "error", exitCode: 1, reason: "NOT_AUTHORIZED" };
1063
+ }
1064
+ if (err.code === "TEAM_DELETED") {
1065
+ output_manager_default.prettyError(err);
1066
+ return { status: "error", exitCode: 1, reason: "TEAM_DELETED" };
1067
+ }
1068
+ }
1069
+ throw err;
984
1070
  }
985
1071
  }
986
1072
  function isErrnoException(err) {
@@ -1082,150 +1168,16 @@ async function linkCrossTeamMatch({
1082
1168
  );
1083
1169
  return { status: "linked", org: match.org, project: match.project };
1084
1170
  }
1085
- async function promptForLimitedTeams(client, teams) {
1086
- if (teams.length === 0) {
1087
- return [];
1088
- }
1089
- return await client.input.checkbox({
1090
- message: "Select teams that require SSO",
1091
- instructions: CHECKBOX_INSTRUCTIONS,
1092
- choices: teams.map((team) => ({
1093
- name: team.name ? `${team.name} (${team.slug})` : team.slug,
1094
- value: team
1095
- }))
1096
- });
1097
- }
1098
- async function searchSelectedLimitedTeams({
1099
- client,
1100
- path: path2,
1101
- projectName,
1102
- gitProjectName,
1103
- teams
1104
- }) {
1105
- const selectedTeams = await promptForLimitedTeams(client, teams);
1106
- if (selectedTeams.length === 0) {
1107
- return [];
1108
- }
1109
- output_manager_default.spinner("Searching selected teams that require SSO\u2026", 1e3);
1110
- try {
1111
- const result = await searchProjectAcrossTeams(client, projectName, path2, {
1112
- teams: selectedTeams,
1113
- skipLimited: false,
1114
- gitProjectName
1115
- });
1116
- printCrossTeamSearchScope({
1117
- searchedTeamSlugs: result.searchedTeamSlugs
1118
- });
1119
- return result.matches;
1120
- } catch (err) {
1121
- output_manager_default.debug(`Selected team search requiring SSO failed: ${err}`);
1122
- return [];
1123
- } finally {
1124
- output_manager_default.stopSpinner();
1125
- }
1126
- }
1127
- async function linkCrossTeamMatches({
1128
- client,
1129
- path: path2,
1130
- matches,
1131
- successEmoji,
1132
- autoConfirm,
1133
- nonInteractive,
1134
- pullEnv
1135
- }) {
1136
- if (matches.length === 0) {
1137
- return null;
1138
- }
1139
- if (matches.length === 1) {
1140
- const match = matches[0];
1141
- if (autoConfirm || nonInteractive) {
1142
- return await linkCrossTeamMatch({
1143
- client,
1144
- path: path2,
1145
- match,
1146
- successEmoji,
1147
- autoConfirm,
1148
- pullEnv
1149
- });
1150
- }
1151
- output_manager_default.print("\n");
1152
- output_manager_default.print(` ${import_chalk6.default.bold("Found existing project")}
1153
- `);
1154
- if (match.reason === "repo-root") {
1155
- printAlignedLabel("Project", `${match.org.slug}/${match.project.name}`);
1156
- printAlignedLabel("Source", formatMatchSource(match));
1157
- } else {
1158
- printAlignedLabel("Project", `${match.org.slug}/${match.project.name}`);
1159
- }
1160
- const confirmed = await client.input.confirm(
1161
- match.reason === "repo-root" ? "Link repository to project?" : "Link directory to project?",
1162
- true
1163
- );
1164
- if (confirmed) {
1165
- return await linkCrossTeamMatch({
1166
- client,
1167
- path: path2,
1168
- match,
1169
- successEmoji,
1170
- autoConfirm,
1171
- pullEnv
1172
- });
1173
- }
1174
- return null;
1175
- }
1176
- const currentTeamMatch = matches.find(
1177
- (match) => match.org.id === client.config.currentTeam
1178
- );
1179
- if (autoConfirm && currentTeamMatch) {
1180
- return await linkCrossTeamMatch({
1181
- client,
1182
- path: path2,
1183
- match: currentTeamMatch,
1184
- successEmoji,
1185
- autoConfirm,
1186
- pullEnv
1187
- });
1188
- }
1189
- if (nonInteractive) {
1190
- return null;
1191
- }
1192
- const choices = matches.map((match) => ({
1193
- name: formatCrossTeamMatch(match),
1194
- value: match
1195
- }));
1196
- choices.push({
1197
- name: "Not one of these projects",
1198
- value: null
1199
- });
1200
- output_manager_default.print("\n");
1201
- printAlignedLabel("Projects", `${matches.length} matches across teams`);
1202
- const selected = await client.input.select({
1203
- message: "Which project?",
1204
- choices,
1205
- default: currentTeamMatch ?? void 0
1206
- });
1207
- if (!selected) {
1208
- return null;
1209
- }
1210
- return await linkCrossTeamMatch({
1211
- client,
1212
- path: path2,
1213
- match: selected,
1214
- successEmoji,
1215
- autoConfirm,
1216
- pullEnv
1217
- });
1218
- }
1219
1171
  async function setupAndLink(client, path2, {
1220
1172
  autoConfirm = false,
1221
1173
  forceDelete = false,
1222
1174
  link,
1175
+ selectedOrg,
1223
1176
  successEmoji = "link",
1224
1177
  projectName,
1225
1178
  nonInteractive = false,
1226
1179
  pullEnv = true,
1227
- v0,
1228
- searchAcrossTeams = false
1180
+ v0
1229
1181
  }) {
1230
1182
  const { config } = client;
1231
1183
  const gitProjectName = projectName;
@@ -1240,130 +1192,122 @@ async function setupAndLink(client, path2, {
1240
1192
  const isTTY = client.stdin.isTTY;
1241
1193
  let rootDirectory = null;
1242
1194
  let newProjectName;
1243
- let org;
1195
+ let org = selectedOrg;
1244
1196
  if (!forceDelete && link.status === "linked") {
1245
1197
  return link;
1246
1198
  }
1199
+ if (getPlatformEnv("ORG_ID") && getPlatformEnv("PROJECT_ID")) {
1200
+ const envLink = await getLinkedProject(client, path2);
1201
+ if (envLink.status === "error") {
1202
+ return envLink;
1203
+ }
1204
+ if (envLink.status === "linked") {
1205
+ config.currentTeam = envLink.org.type === "team" ? envLink.org.id : void 0;
1206
+ output_manager_default.print("\n");
1207
+ printAlignedLabel("Directory", humanizePath(path2));
1208
+ printAlignedLabel("Source", "VERCEL_ORG_ID and VERCEL_PROJECT_ID");
1209
+ output_manager_default.print("\n");
1210
+ printAlignedLabel(
1211
+ "Linked",
1212
+ `${envLink.org.slug}/${envLink.project.name}`,
1213
+ { gutter: "\u2713" }
1214
+ );
1215
+ return envLink;
1216
+ }
1217
+ }
1218
+ if (!isTTY && !autoConfirm && !nonInteractive) {
1219
+ return { status: "error", exitCode: 1, reason: "HEADLESS" };
1220
+ }
1221
+ if (!org && (nonInteractive || !isTTY)) {
1222
+ const resolved = await selectOrgForLink(client, autoConfirm);
1223
+ if ("status" in resolved) {
1224
+ return resolved;
1225
+ }
1226
+ org = resolved;
1227
+ }
1247
1228
  if (forceDelete) {
1248
1229
  const vercelDir = getVercelDirectory(path2);
1249
1230
  (0, import_fs_extra2.remove)(join2(vercelDir, VERCEL_DIR_README));
1250
1231
  (0, import_fs_extra2.remove)(join2(vercelDir, VERCEL_DIR_PROJECT));
1251
1232
  }
1252
- if (!isTTY && !autoConfirm && !nonInteractive) {
1253
- return { status: "error", exitCode: 1, reason: "HEADLESS" };
1254
- }
1255
1233
  output_manager_default.print("\n");
1256
1234
  printAlignedLabel("Directory", humanizePath(path2));
1257
1235
  output_manager_default.print("\n");
1258
- let skipAutoDetect = false;
1259
- if (searchAcrossTeams) {
1260
- let crossTeamMatches = [];
1261
- let searchedTeamSlugs = [];
1262
- let skippedLimitedTeams = [];
1263
- output_manager_default.spinner("Searching for existing projects\u2026", 1e3);
1264
- try {
1265
- const searchResult = await searchProjectAcrossTeams(
1236
+ const interactive = isTTY && !nonInteractive;
1237
+ const searchableTeamPicker = interactive;
1238
+ const showProjectSuggestions = interactive && !gitProjectName;
1239
+ let projectOrNewProjectName;
1240
+ let teamAutoSelected = false;
1241
+ for (; ; ) {
1242
+ if (!org) {
1243
+ const orgMeta = {};
1244
+ const resolved = await selectOrgForLink(
1266
1245
  client,
1267
- projectName,
1268
- path2,
1269
- {
1270
- autoConfirm,
1271
- nonInteractive,
1272
- gitProjectName
1273
- }
1246
+ autoConfirm,
1247
+ searchableTeamPicker,
1248
+ orgMeta
1274
1249
  );
1275
- crossTeamMatches = searchResult.matches;
1276
- searchedTeamSlugs = searchResult.searchedTeamSlugs;
1277
- skippedLimitedTeams = searchResult.skippedLimitedTeams;
1278
- } catch (err) {
1279
- output_manager_default.debug(`Cross-team search failed: ${err}`);
1280
- } finally {
1281
- output_manager_default.stopSpinner();
1282
- }
1283
- if (crossTeamMatches.length > 0 && searchedTeamSlugs.length > 1 && !autoConfirm && !nonInteractive) {
1284
- printCrossTeamSearchScope({
1285
- searchedTeamSlugs
1286
- });
1287
- }
1288
- const linkedMatch = await linkCrossTeamMatches({
1289
- client,
1290
- path: path2,
1291
- matches: crossTeamMatches,
1292
- successEmoji,
1293
- autoConfirm,
1294
- nonInteractive,
1295
- pullEnv
1296
- });
1297
- if (linkedMatch) {
1298
- return linkedMatch;
1250
+ if ("status" in resolved) {
1251
+ return resolved;
1252
+ }
1253
+ org = resolved;
1254
+ teamAutoSelected = orgMeta.choiceCount === 1;
1299
1255
  }
1300
- if (!autoConfirm && !nonInteractive && skippedLimitedTeams.length > 0) {
1301
- if (crossTeamMatches.length === 0) {
1302
- printAlignedLabel(
1303
- "Searched",
1304
- `${searchedTeamSlugs.length} ${searchedTeamSlugs.length === 1 ? "team" : "teams"} available without SSO`
1305
- );
1306
- output_manager_default.print(" No matching projects found\n");
1256
+ let repoMatches = [];
1257
+ if (showProjectSuggestions) {
1258
+ output_manager_default.spinner("Searching for existing projects\u2026", 1e3);
1259
+ try {
1260
+ repoMatches = await searchProjectsByRepoRoot({
1261
+ client,
1262
+ cwd: path2,
1263
+ gitProjectName,
1264
+ orgs: [org],
1265
+ autoConfirm,
1266
+ nonInteractive
1267
+ });
1268
+ } catch (err) {
1269
+ if (isPromptCanceledError(err)) {
1270
+ throw err;
1271
+ }
1272
+ output_manager_default.debug(`Git-linked project search failed: ${err}`);
1273
+ } finally {
1274
+ output_manager_default.stopSpinner();
1307
1275
  }
1308
- const limitedTeamMatches = await searchSelectedLimitedTeams({
1276
+ }
1277
+ try {
1278
+ projectOrNewProjectName = await inputProject(
1309
1279
  client,
1310
- path: path2,
1280
+ org,
1311
1281
  projectName,
1312
- gitProjectName,
1313
- teams: skippedLimitedTeams
1314
- });
1315
- const linkedLimitedMatch = await linkCrossTeamMatches({
1316
- client,
1317
- path: path2,
1318
- matches: limitedTeamMatches,
1319
- successEmoji,
1320
1282
  autoConfirm,
1321
- nonInteractive,
1322
- pullEnv
1323
- });
1324
- if (linkedLimitedMatch) {
1325
- return linkedLimitedMatch;
1326
- }
1327
- if (limitedTeamMatches.length === 0) {
1328
- output_manager_default.print(" No matching projects found in the selected teams.\n");
1329
- }
1330
- skipAutoDetect = skipAutoDetect || crossTeamMatches.length > 0 || limitedTeamMatches.length > 0;
1331
- } else if (crossTeamMatches.length > 0) {
1332
- skipAutoDetect = true;
1333
- }
1334
- }
1335
- try {
1336
- org = await selectOrg(client, "Which team?", autoConfirm);
1337
- } catch (err) {
1338
- if (isAPIError(err)) {
1339
- if (err.code === "NOT_AUTHORIZED") {
1340
- output_manager_default.prettyError(err);
1341
- return { status: "error", exitCode: 1, reason: "NOT_AUTHORIZED" };
1342
- }
1343
- if (err.code === "TEAM_DELETED") {
1344
- output_manager_default.prettyError(err);
1345
- return { status: "error", exitCode: 1, reason: "TEAM_DELETED" };
1283
+ false,
1284
+ showProjectSuggestions,
1285
+ searchableTeamPicker && !selectedOrg && !teamAutoSelected,
1286
+ repoMatches
1287
+ );
1288
+ } catch (err) {
1289
+ if (err instanceof Error && err.code === "HEADLESS") {
1290
+ return { status: "error", exitCode: 1, reason: "HEADLESS" };
1346
1291
  }
1292
+ throw err;
1347
1293
  }
1348
- throw err;
1349
- }
1350
- let projectOrNewProjectName;
1351
- try {
1352
- projectOrNewProjectName = await inputProject(
1353
- client,
1354
- org,
1355
- projectName,
1356
- autoConfirm,
1357
- skipAutoDetect
1358
- );
1359
- } catch (err) {
1360
- if (err instanceof Error && err.code === "HEADLESS") {
1361
- return { status: "error", exitCode: 1, reason: "HEADLESS" };
1294
+ if (projectOrNewProjectName === BACK_TO_TEAM_SELECTION) {
1295
+ org = void 0;
1296
+ continue;
1362
1297
  }
1363
- throw err;
1298
+ break;
1364
1299
  }
1365
1300
  if (typeof projectOrNewProjectName === "string") {
1366
1301
  newProjectName = projectOrNewProjectName;
1302
+ } else if (isCrossTeamMatch(projectOrNewProjectName)) {
1303
+ return await linkCrossTeamMatch({
1304
+ client,
1305
+ path: path2,
1306
+ match: projectOrNewProjectName,
1307
+ successEmoji,
1308
+ autoConfirm,
1309
+ pullEnv
1310
+ });
1367
1311
  } else {
1368
1312
  const project = projectOrNewProjectName;
1369
1313
  await linkFolderToProject(
@@ -1530,6 +1474,9 @@ async function setupAndLink(client, path2, {
1530
1474
  await connectGitRepository(client, path2, project, autoConfirm, org);
1531
1475
  return { status: "linked", org, project };
1532
1476
  } catch (err) {
1477
+ if (isPromptCanceledError(err)) {
1478
+ throw err;
1479
+ }
1533
1480
  if (isAPIError(err) && err.code === "too_many_projects") {
1534
1481
  output_manager_default.prettyError(err);
1535
1482
  return { status: "error", exitCode: 1, reason: "TOO_MANY_PROJECTS" };
@@ -1573,6 +1520,9 @@ async function connectGitRepository(client, path2, project, autoConfirm, org) {
1573
1520
  repoPath: `${repoInfo.org}/${repoInfo.repo}`
1574
1521
  });
1575
1522
  } catch (error) {
1523
+ if (isPromptCanceledError(error)) {
1524
+ return;
1525
+ }
1576
1526
  output_manager_default.debug(`Failed to connect git repository: ${error}`);
1577
1527
  }
1578
1528
  }