From 31d5e0ff856e751d29b7f28f568d1aea5af9a5b3 Mon Sep 17 00:00:00 2001 From: Matthew McEachen Date: Wed, 27 Mar 2024 19:54:40 -0700 Subject: [PATCH] v3.0.0 - add typings - merge bin/mkver with src/mkver - add eslint - add prettier+import cleanup - drop old node builds - switch mkver to async calls --- .eslintrc.js | 7 + .github/workflows/node.js.yml | 8 +- .ncurc.json | 6 + .npmignore | 6 - .prettierrc | 6 +- CHANGELOG.md | 16 +++ LICENSE.txt | 2 +- _config.yml | 1 - bin/.prettierrc | 2 - bin/mkver | 15 --- package.json | 37 +++--- src/mkver.spec.ts | 233 ++++++++++++++++++---------------- src/mkver.ts | 204 +++++++++++++++++------------ tsconfig.json | 6 +- 14 files changed, 308 insertions(+), 241 deletions(-) create mode 100644 .eslintrc.js create mode 100644 .ncurc.json delete mode 100644 _config.yml delete mode 100644 bin/.prettierrc delete mode 100755 bin/mkver diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..cc9c001 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,7 @@ +/* eslint-env node */ +module.exports = { + extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], + parser: '@typescript-eslint/parser', + plugins: ['@typescript-eslint'], + root: true, +}; \ No newline at end of file diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 0dda2a4..e6186b3 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -12,14 +12,14 @@ jobs: strategy: matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - node-version: [14.x, 16.x, 18.x, 19.x, 20.x] + os: [ubuntu-latest, macos-14, windows-latest] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ + node-version: [18.x, 20.x, 21.x] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - run: yarn ci diff --git a/.ncurc.json b/.ncurc.json new file mode 100644 index 0000000..e5848b7 --- /dev/null +++ b/.ncurc.json @@ -0,0 +1,6 @@ +{ + "reject": [ + "@types/chai", + "chai" + ] +} diff --git a/.npmignore b/.npmignore index 6f0fc6d..4dd9748 100644 --- a/.npmignore +++ b/.npmignore @@ -1,16 +1,10 @@ # no hidden files .* -# no system maps (they're only used during development) -*.map - # no testing artifacts *spec* *test* appveyor.yml -# no src -src/ - # no typescript configs ts*.json \ No newline at end of file diff --git a/.prettierrc b/.prettierrc index dbafcf2..e18acc1 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,5 +1,5 @@ { - "semi": false, - "printWidth": 80, - "tabWidth": 2 + "plugins": [ + "prettier-plugin-organize-imports" + ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d0e904..cb483e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,22 @@ This module follows semver. - 🐞 Backwards-compatible bug fixes - 📦 Minor packaging changes +## v3.0.0 + +- 💔 Drop support for obsolete versions of Node.js + +- ✨ Support non-CLI programmatic `mkver` calls. Include typings. + +- 📦 Merge code from bin/mkver.js into mkver.ts + +- 📦 Replace sync calls with async calls + +- 📦 Added eslint. + +- 📦 Added prettier and import reordering. + +- 📦 Upgrade all dev dependencies. + ## v2.1.0 - 📦 Upgrade all dev dependencies. diff --git a/LICENSE.txt b/LICENSE.txt index 8f11340..1a898e5 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright 2018-2023 Matthew McEachen +Copyright 2018-2024 Matthew McEachen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/_config.yml b/_config.yml deleted file mode 100644 index b849713..0000000 --- a/_config.yml +++ /dev/null @@ -1 +0,0 @@ -theme: jekyll-theme-leap-day \ No newline at end of file diff --git a/bin/.prettierrc b/bin/.prettierrc deleted file mode 100644 index 2c63c08..0000000 --- a/bin/.prettierrc +++ /dev/null @@ -1,2 +0,0 @@ -{ -} diff --git a/bin/mkver b/bin/mkver deleted file mode 100755 index 8740751..0000000 --- a/bin/mkver +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env node - -const { argv } = require("process"); - -if (["--help", "-h"].includes(String(argv[2]))) { - // Show them usage instructions: - console.log(`Usage: mkver [FILE] -Provides Node.js access to your app's version and release metadata. - -With no FILE, default output is "./Version.ts". - -See for more information.`); -} else { - require("..").mkver(argv[2]); -} diff --git a/package.json b/package.json index 2645a94..28fe054 100644 --- a/package.json +++ b/package.json @@ -2,15 +2,19 @@ "name": "mkver", "version": "2.1.0", "description": "Node.js access to your app's version and release metadata", - "main": "dist/mkver.js", + "main": "./dist/mkver.js", + "types": "./dist/mkver.d.ts", "scripts": { - "prettier": "prettier --write src/*.ts bin/mkver", - "pretest": "rimraf dist && tsc", + "prettier": "prettier --write src/*.ts", + "lint": "eslint --fix src/*.ts", + "premake": "rimraf dist", + "make": "tsc && chmod +x dist/mkver.js", + "pretest": "yarn make && yarn lint", "test": "mocha dist/**/*.spec.js", "ci": "yarn install --frozen-lockfile" }, "bin": { - "mkver": "bin/mkver" + "mkver": "./dist/mkver.js" }, "repository": { "type": "git", @@ -26,18 +30,21 @@ }, "homepage": "https://github.com/photostructure/mkver#readme", "devDependencies": { - "@types/chai": "^4.3.5", - "@types/chai-as-promised": "^7.1.5", - "@types/mocha": "^10.0.1", - "@types/node": "^20.1.7", - "@types/semver": "^7.5.0", - "chai": "^4.3.7", + "@types/chai": "^4.3.14", + "@types/chai-as-promised": "^7.1.8", + "@types/mocha": "^10.0.6", + "@types/node": "^20.11.30", + "@types/semver": "^7.5.8", + "chai": "^4.3.10", "chai-as-promised": "^7.1.1", - "mocha": "^10.2.0", - "prettier": "^2.8.8", - "rimraf": "^5.0.1", - "semver": "^7.5.1", + "eslint": "^8.57.0", + "mocha": "^10.4.0", + "prettier": "^3.2.5", + "prettier-plugin-organize-imports": "^3.2.4", + "rimraf": "^5.0.5", + "semver": "^7.6.0", "source-map-support": "^0.5.21", - "typescript": "^5.0.4" + "typescript": "^5.4.3", + "typescript-eslint": "^7.4.0" } } diff --git a/src/mkver.spec.ts b/src/mkver.spec.ts index 12a97c3..ac0edcb 100644 --- a/src/mkver.spec.ts +++ b/src/mkver.spec.ts @@ -1,44 +1,48 @@ -import { expect } from "chai" -import { ChildProcess, execFile, execSync, fork, spawn } from "child_process" -import { mkdirSync, writeFileSync } from "fs" -import { platform, tmpdir } from "os" -import { join, parse } from "path" -import * as semver from "semver" -import { fmtYMDHMS } from "./mkver" +import { expect } from "chai"; +import { + ChildProcess, + execFile, + execSync, + fork, + spawn, +} from "node:child_process"; +import { mkdirSync, writeFileSync } from "node:fs"; +import { platform, tmpdir } from "node:os"; +import { join, parse } from "node:path"; +import * as semver from "semver"; +import { fmtYMDHMS } from "./mkver"; class ExpectedVersion { - readonly major: number - readonly minor: number - readonly patch: number - readonly prerelease: (string | number)[] - readonly version: string + readonly major: number; + readonly minor: number; + readonly patch: number; + readonly prerelease: (string | number)[]; + readonly version: string; constructor({ major, minor, patch, prerelease, - version, }: { - major?: number - minor?: number - patch?: number - prerelease?: (string | number)[] - version?: string + major?: number; + minor?: number; + patch?: number; + prerelease?: (string | number)[]; } = {}) { - this.major = major ?? getRandomInt(15) - this.minor = minor ?? getRandomInt(15) - this.patch = patch ?? getRandomInt(15) - this.prerelease = prerelease ?? [] + this.major = major ?? getRandomInt(15); + this.minor = minor ?? getRandomInt(15); + this.patch = patch ?? getRandomInt(15); + this.prerelease = prerelease ?? []; this.version = [this.major, this.minor, this.patch].join(".") + - (this.prerelease.length == 0 ? "" : "-" + this.prerelease.join(".")) + (this.prerelease.length == 0 ? "" : "-" + this.prerelease.join(".")); } } describe("mkver", function () { - this.retries(2) - this.slow(1) + this.retries(2); + this.slow(1); for (const exp of [ new ExpectedVersion({ prerelease: ["alpha"] }), @@ -48,41 +52,41 @@ describe("mkver", function () { ]) { describe(exp.version, () => { it("./ver.js", async () => { - const { gitSha, dir } = mkTestRepo(exp) - return assertResult(gitSha, dir + "/ver.js", exp) - }) + const { gitSha, dir } = mkTestRepo(exp); + return assertResult(gitSha, dir + "/ver.js", exp); + }); it("./version.mjs", async function () { if (!semver.satisfies(process.version, ">=13")) { - return this.skip() + return this.skip(); } - const { gitSha, dir } = mkTestRepo(exp) - return assertResult(gitSha, dir + "/version.mjs", exp) - }) + const { gitSha, dir } = mkTestRepo(exp); + return assertResult(gitSha, dir + "/version.mjs", exp); + }); it("./ver.ts", async function () { if (platform().startsWith("win")) { - return this.skip() + return this.skip(); } - const { gitSha, dir } = mkTestRepo(exp) - return assertResult(gitSha, dir + "/ver.ts", exp) - }) + const { gitSha, dir } = mkTestRepo(exp); + return assertResult(gitSha, dir + "/ver.ts", exp); + }); it("./testdir/version.js", async () => { - const { gitSha, dir } = mkTestRepo(exp) - return assertResult(gitSha, dir + "/testdir/version.js", exp) - }) + const { gitSha, dir } = mkTestRepo(exp); + return assertResult(gitSha, dir + "/testdir/version.js", exp); + }); it("fails for ./ver.go", async () => { - const { gitSha, dir } = mkTestRepo(exp) + const { gitSha, dir } = mkTestRepo(exp); try { - await assertResult(gitSha, dir + "/ver.go", exp) - expect.fail("unsupported format should have thrown") + await assertResult(gitSha, dir + "/ver.go", exp); + expect.fail("unsupported format should have thrown"); } catch (err) { - expect(err).to.match(/Unsupported file extension/i) + expect(err).to.match(/Unsupported file extension/i); } - }) - }) + }); + }); } describe("fmtYMDHMS", () => { @@ -92,57 +96,60 @@ describe("mkver", function () { "1999-10-11T12:13:14", ]) { it(`round-trips ${iso}`, () => { - const expected = iso.replace(/\D/g, "").substring(0, 14) - const d = new Date(iso) - expect(fmtYMDHMS(d)).to.eql(expected) - }) + const expected = iso.replace(/\D/g, "").substring(0, 14); + const d = new Date(iso); + expect(fmtYMDHMS(d)).to.eql(expected); + }); } - }) -}) + }); +}); function mkTestRepo(exp: ExpectedVersion) { - const dir = join(tmpdir(), randomChars()) - mkdirSync(dir) - writeFileSync(dir + "/package.json", JSON.stringify({ version: exp.version })) - execSync("git init", { cwd: dir }) - execSync("git add package.json", { cwd: dir }) - execSync("git config user.name anonymous", { cwd: dir }) - execSync("git config user.email anon@example.com", { cwd: dir }) - execSync("git commit --no-gpg-sign -m test-commit", { cwd: dir }) + const dir = join(tmpdir(), randomChars()); + mkdirSync(dir); + writeFileSync( + dir + "/package.json", + JSON.stringify({ version: exp.version }), + ); + execSync("git init", { cwd: dir }); + execSync("git add package.json", { cwd: dir }); + execSync("git config user.name anonymous", { cwd: dir }); + execSync("git config user.email anon@example.com", { cwd: dir }); + execSync("git commit --no-gpg-sign -m test-commit", { cwd: dir }); const gitSha = execSync("git rev-parse -q HEAD", { cwd: dir }) .toString() - .trim() - return { gitSha, dir } + .trim(); + return { gitSha, dir }; } function _exec(cp: ChildProcess): Promise { - const buf: (string | Buffer)[] = [] + const buf: (string | Buffer)[] = []; return new Promise((res, rej) => { cp.stderr?.on("data", (ea) => { // console.error("cp.stderr.on(data)", ea) - rej(String(ea)) - }) - cp.stderr?.on("error", rej) - cp.stdout?.on("error", rej) - cp.stdout?.on("data", (ea) => buf.push(ea)) + rej(String(ea)); + }); + cp.stderr?.on("error", rej); + cp.stdout?.on("error", rej); + cp.stdout?.on("data", (ea) => buf.push(ea)); cp.on("error", (ea) => { // console.error("cp.on(error)", ea) - rej(ea) - }) + rej(ea); + }); cp.on("close", (code) => { // console.error("cp.on(close)", { code }) - code == 0 ? res(buf.join("")) : rej("bad exit code " + code) - }) + code == 0 ? res(buf.join("")) : rej("bad exit code " + code); + }); cp.on("exit", (code) => { // console.error("cp.on(exit)", { code }) - code == 0 ? res(buf.join("")) : rej("bad exit code " + code) - }) - }) + code == 0 ? res(buf.join("")) : rej("bad exit code " + code); + }); + }); } async function maybeCompile(pathToVersionFile: string): Promise { - const parsed = parse(pathToVersionFile) - const dest = join(parsed.dir, "test" + parsed.ext) + const parsed = parse(pathToVersionFile); + const dest = join(parsed.dir, "test" + parsed.ext); if (parsed.ext === ".js") { writeFileSync( @@ -151,9 +158,9 @@ async function maybeCompile(pathToVersionFile: string): Promise { `const v = require("./${parsed.name}");`, `console.log(JSON.stringify(v));`, "", - ].join("\n") - ) - return dest + ].join("\n"), + ); + return dest; } if (parsed.ext === ".mjs") { @@ -164,9 +171,9 @@ async function maybeCompile(pathToVersionFile: string): Promise { `import * as v from "./${parsed.base}";`, `console.log(JSON.stringify(v));`, "", - ].join("\n") - ) - return dest + ].join("\n"), + ); + return dest; } else { writeFileSync( dest, @@ -174,63 +181,65 @@ async function maybeCompile(pathToVersionFile: string): Promise { `import * as v from "./${parsed.name}";`, `console.log(JSON.stringify(v));`, "", - ].join("\n") - ) - const args = ["--module", "commonjs", "--rootDir", parsed.dir, dest] - await _exec(spawn("node_modules/.bin/tsc", args)) - return dest.replace(/\.ts$/, ".js") + ].join("\n"), + ); + const args = ["--module", "commonjs", "--rootDir", parsed.dir, dest]; + await _exec(spawn("node_modules/.bin/tsc", args)); + return dest.replace(/\.ts$/, ".js"); } } async function assertResult( gitSha: string, pathToVersionFile: string, - exp: ExpectedVersion + exp: ExpectedVersion, ) { await _exec( - fork("bin/mkver", [pathToVersionFile], { detached: false, stdio: "pipe" }) - ) - const dest = await maybeCompile(pathToVersionFile) - const output = await _exec(execFile("node", [dest], { cwd: parse(dest).dir })) + fork("dist/mkver", [pathToVersionFile], { detached: false, stdio: "pipe" }), + ); + const dest = await maybeCompile(pathToVersionFile); + const output = await _exec( + execFile("node", [dest], { cwd: parse(dest).dir }), + ); // console.log("assertResult", { dest, output }) - const result = JSON.parse(output) + const result = JSON.parse(output); - expect(result.gitSha).to.eql(gitSha) + expect(result.gitSha).to.eql(gitSha); const d = - result.gitDate instanceof Date ? result.gitDate : new Date(result.gitDate) + result.gitDate instanceof Date ? result.gitDate : new Date(result.gitDate); expect(d).to.be.within( - new Date(Date.now() - 15000) as any, // CI can take more than 10s to complete - new Date() as any - ) + new Date(Date.now() - 15000), // CI can take more than 10s to complete + new Date(), + ); - expect(result.version).to.eql(exp.version) - expect(result.versionMajor).to.eql(exp.major) - expect(result.versionMinor).to.eql(exp.minor) - expect(result.versionPatch).to.eql(exp.patch) - expect(result.versionPrerelease).to.eql(exp.prerelease) + expect(result.version).to.eql(exp.version); + expect(result.versionMajor).to.eql(exp.major); + expect(result.versionMinor).to.eql(exp.minor); + expect(result.versionPatch).to.eql(exp.patch); + expect(result.versionPrerelease).to.eql(exp.prerelease); // If we run the test right at a minute boundary, the timestamp might be more // than 2 digits wrong (hence the retries) - const ymdhm = trimEnd(fmtYMDHMS(new Date()), 2) - const expectedRelease = `${exp.version}+${ymdhm}` - const releaseWithoutSeconds = trimEnd(result.release, 2) - expect(releaseWithoutSeconds).to.eql(expectedRelease) + const ymdhm = trimEnd(fmtYMDHMS(new Date()), 2); + const expectedRelease = `${exp.version}+${ymdhm}`; + const releaseWithoutSeconds = trimEnd(result.release, 2); + expect(releaseWithoutSeconds).to.eql(expectedRelease); } function getRandomInt(max: number) { - return Math.floor(Math.random() * Math.floor(max)) + return Math.floor(Math.random() * Math.floor(max)); } function trimEnd(s: string, chars: number): string { - return s.substring(0, s.length - chars) + return s.substring(0, s.length - chars); } function randomChars(length = 10) { - let s = "" + let s = ""; while (s.length < length) { - s += Math.random().toString(36).slice(2) + s += Math.random().toString(36).slice(2); } - return s.slice(0, length) + return s.slice(0, length); } diff --git a/src/mkver.ts b/src/mkver.ts index 24bb7a9..ed88aa5 100644 --- a/src/mkver.ts +++ b/src/mkver.ts @@ -1,69 +1,80 @@ -import { execSync } from "child_process" -import { mkdirSync, readFileSync, writeFileSync } from "fs" -import { join, normalize, parse, resolve } from "path" -import { argv, cwd, exit } from "process" -import * as semver from "semver" +import { execFile } from "node:child_process"; +import { mkdir, readFile, writeFile } from "node:fs/promises"; +import type { ParsedPath } from "node:path"; +import { join, normalize, parse, resolve } from "node:path"; +import { argv, exit } from "node:process"; +import { promisify } from "node:util"; +import * as semver from "semver"; + +const execFileP = promisify(execFile); function notBlank(s: string | undefined): boolean { - return s != null && String(s).trim().length > 0 + return s != null && String(s).trim().length > 0; } -function findPackageVersion( - dir: string -): undefined | { version: string; dir: string } { - const path = resolve(join(dir, "package.json")) +async function findPackageVersion( + dir: string, +): Promise { + const path = resolve(join(dir, "package.json")); try { - const json = JSON.parse(readFileSync(path).toString()) + const json = JSON.parse((await readFile(path)).toString()); if (json != null) { if (notBlank(json.version)) { - return { version: json.version, dir } + return { version: json.version, dir }; } else { - throw new Error("No `version` field was found in " + path) + throw new Error("No `version` field was found in " + path); } } } catch (err) { - const parent = resolve(join(dir, "..")) + const parent = resolve(join(dir, "..")); if (resolve(dir) !== parent) { - return findPackageVersion(parent) + return findPackageVersion(parent); } else { - throw err + throw err; } } } -function headSha(cwd: string): string { - const gitSha = execSync("git rev-parse -q HEAD", { cwd }).toString().trim() +async function headSha(cwd: string): Promise { + const gitSha = ( + await execFileP("git", ["rev-parse", "-q", "HEAD"], { cwd }) + ).stdout + .toString() + .trim(); + console.log("headSha", { gitSha }); if (gitSha.length < 40) { - throw new Error("Unexpected git SHA: " + gitSha) + throw new Error("Unexpected git SHA: " + gitSha); } else { - return gitSha + return gitSha; } } -function headUnixtime(cwd: string): Date { - const unixtimeStr = execSync("git log -1 --pretty=format:%ct", { - cwd, - }).toString() - const unixtime = parseInt(unixtimeStr) - const date = new Date(unixtime * 1000) +async function headUnixtime(cwd: string): Promise { + const unixtimeStr = ( + await execFileP("git", ["log", "-1", "--pretty=format:%ct"], { + cwd, + }) + ).stdout.toString(); + const unixtime = parseInt(unixtimeStr); + const date = new Date(unixtime * 1000); if (date > new Date() || date < new Date(2000, 0, 1)) { - throw new Error("Unexpected unixtime for commit: " + unixtime) + throw new Error("Unexpected unixtime for commit: " + unixtime); } - return date + return date; } export interface VersionInfo { - output: string - version: string - release: string - gitSha: string - gitDate: Date + path: ParsedPath; + version: string; + release: string; + gitSha: string; + gitDate: Date; } // NOT FOR GENERAL USE. Only works for positive values. function pad2(i: number) { - const s = String(i) - return s.length >= 2 ? s : ("0" + s).slice(-2) + const s = String(i); + return s.length >= 2 ? s : ("0" + s).slice(-2); } /** @@ -77,28 +88,31 @@ export function fmtYMDHMS(d: Date): string { pad2(d.getHours()) + pad2(d.getMinutes()) + pad2(d.getSeconds()) - ) + ); } function renderVersionInfo(o: VersionInfo): string { - const msg = [] - const cjs = o.output.endsWith(".js") - const mjs = o.output.endsWith(".mjs") - const ts = o.output.endsWith(".ts") + const msg = []; + const ext = o.path.ext.toLowerCase(); + const cjs = ext === ".js"; + const mjs = ext === ".mjs"; + const ts = ext === ".ts"; if (!cjs && !mjs && !ts) { - throw new Error("Unsupported file extension") + throw new Error( + `Unsupported file extension (expected output, ${JSON.stringify(o.path)}, to end in .ts, .js, or .mjs)`, + ); } if (cjs) { msg.push( `"use strict";`, - `Object.defineProperty(exports, "__esModule", { value: true });` - ) + `Object.defineProperty(exports, "__esModule", { value: true });`, + ); } - const parsed = semver.parse(o.version) + const parsed = semver.parse(o.version); - const fields: string[] = [] + const fields: string[] = []; for (const { field, value } of [ { field: "version", value: o.version }, @@ -111,53 +125,83 @@ function renderVersionInfo(o: VersionInfo): string { { field: "gitDate", value: o.gitDate }, ]) { if (value != null) { - fields.push(field) + fields.push(field); const strVal = value instanceof Date ? `new Date(${value.getTime()})` - : JSON.stringify(value) - const ea = `${field} = ${strVal}` - msg.push(cjs ? `exports.${ea};` : `export const ${ea};`) + : JSON.stringify(value); + const ea = `${field} = ${strVal}`; + msg.push(cjs ? `exports.${ea};` : `export const ${ea};`); } } if (ts || mjs) { - msg.push(`export default {${fields.join(",")}};`) + msg.push(`export default {${fields.join(",")}};`); } - return msg.join("\n") + "\n" + return msg.join("\n") + "\n"; } -export function mkver(output: string = join(cwd(), "Version.ts")): void { - const file = resolve(normalize(output)) - const parsed = parse(file) +/** + * Writes a file with version and release metadata to `output` + * + * @param output - The file to write to. Defaults to "./Version.ts". File format + * is determined by the file extension. Supported extensions are ".ts", ".js", + * and ".mjs". + * @returns The version and release metadata written to the file. + */ +export async function mkver(output?: string): Promise { + if (output == null || output.trim().length === 0) { + output = "./Version.ts"; + } + const file = resolve(normalize(output)); + const parsed = parse(file); + const v = await findPackageVersion(parsed.dir); + if (v == null) { + throw new Error( + "No package.json was found in " + parsed.dir + " or parent directories.", + ); + } + const gitSha = await headSha(v.dir); + const gitDate = await headUnixtime(v.dir); + const versionInfo = { + path: parsed, + version: v.version, + release: `${v.version}+${fmtYMDHMS(gitDate)}`, + gitSha, + gitDate, + }; + const buf = renderVersionInfo(versionInfo); + try { - const v = findPackageVersion(parsed.dir) - if (v == null) { - throw new Error( - "No package.json was found in " + parsed.dir + " or parent directories." - ) - } - const gitSha = headSha(v.dir) - const gitDate = headUnixtime(v.dir) - const msg = renderVersionInfo({ - output, - version: v.version, - release: `${v.version}+${fmtYMDHMS(gitDate)}`, - gitSha, - gitDate, - }) + await mkdir(parsed.dir, { recursive: true }); + } catch (err) { + if (err.code !== "EEXIST") throw err; + } - try { - mkdirSync(parsed.dir, { recursive: true }) - } catch (err: any) { - if (err.code !== "EEXIST") throw err - } + await writeFile(file, buf); + + return versionInfo; +} + +async function main() { + const arg = argv[2] ?? ""; - writeFileSync(file, msg) - } catch (err: any) { - console.error( - argv[1] + ": Failed to produce " + output + ": " + err.message - ) - exit(1) + if (["--help", "-h"].includes(arg)) { + // Show them usage instructions: + console.log(`Usage: mkver [FILE] +Provides Node.js access to your app's version and release metadata. + +With no FILE, default output is "./Version.ts". + +See for more information.`); + } else { + return mkver(arg); } } + +if (require.main === module) { + void main().catch((error) => { + console.error("Failed: " + error); + exit(1); + }); +} diff --git a/tsconfig.json b/tsconfig.json index 60b5332..3a90247 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,8 @@ "strict": true, "rootDir": "src", "outDir": "dist", - "sourceMap": true + "sourceMap": true, + "useUnknownInCatchVariables": false, + "declaration": true } -} +} \ No newline at end of file