@invariant-ui/cli 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +105 -0
- package/README.md +250 -2
- package/dist/agent-kit/apply.js +232 -0
- package/dist/agent-kit/guidance.js +236 -0
- package/dist/agent-kit/index.js +5 -0
- package/dist/agent-kit/integrations.js +121 -0
- package/dist/agent-kit/plugin.js +115 -0
- package/dist/artifact-generator/index.js +75 -0
- package/dist/browser-verify/driver.js +2 -0
- package/dist/browser-verify/index.js +4 -0
- package/dist/browser-verify/playwright.js +82 -0
- package/dist/browser-verify/runner.js +175 -0
- package/dist/browser-verify/spec.js +103 -0
- package/dist/cli/args.js +77 -0
- package/dist/cli/baseline.js +64 -0
- package/dist/cli/cli.js +121 -0
- package/dist/cli/commands-browser.js +104 -0
- package/dist/cli/commands-discover.js +312 -0
- package/dist/cli/commands-enforcement.js +35 -0
- package/dist/cli/commands-governance.js +179 -0
- package/dist/cli/commands-hook.js +269 -0
- package/dist/cli/commands-patterns.js +324 -0
- package/dist/cli/commands-setup.js +485 -0
- package/dist/cli/commands-validate.js +157 -0
- package/dist/cli/commands.js +517 -0
- package/dist/cli/config.js +197 -0
- package/dist/cli/diff.js +66 -0
- package/dist/cli/enforcement-log.js +83 -0
- package/dist/cli/enforcement-report.js +96 -0
- package/dist/cli/errors.js +10 -0
- package/dist/cli/format.js +22 -0
- package/dist/cli/index.js +5 -0
- package/dist/cli/io.js +2 -0
- package/dist/cli/main.js +31 -0
- package/dist/cli/pipeline.js +262 -0
- package/dist/cli/publish.js +77 -0
- package/dist/cli/report.js +127 -0
- package/dist/cli/starter-rules.js +57 -0
- package/dist/cli/store.js +60 -0
- package/dist/cli/version.js +5 -0
- package/dist/cli/watch.js +68 -0
- package/dist/cli/workflow.js +52 -0
- package/dist/compiler-angular/adapter.js +115 -0
- package/dist/compiler-angular/analyze.js +222 -0
- package/dist/compiler-angular/classes.js +446 -0
- package/dist/compiler-angular/declared.js +285 -0
- package/dist/compiler-angular/emit.js +183 -0
- package/dist/compiler-angular/index.js +4 -0
- package/dist/compiler-angular/template.js +142 -0
- package/dist/compiler-core/adapter.js +2 -0
- package/dist/compiler-core/adapters/inventory.js +312 -0
- package/dist/compiler-core/compile.js +148 -0
- package/dist/compiler-core/component-facts.js +123 -0
- package/dist/compiler-core/discovery/docs.js +66 -0
- package/dist/compiler-core/discovery/entries.js +270 -0
- package/dist/compiler-core/discovery/exports.js +207 -0
- package/dist/compiler-core/discovery/graph.js +59 -0
- package/dist/compiler-core/discovery/index.js +103 -0
- package/dist/compiler-core/discovery/packages.js +148 -0
- package/dist/compiler-core/discovery/schema.js +144 -0
- package/dist/compiler-core/discovery/storybook.js +232 -0
- package/dist/compiler-core/discovery/support.js +82 -0
- package/dist/compiler-core/discovery/tokens.js +186 -0
- package/dist/compiler-core/discovery/tooling.js +146 -0
- package/dist/compiler-core/fs.js +89 -0
- package/dist/compiler-core/glob.js +3 -0
- package/dist/compiler-core/index.js +16 -0
- package/dist/compiler-core/ir-builder.js +68 -0
- package/dist/compiler-core/json-schema.js +11 -0
- package/dist/compiler-core/testing/index.js +81 -0
- package/dist/compiler-core/ts/ast.js +166 -0
- package/dist/compiler-core/ts/members.js +163 -0
- package/dist/compiler-core/ts/program.js +81 -0
- package/dist/compiler-core/ts/static-eval.js +145 -0
- package/dist/compiler-core/ts/tsconfig-paths.js +60 -0
- package/dist/compiler-core/walk.js +46 -0
- package/dist/compiler-ir/index.js +4 -0
- package/dist/compiler-ir/ir.js +67 -0
- package/dist/compiler-ir/json-schema.js +6 -0
- package/dist/compiler-ir/validate.js +116 -0
- package/dist/compiler-react/adapter.js +147 -0
- package/dist/compiler-react/analyze.js +199 -0
- package/dist/compiler-react/behavior.js +75 -0
- package/dist/compiler-react/detect.js +558 -0
- package/dist/compiler-react/emit.js +188 -0
- package/dist/compiler-react/index.js +2 -0
- package/dist/compiler-react/props.js +157 -0
- package/dist/compiler-react/styling.js +138 -0
- package/dist/compiler-react/usage.js +116 -0
- package/dist/compiler-react/variants.js +111 -0
- package/dist/compiler-storybook/adapter.js +253 -0
- package/dist/compiler-storybook/csf.js +521 -0
- package/dist/compiler-storybook/emit.js +202 -0
- package/dist/compiler-storybook/index-json.js +41 -0
- package/dist/compiler-storybook/index.js +5 -0
- package/dist/compiler-storybook/mdx.js +120 -0
- package/dist/compiler-tokens/adapter.js +158 -0
- package/dist/compiler-tokens/css.js +59 -0
- package/dist/compiler-tokens/index.js +5 -0
- package/dist/compiler-tokens/json-lines.js +106 -0
- package/dist/compiler-tokens/json-tokens.js +52 -0
- package/dist/compiler-vue/adapter.js +107 -0
- package/dist/compiler-vue/analyze.js +344 -0
- package/dist/compiler-vue/declared.js +343 -0
- package/dist/compiler-vue/emit.js +129 -0
- package/dist/compiler-vue/index.js +5 -0
- package/dist/compiler-vue/script.js +472 -0
- package/dist/compiler-vue/sfc.js +44 -0
- package/dist/compiler-vue/template.js +73 -0
- package/dist/contract/archetype-checks.js +153 -0
- package/dist/contract/components.js +102 -0
- package/dist/contract/conflicts.js +39 -0
- package/dist/contract/contract.js +46 -0
- package/dist/contract/evidence.js +84 -0
- package/dist/contract/fact-path.js +39 -0
- package/dist/contract/facts.js +62 -0
- package/dist/contract/index.js +16 -0
- package/dist/contract/integrity.js +21 -0
- package/dist/contract/json-schema.js +11 -0
- package/dist/contract/knowledge.js +95 -0
- package/dist/contract/pattern-checks.js +254 -0
- package/dist/contract/patterns.js +225 -0
- package/dist/contract/primitives.js +81 -0
- package/dist/contract/sources.js +30 -0
- package/dist/contract/tokens.js +41 -0
- package/dist/contract/validate.js +253 -0
- package/dist/contract-query/archetype.js +188 -0
- package/dist/contract-query/index.js +6 -0
- package/dist/contract-query/patterns.js +298 -0
- package/dist/contract-query/search.js +132 -0
- package/dist/contract-query/system.js +148 -0
- package/dist/contract-query/views.js +288 -0
- package/dist/governance/decisions.js +229 -0
- package/dist/governance/index.js +5 -0
- package/dist/governance/patterns-file.js +156 -0
- package/dist/governance/patterns.js +510 -0
- package/dist/governance/policies.js +148 -0
- package/dist/mcp/index.js +7 -0
- package/dist/mcp/loader.js +70 -0
- package/dist/mcp/protocol.js +27 -0
- package/dist/mcp/resources.js +138 -0
- package/dist/mcp/server.js +177 -0
- package/dist/mcp/stdio.js +28 -0
- package/dist/mcp/tools.js +267 -0
- package/dist/reconciliation/code.js +62 -0
- package/dist/reconciliation/components.js +401 -0
- package/dist/reconciliation/conflicts.js +126 -0
- package/dist/reconciliation/facts.js +41 -0
- package/dist/reconciliation/index.js +7 -0
- package/dist/reconciliation/reconcile.js +134 -0
- package/dist/reconciliation/resolve.js +158 -0
- package/dist/reconciliation/review.js +81 -0
- package/dist/reconciliation/storybook.js +370 -0
- package/dist/reconciliation/tokens.js +179 -0
- package/dist/reconciliation/util.js +55 -0
- package/dist/semantic-engine/additive.js +51 -0
- package/dist/semantic-engine/compounds.js +91 -0
- package/dist/semantic-engine/documents.js +157 -0
- package/dist/semantic-engine/enrich.js +42 -0
- package/dist/semantic-engine/index.js +7 -0
- package/dist/semantic-engine/infer.js +38 -0
- package/dist/semantic-engine/tokens.js +120 -0
- package/dist/shared/canonical.js +65 -0
- package/dist/shared/diagnostic.js +78 -0
- package/dist/shared/glob.js +98 -0
- package/dist/shared/index.js +4 -0
- package/dist/shared/json.js +2 -0
- package/dist/validation/card.js +50 -0
- package/dist/validation/discovery/arrangements.js +162 -0
- package/dist/validation/discovery/discover.js +70 -0
- package/dist/validation/discovery/index.js +8 -0
- package/dist/validation/discovery/model.js +127 -0
- package/dist/validation/discovery/promote.js +24 -0
- package/dist/validation/discovery/reviews.js +63 -0
- package/dist/validation/discovery/shapes.js +47 -0
- package/dist/validation/discovery/similar-files.js +146 -0
- package/dist/validation/discovery/util.js +47 -0
- package/dist/validation/index.js +11 -0
- package/dist/validation/interaction-facts.js +341 -0
- package/dist/validation/interaction-workflows.js +633 -0
- package/dist/validation/literals.js +251 -0
- package/dist/validation/pattern-rules.js +608 -0
- package/dist/validation/policy.js +241 -0
- package/dist/validation/report.js +163 -0
- package/dist/validation/rules.js +269 -0
- package/dist/validation/scan.js +295 -0
- package/dist/validation/text.js +34 -0
- package/dist/validation/validate-code.js +513 -0
- package/dist/validation/validate-usage.js +196 -0
- package/package.json +38 -4
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/** Minimal JSON parser that keeps key line numbers, for evidence locations. Throws on invalid JSON. */
|
|
2
|
+
export function parseJsonWithLines(text) {
|
|
3
|
+
let i = 0;
|
|
4
|
+
let line = 1;
|
|
5
|
+
const fail = (message) => {
|
|
6
|
+
throw new SyntaxError(`${message} at line ${line}`);
|
|
7
|
+
};
|
|
8
|
+
const skip = () => {
|
|
9
|
+
while (i < text.length) {
|
|
10
|
+
const c = text[i];
|
|
11
|
+
if (c === "\n")
|
|
12
|
+
line++;
|
|
13
|
+
else if (c !== " " && c !== "\t" && c !== "\r")
|
|
14
|
+
return;
|
|
15
|
+
i++;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
const string = () => {
|
|
19
|
+
const start = i;
|
|
20
|
+
i++;
|
|
21
|
+
while (i < text.length && text[i] !== '"')
|
|
22
|
+
i += text[i] === "\\" ? 2 : 1;
|
|
23
|
+
if (i >= text.length)
|
|
24
|
+
fail("Unterminated string");
|
|
25
|
+
i++;
|
|
26
|
+
return JSON.parse(text.slice(start, i));
|
|
27
|
+
};
|
|
28
|
+
const value = () => {
|
|
29
|
+
skip();
|
|
30
|
+
const startLine = line;
|
|
31
|
+
const c = text[i];
|
|
32
|
+
if (c === "{") {
|
|
33
|
+
i++;
|
|
34
|
+
const members = [];
|
|
35
|
+
skip();
|
|
36
|
+
if (text[i] === "}") {
|
|
37
|
+
i++;
|
|
38
|
+
return { type: "object", members, line: startLine };
|
|
39
|
+
}
|
|
40
|
+
for (;;) {
|
|
41
|
+
skip();
|
|
42
|
+
if (text[i] !== '"')
|
|
43
|
+
fail("Expected a string key");
|
|
44
|
+
const keyLine = line;
|
|
45
|
+
const key = string();
|
|
46
|
+
skip();
|
|
47
|
+
if (text[i] !== ":")
|
|
48
|
+
fail("Expected ':'");
|
|
49
|
+
i++;
|
|
50
|
+
members.push({ key, line: keyLine, value: value() });
|
|
51
|
+
skip();
|
|
52
|
+
if (text[i] === ",") {
|
|
53
|
+
i++;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (text[i] === "}") {
|
|
57
|
+
i++;
|
|
58
|
+
return { type: "object", members, line: startLine };
|
|
59
|
+
}
|
|
60
|
+
fail("Expected ',' or '}'");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (c === "[") {
|
|
64
|
+
i++;
|
|
65
|
+
const items = [];
|
|
66
|
+
skip();
|
|
67
|
+
if (text[i] === "]") {
|
|
68
|
+
i++;
|
|
69
|
+
return { type: "array", items, line: startLine };
|
|
70
|
+
}
|
|
71
|
+
for (;;) {
|
|
72
|
+
items.push(value());
|
|
73
|
+
skip();
|
|
74
|
+
if (text[i] === ",") {
|
|
75
|
+
i++;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (text[i] === "]") {
|
|
79
|
+
i++;
|
|
80
|
+
return { type: "array", items, line: startLine };
|
|
81
|
+
}
|
|
82
|
+
fail("Expected ',' or ']'");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (c === '"')
|
|
86
|
+
return { type: "scalar", value: string(), line: startLine };
|
|
87
|
+
const literal = /^(?:true|false|null|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)/.exec(text.slice(i));
|
|
88
|
+
if (!literal)
|
|
89
|
+
return fail("Unexpected token");
|
|
90
|
+
i += literal[0].length;
|
|
91
|
+
return { type: "scalar", value: JSON.parse(literal[0]), line: startLine };
|
|
92
|
+
};
|
|
93
|
+
const root = value();
|
|
94
|
+
skip();
|
|
95
|
+
if (i < text.length)
|
|
96
|
+
fail("Unexpected trailing content");
|
|
97
|
+
return root;
|
|
98
|
+
}
|
|
99
|
+
export function toJson(node) {
|
|
100
|
+
if (node.type === "scalar")
|
|
101
|
+
return node.value;
|
|
102
|
+
if (node.type === "array")
|
|
103
|
+
return node.items.map(toJson);
|
|
104
|
+
return Object.fromEntries(node.members.map((m) => [m.key, toJson(m.value)]));
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=json-lines.js.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { toJson } from "./json-lines.js";
|
|
2
|
+
const REFERENCE = /^\{([^{}]+)\}$/;
|
|
3
|
+
/**
|
|
4
|
+
* Walks a token tree. A node is a token when it has `$value` (W3C Design Tokens shape; `$type` is
|
|
5
|
+
* inherited from enclosing groups) or a scalar/composite `value` (Style Dictionary shape).
|
|
6
|
+
*/
|
|
7
|
+
export function extractJsonTokens(root) {
|
|
8
|
+
const out = [];
|
|
9
|
+
const visit = (members, path, inheritedType) => {
|
|
10
|
+
const find = (key) => members.find((m) => m.key === key);
|
|
11
|
+
const groupType = scalarString(find("$type")?.value) ?? inheritedType;
|
|
12
|
+
for (const member of members) {
|
|
13
|
+
if (member.key.startsWith("$") || member.value.type !== "object")
|
|
14
|
+
continue;
|
|
15
|
+
const child = member.value.members;
|
|
16
|
+
const childPath = [...path, member.key];
|
|
17
|
+
const dtcgValue = child.find((m) => m.key === "$value");
|
|
18
|
+
const sdValue = dtcgValue ? undefined : child.find((m) => m.key === "value");
|
|
19
|
+
const valueMember = dtcgValue ?? sdValue;
|
|
20
|
+
if (!valueMember) {
|
|
21
|
+
visit(child, childPath, groupType);
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const raw = toJson(valueMember.value);
|
|
25
|
+
const alias = typeof raw === "string" ? REFERENCE.exec(raw.trim())?.[1] : undefined;
|
|
26
|
+
const type = dtcgValue
|
|
27
|
+
? (scalarString(child.find((m) => m.key === "$type")?.value) ?? groupType)
|
|
28
|
+
: scalarString(child.find((m) => m.key === "type")?.value);
|
|
29
|
+
const description = scalarString(child.find((m) => m.key === (dtcgValue ? "$description" : "comment"))?.value) ??
|
|
30
|
+
scalarString(child.find((m) => m.key === "description")?.value);
|
|
31
|
+
const deprecated = child.find((m) => m.key === "$deprecated")?.value;
|
|
32
|
+
out.push({
|
|
33
|
+
name: childPath.join("."),
|
|
34
|
+
line: member.line,
|
|
35
|
+
format: dtcgValue ? "dtcg" : "style-dictionary",
|
|
36
|
+
...(alias ? { aliasOf: alias } : { value: raw }),
|
|
37
|
+
...(type ? { type } : {}),
|
|
38
|
+
...(description ? { description } : {}),
|
|
39
|
+
...(deprecated?.type === "scalar" && deprecated.value !== false
|
|
40
|
+
? { deprecated: typeof deprecated.value === "string" ? { reason: deprecated.value } : {} }
|
|
41
|
+
: {})
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
if (root.type === "object")
|
|
46
|
+
visit(root.members, [], undefined);
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
function scalarString(node) {
|
|
50
|
+
return node?.type === "scalar" && typeof node.value === "string" ? node.value : undefined;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=json-tokens.js.map
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { DEFAULT_IGNORED_DIRS, hashText, IrBuilder, walk } from "../compiler-core/index.js";
|
|
2
|
+
import { hashJson } from "../shared/index.js";
|
|
3
|
+
import { analyzePackage } from "./analyze.js";
|
|
4
|
+
import { CAPABILITIES, emitComponent } from "./emit.js";
|
|
5
|
+
export const ADAPTER_ID = "vue";
|
|
6
|
+
export const ADAPTER_VERSION = "0.1.0";
|
|
7
|
+
const SOURCE_FILE = /\.(?:[cm]?[jt]sx?|vue)$|(?:^|\/)tsconfig[^/]*\.json$/;
|
|
8
|
+
const MAX_FILE_BYTES = 1_000_000;
|
|
9
|
+
/**
|
|
10
|
+
* Vue adapter. For every workspace package that declares Vue it reads single-file components with the
|
|
11
|
+
* official SFC parser and analyzes their script with the TypeScript API (compile-time macros, options
|
|
12
|
+
* objects, `defineComponent`), plus the template for slots, accessibility and composition. Nothing is
|
|
13
|
+
* executed and `node_modules` is not read.
|
|
14
|
+
*/
|
|
15
|
+
export const vueAdapter = {
|
|
16
|
+
id: ADAPTER_ID,
|
|
17
|
+
version: ADAPTER_VERSION,
|
|
18
|
+
type: "vue",
|
|
19
|
+
async discover({ fs, project }) {
|
|
20
|
+
const sources = [];
|
|
21
|
+
const used = new Set();
|
|
22
|
+
for (const pkg of project.packages.filter((p) => p.frameworks.some((f) => f.name === "vue"))) {
|
|
23
|
+
const others = project.packages.filter((p) => p.id !== pkg.id && p.dir !== "." && (pkg.dir === "." || p.dir.startsWith(`${pkg.dir}/`)));
|
|
24
|
+
const { files, hashes } = await readPackageFiles(fs, pkg, others);
|
|
25
|
+
const slug = (pkg.name ?? pkg.dir)
|
|
26
|
+
.toLowerCase()
|
|
27
|
+
.replace(/[^a-z0-9._-]+/g, "-")
|
|
28
|
+
.replace(/^[^a-z0-9]+|[-.]+$/g, "") || "root";
|
|
29
|
+
let id = `vue-${slug}`;
|
|
30
|
+
for (let n = 2; used.has(id); n++)
|
|
31
|
+
id = `vue-${slug}-${n}`;
|
|
32
|
+
used.add(id);
|
|
33
|
+
sources.push({
|
|
34
|
+
id,
|
|
35
|
+
type: "vue",
|
|
36
|
+
location: pkg.dir,
|
|
37
|
+
config: { packageId: pkg.id, files: [...files.keys()] },
|
|
38
|
+
...(pkg.version ? { version: { kind: "package-version", value: pkg.version } } : {}),
|
|
39
|
+
inputHash: hashJson(hashes)
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return { sources, diagnostics: [] };
|
|
43
|
+
},
|
|
44
|
+
async extract({ fs, project, source, clock }, config) {
|
|
45
|
+
const pkg = project.packages.find((p) => p.id === config.packageId);
|
|
46
|
+
if (!pkg)
|
|
47
|
+
throw new Error(`Package ${config.packageId} is not in the project discovery`);
|
|
48
|
+
const files = new Map();
|
|
49
|
+
for (const path of config.files)
|
|
50
|
+
files.set(path, await fs.readFile(path));
|
|
51
|
+
const analysis = analyzePackage({ packageDir: pkg.dir, entryPoints: pkg.entryPoints, files });
|
|
52
|
+
const builder = new IrBuilder({
|
|
53
|
+
source: {
|
|
54
|
+
id: source.id,
|
|
55
|
+
type: source.type,
|
|
56
|
+
location: source.location,
|
|
57
|
+
...(source.version ? { version: source.version } : {}),
|
|
58
|
+
inputHash: source.inputHash,
|
|
59
|
+
adapter: { name: ADAPTER_ID, version: ADAPTER_VERSION }
|
|
60
|
+
},
|
|
61
|
+
extractor: { name: "vue-sfc", version: ADAPTER_VERSION },
|
|
62
|
+
capabilities: CAPABILITIES,
|
|
63
|
+
clock
|
|
64
|
+
});
|
|
65
|
+
for (const component of analysis.components)
|
|
66
|
+
emitComponent(builder, component, pkg);
|
|
67
|
+
for (const d of analysis.diagnostics)
|
|
68
|
+
builder.diagnostic(d);
|
|
69
|
+
return { ir: builder.build(analysis.components.some((c) => !c.propsComplete) ? "partial" : "complete") };
|
|
70
|
+
},
|
|
71
|
+
async validate({ ir }) {
|
|
72
|
+
return ir.entities.length === 0
|
|
73
|
+
? [
|
|
74
|
+
{
|
|
75
|
+
code: "vue.no-components",
|
|
76
|
+
severity: "info",
|
|
77
|
+
message: `No Vue components were found in the entry points of ${ir.source.id}`,
|
|
78
|
+
sourceId: ir.source.id
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
: [];
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
async function readPackageFiles(fs, pkg, nested) {
|
|
85
|
+
// An installed package (opted in through a declaration root) keeps its declaration files in build output.
|
|
86
|
+
const installed = pkg.dir.split("/").includes("node_modules");
|
|
87
|
+
const walked = await walk(fs, {
|
|
88
|
+
ignoreDirs: new Set(installed
|
|
89
|
+
? DEFAULT_IGNORED_DIRS.filter((d) => !["dist", "build", "out"].includes(d))
|
|
90
|
+
: DEFAULT_IGNORED_DIRS),
|
|
91
|
+
maxFiles: 20_000,
|
|
92
|
+
start: pkg.dir
|
|
93
|
+
});
|
|
94
|
+
const files = new Map();
|
|
95
|
+
const hashes = [];
|
|
96
|
+
for (const path of walked.files) {
|
|
97
|
+
if (!SOURCE_FILE.test(path) || nested.some((n) => path === n.dir || path.startsWith(`${n.dir}/`)))
|
|
98
|
+
continue;
|
|
99
|
+
const text = await fs.readFile(path);
|
|
100
|
+
if (text.length > MAX_FILE_BYTES)
|
|
101
|
+
continue;
|
|
102
|
+
files.set(path, text);
|
|
103
|
+
hashes.push([path, hashText(text)]);
|
|
104
|
+
}
|
|
105
|
+
return { files, hashes };
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=adapter.js.map
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import { createAnalysis, cssVariableReferences, declarationOf, isProjectNode, lineOf, posix, relativeFileOf, resolveSymbol, tsconfigPaths, unwrap, variantsFromProps } from "../compiler-core/index.js";
|
|
2
|
+
import ts from "typescript";
|
|
3
|
+
import { analyzeDeclaredComponent } from "./declared.js";
|
|
4
|
+
import { analyzeOptionsDeclaration, analyzeScript } from "./script.js";
|
|
5
|
+
import { parseSfc, sfcLocalName } from "./sfc.js";
|
|
6
|
+
import { analyzeTemplate } from "./template.js";
|
|
7
|
+
const NON_SOURCE = /\.(?:test|spec|stories)\.[cm]?[jt]sx?$|(?:^|\/)__(?:tests|mocks)__\//;
|
|
8
|
+
/**
|
|
9
|
+
* Finds the package's public Vue components (what its entry points export that is a `.vue` file or a
|
|
10
|
+
* `defineComponent(...)` declaration) and analyzes each. Nothing is executed or built.
|
|
11
|
+
*/
|
|
12
|
+
export function analyzePackage(input) {
|
|
13
|
+
const diagnostics = [];
|
|
14
|
+
const note = (severity, code, message, location) => {
|
|
15
|
+
diagnostics.push({ code, severity, message, ...(location ? { location } : {}) });
|
|
16
|
+
};
|
|
17
|
+
// 1. Public SFCs, from what the entry points re-export.
|
|
18
|
+
const publicSfcs = new Map();
|
|
19
|
+
const importCache = new Map();
|
|
20
|
+
for (const entry of input.entryPoints) {
|
|
21
|
+
for (const exported of entry.exports) {
|
|
22
|
+
const target = sfcTargetOf(exported, input.files, importCache);
|
|
23
|
+
if (target)
|
|
24
|
+
publicSfcs.set(target, [
|
|
25
|
+
...(publicSfcs.get(target) ?? []),
|
|
26
|
+
{ subpath: entry.subpath, name: exported.name }
|
|
27
|
+
]);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// 2. Parse each SFC and give its script to the type checker as a virtual module.
|
|
31
|
+
const sfcs = new Map();
|
|
32
|
+
const files = new Map();
|
|
33
|
+
for (const [path, text] of input.files)
|
|
34
|
+
if (/\.[cm]?[jt]sx?$/.test(path) && !NON_SOURCE.test(path))
|
|
35
|
+
files.set(path, text);
|
|
36
|
+
// Every SFC gets a virtual module, public or not: a component's props type is often an interface exported
|
|
37
|
+
// from another SFC's `<script>` block (`import type { XProps } from "../X"`, re-exported from `X.vue`).
|
|
38
|
+
const moduleAliases = {};
|
|
39
|
+
for (const path of [...input.files.keys()].filter((f) => f.endsWith(".vue")).sort()) {
|
|
40
|
+
const sfc = parseSfc(path, input.files.get(path));
|
|
41
|
+
if (publicSfcs.has(path)) {
|
|
42
|
+
sfcs.set(path, sfc);
|
|
43
|
+
for (const message of sfc.errors)
|
|
44
|
+
note("warning", "vue.sfc-parse-error", `${path}: ${message}`, { path });
|
|
45
|
+
}
|
|
46
|
+
if (sfc.script) {
|
|
47
|
+
files.set(sfc.script.path, sfc.script.text);
|
|
48
|
+
moduleAliases[path] = sfc.script.path;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const analysis = createAnalysis(files, {
|
|
52
|
+
moduleAliases,
|
|
53
|
+
paths: tsconfigPaths(input.files, input.packageDir)
|
|
54
|
+
});
|
|
55
|
+
const components = [];
|
|
56
|
+
const refOf = new Map();
|
|
57
|
+
for (const path of [...publicSfcs.keys()].sort())
|
|
58
|
+
refOf.set(path, `vue:${path}#${sfcLocalName(path)}`);
|
|
59
|
+
for (const [path, sfc] of sfcs) {
|
|
60
|
+
components.push(buildSfcComponent({
|
|
61
|
+
analysis,
|
|
62
|
+
sfc,
|
|
63
|
+
exports: publicSfcs.get(path) ?? [],
|
|
64
|
+
ref: refOf.get(path),
|
|
65
|
+
refOf,
|
|
66
|
+
note,
|
|
67
|
+
files: input.files
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
// 3. Components declared in TypeScript with defineComponent(...).
|
|
71
|
+
for (const entry of input.entryPoints) {
|
|
72
|
+
const sf = entry.file ? analysis.sourceFile(entry.file) : undefined;
|
|
73
|
+
const moduleSymbol = sf ? analysis.checker.getSymbolAtLocation(sf) : undefined;
|
|
74
|
+
if (!sf || !moduleSymbol)
|
|
75
|
+
continue;
|
|
76
|
+
for (const exported of analysis.checker.getExportsOfModule(moduleSymbol)) {
|
|
77
|
+
const decl = declarationOf(resolveSymbol(analysis.checker, exported));
|
|
78
|
+
if (!decl ||
|
|
79
|
+
!ts.isVariableDeclaration(decl) ||
|
|
80
|
+
!ts.isIdentifier(decl.name) ||
|
|
81
|
+
!isProjectNode(analysis, decl))
|
|
82
|
+
continue;
|
|
83
|
+
const init = decl.initializer ? unwrap(decl.initializer) : undefined;
|
|
84
|
+
if (!init) {
|
|
85
|
+
// A declaration file's `declare const X: DefineComponent<…>`.
|
|
86
|
+
const file = relativeFileOf(analysis, decl);
|
|
87
|
+
const localName = declaredLocalName(decl.name.text, exported.name, file);
|
|
88
|
+
const ref = `vue:${file}#${localName}`;
|
|
89
|
+
const existing = components.find((c) => c.ref === ref);
|
|
90
|
+
if (existing)
|
|
91
|
+
existing.exports.push({ subpath: entry.subpath, name: exported.name });
|
|
92
|
+
else {
|
|
93
|
+
const script = analyzeDeclaredComponent(analysis, decl);
|
|
94
|
+
if (script)
|
|
95
|
+
components.push(buildTsComponent({
|
|
96
|
+
analysis,
|
|
97
|
+
decl,
|
|
98
|
+
script,
|
|
99
|
+
ref,
|
|
100
|
+
localName,
|
|
101
|
+
kind: "declaration",
|
|
102
|
+
exported: { subpath: entry.subpath, name: exported.name }
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (!init ||
|
|
108
|
+
!ts.isCallExpression(init) ||
|
|
109
|
+
!ts.isIdentifier(init.expression) ||
|
|
110
|
+
init.expression.text !== "defineComponent")
|
|
111
|
+
continue;
|
|
112
|
+
const file = relativeFileOf(analysis, decl);
|
|
113
|
+
const ref = `vue:${file}#${decl.name.text}`;
|
|
114
|
+
const existing = components.find((c) => c.ref === ref);
|
|
115
|
+
if (existing)
|
|
116
|
+
existing.exports.push({ subpath: entry.subpath, name: exported.name });
|
|
117
|
+
else {
|
|
118
|
+
const script = analyzeOptionsDeclaration(analysis, decl);
|
|
119
|
+
if (script)
|
|
120
|
+
components.push(buildTsComponent({
|
|
121
|
+
analysis,
|
|
122
|
+
decl,
|
|
123
|
+
script,
|
|
124
|
+
ref,
|
|
125
|
+
exported: { subpath: entry.subpath, name: exported.name }
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (components.some((c) => c.kind === "declaration"))
|
|
131
|
+
note("info", "vue.declaration-defaults-unavailable", "Components were read from declaration files, which carry types but no default values: prop defaults are not reported");
|
|
132
|
+
for (const c of components) {
|
|
133
|
+
if (!c.propsComplete)
|
|
134
|
+
note("info", "vue.props-incomplete", `${c.localName}: props could not be fully enumerated${c.script.inheritsFrom.length ? ` (${c.script.inheritsFrom.join(", ")})` : c.script.notes[0] ? ` (${c.script.notes[0]})` : ""}`, { path: c.file, line: c.line });
|
|
135
|
+
if (c.template?.skipped)
|
|
136
|
+
note("info", "vue.template-skipped", `${c.localName}: ${c.template.skipped}`, {
|
|
137
|
+
path: c.file,
|
|
138
|
+
line: c.templateLine ?? c.line
|
|
139
|
+
});
|
|
140
|
+
for (const s of c.template?.slots ?? [])
|
|
141
|
+
if (s.dynamic)
|
|
142
|
+
note("info", "vue.dynamic-slot-name", `${c.localName}: a <slot> with a dynamic name was found and is not listed`, { path: c.file, line: s.line });
|
|
143
|
+
}
|
|
144
|
+
components.sort((a, b) => (a.ref < b.ref ? -1 : a.ref > b.ref ? 1 : 0));
|
|
145
|
+
for (const c of components)
|
|
146
|
+
c.exports.sort((a, b) => a.subpath < b.subpath ? -1 : a.subpath > b.subpath ? 1 : a.name < b.name ? -1 : 1);
|
|
147
|
+
return { components, diagnostics };
|
|
148
|
+
}
|
|
149
|
+
/** `declare const _default` (vue-tsc output for one `.vue` file) is named after its file; other names are kept. */
|
|
150
|
+
function declaredLocalName(declName, exportName, file) {
|
|
151
|
+
if (!declName.startsWith("_"))
|
|
152
|
+
return declName;
|
|
153
|
+
if (exportName !== "default")
|
|
154
|
+
return exportName;
|
|
155
|
+
return sfcLocalName(file.replace(/(?:\.vue)?\.d\.[cm]?ts$/, ".vue"));
|
|
156
|
+
}
|
|
157
|
+
function sfcTargetOf(exported, files, cache) {
|
|
158
|
+
const resolve = (spec, from) => {
|
|
159
|
+
if (!spec.startsWith("."))
|
|
160
|
+
return undefined;
|
|
161
|
+
const path = posix.normalize(posix.join(posix.dirname(from), spec));
|
|
162
|
+
return path.endsWith(".vue") && files.has(path) ? path : undefined;
|
|
163
|
+
};
|
|
164
|
+
if (exported.reexportOf)
|
|
165
|
+
return resolve(exported.reexportOf.module, exported.file);
|
|
166
|
+
// `import Button from "./Button.vue"; export { Button }`
|
|
167
|
+
let imports = cache.get(exported.file);
|
|
168
|
+
if (!imports) {
|
|
169
|
+
imports = new Map();
|
|
170
|
+
const text = files.get(exported.file);
|
|
171
|
+
if (text) {
|
|
172
|
+
const sf = ts.createSourceFile(exported.file, text, ts.ScriptTarget.ES2022, false);
|
|
173
|
+
for (const statement of sf.statements) {
|
|
174
|
+
if (ts.isImportDeclaration(statement) &&
|
|
175
|
+
ts.isStringLiteral(statement.moduleSpecifier) &&
|
|
176
|
+
statement.importClause?.name) {
|
|
177
|
+
imports.set(statement.importClause.name.text, statement.moduleSpecifier.text);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
cache.set(exported.file, imports);
|
|
182
|
+
}
|
|
183
|
+
const spec = imports.get(exported.name);
|
|
184
|
+
return spec ? resolve(spec, exported.file) : undefined;
|
|
185
|
+
}
|
|
186
|
+
function buildSfcComponent(input) {
|
|
187
|
+
const { analysis, sfc, note } = input;
|
|
188
|
+
const { descriptor } = sfc;
|
|
189
|
+
const sf = sfc.script ? analysis.sourceFile(sfc.script.path) : undefined;
|
|
190
|
+
const script = sf
|
|
191
|
+
? analyzeScript(analysis, sf)
|
|
192
|
+
: {
|
|
193
|
+
props: [],
|
|
194
|
+
emits: [],
|
|
195
|
+
slots: [],
|
|
196
|
+
exposed: [],
|
|
197
|
+
options: {},
|
|
198
|
+
inheritsFrom: [],
|
|
199
|
+
complete: true,
|
|
200
|
+
notes: [],
|
|
201
|
+
optionsApi: false,
|
|
202
|
+
imports: new Map()
|
|
203
|
+
};
|
|
204
|
+
const external = [descriptor.script, descriptor.scriptSetup].some((b) => b?.src);
|
|
205
|
+
if (external) {
|
|
206
|
+
script.complete = false;
|
|
207
|
+
script.notes.push("a script block loads its content from another file (src), which is not read");
|
|
208
|
+
note("info", "vue.external-block", `${sfc.path}: a <script src> block is not read`, { path: sfc.path });
|
|
209
|
+
}
|
|
210
|
+
const template = analyzeTemplate(descriptor);
|
|
211
|
+
const generic = descriptor.scriptSetup?.attrs.generic;
|
|
212
|
+
if (typeof generic === "string")
|
|
213
|
+
script.notes.push(`generic component <${generic}>`);
|
|
214
|
+
const localName = sfcLocalName(sfc.path);
|
|
215
|
+
const slots = mergeSlots(script.slots, template);
|
|
216
|
+
const variants = variantsFromProps(script.props.map(toSpec));
|
|
217
|
+
return {
|
|
218
|
+
ref: input.ref,
|
|
219
|
+
localName,
|
|
220
|
+
file: sfc.path,
|
|
221
|
+
line: firstBlockLine(descriptor),
|
|
222
|
+
kind: "sfc",
|
|
223
|
+
exports: [...input.exports],
|
|
224
|
+
sfc,
|
|
225
|
+
script,
|
|
226
|
+
template,
|
|
227
|
+
...(descriptor.template ? { templateLine: descriptor.template.loc.start.line } : {}),
|
|
228
|
+
styling: stylingOf(descriptor),
|
|
229
|
+
variants,
|
|
230
|
+
slots,
|
|
231
|
+
renders: rendersOf({ template, script, sfcPath: sfc.path, refOf: input.refOf }),
|
|
232
|
+
propsComplete: script.complete && typeof generic !== "string",
|
|
233
|
+
...(typeof generic === "string" ? { generic } : {}),
|
|
234
|
+
locate: (node) => {
|
|
235
|
+
const virtual = relativeFileOf(analysis, node);
|
|
236
|
+
const line = lineOf(node);
|
|
237
|
+
const script = sfc.script;
|
|
238
|
+
return script && virtual === script.path
|
|
239
|
+
? { file: sfc.path, line: script.lineOf(line) }
|
|
240
|
+
: { file: virtual ?? sfc.path, line };
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function buildTsComponent(input) {
|
|
245
|
+
const { analysis, decl, script } = input;
|
|
246
|
+
const file = relativeFileOf(analysis, decl);
|
|
247
|
+
return {
|
|
248
|
+
ref: input.ref,
|
|
249
|
+
localName: input.localName ?? decl.name.text,
|
|
250
|
+
file,
|
|
251
|
+
line: lineOf(decl),
|
|
252
|
+
kind: input.kind ?? "defineComponent",
|
|
253
|
+
exports: [input.exported],
|
|
254
|
+
script,
|
|
255
|
+
styling: { variables: [], scoped: false, module: false, preprocessors: [], blocks: 0 },
|
|
256
|
+
variants: variantsFromProps(script.props.map(toSpec)),
|
|
257
|
+
slots: mergeSlots(script.slots, { slots: [] }),
|
|
258
|
+
renders: [],
|
|
259
|
+
propsComplete: script.complete,
|
|
260
|
+
locate: (node) => ({ file: relativeFileOf(analysis, node) ?? file, line: lineOf(node) })
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
function firstBlockLine(descriptor) {
|
|
264
|
+
const lines = [descriptor.script, descriptor.scriptSetup, descriptor.template].flatMap((b) => b ? [b.loc.start.line] : []);
|
|
265
|
+
return lines.length > 0 ? Math.min(...lines) : 1;
|
|
266
|
+
}
|
|
267
|
+
function toSpec(p) {
|
|
268
|
+
return {
|
|
269
|
+
name: p.name,
|
|
270
|
+
typeText: p.typeText,
|
|
271
|
+
kind: p.kind,
|
|
272
|
+
required: p.required,
|
|
273
|
+
...(p.default !== undefined ? { default: p.default } : {}),
|
|
274
|
+
...(p.allowedValues ? { allowedValues: p.allowedValues } : {})
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function mergeSlots(declared, template) {
|
|
278
|
+
const byName = new Map();
|
|
279
|
+
for (const s of declared)
|
|
280
|
+
byName.set(s.name, {
|
|
281
|
+
name: s.name,
|
|
282
|
+
...(s.propsText ? { propsText: s.propsText } : {}),
|
|
283
|
+
templateProps: [],
|
|
284
|
+
sources: ["defineSlots"],
|
|
285
|
+
node: s.node
|
|
286
|
+
});
|
|
287
|
+
for (const s of template.slots) {
|
|
288
|
+
if (s.dynamic || !s.name)
|
|
289
|
+
continue;
|
|
290
|
+
const existing = byName.get(s.name);
|
|
291
|
+
if (existing) {
|
|
292
|
+
existing.sources.push("template");
|
|
293
|
+
existing.templateProps = [...new Set([...existing.templateProps, ...s.props])].sort();
|
|
294
|
+
existing.line ??= s.line;
|
|
295
|
+
}
|
|
296
|
+
else
|
|
297
|
+
byName.set(s.name, { name: s.name, templateProps: s.props, sources: ["template"], line: s.line });
|
|
298
|
+
}
|
|
299
|
+
return [...byName.values()].sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
300
|
+
}
|
|
301
|
+
function stylingOf(descriptor) {
|
|
302
|
+
const variables = new Map();
|
|
303
|
+
for (const block of descriptor.styles) {
|
|
304
|
+
for (const ref of cssVariableReferences(block.content))
|
|
305
|
+
if (!variables.has(ref.name))
|
|
306
|
+
variables.set(ref.name, block.loc.start.line + ref.line - 1);
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
variables: [...variables]
|
|
310
|
+
.map(([name, line]) => ({ name, line }))
|
|
311
|
+
.sort((a, b) => (a.name < b.name ? -1 : 1)),
|
|
312
|
+
scoped: descriptor.styles.some((s) => s.scoped),
|
|
313
|
+
module: descriptor.styles.some((s) => s.module),
|
|
314
|
+
preprocessors: [...new Set(descriptor.styles.flatMap((s) => (s.lang ? [s.lang] : [])))].sort(),
|
|
315
|
+
blocks: descriptor.styles.length
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
/** Component tags used in the template, resolved through the script's imports to other public components. */
|
|
319
|
+
function rendersOf(input) {
|
|
320
|
+
const { template, script, sfcPath, refOf } = input;
|
|
321
|
+
const byLocal = new Map();
|
|
322
|
+
for (const [local, spec] of script.imports) {
|
|
323
|
+
if (!spec.startsWith(".") || !spec.endsWith(".vue"))
|
|
324
|
+
continue;
|
|
325
|
+
byLocal.set(local, posix.normalize(posix.join(posix.dirname(sfcPath), spec)));
|
|
326
|
+
}
|
|
327
|
+
const counts = new Map();
|
|
328
|
+
for (const { tag, count } of template.components) {
|
|
329
|
+
const pascal = tag.includes("-")
|
|
330
|
+
? tag
|
|
331
|
+
.split("-")
|
|
332
|
+
.map((p) => p.charAt(0).toUpperCase() + p.slice(1))
|
|
333
|
+
.join("")
|
|
334
|
+
: tag;
|
|
335
|
+
const file = byLocal.get(pascal) ?? byLocal.get(tag);
|
|
336
|
+
const target = file ? refOf.get(file) : undefined;
|
|
337
|
+
if (target && target !== refOf.get(sfcPath))
|
|
338
|
+
counts.set(target, (counts.get(target) ?? 0) + count);
|
|
339
|
+
}
|
|
340
|
+
return [...counts]
|
|
341
|
+
.map(([target, count]) => ({ target, count }))
|
|
342
|
+
.sort((a, b) => (a.target < b.target ? -1 : 1));
|
|
343
|
+
}
|
|
344
|
+
//# sourceMappingURL=analyze.js.map
|