@better-update/cli 0.6.2 → 0.6.4

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.
package/dist/index.mjs CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from "node:module";
3
- import process from "node:process";
4
- import { Args, Command, Options, Prompt } from "@effect/cli";
5
- import { NodeContext, NodeRuntime } from "@effect/platform-node";
6
- import { Cause, Console, Context, Data, Deferred, Duration, Effect, Fiber, Layer, Match, Option, ParseResult, Redacted, Schema, Stream } from "effect";
7
- import { Command as Command$1, FetchHttpClient, FileSystem, HttpApi, HttpApiClient, HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware, HttpApiSchema, HttpApiSecurity, HttpClient, HttpClientRequest, OpenApi } from "@effect/platform";
3
+ import { defineCommand, runMain } from "citty";
4
+ import { Console, Context, Data, Deferred, Duration, Effect, Fiber, Layer, Match, Option, ParseResult, Schema, Stream } from "effect";
5
+ import { Command, FetchHttpClient, FileSystem, HttpApi, HttpApiClient, HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware, HttpApiSchema, HttpApiSecurity, HttpClient, HttpClientRequest, OpenApi } from "@effect/platform";
6
+ import { NodeContext } from "@effect/platform-node";
8
7
  import path from "node:path";
8
+ import process$1 from "node:process";
9
9
  import { maxBy, uniqBy } from "es-toolkit";
10
10
  import { createHash, randomBytes, randomUUID } from "node:crypto";
11
11
  import { createReadStream } from "node:fs";
@@ -14,10 +14,92 @@ import plist from "@expo/plist";
14
14
  import { ExpoRunFormatter } from "@expo/xcpretty";
15
15
  import { getFormattedSerialNumber, getX509Certificate, parsePKCS12 } from "@expo/pkcs12";
16
16
  import { createServer } from "node:http";
17
+ import { cancel, isCancel, password } from "@clack/prompts";
17
18
 
18
19
  //#region \0rolldown/runtime.js
19
20
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
20
21
 
22
+ //#endregion
23
+ //#region package.json
24
+ var version = "0.6.4";
25
+
26
+ //#endregion
27
+ //#region src/lib/exit-codes.ts
28
+ var AuthRequiredError = class extends Data.TaggedError("AuthRequiredError") {};
29
+ var ProjectNotLinkedError = class extends Data.TaggedError("ProjectNotLinkedError") {};
30
+ var UploadFailedError = class extends Data.TaggedError("UploadFailedError") {};
31
+ var BuildProfileError = class extends Data.TaggedError("BuildProfileError") {};
32
+ var RuntimeVersionError = class extends Data.TaggedError("RuntimeVersionError") {};
33
+ var MissingCredentialsError = class extends Data.TaggedError("MissingCredentialsError") {};
34
+ var BuildFailedError = class extends Data.TaggedError("BuildFailedError") {};
35
+ var ReserveError = class extends Data.TaggedError("ReserveError") {};
36
+ var CompleteError = class extends Data.TaggedError("CompleteError") {};
37
+ var PresignedUrlExpiredError = class extends Data.TaggedError("PresignedUrlExpiredError") {};
38
+ var ArtifactNotFoundError = class extends Data.TaggedError("ArtifactNotFoundError") {};
39
+ var KeychainError = class extends Data.TaggedError("KeychainError") {};
40
+ var ProvisioningError = class extends Data.TaggedError("ProvisioningError") {};
41
+ var EnvExportError = class extends Data.TaggedError("EnvExportError") {};
42
+ var UpdatePublishError = class extends Data.TaggedError("UpdatePublishError") {};
43
+ var UpdateRollbackError = class extends Data.TaggedError("UpdateRollbackError") {};
44
+ var UpdatePromoteError = class extends Data.TaggedError("UpdatePromoteError") {};
45
+ var CredentialValidationError = class extends Data.TaggedError("CredentialValidationError") {};
46
+ var AppleAuthError = class extends Data.TaggedError("AppleAuthError") {};
47
+ var InvalidArgumentError = class extends Data.TaggedError("InvalidArgumentError") {};
48
+
49
+ //#endregion
50
+ //#region src/lib/format-error.ts
51
+ const formatCause = (cause) => {
52
+ if (cause instanceof Error) return cause.message;
53
+ if (typeof cause === "object" && cause !== null) {
54
+ const tagged = cause;
55
+ const tag = typeof tagged._tag === "string" ? tagged._tag : void 0;
56
+ const message = typeof tagged.message === "string" ? tagged.message : void 0;
57
+ if (tag && message) return `${tag}: ${message}`;
58
+ if (message) return message;
59
+ if (tag) return tag;
60
+ }
61
+ return String(cause);
62
+ };
63
+
64
+ //#endregion
65
+ //#region src/lib/record.ts
66
+ const isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
67
+ const asRecord = (value) => isRecord(value) ? value : void 0;
68
+
69
+ //#endregion
70
+ //#region src/lib/app-json.ts
71
+ const readAppJson = Effect.gen(function* () {
72
+ const content = yield* (yield* FileSystem.FileSystem).readFileString("./app.json").pipe(Effect.mapError(() => new ProjectNotLinkedError({ message: "app.json not found in current directory" })));
73
+ const parsed = yield* Effect.try({
74
+ try: () => JSON.parse(content),
75
+ catch: () => new ProjectNotLinkedError({ message: "app.json contains malformed JSON" })
76
+ });
77
+ if (!isRecord(parsed)) return yield* new ProjectNotLinkedError({ message: "app.json must be a JSON object" });
78
+ return parsed;
79
+ });
80
+ const readProjectId = Effect.gen(function* () {
81
+ const projectId = asRecord(asRecord(asRecord((yield* readAppJson)["expo"])?.["extra"])?.["betterUpdate"])?.["projectId"];
82
+ if (typeof projectId !== "string") return yield* new ProjectNotLinkedError({ message: "Project not linked. Run `better-update link` to connect this project, or set expo.extra.betterUpdate.projectId in app.json." });
83
+ return projectId;
84
+ });
85
+ const readSlug = Effect.gen(function* () {
86
+ const slug = asRecord((yield* readAppJson)["expo"])?.["slug"];
87
+ if (typeof slug !== "string") return yield* new ProjectNotLinkedError({ message: "Missing expo.slug in app.json. Required to identify the project." });
88
+ return slug;
89
+ });
90
+ const writeProjectId = (id) => Effect.gen(function* () {
91
+ const fs = yield* FileSystem.FileSystem;
92
+ const appJson = yield* readAppJson;
93
+ const expo = asRecord(appJson["expo"]) ?? {};
94
+ const extra = asRecord(expo["extra"]) ?? {};
95
+ const betterUpdate = asRecord(extra["betterUpdate"]) ?? {};
96
+ betterUpdate["projectId"] = id;
97
+ extra["betterUpdate"] = betterUpdate;
98
+ expo["extra"] = extra;
99
+ appJson["expo"] = expo;
100
+ yield* fs.writeFileString("./app.json", `${JSON.stringify(appJson, null, 2)}\n`);
101
+ }).pipe(Effect.mapError((cause) => cause instanceof ProjectNotLinkedError ? cause : new ProjectNotLinkedError({ message: `Failed to write project ID to app.json: ${formatCause(cause)}` })));
102
+
21
103
  //#endregion
22
104
  //#region ../../packages/api/src/auth/context.ts
23
105
  var AuthContext = class extends Context.Tag("api/AuthContext")() {};
@@ -1363,65 +1445,23 @@ var ProtocolApi = class extends HttpApi.make("protocol-api").add(ManifestGroup).
1363
1445
  description: "Expo Updates protocol endpoints (unauthenticated)"
1364
1446
  })) {};
1365
1447
 
1366
- //#endregion
1367
- //#region src/lib/exit-codes.ts
1368
- var AuthRequiredError = class extends Data.TaggedError("AuthRequiredError") {};
1369
- var ProjectNotLinkedError = class extends Data.TaggedError("ProjectNotLinkedError") {};
1370
- var UploadFailedError = class extends Data.TaggedError("UploadFailedError") {};
1371
- var BuildProfileError = class extends Data.TaggedError("BuildProfileError") {};
1372
- var RuntimeVersionError = class extends Data.TaggedError("RuntimeVersionError") {};
1373
- var MissingCredentialsError = class extends Data.TaggedError("MissingCredentialsError") {};
1374
- var BuildFailedError = class extends Data.TaggedError("BuildFailedError") {};
1375
- var ReserveError = class extends Data.TaggedError("ReserveError") {};
1376
- var CompleteError = class extends Data.TaggedError("CompleteError") {};
1377
- var PresignedUrlExpiredError = class extends Data.TaggedError("PresignedUrlExpiredError") {};
1378
- var ArtifactNotFoundError = class extends Data.TaggedError("ArtifactNotFoundError") {};
1379
- var KeychainError = class extends Data.TaggedError("KeychainError") {};
1380
- var ProvisioningError = class extends Data.TaggedError("ProvisioningError") {};
1381
- var EnvExportError = class extends Data.TaggedError("EnvExportError") {};
1382
- var UpdatePublishError = class extends Data.TaggedError("UpdatePublishError") {};
1383
- var UpdateRollbackError = class extends Data.TaggedError("UpdateRollbackError") {};
1384
- var UpdatePromoteError = class extends Data.TaggedError("UpdatePromoteError") {};
1385
- var CredentialValidationError = class extends Data.TaggedError("CredentialValidationError") {};
1386
- var AppleAuthError = class extends Data.TaggedError("AppleAuthError") {};
1387
-
1388
- //#endregion
1389
- //#region src/lib/format-error.ts
1390
- const formatCause = (cause) => {
1391
- if (cause instanceof Error) return cause.message;
1392
- if (typeof cause === "object" && cause !== null) {
1393
- const tagged = cause;
1394
- const tag = typeof tagged._tag === "string" ? tagged._tag : void 0;
1395
- const message = typeof tagged.message === "string" ? tagged.message : void 0;
1396
- if (tag && message) return `${tag}: ${message}`;
1397
- if (message) return message;
1398
- if (tag) return tag;
1399
- }
1400
- return String(cause);
1401
- };
1402
-
1403
- //#endregion
1404
- //#region src/lib/record.ts
1405
- const isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
1406
- const asRecord = (value) => isRecord(value) ? value : void 0;
1407
-
1408
1448
  //#endregion
1409
1449
  //#region src/services/cli-runtime.ts
1410
- const definedEnvironment = () => Object.fromEntries(Object.entries(process.env).flatMap(([key, value]) => typeof value === "string" ? [[key, value]] : []));
1450
+ const definedEnvironment = () => Object.fromEntries(Object.entries(process$1.env).flatMap(([key, value]) => typeof value === "string" ? [[key, value]] : []));
1411
1451
  var CliRuntime = class extends Context.Tag("cli/CliRuntime")() {};
1412
1452
  const CliRuntimeLive = Layer.succeed(CliRuntime, {
1413
- argv: [...process.argv],
1414
- platform: process.platform,
1415
- cwd: Effect.sync(() => process.cwd()),
1416
- getEnv: (name) => Effect.sync(() => process.env[name]),
1417
- homeDirectory: Effect.sync(() => process.env["HOME"] ?? process.env["USERPROFILE"] ?? process.cwd()),
1418
- userName: Effect.sync(() => process.env["USER"] ?? process.env["USERNAME"] ?? "better-update"),
1453
+ argv: [...process$1.argv],
1454
+ platform: process$1.platform,
1455
+ cwd: Effect.sync(() => process$1.cwd()),
1456
+ getEnv: (name) => Effect.sync(() => process$1.env[name]),
1457
+ homeDirectory: Effect.sync(() => process$1.env["HOME"] ?? process$1.env["USERPROFILE"] ?? process$1.cwd()),
1458
+ userName: Effect.sync(() => process$1.env["USER"] ?? process$1.env["USERNAME"] ?? "better-update"),
1419
1459
  commandEnvironment: (overrides = {}) => Effect.sync(() => ({
1420
1460
  ...definedEnvironment(),
1421
1461
  ...overrides
1422
1462
  })),
1423
1463
  setExitCode: (code) => Effect.sync(() => {
1424
- process.exitCode = code;
1464
+ process$1.exitCode = code;
1425
1465
  })
1426
1466
  });
1427
1467
 
@@ -1608,55 +1648,6 @@ const PresignedUploadLayer = PresignedUploadClientLive.pipe(Layer.provide(CliPla
1608
1648
  const UpdateAssetUploaderLayer = UpdateAssetUploaderLive.pipe(Layer.provide(Layer.mergeAll(ApiClientLayer, PresignedUploadLayer)));
1609
1649
  const CliLive = Layer.mergeAll(CliAdapterDependencies, ApiClientLayer, PresignedUploadLayer, UpdateAssetUploaderLayer);
1610
1650
 
1611
- //#endregion
1612
- //#region src/lib/app-json.ts
1613
- const readAppJson = Effect.gen(function* () {
1614
- const content = yield* (yield* FileSystem.FileSystem).readFileString("./app.json").pipe(Effect.mapError(() => new ProjectNotLinkedError({ message: "app.json not found in current directory" })));
1615
- const parsed = yield* Effect.try({
1616
- try: () => JSON.parse(content),
1617
- catch: () => new ProjectNotLinkedError({ message: "app.json contains malformed JSON" })
1618
- });
1619
- if (!isRecord(parsed)) return yield* new ProjectNotLinkedError({ message: "app.json must be a JSON object" });
1620
- return parsed;
1621
- });
1622
- const readProjectId = Effect.gen(function* () {
1623
- const projectId = asRecord(asRecord(asRecord((yield* readAppJson)["expo"])?.["extra"])?.["betterUpdate"])?.["projectId"];
1624
- if (typeof projectId !== "string") return yield* new ProjectNotLinkedError({ message: "Project not linked. Run `better-update link` to connect this project, or set expo.extra.betterUpdate.projectId in app.json." });
1625
- return projectId;
1626
- });
1627
- const readSlug = Effect.gen(function* () {
1628
- const slug = asRecord((yield* readAppJson)["expo"])?.["slug"];
1629
- if (typeof slug !== "string") return yield* new ProjectNotLinkedError({ message: "Missing expo.slug in app.json. Required to identify the project." });
1630
- return slug;
1631
- });
1632
- const writeProjectId = (id) => Effect.gen(function* () {
1633
- const fs = yield* FileSystem.FileSystem;
1634
- const appJson = yield* readAppJson;
1635
- const expo = asRecord(appJson["expo"]) ?? {};
1636
- const extra = asRecord(expo["extra"]) ?? {};
1637
- const betterUpdate = asRecord(extra["betterUpdate"]) ?? {};
1638
- betterUpdate["projectId"] = id;
1639
- extra["betterUpdate"] = betterUpdate;
1640
- expo["extra"] = extra;
1641
- appJson["expo"] = expo;
1642
- yield* fs.writeFileString("./app.json", `${JSON.stringify(appJson, null, 2)}\n`);
1643
- }).pipe(Effect.mapError((cause) => cause instanceof ProjectNotLinkedError ? cause : new ProjectNotLinkedError({ message: `Failed to write project ID to app.json: ${formatCause(cause)}` })));
1644
-
1645
- //#endregion
1646
- //#region src/lib/output.ts
1647
- const printTable = (headers, rows) => Effect.gen(function* () {
1648
- const allRows = [headers, ...rows];
1649
- const colWidths = headers.map((_, colIndex) => Math.max(...allRows.map((row) => (row[colIndex] ?? "").length)));
1650
- const formatRow = (row) => row.map((cell, idx) => cell.padEnd(colWidths[idx] ?? 0)).join(" ");
1651
- yield* Console.log(formatRow(headers));
1652
- yield* Console.log(colWidths.map((width) => "-".repeat(width)).join(" "));
1653
- for (const row of rows) yield* Console.log(formatRow(row));
1654
- });
1655
- const printKeyValue = (pairs) => Effect.gen(function* () {
1656
- const maxKeyLen = Math.max(...pairs.map(([key]) => key.length));
1657
- for (const [key, value] of pairs) yield* Console.log(`${key.padEnd(maxKeyLen)} ${value}`);
1658
- });
1659
-
1660
1651
  //#endregion
1661
1652
  //#region src/application/command-exit.ts
1662
1653
  const exitWith = (code, message) => Console.error(message).pipe(Effect.zipRight(Effect.gen(function* () {
@@ -1671,7 +1662,8 @@ const BASE_TAG_MAP = {
1671
1662
  NotFound: 1,
1672
1663
  Conflict: 1,
1673
1664
  Forbidden: 1,
1674
- BadRequest: 2
1665
+ BadRequest: 2,
1666
+ InvalidArgumentError: 2
1675
1667
  };
1676
1668
  const SYSTEM_TAG_MESSAGE = {
1677
1669
  SystemError: (error) => `Filesystem error: ${error.message}`,
@@ -1698,287 +1690,394 @@ const makeCommandErrorHandler = (extras = {}) => {
1698
1690
  };
1699
1691
 
1700
1692
  //#endregion
1701
- //#region src/commands/analytics/helpers.ts
1702
- const handleAnalyticsCommandErrors = makeCommandErrorHandler();
1693
+ //#region src/lib/citty-effect.ts
1694
+ const runEffect = async (effect, extras = {}) => {
1695
+ const provided = makeCommandErrorHandler(extras)(effect).pipe(Effect.provide(CliLive));
1696
+ return Effect.runPromise(provided.pipe(Effect.asVoid));
1697
+ };
1698
+
1699
+ //#endregion
1700
+ //#region src/lib/output.ts
1701
+ const printTable = (headers, rows) => Effect.gen(function* () {
1702
+ const allRows = [headers, ...rows];
1703
+ const colWidths = headers.map((_, colIndex) => Math.max(...allRows.map((row) => (row[colIndex] ?? "").length)));
1704
+ const formatRow = (row) => row.map((cell, idx) => cell.padEnd(colWidths[idx] ?? 0)).join(" ");
1705
+ yield* Console.log(formatRow(headers));
1706
+ yield* Console.log(colWidths.map((width) => "-".repeat(width)).join(" "));
1707
+ for (const row of rows) yield* Console.log(formatRow(row));
1708
+ });
1709
+ const printKeyValue = (pairs) => Effect.gen(function* () {
1710
+ const maxKeyLen = Math.max(...pairs.map(([key]) => key.length));
1711
+ for (const [key, value] of pairs) yield* Console.log(`${key.padEnd(maxKeyLen)} ${value}`);
1712
+ });
1703
1713
 
1704
1714
  //#endregion
1705
1715
  //#region src/commands/analytics/adoption.ts
1706
- const period$3 = Options.choice("period", [
1707
- "1d",
1708
- "7d",
1709
- "30d",
1710
- "90d"
1711
- ]).pipe(Options.optional);
1712
- const adoptionCommand = Command.make("adoption", { period: period$3 }, (opts) => Effect.gen(function* () {
1713
- const projectId = yield* readProjectId;
1714
- const api = yield* apiClient;
1715
- const periodFilter = Option.match(opts.period, {
1716
- onNone: () => ({}),
1717
- onSome: (periodValue) => ({ period: periodValue })
1718
- });
1719
- const result = yield* api.analytics.adoption({ urlParams: {
1720
- projectId,
1721
- ...periodFilter
1722
- } });
1723
- if (result.updates.length === 0) {
1724
- yield* Console.log("No adoption data found.");
1725
- return;
1726
- }
1727
- yield* printTable([
1728
- "Update ID",
1729
- "Devices",
1730
- "First Seen",
1731
- "Last Seen"
1732
- ], result.updates.map((update) => [
1733
- update.updateId,
1734
- String(update.devices),
1735
- update.firstSeen,
1736
- update.lastSeen
1737
- ]));
1738
- }).pipe(handleAnalyticsCommandErrors));
1716
+ const adoptionCommand = defineCommand({
1717
+ meta: {
1718
+ name: "adoption",
1719
+ description: "Show update adoption across devices"
1720
+ },
1721
+ args: { period: {
1722
+ type: "enum",
1723
+ options: [
1724
+ "1d",
1725
+ "7d",
1726
+ "30d",
1727
+ "90d"
1728
+ ],
1729
+ description: "Time window"
1730
+ } },
1731
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
1732
+ const projectId = yield* readProjectId;
1733
+ const api = yield* apiClient;
1734
+ const periodFilter = args.period ? { period: args.period } : {};
1735
+ const result = yield* api.analytics.adoption({ urlParams: {
1736
+ projectId,
1737
+ ...periodFilter
1738
+ } });
1739
+ if (result.updates.length === 0) {
1740
+ yield* Console.log("No adoption data found.");
1741
+ return;
1742
+ }
1743
+ yield* printTable([
1744
+ "Update ID",
1745
+ "Devices",
1746
+ "First Seen",
1747
+ "Last Seen"
1748
+ ], result.updates.map((update) => [
1749
+ update.updateId,
1750
+ String(update.devices),
1751
+ update.firstSeen,
1752
+ update.lastSeen
1753
+ ]));
1754
+ }))
1755
+ });
1739
1756
 
1740
1757
  //#endregion
1741
1758
  //#region src/commands/analytics/channels.ts
1742
- const channel$1 = Options.text("channel");
1743
- const period$2 = Options.choice("period", [
1744
- "1d",
1745
- "7d",
1746
- "30d",
1747
- "90d"
1748
- ]).pipe(Options.optional);
1749
- const channelsCommand$1 = Command.make("channels", {
1750
- channel: channel$1,
1751
- period: period$2
1752
- }, (opts) => Effect.gen(function* () {
1753
- const projectId = yield* readProjectId;
1754
- const api = yield* apiClient;
1755
- const periodFilter = Option.match(opts.period, {
1756
- onNone: () => ({}),
1757
- onSome: (periodValue) => ({ period: periodValue })
1758
- });
1759
- const result = yield* api.analytics.channels({ urlParams: {
1760
- projectId,
1761
- channel: opts.channel,
1762
- ...periodFilter
1763
- } });
1764
- yield* printKeyValue([
1765
- ["Channel", result.channel],
1766
- ["Total Requests", String(result.totalRequests)],
1767
- ["Unique Devices", String(result.uniqueDevices)],
1768
- ["Manifest", String(result.responseTypeDistribution.manifest)],
1769
- ["Directive", String(result.responseTypeDistribution.directive)],
1770
- ["No Update", String(result.responseTypeDistribution.no_update)]
1771
- ]);
1772
- }).pipe(handleAnalyticsCommandErrors));
1759
+ const channelsCommand$1 = defineCommand({
1760
+ meta: {
1761
+ name: "channels",
1762
+ description: "Stats for a specific channel"
1763
+ },
1764
+ args: {
1765
+ channel: {
1766
+ type: "string",
1767
+ required: true,
1768
+ description: "Channel name"
1769
+ },
1770
+ period: {
1771
+ type: "enum",
1772
+ options: [
1773
+ "1d",
1774
+ "7d",
1775
+ "30d",
1776
+ "90d"
1777
+ ],
1778
+ description: "Time window"
1779
+ }
1780
+ },
1781
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
1782
+ const projectId = yield* readProjectId;
1783
+ const api = yield* apiClient;
1784
+ const periodFilter = args.period ? { period: args.period } : {};
1785
+ const result = yield* api.analytics.channels({ urlParams: {
1786
+ projectId,
1787
+ channel: args.channel,
1788
+ ...periodFilter
1789
+ } });
1790
+ yield* printKeyValue([
1791
+ ["Channel", result.channel],
1792
+ ["Total Requests", String(result.totalRequests)],
1793
+ ["Unique Devices", String(result.uniqueDevices)],
1794
+ ["Manifest", String(result.responseTypeDistribution.manifest)],
1795
+ ["Directive", String(result.responseTypeDistribution.directive)],
1796
+ ["No Update", String(result.responseTypeDistribution.no_update)]
1797
+ ]);
1798
+ }))
1799
+ });
1773
1800
 
1774
1801
  //#endregion
1775
1802
  //#region src/commands/analytics/platforms.ts
1776
- const period$1 = Options.choice("period", [
1777
- "1d",
1778
- "7d",
1779
- "30d",
1780
- "90d"
1781
- ]).pipe(Options.optional);
1782
- const platformsCommand = Command.make("platforms", { period: period$1 }, (opts) => Effect.gen(function* () {
1783
- const projectId = yield* readProjectId;
1784
- const api = yield* apiClient;
1785
- const periodFilter = Option.match(opts.period, {
1786
- onNone: () => ({}),
1787
- onSome: (periodValue) => ({ period: periodValue })
1788
- });
1789
- const result = yield* api.analytics.platforms({ urlParams: {
1790
- projectId,
1791
- ...periodFilter
1792
- } });
1793
- if (result.platforms.length === 0) {
1794
- yield* Console.log("No platform data found.");
1795
- return;
1796
- }
1797
- yield* printTable([
1798
- "Platform",
1799
- "Requests",
1800
- "Devices"
1801
- ], result.platforms.map((platform) => [
1802
- platform.platform,
1803
- String(platform.requests),
1804
- String(platform.devices)
1805
- ]));
1806
- }).pipe(handleAnalyticsCommandErrors));
1803
+ const platformsCommand = defineCommand({
1804
+ meta: {
1805
+ name: "platforms",
1806
+ description: "Stats by platform"
1807
+ },
1808
+ args: { period: {
1809
+ type: "enum",
1810
+ options: [
1811
+ "1d",
1812
+ "7d",
1813
+ "30d",
1814
+ "90d"
1815
+ ],
1816
+ description: "Time window"
1817
+ } },
1818
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
1819
+ const projectId = yield* readProjectId;
1820
+ const api = yield* apiClient;
1821
+ const periodFilter = args.period ? { period: args.period } : {};
1822
+ const result = yield* api.analytics.platforms({ urlParams: {
1823
+ projectId,
1824
+ ...periodFilter
1825
+ } });
1826
+ if (result.platforms.length === 0) {
1827
+ yield* Console.log("No platform data found.");
1828
+ return;
1829
+ }
1830
+ yield* printTable([
1831
+ "Platform",
1832
+ "Requests",
1833
+ "Devices"
1834
+ ], result.platforms.map((platform) => [
1835
+ platform.platform,
1836
+ String(platform.requests),
1837
+ String(platform.devices)
1838
+ ]));
1839
+ }))
1840
+ });
1807
1841
 
