import { Sys } from "../deps.ts"; export class ProjectNormalizer { constructor(readonly sys = Sys) { } async isDirectory(path: string): Promise { try { return (await this.sys.stat(path)).isDirectory; } catch { return false; } } async isFile(path: string): Promise { try { return (await this.sys.stat(path)).isFile; } catch { return false; } } async readContent(path: string): Promise { try { return await this.sys.readTextFile(path); } catch { return ""; } } async ensureDirectory(path: string) { if (!await this.isDirectory(path)) { await this.sys.mkdir(path, { recursive: true }); } } async writeJsonFile(path: string, content: any) { await this.sys.writeTextFile( path, JSON.stringify(content), ); await this.formatPath(path); } async formatPath(path: string) { await this.sys.run({ cmd: ["./run", "fmt", "-q", path], }).status(); } async updateRunScript() { await this.sys.writeTextFile( "run", `#!/bin/sh # Simplified run tool for deno commands if test $# -eq 0 then echo "Usage: $0 [file or command]" exit 1 elif echo $1 | grep -q '.*\.ts' then denocmd=run denoargs=$1 shift else denocmd=$1 shift fi denoargs="$(cat config/$denocmd.flags 2> /dev/null) $denoargs $@" exec deno $denocmd $denoargs `, ); await this.sys.chmod("run", 0o755); } async updateDenoDefs() { const process = this.sys.run({ cmd: ["./run", "types"], stdout: "piped", }); const defs = new TextDecoder("utf-8").decode(await process.output()); await this.sys.writeTextFile("deno.d.ts", defs); } async updateEditorConfig() { await this.sys.writeTextFile( ".editorconfig", `root = true [*.{ts,json}] charset = utf-8 end_of_line = lf insert_final_newline = true indent_style = space indent_size = 2 trim_trailing_whitespace = true `, ); } async updateTsConfig() { await this.writeJsonFile("tsconfig.json", { compilerOptions: { module: "esnext", target: "ESNext", strict: true, noImplicitReturns: true, noFallthroughCasesInSwitch: true, preserveConstEnums: true, }, }); } async updateVscodeConf() { await this.ensureDirectory(".vscode"); const path = ".vscode/settings.json"; const json_config = await this.readContent(path); const config = JSON.parse(json_config || "{}"); if (!config["deno.enable"]) { config["deno.enable"] = true; await this.writeJsonFile(path, config); } await this.writeJsonFile(".vscode/tasks.json", { version: "2.0.0", tasks: [ { label: "test", type: "shell", group: { kind: "test", isDefault: true, }, command: "./run test", }, ], }); if (await this.isFile("cli.ts")) { await this.writeJsonFile(".vscode/launch.json", { version: "0.2.0", configurations: [ { name: "Deno", type: "node", request: "launch", cwd: "\${workspaceFolder}", program: "cli.ts", console: "externalTerminal", attachSimplePort: 9229, runtimeExecutable: "deno", runtimeArgs: [ "run", "--inspect", ].concat( (await this.readContent("config/run.flags")) .split(" ") .filter((part) => !!part), ), }, ], }); } } async updateGitIgnore() { await this.sys.writeTextFile( ".gitignore", `deno.d.ts .vscode .local `, ); } async updateGitHooks() { await this.sys.writeTextFile( ".git/hooks/pre-commit", `#!/bin/sh set -e ./run fmt --check ./run test `, ); await this.sys.chmod(".git/hooks/pre-commit", 0o755); } async updateReadme() { const project = this.sys.cwd().split("/").pop(); let sections = ""; if (await this.isDirectory("doc")) { const index = await this.readContent("doc/index"); for (let section of index.split("\n")) { if (section?.trim()) { sections += "\n" + (await this.readContent(`doc/${section.trim()}.md`)); } } } await this.sys.writeTextFile( "README.md", `# typescript/${project} [![Build Status](https://thunderk.visualstudio.com/typescript/_apis/build/status/${project}?branchName=master)](https://dev.azure.com/thunderk/typescript/_build?pipelineNameFilter=${project}) ${sections}`, ); await this.formatPath("README.md"); } async normalize() { if (!await this.isDirectory(".git")) { throw new Error("Not in a git repository"); } await this.updateRunScript(); await this.updateDenoDefs(); await this.updateVscodeConf(); await this.updateTsConfig(); await this.updateEditorConfig(); await this.updateGitIgnore(); await this.updateGitHooks(); await this.updateReadme(); } } export async function normalize(sys = Sys) { const normalizer = new ProjectNormalizer(sys); await normalizer.normalize(); }