@insforge/cli 0.1.79 → 0.1.81
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/README.md +10 -8
- package/dist/index.js +206 -502
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { readFileSync as
|
|
5
|
-
import { join as
|
|
4
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
5
|
+
import { join as join15, dirname as dirname2 } from "path";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
7
|
import { Command } from "commander";
|
|
8
8
|
import * as clack18 from "@clack/prompts";
|
|
@@ -1215,9 +1215,24 @@ function trackPayments(subcommand, config, properties) {
|
|
|
1215
1215
|
...properties
|
|
1216
1216
|
});
|
|
1217
1217
|
}
|
|
1218
|
+
function trackConfig(subcommand, config, properties) {
|
|
1219
|
+
const distinctId = config?.project_id ?? FAKE_PROJECT_ID;
|
|
1220
|
+
captureEvent(distinctId, "cli_config_invoked", {
|
|
1221
|
+
subcommand,
|
|
1222
|
+
project_id: config?.project_id,
|
|
1223
|
+
project_name: config?.project_name,
|
|
1224
|
+
org_id: config?.org_id,
|
|
1225
|
+
region: config?.region,
|
|
1226
|
+
oss_mode: !config || config.project_id === FAKE_PROJECT_ID,
|
|
1227
|
+
...properties
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1218
1230
|
async function shutdownAnalytics() {
|
|
1231
|
+
if (!client) return;
|
|
1232
|
+
const c = client;
|
|
1233
|
+
client = null;
|
|
1219
1234
|
try {
|
|
1220
|
-
|
|
1235
|
+
await c.shutdown();
|
|
1221
1236
|
} catch {
|
|
1222
1237
|
}
|
|
1223
1238
|
}
|
|
@@ -6961,7 +6976,7 @@ function registerDiagnoseCommands(diagnoseCmd2) {
|
|
|
6961
6976
|
const s = !json ? clack15.spinner() : null;
|
|
6962
6977
|
s?.start("Collecting diagnostic data...");
|
|
6963
6978
|
const data2 = await collectDiagnosticData(projectId, ossMode, apiUrl);
|
|
6964
|
-
const cliVersion = "0.1.
|
|
6979
|
+
const cliVersion = "0.1.81";
|
|
6965
6980
|
s?.stop("Data collected");
|
|
6966
6981
|
if (!json) {
|
|
6967
6982
|
console.log(`
|
|
@@ -8325,8 +8340,6 @@ function registerPaymentsCommands(paymentsCmd2) {
|
|
|
8325
8340
|
}
|
|
8326
8341
|
|
|
8327
8342
|
// src/commands/posthog/setup.ts
|
|
8328
|
-
import { existsSync as existsSync13, readFileSync as readFileSync10, writeFileSync as writeFileSync8, mkdirSync as mkdirSync3 } from "fs";
|
|
8329
|
-
import { join as join16, dirname as dirname2 } from "path";
|
|
8330
8343
|
import * as clack16 from "@clack/prompts";
|
|
8331
8344
|
import pc3 from "picocolors";
|
|
8332
8345
|
|
|
@@ -8492,200 +8505,19 @@ function sleep(ms, signal) {
|
|
|
8492
8505
|
});
|
|
8493
8506
|
}
|
|
8494
8507
|
|
|
8495
|
-
// src/lib/framework-detect.ts
|
|
8496
|
-
import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
|
|
8497
|
-
import { join as join14 } from "path";
|
|
8498
|
-
function contextFromCwd(cwd) {
|
|
8499
|
-
let pkg2 = null;
|
|
8500
|
-
const pkgPath = join14(cwd, "package.json");
|
|
8501
|
-
if (existsSync10(pkgPath)) {
|
|
8502
|
-
try {
|
|
8503
|
-
pkg2 = JSON.parse(readFileSync8(pkgPath, "utf-8"));
|
|
8504
|
-
} catch {
|
|
8505
|
-
pkg2 = null;
|
|
8506
|
-
}
|
|
8507
|
-
}
|
|
8508
|
-
return {
|
|
8509
|
-
hasDir: (rel) => existsSync10(join14(cwd, rel)),
|
|
8510
|
-
pkg: pkg2
|
|
8511
|
-
};
|
|
8512
|
-
}
|
|
8513
|
-
function hasDep(pkg2, name) {
|
|
8514
|
-
if (!pkg2) return false;
|
|
8515
|
-
return Boolean(pkg2.dependencies?.[name] ?? pkg2.devDependencies?.[name]);
|
|
8516
|
-
}
|
|
8517
|
-
function detectFramework(ctx) {
|
|
8518
|
-
if (hasDep(ctx.pkg, "next")) {
|
|
8519
|
-
const hasApp = ctx.hasDir("app") || ctx.hasDir("src/app");
|
|
8520
|
-
const hasPages = ctx.hasDir("pages") || ctx.hasDir("src/pages");
|
|
8521
|
-
if (hasApp && !hasPages) return "next-app";
|
|
8522
|
-
if (hasPages && !hasApp) return "next-pages";
|
|
8523
|
-
if (hasApp && hasPages) return "next-app";
|
|
8524
|
-
return "next-app";
|
|
8525
|
-
}
|
|
8526
|
-
if (hasDep(ctx.pkg, "vite") && hasDep(ctx.pkg, "react")) {
|
|
8527
|
-
return "vite-react";
|
|
8528
|
-
}
|
|
8529
|
-
if (hasDep(ctx.pkg, "@sveltejs/kit")) {
|
|
8530
|
-
return "sveltekit";
|
|
8531
|
-
}
|
|
8532
|
-
if (hasDep(ctx.pkg, "astro")) {
|
|
8533
|
-
return "astro";
|
|
8534
|
-
}
|
|
8535
|
-
return null;
|
|
8536
|
-
}
|
|
8537
|
-
|
|
8538
|
-
// src/lib/package-manager.ts
|
|
8539
|
-
import { existsSync as existsSync11 } from "fs";
|
|
8540
|
-
import { join as join15 } from "path";
|
|
8541
|
-
import { exec as exec4 } from "child_process";
|
|
8542
|
-
import { promisify as promisify5 } from "util";
|
|
8543
|
-
var execAsync4 = promisify5(exec4);
|
|
8544
|
-
function detectPackageManager(cwd) {
|
|
8545
|
-
if (existsSync11(join15(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
8546
|
-
if (existsSync11(join15(cwd, "yarn.lock"))) return "yarn";
|
|
8547
|
-
if (existsSync11(join15(cwd, "bun.lockb")) || existsSync11(join15(cwd, "bun.lock"))) {
|
|
8548
|
-
return "bun";
|
|
8549
|
-
}
|
|
8550
|
-
return "npm";
|
|
8551
|
-
}
|
|
8552
|
-
function installCommand(pm, pkg2) {
|
|
8553
|
-
switch (pm) {
|
|
8554
|
-
case "pnpm":
|
|
8555
|
-
return `pnpm add ${pkg2}`;
|
|
8556
|
-
case "yarn":
|
|
8557
|
-
return `yarn add ${pkg2}`;
|
|
8558
|
-
case "bun":
|
|
8559
|
-
return `bun add ${pkg2}`;
|
|
8560
|
-
case "npm":
|
|
8561
|
-
default:
|
|
8562
|
-
return `npm install ${pkg2}`;
|
|
8563
|
-
}
|
|
8564
|
-
}
|
|
8565
|
-
function hasPackage(pkg2, name) {
|
|
8566
|
-
if (!pkg2) return false;
|
|
8567
|
-
return Boolean(pkg2.dependencies?.[name] ?? pkg2.devDependencies?.[name]);
|
|
8568
|
-
}
|
|
8569
|
-
async function runInstall(pm, pkgName, cwd) {
|
|
8570
|
-
const cmd = installCommand(pm, pkgName);
|
|
8571
|
-
await execAsync4(cmd, { cwd, maxBuffer: 16 * 1024 * 1024 });
|
|
8572
|
-
}
|
|
8573
|
-
|
|
8574
|
-
// src/lib/env-writer.ts
|
|
8575
|
-
import { existsSync as existsSync12, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
|
|
8576
|
-
var KEY_LINE_RE = (key) => (
|
|
8577
|
-
// Match `KEY=...` at the start of a line (allowing leading whitespace).
|
|
8578
|
-
// Captures the value side; we only need the value portion to compare.
|
|
8579
|
-
new RegExp(`^\\s*${key.replace(/[$.*+?^()[\\]{}|]/g, "\\$&")}\\s*=\\s*(.*)$`, "m")
|
|
8580
|
-
);
|
|
8581
|
-
function stripQuotes(v) {
|
|
8582
|
-
const t = v.trim();
|
|
8583
|
-
if (t.startsWith('"') && t.endsWith('"') && t.length >= 2 || t.startsWith("'") && t.endsWith("'") && t.length >= 2) {
|
|
8584
|
-
return t.slice(1, -1);
|
|
8585
|
-
}
|
|
8586
|
-
const hash = t.indexOf(" #");
|
|
8587
|
-
return hash >= 0 ? t.slice(0, hash).trimEnd() : t;
|
|
8588
|
-
}
|
|
8589
|
-
function upsertEnvFile(path6, entries) {
|
|
8590
|
-
const exists = existsSync12(path6);
|
|
8591
|
-
let content = exists ? readFileSync9(path6, "utf-8") : "";
|
|
8592
|
-
const result = { added: [], skipped: [], mismatched: [] };
|
|
8593
|
-
const additions = [];
|
|
8594
|
-
for (const [key, value] of Object.entries(entries)) {
|
|
8595
|
-
const re = KEY_LINE_RE(key);
|
|
8596
|
-
const match = content.match(re);
|
|
8597
|
-
if (match) {
|
|
8598
|
-
const existingValue = stripQuotes(match[1] ?? "");
|
|
8599
|
-
if (existingValue === value) {
|
|
8600
|
-
result.skipped.push(key);
|
|
8601
|
-
} else {
|
|
8602
|
-
result.mismatched.push({ key, existingValue, newValue: value });
|
|
8603
|
-
}
|
|
8604
|
-
continue;
|
|
8605
|
-
}
|
|
8606
|
-
additions.push(`${key}=${value}`);
|
|
8607
|
-
result.added.push(key);
|
|
8608
|
-
}
|
|
8609
|
-
if (additions.length > 0) {
|
|
8610
|
-
if (content.length > 0 && !content.endsWith("\n")) {
|
|
8611
|
-
content += "\n";
|
|
8612
|
-
}
|
|
8613
|
-
content += additions.join("\n") + "\n";
|
|
8614
|
-
writeFileSync7(path6, content);
|
|
8615
|
-
} else if (!exists) {
|
|
8616
|
-
}
|
|
8617
|
-
return result;
|
|
8618
|
-
}
|
|
8619
|
-
|
|
8620
|
-
// src/templates/posthog/next-app/posthog-provider.tsx.txt
|
|
8621
|
-
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";
|
|
8622
|
-
|
|
8623
|
-
// src/templates/posthog/next-app/layout-snippet.tsx.txt
|
|
8624
|
-
var layout_snippet_tsx_default = `// Wrap your <body> children with <PostHogProvider> in app/layout.tsx:
|
|
8625
|
-
//
|
|
8626
|
-
// import { PostHogProvider } from './posthog-provider';
|
|
8627
|
-
//
|
|
8628
|
-
// export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
8629
|
-
// return (
|
|
8630
|
-
// <html lang="en">
|
|
8631
|
-
// <body>
|
|
8632
|
-
// <PostHogProvider>{children}</PostHogProvider>
|
|
8633
|
-
// </body>
|
|
8634
|
-
// </html>
|
|
8635
|
-
// );
|
|
8636
|
-
// }
|
|
8637
|
-
`;
|
|
8638
|
-
|
|
8639
|
-
// src/templates/posthog/next-pages/_app.tsx.txt
|
|
8640
|
-
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";
|
|
8641
|
-
|
|
8642
|
-
// src/templates/posthog/vite-react/main-snippet.tsx.txt
|
|
8643
|
-
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";
|
|
8644
|
-
|
|
8645
|
-
// src/templates/posthog/sveltekit/hooks.client.ts.txt
|
|
8646
|
-
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";
|
|
8647
|
-
|
|
8648
|
-
// src/templates/posthog/astro/posthog-init.ts.txt
|
|
8649
|
-
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";
|
|
8650
|
-
|
|
8651
|
-
// src/templates/posthog/index.ts
|
|
8652
|
-
var templates = {
|
|
8653
|
-
"next-app": {
|
|
8654
|
-
provider: posthog_provider_tsx_default,
|
|
8655
|
-
layoutSnippet: layout_snippet_tsx_default
|
|
8656
|
-
},
|
|
8657
|
-
"next-pages": {
|
|
8658
|
-
app: app_tsx_default
|
|
8659
|
-
},
|
|
8660
|
-
"vite-react": {
|
|
8661
|
-
mainSnippet: main_snippet_tsx_default
|
|
8662
|
-
},
|
|
8663
|
-
sveltekit: {
|
|
8664
|
-
hooks: hooks_client_ts_default
|
|
8665
|
-
},
|
|
8666
|
-
astro: {
|
|
8667
|
-
init: posthog_init_ts_default
|
|
8668
|
-
}
|
|
8669
|
-
};
|
|
8670
|
-
function renderTemplate(raw, vars) {
|
|
8671
|
-
return raw.replace(/\{\{([A-Z0-9_]+)\}\}/g, (match, key) => {
|
|
8672
|
-
return Object.prototype.hasOwnProperty.call(vars, key) ? vars[key] : match;
|
|
8673
|
-
});
|
|
8674
|
-
}
|
|
8675
|
-
|
|
8676
8508
|
// src/commands/posthog/setup.ts
|
|
8677
8509
|
var POLL_INTERVAL_MS4 = 2e3;
|
|
8678
8510
|
var POLL_TIMEOUT_MS4 = 15 * 60 * 1e3;
|
|
8679
8511
|
var MAX_TRANSIENT_RETRIES = 5;
|
|
8512
|
+
var NPX_COMMAND = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
8513
|
+
var WIZARD_COMMAND = `${NPX_COMMAND} -y @posthog/wizard@latest`;
|
|
8680
8514
|
function registerPosthogSetupCommand(program2) {
|
|
8681
|
-
program2.command("setup").description("
|
|
8515
|
+
program2.command("setup").description("Connect PostHog to your InsForge dashboard, then run the official PostHog wizard to wire it into your app").option("--skip-browser", "Do not auto-open the browser for OAuth; only print the URL").action(async (opts, cmd) => {
|
|
8682
8516
|
const { json, apiUrl } = getRootOpts(cmd);
|
|
8683
8517
|
try {
|
|
8684
8518
|
const result = await runSetup({
|
|
8685
8519
|
json,
|
|
8686
8520
|
apiUrl,
|
|
8687
|
-
forceFramework: opts.framework,
|
|
8688
|
-
skipInstall: Boolean(opts.skipInstall),
|
|
8689
8521
|
skipBrowser: Boolean(opts.skipBrowser)
|
|
8690
8522
|
});
|
|
8691
8523
|
if (json) {
|
|
@@ -8709,62 +8541,39 @@ async function runSetup(opts) {
|
|
|
8709
8541
|
clack16.intro("PostHog setup");
|
|
8710
8542
|
outputSuccess(`Linked to InsForge project: ${proj.project_name} (${proj.project_id})`);
|
|
8711
8543
|
}
|
|
8712
|
-
const
|
|
8713
|
-
|
|
8544
|
+
const dashboardConnection = await ensureDashboardConnection(proj.project_id, token, opts);
|
|
8545
|
+
if (!opts.json) {
|
|
8546
|
+
clack16.note(
|
|
8547
|
+
`Run this in your terminal to wire PostHog into your app code:
|
|
8548
|
+
|
|
8549
|
+
${WIZARD_COMMAND}
|
|
8550
|
+
|
|
8551
|
+
Once it completes, open the Analytics page in your InsForge dashboard.`,
|
|
8552
|
+
"Next step"
|
|
8553
|
+
);
|
|
8554
|
+
}
|
|
8555
|
+
return {
|
|
8556
|
+
dashboardConnection,
|
|
8557
|
+
wizardSkipped: true,
|
|
8558
|
+
wizardCommand: WIZARD_COMMAND
|
|
8559
|
+
};
|
|
8560
|
+
}
|
|
8561
|
+
async function ensureDashboardConnection(projectId, token, opts) {
|
|
8562
|
+
const startResult = await startPosthogCliFlow(projectId, token, opts.apiUrl);
|
|
8714
8563
|
if (startResult.type === "connected") {
|
|
8715
8564
|
if (!opts.json) {
|
|
8716
|
-
outputSuccess("PostHog already connected
|
|
8565
|
+
outputSuccess("PostHog is already connected to your InsForge dashboard.");
|
|
8717
8566
|
}
|
|
8718
|
-
const fetchResult = await fetchPosthogConnection(
|
|
8567
|
+
const fetchResult = await fetchPosthogConnection(projectId, token, opts.apiUrl);
|
|
8719
8568
|
if (fetchResult.kind !== "connected") {
|
|
8720
8569
|
throw new CLIError(
|
|
8721
8570
|
"cli-start reported connected, but /connection returned not-connected. Try again, or check the dashboard."
|
|
8722
8571
|
);
|
|
8723
8572
|
}
|
|
8724
|
-
|
|
8725
|
-
} else {
|
|
8726
|
-
conn = await runConnectFlow(proj.project_id, token, startResult.authorizeUrl, opts);
|
|
8727
|
-
}
|
|
8728
|
-
if (!conn.apiKey) {
|
|
8729
|
-
throw new CLIError(
|
|
8730
|
-
"Connection succeeded but cloud-backend returned no apiKey. Try again or check the dashboard."
|
|
8731
|
-
);
|
|
8573
|
+
return "already-connected";
|
|
8732
8574
|
}
|
|
8733
|
-
|
|
8734
|
-
|
|
8735
|
-
return reportNoFramework(conn, opts);
|
|
8736
|
-
}
|
|
8737
|
-
if (!opts.json) outputSuccess(`Detected framework: ${frameworkLabel(framework)}`);
|
|
8738
|
-
const cwd = process.cwd();
|
|
8739
|
-
const ctx = contextFromCwd(cwd);
|
|
8740
|
-
const pm = detectPackageManager(cwd);
|
|
8741
|
-
const alreadyInstalled = hasPackage(ctx.pkg, "posthog-js");
|
|
8742
|
-
let installedSdk = false;
|
|
8743
|
-
if (alreadyInstalled) {
|
|
8744
|
-
if (!opts.json) outputInfo(pc3.dim("posthog-js is already installed \u2014 skipping install."));
|
|
8745
|
-
} else if (opts.skipInstall) {
|
|
8746
|
-
if (!opts.json) {
|
|
8747
|
-
outputInfo(pc3.yellow(`Skipping install. Run manually: ${installCommand(pm, "posthog-js")}`));
|
|
8748
|
-
}
|
|
8749
|
-
} else {
|
|
8750
|
-
installedSdk = await installSdk(pm, cwd, opts);
|
|
8751
|
-
}
|
|
8752
|
-
const filesWritten = [];
|
|
8753
|
-
const notes = [];
|
|
8754
|
-
const envResult = writeForFramework(framework, conn, cwd, filesWritten, notes, opts);
|
|
8755
|
-
if (!opts.json) {
|
|
8756
|
-
if (notes.length > 0) {
|
|
8757
|
-
for (const n of notes) clack16.log.info(n);
|
|
8758
|
-
}
|
|
8759
|
-
clack16.outro("Done. Run your dev server to start sending events.");
|
|
8760
|
-
}
|
|
8761
|
-
return {
|
|
8762
|
-
framework,
|
|
8763
|
-
installedSdk,
|
|
8764
|
-
filesWritten,
|
|
8765
|
-
envWritten: envResult,
|
|
8766
|
-
notes
|
|
8767
|
-
};
|
|
8575
|
+
await runConnectFlow(projectId, token, startResult.authorizeUrl, opts);
|
|
8576
|
+
return "newly-connected";
|
|
8768
8577
|
}
|
|
8769
8578
|
async function runConnectFlow(projectId, token, authorizeUrl, opts) {
|
|
8770
8579
|
if (opts.json) {
|
|
@@ -8772,11 +8581,15 @@ async function runConnectFlow(projectId, token, authorizeUrl, opts) {
|
|
|
8772
8581
|
`);
|
|
8773
8582
|
process.stderr.write("Your browser should open automatically. If not, copy the URL above.\n");
|
|
8774
8583
|
} else {
|
|
8775
|
-
clack16.log.info("PostHog is not connected to
|
|
8776
|
-
|
|
8777
|
-
|
|
8778
|
-
|
|
8779
|
-
|
|
8584
|
+
clack16.log.info("PostHog is not yet connected to your InsForge dashboard.");
|
|
8585
|
+
if (opts.skipBrowser) {
|
|
8586
|
+
clack16.log.info(`Open this URL to authorize PostHog:
|
|
8587
|
+
${pc3.cyan(pc3.underline(authorizeUrl))}`);
|
|
8588
|
+
} else {
|
|
8589
|
+
clack16.log.info("Opening browser to authorize PostHog...");
|
|
8590
|
+
clack16.log.info(`If browser doesn't open, visit:
|
|
8591
|
+
${pc3.cyan(pc3.underline(authorizeUrl))}`);
|
|
8592
|
+
}
|
|
8780
8593
|
}
|
|
8781
8594
|
if (!opts.skipBrowser) {
|
|
8782
8595
|
try {
|
|
@@ -8786,9 +8599,13 @@ async function runConnectFlow(projectId, token, authorizeUrl, opts) {
|
|
|
8786
8599
|
}
|
|
8787
8600
|
}
|
|
8788
8601
|
const spinner11 = !opts.json && isInteractive ? clack16.spinner() : null;
|
|
8789
|
-
|
|
8602
|
+
if (spinner11) {
|
|
8603
|
+
spinner11.start("Waiting for InsForge dashboard connection... (timeout: 15 minutes)");
|
|
8604
|
+
} else if (!opts.json) {
|
|
8605
|
+
clack16.log.info("Waiting for InsForge dashboard connection (up to 15 minutes)...");
|
|
8606
|
+
}
|
|
8790
8607
|
try {
|
|
8791
|
-
|
|
8608
|
+
await pollPosthogConnection(
|
|
8792
8609
|
projectId,
|
|
8793
8610
|
token,
|
|
8794
8611
|
{
|
|
@@ -8800,262 +8617,29 @@ async function runConnectFlow(projectId, token, authorizeUrl, opts) {
|
|
|
8800
8617
|
const secs = Math.floor(elapsed / 1e3);
|
|
8801
8618
|
const mins = Math.floor(secs / 60);
|
|
8802
8619
|
const remaining = `${mins}m ${secs % 60}s elapsed`;
|
|
8803
|
-
spinner11.message(`Waiting for connection... (${remaining})`);
|
|
8620
|
+
spinner11.message(`Waiting for InsForge dashboard connection... (${remaining})`);
|
|
8804
8621
|
}
|
|
8805
8622
|
}
|
|
8806
8623
|
},
|
|
8807
8624
|
opts.apiUrl
|
|
8808
8625
|
);
|
|
8809
|
-
spinner11
|
|
8810
|
-
|
|
8811
|
-
|
|
8812
|
-
|
|
8813
|
-
throw err;
|
|
8814
|
-
}
|
|
8815
|
-
}
|
|
8816
|
-
function resolveFramework(opts) {
|
|
8817
|
-
if (opts.forceFramework) {
|
|
8818
|
-
const valid = ["next-app", "next-pages", "vite-react", "sveltekit", "astro"];
|
|
8819
|
-
if (!valid.includes(opts.forceFramework)) {
|
|
8820
|
-
throw new CLIError(
|
|
8821
|
-
`Invalid --framework "${opts.forceFramework}". Valid: ${valid.join(", ")}`
|
|
8822
|
-
);
|
|
8626
|
+
if (spinner11) {
|
|
8627
|
+
spinner11.stop("InsForge dashboard connection received.");
|
|
8628
|
+
} else if (!opts.json) {
|
|
8629
|
+
clack16.log.success("InsForge dashboard connection received.");
|
|
8823
8630
|
}
|
|
8824
|
-
return opts.forceFramework;
|
|
8825
|
-
}
|
|
8826
|
-
return detectFramework(contextFromCwd(process.cwd()));
|
|
8827
|
-
}
|
|
8828
|
-
async function installSdk(pm, cwd, opts) {
|
|
8829
|
-
const cmd = installCommand(pm, "posthog-js");
|
|
8830
|
-
const spinner11 = !opts.json && isInteractive ? clack16.spinner() : null;
|
|
8831
|
-
spinner11?.start(`Installing posthog-js (${cmd})...`);
|
|
8832
|
-
try {
|
|
8833
|
-
await runInstall(pm, "posthog-js", cwd);
|
|
8834
|
-
spinner11?.stop("Installed posthog-js.");
|
|
8835
|
-
return true;
|
|
8836
8631
|
} catch (err) {
|
|
8837
|
-
spinner11
|
|
8838
|
-
|
|
8839
|
-
|
|
8840
|
-
|
|
8841
|
-
Run it manually, then re-run \`insforge posthog setup\`.`
|
|
8842
|
-
);
|
|
8632
|
+
if (spinner11) {
|
|
8633
|
+
spinner11.stop("InsForge dashboard connection wait failed.");
|
|
8634
|
+
} else if (!opts.json) {
|
|
8635
|
+
clack16.log.error("InsForge dashboard connection wait failed.");
|
|
8843
8636
|
}
|
|
8844
|
-
|
|
8845
|
-
}
|
|
8846
|
-
}
|
|
8847
|
-
function writeForFramework(framework, conn, cwd, filesWritten, notes, opts) {
|
|
8848
|
-
const host = conn.host || "https://us.posthog.com";
|
|
8849
|
-
const phc = conn.apiKey ?? "";
|
|
8850
|
-
switch (framework) {
|
|
8851
|
-
case "next-app":
|
|
8852
|
-
return writeNextApp(cwd, phc, host, filesWritten, notes, opts);
|
|
8853
|
-
case "next-pages":
|
|
8854
|
-
return writeNextPages(cwd, phc, host, filesWritten, notes, opts);
|
|
8855
|
-
case "vite-react":
|
|
8856
|
-
return writeViteReact(cwd, phc, host, filesWritten, notes, opts);
|
|
8857
|
-
case "sveltekit":
|
|
8858
|
-
return writeSveltekit(cwd, phc, host, filesWritten, notes, opts);
|
|
8859
|
-
case "astro":
|
|
8860
|
-
return writeAstro(cwd, phc, host, filesWritten, notes, opts);
|
|
8861
|
-
}
|
|
8862
|
-
}
|
|
8863
|
-
function writeNextApp(cwd, phc, host, filesWritten, notes, opts) {
|
|
8864
|
-
const appDir = existsSync13(join16(cwd, "src/app")) ? "src/app" : "app";
|
|
8865
|
-
const providerPath = join16(cwd, appDir, "posthog-provider.tsx");
|
|
8866
|
-
writeIfMissing(
|
|
8867
|
-
providerPath,
|
|
8868
|
-
renderTemplate(templates["next-app"].provider, { HOST: host }),
|
|
8869
|
-
filesWritten,
|
|
8870
|
-
notes,
|
|
8871
|
-
opts
|
|
8872
|
-
);
|
|
8873
|
-
notes.push(
|
|
8874
|
-
`Add the provider to your ${appDir}/layout.tsx:
|
|
8875
|
-
${templates["next-app"].layoutSnippet}`
|
|
8876
|
-
);
|
|
8877
|
-
const envFile = ".env.local";
|
|
8878
|
-
return writeEnv(
|
|
8879
|
-
cwd,
|
|
8880
|
-
envFile,
|
|
8881
|
-
{
|
|
8882
|
-
NEXT_PUBLIC_POSTHOG_KEY: phc,
|
|
8883
|
-
NEXT_PUBLIC_POSTHOG_HOST: host
|
|
8884
|
-
},
|
|
8885
|
-
opts
|
|
8886
|
-
);
|
|
8887
|
-
}
|
|
8888
|
-
function writeNextPages(cwd, phc, host, filesWritten, notes, opts) {
|
|
8889
|
-
const pagesDir = existsSync13(join16(cwd, "src/pages")) ? "src/pages" : "pages";
|
|
8890
|
-
const appPath = join16(cwd, pagesDir, "_app.tsx");
|
|
8891
|
-
writeIfMissing(
|
|
8892
|
-
appPath,
|
|
8893
|
-
renderTemplate(templates["next-pages"].app, { HOST: host }),
|
|
8894
|
-
filesWritten,
|
|
8895
|
-
notes,
|
|
8896
|
-
opts,
|
|
8897
|
-
"pages/_app.tsx already exists. Open it and add `posthog.init(...)` near the top \u2014 see PostHog Next.js docs."
|
|
8898
|
-
);
|
|
8899
|
-
const envFile = ".env.local";
|
|
8900
|
-
return writeEnv(
|
|
8901
|
-
cwd,
|
|
8902
|
-
envFile,
|
|
8903
|
-
{
|
|
8904
|
-
NEXT_PUBLIC_POSTHOG_KEY: phc,
|
|
8905
|
-
NEXT_PUBLIC_POSTHOG_HOST: host
|
|
8906
|
-
},
|
|
8907
|
-
opts
|
|
8908
|
-
);
|
|
8909
|
-
}
|
|
8910
|
-
function writeViteReact(cwd, phc, host, _filesWritten, notes, opts) {
|
|
8911
|
-
notes.push(
|
|
8912
|
-
`Add this snippet near the top of src/main.tsx:
|
|
8913
|
-
${renderTemplate(templates["vite-react"].mainSnippet, { HOST: host })}`
|
|
8914
|
-
);
|
|
8915
|
-
const envFile = ".env";
|
|
8916
|
-
return writeEnv(
|
|
8917
|
-
cwd,
|
|
8918
|
-
envFile,
|
|
8919
|
-
{
|
|
8920
|
-
VITE_PUBLIC_POSTHOG_KEY: phc,
|
|
8921
|
-
VITE_PUBLIC_POSTHOG_HOST: host
|
|
8922
|
-
},
|
|
8923
|
-
opts
|
|
8924
|
-
);
|
|
8925
|
-
}
|
|
8926
|
-
function writeSveltekit(cwd, phc, host, filesWritten, notes, opts) {
|
|
8927
|
-
const hooksPath = join16(cwd, "src/hooks.client.ts");
|
|
8928
|
-
writeIfMissing(
|
|
8929
|
-
hooksPath,
|
|
8930
|
-
renderTemplate(templates.sveltekit.hooks, { HOST: host }),
|
|
8931
|
-
filesWritten,
|
|
8932
|
-
notes,
|
|
8933
|
-
opts,
|
|
8934
|
-
"src/hooks.client.ts already exists. Add `posthog.init(...)` to it \u2014 see PostHog SvelteKit docs."
|
|
8935
|
-
);
|
|
8936
|
-
const envFile = ".env";
|
|
8937
|
-
return writeEnv(
|
|
8938
|
-
cwd,
|
|
8939
|
-
envFile,
|
|
8940
|
-
{
|
|
8941
|
-
PUBLIC_POSTHOG_KEY: phc,
|
|
8942
|
-
PUBLIC_POSTHOG_HOST: host
|
|
8943
|
-
},
|
|
8944
|
-
opts
|
|
8945
|
-
);
|
|
8946
|
-
}
|
|
8947
|
-
function writeAstro(cwd, phc, host, filesWritten, notes, opts) {
|
|
8948
|
-
const initPath = join16(cwd, "src/lib/posthog.ts");
|
|
8949
|
-
writeIfMissing(
|
|
8950
|
-
initPath,
|
|
8951
|
-
renderTemplate(templates.astro.init, { HOST: host }),
|
|
8952
|
-
filesWritten,
|
|
8953
|
-
notes,
|
|
8954
|
-
opts,
|
|
8955
|
-
"src/lib/posthog.ts already exists. Add `posthog.init(...)` per PostHog Astro docs."
|
|
8956
|
-
);
|
|
8957
|
-
notes.push(
|
|
8958
|
-
`Import the init module from your layout to load it on the client:
|
|
8959
|
-
// src/layouts/Layout.astro (inside <head> or <body>)
|
|
8960
|
-
<script>import '../lib/posthog';</script>`
|
|
8961
|
-
);
|
|
8962
|
-
const envFile = ".env";
|
|
8963
|
-
return writeEnv(
|
|
8964
|
-
cwd,
|
|
8965
|
-
envFile,
|
|
8966
|
-
{
|
|
8967
|
-
PUBLIC_POSTHOG_KEY: phc,
|
|
8968
|
-
PUBLIC_POSTHOG_HOST: host
|
|
8969
|
-
},
|
|
8970
|
-
opts
|
|
8971
|
-
);
|
|
8972
|
-
}
|
|
8973
|
-
function writeIfMissing(filePath, contents, filesWritten, notes, opts, conflictNote) {
|
|
8974
|
-
if (existsSync13(filePath)) {
|
|
8975
|
-
const existing = readFileSync10(filePath, "utf-8");
|
|
8976
|
-
if (existing.includes("posthog.init")) {
|
|
8977
|
-
if (!opts.json) {
|
|
8978
|
-
outputInfo(pc3.dim(`${relative3(filePath)} already calls posthog.init \u2014 leaving it alone.`));
|
|
8979
|
-
}
|
|
8980
|
-
return;
|
|
8981
|
-
}
|
|
8982
|
-
if (conflictNote) notes.push(conflictNote);
|
|
8983
|
-
if (!opts.json) {
|
|
8984
|
-
outputInfo(
|
|
8985
|
-
pc3.yellow(
|
|
8986
|
-
`${relative3(filePath)} exists. Skipped writing \u2014 see notes below for manual changes.`
|
|
8987
|
-
)
|
|
8988
|
-
);
|
|
8989
|
-
}
|
|
8990
|
-
return;
|
|
8991
|
-
}
|
|
8992
|
-
mkdirSync3(dirname2(filePath), { recursive: true });
|
|
8993
|
-
writeFileSync8(filePath, contents);
|
|
8994
|
-
filesWritten.push(filePath);
|
|
8995
|
-
if (!opts.json) outputSuccess(`Wrote ${relative3(filePath)}`);
|
|
8996
|
-
}
|
|
8997
|
-
function writeEnv(cwd, envFile, entries, opts) {
|
|
8998
|
-
const path6 = join16(cwd, envFile);
|
|
8999
|
-
const r = upsertEnvFile(path6, entries);
|
|
9000
|
-
if (!opts.json) {
|
|
9001
|
-
if (r.added.length > 0) {
|
|
9002
|
-
outputSuccess(`Wrote ${envFile}: ${r.added.join(", ")}`);
|
|
9003
|
-
}
|
|
9004
|
-
if (r.skipped.length > 0) {
|
|
9005
|
-
outputInfo(
|
|
9006
|
-
pc3.dim(`${envFile}: ${r.skipped.join(", ")} already set (matching) \u2014 left as-is.`)
|
|
9007
|
-
);
|
|
9008
|
-
}
|
|
9009
|
-
for (const m of r.mismatched) {
|
|
9010
|
-
clack16.log.warn(
|
|
9011
|
-
`${envFile} has ${m.key}=${pc3.dim(m.existingValue)}, expected ${m.newValue}. Left existing value untouched.`
|
|
9012
|
-
);
|
|
9013
|
-
}
|
|
9014
|
-
}
|
|
9015
|
-
return {
|
|
9016
|
-
file: envFile,
|
|
9017
|
-
added: r.added,
|
|
9018
|
-
mismatched: r.mismatched.map((m) => m.key)
|
|
9019
|
-
};
|
|
9020
|
-
}
|
|
9021
|
-
function reportNoFramework(conn, opts) {
|
|
9022
|
-
if (!opts.json) {
|
|
9023
|
-
clack16.log.warn("No supported framework detected in this directory.");
|
|
9024
|
-
outputInfo("");
|
|
9025
|
-
outputInfo(`Your PostHog public key: ${pc3.cyan(conn.apiKey ?? "(missing)")}`);
|
|
9026
|
-
outputInfo(`Your PostHog host: ${conn.host ?? "https://us.posthog.com"}`);
|
|
9027
|
-
outputInfo("");
|
|
9028
|
-
outputInfo("See https://posthog.com/docs/libraries to install the SDK manually.");
|
|
9029
|
-
clack16.outro("Done.");
|
|
9030
|
-
}
|
|
9031
|
-
return {
|
|
9032
|
-
framework: null,
|
|
9033
|
-
installedSdk: false,
|
|
9034
|
-
filesWritten: [],
|
|
9035
|
-
envWritten: { file: "", added: [], mismatched: [] },
|
|
9036
|
-
notes: ["No supported framework detected."]
|
|
9037
|
-
};
|
|
9038
|
-
}
|
|
9039
|
-
function frameworkLabel(framework) {
|
|
9040
|
-
switch (framework) {
|
|
9041
|
-
case "next-app":
|
|
9042
|
-
return "Next.js (App Router)";
|
|
9043
|
-
case "next-pages":
|
|
9044
|
-
return "Next.js (Pages Router)";
|
|
9045
|
-
case "vite-react":
|
|
9046
|
-
return "Vite + React";
|
|
9047
|
-
case "sveltekit":
|
|
9048
|
-
return "SvelteKit";
|
|
9049
|
-
case "astro":
|
|
9050
|
-
return "Astro";
|
|
8637
|
+
throw err;
|
|
9051
8638
|
}
|
|
9052
8639
|
}
|
|
9053
|
-
function relative3(p3) {
|
|
9054
|
-
return p3.replace(process.cwd() + "/", "");
|
|
9055
|
-
}
|
|
9056
8640
|
|
|
9057
8641
|
// src/commands/config/export.ts
|
|
9058
|
-
import { writeFileSync as
|
|
8642
|
+
import { writeFileSync as writeFileSync7, existsSync as existsSync10 } from "fs";
|
|
9059
8643
|
import { resolve as resolve5 } from "path";
|
|
9060
8644
|
import * as p from "@clack/prompts";
|
|
9061
8645
|
import pc4 from "picocolors";
|
|
@@ -9536,10 +9120,12 @@ function configFromMetadata(raw) {
|
|
|
9536
9120
|
function registerConfigExportCommand(cfg) {
|
|
9537
9121
|
cfg.command("export").description("Pull live project config and write insforge.toml").option("--out <path>", "output path", "insforge.toml").option("--force", "overwrite without confirmation").action(async (opts, cmd) => {
|
|
9538
9122
|
const { json } = getRootOpts(cmd);
|
|
9123
|
+
let projectConfig = null;
|
|
9539
9124
|
try {
|
|
9125
|
+
projectConfig = getProjectConfig();
|
|
9540
9126
|
await requireAuth();
|
|
9541
9127
|
const target = resolve5(process.cwd(), opts.out);
|
|
9542
|
-
if (
|
|
9128
|
+
if (existsSync10(target) && !opts.force) {
|
|
9543
9129
|
if (json) {
|
|
9544
9130
|
throw new CLIError(
|
|
9545
9131
|
`${opts.out} exists. Re-run with --force to overwrite.`,
|
|
@@ -9553,6 +9139,12 @@ function registerConfigExportCommand(cfg) {
|
|
|
9553
9139
|
});
|
|
9554
9140
|
if (!ok || p.isCancel(ok)) {
|
|
9555
9141
|
console.log("Aborted.");
|
|
9142
|
+
await reportCliUsage("cli.config.export", true);
|
|
9143
|
+
trackConfig("export", projectConfig, {
|
|
9144
|
+
json_mode: !!json,
|
|
9145
|
+
force: !!opts.force,
|
|
9146
|
+
outcome: "aborted"
|
|
9147
|
+
});
|
|
9556
9148
|
return;
|
|
9557
9149
|
}
|
|
9558
9150
|
}
|
|
@@ -9560,7 +9152,7 @@ function registerConfigExportCommand(cfg) {
|
|
|
9560
9152
|
const raw = await res.json();
|
|
9561
9153
|
const { config, skipped } = configFromMetadata(raw);
|
|
9562
9154
|
const toml = stringifyConfigToml(config);
|
|
9563
|
-
|
|
9155
|
+
writeFileSync7(target, toml, "utf8");
|
|
9564
9156
|
if (json) {
|
|
9565
9157
|
console.log(JSON.stringify({ written: target, config, skipped }, null, 2));
|
|
9566
9158
|
} else {
|
|
@@ -9574,15 +9166,29 @@ function registerConfigExportCommand(cfg) {
|
|
|
9574
9166
|
}
|
|
9575
9167
|
}
|
|
9576
9168
|
await reportCliUsage("cli.config.export", true);
|
|
9169
|
+
trackConfig("export", projectConfig, {
|
|
9170
|
+
json_mode: !!json,
|
|
9171
|
+
force: !!opts.force,
|
|
9172
|
+
skipped_count: skipped.length,
|
|
9173
|
+
outcome: "success"
|
|
9174
|
+
});
|
|
9577
9175
|
} catch (err) {
|
|
9578
9176
|
await reportCliUsage("cli.config.export", false);
|
|
9177
|
+
trackConfig("export", projectConfig, {
|
|
9178
|
+
json_mode: !!json,
|
|
9179
|
+
force: !!opts.force,
|
|
9180
|
+
outcome: "error"
|
|
9181
|
+
});
|
|
9182
|
+
await shutdownAnalytics();
|
|
9579
9183
|
handleError(err, json);
|
|
9184
|
+
} finally {
|
|
9185
|
+
await shutdownAnalytics();
|
|
9580
9186
|
}
|
|
9581
9187
|
});
|
|
9582
9188
|
}
|
|
9583
9189
|
|
|
9584
9190
|
// src/commands/config/plan.ts
|
|
9585
|
-
import { readFileSync as
|
|
9191
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
9586
9192
|
import { resolve as resolve6 } from "path";
|
|
9587
9193
|
import pc5 from "picocolors";
|
|
9588
9194
|
|
|
@@ -9885,10 +9491,12 @@ function authPasswordWireKey(key) {
|
|
|
9885
9491
|
function registerConfigPlanCommand(cfg) {
|
|
9886
9492
|
cfg.command("plan").description("Show diff between insforge.toml and live project state").option("--file <path>", "path to insforge.toml", "insforge.toml").action(async (opts, cmd) => {
|
|
9887
9493
|
const { json } = getRootOpts(cmd);
|
|
9494
|
+
let projectConfig = null;
|
|
9888
9495
|
try {
|
|
9496
|
+
projectConfig = getProjectConfig();
|
|
9889
9497
|
await requireAuth();
|
|
9890
9498
|
const tomlPath = resolve6(process.cwd(), opts.file);
|
|
9891
|
-
const tomlSource =
|
|
9499
|
+
const tomlSource = readFileSync8(tomlPath, "utf8");
|
|
9892
9500
|
const file = parseConfigToml(tomlSource);
|
|
9893
9501
|
const res = await ossFetch("/api/metadata");
|
|
9894
9502
|
const raw = await res.json();
|
|
@@ -9908,31 +9516,52 @@ function registerConfigPlanCommand(cfg) {
|
|
|
9908
9516
|
}
|
|
9909
9517
|
}
|
|
9910
9518
|
await reportCliUsage("cli.config.plan", true);
|
|
9519
|
+
trackConfig("plan", projectConfig, {
|
|
9520
|
+
json_mode: !!json,
|
|
9521
|
+
changes_count: result.changes.length,
|
|
9522
|
+
skipped_count: skipped.length,
|
|
9523
|
+
sections_changed: Array.from(
|
|
9524
|
+
new Set(result.changes.map((c) => changePath(c)))
|
|
9525
|
+
),
|
|
9526
|
+
outcome: "success"
|
|
9527
|
+
});
|
|
9911
9528
|
} catch (err) {
|
|
9912
9529
|
await reportCliUsage("cli.config.plan", false);
|
|
9530
|
+
trackConfig("plan", projectConfig, {
|
|
9531
|
+
json_mode: !!json,
|
|
9532
|
+
outcome: "error"
|
|
9533
|
+
});
|
|
9534
|
+
await shutdownAnalytics();
|
|
9913
9535
|
handleError(err, json);
|
|
9536
|
+
} finally {
|
|
9537
|
+
await shutdownAnalytics();
|
|
9914
9538
|
}
|
|
9915
9539
|
});
|
|
9916
9540
|
}
|
|
9917
9541
|
|
|
9918
9542
|
// src/commands/config/apply.ts
|
|
9919
|
-
import { readFileSync as
|
|
9543
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
9920
9544
|
import { resolve as resolve7 } from "path";
|
|
9921
9545
|
import * as p2 from "@clack/prompts";
|
|
9922
9546
|
import pc6 from "picocolors";
|
|
9923
9547
|
function registerConfigApplyCommand(cfg) {
|
|
9924
9548
|
cfg.command("apply").description("Apply insforge.toml to the live project").option("--file <path>", "path to insforge.toml", "insforge.toml").option("--dry-run", "show plan, do not apply").option("--auto-approve", "skip confirmation prompt").action(async (opts, cmd) => {
|
|
9925
9549
|
const { json, yes } = getRootOpts(cmd);
|
|
9550
|
+
let projectConfig = null;
|
|
9926
9551
|
try {
|
|
9552
|
+
projectConfig = getProjectConfig();
|
|
9927
9553
|
await requireAuth();
|
|
9928
9554
|
const tomlPath = resolve7(process.cwd(), opts.file);
|
|
9929
|
-
const tomlSource =
|
|
9555
|
+
const tomlSource = readFileSync9(tomlPath, "utf8");
|
|
9930
9556
|
const file = parseConfigToml(tomlSource);
|
|
9931
9557
|
const res = await ossFetch("/api/metadata");
|
|
9932
9558
|
const raw = await res.json();
|
|
9933
9559
|
const live = liveFromMetadata(raw);
|
|
9934
9560
|
const result = diffConfig({ live, file });
|
|
9935
9561
|
const approved = opts.autoApprove || yes;
|
|
9562
|
+
const sectionsChanged = Array.from(
|
|
9563
|
+
new Set(result.changes.map((c) => changePath(c)))
|
|
9564
|
+
);
|
|
9936
9565
|
if (!json) {
|
|
9937
9566
|
console.log(formatPlan(result));
|
|
9938
9567
|
}
|
|
@@ -9943,6 +9572,13 @@ function registerConfigApplyCommand(cfg) {
|
|
|
9943
9572
|
);
|
|
9944
9573
|
}
|
|
9945
9574
|
await reportCliUsage("cli.config.apply", true);
|
|
9575
|
+
trackConfig("apply", projectConfig, {
|
|
9576
|
+
dry_run: !!opts.dryRun,
|
|
9577
|
+
json_mode: !!json,
|
|
9578
|
+
changes_count: result.changes.length,
|
|
9579
|
+
sections_changed: sectionsChanged,
|
|
9580
|
+
outcome: result.changes.length === 0 ? "no_changes" : "dry_run"
|
|
9581
|
+
});
|
|
9946
9582
|
return;
|
|
9947
9583
|
}
|
|
9948
9584
|
if (!approved) {
|
|
@@ -9960,6 +9596,12 @@ function registerConfigApplyCommand(cfg) {
|
|
|
9960
9596
|
if (!ok || p2.isCancel(ok)) {
|
|
9961
9597
|
console.log("Aborted.");
|
|
9962
9598
|
await reportCliUsage("cli.config.apply", true);
|
|
9599
|
+
trackConfig("apply", projectConfig, {
|
|
9600
|
+
json_mode: !!json,
|
|
9601
|
+
changes_count: result.changes.length,
|
|
9602
|
+
sections_changed: sectionsChanged,
|
|
9603
|
+
outcome: "aborted"
|
|
9604
|
+
});
|
|
9963
9605
|
return;
|
|
9964
9606
|
}
|
|
9965
9607
|
}
|
|
@@ -9996,9 +9638,25 @@ function registerConfigApplyCommand(cfg) {
|
|
|
9996
9638
|
}
|
|
9997
9639
|
}
|
|
9998
9640
|
await reportCliUsage("cli.config.apply", true);
|
|
9641
|
+
trackConfig("apply", projectConfig, {
|
|
9642
|
+
auto_approved: !!approved,
|
|
9643
|
+
json_mode: !!json,
|
|
9644
|
+
changes_count: result.changes.length,
|
|
9645
|
+
applied_count: applied.length,
|
|
9646
|
+
skipped_count: skipped.length,
|
|
9647
|
+
sections_changed: sectionsChanged,
|
|
9648
|
+
outcome: applied.length > 0 ? "applied" : "all_skipped"
|
|
9649
|
+
});
|
|
9999
9650
|
} catch (err) {
|
|
10000
9651
|
await reportCliUsage("cli.config.apply", false);
|
|
9652
|
+
trackConfig("apply", projectConfig, {
|
|
9653
|
+
json_mode: !!json,
|
|
9654
|
+
outcome: "error"
|
|
9655
|
+
});
|
|
9656
|
+
await shutdownAnalytics();
|
|
10001
9657
|
handleError(err, json);
|
|
9658
|
+
} finally {
|
|
9659
|
+
await shutdownAnalytics();
|
|
10002
9660
|
}
|
|
10003
9661
|
});
|
|
10004
9662
|
}
|
|
@@ -10083,8 +9741,8 @@ function registerConfigCommand(program2) {
|
|
|
10083
9741
|
}
|
|
10084
9742
|
|
|
10085
9743
|
// src/commands/ai/setup.ts
|
|
10086
|
-
import { appendFileSync as appendFileSync2, existsSync as
|
|
10087
|
-
import { isAbsolute, join as
|
|
9744
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
|
|
9745
|
+
import { isAbsolute, join as join14, relative as relative3, resolve as resolve8 } from "path";
|
|
10088
9746
|
import * as clack17 from "@clack/prompts";
|
|
10089
9747
|
import pc7 from "picocolors";
|
|
10090
9748
|
|
|
@@ -10105,6 +9763,52 @@ async function getOpenRouterApiKey() {
|
|
|
10105
9763
|
};
|
|
10106
9764
|
}
|
|
10107
9765
|
|
|
9766
|
+
// src/lib/env-writer.ts
|
|
9767
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "fs";
|
|
9768
|
+
var KEY_LINE_RE = (key) => (
|
|
9769
|
+
// Match `KEY=...` at the start of a line (allowing leading whitespace).
|
|
9770
|
+
// Captures the value side; we only need the value portion to compare.
|
|
9771
|
+
new RegExp(`^\\s*${key.replace(/[$.*+?^()[\\]{}|]/g, "\\$&")}\\s*=\\s*(.*)$`, "m")
|
|
9772
|
+
);
|
|
9773
|
+
function stripQuotes(v) {
|
|
9774
|
+
const t = v.trim();
|
|
9775
|
+
if (t.startsWith('"') && t.endsWith('"') && t.length >= 2 || t.startsWith("'") && t.endsWith("'") && t.length >= 2) {
|
|
9776
|
+
return t.slice(1, -1);
|
|
9777
|
+
}
|
|
9778
|
+
const hash = t.indexOf(" #");
|
|
9779
|
+
return hash >= 0 ? t.slice(0, hash).trimEnd() : t;
|
|
9780
|
+
}
|
|
9781
|
+
function upsertEnvFile(path6, entries) {
|
|
9782
|
+
const exists = existsSync11(path6);
|
|
9783
|
+
let content = exists ? readFileSync10(path6, "utf-8") : "";
|
|
9784
|
+
const result = { added: [], skipped: [], mismatched: [] };
|
|
9785
|
+
const additions = [];
|
|
9786
|
+
for (const [key, value] of Object.entries(entries)) {
|
|
9787
|
+
const re = KEY_LINE_RE(key);
|
|
9788
|
+
const match = content.match(re);
|
|
9789
|
+
if (match) {
|
|
9790
|
+
const existingValue = stripQuotes(match[1] ?? "");
|
|
9791
|
+
if (existingValue === value) {
|
|
9792
|
+
result.skipped.push(key);
|
|
9793
|
+
} else {
|
|
9794
|
+
result.mismatched.push({ key, existingValue, newValue: value });
|
|
9795
|
+
}
|
|
9796
|
+
continue;
|
|
9797
|
+
}
|
|
9798
|
+
additions.push(`${key}=${value}`);
|
|
9799
|
+
result.added.push(key);
|
|
9800
|
+
}
|
|
9801
|
+
if (additions.length > 0) {
|
|
9802
|
+
if (content.length > 0 && !content.endsWith("\n")) {
|
|
9803
|
+
content += "\n";
|
|
9804
|
+
}
|
|
9805
|
+
content += additions.join("\n") + "\n";
|
|
9806
|
+
writeFileSync8(path6, content);
|
|
9807
|
+
} else if (!exists) {
|
|
9808
|
+
}
|
|
9809
|
+
return result;
|
|
9810
|
+
}
|
|
9811
|
+
|
|
10108
9812
|
// src/commands/ai/setup.ts
|
|
10109
9813
|
var DEFAULT_ENV_FILE = ".env.local";
|
|
10110
9814
|
var OPENROUTER_ENV_KEY = "OPENROUTER_API_KEY";
|
|
@@ -10196,7 +9900,7 @@ async function runAiSetup(opts) {
|
|
|
10196
9900
|
};
|
|
10197
9901
|
}
|
|
10198
9902
|
function displayPath(path6) {
|
|
10199
|
-
const rel =
|
|
9903
|
+
const rel = relative3(process.cwd(), path6);
|
|
10200
9904
|
if (!rel || rel.startsWith("..") || isAbsolute(rel)) {
|
|
10201
9905
|
return path6;
|
|
10202
9906
|
}
|
|
@@ -10210,12 +9914,12 @@ function isLocalEnvFile(envFile) {
|
|
|
10210
9914
|
function ensureLocalEnvIgnored(cwd, envFile) {
|
|
10211
9915
|
if (!isLocalEnvFile(envFile)) return false;
|
|
10212
9916
|
const envPath = resolve8(cwd, envFile);
|
|
10213
|
-
const relEnvPath =
|
|
9917
|
+
const relEnvPath = relative3(cwd, envPath);
|
|
10214
9918
|
if (!relEnvPath || relEnvPath.startsWith("..") || isAbsolute(relEnvPath)) {
|
|
10215
9919
|
return false;
|
|
10216
9920
|
}
|
|
10217
|
-
const gitignorePath =
|
|
10218
|
-
const existing =
|
|
9921
|
+
const gitignorePath = join14(cwd, ".gitignore");
|
|
9922
|
+
const existing = existsSync12(gitignorePath) ? readFileSync11(gitignorePath, "utf-8") : "";
|
|
10219
9923
|
const lines = new Set(existing.split(/\r?\n/).map((line) => line.trim()));
|
|
10220
9924
|
const envBasename = envFile.replace(/\\/g, "/").split("/").pop() ?? envFile;
|
|
10221
9925
|
if (lines.has(".env*") || lines.has(".env.*") || lines.has(".env*.local") || lines.has(".env.local") && envBasename === ".env.local") {
|
|
@@ -10235,8 +9939,8 @@ function registerAiCommands(aiCmd2) {
|
|
|
10235
9939
|
}
|
|
10236
9940
|
|
|
10237
9941
|
// src/index.ts
|
|
10238
|
-
var __dirname =
|
|
10239
|
-
var pkg = JSON.parse(
|
|
9942
|
+
var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
9943
|
+
var pkg = JSON.parse(readFileSync12(join15(__dirname, "../package.json"), "utf-8"));
|
|
10240
9944
|
var INSFORGE_LOGO = `
|
|
10241
9945
|
\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
|
|
10242
9946
|
\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
|