UNPKG

@nestia/sdk

Version:

Nestia SDK and Swagger generator

286 lines 11.8 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SdkWebSocketCloneProgrammer = void 0; const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); var SdkWebSocketCloneProgrammer; (function (SdkWebSocketCloneProgrammer) { SdkWebSocketCloneProgrammer.write = (app) => __awaiter(this, void 0, void 0, function* () { var _a; var _b; const ctx = { output: `${app.project.config.output}/structures`, outputs: new Map(), visited: new Map(), }; const cloned = new Set(); for (const route of app.routes) if (route.protocol === "websocket") for (const imp of route.imports) for (const local of imp.elements) { const imported = (_b = (_a = imp.elementAliases) === null || _a === void 0 ? void 0 : _a[local]) !== null && _b !== void 0 ? _b : local; if (yield clone(ctx)(imp.file, imported)) cloned.add(SdkWebSocketCloneProgrammer.importKey(imp.file, imported)); } return cloned; }); const clone = (ctx) => (file, name) => __awaiter(this, void 0, void 0, function* () { const location = yield resolveSourceFile(file); if (location === null || SdkWebSocketCloneProgrammer.isNodeModulesPath(location)) return false; const key = `${location}#${name}`; const status = ctx.visited.get(key); if (status === "written" || status === "pending") return true; else if (status === "missing") return false; ctx.visited.set(key, "pending"); const text = yield fs_1.default.promises.readFile(location, "utf8"); const declarations = getDeclarations(text, name); if (declarations.length === 0) { ctx.visited.set(key, "missing"); return false; } const body = declarations.join("\n\n"); const imports = yield collectImports(ctx)({ body, location, source: text, target: name, }); const content = [...imports, body].filter((line) => line.length).join("\n\n") + "\n"; const oldbie = ctx.outputs.get(name); if (oldbie !== undefined && oldbie !== content) { ctx.visited.set(key, "missing"); return false; } ctx.outputs.set(name, content); yield fs_1.default.promises.mkdir(ctx.output, { recursive: true }); yield fs_1.default.promises.writeFile(`${ctx.output}/${name}.ts`, content, "utf8"); ctx.visited.set(key, "written"); return true; }); const collectImports = (ctx) => (props) => __awaiter(this, void 0, void 0, function* () { const imports = []; const add = (line) => { if (imports.includes(line) === false) imports.push(line); }; for (const imp of getImports(props.source)) { const relative = imp.specifier.startsWith("."); for (const elem of imp.elements) { if (uses(props.body, elem.local) === false) continue; if (relative === false) { add(imp.text); continue; } const sourceFile = path_1.default.resolve(path_1.default.dirname(props.location), imp.specifier); if ((yield clone(ctx)(sourceFile, elem.imported)) === false) { add(imp.text); continue; } add(`import type { ${elem.imported}${elem.imported === elem.local ? "" : ` as ${elem.local}`} } from "./${elem.imported}";`); } for (const name of [imp.default, imp.namespace]) if (name !== null && uses(props.body, name)) add(imp.text); } for (const name of getExportedNames(props.source)) { if (name === null || name === props.target) continue; if (uses(props.body, name) === false) continue; if ((yield clone(ctx)(props.location, name)) === true) add(`import type { ${name} } from "./${name}";`); } return imports.sort(); }); const resolveSourceFile = (file) => __awaiter(this, void 0, void 0, function* () { const base = trimRuntimeExtension(file); const candidates = [ file, base, `${base}.ts`, `${base}.tsx`, `${base}.d.ts`, path_1.default.join(base, "index.ts"), path_1.default.join(base, "index.d.ts"), ]; for (const candidate of candidates) { try { const location = path_1.default.resolve(candidate); const stat = yield fs_1.default.promises.stat(location); if (stat.isFile()) return location; } catch (_a) { } } return null; }); const trimRuntimeExtension = (file) => { for (const ext of [".js", ".jsx", ".mjs", ".cjs"]) if (file.endsWith(ext)) return file.slice(0, -ext.length); return file; }; const getDeclarations = (source, name) => { const output = []; const searchable = maskTrivia(source); const regex = declarationRegex(name); for (const match of searchable.matchAll(regex)) { if (isTopLevel(searchable, match.index) === false) continue; const end = declarationEnd(searchable, match.index, match[1]); if (end !== -1) output.push(source.slice(match.index, end).trim()); } return output; }; const getExportedNames = (source) => { const searchable = maskTrivia(source); return Array.from(searchable.matchAll(declarationRegex("[A-Za-z_$][\\w$]*"))) .filter((match) => isTopLevel(searchable, match.index)) .map((match) => match[2]); }; const declarationRegex = (name) => new RegExp(`export\\s+(?:declare\\s+)?(interface|type|enum|namespace|class)\\s+(${name})\\b`, "g"); const declarationEnd = (source, start, kind) => { let depth = 0; let block = false; for (let i = start; i < source.length; ++i) { const ch = source[i]; if (ch === "{") { ++depth; block = true; } else if (ch === "}") { --depth; if (kind !== "type" && block && depth === 0) return i + 1; } else if (ch === ";" && block === false && depth === 0) return i + 1; else if (ch === ";" && kind === "type" && depth === 0) return i + 1; } return -1; }; const isTopLevel = (source, index) => { let depth = 0; for (let i = 0; i < index; ++i) { const ch = source[i]; if (ch === "{") ++depth; else if (ch === "}") --depth; } return depth === 0; }; const maskTrivia = (source) => { const out = source.split(""); const mask = (index) => { if (out[index] !== "\n" && out[index] !== "\r") out[index] = " "; }; let quote = null; let escaped = false; for (let i = 0; i < out.length; ++i) { const ch = source[i]; const next = source[i + 1]; if (quote !== null) { mask(i); if (escaped) escaped = false; else if (ch === "\\") escaped = true; else if (ch === quote) quote = null; } else if (ch === "/" && next === "/") { mask(i++); for (; i < out.length && source[i] !== "\n"; ++i) mask(i); --i; } else if (ch === "/" && next === "*") { mask(i++); mask(i); for (++i; i < out.length; ++i) { const current = source[i]; const following = source[i + 1]; mask(i); if (current === "*" && following === "/") { mask(++i); break; } } } else if (ch === '"' || ch === "'" || ch === "`") { quote = ch; mask(i); } } return out.join(""); }; const getImports = (source) => Array.from(source.matchAll(/import\s+([\s\S]*?)\s+from\s+["']([^"']+)["'];?/g)).map((match) => { const clause = match[1].trim(); const specifier = match[2]; return { text: match[0].trim(), specifier, default: defaultImportName(clause), namespace: namespaceImportName(clause), elements: namedImportElements(clause), }; }); const defaultImportName = (clause) => { const first = clause .split(",")[0] .trim() .replace(/^type\s+/, ""); return first.length && first.startsWith("{") === false ? first : null; }; const namespaceImportName = (clause) => { var _a; const match = clause.match(/\*\s+as\s+(\w+)/); return (_a = match === null || match === void 0 ? void 0 : match[1]) !== null && _a !== void 0 ? _a : null; }; const namedImportElements = (clause) => { const match = clause.match(/\{([\s\S]*?)\}/); if (match === null) return []; return match[1] .split(",") .map((part) => part.trim().replace(/^type\s+/, "")) .filter((part) => part.length) .map((part) => { var _a; const pieces = part.split(/\s+as\s+/); const imported = pieces[0].trim(); return { imported, local: ((_a = pieces[1]) !== null && _a !== void 0 ? _a : imported).trim(), }; }); }; const uses = (body, name) => new RegExp(`\\b${escapeRegExp(name)}\\b`).test(body); const escapeRegExp = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); SdkWebSocketCloneProgrammer.importKey = (file, name) => `${file}#${name}`; SdkWebSocketCloneProgrammer.isNodeModulesPath = (file) => path_1.default .resolve(file) .split(/[\\/]+/) .includes("node_modules"); })(SdkWebSocketCloneProgrammer || (exports.SdkWebSocketCloneProgrammer = SdkWebSocketCloneProgrammer = {})); //# sourceMappingURL=SdkWebSocketCloneProgrammer.js.map