@insforge/cli 0.1.43 → 0.1.44
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 +1418 -1258
- 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,1183 @@ ${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, 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", "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
|
+
]
|
|
1301
|
+
});
|
|
1302
|
+
if (clack7.isCancel(selected)) process.exit(0);
|
|
1303
|
+
template = selected;
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
captureEvent(orgId, "template_selected", {
|
|
1308
|
+
template,
|
|
1309
|
+
approach: template === "empty" ? "blank" : "template"
|
|
1310
|
+
});
|
|
1311
|
+
const hasTemplate = template !== "empty";
|
|
1312
|
+
let dirName = null;
|
|
1313
|
+
const originalCwd = process.cwd();
|
|
1314
|
+
let projectDir = originalCwd;
|
|
1315
|
+
if (hasTemplate) {
|
|
1316
|
+
dirName = projectName;
|
|
1317
|
+
if (!json) {
|
|
1318
|
+
const inputDir = await clack7.text({
|
|
1319
|
+
message: "Directory name:",
|
|
1320
|
+
initialValue: projectName,
|
|
1321
|
+
validate: (v) => {
|
|
1322
|
+
if (v.length < 1) return "Directory name is required";
|
|
1323
|
+
const normalized = path3.basename(v).replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
1324
|
+
if (!normalized || normalized === "." || normalized === "..") return "Invalid directory name";
|
|
1325
|
+
return void 0;
|
|
1326
|
+
}
|
|
1327
|
+
});
|
|
1328
|
+
if (clack7.isCancel(inputDir)) process.exit(0);
|
|
1329
|
+
dirName = path3.basename(inputDir).replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
1330
|
+
}
|
|
1331
|
+
if (!dirName || dirName === "." || dirName === "..") {
|
|
1332
|
+
throw new CLIError("Invalid directory name.");
|
|
1333
|
+
}
|
|
1334
|
+
projectDir = path3.resolve(originalCwd, dirName);
|
|
1335
|
+
const dirExists = await fs3.stat(projectDir).catch(() => null);
|
|
1336
|
+
if (dirExists) {
|
|
1337
|
+
throw new CLIError(`Directory "${dirName}" already exists.`);
|
|
1338
|
+
}
|
|
1339
|
+
await fs3.mkdir(projectDir);
|
|
1340
|
+
process.chdir(projectDir);
|
|
1341
|
+
}
|
|
1342
|
+
let projectLinked = false;
|
|
1343
|
+
const s = !json ? clack7.spinner() : null;
|
|
1344
|
+
try {
|
|
1345
|
+
s?.start("Creating project...");
|
|
1346
|
+
const project = await createProject(orgId, projectName, opts.region, apiUrl);
|
|
1347
|
+
s?.message("Waiting for project to become active...");
|
|
1348
|
+
await waitForProjectActive(project.id, apiUrl);
|
|
1349
|
+
const apiKey = await getProjectApiKey(project.id, apiUrl);
|
|
1350
|
+
const projectConfig = {
|
|
1351
|
+
project_id: project.id,
|
|
1352
|
+
project_name: project.name,
|
|
1353
|
+
org_id: project.organization_id,
|
|
1354
|
+
appkey: project.appkey,
|
|
1355
|
+
region: project.region,
|
|
1356
|
+
api_key: apiKey,
|
|
1357
|
+
oss_host: buildOssHost(project.appkey, project.region)
|
|
1358
|
+
};
|
|
1359
|
+
saveProjectConfig(projectConfig);
|
|
1360
|
+
projectLinked = true;
|
|
1361
|
+
s?.stop(`Project "${project.name}" created and linked`);
|
|
1362
|
+
const githubTemplates = ["chatbot", "crm", "e-commerce", "nextjs", "react"];
|
|
1363
|
+
if (githubTemplates.includes(template)) {
|
|
1364
|
+
await downloadGitHubTemplate(template, projectConfig, json);
|
|
1365
|
+
} else if (hasTemplate) {
|
|
1366
|
+
await downloadTemplate(template, projectConfig, projectName, json, apiUrl);
|
|
1367
|
+
} else {
|
|
1368
|
+
try {
|
|
1369
|
+
const anonKey = await getAnonKey();
|
|
1370
|
+
if (!anonKey) {
|
|
1371
|
+
if (!json) clack7.log.warn("Could not retrieve anon key. You can add it to .env.local manually.");
|
|
1372
|
+
} else {
|
|
1373
|
+
const envPath = path3.join(process.cwd(), ".env.local");
|
|
1374
|
+
const envContent = [
|
|
1375
|
+
"# InsForge",
|
|
1376
|
+
`NEXT_PUBLIC_INSFORGE_URL=${projectConfig.oss_host}`,
|
|
1377
|
+
`NEXT_PUBLIC_INSFORGE_ANON_KEY=${anonKey}`,
|
|
1378
|
+
""
|
|
1379
|
+
].join("\n");
|
|
1380
|
+
await fs3.writeFile(envPath, envContent, { flag: "wx" });
|
|
1381
|
+
if (!json) {
|
|
1382
|
+
clack7.log.success("Created .env.local with your InsForge credentials");
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
} catch (err) {
|
|
1386
|
+
const error = err;
|
|
1387
|
+
if (!json) {
|
|
1388
|
+
if (error.code === "EEXIST") {
|
|
1389
|
+
clack7.log.warn(".env.local already exists; skipping InsForge key seeding.");
|
|
1390
|
+
} else {
|
|
1391
|
+
clack7.log.warn(`Failed to create .env.local: ${error.message}`);
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
await installSkills(json);
|
|
1397
|
+
trackCommand("create", orgId);
|
|
1398
|
+
await reportCliUsage("cli.create", true, 6);
|
|
1399
|
+
const templateDownloaded = hasTemplate ? await fs3.stat(path3.join(process.cwd(), "package.json")).catch(() => null) : null;
|
|
1400
|
+
if (templateDownloaded) {
|
|
1401
|
+
const installSpinner = !json ? clack7.spinner() : null;
|
|
1402
|
+
installSpinner?.start("Installing dependencies...");
|
|
1403
|
+
try {
|
|
1404
|
+
await execAsync2("npm install", { cwd: process.cwd(), maxBuffer: 10 * 1024 * 1024 });
|
|
1405
|
+
installSpinner?.stop("Dependencies installed");
|
|
1406
|
+
} catch (err) {
|
|
1407
|
+
installSpinner?.stop("Failed to install dependencies");
|
|
1408
|
+
if (!json) {
|
|
1409
|
+
clack7.log.warn(`npm install failed: ${err.message}`);
|
|
1410
|
+
clack7.log.info("Run `npm install` manually to install dependencies.");
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
let liveUrl = null;
|
|
1415
|
+
if (templateDownloaded && !json) {
|
|
1416
|
+
const shouldDeploy = await clack7.confirm({
|
|
1417
|
+
message: "Would you like to deploy now?"
|
|
1418
|
+
});
|
|
1419
|
+
if (!clack7.isCancel(shouldDeploy) && shouldDeploy) {
|
|
1420
|
+
try {
|
|
1421
|
+
const envVars = await readEnvFile(process.cwd());
|
|
1422
|
+
const startBody = {};
|
|
1423
|
+
if (envVars.length > 0) {
|
|
1424
|
+
startBody.envVars = envVars;
|
|
1425
|
+
}
|
|
1426
|
+
const deploySpinner = clack7.spinner();
|
|
1427
|
+
const result = await deployProject({
|
|
1428
|
+
sourceDir: process.cwd(),
|
|
1429
|
+
startBody,
|
|
1430
|
+
spinner: deploySpinner
|
|
1431
|
+
});
|
|
1432
|
+
if (result.isReady) {
|
|
1433
|
+
deploySpinner.stop("Deployment complete");
|
|
1434
|
+
liveUrl = result.liveUrl;
|
|
1435
|
+
} else {
|
|
1436
|
+
deploySpinner.stop("Deployment is still building");
|
|
1437
|
+
clack7.log.info(`Deployment ID: ${result.deploymentId}`);
|
|
1438
|
+
clack7.log.warn("Deployment did not finish within 2 minutes.");
|
|
1439
|
+
clack7.log.info(`Check status with: npx @insforge/cli deployments status ${result.deploymentId}`);
|
|
1440
|
+
}
|
|
1441
|
+
} catch (err) {
|
|
1442
|
+
clack7.log.warn(`Deploy failed: ${err.message}`);
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
const dashboardUrl = `${getFrontendUrl()}/dashboard/project/${project.id}`;
|
|
1447
|
+
if (json) {
|
|
1448
|
+
outputJson({
|
|
1449
|
+
success: true,
|
|
1450
|
+
project: { id: project.id, name: project.name, appkey: project.appkey, region: project.region },
|
|
1451
|
+
template,
|
|
1452
|
+
...dirName ? { directory: dirName } : {},
|
|
1453
|
+
urls: {
|
|
1454
|
+
dashboard: dashboardUrl,
|
|
1455
|
+
...liveUrl ? { liveSite: liveUrl } : {}
|
|
1456
|
+
}
|
|
1457
|
+
});
|
|
1458
|
+
} else {
|
|
1459
|
+
clack7.log.step(`Dashboard: ${dashboardUrl}`);
|
|
1460
|
+
if (liveUrl) {
|
|
1461
|
+
clack7.log.success(`Live site: ${liveUrl}`);
|
|
1462
|
+
}
|
|
1463
|
+
if (templateDownloaded) {
|
|
1464
|
+
const steps = [
|
|
1465
|
+
`cd ${dirName}`,
|
|
1466
|
+
"npm run dev"
|
|
1467
|
+
];
|
|
1468
|
+
clack7.note(steps.join("\n"), "Next steps");
|
|
1469
|
+
clack7.note("Open your coding agent (Claude Code, Codex, Cursor, etc.) to add new features.", "Keep building");
|
|
1470
|
+
} else if (hasTemplate && !templateDownloaded) {
|
|
1471
|
+
clack7.log.warn("Template download failed. You can retry or set up manually.");
|
|
1472
|
+
} else {
|
|
1473
|
+
const prompts = [
|
|
1474
|
+
"Build a todo app with Google OAuth sign-in",
|
|
1475
|
+
"Build an Instagram clone where users can upload photos, like, and comment",
|
|
1476
|
+
"Build an AI chatbot with conversation history"
|
|
1477
|
+
];
|
|
1478
|
+
clack7.note(
|
|
1479
|
+
`Open your coding agent (Claude Code, Codex, Cursor, etc.) and try:
|
|
1480
|
+
|
|
1481
|
+
${prompts.map((p) => `\u2022 "${p}"`).join("\n")}`,
|
|
1482
|
+
"Start building"
|
|
1483
|
+
);
|
|
1484
|
+
}
|
|
1485
|
+
clack7.outro("Done!");
|
|
1486
|
+
}
|
|
1487
|
+
} catch (err) {
|
|
1488
|
+
if (!projectLinked && hasTemplate && projectDir !== originalCwd) {
|
|
1489
|
+
process.chdir(originalCwd);
|
|
1490
|
+
await fs3.rm(projectDir, { recursive: true, force: true }).catch(() => {
|
|
1491
|
+
});
|
|
1492
|
+
}
|
|
1493
|
+
throw err;
|
|
1494
|
+
}
|
|
1495
|
+
} catch (err) {
|
|
1496
|
+
handleError(err, json);
|
|
1497
|
+
} finally {
|
|
1498
|
+
await shutdownAnalytics();
|
|
1499
|
+
}
|
|
1500
|
+
});
|
|
1501
|
+
}
|
|
1502
|
+
async function downloadTemplate(framework, projectConfig, projectName, json, _apiUrl) {
|
|
1503
|
+
const s = !json ? clack7.spinner() : null;
|
|
1504
|
+
s?.start("Downloading template...");
|
|
1505
|
+
try {
|
|
1506
|
+
const anonKey = await getAnonKey();
|
|
1507
|
+
if (!anonKey) {
|
|
1508
|
+
throw new Error("Failed to retrieve anon key from backend");
|
|
1509
|
+
}
|
|
1510
|
+
const tempDir = tmpdir();
|
|
1511
|
+
const targetDir = projectName;
|
|
1512
|
+
const templatePath = path3.join(tempDir, targetDir);
|
|
1513
|
+
try {
|
|
1514
|
+
await fs3.rm(templatePath, { recursive: true, force: true });
|
|
1515
|
+
} catch {
|
|
1516
|
+
}
|
|
1517
|
+
const frame = framework === "nextjs" ? "nextjs" : "react";
|
|
1518
|
+
const esc = (s2) => process.platform === "win32" ? `"${s2.replace(/"/g, '\\"')}"` : `'${s2.replace(/'/g, "'\\''")}'`;
|
|
1519
|
+
const command = `npx --yes create-insforge-app@latest ${esc(targetDir)} --frame ${frame} --base-url ${esc(projectConfig.oss_host)} --anon-key ${esc(anonKey)} --skip-install`;
|
|
1520
|
+
s?.message(`Running create-insforge-app (${frame})...`);
|
|
1521
|
+
await execAsync2(command, {
|
|
1522
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1523
|
+
cwd: tempDir
|
|
1524
|
+
});
|
|
1525
|
+
s?.message("Copying template files...");
|
|
1526
|
+
const cwd = process.cwd();
|
|
1527
|
+
await copyDir(templatePath, cwd);
|
|
1528
|
+
await fs3.rm(templatePath, { recursive: true, force: true }).catch(() => {
|
|
1529
|
+
});
|
|
1530
|
+
s?.stop("Template files downloaded");
|
|
1531
|
+
} catch (err) {
|
|
1532
|
+
s?.stop("Template download failed");
|
|
1533
|
+
if (!json) {
|
|
1534
|
+
clack7.log.warn(`Failed to download template: ${err.message}`);
|
|
1535
|
+
clack7.log.info("You can manually set up the template later.");
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
async function downloadGitHubTemplate(templateName, projectConfig, json) {
|
|
1540
|
+
const s = !json ? clack7.spinner() : null;
|
|
1541
|
+
s?.start(`Downloading ${templateName} template...`);
|
|
1542
|
+
const tempDir = path3.join(tmpdir(), `insforge-template-${Date.now()}`);
|
|
1543
|
+
try {
|
|
1544
|
+
await fs3.mkdir(tempDir, { recursive: true });
|
|
1545
|
+
await execAsync2(
|
|
1546
|
+
"git clone --depth 1 https://github.com/InsForge/insforge-templates.git .",
|
|
1547
|
+
{ cwd: tempDir, maxBuffer: 10 * 1024 * 1024, timeout: 6e4 }
|
|
1548
|
+
);
|
|
1549
|
+
const templateDir = path3.join(tempDir, templateName);
|
|
1550
|
+
const stat5 = await fs3.stat(templateDir).catch(() => null);
|
|
1551
|
+
if (!stat5?.isDirectory()) {
|
|
1552
|
+
throw new Error(`Template "${templateName}" not found in repository`);
|
|
1553
|
+
}
|
|
1554
|
+
s?.message("Copying template files...");
|
|
1555
|
+
const cwd = process.cwd();
|
|
1556
|
+
await copyDir(templateDir, cwd);
|
|
1557
|
+
const envExamplePath = path3.join(cwd, ".env.example");
|
|
1558
|
+
const envExampleExists = await fs3.stat(envExamplePath).catch(() => null);
|
|
1559
|
+
if (envExampleExists) {
|
|
1560
|
+
const anonKey = await getAnonKey();
|
|
1561
|
+
const envExample = await fs3.readFile(envExamplePath, "utf-8");
|
|
1562
|
+
const envContent = envExample.replace(
|
|
1563
|
+
/^([A-Z][A-Z0-9_]*=)(.*)$/gm,
|
|
1564
|
+
(_, prefix, _value) => {
|
|
1565
|
+
const key = prefix.slice(0, -1);
|
|
1566
|
+
if (/INSFORGE.*(URL|BASE_URL)$/.test(key)) return `${prefix}${projectConfig.oss_host}`;
|
|
1567
|
+
if (/INSFORGE.*ANON_KEY$/.test(key)) return `${prefix}${anonKey}`;
|
|
1568
|
+
if (key === "NEXT_PUBLIC_APP_URL") return `${prefix}https://${projectConfig.appkey}.insforge.site`;
|
|
1569
|
+
return `${prefix}${_value}`;
|
|
1570
|
+
}
|
|
1571
|
+
);
|
|
1572
|
+
const envLocalPath = path3.join(cwd, ".env.local");
|
|
1573
|
+
try {
|
|
1574
|
+
await fs3.writeFile(envLocalPath, envContent, { flag: "wx" });
|
|
1575
|
+
} catch (e) {
|
|
1576
|
+
if (e.code === "EEXIST") {
|
|
1577
|
+
if (!json) clack7.log.warn(".env.local already exists; skipping env seeding.");
|
|
1578
|
+
} else {
|
|
1579
|
+
throw e;
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
s?.stop(`${templateName} template downloaded`);
|
|
1584
|
+
const migrationPath = path3.join(cwd, "migrations", "db_init.sql");
|
|
1585
|
+
const migrationExists = await fs3.stat(migrationPath).catch(() => null);
|
|
1586
|
+
if (migrationExists) {
|
|
1587
|
+
const dbSpinner = !json ? clack7.spinner() : null;
|
|
1588
|
+
dbSpinner?.start("Running database migrations...");
|
|
1589
|
+
try {
|
|
1590
|
+
const sql = await fs3.readFile(migrationPath, "utf-8");
|
|
1591
|
+
await runRawSql(sql, true);
|
|
1592
|
+
dbSpinner?.stop("Database migrations applied");
|
|
1593
|
+
} catch (err) {
|
|
1594
|
+
dbSpinner?.stop("Database migration failed");
|
|
1595
|
+
if (!json) {
|
|
1596
|
+
clack7.log.warn(`Migration failed: ${err.message}`);
|
|
1597
|
+
clack7.log.info('You can run the migration manually: npx @insforge/cli db query --unrestricted "$(cat migrations/db_init.sql)"');
|
|
1598
|
+
} else {
|
|
1599
|
+
throw err;
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
} catch (err) {
|
|
1604
|
+
s?.stop(`${templateName} template download failed`);
|
|
1605
|
+
if (!json) {
|
|
1606
|
+
clack7.log.warn(`Failed to download ${templateName} template: ${err.message}`);
|
|
1607
|
+
clack7.log.info("You can manually clone from: https://github.com/InsForge/insforge-templates");
|
|
1608
|
+
}
|
|
1609
|
+
} finally {
|
|
1610
|
+
await fs3.rm(tempDir, { recursive: true, force: true }).catch(() => {
|
|
1611
|
+
});
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
// src/commands/projects/link.ts
|
|
1616
|
+
var execAsync3 = promisify3(exec3);
|
|
1617
|
+
function buildOssHost2(appkey, region) {
|
|
1618
|
+
return `https://${appkey}.${region}.insforge.app`;
|
|
1619
|
+
}
|
|
1620
|
+
function registerProjectLinkCommand(program2) {
|
|
1621
|
+
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").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) => {
|
|
1622
|
+
const { json, apiUrl } = getRootOpts(cmd);
|
|
1623
|
+
try {
|
|
1624
|
+
if (opts.apiBaseUrl || opts.apiKey) {
|
|
1625
|
+
try {
|
|
1626
|
+
if (!opts.apiBaseUrl || !opts.apiKey) {
|
|
1627
|
+
throw new CLIError("Both --api-base-url and --api-key must be provided together for direct linking.");
|
|
1628
|
+
}
|
|
1629
|
+
try {
|
|
1630
|
+
new URL(opts.apiBaseUrl);
|
|
1631
|
+
} catch {
|
|
1632
|
+
throw new CLIError("Invalid --api-base-url. Please provide a valid URL.");
|
|
1633
|
+
}
|
|
1634
|
+
const projectConfig2 = {
|
|
1635
|
+
project_id: "oss-project",
|
|
1636
|
+
project_name: "oss-project",
|
|
1637
|
+
org_id: "oss-org",
|
|
1638
|
+
appkey: "oss",
|
|
1639
|
+
region: "local",
|
|
1640
|
+
api_key: opts.apiKey,
|
|
1641
|
+
oss_host: opts.apiBaseUrl.replace(/\/$/, "")
|
|
1642
|
+
// remove trailing slash if any
|
|
1643
|
+
};
|
|
1644
|
+
saveProjectConfig(projectConfig2);
|
|
1645
|
+
if (json) {
|
|
1646
|
+
outputJson({ success: true, project: { id: projectConfig2.project_id, name: projectConfig2.project_name, region: projectConfig2.region } });
|
|
1647
|
+
} else {
|
|
1648
|
+
outputSuccess(`Linked to direct project at ${projectConfig2.oss_host}`);
|
|
1649
|
+
}
|
|
1650
|
+
trackCommand("link", "oss-org", { direct: true });
|
|
1651
|
+
await installSkills(json);
|
|
1652
|
+
await reportCliUsage("cli.link_direct", true, 6);
|
|
1653
|
+
try {
|
|
1654
|
+
const urlMatch = opts.apiBaseUrl.match(/^https?:\/\/([^.]+)\.[^.]+\.insforge\.app/);
|
|
1655
|
+
if (urlMatch) {
|
|
1656
|
+
await reportAgentConnected({ app_key: urlMatch[1] }, apiUrl);
|
|
1657
|
+
}
|
|
1658
|
+
} catch {
|
|
1659
|
+
}
|
|
1660
|
+
return;
|
|
1661
|
+
} catch (err) {
|
|
1662
|
+
await reportCliUsage("cli.link_direct", false);
|
|
1663
|
+
handleError(err, json);
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
const creds = await requireAuth(apiUrl, false);
|
|
1667
|
+
let orgId = opts.orgId;
|
|
1668
|
+
let projectId = opts.projectId;
|
|
1669
|
+
if (!orgId && !projectId) {
|
|
1670
|
+
const orgs = await listOrganizations(apiUrl);
|
|
1671
|
+
if (orgs.length === 0) {
|
|
1672
|
+
throw new CLIError("No organizations found.");
|
|
1673
|
+
}
|
|
1674
|
+
if (orgs.length === 1) {
|
|
1675
|
+
orgId = orgs[0].id;
|
|
1676
|
+
if (!json) clack8.log.info(`Using organization: ${orgs[0].name}`);
|
|
1677
|
+
} else {
|
|
1678
|
+
if (json) {
|
|
1679
|
+
throw new CLIError("Multiple organizations found. Specify --org-id.");
|
|
1680
|
+
}
|
|
1681
|
+
const selected = await clack8.select({
|
|
1682
|
+
message: "Select an organization:",
|
|
1683
|
+
options: orgs.map((o) => ({
|
|
1684
|
+
value: o.id,
|
|
1685
|
+
label: o.name
|
|
1686
|
+
}))
|
|
1687
|
+
});
|
|
1688
|
+
if (clack8.isCancel(selected)) process.exit(0);
|
|
1689
|
+
orgId = selected;
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
const config = getGlobalConfig();
|
|
1693
|
+
config.default_org_id = orgId;
|
|
1694
|
+
saveGlobalConfig(config);
|
|
1695
|
+
if (!projectId) {
|
|
1696
|
+
const projects = await listProjects(orgId, apiUrl);
|
|
1697
|
+
if (projects.length === 0) {
|
|
1698
|
+
throw new CLIError("No projects found in this organization.");
|
|
1699
|
+
}
|
|
1700
|
+
if (json) {
|
|
1701
|
+
throw new CLIError("Specify --project-id in JSON mode.");
|
|
1702
|
+
}
|
|
1703
|
+
const selected = await clack8.select({
|
|
1704
|
+
message: "Select a project to link:",
|
|
1705
|
+
options: projects.map((p) => ({
|
|
1706
|
+
value: p.id,
|
|
1707
|
+
label: `${p.name} (${p.region}, ${p.status})`
|
|
1708
|
+
}))
|
|
1709
|
+
});
|
|
1710
|
+
if (clack8.isCancel(selected)) process.exit(0);
|
|
1711
|
+
projectId = selected;
|
|
1712
|
+
}
|
|
1713
|
+
let project;
|
|
1714
|
+
let apiKey;
|
|
1715
|
+
try {
|
|
1716
|
+
[project, apiKey] = await Promise.all([
|
|
1717
|
+
getProject(projectId, apiUrl),
|
|
1718
|
+
getProjectApiKey(projectId, apiUrl)
|
|
1719
|
+
]);
|
|
1720
|
+
} catch (err) {
|
|
1721
|
+
if (err instanceof CLIError && (err.exitCode === 5 || err.exitCode === 4 || err.message.includes("not found"))) {
|
|
1722
|
+
const identity = creds.user?.email ?? creds.user?.name ?? "unknown user";
|
|
1723
|
+
throw new CLIError(
|
|
1724
|
+
`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.`,
|
|
1725
|
+
5,
|
|
1726
|
+
"PERMISSION_DENIED"
|
|
1727
|
+
);
|
|
1728
|
+
}
|
|
1729
|
+
throw err;
|
|
1730
|
+
}
|
|
1731
|
+
const projectConfig = {
|
|
1732
|
+
project_id: project.id,
|
|
1733
|
+
project_name: project.name,
|
|
1734
|
+
org_id: project.organization_id,
|
|
1735
|
+
appkey: project.appkey,
|
|
1736
|
+
region: project.region,
|
|
1737
|
+
api_key: apiKey,
|
|
1738
|
+
oss_host: buildOssHost2(project.appkey, project.region)
|
|
1739
|
+
};
|
|
1740
|
+
if (!opts.template) {
|
|
1741
|
+
saveProjectConfig(projectConfig);
|
|
1742
|
+
}
|
|
1743
|
+
trackCommand("link", project.organization_id);
|
|
1744
|
+
if (json) {
|
|
1745
|
+
outputJson({ success: true, project: { id: project.id, name: project.name, region: project.region } });
|
|
1746
|
+
} else {
|
|
1747
|
+
outputSuccess(`Linked to project "${project.name}" (${project.appkey}.${project.region})`);
|
|
1748
|
+
}
|
|
1749
|
+
await installSkills(json);
|
|
1750
|
+
await reportCliUsage("cli.link", true, 6);
|
|
1751
|
+
try {
|
|
1752
|
+
await reportAgentConnected({ project_id: project.id }, apiUrl);
|
|
1753
|
+
} catch {
|
|
1754
|
+
}
|
|
1755
|
+
const template = opts.template;
|
|
1756
|
+
if (template) {
|
|
1757
|
+
const validTemplates = ["react", "nextjs", "chatbot", "crm", "e-commerce"];
|
|
1758
|
+
if (!validTemplates.includes(template)) {
|
|
1759
|
+
throw new CLIError(`Invalid template "${template}". Valid options: ${validTemplates.join(", ")}`);
|
|
1760
|
+
}
|
|
1761
|
+
let dirName = project.name;
|
|
1762
|
+
if (!json) {
|
|
1763
|
+
const inputDir = await clack8.text({
|
|
1764
|
+
message: "Directory name:",
|
|
1765
|
+
initialValue: project.name,
|
|
1766
|
+
validate: (v) => {
|
|
1767
|
+
if (v.length < 1) return "Directory name is required";
|
|
1768
|
+
const normalized = path4.basename(v).replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
1769
|
+
if (!normalized || normalized === "." || normalized === "..") return "Invalid directory name";
|
|
1770
|
+
return void 0;
|
|
1771
|
+
}
|
|
1772
|
+
});
|
|
1773
|
+
if (clack8.isCancel(inputDir)) process.exit(0);
|
|
1774
|
+
dirName = path4.basename(inputDir).replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
1775
|
+
}
|
|
1776
|
+
if (!dirName || dirName === "." || dirName === "..") {
|
|
1777
|
+
throw new CLIError("Invalid directory name.");
|
|
1778
|
+
}
|
|
1779
|
+
const templateDir = path4.resolve(process.cwd(), dirName);
|
|
1780
|
+
const dirExists = await fs4.stat(templateDir).catch(() => null);
|
|
1781
|
+
if (dirExists) {
|
|
1782
|
+
throw new CLIError(`Directory "${dirName}" already exists.`);
|
|
1783
|
+
}
|
|
1784
|
+
await fs4.mkdir(templateDir);
|
|
1785
|
+
process.chdir(templateDir);
|
|
1786
|
+
saveProjectConfig(projectConfig);
|
|
1787
|
+
captureEvent(orgId ?? project.organization_id, "template_selected", { template, source: "link" });
|
|
1788
|
+
const githubTemplates = ["chatbot", "crm", "e-commerce", "nextjs", "react"];
|
|
1789
|
+
if (githubTemplates.includes(template)) {
|
|
1790
|
+
await downloadGitHubTemplate(template, projectConfig, json);
|
|
1791
|
+
} else {
|
|
1792
|
+
await downloadTemplate(template, projectConfig, project.name, json, apiUrl);
|
|
1793
|
+
}
|
|
1794
|
+
const templateDownloaded = await fs4.stat(path4.join(process.cwd(), "package.json")).catch(() => null);
|
|
1795
|
+
if (templateDownloaded && !json) {
|
|
1796
|
+
const installSpinner = clack8.spinner();
|
|
1797
|
+
installSpinner.start("Installing dependencies...");
|
|
1798
|
+
try {
|
|
1799
|
+
await execAsync3("npm install", { cwd: process.cwd(), maxBuffer: 10 * 1024 * 1024 });
|
|
1800
|
+
installSpinner.stop("Dependencies installed");
|
|
1801
|
+
} catch (err) {
|
|
1802
|
+
installSpinner.stop("Failed to install dependencies");
|
|
1803
|
+
clack8.log.warn(`npm install failed: ${err.message}`);
|
|
1804
|
+
clack8.log.info("Run `npm install` manually to install dependencies.");
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
if (!json) {
|
|
1808
|
+
const dashboardUrl = `${getFrontendUrl()}/dashboard/project/${project.id}`;
|
|
1809
|
+
clack8.log.step(`Dashboard: ${dashboardUrl}`);
|
|
1810
|
+
if (templateDownloaded) {
|
|
1811
|
+
const steps = [`cd ${dirName}`, "npm run dev"];
|
|
1812
|
+
clack8.note(steps.join("\n"), "Next steps");
|
|
1813
|
+
clack8.note("Open your coding agent (Claude Code, Codex, Cursor, etc.) to add new features.", "Keep building");
|
|
1814
|
+
} else {
|
|
1815
|
+
clack8.log.warn("Template download failed. You can retry or set up manually.");
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
} else if (!json) {
|
|
1819
|
+
const dashboardUrl = `${getFrontendUrl()}/dashboard/project/${project.id}`;
|
|
1820
|
+
clack8.log.step(`Dashboard: ${dashboardUrl}`);
|
|
1821
|
+
const prompts = [
|
|
1822
|
+
"Build a todo app with Google OAuth sign-in",
|
|
1823
|
+
"Build an Instagram clone where users can upload photos, like, and comment",
|
|
1824
|
+
"Build an AI chatbot with conversation history"
|
|
1825
|
+
];
|
|
1826
|
+
clack8.note(
|
|
1827
|
+
`Open your coding agent (Claude Code, Codex, Cursor, etc.) and try:
|
|
1828
|
+
|
|
1829
|
+
${prompts.map((p) => `\u2022 "${p}"`).join("\n")}`,
|
|
1830
|
+
"Start building"
|
|
1831
|
+
);
|
|
1832
|
+
}
|
|
1833
|
+
} catch (err) {
|
|
1834
|
+
await reportCliUsage("cli.link", false);
|
|
1835
|
+
handleError(err, json);
|
|
1836
|
+
} finally {
|
|
1837
|
+
await shutdownAnalytics();
|
|
1838
|
+
}
|
|
1839
|
+
});
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
// src/commands/db/query.ts
|
|
1843
|
+
function registerDbCommands(dbCmd2) {
|
|
1844
|
+
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) => {
|
|
1845
|
+
const { json } = getRootOpts(cmd);
|
|
1846
|
+
try {
|
|
1847
|
+
await requireAuth();
|
|
1848
|
+
const { rows, raw } = await runRawSql(sql, !!opts.unrestricted);
|
|
1849
|
+
if (json) {
|
|
1850
|
+
outputJson(raw);
|
|
1851
|
+
} else {
|
|
1852
|
+
if (rows.length > 0) {
|
|
1853
|
+
const headers = Object.keys(rows[0]);
|
|
1854
|
+
outputTable(
|
|
1855
|
+
headers,
|
|
1856
|
+
rows.map((row) => headers.map((h) => String(row[h] ?? "")))
|
|
1857
|
+
);
|
|
1858
|
+
console.log(`${rows.length} row(s) returned.`);
|
|
1859
|
+
} else {
|
|
1860
|
+
console.log("Query executed successfully.");
|
|
1861
|
+
if (rows.length === 0) {
|
|
1862
|
+
console.log("No rows returned.");
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
await reportCliUsage("cli.db.query", true);
|
|
1867
|
+
} catch (err) {
|
|
1868
|
+
await reportCliUsage("cli.db.query", false);
|
|
1869
|
+
handleError(err, json);
|
|
1870
|
+
}
|
|
1871
|
+
});
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
// src/commands/db/tables.ts
|
|
1875
|
+
function registerDbTablesCommand(dbCmd2) {
|
|
1876
|
+
dbCmd2.command("tables").description("List all database tables").action(async (_opts, cmd) => {
|
|
1877
|
+
const { json } = getRootOpts(cmd);
|
|
1878
|
+
try {
|
|
1879
|
+
await requireAuth();
|
|
1880
|
+
const res = await ossFetch("/api/database/tables");
|
|
1881
|
+
const tables = await res.json();
|
|
1882
|
+
if (json) {
|
|
1883
|
+
outputJson(tables);
|
|
1884
|
+
} else {
|
|
1885
|
+
if (tables.length === 0) {
|
|
1886
|
+
console.log("No tables found.");
|
|
1887
|
+
return;
|
|
1888
|
+
}
|
|
1889
|
+
outputTable(
|
|
1890
|
+
["Table Name"],
|
|
1891
|
+
tables.map((t) => [t])
|
|
1892
|
+
);
|
|
1893
|
+
}
|
|
1894
|
+
await reportCliUsage("cli.db.tables", true);
|
|
1895
|
+
} catch (err) {
|
|
1896
|
+
await reportCliUsage("cli.db.tables", false);
|
|
1897
|
+
handleError(err, json);
|
|
1898
|
+
}
|
|
1899
|
+
});
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
// src/commands/db/functions.ts
|
|
1903
|
+
function registerDbFunctionsCommand(dbCmd2) {
|
|
1904
|
+
dbCmd2.command("functions").description("List all database functions").action(async (_opts, cmd) => {
|
|
1905
|
+
const { json } = getRootOpts(cmd);
|
|
1906
|
+
try {
|
|
1907
|
+
await requireAuth();
|
|
1908
|
+
const res = await ossFetch("/api/database/functions");
|
|
1909
|
+
const raw = await res.json();
|
|
1910
|
+
const functions = Array.isArray(raw) ? raw : raw.functions ?? [];
|
|
1911
|
+
if (json) {
|
|
1912
|
+
outputJson(raw);
|
|
1913
|
+
} else {
|
|
1914
|
+
if (functions.length === 0) {
|
|
1915
|
+
console.log("No database functions found.");
|
|
1916
|
+
return;
|
|
1917
|
+
}
|
|
1918
|
+
outputTable(
|
|
1919
|
+
["Name", "Definition", "Kind"],
|
|
1920
|
+
functions.map((f) => [f.functionName, f.functionDef, f.kind])
|
|
1921
|
+
);
|
|
1922
|
+
}
|
|
1923
|
+
await reportCliUsage("cli.db.functions", true);
|
|
1924
|
+
} catch (err) {
|
|
1925
|
+
await reportCliUsage("cli.db.functions", false);
|
|
1926
|
+
handleError(err, json);
|
|
1927
|
+
}
|
|
1928
|
+
});
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
// src/commands/db/indexes.ts
|
|
1932
|
+
function registerDbIndexesCommand(dbCmd2) {
|
|
1933
|
+
dbCmd2.command("indexes").description("List all database indexes").action(async (_opts, cmd) => {
|
|
1934
|
+
const { json } = getRootOpts(cmd);
|
|
1935
|
+
try {
|
|
1936
|
+
await requireAuth();
|
|
1937
|
+
const res = await ossFetch("/api/database/indexes");
|
|
1938
|
+
const raw = await res.json();
|
|
1939
|
+
const indexes = Array.isArray(raw) ? raw : raw.indexes ?? [];
|
|
1940
|
+
if (json) {
|
|
1941
|
+
outputJson(raw);
|
|
1942
|
+
} else {
|
|
1943
|
+
if (indexes.length === 0) {
|
|
1944
|
+
console.log("No database indexes found.");
|
|
1945
|
+
return;
|
|
1946
|
+
}
|
|
1947
|
+
outputTable(
|
|
1948
|
+
["Table", "Index Name", "Definition", "Unique", "Primary"],
|
|
1949
|
+
indexes.map((i) => [
|
|
1950
|
+
i.tableName,
|
|
1951
|
+
i.indexName,
|
|
1952
|
+
i.indexDef,
|
|
1953
|
+
i.isUnique ? "Yes" : "No",
|
|
1954
|
+
i.isPrimary ? "Yes" : "No"
|
|
1955
|
+
])
|
|
1956
|
+
);
|
|
1957
|
+
}
|
|
1958
|
+
await reportCliUsage("cli.db.indexes", true);
|
|
1959
|
+
} catch (err) {
|
|
1960
|
+
await reportCliUsage("cli.db.indexes", false);
|
|
1961
|
+
handleError(err, json);
|
|
1962
|
+
}
|
|
1963
|
+
});
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
// src/commands/db/policies.ts
|
|
1967
|
+
function registerDbPoliciesCommand(dbCmd2) {
|
|
1968
|
+
dbCmd2.command("policies").description("List all RLS policies").action(async (_opts, cmd) => {
|
|
1969
|
+
const { json } = getRootOpts(cmd);
|
|
1970
|
+
try {
|
|
1971
|
+
await requireAuth();
|
|
1972
|
+
const res = await ossFetch("/api/database/policies");
|
|
1973
|
+
const raw = await res.json();
|
|
1974
|
+
const policies = Array.isArray(raw) ? raw : raw.policies ?? [];
|
|
1975
|
+
if (json) {
|
|
1976
|
+
outputJson(raw);
|
|
1977
|
+
} else {
|
|
1978
|
+
if (policies.length === 0) {
|
|
1979
|
+
console.log("No RLS policies found.");
|
|
1980
|
+
return;
|
|
1981
|
+
}
|
|
1982
|
+
outputTable(
|
|
1983
|
+
["Table", "Policy Name", "Command", "Roles", "Qual", "With Check"],
|
|
1984
|
+
policies.map((p) => [
|
|
1985
|
+
String(p.tableName ?? "-"),
|
|
1986
|
+
String(p.policyName ?? "-"),
|
|
1987
|
+
String(p.cmd ?? "-"),
|
|
1988
|
+
Array.isArray(p.roles) ? p.roles.join(", ") : String(p.roles ?? "-"),
|
|
1989
|
+
String(p.qual ?? "-"),
|
|
1990
|
+
String(p.withCheck ?? "-")
|
|
1991
|
+
])
|
|
1992
|
+
);
|
|
1993
|
+
}
|
|
1994
|
+
await reportCliUsage("cli.db.policies", true);
|
|
1995
|
+
} catch (err) {
|
|
1996
|
+
await reportCliUsage("cli.db.policies", false);
|
|
1997
|
+
handleError(err, json);
|
|
1998
|
+
}
|
|
1999
|
+
});
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
// src/commands/db/triggers.ts
|
|
2003
|
+
function registerDbTriggersCommand(dbCmd2) {
|
|
2004
|
+
dbCmd2.command("triggers").description("List all database triggers").action(async (_opts, cmd) => {
|
|
2005
|
+
const { json } = getRootOpts(cmd);
|
|
2006
|
+
try {
|
|
2007
|
+
await requireAuth();
|
|
2008
|
+
const res = await ossFetch("/api/database/triggers");
|
|
2009
|
+
const raw = await res.json();
|
|
2010
|
+
const triggers = Array.isArray(raw) ? raw : raw.triggers ?? [];
|
|
2011
|
+
if (json) {
|
|
2012
|
+
outputJson(raw);
|
|
2013
|
+
} else {
|
|
2014
|
+
if (triggers.length === 0) {
|
|
2015
|
+
console.log("No database triggers found.");
|
|
2016
|
+
return;
|
|
2017
|
+
}
|
|
2018
|
+
outputTable(
|
|
2019
|
+
["Name", "Table", "Timing", "Events", "ActionOrientation", "ActionCondition", "ActionStatement"],
|
|
2020
|
+
triggers.map((t) => [
|
|
2021
|
+
t.triggerName,
|
|
2022
|
+
t.tableName,
|
|
2023
|
+
t.actionTiming,
|
|
2024
|
+
t.eventManipulation,
|
|
2025
|
+
t.actionOrientation,
|
|
2026
|
+
t.actionCondition ?? "-",
|
|
2027
|
+
t.actionStatement
|
|
2028
|
+
])
|
|
2029
|
+
);
|
|
2030
|
+
}
|
|
2031
|
+
await reportCliUsage("cli.db.triggers", true);
|
|
2032
|
+
} catch (err) {
|
|
2033
|
+
await reportCliUsage("cli.db.triggers", false);
|
|
2034
|
+
handleError(err, json);
|
|
2035
|
+
}
|
|
2036
|
+
});
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
// src/commands/db/rpc.ts
|
|
2040
|
+
function registerDbRpcCommand(dbCmd2) {
|
|
2041
|
+
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) => {
|
|
2042
|
+
const { json } = getRootOpts(cmd);
|
|
2043
|
+
try {
|
|
2044
|
+
await requireAuth();
|
|
2045
|
+
const body = opts.data ? JSON.stringify(JSON.parse(opts.data)) : void 0;
|
|
2046
|
+
const res = await ossFetch(`/api/database/rpc/${encodeURIComponent(functionName)}`, {
|
|
2047
|
+
method: body ? "POST" : "GET",
|
|
2048
|
+
...body ? { body } : {}
|
|
2049
|
+
});
|
|
2050
|
+
const result = await res.json();
|
|
2051
|
+
if (json) {
|
|
2052
|
+
outputJson(result);
|
|
2053
|
+
} else {
|
|
2054
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2055
|
+
}
|
|
2056
|
+
await reportCliUsage("cli.db.rpc", true);
|
|
2057
|
+
} catch (err) {
|
|
2058
|
+
handleError(err, json);
|
|
2059
|
+
}
|
|
2060
|
+
});
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
// src/commands/db/export.ts
|
|
2064
|
+
import { writeFileSync as writeFileSync2 } from "fs";
|
|
2065
|
+
function registerDbExportCommand(dbCmd2) {
|
|
2066
|
+
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) => {
|
|
2067
|
+
const { json } = getRootOpts(cmd);
|
|
2068
|
+
try {
|
|
2069
|
+
await requireAuth();
|
|
2070
|
+
const body = {
|
|
2071
|
+
format: opts.format,
|
|
2072
|
+
includeData: opts.data !== false
|
|
2073
|
+
};
|
|
2074
|
+
if (opts.tables) {
|
|
2075
|
+
body.tables = opts.tables.split(",").map((t) => t.trim());
|
|
1262
2076
|
}
|
|
1263
2077
|
if (opts.includeFunctions) body.includeFunctions = true;
|
|
1264
2078
|
if (opts.includeSequences) body.includeSequences = true;
|
|
@@ -1302,7 +2116,7 @@ function registerDbExportCommand(dbCmd2) {
|
|
|
1302
2116
|
|
|
1303
2117
|
// src/commands/db/import.ts
|
|
1304
2118
|
import { readFileSync as readFileSync3 } from "fs";
|
|
1305
|
-
import { basename } from "path";
|
|
2119
|
+
import { basename as basename4 } from "path";
|
|
1306
2120
|
function registerDbImportCommand(dbCmd2) {
|
|
1307
2121
|
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
2122
|
const { json } = getRootOpts(cmd);
|
|
@@ -1311,7 +2125,7 @@ function registerDbImportCommand(dbCmd2) {
|
|
|
1311
2125
|
const config = getProjectConfig();
|
|
1312
2126
|
if (!config) throw new ProjectNotLinkedError();
|
|
1313
2127
|
const fileContent = readFileSync3(file);
|
|
1314
|
-
const fileName =
|
|
2128
|
+
const fileName = basename4(file);
|
|
1315
2129
|
const formData = new FormData();
|
|
1316
2130
|
formData.append("file", new Blob([fileContent]), fileName);
|
|
1317
2131
|
if (opts.truncate) {
|
|
@@ -1353,8 +2167,8 @@ function registerRecordsCommands(recordsCmd2) {
|
|
|
1353
2167
|
if (opts.limit) params.set("limit", String(opts.limit));
|
|
1354
2168
|
if (opts.offset) params.set("offset", String(opts.offset));
|
|
1355
2169
|
const query = params.toString();
|
|
1356
|
-
const
|
|
1357
|
-
const res = await ossFetch(
|
|
2170
|
+
const path5 = `/api/database/records/${encodeURIComponent(table)}${query ? `?${query}` : ""}`;
|
|
2171
|
+
const res = await ossFetch(path5);
|
|
1358
2172
|
const data = await res.json();
|
|
1359
2173
|
const records = data.data ?? [];
|
|
1360
2174
|
if (json) {
|
|
@@ -1524,17 +2338,17 @@ function registerFunctionsCommands(functionsCmd2) {
|
|
|
1524
2338
|
|
|
1525
2339
|
// src/commands/functions/deploy.ts
|
|
1526
2340
|
import { readFileSync as readFileSync4, existsSync as existsSync3 } from "fs";
|
|
1527
|
-
import { join as
|
|
2341
|
+
import { join as join6 } from "path";
|
|
1528
2342
|
function registerFunctionsDeployCommand(functionsCmd2) {
|
|
1529
2343
|
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
2344
|
const { json } = getRootOpts(cmd);
|
|
1531
2345
|
try {
|
|
1532
2346
|
await requireAuth();
|
|
1533
|
-
const filePath = opts.file ??
|
|
2347
|
+
const filePath = opts.file ?? join6(process.cwd(), "insforge", "functions", slug, "index.ts");
|
|
1534
2348
|
if (!existsSync3(filePath)) {
|
|
1535
2349
|
throw new CLIError(
|
|
1536
2350
|
`Source file not found: ${filePath}
|
|
1537
|
-
Specify --file <path> or create ${
|
|
2351
|
+
Specify --file <path> or create ${join6("insforge", "functions", slug, "index.ts")}`
|
|
1538
2352
|
);
|
|
1539
2353
|
}
|
|
1540
2354
|
const code = readFileSync4(filePath, "utf-8");
|
|
@@ -1617,238 +2431,15 @@ function registerFunctionsInvokeCommand(functionsCmd2) {
|
|
|
1617
2431
|
const data = await res.json();
|
|
1618
2432
|
if (json) {
|
|
1619
2433
|
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);
|
|
1842
|
-
}
|
|
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);
|
|
2434
|
+
} else {
|
|
2435
|
+
console.log(JSON.stringify(data, null, 2));
|
|
2436
|
+
}
|
|
1850
2437
|
} else {
|
|
1851
|
-
|
|
2438
|
+
const text4 = await res.text();
|
|
2439
|
+
console.log(text4);
|
|
2440
|
+
}
|
|
2441
|
+
if (status >= 400) {
|
|
2442
|
+
throw new CLIError(`HTTP ${status}`, 1, "HTTP_ERROR");
|
|
1852
2443
|
}
|
|
1853
2444
|
} catch (err) {
|
|
1854
2445
|
handleError(err, json);
|
|
@@ -1856,715 +2447,284 @@ function registerStorageDeleteBucketCommand(storageCmd2) {
|
|
|
1856
2447
|
});
|
|
1857
2448
|
}
|
|
1858
2449
|
|
|
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) => {
|
|
2450
|
+
// src/commands/functions/code.ts
|
|
2451
|
+
function registerFunctionsCodeCommand(functionsCmd2) {
|
|
2452
|
+
functionsCmd2.command("code <slug>").description("Fetch and display the source code of an edge function").action(async (slug, _opts, cmd) => {
|
|
1867
2453
|
const { json } = getRootOpts(cmd);
|
|
1868
2454
|
try {
|
|
1869
2455
|
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
|
-
});
|
|
2456
|
+
const res = await ossFetch(`/api/functions/${encodeURIComponent(slug)}`);
|
|
2457
|
+
const fn = await res.json();
|
|
1886
2458
|
if (json) {
|
|
1887
|
-
outputJson(
|
|
2459
|
+
outputJson(fn);
|
|
1888
2460
|
} 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
|
-
);
|
|
2461
|
+
console.log(`Function: ${fn.name} (${fn.slug})`);
|
|
2462
|
+
console.log(`Status: ${fn.status}`);
|
|
2463
|
+
if (fn.description) console.log(`Desc: ${fn.description}`);
|
|
2464
|
+
if (fn.deployed_at) console.log(`Deployed: ${fn.deployed_at}`);
|
|
2465
|
+
console.log("---");
|
|
2466
|
+
console.log(fn.code);
|
|
1907
2467
|
}
|
|
1908
|
-
await reportCliUsage("cli.storage.list-objects", true);
|
|
1909
2468
|
} catch (err) {
|
|
1910
|
-
await reportCliUsage("cli.storage.list-objects", false);
|
|
1911
2469
|
handleError(err, json);
|
|
1912
2470
|
}
|
|
1913
2471
|
});
|
|
1914
2472
|
}
|
|
1915
2473
|
|
|
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";
|
|
2474
|
+
// src/commands/functions/delete.ts
|
|
1955
2475
|
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);
|
|
2476
|
+
function registerFunctionsDeleteCommand(functionsCmd2) {
|
|
2477
|
+
functionsCmd2.command("delete <slug>").description("Delete an edge function").action(async (slug, _opts, cmd) => {
|
|
2478
|
+
const { json, yes } = getRootOpts(cmd);
|
|
2067
2479
|
try {
|
|
2068
|
-
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.");
|
|
2099
|
-
}
|
|
2100
|
-
}
|
|
2101
|
-
const result = await deployProject({ sourceDir, startBody, spinner: s });
|
|
2102
|
-
if (result.isReady) {
|
|
2103
|
-
s?.stop("Deployment complete");
|
|
2104
|
-
if (json) {
|
|
2105
|
-
outputJson(result.deployment);
|
|
2106
|
-
} else {
|
|
2107
|
-
if (result.liveUrl) {
|
|
2108
|
-
clack9.log.success(`Live at: ${result.liveUrl}`);
|
|
2109
|
-
}
|
|
2110
|
-
clack9.log.info(`Deployment ID: ${result.deploymentId}`);
|
|
2480
|
+
await requireAuth();
|
|
2481
|
+
if (!yes && !json) {
|
|
2482
|
+
const confirmed = await clack9.confirm({
|
|
2483
|
+
message: `Delete function "${slug}"? This cannot be undone.`
|
|
2484
|
+
});
|
|
2485
|
+
if (clack9.isCancel(confirmed) || !confirmed) {
|
|
2486
|
+
clack9.log.info("Cancelled.");
|
|
2487
|
+
return;
|
|
2111
2488
|
}
|
|
2489
|
+
}
|
|
2490
|
+
const res = await ossFetch(`/api/functions/${encodeURIComponent(slug)}`, {
|
|
2491
|
+
method: "DELETE"
|
|
2492
|
+
});
|
|
2493
|
+
const result = await res.json();
|
|
2494
|
+
if (json) {
|
|
2495
|
+
outputJson(result);
|
|
2112
2496
|
} else {
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
outputJson({ id: result.deploymentId, status: result.deployment?.status ?? "building", timedOut: true });
|
|
2497
|
+
if (result.success) {
|
|
2498
|
+
outputSuccess(`Function "${slug}" deleted successfully.`);
|
|
2116
2499
|
} 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}`);
|
|
2500
|
+
outputSuccess(`Failed to delete function "${slug}".`);
|
|
2120
2501
|
}
|
|
2121
2502
|
}
|
|
2122
|
-
await reportCliUsage("cli.
|
|
2503
|
+
await reportCliUsage("cli.functions.delete", true);
|
|
2123
2504
|
} catch (err) {
|
|
2124
|
-
await reportCliUsage("cli.
|
|
2505
|
+
await reportCliUsage("cli.functions.delete", false);
|
|
2125
2506
|
handleError(err, json);
|
|
2126
2507
|
}
|
|
2127
2508
|
});
|
|
2128
2509
|
}
|
|
2129
2510
|
|
|
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`;
|
|
2511
|
+
// src/commands/storage/buckets.ts
|
|
2512
|
+
function registerStorageBucketsCommand(storageCmd2) {
|
|
2513
|
+
storageCmd2.command("buckets").description("List all storage buckets").action(async (_opts, cmd) => {
|
|
2514
|
+
const { json } = getRootOpts(cmd);
|
|
2515
|
+
try {
|
|
2516
|
+
await requireAuth();
|
|
2517
|
+
const res = await ossFetch("/api/storage/buckets");
|
|
2518
|
+
const raw = await res.json();
|
|
2519
|
+
const buckets = Array.isArray(raw) ? raw : raw && typeof raw === "object" && "buckets" in raw ? raw.buckets ?? [] : [];
|
|
2520
|
+
if (json) {
|
|
2521
|
+
outputJson(raw);
|
|
2522
|
+
} else {
|
|
2523
|
+
if (buckets.length === 0) {
|
|
2524
|
+
console.log("No buckets found.");
|
|
2525
|
+
return;
|
|
2210
2526
|
}
|
|
2527
|
+
outputTable(
|
|
2528
|
+
["Bucket Name", "Public"],
|
|
2529
|
+
buckets.map((b) => [b.name, b.public ? "Yes" : "No"])
|
|
2530
|
+
);
|
|
2211
2531
|
}
|
|
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);
|
|
2532
|
+
await reportCliUsage("cli.storage.buckets", true);
|
|
2533
|
+
} catch (err) {
|
|
2534
|
+
await reportCliUsage("cli.storage.buckets", false);
|
|
2535
|
+
handleError(err, json);
|
|
2239
2536
|
}
|
|
2240
|
-
}
|
|
2537
|
+
});
|
|
2241
2538
|
}
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2539
|
+
|
|
2540
|
+
// src/commands/storage/upload.ts
|
|
2541
|
+
import { readFileSync as readFileSync5, existsSync as existsSync4 } from "fs";
|
|
2542
|
+
import { basename as basename5 } from "path";
|
|
2543
|
+
function registerStorageUploadCommand(storageCmd2) {
|
|
2544
|
+
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) => {
|
|
2545
|
+
const { json } = getRootOpts(cmd);
|
|
2245
2546
|
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;
|
|
2547
|
+
await requireAuth();
|
|
2548
|
+
const config = getProjectConfig();
|
|
2549
|
+
if (!config) throw new ProjectNotLinkedError();
|
|
2550
|
+
if (!existsSync4(file)) {
|
|
2551
|
+
throw new CLIError(`File not found: ${file}`);
|
|
2284
2552
|
}
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2553
|
+
const fileContent = readFileSync5(file);
|
|
2554
|
+
const objectKey = opts.key ?? basename5(file);
|
|
2555
|
+
const bucketName = opts.bucket;
|
|
2556
|
+
const formData = new FormData();
|
|
2557
|
+
const blob = new Blob([fileContent]);
|
|
2558
|
+
formData.append("file", blob, objectKey);
|
|
2559
|
+
const url = `${config.oss_host}/api/storage/buckets/${encodeURIComponent(bucketName)}/objects/${encodeURIComponent(objectKey)}`;
|
|
2560
|
+
const res = await fetch(url, {
|
|
2561
|
+
method: "PUT",
|
|
2562
|
+
headers: {
|
|
2563
|
+
Authorization: `Bearer ${config.api_key}`
|
|
2564
|
+
},
|
|
2565
|
+
body: formData
|
|
2566
|
+
});
|
|
2567
|
+
if (!res.ok) {
|
|
2568
|
+
const err = await res.json().catch(() => ({}));
|
|
2569
|
+
throw new CLIError(err.error ?? `Upload failed: ${res.status}`);
|
|
2288
2570
|
}
|
|
2289
|
-
const
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2571
|
+
const data = await res.json();
|
|
2572
|
+
if (json) {
|
|
2573
|
+
outputJson(data);
|
|
2574
|
+
} else {
|
|
2575
|
+
outputSuccess(`Uploaded "${basename5(file)}" to bucket "${bucketName}".`);
|
|
2293
2576
|
}
|
|
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
|
-
}
|
|
2577
|
+
} catch (err) {
|
|
2578
|
+
handleError(err, json);
|
|
2579
|
+
}
|
|
2580
|
+
});
|
|
2581
|
+
}
|
|
2582
|
+
|
|
2583
|
+
// src/commands/storage/download.ts
|
|
2584
|
+
import { writeFileSync as writeFileSync3 } from "fs";
|
|
2585
|
+
import { join as join7, basename as basename6 } from "path";
|
|
2586
|
+
function registerStorageDownloadCommand(storageCmd2) {
|
|
2587
|
+
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) => {
|
|
2588
|
+
const { json } = getRootOpts(cmd);
|
|
2589
|
+
try {
|
|
2590
|
+
await requireAuth();
|
|
2591
|
+
const config = getProjectConfig();
|
|
2592
|
+
if (!config) throw new ProjectNotLinkedError();
|
|
2593
|
+
const bucketName = opts.bucket;
|
|
2594
|
+
const url = `${config.oss_host}/api/storage/buckets/${encodeURIComponent(bucketName)}/objects/${encodeURIComponent(objectKey)}`;
|
|
2595
|
+
const res = await fetch(url, {
|
|
2596
|
+
headers: {
|
|
2597
|
+
Authorization: `Bearer ${config.api_key}`
|
|
2325
2598
|
}
|
|
2326
|
-
}
|
|
2327
|
-
captureEvent(orgId, "template_selected", {
|
|
2328
|
-
template,
|
|
2329
|
-
approach: template === "empty" ? "blank" : "template"
|
|
2330
2599
|
});
|
|
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);
|
|
2600
|
+
if (!res.ok) {
|
|
2601
|
+
const err = await res.json().catch(() => ({}));
|
|
2602
|
+
throw new CLIError(err.error ?? `Download failed: ${res.status}`);
|
|
2603
|
+
}
|
|
2604
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
2605
|
+
const outputPath = opts.output ?? join7(process.cwd(), basename6(objectKey));
|
|
2606
|
+
writeFileSync3(outputPath, buffer);
|
|
2607
|
+
if (json) {
|
|
2608
|
+
outputJson({ success: true, path: outputPath, size: buffer.length });
|
|
2354
2609
|
} 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
|
-
}
|
|
2610
|
+
outputSuccess(`Downloaded "${objectKey}" to ${outputPath} (${buffer.length} bytes).`);
|
|
2382
2611
|
}
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
}
|
|
2612
|
+
} catch (err) {
|
|
2613
|
+
handleError(err, json);
|
|
2614
|
+
}
|
|
2615
|
+
});
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2618
|
+
// src/commands/storage/create-bucket.ts
|
|
2619
|
+
function registerStorageCreateBucketCommand(storageCmd2) {
|
|
2620
|
+
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) => {
|
|
2621
|
+
const { json } = getRootOpts(cmd);
|
|
2622
|
+
try {
|
|
2623
|
+
await requireAuth();
|
|
2624
|
+
const isPublic = !opts.private;
|
|
2625
|
+
const res = await ossFetch("/api/storage/buckets", {
|
|
2626
|
+
method: "POST",
|
|
2627
|
+
body: JSON.stringify({ bucketName: name, isPublic })
|
|
2628
|
+
});
|
|
2629
|
+
const data = await res.json();
|
|
2630
|
+
if (json) {
|
|
2631
|
+
outputJson(data);
|
|
2632
|
+
} else {
|
|
2633
|
+
outputSuccess(`Bucket "${name}" created (${isPublic ? "public" : "private"}).`);
|
|
2399
2634
|
}
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2635
|
+
await reportCliUsage("cli.storage.create-bucket", true);
|
|
2636
|
+
} catch (err) {
|
|
2637
|
+
await reportCliUsage("cli.storage.create-bucket", false);
|
|
2638
|
+
handleError(err, json);
|
|
2639
|
+
}
|
|
2640
|
+
});
|
|
2641
|
+
}
|
|
2642
|
+
|
|
2643
|
+
// src/commands/storage/delete-bucket.ts
|
|
2644
|
+
import * as clack10 from "@clack/prompts";
|
|
2645
|
+
function registerStorageDeleteBucketCommand(storageCmd2) {
|
|
2646
|
+
storageCmd2.command("delete-bucket <name>").description("Delete a storage bucket and all its objects").action(async (name, _opts, cmd) => {
|
|
2647
|
+
const { json, yes } = getRootOpts(cmd);
|
|
2648
|
+
try {
|
|
2649
|
+
await requireAuth();
|
|
2650
|
+
if (!yes && !json) {
|
|
2651
|
+
const confirm8 = await clack10.confirm({
|
|
2652
|
+
message: `Delete bucket "${name}" and all its objects? This cannot be undone.`
|
|
2404
2653
|
});
|
|
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
|
-
}
|
|
2654
|
+
if (!confirm8 || clack10.isCancel(confirm8)) {
|
|
2655
|
+
process.exit(0);
|
|
2430
2656
|
}
|
|
2431
2657
|
}
|
|
2432
|
-
const
|
|
2658
|
+
const res = await ossFetch(`/api/storage/buckets/${encodeURIComponent(name)}`, {
|
|
2659
|
+
method: "DELETE"
|
|
2660
|
+
});
|
|
2661
|
+
const data = await res.json();
|
|
2433
2662
|
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
|
-
});
|
|
2663
|
+
outputJson(data);
|
|
2443
2664
|
} else {
|
|
2444
|
-
|
|
2445
|
-
if (liveUrl) {
|
|
2446
|
-
clack10.log.success(`Live site: ${liveUrl}`);
|
|
2447
|
-
}
|
|
2448
|
-
clack10.outro("Done!");
|
|
2665
|
+
outputSuccess(`Bucket "${name}" deleted.`);
|
|
2449
2666
|
}
|
|
2450
2667
|
} catch (err) {
|
|
2451
2668
|
handleError(err, json);
|
|
2452
|
-
} finally {
|
|
2453
|
-
await shutdownAnalytics();
|
|
2454
2669
|
}
|
|
2455
2670
|
});
|
|
2456
2671
|
}
|
|
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
|
-
}
|
|
2672
|
+
|
|
2673
|
+
// src/commands/storage/list-objects.ts
|
|
2674
|
+
function formatSize(bytes) {
|
|
2675
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
2676
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
2677
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
2493
2678
|
}
|
|
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
|
-
}
|
|
2679
|
+
function registerStorageListObjectsCommand(storageCmd2) {
|
|
2680
|
+
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) => {
|
|
2681
|
+
const { json } = getRootOpts(cmd);
|
|
2682
|
+
try {
|
|
2683
|
+
await requireAuth();
|
|
2684
|
+
const params = new URLSearchParams();
|
|
2685
|
+
params.set("limit", opts.limit);
|
|
2686
|
+
params.set("offset", opts.offset);
|
|
2687
|
+
if (opts.prefix) params.set("prefix", opts.prefix);
|
|
2688
|
+
if (opts.search) params.set("search", opts.search);
|
|
2689
|
+
const res = await ossFetch(
|
|
2690
|
+
`/api/storage/buckets/${encodeURIComponent(bucket)}/objects?${params.toString()}`
|
|
2526
2691
|
);
|
|
2527
|
-
const
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
if (
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2692
|
+
const raw = await res.json();
|
|
2693
|
+
const objects = Array.isArray(raw) ? raw : raw.data ?? [];
|
|
2694
|
+
const sortField = opts.sort ?? "key";
|
|
2695
|
+
objects.sort((a, b) => {
|
|
2696
|
+
if (sortField === "size") return a.size - b.size;
|
|
2697
|
+
if (sortField === "uploadedAt") return a.uploadedAt.localeCompare(b.uploadedAt);
|
|
2698
|
+
return a.key.localeCompare(b.key);
|
|
2699
|
+
});
|
|
2700
|
+
if (json) {
|
|
2701
|
+
outputJson(raw);
|
|
2702
|
+
} else {
|
|
2703
|
+
if (objects.length === 0) {
|
|
2704
|
+
console.log(`No objects found in bucket "${bucket}".`);
|
|
2705
|
+
return;
|
|
2535
2706
|
}
|
|
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;
|
|
2707
|
+
const total = raw.pagination?.total;
|
|
2708
|
+
if (total !== void 0) {
|
|
2709
|
+
console.log(`Showing ${objects.length} of ${total} objects:
|
|
2710
|
+
`);
|
|
2555
2711
|
}
|
|
2712
|
+
outputTable(
|
|
2713
|
+
["Key", "Size", "Type", "Uploaded At"],
|
|
2714
|
+
objects.map((o) => [
|
|
2715
|
+
o.key,
|
|
2716
|
+
formatSize(o.size),
|
|
2717
|
+
o.mimeType ?? "-",
|
|
2718
|
+
o.uploadedAt
|
|
2719
|
+
])
|
|
2720
|
+
);
|
|
2556
2721
|
}
|
|
2722
|
+
await reportCliUsage("cli.storage.list-objects", true);
|
|
2723
|
+
} catch (err) {
|
|
2724
|
+
await reportCliUsage("cli.storage.list-objects", false);
|
|
2725
|
+
handleError(err, json);
|
|
2557
2726
|
}
|
|
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
|
-
}
|
|
2727
|
+
});
|
|
2568
2728
|
}
|
|
2569
2729
|
|
|
2570
2730
|
// src/commands/info.ts
|
|
@@ -2902,8 +3062,8 @@ async function listDocs(json) {
|
|
|
2902
3062
|
);
|
|
2903
3063
|
}
|
|
2904
3064
|
}
|
|
2905
|
-
async function fetchDoc(
|
|
2906
|
-
const res = await ossFetch(
|
|
3065
|
+
async function fetchDoc(path5, label, json) {
|
|
3066
|
+
const res = await ossFetch(path5);
|
|
2907
3067
|
const data = await res.json();
|
|
2908
3068
|
const doc = data.data ?? data;
|
|
2909
3069
|
if (json) {
|
|
@@ -3554,10 +3714,10 @@ function registerComputeLogsCommand(computeCmd2) {
|
|
|
3554
3714
|
|
|
3555
3715
|
// src/commands/compute/deploy.ts
|
|
3556
3716
|
import { existsSync as existsSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4, unlinkSync as unlinkSync2, renameSync } from "fs";
|
|
3557
|
-
import { join as
|
|
3717
|
+
import { join as join8 } from "path";
|
|
3558
3718
|
import { execSync, spawn } from "child_process";
|
|
3559
3719
|
function parseFlyToml(dir) {
|
|
3560
|
-
const tomlPath =
|
|
3720
|
+
const tomlPath = join8(dir, "fly.toml");
|
|
3561
3721
|
if (!existsSync5(tomlPath)) return {};
|
|
3562
3722
|
const content = readFileSync6(tomlPath, "utf-8");
|
|
3563
3723
|
const config = {};
|
|
@@ -3629,7 +3789,7 @@ function registerComputeDeployCommand(computeCmd2) {
|
|
|
3629
3789
|
checkFlyctl();
|
|
3630
3790
|
const flyToken = getFlyToken();
|
|
3631
3791
|
const dir = directory ?? process.cwd();
|
|
3632
|
-
const dockerfilePath =
|
|
3792
|
+
const dockerfilePath = join8(dir, "Dockerfile");
|
|
3633
3793
|
if (!existsSync5(dockerfilePath)) {
|
|
3634
3794
|
throw new CLIError(`No Dockerfile found in ${dir}`);
|
|
3635
3795
|
}
|
|
@@ -3675,8 +3835,8 @@ function registerComputeDeployCommand(computeCmd2) {
|
|
|
3675
3835
|
serviceId = service.id;
|
|
3676
3836
|
flyAppId = service.flyAppId;
|
|
3677
3837
|
}
|
|
3678
|
-
const existingTomlPath =
|
|
3679
|
-
const backupTomlPath =
|
|
3838
|
+
const existingTomlPath = join8(dir, "fly.toml");
|
|
3839
|
+
const backupTomlPath = join8(dir, "fly.toml.insforge-backup");
|
|
3680
3840
|
let hadExistingToml = false;
|
|
3681
3841
|
if (existsSync5(existingTomlPath)) {
|
|
3682
3842
|
hadExistingToml = true;
|
|
@@ -3686,14 +3846,14 @@ function registerComputeDeployCommand(computeCmd2) {
|
|
|
3686
3846
|
writeFileSync4(existingTomlPath, tomlContent);
|
|
3687
3847
|
try {
|
|
3688
3848
|
if (!json) outputInfo("Building and deploying (this may take a few minutes)...");
|
|
3689
|
-
await new Promise((
|
|
3849
|
+
await new Promise((resolve4, reject) => {
|
|
3690
3850
|
const child = spawn(
|
|
3691
3851
|
"flyctl",
|
|
3692
3852
|
["deploy", "--remote-only", "--app", flyAppId, "--access-token", flyToken, "--yes"],
|
|
3693
3853
|
{ cwd: dir, stdio: json ? "pipe" : "inherit" }
|
|
3694
3854
|
);
|
|
3695
3855
|
child.on("close", (code) => {
|
|
3696
|
-
if (code === 0)
|
|
3856
|
+
if (code === 0) resolve4();
|
|
3697
3857
|
else reject(new CLIError(`flyctl deploy failed with exit code ${code}`));
|
|
3698
3858
|
});
|
|
3699
3859
|
child.on("error", (err) => reject(new CLIError(`flyctl deploy error: ${err.message}`)));
|
|
@@ -4477,7 +4637,7 @@ function formatBytesCompact(bytes) {
|
|
|
4477
4637
|
|
|
4478
4638
|
// src/index.ts
|
|
4479
4639
|
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
4480
|
-
var pkg = JSON.parse(readFileSync7(
|
|
4640
|
+
var pkg = JSON.parse(readFileSync7(join9(__dirname, "../package.json"), "utf-8"));
|
|
4481
4641
|
var INSFORGE_LOGO = `
|
|
4482
4642
|
\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
4643
|
\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
|