@better-update/cli 0.7.1 → 0.8.1

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
@@ -13,6 +13,7 @@ import os from "node:os";
13
13
  import plist from "@expo/plist";
14
14
  import { ExpoRunFormatter } from "@expo/xcpretty";
15
15
  import { getFormattedSerialNumber, getX509Certificate, parsePKCS12 } from "@expo/pkcs12";
16
+ import { once } from "node:events";
16
17
  import { createServer } from "node:http";
17
18
  import { cancel, isCancel, password } from "@clack/prompts";
18
19
 
@@ -21,7 +22,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
21
22
 
22
23
  //#endregion
23
24
  //#region package.json
24
- var version = "0.7.1";
25
+ var version = "0.8.1";
25
26
 
26
27
  //#endregion
27
28
  //#region ../../packages/type-guards/src/index.ts
@@ -664,8 +665,20 @@ var Branch = class extends Schema.Class("Branch")({
664
665
  id: Id,
665
666
  projectId: Id,
666
667
  name: Schema.String,
667
- createdAt: DateTimeString
668
+ createdAt: DateTimeString,
669
+ updateCount: Schema.Number
668
670
  }) {};
671
+ const BranchSortColumn = Schema.Literal("name", "createdAt", "updateCount");
672
+ /**
673
+ * Sort param: column name optionally prefixed with `-` for descending.
674
+ * Example: `name` (asc), `-createdAt` (desc).
675
+ */
676
+ const BranchSort = Schema.Union(BranchSortColumn, Schema.TemplateLiteral("-", BranchSortColumn));
677
+ const ListBranchesParams = Schema.Struct({
678
+ projectId: Id,
679
+ ...PaginationParams.fields,
680
+ sort: Schema.optional(BranchSort)
681
+ });
669
682
  const CreateBranchBody = Schema.Struct({
670
683
  projectId: Id,
671
684
  name: Schema.String.pipe(Schema.minLength(1))
@@ -679,10 +692,12 @@ const idParam$8 = HttpApiSchema.param("id", Schema.String);
679
692
  var BranchesGroup = class extends HttpApiGroup.make("branches").add(HttpApiEndpoint.post("create", "/api/branches").setPayload(CreateBranchBody).addSuccess(Branch, { status: 201 }).annotateContext(OpenApi.annotations({
680
693
  title: "Create branch",
681
694
  description: "Create a new branch within a project"
682
- }))).add(HttpApiEndpoint.get("list", "/api/branches").setUrlParams(Schema.Struct({
683
- projectId: Id,
684
- ...CursorPaginationParams.fields
685
- })).addSuccess(cursorPageResult(Branch)).annotateContext(OpenApi.annotations({
695
+ }))).add(HttpApiEndpoint.get("list", "/api/branches").setUrlParams(ListBranchesParams).addSuccess(Schema.Struct({
696
+ items: Schema.Array(Branch),
697
+ total: Schema.Number,
698
+ page: Schema.Number,
699
+ limit: Schema.Number
700
+ })).annotateContext(OpenApi.annotations({
686
701
  title: "List branches",
687
702
  description: "List all branches for a project"
688
703
  }))).add(HttpApiEndpoint.patch("rename")`/api/branches/${idParam$8}`.setPayload(UpdateBranchBody).addSuccess(Branch).addError(Conflict).annotateContext(OpenApi.annotations({
@@ -829,6 +844,21 @@ const CreateBuildBody = Schema.Union(Schema.Struct({
829
844
  distribution: Schema.Literal("direct"),
830
845
  artifactFormat: Schema.Literal("apk")
831
846
  }));
847
+ const BuildSortColumn = Schema.Literal("createdAt", "platform", "distribution", "runtimeVersion", "appVersion");
848
+ /**
849
+ * Sort param: column name optionally prefixed with `-` for descending.
850
+ * Example: `runtimeVersion` (asc), `-createdAt` (desc).
851
+ */
852
+ const BuildSort = Schema.Union(BuildSortColumn, Schema.TemplateLiteral("-", BuildSortColumn));
853
+ const ListBuildsParams = Schema.Struct({
854
+ projectId: Id,
855
+ platform: Schema.optional(Platform),
856
+ profile: Schema.optional(Schema.String),
857
+ runtimeVersion: Schema.optional(Schema.String),
858
+ distribution: Schema.optional(Distribution),
859
+ ...PaginationParams.fields,
860
+ sort: Schema.optional(BuildSort)
861
+ });
832
862
  const CompleteBuildBody = Schema.Struct({
833
863
  sha256: Sha256Hex,
834
864
  byteSize: Schema.Number.pipe(Schema.nonNegative())
@@ -892,13 +922,12 @@ var BuildsGroup = class extends HttpApiGroup.make("builds").add(HttpApiEndpoint.
892
922
  }))).add(HttpApiEndpoint.post("complete")`/api/builds/${idParam$7}/complete`.setPayload(CompleteBuildBody).addSuccess(BuildWithArtifact).addError(Conflict).annotateContext(OpenApi.annotations({
893
923
  title: "Complete build",
894
924
  description: "Finalize a build after artifact upload"
895
- }))).add(HttpApiEndpoint.get("list", "/api/builds").setUrlParams(Schema.Struct({
896
- projectId: Id,
897
- platform: Schema.optional(Platform),
898
- profile: Schema.optional(Schema.String),
899
- runtimeVersion: Schema.optional(Schema.String),
900
- ...CursorPaginationParams.fields
901
- })).addSuccess(cursorPageResult(BuildWithArtifact)).annotateContext(OpenApi.annotations({
925
+ }))).add(HttpApiEndpoint.get("list", "/api/builds").setUrlParams(ListBuildsParams).addSuccess(Schema.Struct({
926
+ items: Schema.Array(BuildWithArtifact),
927
+ total: Schema.Number,
928
+ page: Schema.Number,
929
+ limit: Schema.Number
930
+ })).annotateContext(OpenApi.annotations({
902
931
  title: "List builds",
903
932
  description: "List builds for a project with optional filters"
904
933
  }))).add(HttpApiEndpoint.get("compatibilityMatrix", "/api/builds/compatibility-matrix").setUrlParams(Schema.Struct({ projectId: Id })).addSuccess(BuildCompatibilityMatrixResult).annotateContext(OpenApi.annotations({
@@ -930,6 +959,17 @@ var Channel = class extends Schema.Class("Channel")({
930
959
  isPaused: Schema.Boolean,
931
960
  createdAt: DateTimeString
932
961
  }) {};
962
+ const ChannelSortColumn = Schema.Literal("name", "createdAt");
963
+ /**
964
+ * Sort param: column name optionally prefixed with `-` for descending.
965
+ * Example: `name` (asc), `-createdAt` (desc).
966
+ */
967
+ const ChannelSort = Schema.Union(ChannelSortColumn, Schema.TemplateLiteral("-", ChannelSortColumn));
968
+ const ListChannelsParams = Schema.Struct({
969
+ projectId: Id,
970
+ ...PaginationParams.fields,
971
+ sort: Schema.optional(ChannelSort)
972
+ });
933
973
  const CreateChannelBody = Schema.Struct({
934
974
  projectId: Id,
935
975
  name: Schema.String.pipe(Schema.minLength(1)),
@@ -951,10 +991,12 @@ var ChannelsGroup = class extends HttpApiGroup.make("channels").add(HttpApiEndpo
951
991
  }))).add(HttpApiEndpoint.patch("update")`/api/channels/${idParam$6}`.setPayload(UpdateChannelBody).addSuccess(Channel).annotateContext(OpenApi.annotations({
952
992
  title: "Update channel",
953
993
  description: "Relink channel to a different branch"
954
- }))).add(HttpApiEndpoint.get("list", "/api/channels").setUrlParams(Schema.Struct({
955
- projectId: Id,
956
- ...CursorPaginationParams.fields
957
- })).addSuccess(cursorPageResult(Channel)).annotateContext(OpenApi.annotations({
994
+ }))).add(HttpApiEndpoint.get("list", "/api/channels").setUrlParams(ListChannelsParams).addSuccess(Schema.Struct({
995
+ items: Schema.Array(Channel),
996
+ total: Schema.Number,
997
+ page: Schema.Number,
998
+ limit: Schema.Number
999
+ })).annotateContext(OpenApi.annotations({
958
1000
  title: "List channels",
959
1001
  description: "List all channels for a project"
960
1002
  }))).add(HttpApiEndpoint.post("pause")`/api/channels/${idParam$6}/pause`.addSuccess(Channel).annotateContext(OpenApi.annotations({
@@ -1014,11 +1056,18 @@ const UpdateDeviceBody = Schema.Struct({
1014
1056
  appleTeamId: Schema.optional(Schema.NullOr(Id))
1015
1057
  });
1016
1058
  const DeleteDeviceResult = Schema.Struct({ deleted: Schema.Number });
1059
+ const DeviceSortColumn = Schema.Literal("name", "createdAt", "deviceClass");
1060
+ /**
1061
+ * Sort param: column name optionally prefixed with `-` for descending.
1062
+ * Example: `name` (asc), `-createdAt` (desc).
1063
+ */
1064
+ const DeviceSort = Schema.Union(DeviceSortColumn, Schema.TemplateLiteral("-", DeviceSortColumn));
1017
1065
  const ListDevicesParams = Schema.Struct({
1018
- ...CursorPaginationParams.fields,
1066
+ ...PaginationParams.fields,
1019
1067
  deviceClass: Schema.optional(DeviceClass),
1020
1068
  appleTeamId: Schema.optional(Id),
1021
- query: Schema.optional(Schema.String)
1069
+ query: Schema.optional(Schema.String),
1070
+ sort: Schema.optional(DeviceSort)
1022
1071
  });
1023
1072
  var DeviceRegistrationRequest = class extends Schema.Class("DeviceRegistrationRequest")({
1024
1073
  id: Id,
@@ -1049,7 +1098,12 @@ const idParam$5 = HttpApiSchema.param("id", Schema.String);
1049
1098
  var DevicesGroup = class extends HttpApiGroup.make("devices").add(HttpApiEndpoint.post("register", "/api/devices").setPayload(RegisterDeviceBody).addSuccess(Device, { status: 201 }).annotateContext(OpenApi.annotations({
1050
1099
  title: "Register device",
1051
1100
  description: "Register an Apple device UDID in the caller's active organization"
1052
- }))).add(HttpApiEndpoint.get("list", "/api/devices").setUrlParams(ListDevicesParams).addSuccess(cursorPageResult(Device)).annotateContext(OpenApi.annotations({
1101
+ }))).add(HttpApiEndpoint.get("list", "/api/devices").setUrlParams(ListDevicesParams).addSuccess(Schema.Struct({
1102
+ items: Schema.Array(Device),
1103
+ total: Schema.Number,
1104
+ page: Schema.Number,
1105
+ limit: Schema.Number
1106
+ })).annotateContext(OpenApi.annotations({
1053
1107
  title: "List devices",
1054
1108
  description: "List registered Apple devices in the caller's active organization"
1055
1109
  }))).add(HttpApiEndpoint.get("get")`/api/devices/${idParam$5}`.addSuccess(Device).annotateContext(OpenApi.annotations({
@@ -1256,7 +1310,12 @@ var Project = class extends Schema.Class("Project")({
1256
1310
  channelCount: Schema.Number,
1257
1311
  updateCount: Schema.Number
1258
1312
  }) {};
1259
- const ProjectSort = Schema.Literal("lastActivityAt", "name");
1313
+ const ProjectSortColumn = Schema.Literal("lastActivityAt", "name", "createdAt", "branchCount", "channelCount", "updateCount");
1314
+ /**
1315
+ * Sort param: column name optionally prefixed with `-` for descending.
1316
+ * Example: `name` (asc), `-lastActivityAt` (desc).
1317
+ */
1318
+ const ProjectSort = Schema.Union(ProjectSortColumn, Schema.TemplateLiteral("-", ProjectSortColumn));
1260
1319
  const ListProjectsParams = Schema.Struct({
1261
1320
  ...PaginationParams.fields,
1262
1321
  query: Schema.optional(Schema.String),
@@ -1320,6 +1379,19 @@ var Update = class extends Schema.Class("Update")({
1320
1379
  directiveBody: Schema.NullOr(Schema.String),
1321
1380
  createdAt: DateTimeString
1322
1381
  }) {};
1382
+ const UpdateSortColumn = Schema.Literal("createdAt", "runtimeVersion", "platform", "rolloutPercentage");
1383
+ /**
1384
+ * Sort param: column name optionally prefixed with `-` for descending.
1385
+ * Example: `runtimeVersion` (asc), `-createdAt` (desc).
1386
+ */
1387
+ const UpdateSort = Schema.Union(UpdateSortColumn, Schema.TemplateLiteral("-", UpdateSortColumn));
1388
+ const ListUpdatesParams = Schema.Struct({
1389
+ projectId: Id,
1390
+ branchId: Schema.optional(Id),
1391
+ platform: Schema.optional(Platform),
1392
+ ...PaginationParams.fields,
1393
+ sort: Schema.optional(UpdateSort)
1394
+ });
1323
1395
  const AssetRef = Schema.Struct({
1324
1396
  hash: Schema.String,
1325
1397
  key: Schema.String,
@@ -1372,12 +1444,12 @@ const groupIdParam = HttpApiSchema.param("groupId", Schema.String);
1372
1444
  var UpdatesGroup = class extends HttpApiGroup.make("updates").add(HttpApiEndpoint.post("create", "/api/updates").setPayload(CreateUpdateBody).addSuccess(Update, { status: 201 }).addError(Conflict).annotateContext(OpenApi.annotations({
1373
1445
  title: "Create update",
1374
1446
  description: "Publish a new update (manifest + directive) to a branch"
1375
- }))).add(HttpApiEndpoint.get("list", "/api/updates").setUrlParams(Schema.Struct({
1376
- projectId: Id,
1377
- branchId: Schema.optional(Id),
1378
- platform: Schema.optional(Platform),
1379
- ...CursorPaginationParams.fields
1380
- })).addSuccess(cursorPageResult(Update)).annotateContext(OpenApi.annotations({
1447
+ }))).add(HttpApiEndpoint.get("list", "/api/updates").setUrlParams(ListUpdatesParams).addSuccess(Schema.Struct({
1448
+ items: Schema.Array(Update),
1449
+ total: Schema.Number,
1450
+ page: Schema.Number,
1451
+ limit: Schema.Number
1452
+ })).annotateContext(OpenApi.annotations({
1381
1453
  title: "List updates",
1382
1454
  description: "List updates for a project, optionally filtered by branch"
1383
1455
  }))).add(HttpApiEndpoint.del("deleteGroup")`/api/updates/${groupIdParam}`.addSuccess(DeleteUpdateResult).annotateContext(OpenApi.annotations({
@@ -2004,21 +2076,18 @@ const auditLogsCommand = defineCommand({
2004
2076
 
2005
2077
  //#endregion
2006
2078
  //#region src/lib/drain-cursor.ts
2007
- const PAGE_SIZE = 100;
2008
2079
  const MAX_PAGES = 100;
2009
2080
  /**
2010
- * Drain a cursor-paginated list endpoint into a single array. CLI commands
2011
- * that resolve names → IDs (e.g. branch lookup) need the full set, not a
2012
- * page slice.
2081
+ * Drain a page-numbered list endpoint into a single array. Used by CLI
2082
+ * commands that need the full set, not a page slice.
2013
2083
  */
2014
- const drainCursor = (fetchPage) => {
2015
- const loop = (accumulator, cursor, pages) => fetchPage(cursor).pipe(Effect.flatMap((page) => {
2016
- const next = [...accumulator, ...page.items];
2017
- const { nextCursor } = page;
2018
- const reachedLimit = pages + 1 >= MAX_PAGES || next.length >= PAGE_SIZE * MAX_PAGES;
2019
- return nextCursor === null || reachedLimit ? Effect.succeed(next) : loop(next, nextCursor, pages + 1);
2084
+ const drainPages = (fetchPage) => {
2085
+ const loop = (accumulator, page) => fetchPage(page).pipe(Effect.flatMap((response) => {
2086
+ const next = [...accumulator, ...response.items];
2087
+ const fetched = page * response.limit;
2088
+ return page >= MAX_PAGES || next.length >= response.total || fetched >= response.total ? Effect.succeed(next) : loop(next, page + 1);
2020
2089
  }));
2021
- return loop([], void 0, 0);
2090
+ return loop([], 1);
2022
2091
  };
2023
2092
 
2024
2093
  //#endregion
@@ -2031,10 +2100,10 @@ const listCommand$6 = defineCommand({
2031
2100
  run: async () => runEffect(Effect.gen(function* () {
2032
2101
  const projectId = yield* readProjectId;
2033
2102
  const api = yield* apiClient;
2034
- const items = yield* drainCursor((cursor) => api.branches.list({ urlParams: {
2103
+ const items = yield* drainPages((page) => api.branches.list({ urlParams: {
2035
2104
  projectId,
2036
2105
  limit: 100,
2037
- ...cursor ? { cursor } : {}
2106
+ page
2038
2107
  } }));
2039
2108
  if (items.length === 0) {
2040
2109
  yield* Console.log("No branches found.");
@@ -3686,10 +3755,10 @@ const createCommand$2 = defineCommand({
3686
3755
  const projectId = yield* readProjectId;
3687
3756
  const api = yield* apiClient;
3688
3757
  const branchId = yield* resolveNamedResourceId$1({
3689
- items: yield* drainCursor((cursor) => api.branches.list({ urlParams: {
3758
+ items: yield* drainPages((page) => api.branches.list({ urlParams: {
3690
3759
  projectId,
3691
3760
  limit: 100,
3692
- ...cursor ? { cursor } : {}
3761
+ page
3693
3762
  } })),
3694
3763
  kind: "Branch",
3695
3764
  name: args.branch
@@ -3736,14 +3805,14 @@ const listCommand$4 = defineCommand({
3736
3805
  run: async () => runEffect(Effect.gen(function* () {
3737
3806
  const projectId = yield* readProjectId;
3738
3807
  const api = yield* apiClient;
3739
- const [items, branches] = yield* Effect.all([drainCursor((cursor) => api.channels.list({ urlParams: {
3808
+ const [items, branches] = yield* Effect.all([drainPages((page) => api.channels.list({ urlParams: {
3740
3809
  projectId,
3741
3810
  limit: 100,
3742
- ...cursor ? { cursor } : {}
3743
- } })), drainCursor((cursor) => api.branches.list({ urlParams: {
3811
+ page
3812
+ } })), drainPages((page) => api.branches.list({ urlParams: {
3744
3813
  projectId,
3745
3814
  limit: 100,
3746
- ...cursor ? { cursor } : {}
3815
+ page
3747
3816
  } }))]);
3748
3817
  if (items.length === 0) {
3749
3818
  yield* Console.log("No channels found.");
@@ -3851,10 +3920,10 @@ const createCommand$1 = defineCommand({
3851
3920
  const projectId = yield* readProjectId;
3852
3921
  const api = yield* apiClient;
3853
3922
  const newBranchId = yield* resolveNamedResourceId$1({
3854
- items: yield* drainCursor((cursor) => api.branches.list({ urlParams: {
3923
+ items: yield* drainPages((page) => api.branches.list({ urlParams: {
3855
3924
  projectId,
3856
3925
  limit: 100,
3857
- ...cursor ? { cursor } : {}
3926
+ page
3858
3927
  } })),
3859
3928
  kind: "Branch",
3860
3929
  name: args.branch
@@ -3955,10 +4024,10 @@ const updateCommand$1 = defineCommand({
3955
4024
  const projectId = yield* readProjectId;
3956
4025
  const api = yield* apiClient;
3957
4026
  const branchId = yield* resolveNamedResourceId$1({
3958
- items: yield* drainCursor((cursor) => api.branches.list({ urlParams: {
4027
+ items: yield* drainPages((page) => api.branches.list({ urlParams: {
3959
4028
  projectId,
3960
4029
  limit: 100,
3961
- ...cursor ? { cursor } : {}
4030
+ page
3962
4031
  } })),
3963
4032
  kind: "Branch",
3964
4033
  name: args.branch
@@ -4870,12 +4939,13 @@ const handleIncoming = async (req, res, session) => {
4870
4939
  res.end("Local callback failed");
4871
4940
  }
4872
4941
  };
4873
- const createBrowserLoginServer = (options = {}) => {
4942
+ const createBrowserLoginServer = async (options = {}) => {
4874
4943
  const session = createBrowserLoginSession(options);
4875
4944
  const server = createServer((req, res) => {
4876
4945
  handleIncoming(req, res, session).catch(() => void 0);
4877
4946
  });
4878
4947
  server.listen(0, "127.0.0.1");
4948
+ await once(server, "listening");
4879
4949
  const address = server.address();
4880
4950
  return {
4881
4951
  callbackUrl: `http://127.0.0.1:${address !== null && typeof address === "object" ? address.port : 0}${session.callbackPath}`,
@@ -4913,7 +4983,7 @@ const browserLogin = Effect.scoped(Effect.gen(function* () {
4913
4983
  const configStore = yield* ConfigStore;
4914
4984
  const authStore = yield* AuthStore;
4915
4985
  const webUrl = yield* configStore.getWebUrl;
4916
- const loginServer = yield* Effect.acquireRelease(Effect.sync(createBrowserLoginServer), (server) => Effect.sync(server.stop));
4986
+ const loginServer = yield* Effect.acquireRelease(Effect.promise(async () => createBrowserLoginServer()), (server) => Effect.sync(server.stop));
4917
4987
  const loginUrl = `${webUrl}/auth/cli-login?callbackUrl=${encodeURIComponent(loginServer.callbackUrl)}`;
4918
4988
  yield* Console.log("Opening browser for better-update login...");
4919
4989
  yield* Console.log("");
@@ -5162,8 +5232,8 @@ const statusCommand = defineCommand({
5162
5232
  yield* Console.log("");
5163
5233
  yield* Console.log("Builds");
5164
5234
  yield* Console.log("------");
5165
- const moreSuffix = builds.nextCursor === null ? "" : "+";
5166
- yield* printKeyValue([["Recent", `${String(builds.items.length)}${moreSuffix}`]]);
5235
+ const moreSuffix = builds.items.length < builds.total ? "+" : "";
5236
+ yield* printKeyValue([["Recent", `${String(builds.items.length)}${moreSuffix}`], ["Total", String(builds.total)]]);
5167
5237
  }))
5168
5238
  });
5169
5239
 
@@ -5219,10 +5289,10 @@ const listCommand = defineCommand({
5219
5289
  const limit = yield* parseLimit(args.limit, 20);
5220
5290
  const projectId = yield* readProjectId;
5221
5291
  const api = yield* apiClient;
5222
- const branches = yield* drainCursor((cursor) => api.branches.list({ urlParams: {
5292
+ const branches = yield* drainPages((page) => api.branches.list({ urlParams: {
5223
5293
  projectId,
5224
5294
  limit: 100,
5225
- ...cursor ? { cursor } : {}
5295
+ page
5226
5296
  } }));
5227
5297
  const branchId = args.branch ? yield* resolveNamedResourceId({
5228
5298
  items: branches,