@agent-scope/manifest 1.20.0 → 1.20.2

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.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
- const required = decls.length === 0 || !prop.getDeclarations().some((d) => Node.isPropertySignature(d) && d.hasQuestionToken());
380
+ let required = decls.length === 0 || !prop.getDeclarations().some((d) => Node.isPropertySignature(d) && d.hasQuestionToken());
375
381
  const valType = prop.getTypeAtLocation(sourceFile);
376
- props[name] = buildPropDescriptor(valType, required, defaultValues[name]);
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) {
@@ -496,9 +596,10 @@ function matchGlob(pattern, value) {
496
596
  function extractTsDocTags(declNode) {
497
597
  let collection;
498
598
  let internal = false;
599
+ const keywords = [];
499
600
  const nodeWithDocs = declNode;
500
601
  if (typeof nodeWithDocs.getJsDocs !== "function") {
501
- return { collection, internal };
602
+ return { collection, internal, keywords };
502
603
  }
503
604
  const jsDocs = nodeWithDocs.getJsDocs();
504
605
  for (const jsDoc of jsDocs) {
@@ -511,10 +612,18 @@ function extractTsDocTags(declNode) {
511
612
  }
512
613
  } else if (tagName === "internal") {
513
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
+ }
514
623
  }
515
624
  }
516
625
  }
517
- return { collection, internal };
626
+ return { collection, internal, keywords };
518
627
  }
519
628
  function readCollectionFromScopeFile(scopeFilePath, project) {
520
629
  let sf = project.getSourceFile(scopeFilePath);
@@ -595,8 +704,9 @@ function processSourceFile(sourceFile, rootDir, project) {
595
704
  requiredContexts: detectRequiredContexts(fn, sourceFile, project),
596
705
  sideEffects: detectSideEffects(fn),
597
706
  scopeFile: null,
598
- // collection and internal will be filled in after all components are collected
599
- internal: false
707
+ // collection, internal, and keywords will be filled in after all components are collected
708
+ internal: false,
709
+ keywords: []
600
710
  }
601
711
  });
602
712
  }
@@ -634,7 +744,16 @@ function processSourceFile(sourceFile, rootDir, project) {
634
744
  }
635
745
  const propsTypeName = inferPropsTypeName(params);
636
746
  const defaults = extractDefaultsFromDestructuring(params);
637
- const props = propsTypeName ? extractPropsFromType(propsTypeName, sourceFile, defaults) : {};
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
+ }
638
757
  const composes = collectJsxCompositions(bodyNode);
639
758
  const startLine = varDecl.getStartLineNumber();
640
759
  const endLine = varDecl.getEndLineNumber();
@@ -662,7 +781,8 @@ function processSourceFile(sourceFile, rootDir, project) {
662
781
  requiredContexts: detectRequiredContexts(bodyNode, sourceFile, project),
663
782
  sideEffects: detectSideEffects(bodyNode),
664
783
  scopeFile: null,
665
- internal: false
784
+ internal: false,
785
+ keywords: []
666
786
  }
667
787
  });
668
788
  }
@@ -712,19 +832,21 @@ function processSourceFile(sourceFile, rootDir, project) {
712
832
  requiredContexts: [],
713
833
  sideEffects: detectSideEffects(cls),
714
834
  scopeFile: null,
715
- internal: false
835
+ internal: false,
836
+ keywords: []
716
837
  }
717
838
  });
718
839
  }
719
840
  return results;
720
841
  }
721
842
  function generateManifest(config) {
843
+ const normalizedRootDir = realpathSync(config.rootDir);
722
844
  const {
723
- rootDir,
724
845
  include = ["src/**/*.tsx", "src/**/*.ts"],
725
846
  exclude = ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/dist/**", "**/*.d.ts"],
726
- tsConfigFilePath = join(rootDir, "tsconfig.json")
847
+ tsConfigFilePath = join(normalizedRootDir, "tsconfig.json")
727
848
  } = config;
849
+ const rootDir = normalizedRootDir;
728
850
  const project = new Project({
729
851
  tsConfigFilePath,
730
852
  skipAddingFilesFromTsConfig: true
@@ -787,14 +909,16 @@ function generateManifest(config) {
787
909
  const sf = project.getSourceFile(absFilePath);
788
910
  let tsdocCollection;
789
911
  let tsdocInternal = false;
912
+ let tsdocKeywords = [];
790
913
  if (sf) {
791
914
  const fn = sf.getFunction(compName);
792
915
  if (fn) {
793
916
  const tags = extractTsDocTags(fn);
794
917
  tsdocCollection = tags.collection;
795
918
  tsdocInternal = tags.internal;
919
+ tsdocKeywords = tags.keywords;
796
920
  }
797
- if (tsdocCollection === void 0 && !tsdocInternal) {
921
+ if (tsdocCollection === void 0 && !tsdocInternal && tsdocKeywords.length === 0) {
798
922
  const varDecl = sf.getVariableDeclaration(compName);
799
923
  if (varDecl) {
800
924
  const varStmt = varDecl.getVariableStatement();
@@ -802,15 +926,17 @@ function generateManifest(config) {
802
926
  const tags = extractTsDocTags(varStmt);
803
927
  tsdocCollection = tags.collection;
804
928
  tsdocInternal = tags.internal;
929
+ tsdocKeywords = tags.keywords;
805
930
  }
806
931
  }
807
932
  }
808
- if (tsdocCollection === void 0 && !tsdocInternal) {
933
+ if (tsdocCollection === void 0 && !tsdocInternal && tsdocKeywords.length === 0) {
809
934
  const cls = sf.getClass(compName);
810
935
  if (cls) {
811
936
  const tags = extractTsDocTags(cls);
812
937
  tsdocCollection = tags.collection;
813
938
  tsdocInternal = tags.internal;
939
+ tsdocKeywords = tags.keywords;
814
940
  }
815
941
  }
816
942
  }
@@ -831,11 +957,16 @@ function generateManifest(config) {
831
957
  (p) => matchGlob(p, desc.filePath) || matchGlob(p, desc.displayName)
832
958
  );
833
959
  }
960
+ const iconPats = config.iconPatterns ?? [];
961
+ const isIcon = iconPats.length > 0 && iconPats.some((p) => matchGlob(p, desc.filePath) || matchGlob(p, desc.displayName));
834
962
  const resolvedCollection = tsdocCollection ?? scopeFileCollection ?? configCollection;
835
963
  if (resolvedCollection !== void 0) {
836
964
  desc.collection = resolvedCollection;
837
965
  }
838
- desc.internal = tsdocInternal || configInternal;
966
+ desc.internal = tsdocInternal || configInternal || isIcon;
967
+ if (tsdocKeywords.length > 0) {
968
+ desc.keywords = tsdocKeywords;
969
+ }
839
970
  }
840
971
  return {
841
972
  version: "0.1",
@@ -852,7 +983,103 @@ function detectScopeFile(componentFilePath, rootDir) {
852
983
  for (const ext of SCOPE_EXTENSIONS) {
853
984
  const candidate = `${stem}${ext}`;
854
985
  if (existsSync(candidate)) {
855
- return { filePath: candidate, scenarioNames: [], hasWrapper: false };
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);
856
1083
  }
857
1084
  }
858
1085
  return null;