@mhosaic/feedback-cli 0.48.0 → 0.49.1
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 +6 -7
- package/dist/{build-RCLWV2WL.js → build-AHXPNO76.js} +1 -2
- package/dist/{check-FOJPEE4Q.js → check-UZZFPZMA.js} +3 -4
- package/dist/{chunk-COJ75KUD.js → chunk-45QCNACT.js} +0 -1
- package/dist/{chunk-AZQSQJNQ.js → chunk-CFJPCJAQ.js} +0 -1
- package/dist/{chunk-SSLQOK2Z.js → chunk-GJEJSB2Q.js} +2 -3
- package/dist/{chunk-IHJPCMYF.js → chunk-KHQBWJ4A.js} +0 -1
- package/dist/config-75OC57EE.js +7 -0
- package/dist/{doctor-CTL34U4Y.js → doctor-Z4KZBFBN.js} +1 -2
- package/dist/{eject-VNJVOCQM.js → eject-QFPZ7Z3P.js} +1 -2
- package/dist/{generate-VFRQNPOI.js → generate-CLL5W7SX.js} +3 -4
- package/dist/{init-RS7BKALE.js → init-BK6TPKX6.js} +1 -2
- package/dist/{install-skill-BIBDYLKE.js → install-skill-XSUMSGQV.js} +0 -1
- package/dist/{qa-YR4GCV6T.js → qa-APZ5CKN5.js} +9 -10
- package/dist/{sitemap-react-QTEAUKN5.js → sitemap-react-IF5PX2PM.js} +2 -3
- package/dist/{sitemap-vue-EJDQTCYM.js → sitemap-vue-LWK6Y4FE.js} +2 -3
- package/dist/{verify-LCXL3WOX.js → verify-24HTNRXV.js} +0 -1
- package/package.json +1 -1
- package/skills/chantier/SKILL.md +3 -5
- package/skills/feedback-close/SKILL.md +5 -3
- package/skills/feedback-fix/SKILL.md +8 -11
- package/skills/feedback-from-meeting/SKILL.md +25 -12
- package/skills/feedback-pull/SKILL.md +4 -4
- package/skills/feedback-watch-merges/SKILL.md +7 -7
- package/skills/integrate-feedback/SKILL.md +1 -1
- package/skills/integrate-feedback/references/operator-provision.md +5 -5
- package/skills/issue-pull/SKILL.md +5 -3
- package/dist/bin.js.map +0 -1
- package/dist/build-RCLWV2WL.js.map +0 -1
- package/dist/check-FOJPEE4Q.js.map +0 -1
- package/dist/chunk-AZQSQJNQ.js.map +0 -1
- package/dist/chunk-COJ75KUD.js.map +0 -1
- package/dist/chunk-IHJPCMYF.js.map +0 -1
- package/dist/chunk-SSLQOK2Z.js.map +0 -1
- package/dist/config-JE3QRZVH.js +0 -8
- package/dist/config-JE3QRZVH.js.map +0 -1
- package/dist/doctor-CTL34U4Y.js.map +0 -1
- package/dist/eject-VNJVOCQM.js.map +0 -1
- package/dist/generate-VFRQNPOI.js.map +0 -1
- package/dist/init-RS7BKALE.js.map +0 -1
- package/dist/install-skill-BIBDYLKE.js.map +0 -1
- package/dist/qa-YR4GCV6T.js.map +0 -1
- package/dist/sitemap-react-QTEAUKN5.js.map +0 -1
- package/dist/sitemap-vue-EJDQTCYM.js.map +0 -1
- package/dist/verify-LCXL3WOX.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/qa/sitemap-react.ts"],"sourcesContent":["/**\n * react-router adapter for `qa sitemap` — STATIC AST extraction.\n *\n * Reads React Router v7 *declarative* route declarations straight from the\n * TypeScript syntax tree (no module evaluation, no vm, no React). Two readers:\n * - JSX: <Routes><Route path=… /> (e.g. apur/frontend App.tsx)\n * - object: createBrowserRouter([ { path, … } ]) (e.g. mhosaic-core router.tsx)\n * Both emit a normalized ReactRoute tree, mapped to the framework-agnostic\n * RawRoute tree consumed by sitemap-core.buildSitemap.\n *\n * Honest about its limits (spec 2026-06-15 §7): non-literal paths and\n * dynamically-composed object routes are WARNED, never silently dropped.\n */\nimport { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport ts from 'typescript'\nimport { buildSitemap, type RawRoute, type SitemapPayload } from './sitemap-core'\nimport { loadQaConfig } from './config'\n\n/** Normalized, framework-shaped route node produced by a reader. */\nexport interface ReactRoute {\n /** The route's OWN path token, as authored ('app', 'reports/:id', '/login'). */\n path?: string\n /** `<Route index/>` or `{ index: true }`. */\n index?: boolean\n /** The route's `element` is a root `<Navigate/>` (a redirect). */\n isNavigate?: boolean\n children?: ReactRoute[]\n /** Set when the reader saw a node it could not statically resolve. */\n unresolved?: string\n}\n\n// ───────────────────────────────── mapping (§4) ─────────────────────────────\n\n/** True for a react-router catch-all/splat path ('*' or '…/*'). */\nexport function isCatchAllPath(path: string | undefined): boolean {\n return path === '*' || path === '/*' || (path !== undefined && path.endsWith('/*'))\n}\n\n/** Last '/'-segment of a path token; '' / '/' / undefined → 'home'. */\nexport function deriveName(pathToken: string | undefined): string {\n const segs = (pathToken ?? '').split('/').filter(Boolean)\n return segs.at(-1) ?? 'home'\n}\n\n/**\n * Map a normalized ReactRoute tree to the RawRoute tree + collect honesty\n * warnings. Rules (spec §4): drop catch-all / `<Navigate>`-only; `index` → empty\n * path with the parent's derived name; pathless layout (no path, has children) →\n * no name (core marks it `structural`); real pages get a path-derived name.\n */\nexport function mapReactRoutes(roots: ReadonlyArray<ReactRoute>): {\n routes: RawRoute[]\n warnings: string[]\n} {\n const warnings: string[] = []\n\n const visit = (node: ReactRoute, parentPath: string | undefined): RawRoute | null => {\n if (node.unresolved) {\n warnings.push(\n `a route could not be statically resolved (${node.unresolved}) — its subtree is missing from the sitemap`,\n )\n return null\n }\n if (isCatchAllPath(node.path)) return null\n const hasChildren = !!node.children && node.children.length > 0\n if (node.isNavigate && !hasChildren) return null\n\n const childRaws: RawRoute[] = []\n for (const child of node.children ?? []) {\n // index children are named from the nearest pathed ancestor; pass our own\n // path down (undefined for a pathless layout).\n const mapped = visit(child, node.index ? parentPath : node.path)\n if (mapped) childRaws.push(mapped)\n }\n\n // A degenerate node (no path, not index, no surviving children) contributes nothing.\n if (!node.index && node.path === undefined && childRaws.length === 0) return null\n\n const raw: RawRoute = {}\n if (node.index) {\n raw.path = ''\n raw.name = deriveName(parentPath)\n } else if (node.path !== undefined) {\n raw.path = node.path\n raw.name = deriveName(node.path)\n }\n // else: pathless layout → leave path & name undefined (structural in core).\n if (childRaws.length) raw.children = childRaws\n return raw\n }\n\n const routes: RawRoute[] = []\n for (const node of roots) {\n const mapped = visit(node, undefined)\n if (mapped) routes.push(mapped)\n }\n return { routes, warnings }\n}\n\n// ─────────────────────────────────── AST helpers ────────────────────────────\n\ninterface RouterLocals { route?: string; routes?: string; navigate?: string }\n\n/** Local identifiers bound to react-router(-dom) `Route`/`Routes`/`Navigate`. */\nfunction routerLocalNames(sf: ts.SourceFile): RouterLocals {\n const out: RouterLocals = {}\n for (const st of sf.statements) {\n if (!ts.isImportDeclaration(st)) continue\n const spec = st.moduleSpecifier\n if (!ts.isStringLiteral(spec)) continue\n if (spec.text !== 'react-router' && spec.text !== 'react-router-dom') continue\n const named = st.importClause?.namedBindings\n if (!named || !ts.isNamedImports(named)) continue\n for (const el of named.elements) {\n const imported = (el.propertyName ?? el.name).text\n if (imported === 'Route') out.route = el.name.text\n else if (imported === 'Routes') out.routes = el.name.text\n else if (imported === 'Navigate') out.navigate = el.name.text\n }\n }\n return out\n}\n\n/** Static string value of a JSX attribute initializer, or null if non-literal. */\nfunction jsxStringAttr(init: ts.JsxAttributeValue | undefined): string | null {\n if (!init) return null\n if (ts.isStringLiteral(init)) return init.text\n if (ts.isJsxExpression(init) && init.expression) {\n const e = init.expression\n if (ts.isStringLiteral(e) || ts.isNoSubstitutionTemplateLiteral(e)) return e.text\n }\n return null\n}\n\n/** Root JSX tag name of an `element={…}` attribute initializer, or null. */\nfunction jsxElementRootTag(init: ts.JsxAttributeValue | undefined, sf: ts.SourceFile): string | null {\n if (!init || !ts.isJsxExpression(init) || !init.expression) return null\n return jsxRootTag(init.expression, sf)\n}\n\n/** Root JSX tag of an expression (unwrapping parentheses); null if not JSX. */\nfunction jsxRootTag(expr: ts.Expression, sf: ts.SourceFile): string | null {\n let e: ts.Expression = expr\n while (ts.isParenthesizedExpression(e)) e = e.expression\n if (ts.isJsxElement(e)) return e.openingElement.tagName.getText(sf)\n if (ts.isJsxSelfClosingElement(e)) return e.tagName.getText(sf)\n return null\n}\n\n// ─────────────────────────────────── JSX reader ─────────────────────────────\n\n/** Parse `<Routes><Route …/>` JSX into a ReactRoute tree. */\nexport function readJsxRoutes(sf: ts.SourceFile): { roots: ReactRoute[]; warnings: string[] } {\n const warnings: string[] = []\n const local = routerLocalNames(sf)\n\n const isTag = (n: ts.Node, name: string | undefined): boolean => {\n if (!name) return false\n if (ts.isJsxElement(n)) return n.openingElement.tagName.getText(sf) === name\n if (ts.isJsxSelfClosingElement(n)) return n.tagName.getText(sf) === name\n return false\n }\n const attrsOf = (n: ts.JsxElement | ts.JsxSelfClosingElement): ts.NodeArray<ts.JsxAttributeLike> =>\n ts.isJsxElement(n) ? n.openingElement.attributes.properties : n.attributes.properties\n\n const readRoute = (n: ts.JsxElement | ts.JsxSelfClosingElement): ReactRoute => {\n const route: ReactRoute = {}\n for (const a of attrsOf(n)) {\n if (!ts.isJsxAttribute(a)) continue\n const name = a.name.getText(sf)\n if (name === 'index') route.index = true\n else if (name === 'path') {\n const v = jsxStringAttr(a.initializer)\n if (v === null) route.unresolved = 'dynamic-path'\n else route.path = v\n } else if (name === 'element') {\n if (jsxElementRootTag(a.initializer, sf) === local.navigate) route.isNavigate = true\n }\n }\n if (ts.isJsxElement(n)) {\n const kids: ReactRoute[] = []\n for (const child of n.children) {\n if (isTag(child, local.route)) kids.push(readRoute(child as ts.JsxElement | ts.JsxSelfClosingElement))\n }\n if (kids.length) route.children = kids\n }\n return route\n }\n\n const roots: ReactRoute[] = []\n const visit = (n: ts.Node): void => {\n if (isTag(n, local.routes) && ts.isJsxElement(n)) {\n for (const child of n.children) {\n if (isTag(child, local.route)) roots.push(readRoute(child as ts.JsxElement | ts.JsxSelfClosingElement))\n }\n return // consumed this <Routes> subtree\n }\n ts.forEachChild(n, visit)\n }\n visit(sf)\n\n if (!roots.length) warnings.push('no <Routes>/<Route> elements found')\n return { roots, warnings }\n}\n\n// ─────────────────────────────────── object reader ──────────────────────────\n\nconst ROUTER_CREATORS = new Set([\n 'createBrowserRouter', 'createMemoryRouter', 'createHashRouter', 'useRoutes',\n])\n\n/** Property-name text of an object-literal key. */\nfunction propName(name: ts.PropertyName, sf: ts.SourceFile): string {\n if (ts.isIdentifier(name) || ts.isStringLiteral(name)) return name.text\n return name.getText(sf)\n}\n\n/** Static string value of an expression, or null if non-literal. */\nfunction literalString(expr: ts.Expression): string | null {\n if (ts.isStringLiteral(expr) || ts.isNoSubstitutionTemplateLiteral(expr)) return expr.text\n return null\n}\n\n/** One array element → a ReactRoute (or an `unresolved` marker). */\nfunction objectToReactRoute(\n el: ts.Expression,\n navigateName: string | undefined,\n sf: ts.SourceFile,\n): ReactRoute {\n if (ts.isSpreadElement(el)) return { unresolved: 'spread' }\n if (!ts.isObjectLiteralExpression(el)) return { unresolved: 'dynamic' }\n\n const route: ReactRoute = {}\n for (const prop of el.properties) {\n if (!ts.isPropertyAssignment(prop)) continue\n const key = propName(prop.name, sf)\n if (key === 'path') {\n const v = literalString(prop.initializer)\n if (v === null) route.unresolved = 'dynamic-path'\n else route.path = v\n } else if (key === 'index') {\n if (prop.initializer.kind === ts.SyntaxKind.TrueKeyword) route.index = true\n } else if (key === 'element') {\n if (jsxRootTag(prop.initializer, sf) === navigateName) route.isNavigate = true\n } else if (key === 'children') {\n if (ts.isArrayLiteralExpression(prop.initializer)) {\n route.children = prop.initializer.elements.map((c) => objectToReactRoute(c, navigateName, sf))\n } else {\n route.unresolved = 'dynamic-children'\n }\n }\n }\n return route\n}\n\n/** Parse `createBrowserRouter([…])` / a `routes` array into a ReactRoute tree. */\nexport function readDataRoutes(sf: ts.SourceFile): { roots: ReactRoute[]; warnings: string[] } {\n const warnings: string[] = []\n const navigateName = routerLocalNames(sf).navigate\n\n let array: ts.ArrayLiteralExpression | undefined\n const findArray = (n: ts.Node): void => {\n if (array) return\n if (ts.isCallExpression(n) && ts.isIdentifier(n.expression) && ROUTER_CREATORS.has(n.expression.text)) {\n const arg0 = n.arguments[0]\n if (arg0 && ts.isArrayLiteralExpression(arg0)) {\n array = arg0\n return\n }\n }\n ts.forEachChild(n, findArray)\n }\n findArray(sf)\n\n if (!array) {\n for (const st of sf.statements) {\n if (!ts.isVariableStatement(st)) continue\n for (const d of st.declarationList.declarations) {\n const init = d.initializer\n if (ts.isIdentifier(d.name) && d.name.text === 'routes' && init && ts.isArrayLiteralExpression(init)) {\n array = init\n }\n }\n }\n }\n\n if (!array) {\n warnings.push('no createBrowserRouter([…]) call or `routes` array found')\n return { roots: [], warnings }\n }\n\n const roots = array.elements.map((el) => objectToReactRoute(el, navigateName, sf))\n return { roots, warnings }\n}\n\n// ─────────────────────────────────── extractor ──────────────────────────────\n\nexport interface ReactRouterAdapterOptions {\n /** The file holding the route tree (absolute). Preferred. */\n entry?: string\n /** Dir to scan for the entry when `entry` is omitted (absolute). */\n routerDir?: string\n /** Force a reader; otherwise auto-detect from the parsed file. */\n routerStyle?: 'jsx' | 'data'\n}\n\ninterface ExtractResult { payload: SitemapPayload; warnings: string[] }\n\nconst ROUTE_FILE_SIGNALS = /createBrowserRouter|createMemoryRouter|createHashRouter|useRoutes|<Routes\\b/\n\n/** Recursively list candidate router files under `dir` (sorted; tests excluded). */\nfunction scanForRouteFiles(dir: string): string[] {\n const out: string[] = []\n const walk = (d: string): void => {\n let entries: import('node:fs').Dirent[]\n try {\n entries = readdirSync(d, { withFileTypes: true })\n } catch {\n return\n }\n for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {\n if (e.name === 'node_modules' || e.name.startsWith('.')) continue\n const full = join(d, e.name)\n if (e.isDirectory()) {\n walk(full)\n } else if (/\\.(tsx|jsx|ts)$/.test(e.name) && !/\\.(test|spec)\\.[tj]sx?$/.test(e.name)) {\n try {\n if (ROUTE_FILE_SIGNALS.test(readFileSync(full, 'utf8'))) out.push(full)\n } catch {\n // ignore unreadable files\n }\n }\n }\n }\n walk(dir)\n return out\n}\n\n/** Resolve the entry file from opts, or null (with a warning). */\nfunction resolveEntry(opts: ReactRouterAdapterOptions, warnings: string[]): string | null {\n if (opts.entry) {\n if (existsSync(opts.entry) && statSync(opts.entry).isFile()) return opts.entry\n warnings.push(`entry file not found: ${opts.entry}`)\n return null\n }\n if (!opts.routerDir) {\n warnings.push('react-router adapter needs `entry` or `routerDir` in config.sitemap')\n return null\n }\n const candidates = scanForRouteFiles(opts.routerDir)\n const first = candidates[0]\n if (!first) {\n warnings.push(`no router file (with <Routes> or createBrowserRouter) under ${opts.routerDir}`)\n return null\n }\n if (candidates.length > 1) {\n warnings.push(\n `multiple candidate router files under ${opts.routerDir}; using ${first} — set sitemap.entry to disambiguate`,\n )\n }\n return first\n}\n\n/** Auto-detect the reader from the parsed file: a creator call ⇒ 'data', else 'jsx'. */\nfunction detectStyle(sf: ts.SourceFile): 'jsx' | 'data' {\n let isData = false\n const visit = (n: ts.Node): void => {\n if (isData) return\n if (ts.isCallExpression(n) && ts.isIdentifier(n.expression) && ROUTER_CREATORS.has(n.expression.text)) {\n isData = true\n return\n }\n ts.forEachChild(n, visit)\n }\n visit(sf)\n return isData ? 'data' : 'jsx'\n}\n\n/** Internal worker: payload + honesty/IO warnings. */\nexport function extractWithWarnings(opts: ReactRouterAdapterOptions): ExtractResult {\n const warnings: string[] = []\n const entry = resolveEntry(opts, warnings)\n if (!entry) return { payload: { format: 1, pages: [] }, warnings }\n\n const source = readFileSync(entry, 'utf8')\n const sf = ts.createSourceFile(entry, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX)\n\n const style = opts.routerStyle ?? detectStyle(sf)\n const read = style === 'data' ? readDataRoutes(sf) : readJsxRoutes(sf)\n warnings.push(...read.warnings)\n\n const mapped = mapReactRoutes(read.roots)\n warnings.push(...mapped.warnings)\n\n return { payload: buildSitemap(mapped.routes), warnings }\n}\n\n/** Statically extract the react-router sitemap payload (idempotent). */\nexport function extractReactRouterSitemap(opts: ReactRouterAdapterOptions): SitemapPayload {\n return extractWithWarnings(opts).payload\n}\n\n// ─────────────────────────────────── CLI entry ──────────────────────────────\n\n/** CLI: `qa sitemap` for react-router — load config, extract, write, report. */\nexport async function runReactSitemap(_args: string[]): Promise<void> {\n const cfg = loadQaConfig(process.cwd())\n if (!cfg.sitemap || cfg.sitemap.framework !== 'react-router') {\n process.stderr.write(\n 'qa sitemap: config.sitemap.framework must be \"react-router\". ' +\n 'Add a `sitemap` block with `framework: \"react-router\"`, `entry` (or `routerDir`), and `file`.\\n',\n )\n process.exitCode = 1\n return\n }\n if (!cfg.sitemap.entry && !cfg.sitemap.routerDir) {\n process.stderr.write('qa sitemap: react-router adapter needs `entry` (or `routerDir`) in config.sitemap.\\n')\n process.exitCode = 1\n return\n }\n\n const { payload, warnings } = extractWithWarnings({\n ...(cfg.sitemap.entry ? { entry: cfg.sitemap.entry } : {}),\n ...(cfg.sitemap.routerDir ? { routerDir: cfg.sitemap.routerDir } : {}),\n ...(cfg.sitemap.routerStyle ? { routerStyle: cfg.sitemap.routerStyle } : {}),\n })\n\n const outFile = cfg.sitemap.file\n mkdirSync(dirname(outFile), { recursive: true })\n writeFileSync(outFile, `${JSON.stringify(payload, null, 2)}\\n`, 'utf8')\n\n process.stderr.write(`qa sitemap → ${outFile} (${payload.pages.length} pages)\\n`)\n for (const warning of warnings) process.stderr.write(` warning: ${warning}\\n`)\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/qa/sitemap-vue.ts"],"sourcesContent":["/**\n * vue-router adapter for `qa sitemap` (Track A3, generalized from the Vigilance\n * donor `scripts/sync-sitemap.mjs`).\n *\n * Statically evaluates the router declaration files (`routes` export) without\n * booting the app, real `.vue`/store imports, or dev deps (`vite-node`/`tsx`).\n *\n * How: each source file is transpiled ESM/TS → CommonJS with `ts.transpileModule`\n * (the real TS compiler — handles default/named/re-exports, casts, multi-line\n * imports, every syntax for free), then evaluated in a `node:vm` context with a\n * `module`/`exports` sandbox and a custom `require`. The custom require:\n * 1. resolves IN-TREE relative/aliased imports (`./x`, `../x`, `@/x`) to a real\n * file under the router tree and evaluates THAT module recursively (cached by\n * absolute path; cycles return the in-progress `exports`), so sibling route\n * modules contribute their REAL exported arrays — embedded + spread survive;\n * 2. returns a harmless callable STUB Proxy for everything else (`.vue`\n * components, `vue-router`, unresolved aliases, bare/node_modules specifiers)\n * so `component:`/library references never explode.\n *\n * The honesty pass then scans the resolved `routes` tree: any top-level entry that\n * resolved to a STUB (a silently-truncated subtree) is surfaced as a warning.\n */\nimport { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs'\nimport { dirname, isAbsolute, join, resolve } from 'node:path'\nimport vm from 'node:vm'\nimport ts from 'typescript'\nimport { buildSitemap, type RawRoute, type SitemapPayload } from './sitemap-core'\nimport { loadQaConfig } from './config'\n\nexport interface VueRouterAdapterOptions {\n /** Directory holding the router declaration file(s). Absolute. */\n routerDir: string\n /** Optional locale/title JSON; `pages.<routeName>` keys resolve human titles. Absolute. */\n titlesFrom?: string\n /**\n * Optional import-alias map (e.g. `{ '@': '/abs/path/to/src' }`). When a\n * specifier starts with an alias key + '/', it resolves to the mapped absolute\n * base before the in-tree check. Default: none (so `@/…` falls through to the\n * stub).\n */\n alias?: Record<string, string>\n}\n\n/**\n * Replacement value for any non-relevant import (`.vue` components, stores,\n * enums, `vue-router`, lodash…). Callable, constructable, indexable — and never\n * actually read by the builder, which only walks `path`/`name`/`children`.\n * Lifted verbatim from the donor.\n */\nconst STUB: unknown = new Proxy(function stub() {}, {\n get: (_t, prop) => (prop === Symbol.toPrimitive || prop === Symbol.toStringTag ? () => '' : STUB),\n apply: () => STUB,\n construct: () => ({}),\n})\n\n/** True if `v` is the shared STUB Proxy. */\nfunction isStub(v: unknown): boolean {\n return v === STUB\n}\n\n/** Transpile one TS/ESM source to CommonJS via the real compiler (no type-check). */\nfunction transpileToCjs(source: string, fileName: string): string {\n return ts.transpileModule(source, {\n fileName,\n compilerOptions: {\n module: ts.ModuleKind.CommonJS,\n target: ts.ScriptTarget.ES2020,\n esModuleInterop: true,\n isolatedModules: true,\n },\n }).outputText\n}\n\n/**\n * Resolve an import specifier to an absolute file UNDER the router tree, or null\n * if it is out-of-tree / unresolvable (→ caller stubs it).\n *\n * - relative `./x`, `../x` → resolved against the importing file's dir;\n * - aliased `@/x` (when `alias` maps `@`) → resolved against the mapped base;\n * - bare / node_modules / `.vue` / unmapped alias → null.\n *\n * Candidate extensions tried in order: as-is, `.ts`, `.mjs`, `.js`, `/index.ts`,\n * `/index.mjs`, `/index.js`. `.vue` and non-JS/TS asset specifiers (`.json`,\n * `.css`, images, …) are always treated as out-of-tree: the evaluator only\n * transpiles JS/TS, so feeding it raw JSON crashes `ts.transpileModule`\n * (\"Output generation failed\"). Routes never depend on such assets\n * structurally, so stubbing them is the honest, lossless choice.\n */\nfunction resolveInTree(\n specifier: string,\n fromDir: string,\n alias: Record<string, string> | undefined,\n confineDir: string,\n): string | null {\n if (/\\.(vue|json|css|scss|sass|less|svg|png|jpe?g|gif|webp|woff2?|ttf)$/i.test(specifier)) {\n return null\n }\n\n let base: string | null = null\n if (specifier.startsWith('./') || specifier.startsWith('../')) {\n base = resolve(fromDir, specifier)\n } else if (alias) {\n for (const [key, target] of Object.entries(alias)) {\n if (specifier === key || specifier.startsWith(`${key}/`)) {\n const rest = specifier === key ? '' : specifier.slice(key.length + 1)\n base = rest ? join(target, rest) : target\n break\n }\n }\n }\n if (base === null) return null\n\n const candidates = [\n base,\n `${base}.ts`,\n `${base}.mjs`,\n `${base}.js`,\n join(base, 'index.ts'),\n join(base, 'index.mjs'),\n join(base, 'index.js'),\n ]\n for (const cand of candidates) {\n try {\n if (existsSync(cand) && statSync(cand).isFile()) {\n // Confine the follow to the router subtree. Alias maps (`@`) point at\n // the WHOLE `src` tree, so without this an `@/stores/…` or `@/plugins/…`\n // import in a route module would drag the adapter into the app graph —\n // Vite-only code (`import.meta`), real stores, services — that crashes\n // the vm sandbox and is irrelevant to route SHAPE. Route MODULES live\n // under routerDir; everything else (components, stores, models) is a\n // leaf reference the builder never reads, so stubbing it is lossless.\n return isWithin(confineDir, cand) ? cand : null\n }\n } catch {\n // ignore and try the next candidate\n }\n }\n return null\n}\n\n/** True if absolute `child` is `dir` itself or nested under it (path-segment safe). */\nfunction isWithin(dir: string, child: string): boolean {\n const d = resolve(dir)\n const c = resolve(child)\n return c === d || c.startsWith(d.endsWith('/') ? d : `${d}/`)\n}\n\n/**\n * Evaluate a CommonJS-shaped module in a `vm` context, recursively following\n * in-tree `require`s. `cache` keys by absolute path; a module already in-flight\n * (cycle) returns its in-progress `exports`.\n */\nfunction evaluateModule(\n absPath: string,\n alias: Record<string, string> | undefined,\n cache: Map<string, Record<string, unknown>>,\n confineDir: string,\n): Record<string, unknown> {\n const cached = cache.get(absPath)\n if (cached) return cached\n\n const moduleObj: { exports: Record<string, unknown> } = { exports: {} }\n // Register BEFORE evaluating so a cycle resolves to the in-progress exports.\n cache.set(absPath, moduleObj.exports)\n\n const source = readFileSync(absPath, 'utf8')\n const cjs = transpileToCjs(source, absPath)\n const fromDir = dirname(absPath)\n\n const requireFn = (specifier: string): unknown => {\n const resolved = resolveInTree(specifier, fromDir, alias, confineDir)\n if (resolved) {\n const exp = evaluateModule(resolved, alias, cache, confineDir)\n // After evaluating, the module object may have been reassigned\n // (`module.exports = …`); read the freshest value from cache.\n return cache.get(resolved) ?? exp\n }\n return STUB\n }\n\n const context = vm.createContext({\n module: moduleObj,\n exports: moduleObj.exports,\n require: requireFn,\n console,\n })\n vm.runInContext(cjs, context, { filename: absPath })\n\n // `module.exports = …` may have replaced the object; refresh the cache entry.\n cache.set(absPath, moduleObj.exports)\n return moduleObj.exports\n}\n\n/** Locate the router-tree ENTRY file: `<dir>/index.{ts,mjs,js}`, else the first scanned file with a `routes`/`default` array. */\nfunction findEntry(dir: string): string | null {\n for (const name of ['index.ts', 'index.mjs', 'index.js']) {\n const p = join(dir, name)\n if (existsSync(p) && statSync(p).isFile()) return p\n }\n // Fallback: scan recursively, return the first file exporting routes.\n const cache = new Map<string, Record<string, unknown>>()\n for (const file of routerFiles(dir)) {\n try {\n const ns = evaluateModule(file, undefined, cache, dir)\n if (pickRoutes(ns)) return file\n } catch {\n // ignore unreadable candidates during discovery\n }\n }\n return null\n}\n\n/** List candidate router declaration files under `dir` (recursively, sorted). */\nfunction routerFiles(dir: string): string[] {\n const out: string[] = []\n const walk = (d: string): void => {\n let entries: import('node:fs').Dirent[]\n try {\n entries = readdirSync(d, { withFileTypes: true, encoding: 'utf8' })\n } catch {\n return\n }\n for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {\n const full = join(d, entry.name)\n if (entry.isDirectory()) {\n walk(full)\n } else if (/\\.(ts|mjs|js)$/.test(entry.name)) {\n out.push(full)\n }\n }\n }\n walk(dir)\n return out\n}\n\n/** Pull the `routes` array out of an evaluated namespace (named export, then default). */\nfunction pickRoutes(ns: Record<string, unknown>): ReadonlyArray<unknown> | null {\n if (Array.isArray(ns.routes)) return ns.routes as ReadonlyArray<unknown>\n if (Array.isArray(ns.default)) return ns.default as ReadonlyArray<unknown>\n return null\n}\n\n/**\n * Flatten the raw `routes` array: a sibling route MODULE imported as a single\n * embedded element resolves to an ARRAY (`routes: [ adminRoutes, … ]` where\n * `adminRoutes` is `[{…}]`), so we splice nested arrays up one level. Honesty\n * warnings are collected for any top-level entry that is the STUB (a silently\n * truncated import) or otherwise not a plain route object.\n */\nfunction flattenAndCheck(\n raw: ReadonlyArray<unknown>,\n warnings: string[],\n): RawRoute[] {\n const out: RawRoute[] = []\n for (const entry of raw) {\n if (Array.isArray(entry)) {\n // Embedded in-tree route module (its default/named export is an array).\n out.push(...flattenAndCheck(entry, warnings))\n continue\n }\n if (isStub(entry)) {\n warnings.push(\n 'a top-level route entry resolved to a STUB (an import could not be ' +\n 'followed in-tree) — its subtree is missing from the sitemap',\n )\n continue\n }\n if (entry === null || typeof entry !== 'object') {\n warnings.push(\n `a top-level route entry is not a route object (got ${entry === null ? 'null' : typeof entry}) — skipped`,\n )\n continue\n }\n out.push(entry as RawRoute)\n }\n return out\n}\n\n/** Build a `titleFor` resolver from a flat `pages.<routeName>` JSON, or undefined. */\nfunction makeTitleFor(\n titlesFrom: string | undefined,\n): ((routeName: string | undefined, pattern: string) => string | undefined) | undefined {\n if (!titlesFrom || !existsSync(titlesFrom)) return undefined\n let messages: Record<string, unknown> = {}\n try {\n const parsed = JSON.parse(readFileSync(titlesFrom, 'utf8'))\n if (parsed && typeof parsed === 'object') messages = parsed as Record<string, unknown>\n } catch {\n return undefined\n }\n const pages =\n messages.pages && typeof messages.pages === 'object'\n ? (messages.pages as Record<string, unknown>)\n : messages\n return (routeName) => {\n if (!routeName) return undefined\n const v = pages[routeName]\n return typeof v === 'string' ? v : undefined\n }\n}\n\n/** Result of a static extraction: the payload plus any honesty/IO warnings. */\ninterface ExtractResult {\n payload: SitemapPayload\n warnings: string[]\n}\n\n/**\n * Internal worker that also reports warnings (consumed by `runSitemap`).\n * Exported for tests — the public extractor (`extractVueRouterSitemap`) returns\n * only the `SitemapPayload`; this exposes the honesty warnings alongside it.\n * @internal\n */\nexport function extractWithWarnings(opts: VueRouterAdapterOptions): ExtractResult {\n const warnings: string[] = []\n const dir = isAbsolute(opts.routerDir) ? opts.routerDir : resolve(opts.routerDir)\n const entry = findEntry(dir)\n\n let rawRoutes: ReadonlyArray<unknown> | null = null\n if (!entry) {\n warnings.push(`No router entry (index.{ts,mjs,js}) found under ${dir}`)\n } else {\n try {\n const cache = new Map<string, Record<string, unknown>>()\n const ns = evaluateModule(entry, opts.alias, cache, dir)\n rawRoutes = pickRoutes(ns)\n if (!rawRoutes) {\n warnings.push(`No \\`routes\\` export found in ${entry}`)\n }\n } catch (err) {\n warnings.push(`${entry}: ${err instanceof Error ? err.message : String(err)}`)\n }\n }\n\n const routes = rawRoutes ? flattenAndCheck(rawRoutes, warnings) : []\n const titleFor = makeTitleFor(opts.titlesFrom)\n const payload = buildSitemap(routes, titleFor ? { titleFor } : {})\n return { payload, warnings }\n}\n\n/**\n * Statically evaluate the vue-router declarations under `routerDir` and return\n * the doc-03 §3.1 sitemap payload. Pure with respect to the inputs (idempotent).\n */\nexport function extractVueRouterSitemap(opts: VueRouterAdapterOptions): SitemapPayload {\n return extractWithWarnings(opts).payload\n}\n\n/** CLI entry: `qa sitemap` — load config, extract, write pretty JSON, report. */\nexport async function runSitemap(_args: string[]): Promise<void> {\n const cfg = loadQaConfig(process.cwd())\n if (!cfg.sitemap || cfg.sitemap.framework !== 'vue-router') {\n process.stderr.write(\n 'qa sitemap: config.sitemap.framework must be \"vue-router\". ' +\n 'Add a `sitemap` block with `framework: \"vue-router\"`, `routerDir`, and `file` to qa-meter.config.json.\\n',\n )\n process.exitCode = 1\n return\n }\n if (!cfg.sitemap.routerDir) {\n process.stderr.write('qa sitemap: config.sitemap.routerDir is required for the vue-router adapter.\\n')\n process.exitCode = 1\n return\n }\n\n const { payload, warnings } = extractWithWarnings({\n routerDir: cfg.sitemap.routerDir,\n ...(cfg.sitemap.titlesFrom ? { titlesFrom: cfg.sitemap.titlesFrom } : {}),\n ...(cfg.sitemap.alias ? { alias: cfg.sitemap.alias } : {}),\n })\n\n const outFile = cfg.sitemap.file\n mkdirSync(dirname(outFile), { recursive: true })\n writeFileSync(outFile, `${JSON.stringify(payload, null, 2)}\\n`, 'utf8')\n\n process.stderr.write(`qa sitemap → ${outFile} (${payload.pages.length} pages)\\n`)\n for (const warning of warnings) {\n process.stderr.write(` warning: ${warning}\\n`)\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/verify.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\n\nimport kleur from 'kleur'\n\ninterface VerifyArgs {\n cwd: string\n withTestReport: boolean\n origin?: string\n}\n\nfunction parseArgs(argv: string[]): VerifyArgs {\n const out: VerifyArgs = { cwd: process.cwd(), withTestReport: false }\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i]!\n if (a === '--cwd') out.cwd = argv[++i] ?? process.cwd()\n else if (a === '--with-test-report') out.withTestReport = true\n else if (a === '--origin') { const v = argv[++i]; if (v !== undefined) out.origin = v }\n }\n return out\n}\n\ninterface EnvFields {\n apiKey: string | null\n endpoint: string | null\n}\n\nfunction readEnvLocal(cwd: string): EnvFields {\n const path = join(cwd, '.env.local')\n if (!existsSync(path)) return { apiKey: null, endpoint: null }\n const text = readFileSync(path, 'utf8')\n let apiKey: string | null = null\n let endpoint: string | null = null\n for (const line of text.split(/\\r?\\n/)) {\n const m = /^([A-Z0-9_]+)=(.*)$/.exec(line)\n if (!m) continue\n if (m[1] === 'VITE_FEEDBACK_API_KEY') apiKey = m[2]!.trim()\n else if (m[1] === 'VITE_FEEDBACK_ENDPOINT') endpoint = m[2]!.trim()\n }\n return { apiKey, endpoint }\n}\n\ninterface CheckResult {\n name: string\n ok: boolean\n detail?: string | undefined\n hint?: string | undefined\n}\n\nasync function fetchWithTimeout(url: string, init: RequestInit, timeoutMs: number): Promise<Response> {\n const controller = new AbortController()\n const t = setTimeout(() => controller.abort(), timeoutMs)\n try {\n return await fetch(url, { ...init, signal: controller.signal })\n } finally {\n clearTimeout(t)\n }\n}\n\nexport async function runVerify(argv: string[]): Promise<void> {\n const args = parseArgs(argv)\n const checks: CheckResult[] = []\n\n process.stdout.write(kleur.bold('\\n🔍 Mhosaic Feedback verify\\n\\n'))\n\n // 1. .env.local exists\n const env = readEnvLocal(args.cwd)\n checks.push({\n name: '.env.local exists with both keys',\n ok: env.apiKey !== null && env.endpoint !== null,\n hint: 'run `mhosaic-feedback init`',\n })\n\n if (env.apiKey === null || env.endpoint === null) {\n report(checks)\n return\n }\n\n // 2. key format\n checks.push({\n name: 'API key starts with pk_proj_',\n ok: env.apiKey.startsWith('pk_proj_') && env.apiKey.length >= 16,\n detail: env.apiKey.length < 16 ? 'suspiciously short' : undefined,\n hint: 'mint a new key in the admin SPA (Project keys page)',\n })\n\n // 3. endpoint format\n let endpointUrl: URL | null = null\n try {\n endpointUrl = new URL(env.endpoint)\n } catch {\n /* falls through */\n }\n checks.push({\n name: 'Endpoint is a valid http(s) URL',\n ok: endpointUrl !== null && /^https?:$/.test(endpointUrl.protocol),\n detail: endpointUrl ? `${endpointUrl.origin}` : undefined,\n hint: 'should look like https://your-backend.example.com',\n })\n if (endpointUrl === null || !/^https?:$/.test(endpointUrl.protocol)) {\n report(checks)\n return\n }\n\n // 4. endpoint reachable (HEAD root or GET /admin/login/ which always exists)\n let reachable = false\n let reachableDetail = ''\n try {\n const res = await fetchWithTimeout(endpointUrl.origin + '/admin/login/', { method: 'GET' }, 5000)\n reachable = res.status > 0\n reachableDetail = `HTTP ${res.status}`\n } catch (err) {\n reachableDetail = err instanceof Error ? err.message : String(err)\n }\n checks.push({\n name: 'Endpoint reachable over network',\n ok: reachable,\n detail: reachableDetail,\n hint: 'check DNS, firewall, and that the backend is running',\n })\n\n // 5. CORS preflight from the consumer's origin\n // Default origin: from --origin flag, or guessed dev server (http://localhost:5173 — Vite default)\n const origin = args.origin ?? 'http://localhost:5173'\n let corsOk = false\n let corsDetail = ''\n try {\n const res = await fetchWithTimeout(\n endpointUrl.origin + '/api/feedback/v1/reports/',\n {\n method: 'OPTIONS',\n headers: {\n 'Origin': origin,\n 'Access-Control-Request-Method': 'POST',\n 'Access-Control-Request-Headers': 'authorization,content-type',\n },\n },\n 5000,\n )\n const allowOrigin = res.headers.get('access-control-allow-origin')\n corsOk = res.status >= 200 && res.status < 300 && (allowOrigin === origin || allowOrigin === '*')\n corsDetail = allowOrigin ? `allow-origin: ${allowOrigin}` : `HTTP ${res.status}, no allow-origin header`\n } catch (err) {\n corsDetail = err instanceof Error ? err.message : String(err)\n }\n checks.push({\n name: `CORS allows POST from ${origin}`,\n ok: corsOk,\n detail: corsDetail,\n hint: 'add this origin to the project\\'s allowed_origins (admin SPA: Edit project)',\n })\n\n // 6. Optional destructive check: submit a synthetic test report\n if (args.withTestReport) {\n let testReportOk = false\n let testReportDetail = ''\n try {\n const body = new URLSearchParams({\n description: `[mhosaic-feedback verify] CLI smoke test at ${new Date().toISOString()}`,\n feedback_type: 'feature',\n severity: 'low',\n page_url: origin,\n user_agent: `mhosaic-feedback-cli`,\n env: 'dev',\n })\n const res = await fetchWithTimeout(\n endpointUrl.origin + '/api/feedback/v1/reports/',\n {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${env.apiKey}`,\n 'Content-Type': 'application/x-www-form-urlencoded',\n 'Origin': origin,\n },\n body: body.toString(),\n },\n 10_000,\n )\n let json: unknown = null\n try { json = await res.json() } catch { /* non-JSON body */ }\n const reportId = json && typeof json === 'object' && 'id' in json ? (json as { id: string }).id : undefined\n testReportOk = res.status === 201 && Boolean(reportId)\n testReportDetail = testReportOk\n ? `submitted (report id: ${reportId}) — delete it from /reports if you don't want noise`\n : `HTTP ${res.status} — ${typeof json === 'object' ? JSON.stringify(json) : '(no body)'}`\n } catch (err) {\n testReportDetail = err instanceof Error ? err.message : String(err)\n }\n checks.push({\n name: 'POST /api/feedback/v1/reports/ accepts the public key',\n ok: testReportOk,\n detail: testReportDetail,\n hint: '401 = key invalid or revoked; 403 = origin not allowed; 4xx = serializer rejected payload',\n })\n }\n\n report(checks)\n}\n\nfunction report(checks: CheckResult[]): void {\n let failed = 0\n for (const c of checks) {\n const icon = c.ok ? kleur.green('✓') : kleur.red('✗')\n const detail = c.detail ? kleur.gray(` (${c.detail})`) : ''\n const hint = !c.ok && c.hint ? kleur.gray(` — ${c.hint}`) : ''\n process.stdout.write(`${icon} ${c.name}${detail}${hint}\\n`)\n if (!c.ok) failed++\n }\n if (failed > 0) {\n process.stdout.write(kleur.red(`\\n${failed} check(s) failed.\\n`))\n process.exitCode = 1\n } else {\n process.stdout.write(kleur.green('\\nAll checks passed.\\n'))\n }\n}\n"],"mappings":"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","names":[]}
|