@insforge/cli 0.1.43 → 0.1.45
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 +1417 -1256
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { readFileSync as readFileSync7 } from "fs";
|
|
5
|
-
import { join as
|
|
5
|
+
import { join as join9, dirname } from "path";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
7
|
import { Command } from "commander";
|
|
8
8
|
import * as clack14 from "@clack/prompts";
|
|
@@ -204,8 +204,8 @@ function startCallbackServer() {
|
|
|
204
204
|
return new Promise((resolveServer) => {
|
|
205
205
|
let resolveResult;
|
|
206
206
|
let rejectResult;
|
|
207
|
-
const resultPromise = new Promise((
|
|
208
|
-
resolveResult =
|
|
207
|
+
const resultPromise = new Promise((resolve4, reject) => {
|
|
208
|
+
resolveResult = resolve4;
|
|
209
209
|
rejectResult = reject;
|
|
210
210
|
});
|
|
211
211
|
const server = createServer((req, res) => {
|
|
@@ -383,7 +383,7 @@ async function refreshAccessToken(apiUrl) {
|
|
|
383
383
|
}
|
|
384
384
|
|
|
385
385
|
// src/lib/api/platform.ts
|
|
386
|
-
async function platformFetch(
|
|
386
|
+
async function platformFetch(path5, options = {}, apiUrl) {
|
|
387
387
|
const baseUrl = getPlatformApiUrl(apiUrl);
|
|
388
388
|
const token = getAccessToken();
|
|
389
389
|
if (!token) {
|
|
@@ -394,11 +394,11 @@ async function platformFetch(path4, options = {}, apiUrl) {
|
|
|
394
394
|
Authorization: `Bearer ${token}`,
|
|
395
395
|
...options.headers ?? {}
|
|
396
396
|
};
|
|
397
|
-
const res = await fetch(`${baseUrl}${
|
|
397
|
+
const res = await fetch(`${baseUrl}${path5}`, { ...options, headers });
|
|
398
398
|
if (res.status === 401) {
|
|
399
399
|
const newToken = await refreshAccessToken(apiUrl);
|
|
400
400
|
headers.Authorization = `Bearer ${newToken}`;
|
|
401
|
-
const retryRes = await fetch(`${baseUrl}${
|
|
401
|
+
const retryRes = await fetch(`${baseUrl}${path5}`, { ...options, headers });
|
|
402
402
|
if (!retryRes.ok) {
|
|
403
403
|
const err = await retryRes.json().catch(() => ({}));
|
|
404
404
|
throw new CLIError(err.error ?? `Request failed: ${retryRes.status}`, retryRes.status === 403 ? 5 : 1);
|
|
@@ -696,7 +696,11 @@ function registerProjectsCommands(projectsCmd2) {
|
|
|
696
696
|
}
|
|
697
697
|
|
|
698
698
|
// src/commands/projects/link.ts
|
|
699
|
-
import
|
|
699
|
+
import { exec as exec3 } from "child_process";
|
|
700
|
+
import { promisify as promisify3 } from "util";
|
|
701
|
+
import * as fs4 from "fs/promises";
|
|
702
|
+
import * as path4 from "path";
|
|
703
|
+
import * as clack8 from "@clack/prompts";
|
|
700
704
|
|
|
701
705
|
// src/lib/skills.ts
|
|
702
706
|
import { exec } from "child_process";
|
|
@@ -838,146 +842,13 @@ async function shutdownAnalytics() {
|
|
|
838
842
|
}
|
|
839
843
|
}
|
|
840
844
|
|
|
841
|
-
// src/commands/
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
try {
|
|
849
|
-
if (opts.apiBaseUrl || opts.apiKey) {
|
|
850
|
-
try {
|
|
851
|
-
if (!opts.apiBaseUrl || !opts.apiKey) {
|
|
852
|
-
throw new CLIError("Both --api-base-url and --api-key must be provided together for direct linking.");
|
|
853
|
-
}
|
|
854
|
-
try {
|
|
855
|
-
new URL(opts.apiBaseUrl);
|
|
856
|
-
} catch {
|
|
857
|
-
throw new CLIError("Invalid --api-base-url. Please provide a valid URL.");
|
|
858
|
-
}
|
|
859
|
-
const projectConfig2 = {
|
|
860
|
-
project_id: "oss-project",
|
|
861
|
-
project_name: "oss-project",
|
|
862
|
-
org_id: "oss-org",
|
|
863
|
-
appkey: "oss",
|
|
864
|
-
region: "local",
|
|
865
|
-
api_key: opts.apiKey,
|
|
866
|
-
oss_host: opts.apiBaseUrl.replace(/\/$/, "")
|
|
867
|
-
// remove trailing slash if any
|
|
868
|
-
};
|
|
869
|
-
saveProjectConfig(projectConfig2);
|
|
870
|
-
if (json) {
|
|
871
|
-
outputJson({ success: true, project: { id: projectConfig2.project_id, name: projectConfig2.project_name, region: projectConfig2.region } });
|
|
872
|
-
} else {
|
|
873
|
-
outputSuccess(`Linked to direct project at ${projectConfig2.oss_host}`);
|
|
874
|
-
}
|
|
875
|
-
trackCommand("link", "oss-org", { direct: true });
|
|
876
|
-
await installSkills(json);
|
|
877
|
-
await reportCliUsage("cli.link_direct", true, 6);
|
|
878
|
-
try {
|
|
879
|
-
const urlMatch = opts.apiBaseUrl.match(/^https?:\/\/([^.]+)\.[^.]+\.insforge\.app/);
|
|
880
|
-
if (urlMatch) {
|
|
881
|
-
await reportAgentConnected({ app_key: urlMatch[1] }, apiUrl);
|
|
882
|
-
}
|
|
883
|
-
} catch {
|
|
884
|
-
}
|
|
885
|
-
return;
|
|
886
|
-
} catch (err) {
|
|
887
|
-
await reportCliUsage("cli.link_direct", false);
|
|
888
|
-
handleError(err, json);
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
const creds = await requireAuth(apiUrl, false);
|
|
892
|
-
let orgId = opts.orgId;
|
|
893
|
-
let projectId = opts.projectId;
|
|
894
|
-
if (!orgId && !projectId) {
|
|
895
|
-
const orgs = await listOrganizations(apiUrl);
|
|
896
|
-
if (orgs.length === 0) {
|
|
897
|
-
throw new CLIError("No organizations found.");
|
|
898
|
-
}
|
|
899
|
-
if (json) {
|
|
900
|
-
throw new CLIError("Specify --org-id in JSON mode.");
|
|
901
|
-
}
|
|
902
|
-
const selected = await clack6.select({
|
|
903
|
-
message: "Select an organization:",
|
|
904
|
-
options: orgs.map((o) => ({
|
|
905
|
-
value: o.id,
|
|
906
|
-
label: o.name
|
|
907
|
-
}))
|
|
908
|
-
});
|
|
909
|
-
if (clack6.isCancel(selected)) process.exit(0);
|
|
910
|
-
orgId = selected;
|
|
911
|
-
}
|
|
912
|
-
const config = getGlobalConfig();
|
|
913
|
-
config.default_org_id = orgId;
|
|
914
|
-
saveGlobalConfig(config);
|
|
915
|
-
if (!projectId) {
|
|
916
|
-
const projects = await listProjects(orgId, apiUrl);
|
|
917
|
-
if (projects.length === 0) {
|
|
918
|
-
throw new CLIError("No projects found in this organization.");
|
|
919
|
-
}
|
|
920
|
-
if (json) {
|
|
921
|
-
throw new CLIError("Specify --project-id in JSON mode.");
|
|
922
|
-
}
|
|
923
|
-
const selected = await clack6.select({
|
|
924
|
-
message: "Select a project to link:",
|
|
925
|
-
options: projects.map((p) => ({
|
|
926
|
-
value: p.id,
|
|
927
|
-
label: `${p.name} (${p.region}, ${p.status})`
|
|
928
|
-
}))
|
|
929
|
-
});
|
|
930
|
-
if (clack6.isCancel(selected)) process.exit(0);
|
|
931
|
-
projectId = selected;
|
|
932
|
-
}
|
|
933
|
-
let project;
|
|
934
|
-
let apiKey;
|
|
935
|
-
try {
|
|
936
|
-
[project, apiKey] = await Promise.all([
|
|
937
|
-
getProject(projectId, apiUrl),
|
|
938
|
-
getProjectApiKey(projectId, apiUrl)
|
|
939
|
-
]);
|
|
940
|
-
} catch (err) {
|
|
941
|
-
if (err instanceof CLIError && (err.exitCode === 5 || err.exitCode === 4 || err.message.includes("not found"))) {
|
|
942
|
-
const identity = creds.user?.email ?? creds.user?.name ?? "unknown user";
|
|
943
|
-
throw new CLIError(
|
|
944
|
-
`You're logged in as ${identity}, and you don't have access to project ${projectId}. Check that the project ID is correct and belongs to one of your organizations.`,
|
|
945
|
-
5,
|
|
946
|
-
"PERMISSION_DENIED"
|
|
947
|
-
);
|
|
948
|
-
}
|
|
949
|
-
throw err;
|
|
950
|
-
}
|
|
951
|
-
const projectConfig = {
|
|
952
|
-
project_id: project.id,
|
|
953
|
-
project_name: project.name,
|
|
954
|
-
org_id: project.organization_id,
|
|
955
|
-
appkey: project.appkey,
|
|
956
|
-
region: project.region,
|
|
957
|
-
api_key: apiKey,
|
|
958
|
-
oss_host: buildOssHost(project.appkey, project.region)
|
|
959
|
-
};
|
|
960
|
-
saveProjectConfig(projectConfig);
|
|
961
|
-
trackCommand("link", project.organization_id);
|
|
962
|
-
if (json) {
|
|
963
|
-
outputJson({ success: true, project: { id: project.id, name: project.name, region: project.region } });
|
|
964
|
-
} else {
|
|
965
|
-
outputSuccess(`Linked to project "${project.name}" (${project.appkey}.${project.region})`);
|
|
966
|
-
}
|
|
967
|
-
await installSkills(json);
|
|
968
|
-
await reportCliUsage("cli.link", true, 6);
|
|
969
|
-
try {
|
|
970
|
-
await reportAgentConnected({ project_id: project.id }, apiUrl);
|
|
971
|
-
} catch {
|
|
972
|
-
}
|
|
973
|
-
} catch (err) {
|
|
974
|
-
await reportCliUsage("cli.link", false);
|
|
975
|
-
handleError(err, json);
|
|
976
|
-
} finally {
|
|
977
|
-
await shutdownAnalytics();
|
|
978
|
-
}
|
|
979
|
-
});
|
|
980
|
-
}
|
|
845
|
+
// src/commands/create.ts
|
|
846
|
+
import { exec as exec2 } from "child_process";
|
|
847
|
+
import { tmpdir } from "os";
|
|
848
|
+
import { promisify as promisify2 } from "util";
|
|
849
|
+
import * as fs3 from "fs/promises";
|
|
850
|
+
import * as path3 from "path";
|
|
851
|
+
import * as clack7 from "@clack/prompts";
|
|
981
852
|
|
|
982
853
|
// src/lib/api/oss.ts
|
|
983
854
|
function requireProjectConfig() {
|
|
@@ -1002,14 +873,14 @@ async function getAnonKey() {
|
|
|
1002
873
|
const data = await res.json();
|
|
1003
874
|
return data.accessToken;
|
|
1004
875
|
}
|
|
1005
|
-
async function ossFetch(
|
|
876
|
+
async function ossFetch(path5, options = {}) {
|
|
1006
877
|
const config = requireProjectConfig();
|
|
1007
878
|
const headers = {
|
|
1008
879
|
"Content-Type": "application/json",
|
|
1009
880
|
Authorization: `Bearer ${config.api_key}`,
|
|
1010
881
|
...options.headers ?? {}
|
|
1011
882
|
};
|
|
1012
|
-
const res = await fetch(`${config.oss_host}${
|
|
883
|
+
const res = await fetch(`${config.oss_host}${path5}`, { ...options, headers });
|
|
1013
884
|
if (!res.ok) {
|
|
1014
885
|
const err = await res.json().catch(() => ({}));
|
|
1015
886
|
let message = err.message ?? err.error ?? `OSS request failed: ${res.status}`;
|
|
@@ -1017,7 +888,7 @@ async function ossFetch(path4, options = {}) {
|
|
|
1017
888
|
message += `
|
|
1018
889
|
${err.nextActions}`;
|
|
1019
890
|
}
|
|
1020
|
-
if (res.status === 404 &&
|
|
891
|
+
if (res.status === 404 && path5.startsWith("/api/compute")) {
|
|
1021
892
|
message = "Compute services are not available on this backend.\nSelf-hosted: upgrade your InsForge instance. Cloud: contact your InsForge admin to enable compute.";
|
|
1022
893
|
}
|
|
1023
894
|
throw new CLIError(message);
|
|
@@ -1025,240 +896,1184 @@ ${err.nextActions}`;
|
|
|
1025
896
|
return res;
|
|
1026
897
|
}
|
|
1027
898
|
|
|
1028
|
-
// src/
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
console.log("No rows returned.");
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
899
|
+
// src/lib/env.ts
|
|
900
|
+
import * as fs from "fs/promises";
|
|
901
|
+
import * as path from "path";
|
|
902
|
+
async function readEnvFile(cwd) {
|
|
903
|
+
const candidates = [".env.local", ".env.production", ".env"];
|
|
904
|
+
for (const name of candidates) {
|
|
905
|
+
const filePath = path.join(cwd, name);
|
|
906
|
+
const exists = await fs.stat(filePath).catch(() => null);
|
|
907
|
+
if (!exists) continue;
|
|
908
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
909
|
+
const vars = [];
|
|
910
|
+
for (const line of content.split("\n")) {
|
|
911
|
+
const trimmed = line.trim();
|
|
912
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
913
|
+
const eqIndex = trimmed.indexOf("=");
|
|
914
|
+
if (eqIndex === -1) continue;
|
|
915
|
+
const key = trimmed.slice(0, eqIndex).trim();
|
|
916
|
+
let value = trimmed.slice(eqIndex + 1).trim();
|
|
917
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
918
|
+
value = value.slice(1, -1);
|
|
1051
919
|
}
|
|
1052
|
-
|
|
1053
|
-
} catch (err) {
|
|
1054
|
-
await reportCliUsage("cli.db.query", false);
|
|
1055
|
-
handleError(err, json);
|
|
920
|
+
if (key) vars.push({ key, value });
|
|
1056
921
|
}
|
|
1057
|
-
|
|
922
|
+
return vars;
|
|
923
|
+
}
|
|
924
|
+
return [];
|
|
1058
925
|
}
|
|
1059
926
|
|
|
1060
|
-
// src/commands/
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
927
|
+
// src/commands/deployments/deploy.ts
|
|
928
|
+
import * as path2 from "path";
|
|
929
|
+
import * as fs2 from "fs/promises";
|
|
930
|
+
import * as clack6 from "@clack/prompts";
|
|
931
|
+
import archiver from "archiver";
|
|
932
|
+
var POLL_INTERVAL_MS = 5e3;
|
|
933
|
+
var POLL_TIMEOUT_MS = 3e5;
|
|
934
|
+
var EXCLUDE_PATTERNS = [
|
|
935
|
+
"node_modules",
|
|
936
|
+
".git",
|
|
937
|
+
".next",
|
|
938
|
+
".env",
|
|
939
|
+
".env.local",
|
|
940
|
+
"dist",
|
|
941
|
+
"build",
|
|
942
|
+
".DS_Store",
|
|
943
|
+
".insforge",
|
|
944
|
+
// IDE and AI agent configs
|
|
945
|
+
".claude",
|
|
946
|
+
".agents",
|
|
947
|
+
".augment",
|
|
948
|
+
".kilocode",
|
|
949
|
+
".kiro",
|
|
950
|
+
".qoder",
|
|
951
|
+
".qwen",
|
|
952
|
+
".roo",
|
|
953
|
+
".trae",
|
|
954
|
+
".windsurf",
|
|
955
|
+
".vercel",
|
|
956
|
+
".turbo",
|
|
957
|
+
".cache",
|
|
958
|
+
"skills",
|
|
959
|
+
"coverage"
|
|
960
|
+
];
|
|
961
|
+
function shouldExclude(name) {
|
|
962
|
+
const normalized = name.replace(/\\/g, "/");
|
|
963
|
+
for (const pattern of EXCLUDE_PATTERNS) {
|
|
964
|
+
if (normalized === pattern || normalized.startsWith(pattern + "/") || normalized.endsWith("/" + pattern) || normalized.includes("/" + pattern + "/")) {
|
|
965
|
+
return true;
|
|
1084
966
|
}
|
|
1085
|
-
}
|
|
967
|
+
}
|
|
968
|
+
if (normalized.endsWith(".log")) return true;
|
|
969
|
+
return false;
|
|
1086
970
|
}
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
} else {
|
|
1100
|
-
if (functions.length === 0) {
|
|
1101
|
-
console.log("No database functions found.");
|
|
1102
|
-
return;
|
|
1103
|
-
}
|
|
1104
|
-
outputTable(
|
|
1105
|
-
["Name", "Definition", "Kind"],
|
|
1106
|
-
functions.map((f) => [f.functionName, f.functionDef, f.kind])
|
|
1107
|
-
);
|
|
1108
|
-
}
|
|
1109
|
-
await reportCliUsage("cli.db.functions", true);
|
|
1110
|
-
} catch (err) {
|
|
1111
|
-
await reportCliUsage("cli.db.functions", false);
|
|
1112
|
-
handleError(err, json);
|
|
1113
|
-
}
|
|
971
|
+
async function createZipBuffer(sourceDir) {
|
|
972
|
+
return new Promise((resolve4, reject) => {
|
|
973
|
+
const archive = archiver("zip", { zlib: { level: 9 } });
|
|
974
|
+
const chunks = [];
|
|
975
|
+
archive.on("data", (chunk) => chunks.push(chunk));
|
|
976
|
+
archive.on("end", () => resolve4(Buffer.concat(chunks)));
|
|
977
|
+
archive.on("error", (err) => reject(err));
|
|
978
|
+
archive.directory(sourceDir, false, (entry) => {
|
|
979
|
+
if (shouldExclude(entry.name)) return false;
|
|
980
|
+
return entry;
|
|
981
|
+
});
|
|
982
|
+
void archive.finalize();
|
|
1114
983
|
});
|
|
1115
984
|
}
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
985
|
+
async function deployProject(opts) {
|
|
986
|
+
const { sourceDir, startBody = {}, spinner: s } = opts;
|
|
987
|
+
s?.start("Creating deployment...");
|
|
988
|
+
const createRes = await ossFetch("/api/deployments", { method: "POST" });
|
|
989
|
+
const { id: deploymentId, uploadUrl, uploadFields } = await createRes.json();
|
|
990
|
+
s?.message("Compressing source files...");
|
|
991
|
+
const zipBuffer = await createZipBuffer(sourceDir);
|
|
992
|
+
s?.message("Uploading...");
|
|
993
|
+
const formData = new FormData();
|
|
994
|
+
for (const [key, value] of Object.entries(uploadFields)) {
|
|
995
|
+
formData.append(key, value);
|
|
996
|
+
}
|
|
997
|
+
formData.append(
|
|
998
|
+
"file",
|
|
999
|
+
new Blob([zipBuffer], { type: "application/zip" }),
|
|
1000
|
+
"deployment.zip"
|
|
1001
|
+
);
|
|
1002
|
+
const uploadRes = await fetch(uploadUrl, { method: "POST", body: formData });
|
|
1003
|
+
if (!uploadRes.ok) {
|
|
1004
|
+
const uploadErr = await uploadRes.text();
|
|
1005
|
+
throw new CLIError(`Failed to upload: ${uploadErr}`);
|
|
1006
|
+
}
|
|
1007
|
+
s?.message("Starting deployment...");
|
|
1008
|
+
const startRes = await ossFetch(`/api/deployments/${deploymentId}/start`, {
|
|
1009
|
+
method: "POST",
|
|
1010
|
+
body: JSON.stringify(startBody)
|
|
1011
|
+
});
|
|
1012
|
+
await startRes.json();
|
|
1013
|
+
s?.message("Building and deploying...");
|
|
1014
|
+
const startTime = Date.now();
|
|
1015
|
+
let deployment = null;
|
|
1016
|
+
while (Date.now() - startTime < POLL_TIMEOUT_MS) {
|
|
1017
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
1121
1018
|
try {
|
|
1122
|
-
await
|
|
1123
|
-
|
|
1124
|
-
const
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
outputJson(raw);
|
|
1128
|
-
} else {
|
|
1129
|
-
if (indexes.length === 0) {
|
|
1130
|
-
console.log("No database indexes found.");
|
|
1131
|
-
return;
|
|
1132
|
-
}
|
|
1133
|
-
outputTable(
|
|
1134
|
-
["Table", "Index Name", "Definition", "Unique", "Primary"],
|
|
1135
|
-
indexes.map((i) => [
|
|
1136
|
-
i.tableName,
|
|
1137
|
-
i.indexName,
|
|
1138
|
-
i.indexDef,
|
|
1139
|
-
i.isUnique ? "Yes" : "No",
|
|
1140
|
-
i.isPrimary ? "Yes" : "No"
|
|
1141
|
-
])
|
|
1142
|
-
);
|
|
1019
|
+
const statusRes = await ossFetch(`/api/deployments/${deploymentId}`);
|
|
1020
|
+
deployment = await statusRes.json();
|
|
1021
|
+
const status = deployment.status.toUpperCase();
|
|
1022
|
+
if (status === "READY") {
|
|
1023
|
+
break;
|
|
1143
1024
|
}
|
|
1144
|
-
|
|
1025
|
+
if (status === "ERROR" || status === "CANCELED") {
|
|
1026
|
+
s?.stop("Deployment failed");
|
|
1027
|
+
throw new CLIError(getDeploymentError(deployment.metadata) ?? `Deployment failed with status: ${deployment.status}`);
|
|
1028
|
+
}
|
|
1029
|
+
const elapsed = Math.round((Date.now() - startTime) / 1e3);
|
|
1030
|
+
s?.message(`Building and deploying... (${elapsed}s, status: ${deployment.status})`);
|
|
1145
1031
|
} catch (err) {
|
|
1146
|
-
|
|
1147
|
-
handleError(err, json);
|
|
1032
|
+
if (err instanceof CLIError) throw err;
|
|
1148
1033
|
}
|
|
1149
|
-
}
|
|
1034
|
+
}
|
|
1035
|
+
const isReady = deployment?.status.toUpperCase() === "READY";
|
|
1036
|
+
const liveUrl = isReady ? deployment?.url ?? null : null;
|
|
1037
|
+
return { deploymentId, deployment, isReady, liveUrl };
|
|
1150
1038
|
}
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
function registerDbPoliciesCommand(dbCmd2) {
|
|
1154
|
-
dbCmd2.command("policies").description("List all RLS policies").action(async (_opts, cmd) => {
|
|
1039
|
+
function registerDeploymentsDeployCommand(deploymentsCmd2) {
|
|
1040
|
+
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) => {
|
|
1155
1041
|
const { json } = getRootOpts(cmd);
|
|
1156
1042
|
try {
|
|
1157
1043
|
await requireAuth();
|
|
1158
|
-
const
|
|
1159
|
-
|
|
1160
|
-
const
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1044
|
+
const config = getProjectConfig();
|
|
1045
|
+
if (!config) throw new ProjectNotLinkedError();
|
|
1046
|
+
const sourceDir = path2.resolve(directory ?? ".");
|
|
1047
|
+
const stats = await fs2.stat(sourceDir).catch(() => null);
|
|
1048
|
+
if (!stats?.isDirectory()) {
|
|
1049
|
+
throw new CLIError(`"${sourceDir}" is not a valid directory.`);
|
|
1050
|
+
}
|
|
1051
|
+
const dirName = path2.basename(sourceDir);
|
|
1052
|
+
if (EXCLUDE_PATTERNS.includes(dirName)) {
|
|
1053
|
+
throw new CLIError(`"${dirName}" is an excluded directory and cannot be used as a deploy source. Please specify your project root or output directory instead.`);
|
|
1054
|
+
}
|
|
1055
|
+
const s = !json ? clack6.spinner() : null;
|
|
1056
|
+
const startBody = {};
|
|
1057
|
+
if (opts.env) {
|
|
1058
|
+
try {
|
|
1059
|
+
const parsed = JSON.parse(opts.env);
|
|
1060
|
+
if (Array.isArray(parsed)) {
|
|
1061
|
+
startBody.envVars = parsed;
|
|
1062
|
+
} else {
|
|
1063
|
+
startBody.envVars = Object.entries(parsed).map(([key, value]) => ({ key, value }));
|
|
1064
|
+
}
|
|
1065
|
+
} catch {
|
|
1066
|
+
throw new CLIError("Invalid --env JSON.");
|
|
1167
1067
|
}
|
|
1168
|
-
outputTable(
|
|
1169
|
-
["Table", "Policy Name", "Command", "Roles", "Qual", "With Check"],
|
|
1170
|
-
policies.map((p) => [
|
|
1171
|
-
String(p.tableName ?? "-"),
|
|
1172
|
-
String(p.policyName ?? "-"),
|
|
1173
|
-
String(p.cmd ?? "-"),
|
|
1174
|
-
Array.isArray(p.roles) ? p.roles.join(", ") : String(p.roles ?? "-"),
|
|
1175
|
-
String(p.qual ?? "-"),
|
|
1176
|
-
String(p.withCheck ?? "-")
|
|
1177
|
-
])
|
|
1178
|
-
);
|
|
1179
1068
|
}
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
});
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
// src/commands/db/triggers.ts
|
|
1189
|
-
function registerDbTriggersCommand(dbCmd2) {
|
|
1190
|
-
dbCmd2.command("triggers").description("List all database triggers").action(async (_opts, cmd) => {
|
|
1191
|
-
const { json } = getRootOpts(cmd);
|
|
1192
|
-
try {
|
|
1193
|
-
await requireAuth();
|
|
1194
|
-
const res = await ossFetch("/api/database/triggers");
|
|
1195
|
-
const raw = await res.json();
|
|
1196
|
-
const triggers = Array.isArray(raw) ? raw : raw.triggers ?? [];
|
|
1197
|
-
if (json) {
|
|
1198
|
-
outputJson(raw);
|
|
1199
|
-
} else {
|
|
1200
|
-
if (triggers.length === 0) {
|
|
1201
|
-
console.log("No database triggers found.");
|
|
1202
|
-
return;
|
|
1069
|
+
if (opts.meta) {
|
|
1070
|
+
try {
|
|
1071
|
+
startBody.meta = JSON.parse(opts.meta);
|
|
1072
|
+
} catch {
|
|
1073
|
+
throw new CLIError("Invalid --meta JSON.");
|
|
1203
1074
|
}
|
|
1204
|
-
outputTable(
|
|
1205
|
-
["Name", "Table", "Timing", "Events", "ActionOrientation", "ActionCondition", "ActionStatement"],
|
|
1206
|
-
triggers.map((t) => [
|
|
1207
|
-
t.triggerName,
|
|
1208
|
-
t.tableName,
|
|
1209
|
-
t.actionTiming,
|
|
1210
|
-
t.eventManipulation,
|
|
1211
|
-
t.actionOrientation,
|
|
1212
|
-
t.actionCondition ?? "-",
|
|
1213
|
-
t.actionStatement
|
|
1214
|
-
])
|
|
1215
|
-
);
|
|
1216
1075
|
}
|
|
1217
|
-
await
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
const { json } = getRootOpts(cmd);
|
|
1229
|
-
try {
|
|
1230
|
-
await requireAuth();
|
|
1231
|
-
const body = opts.data ? JSON.stringify(JSON.parse(opts.data)) : void 0;
|
|
1232
|
-
const res = await ossFetch(`/api/database/rpc/${encodeURIComponent(functionName)}`, {
|
|
1233
|
-
method: body ? "POST" : "GET",
|
|
1234
|
-
...body ? { body } : {}
|
|
1235
|
-
});
|
|
1236
|
-
const result = await res.json();
|
|
1237
|
-
if (json) {
|
|
1238
|
-
outputJson(result);
|
|
1076
|
+
const result = await deployProject({ sourceDir, startBody, spinner: s });
|
|
1077
|
+
if (result.isReady) {
|
|
1078
|
+
s?.stop("Deployment complete");
|
|
1079
|
+
if (json) {
|
|
1080
|
+
outputJson(result.deployment);
|
|
1081
|
+
} else {
|
|
1082
|
+
if (result.liveUrl) {
|
|
1083
|
+
clack6.log.success(`Live at: ${result.liveUrl}`);
|
|
1084
|
+
}
|
|
1085
|
+
clack6.log.info(`Deployment ID: ${result.deploymentId}`);
|
|
1086
|
+
}
|
|
1239
1087
|
} else {
|
|
1240
|
-
|
|
1088
|
+
s?.stop("Deployment is still building");
|
|
1089
|
+
if (json) {
|
|
1090
|
+
outputJson({ id: result.deploymentId, status: result.deployment?.status ?? "building", timedOut: true });
|
|
1091
|
+
} else {
|
|
1092
|
+
clack6.log.info(`Deployment ID: ${result.deploymentId}`);
|
|
1093
|
+
clack6.log.warn("Deployment did not finish within 5 minutes.");
|
|
1094
|
+
clack6.log.info(`Check status with: npx @insforge/cli deployments status ${result.deploymentId}`);
|
|
1095
|
+
}
|
|
1241
1096
|
}
|
|
1242
|
-
await reportCliUsage("cli.
|
|
1097
|
+
await reportCliUsage("cli.deployments.deploy", true);
|
|
1243
1098
|
} catch (err) {
|
|
1099
|
+
await reportCliUsage("cli.deployments.deploy", false);
|
|
1244
1100
|
handleError(err, json);
|
|
1245
1101
|
}
|
|
1246
1102
|
});
|
|
1247
1103
|
}
|
|
1248
1104
|
|
|
1249
|
-
// src/commands/
|
|
1250
|
-
|
|
1251
|
-
function
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1105
|
+
// src/commands/create.ts
|
|
1106
|
+
var execAsync2 = promisify2(exec2);
|
|
1107
|
+
function buildOssHost(appkey, region) {
|
|
1108
|
+
return `https://${appkey}.${region}.insforge.app`;
|
|
1109
|
+
}
|
|
1110
|
+
async function waitForProjectActive(projectId, apiUrl, timeoutMs = 12e4) {
|
|
1111
|
+
const start = Date.now();
|
|
1112
|
+
while (Date.now() - start < timeoutMs) {
|
|
1113
|
+
const project = await getProject(projectId, apiUrl);
|
|
1114
|
+
if (project.status === "active") return;
|
|
1115
|
+
await new Promise((r) => setTimeout(r, 3e3));
|
|
1116
|
+
}
|
|
1117
|
+
throw new CLIError("Project creation timed out. Check the dashboard for status.");
|
|
1118
|
+
}
|
|
1119
|
+
var INSFORGE_BANNER = [
|
|
1120
|
+
"\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",
|
|
1121
|
+
"\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",
|
|
1122
|
+
"\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2557 ",
|
|
1123
|
+
"\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D ",
|
|
1124
|
+
"\u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
|
|
1125
|
+
"\u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D"
|
|
1126
|
+
];
|
|
1127
|
+
async function animateBanner() {
|
|
1128
|
+
const isTTY = process.stderr.isTTY;
|
|
1129
|
+
if (!isTTY || process.env.CI) {
|
|
1130
|
+
for (const line of INSFORGE_BANNER) {
|
|
1131
|
+
process.stderr.write(`${line}
|
|
1132
|
+
`);
|
|
1133
|
+
}
|
|
1134
|
+
process.stderr.write("\n");
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
const totalLines = INSFORGE_BANNER.length;
|
|
1138
|
+
const maxLen = Math.max(...INSFORGE_BANNER.map((l) => l.length));
|
|
1139
|
+
const cols = process.stderr.columns ?? 0;
|
|
1140
|
+
if (cols > 0 && cols < maxLen) {
|
|
1141
|
+
for (const line of INSFORGE_BANNER) {
|
|
1142
|
+
process.stderr.write(`\x1B[97m${line}\x1B[0m
|
|
1143
|
+
`);
|
|
1144
|
+
}
|
|
1145
|
+
process.stderr.write("\n");
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
const REVEAL_STEPS = 10;
|
|
1149
|
+
const REVEAL_DELAY = 30;
|
|
1150
|
+
for (let lineIdx = 0; lineIdx < totalLines; lineIdx++) {
|
|
1151
|
+
const line = INSFORGE_BANNER[lineIdx];
|
|
1152
|
+
for (let step = 0; step <= REVEAL_STEPS; step++) {
|
|
1153
|
+
const pos = Math.floor(step / REVEAL_STEPS * line.length);
|
|
1154
|
+
let rendered = "";
|
|
1155
|
+
for (let i = 0; i < line.length; i++) {
|
|
1156
|
+
if (i < pos) {
|
|
1157
|
+
rendered += `\x1B[97m${line[i]}\x1B[0m`;
|
|
1158
|
+
} else if (i === pos) {
|
|
1159
|
+
rendered += `\x1B[1;37m${line[i]}\x1B[0m`;
|
|
1160
|
+
} else {
|
|
1161
|
+
rendered += `\x1B[90m${line[i]}\x1B[0m`;
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
process.stderr.write(`\r${rendered}`);
|
|
1165
|
+
await new Promise((r) => setTimeout(r, REVEAL_DELAY));
|
|
1166
|
+
}
|
|
1167
|
+
process.stderr.write("\n");
|
|
1168
|
+
}
|
|
1169
|
+
const SHIMMER_STEPS = 16;
|
|
1170
|
+
const SHIMMER_DELAY = 40;
|
|
1171
|
+
const SHIMMER_WIDTH = 4;
|
|
1172
|
+
for (let step = 0; step < SHIMMER_STEPS; step++) {
|
|
1173
|
+
const shimmerPos = Math.floor(step / SHIMMER_STEPS * (maxLen + SHIMMER_WIDTH));
|
|
1174
|
+
process.stderr.write(`\x1B[${totalLines}A`);
|
|
1175
|
+
for (const line of INSFORGE_BANNER) {
|
|
1176
|
+
let rendered = "";
|
|
1177
|
+
for (let i = 0; i < line.length; i++) {
|
|
1178
|
+
const dist = Math.abs(i - shimmerPos);
|
|
1179
|
+
if (dist === 0) {
|
|
1180
|
+
rendered += `\x1B[1;97m${line[i]}\x1B[0m`;
|
|
1181
|
+
} else if (dist <= SHIMMER_WIDTH) {
|
|
1182
|
+
rendered += `\x1B[37m${line[i]}\x1B[0m`;
|
|
1183
|
+
} else {
|
|
1184
|
+
rendered += `\x1B[90m${line[i]}\x1B[0m`;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
process.stderr.write(`${rendered}
|
|
1188
|
+
`);
|
|
1189
|
+
}
|
|
1190
|
+
await new Promise((r) => setTimeout(r, SHIMMER_DELAY));
|
|
1191
|
+
}
|
|
1192
|
+
process.stderr.write(`\x1B[${totalLines}A`);
|
|
1193
|
+
for (const line of INSFORGE_BANNER) {
|
|
1194
|
+
process.stderr.write(`\x1B[97m${line}\x1B[0m
|
|
1195
|
+
`);
|
|
1196
|
+
}
|
|
1197
|
+
process.stderr.write("\n");
|
|
1198
|
+
}
|
|
1199
|
+
function getDefaultProjectName() {
|
|
1200
|
+
const dirName = path3.basename(process.cwd());
|
|
1201
|
+
const sanitized = dirName.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
1202
|
+
return sanitized.length >= 2 ? sanitized : "";
|
|
1203
|
+
}
|
|
1204
|
+
async function copyDir(src, dest) {
|
|
1205
|
+
const entries = await fs3.readdir(src, { withFileTypes: true });
|
|
1206
|
+
for (const entry of entries) {
|
|
1207
|
+
const srcPath = path3.join(src, entry.name);
|
|
1208
|
+
const destPath = path3.join(dest, entry.name);
|
|
1209
|
+
if (entry.isDirectory()) {
|
|
1210
|
+
await fs3.mkdir(destPath, { recursive: true });
|
|
1211
|
+
await copyDir(srcPath, destPath);
|
|
1212
|
+
} else {
|
|
1213
|
+
await fs3.copyFile(srcPath, destPath);
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
function registerCreateCommand(program2) {
|
|
1218
|
+
program2.command("create").description("Create a new InsForge project").option("--name <name>", "Project name").option("--org-id <id>", "Organization ID").option("--region <region>", "Deployment region (us-east, us-west, eu-central, ap-southeast)").option("--template <template>", "Template to use: react, nextjs, chatbot, crm, e-commerce, todo, or empty").action(async (opts, cmd) => {
|
|
1219
|
+
const { json, apiUrl } = getRootOpts(cmd);
|
|
1220
|
+
try {
|
|
1221
|
+
await requireAuth(apiUrl, false);
|
|
1222
|
+
if (!json) {
|
|
1223
|
+
await animateBanner();
|
|
1224
|
+
clack7.intro("Let's build something great");
|
|
1225
|
+
}
|
|
1226
|
+
let orgId = opts.orgId;
|
|
1227
|
+
if (!orgId) {
|
|
1228
|
+
const orgs = await listOrganizations(apiUrl);
|
|
1229
|
+
if (orgs.length === 0) {
|
|
1230
|
+
throw new CLIError("No organizations found.");
|
|
1231
|
+
}
|
|
1232
|
+
if (orgs.length === 1) {
|
|
1233
|
+
orgId = orgs[0].id;
|
|
1234
|
+
if (!json) clack7.log.info(`Using organization: ${orgs[0].name}`);
|
|
1235
|
+
} else {
|
|
1236
|
+
if (json) {
|
|
1237
|
+
throw new CLIError("Multiple organizations found. Specify --org-id.");
|
|
1238
|
+
}
|
|
1239
|
+
const selected = await clack7.select({
|
|
1240
|
+
message: "Select an organization:",
|
|
1241
|
+
options: orgs.map((o) => ({
|
|
1242
|
+
value: o.id,
|
|
1243
|
+
label: o.name
|
|
1244
|
+
}))
|
|
1245
|
+
});
|
|
1246
|
+
if (clack7.isCancel(selected)) process.exit(0);
|
|
1247
|
+
orgId = selected;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
const globalConfig = getGlobalConfig();
|
|
1251
|
+
globalConfig.default_org_id = orgId;
|
|
1252
|
+
saveGlobalConfig(globalConfig);
|
|
1253
|
+
let projectName = opts.name;
|
|
1254
|
+
if (!projectName) {
|
|
1255
|
+
if (json) throw new CLIError("--name is required in JSON mode.");
|
|
1256
|
+
const defaultName = getDefaultProjectName();
|
|
1257
|
+
const name = await clack7.text({
|
|
1258
|
+
message: "Project name:",
|
|
1259
|
+
...defaultName ? { initialValue: defaultName } : {},
|
|
1260
|
+
validate: (v) => v.length >= 2 ? void 0 : "Name must be at least 2 characters"
|
|
1261
|
+
});
|
|
1262
|
+
if (clack7.isCancel(name)) process.exit(0);
|
|
1263
|
+
projectName = name;
|
|
1264
|
+
}
|
|
1265
|
+
projectName = path3.basename(projectName).replace(/[^a-zA-Z0-9._-]/g, "-").replace(/\.+/g, ".");
|
|
1266
|
+
if (projectName.length < 2 || projectName === "." || projectName === "..") {
|
|
1267
|
+
throw new CLIError("Project name must be at least 2 safe characters (letters, numbers, hyphens).");
|
|
1268
|
+
}
|
|
1269
|
+
const validTemplates = ["react", "nextjs", "chatbot", "crm", "e-commerce", "todo", "empty"];
|
|
1270
|
+
let template = opts.template;
|
|
1271
|
+
if (template && !validTemplates.includes(template)) {
|
|
1272
|
+
throw new CLIError(`Invalid template "${template}". Valid options: ${validTemplates.join(", ")}`);
|
|
1273
|
+
}
|
|
1274
|
+
if (!template) {
|
|
1275
|
+
if (json) {
|
|
1276
|
+
template = "empty";
|
|
1277
|
+
} else {
|
|
1278
|
+
const approach = await clack7.select({
|
|
1279
|
+
message: "How would you like to start?",
|
|
1280
|
+
options: [
|
|
1281
|
+
{ value: "blank", label: "Blank project", hint: "Start from scratch with .env.local ready" },
|
|
1282
|
+
{ value: "template", label: "Start from a template", hint: "Pre-built starter apps" }
|
|
1283
|
+
]
|
|
1284
|
+
});
|
|
1285
|
+
if (clack7.isCancel(approach)) process.exit(0);
|
|
1286
|
+
captureEvent(orgId, "create_approach_selected", {
|
|
1287
|
+
approach
|
|
1288
|
+
});
|
|
1289
|
+
if (approach === "blank") {
|
|
1290
|
+
template = "empty";
|
|
1291
|
+
} else {
|
|
1292
|
+
const selected = await clack7.select({
|
|
1293
|
+
message: "Choose a starter template:",
|
|
1294
|
+
options: [
|
|
1295
|
+
{ value: "react", label: "Web app template with React" },
|
|
1296
|
+
{ value: "nextjs", label: "Web app template with Next.js" },
|
|
1297
|
+
{ value: "chatbot", label: "AI Chatbot with Next.js" },
|
|
1298
|
+
{ value: "crm", label: "CRM with Next.js" },
|
|
1299
|
+
{ value: "e-commerce", label: "E-Commerce store with Next.js" },
|
|
1300
|
+
{ value: "todo", label: "Todo app with Next.js" }
|
|
1301
|
+
]
|
|
1302
|
+
});
|
|
1303
|
+
if (clack7.isCancel(selected)) process.exit(0);
|
|
1304
|
+
template = selected;
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
captureEvent(orgId, "template_selected", {
|
|
1309
|
+
template,
|
|
1310
|
+
approach: template === "empty" ? "blank" : "template"
|
|
1311
|
+
});
|
|
1312
|
+
const hasTemplate = template !== "empty";
|
|
1313
|
+
let dirName = null;
|
|
1314
|
+
const originalCwd = process.cwd();
|
|
1315
|
+
let projectDir = originalCwd;
|
|
1316
|
+
if (hasTemplate) {
|
|
1317
|
+
dirName = projectName;
|
|
1318
|
+
if (!json) {
|
|
1319
|
+
const inputDir = await clack7.text({
|
|
1320
|
+
message: "Directory name:",
|
|
1321
|
+
initialValue: projectName,
|
|
1322
|
+
validate: (v) => {
|
|
1323
|
+
if (v.length < 1) return "Directory name is required";
|
|
1324
|
+
const normalized = path3.basename(v).replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
1325
|
+
if (!normalized || normalized === "." || normalized === "..") return "Invalid directory name";
|
|
1326
|
+
return void 0;
|
|
1327
|
+
}
|
|
1328
|
+
});
|
|
1329
|
+
if (clack7.isCancel(inputDir)) process.exit(0);
|
|
1330
|
+
dirName = path3.basename(inputDir).replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
1331
|
+
}
|
|
1332
|
+
if (!dirName || dirName === "." || dirName === "..") {
|
|
1333
|
+
throw new CLIError("Invalid directory name.");
|
|
1334
|
+
}
|
|
1335
|
+
projectDir = path3.resolve(originalCwd, dirName);
|
|
1336
|
+
const dirExists = await fs3.stat(projectDir).catch(() => null);
|
|
1337
|
+
if (dirExists) {
|
|
1338
|
+
throw new CLIError(`Directory "${dirName}" already exists.`);
|
|
1339
|
+
}
|
|
1340
|
+
await fs3.mkdir(projectDir);
|
|
1341
|
+
process.chdir(projectDir);
|
|
1342
|
+
}
|
|
1343
|
+
let projectLinked = false;
|
|
1344
|
+
const s = !json ? clack7.spinner() : null;
|
|
1345
|
+
try {
|
|
1346
|
+
s?.start("Creating project...");
|
|
1347
|
+
const project = await createProject(orgId, projectName, opts.region, apiUrl);
|
|
1348
|
+
s?.message("Waiting for project to become active...");
|
|
1349
|
+
await waitForProjectActive(project.id, apiUrl);
|
|
1350
|
+
const apiKey = await getProjectApiKey(project.id, apiUrl);
|
|
1351
|
+
const projectConfig = {
|
|
1352
|
+
project_id: project.id,
|
|
1353
|
+
project_name: project.name,
|
|
1354
|
+
org_id: project.organization_id,
|
|
1355
|
+
appkey: project.appkey,
|
|
1356
|
+
region: project.region,
|
|
1357
|
+
api_key: apiKey,
|
|
1358
|
+
oss_host: buildOssHost(project.appkey, project.region)
|
|
1359
|
+
};
|
|
1360
|
+
saveProjectConfig(projectConfig);
|
|
1361
|
+
projectLinked = true;
|
|
1362
|
+
s?.stop(`Project "${project.name}" created and linked`);
|
|
1363
|
+
const githubTemplates = ["chatbot", "crm", "e-commerce", "nextjs", "react", "todo"];
|
|
1364
|
+
if (githubTemplates.includes(template)) {
|
|
1365
|
+
await downloadGitHubTemplate(template, projectConfig, json);
|
|
1366
|
+
} else if (hasTemplate) {
|
|
1367
|
+
await downloadTemplate(template, projectConfig, projectName, json, apiUrl);
|
|
1368
|
+
} else {
|
|
1369
|
+
try {
|
|
1370
|
+
const anonKey = await getAnonKey();
|
|
1371
|
+
if (!anonKey) {
|
|
1372
|
+
if (!json) clack7.log.warn("Could not retrieve anon key. You can add it to .env.local manually.");
|
|
1373
|
+
} else {
|
|
1374
|
+
const envPath = path3.join(process.cwd(), ".env.local");
|
|
1375
|
+
const envContent = [
|
|
1376
|
+
"# InsForge",
|
|
1377
|
+
`NEXT_PUBLIC_INSFORGE_URL=${projectConfig.oss_host}`,
|
|
1378
|
+
`NEXT_PUBLIC_INSFORGE_ANON_KEY=${anonKey}`,
|
|
1379
|
+
""
|
|
1380
|
+
].join("\n");
|
|
1381
|
+
await fs3.writeFile(envPath, envContent, { flag: "wx" });
|
|
1382
|
+
if (!json) {
|
|
1383
|
+
clack7.log.success("Created .env.local with your InsForge credentials");
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
} catch (err) {
|
|
1387
|
+
const error = err;
|
|
1388
|
+
if (!json) {
|
|
1389
|
+
if (error.code === "EEXIST") {
|
|
1390
|
+
clack7.log.warn(".env.local already exists; skipping InsForge key seeding.");
|
|
1391
|
+
} else {
|
|
1392
|
+
clack7.log.warn(`Failed to create .env.local: ${error.message}`);
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
await installSkills(json);
|
|
1398
|
+
trackCommand("create", orgId);
|
|
1399
|
+
await reportCliUsage("cli.create", true, 6);
|
|
1400
|
+
const templateDownloaded = hasTemplate ? await fs3.stat(path3.join(process.cwd(), "package.json")).catch(() => null) : null;
|
|
1401
|
+
if (templateDownloaded) {
|
|
1402
|
+
const installSpinner = !json ? clack7.spinner() : null;
|
|
1403
|
+
installSpinner?.start("Installing dependencies...");
|
|
1404
|
+
try {
|
|
1405
|
+
await execAsync2("npm install", { cwd: process.cwd(), maxBuffer: 10 * 1024 * 1024 });
|
|
1406
|
+
installSpinner?.stop("Dependencies installed");
|
|
1407
|
+
} catch (err) {
|
|
1408
|
+
installSpinner?.stop("Failed to install dependencies");
|
|
1409
|
+
if (!json) {
|
|
1410
|
+
clack7.log.warn(`npm install failed: ${err.message}`);
|
|
1411
|
+
clack7.log.info("Run `npm install` manually to install dependencies.");
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
let liveUrl = null;
|
|
1416
|
+
if (templateDownloaded && !json) {
|
|
1417
|
+
const shouldDeploy = await clack7.confirm({
|
|
1418
|
+
message: "Would you like to deploy now?"
|
|
1419
|
+
});
|
|
1420
|
+
if (!clack7.isCancel(shouldDeploy) && shouldDeploy) {
|
|
1421
|
+
try {
|
|
1422
|
+
const envVars = await readEnvFile(process.cwd());
|
|
1423
|
+
const startBody = {};
|
|
1424
|
+
if (envVars.length > 0) {
|
|
1425
|
+
startBody.envVars = envVars;
|
|
1426
|
+
}
|
|
1427
|
+
const deploySpinner = clack7.spinner();
|
|
1428
|
+
const result = await deployProject({
|
|
1429
|
+
sourceDir: process.cwd(),
|
|
1430
|
+
startBody,
|
|
1431
|
+
spinner: deploySpinner
|
|
1432
|
+
});
|
|
1433
|
+
if (result.isReady) {
|
|
1434
|
+
deploySpinner.stop("Deployment complete");
|
|
1435
|
+
liveUrl = result.liveUrl;
|
|
1436
|
+
} else {
|
|
1437
|
+
deploySpinner.stop("Deployment is still building");
|
|
1438
|
+
clack7.log.info(`Deployment ID: ${result.deploymentId}`);
|
|
1439
|
+
clack7.log.warn("Deployment did not finish within 2 minutes.");
|
|
1440
|
+
clack7.log.info(`Check status with: npx @insforge/cli deployments status ${result.deploymentId}`);
|
|
1441
|
+
}
|
|
1442
|
+
} catch (err) {
|
|
1443
|
+
clack7.log.warn(`Deploy failed: ${err.message}`);
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
const dashboardUrl = `${getFrontendUrl()}/dashboard/project/${project.id}`;
|
|
1448
|
+
if (json) {
|
|
1449
|
+
outputJson({
|
|
1450
|
+
success: true,
|
|
1451
|
+
project: { id: project.id, name: project.name, appkey: project.appkey, region: project.region },
|
|
1452
|
+
template,
|
|
1453
|
+
...dirName ? { directory: dirName } : {},
|
|
1454
|
+
urls: {
|
|
1455
|
+
dashboard: dashboardUrl,
|
|
1456
|
+
...liveUrl ? { liveSite: liveUrl } : {}
|
|
1457
|
+
}
|
|
1458
|
+
});
|
|
1459
|
+
} else {
|
|
1460
|
+
clack7.log.step(`Dashboard: ${dashboardUrl}`);
|
|
1461
|
+
if (liveUrl) {
|
|
1462
|
+
clack7.log.success(`Live site: ${liveUrl}`);
|
|
1463
|
+
}
|
|
1464
|
+
if (templateDownloaded) {
|
|
1465
|
+
const steps = [
|
|
1466
|
+
`cd ${dirName}`,
|
|
1467
|
+
"npm run dev"
|
|
1468
|
+
];
|
|
1469
|
+
clack7.note(steps.join("\n"), "Next steps");
|
|
1470
|
+
clack7.note("Open your coding agent (Claude Code, Codex, Cursor, etc.) to add new features.", "Keep building");
|
|
1471
|
+
} else if (hasTemplate && !templateDownloaded) {
|
|
1472
|
+
clack7.log.warn("Template download failed. You can retry or set up manually.");
|
|
1473
|
+
} else {
|
|
1474
|
+
const prompts = [
|
|
1475
|
+
"Build a todo app with Google OAuth sign-in",
|
|
1476
|
+
"Build an Instagram clone where users can upload photos, like, and comment",
|
|
1477
|
+
"Build an AI chatbot with conversation history"
|
|
1478
|
+
];
|
|
1479
|
+
clack7.note(
|
|
1480
|
+
`Open your coding agent (Claude Code, Codex, Cursor, etc.) and try:
|
|
1481
|
+
|
|
1482
|
+
${prompts.map((p) => `\u2022 "${p}"`).join("\n")}`,
|
|
1483
|
+
"Start building"
|
|
1484
|
+
);
|
|
1485
|
+
}
|
|
1486
|
+
clack7.outro("Done!");
|
|
1487
|
+
}
|
|
1488
|
+
} catch (err) {
|
|
1489
|
+
if (!projectLinked && hasTemplate && projectDir !== originalCwd) {
|
|
1490
|
+
process.chdir(originalCwd);
|
|
1491
|
+
await fs3.rm(projectDir, { recursive: true, force: true }).catch(() => {
|
|
1492
|
+
});
|
|
1493
|
+
}
|
|
1494
|
+
throw err;
|
|
1495
|
+
}
|
|
1496
|
+
} catch (err) {
|
|
1497
|
+
handleError(err, json);
|
|
1498
|
+
} finally {
|
|
1499
|
+
await shutdownAnalytics();
|
|
1500
|
+
}
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1503
|
+
async function downloadTemplate(framework, projectConfig, projectName, json, _apiUrl) {
|
|
1504
|
+
const s = !json ? clack7.spinner() : null;
|
|
1505
|
+
s?.start("Downloading template...");
|
|
1506
|
+
try {
|
|
1507
|
+
const anonKey = await getAnonKey();
|
|
1508
|
+
if (!anonKey) {
|
|
1509
|
+
throw new Error("Failed to retrieve anon key from backend");
|
|
1510
|
+
}
|
|
1511
|
+
const tempDir = tmpdir();
|
|
1512
|
+
const targetDir = projectName;
|
|
1513
|
+
const templatePath = path3.join(tempDir, targetDir);
|
|
1514
|
+
try {
|
|
1515
|
+
await fs3.rm(templatePath, { recursive: true, force: true });
|
|
1516
|
+
} catch {
|
|
1517
|
+
}
|
|
1518
|
+
const frame = framework === "nextjs" ? "nextjs" : "react";
|
|
1519
|
+
const esc = (s2) => process.platform === "win32" ? `"${s2.replace(/"/g, '\\"')}"` : `'${s2.replace(/'/g, "'\\''")}'`;
|
|
1520
|
+
const command = `npx --yes create-insforge-app@latest ${esc(targetDir)} --frame ${frame} --base-url ${esc(projectConfig.oss_host)} --anon-key ${esc(anonKey)} --skip-install`;
|
|
1521
|
+
s?.message(`Running create-insforge-app (${frame})...`);
|
|
1522
|
+
await execAsync2(command, {
|
|
1523
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1524
|
+
cwd: tempDir
|
|
1525
|
+
});
|
|
1526
|
+
s?.message("Copying template files...");
|
|
1527
|
+
const cwd = process.cwd();
|
|
1528
|
+
await copyDir(templatePath, cwd);
|
|
1529
|
+
await fs3.rm(templatePath, { recursive: true, force: true }).catch(() => {
|
|
1530
|
+
});
|
|
1531
|
+
s?.stop("Template files downloaded");
|
|
1532
|
+
} catch (err) {
|
|
1533
|
+
s?.stop("Template download failed");
|
|
1534
|
+
if (!json) {
|
|
1535
|
+
clack7.log.warn(`Failed to download template: ${err.message}`);
|
|
1536
|
+
clack7.log.info("You can manually set up the template later.");
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
async function downloadGitHubTemplate(templateName, projectConfig, json) {
|
|
1541
|
+
const s = !json ? clack7.spinner() : null;
|
|
1542
|
+
s?.start(`Downloading ${templateName} template...`);
|
|
1543
|
+
const tempDir = path3.join(tmpdir(), `insforge-template-${Date.now()}`);
|
|
1544
|
+
try {
|
|
1545
|
+
await fs3.mkdir(tempDir, { recursive: true });
|
|
1546
|
+
await execAsync2(
|
|
1547
|
+
"git clone --depth 1 https://github.com/InsForge/insforge-templates.git .",
|
|
1548
|
+
{ cwd: tempDir, maxBuffer: 10 * 1024 * 1024, timeout: 6e4 }
|
|
1549
|
+
);
|
|
1550
|
+
const templateDir = path3.join(tempDir, templateName);
|
|
1551
|
+
const stat5 = await fs3.stat(templateDir).catch(() => null);
|
|
1552
|
+
if (!stat5?.isDirectory()) {
|
|
1553
|
+
throw new Error(`Template "${templateName}" not found in repository`);
|
|
1554
|
+
}
|
|
1555
|
+
s?.message("Copying template files...");
|
|
1556
|
+
const cwd = process.cwd();
|
|
1557
|
+
await copyDir(templateDir, cwd);
|
|
1558
|
+
const envExamplePath = path3.join(cwd, ".env.example");
|
|
1559
|
+
const envExampleExists = await fs3.stat(envExamplePath).catch(() => null);
|
|
1560
|
+
if (envExampleExists) {
|
|
1561
|
+
const anonKey = await getAnonKey();
|
|
1562
|
+
const envExample = await fs3.readFile(envExamplePath, "utf-8");
|
|
1563
|
+
const envContent = envExample.replace(
|
|
1564
|
+
/^([A-Z][A-Z0-9_]*=)(.*)$/gm,
|
|
1565
|
+
(_, prefix, _value) => {
|
|
1566
|
+
const key = prefix.slice(0, -1);
|
|
1567
|
+
if (/INSFORGE.*(URL|BASE_URL)$/.test(key)) return `${prefix}${projectConfig.oss_host}`;
|
|
1568
|
+
if (/INSFORGE.*ANON_KEY$/.test(key)) return `${prefix}${anonKey}`;
|
|
1569
|
+
if (key === "NEXT_PUBLIC_APP_URL") return `${prefix}https://${projectConfig.appkey}.insforge.site`;
|
|
1570
|
+
return `${prefix}${_value}`;
|
|
1571
|
+
}
|
|
1572
|
+
);
|
|
1573
|
+
const envLocalPath = path3.join(cwd, ".env.local");
|
|
1574
|
+
try {
|
|
1575
|
+
await fs3.writeFile(envLocalPath, envContent, { flag: "wx" });
|
|
1576
|
+
} catch (e) {
|
|
1577
|
+
if (e.code === "EEXIST") {
|
|
1578
|
+
if (!json) clack7.log.warn(".env.local already exists; skipping env seeding.");
|
|
1579
|
+
} else {
|
|
1580
|
+
throw e;
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
s?.stop(`${templateName} template downloaded`);
|
|
1585
|
+
const migrationPath = path3.join(cwd, "migrations", "db_init.sql");
|
|
1586
|
+
const migrationExists = await fs3.stat(migrationPath).catch(() => null);
|
|
1587
|
+
if (migrationExists) {
|
|
1588
|
+
const dbSpinner = !json ? clack7.spinner() : null;
|
|
1589
|
+
dbSpinner?.start("Running database migrations...");
|
|
1590
|
+
try {
|
|
1591
|
+
const sql = await fs3.readFile(migrationPath, "utf-8");
|
|
1592
|
+
await runRawSql(sql, true);
|
|
1593
|
+
dbSpinner?.stop("Database migrations applied");
|
|
1594
|
+
} catch (err) {
|
|
1595
|
+
dbSpinner?.stop("Database migration failed");
|
|
1596
|
+
if (!json) {
|
|
1597
|
+
clack7.log.warn(`Migration failed: ${err.message}`);
|
|
1598
|
+
clack7.log.info('You can run the migration manually: npx @insforge/cli db query --unrestricted "$(cat migrations/db_init.sql)"');
|
|
1599
|
+
} else {
|
|
1600
|
+
throw err;
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
} catch (err) {
|
|
1605
|
+
s?.stop(`${templateName} template download failed`);
|
|
1606
|
+
if (!json) {
|
|
1607
|
+
clack7.log.warn(`Failed to download ${templateName} template: ${err.message}`);
|
|
1608
|
+
clack7.log.info("You can manually clone from: https://github.com/InsForge/insforge-templates");
|
|
1609
|
+
}
|
|
1610
|
+
} finally {
|
|
1611
|
+
await fs3.rm(tempDir, { recursive: true, force: true }).catch(() => {
|
|
1612
|
+
});
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
// src/commands/projects/link.ts
|
|
1617
|
+
var execAsync3 = promisify3(exec3);
|
|
1618
|
+
function buildOssHost2(appkey, region) {
|
|
1619
|
+
return `https://${appkey}.${region}.insforge.app`;
|
|
1620
|
+
}
|
|
1621
|
+
function registerProjectLinkCommand(program2) {
|
|
1622
|
+
program2.command("link").description("Link current directory to an InsForge project").option("--project-id <id>", "Project ID to link").option("--org-id <id>", "Organization ID").option("--template <template>", "Download a template after linking: react, nextjs, chatbot, crm, e-commerce, todo").option("--api-base-url <url>", "API Base URL for direct linking (OSS/Self-hosted)").option("--api-key <key>", "API Key for direct linking (OSS/Self-hosted)").action(async (opts, cmd) => {
|
|
1623
|
+
const { json, apiUrl } = getRootOpts(cmd);
|
|
1624
|
+
try {
|
|
1625
|
+
if (opts.apiBaseUrl || opts.apiKey) {
|
|
1626
|
+
try {
|
|
1627
|
+
if (!opts.apiBaseUrl || !opts.apiKey) {
|
|
1628
|
+
throw new CLIError("Both --api-base-url and --api-key must be provided together for direct linking.");
|
|
1629
|
+
}
|
|
1630
|
+
try {
|
|
1631
|
+
new URL(opts.apiBaseUrl);
|
|
1632
|
+
} catch {
|
|
1633
|
+
throw new CLIError("Invalid --api-base-url. Please provide a valid URL.");
|
|
1634
|
+
}
|
|
1635
|
+
const projectConfig2 = {
|
|
1636
|
+
project_id: "oss-project",
|
|
1637
|
+
project_name: "oss-project",
|
|
1638
|
+
org_id: "oss-org",
|
|
1639
|
+
appkey: "oss",
|
|
1640
|
+
region: "local",
|
|
1641
|
+
api_key: opts.apiKey,
|
|
1642
|
+
oss_host: opts.apiBaseUrl.replace(/\/$/, "")
|
|
1643
|
+
// remove trailing slash if any
|
|
1644
|
+
};
|
|
1645
|
+
saveProjectConfig(projectConfig2);
|
|
1646
|
+
if (json) {
|
|
1647
|
+
outputJson({ success: true, project: { id: projectConfig2.project_id, name: projectConfig2.project_name, region: projectConfig2.region } });
|
|
1648
|
+
} else {
|
|
1649
|
+
outputSuccess(`Linked to direct project at ${projectConfig2.oss_host}`);
|
|
1650
|
+
}
|
|
1651
|
+
trackCommand("link", "oss-org", { direct: true });
|
|
1652
|
+
await installSkills(json);
|
|
1653
|
+
await reportCliUsage("cli.link_direct", true, 6);
|
|
1654
|
+
try {
|
|
1655
|
+
const urlMatch = opts.apiBaseUrl.match(/^https?:\/\/([^.]+)\.[^.]+\.insforge\.app/);
|
|
1656
|
+
if (urlMatch) {
|
|
1657
|
+
await reportAgentConnected({ app_key: urlMatch[1] }, apiUrl);
|
|
1658
|
+
}
|
|
1659
|
+
} catch {
|
|
1660
|
+
}
|
|
1661
|
+
return;
|
|
1662
|
+
} catch (err) {
|
|
1663
|
+
await reportCliUsage("cli.link_direct", false);
|
|
1664
|
+
handleError(err, json);
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
const creds = await requireAuth(apiUrl, false);
|
|
1668
|
+
let orgId = opts.orgId;
|
|
1669
|
+
let projectId = opts.projectId;
|
|
1670
|
+
if (!orgId && !projectId) {
|
|
1671
|
+
const orgs = await listOrganizations(apiUrl);
|
|
1672
|
+
if (orgs.length === 0) {
|
|
1673
|
+
throw new CLIError("No organizations found.");
|
|
1674
|
+
}
|
|
1675
|
+
if (orgs.length === 1) {
|
|
1676
|
+
orgId = orgs[0].id;
|
|
1677
|
+
if (!json) clack8.log.info(`Using organization: ${orgs[0].name}`);
|
|
1678
|
+
} else {
|
|
1679
|
+
if (json) {
|
|
1680
|
+
throw new CLIError("Multiple organizations found. Specify --org-id.");
|
|
1681
|
+
}
|
|
1682
|
+
const selected = await clack8.select({
|
|
1683
|
+
message: "Select an organization:",
|
|
1684
|
+
options: orgs.map((o) => ({
|
|
1685
|
+
value: o.id,
|
|
1686
|
+
label: o.name
|
|
1687
|
+
}))
|
|
1688
|
+
});
|
|
1689
|
+
if (clack8.isCancel(selected)) process.exit(0);
|
|
1690
|
+
orgId = selected;
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
const config = getGlobalConfig();
|
|
1694
|
+
config.default_org_id = orgId;
|
|
1695
|
+
saveGlobalConfig(config);
|
|
1696
|
+
if (!projectId) {
|
|
1697
|
+
const projects = await listProjects(orgId, apiUrl);
|
|
1698
|
+
if (projects.length === 0) {
|
|
1699
|
+
throw new CLIError("No projects found in this organization.");
|
|
1700
|
+
}
|
|
1701
|
+
if (json) {
|
|
1702
|
+
throw new CLIError("Specify --project-id in JSON mode.");
|
|
1703
|
+
}
|
|
1704
|
+
const selected = await clack8.select({
|
|
1705
|
+
message: "Select a project to link:",
|
|
1706
|
+
options: projects.map((p) => ({
|
|
1707
|
+
value: p.id,
|
|
1708
|
+
label: `${p.name} (${p.region}, ${p.status})`
|
|
1709
|
+
}))
|
|
1710
|
+
});
|
|
1711
|
+
if (clack8.isCancel(selected)) process.exit(0);
|
|
1712
|
+
projectId = selected;
|
|
1713
|
+
}
|
|
1714
|
+
let project;
|
|
1715
|
+
let apiKey;
|
|
1716
|
+
try {
|
|
1717
|
+
[project, apiKey] = await Promise.all([
|
|
1718
|
+
getProject(projectId, apiUrl),
|
|
1719
|
+
getProjectApiKey(projectId, apiUrl)
|
|
1720
|
+
]);
|
|
1721
|
+
} catch (err) {
|
|
1722
|
+
if (err instanceof CLIError && (err.exitCode === 5 || err.exitCode === 4 || err.message.includes("not found"))) {
|
|
1723
|
+
const identity = creds.user?.email ?? creds.user?.name ?? "unknown user";
|
|
1724
|
+
throw new CLIError(
|
|
1725
|
+
`You're logged in as ${identity}, and you don't have access to project ${projectId}. Check that the project ID is correct and belongs to one of your organizations.`,
|
|
1726
|
+
5,
|
|
1727
|
+
"PERMISSION_DENIED"
|
|
1728
|
+
);
|
|
1729
|
+
}
|
|
1730
|
+
throw err;
|
|
1731
|
+
}
|
|
1732
|
+
const projectConfig = {
|
|
1733
|
+
project_id: project.id,
|
|
1734
|
+
project_name: project.name,
|
|
1735
|
+
org_id: project.organization_id,
|
|
1736
|
+
appkey: project.appkey,
|
|
1737
|
+
region: project.region,
|
|
1738
|
+
api_key: apiKey,
|
|
1739
|
+
oss_host: buildOssHost2(project.appkey, project.region)
|
|
1740
|
+
};
|
|
1741
|
+
if (!opts.template) {
|
|
1742
|
+
saveProjectConfig(projectConfig);
|
|
1743
|
+
}
|
|
1744
|
+
trackCommand("link", project.organization_id);
|
|
1745
|
+
if (json) {
|
|
1746
|
+
outputJson({ success: true, project: { id: project.id, name: project.name, region: project.region } });
|
|
1747
|
+
} else {
|
|
1748
|
+
outputSuccess(`Linked to project "${project.name}" (${project.appkey}.${project.region})`);
|
|
1749
|
+
}
|
|
1750
|
+
await installSkills(json);
|
|
1751
|
+
await reportCliUsage("cli.link", true, 6);
|
|
1752
|
+
try {
|
|
1753
|
+
await reportAgentConnected({ project_id: project.id }, apiUrl);
|
|
1754
|
+
} catch {
|
|
1755
|
+
}
|
|
1756
|
+
const template = opts.template;
|
|
1757
|
+
if (template) {
|
|
1758
|
+
const validTemplates = ["react", "nextjs", "chatbot", "crm", "e-commerce", "todo"];
|
|
1759
|
+
if (!validTemplates.includes(template)) {
|
|
1760
|
+
throw new CLIError(`Invalid template "${template}". Valid options: ${validTemplates.join(", ")}`);
|
|
1761
|
+
}
|
|
1762
|
+
let dirName = project.name;
|
|
1763
|
+
if (!json) {
|
|
1764
|
+
const inputDir = await clack8.text({
|
|
1765
|
+
message: "Directory name:",
|
|
1766
|
+
initialValue: project.name,
|
|
1767
|
+
validate: (v) => {
|
|
1768
|
+
if (v.length < 1) return "Directory name is required";
|
|
1769
|
+
const normalized = path4.basename(v).replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
1770
|
+
if (!normalized || normalized === "." || normalized === "..") return "Invalid directory name";
|
|
1771
|
+
return void 0;
|
|
1772
|
+
}
|
|
1773
|
+
});
|
|
1774
|
+
if (clack8.isCancel(inputDir)) process.exit(0);
|
|
1775
|
+
dirName = path4.basename(inputDir).replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
1776
|
+
}
|
|
1777
|
+
if (!dirName || dirName === "." || dirName === "..") {
|
|
1778
|
+
throw new CLIError("Invalid directory name.");
|
|
1779
|
+
}
|
|
1780
|
+
const templateDir = path4.resolve(process.cwd(), dirName);
|
|
1781
|
+
const dirExists = await fs4.stat(templateDir).catch(() => null);
|
|
1782
|
+
if (dirExists) {
|
|
1783
|
+
throw new CLIError(`Directory "${dirName}" already exists.`);
|
|
1784
|
+
}
|
|
1785
|
+
await fs4.mkdir(templateDir);
|
|
1786
|
+
process.chdir(templateDir);
|
|
1787
|
+
saveProjectConfig(projectConfig);
|
|
1788
|
+
captureEvent(orgId ?? project.organization_id, "template_selected", { template, source: "link" });
|
|
1789
|
+
const githubTemplates = ["chatbot", "crm", "e-commerce", "nextjs", "react", "todo"];
|
|
1790
|
+
if (githubTemplates.includes(template)) {
|
|
1791
|
+
await downloadGitHubTemplate(template, projectConfig, json);
|
|
1792
|
+
} else {
|
|
1793
|
+
await downloadTemplate(template, projectConfig, project.name, json, apiUrl);
|
|
1794
|
+
}
|
|
1795
|
+
const templateDownloaded = await fs4.stat(path4.join(process.cwd(), "package.json")).catch(() => null);
|
|
1796
|
+
if (templateDownloaded && !json) {
|
|
1797
|
+
const installSpinner = clack8.spinner();
|
|
1798
|
+
installSpinner.start("Installing dependencies...");
|
|
1799
|
+
try {
|
|
1800
|
+
await execAsync3("npm install", { cwd: process.cwd(), maxBuffer: 10 * 1024 * 1024 });
|
|
1801
|
+
installSpinner.stop("Dependencies installed");
|
|
1802
|
+
} catch (err) {
|
|
1803
|
+
installSpinner.stop("Failed to install dependencies");
|
|
1804
|
+
clack8.log.warn(`npm install failed: ${err.message}`);
|
|
1805
|
+
clack8.log.info("Run `npm install` manually to install dependencies.");
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
if (!json) {
|
|
1809
|
+
const dashboardUrl = `${getFrontendUrl()}/dashboard/project/${project.id}`;
|
|
1810
|
+
clack8.log.step(`Dashboard: ${dashboardUrl}`);
|
|
1811
|
+
if (templateDownloaded) {
|
|
1812
|
+
const steps = [`cd ${dirName}`, "npm run dev"];
|
|
1813
|
+
clack8.note(steps.join("\n"), "Next steps");
|
|
1814
|
+
clack8.note("Open your coding agent (Claude Code, Codex, Cursor, etc.) to add new features.", "Keep building");
|
|
1815
|
+
} else {
|
|
1816
|
+
clack8.log.warn("Template download failed. You can retry or set up manually.");
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
} else if (!json) {
|
|
1820
|
+
const dashboardUrl = `${getFrontendUrl()}/dashboard/project/${project.id}`;
|
|
1821
|
+
clack8.log.step(`Dashboard: ${dashboardUrl}`);
|
|
1822
|
+
const prompts = [
|
|
1823
|
+
"Build a todo app with Google OAuth sign-in",
|
|
1824
|
+
"Build an Instagram clone where users can upload photos, like, and comment",
|
|
1825
|
+
"Build an AI chatbot with conversation history"
|
|
1826
|
+
];
|
|
1827
|
+
clack8.note(
|
|
1828
|
+
`Open your coding agent (Claude Code, Codex, Cursor, etc.) and try:
|
|
1829
|
+
|
|
1830
|
+
${prompts.map((p) => `\u2022 "${p}"`).join("\n")}`,
|
|
1831
|
+
"Start building"
|
|
1832
|
+
);
|
|
1833
|
+
}
|
|
1834
|
+
} catch (err) {
|
|
1835
|
+
await reportCliUsage("cli.link", false);
|
|
1836
|
+
handleError(err, json);
|
|
1837
|
+
} finally {
|
|
1838
|
+
await shutdownAnalytics();
|
|
1839
|
+
}
|
|
1840
|
+
});
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
// src/commands/db/query.ts
|
|
1844
|
+
function registerDbCommands(dbCmd2) {
|
|
1845
|
+
dbCmd2.command("query <sql>").description("Execute a SQL query against the database").option("--unrestricted", "Use unrestricted mode (allows system table access)").action(async (sql, opts, cmd) => {
|
|
1846
|
+
const { json } = getRootOpts(cmd);
|
|
1847
|
+
try {
|
|
1848
|
+
await requireAuth();
|
|
1849
|
+
const { rows, raw } = await runRawSql(sql, !!opts.unrestricted);
|
|
1850
|
+
if (json) {
|
|
1851
|
+
outputJson(raw);
|
|
1852
|
+
} else {
|
|
1853
|
+
if (rows.length > 0) {
|
|
1854
|
+
const headers = Object.keys(rows[0]);
|
|
1855
|
+
outputTable(
|
|
1856
|
+
headers,
|
|
1857
|
+
rows.map((row) => headers.map((h) => String(row[h] ?? "")))
|
|
1858
|
+
);
|
|
1859
|
+
console.log(`${rows.length} row(s) returned.`);
|
|
1860
|
+
} else {
|
|
1861
|
+
console.log("Query executed successfully.");
|
|
1862
|
+
if (rows.length === 0) {
|
|
1863
|
+
console.log("No rows returned.");
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
await reportCliUsage("cli.db.query", true);
|
|
1868
|
+
} catch (err) {
|
|
1869
|
+
await reportCliUsage("cli.db.query", false);
|
|
1870
|
+
handleError(err, json);
|
|
1871
|
+
}
|
|
1872
|
+
});
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
// src/commands/db/tables.ts
|
|
1876
|
+
function registerDbTablesCommand(dbCmd2) {
|
|
1877
|
+
dbCmd2.command("tables").description("List all database tables").action(async (_opts, cmd) => {
|
|
1878
|
+
const { json } = getRootOpts(cmd);
|
|
1879
|
+
try {
|
|
1880
|
+
await requireAuth();
|
|
1881
|
+
const res = await ossFetch("/api/database/tables");
|
|
1882
|
+
const tables = await res.json();
|
|
1883
|
+
if (json) {
|
|
1884
|
+
outputJson(tables);
|
|
1885
|
+
} else {
|
|
1886
|
+
if (tables.length === 0) {
|
|
1887
|
+
console.log("No tables found.");
|
|
1888
|
+
return;
|
|
1889
|
+
}
|
|
1890
|
+
outputTable(
|
|
1891
|
+
["Table Name"],
|
|
1892
|
+
tables.map((t) => [t])
|
|
1893
|
+
);
|
|
1894
|
+
}
|
|
1895
|
+
await reportCliUsage("cli.db.tables", true);
|
|
1896
|
+
} catch (err) {
|
|
1897
|
+
await reportCliUsage("cli.db.tables", false);
|
|
1898
|
+
handleError(err, json);
|
|
1899
|
+
}
|
|
1900
|
+
});
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
// src/commands/db/functions.ts
|
|
1904
|
+
function registerDbFunctionsCommand(dbCmd2) {
|
|
1905
|
+
dbCmd2.command("functions").description("List all database functions").action(async (_opts, cmd) => {
|
|
1906
|
+
const { json } = getRootOpts(cmd);
|
|
1907
|
+
try {
|
|
1908
|
+
await requireAuth();
|
|
1909
|
+
const res = await ossFetch("/api/database/functions");
|
|
1910
|
+
const raw = await res.json();
|
|
1911
|
+
const functions = Array.isArray(raw) ? raw : raw.functions ?? [];
|
|
1912
|
+
if (json) {
|
|
1913
|
+
outputJson(raw);
|
|
1914
|
+
} else {
|
|
1915
|
+
if (functions.length === 0) {
|
|
1916
|
+
console.log("No database functions found.");
|
|
1917
|
+
return;
|
|
1918
|
+
}
|
|
1919
|
+
outputTable(
|
|
1920
|
+
["Name", "Definition", "Kind"],
|
|
1921
|
+
functions.map((f) => [f.functionName, f.functionDef, f.kind])
|
|
1922
|
+
);
|
|
1923
|
+
}
|
|
1924
|
+
await reportCliUsage("cli.db.functions", true);
|
|
1925
|
+
} catch (err) {
|
|
1926
|
+
await reportCliUsage("cli.db.functions", false);
|
|
1927
|
+
handleError(err, json);
|
|
1928
|
+
}
|
|
1929
|
+
});
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
// src/commands/db/indexes.ts
|
|
1933
|
+
function registerDbIndexesCommand(dbCmd2) {
|
|
1934
|
+
dbCmd2.command("indexes").description("List all database indexes").action(async (_opts, cmd) => {
|
|
1935
|
+
const { json } = getRootOpts(cmd);
|
|
1936
|
+
try {
|
|
1937
|
+
await requireAuth();
|
|
1938
|
+
const res = await ossFetch("/api/database/indexes");
|
|
1939
|
+
const raw = await res.json();
|
|
1940
|
+
const indexes = Array.isArray(raw) ? raw : raw.indexes ?? [];
|
|
1941
|
+
if (json) {
|
|
1942
|
+
outputJson(raw);
|
|
1943
|
+
} else {
|
|
1944
|
+
if (indexes.length === 0) {
|
|
1945
|
+
console.log("No database indexes found.");
|
|
1946
|
+
return;
|
|
1947
|
+
}
|
|
1948
|
+
outputTable(
|
|
1949
|
+
["Table", "Index Name", "Definition", "Unique", "Primary"],
|
|
1950
|
+
indexes.map((i) => [
|
|
1951
|
+
i.tableName,
|
|
1952
|
+
i.indexName,
|
|
1953
|
+
i.indexDef,
|
|
1954
|
+
i.isUnique ? "Yes" : "No",
|
|
1955
|
+
i.isPrimary ? "Yes" : "No"
|
|
1956
|
+
])
|
|
1957
|
+
);
|
|
1958
|
+
}
|
|
1959
|
+
await reportCliUsage("cli.db.indexes", true);
|
|
1960
|
+
} catch (err) {
|
|
1961
|
+
await reportCliUsage("cli.db.indexes", false);
|
|
1962
|
+
handleError(err, json);
|
|
1963
|
+
}
|
|
1964
|
+
});
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
// src/commands/db/policies.ts
|
|
1968
|
+
function registerDbPoliciesCommand(dbCmd2) {
|
|
1969
|
+
dbCmd2.command("policies").description("List all RLS policies").action(async (_opts, cmd) => {
|
|
1970
|
+
const { json } = getRootOpts(cmd);
|
|
1971
|
+
try {
|
|
1972
|
+
await requireAuth();
|
|
1973
|
+
const res = await ossFetch("/api/database/policies");
|
|
1974
|
+
const raw = await res.json();
|
|
1975
|
+
const policies = Array.isArray(raw) ? raw : raw.policies ?? [];
|
|
1976
|
+
if (json) {
|
|
1977
|
+
outputJson(raw);
|
|
1978
|
+
} else {
|
|
1979
|
+
if (policies.length === 0) {
|
|
1980
|
+
console.log("No RLS policies found.");
|
|
1981
|
+
return;
|
|
1982
|
+
}
|
|
1983
|
+
outputTable(
|
|
1984
|
+
["Table", "Policy Name", "Command", "Roles", "Qual", "With Check"],
|
|
1985
|
+
policies.map((p) => [
|
|
1986
|
+
String(p.tableName ?? "-"),
|
|
1987
|
+
String(p.policyName ?? "-"),
|
|
1988
|
+
String(p.cmd ?? "-"),
|
|
1989
|
+
Array.isArray(p.roles) ? p.roles.join(", ") : String(p.roles ?? "-"),
|
|
1990
|
+
String(p.qual ?? "-"),
|
|
1991
|
+
String(p.withCheck ?? "-")
|
|
1992
|
+
])
|
|
1993
|
+
);
|
|
1994
|
+
}
|
|
1995
|
+
await reportCliUsage("cli.db.policies", true);
|
|
1996
|
+
} catch (err) {
|
|
1997
|
+
await reportCliUsage("cli.db.policies", false);
|
|
1998
|
+
handleError(err, json);
|
|
1999
|
+
}
|
|
2000
|
+
});
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
// src/commands/db/triggers.ts
|
|
2004
|
+
function registerDbTriggersCommand(dbCmd2) {
|
|
2005
|
+
dbCmd2.command("triggers").description("List all database triggers").action(async (_opts, cmd) => {
|
|
2006
|
+
const { json } = getRootOpts(cmd);
|
|
2007
|
+
try {
|
|
2008
|
+
await requireAuth();
|
|
2009
|
+
const res = await ossFetch("/api/database/triggers");
|
|
2010
|
+
const raw = await res.json();
|
|
2011
|
+
const triggers = Array.isArray(raw) ? raw : raw.triggers ?? [];
|
|
2012
|
+
if (json) {
|
|
2013
|
+
outputJson(raw);
|
|
2014
|
+
} else {
|
|
2015
|
+
if (triggers.length === 0) {
|
|
2016
|
+
console.log("No database triggers found.");
|
|
2017
|
+
return;
|
|
2018
|
+
}
|
|
2019
|
+
outputTable(
|
|
2020
|
+
["Name", "Table", "Timing", "Events", "ActionOrientation", "ActionCondition", "ActionStatement"],
|
|
2021
|
+
triggers.map((t) => [
|
|
2022
|
+
t.triggerName,
|
|
2023
|
+
t.tableName,
|
|
2024
|
+
t.actionTiming,
|
|
2025
|
+
t.eventManipulation,
|
|
2026
|
+
t.actionOrientation,
|
|
2027
|
+
t.actionCondition ?? "-",
|
|
2028
|
+
t.actionStatement
|
|
2029
|
+
])
|
|
2030
|
+
);
|
|
2031
|
+
}
|
|
2032
|
+
await reportCliUsage("cli.db.triggers", true);
|
|
2033
|
+
} catch (err) {
|
|
2034
|
+
await reportCliUsage("cli.db.triggers", false);
|
|
2035
|
+
handleError(err, json);
|
|
2036
|
+
}
|
|
2037
|
+
});
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
// src/commands/db/rpc.ts
|
|
2041
|
+
function registerDbRpcCommand(dbCmd2) {
|
|
2042
|
+
dbCmd2.command("rpc <functionName>").description("Call a database function via RPC").option("--data <json>", "JSON body to pass as function parameters").action(async (functionName, opts, cmd) => {
|
|
2043
|
+
const { json } = getRootOpts(cmd);
|
|
2044
|
+
try {
|
|
2045
|
+
await requireAuth();
|
|
2046
|
+
const body = opts.data ? JSON.stringify(JSON.parse(opts.data)) : void 0;
|
|
2047
|
+
const res = await ossFetch(`/api/database/rpc/${encodeURIComponent(functionName)}`, {
|
|
2048
|
+
method: body ? "POST" : "GET",
|
|
2049
|
+
...body ? { body } : {}
|
|
2050
|
+
});
|
|
2051
|
+
const result = await res.json();
|
|
2052
|
+
if (json) {
|
|
2053
|
+
outputJson(result);
|
|
2054
|
+
} else {
|
|
2055
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2056
|
+
}
|
|
2057
|
+
await reportCliUsage("cli.db.rpc", true);
|
|
2058
|
+
} catch (err) {
|
|
2059
|
+
handleError(err, json);
|
|
2060
|
+
}
|
|
2061
|
+
});
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
// src/commands/db/export.ts
|
|
2065
|
+
import { writeFileSync as writeFileSync2 } from "fs";
|
|
2066
|
+
function registerDbExportCommand(dbCmd2) {
|
|
2067
|
+
dbCmd2.command("export").description("Export database schema and/or data").option("--format <format>", "Export format: sql or json", "sql").option("--tables <tables>", "Comma-separated list of tables to export (default: all)").option("--no-data", "Exclude table data (schema only)").option("--include-functions", "Include database functions").option("--include-sequences", "Include sequences").option("--include-views", "Include views").option("--row-limit <n>", "Maximum rows per table").option("-o, --output <file>", "Output file path (default: stdout)").action(async (opts, cmd) => {
|
|
2068
|
+
const { json } = getRootOpts(cmd);
|
|
2069
|
+
try {
|
|
2070
|
+
await requireAuth();
|
|
2071
|
+
const body = {
|
|
2072
|
+
format: opts.format,
|
|
2073
|
+
includeData: opts.data !== false
|
|
2074
|
+
};
|
|
2075
|
+
if (opts.tables) {
|
|
2076
|
+
body.tables = opts.tables.split(",").map((t) => t.trim());
|
|
1262
2077
|
}
|
|
1263
2078
|
if (opts.includeFunctions) body.includeFunctions = true;
|
|
1264
2079
|
if (opts.includeSequences) body.includeSequences = true;
|
|
@@ -1302,7 +2117,7 @@ function registerDbExportCommand(dbCmd2) {
|
|
|
1302
2117
|
|
|
1303
2118
|
// src/commands/db/import.ts
|
|
1304
2119
|
import { readFileSync as readFileSync3 } from "fs";
|
|
1305
|
-
import { basename } from "path";
|
|
2120
|
+
import { basename as basename4 } from "path";
|
|
1306
2121
|
function registerDbImportCommand(dbCmd2) {
|
|
1307
2122
|
dbCmd2.command("import <file>").description("Import database from a local SQL file").option("--truncate", "Truncate existing tables before import").action(async (file, opts, cmd) => {
|
|
1308
2123
|
const { json } = getRootOpts(cmd);
|
|
@@ -1311,7 +2126,7 @@ function registerDbImportCommand(dbCmd2) {
|
|
|
1311
2126
|
const config = getProjectConfig();
|
|
1312
2127
|
if (!config) throw new ProjectNotLinkedError();
|
|
1313
2128
|
const fileContent = readFileSync3(file);
|
|
1314
|
-
const fileName =
|
|
2129
|
+
const fileName = basename4(file);
|
|
1315
2130
|
const formData = new FormData();
|
|
1316
2131
|
formData.append("file", new Blob([fileContent]), fileName);
|
|
1317
2132
|
if (opts.truncate) {
|
|
@@ -1353,8 +2168,8 @@ function registerRecordsCommands(recordsCmd2) {
|
|
|
1353
2168
|
if (opts.limit) params.set("limit", String(opts.limit));
|
|
1354
2169
|
if (opts.offset) params.set("offset", String(opts.offset));
|
|
1355
2170
|
const query = params.toString();
|
|
1356
|
-
const
|
|
1357
|
-
const res = await ossFetch(
|
|
2171
|
+
const path5 = `/api/database/records/${encodeURIComponent(table)}${query ? `?${query}` : ""}`;
|
|
2172
|
+
const res = await ossFetch(path5);
|
|
1358
2173
|
const data = await res.json();
|
|
1359
2174
|
const records = data.data ?? [];
|
|
1360
2175
|
if (json) {
|
|
@@ -1524,17 +2339,17 @@ function registerFunctionsCommands(functionsCmd2) {
|
|
|
1524
2339
|
|
|
1525
2340
|
// src/commands/functions/deploy.ts
|
|
1526
2341
|
import { readFileSync as readFileSync4, existsSync as existsSync3 } from "fs";
|
|
1527
|
-
import { join as
|
|
2342
|
+
import { join as join6 } from "path";
|
|
1528
2343
|
function registerFunctionsDeployCommand(functionsCmd2) {
|
|
1529
2344
|
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) => {
|
|
1530
2345
|
const { json } = getRootOpts(cmd);
|
|
1531
2346
|
try {
|
|
1532
2347
|
await requireAuth();
|
|
1533
|
-
const filePath = opts.file ??
|
|
2348
|
+
const filePath = opts.file ?? join6(process.cwd(), "insforge", "functions", slug, "index.ts");
|
|
1534
2349
|
if (!existsSync3(filePath)) {
|
|
1535
2350
|
throw new CLIError(
|
|
1536
2351
|
`Source file not found: ${filePath}
|
|
1537
|
-
Specify --file <path> or create ${
|
|
2352
|
+
Specify --file <path> or create ${join6("insforge", "functions", slug, "index.ts")}`
|
|
1538
2353
|
);
|
|
1539
2354
|
}
|
|
1540
2355
|
const code = readFileSync4(filePath, "utf-8");
|
|
@@ -1616,239 +2431,16 @@ function registerFunctionsInvokeCommand(functionsCmd2) {
|
|
|
1616
2431
|
if (contentType.includes("application/json")) {
|
|
1617
2432
|
const data = await res.json();
|
|
1618
2433
|
if (json) {
|
|
1619
|
-
outputJson({ status, body: data });
|
|
1620
|
-
} else {
|
|
1621
|
-
console.log(JSON.stringify(data, null, 2));
|
|
1622
|
-
}
|
|
1623
|
-
} else {
|
|
1624
|
-
const text3 = await res.text();
|
|
1625
|
-
console.log(text3);
|
|
1626
|
-
}
|
|
1627
|
-
if (status >= 400) {
|
|
1628
|
-
throw new CLIError(`HTTP ${status}`, 1, "HTTP_ERROR");
|
|
1629
|
-
}
|
|
1630
|
-
} catch (err) {
|
|
1631
|
-
handleError(err, json);
|
|
1632
|
-
}
|
|
1633
|
-
});
|
|
1634
|
-
}
|
|
1635
|
-
|
|
1636
|
-
// src/commands/functions/code.ts
|
|
1637
|
-
function registerFunctionsCodeCommand(functionsCmd2) {
|
|
1638
|
-
functionsCmd2.command("code <slug>").description("Fetch and display the source code of an edge function").action(async (slug, _opts, cmd) => {
|
|
1639
|
-
const { json } = getRootOpts(cmd);
|
|
1640
|
-
try {
|
|
1641
|
-
await requireAuth();
|
|
1642
|
-
const res = await ossFetch(`/api/functions/${encodeURIComponent(slug)}`);
|
|
1643
|
-
const fn = await res.json();
|
|
1644
|
-
if (json) {
|
|
1645
|
-
outputJson(fn);
|
|
1646
|
-
} else {
|
|
1647
|
-
console.log(`Function: ${fn.name} (${fn.slug})`);
|
|
1648
|
-
console.log(`Status: ${fn.status}`);
|
|
1649
|
-
if (fn.description) console.log(`Desc: ${fn.description}`);
|
|
1650
|
-
if (fn.deployed_at) console.log(`Deployed: ${fn.deployed_at}`);
|
|
1651
|
-
console.log("---");
|
|
1652
|
-
console.log(fn.code);
|
|
1653
|
-
}
|
|
1654
|
-
} catch (err) {
|
|
1655
|
-
handleError(err, json);
|
|
1656
|
-
}
|
|
1657
|
-
});
|
|
1658
|
-
}
|
|
1659
|
-
|
|
1660
|
-
// src/commands/functions/delete.ts
|
|
1661
|
-
import * as clack7 from "@clack/prompts";
|
|
1662
|
-
function registerFunctionsDeleteCommand(functionsCmd2) {
|
|
1663
|
-
functionsCmd2.command("delete <slug>").description("Delete an edge function").action(async (slug, _opts, cmd) => {
|
|
1664
|
-
const { json, yes } = getRootOpts(cmd);
|
|
1665
|
-
try {
|
|
1666
|
-
await requireAuth();
|
|
1667
|
-
if (!yes && !json) {
|
|
1668
|
-
const confirmed = await clack7.confirm({
|
|
1669
|
-
message: `Delete function "${slug}"? This cannot be undone.`
|
|
1670
|
-
});
|
|
1671
|
-
if (clack7.isCancel(confirmed) || !confirmed) {
|
|
1672
|
-
clack7.log.info("Cancelled.");
|
|
1673
|
-
return;
|
|
1674
|
-
}
|
|
1675
|
-
}
|
|
1676
|
-
const res = await ossFetch(`/api/functions/${encodeURIComponent(slug)}`, {
|
|
1677
|
-
method: "DELETE"
|
|
1678
|
-
});
|
|
1679
|
-
const result = await res.json();
|
|
1680
|
-
if (json) {
|
|
1681
|
-
outputJson(result);
|
|
1682
|
-
} else {
|
|
1683
|
-
if (result.success) {
|
|
1684
|
-
outputSuccess(`Function "${slug}" deleted successfully.`);
|
|
1685
|
-
} else {
|
|
1686
|
-
outputSuccess(`Failed to delete function "${slug}".`);
|
|
1687
|
-
}
|
|
1688
|
-
}
|
|
1689
|
-
await reportCliUsage("cli.functions.delete", true);
|
|
1690
|
-
} catch (err) {
|
|
1691
|
-
await reportCliUsage("cli.functions.delete", false);
|
|
1692
|
-
handleError(err, json);
|
|
1693
|
-
}
|
|
1694
|
-
});
|
|
1695
|
-
}
|
|
1696
|
-
|
|
1697
|
-
// src/commands/storage/buckets.ts
|
|
1698
|
-
function registerStorageBucketsCommand(storageCmd2) {
|
|
1699
|
-
storageCmd2.command("buckets").description("List all storage buckets").action(async (_opts, cmd) => {
|
|
1700
|
-
const { json } = getRootOpts(cmd);
|
|
1701
|
-
try {
|
|
1702
|
-
await requireAuth();
|
|
1703
|
-
const res = await ossFetch("/api/storage/buckets");
|
|
1704
|
-
const raw = await res.json();
|
|
1705
|
-
const buckets = Array.isArray(raw) ? raw : raw && typeof raw === "object" && "buckets" in raw ? raw.buckets ?? [] : [];
|
|
1706
|
-
if (json) {
|
|
1707
|
-
outputJson(raw);
|
|
1708
|
-
} else {
|
|
1709
|
-
if (buckets.length === 0) {
|
|
1710
|
-
console.log("No buckets found.");
|
|
1711
|
-
return;
|
|
1712
|
-
}
|
|
1713
|
-
outputTable(
|
|
1714
|
-
["Bucket Name", "Public"],
|
|
1715
|
-
buckets.map((b) => [b.name, b.public ? "Yes" : "No"])
|
|
1716
|
-
);
|
|
1717
|
-
}
|
|
1718
|
-
await reportCliUsage("cli.storage.buckets", true);
|
|
1719
|
-
} catch (err) {
|
|
1720
|
-
await reportCliUsage("cli.storage.buckets", false);
|
|
1721
|
-
handleError(err, json);
|
|
1722
|
-
}
|
|
1723
|
-
});
|
|
1724
|
-
}
|
|
1725
|
-
|
|
1726
|
-
// src/commands/storage/upload.ts
|
|
1727
|
-
import { readFileSync as readFileSync5, existsSync as existsSync4 } from "fs";
|
|
1728
|
-
import { basename as basename2 } from "path";
|
|
1729
|
-
function registerStorageUploadCommand(storageCmd2) {
|
|
1730
|
-
storageCmd2.command("upload <file>").description("Upload a file to a storage bucket").requiredOption("--bucket <name>", "Target bucket name").option("--key <objectKey>", "Object key (defaults to filename)").action(async (file, opts, cmd) => {
|
|
1731
|
-
const { json } = getRootOpts(cmd);
|
|
1732
|
-
try {
|
|
1733
|
-
await requireAuth();
|
|
1734
|
-
const config = getProjectConfig();
|
|
1735
|
-
if (!config) throw new ProjectNotLinkedError();
|
|
1736
|
-
if (!existsSync4(file)) {
|
|
1737
|
-
throw new CLIError(`File not found: ${file}`);
|
|
1738
|
-
}
|
|
1739
|
-
const fileContent = readFileSync5(file);
|
|
1740
|
-
const objectKey = opts.key ?? basename2(file);
|
|
1741
|
-
const bucketName = opts.bucket;
|
|
1742
|
-
const formData = new FormData();
|
|
1743
|
-
const blob = new Blob([fileContent]);
|
|
1744
|
-
formData.append("file", blob, objectKey);
|
|
1745
|
-
const url = `${config.oss_host}/api/storage/buckets/${encodeURIComponent(bucketName)}/objects/${encodeURIComponent(objectKey)}`;
|
|
1746
|
-
const res = await fetch(url, {
|
|
1747
|
-
method: "PUT",
|
|
1748
|
-
headers: {
|
|
1749
|
-
Authorization: `Bearer ${config.api_key}`
|
|
1750
|
-
},
|
|
1751
|
-
body: formData
|
|
1752
|
-
});
|
|
1753
|
-
if (!res.ok) {
|
|
1754
|
-
const err = await res.json().catch(() => ({}));
|
|
1755
|
-
throw new CLIError(err.error ?? `Upload failed: ${res.status}`);
|
|
1756
|
-
}
|
|
1757
|
-
const data = await res.json();
|
|
1758
|
-
if (json) {
|
|
1759
|
-
outputJson(data);
|
|
1760
|
-
} else {
|
|
1761
|
-
outputSuccess(`Uploaded "${basename2(file)}" to bucket "${bucketName}".`);
|
|
1762
|
-
}
|
|
1763
|
-
} catch (err) {
|
|
1764
|
-
handleError(err, json);
|
|
1765
|
-
}
|
|
1766
|
-
});
|
|
1767
|
-
}
|
|
1768
|
-
|
|
1769
|
-
// src/commands/storage/download.ts
|
|
1770
|
-
import { writeFileSync as writeFileSync3 } from "fs";
|
|
1771
|
-
import { join as join4, basename as basename3 } from "path";
|
|
1772
|
-
function registerStorageDownloadCommand(storageCmd2) {
|
|
1773
|
-
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) => {
|
|
1774
|
-
const { json } = getRootOpts(cmd);
|
|
1775
|
-
try {
|
|
1776
|
-
await requireAuth();
|
|
1777
|
-
const config = getProjectConfig();
|
|
1778
|
-
if (!config) throw new ProjectNotLinkedError();
|
|
1779
|
-
const bucketName = opts.bucket;
|
|
1780
|
-
const url = `${config.oss_host}/api/storage/buckets/${encodeURIComponent(bucketName)}/objects/${encodeURIComponent(objectKey)}`;
|
|
1781
|
-
const res = await fetch(url, {
|
|
1782
|
-
headers: {
|
|
1783
|
-
Authorization: `Bearer ${config.api_key}`
|
|
1784
|
-
}
|
|
1785
|
-
});
|
|
1786
|
-
if (!res.ok) {
|
|
1787
|
-
const err = await res.json().catch(() => ({}));
|
|
1788
|
-
throw new CLIError(err.error ?? `Download failed: ${res.status}`);
|
|
1789
|
-
}
|
|
1790
|
-
const buffer = Buffer.from(await res.arrayBuffer());
|
|
1791
|
-
const outputPath = opts.output ?? join4(process.cwd(), basename3(objectKey));
|
|
1792
|
-
writeFileSync3(outputPath, buffer);
|
|
1793
|
-
if (json) {
|
|
1794
|
-
outputJson({ success: true, path: outputPath, size: buffer.length });
|
|
1795
|
-
} else {
|
|
1796
|
-
outputSuccess(`Downloaded "${objectKey}" to ${outputPath} (${buffer.length} bytes).`);
|
|
1797
|
-
}
|
|
1798
|
-
} catch (err) {
|
|
1799
|
-
handleError(err, json);
|
|
1800
|
-
}
|
|
1801
|
-
});
|
|
1802
|
-
}
|
|
1803
|
-
|
|
1804
|
-
// src/commands/storage/create-bucket.ts
|
|
1805
|
-
function registerStorageCreateBucketCommand(storageCmd2) {
|
|
1806
|
-
storageCmd2.command("create-bucket <name>").description("Create a new storage bucket").option("--public", "Make the bucket publicly accessible (default)").option("--private", "Make the bucket private").action(async (name, opts, cmd) => {
|
|
1807
|
-
const { json } = getRootOpts(cmd);
|
|
1808
|
-
try {
|
|
1809
|
-
await requireAuth();
|
|
1810
|
-
const isPublic = !opts.private;
|
|
1811
|
-
const res = await ossFetch("/api/storage/buckets", {
|
|
1812
|
-
method: "POST",
|
|
1813
|
-
body: JSON.stringify({ bucketName: name, isPublic })
|
|
1814
|
-
});
|
|
1815
|
-
const data = await res.json();
|
|
1816
|
-
if (json) {
|
|
1817
|
-
outputJson(data);
|
|
1818
|
-
} else {
|
|
1819
|
-
outputSuccess(`Bucket "${name}" created (${isPublic ? "public" : "private"}).`);
|
|
1820
|
-
}
|
|
1821
|
-
await reportCliUsage("cli.storage.create-bucket", true);
|
|
1822
|
-
} catch (err) {
|
|
1823
|
-
await reportCliUsage("cli.storage.create-bucket", false);
|
|
1824
|
-
handleError(err, json);
|
|
1825
|
-
}
|
|
1826
|
-
});
|
|
1827
|
-
}
|
|
1828
|
-
|
|
1829
|
-
// src/commands/storage/delete-bucket.ts
|
|
1830
|
-
import * as clack8 from "@clack/prompts";
|
|
1831
|
-
function registerStorageDeleteBucketCommand(storageCmd2) {
|
|
1832
|
-
storageCmd2.command("delete-bucket <name>").description("Delete a storage bucket and all its objects").action(async (name, _opts, cmd) => {
|
|
1833
|
-
const { json, yes } = getRootOpts(cmd);
|
|
1834
|
-
try {
|
|
1835
|
-
await requireAuth();
|
|
1836
|
-
if (!yes && !json) {
|
|
1837
|
-
const confirm8 = await clack8.confirm({
|
|
1838
|
-
message: `Delete bucket "${name}" and all its objects? This cannot be undone.`
|
|
1839
|
-
});
|
|
1840
|
-
if (!confirm8 || clack8.isCancel(confirm8)) {
|
|
1841
|
-
process.exit(0);
|
|
2434
|
+
outputJson({ status, body: data });
|
|
2435
|
+
} else {
|
|
2436
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1842
2437
|
}
|
|
1843
|
-
}
|
|
1844
|
-
const res = await ossFetch(`/api/storage/buckets/${encodeURIComponent(name)}`, {
|
|
1845
|
-
method: "DELETE"
|
|
1846
|
-
});
|
|
1847
|
-
const data = await res.json();
|
|
1848
|
-
if (json) {
|
|
1849
|
-
outputJson(data);
|
|
1850
2438
|
} else {
|
|
1851
|
-
|
|
2439
|
+
const text4 = await res.text();
|
|
2440
|
+
console.log(text4);
|
|
2441
|
+
}
|
|
2442
|
+
if (status >= 400) {
|
|
2443
|
+
throw new CLIError(`HTTP ${status}`, 1, "HTTP_ERROR");
|
|
1852
2444
|
}
|
|
1853
2445
|
} catch (err) {
|
|
1854
2446
|
handleError(err, json);
|
|
@@ -1856,715 +2448,284 @@ function registerStorageDeleteBucketCommand(storageCmd2) {
|
|
|
1856
2448
|
});
|
|
1857
2449
|
}
|
|
1858
2450
|
|
|
1859
|
-
// src/commands/
|
|
1860
|
-
function
|
|
1861
|
-
|
|
1862
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
1863
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
1864
|
-
}
|
|
1865
|
-
function registerStorageListObjectsCommand(storageCmd2) {
|
|
1866
|
-
storageCmd2.command("list-objects <bucket>").description("List objects in a storage bucket").option("--limit <n>", "Maximum number of objects to return", "100").option("--offset <n>", "Number of objects to skip", "0").option("--prefix <prefix>", "Filter objects by key prefix").option("--search <term>", "Search objects by key (partial match)").option("--sort <field>", "Sort by field: key, size, uploadedAt (default: key)").action(async (bucket, opts, cmd) => {
|
|
2451
|
+
// src/commands/functions/code.ts
|
|
2452
|
+
function registerFunctionsCodeCommand(functionsCmd2) {
|
|
2453
|
+
functionsCmd2.command("code <slug>").description("Fetch and display the source code of an edge function").action(async (slug, _opts, cmd) => {
|
|
1867
2454
|
const { json } = getRootOpts(cmd);
|
|
1868
2455
|
try {
|
|
1869
2456
|
await requireAuth();
|
|
1870
|
-
const
|
|
1871
|
-
|
|
1872
|
-
params.set("offset", opts.offset);
|
|
1873
|
-
if (opts.prefix) params.set("prefix", opts.prefix);
|
|
1874
|
-
if (opts.search) params.set("search", opts.search);
|
|
1875
|
-
const res = await ossFetch(
|
|
1876
|
-
`/api/storage/buckets/${encodeURIComponent(bucket)}/objects?${params.toString()}`
|
|
1877
|
-
);
|
|
1878
|
-
const raw = await res.json();
|
|
1879
|
-
const objects = Array.isArray(raw) ? raw : raw.data ?? [];
|
|
1880
|
-
const sortField = opts.sort ?? "key";
|
|
1881
|
-
objects.sort((a, b) => {
|
|
1882
|
-
if (sortField === "size") return a.size - b.size;
|
|
1883
|
-
if (sortField === "uploadedAt") return a.uploadedAt.localeCompare(b.uploadedAt);
|
|
1884
|
-
return a.key.localeCompare(b.key);
|
|
1885
|
-
});
|
|
2457
|
+
const res = await ossFetch(`/api/functions/${encodeURIComponent(slug)}`);
|
|
2458
|
+
const fn = await res.json();
|
|
1886
2459
|
if (json) {
|
|
1887
|
-
outputJson(
|
|
2460
|
+
outputJson(fn);
|
|
1888
2461
|
} else {
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
}
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
console.log(`Showing ${objects.length} of ${total} objects:
|
|
1896
|
-
`);
|
|
1897
|
-
}
|
|
1898
|
-
outputTable(
|
|
1899
|
-
["Key", "Size", "Type", "Uploaded At"],
|
|
1900
|
-
objects.map((o) => [
|
|
1901
|
-
o.key,
|
|
1902
|
-
formatSize(o.size),
|
|
1903
|
-
o.mimeType ?? "-",
|
|
1904
|
-
o.uploadedAt
|
|
1905
|
-
])
|
|
1906
|
-
);
|
|
2462
|
+
console.log(`Function: ${fn.name} (${fn.slug})`);
|
|
2463
|
+
console.log(`Status: ${fn.status}`);
|
|
2464
|
+
if (fn.description) console.log(`Desc: ${fn.description}`);
|
|
2465
|
+
if (fn.deployed_at) console.log(`Deployed: ${fn.deployed_at}`);
|
|
2466
|
+
console.log("---");
|
|
2467
|
+
console.log(fn.code);
|
|
1907
2468
|
}
|
|
1908
|
-
await reportCliUsage("cli.storage.list-objects", true);
|
|
1909
2469
|
} catch (err) {
|
|
1910
|
-
await reportCliUsage("cli.storage.list-objects", false);
|
|
1911
2470
|
handleError(err, json);
|
|
1912
2471
|
}
|
|
1913
2472
|
});
|
|
1914
2473
|
}
|
|
1915
2474
|
|
|
1916
|
-
// src/commands/
|
|
1917
|
-
import { exec as exec2 } from "child_process";
|
|
1918
|
-
import { tmpdir } from "os";
|
|
1919
|
-
import { promisify as promisify2 } from "util";
|
|
1920
|
-
import * as fs3 from "fs/promises";
|
|
1921
|
-
import * as path3 from "path";
|
|
1922
|
-
import * as clack10 from "@clack/prompts";
|
|
1923
|
-
|
|
1924
|
-
// src/lib/env.ts
|
|
1925
|
-
import * as fs from "fs/promises";
|
|
1926
|
-
import * as path from "path";
|
|
1927
|
-
async function readEnvFile(cwd) {
|
|
1928
|
-
const candidates = [".env.local", ".env.production", ".env"];
|
|
1929
|
-
for (const name of candidates) {
|
|
1930
|
-
const filePath = path.join(cwd, name);
|
|
1931
|
-
const exists = await fs.stat(filePath).catch(() => null);
|
|
1932
|
-
if (!exists) continue;
|
|
1933
|
-
const content = await fs.readFile(filePath, "utf-8");
|
|
1934
|
-
const vars = [];
|
|
1935
|
-
for (const line of content.split("\n")) {
|
|
1936
|
-
const trimmed = line.trim();
|
|
1937
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
1938
|
-
const eqIndex = trimmed.indexOf("=");
|
|
1939
|
-
if (eqIndex === -1) continue;
|
|
1940
|
-
const key = trimmed.slice(0, eqIndex).trim();
|
|
1941
|
-
let value = trimmed.slice(eqIndex + 1).trim();
|
|
1942
|
-
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
1943
|
-
value = value.slice(1, -1);
|
|
1944
|
-
}
|
|
1945
|
-
if (key) vars.push({ key, value });
|
|
1946
|
-
}
|
|
1947
|
-
return vars;
|
|
1948
|
-
}
|
|
1949
|
-
return [];
|
|
1950
|
-
}
|
|
1951
|
-
|
|
1952
|
-
// src/commands/deployments/deploy.ts
|
|
1953
|
-
import * as path2 from "path";
|
|
1954
|
-
import * as fs2 from "fs/promises";
|
|
2475
|
+
// src/commands/functions/delete.ts
|
|
1955
2476
|
import * as clack9 from "@clack/prompts";
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
var EXCLUDE_PATTERNS = [
|
|
1960
|
-
"node_modules",
|
|
1961
|
-
".git",
|
|
1962
|
-
".next",
|
|
1963
|
-
".env",
|
|
1964
|
-
".env.local",
|
|
1965
|
-
"dist",
|
|
1966
|
-
"build",
|
|
1967
|
-
".DS_Store",
|
|
1968
|
-
".insforge",
|
|
1969
|
-
// IDE and AI agent configs
|
|
1970
|
-
".claude",
|
|
1971
|
-
".agents",
|
|
1972
|
-
".augment",
|
|
1973
|
-
".kilocode",
|
|
1974
|
-
".kiro",
|
|
1975
|
-
".qoder",
|
|
1976
|
-
".qwen",
|
|
1977
|
-
".roo",
|
|
1978
|
-
".trae",
|
|
1979
|
-
".windsurf",
|
|
1980
|
-
".vercel",
|
|
1981
|
-
".turbo",
|
|
1982
|
-
".cache",
|
|
1983
|
-
"skills",
|
|
1984
|
-
"coverage"
|
|
1985
|
-
];
|
|
1986
|
-
function shouldExclude(name) {
|
|
1987
|
-
const normalized = name.replace(/\\/g, "/");
|
|
1988
|
-
for (const pattern of EXCLUDE_PATTERNS) {
|
|
1989
|
-
if (normalized === pattern || normalized.startsWith(pattern + "/") || normalized.endsWith("/" + pattern) || normalized.includes("/" + pattern + "/")) {
|
|
1990
|
-
return true;
|
|
1991
|
-
}
|
|
1992
|
-
}
|
|
1993
|
-
if (normalized.endsWith(".log")) return true;
|
|
1994
|
-
return false;
|
|
1995
|
-
}
|
|
1996
|
-
async function createZipBuffer(sourceDir) {
|
|
1997
|
-
return new Promise((resolve2, reject) => {
|
|
1998
|
-
const archive = archiver("zip", { zlib: { level: 9 } });
|
|
1999
|
-
const chunks = [];
|
|
2000
|
-
archive.on("data", (chunk) => chunks.push(chunk));
|
|
2001
|
-
archive.on("end", () => resolve2(Buffer.concat(chunks)));
|
|
2002
|
-
archive.on("error", (err) => reject(err));
|
|
2003
|
-
archive.directory(sourceDir, false, (entry) => {
|
|
2004
|
-
if (shouldExclude(entry.name)) return false;
|
|
2005
|
-
return entry;
|
|
2006
|
-
});
|
|
2007
|
-
void archive.finalize();
|
|
2008
|
-
});
|
|
2009
|
-
}
|
|
2010
|
-
async function deployProject(opts) {
|
|
2011
|
-
const { sourceDir, startBody = {}, spinner: s } = opts;
|
|
2012
|
-
s?.start("Creating deployment...");
|
|
2013
|
-
const createRes = await ossFetch("/api/deployments", { method: "POST" });
|
|
2014
|
-
const { id: deploymentId, uploadUrl, uploadFields } = await createRes.json();
|
|
2015
|
-
s?.message("Compressing source files...");
|
|
2016
|
-
const zipBuffer = await createZipBuffer(sourceDir);
|
|
2017
|
-
s?.message("Uploading...");
|
|
2018
|
-
const formData = new FormData();
|
|
2019
|
-
for (const [key, value] of Object.entries(uploadFields)) {
|
|
2020
|
-
formData.append(key, value);
|
|
2021
|
-
}
|
|
2022
|
-
formData.append(
|
|
2023
|
-
"file",
|
|
2024
|
-
new Blob([zipBuffer], { type: "application/zip" }),
|
|
2025
|
-
"deployment.zip"
|
|
2026
|
-
);
|
|
2027
|
-
const uploadRes = await fetch(uploadUrl, { method: "POST", body: formData });
|
|
2028
|
-
if (!uploadRes.ok) {
|
|
2029
|
-
const uploadErr = await uploadRes.text();
|
|
2030
|
-
throw new CLIError(`Failed to upload: ${uploadErr}`);
|
|
2031
|
-
}
|
|
2032
|
-
s?.message("Starting deployment...");
|
|
2033
|
-
const startRes = await ossFetch(`/api/deployments/${deploymentId}/start`, {
|
|
2034
|
-
method: "POST",
|
|
2035
|
-
body: JSON.stringify(startBody)
|
|
2036
|
-
});
|
|
2037
|
-
await startRes.json();
|
|
2038
|
-
s?.message("Building and deploying...");
|
|
2039
|
-
const startTime = Date.now();
|
|
2040
|
-
let deployment = null;
|
|
2041
|
-
while (Date.now() - startTime < POLL_TIMEOUT_MS) {
|
|
2042
|
-
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
2043
|
-
try {
|
|
2044
|
-
const statusRes = await ossFetch(`/api/deployments/${deploymentId}`);
|
|
2045
|
-
deployment = await statusRes.json();
|
|
2046
|
-
const status = deployment.status.toUpperCase();
|
|
2047
|
-
if (status === "READY") {
|
|
2048
|
-
break;
|
|
2049
|
-
}
|
|
2050
|
-
if (status === "ERROR" || status === "CANCELED") {
|
|
2051
|
-
s?.stop("Deployment failed");
|
|
2052
|
-
throw new CLIError(getDeploymentError(deployment.metadata) ?? `Deployment failed with status: ${deployment.status}`);
|
|
2053
|
-
}
|
|
2054
|
-
const elapsed = Math.round((Date.now() - startTime) / 1e3);
|
|
2055
|
-
s?.message(`Building and deploying... (${elapsed}s, status: ${deployment.status})`);
|
|
2056
|
-
} catch (err) {
|
|
2057
|
-
if (err instanceof CLIError) throw err;
|
|
2058
|
-
}
|
|
2059
|
-
}
|
|
2060
|
-
const isReady = deployment?.status.toUpperCase() === "READY";
|
|
2061
|
-
const liveUrl = isReady ? deployment?.url ?? null : null;
|
|
2062
|
-
return { deploymentId, deployment, isReady, liveUrl };
|
|
2063
|
-
}
|
|
2064
|
-
function registerDeploymentsDeployCommand(deploymentsCmd2) {
|
|
2065
|
-
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) => {
|
|
2066
|
-
const { json } = getRootOpts(cmd);
|
|
2477
|
+
function registerFunctionsDeleteCommand(functionsCmd2) {
|
|
2478
|
+
functionsCmd2.command("delete <slug>").description("Delete an edge function").action(async (slug, _opts, cmd) => {
|
|
2479
|
+
const { json, yes } = getRootOpts(cmd);
|
|
2067
2480
|
try {
|
|
2068
2481
|
await requireAuth();
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
const dirName = path2.basename(sourceDir);
|
|
2077
|
-
if (EXCLUDE_PATTERNS.includes(dirName)) {
|
|
2078
|
-
throw new CLIError(`"${dirName}" is an excluded directory and cannot be used as a deploy source. Please specify your project root or output directory instead.`);
|
|
2079
|
-
}
|
|
2080
|
-
const s = !json ? clack9.spinner() : null;
|
|
2081
|
-
const startBody = {};
|
|
2082
|
-
if (opts.env) {
|
|
2083
|
-
try {
|
|
2084
|
-
const parsed = JSON.parse(opts.env);
|
|
2085
|
-
if (Array.isArray(parsed)) {
|
|
2086
|
-
startBody.envVars = parsed;
|
|
2087
|
-
} else {
|
|
2088
|
-
startBody.envVars = Object.entries(parsed).map(([key, value]) => ({ key, value }));
|
|
2089
|
-
}
|
|
2090
|
-
} catch {
|
|
2091
|
-
throw new CLIError("Invalid --env JSON.");
|
|
2092
|
-
}
|
|
2093
|
-
}
|
|
2094
|
-
if (opts.meta) {
|
|
2095
|
-
try {
|
|
2096
|
-
startBody.meta = JSON.parse(opts.meta);
|
|
2097
|
-
} catch {
|
|
2098
|
-
throw new CLIError("Invalid --meta JSON.");
|
|
2482
|
+
if (!yes && !json) {
|
|
2483
|
+
const confirmed = await clack9.confirm({
|
|
2484
|
+
message: `Delete function "${slug}"? This cannot be undone.`
|
|
2485
|
+
});
|
|
2486
|
+
if (clack9.isCancel(confirmed) || !confirmed) {
|
|
2487
|
+
clack9.log.info("Cancelled.");
|
|
2488
|
+
return;
|
|
2099
2489
|
}
|
|
2100
2490
|
}
|
|
2101
|
-
const
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
if (result.liveUrl) {
|
|
2108
|
-
clack9.log.success(`Live at: ${result.liveUrl}`);
|
|
2109
|
-
}
|
|
2110
|
-
clack9.log.info(`Deployment ID: ${result.deploymentId}`);
|
|
2111
|
-
}
|
|
2491
|
+
const res = await ossFetch(`/api/functions/${encodeURIComponent(slug)}`, {
|
|
2492
|
+
method: "DELETE"
|
|
2493
|
+
});
|
|
2494
|
+
const result = await res.json();
|
|
2495
|
+
if (json) {
|
|
2496
|
+
outputJson(result);
|
|
2112
2497
|
} else {
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
outputJson({ id: result.deploymentId, status: result.deployment?.status ?? "building", timedOut: true });
|
|
2498
|
+
if (result.success) {
|
|
2499
|
+
outputSuccess(`Function "${slug}" deleted successfully.`);
|
|
2116
2500
|
} else {
|
|
2117
|
-
|
|
2118
|
-
clack9.log.warn("Deployment did not finish within 5 minutes.");
|
|
2119
|
-
clack9.log.info(`Check status with: npx @insforge/cli deployments status ${result.deploymentId}`);
|
|
2501
|
+
outputSuccess(`Failed to delete function "${slug}".`);
|
|
2120
2502
|
}
|
|
2121
2503
|
}
|
|
2122
|
-
await reportCliUsage("cli.
|
|
2504
|
+
await reportCliUsage("cli.functions.delete", true);
|
|
2123
2505
|
} catch (err) {
|
|
2124
|
-
await reportCliUsage("cli.
|
|
2506
|
+
await reportCliUsage("cli.functions.delete", false);
|
|
2125
2507
|
handleError(err, json);
|
|
2126
2508
|
}
|
|
2127
2509
|
});
|
|
2128
2510
|
}
|
|
2129
2511
|
|
|
2130
|
-
// src/commands/
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
"\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",
|
|
2146
|
-
"\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",
|
|
2147
|
-
"\u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2557 ",
|
|
2148
|
-
"\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D ",
|
|
2149
|
-
"\u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
|
|
2150
|
-
"\u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D"
|
|
2151
|
-
];
|
|
2152
|
-
async function animateBanner() {
|
|
2153
|
-
const isTTY = process.stderr.isTTY;
|
|
2154
|
-
if (!isTTY || process.env.CI) {
|
|
2155
|
-
for (const line of INSFORGE_BANNER) {
|
|
2156
|
-
process.stderr.write(`${line}
|
|
2157
|
-
`);
|
|
2158
|
-
}
|
|
2159
|
-
process.stderr.write("\n");
|
|
2160
|
-
return;
|
|
2161
|
-
}
|
|
2162
|
-
const totalLines = INSFORGE_BANNER.length;
|
|
2163
|
-
const maxLen = Math.max(...INSFORGE_BANNER.map((l) => l.length));
|
|
2164
|
-
const cols = process.stderr.columns ?? 0;
|
|
2165
|
-
if (cols > 0 && cols < maxLen) {
|
|
2166
|
-
for (const line of INSFORGE_BANNER) {
|
|
2167
|
-
process.stderr.write(`\x1B[97m${line}\x1B[0m
|
|
2168
|
-
`);
|
|
2169
|
-
}
|
|
2170
|
-
process.stderr.write("\n");
|
|
2171
|
-
return;
|
|
2172
|
-
}
|
|
2173
|
-
const REVEAL_STEPS = 10;
|
|
2174
|
-
const REVEAL_DELAY = 30;
|
|
2175
|
-
for (let lineIdx = 0; lineIdx < totalLines; lineIdx++) {
|
|
2176
|
-
const line = INSFORGE_BANNER[lineIdx];
|
|
2177
|
-
for (let step = 0; step <= REVEAL_STEPS; step++) {
|
|
2178
|
-
const pos = Math.floor(step / REVEAL_STEPS * line.length);
|
|
2179
|
-
let rendered = "";
|
|
2180
|
-
for (let i = 0; i < line.length; i++) {
|
|
2181
|
-
if (i < pos) {
|
|
2182
|
-
rendered += `\x1B[97m${line[i]}\x1B[0m`;
|
|
2183
|
-
} else if (i === pos) {
|
|
2184
|
-
rendered += `\x1B[1;37m${line[i]}\x1B[0m`;
|
|
2185
|
-
} else {
|
|
2186
|
-
rendered += `\x1B[90m${line[i]}\x1B[0m`;
|
|
2187
|
-
}
|
|
2188
|
-
}
|
|
2189
|
-
process.stderr.write(`\r${rendered}`);
|
|
2190
|
-
await new Promise((r) => setTimeout(r, REVEAL_DELAY));
|
|
2191
|
-
}
|
|
2192
|
-
process.stderr.write("\n");
|
|
2193
|
-
}
|
|
2194
|
-
const SHIMMER_STEPS = 16;
|
|
2195
|
-
const SHIMMER_DELAY = 40;
|
|
2196
|
-
const SHIMMER_WIDTH = 4;
|
|
2197
|
-
for (let step = 0; step < SHIMMER_STEPS; step++) {
|
|
2198
|
-
const shimmerPos = Math.floor(step / SHIMMER_STEPS * (maxLen + SHIMMER_WIDTH));
|
|
2199
|
-
process.stderr.write(`\x1B[${totalLines}A`);
|
|
2200
|
-
for (const line of INSFORGE_BANNER) {
|
|
2201
|
-
let rendered = "";
|
|
2202
|
-
for (let i = 0; i < line.length; i++) {
|
|
2203
|
-
const dist = Math.abs(i - shimmerPos);
|
|
2204
|
-
if (dist === 0) {
|
|
2205
|
-
rendered += `\x1B[1;97m${line[i]}\x1B[0m`;
|
|
2206
|
-
} else if (dist <= SHIMMER_WIDTH) {
|
|
2207
|
-
rendered += `\x1B[37m${line[i]}\x1B[0m`;
|
|
2208
|
-
} else {
|
|
2209
|
-
rendered += `\x1B[90m${line[i]}\x1B[0m`;
|
|
2512
|
+
// src/commands/storage/buckets.ts
|
|
2513
|
+
function registerStorageBucketsCommand(storageCmd2) {
|
|
2514
|
+
storageCmd2.command("buckets").description("List all storage buckets").action(async (_opts, cmd) => {
|
|
2515
|
+
const { json } = getRootOpts(cmd);
|
|
2516
|
+
try {
|
|
2517
|
+
await requireAuth();
|
|
2518
|
+
const res = await ossFetch("/api/storage/buckets");
|
|
2519
|
+
const raw = await res.json();
|
|
2520
|
+
const buckets = Array.isArray(raw) ? raw : raw && typeof raw === "object" && "buckets" in raw ? raw.buckets ?? [] : [];
|
|
2521
|
+
if (json) {
|
|
2522
|
+
outputJson(raw);
|
|
2523
|
+
} else {
|
|
2524
|
+
if (buckets.length === 0) {
|
|
2525
|
+
console.log("No buckets found.");
|
|
2526
|
+
return;
|
|
2210
2527
|
}
|
|
2528
|
+
outputTable(
|
|
2529
|
+
["Bucket Name", "Public"],
|
|
2530
|
+
buckets.map((b) => [b.name, b.public ? "Yes" : "No"])
|
|
2531
|
+
);
|
|
2211
2532
|
}
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
}
|
|
2217
|
-
process.stderr.write(`\x1B[${totalLines}A`);
|
|
2218
|
-
for (const line of INSFORGE_BANNER) {
|
|
2219
|
-
process.stderr.write(`\x1B[97m${line}\x1B[0m
|
|
2220
|
-
`);
|
|
2221
|
-
}
|
|
2222
|
-
process.stderr.write("\n");
|
|
2223
|
-
}
|
|
2224
|
-
function getDefaultProjectName() {
|
|
2225
|
-
const dirName = path3.basename(process.cwd());
|
|
2226
|
-
const sanitized = dirName.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
2227
|
-
return sanitized.length >= 2 ? sanitized : "";
|
|
2228
|
-
}
|
|
2229
|
-
async function copyDir(src, dest) {
|
|
2230
|
-
const entries = await fs3.readdir(src, { withFileTypes: true });
|
|
2231
|
-
for (const entry of entries) {
|
|
2232
|
-
const srcPath = path3.join(src, entry.name);
|
|
2233
|
-
const destPath = path3.join(dest, entry.name);
|
|
2234
|
-
if (entry.isDirectory()) {
|
|
2235
|
-
await fs3.mkdir(destPath, { recursive: true });
|
|
2236
|
-
await copyDir(srcPath, destPath);
|
|
2237
|
-
} else {
|
|
2238
|
-
await fs3.copyFile(srcPath, destPath);
|
|
2533
|
+
await reportCliUsage("cli.storage.buckets", true);
|
|
2534
|
+
} catch (err) {
|
|
2535
|
+
await reportCliUsage("cli.storage.buckets", false);
|
|
2536
|
+
handleError(err, json);
|
|
2239
2537
|
}
|
|
2240
|
-
}
|
|
2538
|
+
});
|
|
2241
2539
|
}
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2540
|
+
|
|
2541
|
+
// src/commands/storage/upload.ts
|
|
2542
|
+
import { readFileSync as readFileSync5, existsSync as existsSync4 } from "fs";
|
|
2543
|
+
import { basename as basename5 } from "path";
|
|
2544
|
+
function registerStorageUploadCommand(storageCmd2) {
|
|
2545
|
+
storageCmd2.command("upload <file>").description("Upload a file to a storage bucket").requiredOption("--bucket <name>", "Target bucket name").option("--key <objectKey>", "Object key (defaults to filename)").action(async (file, opts, cmd) => {
|
|
2546
|
+
const { json } = getRootOpts(cmd);
|
|
2245
2547
|
try {
|
|
2246
|
-
await requireAuth(
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
let orgId = opts.orgId;
|
|
2252
|
-
if (!orgId) {
|
|
2253
|
-
const orgs = await listOrganizations(apiUrl);
|
|
2254
|
-
if (orgs.length === 0) {
|
|
2255
|
-
throw new CLIError("No organizations found.");
|
|
2256
|
-
}
|
|
2257
|
-
if (json) {
|
|
2258
|
-
throw new CLIError("Specify --org-id in JSON mode.");
|
|
2259
|
-
}
|
|
2260
|
-
const selected = await clack10.select({
|
|
2261
|
-
message: "Select an organization:",
|
|
2262
|
-
options: orgs.map((o) => ({
|
|
2263
|
-
value: o.id,
|
|
2264
|
-
label: o.name
|
|
2265
|
-
}))
|
|
2266
|
-
});
|
|
2267
|
-
if (clack10.isCancel(selected)) process.exit(0);
|
|
2268
|
-
orgId = selected;
|
|
2269
|
-
}
|
|
2270
|
-
const globalConfig = getGlobalConfig();
|
|
2271
|
-
globalConfig.default_org_id = orgId;
|
|
2272
|
-
saveGlobalConfig(globalConfig);
|
|
2273
|
-
let projectName = opts.name;
|
|
2274
|
-
if (!projectName) {
|
|
2275
|
-
if (json) throw new CLIError("--name is required in JSON mode.");
|
|
2276
|
-
const defaultName = getDefaultProjectName();
|
|
2277
|
-
const name = await clack10.text({
|
|
2278
|
-
message: "Project name:",
|
|
2279
|
-
...defaultName ? { initialValue: defaultName } : {},
|
|
2280
|
-
validate: (v) => v.length >= 2 ? void 0 : "Name must be at least 2 characters"
|
|
2281
|
-
});
|
|
2282
|
-
if (clack10.isCancel(name)) process.exit(0);
|
|
2283
|
-
projectName = name;
|
|
2548
|
+
await requireAuth();
|
|
2549
|
+
const config = getProjectConfig();
|
|
2550
|
+
if (!config) throw new ProjectNotLinkedError();
|
|
2551
|
+
if (!existsSync4(file)) {
|
|
2552
|
+
throw new CLIError(`File not found: ${file}`);
|
|
2284
2553
|
}
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2554
|
+
const fileContent = readFileSync5(file);
|
|
2555
|
+
const objectKey = opts.key ?? basename5(file);
|
|
2556
|
+
const bucketName = opts.bucket;
|
|
2557
|
+
const formData = new FormData();
|
|
2558
|
+
const blob = new Blob([fileContent]);
|
|
2559
|
+
formData.append("file", blob, objectKey);
|
|
2560
|
+
const url = `${config.oss_host}/api/storage/buckets/${encodeURIComponent(bucketName)}/objects/${encodeURIComponent(objectKey)}`;
|
|
2561
|
+
const res = await fetch(url, {
|
|
2562
|
+
method: "PUT",
|
|
2563
|
+
headers: {
|
|
2564
|
+
Authorization: `Bearer ${config.api_key}`
|
|
2565
|
+
},
|
|
2566
|
+
body: formData
|
|
2567
|
+
});
|
|
2568
|
+
if (!res.ok) {
|
|
2569
|
+
const err = await res.json().catch(() => ({}));
|
|
2570
|
+
throw new CLIError(err.error ?? `Upload failed: ${res.status}`);
|
|
2288
2571
|
}
|
|
2289
|
-
const
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2572
|
+
const data = await res.json();
|
|
2573
|
+
if (json) {
|
|
2574
|
+
outputJson(data);
|
|
2575
|
+
} else {
|
|
2576
|
+
outputSuccess(`Uploaded "${basename5(file)}" to bucket "${bucketName}".`);
|
|
2293
2577
|
}
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
{ value: "react", label: "Web app template with React" },
|
|
2316
|
-
{ value: "nextjs", label: "Web app template with Next.js" },
|
|
2317
|
-
{ value: "chatbot", label: "AI Chatbot with Next.js" },
|
|
2318
|
-
{ value: "crm", label: "CRM with Next.js" },
|
|
2319
|
-
{ value: "e-commerce", label: "E-Commerce store with Next.js" }
|
|
2320
|
-
]
|
|
2321
|
-
});
|
|
2322
|
-
if (clack10.isCancel(selected)) process.exit(0);
|
|
2323
|
-
template = selected;
|
|
2324
|
-
}
|
|
2578
|
+
} catch (err) {
|
|
2579
|
+
handleError(err, json);
|
|
2580
|
+
}
|
|
2581
|
+
});
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
// src/commands/storage/download.ts
|
|
2585
|
+
import { writeFileSync as writeFileSync3 } from "fs";
|
|
2586
|
+
import { join as join7, basename as basename6 } from "path";
|
|
2587
|
+
function registerStorageDownloadCommand(storageCmd2) {
|
|
2588
|
+
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) => {
|
|
2589
|
+
const { json } = getRootOpts(cmd);
|
|
2590
|
+
try {
|
|
2591
|
+
await requireAuth();
|
|
2592
|
+
const config = getProjectConfig();
|
|
2593
|
+
if (!config) throw new ProjectNotLinkedError();
|
|
2594
|
+
const bucketName = opts.bucket;
|
|
2595
|
+
const url = `${config.oss_host}/api/storage/buckets/${encodeURIComponent(bucketName)}/objects/${encodeURIComponent(objectKey)}`;
|
|
2596
|
+
const res = await fetch(url, {
|
|
2597
|
+
headers: {
|
|
2598
|
+
Authorization: `Bearer ${config.api_key}`
|
|
2325
2599
|
}
|
|
2326
|
-
}
|
|
2327
|
-
captureEvent(orgId, "template_selected", {
|
|
2328
|
-
template,
|
|
2329
|
-
approach: template === "empty" ? "blank" : "template"
|
|
2330
2600
|
});
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
await
|
|
2336
|
-
const
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
org_id: project.organization_id,
|
|
2341
|
-
appkey: project.appkey,
|
|
2342
|
-
region: project.region,
|
|
2343
|
-
api_key: apiKey,
|
|
2344
|
-
oss_host: buildOssHost2(project.appkey, project.region)
|
|
2345
|
-
};
|
|
2346
|
-
saveProjectConfig(projectConfig);
|
|
2347
|
-
s?.stop(`Project "${project.name}" created and linked`);
|
|
2348
|
-
const hasTemplate = template !== "empty";
|
|
2349
|
-
const githubTemplates = ["chatbot", "crm", "e-commerce", "nextjs", "react"];
|
|
2350
|
-
if (githubTemplates.includes(template)) {
|
|
2351
|
-
await downloadGitHubTemplate(template, projectConfig, json);
|
|
2352
|
-
} else if (hasTemplate) {
|
|
2353
|
-
await downloadTemplate(template, projectConfig, projectName, json, apiUrl);
|
|
2601
|
+
if (!res.ok) {
|
|
2602
|
+
const err = await res.json().catch(() => ({}));
|
|
2603
|
+
throw new CLIError(err.error ?? `Download failed: ${res.status}`);
|
|
2604
|
+
}
|
|
2605
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
2606
|
+
const outputPath = opts.output ?? join7(process.cwd(), basename6(objectKey));
|
|
2607
|
+
writeFileSync3(outputPath, buffer);
|
|
2608
|
+
if (json) {
|
|
2609
|
+
outputJson({ success: true, path: outputPath, size: buffer.length });
|
|
2354
2610
|
} else {
|
|
2355
|
-
|
|
2356
|
-
const anonKey = await getAnonKey();
|
|
2357
|
-
if (!anonKey) {
|
|
2358
|
-
if (!json) clack10.log.warn("Could not retrieve anon key. You can add it to .env.local manually.");
|
|
2359
|
-
} else {
|
|
2360
|
-
const envPath = path3.join(process.cwd(), ".env.local");
|
|
2361
|
-
const envContent = [
|
|
2362
|
-
"# InsForge",
|
|
2363
|
-
`NEXT_PUBLIC_INSFORGE_URL=${projectConfig.oss_host}`,
|
|
2364
|
-
`NEXT_PUBLIC_INSFORGE_ANON_KEY=${anonKey}`,
|
|
2365
|
-
""
|
|
2366
|
-
].join("\n");
|
|
2367
|
-
await fs3.writeFile(envPath, envContent, { flag: "wx" });
|
|
2368
|
-
if (!json) {
|
|
2369
|
-
clack10.log.success("Created .env.local with your InsForge credentials");
|
|
2370
|
-
}
|
|
2371
|
-
}
|
|
2372
|
-
} catch (err) {
|
|
2373
|
-
const error = err;
|
|
2374
|
-
if (!json) {
|
|
2375
|
-
if (error.code === "EEXIST") {
|
|
2376
|
-
clack10.log.warn(".env.local already exists; skipping InsForge key seeding.");
|
|
2377
|
-
} else {
|
|
2378
|
-
clack10.log.warn(`Failed to create .env.local: ${error.message}`);
|
|
2379
|
-
}
|
|
2380
|
-
}
|
|
2381
|
-
}
|
|
2611
|
+
outputSuccess(`Downloaded "${objectKey}" to ${outputPath} (${buffer.length} bytes).`);
|
|
2382
2612
|
}
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
}
|
|
2613
|
+
} catch (err) {
|
|
2614
|
+
handleError(err, json);
|
|
2615
|
+
}
|
|
2616
|
+
});
|
|
2617
|
+
}
|
|
2618
|
+
|
|
2619
|
+
// src/commands/storage/create-bucket.ts
|
|
2620
|
+
function registerStorageCreateBucketCommand(storageCmd2) {
|
|
2621
|
+
storageCmd2.command("create-bucket <name>").description("Create a new storage bucket").option("--public", "Make the bucket publicly accessible (default)").option("--private", "Make the bucket private").action(async (name, opts, cmd) => {
|
|
2622
|
+
const { json } = getRootOpts(cmd);
|
|
2623
|
+
try {
|
|
2624
|
+
await requireAuth();
|
|
2625
|
+
const isPublic = !opts.private;
|
|
2626
|
+
const res = await ossFetch("/api/storage/buckets", {
|
|
2627
|
+
method: "POST",
|
|
2628
|
+
body: JSON.stringify({ bucketName: name, isPublic })
|
|
2629
|
+
});
|
|
2630
|
+
const data = await res.json();
|
|
2631
|
+
if (json) {
|
|
2632
|
+
outputJson(data);
|
|
2633
|
+
} else {
|
|
2634
|
+
outputSuccess(`Bucket "${name}" created (${isPublic ? "public" : "private"}).`);
|
|
2399
2635
|
}
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2636
|
+
await reportCliUsage("cli.storage.create-bucket", true);
|
|
2637
|
+
} catch (err) {
|
|
2638
|
+
await reportCliUsage("cli.storage.create-bucket", false);
|
|
2639
|
+
handleError(err, json);
|
|
2640
|
+
}
|
|
2641
|
+
});
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
// src/commands/storage/delete-bucket.ts
|
|
2645
|
+
import * as clack10 from "@clack/prompts";
|
|
2646
|
+
function registerStorageDeleteBucketCommand(storageCmd2) {
|
|
2647
|
+
storageCmd2.command("delete-bucket <name>").description("Delete a storage bucket and all its objects").action(async (name, _opts, cmd) => {
|
|
2648
|
+
const { json, yes } = getRootOpts(cmd);
|
|
2649
|
+
try {
|
|
2650
|
+
await requireAuth();
|
|
2651
|
+
if (!yes && !json) {
|
|
2652
|
+
const confirm8 = await clack10.confirm({
|
|
2653
|
+
message: `Delete bucket "${name}" and all its objects? This cannot be undone.`
|
|
2404
2654
|
});
|
|
2405
|
-
if (!clack10.isCancel(
|
|
2406
|
-
|
|
2407
|
-
const envVars = await readEnvFile(process.cwd());
|
|
2408
|
-
const startBody = {};
|
|
2409
|
-
if (envVars.length > 0) {
|
|
2410
|
-
startBody.envVars = envVars;
|
|
2411
|
-
}
|
|
2412
|
-
const deploySpinner = clack10.spinner();
|
|
2413
|
-
const result = await deployProject({
|
|
2414
|
-
sourceDir: process.cwd(),
|
|
2415
|
-
startBody,
|
|
2416
|
-
spinner: deploySpinner
|
|
2417
|
-
});
|
|
2418
|
-
if (result.isReady) {
|
|
2419
|
-
deploySpinner.stop("Deployment complete");
|
|
2420
|
-
liveUrl = result.liveUrl;
|
|
2421
|
-
} else {
|
|
2422
|
-
deploySpinner.stop("Deployment is still building");
|
|
2423
|
-
clack10.log.info(`Deployment ID: ${result.deploymentId}`);
|
|
2424
|
-
clack10.log.warn("Deployment did not finish within 2 minutes.");
|
|
2425
|
-
clack10.log.info(`Check status with: npx @insforge/cli deployments status ${result.deploymentId}`);
|
|
2426
|
-
}
|
|
2427
|
-
} catch (err) {
|
|
2428
|
-
clack10.log.warn(`Deploy failed: ${err.message}`);
|
|
2429
|
-
}
|
|
2655
|
+
if (!confirm8 || clack10.isCancel(confirm8)) {
|
|
2656
|
+
process.exit(0);
|
|
2430
2657
|
}
|
|
2431
2658
|
}
|
|
2432
|
-
const
|
|
2659
|
+
const res = await ossFetch(`/api/storage/buckets/${encodeURIComponent(name)}`, {
|
|
2660
|
+
method: "DELETE"
|
|
2661
|
+
});
|
|
2662
|
+
const data = await res.json();
|
|
2433
2663
|
if (json) {
|
|
2434
|
-
outputJson(
|
|
2435
|
-
success: true,
|
|
2436
|
-
project: { id: project.id, name: project.name, appkey: project.appkey, region: project.region },
|
|
2437
|
-
template,
|
|
2438
|
-
urls: {
|
|
2439
|
-
dashboard: dashboardUrl,
|
|
2440
|
-
...liveUrl ? { liveSite: liveUrl } : {}
|
|
2441
|
-
}
|
|
2442
|
-
});
|
|
2664
|
+
outputJson(data);
|
|
2443
2665
|
} else {
|
|
2444
|
-
|
|
2445
|
-
if (liveUrl) {
|
|
2446
|
-
clack10.log.success(`Live site: ${liveUrl}`);
|
|
2447
|
-
}
|
|
2448
|
-
clack10.outro("Done!");
|
|
2666
|
+
outputSuccess(`Bucket "${name}" deleted.`);
|
|
2449
2667
|
}
|
|
2450
2668
|
} catch (err) {
|
|
2451
2669
|
handleError(err, json);
|
|
2452
|
-
} finally {
|
|
2453
|
-
await shutdownAnalytics();
|
|
2454
2670
|
}
|
|
2455
2671
|
});
|
|
2456
2672
|
}
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
throw new Error("Failed to retrieve anon key from backend");
|
|
2464
|
-
}
|
|
2465
|
-
const tempDir = tmpdir();
|
|
2466
|
-
const targetDir = projectName;
|
|
2467
|
-
const templatePath = path3.join(tempDir, targetDir);
|
|
2468
|
-
try {
|
|
2469
|
-
await fs3.rm(templatePath, { recursive: true, force: true });
|
|
2470
|
-
} catch {
|
|
2471
|
-
}
|
|
2472
|
-
const frame = framework === "nextjs" ? "nextjs" : "react";
|
|
2473
|
-
const esc = (s2) => process.platform === "win32" ? `"${s2.replace(/"/g, '\\"')}"` : `'${s2.replace(/'/g, "'\\''")}'`;
|
|
2474
|
-
const command = `npx --yes create-insforge-app@latest ${esc(targetDir)} --frame ${frame} --base-url ${esc(projectConfig.oss_host)} --anon-key ${esc(anonKey)} --skip-install`;
|
|
2475
|
-
s?.message(`Running create-insforge-app (${frame})...`);
|
|
2476
|
-
await execAsync2(command, {
|
|
2477
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
2478
|
-
cwd: tempDir
|
|
2479
|
-
});
|
|
2480
|
-
s?.message("Copying template files...");
|
|
2481
|
-
const cwd = process.cwd();
|
|
2482
|
-
await copyDir(templatePath, cwd);
|
|
2483
|
-
await fs3.rm(templatePath, { recursive: true, force: true }).catch(() => {
|
|
2484
|
-
});
|
|
2485
|
-
s?.stop("Template files downloaded");
|
|
2486
|
-
} catch (err) {
|
|
2487
|
-
s?.stop("Template download failed");
|
|
2488
|
-
if (!json) {
|
|
2489
|
-
clack10.log.warn(`Failed to download template: ${err.message}`);
|
|
2490
|
-
clack10.log.info("You can manually set up the template later.");
|
|
2491
|
-
}
|
|
2492
|
-
}
|
|
2673
|
+
|
|
2674
|
+
// src/commands/storage/list-objects.ts
|
|
2675
|
+
function formatSize(bytes) {
|
|
2676
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
2677
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
2678
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
2493
2679
|
}
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
"
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
if (!stat4?.isDirectory()) {
|
|
2507
|
-
throw new Error(`Template "${templateName}" not found in repository`);
|
|
2508
|
-
}
|
|
2509
|
-
s?.message("Copying template files...");
|
|
2510
|
-
const cwd = process.cwd();
|
|
2511
|
-
await copyDir(templateDir, cwd);
|
|
2512
|
-
const envExamplePath = path3.join(cwd, ".env.example");
|
|
2513
|
-
const envExampleExists = await fs3.stat(envExamplePath).catch(() => null);
|
|
2514
|
-
if (envExampleExists) {
|
|
2515
|
-
const anonKey = await getAnonKey();
|
|
2516
|
-
const envExample = await fs3.readFile(envExamplePath, "utf-8");
|
|
2517
|
-
const envContent = envExample.replace(
|
|
2518
|
-
/^([A-Z][A-Z0-9_]*=)(.*)$/gm,
|
|
2519
|
-
(_, prefix, _value) => {
|
|
2520
|
-
const key = prefix.slice(0, -1);
|
|
2521
|
-
if (/INSFORGE.*(URL|BASE_URL)$/.test(key)) return `${prefix}${projectConfig.oss_host}`;
|
|
2522
|
-
if (/INSFORGE.*ANON_KEY$/.test(key)) return `${prefix}${anonKey}`;
|
|
2523
|
-
if (key === "NEXT_PUBLIC_APP_URL") return `${prefix}https://${projectConfig.appkey}.insforge.site`;
|
|
2524
|
-
return `${prefix}${_value}`;
|
|
2525
|
-
}
|
|
2680
|
+
function registerStorageListObjectsCommand(storageCmd2) {
|
|
2681
|
+
storageCmd2.command("list-objects <bucket>").description("List objects in a storage bucket").option("--limit <n>", "Maximum number of objects to return", "100").option("--offset <n>", "Number of objects to skip", "0").option("--prefix <prefix>", "Filter objects by key prefix").option("--search <term>", "Search objects by key (partial match)").option("--sort <field>", "Sort by field: key, size, uploadedAt (default: key)").action(async (bucket, opts, cmd) => {
|
|
2682
|
+
const { json } = getRootOpts(cmd);
|
|
2683
|
+
try {
|
|
2684
|
+
await requireAuth();
|
|
2685
|
+
const params = new URLSearchParams();
|
|
2686
|
+
params.set("limit", opts.limit);
|
|
2687
|
+
params.set("offset", opts.offset);
|
|
2688
|
+
if (opts.prefix) params.set("prefix", opts.prefix);
|
|
2689
|
+
if (opts.search) params.set("search", opts.search);
|
|
2690
|
+
const res = await ossFetch(
|
|
2691
|
+
`/api/storage/buckets/${encodeURIComponent(bucket)}/objects?${params.toString()}`
|
|
2526
2692
|
);
|
|
2527
|
-
const
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
if (
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2693
|
+
const raw = await res.json();
|
|
2694
|
+
const objects = Array.isArray(raw) ? raw : raw.data ?? [];
|
|
2695
|
+
const sortField = opts.sort ?? "key";
|
|
2696
|
+
objects.sort((a, b) => {
|
|
2697
|
+
if (sortField === "size") return a.size - b.size;
|
|
2698
|
+
if (sortField === "uploadedAt") return a.uploadedAt.localeCompare(b.uploadedAt);
|
|
2699
|
+
return a.key.localeCompare(b.key);
|
|
2700
|
+
});
|
|
2701
|
+
if (json) {
|
|
2702
|
+
outputJson(raw);
|
|
2703
|
+
} else {
|
|
2704
|
+
if (objects.length === 0) {
|
|
2705
|
+
console.log(`No objects found in bucket "${bucket}".`);
|
|
2706
|
+
return;
|
|
2535
2707
|
}
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
const migrationExists = await fs3.stat(migrationPath).catch(() => null);
|
|
2541
|
-
if (migrationExists) {
|
|
2542
|
-
const dbSpinner = !json ? clack10.spinner() : null;
|
|
2543
|
-
dbSpinner?.start("Running database migrations...");
|
|
2544
|
-
try {
|
|
2545
|
-
const sql = await fs3.readFile(migrationPath, "utf-8");
|
|
2546
|
-
await runRawSql(sql, true);
|
|
2547
|
-
dbSpinner?.stop("Database migrations applied");
|
|
2548
|
-
} catch (err) {
|
|
2549
|
-
dbSpinner?.stop("Database migration failed");
|
|
2550
|
-
if (!json) {
|
|
2551
|
-
clack10.log.warn(`Migration failed: ${err.message}`);
|
|
2552
|
-
clack10.log.info('You can run the migration manually: npx @insforge/cli db query --unrestricted "$(cat migrations/db_init.sql)"');
|
|
2553
|
-
} else {
|
|
2554
|
-
throw err;
|
|
2708
|
+
const total = raw.pagination?.total;
|
|
2709
|
+
if (total !== void 0) {
|
|
2710
|
+
console.log(`Showing ${objects.length} of ${total} objects:
|
|
2711
|
+
`);
|
|
2555
2712
|
}
|
|
2713
|
+
outputTable(
|
|
2714
|
+
["Key", "Size", "Type", "Uploaded At"],
|
|
2715
|
+
objects.map((o) => [
|
|
2716
|
+
o.key,
|
|
2717
|
+
formatSize(o.size),
|
|
2718
|
+
o.mimeType ?? "-",
|
|
2719
|
+
o.uploadedAt
|
|
2720
|
+
])
|
|
2721
|
+
);
|
|
2556
2722
|
}
|
|
2723
|
+
await reportCliUsage("cli.storage.list-objects", true);
|
|
2724
|
+
} catch (err) {
|
|
2725
|
+
await reportCliUsage("cli.storage.list-objects", false);
|
|
2726
|
+
handleError(err, json);
|
|
2557
2727
|
}
|
|
2558
|
-
}
|
|
2559
|
-
s?.stop(`${templateName} template download failed`);
|
|
2560
|
-
if (!json) {
|
|
2561
|
-
clack10.log.warn(`Failed to download ${templateName} template: ${err.message}`);
|
|
2562
|
-
clack10.log.info("You can manually clone from: https://github.com/InsForge/insforge-templates");
|
|
2563
|
-
}
|
|
2564
|
-
} finally {
|
|
2565
|
-
await fs3.rm(tempDir, { recursive: true, force: true }).catch(() => {
|
|
2566
|
-
});
|
|
2567
|
-
}
|
|
2728
|
+
});
|
|
2568
2729
|
}
|
|
2569
2730
|
|
|
2570
2731
|
// src/commands/info.ts
|
|
@@ -2902,8 +3063,8 @@ async function listDocs(json) {
|
|
|
2902
3063
|
);
|
|
2903
3064
|
}
|
|
2904
3065
|
}
|
|
2905
|
-
async function fetchDoc(
|
|
2906
|
-
const res = await ossFetch(
|
|
3066
|
+
async function fetchDoc(path5, label, json) {
|
|
3067
|
+
const res = await ossFetch(path5);
|
|
2907
3068
|
const data = await res.json();
|
|
2908
3069
|
const doc = data.data ?? data;
|
|
2909
3070
|
if (json) {
|
|
@@ -3554,10 +3715,10 @@ function registerComputeLogsCommand(computeCmd2) {
|
|
|
3554
3715
|
|
|
3555
3716
|
// src/commands/compute/deploy.ts
|
|
3556
3717
|
import { existsSync as existsSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4, unlinkSync as unlinkSync2, renameSync } from "fs";
|
|
3557
|
-
import { join as
|
|
3718
|
+
import { join as join8 } from "path";
|
|
3558
3719
|
import { execSync, spawn } from "child_process";
|
|
3559
3720
|
function parseFlyToml(dir) {
|
|
3560
|
-
const tomlPath =
|
|
3721
|
+
const tomlPath = join8(dir, "fly.toml");
|
|
3561
3722
|
if (!existsSync5(tomlPath)) return {};
|
|
3562
3723
|
const content = readFileSync6(tomlPath, "utf-8");
|
|
3563
3724
|
const config = {};
|
|
@@ -3629,7 +3790,7 @@ function registerComputeDeployCommand(computeCmd2) {
|
|
|
3629
3790
|
checkFlyctl();
|
|
3630
3791
|
const flyToken = getFlyToken();
|
|
3631
3792
|
const dir = directory ?? process.cwd();
|
|
3632
|
-
const dockerfilePath =
|
|
3793
|
+
const dockerfilePath = join8(dir, "Dockerfile");
|
|
3633
3794
|
if (!existsSync5(dockerfilePath)) {
|
|
3634
3795
|
throw new CLIError(`No Dockerfile found in ${dir}`);
|
|
3635
3796
|
}
|
|
@@ -3675,8 +3836,8 @@ function registerComputeDeployCommand(computeCmd2) {
|
|
|
3675
3836
|
serviceId = service.id;
|
|
3676
3837
|
flyAppId = service.flyAppId;
|
|
3677
3838
|
}
|
|
3678
|
-
const existingTomlPath =
|
|
3679
|
-
const backupTomlPath =
|
|
3839
|
+
const existingTomlPath = join8(dir, "fly.toml");
|
|
3840
|
+
const backupTomlPath = join8(dir, "fly.toml.insforge-backup");
|
|
3680
3841
|
let hadExistingToml = false;
|
|
3681
3842
|
if (existsSync5(existingTomlPath)) {
|
|
3682
3843
|
hadExistingToml = true;
|
|
@@ -3686,14 +3847,14 @@ function registerComputeDeployCommand(computeCmd2) {
|
|
|
3686
3847
|
writeFileSync4(existingTomlPath, tomlContent);
|
|
3687
3848
|
try {
|
|
3688
3849
|
if (!json) outputInfo("Building and deploying (this may take a few minutes)...");
|
|
3689
|
-
await new Promise((
|
|
3850
|
+
await new Promise((resolve4, reject) => {
|
|
3690
3851
|
const child = spawn(
|
|
3691
3852
|
"flyctl",
|
|
3692
3853
|
["deploy", "--remote-only", "--app", flyAppId, "--access-token", flyToken, "--yes"],
|
|
3693
3854
|
{ cwd: dir, stdio: json ? "pipe" : "inherit" }
|
|
3694
3855
|
);
|
|
3695
3856
|
child.on("close", (code) => {
|
|
3696
|
-
if (code === 0)
|
|
3857
|
+
if (code === 0) resolve4();
|
|
3697
3858
|
else reject(new CLIError(`flyctl deploy failed with exit code ${code}`));
|
|
3698
3859
|
});
|
|
3699
3860
|
child.on("error", (err) => reject(new CLIError(`flyctl deploy error: ${err.message}`)));
|
|
@@ -4477,7 +4638,7 @@ function formatBytesCompact(bytes) {
|
|
|
4477
4638
|
|
|
4478
4639
|
// src/index.ts
|
|
4479
4640
|
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
4480
|
-
var pkg = JSON.parse(readFileSync7(
|
|
4641
|
+
var pkg = JSON.parse(readFileSync7(join9(__dirname, "../package.json"), "utf-8"));
|
|
4481
4642
|
var INSFORGE_LOGO = `
|
|
4482
4643
|
\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
|
|
4483
4644
|
\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
|