@component-compass/cli 0.1.22 → 0.1.24

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.
Files changed (217) hide show
  1. package/dist/cli.js +103503 -104
  2. package/package.json +21 -25
  3. package/dist/auth/browser.d.ts +0 -13
  4. package/dist/auth/browser.js +0 -21
  5. package/dist/auth/browser.js.map +0 -1
  6. package/dist/auth/client.d.ts +0 -48
  7. package/dist/auth/client.js +0 -89
  8. package/dist/auth/client.js.map +0 -1
  9. package/dist/auth/paths.d.ts +0 -2
  10. package/dist/auth/paths.js +0 -14
  11. package/dist/auth/paths.js.map +0 -1
  12. package/dist/auth/session.d.ts +0 -38
  13. package/dist/auth/session.js +0 -88
  14. package/dist/auth/session.js.map +0 -1
  15. package/dist/auth/store.d.ts +0 -16
  16. package/dist/auth/store.js +0 -37
  17. package/dist/auth/store.js.map +0 -1
  18. package/dist/auth/upload-auth.d.ts +0 -22
  19. package/dist/auth/upload-auth.js +0 -51
  20. package/dist/auth/upload-auth.js.map +0 -1
  21. package/dist/cli/help.d.ts +0 -4
  22. package/dist/cli/help.js +0 -52
  23. package/dist/cli/help.js.map +0 -1
  24. package/dist/cli/parse.d.ts +0 -14
  25. package/dist/cli/parse.js +0 -75
  26. package/dist/cli/parse.js.map +0 -1
  27. package/dist/cli/suggest.d.ts +0 -4
  28. package/dist/cli/suggest.js +0 -38
  29. package/dist/cli/suggest.js.map +0 -1
  30. package/dist/cli.d.ts +0 -2
  31. package/dist/cli.js.map +0 -1
  32. package/dist/commands/auth.d.ts +0 -33
  33. package/dist/commands/auth.js +0 -248
  34. package/dist/commands/auth.js.map +0 -1
  35. package/dist/commands/init.d.ts +0 -11
  36. package/dist/commands/init.js +0 -112
  37. package/dist/commands/init.js.map +0 -1
  38. package/dist/commands/scan.d.ts +0 -33
  39. package/dist/commands/scan.js +0 -745
  40. package/dist/commands/scan.js.map +0 -1
  41. package/dist/composition-rollup.d.ts +0 -23
  42. package/dist/composition-rollup.js +0 -64
  43. package/dist/composition-rollup.js.map +0 -1
  44. package/dist/config/loader.d.ts +0 -6
  45. package/dist/config/loader.js +0 -71
  46. package/dist/config/loader.js.map +0 -1
  47. package/dist/config/schema.d.ts +0 -18
  48. package/dist/config/schema.js +0 -15
  49. package/dist/config/schema.js.map +0 -1
  50. package/dist/dispatcher.d.ts +0 -8
  51. package/dist/dispatcher.js +0 -22
  52. package/dist/dispatcher.js.map +0 -1
  53. package/dist/envelope/git.d.ts +0 -13
  54. package/dist/envelope/git.js +0 -69
  55. package/dist/envelope/git.js.map +0 -1
  56. package/dist/envelope/index.d.ts +0 -1
  57. package/dist/envelope/index.js +0 -12
  58. package/dist/envelope/index.js.map +0 -1
  59. package/dist/envelope/repo-id.d.ts +0 -16
  60. package/dist/envelope/repo-id.js +0 -40
  61. package/dist/envelope/repo-id.js.map +0 -1
  62. package/dist/identity.d.ts +0 -56
  63. package/dist/identity.js +0 -115
  64. package/dist/identity.js.map +0 -1
  65. package/dist/index.d.ts +0 -3
  66. package/dist/index.js +0 -3
  67. package/dist/index.js.map +0 -1
  68. package/dist/local-index/declared-props.d.ts +0 -15
  69. package/dist/local-index/declared-props.js +0 -101
  70. package/dist/local-index/declared-props.js.map +0 -1
  71. package/dist/local-index/detect-react.d.ts +0 -3
  72. package/dist/local-index/detect-react.js +0 -378
  73. package/dist/local-index/detect-react.js.map +0 -1
  74. package/dist/local-index/detect-vue.d.ts +0 -16
  75. package/dist/local-index/detect-vue.js +0 -284
  76. package/dist/local-index/detect-vue.js.map +0 -1
  77. package/dist/local-index/detect-wc.d.ts +0 -8
  78. package/dist/local-index/detect-wc.js +0 -70
  79. package/dist/local-index/detect-wc.js.map +0 -1
  80. package/dist/local-index/index.d.ts +0 -14
  81. package/dist/local-index/index.js +0 -46
  82. package/dist/local-index/index.js.map +0 -1
  83. package/dist/local-index/types.d.ts +0 -2
  84. package/dist/local-index/types.js +0 -2
  85. package/dist/local-index/types.js.map +0 -1
  86. package/dist/manifest/barrel-parser.d.ts +0 -35
  87. package/dist/manifest/barrel-parser.js +0 -76
  88. package/dist/manifest/barrel-parser.js.map +0 -1
  89. package/dist/manifest/lazy-resolver.d.ts +0 -123
  90. package/dist/manifest/lazy-resolver.js +0 -712
  91. package/dist/manifest/lazy-resolver.js.map +0 -1
  92. package/dist/occurrences.d.ts +0 -15
  93. package/dist/occurrences.js +0 -101
  94. package/dist/occurrences.js.map +0 -1
  95. package/dist/parse-by-ext.d.ts +0 -21
  96. package/dist/parse-by-ext.js +0 -53
  97. package/dist/parse-by-ext.js.map +0 -1
  98. package/dist/prompts/adapter.d.ts +0 -43
  99. package/dist/prompts/adapter.js +0 -32
  100. package/dist/prompts/adapter.js.map +0 -1
  101. package/dist/reporter/index.d.ts +0 -39
  102. package/dist/reporter/index.js +0 -144
  103. package/dist/reporter/index.js.map +0 -1
  104. package/dist/reporter/json.d.ts +0 -6
  105. package/dist/reporter/json.js +0 -11
  106. package/dist/reporter/json.js.map +0 -1
  107. package/dist/reporter/stdout.d.ts +0 -3
  108. package/dist/reporter/stdout.js +0 -41
  109. package/dist/reporter/stdout.js.map +0 -1
  110. package/dist/rollup.d.ts +0 -26
  111. package/dist/rollup.js +0 -135
  112. package/dist/rollup.js.map +0 -1
  113. package/dist/scan/cem-index.d.ts +0 -38
  114. package/dist/scan/cem-index.js +0 -139
  115. package/dist/scan/cem-index.js.map +0 -1
  116. package/dist/scan/entry-packages.d.ts +0 -42
  117. package/dist/scan/entry-packages.js +0 -87
  118. package/dist/scan/entry-packages.js.map +0 -1
  119. package/dist/scan/framework-root.d.ts +0 -1
  120. package/dist/scan/framework-root.js +0 -22
  121. package/dist/scan/framework-root.js.map +0 -1
  122. package/dist/scan/git-mtime.d.ts +0 -2
  123. package/dist/scan/git-mtime.js +0 -22
  124. package/dist/scan/git-mtime.js.map +0 -1
  125. package/dist/scan/global-components.d.ts +0 -37
  126. package/dist/scan/global-components.js +0 -228
  127. package/dist/scan/global-components.js.map +0 -1
  128. package/dist/scan/meta.d.ts +0 -5
  129. package/dist/scan/meta.js +0 -58
  130. package/dist/scan/meta.js.map +0 -1
  131. package/dist/scan/stamp-version.d.ts +0 -18
  132. package/dist/scan/stamp-version.js +0 -94
  133. package/dist/scan/stamp-version.js.map +0 -1
  134. package/dist/scan/wrap-relationships.d.ts +0 -12
  135. package/dist/scan/wrap-relationships.js +0 -38
  136. package/dist/scan/wrap-relationships.js.map +0 -1
  137. package/dist/seeds.d.ts +0 -81
  138. package/dist/seeds.js +0 -258
  139. package/dist/seeds.js.map +0 -1
  140. package/dist/types/brand.d.ts +0 -11
  141. package/dist/types/brand.js +0 -5
  142. package/dist/types/brand.js.map +0 -1
  143. package/dist/types/component-id.d.ts +0 -2
  144. package/dist/types/component-id.js +0 -2
  145. package/dist/types/component-id.js.map +0 -1
  146. package/dist/types.d.ts +0 -13
  147. package/dist/types.js +0 -2
  148. package/dist/types.js.map +0 -1
  149. package/dist/upload.d.ts +0 -18
  150. package/dist/upload.js +0 -34
  151. package/dist/upload.js.map +0 -1
  152. package/dist/util/color.d.ts +0 -24
  153. package/dist/util/color.js +0 -45
  154. package/dist/util/color.js.map +0 -1
  155. package/dist/util/git.d.ts +0 -10
  156. package/dist/util/git.js +0 -95
  157. package/dist/util/git.js.map +0 -1
  158. package/dist/util/interactive.d.ts +0 -17
  159. package/dist/util/interactive.js +0 -19
  160. package/dist/util/interactive.js.map +0 -1
  161. package/dist/util/log.d.ts +0 -9
  162. package/dist/util/log.js +0 -20
  163. package/dist/util/log.js.map +0 -1
  164. package/dist/util/pkg.d.ts +0 -1
  165. package/dist/util/pkg.js +0 -13
  166. package/dist/util/pkg.js.map +0 -1
  167. package/dist/util/pnp-check.d.ts +0 -1
  168. package/dist/util/pnp-check.js +0 -6
  169. package/dist/util/pnp-check.js.map +0 -1
  170. package/dist/util/posix.d.ts +0 -2
  171. package/dist/util/posix.js +0 -15
  172. package/dist/util/posix.js.map +0 -1
  173. package/dist/util/progress.d.ts +0 -21
  174. package/dist/util/progress.js +0 -54
  175. package/dist/util/progress.js.map +0 -1
  176. package/dist/util/version.d.ts +0 -7
  177. package/dist/util/version.js +0 -13
  178. package/dist/util/version.js.map +0 -1
  179. package/dist/walker/bounded-definition-resolver.d.ts +0 -9
  180. package/dist/walker/bounded-definition-resolver.js +0 -125
  181. package/dist/walker/bounded-definition-resolver.js.map +0 -1
  182. package/dist/walker/files.d.ts +0 -7
  183. package/dist/walker/files.js +0 -24
  184. package/dist/walker/files.js.map +0 -1
  185. package/dist/walker/package-alias-layers.d.ts +0 -7
  186. package/dist/walker/package-alias-layers.js +0 -40
  187. package/dist/walker/package-alias-layers.js.map +0 -1
  188. package/dist/walker/resolve-import.d.ts +0 -25
  189. package/dist/walker/resolve-import.js +0 -243
  190. package/dist/walker/resolve-import.js.map +0 -1
  191. package/dist/walker/tsconfig-discovery.d.ts +0 -36
  192. package/dist/walker/tsconfig-discovery.js +0 -52
  193. package/dist/walker/tsconfig-discovery.js.map +0 -1
  194. package/dist/walker/tsconfig-loader.d.ts +0 -35
  195. package/dist/walker/tsconfig-loader.js +0 -138
  196. package/dist/walker/tsconfig-loader.js.map +0 -1
  197. package/dist/walker/workspace-exports.d.ts +0 -3
  198. package/dist/walker/workspace-exports.js +0 -120
  199. package/dist/walker/workspace-exports.js.map +0 -1
  200. package/dist/workspace/build-graph.d.ts +0 -15
  201. package/dist/workspace/build-graph.js +0 -120
  202. package/dist/workspace/build-graph.js.map +0 -1
  203. package/dist/workspace/declared-deps.d.ts +0 -5
  204. package/dist/workspace/declared-deps.js +0 -42
  205. package/dist/workspace/declared-deps.js.map +0 -1
  206. package/dist/workspace/find-owning-package.d.ts +0 -8
  207. package/dist/workspace/find-owning-package.js +0 -69
  208. package/dist/workspace/find-owning-package.js.map +0 -1
  209. package/dist/workspace/find-workspace-root.d.ts +0 -1
  210. package/dist/workspace/find-workspace-root.js +0 -60
  211. package/dist/workspace/find-workspace-root.js.map +0 -1
  212. package/dist/workspace/index.d.ts +0 -4
  213. package/dist/workspace/index.js +0 -4
  214. package/dist/workspace/index.js.map +0 -1
  215. package/dist/workspace/types.d.ts +0 -49
  216. package/dist/workspace/types.js +0 -13
  217. package/dist/workspace/types.js.map +0 -1
