UNPKG

@tsed/cli

Version:
177 lines (176 loc) 6.45 kB
import { dirname, join } from "node:path"; import { CliDockerComposeYaml, CliFs, ProjectPackageJson } from "@tsed/cli-core"; import { isString } from "@tsed/core"; import { constant, inject } from "@tsed/di"; import { ObjectLiteralExpression, Project, SyntaxKind } from "ts-morph"; import { OutputFilePathPipe } from "../pipes/OutputFilePathPipe.js"; export class ProjectClient extends Project { constructor({ rootDir, ...options }) { super({ fileSystem: inject(CliFs), ...options }); this.pkg = inject(ProjectPackageJson); this.fs = inject(CliFs); this.dockerCompose = inject(CliDockerComposeYaml); this.rootDir = rootDir; } get srcDir() { return constant("project.srcDir", "/src"); } get serverSourceFile() { return this.getSource(join(this.srcDir, this.serverName)); } get configSourceFile() { return this.getSource(join(this.srcDir, `config/config.ts`)); } get indexSourceFile() { return this.getSource(join(this.srcDir, "index.ts")); } get binSourceFile() { return this.getSource(join(this.srcDir, "bin/index.ts")); } get serverName() { return inject(OutputFilePathPipe).getServerName(); } getSource(path) { const resolved = join(this.rootDir, path.replace("{{srcDir}}", this.srcDir)); return this.getSourceFile(resolved); } async createSource(path, sourceFileText, options) { path = join(this.rootDir, path); if (path.endsWith(".ts") || path.endsWith(".mts")) { await this.fs.mkdir(dirname(path)); if (typeof sourceFileText === "string") { await this.fs.writeFile(path, sourceFileText, { encoding: "utf-8" }); return this.getSourceFile(path) || this.addSourceFileAtPath(path); } const source = this.createSourceFile(path, sourceFileText, options); await this.fs.writeFile(path, source.getFullText(), { encoding: "utf-8" }); return source; } await this.fs.ensureDir(dirname(path)); await this.fs.writeFile(path, sourceFileText, { encoding: "utf-8" }); } findClassDecorator(sourceFile, name) { return sourceFile .getClasses() .flatMap((cls) => cls.getDecorators()) .find((decorator) => decorator.getName() === name); } addMountPath(path, specifier) { if (!this.serverSourceFile) { return; } const options = this.findConfigurationDecorationOptions(); if (!options) { return; } let mount = this.getPropertyAssignment(options, { name: "mount", kind: SyntaxKind.ObjectLiteralExpression, initializer: "{}" }); const escape = '"' + ((path || "").startsWith("/") ? path : "/" + path) + '"'; const property = this.getPropertyAssignment(mount, { name: escape, kind: SyntaxKind.ArrayLiteralExpression, initializer: "[]" }); if (isString(specifier)) { property.addElement(specifier); } options.formatText({ indentSize: 2 }); } addNamespaceImport(sourceFile, moduleSpecifier, name) { return sourceFile .addImportDeclaration({ moduleSpecifier, namespaceImport: name }) .getNamespaceImport(); } findConfiguration(kind) { switch (kind) { case "server": return this.findConfigurationDecorationOptions(); case "config": return this.findConfigConfiguration(); case "bin": return this.findBinConfiguration(); default: throw new Error(`Unknown configuration kind: ${kind}`); } } getPropertyAssignment(input, { name, initializer, kind }) { const assigment = input.getProperty(name)?.getChildAtIndex(2).asKind(kind); if (!initializer) { return assigment; } return (assigment || input .addPropertyAssignment({ name, initializer }) .getInitializerIfKindOrThrow(kind)); } addConfigSource(name, { content = name, moduleSpecifier }) { const sourceFile = this.configSourceFile; const options = this.findConfiguration("config"); if (!options) { return; } const extendsConfig = this.getPropertyAssignment(options, { name: "extends", kind: SyntaxKind.ArrayLiteralExpression, initializer: "[]" }); const has = extendsConfig.getElements().some((expression) => { return expression.getText().includes(name); }); if (!has) { sourceFile.addImportDeclaration({ moduleSpecifier, namedImports: [{ name: name }] }); extendsConfig.addElement("\n" + content); } if (content.match("withOptions")) { if (!sourceFile.getImportDeclarations().some((imp) => { return imp.getText().match("withOptions"); })) { sourceFile.addImportDeclaration({ moduleSpecifier: "@tsed/config", namedImports: [{ name: "withOptions" }] }); } } } findConfigurationDecorationOptions() { if (!this.serverSourceFile) { return; } return this.findClassDecorator(this.serverSourceFile, "Configuration")?.getArguments().at(0); } findConfigConfiguration() { const sourceFile = this.configSourceFile; if (!sourceFile) { return; } return sourceFile.getVariableDeclaration("config")?.getInitializerIfKindOrThrow(SyntaxKind.ObjectLiteralExpression); } findBinConfiguration() { if (!this.binSourceFile) { return; } for (const child of this.binSourceFile.getStatements()) { if (child.getKind() === SyntaxKind.ExpressionStatement && child.getText().includes("CliCore.bootstrap")) { return child.getFirstDescendantByKind(SyntaxKind.ObjectLiteralExpression); } } return undefined; } }