diff --git a/README.md b/README.md index 27a0a06..e5ea53f 100644 --- a/README.md +++ b/README.md @@ -125,13 +125,23 @@ linear-release update --stage="in review" --release-version="1.2.0" ### CLI Options -| Option | Commands | Description | -| ------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `--name` | `sync` | Custom release name. Defaults to short commit hash. | -| `--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 | +| Option | Commands | Description | +| ------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `--name` | `sync` | Custom release name. Continuous pipelines: used when creating a release. Scheduled pipelines: used only when `sync` creates a new release; existing release names are preserved. 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 | + +### Command Targeting + +| Command | With `--release-version` | Without `--release-version` | +| ---------- | -------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | +| `sync` | Targets matching version or creates a release for that version | Continuous pipelines create a release with short SHA name/version. Scheduled pipelines use the current started/planned flow. | +| `update` | Updates that exact release version | Updates latest started release, or latest planned release if no started release exists | +| `complete` | Completes that exact release version | Completes latest started release | + +For scheduled pipelines, prefer always passing `--release-version` in CI, especially when releases overlap. ### JSON Output @@ -168,6 +178,13 @@ Path patterns can also be configured in your pipeline settings in Linear. If bot 4. **Detects pull request numbers** from commit messages (e.g., `Merge pull request #42`) 5. **Syncs to Linear** creating or updating the release with linked issues +## Troubleshooting + +- **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. + ## License Licensed under the [MIT License](./LICENSE). diff --git a/src/args.test.ts b/src/args.test.ts index f36c512..874a8d7 100644 --- a/src/args.test.ts +++ b/src/args.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { parseCLIArgs } from "./args"; +import { getCLIWarnings, parseCLIArgs } from "./args"; describe("parseCLIArgs", () => { it("defaults command to sync when no positional given", () => { @@ -83,4 +83,19 @@ describe("parseCLIArgs", () => { it("throws on unknown flags (strict mode)", () => { expect(() => parseCLIArgs(["--unknown-flag"])).toThrow(); }); + + 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"']); + }); + + it("returns 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"']); + }); + + it("returns no warning when --name is used with sync", () => { + const result = parseCLIArgs(["sync", "--name", "Release 1.2.0"]); + expect(getCLIWarnings(result)).toEqual([]); + }); }); diff --git a/src/args.ts b/src/args.ts index 4881d43..224a89a 100644 --- a/src/args.ts +++ b/src/args.ts @@ -1,6 +1,15 @@ import { parseArgs } from "node:util"; -export function parseCLIArgs(argv: string[]) { +export type ParsedCLIArgs = { + command: string; + releaseName?: string; + releaseVersion?: string; + stageName?: string; + includePaths: string[]; + jsonOutput: boolean; +}; + +export function parseCLIArgs(argv: string[]): ParsedCLIArgs { const { values, positionals } = parseArgs({ args: argv, options: { @@ -28,3 +37,13 @@ export function parseCLIArgs(argv: string[]) { jsonOutput: values.json ?? false, }; } + +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"`); + } + + return warnings; +} diff --git a/src/index.ts b/src/index.ts index f4edea7..4471b49 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,7 +15,7 @@ import { PullRequestSource, RepoInfo, } from "./types"; -import { parseCLIArgs } from "./args"; +import { getCLIWarnings, parseCLIArgs } from "./args"; import { log, setStderr } from "./log"; import { pluralize } from "./util"; import { buildUserAgent } from "./user-agent"; @@ -77,6 +77,7 @@ try { process.exit(1); } const { command, releaseName, releaseVersion, stageName, includePaths, jsonOutput } = parsedArgs; +const cliWarnings = getCLIWarnings(parsedArgs); if (jsonOutput) { setStderr(true); } @@ -85,11 +86,16 @@ const logEnvironmentSummary = () => { log("Using access key authentication"); if (releaseName) { - log(`Using custom release name: ${releaseName}`); + if (command === "sync") { + log(`Using custom release name: ${releaseName}`); + } } if (releaseVersion) { log(`Using custom release version: ${releaseVersion}`); } + for (const warning of cliWarnings) { + log(`Warning: ${warning}`); + } log(`Running in ${process.env.NODE_ENV === "development" ? "development" : "production"} mode`); };