@better-update/cli 0.47.1 → 0.47.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -9,7 +9,7 @@ import path from "node:path";
9
9
  import process$1 from "node:process";
10
10
  import AppleUtils from "@expo/apple-utils";
11
11
  import { autocomplete, cancel, confirm, isCancel, multiselect, password, select, text } from "@clack/prompts";
12
- import { open, readFile, writeFile } from "node:fs/promises";
12
+ import { mkdtemp, open, readFile, rm, writeFile } from "node:fs/promises";
13
13
  import { X509Certificate, createHash, createSign, createVerify, randomBytes, randomUUID } from "node:crypto";
14
14
  import { accessSync, chmodSync, constants, createReadStream, promises } from "node:fs";
15
15
  import { Entry } from "@napi-rs/keyring";
@@ -35,7 +35,7 @@ var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
35
35
 
36
36
  //#endregion
37
37
  //#region package.json
38
- var version = "0.47.1";
38
+ var version = "0.47.3";
39
39
 
40
40
  //#endregion
41
41
  //#region src/lib/interactive-mode.ts
@@ -3466,7 +3466,9 @@ const promptText = (message, options) => Effect.gen(function* () {
3466
3466
  return handleCancel(yield* Effect.promise(async () => text(compact({
3467
3467
  message,
3468
3468
  placeholder: options?.placeholder,
3469
- defaultValue: options?.defaultValue
3469
+ defaultValue: options?.defaultValue,
3470
+ initialValue: options?.initialValue,
3471
+ validate: options?.validate
3470
3472
  }))));
3471
3473
  });
