@mhosaic/feedback-cli 0.27.1 → 0.29.0
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/bin.js +1 -1
- package/dist/doctor-24Z2JARJ.js +163 -0
- package/dist/doctor-24Z2JARJ.js.map +1 -0
- package/package.json +1 -1
- package/dist/doctor-K2C6LBR2.js +0 -42
- package/dist/doctor-K2C6LBR2.js.map +0 -1
package/dist/bin.js
CHANGED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
detectFramework
|
|
4
|
+
} from "./chunk-COJ75KUD.js";
|
|
5
|
+
|
|
6
|
+
// src/commands/doctor.ts
|
|
7
|
+
import { existsSync, readFileSync, readdirSync } from "fs";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
import kleur from "kleur";
|
|
10
|
+
var BUNDLED_IMPORT = /from\s+['"]@mhosaic\/feedback['"]/;
|
|
11
|
+
var CODE_EXT = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".vue", ".svelte", ".astro"]);
|
|
12
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", ".output", ".svelte-kit"]);
|
|
13
|
+
function scanDeliveryPath(cwd, entry) {
|
|
14
|
+
const seen = /* @__PURE__ */ new Set();
|
|
15
|
+
const check = (rel) => {
|
|
16
|
+
const abs = join(cwd, rel);
|
|
17
|
+
if (seen.has(abs) || !existsSync(abs)) return false;
|
|
18
|
+
seen.add(abs);
|
|
19
|
+
try {
|
|
20
|
+
return BUNDLED_IMPORT.test(readFileSync(abs, "utf8"));
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
if (entry && check(entry)) return entry;
|
|
26
|
+
let budget = 2e3;
|
|
27
|
+
const walk = (dir) => {
|
|
28
|
+
let entries;
|
|
29
|
+
try {
|
|
30
|
+
entries = readdirSync(join(cwd, dir), { withFileTypes: true });
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
for (const e of entries) {
|
|
35
|
+
if (budget-- <= 0) return null;
|
|
36
|
+
const rel = dir ? `${dir}/${e.name}` : e.name;
|
|
37
|
+
if (e.isDirectory()) {
|
|
38
|
+
if (SKIP_DIRS.has(e.name)) continue;
|
|
39
|
+
const hit = walk(rel);
|
|
40
|
+
if (hit) return hit;
|
|
41
|
+
} else if (CODE_EXT.has(e.name.slice(e.name.lastIndexOf(".")))) {
|
|
42
|
+
if (check(rel)) return rel;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
};
|
|
47
|
+
return existsSync(join(cwd, "src")) ? walk("src") : null;
|
|
48
|
+
}
|
|
49
|
+
var CDN_HOST = "cdn.jsdelivr.net";
|
|
50
|
+
var CSP_EXT = /* @__PURE__ */ new Set([".html", ".htm", ".conf", ".toml", ".json", ".js", ".cjs", ".mjs", ".ts"]);
|
|
51
|
+
var CSP_EXTRA_NAMES = /* @__PURE__ */ new Set(["_headers"]);
|
|
52
|
+
var PERMISSIVE_SRC = /(?:^|\s)(?:\*|https:)(?=\s|;|"|'|$)/;
|
|
53
|
+
function scriptSrcOf(csp) {
|
|
54
|
+
const flat = csp.replace(/\s+/g, " ");
|
|
55
|
+
const m = /script-src([^;]*)/i.exec(flat) ?? /default-src([^;]*)/i.exec(flat);
|
|
56
|
+
return m ? m[1] ?? null : null;
|
|
57
|
+
}
|
|
58
|
+
function scanCsp(cwd) {
|
|
59
|
+
let budget = 3e3;
|
|
60
|
+
const walk = (dir) => {
|
|
61
|
+
let entries;
|
|
62
|
+
try {
|
|
63
|
+
entries = readdirSync(join(cwd, dir), { withFileTypes: true });
|
|
64
|
+
} catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
for (const e of entries) {
|
|
68
|
+
if (budget-- <= 0) return null;
|
|
69
|
+
const rel = dir ? `${dir}/${e.name}` : e.name;
|
|
70
|
+
if (e.isDirectory()) {
|
|
71
|
+
if (SKIP_DIRS.has(e.name)) continue;
|
|
72
|
+
const hit = walk(rel);
|
|
73
|
+
if (hit) return hit;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const ext = e.name.slice(e.name.lastIndexOf("."));
|
|
77
|
+
if (!CSP_EXT.has(ext) && !CSP_EXTRA_NAMES.has(e.name)) continue;
|
|
78
|
+
let text;
|
|
79
|
+
try {
|
|
80
|
+
text = readFileSync(join(cwd, rel), "utf8");
|
|
81
|
+
} catch {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (!text.includes("script-src") && !text.includes("Content-Security-Policy")) continue;
|
|
85
|
+
const scriptSrc = scriptSrcOf(text);
|
|
86
|
+
if (scriptSrc === null) continue;
|
|
87
|
+
if (scriptSrc.includes(CDN_HOST) || PERMISSIVE_SRC.test(scriptSrc)) continue;
|
|
88
|
+
return rel;
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
};
|
|
92
|
+
return walk("");
|
|
93
|
+
}
|
|
94
|
+
function isBundledByDesign(cwd) {
|
|
95
|
+
try {
|
|
96
|
+
const pkg = JSON.parse(readFileSync(join(cwd, "package.json"), "utf8"));
|
|
97
|
+
return pkg?.mhosaicFeedback?.delivery === "bundled";
|
|
98
|
+
} catch {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async function runDoctor(argv) {
|
|
103
|
+
const cwd = argv.includes("--cwd") ? argv[argv.indexOf("--cwd") + 1] ?? process.cwd() : process.cwd();
|
|
104
|
+
const checks = [];
|
|
105
|
+
const framework = await detectFramework(cwd);
|
|
106
|
+
checks.push({ name: `framework detected: ${framework.kind}`, ok: framework.kind !== "unknown" && framework.kind !== "plain" });
|
|
107
|
+
const envPath = join(cwd, ".env.local");
|
|
108
|
+
const envOk = existsSync(envPath) && readFileSync(envPath, "utf8").includes("VITE_FEEDBACK_API_KEY=");
|
|
109
|
+
checks.push({ name: ".env.local has VITE_FEEDBACK_API_KEY", ok: envOk, hint: "run `mhosaic-feedback init`" });
|
|
110
|
+
const giPath = join(cwd, ".gitignore");
|
|
111
|
+
const giOk = existsSync(giPath) && readFileSync(giPath, "utf8").includes(".env.local");
|
|
112
|
+
checks.push({ name: ".gitignore ignores .env.local", ok: giOk, hint: "add `.env.local` to .gitignore" });
|
|
113
|
+
let wrapOk = false;
|
|
114
|
+
if (framework.entry) {
|
|
115
|
+
const entryPath = join(cwd, framework.entry);
|
|
116
|
+
if (existsSync(entryPath)) {
|
|
117
|
+
const src = readFileSync(entryPath, "utf8");
|
|
118
|
+
wrapOk = src.includes("<FeedbackProvider") && (src.includes("@mhosaic/feedback/loader/react") || src.includes("@mhosaic/feedback/react"));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
checks.push({ name: "<FeedbackProvider> wired in entry", ok: wrapOk, hint: "run `mhosaic-feedback init`" });
|
|
122
|
+
if (isBundledByDesign(cwd)) {
|
|
123
|
+
checks.push({
|
|
124
|
+
name: 'delivery: bundled on purpose (mhosaicFeedback.delivery="bundled") \u2014 update the version manually',
|
|
125
|
+
ok: true
|
|
126
|
+
});
|
|
127
|
+
} else {
|
|
128
|
+
const bundledAt = scanDeliveryPath(cwd, framework.entry ?? void 0);
|
|
129
|
+
checks.push({
|
|
130
|
+
name: "loader delivery path (version pins reach this app)",
|
|
131
|
+
ok: bundledAt === null,
|
|
132
|
+
...bundledAt ? {
|
|
133
|
+
hint: `bundled import in ${bundledAt} \u2014 change '@mhosaic/feedback' to '@mhosaic/feedback/loader' (same API) so pins land without a redeploy. If a strict CSP forbids the CDN, keep it bundled and set package.json "mhosaicFeedback": { "delivery": "bundled" }`
|
|
134
|
+
} : {}
|
|
135
|
+
});
|
|
136
|
+
if (bundledAt === null) {
|
|
137
|
+
const cspBlockedAt = scanCsp(cwd);
|
|
138
|
+
checks.push({
|
|
139
|
+
name: `CSP allows the widget CDN (${CDN_HOST})`,
|
|
140
|
+
ok: cspBlockedAt === null,
|
|
141
|
+
...cspBlockedAt ? {
|
|
142
|
+
hint: `CSP in ${cspBlockedAt} omits the CDN \u2014 add 'https://${CDN_HOST}' to script-src (and the platform origin to connect-src), or if the CSP can't allow an external CDN keep the widget bundled and set package.json "mhosaicFeedback": { "delivery": "bundled" }`
|
|
143
|
+
} : {}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
let hasFailure = false;
|
|
148
|
+
for (const c of checks) {
|
|
149
|
+
const icon = c.ok ? kleur.green("\u2713") : kleur.red("\u2717");
|
|
150
|
+
process.stdout.write(`${icon} ${c.name}${!c.ok && c.hint ? kleur.gray(" \u2014 " + c.hint) : ""}
|
|
151
|
+
`);
|
|
152
|
+
if (!c.ok) hasFailure = true;
|
|
153
|
+
}
|
|
154
|
+
if (hasFailure) process.exitCode = 1;
|
|
155
|
+
}
|
|
156
|
+
export {
|
|
157
|
+
isBundledByDesign,
|
|
158
|
+
runDoctor,
|
|
159
|
+
scanCsp,
|
|
160
|
+
scanDeliveryPath,
|
|
161
|
+
scriptSrcOf
|
|
162
|
+
};
|
|
163
|
+
//# sourceMappingURL=doctor-24Z2JARJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/doctor.ts"],"sourcesContent":["import { existsSync, readFileSync, readdirSync } from 'node:fs'\nimport type { Dirent } from 'node:fs'\nimport { join } from 'node:path'\n\nimport kleur from 'kleur'\n\nimport { detectFramework } from '../detect'\n\n// A bare `@mhosaic/feedback` import (no subpath) pulls the WHOLE widget into\n// the host's build — freezing its version to package.json. That's the\n// \"bundled\" delivery path: Mhosaic version pins/promotions never reach the\n// app without a dependency bump + redeploy. The loader subpaths\n// (`@mhosaic/feedback/loader`, `.../loader/react`) instead resolve the\n// version at runtime from the manifest, so a pin reaches every client with\n// zero per-repo work. This matcher flags the bundled path; see\n// scanDeliveryPath below. (2026-07-07: a client was found silently bundled\n// and stuck four releases behind — doctor missed it because it only checked\n// the React provider, not vanilla `createFeedback` installs.)\nconst BUNDLED_IMPORT = /from\\s+['\"]@mhosaic\\/feedback['\"]/\nconst CODE_EXT = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.vue', '.svelte', '.astro'])\nconst SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.next', '.output', '.svelte-kit'])\n\n/** Shallow-bounded walk of `src/` (and the entry) for a bare bundled import.\n * Returns the first offending repo-relative path, or null if none. Capped so\n * doctor stays fast on large repos. */\nexport function scanDeliveryPath(cwd: string, entry?: string): string | null {\n const seen = new Set<string>()\n const check = (rel: string): boolean => {\n const abs = join(cwd, rel)\n if (seen.has(abs) || !existsSync(abs)) return false\n seen.add(abs)\n try {\n return BUNDLED_IMPORT.test(readFileSync(abs, 'utf8'))\n } catch {\n return false\n }\n }\n if (entry && check(entry)) return entry\n\n let budget = 2000 // files; a backstop, real hosts are far smaller\n const walk = (dir: string): string | null => {\n let entries: Dirent[]\n try {\n entries = readdirSync(join(cwd, dir), { withFileTypes: true })\n } catch {\n return null\n }\n for (const e of entries) {\n if (budget-- <= 0) return null\n const rel = dir ? `${dir}/${e.name}` : e.name\n if (e.isDirectory()) {\n if (SKIP_DIRS.has(e.name)) continue\n const hit = walk(rel)\n if (hit) return hit\n } else if (CODE_EXT.has(e.name.slice(e.name.lastIndexOf('.')))) {\n if (check(rel)) return rel\n }\n }\n return null\n }\n return existsSync(join(cwd, 'src')) ? walk('src') : null\n}\n\n// The loader injects `<script src=\"https://cdn.jsdelivr.net/...widget.min.js\">`\n// at runtime, so a strict CSP must allow that host in `script-src` or the\n// widget silently never loads — the \"Refused to load … not in the script-src\n// directive\" failure that took a client's widget down on 2026-07-08 right\n// after it migrated to the loader. This is the companion requirement to the\n// import swap; catching it here means at migration time, not in production.\nconst CDN_HOST = 'cdn.jsdelivr.net'\n// CSP config lives in many shapes (index.html meta, _headers, nginx confs,\n// *.toml/json framework config). We only need the text.\nconst CSP_EXT = new Set(['.html', '.htm', '.conf', '.toml', '.json', '.js', '.cjs', '.mjs', '.ts'])\nconst CSP_EXTRA_NAMES = new Set(['_headers'])\n// A source token that already permits any cross-origin script host — no CDN\n// entry needed. `'unsafe-inline'`/`'unsafe-eval'` do NOT help a cross-origin\n// src, so they don't count here.\nconst PERMISSIVE_SRC = /(?:^|\\s)(?:\\*|https:)(?=\\s|;|\"|'|$)/\n\n/** The effective script-src value from one CSP string (falls back to\n * default-src), whitespace-flattened so a multi-line meta tag parses. Null\n * when neither directive is present. */\nexport function scriptSrcOf(csp: string): string | null {\n const flat = csp.replace(/\\s+/g, ' ')\n const m = /script-src([^;]*)/i.exec(flat) ?? /default-src([^;]*)/i.exec(flat)\n return m ? (m[1] ?? null) : null\n}\n\n/** Bounded repo walk for a CSP whose (effective) script-src is restrictive\n * AND omits the widget CDN — i.e. would block the loader. Returns the first\n * offending repo-relative file, or null. Permissive policies (`*`, `https:`)\n * and policies that already allow the CDN pass. */\nexport function scanCsp(cwd: string): string | null {\n let budget = 3000\n const walk = (dir: string): string | null => {\n let entries: Dirent[]\n try {\n entries = readdirSync(join(cwd, dir), { withFileTypes: true })\n } catch {\n return null\n }\n for (const e of entries) {\n if (budget-- <= 0) return null\n const rel = dir ? `${dir}/${e.name}` : e.name\n if (e.isDirectory()) {\n if (SKIP_DIRS.has(e.name)) continue\n const hit = walk(rel)\n if (hit) return hit\n continue\n }\n const ext = e.name.slice(e.name.lastIndexOf('.'))\n if (!CSP_EXT.has(ext) && !CSP_EXTRA_NAMES.has(e.name)) continue\n let text: string\n try {\n text = readFileSync(join(cwd, rel), 'utf8')\n } catch {\n continue\n }\n if (!text.includes('script-src') && !text.includes('Content-Security-Policy')) continue\n const scriptSrc = scriptSrcOf(text)\n if (scriptSrc === null) continue // CSP present but no script/default-src → not restricting scripts\n if (scriptSrc.includes(CDN_HOST) || PERMISSIVE_SRC.test(scriptSrc)) continue // already allowed\n return rel // restrictive script-src that omits the CDN → would block the loader\n }\n return null\n }\n return walk('')\n}\n\n/** A host can declare it is intentionally bundled — e.g. a strict CSP that\n * forbids external CDN scripts, so the loader physically cannot run (its\n * jsDelivr <script> is refused). It opts out via package.json\n * `{\"mhosaicFeedback\": {\"delivery\": \"bundled\"}}`. This is the ONE escape\n * hatch; every host without it still gets the hard \"switch to the loader\"\n * failure, so the default stays strong. Expected to be rare (one known case:\n * a CSP-locked client whose widget updates are done manually). */\nexport function isBundledByDesign(cwd: string): boolean {\n try {\n const pkg = JSON.parse(readFileSync(join(cwd, 'package.json'), 'utf8'))\n return pkg?.mhosaicFeedback?.delivery === 'bundled'\n } catch {\n return false\n }\n}\n\nexport async function runDoctor(argv: string[]): Promise<void> {\n const cwd = argv.includes('--cwd') ? argv[argv.indexOf('--cwd') + 1] ?? process.cwd() : process.cwd()\n\n const checks: Array<{ name: string; ok: boolean; hint?: string }> = []\n\n const framework = await detectFramework(cwd)\n checks.push({ name: `framework detected: ${framework.kind}`, ok: framework.kind !== 'unknown' && framework.kind !== 'plain' })\n\n const envPath = join(cwd, '.env.local')\n const envOk = existsSync(envPath) && readFileSync(envPath, 'utf8').includes('VITE_FEEDBACK_API_KEY=')\n checks.push({ name: '.env.local has VITE_FEEDBACK_API_KEY', ok: envOk, hint: 'run `mhosaic-feedback init`' })\n\n const giPath = join(cwd, '.gitignore')\n const giOk = existsSync(giPath) && readFileSync(giPath, 'utf8').includes('.env.local')\n checks.push({ name: '.gitignore ignores .env.local', ok: giOk, hint: 'add `.env.local` to .gitignore' })\n\n let wrapOk = false\n if (framework.entry) {\n const entryPath = join(cwd, framework.entry)\n if (existsSync(entryPath)) {\n const src = readFileSync(entryPath, 'utf8')\n // Accept both the legacy path (`@mhosaic/feedback/react`) and the\n // new loader path (`@mhosaic/feedback/loader/react`) so doctor\n // returns green on already-integrated hosts pre- and post-v0.16.\n wrapOk =\n src.includes('<FeedbackProvider') &&\n (src.includes(\"@mhosaic/feedback/loader/react\") ||\n src.includes(\"@mhosaic/feedback/react\"))\n }\n }\n checks.push({ name: '<FeedbackProvider> wired in entry', ok: wrapOk, hint: 'run `mhosaic-feedback init`' })\n\n // Delivery path: the loader resolves the widget version at runtime, so\n // Mhosaic version pins reach this app automatically. A bare\n // `@mhosaic/feedback` import bundles a frozen version instead. This is the\n // one check that keeps \"every client is loader\" true over time — unless the\n // host has explicitly opted into bundling (isBundledByDesign), the one\n // sanctioned exception for a CSP that can't allow the CDN.\n if (isBundledByDesign(cwd)) {\n checks.push({\n name: 'delivery: bundled on purpose (mhosaicFeedback.delivery=\"bundled\") — update the version manually',\n ok: true,\n })\n // CSP check deliberately skipped: a bundled widget is served same-origin,\n // so there is no CDN host to allowlist.\n } else {\n const bundledAt = scanDeliveryPath(cwd, framework.entry ?? undefined)\n checks.push({\n name: 'loader delivery path (version pins reach this app)',\n ok: bundledAt === null,\n ...(bundledAt\n ? {\n hint: `bundled import in ${bundledAt} — change '@mhosaic/feedback' to '@mhosaic/feedback/loader' (same API) so pins land without a redeploy. If a strict CSP forbids the CDN, keep it bundled and set package.json \"mhosaicFeedback\": { \"delivery\": \"bundled\" }`,\n }\n : {}),\n })\n\n // CSP: the loader fetches the widget from the CDN, so a strict script-src\n // must allow it. Only meaningful on the loader path — a bundled app serves\n // the widget same-origin and needs no CDN entry (the check above already\n // tells it to switch). Gating on `!bundledAt` avoids a confusing double\n // warning on apps that haven't migrated yet.\n if (bundledAt === null) {\n const cspBlockedAt = scanCsp(cwd)\n checks.push({\n name: `CSP allows the widget CDN (${CDN_HOST})`,\n ok: cspBlockedAt === null,\n ...(cspBlockedAt\n ? {\n hint: `CSP in ${cspBlockedAt} omits the CDN — add 'https://${CDN_HOST}' to script-src (and the platform origin to connect-src), or if the CSP can't allow an external CDN keep the widget bundled and set package.json \"mhosaicFeedback\": { \"delivery\": \"bundled\" }`,\n }\n : {}),\n })\n }\n }\n\n let hasFailure = false\n for (const c of checks) {\n const icon = c.ok ? kleur.green('✓') : kleur.red('✗')\n process.stdout.write(`${icon} ${c.name}${!c.ok && c.hint ? kleur.gray(' — ' + c.hint) : ''}\\n`)\n if (!c.ok) hasFailure = true\n }\n if (hasFailure) process.exitCode = 1\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mhosaic/feedback-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.0",
|
|
4
4
|
"description": "CLI to install @mhosaic/feedback into a host app, verify the integration, and drop a guided Claude Code skill (/integrate-feedback) into ~/.claude/skills.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/dist/doctor-K2C6LBR2.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
detectFramework
|
|
4
|
-
} from "./chunk-COJ75KUD.js";
|
|
5
|
-
|
|
6
|
-
// src/commands/doctor.ts
|
|
7
|
-
import { existsSync, readFileSync } from "fs";
|
|
8
|
-
import { join } from "path";
|
|
9
|
-
import kleur from "kleur";
|
|
10
|
-
async function runDoctor(argv) {
|
|
11
|
-
const cwd = argv.includes("--cwd") ? argv[argv.indexOf("--cwd") + 1] ?? process.cwd() : process.cwd();
|
|
12
|
-
const checks = [];
|
|
13
|
-
const framework = await detectFramework(cwd);
|
|
14
|
-
checks.push({ name: `framework detected: ${framework.kind}`, ok: framework.kind !== "unknown" && framework.kind !== "plain" });
|
|
15
|
-
const envPath = join(cwd, ".env.local");
|
|
16
|
-
const envOk = existsSync(envPath) && readFileSync(envPath, "utf8").includes("VITE_FEEDBACK_API_KEY=");
|
|
17
|
-
checks.push({ name: ".env.local has VITE_FEEDBACK_API_KEY", ok: envOk, hint: "run `mhosaic-feedback init`" });
|
|
18
|
-
const giPath = join(cwd, ".gitignore");
|
|
19
|
-
const giOk = existsSync(giPath) && readFileSync(giPath, "utf8").includes(".env.local");
|
|
20
|
-
checks.push({ name: ".gitignore ignores .env.local", ok: giOk, hint: "add `.env.local` to .gitignore" });
|
|
21
|
-
let wrapOk = false;
|
|
22
|
-
if (framework.entry) {
|
|
23
|
-
const entryPath = join(cwd, framework.entry);
|
|
24
|
-
if (existsSync(entryPath)) {
|
|
25
|
-
const src = readFileSync(entryPath, "utf8");
|
|
26
|
-
wrapOk = src.includes("<FeedbackProvider") && (src.includes("@mhosaic/feedback/loader/react") || src.includes("@mhosaic/feedback/react"));
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
checks.push({ name: "<FeedbackProvider> wired in entry", ok: wrapOk, hint: "run `mhosaic-feedback init`" });
|
|
30
|
-
let hasFailure = false;
|
|
31
|
-
for (const c of checks) {
|
|
32
|
-
const icon = c.ok ? kleur.green("\u2713") : kleur.red("\u2717");
|
|
33
|
-
process.stdout.write(`${icon} ${c.name}${!c.ok && c.hint ? kleur.gray(" \u2014 " + c.hint) : ""}
|
|
34
|
-
`);
|
|
35
|
-
if (!c.ok) hasFailure = true;
|
|
36
|
-
}
|
|
37
|
-
if (hasFailure) process.exitCode = 1;
|
|
38
|
-
}
|
|
39
|
-
export {
|
|
40
|
-
runDoctor
|
|
41
|
-
};
|
|
42
|
-
//# sourceMappingURL=doctor-K2C6LBR2.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/doctor.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\n\nimport kleur from 'kleur'\n\nimport { detectFramework } from '../detect'\n\nexport async function runDoctor(argv: string[]): Promise<void> {\n const cwd = argv.includes('--cwd') ? argv[argv.indexOf('--cwd') + 1] ?? process.cwd() : process.cwd()\n\n const checks: Array<{ name: string; ok: boolean; hint?: string }> = []\n\n const framework = await detectFramework(cwd)\n checks.push({ name: `framework detected: ${framework.kind}`, ok: framework.kind !== 'unknown' && framework.kind !== 'plain' })\n\n const envPath = join(cwd, '.env.local')\n const envOk = existsSync(envPath) && readFileSync(envPath, 'utf8').includes('VITE_FEEDBACK_API_KEY=')\n checks.push({ name: '.env.local has VITE_FEEDBACK_API_KEY', ok: envOk, hint: 'run `mhosaic-feedback init`' })\n\n const giPath = join(cwd, '.gitignore')\n const giOk = existsSync(giPath) && readFileSync(giPath, 'utf8').includes('.env.local')\n checks.push({ name: '.gitignore ignores .env.local', ok: giOk, hint: 'add `.env.local` to .gitignore' })\n\n let wrapOk = false\n if (framework.entry) {\n const entryPath = join(cwd, framework.entry)\n if (existsSync(entryPath)) {\n const src = readFileSync(entryPath, 'utf8')\n // Accept both the legacy path (`@mhosaic/feedback/react`) and the\n // new loader path (`@mhosaic/feedback/loader/react`) so doctor\n // returns green on already-integrated hosts pre- and post-v0.16.\n wrapOk =\n src.includes('<FeedbackProvider') &&\n (src.includes(\"@mhosaic/feedback/loader/react\") ||\n src.includes(\"@mhosaic/feedback/react\"))\n }\n }\n checks.push({ name: '<FeedbackProvider> wired in entry', ok: wrapOk, hint: 'run `mhosaic-feedback init`' })\n\n let hasFailure = false\n for (const c of checks) {\n const icon = c.ok ? kleur.green('✓') : kleur.red('✗')\n process.stdout.write(`${icon} ${c.name}${!c.ok && c.hint ? kleur.gray(' — ' + c.hint) : ''}\\n`)\n if (!c.ok) hasFailure = true\n }\n if (hasFailure) process.exitCode = 1\n}\n"],"mappings":";;;;;;AAAA,SAAS,YAAY,oBAAoB;AACzC,SAAS,YAAY;AAErB,OAAO,WAAW;AAIlB,eAAsB,UAAU,MAA+B;AAC7D,QAAM,MAAM,KAAK,SAAS,OAAO,IAAI,KAAK,KAAK,QAAQ,OAAO,IAAI,CAAC,KAAK,QAAQ,IAAI,IAAI,QAAQ,IAAI;AAEpG,QAAM,SAA8D,CAAC;AAErE,QAAM,YAAY,MAAM,gBAAgB,GAAG;AAC3C,SAAO,KAAK,EAAE,MAAM,uBAAuB,UAAU,IAAI,IAAI,IAAI,UAAU,SAAS,aAAa,UAAU,SAAS,QAAQ,CAAC;AAE7H,QAAM,UAAU,KAAK,KAAK,YAAY;AACtC,QAAM,QAAQ,WAAW,OAAO,KAAK,aAAa,SAAS,MAAM,EAAE,SAAS,wBAAwB;AACpG,SAAO,KAAK,EAAE,MAAM,wCAAwC,IAAI,OAAO,MAAM,8BAA8B,CAAC;AAE5G,QAAM,SAAS,KAAK,KAAK,YAAY;AACrC,QAAM,OAAO,WAAW,MAAM,KAAK,aAAa,QAAQ,MAAM,EAAE,SAAS,YAAY;AACrF,SAAO,KAAK,EAAE,MAAM,iCAAiC,IAAI,MAAM,MAAM,iCAAiC,CAAC;AAEvG,MAAI,SAAS;AACb,MAAI,UAAU,OAAO;AACnB,UAAM,YAAY,KAAK,KAAK,UAAU,KAAK;AAC3C,QAAI,WAAW,SAAS,GAAG;AACzB,YAAM,MAAM,aAAa,WAAW,MAAM;AAI1C,eACE,IAAI,SAAS,mBAAmB,MAC/B,IAAI,SAAS,gCAAgC,KAC5C,IAAI,SAAS,yBAAyB;AAAA,IAC5C;AAAA,EACF;AACA,SAAO,KAAK,EAAE,MAAM,qCAAqC,IAAI,QAAQ,MAAM,8BAA8B,CAAC;AAE1G,MAAI,aAAa;AACjB,aAAW,KAAK,QAAQ;AACtB,UAAM,OAAO,EAAE,KAAK,MAAM,MAAM,QAAG,IAAI,MAAM,IAAI,QAAG;AACpD,YAAQ,OAAO,MAAM,GAAG,IAAI,IAAI,EAAE,IAAI,GAAG,CAAC,EAAE,MAAM,EAAE,OAAO,MAAM,KAAK,aAAQ,EAAE,IAAI,IAAI,EAAE;AAAA,CAAI;AAC9F,QAAI,CAAC,EAAE,GAAI,cAAa;AAAA,EAC1B;AACA,MAAI,WAAY,SAAQ,WAAW;AACrC;","names":[]}
|