@insforge/cli 0.1.78 → 0.1.80
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 +477 -570
- 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";
|
|
@@ -1172,7 +1172,7 @@ import * as clack5 from "@clack/prompts";
|
|
|
1172
1172
|
|
|
1173
1173
|
// src/lib/analytics.ts
|
|
1174
1174
|
import { PostHog } from "posthog-node";
|
|
1175
|
-
var POSTHOG_API_KEY = "";
|
|
1175
|
+
var POSTHOG_API_KEY = "phc_ueV1ii62wdBTkH7E70ugyeqHIHu8dFDdjs0qq3TZhJz";
|
|
1176
1176
|
var POSTHOG_HOST = process.env.POSTHOG_HOST || "https://us.i.posthog.com";
|
|
1177
1177
|
var client = null;
|
|
1178
1178
|
function getClient() {
|
|
@@ -6961,7 +6961,7 @@ function registerDiagnoseCommands(diagnoseCmd2) {
|
|
|
6961
6961
|
const s = !json ? clack15.spinner() : null;
|
|
6962
6962
|
s?.start("Collecting diagnostic data...");
|
|
6963
6963
|
const data2 = await collectDiagnosticData(projectId, ossMode, apiUrl);
|
|
6964
|
-
const cliVersion = "0.1.
|
|
6964
|
+
const cliVersion = "0.1.80";
|
|
6965
6965
|
s?.stop("Data collected");
|
|
6966
6966
|
if (!json) {
|
|
6967
6967
|
console.log(`
|
|
@@ -8325,8 +8325,7 @@ function registerPaymentsCommands(paymentsCmd2) {
|
|
|
8325
8325
|
}
|
|
8326
8326
|
|
|
8327
8327
|
// src/commands/posthog/setup.ts
|
|
8328
|
-
import {
|
|
8329
|
-
import { join as join16, dirname as dirname2 } from "path";
|
|
8328
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
8330
8329
|
import * as clack16 from "@clack/prompts";
|
|
8331
8330
|
import pc3 from "picocolors";
|
|
8332
8331
|
|
|
@@ -8492,200 +8491,19 @@ function sleep(ms, signal) {
|
|
|
8492
8491
|
});
|
|
8493
8492
|
}
|
|
8494
8493
|
|
|
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
8494
|
// src/commands/posthog/setup.ts
|
|
8677
8495
|
var POLL_INTERVAL_MS4 = 2e3;
|
|
8678
8496
|
var POLL_TIMEOUT_MS4 = 15 * 60 * 1e3;
|
|
8679
8497
|
var MAX_TRANSIENT_RETRIES = 5;
|
|
8498
|
+
var NPX_COMMAND = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
8499
|
+
var WIZARD_COMMAND = `${NPX_COMMAND} -y @posthog/wizard@latest`;
|
|
8680
8500
|
function registerPosthogSetupCommand(program2) {
|
|
8681
|
-
program2.command("setup").description("
|
|
8501
|
+
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
8502
|
const { json, apiUrl } = getRootOpts(cmd);
|
|
8683
8503
|
try {
|
|
8684
8504
|
const result = await runSetup({
|
|
8685
8505
|
json,
|
|
8686
8506
|
apiUrl,
|
|
8687
|
-
forceFramework: opts.framework,
|
|
8688
|
-
skipInstall: Boolean(opts.skipInstall),
|
|
8689
8507
|
skipBrowser: Boolean(opts.skipBrowser)
|
|
8690
8508
|
});
|
|
8691
8509
|
if (json) {
|
|
@@ -8709,62 +8527,60 @@ async function runSetup(opts) {
|
|
|
8709
8527
|
clack16.intro("PostHog setup");
|
|
8710
8528
|
outputSuccess(`Linked to InsForge project: ${proj.project_name} (${proj.project_id})`);
|
|
8711
8529
|
}
|
|
8712
|
-
const
|
|
8713
|
-
|
|
8530
|
+
const dashboardConnection = await ensureDashboardConnection(proj.project_id, token, opts);
|
|
8531
|
+
if (opts.json) {
|
|
8532
|
+
return {
|
|
8533
|
+
dashboardConnection,
|
|
8534
|
+
wizardSkipped: true,
|
|
8535
|
+
wizardCommand: WIZARD_COMMAND
|
|
8536
|
+
};
|
|
8537
|
+
}
|
|
8538
|
+
outputInfo("Running the official PostHog setup wizard to wire PostHog into your app...");
|
|
8539
|
+
outputInfo(
|
|
8540
|
+
pc3.dim("(it will open a browser for OAuth and let you pick a PostHog project)")
|
|
8541
|
+
);
|
|
8542
|
+
const wizardResult = spawnSync2(NPX_COMMAND, ["-y", "@posthog/wizard@latest"], {
|
|
8543
|
+
stdio: "inherit",
|
|
8544
|
+
env: process.env
|
|
8545
|
+
});
|
|
8546
|
+
if (wizardResult.error) {
|
|
8547
|
+
throw new CLIError(`Failed to launch PostHog wizard: ${wizardResult.error.message}`);
|
|
8548
|
+
}
|
|
8549
|
+
const exitCode = wizardResult.status ?? 1;
|
|
8550
|
+
if (wizardResult.signal === "SIGINT" || exitCode === 130) {
|
|
8551
|
+
clack16.outro("Setup cancelled.");
|
|
8552
|
+
return {
|
|
8553
|
+
dashboardConnection,
|
|
8554
|
+
wizardSkipped: false,
|
|
8555
|
+
wizardExitCode: exitCode
|
|
8556
|
+
};
|
|
8557
|
+
}
|
|
8558
|
+
if (exitCode !== 0) {
|
|
8559
|
+
throw new CLIError(`PostHog wizard exited with code ${exitCode}.`);
|
|
8560
|
+
}
|
|
8561
|
+
clack16.outro("Done. Open the Analytics page in your InsForge dashboard to view data.");
|
|
8562
|
+
return {
|
|
8563
|
+
dashboardConnection,
|
|
8564
|
+
wizardSkipped: false,
|
|
8565
|
+
wizardExitCode: exitCode
|
|
8566
|
+
};
|
|
8567
|
+
}
|
|
8568
|
+
async function ensureDashboardConnection(projectId, token, opts) {
|
|
8569
|
+
const startResult = await startPosthogCliFlow(projectId, token, opts.apiUrl);
|
|
8714
8570
|
if (startResult.type === "connected") {
|
|
8715
8571
|
if (!opts.json) {
|
|
8716
|
-
outputSuccess("PostHog already connected
|
|
8572
|
+
outputSuccess("PostHog is already connected to your InsForge dashboard.");
|
|
8717
8573
|
}
|
|
8718
|
-
const fetchResult = await fetchPosthogConnection(
|
|
8574
|
+
const fetchResult = await fetchPosthogConnection(projectId, token, opts.apiUrl);
|
|
8719
8575
|
if (fetchResult.kind !== "connected") {
|
|
8720
8576
|
throw new CLIError(
|
|
8721
8577
|
"cli-start reported connected, but /connection returned not-connected. Try again, or check the dashboard."
|
|
8722
8578
|
);
|
|
8723
8579
|
}
|
|
8724
|
-
|
|
8725
|
-
} else {
|
|
8726
|
-
conn = await runConnectFlow(proj.project_id, token, startResult.authorizeUrl, opts);
|
|
8580
|
+
return "already-connected";
|
|
8727
8581
|
}
|
|
8728
|
-
|
|
8729
|
-
|
|
8730
|
-
"Connection succeeded but cloud-backend returned no apiKey. Try again or check the dashboard."
|
|
8731
|
-
);
|
|
8732
|
-
}
|
|
8733
|
-
const framework = resolveFramework(opts);
|
|
8734
|
-
if (framework === null) {
|
|
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
|
-
};
|
|
8582
|
+
await runConnectFlow(projectId, token, startResult.authorizeUrl, opts);
|
|
8583
|
+
return "newly-connected";
|
|
8768
8584
|
}
|
|
8769
8585
|
async function runConnectFlow(projectId, token, authorizeUrl, opts) {
|
|
8770
8586
|
if (opts.json) {
|
|
@@ -8772,7 +8588,7 @@ async function runConnectFlow(projectId, token, authorizeUrl, opts) {
|
|
|
8772
8588
|
`);
|
|
8773
8589
|
process.stderr.write("Your browser should open automatically. If not, copy the URL above.\n");
|
|
8774
8590
|
} else {
|
|
8775
|
-
clack16.log.info("PostHog is not connected to
|
|
8591
|
+
clack16.log.info("PostHog is not yet connected to your InsForge dashboard.");
|
|
8776
8592
|
outputInfo("");
|
|
8777
8593
|
outputInfo(`Open this URL to authorize PostHog:
|
|
8778
8594
|
${pc3.cyan(pc3.underline(authorizeUrl))}`);
|
|
@@ -8786,9 +8602,9 @@ async function runConnectFlow(projectId, token, authorizeUrl, opts) {
|
|
|
8786
8602
|
}
|
|
8787
8603
|
}
|
|
8788
8604
|
const spinner11 = !opts.json && isInteractive ? clack16.spinner() : null;
|
|
8789
|
-
spinner11?.start("Waiting for connection... (timeout: 15 minutes)");
|
|
8605
|
+
spinner11?.start("Waiting for InsForge dashboard connection... (timeout: 15 minutes)");
|
|
8790
8606
|
try {
|
|
8791
|
-
|
|
8607
|
+
await pollPosthogConnection(
|
|
8792
8608
|
projectId,
|
|
8793
8609
|
token,
|
|
8794
8610
|
{
|
|
@@ -8800,262 +8616,21 @@ async function runConnectFlow(projectId, token, authorizeUrl, opts) {
|
|
|
8800
8616
|
const secs = Math.floor(elapsed / 1e3);
|
|
8801
8617
|
const mins = Math.floor(secs / 60);
|
|
8802
8618
|
const remaining = `${mins}m ${secs % 60}s elapsed`;
|
|
8803
|
-
spinner11.message(`Waiting for connection... (${remaining})`);
|
|
8619
|
+
spinner11.message(`Waiting for InsForge dashboard connection... (${remaining})`);
|
|
8804
8620
|
}
|
|
8805
8621
|
}
|
|
8806
8622
|
},
|
|
8807
8623
|
opts.apiUrl
|
|
8808
8624
|
);
|
|
8809
|
-
spinner11?.stop("
|
|
8810
|
-
return conn;
|
|
8625
|
+
spinner11?.stop("InsForge dashboard connection received.");
|
|
8811
8626
|
} catch (err) {
|
|
8812
|
-
spinner11?.stop("
|
|
8627
|
+
spinner11?.stop("InsForge dashboard connection wait failed.");
|
|
8813
8628
|
throw err;
|
|
8814
8629
|
}
|
|
8815
8630
|
}
|
|
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
|
-
);
|
|
8823
|
-
}
|
|
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
|
-
} catch (err) {
|
|
8837
|
-
spinner11?.stop("Install failed.");
|
|
8838
|
-
if (!opts.json) {
|
|
8839
|
-
clack16.log.warn(
|
|
8840
|
-
`Could not run \`${cmd}\` automatically: ${err.message}
|
|
8841
|
-
Run it manually, then re-run \`insforge posthog setup\`.`
|
|
8842
|
-
);
|
|
8843
|
-
}
|
|
8844
|
-
return false;
|
|
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";
|
|
9051
|
-
}
|
|
9052
|
-
}
|
|
9053
|
-
function relative3(p3) {
|
|
9054
|
-
return p3.replace(process.cwd() + "/", "");
|
|
9055
|
-
}
|
|
9056
8631
|
|
|
9057
8632
|
// src/commands/config/export.ts
|
|
9058
|
-
import { writeFileSync as
|
|
8633
|
+
import { writeFileSync as writeFileSync7, existsSync as existsSync10 } from "fs";
|
|
9059
8634
|
import { resolve as resolve5 } from "path";
|
|
9060
8635
|
import * as p from "@clack/prompts";
|
|
9061
8636
|
import pc4 from "picocolors";
|
|
@@ -9190,9 +8765,67 @@ function validateAuth(input) {
|
|
|
9190
8765
|
}
|
|
9191
8766
|
out.allowed_redirect_urls = v;
|
|
9192
8767
|
}
|
|
8768
|
+
if ("require_email_verification" in obj) {
|
|
8769
|
+
if (typeof obj.require_email_verification !== "boolean") {
|
|
8770
|
+
throw new ConfigValidationError(
|
|
8771
|
+
"auth.require_email_verification",
|
|
8772
|
+
"must be a boolean"
|
|
8773
|
+
);
|
|
8774
|
+
}
|
|
8775
|
+
out.require_email_verification = obj.require_email_verification;
|
|
8776
|
+
}
|
|
8777
|
+
if ("verify_email_method" in obj) {
|
|
8778
|
+
out.verify_email_method = validateVerificationMethod(
|
|
8779
|
+
"auth.verify_email_method",
|
|
8780
|
+
obj.verify_email_method
|
|
8781
|
+
);
|
|
8782
|
+
}
|
|
8783
|
+
if ("reset_password_method" in obj) {
|
|
8784
|
+
out.reset_password_method = validateVerificationMethod(
|
|
8785
|
+
"auth.reset_password_method",
|
|
8786
|
+
obj.reset_password_method
|
|
8787
|
+
);
|
|
8788
|
+
}
|
|
8789
|
+
if ("password" in obj) out.password = validatePassword(obj.password);
|
|
9193
8790
|
if ("smtp" in obj) out.smtp = validateSmtp(obj.smtp);
|
|
9194
8791
|
return out;
|
|
9195
8792
|
}
|
|
8793
|
+
function validateVerificationMethod(path6, value) {
|
|
8794
|
+
if (value !== "code" && value !== "link") {
|
|
8795
|
+
throw new ConfigValidationError(path6, 'must be "code" or "link"');
|
|
8796
|
+
}
|
|
8797
|
+
return value;
|
|
8798
|
+
}
|
|
8799
|
+
function validatePassword(input) {
|
|
8800
|
+
if (input === null || typeof input !== "object" || Array.isArray(input)) {
|
|
8801
|
+
throw new ConfigValidationError("auth.password", "must be a table");
|
|
8802
|
+
}
|
|
8803
|
+
const obj = input;
|
|
8804
|
+
const out = {};
|
|
8805
|
+
if ("min_length" in obj) {
|
|
8806
|
+
if (typeof obj.min_length !== "number" || !Number.isInteger(obj.min_length) || obj.min_length < 4 || obj.min_length > 128) {
|
|
8807
|
+
throw new ConfigValidationError(
|
|
8808
|
+
"auth.password.min_length",
|
|
8809
|
+
"must be an integer between 4 and 128"
|
|
8810
|
+
);
|
|
8811
|
+
}
|
|
8812
|
+
out.min_length = obj.min_length;
|
|
8813
|
+
}
|
|
8814
|
+
for (const key of [
|
|
8815
|
+
"require_number",
|
|
8816
|
+
"require_lowercase",
|
|
8817
|
+
"require_uppercase",
|
|
8818
|
+
"require_special_char"
|
|
8819
|
+
]) {
|
|
8820
|
+
if (key in obj) {
|
|
8821
|
+
if (typeof obj[key] !== "boolean") {
|
|
8822
|
+
throw new ConfigValidationError(`auth.password.${key}`, "must be a boolean");
|
|
8823
|
+
}
|
|
8824
|
+
out[key] = obj[key];
|
|
8825
|
+
}
|
|
8826
|
+
}
|
|
8827
|
+
return out;
|
|
8828
|
+
}
|
|
9196
8829
|
function validateSmtp(input) {
|
|
9197
8830
|
if (input === null || typeof input !== "object" || Array.isArray(input)) {
|
|
9198
8831
|
throw new ConfigValidationError("auth.smtp", "must be a table");
|
|
@@ -9275,11 +8908,13 @@ function stringifyConfigToml(config) {
|
|
|
9275
8908
|
}
|
|
9276
8909
|
if (config.auth) {
|
|
9277
8910
|
lines.push("[auth]");
|
|
9278
|
-
|
|
9279
|
-
const urls = config.auth.allowed_redirect_urls.map((u) => JSON.stringify(u)).join(", ");
|
|
9280
|
-
lines.push(`allowed_redirect_urls = [${urls}]`);
|
|
9281
|
-
}
|
|
8911
|
+
renderAuthFlatFields(config.auth, lines);
|
|
9282
8912
|
lines.push("");
|
|
8913
|
+
if (config.auth.password !== void 0) {
|
|
8914
|
+
lines.push("[auth.password]");
|
|
8915
|
+
renderPasswordFields(config.auth.password, lines);
|
|
8916
|
+
lines.push("");
|
|
8917
|
+
}
|
|
9283
8918
|
if (config.auth.smtp !== void 0) {
|
|
9284
8919
|
lines.push("[auth.smtp]");
|
|
9285
8920
|
renderSmtpFields(config.auth.smtp, lines);
|
|
@@ -9295,6 +8930,34 @@ function stringifyConfigToml(config) {
|
|
|
9295
8930
|
}
|
|
9296
8931
|
return lines.join("\n").replace(/\n+$/, "\n");
|
|
9297
8932
|
}
|
|
8933
|
+
function renderAuthFlatFields(auth, lines) {
|
|
8934
|
+
if (auth.allowed_redirect_urls !== void 0) {
|
|
8935
|
+
const urls = auth.allowed_redirect_urls.map((u) => JSON.stringify(u)).join(", ");
|
|
8936
|
+
lines.push(`allowed_redirect_urls = [${urls}]`);
|
|
8937
|
+
}
|
|
8938
|
+
if (auth.require_email_verification !== void 0) {
|
|
8939
|
+
lines.push(`require_email_verification = ${auth.require_email_verification}`);
|
|
8940
|
+
}
|
|
8941
|
+
if (auth.verify_email_method !== void 0) {
|
|
8942
|
+
lines.push(`verify_email_method = ${JSON.stringify(auth.verify_email_method)}`);
|
|
8943
|
+
}
|
|
8944
|
+
if (auth.reset_password_method !== void 0) {
|
|
8945
|
+
lines.push(`reset_password_method = ${JSON.stringify(auth.reset_password_method)}`);
|
|
8946
|
+
}
|
|
8947
|
+
}
|
|
8948
|
+
function renderPasswordFields(pw, lines) {
|
|
8949
|
+
if (pw.min_length !== void 0) lines.push(`min_length = ${pw.min_length}`);
|
|
8950
|
+
if (pw.require_number !== void 0) lines.push(`require_number = ${pw.require_number}`);
|
|
8951
|
+
if (pw.require_lowercase !== void 0) {
|
|
8952
|
+
lines.push(`require_lowercase = ${pw.require_lowercase}`);
|
|
8953
|
+
}
|
|
8954
|
+
if (pw.require_uppercase !== void 0) {
|
|
8955
|
+
lines.push(`require_uppercase = ${pw.require_uppercase}`);
|
|
8956
|
+
}
|
|
8957
|
+
if (pw.require_special_char !== void 0) {
|
|
8958
|
+
lines.push(`require_special_char = ${pw.require_special_char}`);
|
|
8959
|
+
}
|
|
8960
|
+
}
|
|
9298
8961
|
function renderSmtpFields(smtp, lines) {
|
|
9299
8962
|
if (smtp.enabled !== void 0) lines.push(`enabled = ${smtp.enabled}`);
|
|
9300
8963
|
if (smtp.host !== void 0) lines.push(`host = ${JSON.stringify(smtp.host)}`);
|
|
@@ -9318,6 +8981,132 @@ function renderSmtpFields(smtp, lines) {
|
|
|
9318
8981
|
}
|
|
9319
8982
|
}
|
|
9320
8983
|
|
|
8984
|
+
// src/lib/config-metadata.ts
|
|
8985
|
+
function liveFromMetadata(raw) {
|
|
8986
|
+
const live = { auth: {} };
|
|
8987
|
+
const a = isPlainObject(raw.auth) ? raw.auth : void 0;
|
|
8988
|
+
if (a && "allowedRedirectUrls" in a) {
|
|
8989
|
+
live.auth.allowed_redirect_urls = asStringArray(a.allowedRedirectUrls) ?? [];
|
|
8990
|
+
}
|
|
8991
|
+
if (a && "requireEmailVerification" in a) {
|
|
8992
|
+
live.auth.require_email_verification = a.requireEmailVerification ?? false;
|
|
8993
|
+
}
|
|
8994
|
+
if (a && "verifyEmailMethod" in a && (a.verifyEmailMethod === "code" || a.verifyEmailMethod === "link")) {
|
|
8995
|
+
live.auth.verify_email_method = a.verifyEmailMethod;
|
|
8996
|
+
}
|
|
8997
|
+
if (a && "resetPasswordMethod" in a && (a.resetPasswordMethod === "code" || a.resetPasswordMethod === "link")) {
|
|
8998
|
+
live.auth.reset_password_method = a.resetPasswordMethod;
|
|
8999
|
+
}
|
|
9000
|
+
if (a && ("passwordMinLength" in a || "requireNumber" in a || "requireLowercase" in a || "requireUppercase" in a || "requireSpecialChar" in a)) {
|
|
9001
|
+
live.auth.password = {
|
|
9002
|
+
min_length: a.passwordMinLength ?? 8,
|
|
9003
|
+
require_number: a.requireNumber ?? false,
|
|
9004
|
+
require_lowercase: a.requireLowercase ?? false,
|
|
9005
|
+
require_uppercase: a.requireUppercase ?? false,
|
|
9006
|
+
require_special_char: a.requireSpecialChar ?? false
|
|
9007
|
+
};
|
|
9008
|
+
}
|
|
9009
|
+
if (isPlainObject(a?.smtpConfig)) {
|
|
9010
|
+
const s = a.smtpConfig;
|
|
9011
|
+
live.auth.smtp = {
|
|
9012
|
+
enabled: s.enabled ?? false,
|
|
9013
|
+
host: s.host ?? "",
|
|
9014
|
+
port: s.port ?? 587,
|
|
9015
|
+
username: s.username ?? "",
|
|
9016
|
+
hasPassword: s.hasPassword ?? false,
|
|
9017
|
+
sender_email: s.senderEmail ?? "",
|
|
9018
|
+
sender_name: s.senderName ?? "",
|
|
9019
|
+
min_interval_seconds: s.minIntervalSeconds ?? 60
|
|
9020
|
+
};
|
|
9021
|
+
}
|
|
9022
|
+
const d = isPlainObject(raw.deployments) ? raw.deployments : void 0;
|
|
9023
|
+
if (d) {
|
|
9024
|
+
live.deployments = {
|
|
9025
|
+
subdomain: typeof d.customSlug === "string" && d.customSlug ? d.customSlug : null
|
|
9026
|
+
};
|
|
9027
|
+
}
|
|
9028
|
+
return live;
|
|
9029
|
+
}
|
|
9030
|
+
function isPlainObject(v) {
|
|
9031
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
9032
|
+
}
|
|
9033
|
+
function asStringArray(v) {
|
|
9034
|
+
return Array.isArray(v) && v.every((x) => typeof x === "string") ? v : null;
|
|
9035
|
+
}
|
|
9036
|
+
function configFromMetadata(raw) {
|
|
9037
|
+
const config = {};
|
|
9038
|
+
const skipped = [];
|
|
9039
|
+
const a = isPlainObject(raw.auth) ? raw.auth : void 0;
|
|
9040
|
+
if (a && "allowedRedirectUrls" in a) {
|
|
9041
|
+
config.auth = config.auth ?? {};
|
|
9042
|
+
config.auth.allowed_redirect_urls = asStringArray(a.allowedRedirectUrls) ?? [];
|
|
9043
|
+
} else {
|
|
9044
|
+
skipped.push("auth.allowed_redirect_urls");
|
|
9045
|
+
}
|
|
9046
|
+
if (a && "requireEmailVerification" in a) {
|
|
9047
|
+
config.auth = config.auth ?? {};
|
|
9048
|
+
config.auth.require_email_verification = a.requireEmailVerification ?? false;
|
|
9049
|
+
} else {
|
|
9050
|
+
skipped.push("auth.require_email_verification");
|
|
9051
|
+
}
|
|
9052
|
+
if (a && "verifyEmailMethod" in a && (a.verifyEmailMethod === "code" || a.verifyEmailMethod === "link")) {
|
|
9053
|
+
config.auth = config.auth ?? {};
|
|
9054
|
+
config.auth.verify_email_method = a.verifyEmailMethod;
|
|
9055
|
+
} else {
|
|
9056
|
+
skipped.push("auth.verify_email_method");
|
|
9057
|
+
}
|
|
9058
|
+
if (a && "resetPasswordMethod" in a && (a.resetPasswordMethod === "code" || a.resetPasswordMethod === "link")) {
|
|
9059
|
+
config.auth = config.auth ?? {};
|
|
9060
|
+
config.auth.reset_password_method = a.resetPasswordMethod;
|
|
9061
|
+
} else {
|
|
9062
|
+
skipped.push("auth.reset_password_method");
|
|
9063
|
+
}
|
|
9064
|
+
if (a && ("passwordMinLength" in a || "requireNumber" in a || "requireLowercase" in a || "requireUppercase" in a || "requireSpecialChar" in a)) {
|
|
9065
|
+
config.auth = config.auth ?? {};
|
|
9066
|
+
config.auth.password = {};
|
|
9067
|
+
if ("passwordMinLength" in a) config.auth.password.min_length = a.passwordMinLength ?? 8;
|
|
9068
|
+
if ("requireNumber" in a) config.auth.password.require_number = a.requireNumber ?? false;
|
|
9069
|
+
if ("requireLowercase" in a) config.auth.password.require_lowercase = a.requireLowercase ?? false;
|
|
9070
|
+
if ("requireUppercase" in a) config.auth.password.require_uppercase = a.requireUppercase ?? false;
|
|
9071
|
+
if ("requireSpecialChar" in a) {
|
|
9072
|
+
config.auth.password.require_special_char = a.requireSpecialChar ?? false;
|
|
9073
|
+
}
|
|
9074
|
+
} else {
|
|
9075
|
+
skipped.push("auth.password");
|
|
9076
|
+
}
|
|
9077
|
+
if (a && "smtpConfig" in a) {
|
|
9078
|
+
const s = a.smtpConfig;
|
|
9079
|
+
if (isPlainObject(s)) {
|
|
9080
|
+
config.auth = config.auth ?? {};
|
|
9081
|
+
config.auth.smtp = {
|
|
9082
|
+
enabled: s.enabled ?? false,
|
|
9083
|
+
host: s.host ?? "",
|
|
9084
|
+
port: s.port ?? 587,
|
|
9085
|
+
username: s.username ?? "",
|
|
9086
|
+
// When backend has a password set, emit a deterministic env() placeholder
|
|
9087
|
+
// so the user knows which secret to define. We do NOT round-trip the
|
|
9088
|
+
// value (it never leaves the backend). Re-applying this TOML force-resends
|
|
9089
|
+
// from the secrets store — see config-diff.ts for the force-resend rationale.
|
|
9090
|
+
...s.hasPassword ? { password: "env(SMTP_PASSWORD)" } : {},
|
|
9091
|
+
sender_email: s.senderEmail ?? "",
|
|
9092
|
+
sender_name: s.senderName ?? "",
|
|
9093
|
+
min_interval_seconds: s.minIntervalSeconds ?? 60
|
|
9094
|
+
};
|
|
9095
|
+
}
|
|
9096
|
+
} else {
|
|
9097
|
+
skipped.push("auth.smtp");
|
|
9098
|
+
}
|
|
9099
|
+
const d = isPlainObject(raw.deployments) ? raw.deployments : void 0;
|
|
9100
|
+
if (d) {
|
|
9101
|
+
if (typeof d.customSlug === "string" && d.customSlug) {
|
|
9102
|
+
config.deployments = { subdomain: d.customSlug };
|
|
9103
|
+
}
|
|
9104
|
+
} else {
|
|
9105
|
+
skipped.push("deployments.subdomain");
|
|
9106
|
+
}
|
|
9107
|
+
return { config, skipped };
|
|
9108
|
+
}
|
|
9109
|
+
|
|
9321
9110
|
// src/commands/config/export.ts
|
|
9322
9111
|
function registerConfigExportCommand(cfg) {
|
|
9323
9112
|
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) => {
|
|
@@ -9325,7 +9114,7 @@ function registerConfigExportCommand(cfg) {
|
|
|
9325
9114
|
try {
|
|
9326
9115
|
await requireAuth();
|
|
9327
9116
|
const target = resolve5(process.cwd(), opts.out);
|
|
9328
|
-
if (
|
|
9117
|
+
if (existsSync10(target) && !opts.force) {
|
|
9329
9118
|
if (json) {
|
|
9330
9119
|
throw new CLIError(
|
|
9331
9120
|
`${opts.out} exists. Re-run with --force to overwrite.`,
|
|
@@ -9344,46 +9133,9 @@ function registerConfigExportCommand(cfg) {
|
|
|
9344
9133
|
}
|
|
9345
9134
|
const res = await ossFetch("/api/metadata");
|
|
9346
9135
|
const raw = await res.json();
|
|
9347
|
-
const config =
|
|
9348
|
-
const skipped = [];
|
|
9349
|
-
const authSlice = raw?.auth;
|
|
9350
|
-
if (authSlice && typeof authSlice === "object" && "allowedRedirectUrls" in authSlice) {
|
|
9351
|
-
config.auth = config.auth ?? {};
|
|
9352
|
-
config.auth.allowed_redirect_urls = authSlice.allowedRedirectUrls ?? [];
|
|
9353
|
-
} else {
|
|
9354
|
-
skipped.push("auth.allowed_redirect_urls");
|
|
9355
|
-
}
|
|
9356
|
-
if (authSlice && typeof authSlice === "object" && "smtpConfig" in authSlice && authSlice.smtpConfig) {
|
|
9357
|
-
const s = authSlice.smtpConfig;
|
|
9358
|
-
config.auth = config.auth ?? {};
|
|
9359
|
-
config.auth.smtp = {
|
|
9360
|
-
enabled: s.enabled ?? false,
|
|
9361
|
-
host: s.host ?? "",
|
|
9362
|
-
port: s.port ?? 587,
|
|
9363
|
-
username: s.username ?? "",
|
|
9364
|
-
// When backend has a password set, emit a deterministic env()
|
|
9365
|
-
// placeholder so the user knows which secret to define. We do
|
|
9366
|
-
// NOT round-trip the value (it never leaves the backend).
|
|
9367
|
-
// Re-applying this TOML force-resends from the secrets store
|
|
9368
|
-
// — see config-diff.ts for the force-resend rationale.
|
|
9369
|
-
...s.hasPassword ? { password: "env(SMTP_PASSWORD)" } : {},
|
|
9370
|
-
sender_email: s.senderEmail ?? "",
|
|
9371
|
-
sender_name: s.senderName ?? "",
|
|
9372
|
-
min_interval_seconds: s.minIntervalSeconds ?? 60
|
|
9373
|
-
};
|
|
9374
|
-
} else {
|
|
9375
|
-
skipped.push("auth.smtp");
|
|
9376
|
-
}
|
|
9377
|
-
const deploymentsSlice = raw?.deployments;
|
|
9378
|
-
if (deploymentsSlice && typeof deploymentsSlice === "object") {
|
|
9379
|
-
if (typeof deploymentsSlice.customSlug === "string" && deploymentsSlice.customSlug) {
|
|
9380
|
-
config.deployments = { subdomain: deploymentsSlice.customSlug };
|
|
9381
|
-
}
|
|
9382
|
-
} else {
|
|
9383
|
-
skipped.push("deployments.subdomain");
|
|
9384
|
-
}
|
|
9136
|
+
const { config, skipped } = configFromMetadata(raw);
|
|
9385
9137
|
const toml = stringifyConfigToml(config);
|
|
9386
|
-
|
|
9138
|
+
writeFileSync7(target, toml, "utf8");
|
|
9387
9139
|
if (json) {
|
|
9388
9140
|
console.log(JSON.stringify({ written: target, config, skipped }, null, 2));
|
|
9389
9141
|
} else {
|
|
@@ -9405,7 +9157,7 @@ function registerConfigExportCommand(cfg) {
|
|
|
9405
9157
|
}
|
|
9406
9158
|
|
|
9407
9159
|
// src/commands/config/plan.ts
|
|
9408
|
-
import { readFileSync as
|
|
9160
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
9409
9161
|
import { resolve as resolve6 } from "path";
|
|
9410
9162
|
import pc5 from "picocolors";
|
|
9411
9163
|
|
|
@@ -9427,6 +9179,48 @@ function diffConfig({ live, file }) {
|
|
|
9427
9179
|
});
|
|
9428
9180
|
}
|
|
9429
9181
|
}
|
|
9182
|
+
if (fileAuth && "require_email_verification" in fileAuth) {
|
|
9183
|
+
const fromV = liveAuth.require_email_verification ?? false;
|
|
9184
|
+
const toV = fileAuth.require_email_verification ?? false;
|
|
9185
|
+
if (fromV !== toV) {
|
|
9186
|
+
changes.push({
|
|
9187
|
+
section: "auth",
|
|
9188
|
+
op: "modify",
|
|
9189
|
+
key: "require_email_verification",
|
|
9190
|
+
from: fromV,
|
|
9191
|
+
to: toV
|
|
9192
|
+
});
|
|
9193
|
+
}
|
|
9194
|
+
}
|
|
9195
|
+
if (fileAuth && "verify_email_method" in fileAuth && fileAuth.verify_email_method) {
|
|
9196
|
+
const fromV = liveAuth.verify_email_method ?? "code";
|
|
9197
|
+
const toV = fileAuth.verify_email_method;
|
|
9198
|
+
if (fromV !== toV) {
|
|
9199
|
+
changes.push({
|
|
9200
|
+
section: "auth",
|
|
9201
|
+
op: "modify",
|
|
9202
|
+
key: "verify_email_method",
|
|
9203
|
+
from: fromV,
|
|
9204
|
+
to: toV
|
|
9205
|
+
});
|
|
9206
|
+
}
|
|
9207
|
+
}
|
|
9208
|
+
if (fileAuth && "reset_password_method" in fileAuth && fileAuth.reset_password_method) {
|
|
9209
|
+
const fromV = liveAuth.reset_password_method ?? "code";
|
|
9210
|
+
const toV = fileAuth.reset_password_method;
|
|
9211
|
+
if (fromV !== toV) {
|
|
9212
|
+
changes.push({
|
|
9213
|
+
section: "auth",
|
|
9214
|
+
op: "modify",
|
|
9215
|
+
key: "reset_password_method",
|
|
9216
|
+
from: fromV,
|
|
9217
|
+
to: toV
|
|
9218
|
+
});
|
|
9219
|
+
}
|
|
9220
|
+
}
|
|
9221
|
+
if (fileAuth?.password) {
|
|
9222
|
+
diffPassword(liveAuth.password, fileAuth.password, changes);
|
|
9223
|
+
}
|
|
9430
9224
|
if (fileAuth?.smtp !== void 0) {
|
|
9431
9225
|
const smtpChange = diffSmtp(liveAuth.smtp, fileAuth.smtp);
|
|
9432
9226
|
if (smtpChange) changes.push(smtpChange);
|
|
@@ -9436,7 +9230,7 @@ function diffConfig({ live, file }) {
|
|
|
9436
9230
|
if (fileDeployments && "subdomain" in fileDeployments) {
|
|
9437
9231
|
const fromV = liveDeployments.subdomain ?? null;
|
|
9438
9232
|
const rawTo = fileDeployments.subdomain;
|
|
9439
|
-
const toV = rawTo === null || rawTo === "" ? null : rawTo;
|
|
9233
|
+
const toV = rawTo === null || rawTo === void 0 || rawTo === "" ? null : rawTo;
|
|
9440
9234
|
if (fromV !== toV) {
|
|
9441
9235
|
changes.push({
|
|
9442
9236
|
section: "deployments",
|
|
@@ -9449,6 +9243,36 @@ function diffConfig({ live, file }) {
|
|
|
9449
9243
|
}
|
|
9450
9244
|
return { changes, summary: summarize(changes) };
|
|
9451
9245
|
}
|
|
9246
|
+
function diffPassword(live, file, changes) {
|
|
9247
|
+
const liveView = live ?? EMPTY_PASSWORD_POLICY;
|
|
9248
|
+
if (file.min_length !== void 0 && liveView.min_length !== file.min_length) {
|
|
9249
|
+
changes.push({
|
|
9250
|
+
section: "auth.password",
|
|
9251
|
+
op: "modify",
|
|
9252
|
+
key: "min_length",
|
|
9253
|
+
from: liveView.min_length,
|
|
9254
|
+
to: file.min_length
|
|
9255
|
+
});
|
|
9256
|
+
}
|
|
9257
|
+
for (const key of [
|
|
9258
|
+
"require_number",
|
|
9259
|
+
"require_lowercase",
|
|
9260
|
+
"require_uppercase",
|
|
9261
|
+
"require_special_char"
|
|
9262
|
+
]) {
|
|
9263
|
+
const fromV = liveView[key];
|
|
9264
|
+
const toV = file[key];
|
|
9265
|
+
if (toV !== void 0 && fromV !== toV) {
|
|
9266
|
+
changes.push({
|
|
9267
|
+
section: "auth.password",
|
|
9268
|
+
op: "modify",
|
|
9269
|
+
key,
|
|
9270
|
+
from: fromV,
|
|
9271
|
+
to: toV
|
|
9272
|
+
});
|
|
9273
|
+
}
|
|
9274
|
+
}
|
|
9275
|
+
}
|
|
9452
9276
|
function diffSmtp(live, fileSmtp) {
|
|
9453
9277
|
const livedView = renderLiveSmtp(live);
|
|
9454
9278
|
const tomlView = renderFileSmtp(fileSmtp);
|
|
@@ -9507,6 +9331,13 @@ var EMPTY_SMTP_VIEW = {
|
|
|
9507
9331
|
sender_name: "",
|
|
9508
9332
|
min_interval_seconds: 60
|
|
9509
9333
|
};
|
|
9334
|
+
var EMPTY_PASSWORD_POLICY = {
|
|
9335
|
+
min_length: 8,
|
|
9336
|
+
require_number: false,
|
|
9337
|
+
require_lowercase: false,
|
|
9338
|
+
require_uppercase: false,
|
|
9339
|
+
require_special_char: false
|
|
9340
|
+
};
|
|
9510
9341
|
function summarize(changes) {
|
|
9511
9342
|
const s = { add: 0, modify: 0, remove: 0, kept: 0 };
|
|
9512
9343
|
for (const c of changes) {
|
|
@@ -9582,10 +9413,22 @@ function formatChange(c) {
|
|
|
9582
9413
|
// src/lib/config-capabilities.ts
|
|
9583
9414
|
function metadataSupports(raw, change) {
|
|
9584
9415
|
if (change.section === "auth" && change.key === "allowed_redirect_urls") {
|
|
9585
|
-
return raw
|
|
9416
|
+
return hasAuthKey(raw, "allowedRedirectUrls");
|
|
9417
|
+
}
|
|
9418
|
+
if (change.section === "auth" && change.key === "require_email_verification") {
|
|
9419
|
+
return hasAuthKey(raw, "requireEmailVerification");
|
|
9420
|
+
}
|
|
9421
|
+
if (change.section === "auth" && change.key === "verify_email_method") {
|
|
9422
|
+
return hasAuthKey(raw, "verifyEmailMethod");
|
|
9423
|
+
}
|
|
9424
|
+
if (change.section === "auth" && change.key === "reset_password_method") {
|
|
9425
|
+
return hasAuthKey(raw, "resetPasswordMethod");
|
|
9426
|
+
}
|
|
9427
|
+
if (change.section === "auth.password") {
|
|
9428
|
+
return hasAuthKey(raw, AUTH_PASSWORD_WIRE_KEY[change.key]);
|
|
9586
9429
|
}
|
|
9587
9430
|
if (change.section === "auth.smtp") {
|
|
9588
|
-
return raw
|
|
9431
|
+
return hasAuthKey(raw, "smtpConfig");
|
|
9589
9432
|
}
|
|
9590
9433
|
if (change.section === "deployments" && change.key === "subdomain") {
|
|
9591
9434
|
return raw?.deployments !== void 0 && raw.deployments !== null && typeof raw.deployments === "object";
|
|
@@ -9594,10 +9437,24 @@ function metadataSupports(raw, change) {
|
|
|
9594
9437
|
void _exhaustive;
|
|
9595
9438
|
return false;
|
|
9596
9439
|
}
|
|
9440
|
+
function hasAuthKey(raw, key) {
|
|
9441
|
+
const auth = raw?.auth;
|
|
9442
|
+
return auth !== void 0 && auth !== null && typeof auth === "object" && key in auth;
|
|
9443
|
+
}
|
|
9444
|
+
var AUTH_PASSWORD_WIRE_KEY = {
|
|
9445
|
+
min_length: "passwordMinLength",
|
|
9446
|
+
require_number: "requireNumber",
|
|
9447
|
+
require_lowercase: "requireLowercase",
|
|
9448
|
+
require_uppercase: "requireUppercase",
|
|
9449
|
+
require_special_char: "requireSpecialChar"
|
|
9450
|
+
};
|
|
9597
9451
|
function changePath(change) {
|
|
9598
9452
|
if (change.section === "auth.smtp") return "auth.smtp";
|
|
9599
9453
|
return `${change.section}.${change.key}`;
|
|
9600
9454
|
}
|
|
9455
|
+
function authPasswordWireKey(key) {
|
|
9456
|
+
return AUTH_PASSWORD_WIRE_KEY[key];
|
|
9457
|
+
}
|
|
9601
9458
|
|
|
9602
9459
|
// src/commands/config/plan.ts
|
|
9603
9460
|
function registerConfigPlanCommand(cfg) {
|
|
@@ -9606,13 +9463,11 @@ function registerConfigPlanCommand(cfg) {
|
|
|
9606
9463
|
try {
|
|
9607
9464
|
await requireAuth();
|
|
9608
9465
|
const tomlPath = resolve6(process.cwd(), opts.file);
|
|
9609
|
-
const tomlSource =
|
|
9466
|
+
const tomlSource = readFileSync8(tomlPath, "utf8");
|
|
9610
9467
|
const file = parseConfigToml(tomlSource);
|
|
9611
9468
|
const res = await ossFetch("/api/metadata");
|
|
9612
9469
|
const raw = await res.json();
|
|
9613
|
-
const live =
|
|
9614
|
-
auth: { allowed_redirect_urls: raw.auth?.allowedRedirectUrls ?? [] }
|
|
9615
|
-
};
|
|
9470
|
+
const live = liveFromMetadata(raw);
|
|
9616
9471
|
const result = diffConfig({ live, file });
|
|
9617
9472
|
const skipped = result.changes.filter((c) => !metadataSupports(raw, c)).map((c) => changePath(c));
|
|
9618
9473
|
if (json) {
|
|
@@ -9636,7 +9491,7 @@ function registerConfigPlanCommand(cfg) {
|
|
|
9636
9491
|
}
|
|
9637
9492
|
|
|
9638
9493
|
// src/commands/config/apply.ts
|
|
9639
|
-
import { readFileSync as
|
|
9494
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
9640
9495
|
import { resolve as resolve7 } from "path";
|
|
9641
9496
|
import * as p2 from "@clack/prompts";
|
|
9642
9497
|
import pc6 from "picocolors";
|
|
@@ -9646,7 +9501,7 @@ function registerConfigApplyCommand(cfg) {
|
|
|
9646
9501
|
try {
|
|
9647
9502
|
await requireAuth();
|
|
9648
9503
|
const tomlPath = resolve7(process.cwd(), opts.file);
|
|
9649
|
-
const tomlSource =
|
|
9504
|
+
const tomlSource = readFileSync9(tomlPath, "utf8");
|
|
9650
9505
|
const file = parseConfigToml(tomlSource);
|
|
9651
9506
|
const res = await ossFetch("/api/metadata");
|
|
9652
9507
|
const raw = await res.json();
|
|
@@ -9722,29 +9577,6 @@ function registerConfigApplyCommand(cfg) {
|
|
|
9722
9577
|
}
|
|
9723
9578
|
});
|
|
9724
9579
|
}
|
|
9725
|
-
function liveFromMetadata(raw) {
|
|
9726
|
-
const live = { auth: {} };
|
|
9727
|
-
if (raw.auth?.allowedRedirectUrls !== void 0) {
|
|
9728
|
-
live.auth.allowed_redirect_urls = raw.auth.allowedRedirectUrls;
|
|
9729
|
-
}
|
|
9730
|
-
if (raw.auth?.smtpConfig) {
|
|
9731
|
-
const s = raw.auth.smtpConfig;
|
|
9732
|
-
live.auth.smtp = {
|
|
9733
|
-
enabled: s.enabled ?? false,
|
|
9734
|
-
host: s.host ?? "",
|
|
9735
|
-
port: s.port ?? 587,
|
|
9736
|
-
username: s.username ?? "",
|
|
9737
|
-
hasPassword: s.hasPassword ?? false,
|
|
9738
|
-
sender_email: s.senderEmail ?? "",
|
|
9739
|
-
sender_name: s.senderName ?? "",
|
|
9740
|
-
min_interval_seconds: s.minIntervalSeconds ?? 60
|
|
9741
|
-
};
|
|
9742
|
-
}
|
|
9743
|
-
if (raw.deployments) {
|
|
9744
|
-
live.deployments = { subdomain: raw.deployments.customSlug ?? null };
|
|
9745
|
-
}
|
|
9746
|
-
return live;
|
|
9747
|
-
}
|
|
9748
9580
|
async function applyChange(change) {
|
|
9749
9581
|
if (change.section === "auth" && change.key === "allowed_redirect_urls") {
|
|
9750
9582
|
await ossFetch("/api/auth/config", {
|
|
@@ -9753,6 +9585,35 @@ async function applyChange(change) {
|
|
|
9753
9585
|
});
|
|
9754
9586
|
return;
|
|
9755
9587
|
}
|
|
9588
|
+
if (change.section === "auth" && change.key === "require_email_verification") {
|
|
9589
|
+
await ossFetch("/api/auth/config", {
|
|
9590
|
+
method: "PUT",
|
|
9591
|
+
body: JSON.stringify({ requireEmailVerification: change.to })
|
|
9592
|
+
});
|
|
9593
|
+
return;
|
|
9594
|
+
}
|
|
9595
|
+
if (change.section === "auth" && change.key === "verify_email_method") {
|
|
9596
|
+
await ossFetch("/api/auth/config", {
|
|
9597
|
+
method: "PUT",
|
|
9598
|
+
body: JSON.stringify({ verifyEmailMethod: change.to })
|
|
9599
|
+
});
|
|
9600
|
+
return;
|
|
9601
|
+
}
|
|
9602
|
+
if (change.section === "auth" && change.key === "reset_password_method") {
|
|
9603
|
+
await ossFetch("/api/auth/config", {
|
|
9604
|
+
method: "PUT",
|
|
9605
|
+
body: JSON.stringify({ resetPasswordMethod: change.to })
|
|
9606
|
+
});
|
|
9607
|
+
return;
|
|
9608
|
+
}
|
|
9609
|
+
if (change.section === "auth.password") {
|
|
9610
|
+
const wireKey = authPasswordWireKey(change.key);
|
|
9611
|
+
await ossFetch("/api/auth/config", {
|
|
9612
|
+
method: "PUT",
|
|
9613
|
+
body: JSON.stringify({ [wireKey]: change.to })
|
|
9614
|
+
});
|
|
9615
|
+
return;
|
|
9616
|
+
}
|
|
9756
9617
|
if (change.section === "auth.smtp") {
|
|
9757
9618
|
const to = change.to;
|
|
9758
9619
|
const body = {
|
|
@@ -9797,8 +9658,8 @@ function registerConfigCommand(program2) {
|
|
|
9797
9658
|
}
|
|
9798
9659
|
|
|
9799
9660
|
// src/commands/ai/setup.ts
|
|
9800
|
-
import { appendFileSync as appendFileSync2, existsSync as
|
|
9801
|
-
import { isAbsolute, join as
|
|
9661
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
|
|
9662
|
+
import { isAbsolute, join as join14, relative as relative3, resolve as resolve8 } from "path";
|
|
9802
9663
|
import * as clack17 from "@clack/prompts";
|
|
9803
9664
|
import pc7 from "picocolors";
|
|
9804
9665
|
|
|
@@ -9819,6 +9680,52 @@ async function getOpenRouterApiKey() {
|
|
|
9819
9680
|
};
|
|
9820
9681
|
}
|
|
9821
9682
|
|
|
9683
|
+
// src/lib/env-writer.ts
|
|
9684
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "fs";
|
|
9685
|
+
var KEY_LINE_RE = (key) => (
|
|
9686
|
+
// Match `KEY=...` at the start of a line (allowing leading whitespace).
|
|
9687
|
+
// Captures the value side; we only need the value portion to compare.
|
|
9688
|
+
new RegExp(`^\\s*${key.replace(/[$.*+?^()[\\]{}|]/g, "\\$&")}\\s*=\\s*(.*)$`, "m")
|
|
9689
|
+
);
|
|
9690
|
+
function stripQuotes(v) {
|
|
9691
|
+
const t = v.trim();
|
|
9692
|
+
if (t.startsWith('"') && t.endsWith('"') && t.length >= 2 || t.startsWith("'") && t.endsWith("'") && t.length >= 2) {
|
|
9693
|
+
return t.slice(1, -1);
|
|
9694
|
+
}
|
|
9695
|
+
const hash = t.indexOf(" #");
|
|
9696
|
+
return hash >= 0 ? t.slice(0, hash).trimEnd() : t;
|
|
9697
|
+
}
|
|
9698
|
+
function upsertEnvFile(path6, entries) {
|
|
9699
|
+
const exists = existsSync11(path6);
|
|
9700
|
+
let content = exists ? readFileSync10(path6, "utf-8") : "";
|
|
9701
|
+
const result = { added: [], skipped: [], mismatched: [] };
|
|
9702
|
+
const additions = [];
|
|
9703
|
+
for (const [key, value] of Object.entries(entries)) {
|
|
9704
|
+
const re = KEY_LINE_RE(key);
|
|
9705
|
+
const match = content.match(re);
|
|
9706
|
+
if (match) {
|
|
9707
|
+
const existingValue = stripQuotes(match[1] ?? "");
|
|
9708
|
+
if (existingValue === value) {
|
|
9709
|
+
result.skipped.push(key);
|
|
9710
|
+
} else {
|
|
9711
|
+
result.mismatched.push({ key, existingValue, newValue: value });
|
|
9712
|
+
}
|
|
9713
|
+
continue;
|
|
9714
|
+
}
|
|
9715
|
+
additions.push(`${key}=${value}`);
|
|
9716
|
+
result.added.push(key);
|
|
9717
|
+
}
|
|
9718
|
+
if (additions.length > 0) {
|
|
9719
|
+
if (content.length > 0 && !content.endsWith("\n")) {
|
|
9720
|
+
content += "\n";
|
|
9721
|
+
}
|
|
9722
|
+
content += additions.join("\n") + "\n";
|
|
9723
|
+
writeFileSync8(path6, content);
|
|
9724
|
+
} else if (!exists) {
|
|
9725
|
+
}
|
|
9726
|
+
return result;
|
|
9727
|
+
}
|
|
9728
|
+
|
|
9822
9729
|
// src/commands/ai/setup.ts
|
|
9823
9730
|
var DEFAULT_ENV_FILE = ".env.local";
|
|
9824
9731
|
var OPENROUTER_ENV_KEY = "OPENROUTER_API_KEY";
|
|
@@ -9910,7 +9817,7 @@ async function runAiSetup(opts) {
|
|
|
9910
9817
|
};
|
|
9911
9818
|
}
|
|
9912
9819
|
function displayPath(path6) {
|
|
9913
|
-
const rel =
|
|
9820
|
+
const rel = relative3(process.cwd(), path6);
|
|
9914
9821
|
if (!rel || rel.startsWith("..") || isAbsolute(rel)) {
|
|
9915
9822
|
return path6;
|
|
9916
9823
|
}
|
|
@@ -9924,12 +9831,12 @@ function isLocalEnvFile(envFile) {
|
|
|
9924
9831
|
function ensureLocalEnvIgnored(cwd, envFile) {
|
|
9925
9832
|
if (!isLocalEnvFile(envFile)) return false;
|
|
9926
9833
|
const envPath = resolve8(cwd, envFile);
|
|
9927
|
-
const relEnvPath =
|
|
9834
|
+
const relEnvPath = relative3(cwd, envPath);
|
|
9928
9835
|
if (!relEnvPath || relEnvPath.startsWith("..") || isAbsolute(relEnvPath)) {
|
|
9929
9836
|
return false;
|
|
9930
9837
|
}
|
|
9931
|
-
const gitignorePath =
|
|
9932
|
-
const existing =
|
|
9838
|
+
const gitignorePath = join14(cwd, ".gitignore");
|
|
9839
|
+
const existing = existsSync12(gitignorePath) ? readFileSync11(gitignorePath, "utf-8") : "";
|
|
9933
9840
|
const lines = new Set(existing.split(/\r?\n/).map((line) => line.trim()));
|
|
9934
9841
|
const envBasename = envFile.replace(/\\/g, "/").split("/").pop() ?? envFile;
|
|
9935
9842
|
if (lines.has(".env*") || lines.has(".env.*") || lines.has(".env*.local") || lines.has(".env.local") && envBasename === ".env.local") {
|
|
@@ -9949,8 +9856,8 @@ function registerAiCommands(aiCmd2) {
|
|
|
9949
9856
|
}
|
|
9950
9857
|
|
|
9951
9858
|
// src/index.ts
|
|
9952
|
-
var __dirname =
|
|
9953
|
-
var pkg = JSON.parse(
|
|
9859
|
+
var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
9860
|
+
var pkg = JSON.parse(readFileSync12(join15(__dirname, "../package.json"), "utf-8"));
|
|
9954
9861
|
var INSFORGE_LOGO = `
|
|
9955
9862
|
\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
|
|
9956
9863
|
\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
|