@amerged/ohmyhost-cli 0.1.40 → 0.1.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,15 @@
1
1
  import { isUserSecretName } from "@ohmyhost/contracts/secret-names";
2
+ import {
3
+ DATABASE_PAGE_MAX_ITEMS,
4
+ parseDatabaseCursor,
5
+ parseDatabaseDeliveryId,
6
+ parseDatabaseListLimit,
7
+ parseDatabaseScheduleName,
8
+ parseDatabaseUsageWindow,
9
+ parseDatabaseWebhookCreateRequest,
10
+ parseDatabaseWebhookId,
11
+ type DatabaseWebhookEventType,
12
+ } from "@ohmyhost/contracts/database-events";
2
13
  import {
3
14
  databaseSizeForMemory,
4
15
  type DatabaseComputeSelection,
@@ -199,8 +210,6 @@ export type ProductCliCommand =
199
210
  | {
200
211
  readonly kind: "credits-usage";
201
212
  readonly organizationId: string;
202
- readonly month: string;
203
- readonly cursor?: string;
204
213
  readonly credentialStore: "native";
205
214
  }
206
215
  | {
@@ -395,6 +404,124 @@ export type ProductCliCommand =
395
404
  readonly yes: true;
396
405
  readonly credentialStore: "native";
397
406
  }
407
+ | {
408
+ readonly kind: "database-webhook-create";
409
+ readonly projectId: string;
410
+ readonly environment: "dev" | "prod";
411
+ readonly url: string;
412
+ readonly eventTypes: readonly DatabaseWebhookEventType[];
413
+ readonly headersFile: string | null;
414
+ readonly idempotencyKey: string;
415
+ readonly yes: true;
416
+ readonly credentialStore: "native";
417
+ }
418
+ | {
419
+ readonly kind: "database-webhook-list";
420
+ readonly projectId: string;
421
+ readonly environment: "dev" | "prod";
422
+ readonly credentialStore: "native";
423
+ }
424
+ | {
425
+ readonly kind: "database-webhook-get";
426
+ readonly projectId: string;
427
+ readonly environment: "dev" | "prod";
428
+ readonly webhookId: string;
429
+ readonly credentialStore: "native";
430
+ }
431
+ | {
432
+ readonly kind: "database-webhook-delete";
433
+ readonly projectId: string;
434
+ readonly environment: "dev" | "prod";
435
+ readonly webhookId: string;
436
+ readonly yes: true;
437
+ readonly credentialStore: "native";
438
+ }
439
+ | {
440
+ readonly kind: "database-webhook-test";
441
+ readonly projectId: string;
442
+ readonly environment: "dev" | "prod";
443
+ readonly webhookId: string;
444
+ readonly yes: true;
445
+ readonly credentialStore: "native";
446
+ }
447
+ | {
448
+ readonly kind: "database-webhook-deliveries";
449
+ readonly projectId: string;
450
+ readonly environment: "dev" | "prod";
451
+ readonly webhookId: string | null;
452
+ readonly limit: number | null;
453
+ readonly cursor: string | null;
454
+ readonly credentialStore: "native";
455
+ }
456
+ | {
457
+ readonly kind: "database-webhook-replay";
458
+ readonly projectId: string;
459
+ readonly environment: "dev" | "prod";
460
+ readonly deliveryId: string;
461
+ readonly yes: true;
462
+ readonly credentialStore: "native";
463
+ }
464
+ | {
465
+ readonly kind: "database-schedule-set";
466
+ readonly projectId: string;
467
+ readonly environment: "dev" | "prod";
468
+ readonly name: string;
469
+ readonly cron: string | null;
470
+ readonly everySeconds: number | null;
471
+ readonly statementsFile: string;
472
+ readonly timeoutMs: number | null;
473
+ readonly enabled: boolean | null;
474
+ readonly yes: true;
475
+ readonly credentialStore: "native";
476
+ }
477
+ | {
478
+ readonly kind: "database-schedule-list";
479
+ readonly projectId: string;
480
+ readonly environment: "dev" | "prod";
481
+ readonly credentialStore: "native";
482
+ }
483
+ | {
484
+ readonly kind: "database-schedule-get";
485
+ readonly projectId: string;
486
+ readonly environment: "dev" | "prod";
487
+ readonly name: string;
488
+ readonly credentialStore: "native";
489
+ }
490
+ | {
491
+ readonly kind: "database-schedule-delete";
492
+ readonly projectId: string;
493
+ readonly environment: "dev" | "prod";
494
+ readonly name: string;
495
+ readonly yes: true;
496
+ readonly credentialStore: "native";
497
+ }
498
+ | {
499
+ readonly kind: "database-schedule-runs";
500
+ readonly projectId: string;
501
+ readonly environment: "dev" | "prod";
502
+ readonly name: string;
503
+ readonly limit: number | null;
504
+ readonly credentialStore: "native";
505
+ }
506
+ | {
507
+ readonly kind: "database-schedule-run";
508
+ readonly projectId: string;
509
+ readonly environment: "dev" | "prod";
510
+ readonly name: string;
511
+ readonly yes: true;
512
+ readonly credentialStore: "native";
513
+ }
514
+ | {
515
+ readonly kind: "database-executions";
516
+ readonly projectId: string;
517
+ readonly environment: "dev" | "prod";
518
+ readonly from: string;
519
+ readonly to: string;
520
+ readonly granularity: "hour" | "day";
521
+ readonly limit: number | null;
522
+ readonly cursor: string | null;
523
+ readonly credentialStore: "native";
524
+ }
398
525
  | {
399
526
  readonly kind: "database-psql";
400
527
  readonly projectId: string;
@@ -876,6 +1003,20 @@ export type RecognizedCommandLabel =
876
1003
  | "database access create"
877
1004
  | "database access list"
878
1005
  | "database access revoke"
1006
+ | "database webhook create"
1007
+ | "database webhook list"
1008
+ | "database webhook get"
1009
+ | "database webhook delete"
1010
+ | "database webhook test"
1011
+ | "database webhook deliveries"
1012
+ | "database webhook replay"
1013
+ | "database schedule set"
1014
+ | "database schedule list"
1015
+ | "database schedule get"
1016
+ | "database schedule delete"
1017
+ | "database schedule runs"
1018
+ | "database schedule run"
1019
+ | "database executions"
879
1020
  | "database psql"
880
1021
  | "link"
881
1022
  | "source auto-deploy set"
@@ -1033,6 +1174,24 @@ export const recognizedCommandLabel = (argv: readonly string[]): RecognizedComma
1033
1174
  if (values[1] === "list") return "database access list";
1034
1175
  if (values[1] === "revoke") return "database access revoke";
1035
1176
  }
1177
+ if (value === "database" && values[0] === "webhook") {
1178
+ if (values[1] === "create") return "database webhook create";
1179
+ if (values[1] === "list") return "database webhook list";
1180
+ if (values[1] === "get") return "database webhook get";
1181
+ if (values[1] === "delete") return "database webhook delete";
1182
+ if (values[1] === "test") return "database webhook test";
1183
+ if (values[1] === "deliveries") return "database webhook deliveries";
1184
+ if (values[1] === "replay") return "database webhook replay";
1185
+ }
1186
+ if (value === "database" && values[0] === "schedule") {
1187
+ if (values[1] === "set") return "database schedule set";
1188
+ if (values[1] === "list") return "database schedule list";
1189
+ if (values[1] === "get") return "database schedule get";
1190
+ if (values[1] === "delete") return "database schedule delete";
1191
+ if (values[1] === "runs") return "database schedule runs";
1192
+ if (values[1] === "run") return "database schedule run";
1193
+ }
1194
+ if (value === "database" && values[0] === "executions") return "database executions";
1036
1195
  if (value === "database" && values[0] === "psql") return "database psql";
1037
1196
  if (value === "link" || value === "plan" || value === "deploy" || value === "logs")
1038
1197
  return value;
@@ -1109,6 +1268,9 @@ export const recognizedCommandLabel = (argv: readonly string[]): RecognizedComma
1109
1268
  return "unknown";
1110
1269
  };