1808
1842
  //#endregion
1809
1843
  //#region src/commands/analytics/updates.ts
1810
- const updateId$4 = Options.text("update-id");
1811
- const period = Options.choice("period", [
1812
- "1d",
1813
- "7d",
1814
- "30d",
1815
- "90d"
1816
- ]).pipe(Options.optional);
1817
- const updatesCommand = Command.make("updates", {
1818
- updateId: updateId$4,
1819
- period
1820
- }, (opts) => Effect.gen(function* () {
1821
- const projectId = yield* readProjectId;
1822
- const api = yield* apiClient;
1823
- const periodFilter = Option.match(opts.period, {
1824
- onNone: () => ({}),
1825
- onSome: (periodValue) => ({ period: periodValue })
1826
- });
1827
- const result = yield* api.analytics.updates({ urlParams: {
1828
- projectId,
1829
- updateId: opts.updateId,
1830
- ...periodFilter
1831
- } });
1832
- yield* printKeyValue([
1833
- ["Update ID", result.updateId],
1834
- ["Total Requests", String(result.totalRequests)],
1835
- ["Unique Devices", String(result.uniqueDevices)],
1836
- ["Manifest", String(result.byResponseType.manifest)],
1837
- ["Directive", String(result.byResponseType.directive)],
1838
- ["No Update", String(result.byResponseType.no_update)]
1839
- ]);
1840
- }).pipe(handleAnalyticsCommandErrors));
1844
+ const updatesCommand = defineCommand({
1845
+ meta: {
1846
+ name: "updates",
1847
+ description: "Stats for a specific update"
1848
+ },
1849
+ args: {
1850
+ "update-id": {
1851
+ type: "string",
1852
+ required: true,
1853
+ description: "Update ID"
1854
+ },
1855
+ period: {
1856
+ type: "enum",
1857
+ options: [
1858
+ "1d",
1859
+ "7d",
1860
+ "30d",
1861
+ "90d"
1862
+ ],
1863
+ description: "Time window"
1864
+ }
1865
+ },
1866
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
1867
+ const projectId = yield* readProjectId;
1868
+ const api = yield* apiClient;
1869
+ const periodFilter = args.period ? { period: args.period } : {};
1870
+ const result = yield* api.analytics.updates({ urlParams: {
1871
+ projectId,
1872
+ updateId: args["update-id"],
1873
+ ...periodFilter
1874
+ } });
1875
+ yield* printKeyValue([
1876
+ ["Update ID", result.updateId],
1877
+ ["Total Requests", String(result.totalRequests)],
1878
+ ["Unique Devices", String(result.uniqueDevices)],
1879
+ ["Manifest", String(result.byResponseType.manifest)],
1880
+ ["Directive", String(result.byResponseType.directive)],
1881
+ ["No Update", String(result.byResponseType.no_update)]
1882
+ ]);
1883
+ }))
1884
+ });
1841
1885
 
1842
1886
  //#endregion
1843
1887
  //#region src/commands/analytics/index.ts
1844
- const analyticsCommand = Command.make("analytics", {}, () => Console.log("View deployment analytics. Run with --help for subcommands.")).pipe(Command.withSubcommands([
1845
- adoptionCommand,
1846
- updatesCommand,
1847
- channelsCommand$1,
1848
- platformsCommand
1849
- ]));
1850
-
1851
- //#endregion
1852
- //#region src/commands/audit-logs/helpers.ts
1853
- const handleAuditLogCommandErrors = makeCommandErrorHandler();
1888
+ const analyticsCommand = defineCommand({
1889
+ meta: {
1890
+ name: "analytics",
1891
+ description: "View deployment analytics"
1892
+ },
1893
+ subCommands: {
1894
+ adoption: adoptionCommand,
1895
+ updates: updatesCommand,
1896
+ channels: channelsCommand$1,
1897
+ platforms: platformsCommand
1898
+ }
1899
+ });
1854
1900
 
1855
1901
  //#endregion
1856
1902
  //#region src/commands/audit-logs/list.ts
1857
- const action = Options.text("action").pipe(Options.optional);
1858
- const resourceType = Options.text("resource-type").pipe(Options.optional);
1859
- const actorId = Options.text("actor-id").pipe(Options.optional);
1860
- const from = Options.text("from").pipe(Options.optional);
1861
- const to = Options.text("to").pipe(Options.optional);
1862
- const listCommand$7 = Command.make("list", {
1863
- action,
1864
- resourceType,
1865
- actorId,
1866
- from,
1867
- to
1868
- }, (opts) => Effect.gen(function* () {
1869
- const api = yield* apiClient;
1870
- const filters = {
1871
- ...Option.match(opts.action, {
1872
- onNone: () => ({}),
1873
- onSome: (value) => ({ action: value })
1874
- }),
1875
- ...Option.match(opts.resourceType, {
1876
- onNone: () => ({}),
1877
- onSome: (value) => ({ resourceType: value })
1878
- }),
1879
- ...Option.match(opts.actorId, {
1880
- onNone: () => ({}),
1881
- onSome: (value) => ({ actorId: value })
1882
- }),
1883
- ...Option.match(opts.from, {
1884
- onNone: () => ({}),
1885
- onSome: (value) => ({ from: value })
1886
- }),
1887
- ...Option.match(opts.to, {
1888
- onNone: () => ({}),
1889
- onSome: (value) => ({ to: value })
1890
- })
1891
- };
1892
- const { items } = yield* api["audit-logs"].list({ urlParams: {
1893
- ...filters,
1894
- page: 1,
1895
- limit: 100
1896
- } });
1897
- if (items.length === 0) {
1898
- yield* Console.log("No audit log entries found.");
1899
- return;
1900
- }
1901
- yield* printTable([
1902
- "ID",
1903
- "Action",
1904
- "Resource Type",
1905
- "Resource ID",
1906
- "Actor",
1907
- "Source",
1908
- "Created"
1909
- ], items.map((log) => [
1910
- log.id,
1911
- log.action,
1912
- log.resourceType,
1913
- log.resourceId ?? "-",
1914
- log.actorEmail,
1915
- log.source,
1916
- log.createdAt
1917
- ]));
1918
- }).pipe(handleAuditLogCommandErrors));
1903
+ const listCommand$7 = defineCommand({
1904
+ meta: {
1905
+ name: "list",
1906
+ description: "List audit log entries"
1907
+ },
1908
+ args: {
1909
+ action: {
1910
+ type: "string",
1911
+ description: "Filter by action"
1912
+ },
1913
+ "resource-type": {
1914
+ type: "string",
1915
+ description: "Filter by resource type"
1916
+ },
1917
+ "actor-id": {
1918
+ type: "string",
1919
+ description: "Filter by actor ID"
1920
+ },
1921
+ from: {
1922
+ type: "string",
1923
+ description: "ISO timestamp lower bound"
1924
+ },
1925
+ to: {
1926
+ type: "string",
1927
+ description: "ISO timestamp upper bound"
1928
+ }
1929
+ },
1930
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
1931
+ const api = yield* apiClient;
1932
+ const filters = {};
1933
+ if (args.action) filters["action"] = args.action;
1934
+ if (args["resource-type"]) filters["resourceType"] = args["resource-type"];
1935
+ if (args["actor-id"]) filters["actorId"] = args["actor-id"];
1936
+ if (args.from) filters["from"] = args.from;
1937
+ if (args.to) filters["to"] = args.to;
1938
+ const { items } = yield* api["audit-logs"].list({ urlParams: {
1939
+ ...filters,
1940
+ page: 1,
1941
+ limit: 100
1942
+ } });
1943
+ if (items.length === 0) {
1944
+ yield* Console.log("No audit log entries found.");
1945
+ return;
1946
+ }
1947
+ yield* printTable([
1948
+ "ID",
1949
+ "Action",
1950
+ "Resource Type",
1951
+ "Resource ID",
1952
+ "Actor",
1953
+ "Source",
1954
+ "Created"
1955
+ ], items.map((log) => [
1956
+ log.id,
1957
+ log.action,
1958
+ log.resourceType,
1959
+ log.resourceId ?? "-",
1960
+ log.actorEmail,
1961
+ log.source,
1962
+ log.createdAt
1963
+ ]));
1964
+ }))
1965
+ });
1919
1966
 
1920
1967
  //#endregion
1921
1968
  //#region src/commands/audit-logs/index.ts
1922
- const auditLogsCommand = Command.make("audit-logs", {}, () => Console.log("View audit logs. Run with --help for subcommands.")).pipe(Command.withSubcommands([listCommand$7]));
1969
+ const auditLogsCommand = defineCommand({
1970
+ meta: {
1971
+ name: "audit-logs",
1972
+ description: "View audit logs"
1973
+ },
1974
+ subCommands: { list: listCommand$7 }
1975
+ });
1923
1976
 
1924
1977
  //#endregion
1925
1978
  //#region src/commands/branches.ts
1926
- const handleErrors$1 = makeCommandErrorHandler();
1927
- const idArg$1 = Args.text({ name: "id" });
1928
- const nameOption$1 = Options.text("name");
1929
- const listCommand$6 = Command.make("list", {}, () => Effect.gen(function* () {
1930
- const projectId = yield* readProjectId;
1931
- const { items } = yield* (yield* apiClient).branches.list({ urlParams: {
1932
- projectId,
1933
- page: 1,
1934
- limit: 1e3
1935
- } });
1936
- if (items.length === 0) {
1937
- yield* Console.log("No branches found.");
1938
- return;
1979
+ const listCommand$6 = defineCommand({
1980
+ meta: {
1981
+ name: "list",
1982
+ description: "List branches for the linked project"
1983
+ },
1984
+ run: async () => runEffect(Effect.gen(function* () {
1985
+ const projectId = yield* readProjectId;
1986
+ const { items } = yield* (yield* apiClient).branches.list({ urlParams: {
1987
+ projectId,
1988
+ page: 1,
1989
+ limit: 1e3
1990
+ } });
1991
+ if (items.length === 0) {
1992
+ yield* Console.log("No branches found.");
1993
+ return;
1994
+ }
1995
+ yield* printTable([
1996
+ "ID",
1997
+ "Name",
1998
+ "Created"
1999
+ ], items.map((branch) => [
2000
+ branch.id,
2001
+ branch.name,
2002
+ branch.createdAt
2003
+ ]));
2004
+ }))
2005
+ });
2006
+ const createCommand$3 = defineCommand({
2007
+ meta: {
2008
+ name: "create",
2009
+ description: "Create a branch"
2010
+ },
2011
+ args: { name: {
2012
+ type: "string",
2013
+ required: true,
2014
+ description: "Branch name"
2015
+ } },
2016
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
2017
+ const projectId = yield* readProjectId;
2018
+ const branch = yield* (yield* apiClient).branches.create({ payload: {
2019
+ projectId,
2020
+ name: args.name
2021
+ } });
2022
+ yield* printKeyValue([
2023
+ ["ID", branch.id],
2024
+ ["Name", branch.name],
2025
+ ["Created", branch.createdAt]
2026
+ ]);
2027
+ }))
2028
+ });
2029
+ const renameCommand$1 = defineCommand({
2030
+ meta: {
2031
+ name: "rename",
2032
+ description: "Rename a branch"
2033
+ },
2034
+ args: {
2035
+ id: {
2036
+ type: "positional",
2037
+ required: true,
2038
+ description: "Branch ID"
2039
+ },
2040
+ name: {
2041
+ type: "string",
2042
+ required: true,
2043
+ description: "New branch name"
2044
+ }
2045
+ },
2046
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
2047
+ const branch = yield* (yield* apiClient).branches.rename({
2048
+ path: { id: args.id },
2049
+ payload: { name: args.name }
2050
+ });
2051
+ yield* Console.log(`Branch renamed to "${branch.name}".`);
2052
+ }))
2053
+ });
2054
+ const deleteCommand$6 = defineCommand({
2055
+ meta: {
2056
+ name: "delete",
2057
+ description: "Delete a branch"
2058
+ },
2059
+ args: { id: {
2060
+ type: "positional",
2061
+ required: true,
2062
+ description: "Branch ID"
2063
+ } },
2064
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
2065
+ yield* (yield* apiClient).branches.delete({ path: { id: args.id } });
2066
+ yield* Console.log(`Branch ${args.id} deleted.`);
2067
+ }))
2068
+ });
2069
+ const branchesCommand = defineCommand({
2070
+ meta: {
2071
+ name: "branches",
2072
+ description: "Manage branches"
2073
+ },
2074
+ subCommands: {
2075
+ list: listCommand$6,
2076
+ create: createCommand$3,
2077
+ rename: renameCommand$1,
2078
+ delete: deleteCommand$6
1939
2079
  }
1940
- yield* printTable([
1941
- "ID",
1942
- "Name",
1943
- "Created"
1944
- ], items.map((branch) => [
1945
- branch.id,
1946
- branch.name,
1947
- branch.createdAt
1948
- ]));
1949
- }).pipe(handleErrors$1));
1950
- const createCommand$3 = Command.make("create", { name: nameOption$1 }, (opts) => Effect.gen(function* () {
1951
- const projectId = yield* readProjectId;
1952
- const branch = yield* (yield* apiClient).branches.create({ payload: {
1953
- projectId,
1954
- name: opts.name
1955
- } });
1956
- yield* printKeyValue([
1957
- ["ID", branch.id],
1958
- ["Name", branch.name],
1959
- ["Created", branch.createdAt]
1960
- ]);
1961
- }).pipe(handleErrors$1));
1962
- const renameCommand$1 = Command.make("rename", {
1963
- id: idArg$1,
1964
- name: nameOption$1
1965
- }, (opts) => Effect.gen(function* () {
1966
- const branch = yield* (yield* apiClient).branches.rename({
1967
- path: { id: opts.id },
1968
- payload: { name: opts.name }
1969
- });
1970
- yield* Console.log(`Branch renamed to "${branch.name}".`);
1971
- }).pipe(handleErrors$1));
1972
- const deleteCommand$6 = Command.make("delete", { id: idArg$1 }, (opts) => Effect.gen(function* () {
1973
- yield* (yield* apiClient).branches.delete({ path: { id: opts.id } });
1974
- yield* Console.log(`Branch ${opts.id} deleted.`);
1975
- }).pipe(handleErrors$1));
1976
- const branchesCommand = Command.make("branches", {}, () => Console.log("Manage branches. Run with --help for subcommands.")).pipe(Command.withSubcommands([
1977
- listCommand$6,
1978
- createCommand$3,
1979
- renameCommand$1,
1980
- deleteCommand$6
1981
- ]));
2080
+ });
1982
2081
 
1983
2082
  //#endregion
1984
2083
  //#region src/lib/android-signing-gradle.ts