3472
3474
  const promptConfirm = (message, options) => Effect.gen(function* () {
@@ -21541,9 +21543,16 @@ const ExecErrorSchema = Schema.Struct({
21541
21543
  stdout: Schema.optional(Schema.String),
21542
21544
  stderr: Schema.optional(Schema.String)
21543
21545
  });
21544
- const runAltool = (args) => Effect.tryPromise({
21546
+ const runAltool = (args, extraEnv) => Effect.tryPromise({
21545
21547
  try: async () => {
21546
- const { stdout, stderr } = await execFileAsync("xcrun", ["altool", ...args]);
21548
+ const options = extraEnv ? {
21549
+ encoding: "utf8",
21550
+ env: {
21551
+ ...process.env,
21552
+ ...extraEnv
21553
+ }
21554
+ } : { encoding: "utf8" };
21555
+ const { stdout, stderr } = await execFileAsync("xcrun", ["altool", ...args], options);
21547
21556
  return {
21548
21557
  exitCode: 0,
21549
21558
  stdout,
@@ -21693,11 +21702,23 @@ const resolveAscUploadCredentials = (params) => Effect.gen(function* () {
21693
21702
  p8Pem: creds.p8Pem
21694
21703
  })), Effect.catchAll((error) => printHuman(`Could not prepare ASC API key ${credsKeyId} (${messageOf(error)}).`).pipe(Effect.as(null))));
21695
21704
  });
21696
- /** `altool` reads the API key from `--apiKeyDir`; write the decrypted `.p8` there. */
21697
- const writeP8ForAltool = (credentials) => Effect.gen(function* () {
21698
- const target = path.join(tmpdir(), `better-update-submit-AuthKey_${credentials.keyId}.p8`);
21699
- yield* Effect.promise(async () => writeFile(target, credentials.p8Pem, "utf8"));
21700
- return target;
21705
+ /**
21706
+ * `altool --apiKey <id>` searches for a file named *exactly* `AuthKey_<id>.p8` in
21707
+ * the standard `private_keys` dirs plus `$API_PRIVATE_KEYS_DIR`. Write the decrypted
21708
+ * `.p8` under that exact name into a fresh private temp dir and return the dir so the
21709
+ * caller can point `API_PRIVATE_KEYS_DIR` at it (and remove it afterward — it holds
21710
+ * the unencrypted signing key).
21711
+ */
21712
+ const writeP8KeyDir = (credentials) => Effect.promise(async () => {
21713
+ const dir = await mkdtemp(path.join(tmpdir(), "better-update-asc-"));
21714
+ await writeFile(path.join(dir, `AuthKey_${credentials.keyId}.p8`), credentials.p8Pem, "utf8");
21715
+ return dir;
21716
+ });
21717
+ const removeKeyDir = (dir) => Effect.promise(async () => {
21718
+ await rm(dir, {
21719
+ recursive: true,
21720
+ force: true
21721
+ });
21701
21722
  });
21702
21723
  const baseAltoolArgs = (ipaPath) => [
21703
21724
  "--upload-app",
@@ -21722,15 +21743,12 @@ const buildAltoolArgs = (params) => Effect.gen(function* () {
21722
21743
  code: "SUBMISSION_ASC_KEY_FETCH_FAILED",
21723
21744
  message: "ASC API key is required for an asc-api-key upload but was not resolved."
21724
21745
  });
21725
- const p8Path = yield* writeP8ForAltool(params.ascCredentials);
21726
21746
  return [
21727
21747
  ...baseAltoolArgs(params.ipaPath),
21728
21748
  "--apiKey",
21729
21749
  params.ascCredentials.keyId,
21730
21750
  "--apiIssuer",
21731
- params.ascCredentials.issuerId,
21732
- "--apiKeyDir",
21733
- path.dirname(p8Path)
21751
+ params.ascCredentials.issuerId
21734
21752
  ];
21735
21753
  });
21736
21754
  const runIosSubmit = (inputs) => Effect.gen(function* () {
@@ -21756,7 +21774,7 @@ const runIosSubmit = (inputs) => Effect.gen(function* () {
21756
21774
  ipaPath
21757
21775
  });
21758
21776
  yield* patchSubmissionStatus(inputs.api, inputs.submissionId, { status: "IN_PROGRESS" });
21759
- const result = yield* runAltool(altoolArgs);
21777
+ const result = inputs.auth.kind === "asc-api-key" && ascCredentials !== null ? yield* Effect.acquireUseRelease(writeP8KeyDir(ascCredentials), (keyDir) => runAltool(altoolArgs, { API_PRIVATE_KEYS_DIR: keyDir }), (keyDir) => removeKeyDir(keyDir)) : yield* runAltool(altoolArgs);
21760
21778
  if (result.exitCode !== 0) {
21761
21779
  yield* patchSubmissionStatus(inputs.api, inputs.submissionId, {
21762
21780
  status: "ERRORED",
@@ -35046,21 +35064,34 @@ const createApp = (cookieCtx, name, companyName, input) => wrapConnect("apple-cr
35046
35064
  });
35047
35065
  }));
35048
35066
  /**
35049
- * Best-effort App Store name default. EAS-style: fall back to the Expo config's
35050
- * `name` (app.json `expo.name`) so a never-empty default reaches `App.createAsync`
35051
- * Apple's iris API 500s on a blank name. Returns `undefined` for non-Expo
35052
- * projects (no `@expo/config`) so the caller drops to a bundle-id placeholder.
35067
+ * Best-effort App Store name default to pre-fill the prompt with. Prefers the
35068
+ * Expo config's `name` (app.json `expo.name`), then the passed-in fallback (the
35069
+ * better-update project name) so non-Expo projects which have no `@expo/config`
35070
+ * still get a sensible default. Returns `undefined` when neither is available.
35053
35071
  */
35054
- const resolveDefaultAppName = (projectRoot) => readExpoConfig(projectRoot).pipe(Effect.map((config) => config.name?.trim() ? config.name.trim() : void 0), Effect.orElseSucceed(() => void 0));
35072
+ const resolveDefaultAppName = (input) => readExpoConfig(input.projectRoot).pipe(Effect.map((config) => config.name?.trim() ? config.name.trim() : void 0), Effect.orElseSucceed(() => void 0), Effect.map((expoName) => {
35073
+ const fallback = input.defaultAppName?.trim();
35074
+ return expoName ?? (fallback || void 0);
35075
+ }));
35076
+ /** Reject a blank app name so the prompt re-asks instead of 500'ing `App.createAsync`. */
35077
+ const requireNonEmptyName = (value) => value?.trim() ? void 0 : "An app name is required.";
35055
35078
  /**
35056
- * The App Store name to create the app under. A configured `appName` wins; else
35057
- * prompt, pre-filled with app.json `expo.name` so an empty Enter still names the
35058
- * app. Trimmed so a blank value is caught before reaching `App.createAsync`.
35079
+ * The App Store name to create the app under. A non-empty configured `appName`
35080
+ * wins; else prompt pre-filled with the resolved default (Expo `expo.name` or
35081
+ * the better-update project name) and *required*, so an empty Enter re-asks
35082
+ * rather than reaching `App.createAsync` with a blank name (which Apple 500s).
35059
35083
  */
35060
35084
  const resolveAppName = (input) => Effect.gen(function* () {
35061
- if (input.appName !== void 0) return input.appName.trim();
35062
- const defaultName = yield* resolveDefaultAppName(input.projectRoot);
35063
- return (yield* promptText("App name (as shown on the App Store)", defaultName === void 0 ? { placeholder: input.bundleIdentifier } : { defaultValue: defaultName })).trim();
35085
+ const configured = input.appName?.trim();
35086
+ if (configured) return configured;
35087
+ const defaultName = yield* resolveDefaultAppName(input);
35088
+ return (yield* promptText("App name (as shown on the App Store)", defaultName === void 0 ? {
35089
+ placeholder: input.bundleIdentifier,
35090
+ validate: requireNonEmptyName
35091
+ } : {
35092
+ initialValue: defaultName,
35093
+ validate: requireNonEmptyName
35094
+ })).trim();
35064
35095
  });
35065
35096
  const ensureAscAppForSubmit = (input) => Effect.gen(function* () {
35066
35097
  const ctx = buildTokenRequestContext(input.credentials);
@@ -35269,16 +35300,20 @@ const submitIosBranch = (params) => Effect.gen(function* () {
35269
35300
  return false;
35270
35301
  }
35271
35302
  let resolvedAscAppId = iosProfile?.ascAppId;
35272
- if (wantsConfig && resolvedAscAppId === void 0 && ascCredentials !== null) resolvedAscAppId = toOptional(yield* ensureAscAppForSubmit({
35273
- credentials: ascCredentials,
35274
- projectRoot: params.projectRoot,
35275
- profileName: params.profile,
35276
- bundleIdentifier: iosConfig.bundleIdentifier,
35277
- appName: iosProfile?.appName,
35278
- sku: iosProfile?.sku,
35279
- companyName: iosProfile?.companyName,
35280
- primaryLocale: iosProfile?.language
35281
- }));
35303
+ if (wantsConfig && resolvedAscAppId === void 0 && ascCredentials !== null) {
35304
+ const defaultAppName = yield* api.projects.get({ path: { id: params.projectId } }).pipe(Effect.map((project) => project.name), Effect.orElseSucceed(() => void 0));
35305
+ resolvedAscAppId = toOptional(yield* ensureAscAppForSubmit({
35306
+ credentials: ascCredentials,
35307
+ projectRoot: params.projectRoot,
35308
+ profileName: params.profile,
35309
+ bundleIdentifier: iosConfig.bundleIdentifier,
35310
+ appName: iosProfile?.appName,
35311
+ defaultAppName,
35312
+ sku: iosProfile?.sku,
35313
+ companyName: iosProfile?.companyName,
35314
+ primaryLocale: iosProfile?.language
35315
+ }));
35316
+ }
35282
35317
  yield* printHuman(auth.kind === "app-specific-password" ? "Running xcrun altool upload (Apple ID app-specific password)..." : "Running xcrun altool upload (ASC API key)...");
35283
35318
  yield* runIosSubmit({
35284
35319
  api,
@@ -35318,6 +35353,7 @@ const runFlow = (api, projectId, args) => Effect.gen(function* () {
35318
35353
  if (args.platform === "ios" && iosConfig !== void 0) {
35319
35354
  if (!(yield* submitIosBranch({
35320
35355
  api,
35356
+ projectId,
35321
35357
  submissionId: submission.id,
35322
35358
  projectRoot: args.projectRoot,
35323
35359
  profile: args.profile,