1111
1270
 
1271
+ /** `database schedule runs --limit` is at most 50 entries, the route's own maximum. */
1272
+ const SCHEDULE_RUNS_MAX_ITEMS = 50;
1273
+
1112
1274
  export const parseProductCliCommand = (argv: readonly string[]): ProductCliCommand => {
1113
1275
  const values = [...argv];
1114
1276
  removeFlag(values, "--json");
@@ -1116,10 +1278,12 @@ export const parseProductCliCommand = (argv: readonly string[]): ProductCliComma
1116
1278
  if ((values.length === 1 && values[0] === "help") || values.length === 0) {
1117
1279
  return { kind: "help" };
1118
1280
  }
1281
+ // `help database` is the same request as `database --help`.
1282
+ if (values[0] === "help") return parseProductCliCommand([...values.slice(1), "--help"]);
1119
1283
  if (help) {
1120
- const topic = recognizedCommandLabel(values);
1121
- if (topic === "unknown" || topic === "version" || topic !== values.join(" "))
1122
- throw new InvalidCommandError();
1284
+ // The topic is only checked for shape here; the CLI answers unknown topics with invalid_command.
1285
+ const topic = values.join(" ");
1286
+ if (!/^[a-z][a-z0-9 -]*$/u.test(topic) || topic === "version") throw new InvalidCommandError();
1123
1287
  return { kind: "help", topic };
1124
1288
  }
1125
1289
  if (values.length === 1 && (values[0] === "--version" || values[0] === "version")) {
@@ -1407,20 +1571,8 @@ export const parseProductCliCommand = (argv: readonly string[]): ProductCliComma
1407
1571
  if (first === "credits" && values[0] === "usage") {
1408
1572
  values.shift();
1409
1573
  if (yes || wait || follow || stdin) throw new InvalidCommandError();
1410
- const options = parseOptionalOptions(values, ["--organization", "--month", "--cursor"]);
1411
- if (
1412
- !options["--organization"] ||
1413
- !options["--month"] ||
1414
- !/^20[0-9]{2}-(0[1-9]|1[0-2])$/u.test(options["--month"])
1415
- )
1416
- throw new InvalidCommandError();
1417
- return {
1418
- kind: "credits-usage",
1419
- organizationId: options["--organization"],
1420
- month: options["--month"],
1421
- ...(options["--cursor"] ? { cursor: options["--cursor"] } : {}),
1422
- credentialStore,
1423
- };
1574
+ const options = parseOptions(values, ["--organization"]);
1575
+ return { kind: "credits-usage", organizationId: options["--organization"], credentialStore };
1424
1576
  }
1425
1577
  if (first === "credits" && values[0] === "account") {
1426
1578
  values.shift();
@@ -1911,6 +2063,318 @@ export const parseProductCliCommand = (argv: readonly string[]): ProductCliComma
1911
2063
  }
1912
2064
  throw new InvalidCommandError();
1913
2065
  }
2066
+ if (first === "database" && values[0] === "webhook") {
2067
+ values.shift();
2068
+ const subcommand = values.shift();
2069
+ if (subcommand === "create") {
2070
+ if (!yes || wait || follow || revoke || stdin) throw new InvalidCommandError();
2071
+ const options = parseOptionalOptions(values, [
2072
+ "--project",
2073
+ "--environment",
2074
+ "--url",
2075
+ "--event-types",
2076
+ "--headers-file",
2077
+ "--idempotency-key",
2078
+ ]);
2079
+ const projectId = options["--project"],
2080
+ environment = options["--environment"],
2081
+ url = options["--url"],
2082
+ eventTypes = options["--event-types"],
2083
+ headersFile = options["--headers-file"],
2084
+ idempotencyKey = options["--idempotency-key"];
2085
+ if (
2086
+ !isValue(projectId) ||
2087
+ !isValue(idempotencyKey) ||
2088
+ !isValue(url) ||
2089
+ !isValue(eventTypes) ||
2090
+ (headersFile !== undefined && !isValue(headersFile)) ||
2091
+ (environment !== "dev" && environment !== "prod")
2092
+ )
2093
+ throw new InvalidCommandError();
2094
+ const request = contractValue(() =>
2095
+ parseDatabaseWebhookCreateRequest({ url, event_types: eventTypes.split(",") }),
2096
+ );
2097
+ return {
2098
+ kind: "database-webhook-create",
2099
+ projectId,
2100
+ environment,
2101
+ url: request.url,
2102
+ eventTypes: request.event_types,
2103
+ headersFile: headersFile ?? null,
2104
+ idempotencyKey,
2105
+ yes: true,
2106
+ credentialStore,
2107
+ };
2108
+ }
2109
+ if (subcommand === "list") {
2110
+ if (yes || wait || follow || revoke || stdin) throw new InvalidCommandError();
2111
+ const options = parseOptionalOptions(values, ["--project", "--environment"]);
2112
+ const projectId = options["--project"],
2113
+ environment = options["--environment"] ?? "dev";
2114
+ if (!isValue(projectId) || (environment !== "dev" && environment !== "prod"))
2115
+ throw new InvalidCommandError();
2116
+ return { kind: "database-webhook-list", projectId, environment, credentialStore };
2117
+ }
2118
+ if (subcommand === "get") {
2119
+ if (yes || wait || follow || revoke || stdin) throw new InvalidCommandError();
2120
+ const options = parseOptionalOptions(values, ["--project", "--environment", "--webhook"]);
2121
+ const projectId = options["--project"],
2122
+ environment = options["--environment"] ?? "dev",
2123
+ webhookId = options["--webhook"];
2124
+ if (!isValue(projectId) || (environment !== "dev" && environment !== "prod"))
2125
+ throw new InvalidCommandError();
2126
+ return {
2127
+ kind: "database-webhook-get",
2128
+ projectId,
2129
+ environment,
2130
+ webhookId: contractValue(() => parseDatabaseWebhookId(webhookId)),
2131
+ credentialStore,
2132
+ };
2133
+ }
2134
+ if (subcommand === "delete" || subcommand === "test") {
2135
+ if (!yes || wait || follow || revoke || stdin) throw new InvalidCommandError();
2136
+ const options = parseOptionalOptions(values, ["--project", "--environment", "--webhook"]);
2137
+ const projectId = options["--project"],
2138
+ environment = options["--environment"],
2139
+ webhookId = options["--webhook"];
2140
+ if (!isValue(projectId)) throw new InvalidCommandError();
2141
+ if (environment !== "dev" && environment !== "prod") throw new InvalidCommandError();
2142
+ const target = contractValue(() => parseDatabaseWebhookId(webhookId));
2143
+ if (subcommand === "delete")
2144
+ return {
2145
+ kind: "database-webhook-delete",
2146
+ projectId,
2147
+ environment,
2148
+ webhookId: target,
2149
+ yes: true,
2150
+ credentialStore,
2151
+ };
2152
+ return {
2153
+ kind: "database-webhook-test",
2154
+ projectId,
2155
+ environment,
2156
+ webhookId: target,
2157
+ yes: true,
2158
+ credentialStore,
2159
+ };
2160
+ }
2161
+ if (subcommand === "deliveries") {
2162
+ if (yes || wait || follow || revoke || stdin) throw new InvalidCommandError();
2163
+ const options = parseOptionalOptions(values, [
2164
+ "--project",
2165
+ "--environment",
2166
+ "--webhook",
2167
+ "--limit",
2168
+ "--cursor",
2169
+ ]);
2170
+ const projectId = options["--project"],
2171
+ environment = options["--environment"] ?? "dev",
2172
+ webhookId = options["--webhook"],
2173
+ limit = options["--limit"],
2174
+ cursor = options["--cursor"];
2175
+ if (!isValue(projectId) || (environment !== "dev" && environment !== "prod"))
2176
+ throw new InvalidCommandError();
2177
+ return {
2178
+ kind: "database-webhook-deliveries",
2179
+ projectId,
2180
+ environment,
2181
+ webhookId:
2182
+ webhookId === undefined ? null : contractValue(() => parseDatabaseWebhookId(webhookId)),
2183
+ limit:
2184
+ limit === undefined
2185
+ ? null
2186
+ : contractValue(() =>
2187
+ parseDatabaseListLimit(limit, DATABASE_PAGE_MAX_ITEMS, DATABASE_PAGE_MAX_ITEMS),
2188
+ ),
2189
+ cursor: contractValue(() => parseDatabaseCursor(cursor)),
2190
+ credentialStore,
2191
+ };
2192
+ }
2193
+ if (subcommand === "replay") {
2194
+ if (!yes || wait || follow || revoke || stdin) throw new InvalidCommandError();
2195
+ const options = parseOptionalOptions(values, ["--project", "--environment", "--delivery"]);
2196
+ const projectId = options["--project"],
2197
+ environment = options["--environment"],
2198
+ deliveryId = options["--delivery"];
2199
+ if (!isValue(projectId) || (environment !== "dev" && environment !== "prod"))
2200
+ throw new InvalidCommandError();
2201
+ return {
2202
+ kind: "database-webhook-replay",
2203
+ projectId,
2204
+ environment,
2205
+ deliveryId: contractValue(() => parseDatabaseDeliveryId(deliveryId)),
2206
+ yes: true,
2207
+ credentialStore,
2208
+ };
2209
+ }
2210
+ throw new InvalidCommandError();
2211
+ }
2212
+ if (first === "database" && values[0] === "schedule") {
2213
+ values.shift();
2214
+ const subcommand = values.shift();
2215
+ if (subcommand === "set") {
2216
+ if (!yes || wait || follow || revoke || stdin) throw new InvalidCommandError();
2217
+ const options = parseOptionalOptions(values, [
2218
+ "--project",
2219
+ "--environment",
2220
+ "--name",
2221
+ "--cron",
2222
+ "--every-seconds",
2223
+ "--statements-file",
2224
+ "--timeout-ms",
2225
+ "--enabled",
2226
+ ]);
2227
+ const projectId = options["--project"],
2228
+ environment = options["--environment"],
2229
+ name = options["--name"],
2230
+ cron = options["--cron"],
2231
+ everySeconds = options["--every-seconds"],
2232
+ statementsFile = options["--statements-file"],
2233
+ timeoutMs = options["--timeout-ms"],
2234
+ enabled = options["--enabled"];
2235
+ if (
2236
+ !isValue(projectId) ||
2237
+ !isValue(statementsFile) ||
2238
+ (environment !== "dev" && environment !== "prod") ||
2239
+ (cron === undefined) === (everySeconds === undefined) ||
2240
+ (everySeconds !== undefined && !/^[0-9]{1,9}$/u.test(everySeconds)) ||
2241
+ (timeoutMs !== undefined && !/^[0-9]{1,6}$/u.test(timeoutMs)) ||
2242
+ (enabled !== undefined && enabled !== "true" && enabled !== "false")
2243
+ )
2244
+ throw new InvalidCommandError();
2245
+ return {
2246
+ kind: "database-schedule-set",
2247
+ projectId,
2248
+ environment,
2249
+ name: contractValue(() => parseDatabaseScheduleName(name)),
2250
+ cron: cron ?? null,
2251
+ everySeconds: everySeconds === undefined ? null : Number(everySeconds),
2252
+ statementsFile,
2253
+ timeoutMs: timeoutMs === undefined ? null : Number(timeoutMs),
2254
+ enabled: enabled === undefined ? null : enabled === "true",
2255
+ yes: true,
2256
+ credentialStore,
2257
+ };
2258
+ }
2259
+ if (subcommand === "list") {
2260
+ if (yes || wait || follow || revoke || stdin) throw new InvalidCommandError();
2261
+ const options = parseOptionalOptions(values, ["--project", "--environment"]);
2262
+ const projectId = options["--project"],
2263
+ environment = options["--environment"] ?? "dev";
2264
+ if (!isValue(projectId) || (environment !== "dev" && environment !== "prod"))
2265
+ throw new InvalidCommandError();
2266
+ return { kind: "database-schedule-list", projectId, environment, credentialStore };
2267
+ }
2268
+ if (subcommand === "get" || subcommand === "runs") {
2269
+ if (yes || wait || follow || revoke || stdin) throw new InvalidCommandError();
2270
+ const options = parseOptionalOptions(
2271
+ values,
2272
+ subcommand === "get"
2273
+ ? ["--project", "--environment", "--name"]
2274
+ : ["--project", "--environment", "--name", "--limit"],
2275
+ );
2276
+ const projectId = options["--project"],
2277
+ environment = options["--environment"] ?? "dev",
2278
+ name = options["--name"],
2279
+ limit = options["--limit"];
2280
+ if (!isValue(projectId) || (environment !== "dev" && environment !== "prod"))
2281
+ throw new InvalidCommandError();
2282
+ const scheduleName = contractValue(() => parseDatabaseScheduleName(name));
2283
+ if (subcommand === "get")
2284
+ return {
2285
+ kind: "database-schedule-get",
2286
+ projectId,
2287
+ environment,
2288
+ name: scheduleName,
2289
+ credentialStore,
2290
+ };
2291
+ return {
2292
+ kind: "database-schedule-runs",
2293
+ projectId,
2294
+ environment,
2295
+ name: scheduleName,
2296
+ limit:
2297
+ limit === undefined
2298
+ ? null
2299
+ : contractValue(() =>
2300
+ parseDatabaseListLimit(limit, SCHEDULE_RUNS_MAX_ITEMS, SCHEDULE_RUNS_MAX_ITEMS),
2301
+ ),
2302
+ credentialStore,
2303
+ };
2304
+ }
2305
+ if (subcommand === "delete" || subcommand === "run") {
2306
+ if (!yes || wait || follow || revoke || stdin) throw new InvalidCommandError();
2307
+ const options = parseOptionalOptions(values, ["--project", "--environment", "--name"]);
2308
+ const projectId = options["--project"],
2309
+ environment = options["--environment"];
2310
+ if (!isValue(projectId) || (environment !== "dev" && environment !== "prod"))
2311
+ throw new InvalidCommandError();
2312
+ const name = contractValue(() => parseDatabaseScheduleName(options["--name"]));
2313
+ return subcommand === "delete"
2314
+ ? {
2315
+ kind: "database-schedule-delete",
2316
+ projectId,
2317
+ environment,
2318
+ name,
2319
+ yes: true,
2320
+ credentialStore,
2321
+ }
2322
+ : {
2323
+ kind: "database-schedule-run",
2324
+ projectId,
2325
+ environment,
2326
+ name,
2327
+ yes: true,
2328
+ credentialStore,
2329
+ };
2330
+ }
2331
+ throw new InvalidCommandError();
2332
+ }
2333
+ if (first === "database" && values[0] === "executions") {
2334
+ values.shift();
2335
+ if (yes || wait || follow || revoke || stdin) throw new InvalidCommandError();
2336
+ const options = parseOptionalOptions(values, [
2337
+ "--project",
2338
+ "--environment",
2339
+ "--from",
2340
+ "--to",
2341
+ "--granularity",
2342
+ "--limit",
2343
+ "--cursor",
2344
+ ]);
2345
+ const projectId = options["--project"],
2346
+ environment = options["--environment"] ?? "dev",
2347
+ from = options["--from"],
2348
+ to = options["--to"],
2349
+ granularity = options["--granularity"],
2350
+ limit = options["--limit"],
2351
+ cursor = options["--cursor"];
2352
+ if (
2353
+ !isValue(projectId) ||
2354
+ (environment !== "dev" && environment !== "prod") ||
2355
+ !isValue(from) ||
2356
+ !isValue(to) ||
2357
+ (granularity !== "hour" && granularity !== "day")
2358
+ )
2359
+ throw new InvalidCommandError();
2360
+ const window = contractValue(() => parseDatabaseUsageWindow(from, to, granularity));
2361
+ return {
2362
+ kind: "database-executions",
2363
+ projectId,
2364
+ environment,
2365
+ from: window.from,
2366
+ to: window.to,
2367
+ granularity: window.granularity,
2368
+ limit:
2369
+ limit === undefined
2370
+ ? null
2371
+ : contractValue(() =>
2372
+ parseDatabaseListLimit(limit, DATABASE_PAGE_MAX_ITEMS, DATABASE_PAGE_MAX_ITEMS),
2373
+ ),
2374
+ cursor: contractValue(() => parseDatabaseCursor(cursor)),
2375
+ credentialStore,
2376
+ };
2377
+ }
1914
2378
  if (first === "database" && values[0] === "psql") {
1915
2379
  values.shift();
1916
2380
  if (yes || wait || follow || revoke || stdin) throw new InvalidCommandError();
@@ -2560,6 +3024,15 @@ const handedAccount = (
2560
3024
  const isValue = (value: string | undefined): value is string =>
2561
3025
  value !== undefined && value.length > 0 && !value.startsWith("--");
2562
3026
 
3027
+ /** A value the contract refuses, or one that is missing, is an invalid command. */
3028
+ const contractValue = <T>(parse: () => T): T => {
3029
+ try {
3030
+ return parse();
3031
+ } catch {
3032
+ throw new InvalidCommandError();
3033
+ }
3034
+ };
3035
+
2563
3036
  const validGithubBranchName = (value: string): boolean =>
2564
3037
  !(
2565
3038
  value.length < 1 ||
@@ -29,7 +29,8 @@ import {
29
29
  cliMetadataResult,
30
30
  CREDENTIAL_STORE_UNAVAILABLE_ACTION,
31
31
  endedLoginAction,
32
- INTERNAL_ERROR_ACTION,
32
+ helpTopicKnown,
33
+ internalErrorAction,
33
34
  invalidCommandAction,
34
35
  refreshFailedAction,
35
36
  NetworkTransportError,
@@ -642,7 +643,7 @@ export const runProductionCli = async (
642
643
  1,
643
644
  "internal_error",
644
645
  false,
645
- INTERNAL_ERROR_ACTION,
646
+ internalErrorAction(recognizedCommandLabel(args)),
646
647
  );
647
648
  if (!(error instanceof InvalidCommandError)) {
648
649
  diagnostics.record({
@@ -683,6 +684,8 @@ export const runProductionCli = async (
683
684
  );
684
685
  }
685
686
  }
687
+ if (command.kind === "help" && command.topic !== undefined && !helpTopicKnown(command.topic))
688
+ return written(invalidCommandFailure(args, environment));
686
689
  if (command.kind === "help" || command.kind === "version") {
687
690
  return written(
688
691
  cliMetadataResult(command.kind, command.kind === "help" ? command.topic : undefined),
@@ -1135,7 +1138,7 @@ const runOfflineInit = async (
1135
1138
  exitCode,
1136
1139
  code,
1137
1140
  false,
1138
- known ? initFailureAction(error) : INTERNAL_ERROR_ACTION,
1141
+ known ? initFailureAction(error) : internalErrorAction("init"),
1139
1142
  );
1140
1143
  io.writeStdout(result.stdout);
1141
1144
  return result.exitCode;