From 279b96868a63420234be28e9a4a2ebbab804d431 Mon Sep 17 00:00:00 2001 From: Tom Moor <380914+tommoor@users.noreply.github.com> Date: Fri, 1 May 2026 13:53:35 +0000 Subject: [PATCH 1/2] Allow --name on complete command Pass the custom release name through the releaseCompleteByAccessKey mutation so CI/CD pipelines can set a release name at completion time. --- README.md | 25 ++++++++++++++----------- src/args.test.ts | 8 +++++--- src/args.ts | 4 ++-- src/index.ts | 14 ++++++++++---- 4 files changed, 31 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index db1f0a6..3ce7afe 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,9 @@ linear-release complete # Completes the release with the specified version linear-release complete --release-version="1.2.0" + +# Sets a custom name when completing the release +linear-release complete --name="Release 1.2.0" ``` ### `update` @@ -143,16 +146,16 @@ linear-release update --stage="in review" --release-version="1.2.0" ### CLI Options -| Option | Commands | Description | -| ------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `--name` | `sync` | Custom release name. Whenever `sync` is called with `--name`, the value is applied to the targeted release — both newly created releases and existing ones get the provided name. Ignored (with warning) for `complete` and `update`. | -| `--release-version` | `sync`, `complete`, `update` | Release version identifier. For `sync`, defaults to short commit hash. For `complete` and `update`, if omitted, targets the most recent started release. | -| `--stage` | `update` | Target deployment stage (required for `update`) | -| `--include-paths` | `sync` | Filter commits by changed file paths | -| `--json` | `sync`, `complete`, `update` | Output result as JSON | -| `--quiet` | `sync`, `complete`, `update` | Only print errors | -| `--verbose` | `sync`, `complete`, `update` | Print detailed progress including debug diagnostics | -| `--timeout` | `sync`, `complete`, `update` | Max duration in seconds before aborting (default: 60) | +| Option | Commands | Description | +| ------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `--name` | `sync`, `complete` | Custom release name. For `sync`, the value is applied to the targeted release — both newly created releases and existing ones get the provided name. For `complete`, sets the name on the release being completed. Ignored (with warning) for `update`. | +| `--release-version` | `sync`, `complete`, `update` | Release version identifier. For `sync`, defaults to short commit hash. For `complete` and `update`, if omitted, targets the most recent started release. | +| `--stage` | `update` | Target deployment stage (required for `update`) | +| `--include-paths` | `sync` | Filter commits by changed file paths | +| `--json` | `sync`, `complete`, `update` | Output result as JSON | +| `--quiet` | `sync`, `complete`, `update` | Only print errors | +| `--verbose` | `sync`, `complete`, `update` | Print detailed progress including debug diagnostics | +| `--timeout` | `sync`, `complete`, `update` | Max duration in seconds before aborting (default: 60) | ### Command Targeting @@ -216,7 +219,7 @@ Path patterns can also be configured in your pipeline settings in Linear. If bot - **Unexpected release was updated/completed**: pass `--release-version` explicitly so the command does not target the latest started/planned release. - **No release created by `sync`**: if no commits match the computed range (or path filters), `sync` returns `{"release":null}`. - **Stage update fails**: verify stage name exactly. If stage names normalize to the same value, use the exact stage name to avoid ambiguity. -- **`--name` seems ignored**: `--name` only applies to `sync`; `complete` and `update` ignore it and print a warning. +- **`--name` seems ignored**: `--name` applies to `sync` and `complete`; `update` ignores it and prints a warning. - **Operation timed out**: the CLI aborts after 60 seconds by default. For large repositories or slow networks, increase the limit with `--timeout=120`. ## License diff --git a/src/args.test.ts b/src/args.test.ts index 904b154..bcc4062 100644 --- a/src/args.test.ts +++ b/src/args.test.ts @@ -87,12 +87,14 @@ describe("parseCLIArgs", () => { it("returns warning when --name is used with update", () => { const result = parseCLIArgs(["update", "--name", "Release 1.2.0"]); - expect(getCLIWarnings(result)).toEqual(['--name is ignored for "update" command; it only applies to "sync"']); + expect(getCLIWarnings(result)).toEqual([ + '--name is ignored for "update" command; it only applies to "sync" and "complete"', + ]); }); - it("returns warning when --name is used with complete", () => { + it("returns no warning when --name is used with complete", () => { const result = parseCLIArgs(["complete", "--name", "Release 1.2.0"]); - expect(getCLIWarnings(result)).toEqual(['--name is ignored for "complete" command; it only applies to "sync"']); + expect(getCLIWarnings(result)).toEqual([]); }); it("returns no warning when --name is used with sync", () => { diff --git a/src/args.ts b/src/args.ts index 9c369e8..3fe9afd 100644 --- a/src/args.ts +++ b/src/args.ts @@ -67,8 +67,8 @@ export function parseCLIArgs(argv: string[]): ParsedCLIArgs { export function getCLIWarnings(args: ParsedCLIArgs): string[] { const warnings: string[] = []; - if (args.releaseName && args.command !== "sync") { - warnings.push(`--name is ignored for "${args.command}" command; it only applies to "sync"`); + if (args.releaseName && args.command !== "sync" && args.command !== "complete") { + warnings.push(`--name is ignored for "${args.command}" command; it only applies to "sync" and "complete"`); } return warnings; diff --git a/src/index.ts b/src/index.ts index ac1fed7..3a8efab 100644 --- a/src/index.ts +++ b/src/index.ts @@ -38,7 +38,7 @@ Commands: update Update the deployment stage of a release Options: - --name= Custom release name (sync only) + --name= Custom release name (sync and complete) --release-version= Release version identifier --stage= Deployment stage (required for update) --include-paths= Filter commits by file paths (comma-separated globs) @@ -87,7 +87,7 @@ if (jsonOutput) { const logEnvironmentSummary = () => { if (releaseName) { - if (command === "sync") { + if (command === "sync" || command === "complete") { info(`Using custom release name: ${releaseName}`); } } @@ -246,6 +246,7 @@ async function completeCommand(): Promise<{ const commitSha = currentCommit.commit; const result = await completeRelease({ + name: releaseName, version: releaseVersion, commitSha, }); @@ -430,11 +431,15 @@ async function syncRelease( return response.data.releaseSyncByAccessKey.release; } -async function completeRelease(options: { version?: string | null; commitSha?: string | null }): Promise<{ +async function completeRelease(options: { + name?: string | null; + version?: string | null; + commitSha?: string | null; +}): Promise<{ success: boolean; release: { id: string; name: string; version?: string; url?: string } | null; }> { - const { version, commitSha } = options; + const { name, version, commitSha } = options; const response = await apiRequest( ` @@ -452,6 +457,7 @@ async function completeRelease(options: { version?: string | null; commitSha?: s `, { input: { + name, version, commitSha, }, From 6619e49808c63c091ba863ecc8eb754d0a25d976 Mon Sep 17 00:00:00 2001 From: Tom Moor <380914+tommoor@users.noreply.github.com> Date: Mon, 4 May 2026 14:05:55 +0000 Subject: [PATCH 2/2] Allow --name on update command Co-authored-by: linear[bot] <44709815+linear[bot]@users.noreply.github.com> --- README.md | 24 +++++++++++++----------- src/args.test.ts | 6 ++---- src/args.ts | 10 ++-------- src/index.ts | 18 +++++++++++------- 4 files changed, 28 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 3ce7afe..035f34f 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,9 @@ linear-release update --stage="in review" # Updates the release with the specified version linear-release update --stage="in review" --release-version="1.2.0" + +# Sets a custom name when updating the release +linear-release update --stage="in review" --name="Release 1.2.0" ``` ## Configuration @@ -146,16 +149,16 @@ linear-release update --stage="in review" --release-version="1.2.0" ### CLI Options -| Option | Commands | Description | -| ------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `--name` | `sync`, `complete` | Custom release name. For `sync`, the value is applied to the targeted release — both newly created releases and existing ones get the provided name. For `complete`, sets the name on the release being completed. Ignored (with warning) for `update`. | -| `--release-version` | `sync`, `complete`, `update` | Release version identifier. For `sync`, defaults to short commit hash. For `complete` and `update`, if omitted, targets the most recent started release. | -| `--stage` | `update` | Target deployment stage (required for `update`) | -| `--include-paths` | `sync` | Filter commits by changed file paths | -| `--json` | `sync`, `complete`, `update` | Output result as JSON | -| `--quiet` | `sync`, `complete`, `update` | Only print errors | -| `--verbose` | `sync`, `complete`, `update` | Print detailed progress including debug diagnostics | -| `--timeout` | `sync`, `complete`, `update` | Max duration in seconds before aborting (default: 60) | +| Option | Commands | Description | +| ------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `--name` | `sync`, `complete`, `update` | Custom release name. For `sync`, the value is applied to the targeted release — both newly created releases and existing ones get the provided name. For `complete` and `update`, sets the name on the targeted release. | +| `--release-version` | `sync`, `complete`, `update` | Release version identifier. For `sync`, defaults to short commit hash. For `complete` and `update`, if omitted, targets the most recent started release. | +| `--stage` | `update` | Target deployment stage (required for `update`) | +| `--include-paths` | `sync` | Filter commits by changed file paths | +| `--json` | `sync`, `complete`, `update` | Output result as JSON | +| `--quiet` | `sync`, `complete`, `update` | Only print errors | +| `--verbose` | `sync`, `complete`, `update` | Print detailed progress including debug diagnostics | +| `--timeout` | `sync`, `complete`, `update` | Max duration in seconds before aborting (default: 60) | ### Command Targeting @@ -219,7 +222,6 @@ Path patterns can also be configured in your pipeline settings in Linear. If bot - **Unexpected release was updated/completed**: pass `--release-version` explicitly so the command does not target the latest started/planned release. - **No release created by `sync`**: if no commits match the computed range (or path filters), `sync` returns `{"release":null}`. - **Stage update fails**: verify stage name exactly. If stage names normalize to the same value, use the exact stage name to avoid ambiguity. -- **`--name` seems ignored**: `--name` applies to `sync` and `complete`; `update` ignores it and prints a warning. - **Operation timed out**: the CLI aborts after 60 seconds by default. For large repositories or slow networks, increase the limit with `--timeout=120`. ## License diff --git a/src/args.test.ts b/src/args.test.ts index bcc4062..21163b1 100644 --- a/src/args.test.ts +++ b/src/args.test.ts @@ -85,11 +85,9 @@ describe("parseCLIArgs", () => { expect(() => parseCLIArgs(["--unknown-flag"])).toThrow(); }); - it("returns warning when --name is used with update", () => { + it("returns no warning when --name is used with update", () => { const result = parseCLIArgs(["update", "--name", "Release 1.2.0"]); - expect(getCLIWarnings(result)).toEqual([ - '--name is ignored for "update" command; it only applies to "sync" and "complete"', - ]); + expect(getCLIWarnings(result)).toEqual([]); }); it("returns no warning when --name is used with complete", () => { diff --git a/src/args.ts b/src/args.ts index 3fe9afd..86ff11a 100644 --- a/src/args.ts +++ b/src/args.ts @@ -64,12 +64,6 @@ export function parseCLIArgs(argv: string[]): ParsedCLIArgs { }; } -export function getCLIWarnings(args: ParsedCLIArgs): string[] { - const warnings: string[] = []; - - if (args.releaseName && args.command !== "sync" && args.command !== "complete") { - warnings.push(`--name is ignored for "${args.command}" command; it only applies to "sync" and "complete"`); - } - - return warnings; +export function getCLIWarnings(_args: ParsedCLIArgs): string[] { + return []; } diff --git a/src/index.ts b/src/index.ts index 3a8efab..a73b294 100644 --- a/src/index.ts +++ b/src/index.ts @@ -38,7 +38,7 @@ Commands: update Update the deployment stage of a release Options: - --name= Custom release name (sync and complete) + --name= Custom release name --release-version= Release version identifier --stage= Deployment stage (required for update) --include-paths= Filter commits by file paths (comma-separated globs) @@ -87,9 +87,7 @@ if (jsonOutput) { const logEnvironmentSummary = () => { if (releaseName) { - if (command === "sync" || command === "complete") { - info(`Using custom release name: ${releaseName}`); - } + info(`Using custom release name: ${releaseName}`); } if (releaseVersion) { info(`Using custom release version: ${releaseVersion}`); @@ -284,6 +282,7 @@ async function updateCommand(): Promise<{ result = await updateReleaseByPipeline({ stage: stageName, version: releaseVersion, + name: releaseName, }); } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; @@ -467,7 +466,11 @@ async function completeRelease(options: { return response.data.releaseCompleteByAccessKey; } -async function updateReleaseByPipeline(options: { stage?: string; version?: string | null }): Promise<{ +async function updateReleaseByPipeline(options: { + stage?: string; + version?: string | null; + name?: string | null; +}): Promise<{ success: boolean; release: { id: string; @@ -477,11 +480,12 @@ async function updateReleaseByPipeline(options: { stage?: string; version?: stri stageName: string; } | null; }> { - const { stage, version } = options; + const { stage, version, name } = options; const versionInput = version ? `, version: "${version}"` : ""; const stageInput = stage ? `, stage: "${stage}"` : ""; + const nameInput = name ? `, name: "${name}"` : ""; - const inputParts = [versionInput, stageInput] + const inputParts = [versionInput, stageInput, nameInput] .filter(Boolean) .map((s) => s.slice(2)) .join(", ");