Skip to content

Instantly share code, notes, and snippets.

@tracker1
Last active November 7, 2025 16:03
Show Gist options
  • Save tracker1/d74329cacbb95e69d3caa7ed6869f396 to your computer and use it in GitHub Desktop.
Save tracker1/d74329cacbb95e69d3caa7ed6869f396 to your computer and use it in GitHub Desktop.
Get a database version string when using release-please
#!/usr/bin/env -S deno run -A --quiet
// deno-lint-ignore-file no-explicit-any
/*******************************************************************************
Deno script to output a tagged version for use with deployments.
deno run -A --quiet _scripts/get-db-version.ts
Inputs:
--baseversion "BASE_VERSION_OVERRIDE"
example "1.1.19"
--githash "GITHASH_OVERRIDE"
--timesamp "TIMESTAMP_STRING_OVERRIDE"
Outputs:
A version string "${manifest-version}-${git-shorrt-hash}-${timestamp}"
manifest-version: from input or Read from ../release-please-manifest.json
git-short-hash: from input or determined from `git rev-parse`
timestamp: from input or base323 encoded from
`+"YYYYMMDDhhmmss"` as an integer
Warning: run `deno check` first to avoid additional deno output
Desting: deno test --coverage --allow-run _scripts/get-db-version.ts
*******************************************************************************/
import { parseArgs } from "jsr:@std/cli/parse-args";
import { git, GitCommand } from "jsr:@gnome/git-cli";
// testing
import { assertEquals, assertMatch } from "jsr:@std/assert";
/******************************************************************************/
type IFileReaderSync = (path: string) => string;
type IGitCliFunction = (args: string) => GitCommand;
function getReleaseVersion(readFile: IFileReaderSync, baseversion?: string) {
if (baseversion) {
return baseversion;
}
const __dirname = import.meta.dirname;
return JSON.parse(
readFile(`${__dirname}/../release-please-manifest.json`),
)["."];
}
function getTimestamp(timestamp?: string) {
if (timestamp) {
return timestamp;
}
return (+(new Date().toJSON().replace(/\D/g, "").substring(0, 14)))
.toString(32).toLowerCase();
}
async function getGitHash(gitCli: IGitCliFunction, githash?: string) {
if (!githash) {
const { code, stdout /*, stderr*/ } = await gitCli("rev-parse HEAD");
if (code != 0) return "unknown";
githash = new TextDecoder().decode(stdout).trim();
}
return githash.substring(0, 7);
}
function formatVersionString(ver: string, shorthash: string, ts: string) {
return `${ver}-${shorthash}.${ts}`;
}
async function getVersionString(
readFile: IFileReaderSync,
gitCli: IGitCliFunction,
baseversion?: string,
githash?: string,
timestamp?: string,
) {
const ver = getReleaseVersion(readFile, baseversion);
const hash = await getGitHash(gitCli, githash);
const ts = getTimestamp(timestamp);
return formatVersionString(ver, hash, ts);
}
async function getVersionStringFromArguments(
readFile: IFileReaderSync,
git: IGitCliFunction,
inputArgs: string[],
) {
const args = parseArgs(inputArgs, {
string: ["baseversion", "githash", "timestamp"],
});
const version = await getVersionString(
readFile,
git,
args.baseversion,
args.githash,
args.timestamp,
);
return version;
}
/*******************************************************************************
MAIN ENTRY -- If called directly -- executes workflow
*******************************************************************************/
// deno-coverage-ignore-start
if (import.meta.main) {
console.log(
await getVersionStringFromArguments(Deno.readTextFileSync, git, Deno.args),
);
}
// deno-coverage-ignore-stop
/*******************************************************************************
* Unit Tests
*/
Deno.test("Can override all inputs (Happy Path)", async () => {
// arrange
const bv = "1.1.1";
const gh = "abcdefghijklmnop";
const ts = "12345678";
const args = [
"--baseversion",
bv,
"--githash",
gh,
"--timestamp",
ts,
];
// act
const result = await getVersionStringFromArguments(
null as any,
null as any,
args,
);
// assert
assertEquals(result, `${bv}-${gh.substring(0, 7)}.${ts}`);
});
Deno.test("Will run without parameters", async () => {
const readFile = () => `{".":"1.1.1"}`;
const gitCli = () =>
Promise.resolve({
stdout: new TextEncoder().encode("testing"),
stdErr: new Uint8Array(0),
code: 0,
signal: undefined,
success: true,
});
const result = await getVersionStringFromArguments(
readFile,
gitCli as any,
[],
);
assertMatch(result, /1\.1\.1\-(\w{7})\.(\w{8})/);
});
Deno.test("Will handle missing git", async () => {
const readFile = () => `{".":"1.1.1"}`;
const gitCli = () =>
Promise.resolve({
stdout: new Uint8Array(0),
stdErr: new TextEncoder().encode(
"fatal: not a git repository (or any of the parent directories): .git\n",
),
code: 128,
signal: undefined,
success: false,
});
const result = await getVersionStringFromArguments(
readFile as any,
gitCli as any,
[],
);
assertMatch(result, /1\.1\.1\-unknown\.(\w{8})/);
});
# In my early steps...
# I'm using powershell because I'm releasing on windows currently in a self-hosted runner
# Writing to a file is because for some reason in the action runner, variable capture doesn't work.
jobs:
deploy_db:
...
steps:
- name: Install Deno Runtime
uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- name: Checkout Project Code
uses: actions/checkout@v4
- id: dbver
name: Get Database Patch Version
shell: pwsh
run: |
deno run -A --quiet _scripts/get-db-version.ts --githash "${{ github.sha }}" > dbver.txt 2>&1
$dbver = Get-Content -Path "dbver.txt" -Raw
rm dbver.txt
echo "DBVER=$dbver" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
echo "Database Version: $dbver"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment