UNPKG

pg-test-util

Version:

PostgreSQL administrative utilities such as creating and dropping tables, users etc.

180 lines (150 loc) 7.35 kB
/* eslint-disable no-use-before-define, import/no-extraneous-dependencies, import/no-unresolved */ const os = require("os"); const { promises: fs, readFileSync } = require("fs"); const { sep, join, normalize, basename, dirname, parse } = require("path"); const childProcess = require("child_process"); const readmeasy = require("readmeasy").default; const cwd = process.env.INIT_CWD || process.cwd(); const pkg = JSON.parse(readFileSync(join(cwd, "package.json"), { encoding: "utf8" })); // eslint-disable-line no-use-before-define const walk = hasDependency("typedoc") ? require("walkdir") : undefined; const concatMd = hasDependency("typedoc") ? require("concat-md").default : undefined; /** * Returns entry file for TypeDoc. Uses basename of main entry in 'package.json' file and prefixes it with 'src'. */ function getTypeDocEntry() { const { name } = parse(pkg.main); return normalize(`src/${name}.ts`); } function hasDependency(moduleName) { const hasNormalDependency = pkg.dependencies && pkg.dependencies[moduleName]; const hasDevDependency = pkg.devDependencies && pkg.devDependencies[moduleName]; return hasNormalDependency || hasDevDependency; } async function createTempDir() { const path = await fs.mkdtemp(`${os.tmpdir()}${sep}`); await fs.mkdir(path, { recursive: true }); return path; } async function spawn(cmd, args, options) { const ps = await childProcess.spawn(cmd, args, options); return new Promise( (resolve, reject) => ps.on("close", (code) => (code === 0 ? resolve() : reject(new Error(`ps process exited with code ${code}`)))) // eslint-disable-line no-promise-executor-return ); } /** Remove directory recursively, but don't throw if it does not exist. */ async function rmdir(path) { try { await fs.rm(path, { recursive: true }); } catch (error) { if (error.code !== "ENOENT") throw error; } } /** * Creates TypeDoc multiple or single markdown from TypeScript source files. * * @param out is output directory or file for generated markdown. * @param singleFile is whether to combine all output into single markdown file. */ async function md({ out, singleFile = false }) { // rm -rf api-docs-md && typedoc --plugin typedoc-plugin-example-tag,typedoc-plugin-markdown --excludeExternals --excludePrivate --excludeProtected --exclude 'src/bin/**/*' --theme markdown --readme none --out api-docs-md src/index.ts && find api-docs-md -name \"index.md\" -exec sh -c 'mv \"$1\" \"${1%index.md}\"index2.md' - {} \\; // const cwd = getCwd(); const bin = join(cwd, "node_modules/.bin/typedoc"); const outDir = singleFile ? await createTempDir() : out; if (!singleFile) await rmdir(outDir); const options = [ "--excludeExternals", "--excludePrivate", "--exclude", "'src/bin/**/*'", "--readme", "none", "--out", outDir, getTypeDocEntry(), ]; try { await spawn(bin, options, { stdio: "inherit" }); // Rename all "index.md" files as "index2.md", because VuePress treats "index.md" special. Renaming does not matter, because title comes from front-matter. const createdFiles = await walk.async(outDir); await Promise.all(createdFiles.map((file) => addFrontMatterToMd(file))); await Promise.all( createdFiles.filter((file) => basename(file) === "index.md").map((file) => fs.rename(file, join(dirname(file), "index2.md"))) ); } catch (error) { if (singleFile) await rmdir(outDir); throw error; } if (singleFile) { // Remove titles at the beginning. README already has a title. const apiDoc = (await concatMd(outDir, { dirNameAsTitle: true })).replace(new RegExp(`${pkg.name}.+?${pkg.name}`, "sm"), ""); fs.writeFile(out, apiDoc); await rmdir(outDir); } } /** * Add "front-matter" title to given markdown file using first level 1 title. * * @param {string} file is the file to add front matter title. */ async function addFrontMatterToMd(file) { try { const content = await fs.readFile(file, { encoding: "utf8" }); // If file has front-matter (---) do not touch. if (content.match(/^s+---/)) return; // "# Class: User" results in "User". "# User" results in "User". const firstTitleMatch = content.match(/^[^#]+?#\\s+(.+?)\\r?\\n/, "s"); const firstTitle = firstTitleMatch !== null && firstTitleMatch[1] ? firstTitleMatch[1].replace(/.+?:\s+/, "").replace("@", "\\@") : undefined; if (firstTitle) await fs.writeFile(file, `---\ntitle: ${firstTitle}\n---\n\n${content}`); } catch (error) { if (error.code !== "EISDIR") throw error; } } /** * Creates TypeDoc HTML files from TypeScript source files. * * @param out is output directory for generated HTML files. */ async function html({ out }) { // rm -rf api-docs-html && typedoc --plugin typedoc-plugin-example-tag --out api-docs-html src/index.ts // const cwd = getCwd(); const bin = join(cwd, "node_modules/.bin/typedoc"); await rmdir(out); await spawn(bin, ["--plugin", "typedoc-plugin-example-tag", "--out", out, getTypeDocEntry()], { stdio: "inherit" }); } /** * Create "README.md" from "README.njk". Also add TypeScript API docs using TypeDoc if `{% include "api.md" %}` is present in "README.njk" */ async function readme() { // if grep -q '{% include \"api.md\" %}' 'README.njk'; then npm run typedoc:single-md; mkdir -p temp && mv api.md temp/; fi && readmeasy --partial-dirs temp,module-files/template-partials && rm -rf temp // const cwd = getCwd(); const partialDirs = [join(cwd, "module-files/template-partials")]; const template = await fs.readFile(join(cwd, "README.njk"), { encoding: "utf8" }); // If remplate contains API partial or template does not exist (default template contains API partial), create API markdown. if (template.includes('{% include "api.md" %}')) { const tmpDir = await createTempDir(); partialDirs.push(tmpDir); await md({ out: join(tmpDir, "api.md"), singleFile: true }); } await readmeasy({ partialDirs, dir: cwd }); // If oclif installed execute `oclif-dev readme`; // if (targetModule.hasAnyDependency(["@oclif/command"])) await targetModule.command("oclif-dev readme", { exitOnProcessFailure }); // intermodular.log("info", "README created: README.md"); } async function vuepressApi() { // npm-run-all -p typedoc:md typedoc:html && rm -rf docs/nav.02.api docs/.vuepress/public/api-site && mv api-docs-md docs/nav.02.api && mv api-docs-html docs/.vuepress/public/api-site && cp assets/typedoc/01.typedoc-iframe.md docs/nav.02.api/ && NODE_ENV=production vuepress build docs const mdPath = join(cwd, "docs/nav.02.api"); const htmlPath = join(cwd, "docs/.vuepress/public/api-site"); const iframePath = join(cwd, "module-files/vuepress/01.typedoc-iframe.md"); await Promise.all([md({ out: mdPath }), html({ out: htmlPath })]); // await md({ out: mdPath }); // await html({ out: htmlPath }); await fs.mkdir(mdPath, { recursive: true }); await fs.copyFile(iframePath, join(mdPath, basename(iframePath))); // Don't put this in `Promise.all` with `md` and `html`. It needs first directory created. Otherwise file is copied same name with directory, since there is no directory yet. } const commands = { readme: () => readme(), "vuepress-api": () => vuepressApi(), }; const [command, ...args] = process.argv.slice(2); commands[command](args);