@@ -2213,7 +2312,7 @@ const sha256Namespaced = (contentType, contentSha256Hex) => {
2213
2312
 
2214
2313
  //#endregion
2215
2314
  //#region src/commands/build/run-step.ts
2216
- const runStep = (cmd, step) => Command$1.exitCode(cmd.pipe(Command$1.stdout("inherit"), Command$1.stderr("inherit"))).pipe(Effect.mapError((cause) => new BuildFailedError({
2315
+ const runStep = (cmd, step) => Command.exitCode(cmd.pipe(Command.stdout("inherit"), Command.stderr("inherit"))).pipe(Effect.mapError((cause) => new BuildFailedError({
2217
2316
  step,
2218
2317
  exitCode: 1,
2219
2318
  message: `${step} failed to spawn: ${String(cause)}`
@@ -2227,7 +2326,7 @@ const runStep = (cmd, step) => Command$1.exitCode(cmd.pipe(Command$1.stdout("inh
2227
2326
  * stderr passes through to the terminal directly.
2228
2327
  */
2229
2328
  const runStepFormatted = (cmd, step, formatter) => Effect.gen(function* () {
2230
- const proc = yield* Command$1.start(cmd.pipe(Command$1.stdout("pipe"), Command$1.stderr("pipe"))).pipe(Effect.mapError((cause) => new BuildFailedError({
2329
+ const proc = yield* Command.start(cmd.pipe(Command.stdout("pipe"), Command.stderr("pipe"))).pipe(Effect.mapError((cause) => new BuildFailedError({
2231
2330
  step,
2232
2331
  exitCode: 1,
2233
2332
  message: `${step} failed to spawn: ${String(cause)}`
@@ -2235,14 +2334,14 @@ const runStepFormatted = (cmd, step, formatter) => Effect.gen(function* () {
2235
2334
  const stdoutFiber = yield* proc.stdout.pipe(Stream.decodeText(), Stream.splitLines, Stream.runForEach((line) => {
2236
2335
  const formatted = formatter.pipe(line);
2237
2336
  return formatted.length > 0 ? Effect.sync(() => {
2238
- for (const output of formatted) process.stdout.write(`${output}\n`);
2337
+ for (const output of formatted) process$1.stdout.write(`${output}\n`);
2239
2338
  }) : Effect.void;
2240
2339
  }), Effect.mapError((cause) => new BuildFailedError({
2241
2340
  step,
2242
2341
  exitCode: 1,
2243
2342
  message: `${step} stdout stream error: ${String(cause)}`
2244
2343
  })), Effect.fork);
2245
- const stderrFiber = yield* proc.stderr.pipe(Stream.decodeText(), Stream.splitLines, Stream.runForEach((line) => Effect.sync(() => process.stderr.write(`${line}\n`))), Effect.mapError((cause) => new BuildFailedError({
2344
+ const stderrFiber = yield* proc.stderr.pipe(Stream.decodeText(), Stream.splitLines, Stream.runForEach((line) => Effect.sync(() => process$1.stderr.write(`${line}\n`))), Effect.mapError((cause) => new BuildFailedError({
2246
2345
  step,
2247
2346
  exitCode: 1,
2248
2347
  message: `${step} stderr stream error: ${String(cause)}`
@@ -2255,7 +2354,7 @@ const runStepFormatted = (cmd, step, formatter) => Effect.gen(function* () {
2255
2354
  })));
2256
2355
  if (code !== 0) {
2257
2356
  const summary = formatter.getBuildSummary();
2258
- if (summary) process.stderr.write(`${summary}\n`);
2357
+ if (summary) process$1.stderr.write(`${summary}\n`);
2259
2358
  return yield* new BuildFailedError({
2260
2359
  step,
2261
2360
  exitCode: code,
@@ -2293,7 +2392,7 @@ const runAndroidBuild = (input) => Effect.gen(function* () {
2293
2392
  applicationIdentifier,
2294
2393
  tempDir
2295
2394
  });
2296
- yield* runStep(Command$1.make("bunx", "expo", "prebuild", "--platform", "android", "--clean").pipe(Command$1.workingDirectory(projectRoot), Command$1.env(commandEnv)), "expo prebuild android");
2395
+ yield* runStep(Command.make("bunx", "expo", "prebuild", "--platform", "android", "--clean").pipe(Command.workingDirectory(projectRoot), Command.env(commandEnv)), "expo prebuild android");
2297
2396
  const fs = yield* FileSystem.FileSystem;
2298
2397
  const signingGradlePath = path.join(tempDir, "signing.gradle");
2299
2398
  yield* fs.writeFileString(signingGradlePath, renderSigningGradle({
@@ -2303,7 +2402,7 @@ const runAndroidBuild = (input) => Effect.gen(function* () {
2303
2402
  keyPassword: credentials.keyPassword
2304
2403
  }));
2305
2404
  const taskName = gradleTaskName(format, flavor, buildType);
2306
- yield* runStep(Command$1.make("./gradlew", "--init-script", signingGradlePath, `:app:${taskName}`).pipe(Command$1.workingDirectory(androidDir), Command$1.env(commandEnv)), "gradlew");
2405
+ yield* runStep(Command.make("./gradlew", "--init-script", signingGradlePath, `:app:${taskName}`).pipe(Command.workingDirectory(androidDir), Command.env(commandEnv)), "gradlew");
2307
2406
  const artifactPath = yield* findAndroidArtifact({
2308
2407
  projectRoot,
2309
2408
  format,
@@ -2358,9 +2457,9 @@ const renderExportOptionsPlist = ({ method, teamId, bundleId, provisioningProfil
2358
2457
 
2359
2458
  //#endregion
2360
2459
  //#region src/lib/ios-keychain.ts
2361
- const runOrFail = (cmd, step) => Command$1.string(cmd).pipe(Effect.mapError((cause) => new KeychainError({ message: `keychain ${step} failed: ${String(cause)}` })));
2460
+ const runOrFail = (cmd, step) => Command.string(cmd).pipe(Effect.mapError((cause) => new KeychainError({ message: `keychain ${step} failed: ${String(cause)}` })));
2362
2461
  const listCurrentKeychains = Effect.gen(function* () {
2363
- return (yield* runOrFail(Command$1.make("security", "list-keychains", "-d", "user"), "list-keychains")).split("\n").map((line) => line.trim().replace(/^"/, "").replace(/"$/, "")).filter((line) => line.length > 0);
2462
+ return (yield* runOrFail(Command.make("security", "list-keychains", "-d", "user"), "list-keychains")).split("\n").map((line) => line.trim().replace(/^"/, "").replace(/"$/, "")).filter((line) => line.length > 0);
2364
2463
  });
2365
2464
  const parseSigningIdentity = (output) => {
2366
2465
  const lines = output.split("\n");
@@ -2381,13 +2480,13 @@ const acquireKeychain = ({ tempDir, p12Path, p12Password }) => {
2381
2480
  const keychainPassword = randomBytes(32).toString("hex");
2382
2481
  return Effect.acquireRelease(Effect.gen(function* () {
2383
2482
  const priorKeychains = yield* listCurrentKeychains;
2384
- yield* runOrFail(Command$1.make("security", "create-keychain", "-p", keychainPassword, keychainPath), "create-keychain");
2385
- yield* runOrFail(Command$1.make("security", "unlock-keychain", "-p", keychainPassword, keychainPath), "unlock-keychain");
2386
- yield* runOrFail(Command$1.make("security", "set-keychain-settings", "-t", "3600", "-l", keychainPath), "set-keychain-settings");
2387
- yield* runOrFail(Command$1.make("security", "import", p12Path, "-k", keychainPath, "-P", p12Password, "-T", "/usr/bin/codesign"), "import");
2388
- yield* runOrFail(Command$1.make("security", "set-key-partition-list", "-S", "apple-tool:,apple:,codesign:", "-s", "-k", keychainPassword, keychainPath), "set-key-partition-list");
2389
- yield* runOrFail(Command$1.make("security", "list-keychains", "-d", "user", "-s", keychainPath, ...priorKeychains), "list-keychains -s (add)");
2390
- const signingIdentity = parseSigningIdentity(yield* runOrFail(Command$1.make("security", "find-identity", "-v", "-p", "codesigning", keychainPath), "find-identity"));
2483
+ yield* runOrFail(Command.make("security", "create-keychain", "-p", keychainPassword, keychainPath), "create-keychain");
2484
+ yield* runOrFail(Command.make("security", "unlock-keychain", "-p", keychainPassword, keychainPath), "unlock-keychain");
2485
+ yield* runOrFail(Command.make("security", "set-keychain-settings", "-t", "3600", "-l", keychainPath), "set-keychain-settings");
2486
+ yield* runOrFail(Command.make("security", "import", p12Path, "-k", keychainPath, "-P", p12Password, "-T", "/usr/bin/codesign"), "import");
2487
+ yield* runOrFail(Command.make("security", "set-key-partition-list", "-S", "apple-tool:,apple:,codesign:", "-s", "-k", keychainPassword, keychainPath), "set-key-partition-list");
2488
+ yield* runOrFail(Command.make("security", "list-keychains", "-d", "user", "-s", keychainPath, ...priorKeychains), "list-keychains -s (add)");
2489
+ const signingIdentity = parseSigningIdentity(yield* runOrFail(Command.make("security", "find-identity", "-v", "-p", "codesigning", keychainPath), "find-identity"));
2391
2490
  if (!signingIdentity) return yield* new KeychainError({ message: "No code signing identity found after importing .p12 into ephemeral keychain." });
2392
2491
  return {
2393
2492
  handle: {
@@ -2398,8 +2497,8 @@ const acquireKeychain = ({ tempDir, p12Path, p12Password }) => {
2398
2497
  priorKeychains
2399
2498
  };
2400
2499
  }), ({ priorKeychains }) => Effect.gen(function* () {
2401
- yield* Command$1.string(Command$1.make("security", "list-keychains", "-d", "user", "-s", ...priorKeychains)).pipe(Effect.catchAll(() => Effect.void));
2402
- yield* Command$1.string(Command$1.make("security", "delete-keychain", keychainPath)).pipe(Effect.catchAll(() => Effect.void));
2500
+ yield* Command.string(Command.make("security", "list-keychains", "-d", "user", "-s", ...priorKeychains)).pipe(Effect.catchAll(() => Effect.void));
2501
+ yield* Command.string(Command.make("security", "delete-keychain", keychainPath)).pipe(Effect.catchAll(() => Effect.void));
2403
2502
  })).pipe(Effect.map(({ handle }) => handle));
2404
2503
  };
2405
2504
 
@@ -2464,7 +2563,7 @@ const userProvisioningProfilesDir = () => path.join(os.homedir(), "Library", "Mo
2464
2563
  */
2465
2564
  const installProvisioningProfile = ({ profilePath }) => Effect.acquireRelease(Effect.gen(function* () {
2466
2565
  const fs = yield* FileSystem.FileSystem;
2467
- const info = yield* extractProvisioningInfo(yield* Command$1.string(Command$1.make("security", "cms", "-D", "-i", profilePath)).pipe(Effect.mapError((cause) => new ProvisioningError({ message: `security cms -D failed for ${profilePath}: ${String(cause)}` }))));
2566
+ const info = yield* extractProvisioningInfo(yield* Command.string(Command.make("security", "cms", "-D", "-i", profilePath)).pipe(Effect.mapError((cause) => new ProvisioningError({ message: `security cms -D failed for ${profilePath}: ${String(cause)}` }))));
2468
2567
  const targetDir = userProvisioningProfilesDir();
2469
2568
  const installedPath = path.join(targetDir, `${info.uuid}.mobileprovision`);
2470
2569
  yield* fs.makeDirectory(targetDir, { recursive: true }).pipe(Effect.catchAll((cause) => new ProvisioningError({ message: `Failed to create provisioning profiles dir: ${String(cause)}` })));
@@ -2534,7 +2633,7 @@ const checkBundleId = (appDir, expectedBundleId) => Effect.gen(function* () {
2534
2633
  const checkEmbeddedProfile = (appDir, expectedUuid, expectedTeamId) => Effect.gen(function* () {
2535
2634
  const warnings = [];
2536
2635
  const profilePath = path.join(appDir, "embedded.mobileprovision");
2537
- const parsed = parsePlistXml(yield* Command$1.string(Command$1.make("security", "cms", "-D", "-i", profilePath)));
2636
+ const parsed = parsePlistXml(yield* Command.string(Command.make("security", "cms", "-D", "-i", profilePath)));
2538
2637
  const actualUuid = parsed["UUID"];
2539
2638
  if (typeof actualUuid === "string" && actualUuid !== expectedUuid) warnings.push(`Profile UUID mismatch: expected "${expectedUuid}", got "${actualUuid}"`);
2540
2639
  const teamIdentifiers = parsed["TeamIdentifier"];
@@ -2585,8 +2684,8 @@ const runIosBuild = (input) => Effect.gen(function* () {
2585
2684
  distribution,
2586
2685
  tempDir
2587
2686
  });
2588
- yield* runStep(Command$1.make("bunx", "expo", "prebuild", "--platform", "ios", "--clean").pipe(Command$1.workingDirectory(projectRoot), Command$1.env(commandEnv)), "expo prebuild ios");
2589
- yield* runStep(Command$1.make("pod", "install").pipe(Command$1.workingDirectory(iosDir), Command$1.env(commandEnv)), "pod install");
2687
+ yield* runStep(Command.make("bunx", "expo", "prebuild", "--platform", "ios", "--clean").pipe(Command.workingDirectory(projectRoot), Command.env(commandEnv)), "expo prebuild ios");
2688
+ yield* runStep(Command.make("pod", "install").pipe(Command.workingDirectory(iosDir), Command.env(commandEnv)), "pod install");
2590
2689
  const keychain = yield* acquireKeychain({
2591
2690
  tempDir,
2592
2691
  p12Path: credentials.p12Path,
@@ -2597,7 +2696,7 @@ const runIosBuild = (input) => Effect.gen(function* () {
2597
2696
  const scheme = iosProfile.scheme ?? workspaceFilename.replace(/\.xcworkspace$/, "");
2598
2697
  const configuration = iosProfile.buildConfiguration ?? "Release";
2599
2698
  const archivePath = path.join(tempDir, "build.xcarchive");
2600
- const archiveCmd = Command$1.make("xcodebuild", "-workspace", workspaceFilename, "-scheme", scheme, "-configuration", configuration, "-archivePath", archivePath, "-allowProvisioningUpdates", "archive", "CODE_SIGN_STYLE=Manual", `DEVELOPMENT_TEAM=${provisioning.teamId}`, `CODE_SIGN_IDENTITY=${keychain.signingIdentity}`, `PROVISIONING_PROFILE_SPECIFIER=${provisioning.name}`).pipe(Command$1.workingDirectory(iosDir), Command$1.env(commandEnv));
2699
+ const archiveCmd = Command.make("xcodebuild", "-workspace", workspaceFilename, "-scheme", scheme, "-configuration", configuration, "-archivePath", archivePath, "-allowProvisioningUpdates", "archive", "CODE_SIGN_STYLE=Manual", `DEVELOPMENT_TEAM=${provisioning.teamId}`, `CODE_SIGN_IDENTITY=${keychain.signingIdentity}`, `PROVISIONING_PROFILE_SPECIFIER=${provisioning.name}`).pipe(Command.workingDirectory(iosDir), Command.env(commandEnv));
2601
2700
  const formatter = input.rawOutput ? void 0 : createXcodebuildFormatter(projectRoot);
2602
2701
  yield* formatter ? runStepFormatted(archiveCmd, "xcodebuild archive", formatter) : runStep(archiveCmd, "xcodebuild archive");
2603
2702
  const fs = yield* FileSystem.FileSystem;
@@ -2609,7 +2708,7 @@ const runIosBuild = (input) => Effect.gen(function* () {
2609
2708
  provisioningProfileName: provisioning.name
2610
2709
  }));
2611
2710
  const exportPath = path.join(tempDir, "export");
2612
- const exportCmd = Command$1.make("xcodebuild", "-exportArchive", "-archivePath", archivePath, "-exportPath", exportPath, "-exportOptionsPlist", exportOptionsPath, "-allowProvisioningUpdates").pipe(Command$1.workingDirectory(iosDir), Command$1.env(commandEnv));
2711
+ const exportCmd = Command.make("xcodebuild", "-exportArchive", "-archivePath", archivePath, "-exportPath", exportPath, "-exportOptionsPlist", exportOptionsPath, "-allowProvisioningUpdates").pipe(Command.workingDirectory(iosDir), Command.env(commandEnv));
2613
2712
  yield* formatter ? runStepFormatted(exportCmd, "xcodebuild exportArchive", formatter) : runStep(exportCmd, "xcodebuild exportArchive");
2614
2713
  yield* validateIosBuild({
2615
2714
  archivePath,
@@ -2819,8 +2918,8 @@ const pullEnvVars = (api, { projectId, environment }) => api["env-vars"].export(
2819
2918
  const readExpoConfig = (projectRoot, envVars = {}) => Effect.acquireUseRelease(Effect.sync(() => {
2820
2919
  const previous = {};
2821
2920
  for (const [key, value] of Object.entries(envVars)) {
2822
- previous[key] = process.env[key];
2823
- process.env[key] = value;
2921
+ previous[key] = process$1.env[key];
2922
+ process$1.env[key] = value;
2824
2923
  }
2825
2924
  return previous;
2826
2925
  }), () => Effect.try({
@@ -2831,8 +2930,8 @@ const readExpoConfig = (projectRoot, envVars = {}) => Effect.acquireUseRelease(E
2831
2930
  },
2832
2931
  catch: () => void 0
2833
2932
  }).pipe(Effect.catchAll(() => Effect.succeed(void 0))), (previous) => Effect.sync(() => {
2834
- for (const [key, value] of Object.entries(previous)) if (value === void 0) delete process.env[key];
2835
- else process.env[key] = value;
2933
+ for (const [key, value] of Object.entries(previous)) if (value === void 0) delete process$1.env[key];
2934
+ else process$1.env[key] = value;
2836
2935
  }));
2837
2936
  const extractBuildNumber = (config, platform) => {
2838
2937
  if (platform === "ios") return config.ios?.buildNumber;
@@ -2862,7 +2961,7 @@ const readAppMetaFromConfig = (config, platform) => Effect.gen(function* () {
2862
2961
 
2863
2962
  //#endregion
2864
2963
  //#region src/lib/git-context.ts
2865
- const runString = (cmd, cwd) => Command$1.string(Command$1.workingDirectory(cmd, cwd));
2964
+ const runString = (cmd, cwd) => Command.string(Command.workingDirectory(cmd, cwd));
2866
2965
  /**
2867
2966
  * Best-effort git context extraction. If git is missing, the directory isn't
2868
2967
  * a repo, or any command fails, we silently return undefined fields so the
@@ -2871,10 +2970,10 @@ const runString = (cmd, cwd) => Command$1.string(Command$1.workingDirectory(cmd,
2871
2970
  */
2872
2971
  const readGitContext = (projectRoot) => Effect.gen(function* () {
2873
2972
  const [commit, ref, commitMessage, status] = yield* Effect.all([
2874
- runString(Command$1.make("git", "rev-parse", "HEAD"), projectRoot).pipe(Effect.map((output) => output.trim()), Effect.catchAll(() => Effect.succeed(""))),
2875
- runString(Command$1.make("git", "symbolic-ref", "--short", "HEAD"), projectRoot).pipe(Effect.map((output) => output.trim()), Effect.catchAll(() => Effect.succeed(""))),
2876
- runString(Command$1.make("git", "log", "-1", "--format=%s"), projectRoot).pipe(Effect.map((output) => output.trim()), Effect.catchAll(() => Effect.succeed(""))),
2877
- runString(Command$1.make("git", "status", "--porcelain"), projectRoot).pipe(Effect.catchAll(() => Effect.succeed("")))
2973
+ runString(Command.make("git", "rev-parse", "HEAD"), projectRoot).pipe(Effect.map((output) => output.trim()), Effect.catchAll(() => Effect.succeed(""))),
2974
+ runString(Command.make("git", "symbolic-ref", "--short", "HEAD"), projectRoot).pipe(Effect.map((output) => output.trim()), Effect.catchAll(() => Effect.succeed(""))),
2975
+ runString(Command.make("git", "log", "-1", "--format=%s"), projectRoot).pipe(Effect.map((output) => output.trim()), Effect.catchAll(() => Effect.succeed(""))),
2976
+ runString(Command.make("git", "status", "--porcelain"), projectRoot).pipe(Effect.catchAll(() => Effect.succeed("")))
2878
2977
  ], { concurrency: "unbounded" });
2879
2978
  return {
2880
2979
  ref: ref.length > 0 ? ref : void 0,
@@ -2946,8 +3045,8 @@ const unquote = (input) => input.startsWith("\"") && input.endsWith("\"") ? inpu
2946
3045
  //#region src/lib/fingerprint.ts
2947
3046
  var FingerprintError = class extends Data.TaggedError("FingerprintError") {};
2948
3047
  const runFingerprintFull = (projectRoot) => Effect.gen(function* () {
2949
- const cmd = Command$1.make("bunx", "@expo/fingerprint", projectRoot).pipe(Command$1.workingDirectory(projectRoot));
2950
- const stdout = yield* Command$1.string(cmd).pipe(Effect.mapError((cause) => new FingerprintError({ message: `Failed to run "@expo/fingerprint": ${cause.message}` })));
3048
+ const cmd = Command.make("bunx", "@expo/fingerprint", projectRoot).pipe(Command.workingDirectory(projectRoot));
3049
+ const stdout = yield* Command.string(cmd).pipe(Effect.mapError((cause) => new FingerprintError({ message: `Failed to run "@expo/fingerprint": ${cause.message}` })));
2951
3050
  const parsed = yield* Effect.try({
2952
3051
  try: () => JSON.parse(stdout),
2953
3052
  catch: () => new FingerprintError({ message: "Failed to parse @expo/fingerprint output as JSON." })
@@ -3124,162 +3223,260 @@ const runBuildWorkflow = (options) => Effect.scoped(Effect.gen(function* () {
3124
3223
 
3125
3224
  //#endregion
3126
3225
  //#region src/commands/build/index.ts
3127
- const platform$7 = Options.choice("platform", ["ios", "android"]);
3128
- const profile$1 = Options.text("profile").pipe(Options.withDefault("production"));
3129
- const message$3 = Options.text("message").pipe(Options.optional);
3130
- const noUpload = Options.boolean("no-upload");
3131
- const rawOutput = Options.boolean("raw-output");
3132
- const buildCommand = Command.make("build", {
3133
- platform: platform$7,
3134
- profile: profile$1,
3135
- message: message$3,
3136
- noUpload,
3137
- rawOutput
3138
- }, (opts) => runBuildWorkflow({
3139
- platform: opts.platform,
3140
- profileName: opts.profile,
3141
- message: Option.getOrUndefined(opts.message),
3142
- noUpload: opts.noUpload,
3143
- rawOutput: opts.rawOutput
3144
- }).pipe(Effect.catchTags({
3145
- AuthRequiredError: (err) => exitWith(3, err.message),
3146
- ProjectNotLinkedError: (err) => exitWith(4, err.message),
3147
- BuildProfileError: (err) => exitWith(2, err.message),
3148
- RuntimeVersionError: (err) => exitWith(2, err.message),
3149
- MissingCredentialsError: (err) => exitWith(5, `${err.message}\n${err.hint}`),
3150
- BuildFailedError: (err) => exitWith(6, err.message),
3151
- KeychainError: (err) => exitWith(6, err.message),
3152
- ProvisioningError: (err) => exitWith(6, err.message),
3153
- ArtifactNotFoundError: (err) => exitWith(6, err.message),
3154
- ReserveError: (err) => exitWith(7, err.message),
3155
- UploadFailedError: (err) => exitWith(7, err.message),
3156
- PresignedUrlExpiredError: (err) => exitWith(7, err.message),
3157
- CompleteError: (err) => exitWith(7, err.message),
3158
- EnvExportError: (err) => exitWith(7, err.message),
3159
- SystemError: (err) => exitWith(6, `Filesystem error: ${err.message}`),
3160
- BadArgument: (err) => exitWith(6, `Invalid argument: ${err.message}`)
3161
- })));
3162
-
3163
- //#endregion
3164
- //#region src/commands/builds/helpers.ts
3165
- const handleBuildsCommandErrors = makeCommandErrorHandler();
3226
+ const BUILD_EXIT_EXTRAS = {
3227
+ BuildProfileError: 2,
3228
+ RuntimeVersionError: 2,
3229
+ MissingCredentialsError: 5,
3230
+ BuildFailedError: 6,
3231
+ KeychainError: 6,
3232
+ ProvisioningError: 6,
3233
+ ArtifactNotFoundError: 6,
3234
+ ReserveError: 7,
3235
+ UploadFailedError: 7,
3236
+ PresignedUrlExpiredError: 7,
3237
+ CompleteError: 7,
3238
+ EnvExportError: 7
3239
+ };
3240
+ const buildCommand = defineCommand({
3241
+ meta: {
3242
+ name: "build",
3243
+ description: "Build the app locally and optionally upload"
3244
+ },
3245
+ args: {
3246
+ platform: {
3247
+ type: "enum",
3248
+ options: ["ios", "android"],
3249
+ required: true
3250
+ },
3251
+ profile: {
3252
+ type: "string",
3253
+ default: "production",
3254
+ description: "Build profile name"
3255
+ },
3256
+ message: {
3257
+ type: "string",
3258
+ description: "Optional build message"
3259
+ },
3260
+ upload: {
3261
+ type: "boolean",
3262
+ default: true,
3263
+ description: "Upload the built artifact to better-update",
3264
+ negativeDescription: "Skip upload (use --no-upload)"
3265
+ },
3266
+ "raw-output": {
3267
+ type: "boolean",
3268
+ description: "Stream raw Gradle/Xcode output"
3269
+ }
3270
+ },
3271
+ run: async ({ args }) => runEffect(runBuildWorkflow({
3272
+ platform: args.platform,
3273
+ profileName: args.profile,
3274
+ message: args.message,
3275
+ noUpload: !args.upload,
3276
+ rawOutput: args["raw-output"] ?? false
3277
+ }), BUILD_EXIT_EXTRAS)
3278
+ });
3166
3279
 
3167
3280
  //#endregion
3168
3281
  //#region src/commands/builds/compatibility-matrix.ts
3169
- const compatibilityMatrixCommand = Command.make("compatibility-matrix", {}, () => Effect.gen(function* () {
3170
- const projectId = yield* readProjectId;
3171
- const result = yield* (yield* apiClient).builds.compatibilityMatrix({ urlParams: { projectId } });
3172
- if (result.rows.length === 0 && result.missingRuntimeVersions.length === 0) {
3173
- yield* Console.log("No compatibility data found.");
3174
- return;
3175
- }
3176
- if (result.rows.length > 0) {
3177
- yield* Console.log("Build-to-Channel Compatibility:");
3178
- yield* printTable([
3179
- "Build ID",
3180
- "Platform",
3181
- "Runtime Version",
3182
- "Channels"
3183
- ], result.rows.map((row) => [
3184
- row.id,
3185
- row.platform,
3186
- row.runtimeVersion ?? "-",
3187
- row.channels.map((channel) => channel.channelName).join(", ") || "-"
3188
- ]));
3189
- }
3190
- if (result.missingRuntimeVersions.length > 0) {
3191
- yield* Console.log("\nMissing Runtime Versions:");
3192
- yield* printTable([
3193
- "Channel",
3194
- "Platform",
3195
- "Runtime Version",
3196
- "Updates"
3197
- ], result.missingRuntimeVersions.map((missing) => [
3198
- missing.channelName,
3199
- missing.platform,
3200
- missing.runtimeVersion,
3201
- String(missing.updateCount)
3202
- ]));
3203
- }
3204
- }).pipe(handleBuildsCommandErrors));
3282
+ const compatibilityMatrixCommand = defineCommand({
3283
+ meta: {
3284
+ name: "compatibility-matrix",
3285
+ description: "Show build-to-channel compatibility and missing runtime versions"
3286
+ },
3287
+ run: async () => runEffect(Effect.gen(function* () {
3288
+ const projectId = yield* readProjectId;
3289
+ const result = yield* (yield* apiClient).builds.compatibilityMatrix({ urlParams: { projectId } });
3290
+ if (result.rows.length === 0 && result.missingRuntimeVersions.length === 0) {
3291
+ yield* Console.log("No compatibility data found.");
3292
+ return;
3293
+ }
3294
+ if (result.rows.length > 0) {
3295
+ yield* Console.log("Build-to-Channel Compatibility:");
3296
+ yield* printTable([
3297
+ "Build ID",
3298
+ "Platform",
3299
+ "Runtime Version",
3300
+ "Channels"
3301
+ ], result.rows.map((row) => [
3302
+ row.id,
3303
+ row.platform,
3304
+ row.runtimeVersion ?? "-",
3305
+ row.channels.map((channel) => channel.channelName).join(", ") || "-"
3306
+ ]));
3307
+ }
3308
+ if (result.missingRuntimeVersions.length > 0) {
3309
+ yield* Console.log("\nMissing Runtime Versions:");
3310
+ yield* printTable([
3311
+ "Channel",
3312
+ "Platform",
3313
+ "Runtime Version",
3314
+ "Updates"
3315
+ ], result.missingRuntimeVersions.map((missing) => [
3316
+ missing.channelName,
3317
+ missing.platform,
3318
+ missing.runtimeVersion,
3319
+ String(missing.updateCount)
3320
+ ]));
3321
+ }
3322
+ }))
3323
+ });
3205
3324
 
3206
3325
  //#endregion
3207
3326
  //#region src/commands/builds/delete.ts
3208
- const id$8 = Args.text({ name: "id" });
3209
- const deleteCommand$5 = Command.make("delete", { id: id$8 }, (opts) => Effect.gen(function* () {
3210
- yield* (yield* apiClient).builds.delete({ path: { id: opts.id } });
3211
- yield* Console.log(`Build ${opts.id} deleted.`);
3212
- }).pipe(handleBuildsCommandErrors));
3327
+ const deleteCommand$5 = defineCommand({
3328
+ meta: {
3329
+ name: "delete",
3330
+ description: "Delete a build"
3331
+ },
3332
+ args: { id: {
3333
+ type: "positional",
3334
+ required: true,
3335
+ description: "Build ID"
3336
+ } },
3337
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
3338
+ yield* (yield* apiClient).builds.delete({ path: { id: args.id } });
3339
+ yield* Console.log(`Build ${args.id} deleted.`);
3340
+ }))
3341
+ });
3213
3342
 
3214
3343
  //#endregion
3215
3344
  //#region src/commands/builds/get.ts
3216
- const id$7 = Args.text({ name: "id" });
3217
- const getCommand$2 = Command.make("get", { id: id$7 }, (opts) => Effect.gen(function* () {
3218
- const build = yield* (yield* apiClient).builds.get({ path: { id: opts.id } });
3219
- yield* printKeyValue([
3220
- ["ID", build.id],
3221
- ["Platform", build.platform],
3222
- ["Profile", build.profile],
3223
- ["Distribution", build.distribution],
3224
- ["Version", build.appVersion ?? "-"],
3225
- ["Build Number", build.buildNumber ?? "-"],
3226
- ["Runtime Version", build.runtimeVersion ?? "-"],
3227
- ["Bundle ID", build.bundleId ?? "-"],
3228
- ["Git Ref", build.gitRef ?? "-"],
3229
- ["Message", build.message ?? "-"],
3230
- ["Artifact", build.artifact ? `${build.artifact.format} (${String(build.artifact.byteSize)} bytes)` : "none"],
3231
- ["Created", build.createdAt]
3232
- ]);
3233
- }).pipe(handleBuildsCommandErrors));
3345
+ const getCommand$2 = defineCommand({
3346
+ meta: {
3347
+ name: "get",
3348
+ description: "Show a build"
3349
+ },
3350
+ args: { id: {
3351
+ type: "positional",
3352
+ required: true,
3353
+ description: "Build ID"
3354
+ } },
3355
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
3356
+ const build = yield* (yield* apiClient).builds.get({ path: { id: args.id } });
3357
+ yield* printKeyValue([
3358
+ ["ID", build.id],
3359
+ ["Platform", build.platform],
3360
+ ["Profile", build.profile],
3361
+ ["Distribution", build.distribution],
3362
+ ["Version", build.appVersion ?? "-"],
3363
+ ["Build Number", build.buildNumber ?? "-"],
3364
+ ["Runtime Version", build.runtimeVersion ?? "-"],
3365
+ ["Bundle ID", build.bundleId ?? "-"],
3366
+ ["Git Ref", build.gitRef ?? "-"],
3367
+ ["Message", build.message ?? "-"],
3368
+ ["Artifact", build.artifact ? `${build.artifact.format} (${String(build.artifact.byteSize)} bytes)` : "none"],
3369
+ ["Created", build.createdAt]
3370
+ ]);
3371
+ }))
3372
+ });
3234
3373
 
3235
3374
  //#endregion
3236
3375
  //#region src/commands/builds/install-link.ts
3237
- const id$6 = Args.text({ name: "id" });
3238
- const installLinkCommand = Command.make("install-link", { id: id$6 }, (opts) => Effect.gen(function* () {
3239
- const result = yield* (yield* apiClient).builds.getInstallLink({ path: { id: opts.id } });
3240
- yield* printKeyValue([
3241
- ["Artifact URL", result.artifactUrl],
3242
- ["Install URL", result.installUrl ?? "-"],
3243
- ["Expires", String(result.expires)]
3244
- ]);
3245
- }).pipe(handleBuildsCommandErrors));
3376
+ const installLinkCommand = defineCommand({
3377
+ meta: {
3378
+ name: "install-link",
3379
+ description: "Get install/artifact URLs for a build"
3380
+ },
3381
+ args: { id: {
3382
+ type: "positional",
3383
+ required: true,
3384
+ description: "Build ID"
3385
+ } },
3386
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
3387
+ const result = yield* (yield* apiClient).builds.getInstallLink({ path: { id: args.id } });
3388
+ yield* printKeyValue([
3389
+ ["Artifact URL", result.artifactUrl],
3390
+ ["Install URL", result.installUrl ?? "-"],
3391
+ ["Expires", String(result.expires)]
3392
+ ]);
3393
+ }))
3394
+ });
3395
+
3396
+ //#endregion
3397
+ //#region src/lib/cli-schemas.ts
3398
+ const RolloutPercentage = Schema.Number.pipe(Schema.int(), Schema.between(1, 100)).annotations({
3399
+ message: () => "Rollout percentage must be between 1 and 100.",
3400
+ identifier: "RolloutPercentage"
3401
+ });
3402
+ const KeyValuePair = Schema.Struct({
3403
+ key: Schema.String,
3404
+ value: Schema.String
3405
+ });
3406
+ const KeyValueFromString = Schema.transformOrFail(Schema.String, KeyValuePair, {
3407
+ strict: true,
3408
+ decode: (input, _options, ast) => {
3409
+ const eqIndex = input.indexOf("=");
3410
+ if (eqIndex <= 0) return ParseResult.fail(new ParseResult.Type(ast, input, "Invalid format. Use KEY=VALUE (e.g. API_KEY=abc123)"));
3411
+ return ParseResult.succeed({
3412
+ key: input.slice(0, eqIndex),
3413
+ value: input.slice(eqIndex + 1)
3414
+ });
3415
+ },
3416
+ encode: ({ key, value }) => ParseResult.succeed(`${key}=${value}`)
3417
+ });
3418
+ const parseRolloutPercentage = (raw, flag) => Effect.try({
3419
+ try: () => Schema.decodeUnknownSync(RolloutPercentage)(Number(raw)),
3420
+ catch: () => new InvalidArgumentError({ message: `--${flag} must be an integer between 1 and 100, got "${raw}".` })
3421
+ });
3422
+ const parseKeyValue = (raw) => Effect.try({
3423
+ try: () => Schema.decodeUnknownSync(KeyValueFromString)(raw),
3424
+ catch: () => new InvalidArgumentError({ message: "Invalid format. Use KEY=VALUE (e.g. API_KEY=abc123)" })
3425
+ });
3426
+ const parseLimit = (raw, defaultValue) => {
3427
+ if (raw === void 0) return Effect.succeed(defaultValue);
3428
+ const parsed = Number(raw);
3429
+ if (!Number.isInteger(parsed) || parsed < 1) return Effect.fail(new InvalidArgumentError({ message: `--limit must be a positive integer, got "${raw}".` }));
3430
+ return Effect.succeed(parsed);
3431
+ };
3246
3432
 
3247
3433
  //#endregion
3248
3434
  //#region src/commands/builds/list.ts
3249
- const platform$6 = Options.choice("platform", ["ios", "android"]).pipe(Options.optional);
3250
- const limit$1 = Options.integer("limit").pipe(Options.withDefault(10));
3251
- const listCommand$5 = Command.make("list", {
3252
- platform: platform$6,
3253
- limit: limit$1
3254
- }, (opts) => Effect.gen(function* () {
3255
- const projectId = yield* readProjectId;
3256
- const api = yield* apiClient;
3257
- const platformFilter = Option.match(opts.platform, {
3258
- onNone: () => ({}),
3259
- onSome: (platformValue) => ({ platform: platformValue })
3260
- });
3261
- const { items } = yield* api.builds.list({ urlParams: {
3262
- projectId,
3263
- ...platformFilter,
3264
- page: 1,
3265
- limit: opts.limit
3266
- } });
3267
- yield* printTable([
3268
- "ID",
3269
- "Platform",
3270
- "Profile",
3271
- "Distribution",
3272
- "Version",
3273
- "Created"
3274
- ], items.map((build) => [
3275
- build.id,
3276
- build.platform,
3277
- build.profile,
3278
- build.distribution,
3279
- build.appVersion ?? "-",
3280
- build.createdAt
3281
- ]));
3282
- }).pipe(handleBuildsCommandErrors));
3435
+ const listCommand$5 = defineCommand({
3436
+ meta: {
3437
+ name: "list",
3438
+ description: "List builds for the linked project"
3439
+ },
3440
+ args: {
3441
+ platform: {
3442
+ type: "enum",
3443
+ options: ["ios", "android"],
3444
+ description: "Filter by platform"
3445
+ },
3446
+ limit: {
3447
+ type: "string",
3448
+ default: "10",
3449
+ description: "Max rows (default 10)"
3450
+ }
3451
+ },
3452
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
3453
+ const limit = yield* parseLimit(args.limit, 10);
3454
+ const projectId = yield* readProjectId;
3455
+ const api = yield* apiClient;
3456
+ const platformFilter = args.platform ? { platform: args.platform } : {};
3457
+ const { items } = yield* api.builds.list({ urlParams: {
3458
+ projectId,
3459
+ ...platformFilter,
3460
+ page: 1,
3461
+ limit
3462
+ } });
3463
+ yield* printTable([
3464
+ "ID",
3465
+ "Platform",
3466
+ "Profile",
3467
+ "Distribution",
3468
+ "Version",
3469
+ "Created"
3470
+ ], items.map((build) => [
3471
+ build.id,
3472
+ build.platform,
3473
+ build.profile,
3474
+ build.distribution,
3475
+ build.appVersion ?? "-",
3476
+ build.createdAt
3477
+ ]));
3478
+ }))
3479
+ });
3283
3480
 
3284
3481
  //#endregion
3285
3482
  //#region src/application/upload-workflow.ts
@@ -3369,44 +3566,67 @@ const runUploadWorkflow = (options) => Effect.gen(function* () {
3369
3566
 
3370
3567
  //#endregion
3371
3568
  //#region src/commands/builds/upload.ts
3372
- const artifactPath = Args.text({ name: "artifact-path" });
3373
- const platform$5 = Options.choice("platform", ["ios", "android"]);
3374
- const profile = Options.text("profile").pipe(Options.withDefault("production"));
3375
- const message$2 = Options.text("message").pipe(Options.optional);
3376
- const uploadCommand$1 = Command.make("upload", {
3377
- artifactPath,
3378
- platform: platform$5,
3379
- profile,
3380
- message: message$2
3381
- }, (opts) => runUploadWorkflow({
3382
- artifactPath: opts.artifactPath,
3383
- platform: opts.platform,
3384
- profileName: opts.profile,
3385
- message: Option.getOrUndefined(opts.message)
3386
- }).pipe(Effect.catchTags({
3387
- AuthRequiredError: (err) => exitWith(3, err.message),
3388
- ProjectNotLinkedError: (err) => exitWith(4, err.message),
3389
- BuildProfileError: (err) => exitWith(2, err.message),
3390
- RuntimeVersionError: (err) => exitWith(2, err.message),
3391
- ArtifactNotFoundError: (err) => exitWith(6, err.message),
3392
- BuildFailedError: (err) => exitWith(6, err.message),
3393
- ReserveError: (err) => exitWith(7, err.message),
3394
- UploadFailedError: (err) => exitWith(7, err.message),
3395
- PresignedUrlExpiredError: (err) => exitWith(7, err.message),
3396
- CompleteError: (err) => exitWith(7, err.message),
3397
- EnvExportError: (err) => exitWith(7, err.message)
3398
- })));
3569
+ const UPLOAD_EXIT_EXTRAS = {
3570
+ BuildProfileError: 2,
3571
+ RuntimeVersionError: 2,
3572
+ ArtifactNotFoundError: 6,
3573
+ BuildFailedError: 6,
3574
+ ReserveError: 7,
3575
+ UploadFailedError: 7,
3576
+ PresignedUrlExpiredError: 7,
3577
+ CompleteError: 7,
3578
+ EnvExportError: 7
3579
+ };
3580
+ const uploadCommand$1 = defineCommand({
3581
+ meta: {
3582
+ name: "upload",
3583
+ description: "Upload an existing artifact to better-update"
3584
+ },
3585
+ args: {
3586
+ "artifact-path": {
3587
+ type: "positional",
3588
+ required: true,
3589
+ description: "Path to artifact"
3590
+ },
3591
+ platform: {
3592
+ type: "enum",
3593
+ options: ["ios", "android"],
3594
+ required: true
3595
+ },
3596
+ profile: {
3597
+ type: "string",
3598
+ default: "production",
3599
+ description: "Build profile name"
3600
+ },
3601
+ message: {
3602
+ type: "string",
3603
+ description: "Optional build message"
3604
+ }
3605
+ },
3606
+ run: async ({ args }) => runEffect(runUploadWorkflow({
3607
+ artifactPath: args["artifact-path"],
3608
+ platform: args.platform,
3609
+ profileName: args.profile,
3610
+ message: args.message
3611
+ }), UPLOAD_EXIT_EXTRAS)
3612
+ });
3399
3613
 
3400
3614
  //#endregion
3401
3615
  //#region src/commands/builds/index.ts
3402
- const buildsCommand = Command.make("builds", {}, () => Console.log("Manage builds. Run with --help for subcommands.")).pipe(Command.withSubcommands([
3403
- listCommand$5,
3404
- getCommand$2,
3405
- deleteCommand$5,
3406
- installLinkCommand,
3407
- compatibilityMatrixCommand,
3408
- uploadCommand$1
3409
- ]));
3616
+ const buildsCommand = defineCommand({
3617
+ meta: {
3618
+ name: "builds",
3619
+ description: "Manage builds"
3620
+ },
3621
+ subCommands: {
3622
+ list: listCommand$5,
3623
+ get: getCommand$2,
3624
+ delete: deleteCommand$5,
3625
+ "install-link": installLinkCommand,
3626
+ "compatibility-matrix": compatibilityMatrixCommand,
3627
+ upload: uploadCommand$1
3628
+ }
3629
+ });
3410
3630
 
3411
3631
  //#endregion
3412
3632
  //#region src/lib/resolve-named-resource.ts
@@ -3419,237 +3639,337 @@ const resolveNamedResourceId$2 = (params, makeError) => Effect.gen(function* ()
3419
3639
  //#endregion
3420
3640
  //#region src/commands/channels/helpers.ts
3421
3641
  var ChannelCommandError = class extends Data.TaggedError("ChannelCommandError") {};
3422
- const handleChannelCommandErrors = makeCommandErrorHandler({ ChannelCommandError: 2 });
3642
+ const channelErrorExtras = { ChannelCommandError: 2 };
3423
3643
  const resolveNamedResourceId$1 = (params) => resolveNamedResourceId$2(params, (message) => new ChannelCommandError({ message }));
3424
3644
 
3425
3645
  //#endregion
3426
3646
  //#region src/commands/channels/create.ts
3427
- const name$1 = Options.text("name");
3428
- const branch$5 = Options.text("branch");
3429
- const createCommand$2 = Command.make("create", {
3430
- name: name$1,
3431
- branch: branch$5
3432
- }, (opts) => Effect.gen(function* () {
3433
- const projectId = yield* readProjectId;
3434
- const api = yield* apiClient;
3435
- const { items: branches } = yield* api.branches.list({ urlParams: {
3436
- projectId,
3437
- page: 1,
3438
- limit: 1e3
3439
- } });
3440
- const branchId = yield* resolveNamedResourceId$1({
3441
- items: branches,
3442
- kind: "Branch",
3443
- name: opts.branch
3444
- });
3445
- const channel = yield* api.channels.create({ payload: {
3446
- projectId,
3447
- name: opts.name,
3448
- branchId
3449
- } });
3450
- yield* printKeyValue([
3451
- ["ID", channel.id],
3452
- ["Name", channel.name],
3453
- ["Branch", opts.branch],
3454
- ["Created", channel.createdAt]
3455
- ]);
3456
- }).pipe(handleChannelCommandErrors));
3647
+ const createCommand$2 = defineCommand({
3648
+ meta: {
3649
+ name: "create",
3650
+ description: "Create a channel"
3651
+ },
3652
+ args: {
3653
+ name: {
3654
+ type: "string",
3655
+ required: true,
3656
+ description: "Channel name"
3657
+ },
3658
+ branch: {
3659
+ type: "string",
3660
+ required: true,
3661
+ description: "Initial branch name"
3662
+ }
3663
+ },
3664
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
3665
+ const projectId = yield* readProjectId;
3666
+ const api = yield* apiClient;
3667
+ const { items: branches } = yield* api.branches.list({ urlParams: {
3668
+ projectId,
3669
+ page: 1,
3670
+ limit: 1e3
3671
+ } });
3672
+ const branchId = yield* resolveNamedResourceId$1({
3673
+ items: branches,
3674
+ kind: "Branch",
3675
+ name: args.branch
3676
+ });
3677
+ const channel = yield* api.channels.create({ payload: {
3678
+ projectId,
3679
+ name: args.name,
3680
+ branchId
3681
+ } });
3682
+ yield* printKeyValue([
3683
+ ["ID", channel.id],
3684
+ ["Name", channel.name],
3685
+ ["Branch", args.branch],
3686
+ ["Created", channel.createdAt]
3687
+ ]);
3688
+ }), channelErrorExtras)
3689
+ });
3457
3690
 
3458
3691
  //#endregion
3459
3692
  //#region src/commands/channels/delete.ts
3460
- const id$5 = Args.text({ name: "id" });
3461
- const deleteCommand$4 = Command.make("delete", { id: id$5 }, (opts) => Effect.gen(function* () {
3462
- yield* (yield* apiClient).channels.delete({ path: { id: opts.id } });
3463
- yield* Console.log(`Channel ${opts.id} deleted.`);
3464
- }).pipe(handleChannelCommandErrors));
3693
+ const deleteCommand$4 = defineCommand({
3694
+ meta: {
3695
+ name: "delete",
3696
+ description: "Delete a channel"
3697
+ },
3698
+ args: { id: {
3699
+ type: "positional",
3700
+ required: true,
3701
+ description: "Channel ID"
3702
+ } },
3703
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
3704
+ yield* (yield* apiClient).channels.delete({ path: { id: args.id } });
3705
+ yield* Console.log(`Channel ${args.id} deleted.`);
3706
+ }), channelErrorExtras)
3707
+ });
3465
3708
 
3466
3709
  //#endregion
3467
3710
  //#region src/commands/channels/list.ts
3468
- const listCommand$4 = Command.make("list", {}, () => Effect.gen(function* () {
3469
- const projectId = yield* readProjectId;
3470
- const api = yield* apiClient;
3471
- const [{ items }, { items: branches }] = yield* Effect.all([api.channels.list({ urlParams: {
3472
- projectId,
3473
- page: 1,
3474
- limit: 1e3
3475
- } }), api.branches.list({ urlParams: {
3476
- projectId,
3477
- page: 1,
3478
- limit: 1e3
3479
- } })]);
3480
- if (items.length === 0) {
3481
- yield* Console.log("No channels found.");
3482
- return;
3483
- }
3484
- const branchNames = new Map(branches.map((branch) => [branch.id, branch.name]));
3485
- yield* printTable([
3486
- "ID",
3487
- "Name",
3488
- "Branch",
3489
- "Paused",
3490
- "Rollout",
3491
- "Created"
3492
- ], items.map((channel) => [
3493
- channel.id,
3494
- channel.name,
3495
- branchNames.get(channel.branchId) ?? channel.branchId,
3496
- channel.isPaused ? "yes" : "no",
3497
- channel.branchMappingJson === null ? "-" : "active",
3498
- channel.createdAt
3499
- ]));
3500
- }).pipe(handleChannelCommandErrors));
3711
+ const listCommand$4 = defineCommand({
3712
+ meta: {
3713
+ name: "list",
3714
+ description: "List channels for the linked project"
3715
+ },
3716
+ run: async () => runEffect(Effect.gen(function* () {
3717
+ const projectId = yield* readProjectId;
3718
+ const api = yield* apiClient;
3719
+ const [{ items }, { items: branches }] = yield* Effect.all([api.channels.list({ urlParams: {
3720
+ projectId,
3721
+ page: 1,
3722
+ limit: 1e3
3723
+ } }), api.branches.list({ urlParams: {
3724
+ projectId,
3725
+ page: 1,
3726
+ limit: 1e3
3727
+ } })]);
3728
+ if (items.length === 0) {
3729
+ yield* Console.log("No channels found.");
3730
+ return;
3731
+ }
3732
+ const branchNames = new Map(branches.map((branch) => [branch.id, branch.name]));
3733
+ yield* printTable([
3734
+ "ID",
3735
+ "Name",
3736
+ "Branch",
3737
+ "Paused",
3738
+ "Rollout",
3739
+ "Created"
3740
+ ], items.map((channel) => [
3741
+ channel.id,
3742
+ channel.name,
3743
+ branchNames.get(channel.branchId) ?? channel.branchId,
3744
+ channel.isPaused ? "yes" : "no",
3745
+ channel.branchMappingJson === null ? "-" : "active",
3746
+ channel.createdAt
3747
+ ]));
3748
+ }), channelErrorExtras)
3749
+ });
3501
3750
 
3502
3751
  //#endregion
3503
3752
  //#region src/commands/channels/pause.ts
3504
- const id$4 = Args.text({ name: "id" });
3505
- const pauseCommand = Command.make("pause", { id: id$4 }, (opts) => Effect.gen(function* () {
3506
- const channel = yield* (yield* apiClient).channels.pause({ path: { id: opts.id } });
3507
- yield* Console.log(`Channel "${channel.name}" paused.`);
3508
- }).pipe(handleChannelCommandErrors));
3753
+ const pauseCommand = defineCommand({
3754
+ meta: {
3755
+ name: "pause",
3756
+ description: "Pause a channel"
3757
+ },
3758
+ args: { id: {
3759
+ type: "positional",
3760
+ required: true,
3761
+ description: "Channel ID"
3762
+ } },
3763
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
3764
+ const channel = yield* (yield* apiClient).channels.pause({ path: { id: args.id } });
3765
+ yield* Console.log(`Channel "${channel.name}" paused.`);
3766
+ }), channelErrorExtras)
3767
+ });
3509
3768
 
3510
3769
  //#endregion
3511
3770
  //#region src/commands/channels/resume.ts
3512
- const id$3 = Args.text({ name: "id" });
3513
- const resumeCommand = Command.make("resume", { id: id$3 }, (opts) => Effect.gen(function* () {
3514
- const channel = yield* (yield* apiClient).channels.resume({ path: { id: opts.id } });
3515
- yield* Console.log(`Channel "${channel.name}" resumed.`);
3516
- }).pipe(handleChannelCommandErrors));
3771
+ const resumeCommand = defineCommand({
3772
+ meta: {
3773
+ name: "resume",
3774
+ description: "Resume a paused channel"
3775
+ },
3776
+ args: { id: {
3777
+ type: "positional",
3778
+ required: true,
3779
+ description: "Channel ID"
3780
+ } },
3781
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
3782
+ const channel = yield* (yield* apiClient).channels.resume({ path: { id: args.id } });
3783
+ yield* Console.log(`Channel "${channel.name}" resumed.`);
3784
+ }), channelErrorExtras)
3785
+ });
3517
3786
 
3518
3787
  //#endregion
3519
3788
  //#region src/commands/channels/rollout/complete.ts
3520
- const channelId$3 = Args.text({ name: "channelId" });
3521
- const completeCommand$1 = Command.make("complete", { channelId: channelId$3 }, (opts) => Effect.gen(function* () {
3522
- const channel = yield* (yield* apiClient).channels.completeBranchRollout({ path: { id: opts.channelId } });
3523
- yield* Console.log(`Completed rollout on channel "${channel.name}".`);
3524
- }).pipe(handleChannelCommandErrors));
3525
-
3526
- //#endregion
3527
- //#region src/lib/cli-schemas.ts
3528
- const RolloutPercentage = Schema.Number.pipe(Schema.int(), Schema.between(1, 100)).annotations({
3529
- message: () => "Rollout percentage must be between 1 and 100.",
3530
- identifier: "RolloutPercentage"
3531
- });
3532
- const rolloutPercentageOption = (name) => Options.integer(name).pipe(Options.withSchema(RolloutPercentage));
3533
- const KeyValuePair = Schema.Struct({
3534
- key: Schema.String,
3535
- value: Schema.String
3536
- });
3537
- const KeyValueFromString = Schema.transformOrFail(Schema.String, KeyValuePair, {
3538
- strict: true,
3539
- decode: (input, _options, ast) => {
3540
- const eqIndex = input.indexOf("=");
3541
- if (eqIndex <= 0) return ParseResult.fail(new ParseResult.Type(ast, input, "Invalid format. Use KEY=VALUE (e.g. API_KEY=abc123)"));
3542
- return ParseResult.succeed({
3543
- key: input.slice(0, eqIndex),
3544
- value: input.slice(eqIndex + 1)
3545
- });
3789
+ const completeCommand$1 = defineCommand({
3790
+ meta: {
3791
+ name: "complete",
3792
+ description: "Complete the active branch rollout"
3546
3793
  },
3547
- encode: ({ key, value }) => ParseResult.succeed(`${key}=${value}`)
3794
+ args: { channelId: {
3795
+ type: "positional",
3796
+ required: true,
3797
+ description: "Channel ID"
3798
+ } },
3799
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
3800
+ const channel = yield* (yield* apiClient).channels.completeBranchRollout({ path: { id: args.channelId } });
3801
+ yield* Console.log(`Completed rollout on channel "${channel.name}".`);
3802
+ }), channelErrorExtras)
3548
3803
  });
3549
- const keyValueArg = (name) => Args.text({ name }).pipe(Args.withSchema(KeyValueFromString));
3550
3804
 
3551
3805
  //#endregion
3552
3806
  //#region src/commands/channels/rollout/create.ts
3553
- const channelId$2 = Args.text({ name: "channelId" });
3554
- const branch$4 = Options.text("branch");
3555
- const percentage$2 = rolloutPercentageOption("percentage");
3556
- const createCommand$1 = Command.make("create", {
3557
- channelId: channelId$2,
3558
- branch: branch$4,
3559
- percentage: percentage$2
3560
- }, (opts) => Effect.gen(function* () {
3561
- const projectId = yield* readProjectId;
3562
- const api = yield* apiClient;
3563
- const { items: branches } = yield* api.branches.list({ urlParams: {
3564
- projectId,
3565
- page: 1,
3566
- limit: 1e3
3567
- } });
3568
- const newBranchId = yield* resolveNamedResourceId$1({
3569
- items: branches,
3570
- kind: "Branch",
3571
- name: opts.branch
3572
- });
3573
- const channel = yield* api.channels.createBranchRollout({
3574
- path: { id: opts.channelId },
3575
- payload: {
3576
- newBranchId,
3577
- percentage: opts.percentage
3807
+ const createCommand$1 = defineCommand({
3808
+ meta: {
3809
+ name: "create",
3810
+ description: "Start a branch rollout on a channel"
3811
+ },
3812
+ args: {
3813
+ channelId: {
3814
+ type: "positional",
3815
+ required: true,
3816
+ description: "Channel ID"
3817
+ },
3818
+ branch: {
3819
+ type: "string",
3820
+ required: true,
3821
+ description: "Target branch name"
3822
+ },
3823
+ percentage: {
3824
+ type: "string",
3825
+ required: true,
3826
+ description: "Initial rollout percentage (1-100)"
3578
3827
  }
3579
- });
3580
- yield* Console.log(`Started rollout on channel "${channel.name}" to branch "${opts.branch}" at ${String(opts.percentage)}%.`);
3581
- }).pipe(handleChannelCommandErrors));
3828
+ },
3829
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
3830
+ const percentage = yield* parseRolloutPercentage(args.percentage, "percentage");
3831
+ const projectId = yield* readProjectId;
3832
+ const api = yield* apiClient;
3833
+ const { items: branches } = yield* api.branches.list({ urlParams: {
3834
+ projectId,
3835
+ page: 1,
3836
+ limit: 1e3
3837
+ } });
3838
+ const newBranchId = yield* resolveNamedResourceId$1({
3839
+ items: branches,
3840
+ kind: "Branch",
3841
+ name: args.branch
3842
+ });
3843
+ const channel = yield* api.channels.createBranchRollout({
3844
+ path: { id: args.channelId },
3845
+ payload: {
3846
+ newBranchId,
3847
+ percentage
3848
+ }
3849
+ });
3850
+ yield* Console.log(`Started rollout on channel "${channel.name}" to branch "${args.branch}" at ${String(percentage)}%.`);
3851
+ }), channelErrorExtras)
3852
+ });
3582
3853
 
3583
3854
  //#endregion
3584
3855
  //#region src/commands/channels/rollout/revert.ts
3585
- const channelId$1 = Args.text({ name: "channelId" });
3586
- const revertCommand$1 = Command.make("revert", { channelId: channelId$1 }, (opts) => Effect.gen(function* () {
3587
- const channel = yield* (yield* apiClient).channels.revertBranchRollout({ path: { id: opts.channelId } });
3588
- yield* Console.log(`Reverted rollout on channel "${channel.name}".`);
3589
- }).pipe(handleChannelCommandErrors));
3856
+ const revertCommand$1 = defineCommand({
3857
+ meta: {
3858
+ name: "revert",
3859
+ description: "Revert the active branch rollout"
3860
+ },
3861
+ args: { channelId: {
3862
+ type: "positional",
3863
+ required: true,
3864
+ description: "Channel ID"
3865
+ } },
3866
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
3867
+ const channel = yield* (yield* apiClient).channels.revertBranchRollout({ path: { id: args.channelId } });
3868
+ yield* Console.log(`Reverted rollout on channel "${channel.name}".`);
3869
+ }), channelErrorExtras)
3870
+ });
3590
3871
 
3591
3872
  //#endregion
3592
3873
  //#region src/commands/channels/rollout/update.ts
3593
- const channelId = Args.text({ name: "channelId" });
3594
- const percentage$1 = rolloutPercentageOption("percentage");
3595
- const updateCommand$2 = Command.make("update", {
3596
- channelId,
3597
- percentage: percentage$1
3598
- }, (opts) => Effect.gen(function* () {
3599
- const channel = yield* (yield* apiClient).channels.updateBranchRollout({
3600
- path: { id: opts.channelId },
3601
- payload: { percentage: opts.percentage }
3602
- });
3603
- yield* Console.log(`Updated rollout on channel "${channel.name}" to ${String(opts.percentage)}%.`);
3604
- }).pipe(handleChannelCommandErrors));
3605
-
3874
+ const updateCommand$2 = defineCommand({
3875
+ meta: {
3876
+ name: "update",
3877
+ description: "Update the rollout percentage on a channel"
3878
+ },
3879
+ args: {
3880
+ channelId: {
3881
+ type: "positional",
3882
+ required: true,
3883
+ description: "Channel ID"
3884
+ },
3885
+ percentage: {
3886
+ type: "string",
3887
+ required: true,
3888
+ description: "New rollout percentage (1-100)"
3889
+ }
3890
+ },
3891
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
3892
+ const percentage = yield* parseRolloutPercentage(args.percentage, "percentage");
3893
+ const channel = yield* (yield* apiClient).channels.updateBranchRollout({
3894
+ path: { id: args.channelId },
3895
+ payload: { percentage }
3896
+ });
3897
+ yield* Console.log(`Updated rollout on channel "${channel.name}" to ${String(percentage)}%.`);
3898
+ }), channelErrorExtras)
3899
+ });
3900
+
3606
3901
  //#endregion
3607
3902
  //#region src/commands/channels/rollout/index.ts
3608
- const rolloutCommand$1 = Command.make("rollout", {}, () => Console.log("Manage channel branch rollouts. Run with --help for subcommands.")).pipe(Command.withSubcommands([
3609
- createCommand$1,
3610
- updateCommand$2,
3611
- completeCommand$1,
3612
- revertCommand$1
3613
- ]));
3903
+ const rolloutCommand$1 = defineCommand({
3904
+ meta: {
3905
+ name: "rollout",
3906
+ description: "Manage channel branch rollouts"
3907
+ },
3908
+ subCommands: {
3909
+ create: createCommand$1,
3910
+ update: updateCommand$2,
3911
+ complete: completeCommand$1,
3912
+ revert: revertCommand$1
3913
+ }
3914
+ });
3614
3915
 
3615
3916
  //#endregion
3616
3917
  //#region src/commands/channels/update.ts
3617
- const id$2 = Args.text({ name: "id" });
3618
- const branch$3 = Options.text("branch");
3619
- const updateCommand$1 = Command.make("update", {
3620
- id: id$2,
3621
- branch: branch$3
3622
- }, (opts) => Effect.gen(function* () {
3623
- const projectId = yield* readProjectId;
3624
- const api = yield* apiClient;
3625
- const { items: branches } = yield* api.branches.list({ urlParams: {
3626
- projectId,
3627
- page: 1,
3628
- limit: 1e3
3629
- } });
3630
- const branchId = yield* resolveNamedResourceId$1({
3631
- items: branches,
3632
- kind: "Branch",
3633
- name: opts.branch
3634
- });
3635
- const channel = yield* api.channels.update({
3636
- path: { id: opts.id },
3637
- payload: { branchId }
3638
- });
3639
- yield* Console.log(`Channel "${channel.name}" relinked to branch "${opts.branch}".`);
3640
- }).pipe(handleChannelCommandErrors));
3918
+ const updateCommand$1 = defineCommand({
3919
+ meta: {
3920
+ name: "update",
3921
+ description: "Relink a channel to a different branch"
3922
+ },
3923
+ args: {
3924
+ id: {
3925
+ type: "positional",
3926
+ required: true,
3927
+ description: "Channel ID"
3928
+ },
3929
+ branch: {
3930
+ type: "string",
3931
+ required: true,
3932
+ description: "Target branch name"
3933
+ }
3934
+ },
3935
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
3936
+ const projectId = yield* readProjectId;
3937
+ const api = yield* apiClient;
3938
+ const { items: branches } = yield* api.branches.list({ urlParams: {
3939
+ projectId,
3940
+ page: 1,
3941
+ limit: 1e3
3942
+ } });
3943
+ const branchId = yield* resolveNamedResourceId$1({
3944
+ items: branches,
3945
+ kind: "Branch",
3946
+ name: args.branch
3947
+ });
3948
+ const channel = yield* api.channels.update({
3949
+ path: { id: args.id },
3950
+ payload: { branchId }
3951
+ });
3952
+ yield* Console.log(`Channel "${channel.name}" relinked to branch "${args.branch}".`);
3953
+ }), channelErrorExtras)
3954
+ });
3641
3955
 
3642
3956
  //#endregion
3643
3957
  //#region src/commands/channels/index.ts
3644
- const channelsCommand = Command.make("channels", {}, () => Console.log("Manage channels. Run with --help for subcommands.")).pipe(Command.withSubcommands([
3645
- listCommand$4,
3646
- createCommand$2,
3647
- updateCommand$1,
3648
- pauseCommand,
3649
- resumeCommand,
3650
- deleteCommand$4,
3651
- rolloutCommand$1
3652
- ]));
3958
+ const channelsCommand = defineCommand({
3959
+ meta: {
3960
+ name: "channels",
3961
+ description: "Manage channels"
3962
+ },
3963
+ subCommands: {
3964
+ list: listCommand$4,
3965
+ create: createCommand$2,
3966
+ update: updateCommand$1,
3967
+ pause: pauseCommand,
3968
+ resume: resumeCommand,
3969
+ delete: deleteCommand$4,
3970
+ rollout: rolloutCommand$1
3971
+ }
3972
+ });
3653
3973
 
3654
3974
  //#endregion
3655
3975
  //#region src/lib/pkcs12.ts
@@ -3882,355 +4202,529 @@ const deleteCredential = (api, input) => {
3882
4202
 
3883
4203
  //#endregion
3884
4204
  //#region src/commands/credentials/delete.ts
3885
- const id$1 = Args.text({ name: "id" });
3886
- const platform$4 = Options.choice("platform", ["ios", "android"]);
3887
- const type$1 = Options.choice("type", [
4205
+ const CREDENTIAL_TYPES$1 = [
3888
4206
  "distribution-certificate",
3889
4207
  "provisioning-profile",
3890
4208
  "push-key",
3891
4209
  "asc-api-key",
3892
4210
  "keystore",
3893
4211
  "google-service-account-key"
3894
- ]);
3895
- const deleteCommand$3 = Command.make("delete", {
3896
- id: id$1,
3897
- platform: platform$4,
3898
- type: type$1
3899
- }, (opts) => Effect.gen(function* () {
3900
- yield* deleteCredential(yield* apiClient, {
3901
- id: opts.id,
3902
- platform: opts.platform,
3903
- type: opts.type
3904
- });
3905
- yield* Console.log(`Credential ${opts.id} deleted.`);
3906
- }));
4212
+ ];
4213
+ const deleteCommand$3 = defineCommand({
4214
+ meta: {
4215
+ name: "delete",
4216
+ description: "Delete a credential"
4217
+ },
4218
+ args: {
4219
+ id: {
4220
+ type: "positional",
4221
+ required: true,
4222
+ description: "Credential ID"
4223
+ },
4224
+ platform: {
4225
+ type: "enum",
4226
+ options: ["ios", "android"],
4227
+ required: true
4228
+ },
4229
+ type: {
4230
+ type: "enum",
4231
+ options: [...CREDENTIAL_TYPES$1],
4232
+ required: true
4233
+ }
4234
+ },
4235
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
4236
+ yield* deleteCredential(yield* apiClient, {
4237
+ id: args.id,
4238
+ platform: args.platform,
4239
+ type: args.type
4240
+ });
4241
+ yield* Console.log(`Credential ${args.id} deleted.`);
4242
+ }))
4243
+ });
3907
4244
 
3908
4245
  //#endregion
3909
4246
  //#region src/commands/credentials/list.ts
3910
- const platform$3 = Options.choice("platform", ["ios", "android"]).pipe(Options.optional);
3911
- const listCommand$3 = Command.make("list", { platform: platform$3 }, (opts) => Effect.gen(function* () {
3912
- const filtered = filterCredentials(yield* listAllCredentials(yield* apiClient), Option.match(opts.platform, {
3913
- onNone: () => ({}),
3914
- onSome: (platformValue) => ({ platform: platformValue })
3915
- }));
3916
- if (filtered.length === 0) {
3917
- yield* Console.log("No credentials found.");
3918
- return;
3919
- }
3920
- yield* printTable([
3921
- "ID",
3922
- "Name",
3923
- "Platform",
3924
- "Type",
3925
- "Distribution"
3926
- ], filtered.map((row) => [
3927
- row.id,
3928
- row.name,
3929
- row.platform,
3930
- row.type,
3931
- row.distribution ?? "-"
3932
- ]));
3933
- }));
4247
+ const listCommand$3 = defineCommand({
4248
+ meta: {
4249
+ name: "list",
4250
+ description: "List credentials across platforms"
4251
+ },
4252
+ args: { platform: {
4253
+ type: "enum",
4254
+ options: ["ios", "android"],
4255
+ description: "Filter by platform"
4256
+ } },
4257
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
4258
+ const filtered = filterCredentials(yield* listAllCredentials(yield* apiClient), args.platform ? { platform: args.platform } : {});
4259
+ if (filtered.length === 0) {
4260
+ yield* Console.log("No credentials found.");
4261
+ return;
4262
+ }
4263
+ yield* printTable([
4264
+ "ID",
4265
+ "Name",
4266
+ "Platform",
4267
+ "Type",
4268
+ "Distribution"
4269
+ ], filtered.map((row) => [
4270
+ row.id,
4271
+ row.name,
4272
+ row.platform,
4273
+ row.type,
4274
+ row.distribution ?? "-"
4275
+ ]));
4276
+ }))
4277
+ });
3934
4278
 
3935
4279
  //#endregion
3936
4280
  //#region src/commands/credentials/upload.ts
3937
- const platform$2 = Options.choice("platform", ["ios", "android"]);
3938
- const type = Options.choice("type", [
4281
+ const CREDENTIAL_TYPES = [
3939
4282
  "distribution-certificate",
3940
4283
  "provisioning-profile",
3941
4284
  "push-key",
3942
4285
  "asc-api-key",
3943
4286
  "keystore",
3944
4287
  "google-service-account-key"
3945
- ]);
3946
- const name = Options.text("name");
3947
- const file = Options.text("file");
3948
- const password = Options.text("password").pipe(Options.optional);
3949
- const keyAlias = Options.text("key-alias").pipe(Options.optional);
3950
- const keyPassword = Options.text("key-password").pipe(Options.optional);
3951
- const keyId = Options.text("key-id").pipe(Options.optional);
3952
- const issuerId = Options.text("issuer-id").pipe(Options.optional);
3953
- const appleTeamIdentifier = Options.text("apple-team-identifier").pipe(Options.optional);
3954
- const uploadCommand = Command.make("upload", {
3955
- platform: platform$2,
3956
- type,
3957
- name,
3958
- file,
3959
- password,
3960
- keyAlias,
3961
- keyPassword,
3962
- keyId,
3963
- issuerId,
3964
- appleTeamIdentifier
3965
- }, (opts) => Effect.gen(function* () {
3966
- const api = yield* apiClient;
3967
- const passwordOpt = Option.getOrUndefined(opts.password);
3968
- const keyAliasOpt = Option.getOrUndefined(opts.keyAlias);
3969
- const keyPasswordOpt = Option.getOrUndefined(opts.keyPassword);
3970
- const keyIdOpt = Option.getOrUndefined(opts.keyId);
3971
- const issuerIdOpt = Option.getOrUndefined(opts.issuerId);
3972
- const appleTeamIdentifierOpt = Option.getOrUndefined(opts.appleTeamIdentifier);
3973
- const credential = yield* uploadCredential(api, {
3974
- platform: opts.platform,
3975
- type: opts.type,
3976
- name: opts.name,
3977
- filePath: opts.file,
3978
- ...passwordOpt === void 0 ? {} : { password: passwordOpt },
3979
- ...keyAliasOpt === void 0 ? {} : { keyAlias: keyAliasOpt },
3980
- ...keyPasswordOpt === void 0 ? {} : { keyPassword: keyPasswordOpt },
3981
- ...keyIdOpt === void 0 ? {} : { keyId: keyIdOpt },
3982
- ...issuerIdOpt === void 0 ? {} : { issuerId: issuerIdOpt },
3983
- ...appleTeamIdentifierOpt === void 0 ? {} : { appleTeamIdentifier: appleTeamIdentifierOpt }
3984
- });
3985
- yield* Console.log("Credential uploaded successfully.");
3986
- yield* Console.log("");
3987
- yield* printKeyValue([
3988
- ["ID", credential.id],
3989
- ["Name", credential.name],
3990
- ["Platform", credential.platform],
3991
- ["Type", credential.type]
3992
- ]);
3993
- }));
4288
+ ];
4289
+ const uploadCommand = defineCommand({
4290
+ meta: {
4291
+ name: "upload",
4292
+ description: "Upload a credential"
4293
+ },
4294
+ args: {
4295
+ platform: {
4296
+ type: "enum",
4297
+ options: ["ios", "android"],
4298
+ required: true
4299
+ },
4300
+ type: {
4301
+ type: "enum",
4302
+ options: [...CREDENTIAL_TYPES],
4303
+ required: true
4304
+ },
4305
+ name: {
4306
+ type: "string",
4307
+ required: true,
4308
+ description: "Display name"
4309
+ },
4310
+ file: {
4311
+ type: "string",
4312
+ required: true,
4313
+ description: "Path to credential file"
4314
+ },
4315
+ password: {
4316
+ type: "string",
4317
+ description: "File password (keystore/p12)"
4318
+ },
4319
+ "key-alias": {
4320
+ type: "string",
4321
+ description: "Keystore alias"
4322
+ },
4323
+ "key-password": {
4324
+ type: "string",
4325
+ description: "Keystore key password"
4326
+ },
4327
+ "key-id": {
4328
+ type: "string",
4329
+ description: "ASC API key ID"
4330
+ },
4331
+ "issuer-id": {
4332
+ type: "string",
4333
+ description: "ASC API issuer ID"
4334
+ },
4335
+ "apple-team-identifier": {
4336
+ type: "string",
4337
+ description: "Apple Team ID"
4338
+ }
4339
+ },
4340
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
4341
+ const credential = yield* uploadCredential(yield* apiClient, {
4342
+ platform: args.platform,
4343
+ type: args.type,
4344
+ name: args.name,
4345
+ filePath: args.file,
4346
+ ...args.password === void 0 ? {} : { password: args.password },
4347
+ ...args["key-alias"] === void 0 ? {} : { keyAlias: args["key-alias"] },
4348
+ ...args["key-password"] === void 0 ? {} : { keyPassword: args["key-password"] },
4349
+ ...args["key-id"] === void 0 ? {} : { keyId: args["key-id"] },
4350
+ ...args["issuer-id"] === void 0 ? {} : { issuerId: args["issuer-id"] },
4351
+ ...args["apple-team-identifier"] === void 0 ? {} : { appleTeamIdentifier: args["apple-team-identifier"] }
4352
+ });
4353
+ yield* Console.log("Credential uploaded successfully.");
4354
+ yield* Console.log("");
4355
+ yield* printKeyValue([
4356
+ ["ID", credential.id],
4357
+ ["Name", credential.name],
4358
+ ["Platform", credential.platform],
4359
+ ["Type", credential.type]
4360
+ ]);
4361
+ }))
4362
+ });
3994
4363
 
3995
4364
  //#endregion
3996
4365
  //#region src/commands/credentials/index.ts
3997
- const credentialsCommand = Command.make("credentials", {}, () => Console.log("Manage credentials. Run with --help for subcommands.")).pipe(Command.withSubcommands([
3998
- listCommand$3,
3999
- uploadCommand,
4000
- deleteCommand$3
4001
- ]));
4366
+ const credentialsCommand = defineCommand({
4367
+ meta: {
4368
+ name: "credentials",
4369
+ description: "Manage credentials"
4370
+ },
4371
+ subCommands: {
4372
+ list: listCommand$3,
4373
+ upload: uploadCommand,
4374
+ delete: deleteCommand$3
4375
+ }
4376
+ });
4002
4377
 
4003
4378
  //#endregion
4004
4379
  //#region src/commands/env/helpers.ts
4005
4380
  var EnvResourceNotFoundError = class extends Data.TaggedError("EnvResourceNotFoundError") {};
4006
- const handleEnvCommandErrors = makeCommandErrorHandler({
4381
+ const envErrorExtras = {
4007
4382
  EnvResourceNotFoundError: 1,
4008
4383
  SystemError: 6,
4009
4384
  BadArgument: 6
4010
- });
4385
+ };
4011
4386
 
4012
4387
  //#endregion
4013
4388
  //#region src/commands/env/delete.ts
4014
- const keyArg = Args.text({ name: "KEY" });
4015
- const environmentOption$5 = Options.text("environment").pipe(Options.withDefault("production"));
4016
- const deleteCommand$2 = Command.make("delete", {
4017
- key: keyArg,
4018
- environment: environmentOption$5
4019
- }, ({ key, environment }) => Effect.gen(function* () {
4020
- const projectId = yield* readProjectId;
4021
- const api = yield* apiClient;
4022
- const match = (yield* api["env-vars"].list({ urlParams: {
4023
- projectId,
4024
- environment
4025
- } })).items.find((item) => item.key === key);
4026
- if (!match) return yield* new EnvResourceNotFoundError({ message: `Environment variable ${key} not found in ${environment}` });
4027
- yield* api["env-vars"].delete({ path: { id: match.id } });
4028
- yield* Console.log(`Deleted ${key} from ${environment}`);
4029
- }).pipe(handleEnvCommandErrors));
4389
+ const deleteCommand$2 = defineCommand({
4390
+ meta: {
4391
+ name: "delete",
4392
+ description: "Delete an environment variable by key"
4393
+ },
4394
+ args: {
4395
+ key: {
4396
+ type: "positional",
4397
+ required: true,
4398
+ description: "Env var key"
4399
+ },
4400
+ environment: {
4401
+ type: "string",
4402
+ default: "production",
4403
+ description: "Target environment"
4404
+ }
4405
+ },
4406
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
4407
+ const projectId = yield* readProjectId;
4408
+ const api = yield* apiClient;
4409
+ const match = (yield* api["env-vars"].list({ urlParams: {
4410
+ projectId,
4411
+ environment: args.environment
4412
+ } })).items.find((item) => item.key === args.key);
4413
+ if (!match) return yield* new EnvResourceNotFoundError({ message: `Environment variable ${args.key} not found in ${args.environment}` });
4414
+ yield* api["env-vars"].delete({ path: { id: match.id } });
4415
+ yield* Console.log(`Deleted ${args.key} from ${args.environment}`);
4416
+ }), envErrorExtras)
4417
+ });
4030
4418
 
4031
4419
  //#endregion
4032
4420
  //#region src/commands/env/export.ts
4033
- const environmentOption$4 = Options.text("environment").pipe(Options.withDefault("production"));
4034
- const exportCommand = Command.make("export", { environment: environmentOption$4 }, ({ environment }) => Effect.gen(function* () {
4035
- const projectId = yield* readProjectId;
4036
- const result = yield* (yield* apiClient)["env-vars"].export({ urlParams: {
4037
- projectId,
4038
- environment
4039
- } });
4040
- for (const item of result.items) {
4041
- const escaped = item.value.replaceAll("'", String.raw`'\''`);
4042
- yield* Console.log(`${item.key}='${escaped}'`);
4043
- }
4044
- }).pipe(handleEnvCommandErrors));
4421
+ const exportCommand = defineCommand({
4422
+ meta: {
4423
+ name: "export",
4424
+ description: "Print env vars in KEY='value' format"
4425
+ },
4426
+ args: { environment: {
4427
+ type: "string",
4428
+ default: "production",
4429
+ description: "Target environment"
4430
+ } },
4431
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
4432
+ const projectId = yield* readProjectId;
4433
+ const result = yield* (yield* apiClient)["env-vars"].export({ urlParams: {
4434
+ projectId,
4435
+ environment: args.environment
4436
+ } });
4437
+ for (const item of result.items) {
4438
+ const escaped = item.value.replaceAll("'", String.raw`'\''`);
4439
+ yield* Console.log(`${item.key}='${escaped}'`);
4440
+ }
4441
+ }), envErrorExtras)
4442
+ });
4045
4443
 
4046
4444
  //#endregion
4047
4445
  //#region src/commands/env/get.ts
4048
- const id = Args.text({ name: "id" });
4049
- const getCommand$1 = Command.make("get", { id }, (opts) => Effect.gen(function* () {
4050
- const envVar = yield* (yield* apiClient)["env-vars"].get({ path: { id: opts.id } });
4051
- yield* printKeyValue([
4052
- ["ID", envVar.id],
4053
- ["Key", envVar.key],
4054
- ["Environment", envVar.environment],
4055
- ["Visibility", envVar.visibility],
4056
- ["Value", envVar.visibility === "plaintext" ? envVar.value ?? "" : "******"],
4057
- ["Created", envVar.createdAt],
4058
- ["Updated", envVar.updatedAt]
4059
- ]);
4060
- }).pipe(handleEnvCommandErrors));
4446
+ const getCommand$1 = defineCommand({
4447
+ meta: {
4448
+ name: "get",
4449
+ description: "Show an environment variable"
4450
+ },
4451
+ args: { id: {
4452
+ type: "positional",
4453
+ required: true,
4454
+ description: "Env var ID"
4455
+ } },
4456
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
4457
+ const envVar = yield* (yield* apiClient)["env-vars"].get({ path: { id: args.id } });
4458
+ yield* printKeyValue([
4459
+ ["ID", envVar.id],
4460
+ ["Key", envVar.key],
4461
+ ["Environment", envVar.environment],
4462
+ ["Visibility", envVar.visibility],
4463
+ ["Value", envVar.visibility === "plaintext" ? envVar.value ?? "" : "******"],
4464
+ ["Created", envVar.createdAt],
4465
+ ["Updated", envVar.updatedAt]
4466
+ ]);
4467
+ }), envErrorExtras)
4468
+ });
4061
4469
 
4062
4470
  //#endregion
4063
4471
  //#region src/commands/env/import.ts
4064
- const fileArg = Args.text({ name: "file" });
4065
- const environmentOption$3 = Options.text("environment").pipe(Options.withDefault("production"));
4066
- const importCommand = Command.make("import", {
4067
- file: fileArg,
4068
- environment: environmentOption$3
4069
- }, ({ file, environment }) => Effect.gen(function* () {
4070
- const content = yield* (yield* FileSystem.FileSystem).readFileString(file);
4071
- const projectId = yield* readProjectId;
4072
- const result = yield* (yield* apiClient)["env-vars"].bulkImport({ payload: {
4073
- projectId,
4074
- environment,
4075
- content,
4076
- visibility: "plaintext"
4077
- } });
4078
- yield* Console.log(`Imported: ${String(result.created)} created, ${String(result.updated)} updated, ${String(result.skipped)} skipped`);
4079
- }).pipe(handleEnvCommandErrors));
4472
+ const importCommand = defineCommand({
4473
+ meta: {
4474
+ name: "import",
4475
+ description: "Bulk-import env vars from a dotenv file"
4476
+ },
4477
+ args: {
4478
+ file: {
4479
+ type: "positional",
4480
+ required: true,
4481
+ description: "Path to .env file"
4482
+ },
4483
+ environment: {
4484
+ type: "string",
4485
+ default: "production",
4486
+ description: "Target environment"
4487
+ }
4488
+ },
4489
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
4490
+ const content = yield* (yield* FileSystem.FileSystem).readFileString(args.file);
4491
+ const projectId = yield* readProjectId;
4492
+ const result = yield* (yield* apiClient)["env-vars"].bulkImport({ payload: {
4493
+ projectId,
4494
+ environment: args.environment,
4495
+ content,
4496
+ visibility: "plaintext"
4497
+ } });
4498
+ yield* Console.log(`Imported: ${String(result.created)} created, ${String(result.updated)} updated, ${String(result.skipped)} skipped`);
4499
+ }), envErrorExtras)
4500
+ });
4080
4501
 
4081
4502
  //#endregion
4082
4503
  //#region src/commands/env/list.ts
4083
- const environmentOption$2 = Options.text("environment").pipe(Options.optional);
4084
- const listCommand$2 = Command.make("list", { environment: environmentOption$2 }, ({ environment }) => Effect.gen(function* () {
4085
- const projectId = yield* readProjectId;
4086
- const api = yield* apiClient;
4087
- const envFilter = Option.match(environment, {
4088
- onNone: () => ({}),
4089
- onSome: (value) => ({ environment: value })
4090
- });
4091
- const result = yield* api["env-vars"].list({ urlParams: {
4092
- projectId,
4093
- ...envFilter
4094
- } });
4095
- if (result.items.length === 0) {
4096
- yield* Console.log("No environment variables found.");
4097
- return;
4098
- }
4099
- yield* printTable([
4100
- "Key",
4101
- "Environment",
4102
- "Visibility",
4103
- "Value"
4104
- ], result.items.map((item) => [
4105
- item.key,
4106
- item.environment,
4107
- item.visibility,
4108
- item.visibility === "plaintext" ? item.value ?? "" : "••••••"
4109
- ]));
4110
- }).pipe(handleEnvCommandErrors));
4504
+ const listCommand$2 = defineCommand({
4505
+ meta: {
4506
+ name: "list",
4507
+ description: "List environment variables"
4508
+ },
4509
+ args: { environment: {
4510
+ type: "string",
4511
+ description: "Filter by environment"
4512
+ } },
4513
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
4514
+ const projectId = yield* readProjectId;
4515
+ const api = yield* apiClient;
4516
+ const envFilter = args.environment ? { environment: args.environment } : {};
4517
+ const result = yield* api["env-vars"].list({ urlParams: {
4518
+ projectId,
4519
+ ...envFilter
4520
+ } });
4521
+ if (result.items.length === 0) {
4522
+ yield* Console.log("No environment variables found.");
4523
+ return;
4524
+ }
4525
+ yield* printTable([
4526
+ "Key",
4527
+ "Environment",
4528
+ "Visibility",
4529
+ "Value"
4530
+ ], result.items.map((item) => [
4531
+ item.key,
4532
+ item.environment,
4533
+ item.visibility,
4534
+ item.visibility === "plaintext" ? item.value ?? "" : "••••••"
4535
+ ]));
4536
+ }), envErrorExtras)
4537
+ });
4111
4538
 
4112
4539
  //#endregion
4113
4540
  //#region src/commands/env/pull.ts
4114
- const environmentOption$1 = Options.text("environment").pipe(Options.withDefault("production"));
4115
- const pullCommand = Command.make("pull", { environment: environmentOption$1 }, ({ environment }) => Effect.gen(function* () {
4116
- const projectId = yield* readProjectId;
4117
- const result = yield* (yield* apiClient)["env-vars"].export({ urlParams: {
4118
- projectId,
4119
- environment
4120
- } });
4121
- for (const item of result.items) {
4122
- const escaped = item.value.replaceAll("'", String.raw`'\''`);
4123
- yield* Console.log(`export ${item.key}='${escaped}'`);
4124
- }
4125
- }).pipe(handleEnvCommandErrors));
4541
+ const pullCommand = defineCommand({
4542
+ meta: {
4543
+ name: "pull",
4544
+ description: "Print env vars in `export KEY='value'` format"
4545
+ },
4546
+ args: { environment: {
4547
+ type: "string",
4548
+ default: "production",
4549
+ description: "Target environment"
4550
+ } },
4551
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
4552
+ const projectId = yield* readProjectId;
4553
+ const result = yield* (yield* apiClient)["env-vars"].export({ urlParams: {
4554
+ projectId,
4555
+ environment: args.environment
4556
+ } });
4557
+ for (const item of result.items) {
4558
+ const escaped = item.value.replaceAll("'", String.raw`'\''`);
4559
+ yield* Console.log(`export ${item.key}='${escaped}'`);
4560
+ }
4561
+ }), envErrorExtras)
4562
+ });
4126
4563
 
4127
4564
  //#endregion
4128
4565
  //#region src/commands/env/set.ts
4129
- const keyValue = keyValueArg("KEY=VALUE");
4130
- const environmentOption = Options.text("environment").pipe(Options.withDefault("production"));
4131
- const visibilityOption = Options.choice("visibility", [
4132
- "plaintext",
4133
- "sensitive",
4134
- "secret"
4135
- ]).pipe(Options.withDefault("plaintext"));
4136
- const setCommand$1 = Command.make("set", {
4137
- keyValue,
4138
- environment: environmentOption,
4139
- visibility: visibilityOption
4140
- }, ({ keyValue: { key, value }, environment, visibility }) => Effect.gen(function* () {
4141
- const projectId = yield* readProjectId;
4142
- const api = yield* apiClient;
4143
- const match = (yield* api["env-vars"].list({ urlParams: {
4144
- projectId,
4145
- environment
4146
- } })).items.find((item) => item.key === key);
4147
- if (match) {
4148
- yield* api["env-vars"].update({
4149
- path: { id: match.id },
4150
- payload: {
4566
+ const setCommand$1 = defineCommand({
4567
+ meta: {
4568
+ name: "set",
4569
+ description: "Create or update an environment variable"
4570
+ },
4571
+ args: {
4572
+ keyValue: {
4573
+ type: "positional",
4574
+ required: true,
4575
+ description: "KEY=VALUE pair (e.g. API_KEY=abc123)"
4576
+ },
4577
+ environment: {
4578
+ type: "string",
4579
+ default: "production",
4580
+ description: "Target environment"
4581
+ },
4582
+ visibility: {
4583
+ type: "enum",
4584
+ options: [
4585
+ "plaintext",
4586
+ "sensitive",
4587
+ "secret"
4588
+ ],
4589
+ default: "plaintext",
4590
+ description: "Value visibility"
4591
+ }
4592
+ },
4593
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
4594
+ const { key, value } = yield* parseKeyValue(args.keyValue);
4595
+ const { environment } = args;
4596
+ const { visibility } = args;
4597
+ const projectId = yield* readProjectId;
4598
+ const api = yield* apiClient;
4599
+ const match = (yield* api["env-vars"].list({ urlParams: {
4600
+ projectId,
4601
+ environment
4602
+ } })).items.find((item) => item.key === key);
4603
+ if (match) {
4604
+ yield* api["env-vars"].update({
4605
+ path: { id: match.id },
4606
+ payload: {
4607
+ value,
4608
+ visibility
4609
+ }
4610
+ });
4611
+ yield* Console.log(`Updated ${key} in ${environment}`);
4612
+ } else {
4613
+ yield* api["env-vars"].create({ payload: {
4614
+ projectId,
4615
+ environment,
4616
+ key,
4151
4617
  value,
4152
4618
  visibility
4153
- }
4154
- });
4155
- yield* Console.log(`Updated ${key} in ${environment}`);
4156
- } else {
4157
- yield* api["env-vars"].create({ payload: {
4158
- projectId,
4159
- environment,
4160
- key,
4161
- value,
4162
- visibility
4163
- } });
4164
- yield* Console.log(`Created ${key} in ${environment}`);
4165
- }
4166
- }).pipe(handleEnvCommandErrors));
4619
+ } });
4620
+ yield* Console.log(`Created ${key} in ${environment}`);
4621
+ }
4622
+ }), envErrorExtras)
4623
+ });
4167
4624
 
4168
4625
  //#endregion
4169
4626
  //#region src/commands/env/index.ts
4170
- const envCommand = Command.make("env", {}, () => Console.log("Manage environment variables. Run with --help for subcommands.")).pipe(Command.withSubcommands([
4171
- listCommand$2,
4172
- getCommand$1,
4173
- setCommand$1,
4174
- deleteCommand$2,
4175
- importCommand,
4176
- exportCommand,
4177
- pullCommand
4178
- ]));
4627
+ const envCommand = defineCommand({
4628
+ meta: {
4629
+ name: "env",
4630
+ description: "Manage environment variables"
4631
+ },
4632
+ subCommands: {
4633
+ list: listCommand$2,
4634
+ get: getCommand$1,
4635
+ set: setCommand$1,
4636
+ delete: deleteCommand$2,
4637
+ import: importCommand,
4638
+ export: exportCommand,
4639
+ pull: pullCommand
4640
+ }
4641
+ });
4179
4642
 
4180
4643
  //#endregion
4181
4644
  //#region src/commands/fingerprint/compare.ts
4182
- const hash = Args.text({ name: "hash" });
4183
- const compareCommand = Command.make("compare", { hash }, (opts) => Effect.gen(function* () {
4184
- const result = yield* runFingerprintFull(yield* (yield* CliRuntime).cwd);
4185
- if (result.hash === opts.hash) {
4186
- yield* Console.log("Fingerprints match.");
4187
- return;
4188
- }
4189
- yield* Console.log("Fingerprints differ.");
4190
- yield* Console.log(` Local: ${result.hash}`);
4191
- yield* Console.log(` Provided: ${opts.hash}`);
4192
- return yield* exitWith(1, "Fingerprint mismatch");
4193
- }).pipe(Effect.catchTag("FingerprintError", (error) => exitWith(2, error.message))));
4645
+ const compareCommand = defineCommand({
4646
+ meta: {
4647
+ name: "compare",
4648
+ description: "Compare a fingerprint hash against the current project"
4649
+ },
4650
+ args: { hash: {
4651
+ type: "positional",
4652
+ required: true,
4653
+ description: "Fingerprint hash to compare"
4654
+ } },
4655
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
4656
+ const result = yield* runFingerprintFull(yield* (yield* CliRuntime).cwd);
4657
+ if (result.hash === args.hash) {
4658
+ yield* Console.log("Fingerprints match.");
4659
+ return;
4660
+ }
4661
+ yield* Console.log("Fingerprints differ.");
4662
+ yield* Console.log(` Local: ${result.hash}`);
4663
+ yield* Console.log(` Provided: ${args.hash}`);
4664
+ return yield* exitWith(1, "Fingerprint mismatch");
4665
+ }), { FingerprintError: 2 })
4666
+ });
4194
4667
 
4195
4668
  //#endregion
4196
4669
  //#region src/commands/fingerprint/generate.ts
4197
- const generateCommand = Command.make("generate", {}, () => Effect.gen(function* () {
4198
- const result = yield* runFingerprintFull(yield* (yield* CliRuntime).cwd);
4199
- yield* Console.log(result.hash);
4200
- if (result.sources.length > 0) yield* Console.log(`${result.sources.length} sources`);
4201
- }).pipe(Effect.catchTag("FingerprintError", (error) => exitWith(2, error.message))));
4670
+ const generateCommand = defineCommand({
4671
+ meta: {
4672
+ name: "generate",
4673
+ description: "Compute a fingerprint for the current project"
4674
+ },
4675
+ run: async () => runEffect(Effect.gen(function* () {
4676
+ const result = yield* runFingerprintFull(yield* (yield* CliRuntime).cwd);
4677
+ yield* Console.log(result.hash);
4678
+ if (result.sources.length > 0) yield* Console.log(`${result.sources.length} sources`);
4679
+ }), { FingerprintError: 2 })
4680
+ });
4202
4681
 
4203
4682
  //#endregion
4204
4683
  //#region src/commands/fingerprint/index.ts
4205
- const fingerprintCommand = Command.make("fingerprint", {}, () => Console.log("Fingerprint utilities. Use --help for subcommands.")).pipe(Command.withSubcommands([generateCommand, compareCommand]));
4684
+ const fingerprintCommand = defineCommand({
4685
+ meta: {
4686
+ name: "fingerprint",
4687
+ description: "Fingerprint utilities"
4688
+ },
4689
+ subCommands: {
4690
+ generate: generateCommand,
4691
+ compare: compareCommand
4692
+ }
4693
+ });
4206
4694
 
4207
4695
  //#endregion
4208
4696
  //#region src/commands/init.ts
4209
- const initCommand = Command.make("init", {}, () => Effect.gen(function* () {
4210
- const expo = asRecord((yield* readAppJson)["expo"]);
4211
- const name = asString$1(expo?.["name"]) ?? asString$1(expo?.["slug"]) ?? "untitled";
4212
- const slug = yield* readSlug;
4213
- yield* Console.log(`Linking project: ${name} (${slug})`);
4214
- const api = yield* apiClient;
4215
- const { items } = yield* api.projects.list({ urlParams: {
4216
- page: 1,
4217
- limit: 100
4218
- } });
4219
- const existing = items.find((project) => project.slug === slug);
4220
- if (existing) {
4221
- yield* Console.log(`Found existing project: ${existing.name} (${existing.id})`);
4222
- yield* writeProjectId(existing.id);
4223
- } else {
4224
- yield* Console.log("No existing project found. Creating new project...");
4225
- const project = yield* api.projects.create({ payload: {
4226
- name,
4227
- slug
4697
+ const initCommand = defineCommand({
4698
+ meta: {
4699
+ name: "init",
4700
+ description: "Link the local Expo project to a better-update project"
4701
+ },
4702
+ run: async () => runEffect(Effect.gen(function* () {
4703
+ const expo = asRecord((yield* readAppJson)["expo"]);
4704
+ const name = asString$1(expo?.["name"]) ?? asString$1(expo?.["slug"]) ?? "untitled";
4705
+ const slug = yield* readSlug;
4706
+ yield* Console.log(`Linking project: ${name} (${slug})`);
4707
+ const api = yield* apiClient;
4708
+ const { items } = yield* api.projects.list({ urlParams: {
4709
+ page: 1,
4710
+ limit: 100
4228
4711
  } });
4229
- yield* Console.log(`Created project: ${project.name} (${project.id})`);
4230
- yield* writeProjectId(project.id);
4231
- }
4232
- yield* Console.log("Project linked successfully. ID saved to app.json.");
4233
- }));
4712
+ const existing = items.find((project) => project.slug === slug);
4713
+ if (existing) {
4714
+ yield* Console.log(`Found existing project: ${existing.name} (${existing.id})`);
4715
+ yield* writeProjectId(existing.id);
4716
+ } else {
4717
+ yield* Console.log("No existing project found. Creating new project...");
4718
+ const project = yield* api.projects.create({ payload: {
4719
+ name,
4720
+ slug
4721
+ } });
4722
+ yield* Console.log(`Created project: ${project.name} (${project.id})`);
4723
+ yield* writeProjectId(project.id);
4724
+ }
4725
+ yield* Console.log("Project linked successfully. ID saved to app.json.");
4726
+ }))
4727
+ });
4234
4728
 
4235
4729
  //#endregion
4236
4730
  //#region src/lib/browser-login.ts
@@ -4375,17 +4869,27 @@ const createBrowserLoginServer = (options = {}) => {
4375
4869
  };
4376
4870
  };
4377
4871
 
4872
+ //#endregion
4873
+ //#region src/lib/prompts.ts
4874
+ const promptPassword = async (message) => {
4875
+ const value = await password({ message });
4876
+ if (isCancel(value)) {
4877
+ cancel("Operation cancelled.");
4878
+ process.exit(130);
4879
+ }
4880
+ return value;
4881
+ };
4882
+
4378
4883
  //#endregion
4379
4884
  //#region src/application/login.ts
4380
- const tokenPrompt = Prompt.password({ message: "Paste your API key (from dashboard > API Keys):" });
4381
4885
  const buildOpenBrowserCommand = (platform, url) => {
4382
- if (platform === "darwin") return Command$1.make("open", url);
4383
- if (platform === "win32") return Command$1.make("cmd", "/c", "start", "", url);
4384
- return Command$1.make("xdg-open", url);
4886
+ if (platform === "darwin") return Command.make("open", url);
4887
+ if (platform === "win32") return Command.make("cmd", "/c", "start", "", url);
4888
+ return Command.make("xdg-open", url);
4385
4889
  };
4386
4890
  const openBrowser = (url) => Effect.gen(function* () {
4387
4891
  const command = buildOpenBrowserCommand((yield* CliRuntime).platform, url);
4388
- if (!(yield* Command$1.exitCode(command).pipe(Effect.map((code) => code === 0), Effect.catchAll(() => Effect.succeed(false))))) yield* Console.log(`Open this URL manually:\n${url}`);
4892
+ if (!(yield* Command.exitCode(command).pipe(Effect.map((code) => code === 0), Effect.catchAll(() => Effect.succeed(false))))) yield* Console.log(`Open this URL manually:\n${url}`);
4389
4893
  });
4390
4894
  const browserLogin = Effect.scoped(Effect.gen(function* () {
4391
4895
  const configStore = yield* ConfigStore;
@@ -4405,7 +4909,7 @@ const manualLogin = Effect.gen(function* () {
4405
4909
  yield* Console.log("Log in to better-update with an existing API key");
4406
4910
  yield* Console.log("Get your API key from the dashboard > API Keys page");
4407
4911
  yield* Console.log("");
4408
- const token = Redacted.value(yield* tokenPrompt);
4912
+ const token = yield* Effect.promise(async () => promptPassword("Paste your API key (from dashboard > API Keys):"));
4409
4913
  yield* (yield* AuthStore).saveToken(token);
4410
4914
  yield* Console.log("");
4411
4915
  yield* Console.log("Logged in successfully. Token saved to ~/.better-update/auth.json");
@@ -4420,198 +4924,298 @@ const runLogin = (options) => Effect.gen(function* () {
4420
4924
 
4421
4925
  //#endregion
4422
4926
  //#region src/commands/login.ts
4423
- const manualApiKey = Options.boolean("api-key");
4424
- const loginFailed = (cause) => exitWith(1, Cause.pretty(cause));
4425
- const loginCommand = Command.make("login", { manualApiKey }, (opts) => runLogin({ manualApiKey: opts.manualApiKey }).pipe(Effect.catchAllCause(loginFailed)));
4927
+ const loginCommand = defineCommand({
4928
+ meta: {
4929
+ name: "login",
4930
+ description: "Log in to better-update"
4931
+ },
4932
+ args: { "api-key": {
4933
+ type: "boolean",
4934
+ description: "Paste an API key manually instead of opening the browser"
4935
+ } },
4936
+ run: async ({ args }) => runEffect(runLogin({ manualApiKey: args["api-key"] ?? false }))
4937
+ });
4426
4938
 
4427
4939
  //#endregion
4428
4940
  //#region src/commands/logout.ts
4429
- const logoutCommand = Command.make("logout", {}, () => Effect.gen(function* () {
4430
- yield* (yield* AuthStore).clearToken;
4431
- yield* Console.log("Logged out. Auth token removed.");
4432
- }));
4941
+ const logoutCommand = defineCommand({
4942
+ meta: {
4943
+ name: "logout",
4944
+ description: "Remove the stored auth token"
4945
+ },
4946
+ run: async () => runEffect(Effect.gen(function* () {
4947
+ yield* (yield* AuthStore).clearToken;
4948
+ yield* Console.log("Logged out. Auth token removed.");
4949
+ }))
4950
+ });
4433
4951
 
4434
4952
  //#endregion
4435
4953
  //#region src/commands/projects.ts
4436
- const handleErrors = makeCommandErrorHandler();
4437
- const idArg = Args.text({ name: "id" });
4438
- const nameOption = Options.text("name");
4439
- const slugOption = Options.text("slug");
4440
- const listCommand$1 = Command.make("list", {}, () => Effect.gen(function* () {
4441
- const { items } = yield* (yield* apiClient).projects.list({ urlParams: {
4442
- page: 1,
4443
- limit: 1e3
4444
- } });
4445
- if (items.length === 0) {
4446
- yield* Console.log("No projects found.");
4447
- return;
4954
+ const listCommand$1 = defineCommand({
4955
+ meta: {
4956
+ name: "list",
4957
+ description: "List all projects"
4958
+ },
4959
+ run: async () => runEffect(Effect.gen(function* () {
4960
+ const { items } = yield* (yield* apiClient).projects.list({ urlParams: {
4961
+ page: 1,
4962
+ limit: 1e3
4963
+ } });
4964
+ if (items.length === 0) {
4965
+ yield* Console.log("No projects found.");
4966
+ return;
4967
+ }
4968
+ yield* printTable([
4969
+ "ID",
4970
+ "Name",
4971
+ "Slug",
4972
+ "Created"
4973
+ ], items.map((project) => [
4974
+ project.id,
4975
+ project.name,
4976
+ project.slug,
4977
+ project.createdAt
4978
+ ]));
4979
+ }))
4980
+ });
4981
+ const createCommand = defineCommand({
4982
+ meta: {
4983
+ name: "create",
4984
+ description: "Create a new project"
4985
+ },
4986
+ args: {
4987
+ name: {
4988
+ type: "string",
4989
+ required: true,
4990
+ description: "Display name"
4991
+ },
4992
+ slug: {
4993
+ type: "string",
4994
+ required: true,
4995
+ description: "URL-safe slug"
4996
+ }
4997
+ },
4998
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
4999
+ const project = yield* (yield* apiClient).projects.create({ payload: {
5000
+ name: args.name,
5001
+ slug: args.slug
5002
+ } });
5003
+ yield* printKeyValue([
5004
+ ["ID", project.id],
5005
+ ["Name", project.name],
5006
+ ["Slug", project.slug],
5007
+ ["Created", project.createdAt]
5008
+ ]);
5009
+ }))
5010
+ });
5011
+ const getCommand = defineCommand({
5012
+ meta: {
5013
+ name: "get",
5014
+ description: "Show a project"
5015
+ },
5016
+ args: { id: {
5017
+ type: "positional",
5018
+ required: true,
5019
+ description: "Project ID"
5020
+ } },
5021
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
5022
+ const project = yield* (yield* apiClient).projects.get({ path: { id: args.id } });
5023
+ yield* printKeyValue([
5024
+ ["ID", project.id],
5025
+ ["Name", project.name],
5026
+ ["Slug", project.slug],
5027
+ ["Created", project.createdAt]
5028
+ ]);
5029
+ }))
5030
+ });
5031
+ const renameCommand = defineCommand({
5032
+ meta: {
5033
+ name: "rename",
5034
+ description: "Rename a project"
5035
+ },
5036
+ args: {
5037
+ id: {
5038
+ type: "positional",
5039
+ required: true,
5040
+ description: "Project ID"
5041
+ },
5042
+ name: {
5043
+ type: "string",
5044
+ required: true,
5045
+ description: "New display name"
5046
+ }
5047
+ },
5048
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
5049
+ const project = yield* (yield* apiClient).projects.rename({
5050
+ path: { id: args.id },
5051
+ payload: { name: args.name }
5052
+ });
5053
+ yield* Console.log(`Project renamed to "${project.name}".`);
5054
+ }))
5055
+ });
5056
+ const deleteCommand$1 = defineCommand({
5057
+ meta: {
5058
+ name: "delete",
5059
+ description: "Delete a project"
5060
+ },
5061
+ args: { id: {
5062
+ type: "positional",
5063
+ required: true,
5064
+ description: "Project ID"
5065
+ } },
5066
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
5067
+ yield* (yield* apiClient).projects.delete({ path: { id: args.id } });
5068
+ yield* Console.log(`Project ${args.id} deleted.`);
5069
+ }))
5070
+ });
5071
+ const projectsCommand = defineCommand({
5072
+ meta: {
5073
+ name: "projects",
5074
+ description: "Manage projects"
5075
+ },
5076
+ subCommands: {
5077
+ list: listCommand$1,
5078
+ create: createCommand,
5079
+ get: getCommand,
5080
+ rename: renameCommand,
5081
+ delete: deleteCommand$1
4448
5082
  }
4449
- yield* printTable([
4450
- "ID",
4451
- "Name",
4452
- "Slug",
4453
- "Created"
4454
- ], items.map((project) => [
4455
- project.id,
4456
- project.name,
4457
- project.slug,
4458
- project.createdAt
4459
- ]));
4460
- }).pipe(handleErrors));
4461
- const createCommand = Command.make("create", {
4462
- name: nameOption,
4463
- slug: slugOption
4464
- }, (opts) => Effect.gen(function* () {
4465
- const project = yield* (yield* apiClient).projects.create({ payload: {
4466
- name: opts.name,
4467
- slug: opts.slug
4468
- } });
4469
- yield* printKeyValue([
4470
- ["ID", project.id],
4471
- ["Name", project.name],
4472
- ["Slug", project.slug],
4473
- ["Created", project.createdAt]
4474
- ]);
4475
- }).pipe(handleErrors));
4476
- const getCommand = Command.make("get", { id: idArg }, (opts) => Effect.gen(function* () {
4477
- const project = yield* (yield* apiClient).projects.get({ path: { id: opts.id } });
4478
- yield* printKeyValue([
4479
- ["ID", project.id],
4480
- ["Name", project.name],
4481
- ["Slug", project.slug],
4482
- ["Created", project.createdAt]
4483
- ]);
4484
- }).pipe(handleErrors));
4485
- const renameCommand = Command.make("rename", {
4486
- id: idArg,
4487
- name: nameOption
4488
- }, (opts) => Effect.gen(function* () {
4489
- const project = yield* (yield* apiClient).projects.rename({
4490
- path: { id: opts.id },
4491
- payload: { name: opts.name }
4492
- });
4493
- yield* Console.log(`Project renamed to "${project.name}".`);
4494
- }).pipe(handleErrors));
4495
- const deleteCommand$1 = Command.make("delete", { id: idArg }, (opts) => Effect.gen(function* () {
4496
- yield* (yield* apiClient).projects.delete({ path: { id: opts.id } });
4497
- yield* Console.log(`Project ${opts.id} deleted.`);
4498
- }).pipe(handleErrors));
4499
- const projectsCommand = Command.make("projects", {}, () => Console.log("Manage projects. Run with --help for subcommands.")).pipe(Command.withSubcommands([
4500
- listCommand$1,
4501
- createCommand,
4502
- getCommand,
4503
- renameCommand,
4504
- deleteCommand$1
4505
- ]));
5083
+ });
4506
5084
 
4507
5085
  //#endregion
4508
5086
  //#region src/commands/status.ts
4509
- const statusCommand = Command.make("status", {}, () => Effect.gen(function* () {
4510
- const projectId = yield* readProjectId;
4511
- const api = yield* apiClient;
4512
- const { project, credentials, builds } = yield* Effect.all({
4513
- project: api.projects.get({ path: { id: projectId } }),
4514
- credentials: listAllCredentials(api),
4515
- builds: api.builds.list({ urlParams: { projectId } })
4516
- }, { concurrency: "unbounded" });
4517
- yield* Console.log("Project");
4518
- yield* Console.log("-------");
4519
- yield* printKeyValue([
4520
- ["Name", project.name],
4521
- ["ID", project.id],
4522
- ["Slug", project.slug],
4523
- ["Created", project.createdAt]
4524
- ]);
4525
- yield* Console.log("");
4526
- yield* Console.log("Credentials");
4527
- yield* Console.log("-----------");
4528
- const iosCreds = credentials.filter((cred) => cred.platform === "ios").length;
4529
- const androidCreds = credentials.filter((cred) => cred.platform === "android").length;
4530
- yield* printKeyValue([
4531
- ["iOS", String(iosCreds)],
4532
- ["Android", String(androidCreds)],
4533
- ["Total", String(credentials.length)]
4534
- ]);
4535
- yield* Console.log("");
4536
- yield* Console.log("Builds");
4537
- yield* Console.log("------");
4538
- yield* printKeyValue([["Total", String(builds.total)]]);
4539
- }));
5087
+ const statusCommand = defineCommand({
5088
+ meta: {
5089
+ name: "status",
5090
+ description: "Show project status (credentials, builds)"
5091
+ },
5092
+ run: async () => runEffect(Effect.gen(function* () {
5093
+ const projectId = yield* readProjectId;
5094
+ const api = yield* apiClient;
5095
+ const { project, credentials, builds } = yield* Effect.all({
5096
+ project: api.projects.get({ path: { id: projectId } }),
5097
+ credentials: listAllCredentials(api),
5098
+ builds: api.builds.list({ urlParams: { projectId } })
5099
+ }, { concurrency: "unbounded" });
5100
+ yield* Console.log("Project");
5101
+ yield* Console.log("-------");
5102
+ yield* printKeyValue([
5103
+ ["Name", project.name],
5104
+ ["ID", project.id],
5105
+ ["Slug", project.slug],
5106
+ ["Created", project.createdAt]
5107
+ ]);
5108
+ yield* Console.log("");
5109
+ yield* Console.log("Credentials");
5110
+ yield* Console.log("-----------");
5111
+ const iosCreds = credentials.filter((cred) => cred.platform === "ios").length;
5112
+ const androidCreds = credentials.filter((cred) => cred.platform === "android").length;
5113
+ yield* printKeyValue([
5114
+ ["iOS", String(iosCreds)],
5115
+ ["Android", String(androidCreds)],
5116
+ ["Total", String(credentials.length)]
5117
+ ]);
5118
+ yield* Console.log("");
5119
+ yield* Console.log("Builds");
5120
+ yield* Console.log("------");
5121
+ yield* printKeyValue([["Total", String(builds.total)]]);
5122
+ }))
5123
+ });
4540
5124
 
4541
5125
  //#endregion
4542
5126
  //#region src/commands/update/helpers.ts
4543
5127
  var UpdateCommandError = class extends Data.TaggedError("UpdateCommandError") {};
4544
- const handleUpdateCommandErrors = makeCommandErrorHandler({
5128
+ const updateErrorExtras = {
4545
5129
  UpdateCommandError: 2,
4546
5130
  BuildProfileError: 2,
4547
5131
  RuntimeVersionError: 2,
4548
5132
  UpdateRollbackError: 2,
4549
5133
  UpdatePromoteError: 2
4550
- });
5134
+ };
4551
5135
  const resolveNamedResourceId = (params) => resolveNamedResourceId$2(params, (message) => new UpdateCommandError({ message }));
4552
5136
 
4553
5137
  //#endregion
4554
5138
  //#region src/commands/update/delete.ts
4555
- const groupId = Args.text({ name: "groupId" });
4556
- const deleteCommand = Command.make("delete", { groupId }, (opts) => Effect.gen(function* () {
4557
- const result = yield* (yield* apiClient).updates.deleteGroup({ path: { groupId: opts.groupId } });
4558
- yield* Console.log(`Deleted ${String(result.deleted)} update(s) from group ${opts.groupId}.`);
4559
- }).pipe(handleUpdateCommandErrors));
5139
+ const deleteCommand = defineCommand({
5140
+ meta: {
5141
+ name: "delete",
5142
+ description: "Delete an update group"
5143
+ },
5144
+ args: { groupId: {
5145
+ type: "positional",
5146
+ required: true,
5147
+ description: "Update group ID"
5148
+ } },
5149
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
5150
+ const result = yield* (yield* apiClient).updates.deleteGroup({ path: { groupId: args.groupId } });
5151
+ yield* Console.log(`Deleted ${String(result.deleted)} update(s) from group ${args.groupId}.`);
5152
+ }), updateErrorExtras)
5153
+ });
4560
5154
 
