I want to create a library with components example pages generated automatically by a node js code. Using some Copilot help, I got this function to read the component's metadata, but it is returning me empty. My knowledge in node is not so developed yet and I want to understand the process, not only copy from AI. This is the function with the interfaces - the code is in typescript for node. The function receives the angular component file path as parameter
interface InputMetadata {
name: string;
type: string | null;
defaultValue: string | null;
}
interface ComponentMetadata {
selector: string;
standalone: boolean;
inputs: InputMetadata[];
}
// Function to read component metadata
function readComponentMetadata(componentPath: string): ComponentMetadata {
const componentFile = fs.readFileSync(componentPath, 'utf-8');
const sourceFile = ts.createSourceFile(componentPath, componentFile, ts.ScriptTarget.Latest, true);
const inputs: InputMetadata[] = [];
let selector: string = '';
let standalone: boolean = false;
function visit(node: ts.Node) {
if (ts.isClassDeclaration(node) && node.decorators) {
node.decorators.forEach(decorator => {
if (ts.isCallExpression(decorator.expression) && decorator.expression.expression.getText() === 'Component') {
const args = decorator.expression.arguments;
if (args.length) {
const componentMetadata = args[0] as ts.ObjectLiteralExpression;
componentMetadata.properties.forEach(property => {
if (ts.isPropertyAssignment(property)) {
if (property.name.getText() === 'selector') {
selector = (property.initializer as ts.StringLiteral).text;
} else if (property.name.getText() === 'standalone') {
standalone = (property.initializer.kind === ts.SyntaxKind.TrueKeyword);
}
}
});
}
}
});
}
if (ts.isPropertyDeclaration(node) && node.decorators) {
node.decorators.forEach(decorator => {
if (ts.isCallExpression(decorator.expression) && decorator.expression.expression.getText() === 'Input') {
const name = node.name.getText();
const type = node.type ? node.type.getText() : null;
const initializer = node.initializer ? node.initializer.getText() : null;
inputs.push({name, type, defaultValue: initializer});
}
});
}
ts.forEachChild(node, visit);
}
visit(sourceFile);
return {selector, standalone, inputs};
}
The file must be "compiled" using npx tsc
and then the js file can be called.
The node and typescript I'm using are:
"devDependencies": {
"@types/node": "^22.10.1",
"typescript": "^5.7.2"
}
This is the tsconfig.json file:
{
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules"]
}
For me the code was throwing type errors, like mentioned in the comments. Seems like a ClassDeclaration
does not have a decorators
property. To get the decorators, you need to call ts.getDecorators(node)
. Updated code:
function readComponentMetadata(componentPath: string): ComponentMetadata {
// nothing changed here
function visit(node: ts.Node) {
if (ts.isClassDeclaration(node)) {
const decorators = ts.getDecorators(node);
decorators?.forEach(decorator => {
// nothing changed here
});
}
if (ts.isPropertyDeclaration(node)) {
const decorators = ts.getDecorators(node);
decorators?.forEach(decorator => {
// nothing changed here
});
}
ts.forEachChild(node, visit);
}
visit(sourceFile);
return {selector, standalone, inputs};
}
I have also tested this code in a real Angular project, seems to work fine.