@better-update/cli 0.12.1 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +186 -34
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -28,7 +28,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
28
28
|
|
|
29
29
|
//#endregion
|
|
30
30
|
//#region package.json
|
|
31
|
-
var version = "0.
|
|
31
|
+
var version = "0.13.1";
|
|
32
32
|
|
|
33
33
|
//#endregion
|
|
34
34
|
//#region src/lib/interactive-mode.ts
|
|
@@ -1820,6 +1820,80 @@ const promptConfirm = (message, options) => Effect.gen(function* () {
|
|
|
1820
1820
|
})));
|
|
1821
1821
|
});
|
|
1822
1822
|
|
|
1823
|
+
//#endregion
|
|
1824
|
+
//#region src/lib/apple-auth.ts
|
|
1825
|
+
const APPLE_PROVIDER_ID_ENV = "APPLE_PROVIDER_ID";
|
|
1826
|
+
const readEnv = (name) => Effect.gen(function* () {
|
|
1827
|
+
return yield* (yield* CliRuntime).getEnv(name);
|
|
1828
|
+
});
|
|
1829
|
+
const parseProviderId = (raw) => {
|
|
1830
|
+
const id = Number(raw);
|
|
1831
|
+
return Number.isInteger(id) ? Effect.succeed(id) : Effect.fail(new AppleAuthError$1({ message: `${APPLE_PROVIDER_ID_ENV} must be a numeric provider ID, got "${raw}".` }));
|
|
1832
|
+
};
|
|
1833
|
+
const readEnvProviderId = Effect.gen(function* () {
|
|
1834
|
+
const raw = yield* readEnv(APPLE_PROVIDER_ID_ENV);
|
|
1835
|
+
if (!raw) return;
|
|
1836
|
+
return yield* parseProviderId(raw);
|
|
1837
|
+
});
|
|
1838
|
+
const switchSessionProvider = (appleUtils, providerId) => Effect.tryPromise({
|
|
1839
|
+
try: async () => appleUtils.Session.setSessionProviderIdAsync(providerId),
|
|
1840
|
+
catch: (error) => new AppleAuthError$1({ message: `Failed to switch App Store Connect provider (${providerId}): ${String(error)}` })
|
|
1841
|
+
}).pipe(Effect.asVoid);
|
|
1842
|
+
/**
|
|
1843
|
+
* Resolve App Store Connect provider for the current session.
|
|
1844
|
+
*
|
|
1845
|
+
* Selection order: APPLE_PROVIDER_ID env → single available provider →
|
|
1846
|
+
* interactive prompt (always, when multi-team + interactive) → fall back to
|
|
1847
|
+
* apple-utils' currentProviderId (non-interactive only).
|
|
1848
|
+
*
|
|
1849
|
+
* Multi-team users are always re-prompted in interactive mode so a wrong pick
|
|
1850
|
+
* from a previous run can be corrected — we do NOT cache the team choice.
|
|
1851
|
+
*
|
|
1852
|
+
* `switched` flags that the apple-utils cookie jar was mutated.
|
|
1853
|
+
*
|
|
1854
|
+
* Non-interactive (CI): env or single-team paths still work; multi-team falls
|
|
1855
|
+
* back to whatever apple-utils auto-resolved from cookies. Fails with
|
|
1856
|
+
* InteractiveProhibitedError when multi-team and no signal at all.
|
|
1857
|
+
*/
|
|
1858
|
+
const resolveProvider = (appleUtils, availableProviders, currentProviderId) => Effect.gen(function* () {
|
|
1859
|
+
let switched = false;
|
|
1860
|
+
const applyChoice = (picked) => Effect.gen(function* () {
|
|
1861
|
+
if (currentProviderId !== picked) {
|
|
1862
|
+
yield* switchSessionProvider(appleUtils, picked);
|
|
1863
|
+
switched = true;
|
|
1864
|
+
}
|
|
1865
|
+
return picked;
|
|
1866
|
+
});
|
|
1867
|
+
const envId = yield* readEnvProviderId;
|
|
1868
|
+
if (envId !== void 0) return {
|
|
1869
|
+
providerId: yield* applyChoice(envId),
|
|
1870
|
+
switched
|
|
1871
|
+
};
|
|
1872
|
+
if (availableProviders.length === 0) return {
|
|
1873
|
+
providerId: currentProviderId,
|
|
1874
|
+
switched
|
|
1875
|
+
};
|
|
1876
|
+
const [firstProvider] = availableProviders;
|
|
1877
|
+
if (availableProviders.length === 1 && firstProvider) return {
|
|
1878
|
+
providerId: yield* applyChoice(firstProvider.providerId),
|
|
1879
|
+
switched
|
|
1880
|
+
};
|
|
1881
|
+
if (!(yield* InteractiveMode).allow) {
|
|
1882
|
+
if (currentProviderId !== void 0) return {
|
|
1883
|
+
providerId: currentProviderId,
|
|
1884
|
+
switched
|
|
1885
|
+
};
|
|
1886
|
+
return yield* new InteractiveProhibitedError({ message: "Multiple App Store Connect providers are available but no APPLE_PROVIDER_ID is set; re-run interactively or set the env var." });
|
|
1887
|
+
}
|
|
1888
|
+
return {
|
|
1889
|
+
providerId: yield* applyChoice(yield* promptSelect("Select App Store Connect provider:", availableProviders.map((provider) => ({
|
|
1890
|
+
value: provider.providerId,
|
|
1891
|
+
label: `${provider.name} [${provider.subType}] (${provider.providerId})`
|
|
1892
|
+
})))),
|
|
1893
|
+
switched
|
|
1894
|
+
};
|
|
1895
|
+
});
|
|
1896
|
+
|
|
1823
1897
|
//#endregion
|
|
1824
1898
|
//#region ../../packages/safe-json/src/index.ts
|
|
1825
1899
|
const parseJsonResult = (text) => {
|
|
@@ -1852,14 +1926,10 @@ const AppleSessionStoreLive = Layer.effect(AppleSessionStore, Effect.gen(functio
|
|
|
1852
1926
|
if (!content) return null;
|
|
1853
1927
|
const parsed = safeJsonParse(content);
|
|
1854
1928
|
if (!isRecord(parsed)) return null;
|
|
1855
|
-
if (typeof parsed["
|
|
1856
|
-
const providerIdRaw = parsed["providerId"];
|
|
1857
|
-
const hasProviderId = typeof providerIdRaw === "number" && Number.isInteger(providerIdRaw);
|
|
1929
|
+
if (typeof parsed["username"] !== "string" || !parsed["cookies"]) return null;
|
|
1858
1930
|
return {
|
|
1859
1931
|
cookies: parsed["cookies"],
|
|
1860
|
-
|
|
1861
|
-
username: parsed["username"],
|
|
1862
|
-
...hasProviderId ? { providerId: providerIdRaw } : {}
|
|
1932
|
+
username: parsed["username"]
|
|
1863
1933
|
};
|
|
1864
1934
|
}),
|
|
1865
1935
|
saveSession: (session) => Effect.gen(function* () {
|
|
@@ -1905,17 +1975,30 @@ const sessionFromInfo = (username, info) => ({
|
|
|
1905
1975
|
teamName: info.provider.name,
|
|
1906
1976
|
providerId: info.provider.providerId
|
|
1907
1977
|
});
|
|
1908
|
-
const
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
},
|
|
1978
|
+
const sessionFromProvider = (username, provider) => ({
|
|
1979
|
+
username,
|
|
1980
|
+
teamId: provider.publicProviderId,
|
|
1981
|
+
teamName: provider.name,
|
|
1982
|
+
providerId: provider.providerId
|
|
1983
|
+
});
|
|
1984
|
+
const restoreFromCookies = (appleUtils, cookies) => Effect.tryPromise({
|
|
1985
|
+
try: async () => appleUtils.Auth.loginWithCookiesAsync({ cookies }),
|
|
1917
1986
|
catch: (cause) => new AppleAuthError$1({ message: `Failed to restore Apple session: ${formatCause(cause)}` })
|
|
1918
1987
|
});
|
|
1988
|
+
/**
|
|
1989
|
+
* After a cookie restore or fresh credentials login, re-resolve the team via
|
|
1990
|
+
* {@link resolveProvider}. The cookies are accepted as-is (auth state) but the
|
|
1991
|
+
* team is treated as a per-run choice — we never trust a previously-cached team,
|
|
1992
|
+
* so a wrong pick can always be corrected on the next run.
|
|
1993
|
+
*/
|
|
1994
|
+
const resolveSessionTeam = (appleUtils, state) => Effect.gen(function* () {
|
|
1995
|
+
const { availableProviders } = state.session;
|
|
1996
|
+
const resolution = yield* resolveProvider(appleUtils, availableProviders, state.context.providerId ?? state.session.provider.providerId);
|
|
1997
|
+
if (!resolution.switched || resolution.providerId === void 0) return sessionFromAuthState(state);
|
|
1998
|
+
const picked = availableProviders.find((provider) => provider.providerId === resolution.providerId);
|
|
1999
|
+
if (picked === void 0) return yield* new AppleAuthError$1({ message: `Selected provider ${String(resolution.providerId)} not in available providers list.` });
|
|
2000
|
+
return sessionFromProvider(state.username, picked);
|
|
2001
|
+
});
|
|
1919
2002
|
const loginWithCredentials = (appleUtils, credentials) => Effect.tryPromise({
|
|
1920
2003
|
try: async () => appleUtils.Auth.loginWithUserCredentialsAsync(credentials, { autoResolveProvider: true }),
|
|
1921
2004
|
catch: (cause) => new AppleAuthError$1({ message: `Apple login failed: ${formatCause(cause)}` })
|
|
@@ -1942,12 +2025,10 @@ const interactiveLogin = (appleUtils, options, cachedUsername) => Effect.gen(fun
|
|
|
1942
2025
|
password
|
|
1943
2026
|
});
|
|
1944
2027
|
if (state === null) return yield* new AppleAuthError$1({ message: "Apple login returned no session (unexpected)." });
|
|
1945
|
-
const session =
|
|
2028
|
+
const session = yield* resolveSessionTeam(appleUtils, state);
|
|
1946
2029
|
yield* store.saveSession({
|
|
1947
2030
|
cookies: readJarCookies(appleUtils),
|
|
1948
|
-
username: session.username
|
|
1949
|
-
teamId: session.teamId,
|
|
1950
|
-
...session.providerId === void 0 ? {} : { providerId: session.providerId }
|
|
2031
|
+
username: session.username
|
|
1951
2032
|
});
|
|
1952
2033
|
yield* store.saveLastUsername(session.username);
|
|
1953
2034
|
return session;
|
|
@@ -1955,15 +2036,15 @@ const interactiveLogin = (appleUtils, options, cachedUsername) => Effect.gen(fun
|
|
|
1955
2036
|
const tryRestore = (appleUtils, store) => Effect.gen(function* () {
|
|
1956
2037
|
const stored = yield* store.loadSession;
|
|
1957
2038
|
if (stored === null) return null;
|
|
1958
|
-
const restored = yield* restoreFromCookies(appleUtils, stored.cookies
|
|
2039
|
+
const restored = yield* restoreFromCookies(appleUtils, stored.cookies).pipe(Effect.catchAll(() => Effect.succeed(null)));
|
|
1959
2040
|
if (restored === null) return null;
|
|
1960
|
-
return
|
|
2041
|
+
return yield* resolveSessionTeam(appleUtils, restored);
|
|
1961
2042
|
});
|
|
1962
2043
|
const makeAppleAuthLive = (appleUtils = defaultAppleUtils) => Layer.effect(AppleAuth, Effect.gen(function* () {
|
|
1963
2044
|
const store = yield* AppleSessionStore;
|
|
1964
2045
|
return {
|
|
1965
2046
|
ensureLoggedIn: (options = {}) => Effect.gen(function* () {
|
|
1966
|
-
const restored = yield* tryRestore(appleUtils, store)
|
|
2047
|
+
const restored = yield* tryRestore(appleUtils, store);
|
|
1967
2048
|
if (restored !== null) return restored;
|
|
1968
2049
|
return yield* interactiveLogin(appleUtils, options, yield* store.loadLastUsername).pipe(Effect.provideService(AppleSessionStore, store));
|
|
1969
2050
|
}),
|
|
@@ -1974,15 +2055,10 @@ const makeAppleAuthLive = (appleUtils = defaultAppleUtils) => Layer.effect(Apple
|
|
|
1974
2055
|
whoami: Effect.gen(function* () {
|
|
1975
2056
|
const stored = yield* store.loadSession;
|
|
1976
2057
|
if (stored === null) return null;
|
|
1977
|
-
const restored = yield* restoreFromCookies(appleUtils, stored.cookies
|
|
2058
|
+
const restored = yield* restoreFromCookies(appleUtils, stored.cookies).pipe(Effect.catchAll(() => Effect.succeed(null)));
|
|
1978
2059
|
if (restored !== null) return sessionFromAuthState(restored);
|
|
1979
2060
|
const info = appleUtils.Session.getAnySessionInfo();
|
|
1980
|
-
return info === null ?
|
|
1981
|
-
username: stored.username,
|
|
1982
|
-
teamId: stored.teamId,
|
|
1983
|
-
teamName: null,
|
|
1984
|
-
providerId: stored.providerId
|
|
1985
|
-
} : sessionFromInfo(stored.username, info);
|
|
2061
|
+
return info === null ? null : sessionFromInfo(stored.username, info);
|
|
1986
2062
|
}),
|
|
1987
2063
|
buildRequestContext: (session) => ({
|
|
1988
2064
|
teamId: session.teamId,
|
|
@@ -5278,17 +5354,30 @@ const DISTRIBUTION_TO_CERTIFICATE_TYPE = {
|
|
|
5278
5354
|
DEVELOPMENT: AppleUtils.CertificateType.IOS_DEVELOPMENT
|
|
5279
5355
|
};
|
|
5280
5356
|
var AppleIdGenerateFailedError = class extends Data.TaggedError("AppleIdGenerateFailedError") {};
|
|
5357
|
+
const CERT_LIMIT_PATTERN = /already have a current.*certificate|pending certificate request/iu;
|
|
5358
|
+
const messageOf = (cause) => cause instanceof Error ? cause.message : String(cause);
|
|
5281
5359
|
const wrap = (step, run) => Effect.tryPromise({
|
|
5282
5360
|
try: run,
|
|
5283
5361
|
catch: (cause) => new AppleIdGenerateFailedError({
|
|
5284
5362
|
step,
|
|
5285
|
-
message:
|
|
5363
|
+
message: messageOf(cause)
|
|
5286
5364
|
})
|
|
5287
5365
|
});
|
|
5366
|
+
const wrapCertificateCreate = (run) => Effect.tryPromise({
|
|
5367
|
+
try: run,
|
|
5368
|
+
catch: (cause) => {
|
|
5369
|
+
const message = messageOf(cause);
|
|
5370
|
+
if (CERT_LIMIT_PATTERN.test(message)) return new CertificateLimitError({ message });
|
|
5371
|
+
return new AppleIdGenerateFailedError({
|
|
5372
|
+
step: "apple-create-certificate",
|
|
5373
|
+
message
|
|
5374
|
+
});
|
|
5375
|
+
}
|
|
5376
|
+
});
|
|
5288
5377
|
const generateAndUploadDistributionCertificateViaAppleId = (api, input) => Effect.gen(function* () {
|
|
5289
5378
|
const ctx = input.context;
|
|
5290
5379
|
const certificateType = input.certificateType === "IOS_DEVELOPMENT" ? AppleUtils.CertificateType.IOS_DEVELOPMENT : AppleUtils.CertificateType.IOS_DISTRIBUTION;
|
|
5291
|
-
const result = yield*
|
|
5380
|
+
const result = yield* wrapCertificateCreate(async () => AppleUtils.createCertificateAndP12Async(ctx, { certificateType }));
|
|
5292
5381
|
const metadata = yield* extractMetadataFromP12({
|
|
5293
5382
|
p12Base64: result.certificateP12,
|
|
5294
5383
|
password: result.password
|
|
@@ -5312,6 +5401,16 @@ const generateAndUploadDistributionCertificateViaAppleId = (api, input) => Effec
|
|
|
5312
5401
|
developerPortalIdentifier: result.certificate.id
|
|
5313
5402
|
};
|
|
5314
5403
|
});
|
|
5404
|
+
const listDistributionCertsViaAppleId = (ctx, certificateType = "IOS_DISTRIBUTION") => Effect.gen(function* () {
|
|
5405
|
+
const filter = certificateType === "IOS_DEVELOPMENT" ? AppleUtils.CertificateType.IOS_DEVELOPMENT : AppleUtils.CertificateType.IOS_DISTRIBUTION;
|
|
5406
|
+
return (yield* wrap("apple-list-certificates", async () => AppleUtils.Certificate.getAsync(ctx, { query: { filter: { certificateType: filter } } }))).map((entry) => ({
|
|
5407
|
+
developerPortalIdentifier: entry.id,
|
|
5408
|
+
serialNumber: entry.attributes.serialNumber,
|
|
5409
|
+
displayName: entry.attributes.displayName,
|
|
5410
|
+
expirationDate: entry.attributes.expirationDate
|
|
5411
|
+
}));
|
|
5412
|
+
});
|
|
5413
|
+
const revokeDistributionCertViaAppleId = (ctx, developerPortalIdentifier) => wrap("apple-revoke-certificate", async () => AppleUtils.Certificate.deleteAsync(ctx, { id: developerPortalIdentifier }));
|
|
5315
5414
|
const findOrCreateBundleId = (ctx, bundleIdentifier) => Effect.gen(function* () {
|
|
5316
5415
|
const existing = yield* wrap("apple-find-bundle-id", async () => AppleUtils.BundleId.findAsync(ctx, { identifier: bundleIdentifier }));
|
|
5317
5416
|
if (existing !== null) return existing.id;
|
|
@@ -5393,13 +5492,66 @@ const chooseIosSetupPath = (api) => Effect.gen(function* () {
|
|
|
5393
5492
|
label: "Use an App Store Connect API key"
|
|
5394
5493
|
}]);
|
|
5395
5494
|
});
|
|
5495
|
+
const interactiveAppleIdCertLimitRecover = (ctx) => Effect.gen(function* () {
|
|
5496
|
+
yield* Console.log("");
|
|
5497
|
+
yield* Console.log("Apple reports the certificate limit was hit (max 3 distribution certs per team).");
|
|
5498
|
+
const certs = yield* listDistributionCertsViaAppleId(ctx, "IOS_DISTRIBUTION");
|
|
5499
|
+
if (certs.length === 0) return yield* new AppleIdGenerateFailedError({
|
|
5500
|
+
step: "limit-recover",
|
|
5501
|
+
message: "Apple says the certificate limit is hit but no existing certificates were returned."
|
|
5502
|
+
});
|
|
5503
|
+
const toRevoke = yield* promptMultiSelect("Select one or more certificates to revoke before retrying", certs.map((entry) => ({
|
|
5504
|
+
value: entry.developerPortalIdentifier,
|
|
5505
|
+
label: `${entry.serialNumber.slice(0, 12)}… (${entry.displayName}, exp ${entry.expirationDate.slice(0, 10)})`
|
|
5506
|
+
})), { required: true });
|
|
5507
|
+
yield* Effect.forEach(toRevoke, (id) => revokeDistributionCertViaAppleId(ctx, id), { concurrency: "inherit" });
|
|
5508
|
+
yield* Console.log(`Revoked ${toRevoke.length} certificate(s); retrying generation...`);
|
|
5509
|
+
});
|
|
5510
|
+
const generateDistributionCertViaAppleIdInteractive = (api, ctx) => Effect.gen(function* () {
|
|
5511
|
+
yield* Console.log("Generating distribution certificate via Apple ID...");
|
|
5512
|
+
const generate = generateAndUploadDistributionCertificateViaAppleId(api, { context: ctx });
|
|
5513
|
+
return yield* generate.pipe(Effect.catchTag("CertificateLimitError", () => interactiveAppleIdCertLimitRecover(ctx).pipe(Effect.flatMap(() => generate))));
|
|
5514
|
+
});
|
|
5515
|
+
const GENERATE_NEW = "__generate__";
|
|
5516
|
+
const chooseDistributionCertViaAppleId = (api, ctx, appleTeamId) => Effect.gen(function* () {
|
|
5517
|
+
const items = (yield* api.appleDistributionCertificates.list()).items.filter((cert) => cert.appleTeamId === appleTeamId);
|
|
5518
|
+
if (items.length === 0) {
|
|
5519
|
+
const created = yield* generateDistributionCertViaAppleIdInteractive(api, ctx);
|
|
5520
|
+
return {
|
|
5521
|
+
id: created.id,
|
|
5522
|
+
appleTeamId: created.appleTeamId
|
|
5523
|
+
};
|
|
5524
|
+
}
|
|
5525
|
+
const choice = yield* promptSelect("Select a distribution certificate (or 'generate' for a fresh one)", [{
|
|
5526
|
+
value: GENERATE_NEW,
|
|
5527
|
+
label: "Generate a new distribution certificate"
|
|
5528
|
+
}, ...items.map((cert) => ({
|
|
5529
|
+
value: cert.id,
|
|
5530
|
+
label: `${cert.serialNumber.slice(0, 12)}… (team ${cert.appleTeamId})`
|
|
5531
|
+
}))]);
|
|
5532
|
+
if (choice === GENERATE_NEW) {
|
|
5533
|
+
const created = yield* generateDistributionCertViaAppleIdInteractive(api, ctx);
|
|
5534
|
+
return {
|
|
5535
|
+
id: created.id,
|
|
5536
|
+
appleTeamId: created.appleTeamId
|
|
5537
|
+
};
|
|
5538
|
+
}
|
|
5539
|
+
const cert = items.find((entry) => entry.id === choice);
|
|
5540
|
+
if (cert === void 0) return yield* new AppleIdGenerateFailedError({
|
|
5541
|
+
step: "pick-certificate",
|
|
5542
|
+
message: `Selected certificate ${choice} not found after listing`
|
|
5543
|
+
});
|
|
5544
|
+
return {
|
|
5545
|
+
id: cert.id,
|
|
5546
|
+
appleTeamId: cert.appleTeamId
|
|
5547
|
+
};
|
|
5548
|
+
});
|
|
5396
5549
|
const setupIosViaAppleId = (api, input) => Effect.gen(function* () {
|
|
5397
5550
|
const auth = yield* AppleAuth;
|
|
5398
5551
|
const session = yield* auth.ensureLoggedIn();
|
|
5399
5552
|
const ctx = auth.buildRequestContext(session);
|
|
5400
5553
|
yield* Console.log(`Logged in as ${session.username}. Team: ${session.teamName ?? session.teamId} (${session.teamId}).`);
|
|
5401
|
-
yield*
|
|
5402
|
-
const cert = yield* generateAndUploadDistributionCertificateViaAppleId(api, { context: ctx });
|
|
5554
|
+
const cert = yield* chooseDistributionCertViaAppleId(api, ctx, session.teamId);
|
|
5403
5555
|
const distributionType = IOS_DISTRIBUTION_TO_TYPE[input.distribution];
|
|
5404
5556
|
yield* Console.log("Generating provisioning profile via Apple ID...");
|
|
5405
5557
|
const profile = yield* generateAndUploadProvisioningProfileViaAppleId(api, {
|