4561
5155
  //#endregion
4562
5156
  //#region src/commands/update/list.ts
4563
- const branch$2 = Options.text("branch").pipe(Options.optional);
4564
- const limit = Options.integer("limit").pipe(Options.withDefault(20));
4565
- const listCommand = Command.make("list", {
4566
- branch: branch$2,
4567
- limit
4568
- }, (opts) => Effect.gen(function* () {
4569
- const projectId = yield* readProjectId;
4570
- const api = yield* apiClient;
4571
- const { items: branches } = yield* api.branches.list({ urlParams: {
4572
- projectId,
4573
- page: 1,
4574
- limit: 1e3
4575
- } });
4576
- const branchId = yield* Option.match(opts.branch, {
4577
- onNone: () => Effect.succeed(void 0),
4578
- onSome: (branchName) => resolveNamedResourceId({
5157
+ const listCommand = defineCommand({
5158
+ meta: {
5159
+ name: "list",
5160
+ description: "List recent updates"
5161
+ },
5162
+ args: {
5163
+ branch: {
5164
+ type: "string",
5165
+ description: "Filter by branch name"
5166
+ },
5167
+ limit: {
5168
+ type: "string",
5169
+ default: "20",
5170
+ description: "Max rows (default 20)"
5171
+ }
5172
+ },
5173
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
5174
+ const limit = yield* parseLimit(args.limit, 20);
5175
+ const projectId = yield* readProjectId;
5176
+ const api = yield* apiClient;
5177
+ const { items: branches } = yield* api.branches.list({ urlParams: {
5178
+ projectId,
5179
+ page: 1,
5180
+ limit: 1e3
5181
+ } });
5182
+ const branchId = args.branch ? yield* resolveNamedResourceId({
4579
5183
  items: branches,
4580
5184
  kind: "Branch",
4581
- name: branchName
4582
- })
4583
- });
4584
- const { items } = yield* api.updates.list({ urlParams: {
4585
- projectId,
4586
- ...branchId === void 0 ? {} : { branchId },
4587
- page: 1,
4588
- limit: opts.limit
4589
- } });
4590
- if (items.length === 0) {
4591
- yield* Console.log("No updates found.");
4592
- return;
4593
- }
4594
- const branchNames = new Map(branches.map((item) => [item.id, item.name]));
4595
- yield* printTable([
4596
- "Update ID",
4597
- "Group",
4598
- "Branch",
4599
- "Platform",
4600
- "Runtime",
4601
- "Rollout",
4602
- "Rollback",
4603
- "Created"
4604
- ], items.map((item) => [
4605
- item.id,
4606
- item.groupId,
4607
- branchNames.get(item.branchId) ?? item.branchId,
4608
- item.platform,
4609
- item.runtimeVersion,
4610
- `${String(item.rolloutPercentage)}%`,
4611
- item.isRollback ? "yes" : "no",
4612
- item.createdAt
4613
- ]));
4614
- }).pipe(handleUpdateCommandErrors));
5185
+ name: args.branch
5186
+ }) : void 0;
5187
+ const { items } = yield* api.updates.list({ urlParams: {
5188
+ projectId,
5189
+ ...branchId === void 0 ? {} : { branchId },
5190
+ page: 1,
5191
+ limit
5192
+ } });
5193
+ if (items.length === 0) {
5194
+ yield* Console.log("No updates found.");
5195
+ return;
5196
+ }
5197
+ const branchNames = new Map(branches.map((item) => [item.id, item.name]));
5198
+ yield* printTable([
5199
+ "Update ID",
5200
+ "Group",
5201
+ "Branch",
5202
+ "Platform",
5203
+ "Runtime",
5204
+ "Rollout",
5205
+ "Rollback",
5206
+ "Created"
5207
+ ], items.map((item) => [
5208
+ item.id,
5209
+ item.groupId,
5210
+ branchNames.get(item.branchId) ?? item.branchId,
5211
+ item.platform,
5212
+ item.runtimeVersion,
5213
+ `${String(item.rolloutPercentage)}%`,
5214
+ item.isRollback ? "yes" : "no",
5215
+ item.createdAt
5216
+ ]));
5217
+ }), updateErrorExtras)
5218
+ });
4615
5219
 
