UNPKG

@sdk-usage/core

Version:
246 lines (238 loc) 7.2 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); let node_fs = require("node:fs"); let node_path = require("node:path"); let node_child_process = require("node:child_process"); let node_module = require("node:module"); let _open_vanilla_visitor = require("@open-vanilla/visitor"); let _swc_core = require("@swc/core"); let fdir = require("fdir"); //#region src/entities/item/createLocation.ts const createLocation = ({ code, file, link, module, offset, path }) => { const linesTillOffset = code.slice(0, Math.max(0, offset)).split(/\n/); const line = linesTillOffset.length; return { column: linesTillOffset[line - 1].length, file: `./${(0, node_path.relative)(path, file)}`, line, link, module }; }; //#endregion //#region src/entities/item/createItem.ts /** * Aggregate factory that creates an item. * @param input - Factory variables. * @param input.name - Name. * @param input.type - Component type. * @param input.module - Source module. * @param input.version - Module version. * @param input.location - Location. * @param input.input - Parameter list and metadata describing the way it's passed. * @returns Created item. * @example * createItem({ ... }); */ const createItem = ({ input, location, module, name, type, version }) => { return { createdAt: (/* @__PURE__ */ new Date()).toISOString(), input: { data: input?.data ?? {}, metadata: { withSpreading: input?.metadata.withSpreading ?? false } }, location: createLocation(location), module, name, type, version }; }; //#endregion //#region src/helpers.ts const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href); const resolvePackageJson = (fromPath) => { const filepath = (0, node_path.join)(fromPath, "./package.json"); if ((0, node_fs.existsSync)(filepath)) return filepath; return resolvePackageJson((0, node_path.join)(fromPath, "../")); }; /** * Execute an external command. * @param command - The command to execute. * @param options - Options including current working directory configuration. * @param options.cwd - Configure the current working directory. * @returns The output (either the command output or error). * @example * exec("ls"); */ const exec = async (command, options = {}) => { return new Promise((resolve, reject) => { let stdout = ""; let stderr = ""; const childProcess = (0, node_child_process.spawn)(command, { cwd: options.cwd, shell: true, stdio: "pipe" }); childProcess.stdout.on("data", (chunk) => { stdout += chunk; }); childProcess.stderr.on("data", (chunk) => { stderr += chunk; }); childProcess.on("close", (exitCode) => { if (exitCode === 0) resolve(stdout.trim()); else { const output = `${stderr}${stdout}`; reject(new Error(output.trim())); } }); }); }; //#endregion //#region src/modules/parser/parse.ts const parse = async (code, { onAdd, plugins }) => { const context = { imports: /* @__PURE__ */ new Map() }; let ast; try { ast = await (0, _swc_core.parse)(code, { syntax: "typescript", tsx: true }); } catch { ast = void 0; } if (ast === void 0) return; const visitor = { ImportDeclaration(node) { const module = node.source.value; node.specifiers.forEach((specifier) => { const specifierValue = specifier.local.value; context.imports.set(specifierValue, { alias: specifierValue, module, name: specifier.imported?.value ?? specifierValue }); }); } }; for (const plugin of plugins) { const pluginOutput = plugin(context, { getJSXAttributeValue }); const nodeKeys = Object.keys(pluginOutput); for (const nodeKey of nodeKeys) { const currentVisitorFunction = visitor[nodeKey]; visitor[nodeKey] = (node) => { if (typeof currentVisitorFunction === "function") currentVisitorFunction(node); const output = pluginOutput[nodeKey]?.(node); if (output) onAdd(output); }; } } (0, _open_vanilla_visitor.visit)(ast, visitor); }; const getJSXAttributeValue = (node) => { if (!node) return true; switch (node.type) { case "BigIntLiteral": case "BooleanLiteral": case "JSXText": case "NumericLiteral": case "StringLiteral": return node.value; case "JSXExpressionContainer": return getJSXAttributeValue(node.expression); case "NullLiteral": return null; default: return createUnknownToken(node.type); } }; /** * Helper to unify the way unknown AST token are managed. * @param token - AST token value. * @returns Formatted AST token. * @example * createUnknownToken("VariableDeclaration"); */ const createUnknownToken = (token) => `#${token}`; //#endregion //#region src/modules/scanner/scan.ts const scan = async (path, options = {}) => { const excludedFolders = options.excludeFolders ?? DEFAULT_EXCLUDED_FOLDERS; const includedFiles = options.includeFiles ?? DEFAULT_INCLUDED_FILES; const projectPaths = new fdir.fdir().withBasePath().glob("**/package.json").exclude((directoryName) => excludedFolders.includes(directoryName)).crawl(path).sync(); const projects = []; for (const projectPath of projectPaths) { const metadata = require$1(projectPath); const folder = (0, node_path.dirname)(projectPath); let link; try { link = await exec("git config --get remote.origin.url", { cwd: folder }); } catch { link = ""; } projects.push({ folder, link, metadata }); } return projects.map((project) => { const files = new fdir.fdir().withBasePath().glob(...includedFiles).exclude((directoryName) => excludedFolders.includes(directoryName)).crawl(project.folder).sync(); return { ...project, files }; }); }; const DEFAULT_EXCLUDED_FOLDERS = [ ".git", "node_modules", "dist", "out" ]; const DEFAULT_INCLUDED_FILES = ["**/*/!(test|*.test|stories|*.stories).?(m){j,t}s?(x)"]; //#endregion //#region src/createInstance.ts const createInstance = (path, options) => { return { async getItems() { const projects = await scan(path); const items = []; for (const project of projects) { const module = project.metadata.name; const dependencies = { ...project.metadata.devDependencies, ...project.metadata.optionalDependencies, ...project.metadata.dependencies }; const link = project.link; for (const file of project.files) { const code = (0, node_fs.readFileSync)(file, "utf8"); await parse(code, { onAdd(item) { if (options.includeModules && options.includeModules.length > 0 && !options.includeModules.includes(item.module)) return; let version = dependencies[item.module] ?? ""; if (options.resolveInstalledVersions) try { version = require$1(resolvePackageJson(require$1.resolve(item.module, { paths: [file] }))).version; } catch {} items.push(createItem({ ...item, location: { code, file, link, module, offset: item.offset, path }, version })); }, plugins: options.plugins ?? [] }); } } return items; } }; }; //#endregion //#region src/modules/plugin/createPlugin.ts const createPlugin = (input) => { return input; }; //#endregion exports.createInstance = createInstance; exports.createPlugin = createPlugin;