ast-utils.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. import path from 'path';
  2. import ts from 'typescript';
  3. import { PluginInfo } from './config-loader.js';
  4. /**
  5. * Get the plugin info from the source file.
  6. */
  7. export function getPluginInfo(sourceFile: ts.SourceFile): PluginInfo | undefined {
  8. const classDeclaration = sourceFile.statements.find(statement => {
  9. return (
  10. statement.kind === ts.SyntaxKind.ClassDeclaration &&
  11. statement.getText().includes('@VendurePlugin(')
  12. );
  13. });
  14. if (classDeclaration) {
  15. const identifier = classDeclaration.getChildren().find(child => {
  16. return child.kind === ts.SyntaxKind.Identifier;
  17. });
  18. const dashboardEntryPath = classDeclaration
  19. .getChildren()
  20. .map(child => {
  21. if (child.kind === ts.SyntaxKind.SyntaxList) {
  22. const pluginDecorator = child.getChildren().find(_child => {
  23. return _child.kind === ts.SyntaxKind.Decorator;
  24. });
  25. if (pluginDecorator) {
  26. const callExpression = findFirstDescendantOfKind(
  27. pluginDecorator,
  28. ts.SyntaxKind.CallExpression,
  29. );
  30. if (callExpression) {
  31. const objectLiteral = findFirstDescendantOfKind(
  32. callExpression,
  33. ts.SyntaxKind.ObjectLiteralExpression,
  34. );
  35. if (objectLiteral && ts.isObjectLiteralExpression(objectLiteral)) {
  36. // Now find the specific 'dashboard' property
  37. const dashboardProperty = objectLiteral.properties.find(
  38. prop =>
  39. ts.isPropertyAssignment(prop) && prop.name?.getText() === 'dashboard',
  40. );
  41. if (
  42. dashboardProperty &&
  43. ts.isPropertyAssignment(dashboardProperty) &&
  44. ts.isStringLiteral(dashboardProperty.initializer)
  45. ) {
  46. const dashboardPath = dashboardProperty.initializer.text;
  47. return dashboardPath;
  48. }
  49. }
  50. }
  51. }
  52. }
  53. })
  54. .filter(Boolean)?.[0];
  55. if (identifier) {
  56. return {
  57. name: identifier.getText(),
  58. pluginPath: path.dirname(sourceFile.fileName),
  59. dashboardEntryPath,
  60. };
  61. }
  62. }
  63. }
  64. /**
  65. * Given the AST of a TypeScript file, finds the name of the variable exported as VendureConfig.
  66. */
  67. export function findConfigExport(sourceFile: ts.SourceFile): string | undefined {
  68. let exportedSymbolName: string | undefined;
  69. function visit(node: ts.Node) {
  70. if (
  71. ts.isVariableStatement(node) &&
  72. node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword)
  73. ) {
  74. node.declarationList.declarations.forEach(declaration => {
  75. if (ts.isVariableDeclaration(declaration)) {
  76. const typeNode = declaration.type;
  77. if (typeNode && ts.isTypeReferenceNode(typeNode)) {
  78. const typeName = typeNode.typeName;
  79. if (ts.isIdentifier(typeName) && typeName.text === 'VendureConfig') {
  80. if (ts.isIdentifier(declaration.name)) {
  81. exportedSymbolName = declaration.name.text;
  82. }
  83. }
  84. }
  85. }
  86. });
  87. }
  88. ts.forEachChild(node, visit);
  89. }
  90. visit(sourceFile);
  91. return exportedSymbolName;
  92. }
  93. function findFirstDescendantOfKind(node: ts.Node, kind: ts.SyntaxKind): ts.Node | undefined {
  94. let foundNode: ts.Node | undefined;
  95. function visit(_node: ts.Node) {
  96. if (foundNode) {
  97. // Stop searching if we already found it
  98. return;
  99. }
  100. if (_node.kind === kind) {
  101. foundNode = _node;
  102. return;
  103. }
  104. // Recursively visit children
  105. ts.forEachChild(_node, visit);
  106. }
  107. // Start the traversal from the initial node's children
  108. ts.forEachChild(node, visit);
  109. return foundNode;
  110. }