From 01a66d76c5d977f8620e710ec8b747239e172d68 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 12 Oct 2025 21:41:43 +0000 Subject: [PATCH 01/13] Initial plan From 6fdff2df5e1bc2f6f7d176487213120867ecbcd1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 12 Oct 2025 21:46:18 +0000 Subject: [PATCH 02/13] Initial planning for sort subcommand feature Co-authored-by: justinmchase <10974+justinmchase@users.noreply.github.com> --- deno.lock | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/deno.lock b/deno.lock index 0770e57..c0c50aa 100644 --- a/deno.lock +++ b/deno.lock @@ -1,16 +1,26 @@ { "version": "5", "specifiers": { + "jsr:@std/assert@^1.0.15": "1.0.15", + "jsr:@std/assert@^1.0.6": "1.0.15", "jsr:@std/fmt@^1.0.8": "1.0.8", "jsr:@std/internal@^1.0.10": "1.0.12", + "jsr:@std/internal@^1.0.12": "1.0.12", "jsr:@std/path@^1.0.6": "1.1.2", "jsr:@std/semver@^1.0.3": "1.0.6", + "jsr:@std/testing@^1.0.3": "1.0.16", "jsr:@std/yaml@^1.0.5": "1.0.10", "npm:json5@^2.2.3": "2.2.3", "npm:jsonc-parser@^3.2.1": "3.3.1", "npm:yargs@^17.7.2": "17.7.2" }, "jsr": { + "@std/assert@1.0.15": { + "integrity": "d64018e951dbdfab9777335ecdb000c0b4e3df036984083be219ce5941e4703b", + "dependencies": [ + "jsr:@std/internal@^1.0.12" + ] + }, "@std/fmt@1.0.8": { "integrity": "71e1fc498787e4434d213647a6e43e794af4fd393ef8f52062246e06f7e372b7" }, @@ -20,12 +30,19 @@ "@std/path@1.1.2": { "integrity": "c0b13b97dfe06546d5e16bf3966b1cadf92e1cc83e56ba5476ad8b498d9e3038", "dependencies": [ - "jsr:@std/internal" + "jsr:@std/internal@^1.0.10" ] }, "@std/semver@1.0.6": { "integrity": "b7c98ae2843547cf3f7ac37f3995889e6e4cee0a97b57b57f17f62722843303c" }, + "@std/testing@1.0.16": { + "integrity": "a917ffdeb5924c9be436dc78bc32e511760e14d3a96e49c607fc5ecca86d0092", + "dependencies": [ + "jsr:@std/assert@^1.0.15", + "jsr:@std/internal@^1.0.12" + ] + }, "@std/yaml@1.0.10": { "integrity": "245706ea3511cc50c8c6d00339c23ea2ffa27bd2c7ea5445338f8feff31fa58e" } From 8e33e4821aaf24a917c1388fc06fbf59254bd494 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 12 Oct 2025 21:50:19 +0000 Subject: [PATCH 03/13] Implement sort subcommand with comprehensive tests Co-authored-by: justinmchase <10974+justinmchase@users.noreply.github.com> --- main.ts | 2 + src/commands/mod.ts | 1 + src/commands/sort.test.ts | 151 ++++++++++++++++++++++++++++++++++++++ src/commands/sort.ts | 91 +++++++++++++++++++++++ 4 files changed, 245 insertions(+) create mode 100644 src/commands/sort.test.ts create mode 100644 src/commands/sort.ts diff --git a/main.ts b/main.ts index d8fa84f..6be4081 100644 --- a/main.ts +++ b/main.ts @@ -12,6 +12,7 @@ import { lte, parse, set, + sort, } from "./src/commands/mod.ts"; import { getContext } from "./src/context.ts"; import { ApplicationError } from "./src/errors/application.error.ts"; @@ -34,6 +35,7 @@ try { .command(lt) .command(lte) .command(eq) + .command(sort) .strictOptions() .strictCommands() .demandCommand(1) diff --git a/src/commands/mod.ts b/src/commands/mod.ts index e1ac22f..4336d95 100644 --- a/src/commands/mod.ts +++ b/src/commands/mod.ts @@ -8,3 +8,4 @@ export * from "./gte.ts"; export * from "./lt.ts"; export * from "./lte.ts"; export * from "./eq.ts"; +export * from "./sort.ts"; diff --git a/src/commands/sort.test.ts b/src/commands/sort.test.ts new file mode 100644 index 0000000..54962db --- /dev/null +++ b/src/commands/sort.test.ts @@ -0,0 +1,151 @@ +import { describe, it } from "testing/bdd"; +import { assertSpyCall, assertSpyCalls, stub } from "testing/mock"; +import { Arguments } from "yargs"; +import { sort } from "./sort.ts"; +import { testContext } from "../util/testContext.ts"; +import { IContext } from "../context.ts"; + +describe("sort", () => { + const ctx = testContext({ + consoleLog: () => stub(console, "log"), + }); + + it("SORT00 - sorts versions in descending order by default", async () => { + await sort.handler( + { + _: [], + versions: ["2.0.0", "1.0.0", "3.0.0"], + asc: false, + desc: false, + } as unknown as Arguments & IContext, + ); + assertSpyCall(ctx.consoleLog, 0, { + args: ["3.0.0"], + }); + assertSpyCall(ctx.consoleLog, 1, { + args: ["2.0.0"], + }); + assertSpyCall(ctx.consoleLog, 2, { + args: ["1.0.0"], + }); + assertSpyCalls(ctx.consoleLog, 3); + }); + + it("SORT01 - sorts versions in ascending order with -a flag", async () => { + await sort.handler( + { + _: [], + versions: ["2.0.0", "1.0.0", "3.0.0"], + asc: true, + desc: false, + } as unknown as Arguments & IContext, + ); + assertSpyCall(ctx.consoleLog, 0, { + args: ["1.0.0"], + }); + assertSpyCall(ctx.consoleLog, 1, { + args: ["2.0.0"], + }); + assertSpyCall(ctx.consoleLog, 2, { + args: ["3.0.0"], + }); + assertSpyCalls(ctx.consoleLog, 3); + }); + + it("SORT02 - sorts versions in descending order with -d flag", async () => { + await sort.handler( + { + _: [], + versions: ["1.0.0", "3.0.0", "2.0.0"], + asc: false, + desc: true, + } as unknown as Arguments & IContext, + ); + assertSpyCall(ctx.consoleLog, 0, { + args: ["3.0.0"], + }); + assertSpyCall(ctx.consoleLog, 1, { + args: ["2.0.0"], + }); + assertSpyCall(ctx.consoleLog, 2, { + args: ["1.0.0"], + }); + assertSpyCalls(ctx.consoleLog, 3); + }); + + it("SORT03 - handles prerelease versions correctly", async () => { + await sort.handler( + { + _: [], + versions: ["1.0.0", "1.0.0-alpha", "1.0.0-beta"], + asc: true, + desc: false, + } as unknown as Arguments & IContext, + ); + assertSpyCall(ctx.consoleLog, 0, { + args: ["1.0.0-alpha"], + }); + assertSpyCall(ctx.consoleLog, 1, { + args: ["1.0.0-beta"], + }); + assertSpyCall(ctx.consoleLog, 2, { + args: ["1.0.0"], + }); + assertSpyCalls(ctx.consoleLog, 3); + }); + + it("SORT04 - handles build metadata correctly", async () => { + await sort.handler( + { + _: [], + versions: ["1.0.0+build1", "1.0.0+build2", "1.0.0"], + asc: true, + desc: false, + } as unknown as Arguments & IContext, + ); + // Build metadata should not affect sort order + assertSpyCalls(ctx.consoleLog, 3); + }); + + it("SORT05 - sorts complex semver versions", async () => { + await sort.handler( + { + _: [], + versions: ["1.0.0", "2.1.0", "2.0.0", "1.1.0", "1.0.1"], + asc: true, + desc: false, + } as unknown as Arguments & IContext, + ); + assertSpyCall(ctx.consoleLog, 0, { + args: ["1.0.0"], + }); + assertSpyCall(ctx.consoleLog, 1, { + args: ["1.0.1"], + }); + assertSpyCall(ctx.consoleLog, 2, { + args: ["1.1.0"], + }); + assertSpyCall(ctx.consoleLog, 3, { + args: ["2.0.0"], + }); + assertSpyCall(ctx.consoleLog, 4, { + args: ["2.1.0"], + }); + assertSpyCalls(ctx.consoleLog, 5); + }); + + it("SORT06 - handles single version", async () => { + await sort.handler( + { + _: [], + versions: ["1.0.0"], + asc: false, + desc: false, + } as unknown as Arguments & IContext, + ); + assertSpyCall(ctx.consoleLog, 0, { + args: ["1.0.0"], + }); + assertSpyCalls(ctx.consoleLog, 1); + }); +}); diff --git a/src/commands/sort.ts b/src/commands/sort.ts new file mode 100644 index 0000000..6268547 --- /dev/null +++ b/src/commands/sort.ts @@ -0,0 +1,91 @@ +import { Arguments, YargsInstance } from "yargs"; +import { compare, parse } from "semver"; +import { InvalidVersionError } from "../errors/mod.ts"; +import { IContext } from "../context.ts"; + +export const sort = { + command: "sort [versions..]", + describe: "Sort semantic versions", + builder(yargs: YargsInstance) { + return yargs + .positional("versions", { + describe: "Versions to sort", + type: "string", + array: true, + }) + .option("asc", { + alias: "a", + type: "boolean", + description: "Sort in ascending order", + default: false, + }) + .option("desc", { + alias: "d", + type: "boolean", + description: "Sort in descending order (default)", + default: false, + }); + }, + async handler(args: Arguments & IContext) { + const { versions, asc, desc } = args; + let versionList: string[] = []; + + // Check if we should read from stdin + // The main.ts filters out "--" from args, so if versions is undefined or empty, + // we check if "--" was in the original Deno.args + const hasStdinFlag = Deno.args.includes("--"); + + if (hasStdinFlag || !versions || versions.length === 0) { + // Read from stdin + const decoder = new TextDecoder(); + const data = await Deno.stdin.readable; + const reader = data.getReader(); + let buffer = ""; + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + buffer += decoder.decode(value, { stream: true }); + } + } finally { + reader.releaseLock(); + } + + versionList = buffer + .split("\n") + .map((line) => line.trim()) + .filter((line) => line.length > 0); + } else if (Array.isArray(versions)) { + versionList = versions; + } + + if (versionList.length === 0) { + throw new Error("No versions provided"); + } + + // Parse and validate all versions + const parsedVersions = versionList.map((v) => { + const parsed = parse(v); + if (!parsed) { + throw new InvalidVersionError(v); + } + return { original: v, parsed }; + }); + + // Sort versions using semver compare + parsedVersions.sort((a, b) => compare(a.parsed, b.parsed)); + + // Determine sort order + // Default is descending unless -a/--asc is explicitly set + const isDescending = desc || (!asc && !desc); + + // Reverse if descending + if (isDescending) { + parsedVersions.reverse(); + } + + // Output sorted versions, one per line + parsedVersions.forEach((v) => console.log(v.original)); + }, +}; From ebbc8045c30c631e70d767edd047f69a821c3196 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 12 Oct 2025 21:52:16 +0000 Subject: [PATCH 04/13] Add README documentation for sort command Co-authored-by: justinmchase <10974+justinmchase@users.noreply.github.com> --- README.md | 47 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 393dee2..3a6f70e 100644 --- a/README.md +++ b/README.md @@ -47,16 +47,17 @@ deno task install semver Commands: - semver get Get the version - semver set Set the version - semver inc Increment the version - semver parse [value] Parse the version and print - semver cmp Compare v1 to v2 and return -1/0/1 - semver gt Return 0 if v1 is greater than v2, else 1 - semver gte Return 0 if v1 is greater than or equal to v2, else 1 - semver lt Return 0 if v1 is less than v2, else 1 - semver lte Return 0 if v1 is less than or equal to v2, else 1 - semver eq Return 0 if v1 is equal to v2, else 1 + semver get Get the version + semver set Set the version + semver inc Increment the version + semver parse [value] Parse the version and print + semver cmp Compare v1 to v2 and return -1/0/1 + semver gt Return 0 if v1 is greater than v2, else 1 + semver gte Return 0 if v1 is greater than or equal to v2, else 1 + semver lt Return 0 if v1 is less than v2, else 1 + semver lte Return 0 if v1 is less than or equal to v2, else 1 + semver eq Return 0 if v1 is equal to v2, else 1 + semver sort [versions..] Sort semantic versions Options: --help Show help [boolean] @@ -74,6 +75,10 @@ command will create the `VERSION` file if it doesn't already exist. The `parse` command accepts a version string as input and parses and prints that version as output if it is valid. +The `sort` command accepts one or more version strings and outputs them in +sorted order (descending by default, one version per line). Use the `-a` flag +for ascending order, or read versions from stdin using `--`. + #### examples ```sh @@ -97,6 +102,28 @@ semver get # 1.2.3 semver parse 1.0.0 # {"major":1,"minor":1,"patch":0,"prerelease":[],"build":[]} ``` +```sh +# sort versions in descending order (default) +semver sort 2.0.0 1.0.0 3.0.0 +# 3.0.0 +# 2.0.0 +# 1.0.0 +``` + +```sh +# sort versions in ascending order +semver sort -a 2.0.0 1.0.0 3.0.0 +# 1.0.0 +# 2.0.0 +# 3.0.0 +``` + +```sh +# sort versions from stdin +cat versions.txt | semver sort -- +# (sorted output) +``` + ### Incrementing When calling the command `inc` the `VERSION` file will be updated based on the From b5e6e11c50813f49279d8d162dc82dbb35fab0cc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Oct 2025 22:06:40 +0000 Subject: [PATCH 05/13] Address PR feedback: handle empty versions gracefully Co-authored-by: justinmchase <10974+justinmchase@users.noreply.github.com> --- src/commands/sort.test.ts | 39 +++++++++++++++++++++++++++++++++++++++ src/commands/sort.ts | 12 ++++++++---- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/src/commands/sort.test.ts b/src/commands/sort.test.ts index 54962db..df288d9 100644 --- a/src/commands/sort.test.ts +++ b/src/commands/sort.test.ts @@ -148,4 +148,43 @@ describe("sort", () => { }); assertSpyCalls(ctx.consoleLog, 1); }); + + it("SORT07 - handles empty versions array gracefully", async () => { + await sort.handler( + { + _: [], + versions: [], + asc: false, + desc: false, + } as unknown as Arguments & IContext, + ); + // Should not output anything and not throw + assertSpyCalls(ctx.consoleLog, 0); + }); + + it("SORT08 - handles null versions gracefully", async () => { + await sort.handler( + { + _: [], + versions: null, + asc: false, + desc: false, + } as unknown as Arguments & IContext, + ); + // Should not output anything and not throw + assertSpyCalls(ctx.consoleLog, 0); + }); + + it("SORT09 - handles undefined versions gracefully", async () => { + await sort.handler( + { + _: [], + versions: undefined, + asc: false, + desc: false, + } as unknown as Arguments & IContext, + ); + // Should not output anything and not throw + assertSpyCalls(ctx.consoleLog, 0); + }); }); diff --git a/src/commands/sort.ts b/src/commands/sort.ts index 6268547..43169aa 100644 --- a/src/commands/sort.ts +++ b/src/commands/sort.ts @@ -31,11 +31,10 @@ export const sort = { let versionList: string[] = []; // Check if we should read from stdin - // The main.ts filters out "--" from args, so if versions is undefined or empty, - // we check if "--" was in the original Deno.args + // The main.ts filters out "--" from args, so we check if "--" was in the original Deno.args const hasStdinFlag = Deno.args.includes("--"); - if (hasStdinFlag || !versions || versions.length === 0) { + if (hasStdinFlag) { // Read from stdin const decoder = new TextDecoder(); const data = await Deno.stdin.readable; @@ -56,12 +55,17 @@ export const sort = { .split("\n") .map((line) => line.trim()) .filter((line) => line.length > 0); + } else if (versions === null || versions === undefined) { + versionList = []; } else if (Array.isArray(versions)) { versionList = versions; + } else { + throw new Error("Invalid versions argument"); } + // If no versions provided, exit early with success if (versionList.length === 0) { - throw new Error("No versions provided"); + return; } // Parse and validate all versions From d56e7247eeff34876fd1c113fff5a49048fb2bdf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Oct 2025 22:17:06 +0000 Subject: [PATCH 06/13] Update test to capture build metadata behavior and update semver to 1.0.6 Co-authored-by: justinmchase <10974+justinmchase@users.noreply.github.com> --- deno.json | 2 +- deno.lock | 4 ++-- src/commands/sort.test.ts | 19 ++++++++++++++++++- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/deno.json b/deno.json index 1a052a8..1734d69 100644 --- a/deno.json +++ b/deno.json @@ -5,7 +5,7 @@ "@std/fmt": "jsr:@std/fmt@^1.0.8", "json5": "npm:json5@^2.2.3", "jsonc-parser": "npm:jsonc-parser@^3.2.1", - "semver": "jsr:@std/semver@^1.0.3", + "semver": "jsr:@std/semver@^1.0.6", "path": "jsr:@std/path@^1.0.6", "assert": "jsr:@std/assert@^1.0.6", "testing/bdd": "jsr:@std/testing@^1.0.3/bdd", diff --git a/deno.lock b/deno.lock index c0c50aa..348d48e 100644 --- a/deno.lock +++ b/deno.lock @@ -7,7 +7,7 @@ "jsr:@std/internal@^1.0.10": "1.0.12", "jsr:@std/internal@^1.0.12": "1.0.12", "jsr:@std/path@^1.0.6": "1.1.2", - "jsr:@std/semver@^1.0.3": "1.0.6", + "jsr:@std/semver@^1.0.6": "1.0.6", "jsr:@std/testing@^1.0.3": "1.0.16", "jsr:@std/yaml@^1.0.5": "1.0.10", "npm:json5@^2.2.3": "2.2.3", @@ -143,7 +143,7 @@ "jsr:@std/assert@^1.0.6", "jsr:@std/fmt@^1.0.8", "jsr:@std/path@^1.0.6", - "jsr:@std/semver@^1.0.3", + "jsr:@std/semver@^1.0.6", "jsr:@std/testing@^1.0.3", "jsr:@std/yaml@^1.0.5", "npm:json5@^2.2.3", diff --git a/src/commands/sort.test.ts b/src/commands/sort.test.ts index df288d9..b1c2adc 100644 --- a/src/commands/sort.test.ts +++ b/src/commands/sort.test.ts @@ -103,8 +103,25 @@ describe("sort", () => { desc: false, } as unknown as Arguments & IContext, ); - // Build metadata should not affect sort order + // Build metadata doesn't affect version precedence (semver spec section 10) + // All three versions are considered equal, so sort order is not guaranteed + // Just verify all three are output assertSpyCalls(ctx.consoleLog, 3); + const calls = [ + ctx.consoleLog.calls[0].args[0], + ctx.consoleLog.calls[1].args[0], + ctx.consoleLog.calls[2].args[0], + ]; + // Verify all three versions are present (order doesn't matter) + if ( + !calls.includes("1.0.0") || + !calls.includes("1.0.0+build1") || + !calls.includes("1.0.0+build2") + ) { + throw new Error( + `Expected all three versions to be output, got: ${calls.join(", ")}`, + ); + } }); it("SORT05 - sorts complex semver versions", async () => { From e8ef854ae9a339435d7facac0b3bd224688efd22 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Oct 2025 01:07:51 +0000 Subject: [PATCH 07/13] Add sort command to action.yml and docker-action tests, update copilot instructions Co-authored-by: justinmchase <10974+justinmchase@users.noreply.github.com> --- .github/copilot-instructions.md | 30 ++++++++++++++++++++++++++++++ .github/workflows/checks.yml | 5 +++++ action.yml | 1 + 3 files changed, 36 insertions(+) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 4430b65..e8623ec 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -200,3 +200,33 @@ The GitHub Actions workflow requires: **NEVER CANCEL operations - they complete very quickly. Set timeouts of 60+ seconds minimum for safety.** + +## Adding New Subcommands + +When adding a new subcommand to the CLI, you MUST also update the following +files: + +1. **action.yml**: Add the new subcommand to the `inputs.command.options` list + - This makes the command available in the GitHub Action + - Keep the list alphabetically sorted for consistency + +2. **.github/workflows/checks.yml**: Add an integration test step in the + `docker-action` job + - Add a step that tests the new subcommand using the action + - Follow the naming pattern: `- name: ` + - Provide appropriate test values via the `with:` section + - This ensures the command works correctly in the Docker action context + +3. **README.md**: Update the usage documentation + - Add the new command to the commands list + - Provide usage examples + - Document any command-specific options or flags + +4. **src/commands/mod.ts**: Export the new command +5. **main.ts**: Register the new command with yargs + +**Example**: When adding the `sort` subcommand: + +- Added `- sort` to action.yml command options +- Added a "Sort" test step in docker-action job with test values +- Updated README with sort command documentation and examples diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 77d1aef..a5d245c 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -141,6 +141,11 @@ jobs: command: lte value: 1.2.3 compare-to: 1.2.3 + - name: Sort + uses: ./ + with: + command: sort + value: 2.0.0 1.0.0 3.0.0 docker: runs-on: ubuntu-latest diff --git a/action.yml b/action.yml index 3442530..cd14b48 100644 --- a/action.yml +++ b/action.yml @@ -20,6 +20,7 @@ inputs: - gte - lt - lte + - sort sub-command: type: choice description: "The kind of increment (major|minor|patch|none) for (get|inc) commands" From 87db27a601a5185139dce758454fb76a7cc7a6e0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Oct 2025 01:13:54 +0000 Subject: [PATCH 08/13] Remove special case logic for prerelease in none increment Co-authored-by: justinmchase <10974+justinmchase@users.noreply.github.com> --- README.md | 4 +-- VERSION | 2 +- setup/action.yml | 2 +- src/info.ts | 2 +- src/util/increment.ts | 81 +++---------------------------------------- 5 files changed, 10 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index 3a6f70e..dd01869 100644 --- a/README.md +++ b/README.md @@ -226,7 +226,7 @@ jobs: steps: - if: ${{ inputs.pre || github.event.release.prerelease }} name: Increment Pre-Release Version - uses: optum/semver-cli@1.0.6 + uses: optum/semver-cli@1.0.6-alpha.0 with: action: inc prerelease: pre${{ github.run_number }} @@ -234,7 +234,7 @@ jobs: - id: version name: Get Version - uses: optum/semver-cli@1.0.6 + uses: optum/semver-cli@1.0.6-alpha.0 - run: echo "The calculated ${{ steps.version.outputs.version }}" ``` diff --git a/VERSION b/VERSION index af0b7dd..e0d1525 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.6 +1.0.6-alpha.0 diff --git a/setup/action.yml b/setup/action.yml index 78700fb..190d176 100644 --- a/setup/action.yml +++ b/setup/action.yml @@ -6,7 +6,7 @@ branding: inputs: version: description: Version - default: 1.0.6 + default: 1.0.6-alpha.0 token: description: GitHub Token required: false diff --git a/src/info.ts b/src/info.ts index 7829060..fa84b1c 100644 --- a/src/info.ts +++ b/src/info.ts @@ -2,4 +2,4 @@ * The current version, kept in sync with VERSION * via the `inc` command. Output with the `--version` flag. */ -export const version = "1.0.6"; +export const version = "1.0.6-alpha.0"; diff --git a/src/util/increment.ts b/src/util/increment.ts index ec641f8..b0519ae 100644 --- a/src/util/increment.ts +++ b/src/util/increment.ts @@ -17,9 +17,6 @@ export interface IncrementOptions { export function increment(options: IncrementOptions) { const { kind, version, prerelease, build } = options; - const pre = bumpPrerelease(version.prerelease, prerelease); - // console.log({ version, pre}) - return { previous: version, current: (() => { @@ -37,82 +34,14 @@ export function increment(options: IncrementOptions) { ? inc(version, "prepatch", { prerelease, build }) : inc(version, "patch", { build }); case IncrementKind.None: - return prerelease - ? { - ...inc(version, "pre", { prerelease, build }), - prerelease: pre, - } - : { - ...version, - prerelease: version.prerelease ?? [], - build: build ? build.split(".") : version.build ?? [], - }; + return prerelease ? inc(version, "pre", { prerelease, build }) : { + ...version, + prerelease: version.prerelease ?? [], + build: build ? build.split(".") : version.build ?? [], + }; default: throw new Error(`Unknown increment kind: ${kind}`); } })(), }; } - -const NUMERIC_IDENTIFIER = "0|[1-9]\\d*"; -const NUMERIC_IDENTIFIER_REGEXP = new RegExp(`^${NUMERIC_IDENTIFIER}$`); -export function isValidNumber(value: unknown): value is number { - return ( - typeof value === "number" && - !Number.isNaN(value) && - (!Number.isFinite(value) || - (0 <= value && value <= Number.MAX_SAFE_INTEGER)) - ); -} - -export function parsePrerelease(prerelease: string) { - return prerelease - .split(".") - .filter(Boolean) - .map((id: string) => { - if (NUMERIC_IDENTIFIER_REGEXP.test(id)) { - const number = Number(id); - if (isValidNumber(number)) return number; - } - return id; - }); -} - -function bumpPrereleaseNumber(prerelease: ReadonlyArray = []) { - const values = [...prerelease]; - - let index = values.length; - while (index >= 0) { - const value = values[index]; - if (typeof value === "number") { - values[index] = value + 1; - break; - } - index -= 1; - } - // if no number was bumped - if (index === -1) values.push(0); - - return values; -} - -function bumpPrerelease( - prerelease: ReadonlyArray = [], - identifier: string | undefined, -) { - const prereleaseValues = bumpPrereleaseNumber(prerelease); - if (!identifier) return prereleaseValues; - - const identifierValues = parsePrerelease(identifier); - // 1.2.0-beta.1 bumps to 1.2.0-beta.2, - // 1.2.0-beta.foobar or 1.2.0-beta bumps to 1.2.0-beta.0 - if ( - prereleaseValues[0] !== identifierValues[0] || - isNaN(prereleaseValues[1] as number) || - !isNaN(identifierValues[1] as number) - ) { - return [identifierValues[0], identifierValues[1] ?? 0]; - } else { - return prereleaseValues; - } -} From 6b7c3d772af6fccabdadb5be938162dd1d43afd0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Oct 2025 01:14:35 +0000 Subject: [PATCH 09/13] Revert unintended test changes to version files Co-authored-by: justinmchase <10974+justinmchase@users.noreply.github.com> --- README.md | 4 ++-- VERSION | 2 +- setup/action.yml | 2 +- src/info.ts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index dd01869..3a6f70e 100644 --- a/README.md +++ b/README.md @@ -226,7 +226,7 @@ jobs: steps: - if: ${{ inputs.pre || github.event.release.prerelease }} name: Increment Pre-Release Version - uses: optum/semver-cli@1.0.6-alpha.0 + uses: optum/semver-cli@1.0.6 with: action: inc prerelease: pre${{ github.run_number }} @@ -234,7 +234,7 @@ jobs: - id: version name: Get Version - uses: optum/semver-cli@1.0.6-alpha.0 + uses: optum/semver-cli@1.0.6 - run: echo "The calculated ${{ steps.version.outputs.version }}" ``` diff --git a/VERSION b/VERSION index e0d1525..af0b7dd 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.6-alpha.0 +1.0.6 diff --git a/setup/action.yml b/setup/action.yml index 190d176..78700fb 100644 --- a/setup/action.yml +++ b/setup/action.yml @@ -6,7 +6,7 @@ branding: inputs: version: description: Version - default: 1.0.6-alpha.0 + default: 1.0.6 token: description: GitHub Token required: false diff --git a/src/info.ts b/src/info.ts index fa84b1c..7829060 100644 --- a/src/info.ts +++ b/src/info.ts @@ -2,4 +2,4 @@ * The current version, kept in sync with VERSION * via the `inc` command. Output with the `--version` flag. */ -export const version = "1.0.6-alpha.0"; +export const version = "1.0.6"; From 69abfed59f92fc5bfde0d58d0bfd581fe42e933f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Oct 2025 04:29:31 +0000 Subject: [PATCH 10/13] Update value input description to include sort command Co-authored-by: justinmchase <10974+justinmchase@users.noreply.github.com> --- action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/action.yml b/action.yml index cd14b48..155c5ca 100644 --- a/action.yml +++ b/action.yml @@ -40,7 +40,7 @@ inputs: required: false value: type: string - description: The Version (for set, parse, eq, cmp, gt, gte, lt, lte commands) + description: The Version (for set, parse, eq, cmp, gt, gte, lt, lte commands) or space-separated versions (for sort command) required: false compare-to: type: string From 84f1eefd27b75a08765e607b945ea7ee7aaaad1b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Oct 2025 04:39:01 +0000 Subject: [PATCH 11/13] Add tests for build metadata in None increment Co-authored-by: justinmchase <10974+justinmchase@users.noreply.github.com> --- src/util/increment.test.ts | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/util/increment.test.ts b/src/util/increment.test.ts index b47c52a..e5de21a 100644 --- a/src/util/increment.test.ts +++ b/src/util/increment.test.ts @@ -99,16 +99,40 @@ const testCases: (IncrementOptions & { expected: string })[] = [ prerelease: "rc", expected: "1.0.0-rc.0", }, + { + kind: IncrementKind.None, + version: parse("1.0.0"), + build: "build.123", + expected: "1.0.0+build.123", + }, + { + kind: IncrementKind.None, + version: parse("1.0.0+build.456"), + build: "build.789", + expected: "1.0.0+build.789", + }, + { + kind: IncrementKind.None, + version: parse("1.0.0-alpha.1"), + build: "build.123", + expected: "1.0.0-alpha.1+build.123", + }, + { + kind: IncrementKind.None, + version: parse("1.0.0-alpha.1+build.456"), + build: "build.789", + expected: "1.0.0-alpha.1+build.789", + }, ]; testCases.forEach((testCases, i) => { - const { kind, version, prerelease, expected } = testCases; + const { kind, version, prerelease, build, expected } = testCases; Deno.test({ name: `INC${i.toLocaleString(undefined, { minimumIntegerDigits: 2 })} - ${ format(version) - }:${kind}:${prerelease} -> ${expected}`, + }:${kind}:${prerelease}${build ? `:build=${build}` : ""} -> ${expected}`, fn: () => { - const result = increment({ kind, version, prerelease }); + const result = increment({ kind, version, prerelease, build }); assertEquals(format(result.current), expected); }, }); From bbdfbe548383bb2526645c0159bc33f098e7fcb6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Oct 2025 04:51:23 +0000 Subject: [PATCH 12/13] Split version strings by whitespace to handle space-separated versions Co-authored-by: justinmchase <10974+justinmchase@users.noreply.github.com> --- src/commands/sort.test.ts | 21 +++++++++++++++++++++ src/commands/sort.ts | 5 ++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/commands/sort.test.ts b/src/commands/sort.test.ts index b1c2adc..148da7a 100644 --- a/src/commands/sort.test.ts +++ b/src/commands/sort.test.ts @@ -204,4 +204,25 @@ describe("sort", () => { // Should not output anything and not throw assertSpyCalls(ctx.consoleLog, 0); }); + + it("SORT10 - handles space-separated versions in single string", async () => { + await sort.handler( + { + _: [], + versions: ["2.0.0 1.0.0", "3.0.0"], + asc: true, + desc: false, + } as unknown as Arguments & IContext, + ); + assertSpyCall(ctx.consoleLog, 0, { + args: ["1.0.0"], + }); + assertSpyCall(ctx.consoleLog, 1, { + args: ["2.0.0"], + }); + assertSpyCall(ctx.consoleLog, 2, { + args: ["3.0.0"], + }); + assertSpyCalls(ctx.consoleLog, 3); + }); }); diff --git a/src/commands/sort.ts b/src/commands/sort.ts index 43169aa..e9859a5 100644 --- a/src/commands/sort.ts +++ b/src/commands/sort.ts @@ -58,7 +58,10 @@ export const sort = { } else if (versions === null || versions === undefined) { versionList = []; } else if (Array.isArray(versions)) { - versionList = versions; + // Split each version string by whitespace to handle cases like "2.0.0 1.0.0" + versionList = versions.flatMap((v) => v.split(/\s+/)).filter((v) => + v.length > 0 + ); } else { throw new Error("Invalid versions argument"); } From 0210a312c626ee53974dd3a883b33ea0588886fe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Oct 2025 05:01:36 +0000 Subject: [PATCH 13/13] Remove --desc option, keep only --asc since descending is default Co-authored-by: justinmchase <10974+justinmchase@users.noreply.github.com> --- src/commands/sort.test.ts | 13 +------------ src/commands/sort.ts | 14 ++------------ 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/src/commands/sort.test.ts b/src/commands/sort.test.ts index 148da7a..b691637 100644 --- a/src/commands/sort.test.ts +++ b/src/commands/sort.test.ts @@ -16,7 +16,6 @@ describe("sort", () => { _: [], versions: ["2.0.0", "1.0.0", "3.0.0"], asc: false, - desc: false, } as unknown as Arguments & IContext, ); assertSpyCall(ctx.consoleLog, 0, { @@ -37,7 +36,6 @@ describe("sort", () => { _: [], versions: ["2.0.0", "1.0.0", "3.0.0"], asc: true, - desc: false, } as unknown as Arguments & IContext, ); assertSpyCall(ctx.consoleLog, 0, { @@ -52,13 +50,12 @@ describe("sort", () => { assertSpyCalls(ctx.consoleLog, 3); }); - it("SORT02 - sorts versions in descending order with -d flag", async () => { + it("SORT02 - sorts versions in descending order by default", async () => { await sort.handler( { _: [], versions: ["1.0.0", "3.0.0", "2.0.0"], asc: false, - desc: true, } as unknown as Arguments & IContext, ); assertSpyCall(ctx.consoleLog, 0, { @@ -79,7 +76,6 @@ describe("sort", () => { _: [], versions: ["1.0.0", "1.0.0-alpha", "1.0.0-beta"], asc: true, - desc: false, } as unknown as Arguments & IContext, ); assertSpyCall(ctx.consoleLog, 0, { @@ -100,7 +96,6 @@ describe("sort", () => { _: [], versions: ["1.0.0+build1", "1.0.0+build2", "1.0.0"], asc: true, - desc: false, } as unknown as Arguments & IContext, ); // Build metadata doesn't affect version precedence (semver spec section 10) @@ -130,7 +125,6 @@ describe("sort", () => { _: [], versions: ["1.0.0", "2.1.0", "2.0.0", "1.1.0", "1.0.1"], asc: true, - desc: false, } as unknown as Arguments & IContext, ); assertSpyCall(ctx.consoleLog, 0, { @@ -157,7 +151,6 @@ describe("sort", () => { _: [], versions: ["1.0.0"], asc: false, - desc: false, } as unknown as Arguments & IContext, ); assertSpyCall(ctx.consoleLog, 0, { @@ -172,7 +165,6 @@ describe("sort", () => { _: [], versions: [], asc: false, - desc: false, } as unknown as Arguments & IContext, ); // Should not output anything and not throw @@ -185,7 +177,6 @@ describe("sort", () => { _: [], versions: null, asc: false, - desc: false, } as unknown as Arguments & IContext, ); // Should not output anything and not throw @@ -198,7 +189,6 @@ describe("sort", () => { _: [], versions: undefined, asc: false, - desc: false, } as unknown as Arguments & IContext, ); // Should not output anything and not throw @@ -211,7 +201,6 @@ describe("sort", () => { _: [], versions: ["2.0.0 1.0.0", "3.0.0"], asc: true, - desc: false, } as unknown as Arguments & IContext, ); assertSpyCall(ctx.consoleLog, 0, { diff --git a/src/commands/sort.ts b/src/commands/sort.ts index e9859a5..05e6d3e 100644 --- a/src/commands/sort.ts +++ b/src/commands/sort.ts @@ -18,16 +18,10 @@ export const sort = { type: "boolean", description: "Sort in ascending order", default: false, - }) - .option("desc", { - alias: "d", - type: "boolean", - description: "Sort in descending order (default)", - default: false, }); }, async handler(args: Arguments & IContext) { - const { versions, asc, desc } = args; + const { versions, asc } = args; let versionList: string[] = []; // Check if we should read from stdin @@ -83,12 +77,8 @@ export const sort = { // Sort versions using semver compare parsedVersions.sort((a, b) => compare(a.parsed, b.parsed)); - // Determine sort order // Default is descending unless -a/--asc is explicitly set - const isDescending = desc || (!asc && !desc); - - // Reverse if descending - if (isDescending) { + if (!asc) { parsedVersions.reverse(); }