Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/args.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,31 @@ describe("parseCLIArgs", () => {
const result = parseCLIArgs(["sync", "--name", "Release 1.2.0"]);
expect(getCLIWarnings(result)).toEqual([]);
});

it("defaults --timeout to 60 seconds", () => {
const result = parseCLIArgs([]);
expect(result.timeoutSeconds).toBe(60);
});

it("parses --timeout with space syntax", () => {
const result = parseCLIArgs(["--timeout", "120"]);
expect(result.timeoutSeconds).toBe(120);
});

it("parses --timeout with = syntax", () => {
const result = parseCLIArgs(["--timeout=30"]);
expect(result.timeoutSeconds).toBe(30);
});

it("throws on non-numeric --timeout", () => {
expect(() => parseCLIArgs(["--timeout", "abc"])).toThrow('Invalid --timeout value: "abc"');
});

it("throws on zero --timeout", () => {
expect(() => parseCLIArgs(["--timeout", "0"])).toThrow('Invalid --timeout value: "0"');
});

it("throws on negative --timeout", () => {
expect(() => parseCLIArgs(["--timeout=-5"])).toThrow('Invalid --timeout value: "-5"');
});
});
13 changes: 13 additions & 0 deletions src/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type ParsedCLIArgs = {
stageName?: string;
includePaths: string[];
jsonOutput: boolean;
timeoutSeconds: number;
};

export function parseCLIArgs(argv: string[]): ParsedCLIArgs {
Expand All @@ -18,11 +19,22 @@ export function parseCLIArgs(argv: string[]): ParsedCLIArgs {
stage: { type: "string" },
"include-paths": { type: "string" },
json: { type: "boolean", default: false },
timeout: { type: "string" },
},
allowPositionals: true,
strict: true,
});

const DEFAULT_TIMEOUT_SECONDS = 60;
let timeoutSeconds = DEFAULT_TIMEOUT_SECONDS;
if (values.timeout !== undefined) {
const parsed = Number(values.timeout);
if (Number.isNaN(parsed) || parsed <= 0) {
throw new Error(`Invalid --timeout value: "${values.timeout}". Must be a positive number of seconds.`);
}
timeoutSeconds = parsed;
}

return {
command: positionals[0] || "sync",
releaseName: values.name,
Expand All @@ -35,6 +47,7 @@ export function parseCLIArgs(argv: string[]): ParsedCLIArgs {
.filter((p) => p.length > 0)
: [],
jsonOutput: values.json ?? false,
timeoutSeconds,
};
}

Expand Down
22 changes: 18 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ Options:
--release-version=<version> Release version identifier
--stage=<stage> Deployment stage (required for update)
--include-paths=<paths> Filter commits by file paths (comma-separated globs)
--timeout=<seconds> Abort if the operation exceeds this duration (default: 60)
--json Output result as JSON
-v, --version Show version number
-h, --help Show this help message
Expand Down Expand Up @@ -77,7 +78,7 @@ try {
console.error("Run linear-release --help for usage information.");
process.exit(1);
}
const { command, releaseName, releaseVersion, stageName, includePaths, jsonOutput } = parsedArgs;
const { command, releaseName, releaseVersion, stageName, includePaths, jsonOutput, timeoutSeconds } = parsedArgs;
const cliWarnings = getCLIWarnings(parsedArgs);
if (jsonOutput) {
setStderr(true);
Expand Down Expand Up @@ -602,7 +603,20 @@ async function main() {
}
}

main().catch((error) => {
console.error(`Error: ${error.message}`);
const timeoutMs = timeoutSeconds * 1000;
const timeout = setTimeout(() => {
console.error(
`Error: Operation timed out after ${timeoutSeconds}s. This may indicate a large repository or slow network. Use --timeout=<seconds> to increase the limit.`,
);
process.exit(1);
});
}, timeoutMs);
timeout.unref();

main()
.catch((error) => {
console.error(`Error: ${error.message}`);
process.exit(1);
})
.finally(() => {
clearTimeout(timeout);
});