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