@insforge/cli 0.1.6 → 0.1.8
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 +292 -198
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { existsSync as
|
|
5
|
-
import { join as
|
|
4
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync6 } from "fs";
|
|
5
|
+
import { join as join6, dirname } from "path";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
7
|
import { Command } from "commander";
|
|
8
8
|
|
|
@@ -17,6 +17,7 @@ var GLOBAL_DIR = join(homedir(), ".insforge");
|
|
|
17
17
|
var CREDENTIALS_FILE = join(GLOBAL_DIR, "credentials.json");
|
|
18
18
|
var CONFIG_FILE = join(GLOBAL_DIR, "config.json");
|
|
19
19
|
var DEFAULT_PLATFORM_URL = "https://api.insforge.dev";
|
|
20
|
+
var DEFAULT_FRONTEND_URL = "https://insforge.dev";
|
|
20
21
|
function ensureGlobalDir() {
|
|
21
22
|
if (!existsSync(GLOBAL_DIR)) {
|
|
22
23
|
mkdirSync(GLOBAL_DIR, { recursive: true });
|
|
@@ -78,6 +79,9 @@ function saveProjectConfig(config) {
|
|
|
78
79
|
function getPlatformApiUrl(override) {
|
|
79
80
|
return process.env.INSFORGE_API_URL ?? override ?? getGlobalConfig().platform_api_url ?? DEFAULT_PLATFORM_URL;
|
|
80
81
|
}
|
|
82
|
+
function getFrontendUrl() {
|
|
83
|
+
return process.env.INSFORGE_FRONTEND_URL ?? DEFAULT_FRONTEND_URL;
|
|
84
|
+
}
|
|
81
85
|
function getAccessToken() {
|
|
82
86
|
return process.env.INSFORGE_ACCESS_TOKEN ?? getCredentials()?.access_token ?? null;
|
|
83
87
|
}
|
|
@@ -98,7 +102,7 @@ var AuthError = class extends CLIError {
|
|
|
98
102
|
};
|
|
99
103
|
var ProjectNotLinkedError = class extends CLIError {
|
|
100
104
|
constructor() {
|
|
101
|
-
super("No project linked. Run `insforge
|
|
105
|
+
super("No project linked. Run `insforge link` first.", 3, "PROJECT_NOT_LINKED");
|
|
102
106
|
}
|
|
103
107
|
};
|
|
104
108
|
function handleError(err, json) {
|
|
@@ -645,9 +649,38 @@ import * as clack6 from "@clack/prompts";
|
|
|
645
649
|
|
|
646
650
|
// src/lib/skills.ts
|
|
647
651
|
import { exec } from "child_process";
|
|
652
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, appendFileSync } from "fs";
|
|
653
|
+
import { join as join2 } from "path";
|
|
648
654
|
import { promisify } from "util";
|
|
649
655
|
import * as clack5 from "@clack/prompts";
|
|
650
656
|
var execAsync = promisify(exec);
|
|
657
|
+
var GITIGNORE_ENTRIES = [
|
|
658
|
+
".insforge",
|
|
659
|
+
".agent",
|
|
660
|
+
".agents",
|
|
661
|
+
".augment",
|
|
662
|
+
".claude",
|
|
663
|
+
".cline",
|
|
664
|
+
".github/copilot*",
|
|
665
|
+
".kilocode",
|
|
666
|
+
".qoder",
|
|
667
|
+
".qwen",
|
|
668
|
+
".roo",
|
|
669
|
+
".trae",
|
|
670
|
+
".windsurf"
|
|
671
|
+
];
|
|
672
|
+
function updateGitignore() {
|
|
673
|
+
const gitignorePath = join2(process.cwd(), ".gitignore");
|
|
674
|
+
const existing = existsSync2(gitignorePath) ? readFileSync2(gitignorePath, "utf-8") : "";
|
|
675
|
+
const lines = new Set(existing.split("\n").map((l) => l.trim()));
|
|
676
|
+
const missing = GITIGNORE_ENTRIES.filter((entry) => !lines.has(entry));
|
|
677
|
+
if (!missing.length) return;
|
|
678
|
+
const block = `
|
|
679
|
+
# InsForge & AI agent skills
|
|
680
|
+
${missing.join("\n")}
|
|
681
|
+
`;
|
|
682
|
+
appendFileSync(gitignorePath, block);
|
|
683
|
+
}
|
|
651
684
|
async function installSkills(json) {
|
|
652
685
|
try {
|
|
653
686
|
if (!json) clack5.log.info("Installing InsForge agent skills...");
|
|
@@ -659,6 +692,10 @@ async function installSkills(json) {
|
|
|
659
692
|
} catch {
|
|
660
693
|
if (!json) clack5.log.warn("Failed to install agent skills. You can run manually: npx skills add insforge/agent-skills");
|
|
661
694
|
}
|
|
695
|
+
try {
|
|
696
|
+
updateGitignore();
|
|
697
|
+
} catch {
|
|
698
|
+
}
|
|
662
699
|
}
|
|
663
700
|
|
|
664
701
|
// src/commands/projects/link.ts
|
|
@@ -1085,7 +1122,7 @@ function registerDbExportCommand(dbCmd2) {
|
|
|
1085
1122
|
}
|
|
1086
1123
|
|
|
1087
1124
|
// src/commands/db/import.ts
|
|
1088
|
-
import { readFileSync as
|
|
1125
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
1089
1126
|
import { basename } from "path";
|
|
1090
1127
|
function registerDbImportCommand(dbCmd2) {
|
|
1091
1128
|
dbCmd2.command("import <file>").description("Import database from a local SQL file").option("--truncate", "Truncate existing tables before import").action(async (file, opts, cmd) => {
|
|
@@ -1094,7 +1131,7 @@ function registerDbImportCommand(dbCmd2) {
|
|
|
1094
1131
|
await requireAuth();
|
|
1095
1132
|
const config = getProjectConfig();
|
|
1096
1133
|
if (!config) throw new ProjectNotLinkedError();
|
|
1097
|
-
const fileContent =
|
|
1134
|
+
const fileContent = readFileSync3(file);
|
|
1098
1135
|
const fileName = basename(file);
|
|
1099
1136
|
const formData = new FormData();
|
|
1100
1137
|
formData.append("file", new Blob([fileContent]), fileName);
|
|
@@ -1305,21 +1342,21 @@ function registerFunctionsCommands(functionsCmd2) {
|
|
|
1305
1342
|
}
|
|
1306
1343
|
|
|
1307
1344
|
// src/commands/functions/deploy.ts
|
|
1308
|
-
import { readFileSync as
|
|
1309
|
-
import { join as
|
|
1345
|
+
import { readFileSync as readFileSync4, existsSync as existsSync3 } from "fs";
|
|
1346
|
+
import { join as join3 } from "path";
|
|
1310
1347
|
function registerFunctionsDeployCommand(functionsCmd2) {
|
|
1311
1348
|
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) => {
|
|
1312
1349
|
const { json } = getRootOpts(cmd);
|
|
1313
1350
|
try {
|
|
1314
1351
|
await requireAuth();
|
|
1315
|
-
const filePath = opts.file ??
|
|
1316
|
-
if (!
|
|
1352
|
+
const filePath = opts.file ?? join3(process.cwd(), "insforge", "functions", slug, "index.ts");
|
|
1353
|
+
if (!existsSync3(filePath)) {
|
|
1317
1354
|
throw new CLIError(
|
|
1318
1355
|
`Source file not found: ${filePath}
|
|
1319
|
-
Specify --file <path> or create ${
|
|
1356
|
+
Specify --file <path> or create ${join3("insforge", "functions", slug, "index.ts")}`
|
|
1320
1357
|
);
|
|
1321
1358
|
}
|
|
1322
|
-
const code =
|
|
1359
|
+
const code = readFileSync4(filePath, "utf-8");
|
|
1323
1360
|
const name = opts.name ?? slug;
|
|
1324
1361
|
const description = opts.description ?? "";
|
|
1325
1362
|
let exists = false;
|
|
@@ -1460,7 +1497,7 @@ function registerStorageBucketsCommand(storageCmd2) {
|
|
|
1460
1497
|
}
|
|
1461
1498
|
|
|
1462
1499
|
// src/commands/storage/upload.ts
|
|
1463
|
-
import { readFileSync as
|
|
1500
|
+
import { readFileSync as readFileSync5, existsSync as existsSync4 } from "fs";
|
|
1464
1501
|
import { basename as basename2 } from "path";
|
|
1465
1502
|
function registerStorageUploadCommand(storageCmd2) {
|
|
1466
1503
|
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) => {
|
|
@@ -1469,10 +1506,10 @@ function registerStorageUploadCommand(storageCmd2) {
|
|
|
1469
1506
|
await requireAuth();
|
|
1470
1507
|
const config = getProjectConfig();
|
|
1471
1508
|
if (!config) throw new ProjectNotLinkedError();
|
|
1472
|
-
if (!
|
|
1509
|
+
if (!existsSync4(file)) {
|
|
1473
1510
|
throw new CLIError(`File not found: ${file}`);
|
|
1474
1511
|
}
|
|
1475
|
-
const fileContent =
|
|
1512
|
+
const fileContent = readFileSync5(file);
|
|
1476
1513
|
const objectKey = opts.key ?? basename2(file);
|
|
1477
1514
|
const bucketName = opts.bucket;
|
|
1478
1515
|
const formData = new FormData();
|
|
@@ -1504,7 +1541,7 @@ function registerStorageUploadCommand(storageCmd2) {
|
|
|
1504
1541
|
|
|
1505
1542
|
// src/commands/storage/download.ts
|
|
1506
1543
|
import { writeFileSync as writeFileSync3 } from "fs";
|
|
1507
|
-
import { join as
|
|
1544
|
+
import { join as join4, basename as basename3 } from "path";
|
|
1508
1545
|
function registerStorageDownloadCommand(storageCmd2) {
|
|
1509
1546
|
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) => {
|
|
1510
1547
|
const { json } = getRootOpts(cmd);
|
|
@@ -1524,7 +1561,7 @@ function registerStorageDownloadCommand(storageCmd2) {
|
|
|
1524
1561
|
throw new CLIError(err.error ?? `Download failed: ${res.status}`);
|
|
1525
1562
|
}
|
|
1526
1563
|
const buffer = Buffer.from(await res.arrayBuffer());
|
|
1527
|
-
const outputPath = opts.output ??
|
|
1564
|
+
const outputPath = opts.output ?? join4(process.cwd(), basename3(objectKey));
|
|
1528
1565
|
writeFileSync3(outputPath, buffer);
|
|
1529
1566
|
if (json) {
|
|
1530
1567
|
outputJson({ success: true, path: outputPath, size: buffer.length });
|
|
@@ -1568,10 +1605,10 @@ function registerStorageDeleteBucketCommand(storageCmd2) {
|
|
|
1568
1605
|
try {
|
|
1569
1606
|
await requireAuth();
|
|
1570
1607
|
if (!yes && !json) {
|
|
1571
|
-
const
|
|
1608
|
+
const confirm6 = await clack7.confirm({
|
|
1572
1609
|
message: `Delete bucket "${name}" and all its objects? This cannot be undone.`
|
|
1573
1610
|
});
|
|
1574
|
-
if (!
|
|
1611
|
+
if (!confirm6 || clack7.isCancel(confirm6)) {
|
|
1575
1612
|
process.exit(0);
|
|
1576
1613
|
}
|
|
1577
1614
|
}
|
|
@@ -1649,9 +1686,166 @@ function registerStorageListObjectsCommand(storageCmd2) {
|
|
|
1649
1686
|
import { exec as exec2 } from "child_process";
|
|
1650
1687
|
import { tmpdir } from "os";
|
|
1651
1688
|
import { promisify as promisify2 } from "util";
|
|
1652
|
-
import * as
|
|
1689
|
+
import * as fs2 from "fs/promises";
|
|
1690
|
+
import * as path2 from "path";
|
|
1691
|
+
import * as clack9 from "@clack/prompts";
|
|
1692
|
+
|
|
1693
|
+
// src/commands/deployments/deploy.ts
|
|
1653
1694
|
import * as path from "path";
|
|
1695
|
+
import * as fs from "fs/promises";
|
|
1654
1696
|
import * as clack8 from "@clack/prompts";
|
|
1697
|
+
import archiver from "archiver";
|
|
1698
|
+
var POLL_INTERVAL_MS = 5e3;
|
|
1699
|
+
var POLL_TIMEOUT_MS = 12e4;
|
|
1700
|
+
var EXCLUDE_PATTERNS = [
|
|
1701
|
+
"node_modules",
|
|
1702
|
+
".git",
|
|
1703
|
+
".next",
|
|
1704
|
+
".env",
|
|
1705
|
+
".env.local",
|
|
1706
|
+
"dist",
|
|
1707
|
+
"build",
|
|
1708
|
+
".DS_Store",
|
|
1709
|
+
".insforge"
|
|
1710
|
+
];
|
|
1711
|
+
function shouldExclude(name) {
|
|
1712
|
+
const normalized = name.replace(/\\/g, "/");
|
|
1713
|
+
for (const pattern of EXCLUDE_PATTERNS) {
|
|
1714
|
+
if (normalized === pattern || normalized.startsWith(pattern + "/") || normalized.endsWith("/" + pattern) || normalized.includes("/" + pattern + "/")) {
|
|
1715
|
+
return true;
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
if (normalized.endsWith(".log")) return true;
|
|
1719
|
+
return false;
|
|
1720
|
+
}
|
|
1721
|
+
async function createZipBuffer(sourceDir) {
|
|
1722
|
+
return new Promise((resolve2, reject) => {
|
|
1723
|
+
const archive = archiver("zip", { zlib: { level: 9 } });
|
|
1724
|
+
const chunks = [];
|
|
1725
|
+
archive.on("data", (chunk) => chunks.push(chunk));
|
|
1726
|
+
archive.on("end", () => resolve2(Buffer.concat(chunks)));
|
|
1727
|
+
archive.on("error", (err) => reject(err));
|
|
1728
|
+
archive.directory(sourceDir, false, (entry) => {
|
|
1729
|
+
if (shouldExclude(entry.name)) return false;
|
|
1730
|
+
return entry;
|
|
1731
|
+
});
|
|
1732
|
+
void archive.finalize();
|
|
1733
|
+
});
|
|
1734
|
+
}
|
|
1735
|
+
async function deployProject(opts) {
|
|
1736
|
+
const { sourceDir, startBody = {}, spinner: s } = opts;
|
|
1737
|
+
s?.start("Creating deployment...");
|
|
1738
|
+
const createRes = await ossFetch("/api/deployments", { method: "POST" });
|
|
1739
|
+
const { id: deploymentId, uploadUrl, uploadFields } = await createRes.json();
|
|
1740
|
+
s?.message("Compressing source files...");
|
|
1741
|
+
const zipBuffer = await createZipBuffer(sourceDir);
|
|
1742
|
+
s?.message("Uploading...");
|
|
1743
|
+
const formData = new FormData();
|
|
1744
|
+
for (const [key, value] of Object.entries(uploadFields)) {
|
|
1745
|
+
formData.append(key, value);
|
|
1746
|
+
}
|
|
1747
|
+
formData.append(
|
|
1748
|
+
"file",
|
|
1749
|
+
new Blob([zipBuffer], { type: "application/zip" }),
|
|
1750
|
+
"deployment.zip"
|
|
1751
|
+
);
|
|
1752
|
+
const uploadRes = await fetch(uploadUrl, { method: "POST", body: formData });
|
|
1753
|
+
if (!uploadRes.ok) {
|
|
1754
|
+
const uploadErr = await uploadRes.text();
|
|
1755
|
+
throw new CLIError(`Failed to upload: ${uploadErr}`);
|
|
1756
|
+
}
|
|
1757
|
+
s?.message("Starting deployment...");
|
|
1758
|
+
const startRes = await ossFetch(`/api/deployments/${deploymentId}/start`, {
|
|
1759
|
+
method: "POST",
|
|
1760
|
+
body: JSON.stringify(startBody)
|
|
1761
|
+
});
|
|
1762
|
+
await startRes.json();
|
|
1763
|
+
s?.message("Building and deploying...");
|
|
1764
|
+
const startTime = Date.now();
|
|
1765
|
+
let deployment = null;
|
|
1766
|
+
while (Date.now() - startTime < POLL_TIMEOUT_MS) {
|
|
1767
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
1768
|
+
try {
|
|
1769
|
+
const statusRes = await ossFetch(`/api/deployments/${deploymentId}`);
|
|
1770
|
+
deployment = await statusRes.json();
|
|
1771
|
+
if (deployment.status === "ready" || deployment.status === "READY") {
|
|
1772
|
+
break;
|
|
1773
|
+
}
|
|
1774
|
+
if (deployment.status === "error" || deployment.status === "ERROR" || deployment.status === "canceled") {
|
|
1775
|
+
s?.stop("Deployment failed");
|
|
1776
|
+
throw new CLIError(deployment.error ?? `Deployment failed with status: ${deployment.status}`);
|
|
1777
|
+
}
|
|
1778
|
+
const elapsed = Math.round((Date.now() - startTime) / 1e3);
|
|
1779
|
+
s?.message(`Building and deploying... (${elapsed}s, status: ${deployment.status})`);
|
|
1780
|
+
} catch (err) {
|
|
1781
|
+
if (err instanceof CLIError) throw err;
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
const isReady = deployment?.status === "ready" || deployment?.status === "READY";
|
|
1785
|
+
const liveUrl = isReady ? deployment?.deploymentUrl ?? deployment?.url ?? null : null;
|
|
1786
|
+
return { deploymentId, deployment, isReady, liveUrl };
|
|
1787
|
+
}
|
|
1788
|
+
function registerDeploymentsDeployCommand(deploymentsCmd2) {
|
|
1789
|
+
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) => {
|
|
1790
|
+
const { json } = getRootOpts(cmd);
|
|
1791
|
+
try {
|
|
1792
|
+
await requireAuth();
|
|
1793
|
+
const config = getProjectConfig();
|
|
1794
|
+
if (!config) throw new ProjectNotLinkedError();
|
|
1795
|
+
const sourceDir = path.resolve(directory ?? ".");
|
|
1796
|
+
const stats = await fs.stat(sourceDir).catch(() => null);
|
|
1797
|
+
if (!stats?.isDirectory()) {
|
|
1798
|
+
throw new CLIError(`"${sourceDir}" is not a valid directory.`);
|
|
1799
|
+
}
|
|
1800
|
+
const s = !json ? clack8.spinner() : null;
|
|
1801
|
+
const startBody = {};
|
|
1802
|
+
if (opts.env) {
|
|
1803
|
+
try {
|
|
1804
|
+
const parsed = JSON.parse(opts.env);
|
|
1805
|
+
if (Array.isArray(parsed)) {
|
|
1806
|
+
startBody.envVars = parsed;
|
|
1807
|
+
} else {
|
|
1808
|
+
startBody.envVars = Object.entries(parsed).map(([key, value]) => ({ key, value }));
|
|
1809
|
+
}
|
|
1810
|
+
} catch {
|
|
1811
|
+
throw new CLIError("Invalid --env JSON.");
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
if (opts.meta) {
|
|
1815
|
+
try {
|
|
1816
|
+
startBody.meta = JSON.parse(opts.meta);
|
|
1817
|
+
} catch {
|
|
1818
|
+
throw new CLIError("Invalid --meta JSON.");
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
const result = await deployProject({ sourceDir, startBody, spinner: s });
|
|
1822
|
+
if (result.isReady) {
|
|
1823
|
+
s?.stop("Deployment complete");
|
|
1824
|
+
if (json) {
|
|
1825
|
+
outputJson(result.deployment);
|
|
1826
|
+
} else {
|
|
1827
|
+
if (result.liveUrl) {
|
|
1828
|
+
clack8.log.success(`Live at: ${result.liveUrl}`);
|
|
1829
|
+
}
|
|
1830
|
+
clack8.log.info(`Deployment ID: ${result.deploymentId}`);
|
|
1831
|
+
}
|
|
1832
|
+
} else {
|
|
1833
|
+
s?.stop("Deployment is still building");
|
|
1834
|
+
if (json) {
|
|
1835
|
+
outputJson({ id: result.deploymentId, status: result.deployment?.status ?? "building", timedOut: true });
|
|
1836
|
+
} else {
|
|
1837
|
+
clack8.log.info(`Deployment ID: ${result.deploymentId}`);
|
|
1838
|
+
clack8.log.warn("Deployment did not finish within 2 minutes.");
|
|
1839
|
+
clack8.log.info(`Check status with: insforge deployments status ${result.deploymentId}`);
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
} catch (err) {
|
|
1843
|
+
handleError(err, json);
|
|
1844
|
+
}
|
|
1845
|
+
});
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
// src/commands/create.ts
|
|
1655
1849
|
var execAsync2 = promisify2(exec2);
|
|
1656
1850
|
function buildOssHost2(appkey, region) {
|
|
1657
1851
|
return `https://${appkey}.${region}.insforge.app`;
|
|
@@ -1666,15 +1860,15 @@ async function waitForProjectActive(projectId, apiUrl, timeoutMs = 12e4) {
|
|
|
1666
1860
|
throw new CLIError("Project creation timed out. Check the dashboard for status.");
|
|
1667
1861
|
}
|
|
1668
1862
|
async function copyDir(src, dest) {
|
|
1669
|
-
const entries = await
|
|
1863
|
+
const entries = await fs2.readdir(src, { withFileTypes: true });
|
|
1670
1864
|
for (const entry of entries) {
|
|
1671
|
-
const srcPath =
|
|
1672
|
-
const destPath =
|
|
1865
|
+
const srcPath = path2.join(src, entry.name);
|
|
1866
|
+
const destPath = path2.join(dest, entry.name);
|
|
1673
1867
|
if (entry.isDirectory()) {
|
|
1674
|
-
await
|
|
1868
|
+
await fs2.mkdir(destPath, { recursive: true });
|
|
1675
1869
|
await copyDir(srcPath, destPath);
|
|
1676
1870
|
} else {
|
|
1677
|
-
await
|
|
1871
|
+
await fs2.copyFile(srcPath, destPath);
|
|
1678
1872
|
}
|
|
1679
1873
|
}
|
|
1680
1874
|
}
|
|
@@ -1684,7 +1878,7 @@ function registerCreateCommand(program2) {
|
|
|
1684
1878
|
try {
|
|
1685
1879
|
await requireAuth(apiUrl);
|
|
1686
1880
|
if (!json) {
|
|
1687
|
-
|
|
1881
|
+
clack9.intro("Create a new InsForge project");
|
|
1688
1882
|
}
|
|
1689
1883
|
let orgId = opts.orgId;
|
|
1690
1884
|
if (!orgId) {
|
|
@@ -1695,14 +1889,14 @@ function registerCreateCommand(program2) {
|
|
|
1695
1889
|
if (json) {
|
|
1696
1890
|
throw new CLIError("Specify --org-id in JSON mode.");
|
|
1697
1891
|
}
|
|
1698
|
-
const selected = await
|
|
1892
|
+
const selected = await clack9.select({
|
|
1699
1893
|
message: "Select an organization:",
|
|
1700
1894
|
options: orgs.map((o) => ({
|
|
1701
1895
|
value: o.id,
|
|
1702
1896
|
label: o.name
|
|
1703
1897
|
}))
|
|
1704
1898
|
});
|
|
1705
|
-
if (
|
|
1899
|
+
if (clack9.isCancel(selected)) process.exit(0);
|
|
1706
1900
|
orgId = selected;
|
|
1707
1901
|
}
|
|
1708
1902
|
const globalConfig = getGlobalConfig();
|
|
@@ -1711,11 +1905,11 @@ function registerCreateCommand(program2) {
|
|
|
1711
1905
|
let projectName = opts.name;
|
|
1712
1906
|
if (!projectName) {
|
|
1713
1907
|
if (json) throw new CLIError("--name is required in JSON mode.");
|
|
1714
|
-
const name = await
|
|
1908
|
+
const name = await clack9.text({
|
|
1715
1909
|
message: "Project name:",
|
|
1716
1910
|
validate: (v) => v.length >= 2 ? void 0 : "Name must be at least 2 characters"
|
|
1717
1911
|
});
|
|
1718
|
-
if (
|
|
1912
|
+
if (clack9.isCancel(name)) process.exit(0);
|
|
1719
1913
|
projectName = name;
|
|
1720
1914
|
}
|
|
1721
1915
|
let template = opts.template;
|
|
@@ -1723,7 +1917,7 @@ function registerCreateCommand(program2) {
|
|
|
1723
1917
|
if (json) {
|
|
1724
1918
|
template = "empty";
|
|
1725
1919
|
} else {
|
|
1726
|
-
const selected = await
|
|
1920
|
+
const selected = await clack9.select({
|
|
1727
1921
|
message: "Choose a starter template:",
|
|
1728
1922
|
options: [
|
|
1729
1923
|
{ value: "react", label: "Web app template with React" },
|
|
@@ -1731,11 +1925,11 @@ function registerCreateCommand(program2) {
|
|
|
1731
1925
|
{ value: "empty", label: "Empty project" }
|
|
1732
1926
|
]
|
|
1733
1927
|
});
|
|
1734
|
-
if (
|
|
1928
|
+
if (clack9.isCancel(selected)) process.exit(0);
|
|
1735
1929
|
template = selected;
|
|
1736
1930
|
}
|
|
1737
1931
|
}
|
|
1738
|
-
const s = !json ?
|
|
1932
|
+
const s = !json ? clack9.spinner() : null;
|
|
1739
1933
|
s?.start("Creating project...");
|
|
1740
1934
|
const project = await createProject(orgId, projectName, opts.region, apiUrl);
|
|
1741
1935
|
s?.message("Waiting for project to become active...");
|
|
@@ -1752,18 +1946,68 @@ function registerCreateCommand(program2) {
|
|
|
1752
1946
|
};
|
|
1753
1947
|
saveProjectConfig(projectConfig);
|
|
1754
1948
|
s?.stop(`Project "${project.name}" created and linked`);
|
|
1755
|
-
|
|
1949
|
+
const hasTemplate = template !== "empty";
|
|
1950
|
+
if (hasTemplate) {
|
|
1756
1951
|
await downloadTemplate(template, projectConfig, projectName, json, apiUrl);
|
|
1757
1952
|
}
|
|
1758
1953
|
await installSkills(json);
|
|
1954
|
+
if (hasTemplate) {
|
|
1955
|
+
const installSpinner = !json ? clack9.spinner() : null;
|
|
1956
|
+
installSpinner?.start("Installing dependencies...");
|
|
1957
|
+
try {
|
|
1958
|
+
await execAsync2("npm install", { cwd: process.cwd(), maxBuffer: 10 * 1024 * 1024 });
|
|
1959
|
+
installSpinner?.stop("Dependencies installed");
|
|
1960
|
+
} catch (err) {
|
|
1961
|
+
installSpinner?.stop("Failed to install dependencies");
|
|
1962
|
+
if (!json) {
|
|
1963
|
+
clack9.log.warn(`npm install failed: ${err.message}`);
|
|
1964
|
+
clack9.log.info("Run `npm install` manually to install dependencies.");
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
let liveUrl = null;
|
|
1969
|
+
if (hasTemplate && !json) {
|
|
1970
|
+
const shouldDeploy = await clack9.confirm({
|
|
1971
|
+
message: "Would you like to deploy now?"
|
|
1972
|
+
});
|
|
1973
|
+
if (!clack9.isCancel(shouldDeploy) && shouldDeploy) {
|
|
1974
|
+
try {
|
|
1975
|
+
const deploySpinner = clack9.spinner();
|
|
1976
|
+
const result = await deployProject({
|
|
1977
|
+
sourceDir: process.cwd(),
|
|
1978
|
+
spinner: deploySpinner
|
|
1979
|
+
});
|
|
1980
|
+
if (result.isReady) {
|
|
1981
|
+
deploySpinner.stop("Deployment complete");
|
|
1982
|
+
liveUrl = result.liveUrl;
|
|
1983
|
+
} else {
|
|
1984
|
+
deploySpinner.stop("Deployment is still building");
|
|
1985
|
+
clack9.log.info(`Deployment ID: ${result.deploymentId}`);
|
|
1986
|
+
clack9.log.warn("Deployment did not finish within 2 minutes.");
|
|
1987
|
+
clack9.log.info(`Check status with: insforge deployments status ${result.deploymentId}`);
|
|
1988
|
+
}
|
|
1989
|
+
} catch (err) {
|
|
1990
|
+
clack9.log.warn(`Deploy failed: ${err.message}`);
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
const dashboardUrl = `${getFrontendUrl()}/dashboard/project/${project.id}`;
|
|
1759
1995
|
if (json) {
|
|
1760
1996
|
outputJson({
|
|
1761
1997
|
success: true,
|
|
1762
1998
|
project: { id: project.id, name: project.name, appkey: project.appkey, region: project.region },
|
|
1763
|
-
template
|
|
1999
|
+
template,
|
|
2000
|
+
urls: {
|
|
2001
|
+
dashboard: dashboardUrl,
|
|
2002
|
+
...liveUrl ? { liveSite: liveUrl } : {}
|
|
2003
|
+
}
|
|
1764
2004
|
});
|
|
1765
2005
|
} else {
|
|
1766
|
-
|
|
2006
|
+
clack9.log.step(`Dashboard: ${dashboardUrl}`);
|
|
2007
|
+
if (liveUrl) {
|
|
2008
|
+
clack9.log.success(`Live site: ${liveUrl}`);
|
|
2009
|
+
}
|
|
2010
|
+
clack9.outro("Done!");
|
|
1767
2011
|
}
|
|
1768
2012
|
} catch (err) {
|
|
1769
2013
|
handleError(err, json);
|
|
@@ -1771,7 +2015,7 @@ function registerCreateCommand(program2) {
|
|
|
1771
2015
|
});
|
|
1772
2016
|
}
|
|
1773
2017
|
async function downloadTemplate(framework, projectConfig, projectName, json, _apiUrl) {
|
|
1774
|
-
const s = !json ?
|
|
2018
|
+
const s = !json ? clack9.spinner() : null;
|
|
1775
2019
|
s?.start("Downloading template...");
|
|
1776
2020
|
try {
|
|
1777
2021
|
const anonKey = await getAnonKey();
|
|
@@ -1780,9 +2024,9 @@ async function downloadTemplate(framework, projectConfig, projectName, json, _ap
|
|
|
1780
2024
|
}
|
|
1781
2025
|
const tempDir = tmpdir();
|
|
1782
2026
|
const targetDir = projectName;
|
|
1783
|
-
const templatePath =
|
|
2027
|
+
const templatePath = path2.join(tempDir, targetDir);
|
|
1784
2028
|
try {
|
|
1785
|
-
await
|
|
2029
|
+
await fs2.rm(templatePath, { recursive: true, force: true });
|
|
1786
2030
|
} catch {
|
|
1787
2031
|
}
|
|
1788
2032
|
const frame = framework === "nextjs" ? "nextjs" : "react";
|
|
@@ -1795,14 +2039,14 @@ async function downloadTemplate(framework, projectConfig, projectName, json, _ap
|
|
|
1795
2039
|
s?.message("Copying template files...");
|
|
1796
2040
|
const cwd = process.cwd();
|
|
1797
2041
|
await copyDir(templatePath, cwd);
|
|
1798
|
-
await
|
|
2042
|
+
await fs2.rm(templatePath, { recursive: true, force: true }).catch(() => {
|
|
1799
2043
|
});
|
|
1800
2044
|
s?.stop("Template files downloaded");
|
|
1801
2045
|
} catch (err) {
|
|
1802
2046
|
s?.stop("Template download failed");
|
|
1803
2047
|
if (!json) {
|
|
1804
|
-
|
|
1805
|
-
|
|
2048
|
+
clack9.log.warn(`Failed to download template: ${err.message}`);
|
|
2049
|
+
clack9.log.info("You can manually set up the template later.");
|
|
1806
2050
|
}
|
|
1807
2051
|
}
|
|
1808
2052
|
}
|
|
@@ -1913,156 +2157,6 @@ function registerListCommand(program2) {
|
|
|
1913
2157
|
});
|
|
1914
2158
|
}
|
|
1915
2159
|
|
|
1916
|
-
// src/commands/deployments/deploy.ts
|
|
1917
|
-
import * as path2 from "path";
|
|
1918
|
-
import * as fs2 from "fs/promises";
|
|
1919
|
-
import * as clack9 from "@clack/prompts";
|
|
1920
|
-
import archiver from "archiver";
|
|
1921
|
-
var POLL_INTERVAL_MS = 5e3;
|
|
1922
|
-
var POLL_TIMEOUT_MS = 12e4;
|
|
1923
|
-
var EXCLUDE_PATTERNS = [
|
|
1924
|
-
"node_modules",
|
|
1925
|
-
".git",
|
|
1926
|
-
".next",
|
|
1927
|
-
".env",
|
|
1928
|
-
".env.local",
|
|
1929
|
-
"dist",
|
|
1930
|
-
"build",
|
|
1931
|
-
".DS_Store",
|
|
1932
|
-
".insforge"
|
|
1933
|
-
];
|
|
1934
|
-
function shouldExclude(name) {
|
|
1935
|
-
const normalized = name.replace(/\\/g, "/");
|
|
1936
|
-
for (const pattern of EXCLUDE_PATTERNS) {
|
|
1937
|
-
if (normalized === pattern || normalized.startsWith(pattern + "/") || normalized.endsWith("/" + pattern) || normalized.includes("/" + pattern + "/")) {
|
|
1938
|
-
return true;
|
|
1939
|
-
}
|
|
1940
|
-
}
|
|
1941
|
-
if (normalized.endsWith(".log")) return true;
|
|
1942
|
-
return false;
|
|
1943
|
-
}
|
|
1944
|
-
async function createZipBuffer(sourceDir) {
|
|
1945
|
-
return new Promise((resolve2, reject) => {
|
|
1946
|
-
const archive = archiver("zip", { zlib: { level: 9 } });
|
|
1947
|
-
const chunks = [];
|
|
1948
|
-
archive.on("data", (chunk) => chunks.push(chunk));
|
|
1949
|
-
archive.on("end", () => resolve2(Buffer.concat(chunks)));
|
|
1950
|
-
archive.on("error", (err) => reject(err));
|
|
1951
|
-
archive.directory(sourceDir, false, (entry) => {
|
|
1952
|
-
if (shouldExclude(entry.name)) return false;
|
|
1953
|
-
return entry;
|
|
1954
|
-
});
|
|
1955
|
-
void archive.finalize();
|
|
1956
|
-
});
|
|
1957
|
-
}
|
|
1958
|
-
function registerDeploymentsDeployCommand(deploymentsCmd2) {
|
|
1959
|
-
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) => {
|
|
1960
|
-
const { json } = getRootOpts(cmd);
|
|
1961
|
-
try {
|
|
1962
|
-
await requireAuth();
|
|
1963
|
-
const config = getProjectConfig();
|
|
1964
|
-
if (!config) throw new ProjectNotLinkedError();
|
|
1965
|
-
const sourceDir = path2.resolve(directory ?? ".");
|
|
1966
|
-
const stats = await fs2.stat(sourceDir).catch(() => null);
|
|
1967
|
-
if (!stats?.isDirectory()) {
|
|
1968
|
-
throw new CLIError(`"${sourceDir}" is not a valid directory.`);
|
|
1969
|
-
}
|
|
1970
|
-
const s = !json ? clack9.spinner() : null;
|
|
1971
|
-
s?.start("Creating deployment...");
|
|
1972
|
-
const createRes = await ossFetch("/api/deployments", { method: "POST" });
|
|
1973
|
-
const { id: deploymentId, uploadUrl, uploadFields } = await createRes.json();
|
|
1974
|
-
s?.message("Compressing source files...");
|
|
1975
|
-
const zipBuffer = await createZipBuffer(sourceDir);
|
|
1976
|
-
s?.message("Uploading...");
|
|
1977
|
-
const formData = new FormData();
|
|
1978
|
-
for (const [key, value] of Object.entries(uploadFields)) {
|
|
1979
|
-
formData.append(key, value);
|
|
1980
|
-
}
|
|
1981
|
-
formData.append(
|
|
1982
|
-
"file",
|
|
1983
|
-
new Blob([zipBuffer], { type: "application/zip" }),
|
|
1984
|
-
"deployment.zip"
|
|
1985
|
-
);
|
|
1986
|
-
const uploadRes = await fetch(uploadUrl, { method: "POST", body: formData });
|
|
1987
|
-
if (!uploadRes.ok) {
|
|
1988
|
-
const uploadErr = await uploadRes.text();
|
|
1989
|
-
throw new CLIError(`Failed to upload: ${uploadErr}`);
|
|
1990
|
-
}
|
|
1991
|
-
s?.message("Starting deployment...");
|
|
1992
|
-
const startBody = {};
|
|
1993
|
-
if (opts.env) {
|
|
1994
|
-
try {
|
|
1995
|
-
const parsed = JSON.parse(opts.env);
|
|
1996
|
-
if (Array.isArray(parsed)) {
|
|
1997
|
-
startBody.envVars = parsed;
|
|
1998
|
-
} else {
|
|
1999
|
-
startBody.envVars = Object.entries(parsed).map(([key, value]) => ({ key, value }));
|
|
2000
|
-
}
|
|
2001
|
-
} catch {
|
|
2002
|
-
throw new CLIError("Invalid --env JSON.");
|
|
2003
|
-
}
|
|
2004
|
-
}
|
|
2005
|
-
if (opts.meta) {
|
|
2006
|
-
try {
|
|
2007
|
-
startBody.meta = JSON.parse(opts.meta);
|
|
2008
|
-
} catch {
|
|
2009
|
-
throw new CLIError("Invalid --meta JSON.");
|
|
2010
|
-
}
|
|
2011
|
-
}
|
|
2012
|
-
const startRes = await ossFetch(`/api/deployments/${deploymentId}/start`, {
|
|
2013
|
-
method: "POST",
|
|
2014
|
-
body: JSON.stringify(startBody)
|
|
2015
|
-
});
|
|
2016
|
-
await startRes.json();
|
|
2017
|
-
s?.message("Building and deploying...");
|
|
2018
|
-
const startTime = Date.now();
|
|
2019
|
-
let deployment = null;
|
|
2020
|
-
while (Date.now() - startTime < POLL_TIMEOUT_MS) {
|
|
2021
|
-
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
2022
|
-
try {
|
|
2023
|
-
const statusRes = await ossFetch(`/api/deployments/${deploymentId}`);
|
|
2024
|
-
deployment = await statusRes.json();
|
|
2025
|
-
if (deployment.status === "ready" || deployment.status === "READY") {
|
|
2026
|
-
break;
|
|
2027
|
-
}
|
|
2028
|
-
if (deployment.status === "error" || deployment.status === "ERROR" || deployment.status === "canceled") {
|
|
2029
|
-
s?.stop("Deployment failed");
|
|
2030
|
-
throw new CLIError(deployment.error ?? `Deployment failed with status: ${deployment.status}`);
|
|
2031
|
-
}
|
|
2032
|
-
const elapsed = Math.round((Date.now() - startTime) / 1e3);
|
|
2033
|
-
s?.message(`Building and deploying... (${elapsed}s, status: ${deployment.status})`);
|
|
2034
|
-
} catch (err) {
|
|
2035
|
-
if (err instanceof CLIError) throw err;
|
|
2036
|
-
}
|
|
2037
|
-
}
|
|
2038
|
-
const isReady = deployment?.status === "ready" || deployment?.status === "READY";
|
|
2039
|
-
if (isReady) {
|
|
2040
|
-
s?.stop("Deployment complete");
|
|
2041
|
-
if (json) {
|
|
2042
|
-
outputJson(deployment);
|
|
2043
|
-
} else {
|
|
2044
|
-
const liveUrl = deployment?.deploymentUrl ?? deployment?.url;
|
|
2045
|
-
if (liveUrl) {
|
|
2046
|
-
clack9.log.success(`Live at: ${liveUrl}`);
|
|
2047
|
-
}
|
|
2048
|
-
clack9.log.info(`Deployment ID: ${deploymentId}`);
|
|
2049
|
-
}
|
|
2050
|
-
} else {
|
|
2051
|
-
s?.stop("Deployment is still building");
|
|
2052
|
-
if (json) {
|
|
2053
|
-
outputJson({ id: deploymentId, status: deployment?.status ?? "building", timedOut: true });
|
|
2054
|
-
} else {
|
|
2055
|
-
clack9.log.info(`Deployment ID: ${deploymentId}`);
|
|
2056
|
-
clack9.log.warn("Deployment did not finish within 2 minutes.");
|
|
2057
|
-
clack9.log.info(`Check status with: insforge deployments status ${deploymentId}`);
|
|
2058
|
-
}
|
|
2059
|
-
}
|
|
2060
|
-
} catch (err) {
|
|
2061
|
-
handleError(err, json);
|
|
2062
|
-
}
|
|
2063
|
-
});
|
|
2064
|
-
}
|
|
2065
|
-
|
|
2066
2160
|
// src/commands/deployments/list.ts
|
|
2067
2161
|
function registerDeploymentsListCommand(deploymentsCmd2) {
|
|
2068
2162
|
deploymentsCmd2.command("list").description("List all deployments").option("--limit <n>", "Limit number of results", "20").option("--offset <n>", "Offset for pagination", "0").action(async (opts, cmd) => {
|
|
@@ -2355,10 +2449,10 @@ function registerSecretsDeleteCommand(secretsCmd2) {
|
|
|
2355
2449
|
try {
|
|
2356
2450
|
await requireAuth();
|
|
2357
2451
|
if (!yes && !json) {
|
|
2358
|
-
const
|
|
2452
|
+
const confirm6 = await clack11.confirm({
|
|
2359
2453
|
message: `Delete secret "${key}"? This cannot be undone.`
|
|
2360
2454
|
});
|
|
2361
|
-
if (!
|
|
2455
|
+
if (!confirm6 || clack11.isCancel(confirm6)) {
|
|
2362
2456
|
process.exit(0);
|
|
2363
2457
|
}
|
|
2364
2458
|
}
|
|
@@ -2537,10 +2631,10 @@ function registerSchedulesDeleteCommand(schedulesCmd2) {
|
|
|
2537
2631
|
try {
|
|
2538
2632
|
await requireAuth();
|
|
2539
2633
|
if (!yes && !json) {
|
|
2540
|
-
const
|
|
2634
|
+
const confirm6 = await clack12.confirm({
|
|
2541
2635
|
message: `Delete schedule "${id}"? This cannot be undone.`
|
|
2542
2636
|
});
|
|
2543
|
-
if (!
|
|
2637
|
+
if (!confirm6 || clack12.isCancel(confirm6)) {
|
|
2544
2638
|
process.exit(0);
|
|
2545
2639
|
}
|
|
2546
2640
|
}
|
|
@@ -2640,7 +2734,7 @@ function registerLogsCommand(program2) {
|
|
|
2640
2734
|
|
|
2641
2735
|
// src/index.ts
|
|
2642
2736
|
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
2643
|
-
var pkg = JSON.parse(
|
|
2737
|
+
var pkg = JSON.parse(readFileSync6(join6(__dirname, "../package.json"), "utf-8"));
|
|
2644
2738
|
var INSFORGE_LOGO = `
|
|
2645
2739
|
\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
|
|
2646
2740
|
\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
|
|
@@ -2651,8 +2745,8 @@ var INSFORGE_LOGO = `
|
|
|
2651
2745
|
`;
|
|
2652
2746
|
function showLogoOnFirstRun() {
|
|
2653
2747
|
if (process.argv.includes("--json")) return;
|
|
2654
|
-
const localDir =
|
|
2655
|
-
if (
|
|
2748
|
+
const localDir = join6(process.cwd(), ".insforge");
|
|
2749
|
+
if (existsSync5(localDir)) return;
|
|
2656
2750
|
console.log(INSFORGE_LOGO);
|
|
2657
2751
|
console.log(" Welcome to InsForge CLI! Run `insforge login` to get started.\n");
|
|
2658
2752
|
mkdirSync2(localDir, { recursive: true });
|