@sigyl-dev/cli
Version:
Official Sigyl CLI for installing and managing MCP packages. Zero-config installation for public packages, secure API-based authentication.
773 lines • 35.9 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExpressScanner = void 0;
const ts_morph_1 = require("ts-morph");
const node_path_1 = require("node:path");
const node_fs_1 = require("node:fs");
const logger_1 = require("../logger");
const chalk_1 = __importDefault(require("chalk"));
class ExpressScanner {
project;
directory;
typeCache = new Map();
importedTypes = new Map(); // Maps imported name to actual type
constructor(directory) {
this.directory = directory;
this.project = new ts_morph_1.Project({
compilerOptions: {
target: ts_morph_1.ScriptTarget.ES2020,
module: ts_morph_1.ModuleKind.ESNext,
moduleResolution: ts_morph_1.ModuleResolutionKind.NodeJs,
allowSyntheticDefaultImports: true,
esModuleInterop: true,
skipLibCheck: true,
strict: false
}
});
}
async scanForEndpoints(framework) {
(0, logger_1.verboseLog)(`Scanning directory: ${this.directory}`);
// First, collect all type definitions and add source files
await this.collectTypes();
// Then scan for routes using already-added source files
let allEndpoints = [];
// Get all source files that were already added during type collection
const sourceFiles = this.project.getSourceFiles();
for (const sourceFile of sourceFiles) {
try {
const endpoints = this.scanFileForRoutes(sourceFile);
allEndpoints.push(...endpoints);
(0, logger_1.verboseLog)(`Found ${endpoints.length} endpoints in ${sourceFile.getFilePath()}`);
}
catch (error) {
console.warn(chalk_1.default.yellow(`Warning: Could not parse ${sourceFile.getFilePath()}: ${error}`));
}
}
return allEndpoints;
}
async collectTypes() {
const sourceFiles = this.findSourceFiles();
for (const filePath of sourceFiles) {
try {
// Check if source file is already added to avoid duplicates
let sourceFile = this.project.getSourceFile(filePath);
if (!sourceFile) {
sourceFile = this.project.addSourceFileAtPath(filePath);
}
this.extractTypesFromFile(sourceFile);
this.extractImportsFromFile(sourceFile);
}
catch (error) {
console.warn(chalk_1.default.yellow(`Warning: Could not parse types from ${filePath}: ${error}`));
}
}
(0, logger_1.verboseLog)(`Collected ${this.typeCache.size} types and ${this.importedTypes.size} imports`);
}
extractImportsFromFile(sourceFile) {
// Extract import statements to understand type mappings
sourceFile.getImportDeclarations().forEach((importDecl) => {
const moduleSpecifier = importDecl.getModuleSpecifierValue();
const namedImports = importDecl.getNamedImports();
namedImports.forEach((namedImport) => {
const importName = namedImport.getName();
const aliasName = namedImport.getAliasNode()?.getText() || importName;
this.importedTypes.set(aliasName, importName);
// Also store the full import path mapping for complex types
const fullImportPath = `import("${moduleSpecifier}").${importName}`;
this.importedTypes.set(fullImportPath, importName);
});
});
}
extractTypesFromFile(sourceFile) {
// Extract interface and type definitions
sourceFile.getInterfaces().forEach((interfaceDecl) => {
const interfaceName = interfaceDecl.getName();
const properties = {};
const required = [];
interfaceDecl.getProperties().forEach((property) => {
const propertyName = property.getName();
const propertyType = this.extractTypeFromNode(property.getType());
const isOptional = property.hasQuestionToken();
properties[propertyName] = {
type: propertyType,
description: this.extractJSDocDescription(property)
};
if (!isOptional) {
required.push(propertyName);
}
});
this.typeCache.set(interfaceName, {
name: interfaceName,
type: "object",
properties,
required
});
});
// Extract type aliases
sourceFile.getTypeAliases().forEach((typeAlias) => {
const typeName = typeAlias.getName();
const typeNode = typeAlias.getType();
const extractedType = this.extractTypeFromNode(typeNode);
this.typeCache.set(typeName, {
name: typeName,
type: extractedType
});
});
}
extractTypeFromNode(type) {
const typeText = type.getText();
// Handle primitive types
if (typeText === "string")
return "string";
if (typeText === "number")
return "number";
if (typeText === "boolean")
return "boolean";
if (typeText === "Date")
return "string"; // Date becomes string in JSON
// Handle arrays
if (typeText.endsWith("[]") || typeText.includes("Array<")) {
return "array";
}
// Handle union types
if (typeText.includes("|")) {
// For union types, try to find a common type or default to string
const unionTypes = typeText.split("|").map((t) => t.trim());
if (unionTypes.every((t) => t === "string" || t === "number" || t === "boolean")) {
// If all are primitives, use the first one
return this.extractTypeFromNode({ getText: () => unionTypes[0] });
}
return "string"; // Default for complex unions
}
// Handle object types
if (typeText.includes("{") || typeText.includes("Record<")) {
return "object";
}
// Check if it's a known interface/type
if (this.typeCache.has(typeText)) {
return "object";
}
// Check if it's an imported type
if (this.importedTypes.has(typeText)) {
const actualType = this.importedTypes.get(typeText);
if (this.typeCache.has(actualType)) {
return "object";
}
}
// Default to object for unknown types
return "object";
}
extractJSDocDescription(node) {
const jsDoc = node.getJsDocs()[0];
return jsDoc?.getDescription()?.getText() || undefined;
}
findSourceFiles() {
const files = [];
const scanDirectory = (dir) => {
const entries = (0, node_fs_1.readdirSync)(dir);
for (const entry of entries) {
const fullPath = (0, node_path_1.join)(dir, entry);
const stat = (0, node_fs_1.statSync)(fullPath);
if (stat.isDirectory() && !entry.startsWith(".") && entry !== "node_modules") {
scanDirectory(fullPath);
}
else if (stat.isFile() && (entry.endsWith(".ts") || entry.endsWith(".js"))) {
files.push(fullPath);
}
}
};
scanDirectory(this.directory);
return files;
}
scanFileForRoutes(sourceFile) {
const endpoints = [];
// Look for Express route patterns: app.get(), app.post(), etc.
sourceFile.forEachDescendant((node) => {
if (node.getKind() === ts_morph_1.SyntaxKind.CallExpression) {
const callExpression = node;
// Check if this is an Express route call
const expression = callExpression.getExpression();
if (expression && expression.getKind() === ts_morph_1.SyntaxKind.PropertyAccessExpression) {
const propertyAccess = expression;
const objectName = propertyAccess.getExpression()?.getText() || "";
const methodName = propertyAccess.getName();
// Check for patterns like app.get, router.post, etc.
const isExpressObject = ["app", "router"].includes(objectName);
const isHttpMethod = ["get", "post", "put", "delete", "patch", "options", "head"].includes(methodName.toLowerCase());
if (isExpressObject && isHttpMethod) {
const args = callExpression.getArguments();
if (args.length >= 2) {
// First argument should be the path
const pathArg = args[0];
const path = this.extractStringValue(pathArg);
if (path) {
const handlerNode = args[args.length - 1];
const endpoint = {
method: methodName.toUpperCase(),
path: path,
handler: this.extractHandlerInfo(handlerNode),
parameters: this.extractRouteParameters(path),
description: this.extractRouteDescription(handlerNode)
};
// Analyze the handler function for types
this.analyzeHandlerTypes(endpoint, handlerNode);
endpoints.push(endpoint);
}
}
}
}
}
});
return endpoints;
}
extractStringValue(node) {
if (node.getKind() === ts_morph_1.SyntaxKind.StringLiteral) {
return node.getLiteralValue();
}
return null;
}
extractHandlerInfo(node) {
if (node.getKind() === ts_morph_1.SyntaxKind.ArrowFunction) {
return "Arrow Function";
}
else if (node.getKind() === ts_morph_1.SyntaxKind.FunctionExpression) {
return "Function Expression";
}
else if (node.getKind() === ts_morph_1.SyntaxKind.Identifier) {
return `Function: ${node.getText()}`;
}
return "Unknown Handler";
}
extractRouteDescription(handlerNode) {
// Try to extract JSDoc comments from the handler
const jsDoc = handlerNode.getJsDocs()[0];
return jsDoc?.getDescription()?.getText() || undefined;
}
extractRouteParameters(path) {
const parameters = [];
// Extract path parameters (e.g., /users/:id)
const pathParamRegex = /:([a-zA-Z_][a-zA-Z0-9_]*)/g;
let match;
while ((match = pathParamRegex.exec(path)) !== null) {
parameters.push({
name: match[1],
type: "string", // Will be overridden by type analysis if found
required: true,
location: "path",
description: `Path parameter: ${match[1]}`
});
}
return parameters;
}
analyzeHandlerTypes(endpoint, handlerNode) {
// Analyze the handler function to understand request/response types
if (handlerNode.getKind() === ts_morph_1.SyntaxKind.ArrowFunction ||
handlerNode.getKind() === ts_morph_1.SyntaxKind.FunctionExpression) {
const parameters = handlerNode.getParameters();
if (parameters.length >= 2) {
const reqParam = parameters[0];
const resParam = parameters[1];
// Analyze request parameter usage
this.analyzeRequestUsage(endpoint, handlerNode, reqParam);
// Analyze response type
this.analyzeResponseType(endpoint, handlerNode, resParam);
}
}
}
analyzeRequestUsage(endpoint, handlerNode, reqParam) {
const reqName = reqParam.getName();
// Look for type annotations on req parameter
const reqType = reqParam.getType();
if (reqType) {
// This would be the Express.Request type, not very useful for our purposes
}
// Look for variable declarations with type annotations
handlerNode.forEachDescendant((node) => {
if (node.getKind() === ts_morph_1.SyntaxKind.VariableStatement) {
const declarations = node.getDeclarationList().getDeclarations();
// Handle each variable declaration in the statement
declarations.forEach((varDecl) => {
const varName = varDecl.getName();
const varType = varDecl.getType();
const initializer = varDecl.getInitializer();
// Check if this variable is initialized with req.body, req.query, etc.
if (initializer) {
const initText = initializer.getText();
if (initText.includes(`${reqName}.body`)) {
this.analyzeTypedBodyUsage(endpoint, varType, varName);
}
else if (initText.includes(`${reqName}.query`)) {
this.analyzeTypedQueryUsage(endpoint, varType, varName);
}
else if (initText.includes(`${reqName}.params`)) {
this.analyzeTypedParamsUsage(endpoint, varType, varName);
}
}
// Handle destructuring assignments
const nameNode = varDecl.getNameNode();
if (nameNode && nameNode.getKind() === ts_morph_1.SyntaxKind.ObjectBindingPattern && initializer) {
const initText = initializer.getText();
// Check if destructuring from req.query, req.body, or req.params
if (initText.includes(`${reqName}.query`)) {
this.analyzeDestructuredQuery(endpoint, nameNode);
}
else if (initText.includes(`${reqName}.body`)) {
this.analyzeDestructuredBody(endpoint, nameNode);
}
else if (initText.includes(`${reqName}.params`)) {
this.analyzeDestructuredParams(endpoint, nameNode);
}
}
});
}
});
// Also look for direct property access patterns
handlerNode.forEachDescendant((node) => {
if (node.getKind() === ts_morph_1.SyntaxKind.PropertyAccessExpression) {
const propertyAccess = node;
const objectName = propertyAccess.getExpression()?.getText();
const propertyName = propertyAccess.getName();
if (objectName === reqName) {
switch (propertyName) {
case "body":
this.analyzeBodyUsage(endpoint, node);
break;
case "params":
this.analyzeParamsUsage(endpoint, node);
break;
case "query":
this.analyzeQueryUsage(endpoint, node);
break;
}
}
}
});
}
analyzeDestructuredQuery(endpoint, bindingPattern) {
// Extract property names from destructuring pattern like { limit, offset, search }
const elements = bindingPattern.getElements();
endpoint.parameters = endpoint.parameters || [];
elements.forEach((element) => {
if (element.getKind() === ts_morph_1.SyntaxKind.BindingElement) {
const propName = element.getName();
// Check if this parameter already exists (avoid duplicates)
const existingParam = endpoint.parameters?.find(p => p.name === propName && p.location === "query");
if (!existingParam) {
// Default to string type for JavaScript, but try to infer better types
let paramType = "string";
// Look for type conversion patterns in the same function
const parentFunction = this.findParentFunction(element);
if (parentFunction) {
paramType = this.inferParameterType(parentFunction, propName);
}
endpoint.parameters.push({
name: propName,
type: paramType,
required: false, // Query parameters are typically optional
location: "query",
description: `Query parameter: ${propName}`
});
}
}
});
}
analyzeDestructuredBody(endpoint, bindingPattern) {
// Extract property names from destructuring pattern like { name, email }
const elements = bindingPattern.getElements();
const properties = {};
const required = [];
elements.forEach((element) => {
if (element.getKind() === ts_morph_1.SyntaxKind.BindingElement) {
const propName = element.getName();
properties[propName] = {
type: "string", // Default type for JavaScript
description: `Body parameter: ${propName}`
};
// Assume destructured body properties are required
required.push(propName);
}
});
if (Object.keys(properties).length > 0) {
endpoint.requestBody = {
type: "object",
properties,
required
};
}
}
analyzeDestructuredParams(endpoint, bindingPattern) {
// Extract property names from destructuring pattern
const elements = bindingPattern.getElements();
endpoint.parameters = endpoint.parameters || [];
elements.forEach((element) => {
if (element.getKind() === ts_morph_1.SyntaxKind.BindingElement) {
const propName = element.getName();
// Check if this parameter already exists (avoid duplicates)
const existingParam = endpoint.parameters?.find(p => p.name === propName && p.location === "path");
if (!existingParam) {
endpoint.parameters.push({
name: propName,
type: "string", // Path parameters are typically strings
required: true,
location: "path",
description: `Path parameter: ${propName}`
});
}
}
});
}
inferParameterType(functionNode, paramName) {
// Look for type conversion patterns like parseInt(paramName) or Number(paramName)
let inferredType = "string"; // Default
functionNode.forEachDescendant((node) => {
if (node.getKind() === ts_morph_1.SyntaxKind.CallExpression) {
const callExpr = node;
const expression = callExpr.getExpression();
const args = callExpr.getArguments();
if (args.length > 0) {
const firstArg = args[0];
const argText = firstArg.getText();
// Check if the argument references our parameter
if (argText.includes(paramName)) {
const functionName = expression.getText();
// Common type conversion patterns
if (functionName === "parseInt" || functionName === "Number" || functionName === "parseFloat") {
inferredType = "number";
}
else if (functionName === "Boolean") {
inferredType = "boolean";
}
}
}
}
});
return inferredType;
}
analyzeTypedBodyUsage(endpoint, varType, varName) {
const typeText = varType.getText();
// Extract the actual type name from complex import paths
let actualTypeName = typeText;
if (typeText.includes('import(') && typeText.includes(').')) {
// Extract type name from import("...").TypeName format
const match = typeText.match(/import\([^)]+\)\.(.+)$/);
if (match) {
actualTypeName = match[1];
}
}
// Check if this type is in our cache (try both full type text and extracted name)
let typeInfo = this.typeCache.get(typeText) || this.typeCache.get(actualTypeName);
if (!typeInfo && this.importedTypes.has(typeText)) {
const mappedType = this.importedTypes.get(typeText);
typeInfo = this.typeCache.get(mappedType);
}
if (!typeInfo && this.importedTypes.has(actualTypeName)) {
const mappedType = this.importedTypes.get(actualTypeName);
typeInfo = this.typeCache.get(mappedType);
}
if (typeInfo && typeInfo.properties) {
endpoint.requestBody = {
type: "object",
properties: typeInfo.properties,
required: typeInfo.required || []
};
}
else {
endpoint.requestBody = {
type: this.extractTypeFromNode(varType)
};
}
}
analyzeTypedQueryUsage(endpoint, varType, varName) {
const typeText = varType.getText();
// Extract the actual type name from complex import paths
let actualTypeName = typeText;
if (typeText.includes('import(') && typeText.includes(').')) {
// Extract type name from import("...").TypeName format
const match = typeText.match(/import\([^)]+\)\.(.+)$/);
if (match) {
actualTypeName = match[1];
}
}
// Check if this type is in our cache (try both full type text and extracted name)
let typeInfo = this.typeCache.get(typeText) || this.typeCache.get(actualTypeName);
if (!typeInfo && this.importedTypes.has(typeText)) {
const mappedType = this.importedTypes.get(typeText);
typeInfo = this.typeCache.get(mappedType);
}
if (!typeInfo && this.importedTypes.has(actualTypeName)) {
const mappedType = this.importedTypes.get(actualTypeName);
typeInfo = this.typeCache.get(mappedType);
}
if (typeInfo && typeInfo.properties) {
// Add query parameters based on the type
endpoint.parameters = endpoint.parameters || [];
Object.entries(typeInfo.properties).forEach(([propName, propInfo]) => {
endpoint.parameters.push({
name: propName,
type: propInfo.type,
required: typeInfo.required?.includes(propName) || false,
location: "query",
description: propInfo.description || `Query parameter: ${propName}`
});
});
}
}
analyzeTypedParamsUsage(endpoint, varType, varName) {
// For params, we typically just have string types, but we can check for number conversion
// Since we can't easily traverse the AST from the type object, we'll rely on the path parameter analysis
// and the fact that path parameters are typically strings that might be converted to numbers
// Look for parseInt usage in the handler by analyzing the entire handler node
// This is a simplified approach - in a real implementation, we'd need more sophisticated AST traversal
if (endpoint.parameters) {
endpoint.parameters.forEach(param => {
if (param.location === "path" && param.name === "id") {
// Common pattern: id parameters are often converted to numbers
param.type = "number";
}
});
}
}
analyzeBodyUsage(endpoint, bodyNode) {
// If we already have detailed request body info from typed analysis, don't overwrite it
if (endpoint.requestBody && endpoint.requestBody.properties) {
return;
}
// Look for property access on req.body to understand the structure
const properties = {};
const required = [];
bodyNode.getParent()?.forEachDescendant((node) => {
if (node.getKind() === ts_morph_1.SyntaxKind.PropertyAccessExpression) {
const propertyAccess = node;
const objectName = propertyAccess.getExpression()?.getText();
if (objectName?.includes("body")) {
const propertyName = propertyAccess.getName();
properties[propertyName] = {
type: "string", // Default type
description: `Body parameter: ${propertyName}`
};
required.push(propertyName);
}
}
});
if (Object.keys(properties).length > 0) {
endpoint.requestBody = {
type: "object",
properties,
required
};
}
else {
endpoint.requestBody = {
type: "object"
};
}
}
analyzeParamsUsage(endpoint, paramsNode) {
// Look for req.params usage to understand path parameters
paramsNode.getParent()?.forEachDescendant((node) => {
if (node.getKind() === ts_morph_1.SyntaxKind.PropertyAccessExpression) {
const propertyAccess = node;
const objectName = propertyAccess.getExpression()?.getText();
if (objectName?.includes("params")) {
const propertyName = propertyAccess.getName();
// Check if this parameter is already in our path parameters
const existingParam = endpoint.parameters?.find(p => p.name === propertyName);
if (!existingParam) {
endpoint.parameters = endpoint.parameters || [];
endpoint.parameters.push({
name: propertyName,
type: "string",
required: true,
location: "path",
description: `Path parameter: ${propertyName}`
});
}
}
}
});
}
analyzeQueryUsage(endpoint, queryNode) {
// Look for req.query usage to understand query parameters
queryNode.getParent()?.forEachDescendant((node) => {
if (node.getKind() === ts_morph_1.SyntaxKind.PropertyAccessExpression) {
const propertyAccess = node;
const objectName = propertyAccess.getExpression()?.getText();
if (objectName?.includes("query")) {
const propertyName = propertyAccess.getName();
endpoint.parameters = endpoint.parameters || [];
endpoint.parameters.push({
name: propertyName,
type: "string",
required: false,
location: "query",
description: `Query parameter: ${propertyName}`
});
}
}
});
}
analyzeResponseType(endpoint, handlerNode, resParam) {
// Look for res.json() calls to understand response type
handlerNode.forEachDescendant((node) => {
if (node.getKind() === ts_morph_1.SyntaxKind.CallExpression) {
const callExpression = node;
const expression = callExpression.getExpression();
if (expression.getKind() === ts_morph_1.SyntaxKind.PropertyAccessExpression) {
const propertyAccess = expression;
const objectName = propertyAccess.getExpression()?.getText();
const methodName = propertyAccess.getName();
if (objectName === resParam.getName() && methodName === "json") {
// Try to infer response type from the argument
const args = callExpression.getArguments();
if (args.length > 0) {
const responseArg = args[0];
const responseInfo = this.analyzeResponseArgument(responseArg);
endpoint.responseType = responseInfo.type;
endpoint.responseSchema = responseInfo.schema;
}
}
}
}
});
}
analyzeResponseArgument(node) {
// Handle array literals
if (node.getKind() === ts_morph_1.SyntaxKind.ArrayLiteralExpression) {
const elements = node.getElements();
if (elements.length > 0) {
// Analyze the first element to understand the array type
const firstElement = elements[0];
const elementInfo = this.analyzeResponseArgument(firstElement);
return {
type: "array",
schema: {
type: "array",
items: elementInfo.schema || { type: elementInfo.type }
}
};
}
return { type: "array" };
}
// Handle object literals
if (node.getKind() === ts_morph_1.SyntaxKind.ObjectLiteralExpression) {
const properties = {};
const required = [];
node.getProperties().forEach((prop) => {
if (prop.getKind() === ts_morph_1.SyntaxKind.PropertyAssignment) {
const propName = prop.getName();
const propValue = prop.getInitializer();
if (propValue) {
const propInfo = this.analyzeResponseArgument(propValue);
properties[propName] = {
type: propInfo.type,
description: `Response property: ${propName}`
};
// Assume all properties are required in response objects
required.push(propName);
}
}
});
return {
type: "object",
schema: {
type: "object",
properties,
required
}
};
}
// Handle identifier references (typed variables)
if (node.getKind() === ts_morph_1.SyntaxKind.Identifier) {
const varName = node.getText();
// Look for variable declarations with types
const parentFunction = this.findParentFunction(node);
if (parentFunction) {
let foundTypeInfo = null;
parentFunction.forEachDescendant((varNode) => {
if (varNode.getKind() === ts_morph_1.SyntaxKind.VariableStatement) {
const varDecl = varNode.getDeclarationList().getDeclarations()[0];
if (varDecl && varDecl.getName() === varName) {
const varType = varDecl.getType();
const typeText = varType.getText();
// Extract type name from complex import paths
let actualTypeName = typeText;
if (typeText.includes('import(') && typeText.includes(').')) {
const match = typeText.match(/import\([^)]+\)\.(.+)$/);
if (match) {
actualTypeName = match[1];
}
}
// Check if this type is in our cache
let typeInfo = this.typeCache.get(typeText) || this.typeCache.get(actualTypeName);
if (!typeInfo && this.importedTypes.has(typeText)) {
const mappedType = this.importedTypes.get(typeText);
typeInfo = this.typeCache.get(mappedType);
}
if (!typeInfo && this.importedTypes.has(actualTypeName)) {
const mappedType = this.importedTypes.get(actualTypeName);
typeInfo = this.typeCache.get(mappedType);
}
if (typeInfo && typeInfo.properties) {
foundTypeInfo = {
type: "object",
schema: {
type: "object",
properties: typeInfo.properties,
required: typeInfo.required || []
}
};
}
}
}
});
if (foundTypeInfo) {
return foundTypeInfo;
}
}
}
// Handle primitive literals
if (node.getKind() === ts_morph_1.SyntaxKind.StringLiteral) {
return { type: "string" };
}
if (node.getKind() === ts_morph_1.SyntaxKind.NumericLiteral) {
return { type: "number" };
}
if (node.getKind() === ts_morph_1.SyntaxKind.TrueKeyword || node.getKind() === ts_morph_1.SyntaxKind.FalseKeyword) {
return { type: "boolean" };
}
// Default fallback
return { type: "object" };
}
findParentFunction(node) {
let current = node.getParent();
while (current) {
if (current.getKind() === ts_morph_1.SyntaxKind.ArrowFunction ||
current.getKind() === ts_morph_1.SyntaxKind.FunctionExpression ||
current.getKind() === ts_morph_1.SyntaxKind.FunctionDeclaration) {
return current;
}
current = current.getParent();
}
return null;
}
inferResponseType(node) {
if (node.getKind() === ts_morph_1.SyntaxKind.ArrayLiteralExpression) {
return "array";
}
if (node.getKind() === ts_morph_1.SyntaxKind.ObjectLiteralExpression) {
return "object";
}
if (node.getKind() === ts_morph_1.SyntaxKind.StringLiteral) {
return "string";
}
if (node.getKind() === ts_morph_1.SyntaxKind.NumericLiteral) {
return "number";
}
return "object";
}
}
exports.ExpressScanner = ExpressScanner;
//# sourceMappingURL=express-scanner.js.map