@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 CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync5 } from "fs";
5
- import { join as join5, dirname } from "path";
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 projects link` first.", 3, "PROJECT_NOT_LINKED");
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 readFileSync2 } from "fs";
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 = readFileSync2(file);
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 readFileSync3, existsSync as existsSync2 } from "fs";
1309
- import { join as join2 } from "path";
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 ?? join2(process.cwd(), "insforge", "functions", slug, "index.ts");
1316
- if (!existsSync2(filePath)) {
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 ${join2("insforge", "functions", slug, "index.ts")}`
1356
+ Specify --file <path> or create ${join3("insforge", "functions", slug, "index.ts")}`
1320
1357
  );
1321
1358
  }
1322
- const code = readFileSync3(filePath, "utf-8");
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 readFileSync4, existsSync as existsSync3 } from "fs";
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 (!existsSync3(file)) {
1509
+ if (!existsSync4(file)) {
1473
1510
  throw new CLIError(`File not found: ${file}`);
1474
1511
  }
1475
- const fileContent = readFileSync4(file);
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 join3, basename as basename3 } from "path";
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 ?? join3(process.cwd(), basename3(objectKey));
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 confirm5 = await clack7.confirm({
1608
+ const confirm6 = await clack7.confirm({
1572
1609
  message: `Delete bucket "${name}" and all its objects? This cannot be undone.`
1573
1610
  });
1574
- if (!confirm5 || clack7.isCancel(confirm5)) {
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 fs from "fs/promises";
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 fs.readdir(src, { withFileTypes: true });
1863
+ const entries = await fs2.readdir(src, { withFileTypes: true });
1670
1864
  for (const entry of entries) {
1671
- const srcPath = path.join(src, entry.name);
1672
- const destPath = path.join(dest, entry.name);
1865
+ const srcPath = path2.join(src, entry.name);
1866
+ const destPath = path2.join(dest, entry.name);
1673
1867
  if (entry.isDirectory()) {
1674
- await fs.mkdir(destPath, { recursive: true });
1868
+ await fs2.mkdir(destPath, { recursive: true });
1675
1869
  await copyDir(srcPath, destPath);
1676
1870
  } else {
1677
- await fs.copyFile(srcPath, destPath);
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
- clack8.intro("Create a new InsForge project");
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 clack8.select({
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 (clack8.isCancel(selected)) process.exit(0);
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 clack8.text({
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 (clack8.isCancel(name)) process.exit(0);
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 clack8.select({
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 (clack8.isCancel(selected)) process.exit(0);
1928
+ if (clack9.isCancel(selected)) process.exit(0);
1735
1929
  template = selected;
1736
1930
  }
1737
1931
  }
1738
- const s = !json ? clack8.spinner() : null;
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
- if (template !== "empty") {
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
- clack8.outro("Done! Run `npm install` to get started.");
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 ? clack8.spinner() : null;
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 = path.join(tempDir, targetDir);
2027
+ const templatePath = path2.join(tempDir, targetDir);
1784
2028
  try {
1785
- await fs.rm(templatePath, { recursive: true, force: true });
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 fs.rm(templatePath, { recursive: true, force: true }).catch(() => {
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
- clack8.log.warn(`Failed to download template: ${err.message}`);
1805
- clack8.log.info("You can manually set up the template later.");
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 confirm5 = await clack11.confirm({
2452
+ const confirm6 = await clack11.confirm({
2359
2453
  message: `Delete secret "${key}"? This cannot be undone.`
2360
2454
  });
2361
- if (!confirm5 || clack11.isCancel(confirm5)) {
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 confirm5 = await clack12.confirm({
2634
+ const confirm6 = await clack12.confirm({
2541
2635
  message: `Delete schedule "${id}"? This cannot be undone.`
2542
2636
  });
2543
- if (!confirm5 || clack12.isCancel(confirm5)) {
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(readFileSync5(join5(__dirname, "../package.json"), "utf-8"));
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 = join5(process.cwd(), ".insforge");
2655
- if (existsSync4(localDir)) return;
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 });