4616
5220
  //#endregion
4617
5221
  //#region src/lib/signed-payloads.ts
@@ -4704,27 +5308,37 @@ const runUpdatePromote = (options) => Effect.gen(function* () {
4704
5308
 
4705
5309
  //#endregion
4706
5310
  //#region src/commands/update/promote.ts
4707
- const updateId$3 = Args.text({ name: "updateId" });
4708
- const channel = Options.text("channel");
4709
- const manifestBodyFile$1 = Options.text("manifest-body-file").pipe(Options.optional);
4710
- const signatureFile$2 = Options.text("signature-file").pipe(Options.optional);
4711
- const certificateChainFile$2 = Options.text("certificate-chain-file").pipe(Options.optional);
4712
- const promoteCommand = Command.make("promote", {
4713
- updateId: updateId$3,
4714
- channel,
4715
- manifestBodyFile: manifestBodyFile$1,
4716
- signatureFile: signatureFile$2,
4717
- certificateChainFile: certificateChainFile$2
4718
- }, (opts) => Effect.gen(function* () {
4719
- const result = yield* runUpdatePromote({
4720
- updateId: opts.updateId,
4721
- channel: opts.channel,
4722
- manifestBodyFile: Option.getOrUndefined(opts.manifestBodyFile),
4723
- signatureFile: Option.getOrUndefined(opts.signatureFile),
4724
- certificateChainFile: Option.getOrUndefined(opts.certificateChainFile)
4725
- });
4726
- yield* Console.log(`Promoted update ${result.sourceUpdateId} to channel "${result.channel}" as update ${result.updateId}.`);
4727
- }).pipe(handleUpdateCommandErrors));
5311
+ const promoteCommand = defineCommand({
5312
+ meta: {
5313
+ name: "promote",
5314
+ description: "Promote an existing update to a channel"
5315
+ },
5316
+ args: {
5317
+ updateId: {
5318
+ type: "positional",
5319
+ required: true,
5320
+ description: "Source update ID"
5321
+ },
5322
+ channel: {
5323
+ type: "string",
5324
+ required: true,
5325
+ description: "Target channel name"
5326
+ },
5327
+ "manifest-body-file": { type: "string" },
5328
+ "signature-file": { type: "string" },
5329
+ "certificate-chain-file": { type: "string" }
5330
+ },
5331
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
5332
+ const result = yield* runUpdatePromote({
5333
+ updateId: args.updateId,
5334
+ channel: args.channel,
5335
+ manifestBodyFile: args["manifest-body-file"],
5336
+ signatureFile: args["signature-file"],
5337
+ certificateChainFile: args["certificate-chain-file"]
5338
+ });
5339
+ yield* Console.log(`Promoted update ${result.sourceUpdateId} to channel "${result.channel}" as update ${result.updateId}.`);
5340
+ }), updateErrorExtras)
5341
+ });
4728
5342
 
