@insforge/cli 0.1.49 → 0.1.51-dev.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/index.js +492 -137
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -2,10 +2,169 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { readFileSync as readFileSync7 } from "fs";
|
|
5
|
-
import { join as
|
|
5
|
+
import { join as join10, dirname } from "path";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
7
|
import { Command } from "commander";
|
|
8
|
-
import * as
|
|
8
|
+
import * as clack11 from "@clack/prompts";
|
|
9
|
+
|
|
10
|
+
// src/lib/prompts.ts
|
|
11
|
+
import * as readline from "readline";
|
|
12
|
+
import * as clack from "@clack/prompts";
|
|
13
|
+
var isInteractive = !!(process.stdin.isTTY && process.stdout.isTTY);
|
|
14
|
+
var LineReader = class {
|
|
15
|
+
constructor(input, output) {
|
|
16
|
+
this.output = output;
|
|
17
|
+
this.rl = readline.createInterface({ input });
|
|
18
|
+
this.rl.on("line", (line) => {
|
|
19
|
+
if (this.waiter) {
|
|
20
|
+
const w = this.waiter;
|
|
21
|
+
this.waiter = null;
|
|
22
|
+
w(line);
|
|
23
|
+
} else {
|
|
24
|
+
this.queue.push(line);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
this.rl.on("close", () => {
|
|
28
|
+
this.closed = true;
|
|
29
|
+
if (this.waiter) {
|
|
30
|
+
const w = this.waiter;
|
|
31
|
+
this.waiter = null;
|
|
32
|
+
w(null);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
queue = [];
|
|
37
|
+
waiter = null;
|
|
38
|
+
closed = false;
|
|
39
|
+
rl;
|
|
40
|
+
async readLine(prompt) {
|
|
41
|
+
this.output.write(prompt);
|
|
42
|
+
if (this.queue.length > 0) return this.queue.shift();
|
|
43
|
+
if (this.closed) return null;
|
|
44
|
+
return new Promise((resolve4) => {
|
|
45
|
+
this.waiter = resolve4;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
close() {
|
|
49
|
+
this.rl.close();
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
var sharedReader = null;
|
|
53
|
+
function getReader() {
|
|
54
|
+
if (!sharedReader) {
|
|
55
|
+
sharedReader = new LineReader(process.stdin, process.stdout);
|
|
56
|
+
}
|
|
57
|
+
return sharedReader;
|
|
58
|
+
}
|
|
59
|
+
var CANCEL = /* @__PURE__ */ Symbol("prompt.cancel");
|
|
60
|
+
function isCancel2(v) {
|
|
61
|
+
return v === CANCEL || clack.isCancel(v);
|
|
62
|
+
}
|
|
63
|
+
async function text2(opts) {
|
|
64
|
+
if (isInteractive) {
|
|
65
|
+
const result = await clack.text({
|
|
66
|
+
message: opts.message,
|
|
67
|
+
initialValue: opts.initialValue,
|
|
68
|
+
placeholder: opts.placeholder,
|
|
69
|
+
validate: opts.validate
|
|
70
|
+
});
|
|
71
|
+
if (clack.isCancel(result)) return CANCEL;
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
return nonTtyText(opts);
|
|
75
|
+
}
|
|
76
|
+
async function select2(opts) {
|
|
77
|
+
if (isInteractive) {
|
|
78
|
+
const result = await clack.select({
|
|
79
|
+
message: opts.message,
|
|
80
|
+
options: opts.options,
|
|
81
|
+
initialValue: opts.initialValue
|
|
82
|
+
});
|
|
83
|
+
if (clack.isCancel(result)) return CANCEL;
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
return nonTtySelect(opts);
|
|
87
|
+
}
|
|
88
|
+
async function confirm2(opts) {
|
|
89
|
+
if (isInteractive) {
|
|
90
|
+
const result = await clack.confirm({
|
|
91
|
+
message: opts.message,
|
|
92
|
+
initialValue: opts.initialValue
|
|
93
|
+
});
|
|
94
|
+
if (clack.isCancel(result)) return CANCEL;
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
return nonTtyConfirm(opts);
|
|
98
|
+
}
|
|
99
|
+
async function password2(opts) {
|
|
100
|
+
if (isInteractive) {
|
|
101
|
+
const result = await clack.password({ message: opts.message });
|
|
102
|
+
if (clack.isCancel(result)) return CANCEL;
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
return nonTtyText({ message: opts.message, trim: false });
|
|
106
|
+
}
|
|
107
|
+
async function nonTtyText(opts, io = {}) {
|
|
108
|
+
const reader = io.reader ?? getReader();
|
|
109
|
+
const stderr = io.stderr ?? process.stderr;
|
|
110
|
+
const shouldTrim = opts.trim ?? true;
|
|
111
|
+
const defaultHint = opts.initialValue ? ` [${opts.initialValue}]` : "";
|
|
112
|
+
for (; ; ) {
|
|
113
|
+
const raw = await reader.readLine(`? ${opts.message}${defaultHint} `);
|
|
114
|
+
if (raw === null) return CANCEL;
|
|
115
|
+
const normalized = shouldTrim ? raw.trim() : raw;
|
|
116
|
+
const value = normalized === "" ? opts.initialValue ?? "" : normalized;
|
|
117
|
+
if (opts.validate) {
|
|
118
|
+
const err = opts.validate(value);
|
|
119
|
+
if (err) {
|
|
120
|
+
stderr.write(` ${err}
|
|
121
|
+
`);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return value;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async function nonTtySelect(opts, io = {}) {
|
|
129
|
+
if (opts.options.length === 0) {
|
|
130
|
+
throw new Error(`No options available for prompt "${opts.message}".`);
|
|
131
|
+
}
|
|
132
|
+
const reader = io.reader ?? getReader();
|
|
133
|
+
const stdout = io.stdout ?? process.stdout;
|
|
134
|
+
const stderr = io.stderr ?? process.stderr;
|
|
135
|
+
stdout.write(`? ${opts.message}
|
|
136
|
+
`);
|
|
137
|
+
opts.options.forEach((o, i) => {
|
|
138
|
+
const hint = o.hint ? ` \u2014 ${o.hint}` : "";
|
|
139
|
+
stdout.write(` ${i + 1}) ${o.label}${hint}
|
|
140
|
+
`);
|
|
141
|
+
});
|
|
142
|
+
for (; ; ) {
|
|
143
|
+
const raw = await reader.readLine(`Enter number [1-${opts.options.length}]: `);
|
|
144
|
+
if (raw === null) return CANCEL;
|
|
145
|
+
const n = Number.parseInt(raw.trim(), 10);
|
|
146
|
+
if (Number.isInteger(n) && n >= 1 && n <= opts.options.length) {
|
|
147
|
+
return opts.options[n - 1].value;
|
|
148
|
+
}
|
|
149
|
+
stderr.write(` Please enter a number between 1 and ${opts.options.length}.
|
|
150
|
+
`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async function nonTtyConfirm(opts, io = {}) {
|
|
154
|
+
const reader = io.reader ?? getReader();
|
|
155
|
+
const stderr = io.stderr ?? process.stderr;
|
|
156
|
+
const defaultHint = opts.initialValue === true ? " [Y/n]" : opts.initialValue === false ? " [y/N]" : " [y/n]";
|
|
157
|
+
for (; ; ) {
|
|
158
|
+
const raw = await reader.readLine(`? ${opts.message}${defaultHint} `);
|
|
159
|
+
if (raw === null) return CANCEL;
|
|
160
|
+
const answer = raw.trim().toLowerCase();
|
|
161
|
+
if (answer === "" && opts.initialValue !== void 0) return opts.initialValue;
|
|
162
|
+
if (answer === "y" || answer === "yes") return true;
|
|
163
|
+
if (answer === "n" || answer === "no") return false;
|
|
164
|
+
stderr.write(` Please answer y or n.
|
|
165
|
+
`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
9
168
|
|
|
10
169
|
// src/lib/config.ts
|
|
11
170
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from "fs";
|
|
@@ -87,7 +246,7 @@ function getAccessToken() {
|
|
|
87
246
|
}
|
|
88
247
|
|
|
89
248
|
// src/commands/login.ts
|
|
90
|
-
import * as
|
|
249
|
+
import * as clack4 from "@clack/prompts";
|
|
91
250
|
|
|
92
251
|
// src/lib/errors.ts
|
|
93
252
|
var CLIError = class extends Error {
|
|
@@ -179,7 +338,8 @@ function getRootOpts(cmd) {
|
|
|
179
338
|
import { createServer } from "http";
|
|
180
339
|
import { randomBytes, createHash } from "crypto";
|
|
181
340
|
import { URL as URL2 } from "url";
|
|
182
|
-
import * as
|
|
341
|
+
import * as clack2 from "@clack/prompts";
|
|
342
|
+
import pc from "picocolors";
|
|
183
343
|
var DEFAULT_CLIENT_ID = "clf_NK8cMUs41gm8ZcfdtSguVw";
|
|
184
344
|
var OAUTH_SCOPES = "user:read organizations:read projects:read projects:write";
|
|
185
345
|
function generatePKCE() {
|
|
@@ -315,25 +475,35 @@ async function performOAuthLogin(apiUrl) {
|
|
|
315
475
|
state,
|
|
316
476
|
scopes: OAUTH_SCOPES
|
|
317
477
|
});
|
|
318
|
-
|
|
319
|
-
|
|
478
|
+
if (isInteractive) {
|
|
479
|
+
clack2.log.info("Opening browser for authentication...");
|
|
480
|
+
clack2.log.info(`If browser doesn't open, visit:
|
|
320
481
|
${authUrl}`);
|
|
482
|
+
} else {
|
|
483
|
+
process.stderr.write(`
|
|
484
|
+
To sign in, open this URL in your browser:
|
|
485
|
+
|
|
486
|
+
${pc.cyan(pc.underline(authUrl))}
|
|
487
|
+
|
|
488
|
+
`);
|
|
489
|
+
}
|
|
321
490
|
try {
|
|
322
491
|
const open = (await import("open")).default;
|
|
323
492
|
await open(authUrl);
|
|
324
493
|
} catch {
|
|
325
|
-
|
|
494
|
+
if (isInteractive) clack2.log.warn("Could not open browser. Please visit the URL above.");
|
|
326
495
|
}
|
|
327
|
-
const s =
|
|
328
|
-
s
|
|
496
|
+
const s = isInteractive ? clack2.spinner() : null;
|
|
497
|
+
s?.start("Waiting for authentication...");
|
|
498
|
+
if (!isInteractive) process.stderr.write("Waiting for authentication...\n");
|
|
329
499
|
try {
|
|
330
500
|
const callbackResult = await result;
|
|
331
501
|
close();
|
|
332
502
|
if (callbackResult.state !== state) {
|
|
333
|
-
s
|
|
503
|
+
s?.stop("Authentication failed");
|
|
334
504
|
throw new Error("State mismatch. Possible CSRF attack.");
|
|
335
505
|
}
|
|
336
|
-
s
|
|
506
|
+
s?.message("Exchanging authorization code...");
|
|
337
507
|
const tokens = await exchangeCodeForTokens({
|
|
338
508
|
platformUrl,
|
|
339
509
|
code: callbackResult.code,
|
|
@@ -351,20 +521,24 @@ ${authUrl}`);
|
|
|
351
521
|
const profile = await getProfile(apiUrl);
|
|
352
522
|
creds.user = profile;
|
|
353
523
|
saveCredentials(creds);
|
|
354
|
-
s
|
|
524
|
+
s?.stop(`Authenticated as ${profile.email}`);
|
|
525
|
+
if (!isInteractive) process.stderr.write(`Authenticated as ${profile.email}
|
|
526
|
+
`);
|
|
355
527
|
} catch {
|
|
356
|
-
s
|
|
528
|
+
s?.stop("Authenticated successfully");
|
|
529
|
+
if (!isInteractive) process.stderr.write("Authenticated successfully\n");
|
|
357
530
|
}
|
|
358
531
|
return creds;
|
|
359
532
|
} catch (err) {
|
|
360
533
|
close();
|
|
361
|
-
s
|
|
534
|
+
s?.stop("Authentication failed");
|
|
535
|
+
if (!isInteractive) process.stderr.write("Authentication failed\n");
|
|
362
536
|
throw err;
|
|
363
537
|
}
|
|
364
538
|
}
|
|
365
539
|
|
|
366
540
|
// src/lib/credentials.ts
|
|
367
|
-
import * as
|
|
541
|
+
import * as clack3 from "@clack/prompts";
|
|
368
542
|
async function requireAuth(apiUrl, allowOssBypass = true) {
|
|
369
543
|
const projConfig = getProjectConfig();
|
|
370
544
|
if (allowOssBypass && projConfig?.project_id === FAKE_PROJECT_ID) {
|
|
@@ -382,16 +556,16 @@ async function requireAuth(apiUrl, allowOssBypass = true) {
|
|
|
382
556
|
}
|
|
383
557
|
const creds = getCredentials();
|
|
384
558
|
if (creds && creds.access_token) return creds;
|
|
385
|
-
|
|
559
|
+
clack3.log.info("You need to log in to continue.");
|
|
386
560
|
for (; ; ) {
|
|
387
561
|
try {
|
|
388
562
|
return await performOAuthLogin(apiUrl);
|
|
389
563
|
} catch (err) {
|
|
390
564
|
if (!process.stdout.isTTY) throw err;
|
|
391
565
|
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
392
|
-
|
|
393
|
-
const retry = await
|
|
394
|
-
if (
|
|
566
|
+
clack3.log.error(`Login failed: ${msg}`);
|
|
567
|
+
const retry = await confirm2({ message: "Would you like to try again?" });
|
|
568
|
+
if (isCancel2(retry) || !retry) {
|
|
395
569
|
throw new AuthError("Authentication required. Run `npx @insforge/cli login` to authenticate.");
|
|
396
570
|
}
|
|
397
571
|
}
|
|
@@ -421,7 +595,7 @@ async function refreshAccessToken(apiUrl) {
|
|
|
421
595
|
return data.access_token;
|
|
422
596
|
} catch {
|
|
423
597
|
if (process.stdout.isTTY) {
|
|
424
|
-
|
|
598
|
+
clack3.log.warn("Session expired. Please log in again.");
|
|
425
599
|
const newCreds = await performOAuthLogin(apiUrl);
|
|
426
600
|
return newCreds.access_token;
|
|
427
601
|
}
|
|
@@ -477,12 +651,12 @@ async function platformFetch(path5, options = {}, apiUrl) {
|
|
|
477
651
|
}
|
|
478
652
|
return res;
|
|
479
653
|
}
|
|
480
|
-
async function login(email,
|
|
654
|
+
async function login(email, password3, apiUrl) {
|
|
481
655
|
const baseUrl = getPlatformApiUrl(apiUrl);
|
|
482
656
|
const res = await fetch(`${baseUrl}/auth/v1/login`, {
|
|
483
657
|
method: "POST",
|
|
484
658
|
headers: { "Content-Type": "application/json" },
|
|
485
|
-
body: JSON.stringify({ email, password:
|
|
659
|
+
body: JSON.stringify({ email, password: password3 })
|
|
486
660
|
});
|
|
487
661
|
if (!res.ok) {
|
|
488
662
|
const err = await res.json().catch(() => ({}));
|
|
@@ -618,30 +792,30 @@ function registerLoginCommand(program2) {
|
|
|
618
792
|
}
|
|
619
793
|
async function loginWithEmail(json, apiUrl) {
|
|
620
794
|
if (!json) {
|
|
621
|
-
|
|
795
|
+
clack4.intro("InsForge CLI");
|
|
622
796
|
}
|
|
623
|
-
const email = json ? process.env.INSFORGE_EMAIL : await
|
|
797
|
+
const email = json ? process.env.INSFORGE_EMAIL : await text2({
|
|
624
798
|
message: "Email:",
|
|
625
799
|
validate: (v) => v.includes("@") ? void 0 : "Please enter a valid email"
|
|
626
800
|
});
|
|
627
|
-
if (
|
|
628
|
-
|
|
801
|
+
if (isCancel2(email)) {
|
|
802
|
+
clack4.cancel("Login cancelled.");
|
|
629
803
|
throw new Error("cancelled");
|
|
630
804
|
}
|
|
631
|
-
const
|
|
805
|
+
const password3 = json ? process.env.INSFORGE_PASSWORD : await password2({
|
|
632
806
|
message: "Password:"
|
|
633
807
|
});
|
|
634
|
-
if (
|
|
635
|
-
|
|
808
|
+
if (isCancel2(password3)) {
|
|
809
|
+
clack4.cancel("Login cancelled.");
|
|
636
810
|
throw new Error("cancelled");
|
|
637
811
|
}
|
|
638
|
-
if (!email || !
|
|
812
|
+
if (!email || !password3) {
|
|
639
813
|
throw new Error("Email and password are required. Set INSFORGE_EMAIL and INSFORGE_PASSWORD environment variables for non-interactive mode.");
|
|
640
814
|
}
|
|
641
815
|
if (!json) {
|
|
642
|
-
const s =
|
|
816
|
+
const s = clack4.spinner();
|
|
643
817
|
s.start("Authenticating...");
|
|
644
|
-
const result = await login(email,
|
|
818
|
+
const result = await login(email, password3, apiUrl);
|
|
645
819
|
const creds = {
|
|
646
820
|
access_token: result.token,
|
|
647
821
|
refresh_token: result._refreshToken ?? "",
|
|
@@ -649,9 +823,9 @@ async function loginWithEmail(json, apiUrl) {
|
|
|
649
823
|
};
|
|
650
824
|
saveCredentials(creds);
|
|
651
825
|
s.stop(`Authenticated as ${result.user.email}`);
|
|
652
|
-
|
|
826
|
+
clack4.outro("Done");
|
|
653
827
|
} else {
|
|
654
|
-
const result = await login(email,
|
|
828
|
+
const result = await login(email, password3, apiUrl);
|
|
655
829
|
const creds = {
|
|
656
830
|
access_token: result.token,
|
|
657
831
|
refresh_token: result._refreshToken ?? "",
|
|
@@ -663,11 +837,11 @@ async function loginWithEmail(json, apiUrl) {
|
|
|
663
837
|
}
|
|
664
838
|
async function loginWithOAuth(json, apiUrl) {
|
|
665
839
|
if (!json) {
|
|
666
|
-
|
|
840
|
+
clack4.intro("InsForge CLI");
|
|
667
841
|
}
|
|
668
842
|
const creds = await performOAuthLogin(apiUrl);
|
|
669
843
|
if (!json) {
|
|
670
|
-
|
|
844
|
+
clack4.outro("Done");
|
|
671
845
|
} else {
|
|
672
846
|
console.log(JSON.stringify({ success: true, user: creds.user }));
|
|
673
847
|
}
|
|
@@ -762,7 +936,6 @@ function registerOrgsCommands(orgsCmd2) {
|
|
|
762
936
|
}
|
|
763
937
|
|
|
764
938
|
// src/commands/projects/list.ts
|
|
765
|
-
import * as clack4 from "@clack/prompts";
|
|
766
939
|
function registerProjectsCommands(projectsCmd2) {
|
|
767
940
|
projectsCmd2.command("list").description("List all projects in an organization").option("--org-id <id>", "Organization ID (uses default if not specified)").action(async (opts, cmd) => {
|
|
768
941
|
const { json, apiUrl } = getRootOpts(cmd);
|
|
@@ -777,14 +950,14 @@ function registerProjectsCommands(projectsCmd2) {
|
|
|
777
950
|
if (orgs.length === 1) {
|
|
778
951
|
orgId = orgs[0].id;
|
|
779
952
|
} else if (!json) {
|
|
780
|
-
const selected = await
|
|
953
|
+
const selected = await select2({
|
|
781
954
|
message: "Select an organization:",
|
|
782
955
|
options: orgs.map((o) => ({
|
|
783
956
|
value: o.id,
|
|
784
957
|
label: o.name
|
|
785
958
|
}))
|
|
786
959
|
});
|
|
787
|
-
if (
|
|
960
|
+
if (isCancel2(selected)) {
|
|
788
961
|
process.exit(0);
|
|
789
962
|
}
|
|
790
963
|
orgId = selected;
|
|
@@ -817,7 +990,7 @@ import { promisify as promisify3 } from "util";
|
|
|
817
990
|
import * as fs4 from "fs/promises";
|
|
818
991
|
import * as path4 from "path";
|
|
819
992
|
import * as clack8 from "@clack/prompts";
|
|
820
|
-
import
|
|
993
|
+
import pc2 from "picocolors";
|
|
821
994
|
|
|
822
995
|
// src/lib/skills.ts
|
|
823
996
|
import { exec } from "child_process";
|
|
@@ -950,7 +1123,7 @@ async function reportCliUsage(toolName, success, maxRetries = 1, explicitConfig)
|
|
|
950
1123
|
|
|
951
1124
|
// src/lib/analytics.ts
|
|
952
1125
|
import { PostHog } from "posthog-node";
|
|
953
|
-
var POSTHOG_API_KEY = "
|
|
1126
|
+
var POSTHOG_API_KEY = "";
|
|
954
1127
|
var POSTHOG_HOST = process.env.POSTHOG_HOST || "https://us.i.posthog.com";
|
|
955
1128
|
var client = null;
|
|
956
1129
|
function getClient() {
|
|
@@ -1074,10 +1247,13 @@ async function readEnvFile(cwd) {
|
|
|
1074
1247
|
// src/commands/deployments/deploy.ts
|
|
1075
1248
|
import * as path2 from "path";
|
|
1076
1249
|
import * as fs2 from "fs/promises";
|
|
1250
|
+
import { createReadStream } from "fs";
|
|
1251
|
+
import { createHash as createHash2 } from "crypto";
|
|
1077
1252
|
import * as clack6 from "@clack/prompts";
|
|
1078
1253
|
import archiver from "archiver";
|
|
1079
1254
|
var POLL_INTERVAL_MS = 5e3;
|
|
1080
1255
|
var POLL_TIMEOUT_MS = 3e5;
|
|
1256
|
+
var DIRECT_UPLOAD_CONCURRENCY = 8;
|
|
1081
1257
|
var EXCLUDE_PATTERNS = [
|
|
1082
1258
|
"node_modules",
|
|
1083
1259
|
".git",
|
|
@@ -1105,6 +1281,12 @@ var EXCLUDE_PATTERNS = [
|
|
|
1105
1281
|
"skills",
|
|
1106
1282
|
"coverage"
|
|
1107
1283
|
];
|
|
1284
|
+
var DirectDeploymentUnsupportedError = class extends Error {
|
|
1285
|
+
constructor() {
|
|
1286
|
+
super("Direct deployment endpoints are not available on this backend");
|
|
1287
|
+
this.name = "DirectDeploymentUnsupportedError";
|
|
1288
|
+
}
|
|
1289
|
+
};
|
|
1108
1290
|
function shouldExclude(name) {
|
|
1109
1291
|
const normalized = name.replace(/\\/g, "/");
|
|
1110
1292
|
for (const pattern of EXCLUDE_PATTERNS) {
|
|
@@ -1115,6 +1297,56 @@ function shouldExclude(name) {
|
|
|
1115
1297
|
if (normalized.endsWith(".log")) return true;
|
|
1116
1298
|
return false;
|
|
1117
1299
|
}
|
|
1300
|
+
function isInsforgeCloudOssHost(ossHost) {
|
|
1301
|
+
try {
|
|
1302
|
+
return new URL(ossHost).hostname.endsWith(".insforge.app");
|
|
1303
|
+
} catch {
|
|
1304
|
+
return false;
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
function normalizeRelativePath(sourceDir, absolutePath) {
|
|
1308
|
+
return path2.relative(sourceDir, absolutePath).split(path2.sep).join("/").replace(/\\/g, "/");
|
|
1309
|
+
}
|
|
1310
|
+
async function hashFile(filePath) {
|
|
1311
|
+
const hash = createHash2("sha1");
|
|
1312
|
+
let size = 0;
|
|
1313
|
+
for await (const chunk of createReadStream(filePath)) {
|
|
1314
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
1315
|
+
size += buffer.length;
|
|
1316
|
+
hash.update(buffer);
|
|
1317
|
+
}
|
|
1318
|
+
return { sha: hash.digest("hex"), size };
|
|
1319
|
+
}
|
|
1320
|
+
async function collectDeploymentFiles(sourceDir) {
|
|
1321
|
+
const files = [];
|
|
1322
|
+
async function walk(currentDir) {
|
|
1323
|
+
const entries = await fs2.readdir(currentDir, { withFileTypes: true });
|
|
1324
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
1325
|
+
for (const entry of entries) {
|
|
1326
|
+
const absolutePath = path2.join(currentDir, entry.name);
|
|
1327
|
+
const normalizedPath = normalizeRelativePath(sourceDir, absolutePath);
|
|
1328
|
+
if (!normalizedPath || shouldExclude(normalizedPath)) {
|
|
1329
|
+
continue;
|
|
1330
|
+
}
|
|
1331
|
+
if (entry.isDirectory()) {
|
|
1332
|
+
await walk(absolutePath);
|
|
1333
|
+
continue;
|
|
1334
|
+
}
|
|
1335
|
+
if (!entry.isFile()) {
|
|
1336
|
+
continue;
|
|
1337
|
+
}
|
|
1338
|
+
const { sha, size } = await hashFile(absolutePath);
|
|
1339
|
+
files.push({
|
|
1340
|
+
absolutePath,
|
|
1341
|
+
path: normalizedPath,
|
|
1342
|
+
sha,
|
|
1343
|
+
size
|
|
1344
|
+
});
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
await walk(sourceDir);
|
|
1348
|
+
return files;
|
|
1349
|
+
}
|
|
1118
1350
|
async function createZipBuffer(sourceDir) {
|
|
1119
1351
|
return new Promise((resolve4, reject) => {
|
|
1120
1352
|
const archive = archiver("zip", { zlib: { level: 9 } });
|
|
@@ -1129,40 +1361,84 @@ async function createZipBuffer(sourceDir) {
|
|
|
1129
1361
|
void archive.finalize();
|
|
1130
1362
|
});
|
|
1131
1363
|
}
|
|
1132
|
-
async function
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
const formData = new FormData();
|
|
1141
|
-
for (const [key, value] of Object.entries(uploadFields)) {
|
|
1142
|
-
formData.append(key, value);
|
|
1364
|
+
async function runWithConcurrency(items, concurrency, worker) {
|
|
1365
|
+
let nextIndex = 0;
|
|
1366
|
+
async function runWorker() {
|
|
1367
|
+
while (nextIndex < items.length) {
|
|
1368
|
+
const index = nextIndex;
|
|
1369
|
+
nextIndex += 1;
|
|
1370
|
+
await worker(items[index], index);
|
|
1371
|
+
}
|
|
1143
1372
|
}
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1373
|
+
const workerCount = Math.min(concurrency, items.length);
|
|
1374
|
+
await Promise.all(Array.from({ length: workerCount }, () => runWorker()));
|
|
1375
|
+
}
|
|
1376
|
+
async function createDirectDeploymentSession(config, files) {
|
|
1377
|
+
const url = `${config.oss_host}/api/deployments/direct`;
|
|
1378
|
+
let response;
|
|
1379
|
+
try {
|
|
1380
|
+
response = await fetch(url, {
|
|
1381
|
+
method: "POST",
|
|
1382
|
+
headers: {
|
|
1383
|
+
"Content-Type": "application/json",
|
|
1384
|
+
Authorization: `Bearer ${config.api_key}`
|
|
1385
|
+
},
|
|
1386
|
+
body: JSON.stringify({ files })
|
|
1387
|
+
});
|
|
1388
|
+
} catch (error) {
|
|
1389
|
+
throw new CLIError(formatFetchError(error, url));
|
|
1153
1390
|
}
|
|
1154
|
-
|
|
1155
|
-
|
|
1391
|
+
if (response.status === 404) {
|
|
1392
|
+
throw new DirectDeploymentUnsupportedError();
|
|
1393
|
+
}
|
|
1394
|
+
if (!response.ok) {
|
|
1395
|
+
const err = await response.json().catch(() => ({}));
|
|
1396
|
+
let message = err.message ?? err.error ?? `OSS request failed: ${response.status}`;
|
|
1397
|
+
if (err.nextActions) {
|
|
1398
|
+
message += `
|
|
1399
|
+
${err.nextActions}`;
|
|
1400
|
+
}
|
|
1401
|
+
throw new CLIError(message);
|
|
1402
|
+
}
|
|
1403
|
+
const payload = await response.json();
|
|
1404
|
+
if (!payload.id || !Array.isArray(payload.files)) {
|
|
1405
|
+
throw new CLIError("Unexpected response from direct deployment create endpoint.");
|
|
1406
|
+
}
|
|
1407
|
+
return payload;
|
|
1408
|
+
}
|
|
1409
|
+
async function uploadDirectDeploymentFile(deploymentId, manifestFile, localFile) {
|
|
1410
|
+
const requestInit = {
|
|
1411
|
+
method: "PUT",
|
|
1412
|
+
headers: {
|
|
1413
|
+
"Content-Type": "application/octet-stream",
|
|
1414
|
+
"Content-Length": String(localFile.size)
|
|
1415
|
+
},
|
|
1416
|
+
body: createReadStream(localFile.absolutePath),
|
|
1417
|
+
duplex: "half"
|
|
1418
|
+
};
|
|
1419
|
+
await ossFetch(
|
|
1420
|
+
`/api/deployments/${encodeURIComponent(deploymentId)}/files/${encodeURIComponent(manifestFile.fileId)}/content`,
|
|
1421
|
+
requestInit
|
|
1422
|
+
);
|
|
1423
|
+
}
|
|
1424
|
+
async function startDirectDeployment(deploymentId, startBody) {
|
|
1425
|
+
const response = await ossFetch(`/api/deployments/${encodeURIComponent(deploymentId)}/start`, {
|
|
1156
1426
|
method: "POST",
|
|
1427
|
+
headers: { "Content-Type": "application/json" },
|
|
1157
1428
|
body: JSON.stringify(startBody)
|
|
1158
1429
|
});
|
|
1159
|
-
await
|
|
1160
|
-
|
|
1430
|
+
await response.json();
|
|
1431
|
+
}
|
|
1432
|
+
async function pollDeployment(deploymentId, spinner7, syncBeforeRead) {
|
|
1433
|
+
spinner7?.message("Building and deploying...");
|
|
1161
1434
|
const startTime = Date.now();
|
|
1162
1435
|
let deployment = null;
|
|
1163
1436
|
while (Date.now() - startTime < POLL_TIMEOUT_MS) {
|
|
1164
|
-
await new Promise((
|
|
1437
|
+
await new Promise((resolve4) => setTimeout(resolve4, POLL_INTERVAL_MS));
|
|
1165
1438
|
try {
|
|
1439
|
+
if (syncBeforeRead) {
|
|
1440
|
+
await ossFetch(`/api/deployments/${deploymentId}/sync`, { method: "POST" });
|
|
1441
|
+
}
|
|
1166
1442
|
const statusRes = await ossFetch(`/api/deployments/${deploymentId}`);
|
|
1167
1443
|
deployment = await statusRes.json();
|
|
1168
1444
|
const status = deployment.status.toUpperCase();
|
|
@@ -1170,11 +1446,13 @@ async function deployProject(opts) {
|
|
|
1170
1446
|
break;
|
|
1171
1447
|
}
|
|
1172
1448
|
if (status === "ERROR" || status === "CANCELED") {
|
|
1173
|
-
|
|
1174
|
-
throw new CLIError(
|
|
1449
|
+
spinner7?.stop("Deployment failed");
|
|
1450
|
+
throw new CLIError(
|
|
1451
|
+
getDeploymentError(deployment.metadata) ?? `Deployment failed with status: ${deployment.status}`
|
|
1452
|
+
);
|
|
1175
1453
|
}
|
|
1176
1454
|
const elapsed = Math.round((Date.now() - startTime) / 1e3);
|
|
1177
|
-
|
|
1455
|
+
spinner7?.message(`Building and deploying... (${elapsed}s, status: ${deployment.status})`);
|
|
1178
1456
|
} catch (err) {
|
|
1179
1457
|
if (err instanceof CLIError) throw err;
|
|
1180
1458
|
}
|
|
@@ -1183,8 +1461,78 @@ async function deployProject(opts) {
|
|
|
1183
1461
|
const liveUrl = isReady ? deployment?.url ?? null : null;
|
|
1184
1462
|
return { deploymentId, deployment, isReady, liveUrl };
|
|
1185
1463
|
}
|
|
1464
|
+
async function deployProjectDirect(opts, config) {
|
|
1465
|
+
const { sourceDir, startBody = {}, spinner: spinner7 } = opts;
|
|
1466
|
+
spinner7?.start("Scanning source files...");
|
|
1467
|
+
const localFiles = await collectDeploymentFiles(sourceDir);
|
|
1468
|
+
if (localFiles.length === 0) {
|
|
1469
|
+
throw new CLIError("No deployable files found in the source directory.");
|
|
1470
|
+
}
|
|
1471
|
+
spinner7?.message("Creating deployment...");
|
|
1472
|
+
const createResult = await createDirectDeploymentSession(
|
|
1473
|
+
config,
|
|
1474
|
+
localFiles.map(({ path: relativePath, sha, size }) => ({ path: relativePath, sha, size }))
|
|
1475
|
+
);
|
|
1476
|
+
const localFileByPath = new Map(localFiles.map((file) => [file.path, file]));
|
|
1477
|
+
const pendingFiles = createResult.files.filter((file) => !file.uploadedAt);
|
|
1478
|
+
spinner7?.message(`Uploading ${pendingFiles.length} file${pendingFiles.length === 1 ? "" : "s"}...`);
|
|
1479
|
+
await runWithConcurrency(pendingFiles, DIRECT_UPLOAD_CONCURRENCY, async (manifestFile) => {
|
|
1480
|
+
const localFile = localFileByPath.get(manifestFile.path);
|
|
1481
|
+
if (!localFile) {
|
|
1482
|
+
throw new CLIError(`Backend returned an unknown file path: ${manifestFile.path}`);
|
|
1483
|
+
}
|
|
1484
|
+
if (localFile.sha !== manifestFile.sha || localFile.size !== manifestFile.size) {
|
|
1485
|
+
throw new CLIError(`Backend file metadata mismatch for: ${manifestFile.path}`);
|
|
1486
|
+
}
|
|
1487
|
+
await uploadDirectDeploymentFile(createResult.id, manifestFile, localFile);
|
|
1488
|
+
});
|
|
1489
|
+
spinner7?.message("Starting deployment...");
|
|
1490
|
+
await startDirectDeployment(createResult.id, startBody);
|
|
1491
|
+
return await pollDeployment(createResult.id, spinner7, !isInsforgeCloudOssHost(config.oss_host));
|
|
1492
|
+
}
|
|
1493
|
+
async function deployProjectLegacy(opts) {
|
|
1494
|
+
const { sourceDir, startBody = {}, spinner: spinner7 } = opts;
|
|
1495
|
+
spinner7?.message("Creating deployment...");
|
|
1496
|
+
const createRes = await ossFetch("/api/deployments", { method: "POST" });
|
|
1497
|
+
const { id: deploymentId, uploadUrl, uploadFields } = await createRes.json();
|
|
1498
|
+
spinner7?.message("Compressing source files...");
|
|
1499
|
+
const zipBuffer = await createZipBuffer(sourceDir);
|
|
1500
|
+
spinner7?.message("Uploading...");
|
|
1501
|
+
const formData = new FormData();
|
|
1502
|
+
for (const [key, value] of Object.entries(uploadFields)) {
|
|
1503
|
+
formData.append(key, value);
|
|
1504
|
+
}
|
|
1505
|
+
formData.append("file", new Blob([zipBuffer], { type: "application/zip" }), "deployment.zip");
|
|
1506
|
+
const uploadRes = await fetch(uploadUrl, { method: "POST", body: formData });
|
|
1507
|
+
if (!uploadRes.ok) {
|
|
1508
|
+
const uploadErr = await uploadRes.text();
|
|
1509
|
+
throw new CLIError(`Failed to upload: ${uploadErr}`);
|
|
1510
|
+
}
|
|
1511
|
+
spinner7?.message("Starting deployment...");
|
|
1512
|
+
const startRes = await ossFetch(`/api/deployments/${deploymentId}/start`, {
|
|
1513
|
+
method: "POST",
|
|
1514
|
+
body: JSON.stringify(startBody)
|
|
1515
|
+
});
|
|
1516
|
+
await startRes.json();
|
|
1517
|
+
return await pollDeployment(deploymentId, spinner7, false);
|
|
1518
|
+
}
|
|
1519
|
+
async function deployProject(opts) {
|
|
1520
|
+
const config = getProjectConfig();
|
|
1521
|
+
if (!config) {
|
|
1522
|
+
throw new ProjectNotLinkedError();
|
|
1523
|
+
}
|
|
1524
|
+
try {
|
|
1525
|
+
return await deployProjectDirect(opts, config);
|
|
1526
|
+
} catch (error) {
|
|
1527
|
+
if (!(error instanceof DirectDeploymentUnsupportedError)) {
|
|
1528
|
+
throw error;
|
|
1529
|
+
}
|
|
1530
|
+
opts.spinner?.message("Direct deployment is not available on this backend. Falling back to the legacy zip upload flow...");
|
|
1531
|
+
return await deployProjectLegacy(opts);
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1186
1534
|
function registerDeploymentsDeployCommand(deploymentsCmd2) {
|
|
1187
|
-
deploymentsCmd2.command("deploy [directory]").description("Deploy a frontend project to Vercel").option("--env <vars>",
|
|
1535
|
+
deploymentsCmd2.command("deploy [directory]").description("Deploy a frontend project to Vercel").option("--env <vars>", 'Environment variables as JSON (e.g. {"KEY":"value"})').option("--meta <meta>", "Deployment metadata as JSON").action(async (directory, opts, cmd) => {
|
|
1188
1536
|
const { json } = getRootOpts(cmd);
|
|
1189
1537
|
try {
|
|
1190
1538
|
await requireAuth();
|
|
@@ -1197,17 +1545,24 @@ function registerDeploymentsDeployCommand(deploymentsCmd2) {
|
|
|
1197
1545
|
}
|
|
1198
1546
|
const dirName = path2.basename(sourceDir);
|
|
1199
1547
|
if (EXCLUDE_PATTERNS.includes(dirName)) {
|
|
1200
|
-
throw new CLIError(
|
|
1548
|
+
throw new CLIError(
|
|
1549
|
+
`"${dirName}" is an excluded directory and cannot be used as a deploy source. Please specify your project root or output directory instead.`
|
|
1550
|
+
);
|
|
1201
1551
|
}
|
|
1202
|
-
const
|
|
1552
|
+
const spinner7 = !json ? clack6.spinner() : null;
|
|
1203
1553
|
const startBody = {};
|
|
1204
1554
|
if (opts.env) {
|
|
1205
1555
|
try {
|
|
1206
1556
|
const parsed = JSON.parse(opts.env);
|
|
1207
1557
|
if (Array.isArray(parsed)) {
|
|
1208
1558
|
startBody.envVars = parsed;
|
|
1559
|
+
} else if (parsed && typeof parsed === "object") {
|
|
1560
|
+
startBody.envVars = Object.entries(parsed).map(([key, value]) => ({
|
|
1561
|
+
key,
|
|
1562
|
+
value: String(value)
|
|
1563
|
+
}));
|
|
1209
1564
|
} else {
|
|
1210
|
-
|
|
1565
|
+
throw new CLIError("Invalid --env JSON. Expected an object or array.");
|
|
1211
1566
|
}
|
|
1212
1567
|
} catch {
|
|
1213
1568
|
throw new CLIError("Invalid --env JSON.");
|
|
@@ -1220,9 +1575,9 @@ function registerDeploymentsDeployCommand(deploymentsCmd2) {
|
|
|
1220
1575
|
throw new CLIError("Invalid --meta JSON.");
|
|
1221
1576
|
}
|
|
1222
1577
|
}
|
|
1223
|
-
const result = await deployProject({ sourceDir, startBody, spinner:
|
|
1578
|
+
const result = await deployProject({ sourceDir, startBody, spinner: spinner7 });
|
|
1224
1579
|
if (result.isReady) {
|
|
1225
|
-
|
|
1580
|
+
spinner7?.stop("Deployment complete");
|
|
1226
1581
|
if (json) {
|
|
1227
1582
|
outputJson(result.deployment);
|
|
1228
1583
|
} else {
|
|
@@ -1232,9 +1587,13 @@ function registerDeploymentsDeployCommand(deploymentsCmd2) {
|
|
|
1232
1587
|
clack6.log.info(`Deployment ID: ${result.deploymentId}`);
|
|
1233
1588
|
}
|
|
1234
1589
|
} else {
|
|
1235
|
-
|
|
1590
|
+
spinner7?.stop("Deployment is still building");
|
|
1236
1591
|
if (json) {
|
|
1237
|
-
outputJson({
|
|
1592
|
+
outputJson({
|
|
1593
|
+
id: result.deploymentId,
|
|
1594
|
+
status: result.deployment?.status ?? "building",
|
|
1595
|
+
timedOut: true
|
|
1596
|
+
});
|
|
1238
1597
|
} else {
|
|
1239
1598
|
clack6.log.info(`Deployment ID: ${result.deploymentId}`);
|
|
1240
1599
|
clack6.log.warn("Deployment did not finish within 5 minutes.");
|
|
@@ -1383,14 +1742,14 @@ function registerCreateCommand(program2) {
|
|
|
1383
1742
|
if (json) {
|
|
1384
1743
|
throw new CLIError("Multiple organizations found. Specify --org-id.");
|
|
1385
1744
|
}
|
|
1386
|
-
const selected = await
|
|
1745
|
+
const selected = await select2({
|
|
1387
1746
|
message: "Select an organization:",
|
|
1388
1747
|
options: orgs.map((o) => ({
|
|
1389
1748
|
value: o.id,
|
|
1390
1749
|
label: o.name
|
|
1391
1750
|
}))
|
|
1392
1751
|
});
|
|
1393
|
-
if (
|
|
1752
|
+
if (isCancel2(selected)) process.exit(0);
|
|
1394
1753
|
orgId = selected;
|
|
1395
1754
|
}
|
|
1396
1755
|
}
|
|
@@ -1401,12 +1760,12 @@ function registerCreateCommand(program2) {
|
|
|
1401
1760
|
if (!projectName) {
|
|
1402
1761
|
if (json) throw new CLIError("--name is required in JSON mode.");
|
|
1403
1762
|
const defaultName = getDefaultProjectName();
|
|
1404
|
-
const name = await
|
|
1763
|
+
const name = await text2({
|
|
1405
1764
|
message: "Project name:",
|
|
1406
1765
|
...defaultName ? { initialValue: defaultName } : {},
|
|
1407
1766
|
validate: (v) => v.length >= 2 ? void 0 : "Name must be at least 2 characters"
|
|
1408
1767
|
});
|
|
1409
|
-
if (
|
|
1768
|
+
if (isCancel2(name)) process.exit(0);
|
|
1410
1769
|
projectName = name;
|
|
1411
1770
|
}
|
|
1412
1771
|
projectName = path3.basename(projectName).replace(/[^a-zA-Z0-9._-]/g, "-").replace(/\.+/g, ".");
|
|
@@ -1422,21 +1781,21 @@ function registerCreateCommand(program2) {
|
|
|
1422
1781
|
if (json) {
|
|
1423
1782
|
template = "empty";
|
|
1424
1783
|
} else {
|
|
1425
|
-
const approach = await
|
|
1784
|
+
const approach = await select2({
|
|
1426
1785
|
message: "How would you like to start?",
|
|
1427
1786
|
options: [
|
|
1428
1787
|
{ value: "blank", label: "Blank project", hint: "Start from scratch with .env.local ready" },
|
|
1429
1788
|
{ value: "template", label: "Start from a template", hint: "Pre-built starter apps" }
|
|
1430
1789
|
]
|
|
1431
1790
|
});
|
|
1432
|
-
if (
|
|
1791
|
+
if (isCancel2(approach)) process.exit(0);
|
|
1433
1792
|
captureEvent(orgId, "create_approach_selected", {
|
|
1434
1793
|
approach
|
|
1435
1794
|
});
|
|
1436
1795
|
if (approach === "blank") {
|
|
1437
1796
|
template = "empty";
|
|
1438
1797
|
} else {
|
|
1439
|
-
const selected = await
|
|
1798
|
+
const selected = await select2({
|
|
1440
1799
|
message: "Choose a starter template:",
|
|
1441
1800
|
options: [
|
|
1442
1801
|
{ value: "react", label: "Web app template with React" },
|
|
@@ -1447,7 +1806,7 @@ function registerCreateCommand(program2) {
|
|
|
1447
1806
|
{ value: "todo", label: "Todo app with Next.js" }
|
|
1448
1807
|
]
|
|
1449
1808
|
});
|
|
1450
|
-
if (
|
|
1809
|
+
if (isCancel2(selected)) process.exit(0);
|
|
1451
1810
|
template = selected;
|
|
1452
1811
|
}
|
|
1453
1812
|
}
|
|
@@ -1463,7 +1822,7 @@ function registerCreateCommand(program2) {
|
|
|
1463
1822
|
if (hasTemplate) {
|
|
1464
1823
|
dirName = projectName;
|
|
1465
1824
|
if (!json) {
|
|
1466
|
-
const inputDir = await
|
|
1825
|
+
const inputDir = await text2({
|
|
1467
1826
|
message: "Directory name:",
|
|
1468
1827
|
initialValue: projectName,
|
|
1469
1828
|
validate: (v) => {
|
|
@@ -1473,7 +1832,7 @@ function registerCreateCommand(program2) {
|
|
|
1473
1832
|
return void 0;
|
|
1474
1833
|
}
|
|
1475
1834
|
});
|
|
1476
|
-
if (
|
|
1835
|
+
if (isCancel2(inputDir)) process.exit(0);
|
|
1477
1836
|
dirName = path3.basename(inputDir).replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
1478
1837
|
}
|
|
1479
1838
|
if (!dirName || dirName === "." || dirName === "..") {
|
|
@@ -1561,10 +1920,10 @@ function registerCreateCommand(program2) {
|
|
|
1561
1920
|
}
|
|
1562
1921
|
let liveUrl = null;
|
|
1563
1922
|
if (templateDownloaded && !json) {
|
|
1564
|
-
const shouldDeploy = await
|
|
1923
|
+
const shouldDeploy = await confirm2({
|
|
1565
1924
|
message: "Would you like to deploy now?"
|
|
1566
1925
|
});
|
|
1567
|
-
if (!
|
|
1926
|
+
if (!isCancel2(shouldDeploy) && shouldDeploy) {
|
|
1568
1927
|
try {
|
|
1569
1928
|
const envVars = await readEnvFile(process.cwd());
|
|
1570
1929
|
const startBody = {};
|
|
@@ -1826,14 +2185,14 @@ function registerProjectLinkCommand(program2) {
|
|
|
1826
2185
|
if (json) {
|
|
1827
2186
|
throw new CLIError("Multiple organizations found. Specify --org-id.");
|
|
1828
2187
|
}
|
|
1829
|
-
const selected = await
|
|
2188
|
+
const selected = await select2({
|
|
1830
2189
|
message: "Select an organization:",
|
|
1831
2190
|
options: orgs.map((o) => ({
|
|
1832
2191
|
value: o.id,
|
|
1833
2192
|
label: o.name
|
|
1834
2193
|
}))
|
|
1835
2194
|
});
|
|
1836
|
-
if (
|
|
2195
|
+
if (isCancel2(selected)) process.exit(0);
|
|
1837
2196
|
orgId = selected;
|
|
1838
2197
|
}
|
|
1839
2198
|
}
|
|
@@ -1848,14 +2207,14 @@ function registerProjectLinkCommand(program2) {
|
|
|
1848
2207
|
if (json) {
|
|
1849
2208
|
throw new CLIError("Specify --project-id in JSON mode.");
|
|
1850
2209
|
}
|
|
1851
|
-
const selected = await
|
|
2210
|
+
const selected = await select2({
|
|
1852
2211
|
message: "Select a project to link:",
|
|
1853
2212
|
options: projects.map((p) => ({
|
|
1854
2213
|
value: p.id,
|
|
1855
2214
|
label: `${p.name} (${p.region}, ${p.status})`
|
|
1856
2215
|
}))
|
|
1857
2216
|
});
|
|
1858
|
-
if (
|
|
2217
|
+
if (isCancel2(selected)) process.exit(0);
|
|
1859
2218
|
projectId = selected;
|
|
1860
2219
|
}
|
|
1861
2220
|
let project;
|
|
@@ -1906,7 +2265,7 @@ function registerProjectLinkCommand(program2) {
|
|
|
1906
2265
|
}
|
|
1907
2266
|
let dirName = project.name;
|
|
1908
2267
|
if (!json) {
|
|
1909
|
-
const inputDir = await
|
|
2268
|
+
const inputDir = await text2({
|
|
1910
2269
|
message: "Directory name:",
|
|
1911
2270
|
initialValue: project.name,
|
|
1912
2271
|
validate: (v) => {
|
|
@@ -1916,7 +2275,7 @@ function registerProjectLinkCommand(program2) {
|
|
|
1916
2275
|
return void 0;
|
|
1917
2276
|
}
|
|
1918
2277
|
});
|
|
1919
|
-
if (
|
|
2278
|
+
if (isCancel2(inputDir)) process.exit(0);
|
|
1920
2279
|
dirName = path4.basename(inputDir).replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
1921
2280
|
}
|
|
1922
2281
|
if (!dirName || dirName === "." || dirName === "..") {
|
|
@@ -1954,12 +2313,12 @@ function registerProjectLinkCommand(program2) {
|
|
|
1954
2313
|
await reportCliUsage("cli.link", true, 6, projectConfig);
|
|
1955
2314
|
if (!json) {
|
|
1956
2315
|
const dashboardUrl = `${getFrontendUrl()}/dashboard/project/${project.id}`;
|
|
1957
|
-
clack8.log.step(`Dashboard: ${
|
|
2316
|
+
clack8.log.step(`Dashboard: ${pc2.underline(dashboardUrl)}`);
|
|
1958
2317
|
if (templateDownloaded) {
|
|
1959
|
-
const runCommand = `${
|
|
2318
|
+
const runCommand = `${pc2.cyan("cd")} ${pc2.green(dirName)} ${pc2.dim("&&")} ${pc2.cyan("npm run dev")}`;
|
|
1960
2319
|
const steps = [
|
|
1961
|
-
`${
|
|
1962
|
-
`${
|
|
2320
|
+
`${pc2.bold("1.")} ${runCommand}`,
|
|
2321
|
+
`${pc2.bold("2.")} Open ${pc2.cyan("Claude Code")} or ${pc2.cyan("Cursor")} and prompt your agent to add more features`
|
|
1963
2322
|
];
|
|
1964
2323
|
clack8.note(steps.join("\n"), "What's next");
|
|
1965
2324
|
} else {
|
|
@@ -2493,17 +2852,17 @@ function registerFunctionsCommands(functionsCmd2) {
|
|
|
2493
2852
|
|
|
2494
2853
|
// src/commands/functions/deploy.ts
|
|
2495
2854
|
import { readFileSync as readFileSync4, existsSync as existsSync3 } from "fs";
|
|
2496
|
-
import { join as
|
|
2855
|
+
import { join as join7 } from "path";
|
|
2497
2856
|
function registerFunctionsDeployCommand(functionsCmd2) {
|
|
2498
2857
|
functionsCmd2.command("deploy <slug>").description("Deploy an edge function (create or update)").option("--file <path>", "Path to the function source file").option("--name <name>", "Function display name").option("--description <desc>", "Function description").action(async (slug, opts, cmd) => {
|
|
2499
2858
|
const { json } = getRootOpts(cmd);
|
|
2500
2859
|
try {
|
|
2501
2860
|
await requireAuth();
|
|
2502
|
-
const filePath = opts.file ??
|
|
2861
|
+
const filePath = opts.file ?? join7(process.cwd(), "insforge", "functions", slug, "index.ts");
|
|
2503
2862
|
if (!existsSync3(filePath)) {
|
|
2504
2863
|
throw new CLIError(
|
|
2505
2864
|
`Source file not found: ${filePath}
|
|
2506
|
-
Specify --file <path> or create ${
|
|
2865
|
+
Specify --file <path> or create ${join7("insforge", "functions", slug, "index.ts")}`
|
|
2507
2866
|
);
|
|
2508
2867
|
}
|
|
2509
2868
|
const code = readFileSync4(filePath, "utf-8");
|
|
@@ -2590,8 +2949,8 @@ function registerFunctionsInvokeCommand(functionsCmd2) {
|
|
|
2590
2949
|
console.log(JSON.stringify(data, null, 2));
|
|
2591
2950
|
}
|
|
2592
2951
|
} else {
|
|
2593
|
-
const
|
|
2594
|
-
console.log(
|
|
2952
|
+
const text3 = await res.text();
|
|
2953
|
+
console.log(text3);
|
|
2595
2954
|
}
|
|
2596
2955
|
if (status >= 400) {
|
|
2597
2956
|
throw new CLIError(`HTTP ${status}`, 1, "HTTP_ERROR");
|
|
@@ -2634,10 +2993,10 @@ function registerFunctionsDeleteCommand(functionsCmd2) {
|
|
|
2634
2993
|
try {
|
|
2635
2994
|
await requireAuth();
|
|
2636
2995
|
if (!yes && !json) {
|
|
2637
|
-
const confirmed = await
|
|
2996
|
+
const confirmed = await confirm2({
|
|
2638
2997
|
message: `Delete function "${slug}"? This cannot be undone.`
|
|
2639
2998
|
});
|
|
2640
|
-
if (
|
|
2999
|
+
if (isCancel2(confirmed) || !confirmed) {
|
|
2641
3000
|
clack9.log.info("Cancelled.");
|
|
2642
3001
|
return;
|
|
2643
3002
|
}
|
|
@@ -2737,7 +3096,7 @@ function registerStorageUploadCommand(storageCmd2) {
|
|
|
2737
3096
|
|
|
2738
3097
|
// src/commands/storage/download.ts
|
|
2739
3098
|
import { writeFileSync as writeFileSync3 } from "fs";
|
|
2740
|
-
import { join as
|
|
3099
|
+
import { join as join8, basename as basename6 } from "path";
|
|
2741
3100
|
function registerStorageDownloadCommand(storageCmd2) {
|
|
2742
3101
|
storageCmd2.command("download <objectKey>").description("Download a file from a storage bucket").requiredOption("--bucket <name>", "Source bucket name").option("--output <path>", "Output file path (defaults to current directory)").action(async (objectKey, opts, cmd) => {
|
|
2743
3102
|
const { json } = getRootOpts(cmd);
|
|
@@ -2757,7 +3116,7 @@ function registerStorageDownloadCommand(storageCmd2) {
|
|
|
2757
3116
|
throw new CLIError(err.error ?? `Download failed: ${res.status}`);
|
|
2758
3117
|
}
|
|
2759
3118
|
const buffer = Buffer.from(await res.arrayBuffer());
|
|
2760
|
-
const outputPath = opts.output ??
|
|
3119
|
+
const outputPath = opts.output ?? join8(process.cwd(), basename6(objectKey));
|
|
2761
3120
|
writeFileSync3(outputPath, buffer);
|
|
2762
3121
|
if (json) {
|
|
2763
3122
|
outputJson({ success: true, path: outputPath, size: buffer.length });
|
|
@@ -2796,17 +3155,16 @@ function registerStorageCreateBucketCommand(storageCmd2) {
|
|
|
2796
3155
|
}
|
|
2797
3156
|
|
|
2798
3157
|
// src/commands/storage/delete-bucket.ts
|
|
2799
|
-
import * as clack10 from "@clack/prompts";
|
|
2800
3158
|
function registerStorageDeleteBucketCommand(storageCmd2) {
|
|
2801
3159
|
storageCmd2.command("delete-bucket <name>").description("Delete a storage bucket and all its objects").action(async (name, _opts, cmd) => {
|
|
2802
3160
|
const { json, yes } = getRootOpts(cmd);
|
|
2803
3161
|
try {
|
|
2804
3162
|
await requireAuth();
|
|
2805
3163
|
if (!yes && !json) {
|
|
2806
|
-
const
|
|
3164
|
+
const confirm3 = await confirm2({
|
|
2807
3165
|
message: `Delete bucket "${name}" and all its objects? This cannot be undone.`
|
|
2808
3166
|
});
|
|
2809
|
-
if (
|
|
3167
|
+
if (isCancel2(confirm3) || !confirm3) {
|
|
2810
3168
|
process.exit(0);
|
|
2811
3169
|
}
|
|
2812
3170
|
}
|
|
@@ -3061,7 +3419,6 @@ function registerDeploymentsStatusCommand(deploymentsCmd2) {
|
|
|
3061
3419
|
}
|
|
3062
3420
|
|
|
3063
3421
|
// src/commands/deployments/cancel.ts
|
|
3064
|
-
import * as clack11 from "@clack/prompts";
|
|
3065
3422
|
function registerDeploymentsCancelCommand(deploymentsCmd2) {
|
|
3066
3423
|
deploymentsCmd2.command("cancel <id>").description("Cancel a deployment").action(async (id, _opts, cmd) => {
|
|
3067
3424
|
const { json, yes } = getRootOpts(cmd);
|
|
@@ -3069,10 +3426,10 @@ function registerDeploymentsCancelCommand(deploymentsCmd2) {
|
|
|
3069
3426
|
await requireAuth();
|
|
3070
3427
|
if (!getProjectConfig()) throw new ProjectNotLinkedError();
|
|
3071
3428
|
if (!yes && !json) {
|
|
3072
|
-
const confirmed = await
|
|
3429
|
+
const confirmed = await confirm2({
|
|
3073
3430
|
message: `Cancel deployment ${id}?`
|
|
3074
3431
|
});
|
|
3075
|
-
if (
|
|
3432
|
+
if (isCancel2(confirmed) || !confirmed) process.exit(0);
|
|
3076
3433
|
}
|
|
3077
3434
|
const res = await ossFetch(`/api/deployments/${id}/cancel`, { method: "POST" });
|
|
3078
3435
|
const result = await res.json();
|
|
@@ -3352,17 +3709,16 @@ function registerSecretsUpdateCommand(secretsCmd2) {
|
|
|
3352
3709
|
}
|
|
3353
3710
|
|
|
3354
3711
|
// src/commands/secrets/delete.ts
|
|
3355
|
-
import * as clack12 from "@clack/prompts";
|
|
3356
3712
|
function registerSecretsDeleteCommand(secretsCmd2) {
|
|
3357
3713
|
secretsCmd2.command("delete <key>").description("Delete a secret").action(async (key, _opts, cmd) => {
|
|
3358
3714
|
const { json, yes } = getRootOpts(cmd);
|
|
3359
3715
|
try {
|
|
3360
3716
|
await requireAuth();
|
|
3361
3717
|
if (!yes && !json) {
|
|
3362
|
-
const
|
|
3718
|
+
const confirm3 = await confirm2({
|
|
3363
3719
|
message: `Delete secret "${key}"? This cannot be undone.`
|
|
3364
3720
|
});
|
|
3365
|
-
if (
|
|
3721
|
+
if (isCancel2(confirm3) || !confirm3) {
|
|
3366
3722
|
process.exit(0);
|
|
3367
3723
|
}
|
|
3368
3724
|
}
|
|
@@ -3538,17 +3894,16 @@ function registerSchedulesUpdateCommand(schedulesCmd2) {
|
|
|
3538
3894
|
}
|
|
3539
3895
|
|
|
3540
3896
|
// src/commands/schedules/delete.ts
|
|
3541
|
-
import * as clack13 from "@clack/prompts";
|
|
3542
3897
|
function registerSchedulesDeleteCommand(schedulesCmd2) {
|
|
3543
3898
|
schedulesCmd2.command("delete <id>").description("Delete a schedule").action(async (id, _opts, cmd) => {
|
|
3544
3899
|
const { json, yes } = getRootOpts(cmd);
|
|
3545
3900
|
try {
|
|
3546
3901
|
await requireAuth();
|
|
3547
3902
|
if (!yes && !json) {
|
|
3548
|
-
const
|
|
3903
|
+
const confirm3 = await confirm2({
|
|
3549
3904
|
message: `Delete schedule "${id}"? This cannot be undone.`
|
|
3550
3905
|
});
|
|
3551
|
-
if (
|
|
3906
|
+
if (isCancel2(confirm3) || !confirm3) {
|
|
3552
3907
|
process.exit(0);
|
|
3553
3908
|
}
|
|
3554
3909
|
}
|
|
@@ -3869,10 +4224,10 @@ function registerComputeLogsCommand(computeCmd2) {
|
|
|
3869
4224
|
|
|
3870
4225
|
// src/commands/compute/deploy.ts
|
|
3871
4226
|
import { existsSync as existsSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4, unlinkSync as unlinkSync2, renameSync } from "fs";
|
|
3872
|
-
import { join as
|
|
4227
|
+
import { join as join9 } from "path";
|
|
3873
4228
|
import { execSync, spawn } from "child_process";
|
|
3874
4229
|
function parseFlyToml(dir) {
|
|
3875
|
-
const tomlPath =
|
|
4230
|
+
const tomlPath = join9(dir, "fly.toml");
|
|
3876
4231
|
if (!existsSync5(tomlPath)) return {};
|
|
3877
4232
|
const content = readFileSync6(tomlPath, "utf-8");
|
|
3878
4233
|
const config = {};
|
|
@@ -3944,7 +4299,7 @@ function registerComputeDeployCommand(computeCmd2) {
|
|
|
3944
4299
|
checkFlyctl();
|
|
3945
4300
|
const flyToken = getFlyToken();
|
|
3946
4301
|
const dir = directory ?? process.cwd();
|
|
3947
|
-
const dockerfilePath =
|
|
4302
|
+
const dockerfilePath = join9(dir, "Dockerfile");
|
|
3948
4303
|
if (!existsSync5(dockerfilePath)) {
|
|
3949
4304
|
throw new CLIError(`No Dockerfile found in ${dir}`);
|
|
3950
4305
|
}
|
|
@@ -3990,8 +4345,8 @@ function registerComputeDeployCommand(computeCmd2) {
|
|
|
3990
4345
|
serviceId = service.id;
|
|
3991
4346
|
flyAppId = service.flyAppId;
|
|
3992
4347
|
}
|
|
3993
|
-
const existingTomlPath =
|
|
3994
|
-
const backupTomlPath =
|
|
4348
|
+
const existingTomlPath = join9(dir, "fly.toml");
|
|
4349
|
+
const backupTomlPath = join9(dir, "fly.toml.insforge-backup");
|
|
3995
4350
|
let hadExistingToml = false;
|
|
3996
4351
|
if (existsSync5(existingTomlPath)) {
|
|
3997
4352
|
hadExistingToml = true;
|
|
@@ -4179,7 +4534,7 @@ function formatSize2(gb) {
|
|
|
4179
4534
|
|
|
4180
4535
|
// src/commands/diagnose/index.ts
|
|
4181
4536
|
import * as os from "os";
|
|
4182
|
-
import * as
|
|
4537
|
+
import * as clack10 from "@clack/prompts";
|
|
4183
4538
|
|
|
4184
4539
|
// src/commands/diagnose/metrics.ts
|
|
4185
4540
|
var METRIC_LABELS = {
|
|
@@ -4716,10 +5071,10 @@ function registerDiagnoseCommands(diagnoseCmd2) {
|
|
|
4716
5071
|
if (question.length === 0 || question.length > 2e3) {
|
|
4717
5072
|
throw new CLIError("Question must be between 1 and 2000 characters.");
|
|
4718
5073
|
}
|
|
4719
|
-
const s = !json ?
|
|
5074
|
+
const s = !json ? clack10.spinner() : null;
|
|
4720
5075
|
s?.start("Collecting diagnostic data...");
|
|
4721
5076
|
const data2 = await collectDiagnosticData(projectId, ossMode, apiUrl);
|
|
4722
|
-
const cliVersion = "0.1.
|
|
5077
|
+
const cliVersion = "0.1.51-dev.0";
|
|
4723
5078
|
s?.stop("Data collected");
|
|
4724
5079
|
if (!json) {
|
|
4725
5080
|
console.log(`
|
|
@@ -4835,7 +5190,7 @@ function registerDiagnoseCommands(diagnoseCmd2) {
|
|
|
4835
5190
|
outputJson({ sessionId, events: jsonEvents });
|
|
4836
5191
|
}
|
|
4837
5192
|
if (!json && sessionId) {
|
|
4838
|
-
const ratingChoice = await
|
|
5193
|
+
const ratingChoice = await select2({
|
|
4839
5194
|
message: "Was this analysis helpful?",
|
|
4840
5195
|
options: [
|
|
4841
5196
|
{ value: "skip", label: "Skip", hint: "no rating" },
|
|
@@ -4844,7 +5199,7 @@ function registerDiagnoseCommands(diagnoseCmd2) {
|
|
|
4844
5199
|
{ value: "incorrect", label: "Incorrect", hint: "diagnosis was wrong or misleading" }
|
|
4845
5200
|
]
|
|
4846
5201
|
});
|
|
4847
|
-
if (!
|
|
5202
|
+
if (!isCancel2(ratingChoice) && ratingChoice !== "skip") {
|
|
4848
5203
|
try {
|
|
4849
5204
|
await rateDiagnosticSession(
|
|
4850
5205
|
sessionId,
|
|
@@ -4852,9 +5207,9 @@ function registerDiagnoseCommands(diagnoseCmd2) {
|
|
|
4852
5207
|
void 0,
|
|
4853
5208
|
apiUrl
|
|
4854
5209
|
);
|
|
4855
|
-
|
|
5210
|
+
clack10.log.success("Thanks for your feedback!");
|
|
4856
5211
|
} catch {
|
|
4857
|
-
|
|
5212
|
+
clack10.log.warn("Failed to submit rating.");
|
|
4858
5213
|
}
|
|
4859
5214
|
}
|
|
4860
5215
|
}
|
|
@@ -4947,7 +5302,7 @@ function formatBytesCompact(bytes) {
|
|
|
4947
5302
|
|
|
4948
5303
|
// src/index.ts
|
|
4949
5304
|
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
4950
|
-
var pkg = JSON.parse(readFileSync7(
|
|
5305
|
+
var pkg = JSON.parse(readFileSync7(join10(__dirname, "../package.json"), "utf-8"));
|
|
4951
5306
|
var INSFORGE_LOGO = `
|
|
4952
5307
|
\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
4953
5308
|
\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D
|
|
@@ -5049,7 +5404,7 @@ async function showInteractiveMenu() {
|
|
|
5049
5404
|
} catch {
|
|
5050
5405
|
}
|
|
5051
5406
|
console.log(INSFORGE_LOGO);
|
|
5052
|
-
|
|
5407
|
+
clack11.intro(`InsForge CLI v${pkg.version}`);
|
|
5053
5408
|
const options = [];
|
|
5054
5409
|
if (!isLoggedIn) {
|
|
5055
5410
|
options.push({ value: "login", label: "Log in to InsForge" });
|
|
@@ -5065,12 +5420,12 @@ async function showInteractiveMenu() {
|
|
|
5065
5420
|
{ value: "docs", label: "View documentation" },
|
|
5066
5421
|
{ value: "help", label: "Show all commands" }
|
|
5067
5422
|
);
|
|
5068
|
-
const action = await
|
|
5423
|
+
const action = await select2({
|
|
5069
5424
|
message: "What would you like to do?",
|
|
5070
5425
|
options
|
|
5071
5426
|
});
|
|
5072
|
-
if (
|
|
5073
|
-
|
|
5427
|
+
if (isCancel2(action)) {
|
|
5428
|
+
clack11.cancel("Bye!");
|
|
5074
5429
|
process.exit(0);
|
|
5075
5430
|
}
|
|
5076
5431
|
switch (action) {
|