From 6de007375d498b0ca9da3608833239633f41afe3 Mon Sep 17 00:00:00 2001 From: Matthew McEachen Date: Fri, 15 Jul 2022 11:06:30 -0700 Subject: [PATCH] add semver-parsed fields to Version.ts --- README.md | 27 +++++++++- src/mkver.spec.ts | 133 +++++++++++++++++++++++++++++++--------------- src/mkver.ts | 35 +++++++++--- 3 files changed, 143 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index a513211..b4cd1b9 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,32 @@ pipeline, import the thing, and then solve the Big Problems. - a `version.js` (if you're using [CommonJS](https://en.wikipedia.org/wiki/CommonJS)) with your git SHA and version information exported as constants. - + +## Example output + +```typescript +// Version.ts + +export const version = "1.2.3-beta.4" +export const versionMajor = 1 +export const versionMinor = 2 +export const versionPatch = 3 +export const versionPrerelease = ["beta", 4] +export const release = "1.2.3-beta.4+20220101105815" +export const gitSha = "dc336bc8e1ea6b4e2f393f98233839b6c23cb812" +export const gitDate = new Date(1641063495000) +export default { + version, + versionMajor, + versionMinor, + versionPatch, + versionPrerelease, + release, + gitSha, + gitDate, +} +``` + The filename can be anything you want, but the file extension must be `.ts`, `.mjs`, or `.js`. diff --git a/src/mkver.spec.ts b/src/mkver.spec.ts index 67a612a..12a97c3 100644 --- a/src/mkver.spec.ts +++ b/src/mkver.spec.ts @@ -6,45 +6,84 @@ import { join, parse } from "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 + + constructor({ + major, + minor, + patch, + prerelease, + version, + }: { + major?: number + minor?: number + patch?: number + prerelease?: (string | number)[] + version?: string + } = {}) { + 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(".")) + } +} + describe("mkver", function () { this.retries(2) this.slow(1) - it("./ver.js", async () => { - const { gitSha, dir } = mkTestRepo() - return assertResult(gitSha, dir + "/ver.js") - }) + for (const exp of [ + new ExpectedVersion({ prerelease: ["alpha"] }), + new ExpectedVersion({ prerelease: ["beta", 3] }), + new ExpectedVersion({ prerelease: ["rc", 1] }), + new ExpectedVersion(), + ]) { + describe(exp.version, () => { + it("./ver.js", async () => { + 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() - } - const { gitSha, dir } = mkTestRepo() - return assertResult(gitSha, dir + "/version.mjs") - }) + it("./version.mjs", async function () { + if (!semver.satisfies(process.version, ">=13")) { + return this.skip() + } + const { gitSha, dir } = mkTestRepo(exp) + return assertResult(gitSha, dir + "/version.mjs", exp) + }) - it("./ver.ts", async function () { - if (platform().startsWith("win")) { - return this.skip() - } - const { gitSha, dir } = mkTestRepo() - return assertResult(gitSha, dir + "/ver.ts") - }) + it("./ver.ts", async function () { + if (platform().startsWith("win")) { + return this.skip() + } + const { gitSha, dir } = mkTestRepo(exp) + return assertResult(gitSha, dir + "/ver.ts", exp) + }) - it("./testdir/version.js", async () => { - const { gitSha, dir } = mkTestRepo() - return assertResult(gitSha, dir + "/testdir/version.js") - }) + it("./testdir/version.js", async () => { + const { gitSha, dir } = mkTestRepo(exp) + return assertResult(gitSha, dir + "/testdir/version.js", exp) + }) - it("fails for ./ver.go", async () => { - const { gitSha, dir } = mkTestRepo() - try { - await assertResult(gitSha, dir + "/ver.go") - expect.fail("unsupported format should have thrown") - } catch (err) { - expect(err).to.match(/Unsupported file extension/i) - } - }) + it("fails for ./ver.go", async () => { + const { gitSha, dir } = mkTestRepo(exp) + try { + await assertResult(gitSha, dir + "/ver.go", exp) + expect.fail("unsupported format should have thrown") + } catch (err) { + expect(err).to.match(/Unsupported file extension/i) + } + }) + }) + } describe("fmtYMDHMS", () => { for (const iso of [ @@ -61,12 +100,10 @@ describe("mkver", function () { }) }) -const expVer = `${getRandomInt(15)}.${getRandomInt(15)}.${getRandomInt(15)}` - -function mkTestRepo() { +function mkTestRepo(exp: ExpectedVersion) { const dir = join(tmpdir(), randomChars()) mkdirSync(dir) - writeFileSync(dir + "/package.json", JSON.stringify({ version: expVer })) + 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 }) @@ -111,8 +148,8 @@ async function maybeCompile(pathToVersionFile: string): Promise { writeFileSync( dest, [ - `const { version, release, gitSha, gitDate } = require("./${parsed.name}");`, - `console.log(JSON.stringify({ version, release, gitSha, gitDate }));`, + `const v = require("./${parsed.name}");`, + `console.log(JSON.stringify(v));`, "", ].join("\n") ) @@ -124,8 +161,8 @@ async function maybeCompile(pathToVersionFile: string): Promise { dest, [ // HUH: .mjs imports require the file extension (!?) - `import { version, release, gitSha, gitDate } from "./${parsed.base}";`, - `console.log(JSON.stringify({ version, release, gitSha, gitDate }));`, + `import * as v from "./${parsed.base}";`, + `console.log(JSON.stringify(v));`, "", ].join("\n") ) @@ -134,8 +171,8 @@ async function maybeCompile(pathToVersionFile: string): Promise { writeFileSync( dest, [ - `import { version, release, gitSha, gitDate } from "./${parsed.name}";`, - `console.log(JSON.stringify({ version, release, gitSha, gitDate }));`, + `import * as v from "./${parsed.name}";`, + `console.log(JSON.stringify(v));`, "", ].join("\n") ) @@ -145,7 +182,11 @@ async function maybeCompile(pathToVersionFile: string): Promise { } } -async function assertResult(gitSha: string, pathToVersionFile: string) { +async function assertResult( + gitSha: string, + pathToVersionFile: string, + exp: ExpectedVersion +) { await _exec( fork("bin/mkver", [pathToVersionFile], { detached: false, stdio: "pipe" }) ) @@ -163,13 +204,17 @@ async function assertResult(gitSha: string, pathToVersionFile: string) { new Date() as any ) - expect(result.version).to.eql(expVer) + 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 = `${expVer}+${ymdhm}` + const expectedRelease = `${exp.version}+${ymdhm}` const releaseWithoutSeconds = trimEnd(result.release, 2) expect(releaseWithoutSeconds).to.eql(expectedRelease) } diff --git a/src/mkver.ts b/src/mkver.ts index 7bf82bc..59db7d4 100644 --- a/src/mkver.ts +++ b/src/mkver.ts @@ -2,11 +2,16 @@ 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" function notBlank(s: string | undefined): boolean { return s != null && String(s).trim().length > 0 } +function map(obj: T | undefined | null, f: (t: T) => U): U | undefined { + return obj == null ? undefined : f(obj) +} + function findPackageVersion( dir: string ): undefined | { version: string; dir: string } { @@ -95,17 +100,33 @@ function renderVersionInfo(o: VersionInfo): string { ) } - for (const ea of [ - `version = "${o.version}"`, - `release = "${o.release}"`, - `gitSha = "${o.gitSha}"`, - `gitDate = new Date(${o.gitDate.getTime()})`, + const parsed = semver.parse(o.version) + + const fields: string[] = [] + + for (const { field, value } of [ + { field: "version", value: o.version }, + { field: "versionMajor", value: parsed?.major }, + { field: "versionMinor", value: parsed?.minor }, + { field: "versionPatch", value: parsed?.patch }, + { field: "versionPrerelease", value: parsed?.prerelease }, + { field: "release", value: o.release }, + { field: "gitSha", value: o.gitSha }, + { field: "gitDate", value: o.gitDate }, ]) { - msg.push(cjs ? `exports.${ea};` : `export const ${ea};`) + if (value != null) { + 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};`) + } } if (ts || mjs) { - msg.push("export default { version, release, gitSha, gitDate };") + msg.push(`export default {${fields.join(",")}};`) } return msg.join("\n") + "\n" }