@insforge/cli 0.1.69 → 0.1.71
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 +903 -147
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { readFileSync as
|
|
5
|
-
import { join as
|
|
4
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
5
|
+
import { join as join17, dirname as dirname3 } from "path";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
7
|
import { Command } from "commander";
|
|
8
|
-
import * as
|
|
8
|
+
import * as clack17 from "@clack/prompts";
|
|
9
9
|
|
|
10
10
|
// src/lib/prompts.ts
|
|
11
11
|
import * as readline from "readline";
|
|
@@ -232,7 +232,11 @@ function getProjectConfig() {
|
|
|
232
232
|
return null;
|
|
233
233
|
}
|
|
234
234
|
const raw = readFileSync(file, "utf-8");
|
|
235
|
-
|
|
235
|
+
const config = JSON.parse(raw);
|
|
236
|
+
if (config.oss_host && !/^https?:\/\//.test(config.oss_host) && config.appkey && config.region) {
|
|
237
|
+
config.oss_host = buildOssHost(config.appkey, config.region);
|
|
238
|
+
}
|
|
239
|
+
return config;
|
|
236
240
|
}
|
|
237
241
|
function saveProjectConfig(config) {
|
|
238
242
|
const dir = getLocalConfigDir();
|
|
@@ -241,6 +245,9 @@ function saveProjectConfig(config) {
|
|
|
241
245
|
}
|
|
242
246
|
writeFileSync(getLocalConfigFile(), JSON.stringify(config, null, 2), { mode: 384 });
|
|
243
247
|
}
|
|
248
|
+
function buildOssHost(appkey, region) {
|
|
249
|
+
return `https://${appkey}.${region}.insforge.app`;
|
|
250
|
+
}
|
|
244
251
|
function getPlatformApiUrl(override) {
|
|
245
252
|
return process.env.INSFORGE_API_URL ?? override ?? getGlobalConfig().platform_api_url ?? DEFAULT_PLATFORM_URL;
|
|
246
253
|
}
|
|
@@ -1160,6 +1167,9 @@ function registerProjectsCommands(projectsCmd2) {
|
|
|
1160
1167
|
});
|
|
1161
1168
|
}
|
|
1162
1169
|
|
|
1170
|
+
// src/commands/branch/create.ts
|
|
1171
|
+
import * as clack5 from "@clack/prompts";
|
|
1172
|
+
|
|
1163
1173
|
// src/lib/analytics.ts
|
|
1164
1174
|
import { PostHog } from "posthog-node";
|
|
1165
1175
|
var POSTHOG_API_KEY = "phc_ueV1ii62wdBTkH7E70ugyeqHIHu8dFDdjs0qq3TZhJz";
|
|
@@ -1261,7 +1271,7 @@ async function runBranchSwitch(input) {
|
|
|
1261
1271
|
copyFileSync(projectFile, parentBackup);
|
|
1262
1272
|
}
|
|
1263
1273
|
const apiKey = await getProjectApiKey(target.id, input.apiUrl);
|
|
1264
|
-
const ossHost =
|
|
1274
|
+
const ossHost = buildOssHost(target.appkey, target.region);
|
|
1265
1275
|
const branched_from = current.branched_from ?? {
|
|
1266
1276
|
project_id: current.project_id,
|
|
1267
1277
|
project_name: current.project_name
|
|
@@ -1319,22 +1329,42 @@ function registerBranchCreateCommand(branch) {
|
|
|
1319
1329
|
throw new CLIError(`Invalid --mode: ${opts.mode} (must be "full" or "schema-only")`);
|
|
1320
1330
|
}
|
|
1321
1331
|
const mode = opts.mode;
|
|
1322
|
-
const
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1332
|
+
const spinner10 = !json ? clack5.spinner() : null;
|
|
1333
|
+
let ready;
|
|
1334
|
+
let provisioned = false;
|
|
1335
|
+
try {
|
|
1336
|
+
spinner10?.start(`Creating branch '${name}'...`);
|
|
1337
|
+
const created = await createBranchApi(project.project_id, { mode, name }, apiUrl);
|
|
1338
|
+
captureEvent(project.project_id, "cli_branch_create", {
|
|
1339
|
+
mode,
|
|
1340
|
+
parent_project_id: project.project_id
|
|
1341
|
+
});
|
|
1342
|
+
spinner10?.message(`Branch '${name}' created (appkey: ${created.appkey}). Provisioning...`);
|
|
1343
|
+
ready = await pollUntilReady(created.id, apiUrl, spinner10);
|
|
1344
|
+
provisioned = ready.branch_state === "ready";
|
|
1345
|
+
if (provisioned && opts.switch) {
|
|
1346
|
+
spinner10?.message("Branch ready. Switching context...");
|
|
1347
|
+
await runBranchSwitch({ name, apiUrl, json, silent: true });
|
|
1348
|
+
spinner10?.stop(`Branch '${name}' is ready and active`);
|
|
1349
|
+
} else if (provisioned) {
|
|
1350
|
+
spinner10?.stop(`Branch '${name}' is ready`);
|
|
1351
|
+
} else {
|
|
1352
|
+
spinner10?.stop(`Branch '${name}' is in '${ready.branch_state}' state`);
|
|
1353
|
+
}
|
|
1354
|
+
} catch (err) {
|
|
1355
|
+
if (provisioned) {
|
|
1356
|
+
spinner10?.stop(
|
|
1357
|
+
`Branch '${name}' is ready, but switching context failed \u2014 run \`insforge branch switch ${name}\` to retry`,
|
|
1358
|
+
1
|
|
1359
|
+
);
|
|
1360
|
+
} else {
|
|
1361
|
+
spinner10?.stop(`Branch '${name}' creation failed`, 1);
|
|
1362
|
+
}
|
|
1363
|
+
throw err;
|
|
1333
1364
|
}
|
|
1334
1365
|
if (json) {
|
|
1335
1366
|
outputJson({ branch: ready });
|
|
1336
1367
|
} else if (ready.branch_state === "ready") {
|
|
1337
|
-
outputSuccess(`Branch '${name}' is ready.`);
|
|
1338
1368
|
if (opts.switch) {
|
|
1339
1369
|
outputInfo(
|
|
1340
1370
|
"\u26A0 Re-source your dev server env (.env) to pick up the new INSFORGE_URL / ANON_KEY."
|
|
@@ -1352,7 +1382,7 @@ function registerBranchCreateCommand(branch) {
|
|
|
1352
1382
|
}
|
|
1353
1383
|
});
|
|
1354
1384
|
}
|
|
1355
|
-
async function pollUntilReady(branchId, apiUrl,
|
|
1385
|
+
async function pollUntilReady(branchId, apiUrl, spinner10) {
|
|
1356
1386
|
const start = Date.now();
|
|
1357
1387
|
let lastState = "";
|
|
1358
1388
|
while (Date.now() - start < POLL_TIMEOUT_MS) {
|
|
@@ -1361,8 +1391,8 @@ async function pollUntilReady(branchId, apiUrl, showProgress) {
|
|
|
1361
1391
|
if (branch2.branch_state === "deleted" || branch2.branch_state === "conflicted") {
|
|
1362
1392
|
throw new CLIError(`Branch creation failed (state: ${branch2.branch_state})`);
|
|
1363
1393
|
}
|
|
1364
|
-
if (
|
|
1365
|
-
|
|
1394
|
+
if (spinner10 && branch2.branch_state !== lastState) {
|
|
1395
|
+
spinner10.message(`Provisioning branch (state: ${branch2.branch_state})...`);
|
|
1366
1396
|
lastState = branch2.branch_state;
|
|
1367
1397
|
}
|
|
1368
1398
|
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
@@ -1414,7 +1444,7 @@ function registerBranchListCommand(branch) {
|
|
|
1414
1444
|
|
|
1415
1445
|
// src/commands/branch/merge.ts
|
|
1416
1446
|
import { writeFileSync as writeFileSync2 } from "fs";
|
|
1417
|
-
import * as
|
|
1447
|
+
import * as clack6 from "@clack/prompts";
|
|
1418
1448
|
function registerBranchMergeCommand(branch) {
|
|
1419
1449
|
branch.command("merge <name>").description("Merge a branch back to its parent project").option("--dry-run", "Compute the diff and print rendered SQL; do not apply").option("--save-sql <path>", "Write rendered SQL preview to a file").action(async (name, opts, cmd) => {
|
|
1420
1450
|
const { json, apiUrl, yes } = getRootOpts(cmd);
|
|
@@ -1471,10 +1501,10 @@ function registerBranchMergeCommand(branch) {
|
|
|
1471
1501
|
}
|
|
1472
1502
|
if (!yes && !json) {
|
|
1473
1503
|
const parentLabel = project.branched_from?.project_name ?? project.project_name;
|
|
1474
|
-
const confirmed = await
|
|
1504
|
+
const confirmed = await clack6.confirm({
|
|
1475
1505
|
message: `Apply this merge to parent project '${parentLabel}'?`
|
|
1476
1506
|
});
|
|
1477
|
-
if (
|
|
1507
|
+
if (clack6.isCancel(confirmed) || !confirmed) {
|
|
1478
1508
|
outputInfo("Merge cancelled.");
|
|
1479
1509
|
return;
|
|
1480
1510
|
}
|
|
@@ -1519,7 +1549,7 @@ function registerBranchMergeCommand(branch) {
|
|
|
1519
1549
|
}
|
|
1520
1550
|
|
|
1521
1551
|
// src/commands/branch/reset.ts
|
|
1522
|
-
import * as
|
|
1552
|
+
import * as clack7 from "@clack/prompts";
|
|
1523
1553
|
var POLL_INTERVAL_MS2 = 3e3;
|
|
1524
1554
|
var POLL_TIMEOUT_MS2 = 5 * 60 * 1e3;
|
|
1525
1555
|
function registerBranchResetCommand(branch) {
|
|
@@ -1540,10 +1570,10 @@ function registerBranchResetCommand(branch) {
|
|
|
1540
1570
|
}
|
|
1541
1571
|
const entryState = target.branch_state;
|
|
1542
1572
|
if (!yes && !json) {
|
|
1543
|
-
const confirmed = await
|
|
1573
|
+
const confirmed = await clack7.confirm({
|
|
1544
1574
|
message: `Reset branch '${name}' back to T0? This wipes all schema/data/policy/function/migration changes made on the branch since creation.` + (entryState === "merged" ? " (Branch is currently merged \u2014 reset will reopen it for further work.)" : "")
|
|
1545
1575
|
});
|
|
1546
|
-
if (
|
|
1576
|
+
if (clack7.isCancel(confirmed) || !confirmed) {
|
|
1547
1577
|
outputInfo("Cancelled.");
|
|
1548
1578
|
return;
|
|
1549
1579
|
}
|
|
@@ -1599,7 +1629,7 @@ async function pollUntilReady2(branchId, apiUrl, showProgress, startingState) {
|
|
|
1599
1629
|
}
|
|
1600
1630
|
|
|
1601
1631
|
// src/commands/branch/delete.ts
|
|
1602
|
-
import * as
|
|
1632
|
+
import * as clack8 from "@clack/prompts";
|
|
1603
1633
|
function registerBranchDeleteCommand(branch) {
|
|
1604
1634
|
branch.command("delete <name>").description("Delete a branch").action(async (name, _opts, cmd) => {
|
|
1605
1635
|
const { json, apiUrl, yes } = getRootOpts(cmd);
|
|
@@ -1612,10 +1642,10 @@ function registerBranchDeleteCommand(branch) {
|
|
|
1612
1642
|
const target = branches.find((b) => b.name === name);
|
|
1613
1643
|
if (!target) throw new CLIError(`Branch '${name}' not found.`);
|
|
1614
1644
|
if (!yes && !json) {
|
|
1615
|
-
const confirmed = await
|
|
1645
|
+
const confirmed = await clack8.confirm({
|
|
1616
1646
|
message: `Delete branch '${name}'? This terminates its EC2 instance.`
|
|
1617
1647
|
});
|
|
1618
|
-
if (
|
|
1648
|
+
if (clack8.isCancel(confirmed) || !confirmed) {
|
|
1619
1649
|
outputInfo("Cancelled.");
|
|
1620
1650
|
return;
|
|
1621
1651
|
}
|
|
@@ -1662,7 +1692,7 @@ import { exec as exec3 } from "child_process";
|
|
|
1662
1692
|
import { promisify as promisify4 } from "util";
|
|
1663
1693
|
import * as fs5 from "fs/promises";
|
|
1664
1694
|
import * as path5 from "path";
|
|
1665
|
-
import * as
|
|
1695
|
+
import * as clack13 from "@clack/prompts";
|
|
1666
1696
|
import pc2 from "picocolors";
|
|
1667
1697
|
|
|
1668
1698
|
// src/lib/skills.ts
|
|
@@ -1670,7 +1700,7 @@ import { exec } from "child_process";
|
|
|
1670
1700
|
import { existsSync as existsSync3, readFileSync as readFileSync2, appendFileSync } from "fs";
|
|
1671
1701
|
import { join as join2 } from "path";
|
|
1672
1702
|
import { promisify } from "util";
|
|
1673
|
-
import * as
|
|
1703
|
+
import * as clack9 from "@clack/prompts";
|
|
1674
1704
|
var execAsync = promisify(exec);
|
|
1675
1705
|
var SKILL_INSTALL_TIMEOUT_MS = 6e4;
|
|
1676
1706
|
function describeExecError(err) {
|
|
@@ -1723,29 +1753,29 @@ ${missing.join("\n")}
|
|
|
1723
1753
|
}
|
|
1724
1754
|
async function installSkills(json) {
|
|
1725
1755
|
try {
|
|
1726
|
-
if (!json)
|
|
1756
|
+
if (!json) clack9.log.info("Installing InsForge agent skills (global)...");
|
|
1727
1757
|
await execAsync("npx skills add insforge/agent-skills -g -y -a antigravity -a augment -a claude-code -a cline -a codex -a cursor -a gemini-cli -a github-copilot -a kilo -a qoder -a qwen-code -a roo -a trae -a windsurf", {
|
|
1728
1758
|
cwd: process.cwd(),
|
|
1729
1759
|
timeout: SKILL_INSTALL_TIMEOUT_MS
|
|
1730
1760
|
});
|
|
1731
|
-
if (!json)
|
|
1761
|
+
if (!json) clack9.log.success("InsForge agent skills installed.");
|
|
1732
1762
|
} catch (err) {
|
|
1733
1763
|
if (!json) {
|
|
1734
|
-
|
|
1735
|
-
|
|
1764
|
+
clack9.log.warn(`Could not install agent skills: ${describeExecError(err)}`);
|
|
1765
|
+
clack9.log.info("Run `npx skills add insforge/agent-skills` once resolved to see the full output.");
|
|
1736
1766
|
}
|
|
1737
1767
|
}
|
|
1738
1768
|
try {
|
|
1739
|
-
if (!json)
|
|
1769
|
+
if (!json) clack9.log.info("Installing find-skills (global)...");
|
|
1740
1770
|
await execAsync("npx skills add https://github.com/vercel-labs/skills --skill find-skills -g -y", {
|
|
1741
1771
|
cwd: process.cwd(),
|
|
1742
1772
|
timeout: SKILL_INSTALL_TIMEOUT_MS
|
|
1743
1773
|
});
|
|
1744
|
-
if (!json)
|
|
1774
|
+
if (!json) clack9.log.success("find-skills installed.");
|
|
1745
1775
|
} catch (err) {
|
|
1746
1776
|
if (!json) {
|
|
1747
|
-
|
|
1748
|
-
|
|
1777
|
+
clack9.log.warn(`Could not install find-skills: ${describeExecError(err)}`);
|
|
1778
|
+
clack9.log.info("Run `npx skills add https://github.com/vercel-labs/skills --skill find-skills` once resolved.");
|
|
1749
1779
|
}
|
|
1750
1780
|
}
|
|
1751
1781
|
try {
|
|
@@ -1801,7 +1831,7 @@ import { tmpdir } from "os";
|
|
|
1801
1831
|
import { execFile } from "child_process";
|
|
1802
1832
|
import { promisify as promisify2 } from "util";
|
|
1803
1833
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
1804
|
-
import * as
|
|
1834
|
+
import * as clack10 from "@clack/prompts";
|
|
1805
1835
|
|
|
1806
1836
|
// src/lib/api/oss.ts
|
|
1807
1837
|
function requireProjectConfig() {
|
|
@@ -2008,7 +2038,7 @@ async function applyAuthProvider(provider, cwd, projectConfig, json) {
|
|
|
2008
2038
|
if (!VALID_AUTH_PROVIDERS.includes(provider)) {
|
|
2009
2039
|
throw new Error(`Unknown auth provider: ${provider}`);
|
|
2010
2040
|
}
|
|
2011
|
-
const fetchSpinner = !json ?
|
|
2041
|
+
const fetchSpinner = !json ? clack10.spinner() : null;
|
|
2012
2042
|
fetchSpinner?.start(`Fetching ${provider} scaffold from templates repo...`);
|
|
2013
2043
|
const { dir: providerDir, cleanup } = await fetchProviderTree(provider);
|
|
2014
2044
|
fetchSpinner?.stop(`${provider} scaffold ready`);
|
|
@@ -2107,15 +2137,15 @@ async function applyAuthProvider(provider, cwd, projectConfig, json) {
|
|
|
2107
2137
|
result.envKeysRefreshed = Array.from(/* @__PURE__ */ new Set([...result.envKeysRefreshed, ...refreshed]));
|
|
2108
2138
|
}
|
|
2109
2139
|
if (!jwtSecret && !json) {
|
|
2110
|
-
|
|
2140
|
+
clack10.log.warn("Could not auto-fill JWT_SECRET \u2014 run `npx @insforge/cli secrets get JWT_SECRET` and paste it into .env.local.");
|
|
2111
2141
|
}
|
|
2112
2142
|
if (result.envKeysSkipped.length > 0 && !json) {
|
|
2113
|
-
|
|
2143
|
+
clack10.log.warn(
|
|
2114
2144
|
`Kept your existing values for: ${result.envKeysSkipped.join(", ")}. If any of these need the auth-provider's defaults, see .env.example for reference.`
|
|
2115
2145
|
);
|
|
2116
2146
|
}
|
|
2117
2147
|
if (result.envKeysRefreshed.length > 0 && !json) {
|
|
2118
|
-
|
|
2148
|
+
clack10.log.info(
|
|
2119
2149
|
`Refreshed stale platform defaults: ${result.envKeysRefreshed.join(", ")}. Your value matched the manifest's default, so we replaced it with the live one.`
|
|
2120
2150
|
);
|
|
2121
2151
|
}
|
|
@@ -2131,7 +2161,7 @@ import { tmpdir as tmpdir2 } from "os";
|
|
|
2131
2161
|
import { promisify as promisify3 } from "util";
|
|
2132
2162
|
import * as fs4 from "fs/promises";
|
|
2133
2163
|
import * as path4 from "path";
|
|
2134
|
-
import * as
|
|
2164
|
+
import * as clack12 from "@clack/prompts";
|
|
2135
2165
|
|
|
2136
2166
|
// src/lib/env.ts
|
|
2137
2167
|
import * as fs2 from "fs/promises";
|
|
@@ -2166,7 +2196,7 @@ import * as path3 from "path";
|
|
|
2166
2196
|
import * as fs3 from "fs/promises";
|
|
2167
2197
|
import { createReadStream } from "fs";
|
|
2168
2198
|
import { createHash as createHash2 } from "crypto";
|
|
2169
|
-
import * as
|
|
2199
|
+
import * as clack11 from "@clack/prompts";
|
|
2170
2200
|
import archiver from "archiver";
|
|
2171
2201
|
var POLL_INTERVAL_MS3 = 5e3;
|
|
2172
2202
|
var POLL_TIMEOUT_MS3 = 3e5;
|
|
@@ -2346,8 +2376,8 @@ async function startDirectDeployment(deploymentId, startBody) {
|
|
|
2346
2376
|
});
|
|
2347
2377
|
await response.json();
|
|
2348
2378
|
}
|
|
2349
|
-
async function pollDeployment(deploymentId,
|
|
2350
|
-
|
|
2379
|
+
async function pollDeployment(deploymentId, spinner10, syncBeforeRead) {
|
|
2380
|
+
spinner10?.message("Building and deploying...");
|
|
2351
2381
|
const startTime = Date.now();
|
|
2352
2382
|
let deployment = null;
|
|
2353
2383
|
while (Date.now() - startTime < POLL_TIMEOUT_MS3) {
|
|
@@ -2363,13 +2393,13 @@ async function pollDeployment(deploymentId, spinner8, syncBeforeRead) {
|
|
|
2363
2393
|
break;
|
|
2364
2394
|
}
|
|
2365
2395
|
if (status === "ERROR" || status === "CANCELED") {
|
|
2366
|
-
|
|
2396
|
+
spinner10?.stop("Deployment failed");
|
|
2367
2397
|
throw new CLIError(
|
|
2368
2398
|
getDeploymentError(deployment.metadata) ?? `Deployment failed with status: ${deployment.status}`
|
|
2369
2399
|
);
|
|
2370
2400
|
}
|
|
2371
2401
|
const elapsed = Math.round((Date.now() - startTime) / 1e3);
|
|
2372
|
-
|
|
2402
|
+
spinner10?.message(`Building and deploying... (${elapsed}s, status: ${deployment.status})`);
|
|
2373
2403
|
} catch (err) {
|
|
2374
2404
|
if (err instanceof CLIError) throw err;
|
|
2375
2405
|
}
|
|
@@ -2379,20 +2409,20 @@ async function pollDeployment(deploymentId, spinner8, syncBeforeRead) {
|
|
|
2379
2409
|
return { deploymentId, deployment, isReady, liveUrl };
|
|
2380
2410
|
}
|
|
2381
2411
|
async function deployProjectDirect(opts, config) {
|
|
2382
|
-
const { sourceDir, startBody = {}, spinner:
|
|
2383
|
-
|
|
2412
|
+
const { sourceDir, startBody = {}, spinner: spinner10 } = opts;
|
|
2413
|
+
spinner10?.start("Scanning source files...");
|
|
2384
2414
|
const localFiles = await collectDeploymentFiles(sourceDir);
|
|
2385
2415
|
if (localFiles.length === 0) {
|
|
2386
2416
|
throw new CLIError("No deployable files found in the source directory.");
|
|
2387
2417
|
}
|
|
2388
|
-
|
|
2418
|
+
spinner10?.message("Creating deployment...");
|
|
2389
2419
|
const createResult = await createDirectDeploymentSession(
|
|
2390
2420
|
config,
|
|
2391
2421
|
localFiles.map(({ path: relativePath, sha, size }) => ({ path: relativePath, sha, size }))
|
|
2392
2422
|
);
|
|
2393
2423
|
const localFileByPath = new Map(localFiles.map((file) => [file.path, file]));
|
|
2394
2424
|
const pendingFiles = createResult.files.filter((file) => !file.uploadedAt);
|
|
2395
|
-
|
|
2425
|
+
spinner10?.message(`Uploading ${pendingFiles.length} file${pendingFiles.length === 1 ? "" : "s"}...`);
|
|
2396
2426
|
await runWithConcurrency(pendingFiles, DIRECT_UPLOAD_CONCURRENCY, async (manifestFile) => {
|
|
2397
2427
|
const localFile = localFileByPath.get(manifestFile.path);
|
|
2398
2428
|
if (!localFile) {
|
|
@@ -2403,18 +2433,18 @@ async function deployProjectDirect(opts, config) {
|
|
|
2403
2433
|
}
|
|
2404
2434
|
await uploadDirectDeploymentFile(createResult.id, manifestFile, localFile);
|
|
2405
2435
|
});
|
|
2406
|
-
|
|
2436
|
+
spinner10?.message("Starting deployment...");
|
|
2407
2437
|
await startDirectDeployment(createResult.id, startBody);
|
|
2408
|
-
return await pollDeployment(createResult.id,
|
|
2438
|
+
return await pollDeployment(createResult.id, spinner10, !isInsforgeCloudOssHost(config.oss_host));
|
|
2409
2439
|
}
|
|
2410
2440
|
async function deployProjectLegacy(opts) {
|
|
2411
|
-
const { sourceDir, startBody = {}, spinner:
|
|
2412
|
-
|
|
2441
|
+
const { sourceDir, startBody = {}, spinner: spinner10 } = opts;
|
|
2442
|
+
spinner10?.message("Creating deployment...");
|
|
2413
2443
|
const createRes = await ossFetch("/api/deployments", { method: "POST" });
|
|
2414
2444
|
const { id: deploymentId, uploadUrl, uploadFields } = await createRes.json();
|
|
2415
|
-
|
|
2445
|
+
spinner10?.message("Compressing source files...");
|
|
2416
2446
|
const zipBuffer = await createZipBuffer(sourceDir);
|
|
2417
|
-
|
|
2447
|
+
spinner10?.message("Uploading...");
|
|
2418
2448
|
const formData = new FormData();
|
|
2419
2449
|
for (const [key, value] of Object.entries(uploadFields)) {
|
|
2420
2450
|
formData.append(key, value);
|
|
@@ -2425,13 +2455,13 @@ async function deployProjectLegacy(opts) {
|
|
|
2425
2455
|
const uploadErr = await uploadRes.text();
|
|
2426
2456
|
throw new CLIError(`Failed to upload: ${uploadErr}`);
|
|
2427
2457
|
}
|
|
2428
|
-
|
|
2458
|
+
spinner10?.message("Starting deployment...");
|
|
2429
2459
|
const startRes = await ossFetch(`/api/deployments/${deploymentId}/start`, {
|
|
2430
2460
|
method: "POST",
|
|
2431
2461
|
body: JSON.stringify(startBody)
|
|
2432
2462
|
});
|
|
2433
2463
|
await startRes.json();
|
|
2434
|
-
return await pollDeployment(deploymentId,
|
|
2464
|
+
return await pollDeployment(deploymentId, spinner10, false);
|
|
2435
2465
|
}
|
|
2436
2466
|
async function deployProject(opts) {
|
|
2437
2467
|
const config = getProjectConfig();
|
|
@@ -2466,7 +2496,7 @@ function registerDeploymentsDeployCommand(deploymentsCmd2) {
|
|
|
2466
2496
|
`"${dirName}" is an excluded directory and cannot be used as a deploy source. Please specify your project root or output directory instead.`
|
|
2467
2497
|
);
|
|
2468
2498
|
}
|
|
2469
|
-
const
|
|
2499
|
+
const spinner10 = !json ? clack11.spinner() : null;
|
|
2470
2500
|
const startBody = {};
|
|
2471
2501
|
if (opts.env) {
|
|
2472
2502
|
try {
|
|
@@ -2492,19 +2522,19 @@ function registerDeploymentsDeployCommand(deploymentsCmd2) {
|
|
|
2492
2522
|
throw new CLIError("Invalid --meta JSON.");
|
|
2493
2523
|
}
|
|
2494
2524
|
}
|
|
2495
|
-
const result = await deployProject({ sourceDir, startBody, spinner:
|
|
2525
|
+
const result = await deployProject({ sourceDir, startBody, spinner: spinner10 });
|
|
2496
2526
|
if (result.isReady) {
|
|
2497
|
-
|
|
2527
|
+
spinner10?.stop("Deployment complete");
|
|
2498
2528
|
if (json) {
|
|
2499
2529
|
outputJson(result.deployment);
|
|
2500
2530
|
} else {
|
|
2501
2531
|
if (result.liveUrl) {
|
|
2502
|
-
|
|
2532
|
+
clack11.log.success(`Live at: ${result.liveUrl}`);
|
|
2503
2533
|
}
|
|
2504
|
-
|
|
2534
|
+
clack11.log.info(`Deployment ID: ${result.deploymentId}`);
|
|
2505
2535
|
}
|
|
2506
2536
|
} else {
|
|
2507
|
-
|
|
2537
|
+
spinner10?.stop("Deployment is still building");
|
|
2508
2538
|
if (json) {
|
|
2509
2539
|
outputJson({
|
|
2510
2540
|
id: result.deploymentId,
|
|
@@ -2512,9 +2542,9 @@ function registerDeploymentsDeployCommand(deploymentsCmd2) {
|
|
|
2512
2542
|
timedOut: true
|
|
2513
2543
|
});
|
|
2514
2544
|
} else {
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2545
|
+
clack11.log.info(`Deployment ID: ${result.deploymentId}`);
|
|
2546
|
+
clack11.log.warn("Deployment did not finish within 5 minutes.");
|
|
2547
|
+
clack11.log.info(`Check status with: npx @insforge/cli deployments status ${result.deploymentId}`);
|
|
2518
2548
|
}
|
|
2519
2549
|
}
|
|
2520
2550
|
await reportCliUsage("cli.deployments.deploy", true);
|
|
@@ -2530,9 +2560,6 @@ var execAsync2 = promisify3(exec2);
|
|
|
2530
2560
|
var execFileAsync2 = promisify3(execFile2);
|
|
2531
2561
|
var SAFE_REPO_PATTERN2 = /^(https?:\/\/|git@)[A-Za-z0-9._:/@~+-]+(\.git)?$/;
|
|
2532
2562
|
var SAFE_BRANCH_PATTERN2 = /^[A-Za-z0-9._/-]+$/;
|
|
2533
|
-
function buildOssHost(appkey, region) {
|
|
2534
|
-
return `https://${appkey}.${region}.insforge.app`;
|
|
2535
|
-
}
|
|
2536
2563
|
async function waitForProjectActive(projectId, apiUrl, timeoutMs = 12e4) {
|
|
2537
2564
|
const start = Date.now();
|
|
2538
2565
|
while (Date.now() - start < timeoutMs) {
|
|
@@ -2647,7 +2674,7 @@ function registerCreateCommand(program2) {
|
|
|
2647
2674
|
await requireAuth(apiUrl, false);
|
|
2648
2675
|
if (!json) {
|
|
2649
2676
|
await animateBanner();
|
|
2650
|
-
|
|
2677
|
+
clack12.intro("Let's build something great");
|
|
2651
2678
|
}
|
|
2652
2679
|
let orgId = opts.orgId;
|
|
2653
2680
|
if (!orgId) {
|
|
@@ -2657,7 +2684,7 @@ function registerCreateCommand(program2) {
|
|
|
2657
2684
|
}
|
|
2658
2685
|
if (orgs.length === 1) {
|
|
2659
2686
|
orgId = orgs[0].id;
|
|
2660
|
-
if (!json)
|
|
2687
|
+
if (!json) clack12.log.info(`Using organization: ${orgs[0].name}`);
|
|
2661
2688
|
} else {
|
|
2662
2689
|
if (json) {
|
|
2663
2690
|
throw new CLIError("Multiple organizations found. Specify --org-id.");
|
|
@@ -2770,7 +2797,7 @@ function registerCreateCommand(program2) {
|
|
|
2770
2797
|
process.chdir(projectDir);
|
|
2771
2798
|
}
|
|
2772
2799
|
let projectLinked = false;
|
|
2773
|
-
const s = !json ?
|
|
2800
|
+
const s = !json ? clack12.spinner() : null;
|
|
2774
2801
|
try {
|
|
2775
2802
|
s?.start("Creating project...");
|
|
2776
2803
|
const project = await createProject(orgId, projectName, opts.region, apiUrl);
|
|
@@ -2798,7 +2825,7 @@ function registerCreateCommand(program2) {
|
|
|
2798
2825
|
try {
|
|
2799
2826
|
const anonKey = await getAnonKey();
|
|
2800
2827
|
if (!anonKey) {
|
|
2801
|
-
if (!json)
|
|
2828
|
+
if (!json) clack12.log.warn("Could not retrieve anon key. You can add it to .env.local manually.");
|
|
2802
2829
|
} else {
|
|
2803
2830
|
const envPath = path4.join(process.cwd(), ".env.local");
|
|
2804
2831
|
const envContent = [
|
|
@@ -2809,16 +2836,16 @@ function registerCreateCommand(program2) {
|
|
|
2809
2836
|
].join("\n");
|
|
2810
2837
|
await fs4.writeFile(envPath, envContent, { flag: "wx" });
|
|
2811
2838
|
if (!json) {
|
|
2812
|
-
|
|
2839
|
+
clack12.log.success("Created .env.local with your InsForge credentials");
|
|
2813
2840
|
}
|
|
2814
2841
|
}
|
|
2815
2842
|
} catch (err) {
|
|
2816
2843
|
const error = err;
|
|
2817
2844
|
if (!json) {
|
|
2818
2845
|
if (error.code === "EEXIST") {
|
|
2819
|
-
|
|
2846
|
+
clack12.log.warn(".env.local already exists; skipping InsForge key seeding.");
|
|
2820
2847
|
} else {
|
|
2821
|
-
|
|
2848
|
+
clack12.log.warn(`Failed to create .env.local: ${error.message}`);
|
|
2822
2849
|
}
|
|
2823
2850
|
}
|
|
2824
2851
|
}
|
|
@@ -2827,12 +2854,12 @@ function registerCreateCommand(program2) {
|
|
|
2827
2854
|
try {
|
|
2828
2855
|
const result = await applyAuthProvider(opts.auth, process.cwd(), projectConfig, json);
|
|
2829
2856
|
if (!json) {
|
|
2830
|
-
|
|
2857
|
+
clack12.log.success(`Wired in ${opts.auth}: ${result.written.length} new, ${result.overwritten.length} replaced`);
|
|
2831
2858
|
}
|
|
2832
2859
|
} catch (err) {
|
|
2833
2860
|
const msg = `Failed to apply --auth ${opts.auth}: ${err.message}`;
|
|
2834
2861
|
if (json) console.error(JSON.stringify({ warning: msg }));
|
|
2835
|
-
else
|
|
2862
|
+
else clack12.log.warn(msg);
|
|
2836
2863
|
}
|
|
2837
2864
|
}
|
|
2838
2865
|
await installSkills(json);
|
|
@@ -2840,7 +2867,7 @@ function registerCreateCommand(program2) {
|
|
|
2840
2867
|
await reportCliUsage("cli.create", true, 6);
|
|
2841
2868
|
const templateDownloaded = hasTemplate ? await fs4.stat(path4.join(process.cwd(), "package.json")).catch(() => null) : null;
|
|
2842
2869
|
if (templateDownloaded) {
|
|
2843
|
-
const installSpinner = !json ?
|
|
2870
|
+
const installSpinner = !json ? clack12.spinner() : null;
|
|
2844
2871
|
installSpinner?.start("Installing dependencies...");
|
|
2845
2872
|
try {
|
|
2846
2873
|
await execAsync2("npm install", { cwd: process.cwd(), maxBuffer: 10 * 1024 * 1024 });
|
|
@@ -2848,8 +2875,8 @@ function registerCreateCommand(program2) {
|
|
|
2848
2875
|
} catch (err) {
|
|
2849
2876
|
installSpinner?.stop("Failed to install dependencies");
|
|
2850
2877
|
if (!json) {
|
|
2851
|
-
|
|
2852
|
-
|
|
2878
|
+
clack12.log.warn(`npm install failed: ${err.message}`);
|
|
2879
|
+
clack12.log.info("Run `npm install` manually to install dependencies.");
|
|
2853
2880
|
}
|
|
2854
2881
|
}
|
|
2855
2882
|
}
|
|
@@ -2865,7 +2892,7 @@ function registerCreateCommand(program2) {
|
|
|
2865
2892
|
if (envVars.length > 0) {
|
|
2866
2893
|
startBody.envVars = envVars;
|
|
2867
2894
|
}
|
|
2868
|
-
const deploySpinner =
|
|
2895
|
+
const deploySpinner = clack12.spinner();
|
|
2869
2896
|
const result = await deployProject({
|
|
2870
2897
|
sourceDir: process.cwd(),
|
|
2871
2898
|
startBody,
|
|
@@ -2876,12 +2903,12 @@ function registerCreateCommand(program2) {
|
|
|
2876
2903
|
liveUrl = result.liveUrl;
|
|
2877
2904
|
} else {
|
|
2878
2905
|
deploySpinner.stop("Deployment is still building");
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2906
|
+
clack12.log.info(`Deployment ID: ${result.deploymentId}`);
|
|
2907
|
+
clack12.log.warn("Deployment did not finish within 2 minutes.");
|
|
2908
|
+
clack12.log.info(`Check status with: npx @insforge/cli deployments status ${result.deploymentId}`);
|
|
2882
2909
|
}
|
|
2883
2910
|
} catch (err) {
|
|
2884
|
-
|
|
2911
|
+
clack12.log.warn(`Deploy failed: ${err.message}`);
|
|
2885
2912
|
}
|
|
2886
2913
|
}
|
|
2887
2914
|
}
|
|
@@ -2898,33 +2925,33 @@ function registerCreateCommand(program2) {
|
|
|
2898
2925
|
}
|
|
2899
2926
|
});
|
|
2900
2927
|
} else {
|
|
2901
|
-
|
|
2928
|
+
clack12.log.step(`Dashboard: ${dashboardUrl}`);
|
|
2902
2929
|
if (liveUrl) {
|
|
2903
|
-
|
|
2930
|
+
clack12.log.success(`Live site: ${liveUrl}`);
|
|
2904
2931
|
}
|
|
2905
2932
|
if (templateDownloaded) {
|
|
2906
2933
|
const steps = [
|
|
2907
2934
|
`cd ${dirName}`,
|
|
2908
2935
|
"npm run dev"
|
|
2909
2936
|
];
|
|
2910
|
-
|
|
2911
|
-
|
|
2937
|
+
clack12.note(steps.join("\n"), "Next steps");
|
|
2938
|
+
clack12.note("Open your coding agent (Claude Code, Codex, Cursor, etc.) to add new features.", "Keep building");
|
|
2912
2939
|
} else if (hasTemplate && !templateDownloaded) {
|
|
2913
|
-
|
|
2940
|
+
clack12.log.warn("Template download failed. You can retry or set up manually.");
|
|
2914
2941
|
} else {
|
|
2915
2942
|
const prompts = [
|
|
2916
2943
|
"Build a todo app with Google OAuth sign-in",
|
|
2917
2944
|
"Build an Instagram clone where users can upload photos, like, and comment",
|
|
2918
2945
|
"Build an AI chatbot with conversation history"
|
|
2919
2946
|
];
|
|
2920
|
-
|
|
2947
|
+
clack12.note(
|
|
2921
2948
|
`Open your coding agent (Claude Code, Codex, Cursor, etc.) and try:
|
|
2922
2949
|
|
|
2923
2950
|
${prompts.map((p) => `\u2022 "${p}"`).join("\n")}`,
|
|
2924
2951
|
"Start building"
|
|
2925
2952
|
);
|
|
2926
2953
|
}
|
|
2927
|
-
|
|
2954
|
+
clack12.outro("Done!");
|
|
2928
2955
|
}
|
|
2929
2956
|
} catch (err) {
|
|
2930
2957
|
if (!projectLinked && hasTemplate && projectDir !== originalCwd) {
|
|
@@ -2943,7 +2970,7 @@ ${prompts.map((p) => `\u2022 "${p}"`).join("\n")}`,
|
|
|
2943
2970
|
});
|
|
2944
2971
|
}
|
|
2945
2972
|
async function downloadTemplate(framework, projectConfig, projectName, json, _apiUrl) {
|
|
2946
|
-
const s = !json ?
|
|
2973
|
+
const s = !json ? clack12.spinner() : null;
|
|
2947
2974
|
s?.start("Downloading template...");
|
|
2948
2975
|
try {
|
|
2949
2976
|
const anonKey = await getAnonKey();
|
|
@@ -2974,13 +3001,13 @@ async function downloadTemplate(framework, projectConfig, projectName, json, _ap
|
|
|
2974
3001
|
} catch (err) {
|
|
2975
3002
|
s?.stop("Template download failed");
|
|
2976
3003
|
if (!json) {
|
|
2977
|
-
|
|
2978
|
-
|
|
3004
|
+
clack12.log.warn(`Failed to download template: ${err.message}`);
|
|
3005
|
+
clack12.log.info("You can manually set up the template later.");
|
|
2979
3006
|
}
|
|
2980
3007
|
}
|
|
2981
3008
|
}
|
|
2982
3009
|
async function downloadGitHubTemplate(templateName, projectConfig, json) {
|
|
2983
|
-
const s = !json ?
|
|
3010
|
+
const s = !json ? clack12.spinner() : null;
|
|
2984
3011
|
s?.start(`Downloading ${templateName} template...`);
|
|
2985
3012
|
const tempDir = path4.join(tmpdir2(), `insforge-template-${Date.now()}`);
|
|
2986
3013
|
try {
|
|
@@ -3029,7 +3056,7 @@ async function downloadGitHubTemplate(templateName, projectConfig, json) {
|
|
|
3029
3056
|
await fs4.writeFile(envLocalPath, envFinal, { flag: "wx" });
|
|
3030
3057
|
} catch (e) {
|
|
3031
3058
|
if (e.code === "EEXIST") {
|
|
3032
|
-
if (!json)
|
|
3059
|
+
if (!json) clack12.log.warn(".env.local already exists; skipping env seeding.");
|
|
3033
3060
|
} else {
|
|
3034
3061
|
throw e;
|
|
3035
3062
|
}
|
|
@@ -3039,7 +3066,7 @@ async function downloadGitHubTemplate(templateName, projectConfig, json) {
|
|
|
3039
3066
|
const migrationPath = path4.join(cwd, "migrations", "db_init.sql");
|
|
3040
3067
|
const migrationExists = await fs4.stat(migrationPath).catch(() => null);
|
|
3041
3068
|
if (migrationExists) {
|
|
3042
|
-
const dbSpinner = !json ?
|
|
3069
|
+
const dbSpinner = !json ? clack12.spinner() : null;
|
|
3043
3070
|
dbSpinner?.start("Running database migrations...");
|
|
3044
3071
|
try {
|
|
3045
3072
|
const sql = await fs4.readFile(migrationPath, "utf-8");
|
|
@@ -3048,8 +3075,8 @@ async function downloadGitHubTemplate(templateName, projectConfig, json) {
|
|
|
3048
3075
|
} catch (err) {
|
|
3049
3076
|
dbSpinner?.stop("Database migration failed");
|
|
3050
3077
|
if (!json) {
|
|
3051
|
-
|
|
3052
|
-
|
|
3078
|
+
clack12.log.warn(`Migration failed: ${err.message}`);
|
|
3079
|
+
clack12.log.info('You can run the migration manually: npx @insforge/cli db query --unrestricted "$(cat migrations/db_init.sql)"');
|
|
3053
3080
|
} else {
|
|
3054
3081
|
throw err;
|
|
3055
3082
|
}
|
|
@@ -3061,8 +3088,8 @@ async function downloadGitHubTemplate(templateName, projectConfig, json) {
|
|
|
3061
3088
|
if (json) {
|
|
3062
3089
|
console.error(JSON.stringify({ warning: msg }));
|
|
3063
3090
|
} else {
|
|
3064
|
-
|
|
3065
|
-
|
|
3091
|
+
clack12.log.warn(msg);
|
|
3092
|
+
clack12.log.info("You can manually clone from: https://github.com/InsForge/insforge-templates");
|
|
3066
3093
|
}
|
|
3067
3094
|
} finally {
|
|
3068
3095
|
await fs4.rm(tempDir, { recursive: true, force: true }).catch(() => {
|
|
@@ -3072,19 +3099,16 @@ async function downloadGitHubTemplate(templateName, projectConfig, json) {
|
|
|
3072
3099
|
|
|
3073
3100
|
// src/commands/projects/link.ts
|
|
3074
3101
|
var execAsync3 = promisify4(exec3);
|
|
3075
|
-
function buildOssHost2(appkey, region) {
|
|
3076
|
-
return `https://${appkey}.${region}.insforge.app`;
|
|
3077
|
-
}
|
|
3078
3102
|
async function runNpmInstall(startMessage = "Installing dependencies...") {
|
|
3079
|
-
const
|
|
3080
|
-
|
|
3103
|
+
const spinner10 = clack13.spinner();
|
|
3104
|
+
spinner10.start(startMessage);
|
|
3081
3105
|
try {
|
|
3082
3106
|
await execAsync3("npm install", { cwd: process.cwd(), maxBuffer: 10 * 1024 * 1024 });
|
|
3083
|
-
|
|
3107
|
+
spinner10.stop("Dependencies installed");
|
|
3084
3108
|
} catch (err) {
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3109
|
+
spinner10.stop("Failed to install dependencies");
|
|
3110
|
+
clack13.log.warn(`npm install failed: ${err.message}`);
|
|
3111
|
+
clack13.log.info("Run `npm install` manually to install dependencies.");
|
|
3088
3112
|
}
|
|
3089
3113
|
}
|
|
3090
3114
|
function registerProjectLinkCommand(program2) {
|
|
@@ -3163,11 +3187,11 @@ function registerProjectLinkCommand(program2) {
|
|
|
3163
3187
|
if (opts.auth) {
|
|
3164
3188
|
try {
|
|
3165
3189
|
const result = await applyAuthProvider(opts.auth, process.cwd(), projectConfig2, json);
|
|
3166
|
-
if (!json)
|
|
3190
|
+
if (!json) clack13.log.success(`Wired in ${opts.auth}: ${result.written.length} new, ${result.overwritten.length} replaced`);
|
|
3167
3191
|
} catch (err) {
|
|
3168
3192
|
const msg = `Failed to apply --auth ${opts.auth}: ${err.message}`;
|
|
3169
3193
|
if (json) console.error(JSON.stringify({ warning: msg }));
|
|
3170
|
-
else
|
|
3194
|
+
else clack13.log.warn(msg);
|
|
3171
3195
|
}
|
|
3172
3196
|
}
|
|
3173
3197
|
if (templateDownloaded && !json) {
|
|
@@ -3190,9 +3214,9 @@ function registerProjectLinkCommand(program2) {
|
|
|
3190
3214
|
`${pc2.bold("1.")} ${runCommand}`,
|
|
3191
3215
|
`${pc2.bold("2.")} Open ${pc2.cyan("Claude Code")} or ${pc2.cyan("Cursor")} and prompt your agent to add more features`
|
|
3192
3216
|
];
|
|
3193
|
-
|
|
3217
|
+
clack13.note(steps.join("\n"), "What's next");
|
|
3194
3218
|
} else {
|
|
3195
|
-
|
|
3219
|
+
clack13.log.warn("Template download failed. You can retry or set up manually.");
|
|
3196
3220
|
}
|
|
3197
3221
|
}
|
|
3198
3222
|
return;
|
|
@@ -3207,16 +3231,16 @@ function registerProjectLinkCommand(program2) {
|
|
|
3207
3231
|
try {
|
|
3208
3232
|
const result = await applyAuthProvider(opts.auth, process.cwd(), projectConfig2, json);
|
|
3209
3233
|
if (!json) {
|
|
3210
|
-
|
|
3234
|
+
clack13.log.success(`Wired in ${opts.auth}: ${result.written.length} new, ${result.overwritten.length} replaced`);
|
|
3211
3235
|
}
|
|
3212
3236
|
if (result.packageJsonPatched && !json) {
|
|
3213
3237
|
await runNpmInstall("Installing new dependencies...");
|
|
3214
3238
|
}
|
|
3215
|
-
if (!json)
|
|
3239
|
+
if (!json) clack13.note(result.nextSteps, "What's next");
|
|
3216
3240
|
} catch (err) {
|
|
3217
3241
|
const msg = `Failed to apply --auth ${opts.auth}: ${err.message}`;
|
|
3218
3242
|
if (json) console.error(JSON.stringify({ warning: msg }));
|
|
3219
|
-
else
|
|
3243
|
+
else clack13.log.warn(msg);
|
|
3220
3244
|
}
|
|
3221
3245
|
}
|
|
3222
3246
|
trackCommand("link", "oss-org", { direct: true });
|
|
@@ -3246,7 +3270,7 @@ function registerProjectLinkCommand(program2) {
|
|
|
3246
3270
|
}
|
|
3247
3271
|
if (orgs.length === 1) {
|
|
3248
3272
|
orgId = orgs[0].id;
|
|
3249
|
-
if (!json)
|
|
3273
|
+
if (!json) clack13.log.info(`Using organization: ${orgs[0].name}`);
|
|
3250
3274
|
} else {
|
|
3251
3275
|
if (json) {
|
|
3252
3276
|
throw new CLIError("Multiple organizations found. Specify --org-id.");
|
|
@@ -3308,7 +3332,7 @@ function registerProjectLinkCommand(program2) {
|
|
|
3308
3332
|
appkey: project.appkey,
|
|
3309
3333
|
region: project.region,
|
|
3310
3334
|
api_key: apiKey,
|
|
3311
|
-
oss_host:
|
|
3335
|
+
oss_host: buildOssHost(project.appkey, project.region)
|
|
3312
3336
|
};
|
|
3313
3337
|
if (!opts.template) {
|
|
3314
3338
|
saveProjectConfig(projectConfig);
|
|
@@ -3357,11 +3381,11 @@ function registerProjectLinkCommand(program2) {
|
|
|
3357
3381
|
if (opts.auth) {
|
|
3358
3382
|
try {
|
|
3359
3383
|
const result = await applyAuthProvider(opts.auth, process.cwd(), projectConfig, json);
|
|
3360
|
-
if (!json)
|
|
3384
|
+
if (!json) clack13.log.success(`Wired in ${opts.auth}: ${result.written.length} new, ${result.overwritten.length} replaced`);
|
|
3361
3385
|
} catch (err) {
|
|
3362
3386
|
const msg = `Failed to apply --auth ${opts.auth}: ${err.message}`;
|
|
3363
3387
|
if (json) console.error(JSON.stringify({ warning: msg }));
|
|
3364
|
-
else
|
|
3388
|
+
else clack13.log.warn(msg);
|
|
3365
3389
|
}
|
|
3366
3390
|
}
|
|
3367
3391
|
if (templateDownloaded && !json) {
|
|
@@ -3371,16 +3395,16 @@ function registerProjectLinkCommand(program2) {
|
|
|
3371
3395
|
await reportCliUsage("cli.link", true, 6, projectConfig);
|
|
3372
3396
|
if (!json) {
|
|
3373
3397
|
const dashboardUrl = `${getFrontendUrl()}/dashboard/project/${project.id}`;
|
|
3374
|
-
|
|
3398
|
+
clack13.log.step(`Dashboard: ${pc2.underline(dashboardUrl)}`);
|
|
3375
3399
|
if (templateDownloaded) {
|
|
3376
3400
|
const runCommand = `${pc2.cyan("cd")} ${pc2.green(dirName)} ${pc2.dim("&&")} ${pc2.cyan("npm run dev")}`;
|
|
3377
3401
|
const steps = [
|
|
3378
3402
|
`${pc2.bold("1.")} ${runCommand}`,
|
|
3379
3403
|
`${pc2.bold("2.")} Open ${pc2.cyan("Claude Code")} or ${pc2.cyan("Cursor")} and prompt your agent to add more features`
|
|
3380
3404
|
];
|
|
3381
|
-
|
|
3405
|
+
clack13.note(steps.join("\n"), "What's next");
|
|
3382
3406
|
} else {
|
|
3383
|
-
|
|
3407
|
+
clack13.log.warn("Template download failed. You can retry or set up manually.");
|
|
3384
3408
|
}
|
|
3385
3409
|
}
|
|
3386
3410
|
} else {
|
|
@@ -3388,29 +3412,29 @@ function registerProjectLinkCommand(program2) {
|
|
|
3388
3412
|
try {
|
|
3389
3413
|
const result = await applyAuthProvider(opts.auth, process.cwd(), projectConfig, json);
|
|
3390
3414
|
if (!json) {
|
|
3391
|
-
|
|
3415
|
+
clack13.log.success(`Wired in ${opts.auth}: ${result.written.length} new, ${result.overwritten.length} replaced`);
|
|
3392
3416
|
}
|
|
3393
3417
|
if (result.packageJsonPatched && !json) {
|
|
3394
3418
|
await runNpmInstall("Installing new dependencies...");
|
|
3395
3419
|
}
|
|
3396
|
-
if (!json)
|
|
3420
|
+
if (!json) clack13.note(result.nextSteps, "What's next");
|
|
3397
3421
|
} catch (err) {
|
|
3398
3422
|
const msg = `Failed to apply --auth ${opts.auth}: ${err.message}`;
|
|
3399
3423
|
if (json) console.error(JSON.stringify({ warning: msg }));
|
|
3400
|
-
else
|
|
3424
|
+
else clack13.log.warn(msg);
|
|
3401
3425
|
}
|
|
3402
3426
|
}
|
|
3403
3427
|
await installSkills(json);
|
|
3404
3428
|
await reportCliUsage("cli.link", true, 6, projectConfig);
|
|
3405
3429
|
if (!json) {
|
|
3406
3430
|
const dashboardUrl = `${getFrontendUrl()}/dashboard/project/${project.id}`;
|
|
3407
|
-
|
|
3431
|
+
clack13.log.step(`Dashboard: ${dashboardUrl}`);
|
|
3408
3432
|
const prompts = [
|
|
3409
3433
|
"Build a todo app with Google OAuth sign-in",
|
|
3410
3434
|
"Build an Instagram clone where users can upload photos, like, and comment",
|
|
3411
3435
|
"Build an AI chatbot with conversation history and deploy it to a live URL"
|
|
3412
3436
|
];
|
|
3413
|
-
|
|
3437
|
+
clack13.note(
|
|
3414
3438
|
`Open your coding agent (Claude Code, Codex, Cursor, etc.) and try:
|
|
3415
3439
|
|
|
3416
3440
|
${prompts.map((p) => `\u2022 "${p}"`).join("\n")}`,
|
|
@@ -4545,7 +4569,7 @@ function registerFunctionsCodeCommand(functionsCmd2) {
|
|
|
4545
4569
|
}
|
|
4546
4570
|
|
|
4547
4571
|
// src/commands/functions/delete.ts
|
|
4548
|
-
import * as
|
|
4572
|
+
import * as clack14 from "@clack/prompts";
|
|
4549
4573
|
function registerFunctionsDeleteCommand(functionsCmd2) {
|
|
4550
4574
|
functionsCmd2.command("delete <slug>").description("Delete an edge function").action(async (slug, _opts, cmd) => {
|
|
4551
4575
|
const { json, yes } = getRootOpts(cmd);
|
|
@@ -4556,7 +4580,7 @@ function registerFunctionsDeleteCommand(functionsCmd2) {
|
|
|
4556
4580
|
message: `Delete function "${slug}"? This cannot be undone.`
|
|
4557
4581
|
});
|
|
4558
4582
|
if (isCancel2(confirmed) || !confirmed) {
|
|
4559
|
-
|
|
4583
|
+
clack14.log.info("Cancelled.");
|
|
4560
4584
|
return;
|
|
4561
4585
|
}
|
|
4562
4586
|
}
|
|
@@ -6285,7 +6309,7 @@ function formatSize2(gb) {
|
|
|
6285
6309
|
|
|
6286
6310
|
// src/commands/diagnose/index.ts
|
|
6287
6311
|
import * as os from "os";
|
|
6288
|
-
import * as
|
|
6312
|
+
import * as clack15 from "@clack/prompts";
|
|
6289
6313
|
|
|
6290
6314
|
// src/commands/diagnose/metrics.ts
|
|
6291
6315
|
var METRIC_LABELS = {
|
|
@@ -6826,10 +6850,10 @@ function registerDiagnoseCommands(diagnoseCmd2) {
|
|
|
6826
6850
|
if (question.length === 0 || question.length > 2e3) {
|
|
6827
6851
|
throw new CLIError("Question must be between 1 and 2000 characters.");
|
|
6828
6852
|
}
|
|
6829
|
-
const s = !json ?
|
|
6853
|
+
const s = !json ? clack15.spinner() : null;
|
|
6830
6854
|
s?.start("Collecting diagnostic data...");
|
|
6831
6855
|
const data2 = await collectDiagnosticData(projectId, ossMode, apiUrl);
|
|
6832
|
-
const cliVersion = "0.1.
|
|
6856
|
+
const cliVersion = "0.1.71";
|
|
6833
6857
|
s?.stop("Data collected");
|
|
6834
6858
|
if (!json) {
|
|
6835
6859
|
console.log(`
|
|
@@ -6962,9 +6986,9 @@ function registerDiagnoseCommands(diagnoseCmd2) {
|
|
|
6962
6986
|
void 0,
|
|
6963
6987
|
apiUrl
|
|
6964
6988
|
);
|
|
6965
|
-
|
|
6989
|
+
clack15.log.success("Thanks for your feedback!");
|
|
6966
6990
|
} catch {
|
|
6967
|
-
|
|
6991
|
+
clack15.log.warn("Failed to submit rating.");
|
|
6968
6992
|
}
|
|
6969
6993
|
}
|
|
6970
6994
|
}
|
|
@@ -8192,9 +8216,739 @@ function registerPaymentsCommands(paymentsCmd2) {
|
|
|
8192
8216
|
registerPaymentsHistoryCommand(paymentsCmd2);
|
|
8193
8217
|
}
|
|
8194
8218
|
|
|
8219
|
+
// src/commands/posthog/setup.ts
|
|
8220
|
+
import { existsSync as existsSync13, readFileSync as readFileSync10, writeFileSync as writeFileSync8, mkdirSync as mkdirSync3 } from "fs";
|
|
8221
|
+
import { join as join16, dirname as dirname2 } from "path";
|
|
8222
|
+
import * as clack16 from "@clack/prompts";
|
|
8223
|
+
import pc3 from "picocolors";
|
|
8224
|
+
|
|
8225
|
+
// src/lib/api/posthog.ts
|
|
8226
|
+
var REQUEST_TIMEOUT_MS = 3e4;
|
|
8227
|
+
async function fetchWithTimeout(url, init, callerSignal) {
|
|
8228
|
+
const ac = new AbortController();
|
|
8229
|
+
const timer = setTimeout(() => ac.abort(), REQUEST_TIMEOUT_MS);
|
|
8230
|
+
const onCallerAbort = () => ac.abort();
|
|
8231
|
+
callerSignal?.addEventListener("abort", onCallerAbort);
|
|
8232
|
+
try {
|
|
8233
|
+
return await fetch(url, { ...init, signal: ac.signal });
|
|
8234
|
+
} finally {
|
|
8235
|
+
clearTimeout(timer);
|
|
8236
|
+
callerSignal?.removeEventListener("abort", onCallerAbort);
|
|
8237
|
+
}
|
|
8238
|
+
}
|
|
8239
|
+
async function fetchPosthogConnection(projectId, jwt, apiUrl, signal) {
|
|
8240
|
+
const baseUrl = getPlatformApiUrl(apiUrl);
|
|
8241
|
+
const url = `${baseUrl}/integrations/posthog/v1/connection?project_id=${encodeURIComponent(projectId)}`;
|
|
8242
|
+
let res;
|
|
8243
|
+
try {
|
|
8244
|
+
res = await fetchWithTimeout(
|
|
8245
|
+
url,
|
|
8246
|
+
{
|
|
8247
|
+
method: "GET",
|
|
8248
|
+
headers: {
|
|
8249
|
+
Authorization: `Bearer ${jwt}`,
|
|
8250
|
+
Accept: "application/json"
|
|
8251
|
+
}
|
|
8252
|
+
},
|
|
8253
|
+
signal
|
|
8254
|
+
);
|
|
8255
|
+
} catch (err) {
|
|
8256
|
+
return { kind: "error", message: formatFetchError(err, url) };
|
|
8257
|
+
}
|
|
8258
|
+
if (res.status === 404) {
|
|
8259
|
+
return { kind: "not-connected" };
|
|
8260
|
+
}
|
|
8261
|
+
if (res.status === 403) {
|
|
8262
|
+
const body = await res.json().catch(() => ({}));
|
|
8263
|
+
return {
|
|
8264
|
+
kind: "forbidden",
|
|
8265
|
+
message: body.error ?? "Forbidden \u2014 you may not have access to this project."
|
|
8266
|
+
};
|
|
8267
|
+
}
|
|
8268
|
+
if (!res.ok) {
|
|
8269
|
+
const body = await res.json().catch(() => ({}));
|
|
8270
|
+
return {
|
|
8271
|
+
kind: "error",
|
|
8272
|
+
message: body.error ?? `Request failed: HTTP ${res.status}`,
|
|
8273
|
+
status: res.status
|
|
8274
|
+
};
|
|
8275
|
+
}
|
|
8276
|
+
let data;
|
|
8277
|
+
try {
|
|
8278
|
+
data = await res.json();
|
|
8279
|
+
} catch (err) {
|
|
8280
|
+
return {
|
|
8281
|
+
kind: "error",
|
|
8282
|
+
message: `Could not parse connection response: ${err.message}`
|
|
8283
|
+
};
|
|
8284
|
+
}
|
|
8285
|
+
const conn = data ?? {};
|
|
8286
|
+
if (!conn.apiKey) {
|
|
8287
|
+
return { kind: "not-connected" };
|
|
8288
|
+
}
|
|
8289
|
+
if (conn.status && conn.status !== "active") {
|
|
8290
|
+
return { kind: "not-connected" };
|
|
8291
|
+
}
|
|
8292
|
+
return { kind: "connected", connection: conn };
|
|
8293
|
+
}
|
|
8294
|
+
async function pollPosthogConnection(projectId, jwt, opts, apiUrl) {
|
|
8295
|
+
const start = Date.now();
|
|
8296
|
+
let consecutiveErrors = 0;
|
|
8297
|
+
for (; ; ) {
|
|
8298
|
+
if (opts.signal?.aborted) {
|
|
8299
|
+
throw new CLIError("Connection wait cancelled.");
|
|
8300
|
+
}
|
|
8301
|
+
const elapsed = Date.now() - start;
|
|
8302
|
+
if (elapsed >= opts.timeoutMs) {
|
|
8303
|
+
throw new CLIError(
|
|
8304
|
+
"Timed out waiting for PostHog connection. Re-run `insforge posthog setup` after authorizing."
|
|
8305
|
+
);
|
|
8306
|
+
}
|
|
8307
|
+
opts.onTick?.(elapsed);
|
|
8308
|
+
const result = await fetchPosthogConnection(projectId, jwt, apiUrl, opts.signal);
|
|
8309
|
+
switch (result.kind) {
|
|
8310
|
+
case "connected":
|
|
8311
|
+
return result.connection;
|
|
8312
|
+
case "forbidden":
|
|
8313
|
+
throw new CLIError(`Forbidden: ${result.message}`, 5);
|
|
8314
|
+
case "error":
|
|
8315
|
+
consecutiveErrors += 1;
|
|
8316
|
+
if (consecutiveErrors > opts.maxTransientRetries) {
|
|
8317
|
+
throw new CLIError(
|
|
8318
|
+
`Connection check failed after ${opts.maxTransientRetries} retries: ${result.message}`
|
|
8319
|
+
);
|
|
8320
|
+
}
|
|
8321
|
+
break;
|
|
8322
|
+
case "not-connected":
|
|
8323
|
+
consecutiveErrors = 0;
|
|
8324
|
+
break;
|
|
8325
|
+
}
|
|
8326
|
+
await sleep(opts.intervalMs, opts.signal);
|
|
8327
|
+
}
|
|
8328
|
+
}
|
|
8329
|
+
async function startPosthogCliFlow(projectId, jwt, apiUrl) {
|
|
8330
|
+
const baseUrl = getPlatformApiUrl(apiUrl);
|
|
8331
|
+
const url = `${baseUrl}/integrations/posthog/v1/cli-start?p=${encodeURIComponent(projectId)}`;
|
|
8332
|
+
let res;
|
|
8333
|
+
try {
|
|
8334
|
+
res = await fetchWithTimeout(url, {
|
|
8335
|
+
method: "GET",
|
|
8336
|
+
headers: {
|
|
8337
|
+
Authorization: `Bearer ${jwt}`,
|
|
8338
|
+
Accept: "application/json"
|
|
8339
|
+
}
|
|
8340
|
+
});
|
|
8341
|
+
} catch (err) {
|
|
8342
|
+
throw new CLIError(`Failed to start PostHog connect flow: ${formatFetchError(err, url)}`);
|
|
8343
|
+
}
|
|
8344
|
+
if (!res.ok) {
|
|
8345
|
+
const body = await res.json().catch(() => ({}));
|
|
8346
|
+
const msg = body.error ?? res.statusText ?? `HTTP ${res.status}`;
|
|
8347
|
+
if (res.status === 401) {
|
|
8348
|
+
throw new CLIError(`Not authenticated (HTTP 401): ${msg}. Re-run \`insforge login\`.`);
|
|
8349
|
+
}
|
|
8350
|
+
if (res.status === 403) {
|
|
8351
|
+
throw new CLIError(`Forbidden (HTTP 403): ${msg}`, 5);
|
|
8352
|
+
}
|
|
8353
|
+
if (res.status === 404) {
|
|
8354
|
+
throw new CLIError(
|
|
8355
|
+
`PostHog connect flow unavailable (HTTP 404): ${msg}. Check that the project is linked.`
|
|
8356
|
+
);
|
|
8357
|
+
}
|
|
8358
|
+
throw new CLIError(`PostHog cli-start failed (HTTP ${res.status}): ${msg}`);
|
|
8359
|
+
}
|
|
8360
|
+
const data = await res.json().catch(() => ({}));
|
|
8361
|
+
if (data.type === "connected") {
|
|
8362
|
+
return { type: "connected" };
|
|
8363
|
+
}
|
|
8364
|
+
if (data.type === "authorize" && typeof data.authorizeUrl === "string" && data.authorizeUrl) {
|
|
8365
|
+
return { type: "authorize", authorizeUrl: data.authorizeUrl };
|
|
8366
|
+
}
|
|
8367
|
+
throw new CLIError("PostHog cli-start returned an unexpected response shape.");
|
|
8368
|
+
}
|
|
8369
|
+
function sleep(ms, signal) {
|
|
8370
|
+
return new Promise((resolve5, reject) => {
|
|
8371
|
+
if (signal?.aborted) {
|
|
8372
|
+
reject(new CLIError("Connection wait cancelled."));
|
|
8373
|
+
return;
|
|
8374
|
+
}
|
|
8375
|
+
const timer = setTimeout(() => {
|
|
8376
|
+
signal?.removeEventListener("abort", onAbort);
|
|
8377
|
+
resolve5();
|
|
8378
|
+
}, ms);
|
|
8379
|
+
const onAbort = () => {
|
|
8380
|
+
clearTimeout(timer);
|
|
8381
|
+
reject(new CLIError("Connection wait cancelled."));
|
|
8382
|
+
};
|
|
8383
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
8384
|
+
});
|
|
8385
|
+
}
|
|
8386
|
+
|
|
8387
|
+
// src/lib/framework-detect.ts
|
|
8388
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
|
|
8389
|
+
import { join as join14 } from "path";
|
|
8390
|
+
function contextFromCwd(cwd) {
|
|
8391
|
+
let pkg2 = null;
|
|
8392
|
+
const pkgPath = join14(cwd, "package.json");
|
|
8393
|
+
if (existsSync10(pkgPath)) {
|
|
8394
|
+
try {
|
|
8395
|
+
pkg2 = JSON.parse(readFileSync8(pkgPath, "utf-8"));
|
|
8396
|
+
} catch {
|
|
8397
|
+
pkg2 = null;
|
|
8398
|
+
}
|
|
8399
|
+
}
|
|
8400
|
+
return {
|
|
8401
|
+
hasDir: (rel) => existsSync10(join14(cwd, rel)),
|
|
8402
|
+
pkg: pkg2
|
|
8403
|
+
};
|
|
8404
|
+
}
|
|
8405
|
+
function hasDep(pkg2, name) {
|
|
8406
|
+
if (!pkg2) return false;
|
|
8407
|
+
return Boolean(pkg2.dependencies?.[name] ?? pkg2.devDependencies?.[name]);
|
|
8408
|
+
}
|
|
8409
|
+
function detectFramework(ctx) {
|
|
8410
|
+
if (hasDep(ctx.pkg, "next")) {
|
|
8411
|
+
const hasApp = ctx.hasDir("app") || ctx.hasDir("src/app");
|
|
8412
|
+
const hasPages = ctx.hasDir("pages") || ctx.hasDir("src/pages");
|
|
8413
|
+
if (hasApp && !hasPages) return "next-app";
|
|
8414
|
+
if (hasPages && !hasApp) return "next-pages";
|
|
8415
|
+
if (hasApp && hasPages) return "next-app";
|
|
8416
|
+
return "next-app";
|
|
8417
|
+
}
|
|
8418
|
+
if (hasDep(ctx.pkg, "vite") && hasDep(ctx.pkg, "react")) {
|
|
8419
|
+
return "vite-react";
|
|
8420
|
+
}
|
|
8421
|
+
if (hasDep(ctx.pkg, "@sveltejs/kit")) {
|
|
8422
|
+
return "sveltekit";
|
|
8423
|
+
}
|
|
8424
|
+
if (hasDep(ctx.pkg, "astro")) {
|
|
8425
|
+
return "astro";
|
|
8426
|
+
}
|
|
8427
|
+
return null;
|
|
8428
|
+
}
|
|
8429
|
+
|
|
8430
|
+
// src/lib/package-manager.ts
|
|
8431
|
+
import { existsSync as existsSync11 } from "fs";
|
|
8432
|
+
import { join as join15 } from "path";
|
|
8433
|
+
import { exec as exec4 } from "child_process";
|
|
8434
|
+
import { promisify as promisify5 } from "util";
|
|
8435
|
+
var execAsync4 = promisify5(exec4);
|
|
8436
|
+
function detectPackageManager(cwd) {
|
|
8437
|
+
if (existsSync11(join15(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
8438
|
+
if (existsSync11(join15(cwd, "yarn.lock"))) return "yarn";
|
|
8439
|
+
if (existsSync11(join15(cwd, "bun.lockb")) || existsSync11(join15(cwd, "bun.lock"))) {
|
|
8440
|
+
return "bun";
|
|
8441
|
+
}
|
|
8442
|
+
return "npm";
|
|
8443
|
+
}
|
|
8444
|
+
function installCommand(pm, pkg2) {
|
|
8445
|
+
switch (pm) {
|
|
8446
|
+
case "pnpm":
|
|
8447
|
+
return `pnpm add ${pkg2}`;
|
|
8448
|
+
case "yarn":
|
|
8449
|
+
return `yarn add ${pkg2}`;
|
|
8450
|
+
case "bun":
|
|
8451
|
+
return `bun add ${pkg2}`;
|
|
8452
|
+
case "npm":
|
|
8453
|
+
default:
|
|
8454
|
+
return `npm install ${pkg2}`;
|
|
8455
|
+
}
|
|
8456
|
+
}
|
|
8457
|
+
function hasPackage(pkg2, name) {
|
|
8458
|
+
if (!pkg2) return false;
|
|
8459
|
+
return Boolean(pkg2.dependencies?.[name] ?? pkg2.devDependencies?.[name]);
|
|
8460
|
+
}
|
|
8461
|
+
async function runInstall(pm, pkgName, cwd) {
|
|
8462
|
+
const cmd = installCommand(pm, pkgName);
|
|
8463
|
+
await execAsync4(cmd, { cwd, maxBuffer: 16 * 1024 * 1024 });
|
|
8464
|
+
}
|
|
8465
|
+
|
|
8466
|
+
// src/lib/env-writer.ts
|
|
8467
|
+
import { existsSync as existsSync12, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
|
|
8468
|
+
var KEY_LINE_RE = (key) => (
|
|
8469
|
+
// Match `KEY=...` at the start of a line (allowing leading whitespace).
|
|
8470
|
+
// Captures the value side; we only need the value portion to compare.
|
|
8471
|
+
new RegExp(`^\\s*${key.replace(/[$.*+?^()[\\]{}|]/g, "\\$&")}\\s*=\\s*(.*)$`, "m")
|
|
8472
|
+
);
|
|
8473
|
+
function stripQuotes(v) {
|
|
8474
|
+
const t = v.trim();
|
|
8475
|
+
if (t.startsWith('"') && t.endsWith('"') && t.length >= 2 || t.startsWith("'") && t.endsWith("'") && t.length >= 2) {
|
|
8476
|
+
return t.slice(1, -1);
|
|
8477
|
+
}
|
|
8478
|
+
const hash = t.indexOf(" #");
|
|
8479
|
+
return hash >= 0 ? t.slice(0, hash).trimEnd() : t;
|
|
8480
|
+
}
|
|
8481
|
+
function upsertEnvFile(path6, entries) {
|
|
8482
|
+
const exists = existsSync12(path6);
|
|
8483
|
+
let content = exists ? readFileSync9(path6, "utf-8") : "";
|
|
8484
|
+
const result = { added: [], skipped: [], mismatched: [] };
|
|
8485
|
+
const additions = [];
|
|
8486
|
+
for (const [key, value] of Object.entries(entries)) {
|
|
8487
|
+
const re = KEY_LINE_RE(key);
|
|
8488
|
+
const match = content.match(re);
|
|
8489
|
+
if (match) {
|
|
8490
|
+
const existingValue = stripQuotes(match[1] ?? "");
|
|
8491
|
+
if (existingValue === value) {
|
|
8492
|
+
result.skipped.push(key);
|
|
8493
|
+
} else {
|
|
8494
|
+
result.mismatched.push({ key, existingValue, newValue: value });
|
|
8495
|
+
}
|
|
8496
|
+
continue;
|
|
8497
|
+
}
|
|
8498
|
+
additions.push(`${key}=${value}`);
|
|
8499
|
+
result.added.push(key);
|
|
8500
|
+
}
|
|
8501
|
+
if (additions.length > 0) {
|
|
8502
|
+
if (content.length > 0 && !content.endsWith("\n")) {
|
|
8503
|
+
content += "\n";
|
|
8504
|
+
}
|
|
8505
|
+
content += additions.join("\n") + "\n";
|
|
8506
|
+
writeFileSync7(path6, content);
|
|
8507
|
+
} else if (!exists) {
|
|
8508
|
+
}
|
|
8509
|
+
return result;
|
|
8510
|
+
}
|
|
8511
|
+
|
|
8512
|
+
// src/templates/posthog/next-app/posthog-provider.tsx.txt
|
|
8513
|
+
var posthog_provider_tsx_default = "'use client';\n\nimport { useEffect } from 'react';\nimport posthog from 'posthog-js';\n\n// PostHog client-side provider for the Next.js App Router.\n// Initialises posthog-js exactly once on the client; SSR is skipped because\n// `useEffect` only runs in the browser.\nexport function PostHogProvider({ children }: { children: React.ReactNode }) {\n useEffect(() => {\n if (typeof window === 'undefined') return;\n if (posthog.__loaded) return;\n\n const key = process.env.NEXT_PUBLIC_POSTHOG_KEY;\n if (!key) {\n // Fail closed in production: missing env var \u2192 no init, no events.\n // Avoids accidentally firing events without a key in CI/preview builds.\n return;\n }\n\n posthog.init(key, {\n api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST || '{{HOST}}',\n capture_pageview: true,\n capture_pageleave: true,\n });\n }, []);\n\n return <>{children}</>;\n}\n";
|
|
8514
|
+
|
|
8515
|
+
// src/templates/posthog/next-app/layout-snippet.tsx.txt
|
|
8516
|
+
var layout_snippet_tsx_default = `// Wrap your <body> children with <PostHogProvider> in app/layout.tsx:
|
|
8517
|
+
//
|
|
8518
|
+
// import { PostHogProvider } from './posthog-provider';
|
|
8519
|
+
//
|
|
8520
|
+
// export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
8521
|
+
// return (
|
|
8522
|
+
// <html lang="en">
|
|
8523
|
+
// <body>
|
|
8524
|
+
// <PostHogProvider>{children}</PostHogProvider>
|
|
8525
|
+
// </body>
|
|
8526
|
+
// </html>
|
|
8527
|
+
// );
|
|
8528
|
+
// }
|
|
8529
|
+
`;
|
|
8530
|
+
|
|
8531
|
+
// src/templates/posthog/next-pages/_app.tsx.txt
|
|
8532
|
+
var app_tsx_default = "import type { AppProps } from 'next/app';\nimport { useEffect } from 'react';\nimport posthog from 'posthog-js';\n\nif (typeof window !== 'undefined') {\n const key = process.env.NEXT_PUBLIC_POSTHOG_KEY;\n if (key && !posthog.__loaded) {\n posthog.init(key, {\n api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST || '{{HOST}}',\n capture_pageview: true,\n capture_pageleave: true,\n });\n }\n}\n\nexport default function App({ Component, pageProps }: AppProps) {\n useEffect(() => {\n // Capture pageviews on client-side route changes.\n const handleRouteChange = () => posthog.capture('$pageview');\n if (typeof window !== 'undefined') {\n window.addEventListener('popstate', handleRouteChange);\n return () => window.removeEventListener('popstate', handleRouteChange);\n }\n }, []);\n\n return <Component {...pageProps} />;\n}\n";
|
|
8533
|
+
|
|
8534
|
+
// src/templates/posthog/vite-react/main-snippet.tsx.txt
|
|
8535
|
+
var main_snippet_tsx_default = "// Add this near the top of src/main.tsx, before ReactDOM.createRoot:\nimport posthog from 'posthog-js';\n\nconst posthogKey = import.meta.env.VITE_PUBLIC_POSTHOG_KEY;\nif (posthogKey) {\n posthog.init(posthogKey, {\n api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST || '{{HOST}}',\n capture_pageview: true,\n capture_pageleave: true,\n });\n}\n";
|
|
8536
|
+
|
|
8537
|
+
// src/templates/posthog/sveltekit/hooks.client.ts.txt
|
|
8538
|
+
var hooks_client_ts_default = "import posthog from 'posthog-js';\nimport { browser } from '$app/environment';\nimport { PUBLIC_POSTHOG_KEY, PUBLIC_POSTHOG_HOST } from '$env/static/public';\n\n// `hooks.client.ts` only runs in the browser, so we don't need an explicit\n// `typeof window` guard. The `browser` import is included so future edits\n// (e.g. moving init to a non-client hook) don't accidentally fire on the server.\nif (browser && PUBLIC_POSTHOG_KEY) {\n posthog.init(PUBLIC_POSTHOG_KEY, {\n api_host: PUBLIC_POSTHOG_HOST || '{{HOST}}',\n capture_pageview: true,\n capture_pageleave: true,\n });\n}\n\nexport const handleError = ({ error }: { error: unknown }) => {\n posthog.capture('$exception', { error: String(error) });\n};\n";
|
|
8539
|
+
|
|
8540
|
+
// src/templates/posthog/astro/posthog-init.ts.txt
|
|
8541
|
+
var posthog_init_ts_default = "import posthog from 'posthog-js';\n\n// PostHog client init for Astro. This module runs only in the browser bundle\n// (Astro inlines `client:load` / `<script>` imports into client JS). We still\n// guard with `typeof window` because the same file may be transitively\n// imported during SSR \u2014 the guard prevents init from accidentally running on\n// the server during static generation.\nif (typeof window !== 'undefined') {\n const key = import.meta.env.PUBLIC_POSTHOG_KEY;\n if (key) {\n posthog.init(key, {\n api_host: import.meta.env.PUBLIC_POSTHOG_HOST || '{{HOST}}',\n capture_pageview: 'history_change',\n capture_pageleave: true,\n });\n }\n}\n";
|
|
8542
|
+
|
|
8543
|
+
// src/templates/posthog/index.ts
|
|
8544
|
+
var templates = {
|
|
8545
|
+
"next-app": {
|
|
8546
|
+
provider: posthog_provider_tsx_default,
|
|
8547
|
+
layoutSnippet: layout_snippet_tsx_default
|
|
8548
|
+
},
|
|
8549
|
+
"next-pages": {
|
|
8550
|
+
app: app_tsx_default
|
|
8551
|
+
},
|
|
8552
|
+
"vite-react": {
|
|
8553
|
+
mainSnippet: main_snippet_tsx_default
|
|
8554
|
+
},
|
|
8555
|
+
sveltekit: {
|
|
8556
|
+
hooks: hooks_client_ts_default
|
|
8557
|
+
},
|
|
8558
|
+
astro: {
|
|
8559
|
+
init: posthog_init_ts_default
|
|
8560
|
+
}
|
|
8561
|
+
};
|
|
8562
|
+
function renderTemplate(raw, vars) {
|
|
8563
|
+
return raw.replace(/\{\{([A-Z0-9_]+)\}\}/g, (match, key) => {
|
|
8564
|
+
return Object.prototype.hasOwnProperty.call(vars, key) ? vars[key] : match;
|
|
8565
|
+
});
|
|
8566
|
+
}
|
|
8567
|
+
|
|
8568
|
+
// src/commands/posthog/setup.ts
|
|
8569
|
+
var POLL_INTERVAL_MS4 = 2e3;
|
|
8570
|
+
var POLL_TIMEOUT_MS4 = 15 * 60 * 1e3;
|
|
8571
|
+
var MAX_TRANSIENT_RETRIES = 5;
|
|
8572
|
+
function registerPosthogSetupCommand(program2) {
|
|
8573
|
+
program2.command("setup").description("Install the PostHog SDK into the current directory app").option("--framework <name>", "Force framework (next-app|next-pages|vite-react|sveltekit|astro)").option("--skip-install", "Do not run the package manager install step").option("--skip-browser", "Do not auto-open the browser; only print the URL").action(async (opts, cmd) => {
|
|
8574
|
+
const { json, apiUrl } = getRootOpts(cmd);
|
|
8575
|
+
try {
|
|
8576
|
+
const result = await runSetup({
|
|
8577
|
+
json,
|
|
8578
|
+
apiUrl,
|
|
8579
|
+
forceFramework: opts.framework,
|
|
8580
|
+
skipInstall: Boolean(opts.skipInstall),
|
|
8581
|
+
skipBrowser: Boolean(opts.skipBrowser)
|
|
8582
|
+
});
|
|
8583
|
+
if (json) {
|
|
8584
|
+
outputJson({ success: true, ...result });
|
|
8585
|
+
}
|
|
8586
|
+
} catch (err) {
|
|
8587
|
+
handleError(err, json);
|
|
8588
|
+
}
|
|
8589
|
+
});
|
|
8590
|
+
}
|
|
8591
|
+
async function runSetup(opts) {
|
|
8592
|
+
const proj = getProjectConfig();
|
|
8593
|
+
if (!proj || !proj.project_id) {
|
|
8594
|
+
throw new ProjectNotLinkedError();
|
|
8595
|
+
}
|
|
8596
|
+
const token = getAccessToken();
|
|
8597
|
+
if (!token) {
|
|
8598
|
+
throw new AuthError("Not logged in. Run `insforge login` first.");
|
|
8599
|
+
}
|
|
8600
|
+
if (!opts.json) {
|
|
8601
|
+
clack16.intro("PostHog setup");
|
|
8602
|
+
outputSuccess(`Linked to InsForge project: ${proj.project_name} (${proj.project_id})`);
|
|
8603
|
+
}
|
|
8604
|
+
const startResult = await startPosthogCliFlow(proj.project_id, token, opts.apiUrl);
|
|
8605
|
+
let conn;
|
|
8606
|
+
if (startResult.type === "connected") {
|
|
8607
|
+
if (!opts.json) {
|
|
8608
|
+
outputSuccess("PostHog already connected (or auto-provisioned for new user). Continuing...");
|
|
8609
|
+
}
|
|
8610
|
+
const fetchResult = await fetchPosthogConnection(proj.project_id, token, opts.apiUrl);
|
|
8611
|
+
if (fetchResult.kind !== "connected") {
|
|
8612
|
+
throw new CLIError(
|
|
8613
|
+
"cli-start reported connected, but /connection returned not-connected. Try again, or check the dashboard."
|
|
8614
|
+
);
|
|
8615
|
+
}
|
|
8616
|
+
conn = fetchResult.connection;
|
|
8617
|
+
} else {
|
|
8618
|
+
conn = await runConnectFlow(proj.project_id, token, startResult.authorizeUrl, opts);
|
|
8619
|
+
}
|
|
8620
|
+
if (!conn.apiKey) {
|
|
8621
|
+
throw new CLIError(
|
|
8622
|
+
"Connection succeeded but cloud-backend returned no apiKey. Try again or check the dashboard."
|
|
8623
|
+
);
|
|
8624
|
+
}
|
|
8625
|
+
const framework = resolveFramework(opts);
|
|
8626
|
+
if (framework === null) {
|
|
8627
|
+
return reportNoFramework(conn, opts);
|
|
8628
|
+
}
|
|
8629
|
+
if (!opts.json) outputSuccess(`Detected framework: ${frameworkLabel(framework)}`);
|
|
8630
|
+
const cwd = process.cwd();
|
|
8631
|
+
const ctx = contextFromCwd(cwd);
|
|
8632
|
+
const pm = detectPackageManager(cwd);
|
|
8633
|
+
const alreadyInstalled = hasPackage(ctx.pkg, "posthog-js");
|
|
8634
|
+
let installedSdk = false;
|
|
8635
|
+
if (alreadyInstalled) {
|
|
8636
|
+
if (!opts.json) outputInfo(pc3.dim("posthog-js is already installed \u2014 skipping install."));
|
|
8637
|
+
} else if (opts.skipInstall) {
|
|
8638
|
+
if (!opts.json) {
|
|
8639
|
+
outputInfo(pc3.yellow(`Skipping install. Run manually: ${installCommand(pm, "posthog-js")}`));
|
|
8640
|
+
}
|
|
8641
|
+
} else {
|
|
8642
|
+
installedSdk = await installSdk(pm, cwd, opts);
|
|
8643
|
+
}
|
|
8644
|
+
const filesWritten = [];
|
|
8645
|
+
const notes = [];
|
|
8646
|
+
const envResult = writeForFramework(framework, conn, cwd, filesWritten, notes, opts);
|
|
8647
|
+
if (!opts.json) {
|
|
8648
|
+
if (notes.length > 0) {
|
|
8649
|
+
for (const n of notes) clack16.log.info(n);
|
|
8650
|
+
}
|
|
8651
|
+
clack16.outro("Done. Run your dev server to start sending events.");
|
|
8652
|
+
}
|
|
8653
|
+
return {
|
|
8654
|
+
framework,
|
|
8655
|
+
installedSdk,
|
|
8656
|
+
filesWritten,
|
|
8657
|
+
envWritten: envResult,
|
|
8658
|
+
notes
|
|
8659
|
+
};
|
|
8660
|
+
}
|
|
8661
|
+
async function runConnectFlow(projectId, token, authorizeUrl, opts) {
|
|
8662
|
+
if (opts.json) {
|
|
8663
|
+
process.stderr.write(`Authorize PostHog: ${authorizeUrl}
|
|
8664
|
+
`);
|
|
8665
|
+
process.stderr.write("Your browser should open automatically. If not, copy the URL above.\n");
|
|
8666
|
+
} else {
|
|
8667
|
+
clack16.log.info("PostHog is not connected to this project yet.");
|
|
8668
|
+
outputInfo("");
|
|
8669
|
+
outputInfo(`Open this URL to authorize PostHog:
|
|
8670
|
+
${pc3.cyan(pc3.underline(authorizeUrl))}`);
|
|
8671
|
+
outputInfo("");
|
|
8672
|
+
}
|
|
8673
|
+
if (!opts.skipBrowser) {
|
|
8674
|
+
try {
|
|
8675
|
+
const open = (await import("open")).default;
|
|
8676
|
+
await open(authorizeUrl);
|
|
8677
|
+
} catch {
|
|
8678
|
+
}
|
|
8679
|
+
}
|
|
8680
|
+
const spinner10 = !opts.json && isInteractive ? clack16.spinner() : null;
|
|
8681
|
+
spinner10?.start("Waiting for connection... (timeout: 15 minutes)");
|
|
8682
|
+
try {
|
|
8683
|
+
const conn = await pollPosthogConnection(
|
|
8684
|
+
projectId,
|
|
8685
|
+
token,
|
|
8686
|
+
{
|
|
8687
|
+
intervalMs: POLL_INTERVAL_MS4,
|
|
8688
|
+
timeoutMs: POLL_TIMEOUT_MS4,
|
|
8689
|
+
maxTransientRetries: MAX_TRANSIENT_RETRIES,
|
|
8690
|
+
onTick: (elapsed) => {
|
|
8691
|
+
if (spinner10) {
|
|
8692
|
+
const secs = Math.floor(elapsed / 1e3);
|
|
8693
|
+
const mins = Math.floor(secs / 60);
|
|
8694
|
+
const remaining = `${mins}m ${secs % 60}s elapsed`;
|
|
8695
|
+
spinner10.message(`Waiting for connection... (${remaining})`);
|
|
8696
|
+
}
|
|
8697
|
+
}
|
|
8698
|
+
},
|
|
8699
|
+
opts.apiUrl
|
|
8700
|
+
);
|
|
8701
|
+
spinner10?.stop("Connection received from PostHog.");
|
|
8702
|
+
return conn;
|
|
8703
|
+
} catch (err) {
|
|
8704
|
+
spinner10?.stop("Connection wait failed.");
|
|
8705
|
+
throw err;
|
|
8706
|
+
}
|
|
8707
|
+
}
|
|
8708
|
+
function resolveFramework(opts) {
|
|
8709
|
+
if (opts.forceFramework) {
|
|
8710
|
+
const valid = ["next-app", "next-pages", "vite-react", "sveltekit", "astro"];
|
|
8711
|
+
if (!valid.includes(opts.forceFramework)) {
|
|
8712
|
+
throw new CLIError(
|
|
8713
|
+
`Invalid --framework "${opts.forceFramework}". Valid: ${valid.join(", ")}`
|
|
8714
|
+
);
|
|
8715
|
+
}
|
|
8716
|
+
return opts.forceFramework;
|
|
8717
|
+
}
|
|
8718
|
+
return detectFramework(contextFromCwd(process.cwd()));
|
|
8719
|
+
}
|
|
8720
|
+
async function installSdk(pm, cwd, opts) {
|
|
8721
|
+
const cmd = installCommand(pm, "posthog-js");
|
|
8722
|
+
const spinner10 = !opts.json && isInteractive ? clack16.spinner() : null;
|
|
8723
|
+
spinner10?.start(`Installing posthog-js (${cmd})...`);
|
|
8724
|
+
try {
|
|
8725
|
+
await runInstall(pm, "posthog-js", cwd);
|
|
8726
|
+
spinner10?.stop("Installed posthog-js.");
|
|
8727
|
+
return true;
|
|
8728
|
+
} catch (err) {
|
|
8729
|
+
spinner10?.stop("Install failed.");
|
|
8730
|
+
if (!opts.json) {
|
|
8731
|
+
clack16.log.warn(
|
|
8732
|
+
`Could not run \`${cmd}\` automatically: ${err.message}
|
|
8733
|
+
Run it manually, then re-run \`insforge posthog setup\`.`
|
|
8734
|
+
);
|
|
8735
|
+
}
|
|
8736
|
+
return false;
|
|
8737
|
+
}
|
|
8738
|
+
}
|
|
8739
|
+
function writeForFramework(framework, conn, cwd, filesWritten, notes, opts) {
|
|
8740
|
+
const host = conn.host || "https://us.posthog.com";
|
|
8741
|
+
const phc = conn.apiKey ?? "";
|
|
8742
|
+
switch (framework) {
|
|
8743
|
+
case "next-app":
|
|
8744
|
+
return writeNextApp(cwd, phc, host, filesWritten, notes, opts);
|
|
8745
|
+
case "next-pages":
|
|
8746
|
+
return writeNextPages(cwd, phc, host, filesWritten, notes, opts);
|
|
8747
|
+
case "vite-react":
|
|
8748
|
+
return writeViteReact(cwd, phc, host, filesWritten, notes, opts);
|
|
8749
|
+
case "sveltekit":
|
|
8750
|
+
return writeSveltekit(cwd, phc, host, filesWritten, notes, opts);
|
|
8751
|
+
case "astro":
|
|
8752
|
+
return writeAstro(cwd, phc, host, filesWritten, notes, opts);
|
|
8753
|
+
}
|
|
8754
|
+
}
|
|
8755
|
+
function writeNextApp(cwd, phc, host, filesWritten, notes, opts) {
|
|
8756
|
+
const appDir = existsSync13(join16(cwd, "src/app")) ? "src/app" : "app";
|
|
8757
|
+
const providerPath = join16(cwd, appDir, "posthog-provider.tsx");
|
|
8758
|
+
writeIfMissing(
|
|
8759
|
+
providerPath,
|
|
8760
|
+
renderTemplate(templates["next-app"].provider, { HOST: host }),
|
|
8761
|
+
filesWritten,
|
|
8762
|
+
notes,
|
|
8763
|
+
opts
|
|
8764
|
+
);
|
|
8765
|
+
notes.push(
|
|
8766
|
+
`Add the provider to your ${appDir}/layout.tsx:
|
|
8767
|
+
${templates["next-app"].layoutSnippet}`
|
|
8768
|
+
);
|
|
8769
|
+
const envFile = ".env.local";
|
|
8770
|
+
return writeEnv(
|
|
8771
|
+
cwd,
|
|
8772
|
+
envFile,
|
|
8773
|
+
{
|
|
8774
|
+
NEXT_PUBLIC_POSTHOG_KEY: phc,
|
|
8775
|
+
NEXT_PUBLIC_POSTHOG_HOST: host
|
|
8776
|
+
},
|
|
8777
|
+
opts
|
|
8778
|
+
);
|
|
8779
|
+
}
|
|
8780
|
+
function writeNextPages(cwd, phc, host, filesWritten, notes, opts) {
|
|
8781
|
+
const pagesDir = existsSync13(join16(cwd, "src/pages")) ? "src/pages" : "pages";
|
|
8782
|
+
const appPath = join16(cwd, pagesDir, "_app.tsx");
|
|
8783
|
+
writeIfMissing(
|
|
8784
|
+
appPath,
|
|
8785
|
+
renderTemplate(templates["next-pages"].app, { HOST: host }),
|
|
8786
|
+
filesWritten,
|
|
8787
|
+
notes,
|
|
8788
|
+
opts,
|
|
8789
|
+
"pages/_app.tsx already exists. Open it and add `posthog.init(...)` near the top \u2014 see PostHog Next.js docs."
|
|
8790
|
+
);
|
|
8791
|
+
const envFile = ".env.local";
|
|
8792
|
+
return writeEnv(
|
|
8793
|
+
cwd,
|
|
8794
|
+
envFile,
|
|
8795
|
+
{
|
|
8796
|
+
NEXT_PUBLIC_POSTHOG_KEY: phc,
|
|
8797
|
+
NEXT_PUBLIC_POSTHOG_HOST: host
|
|
8798
|
+
},
|
|
8799
|
+
opts
|
|
8800
|
+
);
|
|
8801
|
+
}
|
|
8802
|
+
function writeViteReact(cwd, phc, host, _filesWritten, notes, opts) {
|
|
8803
|
+
notes.push(
|
|
8804
|
+
`Add this snippet near the top of src/main.tsx:
|
|
8805
|
+
${renderTemplate(templates["vite-react"].mainSnippet, { HOST: host })}`
|
|
8806
|
+
);
|
|
8807
|
+
const envFile = ".env";
|
|
8808
|
+
return writeEnv(
|
|
8809
|
+
cwd,
|
|
8810
|
+
envFile,
|
|
8811
|
+
{
|
|
8812
|
+
VITE_PUBLIC_POSTHOG_KEY: phc,
|
|
8813
|
+
VITE_PUBLIC_POSTHOG_HOST: host
|
|
8814
|
+
},
|
|
8815
|
+
opts
|
|
8816
|
+
);
|
|
8817
|
+
}
|
|
8818
|
+
function writeSveltekit(cwd, phc, host, filesWritten, notes, opts) {
|
|
8819
|
+
const hooksPath = join16(cwd, "src/hooks.client.ts");
|
|
8820
|
+
writeIfMissing(
|
|
8821
|
+
hooksPath,
|
|
8822
|
+
renderTemplate(templates.sveltekit.hooks, { HOST: host }),
|
|
8823
|
+
filesWritten,
|
|
8824
|
+
notes,
|
|
8825
|
+
opts,
|
|
8826
|
+
"src/hooks.client.ts already exists. Add `posthog.init(...)` to it \u2014 see PostHog SvelteKit docs."
|
|
8827
|
+
);
|
|
8828
|
+
const envFile = ".env";
|
|
8829
|
+
return writeEnv(
|
|
8830
|
+
cwd,
|
|
8831
|
+
envFile,
|
|
8832
|
+
{
|
|
8833
|
+
PUBLIC_POSTHOG_KEY: phc,
|
|
8834
|
+
PUBLIC_POSTHOG_HOST: host
|
|
8835
|
+
},
|
|
8836
|
+
opts
|
|
8837
|
+
);
|
|
8838
|
+
}
|
|
8839
|
+
function writeAstro(cwd, phc, host, filesWritten, notes, opts) {
|
|
8840
|
+
const initPath = join16(cwd, "src/lib/posthog.ts");
|
|
8841
|
+
writeIfMissing(
|
|
8842
|
+
initPath,
|
|
8843
|
+
renderTemplate(templates.astro.init, { HOST: host }),
|
|
8844
|
+
filesWritten,
|
|
8845
|
+
notes,
|
|
8846
|
+
opts,
|
|
8847
|
+
"src/lib/posthog.ts already exists. Add `posthog.init(...)` per PostHog Astro docs."
|
|
8848
|
+
);
|
|
8849
|
+
notes.push(
|
|
8850
|
+
`Import the init module from your layout to load it on the client:
|
|
8851
|
+
// src/layouts/Layout.astro (inside <head> or <body>)
|
|
8852
|
+
<script>import '../lib/posthog';</script>`
|
|
8853
|
+
);
|
|
8854
|
+
const envFile = ".env";
|
|
8855
|
+
return writeEnv(
|
|
8856
|
+
cwd,
|
|
8857
|
+
envFile,
|
|
8858
|
+
{
|
|
8859
|
+
PUBLIC_POSTHOG_KEY: phc,
|
|
8860
|
+
PUBLIC_POSTHOG_HOST: host
|
|
8861
|
+
},
|
|
8862
|
+
opts
|
|
8863
|
+
);
|
|
8864
|
+
}
|
|
8865
|
+
function writeIfMissing(filePath, contents, filesWritten, notes, opts, conflictNote) {
|
|
8866
|
+
if (existsSync13(filePath)) {
|
|
8867
|
+
const existing = readFileSync10(filePath, "utf-8");
|
|
8868
|
+
if (existing.includes("posthog.init")) {
|
|
8869
|
+
if (!opts.json) {
|
|
8870
|
+
outputInfo(pc3.dim(`${relative3(filePath)} already calls posthog.init \u2014 leaving it alone.`));
|
|
8871
|
+
}
|
|
8872
|
+
return;
|
|
8873
|
+
}
|
|
8874
|
+
if (conflictNote) notes.push(conflictNote);
|
|
8875
|
+
if (!opts.json) {
|
|
8876
|
+
outputInfo(
|
|
8877
|
+
pc3.yellow(
|
|
8878
|
+
`${relative3(filePath)} exists. Skipped writing \u2014 see notes below for manual changes.`
|
|
8879
|
+
)
|
|
8880
|
+
);
|
|
8881
|
+
}
|
|
8882
|
+
return;
|
|
8883
|
+
}
|
|
8884
|
+
mkdirSync3(dirname2(filePath), { recursive: true });
|
|
8885
|
+
writeFileSync8(filePath, contents);
|
|
8886
|
+
filesWritten.push(filePath);
|
|
8887
|
+
if (!opts.json) outputSuccess(`Wrote ${relative3(filePath)}`);
|
|
8888
|
+
}
|
|
8889
|
+
function writeEnv(cwd, envFile, entries, opts) {
|
|
8890
|
+
const path6 = join16(cwd, envFile);
|
|
8891
|
+
const r = upsertEnvFile(path6, entries);
|
|
8892
|
+
if (!opts.json) {
|
|
8893
|
+
if (r.added.length > 0) {
|
|
8894
|
+
outputSuccess(`Wrote ${envFile}: ${r.added.join(", ")}`);
|
|
8895
|
+
}
|
|
8896
|
+
if (r.skipped.length > 0) {
|
|
8897
|
+
outputInfo(
|
|
8898
|
+
pc3.dim(`${envFile}: ${r.skipped.join(", ")} already set (matching) \u2014 left as-is.`)
|
|
8899
|
+
);
|
|
8900
|
+
}
|
|
8901
|
+
for (const m of r.mismatched) {
|
|
8902
|
+
clack16.log.warn(
|
|
8903
|
+
`${envFile} has ${m.key}=${pc3.dim(m.existingValue)}, expected ${m.newValue}. Left existing value untouched.`
|
|
8904
|
+
);
|
|
8905
|
+
}
|
|
8906
|
+
}
|
|
8907
|
+
return {
|
|
8908
|
+
file: envFile,
|
|
8909
|
+
added: r.added,
|
|
8910
|
+
mismatched: r.mismatched.map((m) => m.key)
|
|
8911
|
+
};
|
|
8912
|
+
}
|
|
8913
|
+
function reportNoFramework(conn, opts) {
|
|
8914
|
+
if (!opts.json) {
|
|
8915
|
+
clack16.log.warn("No supported framework detected in this directory.");
|
|
8916
|
+
outputInfo("");
|
|
8917
|
+
outputInfo(`Your PostHog public key: ${pc3.cyan(conn.apiKey ?? "(missing)")}`);
|
|
8918
|
+
outputInfo(`Your PostHog host: ${conn.host ?? "https://us.posthog.com"}`);
|
|
8919
|
+
outputInfo("");
|
|
8920
|
+
outputInfo("See https://posthog.com/docs/libraries to install the SDK manually.");
|
|
8921
|
+
clack16.outro("Done.");
|
|
8922
|
+
}
|
|
8923
|
+
return {
|
|
8924
|
+
framework: null,
|
|
8925
|
+
installedSdk: false,
|
|
8926
|
+
filesWritten: [],
|
|
8927
|
+
envWritten: { file: "", added: [], mismatched: [] },
|
|
8928
|
+
notes: ["No supported framework detected."]
|
|
8929
|
+
};
|
|
8930
|
+
}
|
|
8931
|
+
function frameworkLabel(framework) {
|
|
8932
|
+
switch (framework) {
|
|
8933
|
+
case "next-app":
|
|
8934
|
+
return "Next.js (App Router)";
|
|
8935
|
+
case "next-pages":
|
|
8936
|
+
return "Next.js (Pages Router)";
|
|
8937
|
+
case "vite-react":
|
|
8938
|
+
return "Vite + React";
|
|
8939
|
+
case "sveltekit":
|
|
8940
|
+
return "SvelteKit";
|
|
8941
|
+
case "astro":
|
|
8942
|
+
return "Astro";
|
|
8943
|
+
}
|
|
8944
|
+
}
|
|
8945
|
+
function relative3(p) {
|
|
8946
|
+
return p.replace(process.cwd() + "/", "");
|
|
8947
|
+
}
|
|
8948
|
+
|
|
8195
8949
|
// src/index.ts
|
|
8196
|
-
var __dirname =
|
|
8197
|
-
var pkg = JSON.parse(
|
|
8950
|
+
var __dirname = dirname3(fileURLToPath(import.meta.url));
|
|
8951
|
+
var pkg = JSON.parse(readFileSync11(join17(__dirname, "../package.json"), "utf-8"));
|
|
8198
8952
|
var INSFORGE_LOGO = `
|
|
8199
8953
|
\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
|
|
8200
8954
|
\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
|
|
@@ -8276,6 +9030,8 @@ registerComputeDeleteCommand(computeCmd);
|
|
|
8276
9030
|
registerComputeStartCommand(computeCmd);
|
|
8277
9031
|
registerComputeStopCommand(computeCmd);
|
|
8278
9032
|
registerComputeEventsCommand(computeCmd);
|
|
9033
|
+
var posthogCmd = program.command("posthog").description("Manage PostHog product analytics integration");
|
|
9034
|
+
registerPosthogSetupCommand(posthogCmd);
|
|
8279
9035
|
var schedulesCmd = program.command("schedules").description("Manage scheduled tasks (cron jobs)");
|
|
8280
9036
|
registerSchedulesListCommand(schedulesCmd);
|
|
8281
9037
|
registerSchedulesGetCommand(schedulesCmd);
|
|
@@ -8300,7 +9056,7 @@ async function showInteractiveMenu() {
|
|
|
8300
9056
|
} catch {
|
|
8301
9057
|
}
|
|
8302
9058
|
console.log(INSFORGE_LOGO);
|
|
8303
|
-
|
|
9059
|
+
clack17.intro(`InsForge CLI v${pkg.version}`);
|
|
8304
9060
|
const options = [];
|
|
8305
9061
|
if (!isLoggedIn) {
|
|
8306
9062
|
options.push({ value: "login", label: "Log in to InsForge" });
|
|
@@ -8321,7 +9077,7 @@ async function showInteractiveMenu() {
|
|
|
8321
9077
|
options
|
|
8322
9078
|
});
|
|
8323
9079
|
if (isCancel2(action)) {
|
|
8324
|
-
|
|
9080
|
+
clack17.cancel("Bye!");
|
|
8325
9081
|
process.exit(0);
|
|
8326
9082
|
}
|
|
8327
9083
|
switch (action) {
|