@@ -1,745 +0,0 @@
1
- import { performance } from "node:perf_hooks";
2
- import { isAbsolute, relative, resolve } from "node:path";
3
- import { readFile } from "node:fs/promises";
4
- import { loadConfig, ConfigError } from "../config/loader.js";
5
- import { createLazyResolver } from "../manifest/lazy-resolver.js";
6
- import { buildLocalIndex } from "../local-index/index.js";
7
- import { detectReactComponents } from "../local-index/detect-react.js";
8
- import { detectVueComponents } from "../local-index/detect-vue.js";
9
- import { detectWebComponents } from "../local-index/detect-wc.js";
10
- import { parseByExt } from "../parse-by-ext.js";
11
- import { createProgress } from "../util/progress.js";
12
- import { walkFiles } from "../walker/files.js";
13
- import { createImportResolver } from "../walker/resolve-import.js";
14
- import { resolveTsconfigPath } from "../walker/tsconfig-discovery.js";
15
- import { buildPackageAliasLayers } from "../walker/package-alias-layers.js";
16
- import { Dispatcher } from "../dispatcher.js";
17
- import { reactPlugin, emitReact } from "@component-compass/parser-react";
18
- import { htmlPlugin } from "@component-compass/parser-html";
19
- import { litTemplatePlugin } from "@component-compass/parser-lit";
20
- import { emitVueTemplate } from "@component-compass/parser-vue";
21
- import { writeJson } from "../reporter/json.js";
22
- import { printSummary } from "../reporter/stdout.js";
23
- import { stampMeta } from "../scan/meta.js";
24
- import { stampGitMTime } from "../scan/git-mtime.js";
25
- import { stampVersion } from "../scan/stamp-version.js";
26
- import { applyWrapRelationships } from "../scan/wrap-relationships.js";
27
- import { applyEntryPackages, createEntryPackageRecorder } from "../scan/entry-packages.js";
28
- import { buildCemIndex } from "../scan/cem-index.js";
29
- import { isPnpProject } from "../util/pnp-check.js";
30
- import { readGitToplevel } from "../util/git.js";
31
- import { findWorkspaceRoot } from "../workspace/find-workspace-root.js";
32
- import { Logger } from "../util/log.js";
33
- import { errorMessage } from "@component-compass/ast-utils";
34
- import { createGraphBuilder, resolve as resolveGraph, } from "@component-compass/reference-graph";
35
- import { attachManifestEnrichment, buildComponentSeeds } from "../seeds.js";
36
- import { parseResultsToOccurrences } from "../occurrences.js";
37
- import { computeStableIdFromComponentId, computeOutputIdentity } from "../identity.js";
38
- import { buildScanOutput } from "../reporter/index.js";
39
- import { buildWorkspaceGraph, declaredDeps, findOwningPackage, resetFindOwningPackageCache, } from "../workspace/index.js";
40
- import { createBoundedDefinitionResolver } from "../walker/bounded-definition-resolver.js";
41
- import { loadGlobalComponents, detectAutoImportFramework } from "../scan/global-components.js";
42
- import { createDiagnosticCollector, computeOccurrenceId, externalPackageOf, posixPath } from "@component-compass/plugin-core";
43
- /** Map a scan result to a process exit code: 2 = scan failed, 1 = requested upload failed, 0 = ok. */
44
- export function scanExitCode(result) {
45
- if (result.output === null)
46
- return 2;
47
- if (result.upload === "failed")
48
- return 1;
49
- return 0;
50
- }
51
- export async function runScan(opts) {
52
- const log = new Logger(opts.quiet);
53
- const t0 = performance.now();
54
- const configPath = opts.configPath ??
55
- resolve(opts.cwd ?? process.cwd(), "component-compass.config.json");
56
- let cfg;
57
- try {
58
- cfg = await loadConfig(configPath);
59
- }
60
- catch (err) {
61
- if (err instanceof ConfigError) {
62
- log.error(err.message);
63
- return { output: null, upload: "skipped" };
64
- }
65
- throw err;
66
- }
67
- if (isPnpProject(cfg.configDir)) {
68
- log.error("yarn PnP detected; component-compass requires nodeLinker: node-modules.");
69
- return { output: null, upload: "skipped" };
70
- }
71
- // Stamp meta early — throws if not a git work tree, which is a hard
72
- // prerequisite for a stable repo identity.
73
- const metaOpts = { cwd: cfg.configDir };
74
- const repoIdOverride = opts.repoId ?? cfg.repoId;
75
- if (repoIdOverride !== undefined)
76
- metaOpts.repoIdOverride = repoIdOverride;
77
- const meta = await stampMeta(metaOpts);
78
- // Output path base. An explicit `repoRoot` override wins;
79
- // otherwise the git toplevel when the scan dir sits inside a work tree; else
80
- // configDir (non-git or pathological cases).
81
- // Guard: outputRoot must contain configDir, else re-basing would produce
82
- // `..`-prefixed paths for every file.
83
- const gitToplevel = await readGitToplevel(cfg.configDir);
84
- const outputRoot = opts.repoRoot
85
- ? (isAbsolute(opts.repoRoot)
86
- ? opts.repoRoot
87
- : resolve(opts.cwd ?? process.cwd(), opts.repoRoot))
88
- : (gitToplevel && !relative(gitToplevel, cfg.configDir).startsWith("..")
89
- ? gitToplevel
90
- : cfg.configDir);
91
- // Re-base a scanRoot-relative POSIX path into outputRoot-relative POSIX.
92
- // Identity when the roots coincide.
93
- const rebase = (scanRel) => outputRoot === cfg.configDir
94
- ? scanRel
95
- : posixPath(relative(outputRoot, resolve(cfg.configDir, scanRel)));
96
- if (opts.outputOverride)
97
- cfg.output = opts.outputOverride;
98
- const writer = opts.quiet ? () => { } : (s) => process.stderr.write(s);
99
- // Ownership/resolution anchor. Walks up from configDir to the nearest
100
- // enclosing workspace root (bounded by outputRoot), so scanning a monorepo
101
- // subfolder still resolves against the real hoisted node_modules / tsconfig
102
- // instead of a partial view rooted at the scan dir. Falls back to configDir
103
- // when no enclosing workspace claims it (the common case — existing
104
- // fixtures all scan from their own root).
105
- const workspaceRoot = findWorkspaceRoot(cfg.configDir, outputRoot) ?? cfg.configDir;
106
- if (workspaceRoot !== cfg.configDir) {
107
- writer(`[scan] workspace root: ${workspaceRoot}\n`);
108
- }
109
- // Reset per-scan caches so in-process invocations (tests, watch mode) start
110
- // clean. Memoized realpath results from a prior scan would still be valid,
111
- // but explicit reset avoids state leakage if the filesystem changed.
112
- resetFindOwningPackageCache();
113
- const workspaceGraph = buildWorkspaceGraph(workspaceRoot);
114
- const resolveImportOpts = {
115
- repoRoot: workspaceRoot,
116
- workspaceGraph,
117
- onWarning: (msg) => writer(`[scan] tsconfig: ${msg}\n`),
118
- };
119
- if (cfg.aliases)
120
- resolveImportOpts.aliases = cfg.aliases;
121
- resolveImportOpts.packageAliasLayers = buildPackageAliasLayers(workspaceGraph, (msg) => writer(`[scan] tsconfig(workspace): ${msg}\n`));
122
- const tsconfigPath = resolveTsconfigPath({
123
- configDir: cfg.configDir,
124
- repoRoot: workspaceRoot,
125
- ...(cfg.tsconfigPath !== undefined ? { tsconfigPath: cfg.tsconfigPath } : {}),
126
- });
127
- if (tsconfigPath)
128
- resolveImportOpts.tsconfigPath = tsconfigPath;
129
- const resolveImport = createImportResolver(resolveImportOpts);
130
- const collector = createDiagnosticCollector();
131
- // Build global CEM index once per scan. Enumerates every package under
132
- // <root>/node_modules, finds CEM/CCM files, and builds byTag + byPackageName
133
- // maps so parsers can resolve hyphenated tags without consumer-side config.
134
- // Parsers consume cemIndex.
135
- const cemIndex = await buildCemIndex(workspaceRoot);
136
- // Identity resolution (leafPackage + modulePath) runs unconditionally for
137
- // external imports. `manifestPatterns` is the per-package gate for manifest
138
- // IO only. `["**"]` opts every package in — picomatch's `*` won't cross `/`
139
- // so it misses scoped names, but `**` does. The workspace gate inside
140
- // extractDerived (realpathSync check for node_modules in path) decides which
141
- // packages actually get extractor enrichment; published packages bail
142
- // immediately, so there is no meaningful performance cost for the wider gate.
143
- const manifestPatterns = ["**"];
144
- // Entry-package provenance capture. Fed by the resolver's walk for
145
- // both lanes (JS relabel via lookupExternalLeaf, side-effect priming via
146
- // resolveLazyManifest); drained into applyEntryPackages post-resolve.
147
- const entryPackageRecorder = createEntryPackageRecorder();
148
- const lazyResolver = createLazyResolver({
149
- resolveImport,
150
- repoRoot: workspaceRoot,
151
- collector,
152
- manifestPatterns,
153
- onEntryPackage: entryPackageRecorder.record,
154
- });
155
- // Warm-up: load each declared dep's root CEM/CCM (if any) so tag-only
156
- // consumers (HTML files, Vue templates with no JS-side import for the tag)
157
- // can resolve external custom elements via lookupByTag. Cheap (one
158
- // package.json + one manifest read per dep) and matches the lazy
159
- // resolver's terminal-load step in depth — no barrel walking, no
160
- // re-export chasing. `.warm()` returns a WarmedResolver whose lookupByTag
161
- // is the only typed access to the byTag index.
162
- const declared = declaredDeps(workspaceGraph);
163
- const warmedResolver = lazyResolver.warm(declared);
164
- const files = await walkFiles({
165
- root: cfg.configDir,
166
- include: cfg.include,
167
- exclude: cfg.exclude,
168
- gitignore: cfg.gitignore,
169
- });
170
- if (files.length === 0) {
171
- log.warn(`No files matched include globs: ${cfg.include.join(", ")}`);
172
- }
173
- // Sort files so JS/TS/Vue process before HTML; this primes the by-tag cache
174
- // in the lazy resolver before plain HTML files (which only have tag names,
175
- // no import specifiers) need to look up custom elements.
176
- const orderedFiles = [...files].sort((a, b) => {
177
- const aHtml = /\.html?$/.test(a) ? 1 : 0;
178
- const bHtml = /\.html?$/.test(b) ? 1 : 0;
179
- return aHtml - bHtml;
180
- });
181
- const dispatcher = new Dispatcher([reactPlugin, htmlPlugin, litTemplatePlugin]);
182
- const results = [];
183
- // Single-pass scan: one IO + one parse per file, shared across the local-
184
- // index build and plugin dispatch. ASTs are cached for the gap between the
185
- // two phases, then evicted post-dispatch to bound peak memory on large
186
- // monorepos. Two progress reporters cover each phase so the user sees both
187
- // legs — phase 2's plugin dispatch can dominate on JSX-heavy repos, and a
188
- // single phase-1-only reporter looked frozen at 99% during phase 2.
189
- if (tsconfigPath) {
190
- const rel = relative(cfg.configDir, tsconfigPath);
191
- writer(`[scan] using tsconfig: ${rel || tsconfigPath}\n`);
192
- }
193
- else {
194
- writer("[scan] tsconfig: not found (path aliases will not resolve)\n");
195
- }
196
- const isTTY = !!process.stderr.isTTY;
197
- const parseProgress = createProgress({
198
- total: orderedFiles.length,
199
- writer,
200
- isTTY,
201
- label: "Parsing",
202
- });
203
- const astCache = new Map();
204
- const sourceCache = new Map();
205
- const localDefs = [];
206
- // Graph builder for the reference-graph engine. Populated during Phase 1
207
- // (alongside the local-definition scan) so both the local index and the
208
- // graph see every file exactly once. The engine's Resolve phase runs after
209
- // Phase 2's plugin dispatch and its output is merged with the
210
- // Vue/Lit/HTML occurrences before Assemble.
211
- const graphBuilder = createGraphBuilder({ moduleResolver: resolveImport, repoRoot: cfg.configDir });
212
- // Generated GlobalComponents declarations: the Vue-ecosystem
213
- // registry of globally-available components (Nuxt writes one). Loaded once
214
- // per scan; misses degrade to diagnostics, never gates.
215
- const autoImports = loadGlobalComponents(cfg.configDir);
216
- if (autoImports !== null) {
217
- for (const s of autoImports.stale) {
218
- collector.emit({
219
- code: "auto-import-stale-entry",
220
- severity: "warning",
221
- filePath: autoImports.declarationPath,
222
- componentName: s.componentName,
223
- // s.target is absolute (global-components.ts resolves in-repo
224
- // entries to an absolute path before checking existence) — emit
225
- // repo-relative POSIX so the artefact stays portable across machines,
226
- // matching the convention used for occurrence/component filePaths.
227
- target: posixPath(relative(cfg.configDir, s.target)),
228
- });
229
- }
230
- }
231
- else {
232
- const framework = detectAutoImportFramework(cfg.configDir);
233
- if (framework !== null) {
234
- const missing = {
235
- code: "auto-import-manifest-missing",
236
- severity: "warning",
237
- filePath: framework.expectedPath,
238
- detail: "auto-import framework detected but no components manifest was found — auto-imported components can't be resolved. Run `nuxt prepare` (or any dev/build) and re-scan.",
239
- };
240
- collector.emit(missing);
241
- // Surface at scan time, not only in the artifact.
242
- log.warn(formatDiagnostic(missing));
243
- }
244
- }
245
- // ── Phase 1: read + parse + emit local definitions ──────────────────────
246
- for (const file of orderedFiles) {
247
- const source = await readFile(file, "utf8").catch(() => "");
248
- if (!source) {
249
- parseProgress.tick();
250
- continue;
251
- }
252
- let parsed;
253
- try {
254
- parsed = parseByExt(file, source);
255
- }
256
- catch (err) {
257
- // parseByExt throws on unrecoverable syntax (EOF mid-expression, malformed
258
- // SFC). Mirror the plugin-loop's warn-and-continue pattern so one broken
259
- // file can't tank the scan.
260
- log.warn(`[component-compass] parse failure in ${file}: ${errorMessage(err)}`);
261
- parseProgress.tick();
262
- continue;
263
- }
264
- // `unsupported` extensions never reach phase 2; skip caching to bound
265
- // peak memory on scans whose include glob covers unsupported file types.
266
- if (parsed.kind === "unsupported") {
267
- parseProgress.tick();
268
- continue;
269
- }
270
- astCache.set(file, parsed);
271
- sourceCache.set(file, source);
272
- // Detectors record `componentId.source.filePath` as POSIX repo-relative so
273
- // it joins with seeds + parser occurrences without a normalisation step.
274
- const relPath = posixPath(relative(cfg.configDir, file));
275
- // Detectors feed the local-definition index, which is output-space
276
- // Rebase to outputRoot before handing paths to them.
277
- const outRel = rebase(relPath);
278
- if (parsed.kind === "babel") {
279
- localDefs.push(...detectReactComponents(parsed.ast, outRel, parsed.source));
280
- localDefs.push(...detectWebComponents(parsed.ast, outRel));
281
- // Vue script-mode catches `defineComponent` in plain .ts/.js, mirroring
282
- // local-index/index.ts routing for non-SFC files.
283
- localDefs.push(...detectVueComponents({ kind: "script", program: parsed.ast, source: parsed.source }, outRel));
284
- // Emit JSX references into the graph for later resolution by the engine.
285
- // relPath is repo-relative POSIX — the same form used for FileGraph keys.
286
- try {
287
- const fileBuilder = graphBuilder.beginFile(relPath);
288
- emitReact({
289
- file: relPath,
290
- source,
291
- ast: parsed.ast,
292
- fileBuilder,
293
- // Side-effect imports prime the lazy resolver: mirrors the Vue
294
- // lane's priming (emit-template.ts), warming byTag for tag
295
- // resolution and feeding entry-package provenance.
296
- // exportName "" = side-effect signal. Anchor is absolute here,
297
- // which resolveLeaf handles identically — the Vue lane
298
- // (emit-template.ts, below) anchors absolute too, for the same
299
- // reason.
300
- onSideEffectImport: (spec) => {
301
- warmedResolver.resolveLazyManifest(file, spec, "");
302
- },
303
- });
304
- }
305
- catch (err) {
306
- log.warn(`[component-compass] emit failure in ${file}: ${errorMessage(err)}`);
307
- }
308
- }
309
- else if (parsed.kind === "vue") {
310
- const wrapper = parsed.scriptAst !== undefined
311
- ? { descriptor: parsed.descriptor, scriptProgram: parsed.scriptAst }
312
- : { descriptor: parsed.descriptor };
313
- const sfcDefs = detectVueComponents({ kind: "sfc", wrapper }, outRel);
314
- localDefs.push(...sfcDefs);
315
- try {
316
- const fileBuilder = graphBuilder.beginFile(relPath, "vue");
317
- emitVueTemplate({
318
- file: relPath,
319
- source,
320
- wrapper,
321
- fileBuilder,
322
- // Anchor on the absolute `file`, discarding the emitter's own
323
- // repo-relative `_from` (relPath, relative to cfg.configDir).
324
- // resolveLazyManifest ultimately calls resolveImport(from, spec),
325
- // which rebases a non-absolute `from` onto the import resolver's
326
- // repoRoot — workspaceRoot, not cfg.configDir (walker/resolve-
327
- // import.ts). Those coincide for every scan rooted at its own
328
- // workspace root, but whenever cfg.configDir sits in a workspace
329
- // subdirectory (consumer-ci's per-app targets), rebasing the
330
- // relative anchor onto workspaceRoot produces a path that doesn't
331
- // exist, and the node_modules walk starts from the wrong
332
- // directory — nested per-app deps become unreachable. The
333
- // React/babel lane (emitReact's onSideEffectImport, above) already
334
- // anchors on the absolute `file` for the same reason; mirror it
335
- // here instead of "simplifying" this back to the emitter's arg.
336
- resolveLazyManifest: (_from, spec, exportName) => warmedResolver.resolveLazyManifest(file, spec, exportName),
337
- // Reuse detectVueComponents' resolved symbol so the SFC's local-index
338
- // seed and the engine's owner ComponentId hash to the same id —
339
- // otherwise the SFC seed is pruned as an unreachable local.
340
- ...(sfcDefs[0]?.exportName !== undefined ? { sfcSymbol: sfcDefs[0].exportName } : {}),
341
- ...(autoImports !== null ? { resolveAutoImport: autoImports.lookup } : {}),
342
- collector,
343
- });
344
- }
345
- catch (err) {
346
- log.warn(`[component-compass] emit failure in ${file}: ${errorMessage(err)}`);
347
- }
348
- }
349
- parseProgress.tick();
350
- }
351
- parseProgress.done();
352
- const localIndex = buildLocalIndex(localDefs, collector);
353
- // ── Phase 2: dispatch plugins with shared oxc Program through ctx.ast ────
354
- // walkProgress total is the cached-file count: files dropped by phase 1
355
- // (empty source / parse failure / unsupported extension) aren't in the
356
- // cache and won't be walked.
357
- const walkProgress = createProgress({
358
- total: astCache.size,
359
- writer,
360
- isTTY,
361
- label: "Walking",
362
- });
363
- for (const file of orderedFiles) {
364
- const parsed = astCache.get(file);
365
- const source = sourceCache.get(file);
366
- if (!parsed || parsed.kind === "unsupported" || source === undefined)
367
- continue;
368
- // Carrier shape per file-kind; plugins narrow internally via their own
369
- // ctx.ast cast. `babel` → oxc Program, `vue` → VueParseWrapper,
370
- // `html` → parse5 Document. Unsupported never reaches here.
371
- const astForCtx = parsed.kind === "babel" ? parsed.ast :
372
- parsed.kind === "vue" ? (parsed.scriptAst !== undefined
373
- ? { descriptor: parsed.descriptor, scriptProgram: parsed.scriptAst }
374
- : { descriptor: parsed.descriptor }) :
375
- parsed.kind === "html" ? parsed.document :
376
- undefined;
377
- for (const plugin of dispatcher.allPluginsFor(file)) {
378
- try {
379
- results.push(plugin.parse(file, source, {
380
- localIndex,
381
- config: cfg,
382
- resolveImport,
383
- resolveLazyManifest: warmedResolver.resolveLazyManifest,
384
- resolveLocalExport: warmedResolver.resolveLocalExport,
385
- lookupByTag: warmedResolver.lookupByTag,
386
- repoRoot: outputRoot,
387
- collector,
388
- ast: astForCtx,
389
- cemIndex,
390
- }));
391
- }
392
- catch (err) {
393
- log.warn(`[component-compass] parse failure in ${file}: ${errorMessage(err)}`);
394
- }
395
- }
396
- // Evict after dispatch — keeps peak heap proportional to one file, not
397
- // the whole repo, on large monorepos.
398
- astCache.delete(file);
399
- sourceCache.delete(file);
400
- walkProgress.tick();
401
- }
402
- walkProgress.done();
403
- // ── Resolve phase: engine walks the populated graph ──────────────────────
404
- // Produces EngineOccurrence[] for every component reference in the graph:
405
- // external packages, workspace siblings, local symbols, and relative-
406
- // specifier imports alike.
407
- //
408
- // tagIndex serves tag-based emitters (parser-vue, parser-lit,
409
- // parser-html). External lookups try the warmed lazy
410
- // resolver first (populated by `.warm()`'s declared-dep eager-load and by
411
- // side-effect-import priming, both re-export-aware — sees barrels the
412
- // static CemIndex glob can't reach, e.g. nested node_modules leaves),
413
- // falling back to CemIndex.byTag for tags whose package was never imported
414
- // or warmed. Local lookups go through the workspace-WC entries in
415
- // LocalDefinitionIndex.byTag.
416
- // The collector is the same shared one parsers emit through; the engine adds
417
- // its own diagnostics (low-confidence-tag, etc.).
418
- const tagIndex = {
419
- externalByTag: (tag) => {
420
- const warmed = warmedResolver.lookupByTag(tag);
421
- if (warmed && warmed.source.type === "external") {
422
- return { packageName: externalPackageOf(warmed.source) };
423
- }
424
- const hit = cemIndex.byTag.get(tag);
425
- if (!hit)
426
- return undefined;
427
- return { packageName: hit.packageName };
428
- },
429
- localByTag: (tag) => {
430
- const hit = localIndex.byTag.get(tag);
431
- if (!hit || hit.componentId.source.type !== "local")
432
- return undefined;
433
- return { filePath: hit.componentId.source.filePath, exportName: hit.exportName };
434
- },
435
- };
436
- // Bounded re-export resolver pins an un-walked workspace member's identity to
437
- // its definition file so ids are scope-invariant between whole-repo and
438
- // single-app scans. Only invoked for out-of-graph members.
439
- const resolveLocalDefinition = createBoundedDefinitionResolver({
440
- moduleResolver: resolveImport,
441
- onWarning: (msg) => writer(`[scan] ${msg}\n`),
442
- });
443
- const resolveOpts = {
444
- wrapCallback: (pkg, exportName) => warmedResolver.lookupReactWrapper(pkg, exportName),
445
- // Follow external aggregator barrels to the leaf package that
446
- // defines the export. Graph keys are configDir-relative — rebase before
447
- // handing to the resolver's Node-resolution walk. Same-package hits are
448
- // ignored by the engine but still terminal-load the leaf CEM, which is
449
- // what makes wrapCallback hit for undeclared-transitive direct imports.
450
- // Uses `lookupExternalLeaf` (not `resolveLazyManifest`) so these
451
- // engine-driven visits stay out of `snapshotComponents()` /
452
- // `snapshotManifestRefs()` — otherwise an undeclared package's full CEM
453
- // catalog gets broadcast as zero-occurrence components.
454
- resolveExternalLeaf: (fromFile, specifier, exportName) => {
455
- const absFrom = isAbsolute(fromFile) ? fromFile : resolve(cfg.configDir, fromFile);
456
- const hit = warmedResolver.lookupExternalLeaf(absFrom, specifier, exportName);
457
- if (!hit)
458
- return null;
459
- return { leafPackage: hit.leafPackage, modulePath: hit.modulePath, exportName: hit.exportName };
460
- },
461
- tagIndex,
462
- collector,
463
- };
464
- // Workspace-membership seam: findOwningPackage is the ONE membership
465
- // predicate (docs/CONTEXT.md). Never re-derive "is this path first-party".
466
- // Hooks attach at build() time so both engine lane-classification sites
467
- // (direct-import branch and wrapper-folding's composition fallbacks)
468
- // reach them through the Graph.
469
- const graph = graphBuilder.build({
470
- workspaceMembership: (p) => findOwningPackage(workspaceGraph, p) !== null,
471
- resolveLocalDefinition,
472
- });
473
- const { occurrences: engineOccurrences, registry } = resolveGraph(graph, resolveOpts);
474
- const t1 = performance.now();
475
- // Drain the shared collector.
476
- const rawDiagnostics = collector.drain();
477
- if (!opts.quiet) {
478
- for (const d of rawDiagnostics) {
479
- log.warn(formatDiagnostic(d));
480
- }
481
- }
482
- // Normalise diagnostic filePaths to repo-relative POSIX so the artefact is
483
- // portable across machines (matches the convention used for occurrences and
484
- // component identity). Warn logs above keep the original absolute path for
485
- // click-through ergonomics in the terminal.
486
- const diagnostics = rawDiagnostics.map((d) => ({
487
- ...d,
488
- filePath: isAbsolute(d.filePath) ? posixPath(relative(outputRoot, d.filePath)) : d.filePath,
489
- }));
490
- const snapshot = warmedResolver
491
- .snapshotComponents()
492
- .map((c) => ({ component: c }));
493
- const manifests = warmedResolver.snapshotManifestRefs();
494
- // Registry entries are keyed by graph key (configDir-relative); the roster
495
- // lives in output space. Rebase once here through the same `rebase` every
496
- // other engine path uses, so the join compares like with like. Keyed on
497
- // both `symbol` and `exportName` since `hasLocal` must also answer by the
498
- // local symbol when the entry's `exportName` is an alias.
499
- const rebasedEntries = registry.localEntries().map((e) => ({ ...e, filePath: rebase(e.filePath) }));
500
- const rebasedKeys = new Set(rebasedEntries.flatMap((e) => [`${e.filePath}::${e.symbol}`, `${e.filePath}::${e.exportName}`]));
501
- const rosterRegistry = {
502
- hasLocal: (filePath, exportName) => rebasedKeys.has(`${filePath}::${exportName}`),
503
- localEntries: () => rebasedEntries,
504
- };
505
- const seeds = buildComponentSeeds(results, localIndex, outputRoot, workspaceGraph, rosterRegistry, meta.repo.id);
506
- // Occurrences from the Vue/Lit/HTML parsers (reactPlugin.parse is a no-op).
507
- const legacyOccurrences = parseResultsToOccurrences(results, outputRoot);
508
- // Convert EngineOccurrence → OutputOccurrence and merge with the parser output.
509
- // Engine occurrences carry rawComponentId (structured ComponentId); translate
510
- // to the stable short-hash string via computeStableIdFromComponentId, then
511
- // re-derive occurrenceId using the final hashed componentId (matches the
512
- // convention in occurrences.ts so rollup correlates correctly).
513
- //
514
- // Also accumulate seeds for engine-discovered components (external or local)
515
- // that weren't already seeded by the parser path or the snapshot.
516
- // Without seeds, these components won't appear in components[] and their ids
517
- // won't be in validIds — causing composition edges to them to be silently
518
- // dropped during applyCompositionRollup.
519
- const engineSeeds = new Map();
520
- // Track wraps cross-links emitted by the engine so we can patch them onto
521
- // seeds that were built before the engine ran (manifest snapshot path).
522
- // Keys are componentId hashes; values are the wraps field from EngineOccurrence.
523
- const wrapsById = new Map();
524
- const resolvedEngineOccurrences = engineOccurrences.map((eo) => {
525
- // Normalise local filePaths in rawComponentId / rawOwnerComponentId to
526
- // output-space POSIX so the stable hash matches the one produced for seeds
527
- // built from the local index (which uses output-space relative paths).
528
- const normalisedComponentId = normaliseEngineLocalId(eo.rawComponentId, cfg.configDir, rebase);
529
- const componentId = computeStableIdFromComponentId(normalisedComponentId);
530
- // Resolve rawOwnerComponentId → hashed ownerComponentId string (if present).
531
- // Must be computed BEFORE occurrenceId so it can participate in the hash
532
- // (distinguishes fanned-out occurrences that share the same JSX call site).
533
- const ownerComponentId = eo.rawOwnerComponentId !== undefined
534
- ? computeStableIdFromComponentId(normaliseEngineLocalId(eo.rawOwnerComponentId, cfg.configDir, rebase))
535
- : eo.ownerComponentId;
536
- // eo.filePath is a scanRoot-relative graph key — rebase to outputRoot for
537
- // both the occurrenceId hash and the emitted OutputOccurrence.filePath.
538
- const outFile = rebase(eo.filePath);
539
- const occurrenceId = computeOccurrenceId(componentId, outFile, eo.line, eo.column, ownerComponentId);
540
- // Normalise absolute `via`/`viaChain` specifiers (e.g. GlobalComponents
541
- // entries for in-repo Vue components — see global-components.ts) to
542
- // output-space POSIX through the same rebase used for local identity
543
- // paths above. Authored specifiers are never absolute, so the isAbsolute
544
- // guard only ever fires for these synthetic entries. `via` is always
545
- // `viaChain[0]` (see engine/index.ts) — derive it from the normalised
546
- // chain so the two stay in sync.
547
- const normalisedViaChain = eo.viaChain.map((v) => normaliseVia(v, cfg.configDir, rebase));
548
- const via = normalisedViaChain[0] ?? normaliseVia(eo.via, cfg.configDir, rebase);
549
- // Propagate wraps cross-link so it reaches OutputComponent.identity.wraps.
550
- // We record it in wrapsById so it can be patched onto pre-existing seeds
551
- // (manifest snapshot path) that were built before the engine ran.
552
- if (eo.wraps !== undefined && !wrapsById.has(componentId)) {
553
- wrapsById.set(componentId, eo.wraps);
554
- }
555
- // Ensure a seed exists for this component so it appears in components[].
556
- // Local seeds are already seeded from localIndex (step 3 of buildComponentSeeds);
557
- // this covers external occurrences whose package has no manifest snapshot entry.
558
- if (!engineSeeds.has(componentId)) {
559
- const existingSeed = seeds.find((s) => s.id === componentId);
560
- if (!existingSeed) {
561
- const identity = computeOutputIdentity({
562
- componentId: normalisedComponentId,
563
- repoId: meta.repo.id,
564
- workspaceGraph,
565
- outputRoot,
566
- ...(eo.definition !== undefined ? { definitionLoc: eo.definition } : {}),
567
- });
568
- // Attach wraps cross-link to the identity when present.
569
- if (eo.wraps !== undefined)
570
- identity.wraps = eo.wraps;
571
- engineSeeds.set(componentId, { id: componentId, identity, manifest: null });
572
- }
573
- }
574
- return {
575
- componentId,
576
- filePath: outFile,
577
- line: eo.line,
578
- column: eo.column,
579
- via,
580
- viaChain: normalisedViaChain,
581
- props: eo.props,
582
- occurrenceId,
583
- depth: eo.depth,
584
- ...(ownerComponentId !== undefined ? { ownerComponentId } : {}),
585
- ...(eo.events && eo.events.length > 0 ? { events: eo.events } : {}),
586
- };
587
- });
588
- // Merge engine seeds into the primary seeds array (deduplication via existingSeed check above).
589
- // Patch wraps onto pre-existing seeds (manifest snapshot path) where the engine
590
- // resolved a wrapCallback hit — those seeds were built before resolveGraph ran.
591
- const allSeeds = [
592
- ...seeds.map((s) => {
593
- const wraps = wrapsById.get(s.id);
594
- if (wraps === undefined)
595
- return s;
596
- return { ...s, identity: { ...s.identity, wraps } };
597
- }),
598
- ...engineSeeds.values(),
599
- ];
600
- // Attach manifest enrichment to every observed seed — parser-observed and
601
- // engine-discovered alike — via the ONE enrichment seam. Must run after
602
- // engine seeds are merged in so engine-only-discovered components (Vue
603
- // tag-only usages, wrap targets) are eligible for enrichment too, and before
604
- // buildScanOutput so the manifest lands in the emitted artefact.
605
- attachManifestEnrichment(allSeeds, snapshot, workspaceGraph);
606
- const occurrences = [...legacyOccurrences, ...resolvedEngineOccurrences];
607
- const partialStats = {
608
- filesScanned: files.length,
609
- filesWithUsage: countFilesWithUsage(occurrences),
610
- scanDurationMs: Math.round(t1 - t0),
611
- };
612
- const rawScanOutput = buildScanOutput(meta, manifests, partialStats, allSeeds, occurrences, diagnostics);
613
- // Post-resolve aggregation: stamp logicalId on every component and
614
- // emit wraps relationship edges — no synthetic zero-occurrence rows.
615
- // Leaf-package-lane records for UNOBSERVED targets match no row and
616
- // drop — the relationship edge carries that provenance.
617
- const { components: withRelationships, relationships } = applyWrapRelationships(rawScanOutput.components);
618
- const aggregated = applyEntryPackages(withRelationships, entryPackageRecorder.drain());
619
- const scanOutput = {
620
- ...rawScanOutput,
621
- components: aggregated,
622
- relationships,
623
- stats: { ...rawScanOutput.stats, componentsDetected: aggregated.length },
624
- };
625
- // Both functions join their root arg with output-space (outputRoot-relative)
626
- // component/occurrence filePaths — must use outputRoot, not cfg.configDir,
627
- // or the join produces a path that doesn't exist on disk once the two roots
628
- // diverge (git-mtime lookups silently return empty; version resolution
629
- // walks up from the wrong directory).
630
- await stampGitMTime(outputRoot, scanOutput.components);
631
- await stampVersion(outputRoot, scanOutput.components, scanOutput.occurrences);
632
- // cfg.output is a physical write destination (relative to the scan
633
- // invocation / config dir), not an output-space artefact field — stays
634
- // configDir-relative.
635
- const outputPath = isAbsolute(cfg.output) ? cfg.output : resolve(cfg.configDir, cfg.output);
636
- await writeJson(scanOutput, outputPath);
637
- if (!opts.quiet) {
638
- printSummary(scanOutput, outputPath);
639
- }
640
- let upload = "skipped";
641
- if (opts.upload) {
642
- try {
643
- const { createAuthedUploader } = await import("../auth/upload-auth.js");
644
- const uploader = await createAuthedUploader({
645
- ...(opts.hostOverride !== undefined ? { flagHost: opts.hostOverride } : {}),
646
- ...(cfg.host !== undefined ? { configHost: cfg.host } : {}),
647
- });
648
- const result = await uploader.upload(JSON.stringify(scanOutput));
649
- upload = result.status === "inserted" ? "ok" : "exists";
650
- log.info(result.status === "inserted"
651
- ? `Uploaded scan ${result.scanId} → ${uploader.base}${result.url}`
652
- : `Scan ${result.scanId} already exists at ${uploader.base}${result.url}; no changes.`);
653
- }
654
- catch (err) {
655
- upload = "failed";
656
- const { formatAuthError } = await import("../auth/session.js");
657
- const authMsg = formatAuthError(err);
658
- const msg = authMsg ?? (err instanceof Error ? err.message : String(err));
659
- log.error(`Upload failed: ${msg}`);
660
- }
661
- }
662
- return { output: scanOutput, upload };
663
- }
664
- function countFilesWithUsage(occurrences) {
665
- const files = new Set();
666
- for (const o of occurrences)
667
- files.add(o.filePath);
668
- return files.size;
669
- }
670
- /**
671
- * Normalise a local-source ComponentId's filePath to output-space
672
- * (outputRoot-relative POSIX) so the stable hash matches the one produced
673
- * for seeds built from the local index.
674
- *
675
- * The engine emits one of two shapes: an absolute path (relative-specifier
676
- * import whose target didn't resolve to a graph key) or a scanRoot-relative
677
- * graph key (the common case — `graph.resolveToGraphKey` succeeds because
678
- * the target file is part of this scan; owner refs are always this shape,
679
- * per the FileGraph-key convention). Both are normalised to scanRoot-relative
680
- * first, then projected into output-space via `rebase` — `rebase` alone
681
- * isn't enough because it expects a scanRoot-relative input, not an
682
- * absolute one.
683
- */
684
- function normaliseEngineLocalId(c, configDir, rebase) {
685
- if (c.source.type !== "local")
686
- return c;
687
- const fp = c.source.filePath;
688
- const scanRel = isAbsolute(fp) ? posixPath(relative(configDir, fp)) : fp;
689
- return {
690
- ...c,
691
- source: { type: "local", filePath: rebase(scanRel) },
692
- };
693
- }
694
- /**
695
- * Rebase an absolute `via.specifier` (or `viaChain[n].specifier`) to
696
- * output-space POSIX, mirroring `normaliseEngineLocalId`'s treatment of
697
- * local ComponentId filePaths. The GlobalComponents reader
698
- * (scan/global-components.ts) emits absolute specifiers for in-repo
699
- * entries — parser-vue stamps them straight into `viaOverride.specifier` —
700
- * so without this the artefact leaks machine-local absolute paths.
701
- * Authored specifiers (explicit imports) are always bare/relative import
702
- * strings, never absolute, so the `isAbsolute` guard only ever fires for
703
- * these synthetic auto-import entries.
704
- */
705
- function normaliseVia(v, configDir, rebase) {
706
- if (!("specifier" in v) || v.specifier === undefined || !isAbsolute(v.specifier))
707
- return v;
708
- const scanRel = posixPath(relative(configDir, v.specifier));
709
- return { ...v, specifier: rebase(scanRel) };
710
- }
711
- function assertUnreachable(x) {
712
- throw new Error(`Unhandled diagnostic code: ${x.code}`);
713
- }
714
- function formatDiagnostic(d) {
715
- const tag = d.severity === "warning" ? "warn " : "info ";
716
- switch (d.code) {
717
- case "jsx-shape-unsupported":
718
- return `${tag} [component-compass] ${d.code} at ${d.filePath}:${d.line ?? "?"} — ${d.detail}`;
719
- case "low-confidence-tag":
720
- return `${tag} [component-compass] ${d.code} at ${d.filePath}:${d.line ?? "?"} — <${d.tagName}>`;
721
- case "local-index-duplicate-tag":
722
- return `${tag} [component-compass] ${d.code} at ${d.filePath} — <${d.tagName}> conflicts with ${d.conflictsWith}`;
723
- case "cycle-detected":
724
- return `${tag} [component-compass] ${d.code} at ${d.filePath} (export: ${d.exportName})`;
725
- case "chain-too-deep":
726
- return `${tag} [component-compass] ${d.code} at ${d.filePath} (export: ${d.exportName}, depth: ${d.depth})`;
727
- case "unresolved-import":
728
- return `${tag} [component-compass] ${d.code} at ${d.filePath}:${d.line}:${d.column} — could not resolve '${d.specifier}'`;
729
- case "lazy-import-unsupported":
730
- return `${tag} [component-compass] ${d.code} at ${d.filePath}:${d.line}:${d.column} — '${d.specifier}': ${d.detail}`;
731
- case "unresolved-reference":
732
- return `${tag} [component-compass] ${d.code} at ${d.filePath}:${d.line}:${d.column} — ${d.symbol}${d.memberChain.length ? `.${d.memberChain.join(".")}` : ""}`;
733
- case "cycle-in-resolution":
734
- return `${tag} [component-compass] ${d.code} at ${d.filePath}:${d.line}:${d.column} — ${d.symbol} (depth ${d.depth})`;
735
- case "unresolved-component-tag":
736
- return `${tag} [component-compass] ${d.code} at ${d.filePath}:${d.line ?? "?"}:${d.column ?? "?"} — <${d.tagName}> has no import, auto-import entry, or tag registration`;
737
- case "auto-import-stale-entry":
738
- return `${tag} [component-compass] ${d.code} in ${d.filePath} — ${d.componentName} → ${d.target} (file missing; regenerate the manifest)`;
739
- case "auto-import-manifest-missing":
740
- return `${tag} [component-compass] ${d.code} at ${d.filePath} — ${d.detail}`;
741
- default:
742
- return assertUnreachable(d);
743
- }
744
- }
745
- //# sourceMappingURL=scan.js.map