@better-update/cli 0.26.1 → 0.27.0
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 +933 -209
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -34,7 +34,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
34
34
|
|
|
35
35
|
//#endregion
|
|
36
36
|
//#region package.json
|
|
37
|
-
var version = "0.
|
|
37
|
+
var version = "0.27.0";
|
|
38
38
|
|
|
39
39
|
//#endregion
|
|
40
40
|
//#region src/lib/interactive-mode.ts
|
|
@@ -67,6 +67,7 @@ const cookieSecurity = HttpApiSecurity.apiKey({
|
|
|
67
67
|
key: "__Secure-better-auth.session_token",
|
|
68
68
|
in: "cookie"
|
|
69
69
|
});
|
|
70
|
+
/** @effect-expect-leaking HttpServerRequest | ParsedSearchParams | RouteContext */
|
|
70
71
|
var Authentication = class extends HttpApiMiddleware.Tag()("api/Authentication", {
|
|
71
72
|
failure: Schema.Union(Unauthorized, Forbidden),
|
|
72
73
|
provides: AuthContext,
|
|
@@ -1177,6 +1178,44 @@ var BuildsGroup = class extends HttpApiGroup.make("builds").add(HttpApiEndpoint.
|
|
|
1177
1178
|
description: "Build artifact upload, tracking, and download endpoints"
|
|
1178
1179
|
})) {};
|
|
1179
1180
|
|
|
1181
|
+
//#endregion
|
|
1182
|
+
//#region ../../packages/api/src/domain/channel-grant.ts
|
|
1183
|
+
const GrantEffectSchema = Schema.Literal("allow", "deny");
|
|
1184
|
+
/** One member's allow/deny set on a channel; `actions` are "resource:action". */
|
|
1185
|
+
var ChannelGrant = class extends Schema.Class("ChannelGrant")({
|
|
1186
|
+
id: Id,
|
|
1187
|
+
memberId: Id,
|
|
1188
|
+
scopeKind: Schema.Literal("channel"),
|
|
1189
|
+
scopeId: Id,
|
|
1190
|
+
effect: GrantEffectSchema,
|
|
1191
|
+
actions: Schema.Array(Schema.String),
|
|
1192
|
+
createdAt: DateTimeString
|
|
1193
|
+
}) {};
|
|
1194
|
+
/** Upsert one (member, channel, effect) grant. effect defaults to "allow". */
|
|
1195
|
+
const UpsertChannelGrantBody = Schema.Struct({
|
|
1196
|
+
effect: Schema.optionalWith(GrantEffectSchema, { default: () => "allow" }),
|
|
1197
|
+
actions: Schema.Array(Schema.String).pipe(Schema.minItems(1))
|
|
1198
|
+
});
|
|
1199
|
+
const ListChannelGrantsParams = Schema.Struct({});
|
|
1200
|
+
const DeleteChannelGrantResult = Schema.Struct({ deleted: Schema.Number });
|
|
1201
|
+
|
|
1202
|
+
//#endregion
|
|
1203
|
+
//#region ../../packages/api/src/groups/channel-grants.ts
|
|
1204
|
+
const memberIdParam = HttpApiSchema.param("memberId", Schema.String);
|
|
1205
|
+
var ChannelGrantsGroup = class extends HttpApiGroup.make("channelGrants").add(HttpApiEndpoint.get("list")`/api/channels/${idParam}/grants`.setUrlParams(ListChannelGrantsParams).addSuccess(Schema.Array(ChannelGrant)).annotateContext(OpenApi.annotations({
|
|
1206
|
+
title: "List channel grants",
|
|
1207
|
+
description: "List all per-member allow/deny grants on a channel"
|
|
1208
|
+
}))).add(HttpApiEndpoint.put("upsert")`/api/channels/${idParam}/grants/${memberIdParam}`.setPayload(UpsertChannelGrantBody).addSuccess(ChannelGrant).annotateContext(OpenApi.annotations({
|
|
1209
|
+
title: "Upsert channel grant",
|
|
1210
|
+
description: "Create or replace a member's allow/deny grant on a channel"
|
|
1211
|
+
}))).add(HttpApiEndpoint.del("delete")`/api/channels/${idParam}/grants/${memberIdParam}`.addSuccess(DeleteChannelGrantResult).annotateContext(OpenApi.annotations({
|
|
1212
|
+
title: "Delete channel grant",
|
|
1213
|
+
description: "Revoke a member's grants on a channel"
|
|
1214
|
+
}))).addError(NotFound).addError(Forbidden).annotateContext(OpenApi.annotations({
|
|
1215
|
+
title: "Channel grants",
|
|
1216
|
+
description: "Per-channel ABAC permission grants (allow/deny by member)"
|
|
1217
|
+
})) {};
|
|
1218
|
+
|
|
1180
1219
|
//#endregion
|
|
1181
1220
|
//#region ../../packages/api/src/domain/channel.ts
|
|
1182
1221
|
var Channel = class extends Schema.Class("Channel")({
|
|
@@ -1432,6 +1471,71 @@ const EnvVarRevision = Schema.Struct({
|
|
|
1432
1471
|
const EnvVarRevisionsResult = Schema.Struct({ items: Schema.Array(EnvVarRevision) });
|
|
1433
1472
|
const RollbackEnvVarBody = Schema.Struct({ toRevisionId: Id });
|
|
1434
1473
|
|
|
1474
|
+
//#endregion
|
|
1475
|
+
//#region ../../packages/api/src/domain/env-grant.ts
|
|
1476
|
+
/**
|
|
1477
|
+
* One member's allow/deny set on a (project × environment) env-var scope.
|
|
1478
|
+
* `scopeKind` is fixed to "env_var_environment". `scopeId` is the encoded
|
|
1479
|
+
* `<projectId|global>:<environment>` token (server-built). `actions` are
|
|
1480
|
+
* "resource:action" tokens (here always envVar:*).
|
|
1481
|
+
*/
|
|
1482
|
+
var EnvGrant = class extends Schema.Class("EnvGrant")({
|
|
1483
|
+
id: Id,
|
|
1484
|
+
memberId: Id,
|
|
1485
|
+
scopeKind: Schema.Literal("env_var_environment"),
|
|
1486
|
+
scopeId: Schema.String,
|
|
1487
|
+
effect: GrantEffectSchema,
|
|
1488
|
+
actions: Schema.Array(Schema.String),
|
|
1489
|
+
createdAt: DateTimeString
|
|
1490
|
+
}) {};
|
|
1491
|
+
/** A flattened row for the list UI: one member × environment cell. */
|
|
1492
|
+
var EnvGrantRow = class extends Schema.Class("EnvGrantRow")({
|
|
1493
|
+
memberId: Id,
|
|
1494
|
+
environment: EnvVarEnvironment,
|
|
1495
|
+
effect: GrantEffectSchema,
|
|
1496
|
+
actions: Schema.Array(Schema.String)
|
|
1497
|
+
}) {};
|
|
1498
|
+
/**
|
|
1499
|
+
* URL params for listing grants on a project-or-global scope. `projectId` is the
|
|
1500
|
+
* sentinel "global" or a real project id (the server resolves null vs the
|
|
1501
|
+
* sentinel). Carried as a query param.
|
|
1502
|
+
*/
|
|
1503
|
+
const ListEnvGrantsParams = Schema.Struct({ projectId: Schema.String });
|
|
1504
|
+
/**
|
|
1505
|
+
* Upsert one (member, project-or-global, environment) grant. `projectId` null =
|
|
1506
|
+
* org-global vault. effect defaults to "allow". actions are envVar:* tokens.
|
|
1507
|
+
*/
|
|
1508
|
+
const UpsertEnvGrantBody = Schema.Struct({
|
|
1509
|
+
memberId: Id,
|
|
1510
|
+
projectId: Schema.NullOr(Id),
|
|
1511
|
+
environment: EnvVarEnvironment,
|
|
1512
|
+
effect: Schema.optionalWith(GrantEffectSchema, { default: () => "allow" }),
|
|
1513
|
+
actions: Schema.Array(Schema.String).pipe(Schema.minItems(1))
|
|
1514
|
+
});
|
|
1515
|
+
/** Delete both effects for (member, project-or-global, environment). */
|
|
1516
|
+
const DeleteEnvGrantBody = Schema.Struct({
|
|
1517
|
+
memberId: Id,
|
|
1518
|
+
projectId: Schema.NullOr(Id),
|
|
1519
|
+
environment: EnvVarEnvironment
|
|
1520
|
+
});
|
|
1521
|
+
const DeleteEnvGrantResult = Schema.Struct({ deleted: Schema.Number });
|
|
1522
|
+
|
|
1523
|
+
//#endregion
|
|
1524
|
+
//#region ../../packages/api/src/groups/env-grants.ts
|
|
1525
|
+
var EnvGrantsGroup = class extends HttpApiGroup.make("envGrants").add(HttpApiEndpoint.get("list", "/api/env-grants").setUrlParams(ListEnvGrantsParams).addSuccess(Schema.Array(EnvGrantRow)).annotateContext(OpenApi.annotations({
|
|
1526
|
+
title: "List env-var environment grants",
|
|
1527
|
+
description: "List per-member allow/deny env-var grants on a project-or-global scope across all environments. projectId is a real id or the sentinel 'global'."
|
|
1528
|
+
}))).add(HttpApiEndpoint.put("upsert", "/api/env-grants").setPayload(UpsertEnvGrantBody).addSuccess(EnvGrant).annotateContext(OpenApi.annotations({
|
|
1529
|
+
title: "Upsert env-var environment grant",
|
|
1530
|
+
description: "Create or replace a member's allow/deny env-var grant on one (project-or-global × environment) scope. projectId null = org-global."
|
|
1531
|
+
}))).add(HttpApiEndpoint.del("delete", "/api/env-grants").setPayload(DeleteEnvGrantBody).addSuccess(DeleteEnvGrantResult).annotateContext(OpenApi.annotations({
|
|
1532
|
+
title: "Delete env-var environment grants",
|
|
1533
|
+
description: "Revoke both allow and deny grants for a member on one (project-or-global × environment) scope."
|
|
1534
|
+
}))).addError(NotFound).addError(Forbidden).addError(BadRequest).annotateContext(OpenApi.annotations({
|
|
1535
|
+
title: "Env-var environment grants",
|
|
1536
|
+
description: "Per (project × environment) ABAC permission grants for env vars (allow/deny by member)"
|
|
1537
|
+
})) {};
|
|
1538
|
+
|
|
1435
1539
|
//#endregion
|
|
1436
1540
|
//#region ../../packages/api/src/groups/env-vars.ts
|
|
1437
1541
|
var EnvVarsGroup = class extends HttpApiGroup.make("env-vars").add(HttpApiEndpoint.post("create", "/api/env-vars").setPayload(CreateEnvVarBody).addSuccess(EnvVar, { status: 201 }).annotateContext(OpenApi.annotations({
|
|
@@ -1805,6 +1909,54 @@ var MeGroup = class extends HttpApiGroup.make("me").add(HttpApiEndpoint.get("get
|
|
|
1805
1909
|
description: "Current authenticated actor information"
|
|
1806
1910
|
})) {};
|
|
1807
1911
|
|
|
1912
|
+
//#endregion
|
|
1913
|
+
//#region ../../packages/api/src/domain/org-role.ts
|
|
1914
|
+
/** One resource→actions grant inside a role's permission set. */
|
|
1915
|
+
const PermissionGrantSchema = Schema.Struct({
|
|
1916
|
+
resource: Schema.String,
|
|
1917
|
+
actions: Schema.Array(Schema.String)
|
|
1918
|
+
});
|
|
1919
|
+
var OrgRole = class extends Schema.Class("OrgRole")({
|
|
1920
|
+
id: Id,
|
|
1921
|
+
organizationId: Id,
|
|
1922
|
+
role: Schema.String,
|
|
1923
|
+
permissions: Schema.Array(PermissionGrantSchema),
|
|
1924
|
+
createdAt: DateTimeString,
|
|
1925
|
+
updatedAt: Schema.NullOr(DateTimeString)
|
|
1926
|
+
}) {};
|
|
1927
|
+
const CreateOrgRoleBody = Schema.Struct({
|
|
1928
|
+
name: Schema.String.pipe(Schema.minLength(1)),
|
|
1929
|
+
permissions: Schema.Array(PermissionGrantSchema)
|
|
1930
|
+
});
|
|
1931
|
+
const UpdateOrgRoleBody = Schema.Struct({
|
|
1932
|
+
permissions: Schema.optional(Schema.Array(PermissionGrantSchema)),
|
|
1933
|
+
name: Schema.optional(Schema.String.pipe(Schema.minLength(1)))
|
|
1934
|
+
});
|
|
1935
|
+
const ListOrgRolesParams = Schema.Struct({ organizationId: Id });
|
|
1936
|
+
const DeleteOrgRoleResult = Schema.Struct({ deleted: Schema.Number });
|
|
1937
|
+
|
|
1938
|
+
//#endregion
|
|
1939
|
+
//#region ../../packages/api/src/groups/org-roles.ts
|
|
1940
|
+
var OrgRolesGroup = class extends HttpApiGroup.make("roles").add(HttpApiEndpoint.get("list", "/api/roles").setUrlParams(ListOrgRolesParams).addSuccess(Schema.Array(OrgRole)).annotateContext(OpenApi.annotations({
|
|
1941
|
+
title: "List custom roles",
|
|
1942
|
+
description: "List all custom roles defined for an organization"
|
|
1943
|
+
}))).add(HttpApiEndpoint.post("create", "/api/roles").setPayload(CreateOrgRoleBody).addSuccess(OrgRole, { status: 201 }).annotateContext(OpenApi.annotations({
|
|
1944
|
+
title: "Create custom role",
|
|
1945
|
+
description: "Create a new custom role with a permission set"
|
|
1946
|
+
}))).add(HttpApiEndpoint.get("get")`/api/roles/${idParam}`.addSuccess(OrgRole).annotateContext(OpenApi.annotations({
|
|
1947
|
+
title: "Get custom role",
|
|
1948
|
+
description: "Fetch a single custom role by id"
|
|
1949
|
+
}))).add(HttpApiEndpoint.patch("update")`/api/roles/${idParam}`.setPayload(UpdateOrgRoleBody).addSuccess(OrgRole).annotateContext(OpenApi.annotations({
|
|
1950
|
+
title: "Update custom role",
|
|
1951
|
+
description: "Rename a custom role or replace its permission set"
|
|
1952
|
+
}))).add(HttpApiEndpoint.del("delete")`/api/roles/${idParam}`.addSuccess(DeleteOrgRoleResult).annotateContext(OpenApi.annotations({
|
|
1953
|
+
title: "Delete custom role",
|
|
1954
|
+
description: "Delete a custom role"
|
|
1955
|
+
}))).addError(NotFound).addError(Conflict).addError(Forbidden).annotateContext(OpenApi.annotations({
|
|
1956
|
+
title: "Roles",
|
|
1957
|
+
description: "Custom organization role management (dynamic access control)"
|
|
1958
|
+
})) {};
|
|
1959
|
+
|
|
1808
1960
|
//#endregion
|
|
1809
1961
|
//#region ../../packages/api/src/groups/org-vault.ts
|
|
1810
1962
|
/** `:keyId` path parameter — a registered recipient's `user_encryption_keys.id`. */
|
|
@@ -2155,7 +2307,7 @@ var WebhooksGroup = class extends HttpApiGroup.make("webhooks").add(HttpApiEndpo
|
|
|
2155
2307
|
|
|
2156
2308
|
//#endregion
|
|
2157
2309
|
//#region ../../packages/api/src/api.ts
|
|
2158
|
-
var ManagementApi = class extends HttpApi.make("management-api").add(ProjectsGroup).add(BranchesGroup).add(ChannelsGroup).add(UpdatesGroup).add(AssetsGroup).add(AnalyticsGroup).add(BuildsGroup).add(EnvVarsGroup).add(FingerprintsGroup).add(AuditLogsGroup).add(DevicesGroup).add(AppleTeamsGroup).add(AppleDistributionCertificatesGroup).add(ApplePushKeysGroup).add(AscApiKeysGroup).add(AppleProvisioningProfilesGroup).add(GoogleServiceAccountKeysGroup).add(IosBundleConfigurationsGroup).add(IosAppMetadataGroup).add(SubmissionsGroup).add(AndroidApplicationIdentifiersGroup).add(AndroidUploadKeystoresGroup).add(AndroidBuildCredentialsGroup).add(BuildCredentialsGroup).add(UserEncryptionKeysGroup).add(OrgVaultGroup).add(MeGroup).add(WebhooksGroup).add(AdminGroup).middleware(Authentication).annotateContext(OpenApi.annotations({
|
|
2310
|
+
var ManagementApi = class extends HttpApi.make("management-api").add(ProjectsGroup).add(BranchesGroup).add(ChannelsGroup).add(UpdatesGroup).add(AssetsGroup).add(AnalyticsGroup).add(BuildsGroup).add(EnvVarsGroup).add(FingerprintsGroup).add(AuditLogsGroup).add(DevicesGroup).add(AppleTeamsGroup).add(AppleDistributionCertificatesGroup).add(ApplePushKeysGroup).add(AscApiKeysGroup).add(AppleProvisioningProfilesGroup).add(GoogleServiceAccountKeysGroup).add(IosBundleConfigurationsGroup).add(IosAppMetadataGroup).add(SubmissionsGroup).add(AndroidApplicationIdentifiersGroup).add(AndroidUploadKeystoresGroup).add(AndroidBuildCredentialsGroup).add(BuildCredentialsGroup).add(UserEncryptionKeysGroup).add(OrgVaultGroup).add(MeGroup).add(WebhooksGroup).add(AdminGroup).add(OrgRolesGroup).add(ChannelGrantsGroup).add(EnvGrantsGroup).middleware(Authentication).annotateContext(OpenApi.annotations({
|
|
2159
2311
|
title: "Better Update Management API",
|
|
2160
2312
|
version: "1.0.0",
|
|
2161
2313
|
description: "Management API for OTA update publishing, deployment, and analytics"
|
|
@@ -2308,13 +2460,13 @@ const ConfigStoreLive = Layer.effect(ConfigStore, Effect.gen(function* () {
|
|
|
2308
2460
|
const runtime = yield* CliRuntime;
|
|
2309
2461
|
const homeDirectory = yield* runtime.homeDirectory;
|
|
2310
2462
|
const configFile = path.join(homeDirectory, ".better-update", "config.json");
|
|
2311
|
-
const readConfig = fs.readFileString(configFile).pipe(Effect.
|
|
2463
|
+
const readConfig = fs.readFileString(configFile).pipe(Effect.orElseSucceed(() => ""), Effect.flatMap((content) => content.length === 0 ? Effect.void : Effect.try({
|
|
2312
2464
|
try: () => JSON.parse(content),
|
|
2313
2465
|
catch: (cause) => new ConfigStoreParseError({
|
|
2314
2466
|
message: "Config file contains invalid JSON",
|
|
2315
2467
|
cause
|
|
2316
2468
|
})
|
|
2317
|
-
}).pipe(Effect.map((parsed) => isRecord(parsed) ? parsed : void 0), Effect.
|
|
2469
|
+
}).pipe(Effect.map((parsed) => isRecord(parsed) ? parsed : void 0), Effect.orElseSucceed(() => void 0))));
|
|
2318
2470
|
return {
|
|
2319
2471
|
getBaseUrl: Effect.gen(function* () {
|
|
2320
2472
|
const envUrl = yield* runtime.getEnv("BETTER_UPDATE_URL");
|
|
@@ -2537,7 +2689,7 @@ const AppleSessionStoreLive = Layer.effect(AppleSessionStore, Effect.gen(functio
|
|
|
2537
2689
|
const usernameFile = path.join(sessionDir, "apple-username.json");
|
|
2538
2690
|
return {
|
|
2539
2691
|
loadSession: Effect.gen(function* () {
|
|
2540
|
-
const content = yield* fs.readFileString(sessionFile).pipe(Effect.
|
|
2692
|
+
const content = yield* fs.readFileString(sessionFile).pipe(Effect.orElseSucceed(() => null));
|
|
2541
2693
|
if (!content) return null;
|
|
2542
2694
|
const parsed = safeJsonParse(content);
|
|
2543
2695
|
if (!isRecord(parsed)) return null;
|
|
@@ -2555,7 +2707,7 @@ const AppleSessionStoreLive = Layer.effect(AppleSessionStore, Effect.gen(functio
|
|
|
2555
2707
|
}).pipe(Effect.mapError((cause) => new AppleAuthError$1({ message: `Failed to save Apple session: ${formatCause(cause)}` }))),
|
|
2556
2708
|
clearSession: fs.remove(sessionFile).pipe(Effect.catchAll(() => Effect.void)),
|
|
2557
2709
|
loadLastUsername: Effect.gen(function* () {
|
|
2558
|
-
const content = yield* fs.readFileString(usernameFile).pipe(Effect.
|
|
2710
|
+
const content = yield* fs.readFileString(usernameFile).pipe(Effect.orElseSucceed(() => null));
|
|
2559
2711
|
if (!content) return null;
|
|
2560
2712
|
const parsed = safeJsonParse(content);
|
|
2561
2713
|
if (!isRecord(parsed) || typeof parsed["username"] !== "string") return null;
|
|
@@ -2651,7 +2803,7 @@ const interactiveLogin = (appleUtils, options, cachedUsername) => Effect.gen(fun
|
|
|
2651
2803
|
const tryRestore = (appleUtils, store) => Effect.gen(function* () {
|
|
2652
2804
|
const stored = yield* store.loadSession;
|
|
2653
2805
|
if (stored === null) return null;
|
|
2654
|
-
const restored = yield* restoreFromCookies(appleUtils, stored.cookies).pipe(Effect.
|
|
2806
|
+
const restored = yield* restoreFromCookies(appleUtils, stored.cookies).pipe(Effect.orElseSucceed(() => null));
|
|
2655
2807
|
if (restored === null) return null;
|
|
2656
2808
|
return yield* resolveSessionTeam(appleUtils, restored);
|
|
2657
2809
|
});
|
|
@@ -2670,7 +2822,7 @@ const makeAppleAuthLive = (appleUtils = defaultAppleUtils) => Layer.effect(Apple
|
|
|
2670
2822
|
whoami: Effect.gen(function* () {
|
|
2671
2823
|
const stored = yield* store.loadSession;
|
|
2672
2824
|
if (stored === null) return null;
|
|
2673
|
-
const restored = yield* restoreFromCookies(appleUtils, stored.cookies).pipe(Effect.
|
|
2825
|
+
const restored = yield* restoreFromCookies(appleUtils, stored.cookies).pipe(Effect.orElseSucceed(() => null));
|
|
2674
2826
|
if (restored !== null) return sessionFromAuthState(restored);
|
|
2675
2827
|
const info = appleUtils.Session.getAnySessionInfo();
|
|
2676
2828
|
return info === null ? null : sessionFromInfo(stored.username, info);
|
|
@@ -2756,7 +2908,7 @@ const IdentityStoreLive = Layer.effect(IdentityStore, Effect.gen(function* () {
|
|
|
2756
2908
|
const identityFile = path.join(identityDir, "identity.json");
|
|
2757
2909
|
return {
|
|
2758
2910
|
load: Effect.gen(function* () {
|
|
2759
|
-
const content = yield* fs.readFileString(identityFile).pipe(Effect.
|
|
2911
|
+
const content = yield* fs.readFileString(identityFile).pipe(Effect.orElseSucceed(() => ""));
|
|
2760
2912
|
if (content.length === 0) return null;
|
|
2761
2913
|
const parsed = safeJsonParse(content);
|
|
2762
2914
|
return isIdentityFile(parsed) ? parsed : null;
|
|
@@ -2980,7 +3132,7 @@ const VaultCacheLive = Layer.effect(VaultCache, Effect.gen(function* () {
|
|
|
2980
3132
|
const flag = yield* runtime.getEnv("BETTER_UPDATE_NO_CACHE");
|
|
2981
3133
|
return flag !== void 0 && flag.length > 0 && flag !== "0" && flag !== "false";
|
|
2982
3134
|
});
|
|
2983
|
-
const readRaw = (publicKey) => Effect.try(() => new Entry(KEYCHAIN_SERVICE, publicKey).getPassword()).pipe(Effect.
|
|
3135
|
+
const readRaw = (publicKey) => Effect.try(() => new Entry(KEYCHAIN_SERVICE, publicKey).getPassword()).pipe(Effect.orElseSucceed(() => null));
|
|
2984
3136
|
const writeRaw = (publicKey, blob) => Effect.try(() => {
|
|
2985
3137
|
new Entry(KEYCHAIN_SERVICE, publicKey).setPassword(blob);
|
|
2986
3138
|
}).pipe(Effect.ignore);
|
|
@@ -3018,12 +3170,12 @@ const VersionCheckLive = Layer.effect(VersionCheck, Effect.gen(function* () {
|
|
|
3018
3170
|
const cacheDir = path.join(homeDirectory, ".better-update");
|
|
3019
3171
|
const cacheFile = path.join(cacheDir, "version-check.json");
|
|
3020
3172
|
const readCache = Effect.gen(function* () {
|
|
3021
|
-
const content = yield* fs.readFileString(cacheFile).pipe(Effect.
|
|
3173
|
+
const content = yield* fs.readFileString(cacheFile).pipe(Effect.orElseSucceed(() => ""));
|
|
3022
3174
|
if (content.length === 0) return;
|
|
3023
3175
|
const parsed = yield* Effect.try({
|
|
3024
3176
|
try: () => JSON.parse(content),
|
|
3025
3177
|
catch: () => "parse-error"
|
|
3026
|
-
}).pipe(Effect.
|
|
3178
|
+
}).pipe(Effect.orElseSucceed(() => void 0));
|
|
3027
3179
|
if (isRecord(parsed) && typeof parsed["latest"] === "string" && typeof parsed["checkedAt"] === "number") return {
|
|
3028
3180
|
latest: parsed["latest"],
|
|
3029
3181
|
checkedAt: parsed["checkedAt"]
|
|
@@ -3224,7 +3376,7 @@ const buildOpenBrowserCommand = (platform, url) => {
|
|
|
3224
3376
|
};
|
|
3225
3377
|
const openBrowser = (url) => Effect.gen(function* () {
|
|
3226
3378
|
const command = buildOpenBrowserCommand((yield* CliRuntime).platform, url);
|
|
3227
|
-
if (!(yield* Command.exitCode(command).pipe(Effect.map((code) => code === 0), Effect.
|
|
3379
|
+
if (!(yield* Command.exitCode(command).pipe(Effect.map((code) => code === 0), Effect.orElseSucceed(() => false)))) yield* Console.log(`Open this URL manually:\n${url}`);
|
|
3228
3380
|
});
|
|
3229
3381
|
const browserLogin = Effect.scoped(Effect.gen(function* () {
|
|
3230
3382
|
const configStore = yield* ConfigStore;
|
|
@@ -3623,9 +3775,9 @@ const configPath = (projectRoot) => path.join(projectRoot, BETTER_UPDATE_CONFIG_
|
|
|
3623
3775
|
* graceful `readConfig`.
|
|
3624
3776
|
*/
|
|
3625
3777
|
const readBetterUpdateConfig = (projectRoot) => Effect.gen(function* () {
|
|
3626
|
-
const content = yield* (yield* FileSystem.FileSystem).readFileString(configPath(projectRoot)).pipe(Effect.
|
|
3778
|
+
const content = yield* (yield* FileSystem.FileSystem).readFileString(configPath(projectRoot)).pipe(Effect.orElseSucceed(() => ""));
|
|
3627
3779
|
if (content.length === 0) return;
|
|
3628
|
-
return yield* Effect.try(() => JSON.parse(content)).pipe(Effect.map((parsed) => isRecord(parsed) ? parsed : void 0), Effect.
|
|
3780
|
+
return yield* Effect.try(() => JSON.parse(content)).pipe(Effect.map((parsed) => isRecord(parsed) ? parsed : void 0), Effect.orElseSucceed(() => void 0));
|
|
3629
3781
|
});
|
|
3630
3782
|
/**
|
|
3631
3783
|
* Resolve the linked project id from `better-update.json`, or `undefined` when
|
|
@@ -4354,14 +4506,8 @@ const KeyValueFromString = Schema.transformOrFail(Schema.String, KeyValuePair, {
|
|
|
4354
4506
|
},
|
|
4355
4507
|
encode: ({ key, value }) => ParseResult.succeed(`${key}=${value}`)
|
|
4356
4508
|
});
|
|
4357
|
-
const parseRolloutPercentage = (raw, flag) => Effect.
|
|
4358
|
-
|
|
4359
|
-
catch: () => new InvalidArgumentError({ message: `--${flag} must be an integer between 1 and 100, got "${raw}".` })
|
|
4360
|
-
});
|
|
4361
|
-
const parseKeyValue = (raw) => Effect.try({
|
|
4362
|
-
try: () => Schema.decodeUnknownSync(KeyValueFromString)(raw),
|
|
4363
|
-
catch: () => new InvalidArgumentError({ message: "Invalid format. Use KEY=VALUE (e.g. API_KEY=abc123)" })
|
|
4364
|
-
});
|
|
4509
|
+
const parseRolloutPercentage = (raw, flag) => Schema.decodeUnknown(RolloutPercentage)(Number(raw)).pipe(Effect.mapError(() => new InvalidArgumentError({ message: `--${flag} must be an integer between 1 and 100, got "${raw}".` })));
|
|
4510
|
+
const parseKeyValue = (raw) => Schema.decodeUnknown(KeyValueFromString)(raw).pipe(Effect.mapError(() => new InvalidArgumentError({ message: "Invalid format. Use KEY=VALUE (e.g. API_KEY=abc123)" })));
|
|
4365
4511
|
const parseLimit = (raw, defaultValue) => {
|
|
4366
4512
|
if (raw === void 0) return Effect.succeed(defaultValue);
|
|
4367
4513
|
const parsed = Number(raw);
|
|
@@ -4371,7 +4517,7 @@ const parseLimit = (raw, defaultValue) => {
|
|
|
4371
4517
|
|
|
4372
4518
|
//#endregion
|
|
4373
4519
|
//#region src/commands/audit-logs/list.ts
|
|
4374
|
-
const listCommand$
|
|
4520
|
+
const listCommand$12 = defineCommand({
|
|
4375
4521
|
meta: {
|
|
4376
4522
|
name: "list",
|
|
4377
4523
|
description: "List audit log entries"
|
|
@@ -4433,7 +4579,7 @@ const auditLogsCommand = defineCommand({
|
|
|
4433
4579
|
name: "audit-logs",
|
|
4434
4580
|
description: "View audit logs"
|
|
4435
4581
|
},
|
|
4436
|
-
subCommands: { list: listCommand$
|
|
4582
|
+
subCommands: { list: listCommand$12 }
|
|
4437
4583
|
});
|
|
4438
4584
|
|
|
4439
4585
|
//#endregion
|
|
@@ -4537,7 +4683,7 @@ const drainPages = (fetchPage) => {
|
|
|
4537
4683
|
|
|
4538
4684
|
//#endregion
|
|
4539
4685
|
//#region src/commands/branches.ts
|
|
4540
|
-
const listCommand$
|
|
4686
|
+
const listCommand$11 = defineCommand({
|
|
4541
4687
|
meta: {
|
|
4542
4688
|
name: "list",
|
|
4543
4689
|
description: "List branches for the linked project"
|
|
@@ -4560,7 +4706,7 @@ const listCommand$8 = defineCommand({
|
|
|
4560
4706
|
]), "No branches found.");
|
|
4561
4707
|
}))
|
|
4562
4708
|
});
|
|
4563
|
-
const createCommand$
|
|
4709
|
+
const createCommand$5 = defineCommand({
|
|
4564
4710
|
meta: {
|
|
4565
4711
|
name: "create",
|
|
4566
4712
|
description: "Create a branch"
|
|
@@ -4583,7 +4729,7 @@ const createCommand$4 = defineCommand({
|
|
|
4583
4729
|
]);
|
|
4584
4730
|
}))
|
|
4585
4731
|
});
|
|
4586
|
-
const viewCommand$
|
|
4732
|
+
const viewCommand$4 = defineCommand({
|
|
4587
4733
|
meta: {
|
|
4588
4734
|
name: "view",
|
|
4589
4735
|
description: "Show a branch by ID or name"
|
|
@@ -4641,7 +4787,7 @@ const renameCommand$1 = defineCommand({
|
|
|
4641
4787
|
return branch;
|
|
4642
4788
|
}), { json: "value" })
|
|
4643
4789
|
});
|
|
4644
|
-
const deleteCommand$
|
|
4790
|
+
const deleteCommand$7 = defineCommand({
|
|
4645
4791
|
meta: {
|
|
4646
4792
|
name: "delete",
|
|
4647
4793
|
description: "Delete a branch"
|
|
@@ -4666,11 +4812,11 @@ const branchesCommand = defineCommand({
|
|
|
4666
4812
|
description: "Manage branches"
|
|
4667
4813
|
},
|
|
4668
4814
|
subCommands: {
|
|
4669
|
-
list: listCommand$
|
|
4670
|
-
view: viewCommand$
|
|
4671
|
-
create: createCommand$
|
|
4815
|
+
list: listCommand$11,
|
|
4816
|
+
view: viewCommand$4,
|
|
4817
|
+
create: createCommand$5,
|
|
4672
4818
|
rename: renameCommand$1,
|
|
4673
|
-
delete: deleteCommand$
|
|
4819
|
+
delete: deleteCommand$7
|
|
4674
4820
|
}
|
|
4675
4821
|
});
|
|
4676
4822
|
|
|
@@ -18709,7 +18855,7 @@ const asArrayBuffer$1 = (bytes) => {
|
|
|
18709
18855
|
};
|
|
18710
18856
|
const signAscJwt = (credentials) => Effect.gen(function* () {
|
|
18711
18857
|
const der = pemToPkcs8Der(credentials.p8Pem);
|
|
18712
|
-
if (der === null) return yield*
|
|
18858
|
+
if (der === null) return yield* new AppleAuthError({ cause: /* @__PURE__ */ new Error("Invalid .p8 PEM") });
|
|
18713
18859
|
const header = {
|
|
18714
18860
|
alg: "ES256",
|
|
18715
18861
|
kid: credentials.keyId,
|
|
@@ -18793,7 +18939,7 @@ const fetchRaw = (jwt, path, init) => Effect.gen(function* () {
|
|
|
18793
18939
|
try: () => text.length === 0 ? {} : JSON.parse(text),
|
|
18794
18940
|
catch: (cause) => new AscNetworkError({ cause })
|
|
18795
18941
|
});
|
|
18796
|
-
if (!response.ok) return yield*
|
|
18942
|
+
if (!response.ok) return yield* parseApiError(response, body, text);
|
|
18797
18943
|
return body;
|
|
18798
18944
|
});
|
|
18799
18945
|
const toAscCertificate = (value) => {
|
|
@@ -18893,12 +19039,10 @@ const createCertificate = (credentials, params) => withJwt(credentials, (jwt) =>
|
|
|
18893
19039
|
}
|
|
18894
19040
|
} })
|
|
18895
19041
|
}), toAscCertificate);
|
|
18896
|
-
if (resource === null) return yield*
|
|
19042
|
+
if (resource === null) return yield* malformed("certificate");
|
|
18897
19043
|
return resource;
|
|
18898
19044
|
}));
|
|
18899
|
-
const deleteCertificate = (credentials, id) => withJwt(credentials, (jwt) => Effect.
|
|
18900
|
-
yield* fetchRaw(jwt, `/v1/certificates/${encodeURIComponent(id)}`, { method: "DELETE" });
|
|
18901
|
-
}));
|
|
19045
|
+
const deleteCertificate = (credentials, id) => withJwt(credentials, (jwt) => Effect.asVoid(fetchRaw(jwt, `/v1/certificates/${encodeURIComponent(id)}`, { method: "DELETE" })));
|
|
18902
19046
|
const listBundleIds = (credentials) => withJwt(credentials, (jwt) => Effect.gen(function* () {
|
|
18903
19047
|
return extractList(yield* fetchRaw(jwt, "/v1/bundleIds?limit=200"), toAscBundleId);
|
|
18904
19048
|
}));
|
|
@@ -18914,7 +19058,7 @@ const createBundleId = (credentials, params) => withJwt(credentials, (jwt) => Ef
|
|
|
18914
19058
|
}
|
|
18915
19059
|
} })
|
|
18916
19060
|
}), toAscBundleId);
|
|
18917
|
-
if (resource === null) return yield*
|
|
19061
|
+
if (resource === null) return yield* malformed("bundleId");
|
|
18918
19062
|
return resource;
|
|
18919
19063
|
}));
|
|
18920
19064
|
const listDevices = (credentials) => withJwt(credentials, (jwt) => Effect.gen(function* () {
|
|
@@ -18946,7 +19090,7 @@ const createProvisioningProfile = (credentials, params) => withJwt(credentials,
|
|
|
18946
19090
|
relationships
|
|
18947
19091
|
} })
|
|
18948
19092
|
}), toAscProfile);
|
|
18949
|
-
if (resource === null) return yield*
|
|
19093
|
+
if (resource === null) return yield* malformed("profile");
|
|
18950
19094
|
return resource;
|
|
18951
19095
|
}));
|
|
18952
19096
|
const isCertificateLimitError = (error) => {
|
|
@@ -18982,7 +19126,7 @@ const parseCert = (certDerBytes) => {
|
|
|
18982
19126
|
const generatePassword = () => forge.util.encode64(forge.random.getBytesSync(16));
|
|
18983
19127
|
const extractCertMetadata = (cert) => Effect.gen(function* () {
|
|
18984
19128
|
const appleTeamId = extractTeamId$1(cert);
|
|
18985
|
-
if (appleTeamId === null) return yield*
|
|
19129
|
+
if (appleTeamId === null) return yield* new CertParseError({ message: "Could not extract Apple team identifier from certificate subject" });
|
|
18986
19130
|
return {
|
|
18987
19131
|
serialNumber: cert.serialNumber.toUpperCase(),
|
|
18988
19132
|
validFrom: cert.validity.notBefore.toISOString(),
|
|
@@ -19000,7 +19144,7 @@ const extractCertMetadata = (cert) => Effect.gen(function* () {
|
|
|
19000
19144
|
*/
|
|
19001
19145
|
const extractMetadataFromP12 = (params) => Effect.gen(function* () {
|
|
19002
19146
|
const certBagOid = forge.pki.oids["certBag"];
|
|
19003
|
-
if (certBagOid === void 0) return yield*
|
|
19147
|
+
if (certBagOid === void 0) return yield* new CertParseError({ message: "PKCS#12 OID lookup for certBag failed" });
|
|
19004
19148
|
const [first] = yield* Effect.try({
|
|
19005
19149
|
try: () => {
|
|
19006
19150
|
const p12Der = forge.util.decode64(params.p12Base64);
|
|
@@ -19009,7 +19153,7 @@ const extractMetadataFromP12 = (params) => Effect.gen(function* () {
|
|
|
19009
19153
|
},
|
|
19010
19154
|
catch: (error) => new CertParseError({ message: `Failed to parse PKCS#12 bundle: ${error instanceof Error ? error.message : String(error)}` })
|
|
19011
19155
|
});
|
|
19012
|
-
if (first?.cert === void 0) return yield*
|
|
19156
|
+
if (first?.cert === void 0) return yield* new CertParseError({ message: "PKCS#12 bundle does not contain a certificate" });
|
|
19013
19157
|
return yield* extractCertMetadata(first.cert);
|
|
19014
19158
|
});
|
|
19015
19159
|
const buildDistributionCertP12 = (params) => Effect.gen(function* () {
|
|
@@ -19190,10 +19334,10 @@ const generateAndUploadDistributionCertificate = (api, input) => Effect.gen(func
|
|
|
19190
19334
|
csrPem: csrResult.csrPem,
|
|
19191
19335
|
certificateType
|
|
19192
19336
|
}).pipe(Effect.mapError(wrapAscError("apple-create-certificate")));
|
|
19193
|
-
if (apple.certificateContent === null) return yield*
|
|
19337
|
+
if (apple.certificateContent === null) return yield* new GenerateFailedError({
|
|
19194
19338
|
step: "apple-create-certificate",
|
|
19195
19339
|
message: "Apple response missing certificateContent"
|
|
19196
|
-
})
|
|
19340
|
+
});
|
|
19197
19341
|
const bundle = yield* buildDistributionCertP12({
|
|
19198
19342
|
certificateContentBase64: apple.certificateContent,
|
|
19199
19343
|
privateKey: csrResult.privateKey
|
|
@@ -19243,10 +19387,10 @@ const revokeAppleCertificate = (api, input) => Effect.gen(function* () {
|
|
|
19243
19387
|
});
|
|
19244
19388
|
const revokeLocalDistributionCertificate = (api, input) => Effect.gen(function* () {
|
|
19245
19389
|
const local = (yield* api.appleDistributionCertificates.list()).items.find((entry) => entry.id === input.distributionCertificateId);
|
|
19246
|
-
if (local === void 0) return yield*
|
|
19390
|
+
if (local === void 0) return yield* new GenerateFailedError({
|
|
19247
19391
|
step: "load-distribution-certificate",
|
|
19248
19392
|
message: `Distribution certificate ${input.distributionCertificateId} not found on this account`
|
|
19249
|
-
})
|
|
19393
|
+
});
|
|
19250
19394
|
const creds = yield* fetchAscCredentials(api, input.ascApiKeyId);
|
|
19251
19395
|
const ascCreds = {
|
|
19252
19396
|
keyId: creds.keyId,
|
|
@@ -19283,10 +19427,10 @@ const listAppleCertificates = (api, input) => Effect.gen(function* () {
|
|
|
19283
19427
|
});
|
|
19284
19428
|
const resolveCertAscId = (creds, serialNumber, certificateType) => Effect.gen(function* () {
|
|
19285
19429
|
const match = (yield* listCertificates(creds, { certificateType }).pipe(Effect.mapError(wrapAscError("apple-list-certificates")))).find((entry) => entry.serialNumber.toUpperCase() === serialNumber);
|
|
19286
|
-
if (match === void 0) return yield*
|
|
19430
|
+
if (match === void 0) return yield* new GenerateFailedError({
|
|
19287
19431
|
step: "match-apple-certificate",
|
|
19288
19432
|
message: `Distribution certificate ${serialNumber} not present on Apple Developer Portal; upload or re-generate it`
|
|
19289
|
-
})
|
|
19433
|
+
});
|
|
19290
19434
|
return match.id;
|
|
19291
19435
|
});
|
|
19292
19436
|
const ensureBundleId = (creds, bundleIdentifier) => Effect.gen(function* () {
|
|
@@ -19319,10 +19463,10 @@ const generateAndUploadProvisioningProfile = (api, input) => Effect.gen(function
|
|
|
19319
19463
|
const [certAscId, bundleIdAscId] = yield* Effect.all([resolveCertAscId(ascCreds, cert.serialNumber.toUpperCase(), certificateType), ensureBundleId(ascCreds, input.bundleIdentifier)], { concurrency: 2 });
|
|
19320
19464
|
const useDevices = input.distributionType === "AD_HOC" || input.distributionType === "DEVELOPMENT";
|
|
19321
19465
|
const { ids: deviceAscIds } = useDevices ? yield* collectDeviceAscIds(ascCreds, cert.appleTeamId, input.deviceIds) : { ids: [] };
|
|
19322
|
-
if (useDevices && deviceAscIds.length === 0) return yield*
|
|
19466
|
+
if (useDevices && deviceAscIds.length === 0) return yield* new GenerateFailedError({
|
|
19323
19467
|
step: "collect-devices",
|
|
19324
19468
|
message: "No registered devices to attach to the provisioning profile"
|
|
19325
|
-
})
|
|
19469
|
+
});
|
|
19326
19470
|
const profileBytes = fromBase64((yield* createProvisioningProfile(ascCreds, {
|
|
19327
19471
|
profileName: `${input.bundleIdentifier} ${input.distributionType} ${Date.now()}`,
|
|
19328
19472
|
profileType: DISTRIBUTION_TO_PROFILE_TYPE$1[input.distributionType],
|
|
@@ -19633,10 +19777,10 @@ const downloadAndroidCredentials = (api, options) => Effect.gen(function* () {
|
|
|
19633
19777
|
...compact({ buildProfile: options.buildProfile })
|
|
19634
19778
|
}
|
|
19635
19779
|
}).pipe(Effect.mapError((cause) => resolveErrorToMissingCredentials(cause, "android")));
|
|
19636
|
-
if (resolved.platform !== "android") return yield*
|
|
19780
|
+
if (resolved.platform !== "android") return yield* new MissingCredentialsError({
|
|
19637
19781
|
message: "Server returned non-Android credentials for an Android build request",
|
|
19638
19782
|
hint: androidBindHint
|
|
19639
|
-
})
|
|
19783
|
+
});
|
|
19640
19784
|
const secret = yield* decryptResolveSecret({
|
|
19641
19785
|
session: yield* openVaultSessionForBuild(api, androidBindHint),
|
|
19642
19786
|
credentialType: "keystore",
|
|
@@ -19757,7 +19901,7 @@ const credentialsJsonPath = (projectRoot) => path.join(projectRoot, CREDENTIALS_
|
|
|
19757
19901
|
const readCredentialsJson = (projectRoot) => Effect.gen(function* () {
|
|
19758
19902
|
const fs = yield* FileSystem.FileSystem;
|
|
19759
19903
|
const filePath = credentialsJsonPath(projectRoot);
|
|
19760
|
-
if (!(yield* fs.exists(filePath).pipe(Effect.
|
|
19904
|
+
if (!(yield* fs.exists(filePath).pipe(Effect.orElseSucceed(() => false)))) return yield* new CredentialsJsonError({ message: `credentials.json not found at ${filePath}.` });
|
|
19761
19905
|
return yield* parseCredentialsJson(yield* fs.readFileString(filePath).pipe(Effect.mapError((cause) => new CredentialsJsonError({ message: `Failed to read credentials.json: ${String(cause)}` }))));
|
|
19762
19906
|
});
|
|
19763
19907
|
const writeCredentialsJson = (projectRoot, data) => Effect.gen(function* () {
|
|
@@ -19774,7 +19918,7 @@ const resolveCredentialPath = (projectRoot, candidate) => path.isAbsolute(candid
|
|
|
19774
19918
|
|
|
19775
19919
|
//#endregion
|
|
19776
19920
|
//#region src/lib/local-credentials.ts
|
|
19777
|
-
const requirePath = (fs, absolutePath, label) => fs.exists(absolutePath).pipe(Effect.
|
|
19921
|
+
const requirePath = (fs, absolutePath, label) => fs.exists(absolutePath).pipe(Effect.orElseSucceed(() => false), Effect.flatMap((exists) => exists ? Effect.void : Effect.fail(new MissingCredentialsError({
|
|
19778
19922
|
message: `Local credentials.json: ${label} not found at ${absolutePath}.`,
|
|
19779
19923
|
hint: "Run `better-update credentials sync pull` to materialize files, or fix the path in credentials.json."
|
|
19780
19924
|
}))));
|
|
@@ -20029,7 +20173,7 @@ const runStepFormatted = (cmd, step, formatter) => Effect.gen(function* () {
|
|
|
20029
20173
|
if (code !== 0) {
|
|
20030
20174
|
const summary = formatter.getBuildSummary();
|
|
20031
20175
|
if (summary.length > 0) process$1.stderr.write(`${summary}\n`);
|
|
20032
|
-
return yield*
|
|
20176
|
+
return yield* buildFailed(step, code, `${step} exited with code ${code}`);
|
|
20033
20177
|
}
|
|
20034
20178
|
});
|
|
20035
20179
|
|
|
@@ -20265,10 +20409,10 @@ const findAscCertificateId = (ctx, serialNumber, certificateType) => Effect.gen(
|
|
|
20265
20409
|
const certs = yield* wrap("apple-list-certificates", async () => AppleUtils.Certificate.getAsync(ctx, { query: { filter: { certificateType } } }));
|
|
20266
20410
|
const upper = serialNumber.toUpperCase();
|
|
20267
20411
|
const match = certs.find((entry) => entry.attributes.serialNumber.toUpperCase() === upper);
|
|
20268
|
-
if (match === void 0) return yield*
|
|
20412
|
+
if (match === void 0) return yield* new AppleIdGenerateFailedError({
|
|
20269
20413
|
step: "match-apple-certificate",
|
|
20270
20414
|
message: `Distribution certificate ${serialNumber} not present on Apple Developer Portal; upload or re-generate it`
|
|
20271
|
-
})
|
|
20415
|
+
});
|
|
20272
20416
|
return match.id;
|
|
20273
20417
|
});
|
|
20274
20418
|
const collectIosDeviceIds = (ctx, deviceIds) => Effect.gen(function* () {
|
|
@@ -20287,10 +20431,10 @@ const generateAndUploadProvisioningProfileViaAppleId = (api, input) => Effect.ge
|
|
|
20287
20431
|
const [certAscId, bundleIdAscId] = yield* Effect.all([findAscCertificateId(ctx, cert.serialNumber, certificateType), findOrCreateBundleId(ctx, input.bundleIdentifier)], { concurrency: 2 });
|
|
20288
20432
|
const useDevices = input.distributionType === "AD_HOC" || input.distributionType === "DEVELOPMENT";
|
|
20289
20433
|
const deviceIds = useDevices ? yield* collectIosDeviceIds(ctx, input.deviceIds) : [];
|
|
20290
|
-
if (useDevices && deviceIds.length === 0) return yield*
|
|
20434
|
+
if (useDevices && deviceIds.length === 0) return yield* new AppleIdGenerateFailedError({
|
|
20291
20435
|
step: "collect-devices",
|
|
20292
20436
|
message: "No registered devices to attach to the provisioning profile"
|
|
20293
|
-
})
|
|
20437
|
+
});
|
|
20294
20438
|
const profileName = `${input.bundleIdentifier} ${input.distributionType} ${Date.now()}`;
|
|
20295
20439
|
const { profileContent } = (yield* wrap("apple-create-profile", async () => AppleUtils.Profile.createAsync(ctx, {
|
|
20296
20440
|
bundleId: bundleIdAscId,
|
|
@@ -20299,10 +20443,10 @@ const generateAndUploadProvisioningProfileViaAppleId = (api, input) => Effect.ge
|
|
|
20299
20443
|
name: profileName,
|
|
20300
20444
|
profileType: DISTRIBUTION_TO_PROFILE_TYPE[input.distributionType]
|
|
20301
20445
|
}))).attributes;
|
|
20302
|
-
if (profileContent === null) return yield*
|
|
20446
|
+
if (profileContent === null) return yield* new AppleIdGenerateFailedError({
|
|
20303
20447
|
step: "extract-profile-content",
|
|
20304
20448
|
message: "Apple returned a profile with no content (likely expired/invalid)"
|
|
20305
|
-
})
|
|
20449
|
+
});
|
|
20306
20450
|
const profileBytes = fromBase64(profileContent);
|
|
20307
20451
|
const rosterHash = useDevices ? computeDeviceRosterHashHex(deviceIds) : void 0;
|
|
20308
20452
|
const created = yield* api.appleProvisioningProfiles.upload({ payload: {
|
|
@@ -20482,10 +20626,10 @@ const interactiveCertLimitRecover = (api, ascApiKeyId) => Effect.gen(function* (
|
|
|
20482
20626
|
ascApiKeyId,
|
|
20483
20627
|
certificateType: "IOS_DISTRIBUTION"
|
|
20484
20628
|
});
|
|
20485
|
-
if (certs.length === 0) return yield*
|
|
20629
|
+
if (certs.length === 0) return yield* new MissingCredentialsError({
|
|
20486
20630
|
message: "Apple says the certificate limit is hit but no existing certificates were returned.",
|
|
20487
20631
|
hint: "Try again later or check the Apple Developer portal."
|
|
20488
|
-
})
|
|
20632
|
+
});
|
|
20489
20633
|
const toRevoke = yield* promptMultiSelect("Select one or more certificates to revoke before retrying", certs.map((entry) => ({
|
|
20490
20634
|
value: entry.id,
|
|
20491
20635
|
label: `${entry.serialNumber.slice(0, 12)}… (${entry.displayName ?? entry.certificateType}, exp ${entry.expirationDate.slice(0, 10)})`
|
|
@@ -20498,10 +20642,10 @@ const interactiveCertLimitRecover = (api, ascApiKeyId) => Effect.gen(function* (
|
|
|
20498
20642
|
});
|
|
20499
20643
|
const generateDistributionCertInteractive = (api) => Effect.gen(function* () {
|
|
20500
20644
|
const teamAscKeys = (yield* api.ascApiKeys.list()).items.filter((key) => key.appleTeamId !== null);
|
|
20501
|
-
if (teamAscKeys.length === 0) return yield*
|
|
20645
|
+
if (teamAscKeys.length === 0) return yield* new MissingCredentialsError({
|
|
20502
20646
|
message: "No ASC API key linked to an Apple team in this organization.",
|
|
20503
20647
|
hint: "Upload an ASC API key with a team assignment via the dashboard, then retry."
|
|
20504
|
-
})
|
|
20648
|
+
});
|
|
20505
20649
|
const ascKeyId = yield* promptSelect("Select an ASC API key to issue the certificate against", teamAscKeys.map((key) => ({
|
|
20506
20650
|
value: key.id,
|
|
20507
20651
|
label: `${key.name} (${key.keyId})`
|
|
@@ -20520,10 +20664,10 @@ const chooseIosCertificateId = (api) => Effect.gen(function* () {
|
|
|
20520
20664
|
}, {
|
|
20521
20665
|
value: "abort",
|
|
20522
20666
|
label: "Abort — I'll upload one manually"
|
|
20523
|
-
}])) === "abort") return yield*
|
|
20667
|
+
}])) === "abort") return yield* new MissingCredentialsError({
|
|
20524
20668
|
message: "Build aborted — no distribution certificate available.",
|
|
20525
20669
|
hint: "Run `better-update credentials generate distribution-certificate --asc-key-id <id>` or upload via the dashboard."
|
|
20526
|
-
})
|
|
20670
|
+
});
|
|
20527
20671
|
return (yield* generateDistributionCertInteractive(api)).id;
|
|
20528
20672
|
}
|
|
20529
20673
|
const choice = yield* promptSelect("Select a distribution certificate (or 'generate' for a fresh one)", [{
|
|
@@ -20539,10 +20683,10 @@ const chooseIosCertificateId = (api) => Effect.gen(function* () {
|
|
|
20539
20683
|
const pickIosCertificate = (api) => Effect.gen(function* () {
|
|
20540
20684
|
const chosenId = yield* chooseIosCertificateId(api);
|
|
20541
20685
|
const cert = (yield* api.appleDistributionCertificates.list()).items.find((entry) => entry.id === chosenId);
|
|
20542
|
-
if (cert === void 0) return yield*
|
|
20686
|
+
if (cert === void 0) return yield* new MissingCredentialsError({
|
|
20543
20687
|
message: "Selected certificate not found after generation.",
|
|
20544
20688
|
hint: "Retry."
|
|
20545
|
-
})
|
|
20689
|
+
});
|
|
20546
20690
|
return {
|
|
20547
20691
|
certId: chosenId,
|
|
20548
20692
|
cert
|
|
@@ -20550,10 +20694,10 @@ const pickIosCertificate = (api) => Effect.gen(function* () {
|
|
|
20550
20694
|
});
|
|
20551
20695
|
const pickIosAscKey = (api, appleTeamId) => Effect.gen(function* () {
|
|
20552
20696
|
const teamAscKeys = (yield* api.ascApiKeys.list()).items.filter((key) => key.appleTeamId !== null && key.appleTeamId === appleTeamId);
|
|
20553
|
-
if (teamAscKeys.length === 0) return yield*
|
|
20697
|
+
if (teamAscKeys.length === 0) return yield* new MissingCredentialsError({
|
|
20554
20698
|
message: `No ASC API key linked to Apple team ${appleTeamId}.`,
|
|
20555
20699
|
hint: "Upload an ASC API key for that team via the dashboard, then retry."
|
|
20556
|
-
})
|
|
20700
|
+
});
|
|
20557
20701
|
return yield* promptSelect("Select an ASC API key", teamAscKeys.map((key) => ({
|
|
20558
20702
|
value: key.id,
|
|
20559
20703
|
label: `${key.name} (${key.keyId})`
|
|
@@ -20634,10 +20778,10 @@ const generateKeystoreInteractive = (api) => Effect.gen(function* () {
|
|
|
20634
20778
|
});
|
|
20635
20779
|
const pickExistingKeystore = (api) => Effect.gen(function* () {
|
|
20636
20780
|
const keystores = yield* api.androidUploadKeystores.list();
|
|
20637
|
-
if (keystores.items.length === 0) return yield*
|
|
20781
|
+
if (keystores.items.length === 0) return yield* new MissingCredentialsError({
|
|
20638
20782
|
message: "No existing keystores in this organization.",
|
|
20639
20783
|
hint: "Re-run and choose 'Generate new keystore'."
|
|
20640
|
-
})
|
|
20784
|
+
});
|
|
20641
20785
|
return yield* promptSelect("Select a keystore", keystores.items.map((item) => ({
|
|
20642
20786
|
value: item.id,
|
|
20643
20787
|
label: item.keyAlias
|
|
@@ -20670,10 +20814,10 @@ const setupAndroidInteractive = (api, input) => Effect.gen(function* () {
|
|
|
20670
20814
|
label: "Abort — I'll configure it in the dashboard"
|
|
20671
20815
|
}
|
|
20672
20816
|
]);
|
|
20673
|
-
if (choice === "abort") return yield*
|
|
20817
|
+
if (choice === "abort") return yield* new MissingCredentialsError({
|
|
20674
20818
|
message: `Build aborted — no keystore bound to ${input.applicationIdentifier}.`,
|
|
20675
20819
|
hint: "Run `better-update credentials generate keystore` or upload via the dashboard."
|
|
20676
|
-
})
|
|
20820
|
+
});
|
|
20677
20821
|
const keystoreId = yield* choice === "generate" ? generateKeystoreAuto(api, input.applicationIdentifier) : pickExistingKeystore(api);
|
|
20678
20822
|
yield* api.androidBuildCredentials.create({
|
|
20679
20823
|
path: { applicationIdentifierId: appId },
|
|
@@ -20694,10 +20838,10 @@ const ensureAndroidCredentialsAvailable = (api, input) => api.buildCredentials.r
|
|
|
20694
20838
|
}).pipe(Effect.asVoid);
|
|
20695
20839
|
const ensureAndroidCredentials = (api, input, options) => ensureAndroidCredentialsAvailable(api, input).pipe(Effect.catchIf(isMissingResolveError, () => Effect.gen(function* () {
|
|
20696
20840
|
const mode = yield* InteractiveMode;
|
|
20697
|
-
if (options.freezeCredentials || !mode.allow) return yield*
|
|
20841
|
+
if (options.freezeCredentials || !mode.allow) return yield* new MissingCredentialsError({
|
|
20698
20842
|
message: `No Android build credentials for ${input.applicationIdentifier}.`,
|
|
20699
20843
|
hint: options.freezeCredentials ? "Run `better-update credentials generate` first, or remove --freeze-credentials." : "Run `better-update credentials generate` first, or rerun with --interactive to configure now."
|
|
20700
|
-
})
|
|
20844
|
+
});
|
|
20701
20845
|
yield* setupAndroidInteractive(api, input);
|
|
20702
20846
|
return yield* ensureAndroidCredentialsAvailable(api, input);
|
|
20703
20847
|
})));
|
|
@@ -20718,10 +20862,10 @@ const resolveIosBuildCredentials = (api, input) => api.buildCredentials.resolve(
|
|
|
20718
20862
|
const findBoundIosConfig = (api, input) => Effect.gen(function* () {
|
|
20719
20863
|
const distributionType = IOS_DISTRIBUTION_TO_TYPE[input.distribution];
|
|
20720
20864
|
const match = (yield* api.iosBundleConfigurations.list({ path: { projectId: input.projectId } })).items.find((config) => config.bundleIdentifier === input.bundleIdentifier && config.distributionType === distributionType);
|
|
20721
|
-
if (match === void 0) return yield*
|
|
20865
|
+
if (match === void 0) return yield* new MissingCredentialsError({
|
|
20722
20866
|
message: `iOS bundle configuration vanished while regenerating stale profile for ${input.bundleIdentifier}`,
|
|
20723
20867
|
hint: "Retry; the configuration must exist before regeneration"
|
|
20724
|
-
})
|
|
20868
|
+
});
|
|
20725
20869
|
return match;
|
|
20726
20870
|
});
|
|
20727
20871
|
const regenerateProvisioningProfile = (api, input) => Effect.gen(function* () {
|
|
@@ -20752,19 +20896,19 @@ const regenerateProvisioningProfile = (api, input) => Effect.gen(function* () {
|
|
|
20752
20896
|
});
|
|
20753
20897
|
const ensureIosCredentials = (api, input, options) => resolveIosBuildCredentials(api, input).pipe(Effect.catchIf(isMissingResolveError, () => Effect.gen(function* () {
|
|
20754
20898
|
const mode = yield* InteractiveMode;
|
|
20755
|
-
if (options.freezeCredentials || !mode.allow) return yield*
|
|
20899
|
+
if (options.freezeCredentials || !mode.allow) return yield* new MissingCredentialsError({
|
|
20756
20900
|
message: `No iOS build credentials for ${input.bundleIdentifier} (${input.distribution}).`,
|
|
20757
20901
|
hint: options.freezeCredentials ? "Run `better-update credentials generate` first, or remove --freeze-credentials." : "Run `better-update credentials generate` first, or rerun with --interactive to configure now."
|
|
20758
|
-
})
|
|
20902
|
+
});
|
|
20759
20903
|
yield* setupIosInteractive(api, input);
|
|
20760
20904
|
return yield* resolveIosBuildCredentials(api, input);
|
|
20761
20905
|
})), Effect.flatMap((resolved) => Effect.gen(function* () {
|
|
20762
20906
|
if (resolved.platform !== "ios" || !resolved.profileStale) return;
|
|
20763
20907
|
const mode = yield* InteractiveMode;
|
|
20764
|
-
if (options.freezeCredentials || !mode.allow) return yield*
|
|
20908
|
+
if (options.freezeCredentials || !mode.allow) return yield* new MissingCredentialsError({
|
|
20765
20909
|
message: `Stale provisioning profile for ${input.bundleIdentifier}; cannot regenerate without an interactive session.`,
|
|
20766
20910
|
hint: options.freezeCredentials ? "Run a build without --freeze-credentials once to refresh the profile, or run `better-update credentials regenerate-profile`." : "Run `better-update credentials regenerate-profile --bundle <id> --distribution <type>` from an interactive terminal."
|
|
20767
|
-
})
|
|
20911
|
+
});
|
|
20768
20912
|
yield* Console.log(`Stale provisioning profile for ${input.bundleIdentifier} (device roster changed). Regenerating...`);
|
|
20769
20913
|
yield* regenerateProvisioningProfile(api, input);
|
|
20770
20914
|
})));
|
|
@@ -20814,7 +20958,7 @@ const applyTargetSigning = (options) => Effect.gen(function* () {
|
|
|
20814
20958
|
const projectDir = yield* findXcodeProjectDir$1(options.iosDir);
|
|
20815
20959
|
const pbxprojPath = path.join(projectDir, "project.pbxproj");
|
|
20816
20960
|
const project = yield* parseProject$1(pbxprojPath);
|
|
20817
|
-
for (const entry of options.entries) for (const configUuid of entry.buildConfigurationUuids) if (!mutateConfig(project, configUuid, entry.settings)) yield* new XcodeProjectError({ message: `Build configuration ${configUuid} not found for target "${entry.targetName}" in ${pbxprojPath}.` });
|
|
20961
|
+
for (const entry of options.entries) for (const configUuid of entry.buildConfigurationUuids) if (!mutateConfig(project, configUuid, entry.settings)) return yield* new XcodeProjectError({ message: `Build configuration ${configUuid} not found for target "${entry.targetName}" in ${pbxprojPath}.` });
|
|
20818
20962
|
const serialized = yield* Effect.try({
|
|
20819
20963
|
try: () => project.writeSync(),
|
|
20820
20964
|
catch: (cause) => new XcodeProjectError({ message: `Failed to serialize ${pbxprojPath}: ${cause instanceof Error ? cause.message : String(cause)}` })
|
|
@@ -21002,7 +21146,7 @@ const installProvisioningProfile = ({ profilePath }) => Effect.acquireRelease(Ef
|
|
|
21002
21146
|
//#endregion
|
|
21003
21147
|
//#region src/lib/post-build-validation.ts
|
|
21004
21148
|
const validateOneBundle = (bundleDir, expectedByBundleId, expectedTeamId) => Effect.gen(function* () {
|
|
21005
|
-
const bundleId = yield* readBundleId(bundleDir).pipe(Effect.
|
|
21149
|
+
const bundleId = yield* readBundleId(bundleDir).pipe(Effect.orElseSucceed(() => void 0));
|
|
21006
21150
|
if (!bundleId) return {
|
|
21007
21151
|
bundleId: void 0,
|
|
21008
21152
|
warnings: [`Missing CFBundleIdentifier in Info.plist at ${bundleDir}`]
|
|
@@ -21014,16 +21158,16 @@ const validateOneBundle = (bundleDir, expectedByBundleId, expectedTeamId) => Eff
|
|
|
21014
21158
|
};
|
|
21015
21159
|
return {
|
|
21016
21160
|
bundleId,
|
|
21017
|
-
warnings: yield* validateEmbeddedProfile(bundleDir, expected.profileUuid, expectedTeamId, bundleId).pipe(Effect.
|
|
21161
|
+
warnings: yield* validateEmbeddedProfile(bundleDir, expected.profileUuid, expectedTeamId, bundleId).pipe(Effect.orElseSucceed(() => []))
|
|
21018
21162
|
};
|
|
21019
21163
|
});
|
|
21020
21164
|
const validateIosBuild = (params) => Effect.gen(function* () {
|
|
21021
|
-
const appDir = yield* findAppDirectory$1(params.archivePath).pipe(Effect.
|
|
21165
|
+
const appDir = yield* findAppDirectory$1(params.archivePath).pipe(Effect.orElseSucceed(() => void 0));
|
|
21022
21166
|
if (!appDir) return {
|
|
21023
21167
|
passed: false,
|
|
21024
21168
|
warnings: ["Could not locate .app bundle in archive — skipping post-build validation"]
|
|
21025
21169
|
};
|
|
21026
|
-
const bundleDirs = yield* listSignedBundleDirs(appDir).pipe(Effect.
|
|
21170
|
+
const bundleDirs = yield* listSignedBundleDirs(appDir).pipe(Effect.orElseSucceed(() => [appDir]));
|
|
21027
21171
|
const expectedByBundleId = new Map(params.expectedTargets.map((target) => [target.bundleId, target]));
|
|
21028
21172
|
const perBundle = yield* Effect.forEach(bundleDirs, (bundleDir) => validateOneBundle(bundleDir, expectedByBundleId, params.expectedTeamId));
|
|
21029
21173
|
const warnings = perBundle.flatMap((entry) => [...entry.warnings]);
|
|
@@ -21053,7 +21197,7 @@ const findAppDirectory$1 = (archivePath) => Effect.gen(function* () {
|
|
|
21053
21197
|
const listSignedBundleDirs = (appDir) => Effect.gen(function* () {
|
|
21054
21198
|
const fs = yield* FileSystem.FileSystem;
|
|
21055
21199
|
const plugInsDir = path.join(appDir, "PlugIns");
|
|
21056
|
-
if (!(yield* fs.exists(plugInsDir).pipe(Effect.
|
|
21200
|
+
if (!(yield* fs.exists(plugInsDir).pipe(Effect.orElseSucceed(() => false)))) return [appDir];
|
|
21057
21201
|
return [appDir, ...(yield* fs.readDirectory(plugInsDir)).filter((entry) => entry.endsWith(".appex")).map((entry) => path.join(plugInsDir, entry))];
|
|
21058
21202
|
});
|
|
21059
21203
|
const readBundleId = (bundleDir) => Effect.gen(function* () {
|
|
@@ -21996,7 +22140,7 @@ const easJsonPath = (projectRoot) => Effect.gen(function* () {
|
|
|
21996
22140
|
const readEasJson = (projectRoot) => Effect.gen(function* () {
|
|
21997
22141
|
const fs = yield* FileSystem.FileSystem;
|
|
21998
22142
|
const filePath = yield* easJsonPath(projectRoot);
|
|
21999
|
-
return yield* parseEasConfig(yield* fs.readFileString(filePath).pipe(Effect.
|
|
22143
|
+
return yield* parseEasConfig(yield* fs.readFileString(filePath).pipe(Effect.mapError((cause) => new BuildProfileError({ message: cause._tag === "SystemError" && cause.reason === "NotFound" ? `No eas.json found at ${filePath}. Create one with a "build" section.` : `Failed to read eas.json: ${cause.message}` }))));
|
|
22000
22144
|
});
|
|
22001
22145
|
const mergeCustom = (base, overlay) => {
|
|
22002
22146
|
const merged = shallowMerge(base, overlay);
|
|
@@ -22208,8 +22352,8 @@ const clearBuildCaches = (projectRoot) => Effect.gen(function* () {
|
|
|
22208
22352
|
const removed = [];
|
|
22209
22353
|
yield* Effect.forEach(CACHE_DIRS, (rel) => Effect.gen(function* () {
|
|
22210
22354
|
const target = path.join(projectRoot, rel);
|
|
22211
|
-
if (!(yield* fs.exists(target).pipe(Effect.
|
|
22212
|
-
yield* fs.remove(target, { recursive: true }).pipe(Effect.
|
|
22355
|
+
if (!(yield* fs.exists(target).pipe(Effect.orElseSucceed(() => false)))) return;
|
|
22356
|
+
yield* fs.remove(target, { recursive: true }).pipe(Effect.orElseSucceed(() => void 0));
|
|
22213
22357
|
removed.push(rel);
|
|
22214
22358
|
}), { concurrency: 4 });
|
|
22215
22359
|
if (removed.length > 0) yield* Console.error(`Cleared caches: ${removed.join(", ")}`);
|
|
@@ -22538,10 +22682,10 @@ const runString = (cmd, cwd) => Command.string(Command.workingDirectory(cmd, cwd
|
|
|
22538
22682
|
*/
|
|
22539
22683
|
const readGitContext = (projectRoot) => Effect.gen(function* () {
|
|
22540
22684
|
const [commit, ref, commitMessage, status] = yield* Effect.all([
|
|
22541
|
-
runString(Command.make("git", "rev-parse", "HEAD"), projectRoot).pipe(Effect.map((output) => output.trim()), Effect.
|
|
22542
|
-
runString(Command.make("git", "symbolic-ref", "--short", "HEAD"), projectRoot).pipe(Effect.map((output) => output.trim()), Effect.
|
|
22543
|
-
runString(Command.make("git", "log", "-1", "--format=%s"), projectRoot).pipe(Effect.map((output) => output.trim()), Effect.
|
|
22544
|
-
runString(Command.make("git", "status", "--porcelain"), projectRoot).pipe(Effect.
|
|
22685
|
+
runString(Command.make("git", "rev-parse", "HEAD"), projectRoot).pipe(Effect.map((output) => output.trim()), Effect.orElseSucceed(() => "")),
|
|
22686
|
+
runString(Command.make("git", "symbolic-ref", "--short", "HEAD"), projectRoot).pipe(Effect.map((output) => output.trim()), Effect.orElseSucceed(() => "")),
|
|
22687
|
+
runString(Command.make("git", "log", "-1", "--format=%s"), projectRoot).pipe(Effect.map((output) => output.trim()), Effect.orElseSucceed(() => "")),
|
|
22688
|
+
runString(Command.make("git", "status", "--porcelain"), projectRoot).pipe(Effect.orElseSucceed(() => ""))
|
|
22545
22689
|
], { concurrency: "unbounded" });
|
|
22546
22690
|
return {
|
|
22547
22691
|
ref: ref.length > 0 ? ref : void 0,
|
|
@@ -22570,14 +22714,14 @@ const readGradleConfig = (androidDir) => Effect.gen(function* () {
|
|
|
22570
22714
|
const hasKts = yield* fs.exists(ktsPath).pipe(Effect.orElseSucceed(() => false));
|
|
22571
22715
|
if (!hasGroovy && hasKts) return;
|
|
22572
22716
|
if (!hasGroovy) return;
|
|
22573
|
-
const content = yield* fs.readFileString(gradlePath).pipe(Effect.
|
|
22717
|
+
const content = yield* fs.readFileString(gradlePath).pipe(Effect.orElseSucceed(() => void 0));
|
|
22574
22718
|
if (!content) return;
|
|
22575
22719
|
return yield* Effect.tryPromise({
|
|
22576
22720
|
try: async () => {
|
|
22577
22721
|
return __require("gradle-to-js").parseText(stripGroovyComments(content));
|
|
22578
22722
|
},
|
|
22579
22723
|
catch: () => void 0
|
|
22580
|
-
}).pipe(Effect.map(extractGradleConfig), Effect.
|
|
22724
|
+
}).pipe(Effect.map(extractGradleConfig), Effect.orElseSucceed(() => void 0));
|
|
22581
22725
|
});
|
|
22582
22726
|
/**
|
|
22583
22727
|
* Log a warning if Gradle applicationId differs from app.json package name.
|
|
@@ -22697,7 +22841,7 @@ const ALWAYS_IGNORE = [...[
|
|
|
22697
22841
|
"dist"
|
|
22698
22842
|
], ...NATIVE_BUILD_OUTPUTS];
|
|
22699
22843
|
const findLockfile = (fs, dir) => Effect.gen(function* () {
|
|
22700
|
-
for (const [name, pm] of LOCKFILES) if (yield* fs.exists(path.join(dir, name)).pipe(Effect.
|
|
22844
|
+
for (const [name, pm] of LOCKFILES) if (yield* fs.exists(path.join(dir, name)).pipe(Effect.orElseSucceed(() => false))) return pm;
|
|
22701
22845
|
});
|
|
22702
22846
|
const walkUpForLockfile = (startCwd, dir) => Effect.gen(function* () {
|
|
22703
22847
|
const pm = yield* findLockfile(yield* FileSystem.FileSystem, dir);
|
|
@@ -22733,13 +22877,13 @@ const buildIgnoreInstance = (workspaceRoot, options = {}) => Effect.gen(function
|
|
|
22733
22877
|
const ig = ignore();
|
|
22734
22878
|
ig.add([...ALWAYS_IGNORE]);
|
|
22735
22879
|
const easignorePath = path.join(workspaceRoot, ".easignore");
|
|
22736
|
-
if (yield* fs.exists(easignorePath).pipe(Effect.
|
|
22737
|
-
const content = yield* fs.readFileString(easignorePath).pipe(Effect.
|
|
22880
|
+
if (yield* fs.exists(easignorePath).pipe(Effect.orElseSucceed(() => false))) {
|
|
22881
|
+
const content = yield* fs.readFileString(easignorePath).pipe(Effect.orElseSucceed(() => ""));
|
|
22738
22882
|
ig.add(content);
|
|
22739
22883
|
} else {
|
|
22740
22884
|
const gitignorePath = path.join(workspaceRoot, ".gitignore");
|
|
22741
|
-
if (yield* fs.exists(gitignorePath).pipe(Effect.
|
|
22742
|
-
const content = yield* fs.readFileString(gitignorePath).pipe(Effect.
|
|
22885
|
+
if (yield* fs.exists(gitignorePath).pipe(Effect.orElseSucceed(() => false))) {
|
|
22886
|
+
const content = yield* fs.readFileString(gitignorePath).pipe(Effect.orElseSucceed(() => ""));
|
|
22743
22887
|
ig.add(content);
|
|
22744
22888
|
}
|
|
22745
22889
|
}
|
|
@@ -22810,7 +22954,7 @@ const prepareStagingProject = (input) => Effect.gen(function* () {
|
|
|
22810
22954
|
})
|
|
22811
22955
|
});
|
|
22812
22956
|
yield* initGitRepo(stagingRoot);
|
|
22813
|
-
if (yield* fs.exists(path.join(workspaceRoot, "package.json")).pipe(Effect.
|
|
22957
|
+
if (yield* fs.exists(path.join(workspaceRoot, "package.json")).pipe(Effect.orElseSucceed(() => false))) yield* runInstall({
|
|
22814
22958
|
stagingRoot,
|
|
22815
22959
|
packageManager,
|
|
22816
22960
|
env: yield* runtime.commandEnvironment(input.envVars)
|
|
@@ -22827,7 +22971,7 @@ const prepareStagingProject = (input) => Effect.gen(function* () {
|
|
|
22827
22971
|
//#endregion
|
|
22828
22972
|
//#region src/lib/repo-clean.ts
|
|
22829
22973
|
const MAX_FILES_SHOWN = 10;
|
|
22830
|
-
const readPorcelain = (projectRoot) => Command.make("git", "status", "--porcelain").pipe(Command.workingDirectory(projectRoot), Command.string, Effect.map((output) => output.split(/\r?\n/u).map((line) => line.trim()).filter((line) => line.length > 0)), Effect.
|
|
22974
|
+
const readPorcelain = (projectRoot) => Command.make("git", "status", "--porcelain").pipe(Command.workingDirectory(projectRoot), Command.string, Effect.map((output) => output.split(/\r?\n/u).map((line) => line.trim()).filter((line) => line.length > 0)), Effect.orElseSucceed(() => []));
|
|
22831
22975
|
/**
|
|
22832
22976
|
* Refuse to proceed when the working tree has uncommitted changes. Skipped when
|
|
22833
22977
|
* `allowDirty` is true. In interactive mode, prompts the user to confirm; in
|
|
@@ -22840,11 +22984,8 @@ const ensureRepoClean = ({ projectRoot, allowDirty, label }) => Effect.gen(funct
|
|
|
22840
22984
|
const preview = dirty.slice(0, MAX_FILES_SHOWN).join("\n ");
|
|
22841
22985
|
const overflow = dirty.length > MAX_FILES_SHOWN ? `\n ... and ${String(dirty.length - MAX_FILES_SHOWN)} more` : "";
|
|
22842
22986
|
yield* Console.error(`Uncommitted changes (${String(dirty.length)} file(s)):\n ${preview}${overflow}`);
|
|
22843
|
-
if (!(yield* InteractiveMode).allow) {
|
|
22844
|
-
|
|
22845
|
-
return;
|
|
22846
|
-
}
|
|
22847
|
-
if (!(yield* promptConfirm(`Continue ${label} with uncommitted changes?`, { initialValue: false }))) yield* new DirtyRepoError({ message: `${label} cancelled by user.` });
|
|
22987
|
+
if (!(yield* InteractiveMode).allow) return yield* new DirtyRepoError({ message: `Refusing to ${label} with a dirty working tree. Commit your changes or pass --allow-dirty.` });
|
|
22988
|
+
if (!(yield* promptConfirm(`Continue ${label} with uncommitted changes?`, { initialValue: false }))) return yield* new DirtyRepoError({ message: `${label} cancelled by user.` });
|
|
22848
22989
|
});
|
|
22849
22990
|
|
|
22850
22991
|
//#endregion
|
|
@@ -22984,7 +23125,7 @@ const postTokenRequest = (tokenUri, jwt) => Effect.tryPromise({
|
|
|
22984
23125
|
});
|
|
22985
23126
|
const exchangeJwtForAccessToken = (tokenUri, jwt) => Effect.gen(function* () {
|
|
22986
23127
|
const result = yield* postTokenRequest(tokenUri, jwt);
|
|
22987
|
-
if (!result.ok) return yield*
|
|
23128
|
+
if (!result.ok) return yield* new GooglePlayAuthError({ message: `OAuth token exchange failed: ${String(result.status)} ${result.text}` });
|
|
22988
23129
|
const json = yield* Effect.try({
|
|
22989
23130
|
try: () => JSON.parse(result.text),
|
|
22990
23131
|
catch: (cause) => new GooglePlayAuthError({
|
|
@@ -23009,7 +23150,7 @@ const acquireGooglePlayAccessToken = (serviceAccountJson) => Effect.gen(function
|
|
|
23009
23150
|
message: "Service account JSON missing required fields (type, client_email, private_key)",
|
|
23010
23151
|
cause
|
|
23011
23152
|
})));
|
|
23012
|
-
if (parsed.type !== "service_account") return yield*
|
|
23153
|
+
if (parsed.type !== "service_account") return yield* new GooglePlayAuthError({ message: `Service account JSON has wrong type: ${parsed.type}` });
|
|
23013
23154
|
const tokenUri = parsed.token_uri ?? GOOGLE_OAUTH_TOKEN_URL;
|
|
23014
23155
|
return {
|
|
23015
23156
|
accessToken: yield* exchangeJwtForAccessToken(tokenUri, yield* signJwt(yield* importPrivateKey(parsed.private_key), buildJwtAssertion({
|
|
@@ -23049,10 +23190,10 @@ const performFetch = (params) => Effect.tryPromise({
|
|
|
23049
23190
|
});
|
|
23050
23191
|
const callJsonRaw = (params) => Effect.gen(function* () {
|
|
23051
23192
|
const result = yield* performFetch(params);
|
|
23052
|
-
if (!result.ok) return yield*
|
|
23193
|
+
if (!result.ok) return yield* new GooglePlayApiError({
|
|
23053
23194
|
message: `${params.label} failed: ${String(result.status)} ${result.text}`,
|
|
23054
23195
|
httpStatus: result.status
|
|
23055
|
-
})
|
|
23196
|
+
});
|
|
23056
23197
|
return yield* Effect.try({
|
|
23057
23198
|
try: () => result.text === "" ? {} : JSON.parse(result.text),
|
|
23058
23199
|
catch: (cause) => new GooglePlayApiError({
|
|
@@ -23107,10 +23248,10 @@ const performBundleUpload = (params) => Effect.tryPromise({
|
|
|
23107
23248
|
});
|
|
23108
23249
|
const uploadBundle = (params) => Effect.gen(function* () {
|
|
23109
23250
|
const result = yield* performBundleUpload(params);
|
|
23110
|
-
if (!result.ok) return yield*
|
|
23251
|
+
if (!result.ok) return yield* new GooglePlayApiError({
|
|
23111
23252
|
message: `edits.bundles.upload failed: ${String(result.status)} ${result.text}`,
|
|
23112
23253
|
httpStatus: result.status
|
|
23113
|
-
})
|
|
23254
|
+
});
|
|
23114
23255
|
const raw = yield* Effect.try({
|
|
23115
23256
|
try: () => JSON.parse(result.text),
|
|
23116
23257
|
catch: (cause) => new GooglePlayApiError({
|
|
@@ -23301,10 +23442,10 @@ const fetchArchiveOverHttp = (url) => Effect.gen(function* () {
|
|
|
23301
23442
|
message: `Failed to download AAB from ${url}: ${cause instanceof Error ? cause.message : String(cause)}`
|
|
23302
23443
|
})
|
|
23303
23444
|
});
|
|
23304
|
-
if (!result.ok || result.bytes === null) return yield*
|
|
23445
|
+
if (!result.ok || result.bytes === null) return yield* new CliSubmitError({
|
|
23305
23446
|
code: "SUBMISSION_ARCHIVE_DOWNLOAD_FAILED",
|
|
23306
23447
|
message: `HTTP ${String(result.status)} fetching archive at ${url}`
|
|
23307
|
-
})
|
|
23448
|
+
});
|
|
23308
23449
|
return result.bytes;
|
|
23309
23450
|
});
|
|
23310
23451
|
const readArchiveBytes = (archive) => archive.source === "path" ? Effect.map(readLocalFile(archive.value, "SUBMISSION_ARCHIVE_READ_FAILED", (cause) => `Failed to read AAB at ${archive.value}: ${cause instanceof Error ? cause.message : String(cause)}`), (buf) => new Uint8Array(buf)) : fetchArchiveOverHttp(archive.value);
|
|
@@ -23379,10 +23520,10 @@ const runGooglePlayPipeline = (params) => Effect.gen(function* () {
|
|
|
23379
23520
|
});
|
|
23380
23521
|
const runAndroidGooglePlayUpload = (inputs) => Effect.gen(function* () {
|
|
23381
23522
|
const { applicationId } = inputs.androidProfile;
|
|
23382
|
-
if (applicationId === void 0) return yield*
|
|
23523
|
+
if (applicationId === void 0) return yield* new CliSubmitError({
|
|
23383
23524
|
code: "SUBMISSION_ANDROID_APP_ID_MISSING",
|
|
23384
23525
|
message: "Android submit profile requires applicationId — set submit.<profile>.android.applicationId in eas.json"
|
|
23385
|
-
})
|
|
23526
|
+
});
|
|
23386
23527
|
const serviceAccountJson = yield* resolveServiceAccountJson({
|
|
23387
23528
|
api: inputs.api,
|
|
23388
23529
|
serviceAccountKeyId: inputs.serviceAccountKeyId,
|
|
@@ -23407,7 +23548,7 @@ const runAndroidGooglePlayUpload = (inputs) => Effect.gen(function* () {
|
|
|
23407
23548
|
errorCode: engineError.code,
|
|
23408
23549
|
errorMessage: engineError.message
|
|
23409
23550
|
});
|
|
23410
|
-
return yield*
|
|
23551
|
+
return yield* engineError;
|
|
23411
23552
|
})));
|
|
23412
23553
|
yield* patchSubmissionStatus(inputs.api, inputs.submissionId, { status: "FINISHED" });
|
|
23413
23554
|
yield* printHuman(`Google Play bundle uploaded (versionCode ${String(result.versionCode)})`);
|
|
@@ -23824,7 +23965,7 @@ const runBuildWorkflow = (options) => Effect.scoped(Effect.gen(function* () {
|
|
|
23824
23965
|
commit: rawGitContext.commit,
|
|
23825
23966
|
dirty: rawGitContext.dirty
|
|
23826
23967
|
});
|
|
23827
|
-
const fingerprintHash = isExpo ? yield* runFingerprintForPlatform(userCwd, platform).pipe(Effect.map((entry) => entry.hash), Effect.
|
|
23968
|
+
const fingerprintHash = isExpo ? yield* runFingerprintForPlatform(userCwd, platform).pipe(Effect.map((entry) => entry.hash), Effect.orElseSucceed(() => void 0)) : void 0;
|
|
23828
23969
|
const result = yield* reserveAndUpload(api, {
|
|
23829
23970
|
target,
|
|
23830
23971
|
projectId,
|
|
@@ -24123,7 +24264,7 @@ const compatibilityMatrixCommand = defineCommand({
|
|
|
24123
24264
|
|
|
24124
24265
|
//#endregion
|
|
24125
24266
|
//#region src/commands/builds/delete.ts
|
|
24126
|
-
const deleteCommand$
|
|
24267
|
+
const deleteCommand$6 = defineCommand({
|
|
24127
24268
|
meta: {
|
|
24128
24269
|
name: "delete",
|
|
24129
24270
|
description: "Delete a build"
|
|
@@ -24179,10 +24320,7 @@ const downloadCommand$1 = defineCommand({
|
|
|
24179
24320
|
const fs = yield* FileSystem.FileSystem;
|
|
24180
24321
|
const cwd = yield* (yield* CliRuntime).cwd;
|
|
24181
24322
|
const { artifact } = yield* api.builds.get({ path: { id: args.id } });
|
|
24182
|
-
if (!artifact) {
|
|
24183
|
-
yield* Effect.fail(new UploadFailedError({ message: `Build ${args.id} has no artifact yet.` }));
|
|
24184
|
-
return;
|
|
24185
|
-
}
|
|
24323
|
+
if (!artifact) return yield* new UploadFailedError({ message: `Build ${args.id} has no artifact yet.` });
|
|
24186
24324
|
const link = yield* api.builds.getInstallLink({ path: { id: args.id } });
|
|
24187
24325
|
const ext = artifact.format;
|
|
24188
24326
|
const outputPath = args.output ?? path.join(cwd, `${args.id}.${ext}`);
|
|
@@ -24272,7 +24410,7 @@ const DISTRIBUTION_OPTIONS$1 = [
|
|
|
24272
24410
|
"play-store",
|
|
24273
24411
|
"direct"
|
|
24274
24412
|
];
|
|
24275
|
-
const listCommand$
|
|
24413
|
+
const listCommand$10 = defineCommand({
|
|
24276
24414
|
meta: {
|
|
24277
24415
|
name: "list",
|
|
24278
24416
|
description: "List builds for the linked project"
|
|
@@ -24455,7 +24593,7 @@ const runInherit = (step, bin, ...args) => Command.exitCode(Command.make(bin, ..
|
|
|
24455
24593
|
* Locate a tool on PATH. Returns the absolute path or fails with NativeRunError.
|
|
24456
24594
|
*/
|
|
24457
24595
|
const which = (bin) => Effect.gen(function* () {
|
|
24458
|
-
const trimmed = (yield* Command.string(Command.make("which", bin)).pipe(Effect.
|
|
24596
|
+
const trimmed = (yield* Command.string(Command.make("which", bin)).pipe(Effect.mapError(() => new NativeRunError({ message: `${bin} not found in PATH` })))).trim();
|
|
24459
24597
|
if (trimmed === "") return yield* new NativeRunError({ message: `${bin} not found in PATH` });
|
|
24460
24598
|
return trimmed;
|
|
24461
24599
|
});
|
|
@@ -24474,7 +24612,7 @@ const findAppBundle = (root) => Effect.gen(function* () {
|
|
|
24474
24612
|
const fs = yield* FileSystem.FileSystem;
|
|
24475
24613
|
const candidates = [root, path.join(root, "Payload")];
|
|
24476
24614
|
for (const candidate of candidates) {
|
|
24477
|
-
const app = (yield* fs.readDirectory(candidate).pipe(Effect.
|
|
24615
|
+
const app = (yield* fs.readDirectory(candidate).pipe(Effect.orElseSucceed(() => []))).find((entry) => entry.endsWith(".app"));
|
|
24478
24616
|
if (app) return path.join(candidate, app);
|
|
24479
24617
|
}
|
|
24480
24618
|
return yield* new NativeRunError({ message: `No .app bundle found inside ${root} or ${path.join(root, "Payload")}.` });
|
|
@@ -24553,8 +24691,8 @@ const installAndLaunchIosDevice = (params) => Effect.gen(function* () {
|
|
|
24553
24691
|
* fall back to a CLI flag.
|
|
24554
24692
|
*/
|
|
24555
24693
|
const tryReadApkPackageWith = (bin, apkPath) => Effect.gen(function* () {
|
|
24556
|
-
if (!(yield* which(bin).pipe(Effect.
|
|
24557
|
-
const raw = yield* execCapture(`${bin} dump`, bin, "dump", "badging", apkPath).pipe(Effect.
|
|
24694
|
+
if (!(yield* which(bin).pipe(Effect.orElseSucceed(() => null)))) return;
|
|
24695
|
+
const raw = yield* execCapture(`${bin} dump`, bin, "dump", "badging", apkPath).pipe(Effect.orElseSucceed(() => null));
|
|
24558
24696
|
if (!raw) return;
|
|
24559
24697
|
return /package: name='(?<packageName>[^']+)'/u.exec(raw)?.[1];
|
|
24560
24698
|
});
|
|
@@ -24626,10 +24764,7 @@ const runIosSimulator = (params) => Effect.gen(function* () {
|
|
|
24626
24764
|
});
|
|
24627
24765
|
const runIosDevice = (params) => Effect.gen(function* () {
|
|
24628
24766
|
const { deviceSelector } = params;
|
|
24629
|
-
if (deviceSelector === void 0) {
|
|
24630
|
-
yield* Effect.fail(new InvalidArgumentError({ message: "Pass --device-id <udid>. Run `xcrun devicectl list devices` to list connected devices." }));
|
|
24631
|
-
return;
|
|
24632
|
-
}
|
|
24767
|
+
if (deviceSelector === void 0) return yield* new InvalidArgumentError({ message: "Pass --device-id <udid>. Run `xcrun devicectl list devices` to list connected devices." });
|
|
24633
24768
|
const bundleId = yield* readBundleIdFromApp(yield* findAppBundle(yield* extractIosArtifact({
|
|
24634
24769
|
tempDir: params.tempDir,
|
|
24635
24770
|
artifactPath: params.artifactPath,
|
|
@@ -24654,21 +24789,12 @@ const runIos = (params) => {
|
|
|
24654
24789
|
return Effect.fail(new NativeRunError({ message: `Cannot install ${params.format} on iOS; only tar.gz (simulator) or ipa are supported.` }));
|
|
24655
24790
|
};
|
|
24656
24791
|
const runAndroid = (params) => Effect.gen(function* () {
|
|
24657
|
-
if (params.format === "aab") {
|
|
24658
|
-
|
|
24659
|
-
return;
|
|
24660
|
-
}
|
|
24661
|
-
if (params.format !== "apk") {
|
|
24662
|
-
yield* Effect.fail(new NativeRunError({ message: `Cannot install ${params.format} on Android; only apk is supported.` }));
|
|
24663
|
-
return;
|
|
24664
|
-
}
|
|
24792
|
+
if (params.format === "aab") return yield* new InvalidArgumentError({ message: ".aab artifacts cannot be installed directly. Use bundletool to convert to apks, or download the play-store APK." });
|
|
24793
|
+
if (params.format !== "apk") return yield* new NativeRunError({ message: `Cannot install ${params.format} on Android; only apk is supported.` });
|
|
24665
24794
|
const device = yield* pickAndroidDevice(params.emulatorSelector);
|
|
24666
24795
|
const detected = yield* readApkPackageName(params.artifactPath);
|
|
24667
24796
|
const packageName = params.packageOverride ?? detected;
|
|
24668
|
-
if (!packageName) {
|
|
24669
|
-
yield* Effect.fail(new InvalidArgumentError({ message: "Could not detect APK package name (aapt/aapt2 not on PATH). Pass --package <name> explicitly." }));
|
|
24670
|
-
return;
|
|
24671
|
-
}
|
|
24797
|
+
if (!packageName) return yield* new InvalidArgumentError({ message: "Could not detect APK package name (aapt/aapt2 not on PATH). Pass --package <name> explicitly." });
|
|
24672
24798
|
yield* printHuman(`Installing on Android device ${device.serial}...`);
|
|
24673
24799
|
yield* installAndLaunchAndroid({
|
|
24674
24800
|
serial: device.serial,
|
|
@@ -24733,7 +24859,7 @@ const runCommand$1 = defineCommand({
|
|
|
24733
24859
|
projectId
|
|
24734
24860
|
});
|
|
24735
24861
|
const { artifact } = build;
|
|
24736
|
-
if (!artifact) return yield*
|
|
24862
|
+
if (!artifact) return yield* new UploadFailedError({ message: `Build ${build.id} has no artifact yet.` });
|
|
24737
24863
|
const link = yield* api.builds.getInstallLink({ path: { id: build.id } });
|
|
24738
24864
|
const tempDir = yield* acquireBuildTempDir;
|
|
24739
24865
|
const artifactPath = path.join(tempDir, `artifact.${artifact.format}`);
|
|
@@ -24828,7 +24954,7 @@ const resolveUploadMeta = (params) => Effect.gen(function* () {
|
|
|
24828
24954
|
const runUploadWorkflow = (options) => Effect.gen(function* () {
|
|
24829
24955
|
const api = yield* apiClient;
|
|
24830
24956
|
const projectRoot = yield* (yield* CliRuntime).cwd;
|
|
24831
|
-
if (!(yield* (yield* FileSystem.FileSystem).exists(options.artifactPath).pipe(Effect.orElseSucceed(() => false)))) yield* new ArtifactNotFoundError({ message: `Artifact not found at ${options.artifactPath}.` });
|
|
24957
|
+
if (!(yield* (yield* FileSystem.FileSystem).exists(options.artifactPath).pipe(Effect.orElseSucceed(() => false)))) return yield* new ArtifactNotFoundError({ message: `Artifact not found at ${options.artifactPath}.` });
|
|
24832
24958
|
const projectType = yield* detectProjectType({
|
|
24833
24959
|
projectRoot,
|
|
24834
24960
|
override: asProjectType((yield* readBetterUpdateConfig(projectRoot))?.["projectType"])
|
|
@@ -24858,7 +24984,7 @@ const runUploadWorkflow = (options) => Effect.gen(function* () {
|
|
|
24858
24984
|
commit: rawGitContext.commit,
|
|
24859
24985
|
dirty: rawGitContext.dirty
|
|
24860
24986
|
});
|
|
24861
|
-
const fingerprintHash = isExpo ? yield* runFingerprintForPlatform(projectRoot, options.platform).pipe(Effect.map((entry) => entry.hash), Effect.
|
|
24987
|
+
const fingerprintHash = isExpo ? yield* runFingerprintForPlatform(projectRoot, options.platform).pipe(Effect.map((entry) => entry.hash), Effect.orElseSucceed(() => void 0)) : void 0;
|
|
24862
24988
|
const result = yield* reserveAndUpload(api, compact({
|
|
24863
24989
|
target,
|
|
24864
24990
|
projectId,
|
|
@@ -24942,9 +25068,9 @@ const buildsCommand = defineCommand({
|
|
|
24942
25068
|
description: "Manage builds"
|
|
24943
25069
|
},
|
|
24944
25070
|
subCommands: {
|
|
24945
|
-
list: listCommand$
|
|
25071
|
+
list: listCommand$10,
|
|
24946
25072
|
get: getCommand$2,
|
|
24947
|
-
delete: deleteCommand$
|
|
25073
|
+
delete: deleteCommand$6,
|
|
24948
25074
|
download: downloadCommand$1,
|
|
24949
25075
|
run: runCommand$1,
|
|
24950
25076
|
"install-link": installLinkCommand,
|
|
@@ -24970,7 +25096,7 @@ const resolveNamedResourceId$1 = (params) => resolveNamedResourceId$2(params, (m
|
|
|
24970
25096
|
|
|
24971
25097
|
//#endregion
|
|
24972
25098
|
//#region src/commands/channels/create.ts
|
|
24973
|
-
const createCommand$
|
|
25099
|
+
const createCommand$4 = defineCommand({
|
|
24974
25100
|
meta: {
|
|
24975
25101
|
name: "create",
|
|
24976
25102
|
description: "Create a channel"
|
|
@@ -25015,7 +25141,7 @@ const createCommand$3 = defineCommand({
|
|
|
25015
25141
|
|
|
25016
25142
|
//#endregion
|
|
25017
25143
|
//#region src/commands/channels/delete.ts
|
|
25018
|
-
const deleteCommand$
|
|
25144
|
+
const deleteCommand$5 = defineCommand({
|
|
25019
25145
|
meta: {
|
|
25020
25146
|
name: "delete",
|
|
25021
25147
|
description: "Delete a channel"
|
|
@@ -25038,6 +25164,193 @@ const deleteCommand$4 = defineCommand({
|
|
|
25038
25164
|
})
|
|
25039
25165
|
});
|
|
25040
25166
|
|
|
25167
|
+
//#endregion
|
|
25168
|
+
//#region src/commands/channels/grants/helpers.ts
|
|
25169
|
+
var GrantCommandError = class extends Data.TaggedError("GrantCommandError") {};
|
|
25170
|
+
const grantErrorExtras = { GrantCommandError: 2 };
|
|
25171
|
+
|
|
25172
|
+
//#endregion
|
|
25173
|
+
//#region src/commands/channels/grants/list.ts
|
|
25174
|
+
const resolveChannel = (channels, target) => Effect.gen(function* () {
|
|
25175
|
+
const channel = channels.find((ch) => ch.id === target) ?? channels.find((ch) => ch.name === target);
|
|
25176
|
+
if (!channel) return yield* new ChannelCommandError({ message: `Channel "${target}" not found by ID or name.` });
|
|
25177
|
+
return channel;
|
|
25178
|
+
});
|
|
25179
|
+
const listCommand$9 = defineCommand({
|
|
25180
|
+
meta: {
|
|
25181
|
+
name: "list",
|
|
25182
|
+
description: "List per-member grants on a channel"
|
|
25183
|
+
},
|
|
25184
|
+
args: { channel: {
|
|
25185
|
+
type: "positional",
|
|
25186
|
+
required: true,
|
|
25187
|
+
description: "Channel ID or channel name"
|
|
25188
|
+
} },
|
|
25189
|
+
run: async ({ args }) => runEffect(Effect.gen(function* () {
|
|
25190
|
+
const projectId = yield* readProjectId;
|
|
25191
|
+
const api = yield* apiClient;
|
|
25192
|
+
const channel = yield* resolveChannel(yield* drainPages((page) => api.channels.list({ urlParams: {
|
|
25193
|
+
projectId,
|
|
25194
|
+
limit: 100,
|
|
25195
|
+
page
|
|
25196
|
+
} })), args.channel);
|
|
25197
|
+
yield* printList([
|
|
25198
|
+
"ID",
|
|
25199
|
+
"Member ID",
|
|
25200
|
+
"Effect",
|
|
25201
|
+
"Actions",
|
|
25202
|
+
"Created"
|
|
25203
|
+
], (yield* api.channelGrants.list({
|
|
25204
|
+
path: { id: channel.id },
|
|
25205
|
+
urlParams: {}
|
|
25206
|
+
})).map((grant) => [
|
|
25207
|
+
grant.id,
|
|
25208
|
+
grant.memberId,
|
|
25209
|
+
grant.effect,
|
|
25210
|
+
grant.actions.join(", "),
|
|
25211
|
+
grant.createdAt
|
|
25212
|
+
]), "No grants found for this channel.");
|
|
25213
|
+
}), { exits: {
|
|
25214
|
+
...channelErrorExtras,
|
|
25215
|
+
...grantErrorExtras
|
|
25216
|
+
} })
|
|
25217
|
+
});
|
|
25218
|
+
|
|
25219
|
+
//#endregion
|
|
25220
|
+
//#region src/commands/channels/grants/revoke.ts
|
|
25221
|
+
const revokeCommand$2 = defineCommand({
|
|
25222
|
+
meta: {
|
|
25223
|
+
name: "revoke",
|
|
25224
|
+
description: "Revoke all grants for a member on a channel"
|
|
25225
|
+
},
|
|
25226
|
+
args: {
|
|
25227
|
+
channel: {
|
|
25228
|
+
type: "positional",
|
|
25229
|
+
required: true,
|
|
25230
|
+
description: "Channel ID or channel name"
|
|
25231
|
+
},
|
|
25232
|
+
member: {
|
|
25233
|
+
type: "string",
|
|
25234
|
+
required: true,
|
|
25235
|
+
description: "Member ID whose grants to revoke"
|
|
25236
|
+
},
|
|
25237
|
+
yes: {
|
|
25238
|
+
type: "boolean",
|
|
25239
|
+
description: "Skip confirmation prompt"
|
|
25240
|
+
}
|
|
25241
|
+
},
|
|
25242
|
+
run: async ({ args }) => runEffect(Effect.gen(function* () {
|
|
25243
|
+
if (!args.yes) {
|
|
25244
|
+
if (!(yield* promptConfirm(`Revoke all grants for member ${args.member} on channel ${args.channel}?`, { initialValue: false }))) {
|
|
25245
|
+
yield* printHuman("Cancelled.");
|
|
25246
|
+
return { deleted: 0 };
|
|
25247
|
+
}
|
|
25248
|
+
}
|
|
25249
|
+
const projectId = yield* readProjectId;
|
|
25250
|
+
const api = yield* apiClient;
|
|
25251
|
+
const channels = yield* drainPages((page) => api.channels.list({ urlParams: {
|
|
25252
|
+
projectId,
|
|
25253
|
+
limit: 100,
|
|
25254
|
+
page
|
|
25255
|
+
} }));
|
|
25256
|
+
const channel = channels.find((ch) => ch.id === args.channel) ?? channels.find((ch) => ch.name === args.channel);
|
|
25257
|
+
if (!channel) return yield* new ChannelCommandError({ message: `Channel "${args.channel}" not found by ID or name.` });
|
|
25258
|
+
const result = yield* api.channelGrants.delete({ path: {
|
|
25259
|
+
id: channel.id,
|
|
25260
|
+
memberId: args.member
|
|
25261
|
+
} });
|
|
25262
|
+
yield* printHuman(`Revoked grants for member ${args.member} on channel "${channel.name}".`);
|
|
25263
|
+
return result;
|
|
25264
|
+
}), {
|
|
25265
|
+
exits: {
|
|
25266
|
+
...channelErrorExtras,
|
|
25267
|
+
...grantErrorExtras
|
|
25268
|
+
},
|
|
25269
|
+
json: "value"
|
|
25270
|
+
})
|
|
25271
|
+
});
|
|
25272
|
+
|
|
25273
|
+
//#endregion
|
|
25274
|
+
//#region src/commands/channels/grants/set.ts
|
|
25275
|
+
const setCommand$3 = defineCommand({
|
|
25276
|
+
meta: {
|
|
25277
|
+
name: "set",
|
|
25278
|
+
description: "Create or replace a member's grant on a channel"
|
|
25279
|
+
},
|
|
25280
|
+
args: {
|
|
25281
|
+
channel: {
|
|
25282
|
+
type: "positional",
|
|
25283
|
+
required: true,
|
|
25284
|
+
description: "Channel ID or channel name"
|
|
25285
|
+
},
|
|
25286
|
+
member: {
|
|
25287
|
+
type: "string",
|
|
25288
|
+
required: true,
|
|
25289
|
+
description: "Member ID to grant permissions to"
|
|
25290
|
+
},
|
|
25291
|
+
actions: {
|
|
25292
|
+
type: "string",
|
|
25293
|
+
required: true,
|
|
25294
|
+
description: "Permission action tokens in resource:action format, comma-separated (e.g. update:create,rollout:update)"
|
|
25295
|
+
},
|
|
25296
|
+
effect: {
|
|
25297
|
+
type: "string",
|
|
25298
|
+
description: "Grant effect: allow (default) or deny"
|
|
25299
|
+
}
|
|
25300
|
+
},
|
|
25301
|
+
run: async ({ args }) => runEffect(Effect.gen(function* () {
|
|
25302
|
+
const effectValue = args.effect ?? "allow";
|
|
25303
|
+
if (effectValue !== "allow" && effectValue !== "deny") return yield* new GrantCommandError({ message: `Invalid effect "${effectValue}" — must be "allow" or "deny".` });
|
|
25304
|
+
const actionTokens = args.actions.split(",").map((tok) => tok.trim()).filter((tok) => tok.length > 0);
|
|
25305
|
+
if (actionTokens.length === 0) return yield* new GrantCommandError({ message: "At least one action token is required." });
|
|
25306
|
+
const projectId = yield* readProjectId;
|
|
25307
|
+
const api = yield* apiClient;
|
|
25308
|
+
const channels = yield* drainPages((page) => api.channels.list({ urlParams: {
|
|
25309
|
+
projectId,
|
|
25310
|
+
limit: 100,
|
|
25311
|
+
page
|
|
25312
|
+
} }));
|
|
25313
|
+
const channel = channels.find((ch) => ch.id === args.channel) ?? channels.find((ch) => ch.name === args.channel);
|
|
25314
|
+
if (!channel) return yield* new ChannelCommandError({ message: `Channel "${args.channel}" not found by ID or name.` });
|
|
25315
|
+
const grant = yield* api.channelGrants.upsert({
|
|
25316
|
+
path: {
|
|
25317
|
+
id: channel.id,
|
|
25318
|
+
memberId: args.member
|
|
25319
|
+
},
|
|
25320
|
+
payload: {
|
|
25321
|
+
effect: effectValue,
|
|
25322
|
+
actions: actionTokens
|
|
25323
|
+
}
|
|
25324
|
+
});
|
|
25325
|
+
yield* printHumanKeyValue([
|
|
25326
|
+
["ID", grant.id],
|
|
25327
|
+
["Member ID", grant.memberId],
|
|
25328
|
+
["Channel ID", grant.scopeId],
|
|
25329
|
+
["Effect", grant.effect],
|
|
25330
|
+
["Actions", grant.actions.join(", ")],
|
|
25331
|
+
["Created", grant.createdAt]
|
|
25332
|
+
]);
|
|
25333
|
+
return grant;
|
|
25334
|
+
}), { exits: {
|
|
25335
|
+
...channelErrorExtras,
|
|
25336
|
+
...grantErrorExtras
|
|
25337
|
+
} })
|
|
25338
|
+
});
|
|
25339
|
+
|
|
25340
|
+
//#endregion
|
|
25341
|
+
//#region src/commands/channels/grants/index.ts
|
|
25342
|
+
const grantsCommand$1 = defineCommand({
|
|
25343
|
+
meta: {
|
|
25344
|
+
name: "grants",
|
|
25345
|
+
description: "Manage per-member permission grants on a channel"
|
|
25346
|
+
},
|
|
25347
|
+
subCommands: {
|
|
25348
|
+
list: listCommand$9,
|
|
25349
|
+
set: setCommand$3,
|
|
25350
|
+
revoke: revokeCommand$2
|
|
25351
|
+
}
|
|
25352
|
+
});
|
|
25353
|
+
|
|
25041
25354
|
//#endregion
|
|
25042
25355
|
//#region src/commands/channels/insights.ts
|
|
25043
25356
|
const insightsCommand$1 = defineCommand({
|
|
@@ -25084,7 +25397,7 @@ const insightsCommand$1 = defineCommand({
|
|
|
25084
25397
|
|
|
25085
25398
|
//#endregion
|
|
25086
25399
|
//#region src/commands/channels/list.ts
|
|
25087
|
-
const listCommand$
|
|
25400
|
+
const listCommand$8 = defineCommand({
|
|
25088
25401
|
meta: {
|
|
25089
25402
|
name: "list",
|
|
25090
25403
|
description: "List channels for the linked project"
|
|
@@ -25188,7 +25501,7 @@ const completeCommand$1 = defineCommand({
|
|
|
25188
25501
|
|
|
25189
25502
|
//#endregion
|
|
25190
25503
|
//#region src/commands/channels/rollout/create.ts
|
|
25191
|
-
const createCommand$
|
|
25504
|
+
const createCommand$3 = defineCommand({
|
|
25192
25505
|
meta: {
|
|
25193
25506
|
name: "create",
|
|
25194
25507
|
description: "Start a branch rollout on a channel"
|
|
@@ -25269,7 +25582,7 @@ const revertCommand$2 = defineCommand({
|
|
|
25269
25582
|
|
|
25270
25583
|
//#endregion
|
|
25271
25584
|
//#region src/commands/channels/rollout/update.ts
|
|
25272
|
-
const updateCommand$
|
|
25585
|
+
const updateCommand$4 = defineCommand({
|
|
25273
25586
|
meta: {
|
|
25274
25587
|
name: "update",
|
|
25275
25588
|
description: "Update the rollout percentage on a channel"
|
|
@@ -25308,8 +25621,8 @@ const rolloutCommand$1 = defineCommand({
|
|
|
25308
25621
|
description: "Manage channel branch rollouts"
|
|
25309
25622
|
},
|
|
25310
25623
|
subCommands: {
|
|
25311
|
-
create: createCommand$
|
|
25312
|
-
update: updateCommand$
|
|
25624
|
+
create: createCommand$3,
|
|
25625
|
+
update: updateCommand$4,
|
|
25313
25626
|
complete: completeCommand$1,
|
|
25314
25627
|
revert: revertCommand$2
|
|
25315
25628
|
}
|
|
@@ -25317,7 +25630,7 @@ const rolloutCommand$1 = defineCommand({
|
|
|
25317
25630
|
|
|
25318
25631
|
//#endregion
|
|
25319
25632
|
//#region src/commands/channels/update.ts
|
|
25320
|
-
const updateCommand$
|
|
25633
|
+
const updateCommand$3 = defineCommand({
|
|
25321
25634
|
meta: {
|
|
25322
25635
|
name: "update",
|
|
25323
25636
|
description: "Relink a channel to a different branch"
|
|
@@ -25360,7 +25673,7 @@ const updateCommand$2 = defineCommand({
|
|
|
25360
25673
|
|
|
25361
25674
|
//#endregion
|
|
25362
25675
|
//#region src/commands/channels/view.ts
|
|
25363
|
-
const viewCommand$
|
|
25676
|
+
const viewCommand$3 = defineCommand({
|
|
25364
25677
|
meta: {
|
|
25365
25678
|
name: "view",
|
|
25366
25679
|
description: "Show a channel by ID or name"
|
|
@@ -25383,7 +25696,7 @@ const viewCommand$2 = defineCommand({
|
|
|
25383
25696
|
page
|
|
25384
25697
|
} }))]);
|
|
25385
25698
|
const channel = channels.find((entry) => entry.id === args.target) ?? channels.find((entry) => entry.name === args.target);
|
|
25386
|
-
if (!channel) return yield*
|
|
25699
|
+
if (!channel) return yield* new ChannelCommandError({ message: `Channel "${args.target}" not found by ID or name.` });
|
|
25387
25700
|
const branchName = new Map(branches.map((branch) => [branch.id, branch.name])).get(channel.branchId) ?? channel.branchId;
|
|
25388
25701
|
yield* printHumanKeyValue([
|
|
25389
25702
|
["ID", channel.id],
|
|
@@ -25420,14 +25733,15 @@ const channelsCommand = defineCommand({
|
|
|
25420
25733
|
description: "Manage channels"
|
|
25421
25734
|
},
|
|
25422
25735
|
subCommands: {
|
|
25423
|
-
list: listCommand$
|
|
25424
|
-
view: viewCommand$
|
|
25425
|
-
create: createCommand$
|
|
25426
|
-
update: updateCommand$
|
|
25736
|
+
list: listCommand$8,
|
|
25737
|
+
view: viewCommand$3,
|
|
25738
|
+
create: createCommand$4,
|
|
25739
|
+
update: updateCommand$3,
|
|
25427
25740
|
pause: pauseCommand,
|
|
25428
25741
|
resume: resumeCommand,
|
|
25429
|
-
delete: deleteCommand$
|
|
25742
|
+
delete: deleteCommand$5,
|
|
25430
25743
|
rollout: rolloutCommand$1,
|
|
25744
|
+
grants: grantsCommand$1,
|
|
25431
25745
|
insights: insightsCommand$1
|
|
25432
25746
|
}
|
|
25433
25747
|
});
|
|
@@ -25808,7 +26122,7 @@ const announce = (heading) => Effect.gen(function* () {
|
|
|
25808
26122
|
});
|
|
25809
26123
|
const reportError = (label, cause) => Console.log(`✗ ${label}: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
25810
26124
|
const safely = (label, effect) => effect.pipe(Effect.catchAll((cause) => reportError(label, cause)), Effect.asVoid);
|
|
25811
|
-
const safePrompt = (effect) => effect.pipe(Effect.
|
|
26125
|
+
const safePrompt = (effect) => effect.pipe(Effect.orElseSucceed(() => BACK));
|
|
25812
26126
|
const promptForBundleConfig = (ctx) => Effect.gen(function* () {
|
|
25813
26127
|
const list = yield* ctx.api.iosBundleConfigurations.list({ path: { projectId: ctx.projectId } });
|
|
25814
26128
|
if (list.items.length === 0) return yield* new MissingCredentialsError({
|
|
@@ -26841,7 +27155,7 @@ const toRecipientView = (userEncryptionKeyId, key) => ({
|
|
|
26841
27155
|
fingerprint: key?.fingerprint
|
|
26842
27156
|
})
|
|
26843
27157
|
});
|
|
26844
|
-
const listCommand$
|
|
27158
|
+
const listCommand$7 = defineCommand({
|
|
26845
27159
|
meta: {
|
|
26846
27160
|
name: "list",
|
|
26847
27161
|
description: "List recipients that currently hold the org vault key"
|
|
@@ -27068,7 +27382,7 @@ const accessCommand = defineCommand({
|
|
|
27068
27382
|
description: "Inspect, grant, rotate, revoke, and recover access to the org credential vault"
|
|
27069
27383
|
},
|
|
27070
27384
|
subCommands: {
|
|
27071
|
-
list: listCommand$
|
|
27385
|
+
list: listCommand$7,
|
|
27072
27386
|
grant: grantCommand,
|
|
27073
27387
|
rotate: rotateCommand,
|
|
27074
27388
|
revoke: revokeCommand$1,
|
|
@@ -27277,7 +27591,7 @@ const CREDENTIAL_TYPES$3 = [
|
|
|
27277
27591
|
"keystore",
|
|
27278
27592
|
"google-service-account-key"
|
|
27279
27593
|
];
|
|
27280
|
-
const deleteCommand$
|
|
27594
|
+
const deleteCommand$4 = defineCommand({
|
|
27281
27595
|
meta: {
|
|
27282
27596
|
name: "delete",
|
|
27283
27597
|
description: "Delete a credential"
|
|
@@ -27319,7 +27633,7 @@ const deleteCommand$3 = defineCommand({
|
|
|
27319
27633
|
//#region src/commands/credentials/device.ts
|
|
27320
27634
|
/** Self-linking is for your own device keys; recovery/machine keys go through `access grant`. */
|
|
27321
27635
|
const requireDeviceKind = (target) => target.kind === "device" ? Effect.void : new IdentityError({ message: `Key ${target.id} is a ${target.kind} key, not a device. Use \`better-update credentials access grant\` for recovery/machine keys.` });
|
|
27322
|
-
const listCommand$
|
|
27636
|
+
const listCommand$6 = defineCommand({
|
|
27323
27637
|
meta: {
|
|
27324
27638
|
name: "list",
|
|
27325
27639
|
description: "List your registered device keys (the active one is marked)"
|
|
@@ -27377,7 +27691,7 @@ const deviceCommand = defineCommand({
|
|
|
27377
27691
|
description: "Manage your vault device keys"
|
|
27378
27692
|
},
|
|
27379
27693
|
subCommands: {
|
|
27380
|
-
list: listCommand$
|
|
27694
|
+
list: listCommand$6,
|
|
27381
27695
|
link: linkCommand
|
|
27382
27696
|
},
|
|
27383
27697
|
default: "list"
|
|
@@ -27739,7 +28053,7 @@ const handleCertLimitInteractive = (api, ascApiKeyId, certificateType) => Effect
|
|
|
27739
28053
|
ascApiKeyId,
|
|
27740
28054
|
certificateType
|
|
27741
28055
|
});
|
|
27742
|
-
if (certs.length === 0) return yield*
|
|
28056
|
+
if (certs.length === 0) return yield* new CertificateLimitError({ message: "Apple says the certificate limit is hit but no existing certificates were returned — try again later." });
|
|
27743
28057
|
const toRevoke = yield* promptMultiSelect("Select one or more certificates to revoke before retrying", certs.map((entry) => ({
|
|
27744
28058
|
value: entry.id,
|
|
27745
28059
|
label: `${entry.serialNumber.slice(0, 12)}… (${entry.displayName ?? entry.certificateType}, exp ${entry.expirationDate.slice(0, 10)})`
|
|
@@ -28032,7 +28346,7 @@ const printRecipient = (key) => printKeyValue([
|
|
|
28032
28346
|
["Recipient (public key)", key.publicKey],
|
|
28033
28347
|
["Fingerprint", key.fingerprint]
|
|
28034
28348
|
]);
|
|
28035
|
-
const createCommand$
|
|
28349
|
+
const createCommand$2 = defineCommand({
|
|
28036
28350
|
meta: {
|
|
28037
28351
|
name: "create",
|
|
28038
28352
|
description: "Create this device's encryption identity and register it as a recipient"
|
|
@@ -28135,7 +28449,7 @@ const identityCommand = defineCommand({
|
|
|
28135
28449
|
description: "Manage this device's end-to-end encryption identity"
|
|
28136
28450
|
},
|
|
28137
28451
|
subCommands: {
|
|
28138
|
-
create: createCommand$
|
|
28452
|
+
create: createCommand$2,
|
|
28139
28453
|
init: initCommand$1,
|
|
28140
28454
|
register: registerCommand,
|
|
28141
28455
|
show: showCommand
|
|
@@ -28145,7 +28459,7 @@ const identityCommand = defineCommand({
|
|
|
28145
28459
|
|
|
28146
28460
|
//#endregion
|
|
28147
28461
|
//#region src/commands/credentials/list.ts
|
|
28148
|
-
const listCommand$
|
|
28462
|
+
const listCommand$5 = defineCommand({
|
|
28149
28463
|
meta: {
|
|
28150
28464
|
name: "list",
|
|
28151
28465
|
description: "List credentials across platforms"
|
|
@@ -28501,7 +28815,7 @@ const writeArtifact = (fs, projectRoot, relPath, bytes) => Effect.gen(function*
|
|
|
28501
28815
|
const writeText = (fs, projectRoot, relPath, text) => writeArtifact(fs, projectRoot, relPath, new TextEncoder().encode(text));
|
|
28502
28816
|
const ensureGitignoreEntries = (fs, projectRoot, paths) => Effect.gen(function* () {
|
|
28503
28817
|
const filePath = path.join(projectRoot, ".gitignore");
|
|
28504
|
-
const lines = ((yield* fs.exists(filePath).pipe(Effect.
|
|
28818
|
+
const lines = ((yield* fs.exists(filePath).pipe(Effect.orElseSucceed(() => false))) ? yield* fs.readFileString(filePath).pipe(Effect.orElseSucceed(() => "")) : "").split("\n");
|
|
28505
28819
|
const added = [];
|
|
28506
28820
|
const next = [...lines];
|
|
28507
28821
|
for (const entry of paths) if (!lines.includes(entry)) {
|
|
@@ -29300,7 +29614,7 @@ const lookupByType = (api, id, type) => {
|
|
|
29300
29614
|
default: return Effect.fail(new CredentialValidationError({ message: `Unsupported credential type: ${String(type)}` }));
|
|
29301
29615
|
}
|
|
29302
29616
|
};
|
|
29303
|
-
const viewCommand$
|
|
29617
|
+
const viewCommand$2 = defineCommand({
|
|
29304
29618
|
meta: {
|
|
29305
29619
|
name: "view",
|
|
29306
29620
|
description: "Show details for a single credential (without secrets)"
|
|
@@ -29347,14 +29661,14 @@ const credentialsCommand = defineCommand({
|
|
|
29347
29661
|
unlock: unlockCommand,
|
|
29348
29662
|
lock: lockCommand,
|
|
29349
29663
|
status: statusCommand$1,
|
|
29350
|
-
list: listCommand$
|
|
29351
|
-
view: viewCommand$
|
|
29664
|
+
list: listCommand$5,
|
|
29665
|
+
view: viewCommand$2,
|
|
29352
29666
|
download: downloadCommand,
|
|
29353
29667
|
upload: uploadCommand,
|
|
29354
29668
|
"upload-asc-key": uploadAscKeyCommand,
|
|
29355
29669
|
generate: generateCommand$1,
|
|
29356
29670
|
"regenerate-profile": regenerateProfileCommand,
|
|
29357
|
-
delete: deleteCommand$
|
|
29671
|
+
delete: deleteCommand$4,
|
|
29358
29672
|
remove: removeCommand,
|
|
29359
29673
|
revoke: revokeCommand,
|
|
29360
29674
|
configure: configureCommand$1,
|
|
@@ -29721,6 +30035,7 @@ const devicesCommand = defineCommand({
|
|
|
29721
30035
|
|
|
29722
30036
|
//#endregion
|
|
29723
30037
|
//#region src/commands/doctor.ts
|
|
30038
|
+
var HealthCheckError = class extends Data.TaggedError("HealthCheckError") {};
|
|
29724
30039
|
const pass = (id, name, message) => ({
|
|
29725
30040
|
id,
|
|
29726
30041
|
name,
|
|
@@ -29759,7 +30074,10 @@ const checkServerHealth = Effect.gen(function* () {
|
|
|
29759
30074
|
const url = `${yield* (yield* ConfigStore).getBaseUrl}/api/health`;
|
|
29760
30075
|
const response = yield* Effect.tryPromise({
|
|
29761
30076
|
try: async () => fetch(url, { signal: AbortSignal.timeout(3e3) }),
|
|
29762
|
-
catch: (cause) => new
|
|
30077
|
+
catch: (cause) => new HealthCheckError({
|
|
30078
|
+
message: String(cause),
|
|
30079
|
+
cause
|
|
30080
|
+
})
|
|
29763
30081
|
}).pipe(Effect.either);
|
|
29764
30082
|
if (response._tag === "Left") return fail("health", "Server reachable", `${url} unreachable: ${response.left.message}`);
|
|
29765
30083
|
const res = response.right;
|
|
@@ -29850,19 +30168,19 @@ const envErrorExtras = {
|
|
|
29850
30168
|
SystemError: 6,
|
|
29851
30169
|
BadArgument: 6
|
|
29852
30170
|
};
|
|
29853
|
-
const isEnvironmentName = (value) => value === "development" || value === "preview" || value === "production";
|
|
30171
|
+
const isEnvironmentName$1 = (value) => value === "development" || value === "preview" || value === "production";
|
|
29854
30172
|
const parseEnvironmentsArg = (raw) => Effect.gen(function* () {
|
|
29855
30173
|
const tokens = raw.split(",").map((token) => token.trim()).filter((token) => token.length > 0);
|
|
29856
30174
|
if (tokens.length === 0) return yield* new InvalidArgumentError({ message: "Provide at least one environment (development, preview, production)." });
|
|
29857
30175
|
const seen = /* @__PURE__ */ new Set();
|
|
29858
30176
|
yield* Effect.forEach(tokens, (token) => Effect.gen(function* () {
|
|
29859
|
-
if (!isEnvironmentName(token)) return yield* new InvalidArgumentError({ message: `Invalid environment "${token}". Must be one of: development, preview, production.` });
|
|
30177
|
+
if (!isEnvironmentName$1(token)) return yield* new InvalidArgumentError({ message: `Invalid environment "${token}". Must be one of: development, preview, production.` });
|
|
29860
30178
|
seen.add(token);
|
|
29861
30179
|
}), { discard: true });
|
|
29862
30180
|
return [...seen];
|
|
29863
30181
|
});
|
|
29864
30182
|
const parseSingleEnvironmentArg = (raw) => Effect.gen(function* () {
|
|
29865
|
-
if (!isEnvironmentName(raw)) return yield* new InvalidArgumentError({ message: `Invalid environment "${raw}". Must be one of: development, preview, production.` });
|
|
30183
|
+
if (!isEnvironmentName$1(raw)) return yield* new InvalidArgumentError({ message: `Invalid environment "${raw}". Must be one of: development, preview, production.` });
|
|
29866
30184
|
return raw;
|
|
29867
30185
|
});
|
|
29868
30186
|
const formatEnvironments = (environments) => [...environments].toSorted((left, right) => left.localeCompare(right)).join(",");
|
|
@@ -29901,7 +30219,7 @@ const findProjectEnvVar = (api, projectId, key, environment) => Effect.gen(funct
|
|
|
29901
30219
|
|
|
29902
30220
|
//#endregion
|
|
29903
30221
|
//#region src/commands/env/delete.ts
|
|
29904
|
-
const deleteCommand$
|
|
30222
|
+
const deleteCommand$3 = defineCommand({
|
|
29905
30223
|
meta: {
|
|
29906
30224
|
name: "delete",
|
|
29907
30225
|
description: "Delete a project env var (one environment, or every environment by default)"
|
|
@@ -29970,7 +30288,7 @@ const getExecTrailingArgv = () => trailing$1;
|
|
|
29970
30288
|
const pullForExec = (api, projectId, environment) => pullEnvVars(api, {
|
|
29971
30289
|
projectId,
|
|
29972
30290
|
environment
|
|
29973
|
-
}).pipe(Effect.
|
|
30291
|
+
}).pipe(Effect.orElseSucceed(() => ({})));
|
|
29974
30292
|
const splitTrailing = (trailing) => {
|
|
29975
30293
|
if (!trailing || trailing.length === 0) return Effect.fail(new InvalidArgumentError({ message: "Pass the command after `--`. Example: `better-update env exec production -- bun run dev`." }));
|
|
29976
30294
|
const [bin, ...rest] = trailing;
|
|
@@ -30073,6 +30391,191 @@ const getCommand$1 = defineCommand({
|
|
|
30073
30391
|
}), envErrorExtras)
|
|
30074
30392
|
});
|
|
30075
30393
|
|
|
30394
|
+
//#endregion
|
|
30395
|
+
//#region src/commands/env/grants/helpers.ts
|
|
30396
|
+
var EnvGrantCommandError = class extends Data.TaggedError("EnvGrantCommandError") {};
|
|
30397
|
+
const envGrantErrorExtras = { EnvGrantCommandError: 2 };
|
|
30398
|
+
/** Sentinel project token for the org-global env-var scope (mirrors server). */
|
|
30399
|
+
const ENV_GRANT_GLOBAL = "global";
|
|
30400
|
+
const ENVIRONMENTS = [
|
|
30401
|
+
"development",
|
|
30402
|
+
"preview",
|
|
30403
|
+
"production"
|
|
30404
|
+
];
|
|
30405
|
+
/**
|
|
30406
|
+
* Type guard narrowing a raw arg to an {@link EnvironmentName}. Lets set/unset
|
|
30407
|
+
* validate `args.environment` AND narrow it without an unsafe `as` assertion.
|
|
30408
|
+
*/
|
|
30409
|
+
const isEnvironmentName = (value) => ENVIRONMENTS.includes(value);
|
|
30410
|
+
|
|
30411
|
+
//#endregion
|
|
30412
|
+
//#region src/commands/env/grants/list.ts
|
|
30413
|
+
const listCommand$4 = defineCommand({
|
|
30414
|
+
meta: {
|
|
30415
|
+
name: "list",
|
|
30416
|
+
description: "List env-var grants on a (project × environment) scope"
|
|
30417
|
+
},
|
|
30418
|
+
args: {
|
|
30419
|
+
project: {
|
|
30420
|
+
type: "string",
|
|
30421
|
+
description: `Project id, or "${ENV_GRANT_GLOBAL}" for the org-global scope (default: linked project)`
|
|
30422
|
+
},
|
|
30423
|
+
global: {
|
|
30424
|
+
type: "boolean",
|
|
30425
|
+
default: false,
|
|
30426
|
+
description: "Target the org-global env-var scope instead of a project"
|
|
30427
|
+
}
|
|
30428
|
+
},
|
|
30429
|
+
run: async ({ args }) => runEffect(Effect.gen(function* () {
|
|
30430
|
+
const projectId = args.global ? ENV_GRANT_GLOBAL : args.project ?? (yield* readProjectId);
|
|
30431
|
+
yield* printList([
|
|
30432
|
+
"Member ID",
|
|
30433
|
+
"Environment",
|
|
30434
|
+
"Effect",
|
|
30435
|
+
"Actions"
|
|
30436
|
+
], (yield* (yield* apiClient).envGrants.list({ urlParams: { projectId } })).map((row) => [
|
|
30437
|
+
row.memberId,
|
|
30438
|
+
row.environment,
|
|
30439
|
+
row.effect,
|
|
30440
|
+
row.actions.join(", ")
|
|
30441
|
+
]), "No env-var grants found for this scope.");
|
|
30442
|
+
}), { exits: { ...envGrantErrorExtras } })
|
|
30443
|
+
});
|
|
30444
|
+
|
|
30445
|
+
//#endregion
|
|
30446
|
+
//#region src/commands/env/grants/set.ts
|
|
30447
|
+
const setCommand$2 = defineCommand({
|
|
30448
|
+
meta: {
|
|
30449
|
+
name: "set",
|
|
30450
|
+
description: "Create or replace a member's env-var grant on a scope"
|
|
30451
|
+
},
|
|
30452
|
+
args: {
|
|
30453
|
+
member: {
|
|
30454
|
+
type: "string",
|
|
30455
|
+
required: true,
|
|
30456
|
+
description: "Member ID to grant"
|
|
30457
|
+
},
|
|
30458
|
+
environment: {
|
|
30459
|
+
type: "string",
|
|
30460
|
+
required: true,
|
|
30461
|
+
description: "Environment: development | preview | production"
|
|
30462
|
+
},
|
|
30463
|
+
actions: {
|
|
30464
|
+
type: "string",
|
|
30465
|
+
description: "Comma-separated envVar:* tokens (default: envVar:read)"
|
|
30466
|
+
},
|
|
30467
|
+
effect: {
|
|
30468
|
+
type: "string",
|
|
30469
|
+
description: "allow (default) or deny"
|
|
30470
|
+
},
|
|
30471
|
+
project: {
|
|
30472
|
+
type: "string",
|
|
30473
|
+
description: `Project id (default: linked project)`
|
|
30474
|
+
},
|
|
30475
|
+
global: {
|
|
30476
|
+
type: "boolean",
|
|
30477
|
+
default: false,
|
|
30478
|
+
description: "Target the org-global env-var scope"
|
|
30479
|
+
}
|
|
30480
|
+
},
|
|
30481
|
+
run: async ({ args }) => runEffect(Effect.gen(function* () {
|
|
30482
|
+
const effectValue = args.effect ?? "allow";
|
|
30483
|
+
if (effectValue !== "allow" && effectValue !== "deny") return yield* new EnvGrantCommandError({ message: `Invalid effect "${effectValue}".` });
|
|
30484
|
+
const { environment } = args;
|
|
30485
|
+
if (!isEnvironmentName(environment)) return yield* new EnvGrantCommandError({ message: `Invalid environment "${environment}". One of: ${ENVIRONMENTS.join(", ")}.` });
|
|
30486
|
+
const actionTokens = (args.actions ?? "envVar:read").split(",").map((tok) => tok.trim()).filter((tok) => tok.length > 0);
|
|
30487
|
+
if (actionTokens.length === 0) return yield* new EnvGrantCommandError({ message: "At least one action token is required." });
|
|
30488
|
+
const projectId = args.global ? null : args.project ?? (yield* readProjectId);
|
|
30489
|
+
const grant = yield* (yield* apiClient).envGrants.upsert({ payload: {
|
|
30490
|
+
memberId: args.member,
|
|
30491
|
+
projectId,
|
|
30492
|
+
environment,
|
|
30493
|
+
effect: effectValue,
|
|
30494
|
+
actions: actionTokens
|
|
30495
|
+
} });
|
|
30496
|
+
yield* printHumanKeyValue([
|
|
30497
|
+
["ID", grant.id],
|
|
30498
|
+
["Member ID", grant.memberId],
|
|
30499
|
+
["Scope", grant.scopeId],
|
|
30500
|
+
["Effect", grant.effect],
|
|
30501
|
+
["Actions", grant.actions.join(", ")],
|
|
30502
|
+
["Created", grant.createdAt]
|
|
30503
|
+
]);
|
|
30504
|
+
return grant;
|
|
30505
|
+
}), { exits: { ...envGrantErrorExtras } })
|
|
30506
|
+
});
|
|
30507
|
+
|
|
30508
|
+
//#endregion
|
|
30509
|
+
//#region src/commands/env/grants/unset.ts
|
|
30510
|
+
const unsetCommand = defineCommand({
|
|
30511
|
+
meta: {
|
|
30512
|
+
name: "unset",
|
|
30513
|
+
description: "Revoke a member's env-var grants on a scope"
|
|
30514
|
+
},
|
|
30515
|
+
args: {
|
|
30516
|
+
member: {
|
|
30517
|
+
type: "string",
|
|
30518
|
+
required: true,
|
|
30519
|
+
description: "Member ID whose grants to revoke"
|
|
30520
|
+
},
|
|
30521
|
+
environment: {
|
|
30522
|
+
type: "string",
|
|
30523
|
+
required: true,
|
|
30524
|
+
description: "Environment"
|
|
30525
|
+
},
|
|
30526
|
+
project: {
|
|
30527
|
+
type: "string",
|
|
30528
|
+
description: "Project id (default: linked project)"
|
|
30529
|
+
},
|
|
30530
|
+
global: {
|
|
30531
|
+
type: "boolean",
|
|
30532
|
+
default: false,
|
|
30533
|
+
description: "Target the org-global scope"
|
|
30534
|
+
},
|
|
30535
|
+
yes: {
|
|
30536
|
+
type: "boolean",
|
|
30537
|
+
default: false,
|
|
30538
|
+
description: "Skip confirmation prompt"
|
|
30539
|
+
}
|
|
30540
|
+
},
|
|
30541
|
+
run: async ({ args }) => runEffect(Effect.gen(function* () {
|
|
30542
|
+
const { environment } = args;
|
|
30543
|
+
if (!isEnvironmentName(environment)) return yield* new EnvGrantCommandError({ message: `Invalid environment "${environment}".` });
|
|
30544
|
+
if (!args.yes) {
|
|
30545
|
+
const scopeLabel = args.global ? ENV_GRANT_GLOBAL : args.project ?? "linked project";
|
|
30546
|
+
if (!(yield* promptConfirm(`Revoke env-var grants for member ${args.member} on ${scopeLabel}/${args.environment}?`, { initialValue: false }))) {
|
|
30547
|
+
yield* printHuman("Cancelled.");
|
|
30548
|
+
return { deleted: 0 };
|
|
30549
|
+
}
|
|
30550
|
+
}
|
|
30551
|
+
const projectId = args.global ? null : args.project ?? (yield* readProjectId);
|
|
30552
|
+
const result = yield* (yield* apiClient).envGrants.delete({ payload: {
|
|
30553
|
+
memberId: args.member,
|
|
30554
|
+
projectId,
|
|
30555
|
+
environment
|
|
30556
|
+
} });
|
|
30557
|
+
yield* printHuman(`Revoked env-var grants for member ${args.member}.`);
|
|
30558
|
+
return result;
|
|
30559
|
+
}), {
|
|
30560
|
+
exits: { ...envGrantErrorExtras },
|
|
30561
|
+
json: "value"
|
|
30562
|
+
})
|
|
30563
|
+
});
|
|
30564
|
+
|
|
30565
|
+
//#endregion
|
|
30566
|
+
//#region src/commands/env/grants/index.ts
|
|
30567
|
+
const grantsCommand = defineCommand({
|
|
30568
|
+
meta: {
|
|
30569
|
+
name: "grants",
|
|
30570
|
+
description: "Manage per-member env-var access grants on a (project × environment) scope"
|
|
30571
|
+
},
|
|
30572
|
+
subCommands: {
|
|
30573
|
+
list: listCommand$4,
|
|
30574
|
+
set: setCommand$2,
|
|
30575
|
+
unset: unsetCommand
|
|
30576
|
+
}
|
|
30577
|
+
});
|
|
30578
|
+
|
|
30076
30579
|
//#endregion
|
|
30077
30580
|
//#region src/commands/env/history.ts
|
|
30078
30581
|
const historyCommand = defineCommand({
|
|
@@ -30171,7 +30674,7 @@ const importCommand = defineCommand({
|
|
|
30171
30674
|
|
|
30172
30675
|
//#endregion
|
|
30173
30676
|
//#region src/commands/env/list.ts
|
|
30174
|
-
const listCommand$
|
|
30677
|
+
const listCommand$3 = defineCommand({
|
|
30175
30678
|
meta: {
|
|
30176
30679
|
name: "list",
|
|
30177
30680
|
description: "List environment variable metadata. Values are end-to-end encrypted — read them with `env pull`, `env export`, or `env get`."
|
|
@@ -30437,7 +30940,7 @@ const setCommand$1 = defineCommand({
|
|
|
30437
30940
|
|
|
30438
30941
|
//#endregion
|
|
30439
30942
|
//#region src/commands/env/update.ts
|
|
30440
|
-
const updateCommand$
|
|
30943
|
+
const updateCommand$2 = defineCommand({
|
|
30441
30944
|
meta: {
|
|
30442
30945
|
name: "update",
|
|
30443
30946
|
description: "Update a project env var's value or visibility for an environment"
|
|
@@ -30518,18 +31021,19 @@ const envCommand = defineCommand({
|
|
|
30518
31021
|
description: "Manage environment variables"
|
|
30519
31022
|
},
|
|
30520
31023
|
subCommands: {
|
|
30521
|
-
list: listCommand$
|
|
31024
|
+
list: listCommand$3,
|
|
30522
31025
|
get: getCommand$1,
|
|
30523
31026
|
set: setCommand$1,
|
|
30524
|
-
update: updateCommand$
|
|
30525
|
-
delete: deleteCommand$
|
|
31027
|
+
update: updateCommand$2,
|
|
31028
|
+
delete: deleteCommand$3,
|
|
30526
31029
|
history: historyCommand,
|
|
30527
31030
|
rollback: rollbackCommand$1,
|
|
30528
31031
|
import: importCommand,
|
|
30529
31032
|
push: pushCommand,
|
|
30530
31033
|
export: exportCommand,
|
|
30531
31034
|
pull: pullCommand,
|
|
30532
|
-
exec: execCommand
|
|
31035
|
+
exec: execCommand,
|
|
31036
|
+
grants: grantsCommand
|
|
30533
31037
|
}
|
|
30534
31038
|
});
|
|
30535
31039
|
|
|
@@ -30800,7 +31304,7 @@ const fingerprintCommand = defineCommand({
|
|
|
30800
31304
|
const checkExistingLink = (api, config, localSlug) => Effect.gen(function* () {
|
|
30801
31305
|
const existingId = config.extra?.betterUpdate?.projectId;
|
|
30802
31306
|
if (typeof existingId !== "string" || existingId.length === 0) return "no-link";
|
|
30803
|
-
const project = yield* api.projects.get({ path: { id: existingId } }).pipe(Effect.
|
|
31307
|
+
const project = yield* api.projects.get({ path: { id: existingId } }).pipe(Effect.orElseSucceed(() => void 0));
|
|
30804
31308
|
if (project === void 0) {
|
|
30805
31309
|
yield* printHuman(`Existing projectId "${existingId}" not found on server. Re-linking by local slug "${localSlug}".`);
|
|
30806
31310
|
return "stale";
|
|
@@ -30820,9 +31324,9 @@ const checkExistingLink = (api, config, localSlug) => Effect.gen(function* () {
|
|
|
30820
31324
|
const slugify = (value) => value.toLowerCase().replaceAll(/[^a-z0-9]+/gu, "-").replaceAll(/^-+|-+$/gu, "");
|
|
30821
31325
|
/** Best-effort `name` from the local package.json, or undefined when absent. */
|
|
30822
31326
|
const readPackageJsonName = (projectRoot) => Effect.gen(function* () {
|
|
30823
|
-
const content = yield* (yield* FileSystem.FileSystem).readFileString(path.join(projectRoot, "package.json")).pipe(Effect.
|
|
31327
|
+
const content = yield* (yield* FileSystem.FileSystem).readFileString(path.join(projectRoot, "package.json")).pipe(Effect.orElseSucceed(() => ""));
|
|
30824
31328
|
if (content.length === 0) return;
|
|
30825
|
-
const parsed = yield* Effect.try(() => JSON.parse(content)).pipe(Effect.
|
|
31329
|
+
const parsed = yield* Effect.try(() => JSON.parse(content)).pipe(Effect.orElseSucceed(() => void 0));
|
|
30826
31330
|
const name = isRecord(parsed) ? parsed["name"] : void 0;
|
|
30827
31331
|
return typeof name === "string" && name.length > 0 ? name : void 0;
|
|
30828
31332
|
});
|
|
@@ -31066,7 +31570,7 @@ const openCommand = defineCommand({
|
|
|
31066
31570
|
|
|
31067
31571
|
//#endregion
|
|
31068
31572
|
//#region src/commands/projects.ts
|
|
31069
|
-
const listCommand$
|
|
31573
|
+
const listCommand$2 = defineCommand({
|
|
31070
31574
|
meta: {
|
|
31071
31575
|
name: "list",
|
|
31072
31576
|
description: "List projects (most recently active first)"
|
|
@@ -31117,7 +31621,7 @@ const listCommand$1 = defineCommand({
|
|
|
31117
31621
|
yield* printHuman(`Page ${result.page} · ${result.items.length} of ${result.total} project(s)`);
|
|
31118
31622
|
}))
|
|
31119
31623
|
});
|
|
31120
|
-
const createCommand = defineCommand({
|
|
31624
|
+
const createCommand$1 = defineCommand({
|
|
31121
31625
|
meta: {
|
|
31122
31626
|
name: "create",
|
|
31123
31627
|
description: "Create a new project"
|
|
@@ -31195,7 +31699,7 @@ const renameCommand = defineCommand({
|
|
|
31195
31699
|
return project;
|
|
31196
31700
|
}), { json: "value" })
|
|
31197
31701
|
});
|
|
31198
|
-
const deleteCommand$
|
|
31702
|
+
const deleteCommand$2 = defineCommand({
|
|
31199
31703
|
meta: {
|
|
31200
31704
|
name: "delete",
|
|
31201
31705
|
description: "Delete a project"
|
|
@@ -31220,10 +31724,229 @@ const projectsCommand = defineCommand({
|
|
|
31220
31724
|
description: "Manage projects"
|
|
31221
31725
|
},
|
|
31222
31726
|
subCommands: {
|
|
31223
|
-
list: listCommand$
|
|
31224
|
-
create: createCommand,
|
|
31727
|
+
list: listCommand$2,
|
|
31728
|
+
create: createCommand$1,
|
|
31225
31729
|
get: getCommand,
|
|
31226
31730
|
rename: renameCommand,
|
|
31731
|
+
delete: deleteCommand$2
|
|
31732
|
+
}
|
|
31733
|
+
});
|
|
31734
|
+
|
|
31735
|
+
//#endregion
|
|
31736
|
+
//#region src/commands/roles/helpers.ts
|
|
31737
|
+
var RoleCommandError = class extends Data.TaggedError("RoleCommandError") {};
|
|
31738
|
+
const roleErrorExtras = { RoleCommandError: 2 };
|
|
31739
|
+
/**
|
|
31740
|
+
* Parse comma-separated "resource:action" tokens into the PermissionGrant array
|
|
31741
|
+
* the API expects. Multiple actions for the same resource are grouped.
|
|
31742
|
+
*
|
|
31743
|
+
* Input examples:
|
|
31744
|
+
* "channel:read,channel:update"
|
|
31745
|
+
* "channel:read, rollout:create, rollout:update"
|
|
31746
|
+
*/
|
|
31747
|
+
const parsePermissionTokens = (raw) => Effect.gen(function* () {
|
|
31748
|
+
const tokens = raw.split(",").map((tok) => tok.trim()).filter((tok) => tok.length > 0);
|
|
31749
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
31750
|
+
for (const token of tokens) {
|
|
31751
|
+
const colonIdx = token.indexOf(":");
|
|
31752
|
+
if (colonIdx === -1) return yield* new RoleCommandError({ message: `Invalid permission token "${token}" — expected "resource:action" format.` });
|
|
31753
|
+
const resource = token.slice(0, colonIdx).trim();
|
|
31754
|
+
const action = token.slice(colonIdx + 1).trim();
|
|
31755
|
+
if (!resource || !action) return yield* new RoleCommandError({ message: `Invalid permission token "${token}" — resource and action must be non-empty.` });
|
|
31756
|
+
const actions = grouped.get(resource) ?? /* @__PURE__ */ new Set();
|
|
31757
|
+
actions.add(action);
|
|
31758
|
+
grouped.set(resource, actions);
|
|
31759
|
+
}
|
|
31760
|
+
return [...grouped.entries()].map(([resource, actions]) => ({
|
|
31761
|
+
resource,
|
|
31762
|
+
actions: [...actions]
|
|
31763
|
+
}));
|
|
31764
|
+
});
|
|
31765
|
+
|
|
31766
|
+
//#endregion
|
|
31767
|
+
//#region src/commands/roles/create.ts
|
|
31768
|
+
const createCommand = defineCommand({
|
|
31769
|
+
meta: {
|
|
31770
|
+
name: "create",
|
|
31771
|
+
description: "Create a custom role"
|
|
31772
|
+
},
|
|
31773
|
+
args: {
|
|
31774
|
+
name: {
|
|
31775
|
+
type: "string",
|
|
31776
|
+
required: true,
|
|
31777
|
+
description: "Role name (unique per organization)"
|
|
31778
|
+
},
|
|
31779
|
+
permission: {
|
|
31780
|
+
type: "string",
|
|
31781
|
+
required: true,
|
|
31782
|
+
description: "Permission tokens in resource:action format, comma-separated (e.g. channel:read,channel:update)"
|
|
31783
|
+
}
|
|
31784
|
+
},
|
|
31785
|
+
run: async ({ args }) => runEffect(Effect.gen(function* () {
|
|
31786
|
+
const permissions = yield* parsePermissionTokens(args.permission);
|
|
31787
|
+
const role = yield* (yield* apiClient).roles.create({ payload: {
|
|
31788
|
+
name: args.name,
|
|
31789
|
+
permissions
|
|
31790
|
+
} });
|
|
31791
|
+
yield* printHumanKeyValue([
|
|
31792
|
+
["ID", role.id],
|
|
31793
|
+
["Name", role.role],
|
|
31794
|
+
["Permissions", role.permissions.map((perm) => `${perm.resource}:[${perm.actions.join(",")}]`).join("; ")],
|
|
31795
|
+
["Created", role.createdAt]
|
|
31796
|
+
]);
|
|
31797
|
+
return role;
|
|
31798
|
+
}), {
|
|
31799
|
+
exits: roleErrorExtras,
|
|
31800
|
+
json: "value"
|
|
31801
|
+
})
|
|
31802
|
+
});
|
|
31803
|
+
|
|
31804
|
+
//#endregion
|
|
31805
|
+
//#region src/commands/roles/delete.ts
|
|
31806
|
+
const deleteCommand$1 = defineCommand({
|
|
31807
|
+
meta: {
|
|
31808
|
+
name: "delete",
|
|
31809
|
+
description: "Delete a custom role"
|
|
31810
|
+
},
|
|
31811
|
+
args: {
|
|
31812
|
+
id: {
|
|
31813
|
+
type: "positional",
|
|
31814
|
+
required: true,
|
|
31815
|
+
description: "Role ID"
|
|
31816
|
+
},
|
|
31817
|
+
yes: {
|
|
31818
|
+
type: "boolean",
|
|
31819
|
+
description: "Skip confirmation prompt"
|
|
31820
|
+
}
|
|
31821
|
+
},
|
|
31822
|
+
run: async ({ args }) => runEffect(Effect.gen(function* () {
|
|
31823
|
+
if (!args.yes) {
|
|
31824
|
+
if (!(yield* promptConfirm(`Delete role ${args.id}?`, { initialValue: false }))) {
|
|
31825
|
+
yield* printHuman("Cancelled.");
|
|
31826
|
+
return { deleted: 0 };
|
|
31827
|
+
}
|
|
31828
|
+
}
|
|
31829
|
+
const result = yield* (yield* apiClient).roles.delete({ path: { id: args.id } });
|
|
31830
|
+
yield* printHuman(`Role ${args.id} deleted.`);
|
|
31831
|
+
return result;
|
|
31832
|
+
}), {
|
|
31833
|
+
exits: roleErrorExtras,
|
|
31834
|
+
json: "value"
|
|
31835
|
+
})
|
|
31836
|
+
});
|
|
31837
|
+
|
|
31838
|
+
//#endregion
|
|
31839
|
+
//#region src/commands/roles/list.ts
|
|
31840
|
+
const listCommand$1 = defineCommand({
|
|
31841
|
+
meta: {
|
|
31842
|
+
name: "list",
|
|
31843
|
+
description: "List custom roles for the active organization"
|
|
31844
|
+
},
|
|
31845
|
+
args: { "organization-id": {
|
|
31846
|
+
type: "string",
|
|
31847
|
+
required: true,
|
|
31848
|
+
description: "Organization ID to list roles for"
|
|
31849
|
+
} },
|
|
31850
|
+
run: async ({ args }) => runEffect(Effect.gen(function* () {
|
|
31851
|
+
yield* printList([
|
|
31852
|
+
"ID",
|
|
31853
|
+
"Name",
|
|
31854
|
+
"Permissions",
|
|
31855
|
+
"Created"
|
|
31856
|
+
], (yield* (yield* apiClient).roles.list({ urlParams: { organizationId: args["organization-id"] } })).map((role) => [
|
|
31857
|
+
role.id,
|
|
31858
|
+
role.role,
|
|
31859
|
+
role.permissions.map((perm) => `${perm.resource}:[${perm.actions.join(",")}]`).join("; "),
|
|
31860
|
+
role.createdAt
|
|
31861
|
+
]), "No custom roles found.");
|
|
31862
|
+
}), roleErrorExtras)
|
|
31863
|
+
});
|
|
31864
|
+
|
|
31865
|
+
//#endregion
|
|
31866
|
+
//#region src/commands/roles/update.ts
|
|
31867
|
+
const updateCommand$1 = defineCommand({
|
|
31868
|
+
meta: {
|
|
31869
|
+
name: "update",
|
|
31870
|
+
description: "Update a custom role's name or permissions"
|
|
31871
|
+
},
|
|
31872
|
+
args: {
|
|
31873
|
+
id: {
|
|
31874
|
+
type: "positional",
|
|
31875
|
+
required: true,
|
|
31876
|
+
description: "Role ID"
|
|
31877
|
+
},
|
|
31878
|
+
name: {
|
|
31879
|
+
type: "string",
|
|
31880
|
+
description: "New role name"
|
|
31881
|
+
},
|
|
31882
|
+
permission: {
|
|
31883
|
+
type: "string",
|
|
31884
|
+
description: "Replacement permission tokens in resource:action format, comma-separated (e.g. channel:read,channel:update)"
|
|
31885
|
+
}
|
|
31886
|
+
},
|
|
31887
|
+
run: async ({ args }) => runEffect(Effect.gen(function* () {
|
|
31888
|
+
const permissions = args.permission === void 0 ? void 0 : yield* parsePermissionTokens(args.permission);
|
|
31889
|
+
const role = yield* (yield* apiClient).roles.update({
|
|
31890
|
+
path: { id: args.id },
|
|
31891
|
+
payload: compact({
|
|
31892
|
+
name: args.name,
|
|
31893
|
+
permissions
|
|
31894
|
+
})
|
|
31895
|
+
});
|
|
31896
|
+
yield* printHumanKeyValue([
|
|
31897
|
+
["ID", role.id],
|
|
31898
|
+
["Name", role.role],
|
|
31899
|
+
["Permissions", role.permissions.map((perm) => `${perm.resource}:[${perm.actions.join(",")}]`).join("; ")],
|
|
31900
|
+
["Updated", role.updatedAt ?? "-"]
|
|
31901
|
+
]);
|
|
31902
|
+
return role;
|
|
31903
|
+
}), {
|
|
31904
|
+
exits: roleErrorExtras,
|
|
31905
|
+
json: "value"
|
|
31906
|
+
})
|
|
31907
|
+
});
|
|
31908
|
+
|
|
31909
|
+
//#endregion
|
|
31910
|
+
//#region src/commands/roles/view.ts
|
|
31911
|
+
const viewCommand$1 = defineCommand({
|
|
31912
|
+
meta: {
|
|
31913
|
+
name: "view",
|
|
31914
|
+
description: "Show a custom role by ID"
|
|
31915
|
+
},
|
|
31916
|
+
args: { id: {
|
|
31917
|
+
type: "positional",
|
|
31918
|
+
required: true,
|
|
31919
|
+
description: "Role ID"
|
|
31920
|
+
} },
|
|
31921
|
+
run: async ({ args }) => runEffect(Effect.gen(function* () {
|
|
31922
|
+
const role = yield* (yield* apiClient).roles.get({ path: { id: args.id } });
|
|
31923
|
+
yield* printHumanKeyValue([
|
|
31924
|
+
["ID", role.id],
|
|
31925
|
+
["Name", role.role],
|
|
31926
|
+
["Organization ID", role.organizationId],
|
|
31927
|
+
["Permissions", role.permissions.map((perm) => `${perm.resource}:[${perm.actions.join(",")}]`).join("; ")],
|
|
31928
|
+
["Created", role.createdAt],
|
|
31929
|
+
["Updated", role.updatedAt ?? "-"]
|
|
31930
|
+
]);
|
|
31931
|
+
return role;
|
|
31932
|
+
}), {
|
|
31933
|
+
exits: roleErrorExtras,
|
|
31934
|
+
json: "value"
|
|
31935
|
+
})
|
|
31936
|
+
});
|
|
31937
|
+
|
|
31938
|
+
//#endregion
|
|
31939
|
+
//#region src/commands/roles/index.ts
|
|
31940
|
+
const rolesCommand = defineCommand({
|
|
31941
|
+
meta: {
|
|
31942
|
+
name: "roles",
|
|
31943
|
+
description: "Manage custom organization roles"
|
|
31944
|
+
},
|
|
31945
|
+
subCommands: {
|
|
31946
|
+
list: listCommand$1,
|
|
31947
|
+
view: viewCommand$1,
|
|
31948
|
+
create: createCommand,
|
|
31949
|
+
update: updateCommand$1,
|
|
31227
31950
|
delete: deleteCommand$1
|
|
31228
31951
|
}
|
|
31229
31952
|
});
|
|
@@ -32872,7 +33595,7 @@ const runPatchPhase = (input) => Effect.gen(function* () {
|
|
|
32872
33595
|
runtimeVersion: input.runtimeVersion,
|
|
32873
33596
|
platform: input.platform,
|
|
32874
33597
|
limit: Math.max(1, input.baseWindow)
|
|
32875
|
-
} }).pipe(Effect.
|
|
33598
|
+
} }).pipe(Effect.orElseSucceed(() => []));
|
|
32876
33599
|
const bases = selectBaseWindow(candidates, {
|
|
32877
33600
|
newUpdateId: input.newUpdateId,
|
|
32878
33601
|
maxRecent: input.baseWindow
|
|
@@ -32888,7 +33611,7 @@ const runPatchPhase = (input) => Effect.gen(function* () {
|
|
|
32888
33611
|
bestSavingsPct: void 0
|
|
32889
33612
|
};
|
|
32890
33613
|
}
|
|
32891
|
-
const newBundleBytes = yield* sha256File(input.newLaunchPath).pipe(Effect.map((result) => result.byteSize), Effect.
|
|
33614
|
+
const newBundleBytes = yield* sha256File(input.newLaunchPath).pipe(Effect.map((result) => result.byteSize), Effect.orElseSucceed(() => void 0));
|
|
32892
33615
|
yield* printHuman(`Diffing against ${bases.length} base(s) (window=${input.baseWindow}; ${candidates.length} candidate(s) available).`);
|
|
32893
33616
|
const uploadedOutcomes = (yield* Effect.forEach(bases, (base, index) => Effect.gen(function* () {
|
|
32894
33617
|
const basePath = path.join(input.workDir, `base-${index}.bundle`);
|
|
@@ -32983,7 +33706,7 @@ const dedupeAssetsByHash = (assets) => uniqBy(assets, (asset) => asset.hash);
|
|
|
32983
33706
|
* is informational, so a fingerprint failure resolves to `undefined` rather than
|
|
32984
33707
|
* failing the publish.
|
|
32985
33708
|
*/
|
|
32986
|
-
const resolvePlatformFingerprintHash = (projectRoot, platform) => runFingerprintForPlatform(projectRoot, platform).pipe(Effect.map((result) => result.hash), Effect.
|
|
33709
|
+
const resolvePlatformFingerprintHash = (projectRoot, platform) => runFingerprintForPlatform(projectRoot, platform).pipe(Effect.map((result) => result.hash), Effect.orElseSucceed(() => void 0));
|
|
32987
33710
|
const preparePlatformAssets = ({ exportDir, platform }) => Effect.gen(function* () {
|
|
32988
33711
|
const exportedAssets = yield* readExpoExportAssets({
|
|
32989
33712
|
exportDir,
|
|
@@ -33079,7 +33802,7 @@ const publishPlatform = (params) => Effect.gen(function* () {
|
|
|
33079
33802
|
const uploadDetailsByHash = new Map(assetRegistration.uploaded.map((asset) => [asset.hash, asset]));
|
|
33080
33803
|
yield* Effect.forEach(uniqueAssets.filter((asset) => uploadDetailsByHash.has(asset.hash)), (asset) => Effect.gen(function* () {
|
|
33081
33804
|
const detail = uploadDetailsByHash.get(asset.hash);
|
|
33082
|
-
if (!detail) return yield*
|
|
33805
|
+
if (!detail) return yield* new UpdatePublishError({ message: `Missing upload details for asset ${asset.hash}` });
|
|
33083
33806
|
return yield* assetUploader.uploadAssetBinary({
|
|
33084
33807
|
path: asset.path,
|
|
33085
33808
|
hash: asset.hash,
|
|
@@ -34464,6 +35187,7 @@ const commandRegistry = {
|
|
|
34464
35187
|
projects: projectsCommand,
|
|
34465
35188
|
branches: branchesCommand,
|
|
34466
35189
|
channels: channelsCommand,
|
|
35190
|
+
roles: rolesCommand,
|
|
34467
35191
|
build: buildCommand,
|
|
34468
35192
|
builds: buildsCommand,
|
|
34469
35193
|
credentials: credentialsCommand,
|