@agent-scope/manifest 1.20.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) {
@@ -498,9 +598,10 @@ function matchGlob(pattern, value) {
498
598
  function extractTsDocTags(declNode) {
499
599
  let collection;
500
600
  let internal = false;
601
+ const keywords = [];
501
602
  const nodeWithDocs = declNode;
502
603
  if (typeof nodeWithDocs.getJsDocs !== "function") {
503
- return { collection, internal };
604
+ return { collection, internal, keywords };
504
605
  }
505
606
  const jsDocs = nodeWithDocs.getJsDocs();
506
607
  for (const jsDoc of jsDocs) {
@@ -513,10 +614,18 @@ function extractTsDocTags(declNode) {
513
614
  }
514
615
  } else if (tagName === "internal") {
515
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
+ }
516
625
  }
517
626
  }
518
627
  }
519
- return { collection, internal };
628
+ return { collection, internal, keywords };
520
629
  }
521
630
  function readCollectionFromScopeFile(scopeFilePath, project) {
522
631
  let sf = project.getSourceFile(scopeFilePath);
@@ -597,8 +706,9 @@ function processSourceFile(sourceFile, rootDir, project) {
597
706
  requiredContexts: detectRequiredContexts(fn, sourceFile, project),
598
707
  sideEffects: detectSideEffects(fn),
599
708
  scopeFile: null,
600
- // collection and internal will be filled in after all components are collected
601
- internal: false
709
+ // collection, internal, and keywords will be filled in after all components are collected
710
+ internal: false,
711
+ keywords: []
602
712
  }
603
713
  });
604
714
  }
@@ -636,7 +746,16 @@ function processSourceFile(sourceFile, rootDir, project) {
636
746
  }
637
747
  const propsTypeName = inferPropsTypeName(params);
638
748
  const defaults = extractDefaultsFromDestructuring(params);
639
- 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
+ }
640
759
  const composes = collectJsxCompositions(bodyNode);
641
760
  const startLine = varDecl.getStartLineNumber();
642
761
  const endLine = varDecl.getEndLineNumber();
@@ -664,7 +783,8 @@ function processSourceFile(sourceFile, rootDir, project) {
664
783
  requiredContexts: detectRequiredContexts(bodyNode, sourceFile, project),
665
784
  sideEffects: detectSideEffects(bodyNode),
666
785
  scopeFile: null,
667
- internal: false
786
+ internal: false,
787
+ keywords: []
668
788
  }
669
789
  });
670
790
  }
@@ -714,19 +834,21 @@ function processSourceFile(sourceFile, rootDir, project) {
714
834
  requiredContexts: [],
715
835
  sideEffects: detectSideEffects(cls),
716
836
  scopeFile: null,
717
- internal: false
837
+ internal: false,
838
+ keywords: []
718
839
  }
719
840
  });
720
841
  }
721
842
  return results;
722
843
  }
723
844
  function generateManifest(config) {
845
+ const normalizedRootDir = fs.realpathSync(config.rootDir);
724
846
  const {
725
- rootDir,
726
847
  include = ["src/**/*.tsx", "src/**/*.ts"],
727
848
  exclude = ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/dist/**", "**/*.d.ts"],
728
- tsConfigFilePath = path.join(rootDir, "tsconfig.json")
849
+ tsConfigFilePath = path.join(normalizedRootDir, "tsconfig.json")
729
850
  } = config;
851
+ const rootDir = normalizedRootDir;
730
852
  const project = new tsMorph.Project({
731
853
  tsConfigFilePath,
732
854
  skipAddingFilesFromTsConfig: true
@@ -789,14 +911,16 @@ function generateManifest(config) {
789
911
  const sf = project.getSourceFile(absFilePath);
790
912
  let tsdocCollection;
791
913
  let tsdocInternal = false;
914
+ let tsdocKeywords = [];
792
915
  if (sf) {
793
916
  const fn = sf.getFunction(compName);
794
917
  if (fn) {
795
918
  const tags = extractTsDocTags(fn);
796
919
  tsdocCollection = tags.collection;
797
920
  tsdocInternal = tags.internal;
921
+ tsdocKeywords = tags.keywords;
798
922
  }
799
- if (tsdocCollection === void 0 && !tsdocInternal) {
923
+ if (tsdocCollection === void 0 && !tsdocInternal && tsdocKeywords.length === 0) {
800
924
  const varDecl = sf.getVariableDeclaration(compName);
801
925
  if (varDecl) {
802
926
  const varStmt = varDecl.getVariableStatement();
@@ -804,15 +928,17 @@ function generateManifest(config) {
804
928
  const tags = extractTsDocTags(varStmt);
805
929
  tsdocCollection = tags.collection;
806
930
  tsdocInternal = tags.internal;
931
+ tsdocKeywords = tags.keywords;
807
932
  }
808
933
  }
809
934
  }
810
- if (tsdocCollection === void 0 && !tsdocInternal) {
935
+ if (tsdocCollection === void 0 && !tsdocInternal && tsdocKeywords.length === 0) {
811
936
  const cls = sf.getClass(compName);
812
937
  if (cls) {
813
938
  const tags = extractTsDocTags(cls);
814
939
  tsdocCollection = tags.collection;
815
940
  tsdocInternal = tags.internal;
941
+ tsdocKeywords = tags.keywords;
816
942
  }
817
943
  }
818
944
  }
@@ -833,11 +959,16 @@ function generateManifest(config) {
833
959
  (p) => matchGlob(p, desc.filePath) || matchGlob(p, desc.displayName)
834
960
  );
835
961
  }
962
+ const iconPats = config.iconPatterns ?? [];
963
+ const isIcon = iconPats.length > 0 && iconPats.some((p) => matchGlob(p, desc.filePath) || matchGlob(p, desc.displayName));
836
964
  const resolvedCollection = tsdocCollection ?? scopeFileCollection ?? configCollection;
837
965
  if (resolvedCollection !== void 0) {
838
966
  desc.collection = resolvedCollection;
839
967
  }
840
- desc.internal = tsdocInternal || configInternal;
968
+ desc.internal = tsdocInternal || configInternal || isIcon;
969
+ if (tsdocKeywords.length > 0) {
970
+ desc.keywords = tsdocKeywords;
971
+ }
841
972
  }
842
973
  return {
843
974
  version: "0.1",
@@ -854,7 +985,103 @@ function detectScopeFile(componentFilePath, rootDir) {
854
985
  for (const ext of SCOPE_EXTENSIONS) {
855
986
  const candidate = `${stem}${ext}`;
856
987
  if (fs.existsSync(candidate)) {
857
- 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);
858
1085
  }
859
1086
  }
860
1087
  return null;