1
0
Fork 0
scaffold/src/normalize.ts

256 lines
6.1 KiB
TypeScript
Executable File

import { Sys } from "../deps.ts";
export class ProjectNormalizer {
constructor(readonly sys = Sys) {
}
async isDirectory(path: string): Promise<boolean> {
try {
return (await this.sys.stat(path)).isDirectory;
} catch {
return false;
}
}
async isFile(path: string): Promise<boolean> {
try {
return (await this.sys.stat(path)).isFile;
} catch {
return false;
}
}
async readContent(path: string): Promise<string> {
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 writeLines(path: string, lines: string[]) {
await this.sys.writeTextFile(
path,
lines.concat("").join("\n"),
);
}
async run(...cmd: string[]): Promise<string> {
const p = this.sys.run({ cmd, stdout: "piped", stderr: "piped" });
const [status, stdout, stderr] = await Promise.all([
p.status(),
p.output(),
p.stderrOutput(),
]);
if (status.success) {
return new TextDecoder().decode(stdout);
} else {
console.error(new TextDecoder().decode(stderr));
throw new Error(`Command failed: ${cmd.join(" ")}`);
}
}
async formatPath(path: string) {
await this.run("./run", "fmt", "-q", path);
}
async updateRunScript() {
await this.writeLines(
"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 defs = await this.run("./run", "types");
await this.sys.writeTextFile("deno.d.ts", defs);
}
async updateEditorConfig() {
await this.writeLines(
".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"))
.trim()
.split(" ")
.filter((part) => !!part),
),
},
],
});
}
}
async updateGitIgnore() {
await this.writeLines(
".gitignore",
[
`deno.d.ts`,
`.vscode`,
`.local`,
`.output`,
`web/*.js`,
],
);
}
async updateGitHooks() {
await this.writeLines(
".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.writeLines(
"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();
}