@better-update/cli 0.13.0 → 0.14.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 CHANGED
@@ -8,7 +8,7 @@ import { NodeContext } from "@effect/platform-node";
8
8
  import path, { join } from "node:path";
9
9
  import process$1 from "node:process";
10
10
  import AppleUtils from "@expo/apple-utils";
11
- import { cancel, confirm, isCancel, multiselect, password, select, text } from "@clack/prompts";
11
+ import { autocomplete, cancel, confirm, isCancel, multiselect, password, select, text } from "@clack/prompts";
12
12
  import { once } from "node:events";
13
13
  import { createServer } from "node:http";
14
14
  import { maxBy, uniqBy } from "es-toolkit";
@@ -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.13.0";
31
+ var version = "0.14.0";
32
32
 
33
33
  //#endregion
34
34
  //#region src/lib/interactive-mode.ts
@@ -1796,6 +1796,15 @@ const promptSelect = (message, options) => Effect.gen(function* () {
1796
1796
  options: [...options]
1797
1797
  })));
1798
1798
  });
1799
+ const promptAutocomplete = (message, options, config) => Effect.gen(function* () {
1800
+ yield* ensureInteractive(message);
1801
+ return handleCancel(yield* Effect.promise(async () => autocomplete({
1802
+ message,
1803
+ options: [...options],
1804
+ ...config?.placeholder === void 0 ? {} : { placeholder: config.placeholder },
1805
+ ...config?.maxItems === void 0 ? {} : { maxItems: config.maxItems }
1806
+ })));
1807
+ });
1799
1808
  const promptMultiSelect = (message, options, config) => Effect.gen(function* () {
1800
1809
  yield* ensureInteractive(message);
1801
1810
  return handleCancel(yield* Effect.promise(async () => multiselect({
@@ -1820,6 +1829,83 @@ const promptConfirm = (message, options) => Effect.gen(function* () {
1820
1829
  })));
1821
1830
  });
1822
1831
 
1832
+ //#endregion
1833
+ //#region src/lib/apple-auth.ts
1834
+ const APPLE_PROVIDER_ID_ENV = "APPLE_PROVIDER_ID";
1835
+ const readEnv = (name) => Effect.gen(function* () {
1836
+ return yield* (yield* CliRuntime).getEnv(name);
1837
+ });
1838
+ const parseProviderId = (raw) => {
1839
+ const id = Number(raw);
1840
+ 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}".` }));
1841
+ };
1842
+ const readEnvProviderId = Effect.gen(function* () {
1843
+ const raw = yield* readEnv(APPLE_PROVIDER_ID_ENV);
1844
+ if (!raw) return;
1845
+ return yield* parseProviderId(raw);
1846
+ });
1847
+ const switchSessionProvider = (appleUtils, providerId) => Effect.tryPromise({
1848
+ try: async () => appleUtils.Session.setSessionProviderIdAsync(providerId),
1849
+ catch: (error) => new AppleAuthError$1({ message: `Failed to switch App Store Connect provider (${providerId}): ${String(error)}` })
1850
+ }).pipe(Effect.asVoid);
1851
+ /**
1852
+ * Resolve App Store Connect provider for the current session.
1853
+ *
1854
+ * Selection order: APPLE_PROVIDER_ID env → single available provider →
1855
+ * interactive prompt (always, when multi-team + interactive) → fall back to
1856
+ * apple-utils' currentProviderId (non-interactive only).
1857
+ *
1858
+ * Multi-team users are always re-prompted in interactive mode so a wrong pick
1859
+ * from a previous run can be corrected — we do NOT cache the team choice.
1860
+ *
1861
+ * `switched` flags that the apple-utils cookie jar was mutated.
1862
+ *
1863
+ * Non-interactive (CI): env or single-team paths still work; multi-team falls
1864
+ * back to whatever apple-utils auto-resolved from cookies. Fails with
1865
+ * InteractiveProhibitedError when multi-team and no signal at all.
1866
+ */
1867
+ const resolveProvider = (appleUtils, availableProviders, currentProviderId) => Effect.gen(function* () {
1868
+ let switched = false;
1869
+ const applyChoice = (picked) => Effect.gen(function* () {
1870
+ if (currentProviderId !== picked) {
1871
+ yield* switchSessionProvider(appleUtils, picked);
1872
+ switched = true;
1873
+ }
1874
+ return picked;
1875
+ });
1876
+ const envId = yield* readEnvProviderId;
1877
+ if (envId !== void 0) return {
1878
+ providerId: yield* applyChoice(envId),
1879
+ switched
1880
+ };
1881
+ if (availableProviders.length === 0) return {
1882
+ providerId: currentProviderId,
1883
+ switched
1884
+ };
1885
+ const [firstProvider] = availableProviders;
1886
+ if (availableProviders.length === 1 && firstProvider) return {
1887
+ providerId: yield* applyChoice(firstProvider.providerId),
1888
+ switched
1889
+ };
1890
+ if (!(yield* InteractiveMode).allow) {
1891
+ if (currentProviderId !== void 0) return {
1892
+ providerId: currentProviderId,
1893
+ switched
1894
+ };
1895
+ 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." });
1896
+ }
1897
+ return {
1898
+ providerId: yield* applyChoice(yield* promptAutocomplete("Select App Store Connect provider:", availableProviders.map((provider) => ({
1899
+ value: provider.providerId,
1900
+ label: `${provider.name} [${provider.subType}] (${provider.providerId})`
1901
+ })), {
1902
+ placeholder: "Type to filter…",
1903
+ maxItems: 10
1904
+ })),
1905
+ switched
1906
+ };
1907
+ });
1908
+
1823
1909
  //#endregion
1824
1910
  //#region ../../packages/safe-json/src/index.ts
1825
1911
  const parseJsonResult = (text) => {
@@ -1852,14 +1938,10 @@ const AppleSessionStoreLive = Layer.effect(AppleSessionStore, Effect.gen(functio
1852
1938
  if (!content) return null;
1853
1939
  const parsed = safeJsonParse(content);
1854
1940
  if (!isRecord(parsed)) return null;
1855
- if (typeof parsed["teamId"] !== "string" || typeof parsed["username"] !== "string" || !parsed["cookies"]) return null;
1856
- const providerIdRaw = parsed["providerId"];
1857
- const hasProviderId = typeof providerIdRaw === "number" && Number.isInteger(providerIdRaw);
1941
+ if (typeof parsed["username"] !== "string" || !parsed["cookies"]) return null;
1858
1942
  return {
1859
1943
  cookies: parsed["cookies"],
1860
- teamId: parsed["teamId"],
1861
- username: parsed["username"],
1862
- ...hasProviderId ? { providerId: providerIdRaw } : {}
1944
+ username: parsed["username"]
1863
1945
  };
1864
1946
  }),
1865
1947
  saveSession: (session) => Effect.gen(function* () {
@@ -1905,17 +1987,30 @@ const sessionFromInfo = (username, info) => ({
1905
1987
  teamName: info.provider.name,
1906
1988
  providerId: info.provider.providerId
1907
1989
  });
1908
- const restoreFromCookies = (appleUtils, cookies, providerId, teamId) => Effect.tryPromise({
1909
- try: async () => {
1910
- const input = {
1911
- cookies,
1912
- ...providerId === void 0 ? {} : { providerId },
1913
- ...teamId === void 0 ? {} : { teamId }
1914
- };
1915
- return appleUtils.Auth.loginWithCookiesAsync(input);
1916
- },
1990
+ const sessionFromProvider = (username, provider) => ({
1991
+ username,
1992
+ teamId: provider.publicProviderId,
1993
+ teamName: provider.name,
1994
+ providerId: provider.providerId
1995
+ });
1996
+ const restoreFromCookies = (appleUtils, cookies) => Effect.tryPromise({
1997
+ try: async () => appleUtils.Auth.loginWithCookiesAsync({ cookies }),
1917
1998
  catch: (cause) => new AppleAuthError$1({ message: `Failed to restore Apple session: ${formatCause(cause)}` })
1918
1999
  });
2000
+ /**
2001
+ * After a cookie restore or fresh credentials login, re-resolve the team via
2002
+ * {@link resolveProvider}. The cookies are accepted as-is (auth state) but the
2003
+ * team is treated as a per-run choice — we never trust a previously-cached team,
2004
+ * so a wrong pick can always be corrected on the next run.
2005
+ */
2006
+ const resolveSessionTeam = (appleUtils, state) => Effect.gen(function* () {
2007
+ const { availableProviders } = state.session;
2008
+ const resolution = yield* resolveProvider(appleUtils, availableProviders, state.context.providerId ?? state.session.provider.providerId);
2009
+ if (!resolution.switched || resolution.providerId === void 0) return sessionFromAuthState(state);
2010
+ const picked = availableProviders.find((provider) => provider.providerId === resolution.providerId);
2011
+ if (picked === void 0) return yield* new AppleAuthError$1({ message: `Selected provider ${String(resolution.providerId)} not in available providers list.` });
2012
+ return sessionFromProvider(state.username, picked);
2013
+ });
1919
2014
  const loginWithCredentials = (appleUtils, credentials) => Effect.tryPromise({
1920
2015
  try: async () => appleUtils.Auth.loginWithUserCredentialsAsync(credentials, { autoResolveProvider: true }),
1921
2016
  catch: (cause) => new AppleAuthError$1({ message: `Apple login failed: ${formatCause(cause)}` })
@@ -1942,12 +2037,10 @@ const interactiveLogin = (appleUtils, options, cachedUsername) => Effect.gen(fun
1942
2037
  password
1943
2038
  });
1944
2039
  if (state === null) return yield* new AppleAuthError$1({ message: "Apple login returned no session (unexpected)." });
1945
- const session = sessionFromAuthState(state);
2040
+ const session = yield* resolveSessionTeam(appleUtils, state);
1946
2041
  yield* store.saveSession({
1947
2042
  cookies: readJarCookies(appleUtils),
1948
- username: session.username,
1949
- teamId: session.teamId,
1950
- ...session.providerId === void 0 ? {} : { providerId: session.providerId }
2043
+ username: session.username
1951
2044
  });
1952
2045
  yield* store.saveLastUsername(session.username);
1953
2046
  return session;
@@ -1955,15 +2048,15 @@ const interactiveLogin = (appleUtils, options, cachedUsername) => Effect.gen(fun
1955
2048
  const tryRestore = (appleUtils, store) => Effect.gen(function* () {
1956
2049
  const stored = yield* store.loadSession;
1957
2050
  if (stored === null) return null;
1958
- const restored = yield* restoreFromCookies(appleUtils, stored.cookies, stored.providerId, stored.teamId);
2051
+ const restored = yield* restoreFromCookies(appleUtils, stored.cookies).pipe(Effect.catchAll(() => Effect.succeed(null)));
1959
2052
  if (restored === null) return null;
1960
- return sessionFromAuthState(restored);
2053
+ return yield* resolveSessionTeam(appleUtils, restored);
1961
2054
  });
1962
2055
  const makeAppleAuthLive = (appleUtils = defaultAppleUtils) => Layer.effect(AppleAuth, Effect.gen(function* () {
1963
2056
  const store = yield* AppleSessionStore;
1964
2057
  return {
1965
2058
  ensureLoggedIn: (options = {}) => Effect.gen(function* () {
1966
- const restored = yield* tryRestore(appleUtils, store).pipe(Effect.catchAll(() => Effect.succeed(null)));
2059
+ const restored = yield* tryRestore(appleUtils, store);
1967
2060
  if (restored !== null) return restored;
1968
2061
  return yield* interactiveLogin(appleUtils, options, yield* store.loadLastUsername).pipe(Effect.provideService(AppleSessionStore, store));
1969
2062
  }),
@@ -1974,15 +2067,10 @@ const makeAppleAuthLive = (appleUtils = defaultAppleUtils) => Layer.effect(Apple
1974
2067
  whoami: Effect.gen(function* () {
1975
2068
  const stored = yield* store.loadSession;
1976
2069
  if (stored === null) return null;
1977
- const restored = yield* restoreFromCookies(appleUtils, stored.cookies, stored.providerId, stored.teamId).pipe(Effect.catchAll(() => Effect.succeed(null)));
2070
+ const restored = yield* restoreFromCookies(appleUtils, stored.cookies).pipe(Effect.catchAll(() => Effect.succeed(null)));
1978
2071
  if (restored !== null) return sessionFromAuthState(restored);
1979
2072
  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);
2073
+ return info === null ? null : sessionFromInfo(stored.username, info);
1986
2074
  }),
1987
2075
  buildRequestContext: (session) => ({
1988
2076
  teamId: session.teamId,