@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,312 @@
|
|
|
1
|
+
import { hashJson } from "../../shared/index.js";
|
|
2
|
+
import { cmp } from "../discovery/support.js";
|
|
3
|
+
import { IrBuilder } from "../ir-builder.js";
|
|
4
|
+
export const npmPackageAdapter = {
|
|
5
|
+
id: "inventory-npm",
|
|
6
|
+
version: "0.1.0",
|
|
7
|
+
type: "npm",
|
|
8
|
+
async discover({ project }) {
|
|
9
|
+
const ids = new SourceIds("npm");
|
|
10
|
+
return {
|
|
11
|
+
sources: project.packages.map((pkg) => ({
|
|
12
|
+
id: ids.next(pkg.name ?? pkg.dir),
|
|
13
|
+
type: "npm",
|
|
14
|
+
location: pkg.dir,
|
|
15
|
+
config: { packageId: pkg.id },
|
|
16
|
+
...(pkg.version
|
|
17
|
+
? { version: { kind: "package-version", value: pkg.version } }
|
|
18
|
+
: {}),
|
|
19
|
+
inputHash: hashJson({
|
|
20
|
+
manifest: pkg.manifest.contentHash,
|
|
21
|
+
parsed: pkg.entryPoints.flatMap((e) => e.parsedFiles.map((f) => [f.path, f.contentHash]))
|
|
22
|
+
})
|
|
23
|
+
})),
|
|
24
|
+
diagnostics: []
|
|
25
|
+
};
|
|
26
|
+
},
|
|
27
|
+
async extract({ project, source, clock }, config) {
|
|
28
|
+
const pkg = requirePackage(project, config.packageId);
|
|
29
|
+
const builder = new IrBuilder({
|
|
30
|
+
source: irSource(source, npmPackageAdapter),
|
|
31
|
+
extractor: { name: "inventory-npm", version: npmPackageAdapter.version },
|
|
32
|
+
capabilities: ["package.*", "frameworks", "buildTools", "entryPoints", "export.*"],
|
|
33
|
+
clock
|
|
34
|
+
});
|
|
35
|
+
const manifest = (line) => builder.evidence({
|
|
36
|
+
sourceRef: pkg.manifest.path,
|
|
37
|
+
location: { type: "file", path: pkg.manifest.path, ...(line ? { line } : {}) },
|
|
38
|
+
method: "structured-parse"
|
|
39
|
+
});
|
|
40
|
+
const subject = builder.entity(pkg.id, "package", pkg.name ?? pkg.dir);
|
|
41
|
+
const base = manifest();
|
|
42
|
+
if (pkg.name)
|
|
43
|
+
builder.fact(subject, "package.name", pkg.name, base);
|
|
44
|
+
if (pkg.version)
|
|
45
|
+
builder.fact(subject, "package.version", pkg.version, base);
|
|
46
|
+
builder.fact(subject, "package.private", pkg.private, base);
|
|
47
|
+
builder.fact(subject, "package.directory", pkg.dir, base);
|
|
48
|
+
if (pkg.dir === ".")
|
|
49
|
+
builder.fact(subject, "package.manager", project.packageManager, base);
|
|
50
|
+
builder.fact(subject, "frameworks", pkg.frameworks, manifest(pkg.frameworks[0]?.line));
|
|
51
|
+
builder.fact(subject, "buildTools", pkg.buildTools, base);
|
|
52
|
+
builder.fact(subject, "entryPoints", pkg.entryPoints.map(({ subpath, condition, target, resolution, file, exportsComplete }) => ({
|
|
53
|
+
subpath,
|
|
54
|
+
condition,
|
|
55
|
+
target,
|
|
56
|
+
resolution,
|
|
57
|
+
...(file ? { file } : {}),
|
|
58
|
+
exportsComplete
|
|
59
|
+
})), base);
|
|
60
|
+
for (const entry of pkg.entryPoints) {
|
|
61
|
+
for (const exported of entry.exports) {
|
|
62
|
+
const ref = `${pkg.id}#${entry.subpath}:${exported.name}`;
|
|
63
|
+
builder.entity(ref, "export", exported.name);
|
|
64
|
+
const evidence = builder.evidence({
|
|
65
|
+
sourceRef: `${exported.file}#${exported.name}`,
|
|
66
|
+
location: { type: "file", path: exported.file, line: exported.line },
|
|
67
|
+
method: "ast"
|
|
68
|
+
});
|
|
69
|
+
const completeness = entry.exportsComplete ? "complete" : "partial";
|
|
70
|
+
builder.fact(ref, "export.name", exported.name, evidence, { completeness });
|
|
71
|
+
builder.fact(ref, "export.kind", exported.kind, evidence, { completeness });
|
|
72
|
+
builder.fact(ref, "export.typeOnly", exported.typeOnly, evidence, { completeness });
|
|
73
|
+
builder.fact(ref, "export.entry", entry.subpath, evidence, { completeness });
|
|
74
|
+
if (exported.reexportOf)
|
|
75
|
+
builder.fact(ref, "export.reexportOf", exported.reexportOf, evidence, { completeness });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
for (const d of diagnosticsAbout(project, [
|
|
79
|
+
pkg.manifest.path,
|
|
80
|
+
...pkg.entryPoints.flatMap((e) => e.parsedFiles.map((f) => f.path))
|
|
81
|
+
]))
|
|
82
|
+
builder.diagnostic(d);
|
|
83
|
+
const partial = pkg.entryPoints.some((e) => !e.exportsComplete);
|
|
84
|
+
return { ir: builder.build(partial ? "partial" : "complete") };
|
|
85
|
+
},
|
|
86
|
+
async validate() {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
export const storybookConfigAdapter = {
|
|
91
|
+
id: "inventory-storybook",
|
|
92
|
+
version: "0.1.0",
|
|
93
|
+
type: "storybook",
|
|
94
|
+
async discover({ project }) {
|
|
95
|
+
const ids = new SourceIds("storybook");
|
|
96
|
+
return {
|
|
97
|
+
sources: project.storybooks.map((sb) => ({
|
|
98
|
+
id: ids.next(sb.directory === "." ? "root" : sb.directory),
|
|
99
|
+
type: "storybook",
|
|
100
|
+
location: sb.directory,
|
|
101
|
+
config: { storybookId: sb.id },
|
|
102
|
+
...(sb.version?.installedVersion
|
|
103
|
+
? { version: { kind: "tool-version", value: sb.version.installedVersion } }
|
|
104
|
+
: {}),
|
|
105
|
+
inputHash: hashJson({ config: sb.configHash, stories: sb.storyFiles })
|
|
106
|
+
})),
|
|
107
|
+
diagnostics: []
|
|
108
|
+
};
|
|
109
|
+
},
|
|
110
|
+
async extract({ project, source, clock }, config) {
|
|
111
|
+
const sb = project.storybooks.find((s) => s.id === config.storybookId);
|
|
112
|
+
if (!sb)
|
|
113
|
+
throw new Error(`Storybook ${config.storybookId} is not in the project discovery`);
|
|
114
|
+
const builder = new IrBuilder({
|
|
115
|
+
source: irSource(source, storybookConfigAdapter),
|
|
116
|
+
extractor: { name: "inventory-storybook", version: storybookConfigAdapter.version },
|
|
117
|
+
capabilities: ["config.*"],
|
|
118
|
+
clock
|
|
119
|
+
});
|
|
120
|
+
const subject = builder.entity(sb.id, "config", `Storybook (${sb.directory})`);
|
|
121
|
+
const evidence = builder.evidence({
|
|
122
|
+
sourceRef: sb.configPath,
|
|
123
|
+
location: { type: "file", path: sb.configPath },
|
|
124
|
+
method: "ast"
|
|
125
|
+
});
|
|
126
|
+
const complete = sb.dynamicProperties.length === 0 ? "complete" : "partial";
|
|
127
|
+
const put = (path, value, completeness = complete) => {
|
|
128
|
+
if (value !== undefined)
|
|
129
|
+
builder.fact(subject, path, value, evidence, { completeness });
|
|
130
|
+
};
|
|
131
|
+
put("config.path", sb.configPath);
|
|
132
|
+
put("config.framework", sb.framework);
|
|
133
|
+
put("config.addons", sb.addons);
|
|
134
|
+
put("config.stories", sb.stories);
|
|
135
|
+
put("config.staticDirs", sb.staticDirs);
|
|
136
|
+
put("config.dynamicProperties", sb.dynamicProperties);
|
|
137
|
+
put("config.version", sb.version);
|
|
138
|
+
put("config.builtOutput", sb.builtOutput);
|
|
139
|
+
put("config.storyFileCount", sb.storyFileCount);
|
|
140
|
+
put("config.storyFiles", sb.storyFiles, sb.storyFilesTruncated ? "partial" : "complete");
|
|
141
|
+
for (const d of diagnosticsAbout(project, [sb.configPath]))
|
|
142
|
+
builder.diagnostic(d);
|
|
143
|
+
return {
|
|
144
|
+
ir: builder.build(sb.dynamicProperties.length > 0 || sb.storyFilesTruncated ? "partial" : "complete")
|
|
145
|
+
};
|
|
146
|
+
},
|
|
147
|
+
async validate() {
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
export const tokenFilesAdapter = {
|
|
152
|
+
id: "inventory-tokens",
|
|
153
|
+
version: "0.1.0",
|
|
154
|
+
type: "tokens",
|
|
155
|
+
async discover({ project }) {
|
|
156
|
+
const ids = new SourceIds("tokens");
|
|
157
|
+
return {
|
|
158
|
+
sources: groupByPackage(project, project.tokenFiles).map(({ pkg, files }) => ({
|
|
159
|
+
id: ids.next(pkg?.name ?? pkg?.dir ?? "root"),
|
|
160
|
+
type: "tokens",
|
|
161
|
+
location: pkg?.dir ?? ".",
|
|
162
|
+
config: { packageId: pkg?.id ?? "", files: files.map((f) => f.path) },
|
|
163
|
+
inputHash: hashJson(files.map((f) => [f.path, f.contentHash]))
|
|
164
|
+
})),
|
|
165
|
+
diagnostics: []
|
|
166
|
+
};
|
|
167
|
+
},
|
|
168
|
+
async extract({ project, source, clock }, config) {
|
|
169
|
+
const builder = new IrBuilder({
|
|
170
|
+
source: irSource(source, tokenFilesAdapter),
|
|
171
|
+
extractor: { name: "inventory-tokens", version: tokenFilesAdapter.version },
|
|
172
|
+
capabilities: ["file.*", "name", "extensions.css.*"],
|
|
173
|
+
clock
|
|
174
|
+
});
|
|
175
|
+
let partial = false;
|
|
176
|
+
for (const file of project.tokenFiles.filter((f) => config.files.includes(f.path))) {
|
|
177
|
+
const fileRef = builder.entity(`token-file:${file.path}`, "token-file", file.path);
|
|
178
|
+
const fileEvidence = builder.evidence({
|
|
179
|
+
sourceRef: file.path,
|
|
180
|
+
location: { type: "file", path: file.path },
|
|
181
|
+
method: "structured-parse"
|
|
182
|
+
});
|
|
183
|
+
builder.fact(fileRef, "file.kind", file.kind, fileEvidence);
|
|
184
|
+
if (file.format)
|
|
185
|
+
builder.fact(fileRef, "file.format", file.format, fileEvidence);
|
|
186
|
+
if (file.tokenCount !== undefined)
|
|
187
|
+
builder.fact(fileRef, "file.tokenCount", file.tokenCount, fileEvidence);
|
|
188
|
+
if (file.cssVariablesTruncated)
|
|
189
|
+
partial = true;
|
|
190
|
+
for (const variable of file.cssVariables ?? []) {
|
|
191
|
+
const ref = `css:${file.path}#${variable.name}@${variable.scope}`;
|
|
192
|
+
builder.entity(ref, "token", variable.name);
|
|
193
|
+
const evidence = builder.evidence({
|
|
194
|
+
sourceRef: `${file.path}#${variable.name}`,
|
|
195
|
+
location: { type: "file", path: file.path, line: variable.line },
|
|
196
|
+
method: "structured-parse",
|
|
197
|
+
original: `${variable.name}: ${variable.value};`
|
|
198
|
+
});
|
|
199
|
+
builder.fact(ref, "name", variable.name, evidence);
|
|
200
|
+
builder.fact(ref, "extensions.css.scope", variable.scope, evidence);
|
|
201
|
+
builder.fact(ref, "extensions.css.rawValue", variable.value, evidence);
|
|
202
|
+
if (variable.aliasOf)
|
|
203
|
+
builder.fact(ref, "extensions.css.aliasOf", variable.aliasOf, evidence);
|
|
204
|
+
}
|
|
205
|
+
for (const d of diagnosticsAbout(project, [file.path]))
|
|
206
|
+
builder.diagnostic(d);
|
|
207
|
+
}
|
|
208
|
+
return { ir: builder.build(partial ? "partial" : "complete") };
|
|
209
|
+
},
|
|
210
|
+
async validate() {
|
|
211
|
+
return [];
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
export const markdownDocsAdapter = {
|
|
215
|
+
id: "inventory-markdown",
|
|
216
|
+
version: "0.1.0",
|
|
217
|
+
type: "markdown",
|
|
218
|
+
async discover({ project }) {
|
|
219
|
+
const ids = new SourceIds("docs");
|
|
220
|
+
return {
|
|
221
|
+
sources: groupByPackage(project, project.docFiles).map(({ pkg, files }) => ({
|
|
222
|
+
id: ids.next(pkg?.name ?? pkg?.dir ?? "root"),
|
|
223
|
+
type: "markdown",
|
|
224
|
+
location: pkg?.dir ?? ".",
|
|
225
|
+
config: { packageId: pkg?.id ?? "", files: files.map((f) => f.path) },
|
|
226
|
+
inputHash: hashJson(files.map((f) => [f.path, f.contentHash]))
|
|
227
|
+
})),
|
|
228
|
+
diagnostics: []
|
|
229
|
+
};
|
|
230
|
+
},
|
|
231
|
+
async extract({ project, source, clock }, config) {
|
|
232
|
+
const builder = new IrBuilder({
|
|
233
|
+
source: irSource(source, markdownDocsAdapter),
|
|
234
|
+
extractor: { name: "inventory-markdown", version: markdownDocsAdapter.version },
|
|
235
|
+
capabilities: ["doc.*"],
|
|
236
|
+
clock
|
|
237
|
+
});
|
|
238
|
+
for (const file of project.docFiles.filter((f) => config.files.includes(f.path))) {
|
|
239
|
+
const ref = builder.entity(`doc:${file.path}`, "doc-page", file.title ?? file.path);
|
|
240
|
+
const evidence = builder.evidence({
|
|
241
|
+
sourceRef: file.path,
|
|
242
|
+
location: { type: "file", path: file.path, line: 1 },
|
|
243
|
+
method: "document-parse"
|
|
244
|
+
});
|
|
245
|
+
builder.fact(ref, "doc.kind", file.kind, evidence, { origin: "authored" });
|
|
246
|
+
if (file.title)
|
|
247
|
+
builder.fact(ref, "doc.title", file.title, evidence, { origin: "authored" });
|
|
248
|
+
builder.fact(ref, "doc.headings", file.headings, evidence, {
|
|
249
|
+
origin: "authored"
|
|
250
|
+
});
|
|
251
|
+
builder.fact(ref, "doc.lineCount", file.lineCount, evidence, { origin: "authored" });
|
|
252
|
+
}
|
|
253
|
+
return { ir: builder.build("complete") };
|
|
254
|
+
},
|
|
255
|
+
async validate() {
|
|
256
|
+
return [];
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
export const INVENTORY_ADAPTERS = [
|
|
260
|
+
npmPackageAdapter,
|
|
261
|
+
storybookConfigAdapter,
|
|
262
|
+
tokenFilesAdapter,
|
|
263
|
+
markdownDocsAdapter
|
|
264
|
+
];
|
|
265
|
+
function irSource(source, adapter) {
|
|
266
|
+
return {
|
|
267
|
+
id: source.id,
|
|
268
|
+
type: source.type,
|
|
269
|
+
location: source.location,
|
|
270
|
+
...(source.version ? { version: source.version } : {}),
|
|
271
|
+
inputHash: source.inputHash,
|
|
272
|
+
adapter: { name: adapter.id, version: adapter.version }
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
function requirePackage(project, id) {
|
|
276
|
+
const pkg = project.packages.find((p) => p.id === id);
|
|
277
|
+
if (!pkg)
|
|
278
|
+
throw new Error(`Package ${id} is not in the project discovery`);
|
|
279
|
+
return pkg;
|
|
280
|
+
}
|
|
281
|
+
function diagnosticsAbout(project, paths) {
|
|
282
|
+
const set = new Set(paths);
|
|
283
|
+
return project.diagnostics.filter((d) => d.location !== undefined && set.has(d.location.path));
|
|
284
|
+
}
|
|
285
|
+
function groupByPackage(project, files) {
|
|
286
|
+
const groups = new Map();
|
|
287
|
+
for (const file of files)
|
|
288
|
+
groups.set(file.package ?? "", [...(groups.get(file.package ?? "") ?? []), file]);
|
|
289
|
+
return [...groups]
|
|
290
|
+
.map(([id, group]) => ({ pkg: project.packages.find((p) => p.id === id), files: group }))
|
|
291
|
+
.sort((a, b) => cmp(a.pkg?.dir ?? "", b.pkg?.dir ?? ""));
|
|
292
|
+
}
|
|
293
|
+
/** Slug generator that stays unique within one adapter run. */
|
|
294
|
+
class SourceIds {
|
|
295
|
+
prefix;
|
|
296
|
+
used = new Set();
|
|
297
|
+
constructor(prefix) {
|
|
298
|
+
this.prefix = prefix;
|
|
299
|
+
}
|
|
300
|
+
next(label) {
|
|
301
|
+
const slug = label
|
|
302
|
+
.toLowerCase()
|
|
303
|
+
.replace(/[^a-z0-9._-]+/g, "-")
|
|
304
|
+
.replace(/^[^a-z0-9]+|[-.]+$/g, "") || "root";
|
|
305
|
+
let id = `${this.prefix}-${slug}`;
|
|
306
|
+
for (let n = 2; this.used.has(id); n++)
|
|
307
|
+
id = `${this.prefix}-${slug}-${n}`;
|
|
308
|
+
this.used.add(id);
|
|
309
|
+
return id;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
//# sourceMappingURL=inventory.js.map
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { canonicalizeIrDocument, computeIrContentHash, IR_VERSION, irDocumentSchema, verifyIrDocument } from "../compiler-ir/index.js";
|
|
2
|
+
import { canonicalStringify, DIAGNOSTICS_FILE_VERSION, DiagnosticCollector, hasErrors, sortDiagnostics } from "../shared/index.js";
|
|
3
|
+
import { INVENTORY_ADAPTERS } from "./adapters/inventory.js";
|
|
4
|
+
import { discoverProject } from "./discovery/index.js";
|
|
5
|
+
/**
|
|
6
|
+
* Orchestrates discover, extract and validate for every adapter, in a fixed order (adapter id, then
|
|
7
|
+
* source id). A failing adapter never aborts the run: it yields a `failed` IR document and an error
|
|
8
|
+
* diagnostic, because a missing source must not be read as proof that its facts do not exist.
|
|
9
|
+
* Stops at IR; reconciliation and contract generation belong to later phases.
|
|
10
|
+
*/
|
|
11
|
+
export async function compile(options) {
|
|
12
|
+
const clock = options.clock ?? (() => new Date().toISOString());
|
|
13
|
+
const signal = options.signal;
|
|
14
|
+
const collector = new DiagnosticCollector();
|
|
15
|
+
signal?.throwIfAborted();
|
|
16
|
+
const project = await discoverProject({ fs: options.fs, ...options.discovery });
|
|
17
|
+
collector.addAll(project.diagnostics);
|
|
18
|
+
const adapters = [...(options.adapters ?? INVENTORY_ADAPTERS)]
|
|
19
|
+
.filter((a) => !options.only || options.only.includes(a.id))
|
|
20
|
+
.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
21
|
+
const planned = [];
|
|
22
|
+
const seen = new Set();
|
|
23
|
+
for (const adapter of adapters) {
|
|
24
|
+
signal?.throwIfAborted();
|
|
25
|
+
try {
|
|
26
|
+
const found = await adapter.discover({
|
|
27
|
+
fs: options.fs,
|
|
28
|
+
project,
|
|
29
|
+
...(signal ? { signal } : {})
|
|
30
|
+
});
|
|
31
|
+
collector.addAll(found.diagnostics);
|
|
32
|
+
for (const source of [...found.sources].sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))) {
|
|
33
|
+
if (seen.has(source.id)) {
|
|
34
|
+
collector.error("compile.duplicate-source-id", `Source id ${source.id} from adapter ${adapter.id} is already in use; skipped`, { sourceId: source.id });
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
seen.add(source.id);
|
|
38
|
+
planned.push({ adapter, source });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
collector.error("compile.adapter-failed", `Adapter ${adapter.id} failed during discovery: ${messageOf(error)}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const irDocuments = [];
|
|
46
|
+
const sources = [];
|
|
47
|
+
for (const { adapter, source } of planned) {
|
|
48
|
+
signal?.throwIfAborted();
|
|
49
|
+
const ir = await extractOne({ adapter, source, options, project, clock, collector });
|
|
50
|
+
irDocuments.push(ir);
|
|
51
|
+
sources.push({
|
|
52
|
+
id: source.id,
|
|
53
|
+
type: source.type,
|
|
54
|
+
adapterId: adapter.id,
|
|
55
|
+
adapterVersion: adapter.version,
|
|
56
|
+
inputHash: source.inputHash,
|
|
57
|
+
irContentHash: computeIrContentHash(ir),
|
|
58
|
+
completeness: ir.completeness
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
const diagnostics = dedupe(collector.toArray());
|
|
62
|
+
return { project, sources, irDocuments, diagnostics, ok: !hasErrors(diagnostics) };
|
|
63
|
+
}
|
|
64
|
+
/** Entries for the artifact generator: project discovery, one IR file per source, and diagnostics. */
|
|
65
|
+
export function toArtifactEntries(result) {
|
|
66
|
+
return [
|
|
67
|
+
{ path: "invariant.project.json", value: result.project },
|
|
68
|
+
...result.irDocuments.map((ir) => ({
|
|
69
|
+
path: `ir/${ir.source.id}.ir.json`,
|
|
70
|
+
value: ir
|
|
71
|
+
})),
|
|
72
|
+
{
|
|
73
|
+
path: "invariant.diagnostics.json",
|
|
74
|
+
value: {
|
|
75
|
+
diagnosticsVersion: DIAGNOSTICS_FILE_VERSION,
|
|
76
|
+
diagnostics: result.diagnostics
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
];
|
|
80
|
+
}
|
|
81
|
+
async function extractOne(input) {
|
|
82
|
+
const { adapter, source, options, project, clock, collector } = input;
|
|
83
|
+
const failed = (message) => failedDocument({ adapter, source, clock, message });
|
|
84
|
+
let ir;
|
|
85
|
+
try {
|
|
86
|
+
const result = await adapter.extract({ fs: options.fs, project, source, clock, ...(options.signal ? { signal: options.signal } : {}) }, source.config);
|
|
87
|
+
const parsed = irDocumentSchema.safeParse(result.ir);
|
|
88
|
+
if (!parsed.success) {
|
|
89
|
+
collector.error("compile.invalid-ir", `Adapter ${adapter.id} produced an invalid IR document for ${source.id}: ${parsed.error.issues[0]?.message ?? "schema error"}`, { sourceId: source.id });
|
|
90
|
+
return failed("invalid IR");
|
|
91
|
+
}
|
|
92
|
+
ir = canonicalizeIrDocument(parsed.data);
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
if (options.signal?.aborted)
|
|
96
|
+
throw error;
|
|
97
|
+
collector.error("compile.adapter-failed", `Adapter ${adapter.id} failed extracting ${source.id}: ${messageOf(error)}`, { sourceId: source.id });
|
|
98
|
+
return failed(messageOf(error));
|
|
99
|
+
}
|
|
100
|
+
collector.addAll(verifyIrDocument(ir));
|
|
101
|
+
collector.addAll(ir.diagnostics);
|
|
102
|
+
try {
|
|
103
|
+
collector.addAll(await adapter.validate({ project, source, ir }));
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
collector.error("compile.adapter-failed", `Adapter ${adapter.id} failed validating ${source.id}: ${messageOf(error)}`, { sourceId: source.id });
|
|
107
|
+
}
|
|
108
|
+
return ir;
|
|
109
|
+
}
|
|
110
|
+
function failedDocument(input) {
|
|
111
|
+
const { adapter, source, message } = input;
|
|
112
|
+
return {
|
|
113
|
+
irVersion: IR_VERSION,
|
|
114
|
+
source: {
|
|
115
|
+
id: source.id,
|
|
116
|
+
type: source.type,
|
|
117
|
+
location: source.location,
|
|
118
|
+
...(source.version ? { version: source.version } : {}),
|
|
119
|
+
inputHash: source.inputHash,
|
|
120
|
+
adapter: { name: adapter.id, version: adapter.version }
|
|
121
|
+
},
|
|
122
|
+
capabilities: [],
|
|
123
|
+
completeness: "failed",
|
|
124
|
+
entities: [],
|
|
125
|
+
facts: [],
|
|
126
|
+
evidence: [],
|
|
127
|
+
diagnostics: [{ code: "compile.adapter-failed", severity: "error", message, sourceId: source.id }]
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Drops duplicate diagnostics. The same finding is often reported once by discovery and again, with a
|
|
132
|
+
* `sourceId`, when an adapter forwards it into its IR; those are merged, keeping the attributed copy.
|
|
133
|
+
*/
|
|
134
|
+
function dedupe(diagnostics) {
|
|
135
|
+
const byKey = new Map();
|
|
136
|
+
for (const d of diagnostics) {
|
|
137
|
+
const { sourceId: _sourceId, ...rest } = d;
|
|
138
|
+
const key = canonicalStringify(rest);
|
|
139
|
+
const existing = byKey.get(key);
|
|
140
|
+
if (!existing || (existing.sourceId === undefined && d.sourceId !== undefined))
|
|
141
|
+
byKey.set(key, d);
|
|
142
|
+
}
|
|
143
|
+
return sortDiagnostics([...byKey.values()]);
|
|
144
|
+
}
|
|
145
|
+
function messageOf(error) {
|
|
146
|
+
return error instanceof Error ? error.message : String(error);
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=compile.js.map
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
const FACT_SEGMENT = /^[A-Za-z0-9_:-]+$/;
|
|
2
|
+
/**
|
|
3
|
+
* Writes a component's facts with the neutral paths every framework adapter shares, so React, Angular
|
|
4
|
+
* and Vue describe equivalent concepts identically and keep framework detail under
|
|
5
|
+
* `extensions.<framework>`. Names that cannot be addressed by a fact path are reported through `skipped`.
|
|
6
|
+
*/
|
|
7
|
+
export class ComponentFacts {
|
|
8
|
+
skipped = [];
|
|
9
|
+
builder;
|
|
10
|
+
ref;
|
|
11
|
+
framework;
|
|
12
|
+
constructor(builder, ref, framework) {
|
|
13
|
+
this.builder = builder;
|
|
14
|
+
this.ref = ref;
|
|
15
|
+
this.framework = framework;
|
|
16
|
+
}
|
|
17
|
+
put(path, value, evidence, origin = "extracted") {
|
|
18
|
+
this.builder.fact(this.ref, path, value, evidence, { origin });
|
|
19
|
+
}
|
|
20
|
+
/** `implementations.<fw>.extensions.<fw>.<path>` */
|
|
21
|
+
ext(path, value, evidence) {
|
|
22
|
+
this.put(`implementations.${this.framework}.extensions.${this.framework}.${path}`, value, evidence);
|
|
23
|
+
}
|
|
24
|
+
/** The neutral link key: sources are matched on (file, localName), never on display names. */
|
|
25
|
+
link(file, localName, evidence) {
|
|
26
|
+
this.put(`implementations.${this.framework}.framework`, this.framework, evidence);
|
|
27
|
+
this.ext("file", file, evidence);
|
|
28
|
+
this.ext("localName", localName, evidence);
|
|
29
|
+
}
|
|
30
|
+
prop(spec, evidence, extra = {}) {
|
|
31
|
+
if (!this.addressable(spec.name, "prop"))
|
|
32
|
+
return;
|
|
33
|
+
const base = `props.${spec.name}`;
|
|
34
|
+
this.put(`${base}.type.text`, spec.typeText, evidence);
|
|
35
|
+
this.put(`${base}.type.kind`, spec.kind, evidence);
|
|
36
|
+
this.put(`${base}.required`, spec.required, evidence);
|
|
37
|
+
if (spec.default !== undefined)
|
|
38
|
+
this.put(`${base}.default`, spec.default, [evidence, ...(extra.default ?? [])]);
|
|
39
|
+
if (spec.defaultExpression !== undefined)
|
|
40
|
+
this.put(`${base}.extensions.${this.framework}.defaultExpression`, spec.defaultExpression, extra.default?.[0] ?? evidence);
|
|
41
|
+
if (spec.allowedValues)
|
|
42
|
+
this.put(`${base}.allowedValues`, spec.allowedValues, evidence);
|
|
43
|
+
if (spec.description)
|
|
44
|
+
this.put(`${base}.description`, spec.description, evidence, "authored");
|
|
45
|
+
if (spec.deprecated)
|
|
46
|
+
this.put(`${base}.deprecated`, spec.deprecated, evidence, "authored");
|
|
47
|
+
for (const [key, value] of Object.entries(spec.extensions ?? {}))
|
|
48
|
+
this.put(`${base}.extensions.${this.framework}.${key}`, value, evidence);
|
|
49
|
+
}
|
|
50
|
+
event(spec, evidence) {
|
|
51
|
+
if (!this.addressable(spec.name, "event"))
|
|
52
|
+
return;
|
|
53
|
+
const base = `events.${spec.name}`;
|
|
54
|
+
this.put(`${base}.declared`, true, evidence);
|
|
55
|
+
if (spec.payloadText)
|
|
56
|
+
this.put(`${base}.payload.text`, spec.payloadText, evidence);
|
|
57
|
+
if (spec.description)
|
|
58
|
+
this.put(`${base}.description`, spec.description, evidence, "authored");
|
|
59
|
+
if (spec.deprecated)
|
|
60
|
+
this.put(`${base}.deprecated`, spec.deprecated, evidence, "authored");
|
|
61
|
+
for (const [key, value] of Object.entries(spec.extensions ?? {}))
|
|
62
|
+
this.put(`${base}.extensions.${this.framework}.${key}`, value, evidence);
|
|
63
|
+
}
|
|
64
|
+
slot(spec, evidence) {
|
|
65
|
+
if (!this.addressable(spec.name, "slot"))
|
|
66
|
+
return;
|
|
67
|
+
const base = `slots.${spec.name}`;
|
|
68
|
+
this.put(`${base}.declared`, true, evidence);
|
|
69
|
+
if (spec.required !== undefined)
|
|
70
|
+
this.put(`${base}.required`, spec.required, evidence);
|
|
71
|
+
if (spec.description)
|
|
72
|
+
this.put(`${base}.description`, spec.description, evidence, "authored");
|
|
73
|
+
for (const [key, value] of Object.entries(spec.extensions ?? {}))
|
|
74
|
+
this.put(`${base}.extensions.${this.framework}.${key}`, value, evidence);
|
|
75
|
+
}
|
|
76
|
+
variant(spec, evidence) {
|
|
77
|
+
if (!this.addressable(spec.name, "variant"))
|
|
78
|
+
return;
|
|
79
|
+
const base = `variants.${spec.name}`;
|
|
80
|
+
this.put(`${base}.values`, spec.values, evidence);
|
|
81
|
+
if (spec.default !== undefined)
|
|
82
|
+
this.put(`${base}.default`, spec.default, evidence);
|
|
83
|
+
for (const [key, value] of Object.entries(spec.extensions ?? {}))
|
|
84
|
+
this.put(`${base}.extensions.${this.framework}.${key}`, value, evidence);
|
|
85
|
+
}
|
|
86
|
+
accessibility(info, evidence) {
|
|
87
|
+
if (info.role)
|
|
88
|
+
this.put("accessibility.role", info.role, evidence);
|
|
89
|
+
if (info.attributes.length > 0)
|
|
90
|
+
this.put("accessibility.attributes", info.attributes, evidence);
|
|
91
|
+
}
|
|
92
|
+
addressable(name, what) {
|
|
93
|
+
if (FACT_SEGMENT.test(name))
|
|
94
|
+
return true;
|
|
95
|
+
this.skipped.push(`${what} "${name}"`);
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** Variants from props typed as a union of two or more string literals (the same rule in every framework). */
|
|
100
|
+
export function variantsFromProps(props) {
|
|
101
|
+
const out = [];
|
|
102
|
+
for (const p of props) {
|
|
103
|
+
const values = p.allowedValues;
|
|
104
|
+
if (p.kind !== "enum" || !values || values.length < 2 || !values.every((v) => typeof v === "string"))
|
|
105
|
+
continue;
|
|
106
|
+
const fallback = typeof p.default === "string" && values.includes(p.default) ? p.default : undefined;
|
|
107
|
+
out.push({ name: p.name, values: values, ...(fallback ? { default: fallback } : {}) });
|
|
108
|
+
}
|
|
109
|
+
return out.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
110
|
+
}
|
|
111
|
+
/** `var(--x)` references in CSS or source text, with the first line each appears on. */
|
|
112
|
+
export function cssVariableReferences(text) {
|
|
113
|
+
const seen = new Map();
|
|
114
|
+
text.split("\n").forEach((line, index) => {
|
|
115
|
+
for (const match of line.matchAll(/var\(\s*(--[\w-]+)/g))
|
|
116
|
+
if (!seen.has(match[1]))
|
|
117
|
+
seen.set(match[1], index + 1);
|
|
118
|
+
});
|
|
119
|
+
return [...seen]
|
|
120
|
+
.map(([name, line]) => ({ name, line }))
|
|
121
|
+
.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=component-facts.js.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { cmp, hashText, posix, readBounded } from "./support.js";
|
|
2
|
+
/** Markdown and MDX files outside ignored directories, with title and heading outline. */
|
|
3
|
+
export async function discoverDocFiles(env) {
|
|
4
|
+
const out = [];
|
|
5
|
+
for (const file of env.files) {
|
|
6
|
+
if (!/\.(?:md|mdx)$/i.test(file))
|
|
7
|
+
continue;
|
|
8
|
+
const text = await readBounded(env, file);
|
|
9
|
+
if (text === undefined)
|
|
10
|
+
continue;
|
|
11
|
+
const { headings, title } = outline(text, env.limits.maxHeadings);
|
|
12
|
+
out.push({
|
|
13
|
+
path: file,
|
|
14
|
+
kind: kindOf(file),
|
|
15
|
+
...(title ? { title } : {}),
|
|
16
|
+
headings,
|
|
17
|
+
lineCount: text.length === 0 ? 0 : text.split("\n").length,
|
|
18
|
+
contentHash: hashText(text)
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
return out.sort((a, b) => cmp(a.path, b.path));
|
|
22
|
+
}
|
|
23
|
+
function kindOf(file) {
|
|
24
|
+
const base = posix.basename(file);
|
|
25
|
+
if (/^readme(\.|$)/i.test(base))
|
|
26
|
+
return "readme";
|
|
27
|
+
if (/^changelog(\.|$)/i.test(base))
|
|
28
|
+
return "changelog";
|
|
29
|
+
return /\.mdx$/i.test(base) ? "mdx" : "markdown";
|
|
30
|
+
}
|
|
31
|
+
function outline(text, maxHeadings) {
|
|
32
|
+
const lines = text.split("\n");
|
|
33
|
+
let start = 0;
|
|
34
|
+
let frontmatterTitle;
|
|
35
|
+
if (lines[0]?.trim() === "---") {
|
|
36
|
+
const end = lines.findIndex((l, i) => i > 0 && l.trim() === "---");
|
|
37
|
+
if (end > 0) {
|
|
38
|
+
for (const line of lines.slice(1, end)) {
|
|
39
|
+
const match = /^title\s*:\s*(.+?)\s*$/.exec(line);
|
|
40
|
+
if (match)
|
|
41
|
+
frontmatterTitle = match[1].replace(/^["']|["']$/g, "");
|
|
42
|
+
}
|
|
43
|
+
start = end + 1;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const headings = [];
|
|
47
|
+
let fence;
|
|
48
|
+
for (let i = start; i < lines.length && headings.length < maxHeadings; i++) {
|
|
49
|
+
const line = lines[i];
|
|
50
|
+
const fenceMatch = /^\s*(```+|~~~+)/.exec(line);
|
|
51
|
+
if (fenceMatch) {
|
|
52
|
+
const marker = fenceMatch[1].slice(0, 3);
|
|
53
|
+
fence = fence === undefined ? marker : fence === marker ? undefined : fence;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (fence !== undefined)
|
|
57
|
+
continue;
|
|
58
|
+
const heading = /^(#{1,6})\s+(.+?)\s*#*\s*$/.exec(line);
|
|
59
|
+
if (heading)
|
|
60
|
+
headings.push({ level: heading[1].length, text: heading[2], line: i + 1 });
|
|
61
|
+
}
|
|
62
|
+
const firstH1 = headings.find((h) => h.level === 1)?.text;
|
|
63
|
+
const title = frontmatterTitle ?? firstH1;
|
|
64
|
+
return { headings, ...(title ? { title } : {}) };
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=docs.js.map
|