4729
5343
  //#endregion
4730
5344
  //#region src/lib/expo-export.ts
@@ -4754,8 +5368,8 @@ const inferContentType = (fileExt, isLaunch) => {
4754
5368
  default: return "application/octet-stream";
4755
5369
  }
4756
5370
  };
4757
- const makeBunxCommand = (...args) => Command$1.make("bunx", ...args);
4758
- const runCommand = (cmd, step) => Command$1.exitCode(cmd.pipe(Command$1.stdout("inherit"), Command$1.stderr("inherit"))).pipe(Effect.mapError((cause) => new BuildFailedError({
5371
+ const makeBunxCommand = (...args) => Command.make("bunx", ...args);
5372
+ const runCommand = (cmd, step) => Command.exitCode(cmd.pipe(Command.stdout("inherit"), Command.stderr("inherit"))).pipe(Effect.mapError((cause) => new BuildFailedError({
4759
5373
  step,
4760
5374
  exitCode: 1,
4761
5375
  message: `${step} failed to spawn: ${String(cause)}`
@@ -4766,7 +5380,7 @@ const runCommand = (cmd, step) => Command$1.exitCode(cmd.pipe(Command$1.stdout("
4766
5380
  }))));
4767
5381
  const readExpoPublicConfig = ({ projectRoot, envVars }) => Effect.gen(function* () {
4768
5382
  const commandEnv = yield* (yield* CliRuntime).commandEnvironment(envVars);
4769
- const stdout = yield* Command$1.string(makeBunxCommand("expo", "config", "--type", "public", "--json").pipe(Command$1.workingDirectory(projectRoot), Command$1.env(commandEnv))).pipe(Effect.mapError((cause) => new UpdatePublishError({ message: `Failed to read Expo public config: ${String(cause)}` })));
5383
+ const stdout = yield* Command.string(makeBunxCommand("expo", "config", "--type", "public", "--json").pipe(Command.workingDirectory(projectRoot), Command.env(commandEnv))).pipe(Effect.mapError((cause) => new UpdatePublishError({ message: `Failed to read Expo public config: ${String(cause)}` })));
4770
5384
  const config = asRecord(yield* Effect.try({
4771
5385
  try: () => JSON.parse(stdout),
4772
5386
  catch: () => new UpdatePublishError({ message: "Expo public config output was not valid JSON." })
@@ -4786,7 +5400,7 @@ const runExpoExport = ({ projectRoot, exportDir, platform, envVars, clear }) =>
4786
5400
  "--dump-assetmap"
4787
5401
  ];
4788
5402
  if (clear) args.push("--clear");
4789
- return yield* runCommand(makeBunxCommand(...args).pipe(Command$1.workingDirectory(projectRoot), Command$1.env(commandEnv)), `expo export ${platform}`);
5403
+ return yield* runCommand(makeBunxCommand(...args).pipe(Command.workingDirectory(projectRoot), Command.env(commandEnv)), `expo export ${platform}`);
4790
5404
  });
4791
5405
  const readExpoExportAssets = ({ exportDir, platform }) => Effect.gen(function* () {
4792
5406
  const fs = yield* FileSystem.FileSystem;
@@ -5000,86 +5614,101 @@ const runUpdatePublish = (options) => Effect.scoped(Effect.gen(function* () {
5000
5614
 
5001
5615
  //#endregion
5002
5616
  //#region src/commands/update/publish.ts
5003
- const branch$1 = Options.text("branch").pipe(Options.optional);
5004
- const platform$1 = Options.choice("platform", [
5005
- "ios",
5006
- "android",
5007
- "all"
5008
- ]).pipe(Options.withDefault("all"));
5009
- const message$1 = Options.text("message").pipe(Options.optional);
5010
- const environment = Options.text("environment").pipe(Options.withDefault("production"));
5011
- const auto = Options.boolean("auto").pipe(Options.withDefault(false));
5012
- const clear = Options.boolean("clear");
5013
- const rolloutPercentage = rolloutPercentageOption("rollout-percentage").pipe(Options.optional);
5014
- const manifestBodyFile = Options.text("manifest-body-file").pipe(Options.optional);
5015
- const signatureFile$1 = Options.text("signature-file").pipe(Options.optional);
5016
- const certificateChainFile$1 = Options.text("certificate-chain-file").pipe(Options.optional);
5017
- const manifestBodyFileIos = Options.text("manifest-body-file-ios").pipe(Options.optional);
5018
- const signatureFileIos = Options.text("signature-file-ios").pipe(Options.optional);
5019
- const certificateChainFileIos = Options.text("certificate-chain-file-ios").pipe(Options.optional);
5020
- const manifestBodyFileAndroid = Options.text("manifest-body-file-android").pipe(Options.optional);
5021
- const signatureFileAndroid = Options.text("signature-file-android").pipe(Options.optional);
5022
- const certificateChainFileAndroid = Options.text("certificate-chain-file-android").pipe(Options.optional);
5023
- const publishCommand = Command.make("publish", {
5024
- branch: branch$1,
5025
- platform: platform$1,
5026
- message: message$1,
5027
- environment,
5028
- auto,
5029
- clear,
5030
- rolloutPercentage,
5031
- manifestBodyFile,
5032
- signatureFile: signatureFile$1,
5033
- certificateChainFile: certificateChainFile$1,
5034
- manifestBodyFileIos,
5035
- signatureFileIos,
5036
- certificateChainFileIos,
5037
- manifestBodyFileAndroid,
5038
- signatureFileAndroid,
5039
- certificateChainFileAndroid
5040
- }, (opts) => Effect.gen(function* () {
5041
- const result = yield* runUpdatePublish({
5042
- branch: Option.getOrUndefined(opts.branch),
5043
- platform: opts.platform,
5044
- message: Option.getOrUndefined(opts.message),
5045
- auto: opts.auto,
5046
- environment: opts.environment,
5047
- clear: opts.clear,
5048
- rolloutPercentage: Option.getOrUndefined(opts.rolloutPercentage),
5049
- manifestBodyFile: Option.getOrUndefined(opts.manifestBodyFile),
5050
- signatureFile: Option.getOrUndefined(opts.signatureFile),
5051
- certificateChainFile: Option.getOrUndefined(opts.certificateChainFile),
5052
- manifestBodyFileIos: Option.getOrUndefined(opts.manifestBodyFileIos),
5053
- signatureFileIos: Option.getOrUndefined(opts.signatureFileIos),
5054
- certificateChainFileIos: Option.getOrUndefined(opts.certificateChainFileIos),
5055
- manifestBodyFileAndroid: Option.getOrUndefined(opts.manifestBodyFileAndroid),
5056
- signatureFileAndroid: Option.getOrUndefined(opts.signatureFileAndroid),
5057
- certificateChainFileAndroid: Option.getOrUndefined(opts.certificateChainFileAndroid)
5058
- });
5059
- yield* Console.log(`Published update group ${result.groupId} to branch "${result.branch}".`);
5060
- yield* Console.log("");
5061
- yield* printTable([
5062
- "Platform",
5063
- "Update ID",
5064
- "Runtime Version",
5065
- "Uploaded",
5066
- "Reused"
5067
- ], result.results.map((entry) => [
5068
- entry.platform,
5069
- entry.updateId,
5070
- entry.runtimeVersion,
5071
- String(entry.uploadedAssets),
5072
- String(entry.deduplicatedAssets)
5073
- ]));
5074
- }).pipe(Effect.catchTags({
5075
- AuthRequiredError: (error) => exitWith(3, error.message),
5076
- ProjectNotLinkedError: (error) => exitWith(4, error.message),
5077
- BuildProfileError: (error) => exitWith(2, error.message),
5078
- RuntimeVersionError: (error) => exitWith(2, error.message),
5079
- EnvExportError: (error) => exitWith(7, error.message),
5080
- BuildFailedError: (error) => exitWith(6, error.message),
5081
- UpdatePublishError: (error) => exitWith(7, error.message)
5082
- })));
5617
+ const PUBLISH_EXIT_EXTRAS = {
5618
+ BuildProfileError: 2,
5619
+ RuntimeVersionError: 2,
5620
+ EnvExportError: 7,
5621
+ BuildFailedError: 6,
5622
+ UpdatePublishError: 7
5623
+ };
5624
+ const publishCommand = defineCommand({
5625
+ meta: {
5626
+ name: "publish",
5627
+ description: "Publish a new OTA update group"
5628
+ },
5629
+ args: {
5630
+ branch: {
5631
+ type: "string",
5632
+ description: "Target branch name"
5633
+ },
5634
+ platform: {
5635
+ type: "enum",
5636
+ options: [
5637
+ "ios",
5638
+ "android",
5639
+ "all"
5640
+ ],
5641
+ default: "all",
5642
+ description: "Platform(s) to publish"
5643
+ },
5644
+ message: {
5645
+ type: "string",
5646
+ description: "Optional update message"
5647
+ },
5648
+ environment: {
5649
+ type: "string",
5650
+ default: "production",
5651
+ description: "Env vars scope"
5652
+ },
5653
+ auto: {
5654
+ type: "boolean",
5655
+ description: "Skip prompts (for CI)"
5656
+ },
5657
+ clear: {
5658
+ type: "boolean",
5659
+ description: "Drop existing assets before upload"
5660
+ },
5661
+ "rollout-percentage": {
5662
+ type: "string",
5663
+ description: "Initial rollout percentage (1-100)"
5664
+ },
5665
+ "manifest-body-file": { type: "string" },
5666
+ "signature-file": { type: "string" },
5667
+ "certificate-chain-file": { type: "string" },
5668
+ "manifest-body-file-ios": { type: "string" },
5669
+ "signature-file-ios": { type: "string" },
5670
+ "certificate-chain-file-ios": { type: "string" },
5671
+ "manifest-body-file-android": { type: "string" },
5672
+ "signature-file-android": { type: "string" },
5673
+ "certificate-chain-file-android": { type: "string" }
5674
+ },
5675
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
5676
+ const rolloutPercentage = args["rollout-percentage"] ? yield* parseRolloutPercentage(args["rollout-percentage"], "rollout-percentage") : void 0;
5677
+ const result = yield* runUpdatePublish({
5678
+ branch: args.branch,
5679
+ platform: args.platform,
5680
+ message: args.message,
5681
+ auto: args.auto ?? false,
5682
+ environment: args.environment,
5683
+ clear: args.clear ?? false,
5684
+ rolloutPercentage,
5685
+ manifestBodyFile: args["manifest-body-file"],
5686
+ signatureFile: args["signature-file"],
5687
+ certificateChainFile: args["certificate-chain-file"],
5688
+ manifestBodyFileIos: args["manifest-body-file-ios"],
5689
+ signatureFileIos: args["signature-file-ios"],
5690
+ certificateChainFileIos: args["certificate-chain-file-ios"],
5691
+ manifestBodyFileAndroid: args["manifest-body-file-android"],
5692
+ signatureFileAndroid: args["signature-file-android"],
5693
+ certificateChainFileAndroid: args["certificate-chain-file-android"]
5694
+ });
5695
+ yield* Console.log(`Published update group ${result.groupId} to branch "${result.branch}".`);
5696
+ yield* Console.log("");
5697
+ yield* printTable([
5698
+ "Platform",
5699
+ "Update ID",
5700
+ "Runtime Version",
5701
+ "Uploaded",
5702
+ "Reused"
5703
+ ], result.results.map((entry) => [
5704
+ entry.platform,
5705
+ entry.updateId,
5706
+ entry.runtimeVersion,
5707
+ String(entry.uploadedAssets),
5708
+ String(entry.deduplicatedAssets)
5709
+ ]));
5710
+ }), PUBLISH_EXIT_EXTRAS)
5711
+ });
5083
5712
 
5084
5713
  //#endregion
5085
5714
  //#region ../../packages/expo-protocol/src/index.ts
@@ -5186,121 +5815,179 @@ const runUpdateRollback = (options) => Effect.gen(function* () {
5186
5815
 
5187
5816
  //#endregion
5188
5817
  //#region src/commands/update/rollback.ts
5189
- const branch = Options.text("branch");
5190
- const platform = Options.choice("platform", [
5191
- "ios",
5192
- "android",
5193
- "all"
5194
- ]).pipe(Options.withDefault("all"));
5195
- const message = Options.text("message").pipe(Options.optional);
5196
- const commitTime = Options.text("commit-time").pipe(Options.optional);
5197
- const directiveBodyFile = Options.text("directive-body-file").pipe(Options.optional);
5198
- const signatureFile = Options.text("signature-file").pipe(Options.optional);
5199
- const certificateChainFile = Options.text("certificate-chain-file").pipe(Options.optional);
5200
- const rollbackCommand = Command.make("rollback", {
5201
- branch,
5202
- platform,
5203
- message,
5204
- commitTime,
5205
- directiveBodyFile,
5206
- signatureFile,
5207
- certificateChainFile
5208
- }, (opts) => Effect.gen(function* () {
5209
- const result = yield* runUpdateRollback({
5210
- branch: opts.branch,
5211
- platform: opts.platform,
5212
- message: Option.getOrUndefined(opts.message),
5213
- commitTime: Option.getOrUndefined(opts.commitTime),
5214
- directiveBodyFile: Option.getOrUndefined(opts.directiveBodyFile),
5215
- signatureFile: Option.getOrUndefined(opts.signatureFile),
5216
- certificateChainFile: Option.getOrUndefined(opts.certificateChainFile)
5217
- });
5218
- yield* Console.log(`Created rollback group ${result.groupId} on branch "${result.branch}" at ${result.commitTime}.`);
5219
- yield* Console.log("");
5220
- yield* printTable([
5221
- "Platform",
5222
- "Update ID",
5223
- "Runtime Version"
5224
- ], result.results.map((entry) => [
5225
- entry.platform,
5226
- entry.updateId,
5227
- entry.runtimeVersion
5228
- ]));
5229
- }).pipe(handleUpdateCommandErrors));
5818
+ const rollbackCommand = defineCommand({
5819
+ meta: {
5820
+ name: "rollback",
5821
+ description: "Roll back updates on a branch"
5822
+ },
5823
+ args: {
5824
+ branch: {
5825
+ type: "string",
5826
+ required: true,
5827
+ description: "Branch to roll back"
5828
+ },
5829
+ platform: {
5830
+ type: "enum",
5831
+ options: [
5832
+ "ios",
5833
+ "android",
5834
+ "all"
5835
+ ],
5836
+ default: "all",
5837
+ description: "Platform(s) to roll back"
5838
+ },
5839
+ message: { type: "string" },
5840
+ "commit-time": { type: "string" },
5841
+ "directive-body-file": { type: "string" },
5842
+ "signature-file": { type: "string" },
5843
+ "certificate-chain-file": { type: "string" }
5844
+ },
5845
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
5846
+ const result = yield* runUpdateRollback({
5847
+ branch: args.branch,
5848
+ platform: args.platform,
5849
+ message: args.message,
5850
+ commitTime: args["commit-time"],
5851
+ directiveBodyFile: args["directive-body-file"],
5852
+ signatureFile: args["signature-file"],
5853
+ certificateChainFile: args["certificate-chain-file"]
5854
+ });
5855
+ yield* Console.log(`Created rollback group ${result.groupId} on branch "${result.branch}" at ${result.commitTime}.`);
5856
+ yield* Console.log("");
5857
+ yield* printTable([
5858
+ "Platform",
5859
+ "Update ID",
5860
+ "Runtime Version"
5861
+ ], result.results.map((entry) => [
5862
+ entry.platform,
5863
+ entry.updateId,
5864
+ entry.runtimeVersion
5865
+ ]));
5866
+ }), updateErrorExtras)
5867
+ });
5230
5868
 
5231
5869
  //#endregion
5232
5870
  //#region src/commands/update/rollout/complete.ts
5233
- const updateId$2 = Args.text({ name: "updateId" });
5234
- const completeCommand = Command.make("complete", { updateId: updateId$2 }, (opts) => Effect.gen(function* () {
5235
- const result = yield* (yield* apiClient).updates.completeRollout({ path: { id: opts.updateId } });
5236
- yield* Console.log(`Completed rollout for ${opts.updateId}. Current rollout is ${String(result.rolloutPercentage)}%.`);
5237
- }).pipe(handleUpdateCommandErrors));
5871
+ const completeCommand = defineCommand({
5872
+ meta: {
5873
+ name: "complete",
5874
+ description: "Complete the rollout for an update"
5875
+ },
5876
+ args: { updateId: {
5877
+ type: "positional",
5878
+ required: true,
5879
+ description: "Update ID"
5880
+ } },
5881
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
5882
+ const result = yield* (yield* apiClient).updates.completeRollout({ path: { id: args.updateId } });
5883
+ yield* Console.log(`Completed rollout for ${args.updateId}. Current rollout is ${String(result.rolloutPercentage)}%.`);
5884
+ }), updateErrorExtras)
5885
+ });
5238
5886
 
5239
5887
  //#endregion
5240
5888
  //#region src/commands/update/rollout/revert.ts
5241
- const updateId$1 = Args.text({ name: "updateId" });
5242
- const revertCommand = Command.make("revert", { updateId: updateId$1 }, (opts) => Effect.gen(function* () {
5243
- const result = yield* (yield* apiClient).updates.revertRollout({ path: { id: opts.updateId } });
5244
- yield* Console.log(`Reverted rollout for ${opts.updateId}. Current rollout is ${String(result.rolloutPercentage)}%.`);
5245
- }).pipe(handleUpdateCommandErrors));
5889
+ const revertCommand = defineCommand({
5890
+ meta: {
5891
+ name: "revert",
5892
+ description: "Revert the rollout for an update"
5893
+ },
5894
+ args: { updateId: {
5895
+ type: "positional",
5896
+ required: true,
5897
+ description: "Update ID"
5898
+ } },
5899
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
5900
+ const result = yield* (yield* apiClient).updates.revertRollout({ path: { id: args.updateId } });
5901
+ yield* Console.log(`Reverted rollout for ${args.updateId}. Current rollout is ${String(result.rolloutPercentage)}%.`);
5902
+ }), updateErrorExtras)
5903
+ });
5246
5904
 
5247
5905
  //#endregion
5248
5906
  //#region src/commands/update/rollout/set.ts
5249
- const updateId = Args.text({ name: "updateId" });
5250
- const percentage = rolloutPercentageOption("percentage");
5251
- const setCommand = Command.make("set", {
5252
- updateId,
5253
- percentage
5254
- }, (opts) => Effect.gen(function* () {
5255
- const result = yield* (yield* apiClient).updates.editRollout({
5256
- path: { id: opts.updateId },
5257
- payload: { percentage: opts.percentage }
5258
- });
5259
- yield* Console.log(`Updated rollout for ${opts.updateId} to ${String(result.rolloutPercentage)}%.`);
5260
- }).pipe(handleUpdateCommandErrors));
5907
+ const setCommand = defineCommand({
5908
+ meta: {
5909
+ name: "set",
5910
+ description: "Set the rollout percentage for an update"
5911
+ },
5912
+ args: {
5913
+ updateId: {
5914
+ type: "positional",
5915
+ required: true,
5916
+ description: "Update ID"
5917
+ },
5918
+ percentage: {
5919
+ type: "string",
5920
+ required: true,
5921
+ description: "Rollout percentage (1-100)"
5922
+ }
5923
+ },
5924
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
5925
+ const percentage = yield* parseRolloutPercentage(args.percentage, "percentage");
5926
+ const result = yield* (yield* apiClient).updates.editRollout({
5927
+ path: { id: args.updateId },
5928
+ payload: { percentage }
5929
+ });
5930
+ yield* Console.log(`Updated rollout for ${args.updateId} to ${String(result.rolloutPercentage)}%.`);
5931
+ }), updateErrorExtras)
5932
+ });
5261
5933
 
