diff --git a/AGENTS.md b/AGENTS.md index dc14715..784b475 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -128,8 +128,8 @@ to run non-interactively. - static: `--static-dir `, `--single-page-app` - `--framework-preset `, `--install-command`, `--build-command`, `--pre-deploy-command`, `--app-directory ` -- `--region `, `--build-timeout `, - `--build-memory-limit ` +- `--region `, `--build-timeout ` (minutes, or e.g. + `10m`/`600s`), `--build-memory-limit ` ### `env` diff --git a/deploy/create/flow.ts b/deploy/create/flow.ts index e707e33..612dbd1 100644 --- a/deploy/create/flow.ts +++ b/deploy/create/flow.ts @@ -22,6 +22,28 @@ export const AVAILABLE_BUILD_TIMEOUTS = [5, 10, 15, 20, 25, 30]; export const AVAILABLE_BUILD_MEMORY_LIMITS = [1024, 2048, 3072, 4096]; export const REGIONS = ["us", "eu", "global"]; +const DURATION_UNIT_MINUTES: Record = { + s: 1 / 60, + m: 1, + h: 60, +}; + +/** + * Parses `--build-timeout`: a number of minutes (`10`, as the flag has always + * taken), or a whole number with a unit suffix as in deno.json (`"600s"`, + * `"10m"`). Returns the timeout in minutes if it is one of the available + * steps (5 to 30 minutes), and null otherwise. + */ +export function parseBuildTimeoutFlag(value: string): number | null { + const match = /^([1-9][0-9]*)([smh])$/.exec(value); + // Without a unit, convert as the flag's former numeric type did, so that + // spellings such as `10.0` keep working. + const minutes = match === null + ? Number(value) + : Number(match[1]) * DURATION_UNIT_MINUTES[match[2]]; + return AVAILABLE_BUILD_TIMEOUTS.includes(minutes) ? minutes : null; +} + /** * The build timeout a detected build config asks for (deno.json * `deploy.buildTimeout`), as the largest available step not above it or diff --git a/deploy/create/mod.ts b/deploy/create/mod.ts index a9fd3ed..54e8216 100644 --- a/deploy/create/mod.ts +++ b/deploy/create/mod.ts @@ -6,6 +6,7 @@ import { AVAILABLE_BUILD_TIMEOUTS, createFlow, detectedBuildTimeoutForOrg, + parseBuildTimeoutFlag, REGIONS, renderBuildConfig, } from "./flow.ts"; @@ -126,21 +127,24 @@ export const createCommand = new Command() "For static configuration", ) .option( - "--build-timeout ", - `The build timeout in minutes. One of ${ + "--build-timeout ", + `The build timeout, as minutes or with a unit suffix (e.g. 10, 10m, 600s). One of ${ AVAILABLE_BUILD_TIMEOUTS.join(", ") - }. Defaults to deploy.buildTimeout from a detected deno.json, or ${ + } minutes. Defaults to deploy.buildTimeout from a detected deno.json, or ${ AVAILABLE_BUILD_TIMEOUTS[0] - }`, + } minutes`, { - value(value: number) { - if (AVAILABLE_BUILD_TIMEOUTS.includes(value)) { - return value; + value(value: string) { + const minutes = parseBuildTimeoutFlag(value); + if (minutes !== null) { + return minutes; } else { throw new ValidationError( `Invalid build timeout: ${value}. Valid values are ${ AVAILABLE_BUILD_TIMEOUTS.join(", ") - }.`, + } minutes, e.g. ${AVAILABLE_BUILD_TIMEOUTS[1]} or ${ + AVAILABLE_BUILD_TIMEOUTS[1] + }m.`, ); } }, diff --git a/tests/build_timeout.test.ts b/tests/build_timeout.test.ts index c60ebee..384c93c 100644 --- a/tests/build_timeout.test.ts +++ b/tests/build_timeout.test.ts @@ -3,6 +3,7 @@ import type { TRPCClient } from "../auth.ts"; import { detectedBuildTimeout, detectedBuildTimeoutForOrg, + parseBuildTimeoutFlag, } from "../deploy/create/flow.ts"; Deno.test("detectedBuildTimeout: undefined without a deno.json build timeout", () => { @@ -87,3 +88,18 @@ Deno.test("detectedBuildTimeoutForOrg: leaves an unknown org uncapped for the co 20, ); }); + +Deno.test("parseBuildTimeoutFlag: minutes, or a duration with a unit suffix", () => { + assertEquals(parseBuildTimeoutFlag("10"), 10); + assertEquals(parseBuildTimeoutFlag("10m"), 10); + assertEquals(parseBuildTimeoutFlag("600s"), 10); + assertEquals(parseBuildTimeoutFlag("30m"), 30); + // A unitless value converts like the flag's former numeric type. + assertEquals(parseBuildTimeoutFlag("10.0"), 10); + assertEquals(parseBuildTimeoutFlag("05"), 5); + for ( + const value of ["7", "90s", "1h", "0", "10min", "1.5m", "", "-5", "05m"] + ) { + assertEquals(parseBuildTimeoutFlag(value), null, value); + } +});