naro-cli 0.4.0 → 0.5.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.
- package/dist/{chunk-YLFLDW2H.js → chunk-ZHVBIQBE.js} +11 -1
- package/dist/deploy.d.ts +7 -0
- package/dist/deploy.js +1 -1
- package/dist/index.js +232 -125
- package/package.json +12 -13
|
@@ -207,6 +207,11 @@ function pathnameOf(url2) {
|
|
|
207
207
|
}
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
+
// ../../packages/core/src/paths.ts
|
|
211
|
+
function projectPath(teamSlug, projectSlug) {
|
|
212
|
+
return `/${teamSlug}/projects/${projectSlug}`;
|
|
213
|
+
}
|
|
214
|
+
|
|
210
215
|
// ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
|
|
211
216
|
var external_exports = {};
|
|
212
217
|
__export(external_exports, {
|
|
@@ -17224,6 +17229,7 @@ async function deployDirectory(sourceDir, options, log2) {
|
|
|
17224
17229
|
log2("\u25B8 starting deployment");
|
|
17225
17230
|
const ctx = await client.deployments.createCli({
|
|
17226
17231
|
...options.projectId ? { projectId: options.projectId } : { name },
|
|
17232
|
+
...options.teamSlug ? { teamSlug: options.teamSlug } : {},
|
|
17227
17233
|
production,
|
|
17228
17234
|
...git.repositoryUrl != null && { repositoryUrl: git.repositoryUrl },
|
|
17229
17235
|
...branch != null && { branch },
|
|
@@ -17284,7 +17290,10 @@ async function deployDirectory(sourceDir, options, log2) {
|
|
|
17284
17290
|
status: detail.deployment.status,
|
|
17285
17291
|
url: url2,
|
|
17286
17292
|
production,
|
|
17287
|
-
missingEnvKeys
|
|
17293
|
+
missingEnvKeys,
|
|
17294
|
+
teamSlug: ctx.teamSlug,
|
|
17295
|
+
projectSlug: ctx.projectSlug,
|
|
17296
|
+
gitConnection: ctx.gitConnection
|
|
17288
17297
|
};
|
|
17289
17298
|
}
|
|
17290
17299
|
async function waitForFinish(client, ctx, log2) {
|
|
@@ -17305,6 +17314,7 @@ function deploymentUrl(detail, ctx, production) {
|
|
|
17305
17314
|
|
|
17306
17315
|
export {
|
|
17307
17316
|
parseGitHubRemote,
|
|
17317
|
+
projectPath,
|
|
17308
17318
|
NaroError,
|
|
17309
17319
|
isNaroError,
|
|
17310
17320
|
toNaroError,
|
package/dist/deploy.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type CliGitConnection } from "@next-deploy/sdk";
|
|
1
2
|
import type { BuildObserver, DeploymentArtifact, DeploymentStatus } from "./artifact-types";
|
|
2
3
|
/** The slice of LocalBuildExecutor the deploy flow needs — injectable in tests. */
|
|
3
4
|
export interface DeployBuildExecutor {
|
|
@@ -22,6 +23,8 @@ export interface DeployOptions {
|
|
|
22
23
|
name?: string | undefined;
|
|
23
24
|
/** Linked project id (.naro/project.json or --project). */
|
|
24
25
|
projectId?: string | undefined;
|
|
26
|
+
/** Owning team slug, resolved by the caller (see commands/link.ts). */
|
|
27
|
+
teamSlug?: string | undefined;
|
|
25
28
|
rootDirectory?: string | null;
|
|
26
29
|
production?: boolean;
|
|
27
30
|
env?: Record<string, string> | undefined;
|
|
@@ -49,6 +52,10 @@ export interface DeployResult {
|
|
|
49
52
|
production: boolean;
|
|
50
53
|
/** 끝까지 채워지지 않은 키. */
|
|
51
54
|
missingEnvKeys: string[];
|
|
55
|
+
/** 서버가 확정한 목적지 — 출력과 링크 파일이 이 값을 쓴다. */
|
|
56
|
+
teamSlug: string;
|
|
57
|
+
projectSlug: string;
|
|
58
|
+
gitConnection: CliGitConnection;
|
|
52
59
|
}
|
|
53
60
|
/**
|
|
54
61
|
* The full CLI deploy: authenticate, register the deployment with the
|
package/dist/deploy.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
login,
|
|
16
16
|
openBrowser,
|
|
17
17
|
parseGitHubRemote,
|
|
18
|
+
projectPath,
|
|
18
19
|
promptForMissingEnv,
|
|
19
20
|
readProjectLink,
|
|
20
21
|
refreshStoredSession,
|
|
@@ -25,7 +26,7 @@ import {
|
|
|
25
26
|
slugify,
|
|
26
27
|
toNaroError,
|
|
27
28
|
writeProjectLink
|
|
28
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-ZHVBIQBE.js";
|
|
29
30
|
|
|
30
31
|
// src/args.ts
|
|
31
32
|
var BOOLEAN_FLAGS = {
|
|
@@ -522,20 +523,34 @@ async function runGitDisconnect(context) {
|
|
|
522
523
|
" Deploy from here with: naro deploy"
|
|
523
524
|
]);
|
|
524
525
|
}
|
|
525
|
-
async function offerAutoDeploy(context, projectId, overrides
|
|
526
|
+
async function offerAutoDeploy(context, projectId, overrides) {
|
|
526
527
|
const deps = { ...defaults, ...overrides };
|
|
527
528
|
if (overrides.canPrompt === false) return;
|
|
528
529
|
try {
|
|
529
530
|
if (readProjectLink(context.cwd)?.autoDeployDeclined === true) return;
|
|
530
|
-
const
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
if (
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
531
|
+
const connection = overrides.gitConnection;
|
|
532
|
+
if (connection.state === "connected" || connection.state === "none") return;
|
|
533
|
+
const fullName = connection.fullName;
|
|
534
|
+
if (!fullName) return;
|
|
535
|
+
let wanted;
|
|
536
|
+
if (connection.state === "unreachable") {
|
|
537
|
+
const installed = connection.installedAccounts ?? [];
|
|
538
|
+
context.output.log(
|
|
539
|
+
`
|
|
540
|
+
\u25B8 Automatic deploys need the Naro GitHub App on \`${connection.repoOwner ?? fullName}\`.`
|
|
541
|
+
);
|
|
542
|
+
if (overrides.teamSlug && installed.length > 0)
|
|
543
|
+
context.output.log(
|
|
544
|
+
` Team ${overrides.teamSlug} has it on: ${installed.join(", ")}.`
|
|
545
|
+
);
|
|
546
|
+
wanted = await context.prompter.confirm(
|
|
547
|
+
"Install it for this team now? (opens your browser)"
|
|
548
|
+
);
|
|
549
|
+
} else {
|
|
550
|
+
wanted = await context.prompter.confirm(
|
|
551
|
+
"Should your changes deploy automatically from now on?"
|
|
552
|
+
);
|
|
553
|
+
}
|
|
539
554
|
if (!wanted) {
|
|
540
555
|
const link = readProjectLink(context.cwd);
|
|
541
556
|
writeProjectLink(context.cwd, {
|
|
@@ -547,18 +562,19 @@ async function offerAutoDeploy(context, projectId, overrides = {}) {
|
|
|
547
562
|
);
|
|
548
563
|
return;
|
|
549
564
|
}
|
|
550
|
-
const
|
|
565
|
+
const client = await context.client();
|
|
566
|
+
const result = await client.projects.connectGit(projectId, fullName);
|
|
551
567
|
if (result.ok) {
|
|
552
568
|
connected(context, result.project);
|
|
553
569
|
return;
|
|
554
570
|
}
|
|
555
571
|
if (!result.installUrl) return;
|
|
556
572
|
context.output.log(`
|
|
557
|
-
\u25B8 Naro needs access to ${
|
|
573
|
+
\u25B8 Naro needs access to ${fullName}.`);
|
|
558
574
|
context.output.log(` Opening ${result.installUrl}`);
|
|
559
575
|
deps.openBrowser(result.installUrl);
|
|
560
576
|
context.output.log(" Waiting for the installation to finish...\n");
|
|
561
|
-
await waitForInstall(context, projectId,
|
|
577
|
+
await waitForInstall(context, projectId, fullName, deps);
|
|
562
578
|
} catch (error) {
|
|
563
579
|
context.output.log(
|
|
564
580
|
`\u25B8 Could not set up automatic deploys (${error instanceof Error ? error.message : String(error)}).`
|
|
@@ -567,10 +583,150 @@ async function offerAutoDeploy(context, projectId, overrides = {}) {
|
|
|
567
583
|
}
|
|
568
584
|
}
|
|
569
585
|
|
|
586
|
+
// src/commands/link.ts
|
|
587
|
+
var SWITCH_ACCOUNT = "Sign in as a different account\u2026";
|
|
588
|
+
async function resolveTeam(context, teams, deps = {}) {
|
|
589
|
+
const explicit = context.flags.team ?? process.env.NARO_TEAM;
|
|
590
|
+
if (explicit) {
|
|
591
|
+
const match = teams.find((team) => team.slug === explicit);
|
|
592
|
+
if (!match)
|
|
593
|
+
throw new NaroError(
|
|
594
|
+
"NOT_FOUND",
|
|
595
|
+
`You are not a member of team "${explicit}".`
|
|
596
|
+
);
|
|
597
|
+
return match;
|
|
598
|
+
}
|
|
599
|
+
const linked = context.projectLink();
|
|
600
|
+
if (linked?.teamSlug) {
|
|
601
|
+
const match = teams.find((team) => team.slug === linked.teamSlug);
|
|
602
|
+
if (match) return match;
|
|
603
|
+
}
|
|
604
|
+
if (teams.length === 0)
|
|
605
|
+
throw new NaroError("NOT_FOUND", "Your account has no teams yet.");
|
|
606
|
+
const canPrompt = deps.canPrompt?.() ?? (!context.flags.json && !context.flags.yes && isInteractive());
|
|
607
|
+
if (!canPrompt)
|
|
608
|
+
throw new NaroError(
|
|
609
|
+
"TEAM_REQUIRED",
|
|
610
|
+
`Cannot pick a team without a prompt. Pass --team <slug>, set NARO_TEAM, or run \`naro link\` first.
|
|
611
|
+
Your teams: ${teams.map((team) => team.slug).join(", ")}`
|
|
612
|
+
);
|
|
613
|
+
const account = deps.accountLabel ? await deps.accountLabel() : "this account";
|
|
614
|
+
if (teams.length === 1) {
|
|
615
|
+
const only = teams[0];
|
|
616
|
+
const label = only.isPersonal ? `${only.name} (personal)` : only.name;
|
|
617
|
+
context.output.log(`
|
|
618
|
+
${account} \u2192 ${label}`);
|
|
619
|
+
const ok = await context.prompter.confirm(
|
|
620
|
+
"Deploy to this account and team?"
|
|
621
|
+
);
|
|
622
|
+
if (ok) return only;
|
|
623
|
+
return switchAndRetry(context, deps);
|
|
624
|
+
}
|
|
625
|
+
const options = [
|
|
626
|
+
...teams.map(
|
|
627
|
+
(team) => `${team.name}${team.isPersonal ? " (personal)" : ""}`
|
|
628
|
+
),
|
|
629
|
+
SWITCH_ACCOUNT
|
|
630
|
+
];
|
|
631
|
+
const index = await context.prompter.select(
|
|
632
|
+
`Deploying as ${account} \u2014 which team owns this project?`,
|
|
633
|
+
options
|
|
634
|
+
);
|
|
635
|
+
const picked = teams[index];
|
|
636
|
+
if (picked) return picked;
|
|
637
|
+
return switchAndRetry(context, deps);
|
|
638
|
+
}
|
|
639
|
+
async function switchAndRetry(context, deps) {
|
|
640
|
+
if (!deps.switchAccount || !deps.listTeams)
|
|
641
|
+
throw new NaroError(
|
|
642
|
+
"CANCELED",
|
|
643
|
+
"Canceled. Switch accounts with `naro logout && naro login`."
|
|
644
|
+
);
|
|
645
|
+
await deps.switchAccount();
|
|
646
|
+
const teams = await deps.listTeams();
|
|
647
|
+
return resolveTeam(context, teams, deps);
|
|
648
|
+
}
|
|
649
|
+
function saveLink(context, project, teamSlug) {
|
|
650
|
+
writeProjectLink(context.cwd, {
|
|
651
|
+
projectId: project.id,
|
|
652
|
+
teamSlug,
|
|
653
|
+
projectName: project.name
|
|
654
|
+
});
|
|
655
|
+
if (ensureGitignoreHasNaro(context.cwd))
|
|
656
|
+
context.output.log("\u25B8 added .naro to .gitignore");
|
|
657
|
+
}
|
|
658
|
+
async function runLink(context) {
|
|
659
|
+
const client = await context.client();
|
|
660
|
+
const teams = await client.teams.list();
|
|
661
|
+
const team = await resolveTeam(context, teams);
|
|
662
|
+
const projects = await client.projects.list(team.slug);
|
|
663
|
+
let project;
|
|
664
|
+
if (context.flags.project) {
|
|
665
|
+
project = projects.find(
|
|
666
|
+
(candidate) => candidate.id === context.flags.project
|
|
667
|
+
);
|
|
668
|
+
if (!project) {
|
|
669
|
+
project = await client.projects.get(context.flags.project);
|
|
670
|
+
}
|
|
671
|
+
} else if (context.flags.name) {
|
|
672
|
+
project = projects.find(
|
|
673
|
+
(candidate) => candidate.name === context.flags.name || candidate.slug === context.flags.name
|
|
674
|
+
);
|
|
675
|
+
if (!project)
|
|
676
|
+
throw new NaroError(
|
|
677
|
+
"PROJECT_NOT_FOUND",
|
|
678
|
+
`No project named "${context.flags.name}" in team ${team.slug}. Run \`naro deploy\` to create it.`
|
|
679
|
+
);
|
|
680
|
+
} else {
|
|
681
|
+
const guess = slugify(context.cwd.split("/").at(-1) ?? "app");
|
|
682
|
+
const exact = projects.find(
|
|
683
|
+
(candidate) => candidate.name === guess || candidate.slug === guess
|
|
684
|
+
);
|
|
685
|
+
if (exact && (context.flags.yes || context.flags.json)) {
|
|
686
|
+
project = exact;
|
|
687
|
+
} else if (projects.length === 0) {
|
|
688
|
+
throw new NaroError(
|
|
689
|
+
"PROJECT_NOT_FOUND",
|
|
690
|
+
`Team ${team.slug} has no projects yet. Run \`naro deploy\` to create one.`
|
|
691
|
+
);
|
|
692
|
+
} else {
|
|
693
|
+
const options = projects.map(
|
|
694
|
+
(candidate) => `${candidate.name} (${candidate.subdomain})`
|
|
695
|
+
);
|
|
696
|
+
const index = await context.prompter.select(
|
|
697
|
+
`Link ${context.cwd} to which project?`,
|
|
698
|
+
options
|
|
699
|
+
);
|
|
700
|
+
project = projects[index];
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
if (!project)
|
|
704
|
+
throw new NaroError("PROJECT_NOT_FOUND", "No project selected.");
|
|
705
|
+
const owningTeam = teams.find((candidate) => candidate.id === project.teamId) ?? team;
|
|
706
|
+
saveLink(context, project, owningTeam.slug);
|
|
707
|
+
context.output.result(
|
|
708
|
+
{
|
|
709
|
+
projectId: project.id,
|
|
710
|
+
projectName: project.name,
|
|
711
|
+
teamSlug: owningTeam.slug,
|
|
712
|
+
subdomain: project.subdomain
|
|
713
|
+
},
|
|
714
|
+
[`\u2705 Linked to ${owningTeam.slug}/${project.name} (.naro/project.json)`]
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
async function runUnlink(context) {
|
|
718
|
+
const existing = readProjectLink(context.cwd);
|
|
719
|
+
const removed = removeProjectLink(context.cwd);
|
|
720
|
+
context.output.result(
|
|
721
|
+
{ removed, ...existing ? { projectId: existing.projectId } : {} },
|
|
722
|
+
[removed ? "\u2705 Unlinked (.naro removed)" : "Nothing to unlink here."]
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
|
|
570
726
|
// src/commands/deploy.ts
|
|
571
727
|
async function runDeploy(context, deps = { deploy: deployDirectory, offerAutoDeploy }) {
|
|
572
728
|
const { flags, output } = context;
|
|
573
|
-
const link = flags.project ? { projectId: flags.project } : readProjectLink(context.cwd);
|
|
729
|
+
const link = flags.project ? { projectId: flags.project, teamSlug: void 0 } : readProjectLink(context.cwd);
|
|
574
730
|
if (flags.prod && !flags.yes && !flags.json && isInteractive()) {
|
|
575
731
|
const confirmed = await context.prompter.confirm("Deploy to production?");
|
|
576
732
|
if (!confirmed) throw new NaroError("CANCELED", "Canceled.");
|
|
@@ -578,12 +734,17 @@ async function runDeploy(context, deps = { deploy: deployDirectory, offerAutoDep
|
|
|
578
734
|
output.log(`
|
|
579
735
|
\u{1F680} Deploying ${context.cwd}
|
|
580
736
|
`);
|
|
581
|
-
const canPrompt = !flags.json && !flags.yes && isInteractive();
|
|
737
|
+
const canPrompt = deps.canPrompt?.() ?? (!flags.json && !flags.yes && isInteractive());
|
|
738
|
+
const team = await resolveDestination(context, link, canPrompt);
|
|
739
|
+
const account = await accountLabel(context);
|
|
740
|
+
output.log(` ${account} \u2192 ${team.slug}
|
|
741
|
+
`);
|
|
582
742
|
const result = await deps.deploy(
|
|
583
743
|
context.cwd,
|
|
584
744
|
{
|
|
585
745
|
name: flags.name,
|
|
586
746
|
projectId: link?.projectId,
|
|
747
|
+
teamSlug: team.slug,
|
|
587
748
|
rootDirectory: flags.root ?? null,
|
|
588
749
|
branch: flags.branch,
|
|
589
750
|
production: flags.prod,
|
|
@@ -598,13 +759,24 @@ async function runDeploy(context, deps = { deploy: deployDirectory, offerAutoDep
|
|
|
598
759
|
},
|
|
599
760
|
(message) => output.log(message)
|
|
600
761
|
);
|
|
762
|
+
const existing = readProjectLink(context.cwd);
|
|
763
|
+
writeProjectLink(context.cwd, {
|
|
764
|
+
projectId: result.projectId,
|
|
765
|
+
teamSlug: result.teamSlug,
|
|
766
|
+
projectName: result.projectSlug,
|
|
767
|
+
...existing?.autoDeployDeclined === true ? { autoDeployDeclined: true } : {}
|
|
768
|
+
});
|
|
601
769
|
if (!link) {
|
|
602
|
-
writeProjectLink(context.cwd, { projectId: result.projectId });
|
|
603
770
|
if (ensureGitignoreHasNaro(context.cwd))
|
|
604
771
|
output.log("\u25B8 added .naro to .gitignore");
|
|
605
772
|
output.log("\u25B8 linked (.naro/project.json)");
|
|
606
773
|
}
|
|
607
|
-
await deps.offerAutoDeploy?.(context, result.projectId, {
|
|
774
|
+
await deps.offerAutoDeploy?.(context, result.projectId, {
|
|
775
|
+
canPrompt,
|
|
776
|
+
gitConnection: result.gitConnection,
|
|
777
|
+
teamSlug: result.teamSlug
|
|
778
|
+
});
|
|
779
|
+
const inspect = `${controlPlaneUrl()}${projectPath(result.teamSlug, result.projectSlug)}`;
|
|
608
780
|
context.output.result(
|
|
609
781
|
{
|
|
610
782
|
deploymentId: result.deploymentId,
|
|
@@ -612,16 +784,54 @@ async function runDeploy(context, deps = { deploy: deployDirectory, offerAutoDep
|
|
|
612
784
|
status: result.status,
|
|
613
785
|
url: result.url,
|
|
614
786
|
target: result.production ? "production" : "preview",
|
|
787
|
+
teamSlug: result.teamSlug,
|
|
788
|
+
inspectUrl: inspect,
|
|
615
789
|
missingEnvKeys: result.missingEnvKeys
|
|
616
790
|
},
|
|
617
791
|
[
|
|
618
792
|
"",
|
|
619
793
|
`\u2705 Deployment ready (${result.production ? "production" : "preview"})`,
|
|
620
794
|
"",
|
|
621
|
-
result.url
|
|
795
|
+
` ${result.url}`,
|
|
796
|
+
` Inspect ${inspect}`
|
|
622
797
|
]
|
|
623
798
|
);
|
|
624
799
|
}
|
|
800
|
+
async function resolveDestination(context, link, canPrompt) {
|
|
801
|
+
const client = await context.client();
|
|
802
|
+
const teams = await client.teams.list();
|
|
803
|
+
const deps = scopeDeps(context, canPrompt);
|
|
804
|
+
if (link?.teamSlug) {
|
|
805
|
+
const match = teams.find((team) => team.slug === link.teamSlug);
|
|
806
|
+
if (match) return match;
|
|
807
|
+
} else if (link) {
|
|
808
|
+
const project = await client.projects.get(link.projectId);
|
|
809
|
+
const match = teams.find((team) => team.id === project.teamId);
|
|
810
|
+
if (match) return match;
|
|
811
|
+
}
|
|
812
|
+
return resolveTeam(context, teams, deps);
|
|
813
|
+
}
|
|
814
|
+
function scopeDeps(context, canPrompt) {
|
|
815
|
+
return {
|
|
816
|
+
canPrompt: () => canPrompt,
|
|
817
|
+
accountLabel: () => accountLabel(context),
|
|
818
|
+
switchAccount: async () => {
|
|
819
|
+
clearSession();
|
|
820
|
+
await runLogin(context);
|
|
821
|
+
},
|
|
822
|
+
listTeams: async () => (await context.client()).teams.list()
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
async function accountLabel(context) {
|
|
826
|
+
try {
|
|
827
|
+
const { user } = await context.auth();
|
|
828
|
+
if (!user) return "this account";
|
|
829
|
+
const name = user.email ?? user.id;
|
|
830
|
+
return user.github ? `${name} (${user.github})` : name;
|
|
831
|
+
} catch {
|
|
832
|
+
return "this account";
|
|
833
|
+
}
|
|
834
|
+
}
|
|
625
835
|
|
|
626
836
|
// src/commands/deployments.ts
|
|
627
837
|
function shortSha(sha) {
|
|
@@ -875,112 +1085,6 @@ ${serializeDotenv(vars)}`,
|
|
|
875
1085
|
);
|
|
876
1086
|
}
|
|
877
1087
|
|
|
878
|
-
// src/commands/link.ts
|
|
879
|
-
async function resolveTeam(context, teams) {
|
|
880
|
-
if (context.flags.team) {
|
|
881
|
-
const match = teams.find((team) => team.slug === context.flags.team);
|
|
882
|
-
if (!match)
|
|
883
|
-
throw new NaroError(
|
|
884
|
-
"NOT_FOUND",
|
|
885
|
-
`You are not a member of team "${context.flags.team}".`
|
|
886
|
-
);
|
|
887
|
-
return match;
|
|
888
|
-
}
|
|
889
|
-
const linked = context.projectLink();
|
|
890
|
-
if (linked?.teamSlug) {
|
|
891
|
-
const match = teams.find((team) => team.slug === linked.teamSlug);
|
|
892
|
-
if (match) return match;
|
|
893
|
-
}
|
|
894
|
-
const personal = teams.find((team) => team.isPersonal);
|
|
895
|
-
const fallback = personal ?? teams[0];
|
|
896
|
-
if (!fallback)
|
|
897
|
-
throw new NaroError("NOT_FOUND", "Your account has no teams yet.");
|
|
898
|
-
if (teams.length > 1 && !personal) {
|
|
899
|
-
const index = await context.prompter.select(
|
|
900
|
-
"Which team?",
|
|
901
|
-
teams.map((team) => `${team.name} (${team.slug})`)
|
|
902
|
-
);
|
|
903
|
-
return teams[index] ?? fallback;
|
|
904
|
-
}
|
|
905
|
-
return fallback;
|
|
906
|
-
}
|
|
907
|
-
function saveLink(context, project, teamSlug) {
|
|
908
|
-
writeProjectLink(context.cwd, {
|
|
909
|
-
projectId: project.id,
|
|
910
|
-
teamSlug,
|
|
911
|
-
projectName: project.name
|
|
912
|
-
});
|
|
913
|
-
if (ensureGitignoreHasNaro(context.cwd))
|
|
914
|
-
context.output.log("\u25B8 added .naro to .gitignore");
|
|
915
|
-
}
|
|
916
|
-
async function runLink(context) {
|
|
917
|
-
const client = await context.client();
|
|
918
|
-
const teams = await client.teams.list();
|
|
919
|
-
const team = await resolveTeam(context, teams);
|
|
920
|
-
const projects = await client.projects.list(team.slug);
|
|
921
|
-
let project;
|
|
922
|
-
if (context.flags.project) {
|
|
923
|
-
project = projects.find(
|
|
924
|
-
(candidate) => candidate.id === context.flags.project
|
|
925
|
-
);
|
|
926
|
-
if (!project) {
|
|
927
|
-
project = await client.projects.get(context.flags.project);
|
|
928
|
-
}
|
|
929
|
-
} else if (context.flags.name) {
|
|
930
|
-
project = projects.find(
|
|
931
|
-
(candidate) => candidate.name === context.flags.name || candidate.slug === context.flags.name
|
|
932
|
-
);
|
|
933
|
-
if (!project)
|
|
934
|
-
throw new NaroError(
|
|
935
|
-
"PROJECT_NOT_FOUND",
|
|
936
|
-
`No project named "${context.flags.name}" in team ${team.slug}. Run \`naro deploy\` to create it.`
|
|
937
|
-
);
|
|
938
|
-
} else {
|
|
939
|
-
const guess = slugify(context.cwd.split("/").at(-1) ?? "app");
|
|
940
|
-
const exact = projects.find(
|
|
941
|
-
(candidate) => candidate.name === guess || candidate.slug === guess
|
|
942
|
-
);
|
|
943
|
-
if (exact && (context.flags.yes || context.flags.json)) {
|
|
944
|
-
project = exact;
|
|
945
|
-
} else if (projects.length === 0) {
|
|
946
|
-
throw new NaroError(
|
|
947
|
-
"PROJECT_NOT_FOUND",
|
|
948
|
-
`Team ${team.slug} has no projects yet. Run \`naro deploy\` to create one.`
|
|
949
|
-
);
|
|
950
|
-
} else {
|
|
951
|
-
const options = projects.map(
|
|
952
|
-
(candidate) => `${candidate.name} (${candidate.subdomain})`
|
|
953
|
-
);
|
|
954
|
-
const index = await context.prompter.select(
|
|
955
|
-
`Link ${context.cwd} to which project?`,
|
|
956
|
-
options
|
|
957
|
-
);
|
|
958
|
-
project = projects[index];
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
if (!project)
|
|
962
|
-
throw new NaroError("PROJECT_NOT_FOUND", "No project selected.");
|
|
963
|
-
const owningTeam = teams.find((candidate) => candidate.id === project.teamId) ?? team;
|
|
964
|
-
saveLink(context, project, owningTeam.slug);
|
|
965
|
-
context.output.result(
|
|
966
|
-
{
|
|
967
|
-
projectId: project.id,
|
|
968
|
-
projectName: project.name,
|
|
969
|
-
teamSlug: owningTeam.slug,
|
|
970
|
-
subdomain: project.subdomain
|
|
971
|
-
},
|
|
972
|
-
[`\u2705 Linked to ${owningTeam.slug}/${project.name} (.naro/project.json)`]
|
|
973
|
-
);
|
|
974
|
-
}
|
|
975
|
-
async function runUnlink(context) {
|
|
976
|
-
const existing = readProjectLink(context.cwd);
|
|
977
|
-
const removed = removeProjectLink(context.cwd);
|
|
978
|
-
context.output.result(
|
|
979
|
-
{ removed, ...existing ? { projectId: existing.projectId } : {} },
|
|
980
|
-
[removed ? "\u2705 Unlinked (.naro removed)" : "Nothing to unlink here."]
|
|
981
|
-
);
|
|
982
|
-
}
|
|
983
|
-
|
|
984
1088
|
// src/commands/logs.ts
|
|
985
1089
|
var FOLLOW_INTERVAL_MS = 3e3;
|
|
986
1090
|
function sleep(ms) {
|
|
@@ -1248,6 +1352,9 @@ Usage:
|
|
|
1248
1352
|
naro deployments List deployments of the linked project
|
|
1249
1353
|
naro inspect <deployment-id> Show one deployment + build logs
|
|
1250
1354
|
|
|
1355
|
+
naro git connect [owner/repo] Deploy on every push (defaults to your origin remote)
|
|
1356
|
+
naro git disconnect Stop deploying on push
|
|
1357
|
+
|
|
1251
1358
|
naro logs [deployment-id] Runtime logs (or build logs of a deployment)
|
|
1252
1359
|
[--type build|runtime] [--env production|preview] [--level error] [--follow]
|
|
1253
1360
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "naro-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"naro": "./dist/index.js"
|
|
@@ -18,26 +18,25 @@
|
|
|
18
18
|
"default": "./dist/deploy.js"
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
|
-
"scripts": {
|
|
22
|
-
"build": "tsup && pnpm build:types",
|
|
23
|
-
"build:types": "tsc --project tsconfig.build.json",
|
|
24
|
-
"test": "vitest run",
|
|
25
|
-
"typecheck": "tsc --noEmit",
|
|
26
|
-
"prepublishOnly": "pnpm build && node ../../scripts/check-publish.mjs naro-cli"
|
|
27
|
-
},
|
|
28
21
|
"dependencies": {
|
|
29
22
|
"@opennextjs/cloudflare": "1.20.2",
|
|
30
23
|
"@supabase/supabase-js": "2.112.3",
|
|
31
24
|
"wrangler": "4.124.0"
|
|
32
25
|
},
|
|
33
26
|
"devDependencies": {
|
|
34
|
-
"@next-deploy/build": "workspace:*",
|
|
35
|
-
"@next-deploy/core": "workspace:*",
|
|
36
|
-
"@next-deploy/config": "workspace:*",
|
|
37
27
|
"tsup": "8.5.0",
|
|
38
28
|
"tsx": "4.23.12",
|
|
39
29
|
"typescript": "7.0.2",
|
|
40
30
|
"vitest": "3.2.7",
|
|
41
|
-
"@next-deploy/
|
|
31
|
+
"@next-deploy/core": "0.1.0",
|
|
32
|
+
"@next-deploy/build": "0.1.0",
|
|
33
|
+
"@next-deploy/config": "0.1.0",
|
|
34
|
+
"@next-deploy/sdk": "0.1.0"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsup && pnpm build:types",
|
|
38
|
+
"build:types": "tsc --project tsconfig.build.json",
|
|
39
|
+
"test": "vitest run",
|
|
40
|
+
"typecheck": "tsc --noEmit"
|
|
42
41
|
}
|
|
43
|
-
}
|
|
42
|
+
}
|