5262
5934
  //#endregion
5263
5935
  //#region src/commands/update/rollout/index.ts
5264
- const rolloutCommand = Command.make("rollout", {}, () => Console.log("Manage per-update rollouts. Run with --help for subcommands.")).pipe(Command.withSubcommands([
5265
- setCommand,
5266
- completeCommand,
5267
- revertCommand
5268
- ]));
5936
+ const rolloutCommand = defineCommand({
5937
+ meta: {
5938
+ name: "rollout",
5939
+ description: "Manage per-update rollouts"
5940
+ },
5941
+ subCommands: {
5942
+ set: setCommand,
5943
+ complete: completeCommand,
5944
+ revert: revertCommand
5945
+ }
5946
+ });
5269
5947
 
5270
5948
  //#endregion
5271
5949
  //#region src/commands/update/index.ts
5272
- const updateCommand = Command.make("update", {}, () => Console.log("Manage OTA updates. Run with --help for subcommands.")).pipe(Command.withSubcommands([
5273
- publishCommand,
5274
- listCommand,
5275
- deleteCommand,
5276
- promoteCommand,
5277
- rollbackCommand,
5278
- rolloutCommand
5279
- ]));
5950
+ const updateCommand = defineCommand({
5951
+ meta: {
5952
+ name: "update",
5953
+ description: "Manage OTA updates"
5954
+ },
5955
+ subCommands: {
5956
+ publish: publishCommand,
5957
+ list: listCommand,
5958
+ delete: deleteCommand,
5959
+ promote: promoteCommand,
5960
+ rollback: rollbackCommand,
5961
+ rollout: rolloutCommand
5962
+ }
5963
+ });
5280
5964
 
