@insforge/cli 0.1.70 → 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 +777 -45
- 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";
|
|
@@ -1329,36 +1329,36 @@ function registerBranchCreateCommand(branch) {
|
|
|
1329
1329
|
throw new CLIError(`Invalid --mode: ${opts.mode} (must be "full" or "schema-only")`);
|
|
1330
1330
|
}
|
|
1331
1331
|
const mode = opts.mode;
|
|
1332
|
-
const
|
|
1332
|
+
const spinner10 = !json ? clack5.spinner() : null;
|
|
1333
1333
|
let ready;
|
|
1334
1334
|
let provisioned = false;
|
|
1335
1335
|
try {
|
|
1336
|
-
|
|
1336
|
+
spinner10?.start(`Creating branch '${name}'...`);
|
|
1337
1337
|
const created = await createBranchApi(project.project_id, { mode, name }, apiUrl);
|
|
1338
1338
|
captureEvent(project.project_id, "cli_branch_create", {
|
|
1339
1339
|
mode,
|
|
1340
1340
|
parent_project_id: project.project_id
|
|
1341
1341
|
});
|
|
1342
|
-
|
|
1343
|
-
ready = await pollUntilReady(created.id, apiUrl,
|
|
1342
|
+
spinner10?.message(`Branch '${name}' created (appkey: ${created.appkey}). Provisioning...`);
|
|
1343
|
+
ready = await pollUntilReady(created.id, apiUrl, spinner10);
|
|
1344
1344
|
provisioned = ready.branch_state === "ready";
|
|
1345
1345
|
if (provisioned && opts.switch) {
|
|
1346
|
-
|
|
1346
|
+
spinner10?.message("Branch ready. Switching context...");
|
|
1347
1347
|
await runBranchSwitch({ name, apiUrl, json, silent: true });
|
|
1348
|
-
|
|
1348
|
+
spinner10?.stop(`Branch '${name}' is ready and active`);
|
|
1349
1349
|
} else if (provisioned) {
|
|
1350
|
-
|
|
1350
|
+
spinner10?.stop(`Branch '${name}' is ready`);
|
|
1351
1351
|
} else {
|
|
1352
|
-
|
|
1352
|
+
spinner10?.stop(`Branch '${name}' is in '${ready.branch_state}' state`);
|
|
1353
1353
|
}
|
|
1354
1354
|
} catch (err) {
|
|
1355
1355
|
if (provisioned) {
|
|
1356
|
-
|
|
1356
|
+
spinner10?.stop(
|
|
1357
1357
|
`Branch '${name}' is ready, but switching context failed \u2014 run \`insforge branch switch ${name}\` to retry`,
|
|
1358
1358
|
1
|
|
1359
1359
|
);
|
|
1360
1360
|
} else {
|
|
1361
|
-
|
|
1361
|
+
spinner10?.stop(`Branch '${name}' creation failed`, 1);
|
|
1362
1362
|
}
|
|
1363
1363
|
throw err;
|
|
1364
1364
|
}
|
|
@@ -1382,7 +1382,7 @@ function registerBranchCreateCommand(branch) {
|
|
|
1382
1382
|
}
|
|
1383
1383
|
});
|
|
1384
1384
|
}
|
|
1385
|
-
async function pollUntilReady(branchId, apiUrl,
|
|
1385
|
+
async function pollUntilReady(branchId, apiUrl, spinner10) {
|
|
1386
1386
|
const start = Date.now();
|
|
1387
1387
|
let lastState = "";
|
|
1388
1388
|
while (Date.now() - start < POLL_TIMEOUT_MS) {
|
|
@@ -1391,8 +1391,8 @@ async function pollUntilReady(branchId, apiUrl, spinner9) {
|
|
|
1391
1391
|
if (branch2.branch_state === "deleted" || branch2.branch_state === "conflicted") {
|
|
1392
1392
|
throw new CLIError(`Branch creation failed (state: ${branch2.branch_state})`);
|
|
1393
1393
|
}
|
|
1394
|
-
if (
|
|
1395
|
-
|
|
1394
|
+
if (spinner10 && branch2.branch_state !== lastState) {
|
|
1395
|
+
spinner10.message(`Provisioning branch (state: ${branch2.branch_state})...`);
|
|
1396
1396
|
lastState = branch2.branch_state;
|
|
1397
1397
|
}
|
|
1398
1398
|
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
@@ -2376,8 +2376,8 @@ async function startDirectDeployment(deploymentId, startBody) {
|
|
|
2376
2376
|
});
|
|
2377
2377
|
await response.json();
|
|
2378
2378
|
}
|
|
2379
|
-
async function pollDeployment(deploymentId,
|
|
2380
|
-
|
|
2379
|
+
async function pollDeployment(deploymentId, spinner10, syncBeforeRead) {
|
|
2380
|
+
spinner10?.message("Building and deploying...");
|
|
2381
2381
|
const startTime = Date.now();
|
|
2382
2382
|
let deployment = null;
|
|
2383
2383
|
while (Date.now() - startTime < POLL_TIMEOUT_MS3) {
|
|
@@ -2393,13 +2393,13 @@ async function pollDeployment(deploymentId, spinner9, syncBeforeRead) {
|
|
|
2393
2393
|
break;
|
|
2394
2394
|
}
|
|
2395
2395
|
if (status === "ERROR" || status === "CANCELED") {
|
|
2396
|
-
|
|
2396
|
+
spinner10?.stop("Deployment failed");
|
|
2397
2397
|
throw new CLIError(
|
|
2398
2398
|
getDeploymentError(deployment.metadata) ?? `Deployment failed with status: ${deployment.status}`
|
|
2399
2399
|
);
|
|
2400
2400
|
}
|
|
2401
2401
|
const elapsed = Math.round((Date.now() - startTime) / 1e3);
|
|
2402
|
-
|
|
2402
|
+
spinner10?.message(`Building and deploying... (${elapsed}s, status: ${deployment.status})`);
|
|
2403
2403
|
} catch (err) {
|
|
2404
2404
|
if (err instanceof CLIError) throw err;
|
|
2405
2405
|
}
|
|
@@ -2409,20 +2409,20 @@ async function pollDeployment(deploymentId, spinner9, syncBeforeRead) {
|
|
|
2409
2409
|
return { deploymentId, deployment, isReady, liveUrl };
|
|
2410
2410
|
}
|
|
2411
2411
|
async function deployProjectDirect(opts, config) {
|
|
2412
|
-
const { sourceDir, startBody = {}, spinner:
|
|
2413
|
-
|
|
2412
|
+
const { sourceDir, startBody = {}, spinner: spinner10 } = opts;
|
|
2413
|
+
spinner10?.start("Scanning source files...");
|
|
2414
2414
|
const localFiles = await collectDeploymentFiles(sourceDir);
|
|
2415
2415
|
if (localFiles.length === 0) {
|
|
2416
2416
|
throw new CLIError("No deployable files found in the source directory.");
|
|
2417
2417
|
}
|
|
2418
|
-
|
|
2418
|
+
spinner10?.message("Creating deployment...");
|
|
2419
2419
|
const createResult = await createDirectDeploymentSession(
|
|
2420
2420
|
config,
|
|
2421
2421
|
localFiles.map(({ path: relativePath, sha, size }) => ({ path: relativePath, sha, size }))
|
|
2422
2422
|
);
|
|
2423
2423
|
const localFileByPath = new Map(localFiles.map((file) => [file.path, file]));
|
|
2424
2424
|
const pendingFiles = createResult.files.filter((file) => !file.uploadedAt);
|
|
2425
|
-
|
|
2425
|
+
spinner10?.message(`Uploading ${pendingFiles.length} file${pendingFiles.length === 1 ? "" : "s"}...`);
|
|
2426
2426
|
await runWithConcurrency(pendingFiles, DIRECT_UPLOAD_CONCURRENCY, async (manifestFile) => {
|
|
2427
2427
|
const localFile = localFileByPath.get(manifestFile.path);
|
|
2428
2428
|
if (!localFile) {
|
|
@@ -2433,18 +2433,18 @@ async function deployProjectDirect(opts, config) {
|
|
|
2433
2433
|
}
|
|
2434
2434
|
await uploadDirectDeploymentFile(createResult.id, manifestFile, localFile);
|
|
2435
2435
|
});
|
|
2436
|
-
|
|
2436
|
+
spinner10?.message("Starting deployment...");
|
|
2437
2437
|
await startDirectDeployment(createResult.id, startBody);
|
|
2438
|
-
return await pollDeployment(createResult.id,
|
|
2438
|
+
return await pollDeployment(createResult.id, spinner10, !isInsforgeCloudOssHost(config.oss_host));
|
|
2439
2439
|
}
|
|
2440
2440
|
async function deployProjectLegacy(opts) {
|
|
2441
|
-
const { sourceDir, startBody = {}, spinner:
|
|
2442
|
-
|
|
2441
|
+
const { sourceDir, startBody = {}, spinner: spinner10 } = opts;
|
|
2442
|
+
spinner10?.message("Creating deployment...");
|
|
2443
2443
|
const createRes = await ossFetch("/api/deployments", { method: "POST" });
|
|
2444
2444
|
const { id: deploymentId, uploadUrl, uploadFields } = await createRes.json();
|
|
2445
|
-
|
|
2445
|
+
spinner10?.message("Compressing source files...");
|
|
2446
2446
|
const zipBuffer = await createZipBuffer(sourceDir);
|
|
2447
|
-
|
|
2447
|
+
spinner10?.message("Uploading...");
|
|
2448
2448
|
const formData = new FormData();
|
|
2449
2449
|
for (const [key, value] of Object.entries(uploadFields)) {
|
|
2450
2450
|
formData.append(key, value);
|
|
@@ -2455,13 +2455,13 @@ async function deployProjectLegacy(opts) {
|
|
|
2455
2455
|
const uploadErr = await uploadRes.text();
|
|
2456
2456
|
throw new CLIError(`Failed to upload: ${uploadErr}`);
|
|
2457
2457
|
}
|
|
2458
|
-
|
|
2458
|
+
spinner10?.message("Starting deployment...");
|
|
2459
2459
|
const startRes = await ossFetch(`/api/deployments/${deploymentId}/start`, {
|
|
2460
2460
|
method: "POST",
|
|
2461
2461
|
body: JSON.stringify(startBody)
|
|
2462
2462
|
});
|
|
2463
2463
|
await startRes.json();
|
|
2464
|
-
return await pollDeployment(deploymentId,
|
|
2464
|
+
return await pollDeployment(deploymentId, spinner10, false);
|
|
2465
2465
|
}
|
|
2466
2466
|
async function deployProject(opts) {
|
|
2467
2467
|
const config = getProjectConfig();
|
|
@@ -2496,7 +2496,7 @@ function registerDeploymentsDeployCommand(deploymentsCmd2) {
|
|
|
2496
2496
|
`"${dirName}" is an excluded directory and cannot be used as a deploy source. Please specify your project root or output directory instead.`
|
|
2497
2497
|
);
|
|
2498
2498
|
}
|
|
2499
|
-
const
|
|
2499
|
+
const spinner10 = !json ? clack11.spinner() : null;
|
|
2500
2500
|
const startBody = {};
|
|
2501
2501
|
if (opts.env) {
|
|
2502
2502
|
try {
|
|
@@ -2522,9 +2522,9 @@ function registerDeploymentsDeployCommand(deploymentsCmd2) {
|
|
|
2522
2522
|
throw new CLIError("Invalid --meta JSON.");
|
|
2523
2523
|
}
|
|
2524
2524
|
}
|
|
2525
|
-
const result = await deployProject({ sourceDir, startBody, spinner:
|
|
2525
|
+
const result = await deployProject({ sourceDir, startBody, spinner: spinner10 });
|
|
2526
2526
|
if (result.isReady) {
|
|
2527
|
-
|
|
2527
|
+
spinner10?.stop("Deployment complete");
|
|
2528
2528
|
if (json) {
|
|
2529
2529
|
outputJson(result.deployment);
|
|
2530
2530
|
} else {
|
|
@@ -2534,7 +2534,7 @@ function registerDeploymentsDeployCommand(deploymentsCmd2) {
|
|
|
2534
2534
|
clack11.log.info(`Deployment ID: ${result.deploymentId}`);
|
|
2535
2535
|
}
|
|
2536
2536
|
} else {
|
|
2537
|
-
|
|
2537
|
+
spinner10?.stop("Deployment is still building");
|
|
2538
2538
|
if (json) {
|
|
2539
2539
|
outputJson({
|
|
2540
2540
|
id: result.deploymentId,
|
|
@@ -3100,13 +3100,13 @@ async function downloadGitHubTemplate(templateName, projectConfig, json) {
|
|
|
3100
3100
|
// src/commands/projects/link.ts
|
|
3101
3101
|
var execAsync3 = promisify4(exec3);
|
|
3102
3102
|
async function runNpmInstall(startMessage = "Installing dependencies...") {
|
|
3103
|
-
const
|
|
3104
|
-
|
|
3103
|
+
const spinner10 = clack13.spinner();
|
|
3104
|
+
spinner10.start(startMessage);
|
|
3105
3105
|
try {
|
|
3106
3106
|
await execAsync3("npm install", { cwd: process.cwd(), maxBuffer: 10 * 1024 * 1024 });
|
|
3107
|
-
|
|
3107
|
+
spinner10.stop("Dependencies installed");
|
|
3108
3108
|
} catch (err) {
|
|
3109
|
-
|
|
3109
|
+
spinner10.stop("Failed to install dependencies");
|
|
3110
3110
|
clack13.log.warn(`npm install failed: ${err.message}`);
|
|
3111
3111
|
clack13.log.info("Run `npm install` manually to install dependencies.");
|
|
3112
3112
|
}
|
|
@@ -6853,7 +6853,7 @@ function registerDiagnoseCommands(diagnoseCmd2) {
|
|
|
6853
6853
|
const s = !json ? clack15.spinner() : null;
|
|
6854
6854
|
s?.start("Collecting diagnostic data...");
|
|
6855
6855
|
const data2 = await collectDiagnosticData(projectId, ossMode, apiUrl);
|
|
6856
|
-
const cliVersion = "0.1.
|
|
6856
|
+
const cliVersion = "0.1.71";
|
|
6857
6857
|
s?.stop("Data collected");
|
|
6858
6858
|
if (!json) {
|
|
6859
6859
|
console.log(`
|
|
@@ -8216,9 +8216,739 @@ function registerPaymentsCommands(paymentsCmd2) {
|
|
|
8216
8216
|
registerPaymentsHistoryCommand(paymentsCmd2);
|
|
8217
8217
|
}
|
|
8218
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
|
+
|
|
8219
8949
|
// src/index.ts
|
|
8220
|
-
var __dirname =
|
|
8221
|
-
var pkg = JSON.parse(
|
|
8950
|
+
var __dirname = dirname3(fileURLToPath(import.meta.url));
|
|
8951
|
+
var pkg = JSON.parse(readFileSync11(join17(__dirname, "../package.json"), "utf-8"));
|
|
8222
8952
|
var INSFORGE_LOGO = `
|
|
8223
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
|
|
8224
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
|
|
@@ -8300,6 +9030,8 @@ registerComputeDeleteCommand(computeCmd);
|
|
|
8300
9030
|
registerComputeStartCommand(computeCmd);
|
|
8301
9031
|
registerComputeStopCommand(computeCmd);
|
|
8302
9032
|
registerComputeEventsCommand(computeCmd);
|
|
9033
|
+
var posthogCmd = program.command("posthog").description("Manage PostHog product analytics integration");
|
|
9034
|
+
registerPosthogSetupCommand(posthogCmd);
|
|
8303
9035
|
var schedulesCmd = program.command("schedules").description("Manage scheduled tasks (cron jobs)");
|
|
8304
9036
|
registerSchedulesListCommand(schedulesCmd);
|
|
8305
9037
|
registerSchedulesGetCommand(schedulesCmd);
|
|
@@ -8324,7 +9056,7 @@ async function showInteractiveMenu() {
|
|
|
8324
9056
|
} catch {
|
|
8325
9057
|
}
|
|
8326
9058
|
console.log(INSFORGE_LOGO);
|
|
8327
|
-
|
|
9059
|
+
clack17.intro(`InsForge CLI v${pkg.version}`);
|
|
8328
9060
|
const options = [];
|
|
8329
9061
|
if (!isLoggedIn) {
|
|
8330
9062
|
options.push({ value: "login", label: "Log in to InsForge" });
|
|
@@ -8345,7 +9077,7 @@ async function showInteractiveMenu() {
|
|
|
8345
9077
|
options
|
|
8346
9078
|
});
|
|
8347
9079
|
if (isCancel2(action)) {
|
|
8348
|
-
|
|
9080
|
+
clack17.cancel("Bye!");
|
|
8349
9081
|
process.exit(0);
|
|
8350
9082
|
}
|
|
8351
9083
|
switch (action) {
|