@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 CHANGED
@@ -335,7 +335,7 @@ function expandUnionValues(type) {
335
335
  }
336
336
  return values.length > 0 ? values : void 0;
337
337
  }
338
- function buildPropDescriptor(type, required, defaultValue) {
338
+ function buildPropDescriptor(type, required, defaultValue, source, sourceGroup) {
339
339
  const kind = resolvePropKind(type);
340
340
  const desc = {
341
341
  type: kind,
@@ -350,6 +350,12 @@ function buildPropDescriptor(type, required, defaultValue) {
350
350
  desc.default = defaultValue;
351
351
  desc.required = false;
352
352
  }
353
+ if (source !== void 0) {
354
+ desc.source = source;
355
+ }
356
+ if (sourceGroup !== void 0) {
357
+ desc.sourceGroup = sourceGroup;
358
+ }
353
359
  return desc;
354
360
  }
355
361
  function extractPropsFromType(typeName, sourceFile, defaultValues = {}) {
@@ -361,25 +367,47 @@ function extractPropsFromType(typeName, sourceFile, defaultValues = {}) {
361
367
  if (name.startsWith("[")) continue;
362
368
  const type = prop.getType();
363
369
  const required = !prop.hasQuestionToken();
364
- props[name] = buildPropDescriptor(type, required, defaultValues[name]);
370
+ props[name] = buildPropDescriptor(type, required, defaultValues[name], "own");
365
371
  }
366
372
  return props;
367
373
  }
368
374
  const typeAlias = sourceFile.getTypeAlias(typeName);
369
375
  if (typeAlias) {
370
376
  const aliasType = typeAlias.getType();
371
- if (aliasType.isObject()) {
377
+ if (aliasType.isObject() || aliasType.isIntersection()) {
372
378
  for (const prop of aliasType.getProperties()) {
373
379
  const name = prop.getName();
374
380
  if (name.startsWith("[")) continue;
375
381
  const decls = prop.getDeclarations();
376
- const required = decls.length === 0 || !prop.getDeclarations().some((d) => tsMorph.Node.isPropertySignature(d) && d.hasQuestionToken());
382
+ let required = decls.length === 0 || !prop.getDeclarations().some((d) => tsMorph.Node.isPropertySignature(d) && d.hasQuestionToken());
377
383
  const valType = prop.getTypeAtLocation(sourceFile);
378
- props[name] = buildPropDescriptor(valType, required, defaultValues[name]);
384
+ if (required && typeIncludesUndefined(valType)) {
385
+ required = false;
386
+ }
387
+ const { source, sourceGroup } = classifyPropSource(prop);
388
+ props[name] = buildPropDescriptor(
389
+ valType,
390
+ required,
391
+ defaultValues[name],
392
+ source,
393
+ sourceGroup
394
+ );
379
395
  }
380
396
  }
381
397
  return props;
382
398
  }
399
+ for (const importDecl of sourceFile.getImportDeclarations()) {
400
+ const match = importDecl.getNamedImports().find((ni) => {
401
+ const localName = ni.getAliasNode()?.getText() ?? ni.getName();
402
+ return localName === typeName;
403
+ });
404
+ if (!match) continue;
405
+ const importedFile = importDecl.getModuleSpecifierSourceFile();
406
+ if (importedFile) {
407
+ const originalName = match.getName();
408
+ return extractPropsFromType(originalName, importedFile, defaultValues);
409
+ }
410
+ }
383
411
  return props;
384
412
  }
385
413
  function inferPropsTypeName(params) {
@@ -476,6 +504,78 @@ function extractNameFromWrappedCall(node) {
476
504
  }
477
505
  return void 0;
478
506
  }
507
+ function extractForwardRefPropsTypeNode(node) {
508
+ if (!tsMorph.Node.isCallExpression(node)) return void 0;
509
+ const expr = node.getExpression();
510
+ const name = expr.getText();
511
+ if (name === "React.forwardRef" || name === "forwardRef") {
512
+ const typeArgs = node.getTypeArguments();
513
+ if (typeArgs.length >= 2 && typeArgs[1]) {
514
+ return typeArgs[1];
515
+ }
516
+ return void 0;
517
+ }
518
+ const args = node.getArguments();
519
+ if (args[0] && tsMorph.Node.isCallExpression(args[0])) {
520
+ return extractForwardRefPropsTypeNode(args[0]);
521
+ }
522
+ return void 0;
523
+ }
524
+ function typeIncludesUndefined(type) {
525
+ if (type.isUndefined()) return true;
526
+ if (type.isUnion()) {
527
+ return type.getUnionTypes().some((t) => t.isUndefined());
528
+ }
529
+ return false;
530
+ }
531
+ var REACT_INTERFACES = /* @__PURE__ */ new Set([
532
+ "Attributes",
533
+ "RefAttributes",
534
+ "ClassAttributes",
535
+ "DOMAttributes"
536
+ ]);
537
+ function classifyPropSource(prop) {
538
+ const decls = prop.getDeclarations();
539
+ if (decls.length === 0) return { source: "inherited" };
540
+ const allInNodeModules = decls.every(
541
+ (d) => d.getSourceFile().getFilePath().includes("/node_modules/")
542
+ );
543
+ if (!allInNodeModules) return { source: "own" };
544
+ for (const d of decls) {
545
+ const parent = d.getParent();
546
+ if (!tsMorph.Node.isInterfaceDeclaration(parent)) continue;
547
+ const ifaceName = parent.getName();
548
+ if (ifaceName === "AriaAttributes") return { source: "inherited", sourceGroup: "aria" };
549
+ if (ifaceName.endsWith("HTMLAttributes")) return { source: "inherited", sourceGroup: "html" };
550
+ if (REACT_INTERFACES.has(ifaceName)) return { source: "inherited", sourceGroup: "react" };
551
+ }
552
+ const filePath = decls[0]?.getSourceFile().getFilePath() ?? "";
553
+ const pkgMatch = filePath.match(/node_modules\/((?:@[^/]+\/)?[^/]+)/);
554
+ if (pkgMatch?.[1]) {
555
+ const pkg = pkgMatch[1];
556
+ if (pkg === "@types/react" || pkg === "react")
557
+ return { source: "inherited", sourceGroup: "react" };
558
+ return { source: "inherited", sourceGroup: pkg };
559
+ }
560
+ return { source: "inherited" };
561
+ }
562
+ function extractPropsFromResolvedType(resolvedType, sourceFile, defaultValues = {}) {
563
+ const props = {};
564
+ if (!resolvedType.isObject() && !resolvedType.isIntersection()) return props;
565
+ for (const prop of resolvedType.getProperties()) {
566
+ const name = prop.getName();
567
+ if (name.startsWith("[")) continue;
568
+ const decls = prop.getDeclarations();
569
+ let required = decls.length === 0 || !decls.some((d) => tsMorph.Node.isPropertySignature(d) && d.hasQuestionToken());
570
+ const valType = prop.getTypeAtLocation(sourceFile);
571
+ if (required && typeIncludesUndefined(valType)) {
572
+ required = false;
573
+ }
574
+ const { source, sourceGroup } = classifyPropSource(prop);
575
+ props[name] = buildPropDescriptor(valType, required, defaultValues[name], source, sourceGroup);
576
+ }
577
+ return props;
578
+ }
479
579
  function nodeReturnsJsx(node) {
480
580
  let found = false;
481
581
  function visit(n) {
@@ -489,6 +589,62 @@ function nodeReturnsJsx(node) {
489
589
  visit(node);
490
590
  return found;
491
591
  }
592
+ function matchGlob(pattern, value) {
593
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
594
+ const regexStr = escaped.replace(/\*\*/g, "\xA7GLOBSTAR\xA7").replace(/\*/g, "[^/]*").replace(/§GLOBSTAR§/g, ".*");
595
+ const regex = new RegExp(`^${regexStr}$`, "i");
596
+ return regex.test(value);
597
+ }
598
+ function extractTsDocTags(declNode) {
599
+ let collection;
600
+ let internal = false;
601
+ const keywords = [];
602
+ const nodeWithDocs = declNode;
603
+ if (typeof nodeWithDocs.getJsDocs !== "function") {
604
+ return { collection, internal, keywords };
605
+ }
606
+ const jsDocs = nodeWithDocs.getJsDocs();
607
+ for (const jsDoc of jsDocs) {
608
+ for (const tag of jsDoc.getTags()) {
609
+ const tagName = tag.getTagName();
610
+ if (tagName === "collection") {
611
+ const comment = tag.getComment();
612
+ if (comment && typeof comment === "string") {
613
+ collection = comment.trim();
614
+ }
615
+ } else if (tagName === "internal") {
616
+ internal = true;
617
+ } else if (tagName === "keywords") {
618
+ const comment = tag.getComment();
619
+ if (comment && typeof comment === "string") {
620
+ for (const kw of comment.split(",")) {
621
+ const trimmed = kw.trim();
622
+ if (trimmed) keywords.push(trimmed);
623
+ }
624
+ }
625
+ }
626
+ }
627
+ }
628
+ return { collection, internal, keywords };
629
+ }
630
+ function readCollectionFromScopeFile(scopeFilePath, project) {
631
+ let sf = project.getSourceFile(scopeFilePath);
632
+ if (!sf) {
633
+ sf = project.addSourceFileAtPath(scopeFilePath);
634
+ }
635
+ for (const varStmt of sf.getVariableStatements()) {
636
+ if (!varStmt.isExported()) continue;
637
+ for (const varDecl of varStmt.getDeclarations()) {
638
+ if (varDecl.getName() !== "collection") continue;
639
+ const initializer = varDecl.getInitializer();
640
+ if (!initializer) continue;
641
+ if (tsMorph.Node.isStringLiteral(initializer)) {
642
+ return initializer.getLiteralValue();
643
+ }
644
+ }
645
+ }
646
+ return void 0;
647
+ }
492
648
  function processSourceFile(sourceFile, rootDir, project) {
493
649
  const results = [];
494
650
  const filePath = path.relative(rootDir, sourceFile.getFilePath());
@@ -549,7 +705,10 @@ function processSourceFile(sourceFile, rootDir, project) {
549
705
  detectedHooks: detectHooks(fn),
550
706
  requiredContexts: detectRequiredContexts(fn, sourceFile, project),
551
707
  sideEffects: detectSideEffects(fn),
552
- scopeFile: null
708
+ scopeFile: null,
709
+ // collection, internal, and keywords will be filled in after all components are collected
710
+ internal: false,
711
+ keywords: []
553
712
  }
554
713
  });
555
714
  }
@@ -587,7 +746,16 @@ function processSourceFile(sourceFile, rootDir, project) {
587
746
  }
588
747
  const propsTypeName = inferPropsTypeName(params);
589
748
  const defaults = extractDefaultsFromDestructuring(params);
590
- const props = propsTypeName ? extractPropsFromType(propsTypeName, sourceFile, defaults) : {};
749
+ let props = {};
750
+ if (propsTypeName) {
751
+ props = extractPropsFromType(propsTypeName, sourceFile, defaults);
752
+ }
753
+ if (Object.keys(props).length === 0 && wrappers.forwardedRef) {
754
+ const propsTypeNode = extractForwardRefPropsTypeNode(initializer);
755
+ if (propsTypeNode) {
756
+ props = extractPropsFromResolvedType(propsTypeNode.getType(), sourceFile, defaults);
757
+ }
758
+ }
591
759
  const composes = collectJsxCompositions(bodyNode);
592
760
  const startLine = varDecl.getStartLineNumber();
593
761
  const endLine = varDecl.getEndLineNumber();
@@ -614,7 +782,9 @@ function processSourceFile(sourceFile, rootDir, project) {
614
782
  detectedHooks: detectHooks(bodyNode),
615
783
  requiredContexts: detectRequiredContexts(bodyNode, sourceFile, project),
616
784
  sideEffects: detectSideEffects(bodyNode),
617
- scopeFile: null
785
+ scopeFile: null,
786
+ internal: false,
787
+ keywords: []
618
788
  }
619
789
  });
