-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a18d1b4
commit 5e91539
Showing
10 changed files
with
301 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/tests/ver.ts |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"deno.enable": true, | ||
"deno.lint": true, | ||
"deno.unstable": false, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,31 @@ | ||
# package | ||
This module generates a package (with name and version) file from the latest git tag for you to use in code | ||
# ver | ||
This module generates a ver.ts with as version from the latest git tag for you to use in code. | ||
|
||
## What? | ||
In node.js there is een idiomatic way of storing the version of whichever program you're building which is the package.json. | ||
|
||
In Deno the versions are managed by git tags, which is better, because you manage the version of your software in a more abstract way: using git, and not npm. npm is more locked-in to a technology. (git is also a technology but if you're not using that then I don't know what to say). | ||
|
||
This presents a problem though: | ||
|
||
The .git directory (where the tags are stored) is not available in production code (or at least shouldn't be) but it's a pretty common approach in node.js to parse the package.json and use the version found there to display in your app. For example, an api could return it's version in a header, and a cli wants to display the version in it's --help. | ||
|
||
There is a Deno module that solves this problem with a rich feature set, including a cli which does the git tagging. Check it out here: https://deno.land/x/version_ts | ||
|
||
While the above solution is excellent, I wanted a super simple version of this. Not requiring a cli to be installed but just generating a single .ts file for you to use based on the latest git tag. | ||
|
||
## How? | ||
|
||
In the startup of your app, put | ||
```typescript | ||
await ensureVersion(); | ||
``` | ||
This will generate a ver.ts file in the cwd of your project. ensureVersion will make just that file always contains the latest version from your git tag list. | ||
|
||
Whenever you need the current version, you can import the ver.ts or use | ||
```typescript | ||
await getVersion(); | ||
``` | ||
anywhere in your application to get the sematic version. | ||
|
||
Note that using getVersion is better because it lazy loads (with await import) the ver.ts |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export * as log from "https://deno.land/[email protected]/log/mod.ts"; | ||
export * as fs from "https://deno.land/[email protected]/fs/mod.ts"; | ||
export * as asserts from "https://deno.land/[email protected]/testing/asserts.ts"; | ||
export * as path from "https://deno.land/[email protected]/path/mod.ts"; | ||
export * as semver from "https://deno.land/x/semver/mod.ts"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
import { fs, path, semver } from "./deps.ts"; | ||
|
||
export async function ensureVersion( | ||
root: string = Deno.cwd() | ||
): Promise<semver.SemVer | null> { | ||
let parsedVersion: semver.SemVer | null = null; | ||
let maybeError: Error | null = null; | ||
|
||
try { | ||
await del(root); | ||
const output = await mostRecentGitTag(); | ||
parsedVersion = semver.parse(output); | ||
|
||
if (!parsedVersion) { | ||
throw new Error( | ||
`Failed to parse version: ${output}. (which was the most recent git tag)` | ||
); | ||
} | ||
} catch (err) { | ||
maybeError = err; | ||
} | ||
|
||
await save(parsedVersion, root, maybeError); | ||
|
||
return await get(root); | ||
} | ||
|
||
export async function getVersion( | ||
root: string = Deno.cwd() | ||
): Promise<semver.SemVer | null> { | ||
return await get(root); | ||
} | ||
|
||
const fileName = "ver.ts"; | ||
|
||
async function get(root: string): Promise<semver.SemVer | null> { | ||
try { | ||
const p = path.join(root, fileName); | ||
await Deno.stat(p); | ||
|
||
const v = await import(p); | ||
const keys = Object.keys(v); | ||
|
||
if (!keys.includes("default")) { | ||
throw new TypeError(`${fileName} does not include a default export`); | ||
} | ||
|
||
const val = v["default"]; | ||
|
||
if (typeof val !== "string" && !(val instanceof String)) { | ||
throw new TypeError( | ||
`${fileName} default export did not contain a string value` | ||
); | ||
} | ||
|
||
return semver.parse(val.toString()); | ||
} catch (err) { | ||
console.error(err); | ||
return null; | ||
} | ||
} | ||
|
||
async function save( | ||
version: semver.SemVer | null, | ||
root = Deno.cwd(), | ||
err: Error | null = null | ||
): Promise<void> { | ||
const dest = path.join(root, fileName); | ||
|
||
const versionString = version | ||
? `export default '${version.toString()}';` | ||
: '// "git tag" last line did not return a valid version'; | ||
|
||
const datetimeString = `// Auto-generated from 'git tag' on ${new Date().toString()}.`; | ||
let errString = ""; | ||
|
||
if (err) { | ||
errString = `// Error: ${err.message}`; | ||
} | ||
|
||
await Deno.writeTextFile( | ||
dest, | ||
`${datetimeString} | ||
${errString} | ||
${versionString}` | ||
); | ||
} | ||
|
||
export async function del(root = Deno.cwd()) { | ||
const dest = path.join(root, fileName); | ||
const exists = await Deno.stat(dest) | ||
.then(() => true) | ||
.catch(() => false); | ||
|
||
if (exists) { | ||
await Deno.remove(dest); | ||
} | ||
} | ||
|
||
const textDecoder = new TextDecoder(); | ||
export async function run(args: string[]): Promise<string> { | ||
const process = Deno.run({ | ||
cmd: args, | ||
stdout: "piped", | ||
stdin: "piped", | ||
stderr: "piped", | ||
}); | ||
|
||
const rawOutput = await process.output(); | ||
const rawError = await process.stderrOutput(); | ||
|
||
if (rawError && rawError.length > 0) { | ||
const err = textDecoder.decode(rawError); | ||
throw new Error(err); | ||
} | ||
//await process.status(); | ||
return textDecoder.decode(rawOutput); | ||
} | ||
|
||
export async function mostRecentGitTag(): Promise<string> { | ||
const output = await run(["git", "tag"]); | ||
const nl = fs.detect(output) || fs.EOL.LF; | ||
const lines = output.split(nl); | ||
|
||
const versions = lines.map((l) => l.trim()).filter((l) => l.length > 0); | ||
|
||
if (versions.length === 0) { | ||
throw new Error("Could not find any git tags"); | ||
} | ||
|
||
return versions[versions.length - 1]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { asserts } from "../deps.ts"; | ||
import { getVersion } from "../mod.ts"; | ||
import { deleteTestVer } from "./testHelpers.ts"; | ||
|
||
Deno.test( | ||
"Get Version (Fail)", | ||
{ | ||
permissions: { | ||
read: true, | ||
write: true | ||
}, | ||
}, | ||
async (ctx: Deno.TestContext) => { | ||
await ctx.step("Delete Existing", async () => await deleteTestVer()); | ||
await ctx.step("Get Undefined Version", async () => { | ||
const v = await getVersion(); | ||
asserts.assert(v === null); | ||
}); | ||
await ctx.step("Delete Existing", async () => await deleteTestVer()); | ||
} | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { asserts } from "../deps.ts"; | ||
import { getVersion } from "../mod.ts"; | ||
import { deleteTestVer,createTestVer,testPath } from "./testHelpers.ts"; | ||
|
||
Deno.test( | ||
"Get Version (Success)", | ||
{ | ||
permissions: { | ||
read: true, | ||
write: true, | ||
}, | ||
}, | ||
async (ctx: Deno.TestContext) => { | ||
await ctx.step("Delete Existing", async () => await deleteTestVer()); | ||
await ctx.step("Create New", async () => await createTestVer()); | ||
await ctx.step("Get Defined Version", async () => { | ||
const v = await getVersion(testPath); | ||
|
||
asserts.assert(v !== null); | ||
asserts.assert(v.major === 1); | ||
asserts.assert(v.minor === 2); | ||
asserts.assert(v.patch === 3); | ||
}); | ||
|
||
await ctx.step("Delete Existing", async () => await deleteTestVer()); | ||
} | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { asserts } from "../deps.ts"; | ||
import { ensureVersion } from "../mod.ts"; | ||
import { addBogusGitTag, deleteTestVer, removeBogusGitTag, testPath } from "./testHelpers.ts"; | ||
|
||
Deno.test( | ||
"Git Tag (Fail)", | ||
{ | ||
permissions: { | ||
read: true, | ||
write: true | ||
}, | ||
sanitizeResources: false | ||
}, | ||
async (ctx: Deno.TestContext) => { | ||
const testTag = "bogus-tag"; | ||
await ctx.step("Delete Existing", async () => await deleteTestVer()); | ||
await ctx.step("Add Bogus Test Tag", async () => await addBogusGitTag(testTag)); | ||
await ctx.step("Ensure Version", async () => { | ||
const v = await ensureVersion(testPath); | ||
asserts.assert(v === null); | ||
}); | ||
await ctx.step("Remove Bogus Test Tag", async () => await removeBogusGitTag(testTag)); | ||
await ctx.step("Delete Existing", async () => await deleteTestVer()); | ||
} | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { asserts } from "../deps.ts"; | ||
import { ensureVersion } from "../mod.ts"; | ||
import { addBogusGitTag, deleteTestVer, removeBogusGitTag, testPath } from "./testHelpers.ts"; | ||
|
||
Deno.test( | ||
"Git Tag (Success)", | ||
{ | ||
permissions: { | ||
read: true, | ||
write: true | ||
}, | ||
sanitizeResources: false | ||
}, | ||
async (ctx: Deno.TestContext) => { | ||
const testTag = "v45.78.90"; | ||
await ctx.step("Delete Existing", async () => await deleteTestVer()); | ||
await ctx.step("Add Bogus Test Tag", async () => await addBogusGitTag(testTag)); | ||
await ctx.step("Ensure Version", async () => { | ||
const v = await ensureVersion(testPath); | ||
asserts.assert(v !== null); | ||
asserts.assert(v.major === 45); | ||
asserts.assert(v.minor === 78); | ||
asserts.assert(v.patch === 90); | ||
}); | ||
await ctx.step("Remove Bogus Test Tag", async () => await removeBogusGitTag(testTag)); | ||
await ctx.step("Delete Existing", async () => await deleteTestVer()); | ||
} | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { path } from "../deps.ts"; | ||
import { run } from "../mod.ts"; | ||
|
||
export const testPath = path.join(Deno.cwd(), "tests"); | ||
|
||
export async function createTestVer(ver = "1.2.3"): Promise<void> { | ||
const p = path.join(testPath, "ver.ts"); | ||
await Deno.writeTextFile(p, `export default '${ver}';`); | ||
} | ||
|
||
export async function deleteTestVer(): Promise<void> { | ||
const p = path.join(testPath, "ver.ts"); | ||
const exists = await Deno.stat(p).then(() => true).catch(() => false); | ||
|
||
if (exists) { | ||
await Deno.remove(p); | ||
} | ||
} | ||
|
||
export async function addBogusGitTag(tag: string): Promise<void> { | ||
await run(["git", "tag", tag]); | ||
} | ||
|
||
export async function removeBogusGitTag(tag: string): Promise<void> { | ||
await run(["git", "tag", "-d", tag]); | ||
} |