From a1f86829d9dae3dc45b2c7d56dfad30f8fb55714 Mon Sep 17 00:00:00 2001 From: Romain Cascino Date: Wed, 6 May 2026 10:41:38 +0100 Subject: [PATCH 1/2] Ensure git is available --- src/git.test.ts | 33 +++++++++++++++++++++++++++++++++ src/git.ts | 29 +++++++++++++++++++++++++++++ src/index.ts | 10 +++++++++- 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/src/git.test.ts b/src/git.test.ts index a95a14a..b9e8083 100644 --- a/src/git.test.ts +++ b/src/git.test.ts @@ -4,6 +4,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { + assertGitAvailable, buildPathspecArgs, ensureCommitAvailable, extractBranchName, @@ -745,3 +746,35 @@ describe("merge commit handling", () => { }); }); }); + +describe("assertGitAvailable", () => { + it("succeeds inside a git repository with git on PATH", () => { + const repo = createTempRepo(); + try { + expect(() => assertGitAvailable(repo.cwd)).not.toThrow(); + } finally { + rmSync(repo.cwd, { recursive: true, force: true }); + } + }); + + it("throws when not inside a git repository", () => { + const cwd = mkdtempSync(join(tmpdir(), "linear-release-no-repo-")); + try { + expect(() => assertGitAvailable(cwd)).toThrow(/git repository/); + } finally { + rmSync(cwd, { recursive: true, force: true }); + } + }); + + it("throws with a PATH hint when the git binary is missing", () => { + const repo = createTempRepo(); + const originalPath = process.env.PATH; + process.env.PATH = "/nonexistent-linear-release-test-dir"; + try { + expect(() => assertGitAvailable(repo.cwd)).toThrow(/git.*on PATH/); + } finally { + process.env.PATH = originalPath; + rmSync(repo.cwd, { recursive: true, force: true }); + } + }); +}); diff --git a/src/git.ts b/src/git.ts index 87d927c..81ad872 100644 --- a/src/git.ts +++ b/src/git.ts @@ -30,6 +30,35 @@ export function buildPathspecArgs(includePaths: string[] | null): string { return `-- ${patterns.join(" ")}`; } +/** + * Verifies the runtime environment can satisfy the CLI's git requirements: + * 1. The `git` binary is on PATH. + * 2. The current working directory is inside a git repository. + * + * Call once at startup, before any other git operations, so cryptic + * downstream failures (ENOENT, "not a git repository") become useful + * diagnostics for CI users. + */ +export function assertGitAvailable(cwd: string = process.cwd()): void { + try { + execSync("git --version", { + cwd, + stdio: ["ignore", "ignore", "pipe"], + }); + } catch { + throw new Error("linear-release requires `git` on PATH, but `git --version` failed. Install git in your CI image."); + } + + try { + execSync("git rev-parse --is-inside-work-tree", { + cwd, + stdio: ["ignore", "ignore", "pipe"], + }); + } catch { + throw new Error("linear-release must run inside a git repository, but no `.git` directory was found."); + } +} + export function getCurrentGitInfo(cwd: string = process.cwd()): GitInfo { try { const branch = execSync("git rev-parse --abbrev-ref HEAD", { diff --git a/src/index.ts b/src/index.ts index a73b294..7d23b73 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,11 @@ import { LinearClient, LinearClientOptions } from "@linear/sdk"; -import { ensureCommitAvailable, getCommitContextsBetweenShas, getCurrentGitInfo, getRepoInfo } from "./git"; +import { + assertGitAvailable, + ensureCommitAvailable, + getCommitContextsBetweenShas, + getCurrentGitInfo, + getRepoInfo, +} from "./git"; import { scanCommits } from "./scan"; import { Release, @@ -524,6 +530,8 @@ async function updateReleaseByPipeline(options: { } async function main() { + assertGitAvailable(); + let result: { release: { id: string; name: string; version?: string; url?: string }; } | null = null; From 624af08c5c7709056d56ecbc83d1ca6886b0960a Mon Sep 17 00:00:00 2001 From: Romain Cascino <6696530+RomainCscn@users.noreply.github.com> Date: Wed, 6 May 2026 10:40:11 +0000 Subject: [PATCH 2/2] Update error message to not assume CI --- src/git.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/git.ts b/src/git.ts index 81ad872..aab13ec 100644 --- a/src/git.ts +++ b/src/git.ts @@ -46,7 +46,9 @@ export function assertGitAvailable(cwd: string = process.cwd()): void { stdio: ["ignore", "ignore", "pipe"], }); } catch { - throw new Error("linear-release requires `git` on PATH, but `git --version` failed. Install git in your CI image."); + throw new Error( + "linear-release requires `git` on PATH, but `git --version` failed. Please make sure that git is installed and available.", + ); } try {