620
790
  }
@@ -663,19 +833,22 @@ function processSourceFile(sourceFile, rootDir, project) {
663
833
  detectedHooks: [],
664
834
  requiredContexts: [],
665
835
  sideEffects: detectSideEffects(cls),
666
- scopeFile: null
836
+ scopeFile: null,
837
+ internal: false,
838
+ keywords: []
667
839
  }
668
840
  });
669
841
  }
670
842
  return results;
671
843
  }
672
844
  function generateManifest(config) {
845
+ const normalizedRootDir = fs.realpathSync(config.rootDir);
673
846
  const {
674
- rootDir,
675
847
  include = ["src/**/*.tsx", "src/**/*.ts"],
676
848
  exclude = ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/dist/**", "**/*.d.ts"],
677
- tsConfigFilePath = path.join(rootDir, "tsconfig.json")
849
+ tsConfigFilePath = path.join(normalizedRootDir, "tsconfig.json")
678
850
  } = config;
851
+ const rootDir = normalizedRootDir;
679
852
  const project = new tsMorph.Project({
680
853
  tsConfigFilePath,
681
854
  skipAddingFilesFromTsConfig: true
@@ -731,11 +904,78 @@ function generateManifest(config) {
731
904
  desc.scopeFile = scopeMeta;
732
905
  }
733
906
  }
907
+ const configCollections = config.collections ?? [];
908
+ const internalPatterns = config.internalPatterns ?? [];
909
+ for (const [compName, desc] of Object.entries(allComponents)) {
910
+ const absFilePath = desc.filePath.startsWith("/") ? desc.filePath : path.join(rootDir, desc.filePath);
911
+ const sf = project.getSourceFile(absFilePath);
912
+ let tsdocCollection;
913
+ let tsdocInternal = false;
914
+ let tsdocKeywords = [];
915
+ if (sf) {
916
+ const fn = sf.getFunction(compName);
917
+ if (fn) {
918
+ const tags = extractTsDocTags(fn);
919
+ tsdocCollection = tags.collection;
920
+ tsdocInternal = tags.internal;
921
+ tsdocKeywords = tags.keywords;
922
+ }
923
+ if (tsdocCollection === void 0 && !tsdocInternal && tsdocKeywords.length === 0) {
924
+ const varDecl = sf.getVariableDeclaration(compName);
925
+ if (varDecl) {
926
+ const varStmt = varDecl.getVariableStatement();
927
+ if (varStmt) {
928
+ const tags = extractTsDocTags(varStmt);
929
+ tsdocCollection = tags.collection;
930
+ tsdocInternal = tags.internal;
931
+ tsdocKeywords = tags.keywords;
932
+ }
933
+ }
934
+ }
935
+ if (tsdocCollection === void 0 && !tsdocInternal && tsdocKeywords.length === 0) {
936
+ const cls = sf.getClass(compName);
937
+ if (cls) {
938
+ const tags = extractTsDocTags(cls);
939
+ tsdocCollection = tags.collection;
940
+ tsdocInternal = tags.internal;
941
+ tsdocKeywords = tags.keywords;
942
+ }
943
+ }
944
+ }
945
+ let scopeFileCollection;
946
+ if (desc.scopeFile) {
947
+ scopeFileCollection = readCollectionFromScopeFile(desc.scopeFile.filePath, project);
948
+ }
949
+ let configCollection;
950
+ for (const colConfig of configCollections) {
951
+ if (colConfig.patterns.some((p) => matchGlob(p, desc.filePath))) {
952
+ configCollection = colConfig.name;
953
+ break;
954
+ }
955
+ }
956
+ let configInternal = false;
957
+ if (internalPatterns.length > 0) {
958
+ configInternal = internalPatterns.some(
959
+ (p) => matchGlob(p, desc.filePath) || matchGlob(p, desc.displayName)
960
+ );
961
+ }
962
+ const iconPats = config.iconPatterns ?? [];
963
+ const isIcon = iconPats.length > 0 && iconPats.some((p) => matchGlob(p, desc.filePath) || matchGlob(p, desc.displayName));
964
+ const resolvedCollection = tsdocCollection ?? scopeFileCollection ?? configCollection;
965
+ if (resolvedCollection !== void 0) {
966
+ desc.collection = resolvedCollection;
967
+ }
968
+ desc.internal = tsdocInternal || configInternal || isIcon;
969
+ if (tsdocKeywords.length > 0) {
970
+ desc.keywords = tsdocKeywords;
971
+ }
972
+ }
734
973
  return {
735
974
  version: "0.1",
736
975
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
737
976
  components: allComponents,
738
- tree
977
+ tree,
978
+ collections: configCollections
739
979
  };
740
980
  }
741
981
  var SCOPE_EXTENSIONS = [".scope.tsx", ".scope.ts", ".scope.jsx", ".scope.js"];
@@ -745,7 +985,103 @@ function detectScopeFile(componentFilePath, rootDir) {
745
985
  for (const ext of SCOPE_EXTENSIONS) {
746
986
  const candidate = `${stem}${ext}`;
747
987
  if (fs.existsSync(candidate)) {
748
- return { filePath: candidate, scenarioNames: [], hasWrapper: false };
988
+ return readScopeFileMeta(candidate);
989
+ }
990
+ }
991
+ return null;
992
+ }
993
+ function readScopeFileMeta(filePath) {
994
+ const source = fs.readFileSync(filePath, "utf-8");
995
+ const scenarioNames = extractScenarioNames(source);
996
+ const hasWrapper = /\bexport\s+(?:const|function)\s+wrapper\b/.test(source) || /\bwrapper\s*:/.test(source);
997
+ return { filePath, scenarioNames, hasWrapper };
998
+ }
999
+ function extractScenarioNames(source) {
1000
+ const objectSource = extractScenarioObjectLiteral(source, /\bexport\s+const\s+scenarios\s*=\s*\{/) ?? extractScenarioObjectLiteral(source, /\bscenarios\s*:\s*\{/);
1001
+ if (objectSource === null) return [];
1002
+ const names = /* @__PURE__ */ new Set();
1003
+ let depth = 0;
1004
+ let quote = null;
1005
+ let escaped = false;
1006
+ let token = "";
1007
+ const flushTokenAsKey = () => {
1008
+ const name = token.trim().replace(/^['"]|['"]$/g, "");
1009
+ if (depth === 0 && name && name !== "scenarios" && name !== "wrapper") {
1010
+ names.add(name);
1011
+ }
1012
+ token = "";
1013
+ };
1014
+ for (let i = 0; i < objectSource.length; i += 1) {
1015
+ const char = objectSource[i];
1016
+ if (quote !== null) {
1017
+ token += char;
1018
+ if (escaped) {
1019
+ escaped = false;
1020
+ } else if (char === "\\") {
1021
+ escaped = true;
1022
+ } else if (char === quote) {
1023
+ quote = null;
1024
+ }
1025
+ continue;
1026
+ }
1027
+ if (char === '"' || char === "'" || char === "`") {
1028
+ quote = char;
1029
+ token += char;
1030
+ continue;
1031
+ }
1032
+ if (char === "{") {
1033
+ depth += 1;
1034
+ token = "";
1035
+ continue;
1036
+ }
1037
+ if (char === "}") {
1038
+ depth = Math.max(0, depth - 1);
1039
+ token = "";
1040
+ continue;
1041
+ }
1042
+ if (depth === 0 && char === ":") {
1043
+ flushTokenAsKey();
1044
+ continue;
1045
+ }
1046
+ if (depth === 0 && char === ",") {
1047
+ token = "";
1048
+ continue;
1049
+ }
1050
+ token += char;
1051
+ }
1052
+ return [...names];
1053
+ }
1054
+ function extractScenarioObjectLiteral(source, pattern) {
1055
+ const match = pattern.exec(source);
1056
+ if (!match) return null;
1057
+ const openBraceIndex = source.indexOf("{", match.index);
1058
+ if (openBraceIndex < 0) return null;
1059
+ let depth = 0;
1060
+ let quote = null;
1061
+ let escaped = false;
1062
+ for (let i = openBraceIndex; i < source.length; i += 1) {
1063
+ const char = source[i];
1064
+ if (quote !== null) {
1065
+ if (escaped) {
1066
+ escaped = false;
1067
+ } else if (char === "\\") {
1068
+ escaped = true;
1069
+ } else if (char === quote) {
1070
+ quote = null;
1071
+ }
1072
+ continue;
1073
+ }
1074
+ if (char === '"' || char === "'" || char === "`") {
1075
+ quote = char;
1076
+ continue;
1077
+ }
1078
+ if (char === "{") {
1079
+ depth += 1;
1080
+ continue;
1081
+ }
1082
+ if (char === "}") {
1083
+ depth -= 1;
1084
+ if (depth === 0) return source.slice(openBraceIndex + 1, i);
749
1085
  }
750
1086
  }
751
1087
  return null;