@agent-scope/manifest 1.19.0 → 1.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +349 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +87 -1
- package/dist/index.d.ts +87 -1
- package/dist/index.js +350 -14
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -30,6 +30,32 @@ interface PropDescriptor {
|
|
|
30
30
|
required: boolean;
|
|
31
31
|
/** Raw TypeScript type string as written in source. */
|
|
32
32
|
rawType: string;
|
|
33
|
+
/**
|
|
34
|
+
* Where this prop is defined.
|
|
35
|
+
*
|
|
36
|
+
* `"own"` — declared in the project's source files (component-specific prop).
|
|
37
|
+
* `"inherited"` — inherited from React/HTML/third-party types in node_modules
|
|
38
|
+
* (e.g. `onClick`, `className` from `React.ComponentProps<'button'>`).
|
|
39
|
+
*
|
|
40
|
+
* Omitted when the source cannot be determined (legacy extraction paths).
|
|
41
|
+
*/
|
|
42
|
+
source?: "own" | "inherited";
|
|
43
|
+
/**
|
|
44
|
+
* For inherited props, which category the prop belongs to — determined by
|
|
45
|
+
* the declaring interface in the type definitions.
|
|
46
|
+
*
|
|
47
|
+
* `"react"` — React-specific (Attributes, RefAttributes, DOMAttributes — includes
|
|
48
|
+
* ref, key, children, synthetic event handlers like onClick).
|
|
49
|
+
* `"html"` — HTML element attributes (HTMLAttributes, ButtonHTMLAttributes, etc. —
|
|
50
|
+
* includes className, style, id, disabled, title).
|
|
51
|
+
* `"aria"` — Accessibility attributes (AriaAttributes — aria-label, aria-hidden, etc.).
|
|
52
|
+
*
|
|
53
|
+
* When the prop comes from a third-party package (not React), this is the
|
|
54
|
+
* package name (e.g. `"class-variance-authority"`).
|
|
55
|
+
*
|
|
56
|
+
* Omitted for own props or when the group cannot be determined.
|
|
57
|
+
*/
|
|
58
|
+
sourceGroup?: string;
|
|
33
59
|
}
|
|
34
60
|
/**
|
|
35
61
|
* How the component is exported from its module.
|
|
@@ -132,6 +158,32 @@ interface ComponentDescriptor {
|
|
|
132
158
|
* `null` when no scope file is found next to the component source file.
|
|
133
159
|
*/
|
|
134
160
|
scopeFile: ScopeFileMeta | null;
|
|
161
|
+
/**
|
|
162
|
+
* The collection this component belongs to (e.g. "Forms", "Navigation").
|
|
163
|
+
* `undefined` means ungrouped.
|
|
164
|
+
*
|
|
165
|
+
* Resolution precedence (highest → lowest):
|
|
166
|
+
* 1. `@collection <name>` TSDoc tag on the component declaration
|
|
167
|
+
* 2. `export const collection = "name"` in a co-located `.scope.ts` file
|
|
168
|
+
* 3. First matching pattern in `config.collections[].patterns`
|
|
169
|
+
*/
|
|
170
|
+
collection?: string;
|
|
171
|
+
/**
|
|
172
|
+
* Whether this component is internal (not intended for external consumers).
|
|
173
|
+
* Defaults to `false`.
|
|
174
|
+
*
|
|
175
|
+
* Set to `true` when:
|
|
176
|
+
* - `@internal` TSDoc tag is present on the component declaration, OR
|
|
177
|
+
* - The component's `filePath` or `displayName` matches any pattern in
|
|
178
|
+
* `config.internalPatterns`.
|
|
179
|
+
*/
|
|
180
|
+
internal: boolean;
|
|
181
|
+
/**
|
|
182
|
+
* Search keywords extracted from `@keywords` TSDoc tag.
|
|
183
|
+
* Comma-separated values are split into individual strings.
|
|
184
|
+
* Empty when no `@keywords` tag is present.
|
|
185
|
+
*/
|
|
186
|
+
keywords: string[];
|
|
135
187
|
}
|
|
136
188
|
/**
|
|
137
189
|
* Metadata extracted from a `.scope.ts(x)` / `.scope.js(x)` file that lives
|
|
@@ -151,6 +203,17 @@ interface ScopeFileMeta {
|
|
|
151
203
|
*/
|
|
152
204
|
hasWrapper: boolean;
|
|
153
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* Configuration for a named component collection.
|
|
208
|
+
*/
|
|
209
|
+
interface CollectionConfig {
|
|
210
|
+
/** Human-readable collection name (e.g. "Forms", "Navigation"). */
|
|
211
|
+
name: string;
|
|
212
|
+
/** Optional description of the collection. */
|
|
213
|
+
description?: string;
|
|
214
|
+
/** Glob patterns matching component file paths that belong to this collection. */
|
|
215
|
+
patterns: string[];
|
|
216
|
+
}
|
|
154
217
|
/**
|
|
155
218
|
* A node in the composition tree.
|
|
156
219
|
*/
|
|
@@ -172,6 +235,11 @@ interface Manifest {
|
|
|
172
235
|
components: Record<string, ComponentDescriptor>;
|
|
173
236
|
/** Composition tree (child/parent relationships). */
|
|
174
237
|
tree: Record<string, TreeNode>;
|
|
238
|
+
/**
|
|
239
|
+
* All defined collections, echoing `config.collections`.
|
|
240
|
+
* Empty array when no collections are configured.
|
|
241
|
+
*/
|
|
242
|
+
collections: CollectionConfig[];
|
|
175
243
|
}
|
|
176
244
|
/**
|
|
177
245
|
* Configuration for `generateManifest`.
|
|
@@ -199,6 +267,24 @@ interface ManifestConfig {
|
|
|
199
267
|
* Defaults to `<rootDir>/tsconfig.json`.
|
|
200
268
|
*/
|
|
201
269
|
tsConfigFilePath?: string;
|
|
270
|
+
/**
|
|
271
|
+
* Named component collections. Each entry maps a set of glob patterns to a
|
|
272
|
+
* collection name. Components whose `filePath` matches a pattern are assigned
|
|
273
|
+
* to that collection (config-level precedence — lower than TSDoc / .scope.ts).
|
|
274
|
+
*/
|
|
275
|
+
collections?: CollectionConfig[];
|
|
276
|
+
/**
|
|
277
|
+
* Glob patterns for components that should be flagged as internal.
|
|
278
|
+
* Matched against both `filePath` and `displayName`.
|
|
279
|
+
* Config-level precedence — lower than an explicit `@internal` TSDoc tag.
|
|
280
|
+
*/
|
|
281
|
+
internalPatterns?: string[];
|
|
282
|
+
/**
|
|
283
|
+
* Glob patterns identifying icon components. Matched against both
|
|
284
|
+
* `filePath` and `displayName`. Icon-matching components are
|
|
285
|
+
* automatically flagged as internal (hidden from sidebar/index).
|
|
286
|
+
*/
|
|
287
|
+
iconPatterns?: string[];
|
|
202
288
|
}
|
|
203
289
|
|
|
204
290
|
/**
|
|
@@ -206,4 +292,4 @@ interface ManifestConfig {
|
|
|
206
292
|
*/
|
|
207
293
|
declare function generateManifest(config: ManifestConfig): Manifest;
|
|
208
294
|
|
|
209
|
-
export { type ComplexityClass, type ComponentDescriptor, type ExportType, type Manifest, type ManifestConfig, type PropDescriptor, type PropKind, type ScopeFileMeta, type SideEffects, type TreeNode, generateManifest };
|
|
295
|
+
export { type CollectionConfig, type ComplexityClass, type ComponentDescriptor, type ExportType, type Manifest, type ManifestConfig, type PropDescriptor, type PropKind, type ScopeFileMeta, type SideEffects, type TreeNode, generateManifest };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync } from 'fs';
|
|
1
|
+
import { realpathSync, existsSync, readFileSync } from 'fs';
|
|
2
2
|
import { join, relative } from 'path';
|
|
3
3
|
import { Project, Node } from 'ts-morph';
|
|
4
4
|
|
|
@@ -333,7 +333,7 @@ function expandUnionValues(type) {
|
|
|
333
333
|
}
|
|
334
334
|
return values.length > 0 ? values : void 0;
|
|
335
335
|
}
|
|
336
|
-
function buildPropDescriptor(type, required, defaultValue) {
|
|
336
|
+
function buildPropDescriptor(type, required, defaultValue, source, sourceGroup) {
|
|
337
337
|
const kind = resolvePropKind(type);
|
|
338
338
|
const desc = {
|
|
339
339
|
type: kind,
|
|
@@ -348,6 +348,12 @@ function buildPropDescriptor(type, required, defaultValue) {
|
|
|
348
348
|
desc.default = defaultValue;
|
|
349
349
|
desc.required = false;
|
|
350
350
|
}
|
|
351
|
+
if (source !== void 0) {
|
|
352
|
+
desc.source = source;
|
|
353
|
+
}
|
|
354
|
+
if (sourceGroup !== void 0) {
|
|
355
|
+
desc.sourceGroup = sourceGroup;
|
|
356
|
+
}
|
|
351
357
|
return desc;
|
|
352
358
|
}
|
|
353
359
|
function extractPropsFromType(typeName, sourceFile, defaultValues = {}) {
|
|
@@ -359,25 +365,47 @@ function extractPropsFromType(typeName, sourceFile, defaultValues = {}) {
|
|
|
359
365
|
if (name.startsWith("[")) continue;
|
|
360
366
|
const type = prop.getType();
|
|
361
367
|
const required = !prop.hasQuestionToken();
|
|
362
|
-
props[name] = buildPropDescriptor(type, required, defaultValues[name]);
|
|
368
|
+
props[name] = buildPropDescriptor(type, required, defaultValues[name], "own");
|
|
363
369
|
}
|
|
364
370
|
return props;
|
|
365
371
|
}
|
|
366
372
|
const typeAlias = sourceFile.getTypeAlias(typeName);
|
|
367
373
|
if (typeAlias) {
|
|
368
374
|
const aliasType = typeAlias.getType();
|
|
369
|
-
if (aliasType.isObject()) {
|
|
375
|
+
if (aliasType.isObject() || aliasType.isIntersection()) {
|
|
370
376
|
for (const prop of aliasType.getProperties()) {
|
|
371
377
|
const name = prop.getName();
|
|
372
378
|
if (name.startsWith("[")) continue;
|
|
373
379
|
const decls = prop.getDeclarations();
|
|
374
|
-
|
|
380
|
+
let required = decls.length === 0 || !prop.getDeclarations().some((d) => Node.isPropertySignature(d) && d.hasQuestionToken());
|
|
375
381
|
const valType = prop.getTypeAtLocation(sourceFile);
|
|
376
|
-
|
|
382
|
+
if (required && typeIncludesUndefined(valType)) {
|
|
383
|
+
required = false;
|
|
384
|
+
}
|
|
385
|
+
const { source, sourceGroup } = classifyPropSource(prop);
|
|
386
|
+
props[name] = buildPropDescriptor(
|
|
387
|
+
valType,
|
|
388
|
+
required,
|
|
389
|
+
defaultValues[name],
|
|
390
|
+
source,
|
|
391
|
+
sourceGroup
|
|
392
|
+
);
|
|
377
393
|
}
|
|
378
394
|
}
|
|
379
395
|
return props;
|
|
380
396
|
}
|
|
397
|
+
for (const importDecl of sourceFile.getImportDeclarations()) {
|
|
398
|
+
const match = importDecl.getNamedImports().find((ni) => {
|
|
399
|
+
const localName = ni.getAliasNode()?.getText() ?? ni.getName();
|
|
400
|
+
return localName === typeName;
|
|
401
|
+
});
|
|
402
|
+
if (!match) continue;
|
|
403
|
+
const importedFile = importDecl.getModuleSpecifierSourceFile();
|
|
404
|
+
if (importedFile) {
|
|
405
|
+
const originalName = match.getName();
|
|
406
|
+
return extractPropsFromType(originalName, importedFile, defaultValues);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
381
409
|
return props;
|
|
382
410
|
}
|
|
383
411
|
function inferPropsTypeName(params) {
|
|
@@ -474,6 +502,78 @@ function extractNameFromWrappedCall(node) {
|
|
|
474
502
|
}
|
|
475
503
|
return void 0;
|
|
476
504
|
}
|
|
505
|
+
function extractForwardRefPropsTypeNode(node) {
|
|
506
|
+
if (!Node.isCallExpression(node)) return void 0;
|
|
507
|
+
const expr = node.getExpression();
|
|
508
|
+
const name = expr.getText();
|
|
509
|
+
if (name === "React.forwardRef" || name === "forwardRef") {
|
|
510
|
+
const typeArgs = node.getTypeArguments();
|
|
511
|
+
if (typeArgs.length >= 2 && typeArgs[1]) {
|
|
512
|
+
return typeArgs[1];
|
|
513
|
+
}
|
|
514
|
+
return void 0;
|
|
515
|
+
}
|
|
516
|
+
const args = node.getArguments();
|
|
517
|
+
if (args[0] && Node.isCallExpression(args[0])) {
|
|
518
|
+
return extractForwardRefPropsTypeNode(args[0]);
|
|
519
|
+
}
|
|
520
|
+
return void 0;
|
|
521
|
+
}
|
|
522
|
+
function typeIncludesUndefined(type) {
|
|
523
|
+
if (type.isUndefined()) return true;
|
|
524
|
+
if (type.isUnion()) {
|
|
525
|
+
return type.getUnionTypes().some((t) => t.isUndefined());
|
|
526
|
+
}
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
var REACT_INTERFACES = /* @__PURE__ */ new Set([
|
|
530
|
+
"Attributes",
|
|
531
|
+
"RefAttributes",
|
|
532
|
+
"ClassAttributes",
|
|
533
|
+
"DOMAttributes"
|
|
534
|
+
]);
|
|
535
|
+
function classifyPropSource(prop) {
|
|
536
|
+
const decls = prop.getDeclarations();
|
|
537
|
+
if (decls.length === 0) return { source: "inherited" };
|
|
538
|
+
const allInNodeModules = decls.every(
|
|
539
|
+
(d) => d.getSourceFile().getFilePath().includes("/node_modules/")
|
|
540
|
+
);
|
|
541
|
+
if (!allInNodeModules) return { source: "own" };
|
|
542
|
+
for (const d of decls) {
|
|
543
|
+
const parent = d.getParent();
|
|
544
|
+
if (!Node.isInterfaceDeclaration(parent)) continue;
|
|
545
|
+
const ifaceName = parent.getName();
|
|
546
|
+
if (ifaceName === "AriaAttributes") return { source: "inherited", sourceGroup: "aria" };
|
|
547
|
+
if (ifaceName.endsWith("HTMLAttributes")) return { source: "inherited", sourceGroup: "html" };
|
|
548
|
+
if (REACT_INTERFACES.has(ifaceName)) return { source: "inherited", sourceGroup: "react" };
|
|
549
|
+
}
|
|
550
|
+
const filePath = decls[0]?.getSourceFile().getFilePath() ?? "";
|
|
551
|
+
const pkgMatch = filePath.match(/node_modules\/((?:@[^/]+\/)?[^/]+)/);
|
|
552
|
+
if (pkgMatch?.[1]) {
|
|
553
|
+
const pkg = pkgMatch[1];
|
|
554
|
+
if (pkg === "@types/react" || pkg === "react")
|
|
555
|
+
return { source: "inherited", sourceGroup: "react" };
|
|
556
|
+
return { source: "inherited", sourceGroup: pkg };
|
|
557
|
+
}
|
|
558
|
+
return { source: "inherited" };
|
|
559
|
+
}
|
|
560
|
+
function extractPropsFromResolvedType(resolvedType, sourceFile, defaultValues = {}) {
|
|
561
|
+
const props = {};
|
|
562
|
+
if (!resolvedType.isObject() && !resolvedType.isIntersection()) return props;
|
|
563
|
+
for (const prop of resolvedType.getProperties()) {
|
|
564
|
+
const name = prop.getName();
|
|
565
|
+
if (name.startsWith("[")) continue;
|
|
566
|
+
const decls = prop.getDeclarations();
|
|
567
|
+
let required = decls.length === 0 || !decls.some((d) => Node.isPropertySignature(d) && d.hasQuestionToken());
|
|
568
|
+
const valType = prop.getTypeAtLocation(sourceFile);
|
|
569
|
+
if (required && typeIncludesUndefined(valType)) {
|
|
570
|
+
required = false;
|
|
571
|
+
}
|
|
572
|
+
const { source, sourceGroup } = classifyPropSource(prop);
|
|
573
|
+
props[name] = buildPropDescriptor(valType, required, defaultValues[name], source, sourceGroup);
|
|
574
|
+
}
|
|
575
|
+
return props;
|
|
576
|
+
}
|
|
477
577
|
function nodeReturnsJsx(node) {
|
|
478
578
|
let found = false;
|
|
479
579
|
function visit(n) {
|
|
@@ -487,6 +587,62 @@ function nodeReturnsJsx(node) {
|
|
|
487
587
|
visit(node);
|
|
488
588
|
return found;
|
|
489
589
|
}
|
|
590
|
+
function matchGlob(pattern, value) {
|
|
591
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
592
|
+
const regexStr = escaped.replace(/\*\*/g, "\xA7GLOBSTAR\xA7").replace(/\*/g, "[^/]*").replace(/§GLOBSTAR§/g, ".*");
|
|
593
|
+
const regex = new RegExp(`^${regexStr}$`, "i");
|
|
594
|
+
return regex.test(value);
|
|
595
|
+
}
|
|
596
|
+
function extractTsDocTags(declNode) {
|
|
597
|
+
let collection;
|
|
598
|
+
let internal = false;
|
|
599
|
+
const keywords = [];
|
|
600
|
+
const nodeWithDocs = declNode;
|
|
601
|
+
if (typeof nodeWithDocs.getJsDocs !== "function") {
|
|
602
|
+
return { collection, internal, keywords };
|
|
603
|
+
}
|
|
604
|
+
const jsDocs = nodeWithDocs.getJsDocs();
|
|
605
|
+
for (const jsDoc of jsDocs) {
|
|
606
|
+
for (const tag of jsDoc.getTags()) {
|
|
607
|
+
const tagName = tag.getTagName();
|
|
608
|
+
if (tagName === "collection") {
|
|
609
|
+
const comment = tag.getComment();
|
|
610
|
+
if (comment && typeof comment === "string") {
|
|
611
|
+
collection = comment.trim();
|
|
612
|
+
}
|
|
613
|
+
} else if (tagName === "internal") {
|
|
614
|
+
internal = true;
|
|
615
|
+
} else if (tagName === "keywords") {
|
|
616
|
+
const comment = tag.getComment();
|
|
617
|
+
if (comment && typeof comment === "string") {
|
|
618
|
+
for (const kw of comment.split(",")) {
|
|
619
|
+
const trimmed = kw.trim();
|
|
620
|
+
if (trimmed) keywords.push(trimmed);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
return { collection, internal, keywords };
|
|
627
|
+
}
|
|
628
|
+
function readCollectionFromScopeFile(scopeFilePath, project) {
|
|
629
|
+
let sf = project.getSourceFile(scopeFilePath);
|
|
630
|
+
if (!sf) {
|
|
631
|
+
sf = project.addSourceFileAtPath(scopeFilePath);
|
|
632
|
+
}
|
|
633
|
+
for (const varStmt of sf.getVariableStatements()) {
|
|
634
|
+
if (!varStmt.isExported()) continue;
|
|
635
|
+
for (const varDecl of varStmt.getDeclarations()) {
|
|
636
|
+
if (varDecl.getName() !== "collection") continue;
|
|
637
|
+
const initializer = varDecl.getInitializer();
|
|
638
|
+
if (!initializer) continue;
|
|
639
|
+
if (Node.isStringLiteral(initializer)) {
|
|
640
|
+
return initializer.getLiteralValue();
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
return void 0;
|
|
645
|
+
}
|
|
490
646
|
function processSourceFile(sourceFile, rootDir, project) {
|
|
491
647
|
const results = [];
|
|
492
648
|
const filePath = relative(rootDir, sourceFile.getFilePath());
|
|
@@ -547,7 +703,10 @@ function processSourceFile(sourceFile, rootDir, project) {
|
|
|
547
703
|
detectedHooks: detectHooks(fn),
|
|
548
704
|
requiredContexts: detectRequiredContexts(fn, sourceFile, project),
|
|
549
705
|
sideEffects: detectSideEffects(fn),
|
|
550
|
-
scopeFile: null
|
|
706
|
+
scopeFile: null,
|
|
707
|
+
// collection, internal, and keywords will be filled in after all components are collected
|
|
708
|
+
internal: false,
|
|
709
|
+
keywords: []
|
|
551
710
|
}
|
|
552
711
|
});
|
|
553
712
|
}
|
|
@@ -585,7 +744,16 @@ function processSourceFile(sourceFile, rootDir, project) {
|
|
|
585
744
|
}
|
|
586
745
|
const propsTypeName = inferPropsTypeName(params);
|
|
587
746
|
const defaults = extractDefaultsFromDestructuring(params);
|
|
588
|
-
|
|
747
|
+
let props = {};
|
|
748
|
+
if (propsTypeName) {
|
|
749
|
+
props = extractPropsFromType(propsTypeName, sourceFile, defaults);
|
|
750
|
+
}
|
|
751
|
+
if (Object.keys(props).length === 0 && wrappers.forwardedRef) {
|
|
752
|
+
const propsTypeNode = extractForwardRefPropsTypeNode(initializer);
|
|
753
|
+
if (propsTypeNode) {
|
|
754
|
+
props = extractPropsFromResolvedType(propsTypeNode.getType(), sourceFile, defaults);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
589
757
|
const composes = collectJsxCompositions(bodyNode);
|
|
590
758
|
const startLine = varDecl.getStartLineNumber();
|
|
591
759
|
const endLine = varDecl.getEndLineNumber();
|
|
@@ -612,7 +780,9 @@ function processSourceFile(sourceFile, rootDir, project) {
|
|
|
612
780
|
detectedHooks: detectHooks(bodyNode),
|
|
613
781
|
requiredContexts: detectRequiredContexts(bodyNode, sourceFile, project),
|
|
614
782
|
sideEffects: detectSideEffects(bodyNode),
|
|
615
|
-
scopeFile: null
|
|
783
|
+
scopeFile: null,
|
|
784
|
+
internal: false,
|
|
785
|
+
keywords: []
|
|
616
786
|
}
|
|
617
787
|
});
|
|
618
788
|
}
|
|
@@ -661,19 +831,22 @@ function processSourceFile(sourceFile, rootDir, project) {
|
|
|
661
831
|
detectedHooks: [],
|
|
662
832
|
requiredContexts: [],
|
|
663
833
|
sideEffects: detectSideEffects(cls),
|
|
664
|
-
scopeFile: null
|
|
834
|
+
scopeFile: null,
|
|
835
|
+
internal: false,
|
|
836
|
+
keywords: []
|
|
665
837
|
}
|
|
666
838
|
});
|
|
667
839
|
}
|
|
668
840
|
return results;
|
|
669
841
|
}
|
|
670
842
|
function generateManifest(config) {
|
|
843
|
+
const normalizedRootDir = realpathSync(config.rootDir);
|
|
671
844
|
const {
|
|
672
|
-
rootDir,
|
|
673
845
|
include = ["src/**/*.tsx", "src/**/*.ts"],
|
|
674
846
|
exclude = ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/dist/**", "**/*.d.ts"],
|
|
675
|
-
tsConfigFilePath = join(
|
|
847
|
+
tsConfigFilePath = join(normalizedRootDir, "tsconfig.json")
|
|
676
848
|
} = config;
|
|
849
|
+
const rootDir = normalizedRootDir;
|
|
677
850
|
const project = new Project({
|
|
678
851
|
tsConfigFilePath,
|
|
679
852
|
skipAddingFilesFromTsConfig: true
|
|
@@ -729,11 +902,78 @@ function generateManifest(config) {
|
|
|
729
902
|
desc.scopeFile = scopeMeta;
|
|
730
903
|
}
|
|
731
904
|
}
|
|
905
|
+
const configCollections = config.collections ?? [];
|
|
906
|
+
const internalPatterns = config.internalPatterns ?? [];
|
|
907
|
+
for (const [compName, desc] of Object.entries(allComponents)) {
|
|
908
|
+
const absFilePath = desc.filePath.startsWith("/") ? desc.filePath : join(rootDir, desc.filePath);
|
|
909
|
+
const sf = project.getSourceFile(absFilePath);
|
|
910
|
+
let tsdocCollection;
|
|
911
|
+
let tsdocInternal = false;
|
|
912
|
+
let tsdocKeywords = [];
|
|
913
|
+
if (sf) {
|
|
914
|
+
const fn = sf.getFunction(compName);
|
|
915
|
+
if (fn) {
|
|
916
|
+
const tags = extractTsDocTags(fn);
|
|
917
|
+
tsdocCollection = tags.collection;
|
|
918
|
+
tsdocInternal = tags.internal;
|
|
919
|
+
tsdocKeywords = tags.keywords;
|
|
920
|
+
}
|
|
921
|
+
if (tsdocCollection === void 0 && !tsdocInternal && tsdocKeywords.length === 0) {
|
|
922
|
+
const varDecl = sf.getVariableDeclaration(compName);
|
|
923
|
+
if (varDecl) {
|
|
924
|
+
const varStmt = varDecl.getVariableStatement();
|
|
925
|
+
if (varStmt) {
|
|
926
|
+
const tags = extractTsDocTags(varStmt);
|
|
927
|
+
tsdocCollection = tags.collection;
|
|
928
|
+
tsdocInternal = tags.internal;
|
|
929
|
+
tsdocKeywords = tags.keywords;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
if (tsdocCollection === void 0 && !tsdocInternal && tsdocKeywords.length === 0) {
|
|
934
|
+
const cls = sf.getClass(compName);
|
|
935
|
+
if (cls) {
|
|
936
|
+
const tags = extractTsDocTags(cls);
|
|
937
|
+
tsdocCollection = tags.collection;
|
|
938
|
+
tsdocInternal = tags.internal;
|
|
939
|
+
tsdocKeywords = tags.keywords;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
let scopeFileCollection;
|
|
944
|
+
if (desc.scopeFile) {
|
|
945
|
+
scopeFileCollection = readCollectionFromScopeFile(desc.scopeFile.filePath, project);
|
|
946
|
+
}
|
|
947
|
+
let configCollection;
|
|
948
|
+
for (const colConfig of configCollections) {
|
|
949
|
+
if (colConfig.patterns.some((p) => matchGlob(p, desc.filePath))) {
|
|
950
|
+
configCollection = colConfig.name;
|
|
951
|
+
break;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
let configInternal = false;
|
|
955
|
+
if (internalPatterns.length > 0) {
|
|
956
|
+
configInternal = internalPatterns.some(
|
|
957
|
+
(p) => matchGlob(p, desc.filePath) || matchGlob(p, desc.displayName)
|
|
958
|
+
);
|
|
959
|
+
}
|
|
960
|
+
const iconPats = config.iconPatterns ?? [];
|
|
961
|
+
const isIcon = iconPats.length > 0 && iconPats.some((p) => matchGlob(p, desc.filePath) || matchGlob(p, desc.displayName));
|
|
962
|
+
const resolvedCollection = tsdocCollection ?? scopeFileCollection ?? configCollection;
|
|
963
|
+
if (resolvedCollection !== void 0) {
|
|
964
|
+
desc.collection = resolvedCollection;
|
|
965
|
+
}
|
|
966
|
+
desc.internal = tsdocInternal || configInternal || isIcon;
|
|
967
|
+
if (tsdocKeywords.length > 0) {
|
|
968
|
+
desc.keywords = tsdocKeywords;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
732
971
|
return {
|
|
733
972
|
version: "0.1",
|
|
734
973
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
735
974
|
components: allComponents,
|
|
736
|
-
tree
|
|
975
|
+
tree,
|
|
976
|
+
collections: configCollections
|
|
737
977
|
};
|
|
738
978
|
}
|
|
739
979
|
var SCOPE_EXTENSIONS = [".scope.tsx", ".scope.ts", ".scope.jsx", ".scope.js"];
|
|
@@ -743,7 +983,103 @@ function detectScopeFile(componentFilePath, rootDir) {
|
|
|
743
983
|
for (const ext of SCOPE_EXTENSIONS) {
|
|
744
984
|
const candidate = `${stem}${ext}`;
|
|
745
985
|
if (existsSync(candidate)) {
|
|
746
|
-
return
|
|
986
|
+
return readScopeFileMeta(candidate);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
return null;
|
|
990
|
+
}
|
|
991
|
+
function readScopeFileMeta(filePath) {
|
|
992
|
+
const source = readFileSync(filePath, "utf-8");
|
|
993
|
+
const scenarioNames = extractScenarioNames(source);
|
|
994
|
+
const hasWrapper = /\bexport\s+(?:const|function)\s+wrapper\b/.test(source) || /\bwrapper\s*:/.test(source);
|
|
995
|
+
return { filePath, scenarioNames, hasWrapper };
|
|
996
|
+
}
|
|
997
|
+
function extractScenarioNames(source) {
|
|
998
|
+
const objectSource = extractScenarioObjectLiteral(source, /\bexport\s+const\s+scenarios\s*=\s*\{/) ?? extractScenarioObjectLiteral(source, /\bscenarios\s*:\s*\{/);
|
|
999
|
+
if (objectSource === null) return [];
|
|
1000
|
+
const names = /* @__PURE__ */ new Set();
|
|
1001
|
+
let depth = 0;
|
|
1002
|
+
let quote = null;
|
|
1003
|
+
let escaped = false;
|
|
1004
|
+
let token = "";
|
|
1005
|
+
const flushTokenAsKey = () => {
|
|
1006
|
+
const name = token.trim().replace(/^['"]|['"]$/g, "");
|
|
1007
|
+
if (depth === 0 && name && name !== "scenarios" && name !== "wrapper") {
|
|
1008
|
+
names.add(name);
|
|
1009
|
+
}
|
|
1010
|
+
token = "";
|
|
1011
|
+
};
|
|
1012
|
+
for (let i = 0; i < objectSource.length; i += 1) {
|
|
1013
|
+
const char = objectSource[i];
|
|
1014
|
+
if (quote !== null) {
|
|
1015
|
+
token += char;
|
|
1016
|
+
if (escaped) {
|
|
1017
|
+
escaped = false;
|
|
1018
|
+
} else if (char === "\\") {
|
|
1019
|
+
escaped = true;
|
|
1020
|
+
} else if (char === quote) {
|
|
1021
|
+
quote = null;
|
|
1022
|
+
}
|
|
1023
|
+
continue;
|
|
1024
|
+
}
|
|
1025
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
1026
|
+
quote = char;
|
|
1027
|
+
token += char;
|
|
1028
|
+
continue;
|
|
1029
|
+
}
|
|
1030
|
+
if (char === "{") {
|
|
1031
|
+
depth += 1;
|
|
1032
|
+
token = "";
|
|
1033
|
+
continue;
|
|
1034
|
+
}
|
|
1035
|
+
if (char === "}") {
|
|
1036
|
+
depth = Math.max(0, depth - 1);
|
|
1037
|
+
token = "";
|
|
1038
|
+
continue;
|
|
1039
|
+
}
|
|
1040
|
+
if (depth === 0 && char === ":") {
|
|
1041
|
+
flushTokenAsKey();
|
|
1042
|
+
continue;
|
|
1043
|
+
}
|
|
1044
|
+
if (depth === 0 && char === ",") {
|
|
1045
|
+
token = "";
|
|
1046
|
+
continue;
|
|
1047
|
+
}
|
|
1048
|
+
token += char;
|
|
1049
|
+
}
|
|
1050
|
+
return [...names];
|
|
1051
|
+
}
|
|
1052
|
+
function extractScenarioObjectLiteral(source, pattern) {
|
|
1053
|
+
const match = pattern.exec(source);
|
|
1054
|
+
if (!match) return null;
|
|
1055
|
+
const openBraceIndex = source.indexOf("{", match.index);
|
|
1056
|
+
if (openBraceIndex < 0) return null;
|
|
1057
|
+
let depth = 0;
|
|
1058
|
+
let quote = null;
|
|
1059
|
+
let escaped = false;
|
|
1060
|
+
for (let i = openBraceIndex; i < source.length; i += 1) {
|
|
1061
|
+
const char = source[i];
|
|
1062
|
+
if (quote !== null) {
|
|
1063
|
+
if (escaped) {
|
|
1064
|
+
escaped = false;
|
|
1065
|
+
} else if (char === "\\") {
|
|
1066
|
+
escaped = true;
|
|
1067
|
+
} else if (char === quote) {
|
|
1068
|
+
quote = null;
|
|
1069
|
+
}
|
|
1070
|
+
continue;
|
|
1071
|
+
}
|
|
1072
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
1073
|
+
quote = char;
|
|
1074
|
+
continue;
|
|
1075
|
+
}
|
|
1076
|
+
if (char === "{") {
|
|
1077
|
+
depth += 1;
|
|
1078
|
+
continue;
|
|
1079
|
+
}
|
|
1080
|
+
if (char === "}") {
|
|
1081
|
+
depth -= 1;
|
|
1082
|
+
if (depth === 0) return source.slice(openBraceIndex + 1, i);
|
|
747
1083
|
}
|
|
748
1084
|
}
|
|
749
1085
|
return null;
|