@better-update/cli 0.6.2 → 0.6.3

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