5281
5965
  //#endregion
5282
5966
  //#region src/index.ts
5283
- const command = Command.make("better-update", {}, () => Console.log("better-update CLI - Run with --help to see available commands")).pipe(Command.withSubcommands([
5284
- loginCommand,
5285
- logoutCommand,
5286
- initCommand,
5287
- statusCommand,
5288
- projectsCommand,
5289
- branchesCommand,
5290
- channelsCommand,
5291
- buildCommand,
5292
- buildsCommand,
5293
- credentialsCommand,
5294
- envCommand,
5295
- fingerprintCommand,
5296
- updateCommand,
5297
- analyticsCommand,
5298
- auditLogsCommand
5299
- ]));
5300
- Command.run(command, {
5301
- name: "better-update",
5302
- version: "0.1.0"
5303
- })(process.argv).pipe(Effect.provide(CliLive), NodeRuntime.runMain);
5967
+ await runMain(defineCommand({
5968
+ meta: {
5969
+ name: "better-update",
5970
+ version,
5971
+ description: "Publish OTA updates and builds for Expo apps"
5972
+ },
5973
+ subCommands: {
5974
+ login: loginCommand,
5975
+ logout: logoutCommand,
5976
+ init: initCommand,
5977
+ status: statusCommand,
5978
+ projects: projectsCommand,
5979
+ branches: branchesCommand,
5980
+ channels: channelsCommand,
5981
+ build: buildCommand,
5982
+ builds: buildsCommand,
5983
+ credentials: credentialsCommand,
5984
+ env: envCommand,
5985
+ fingerprint: fingerprintCommand,
5986
+ update: updateCommand,
5987
+ analytics: analyticsCommand,
5988
+ "audit-logs": auditLogsCommand
5989
+ }
5990
+ }));
5304
5991
 
5305
5992
  //#endregion
5306
5993
  export { };