@better-update/cli 0.47.2 → 0.47.4
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 +82 -39
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
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.
|
|
38
|
+
var version = "0.47.4";
|
|
39
39
|
|
|
40
40
|
//#endregion
|
|
41
41
|
//#region src/lib/interactive-mode.ts
|
|
@@ -21280,6 +21280,66 @@ const commitEdit = (params) => callJsonRaw({
|
|
|
21280
21280
|
label: "edits.commit"
|
|
21281
21281
|
});
|
|
21282
21282
|
|
|
21283
|
+
//#endregion
|
|
21284
|
+
//#region src/lib/altool.ts
|
|
21285
|
+
/**
|
|
21286
|
+
* Thin wrapper around `xcrun altool` for App Store delivery: run it without
|
|
21287
|
+
* throwing (failures come back as an {@link ExecResult} with a non-zero exit), and
|
|
21288
|
+
* extract the real failure reason from altool's `--output-format xml` output.
|
|
21289
|
+
*/
|
|
21290
|
+
const execFileAsync = promisify(execFile);
|
|
21291
|
+
const ExecErrorSchema = Schema.Struct({
|
|
21292
|
+
code: Schema.optional(Schema.Number),
|
|
21293
|
+
stdout: Schema.optional(Schema.String),
|
|
21294
|
+
stderr: Schema.optional(Schema.String)
|
|
21295
|
+
});
|
|
21296
|
+
const runAltool = (args, extraEnv) => Effect.tryPromise({
|
|
21297
|
+
try: async () => {
|
|
21298
|
+
const options = extraEnv ? {
|
|
21299
|
+
encoding: "utf8",
|
|
21300
|
+
env: {
|
|
21301
|
+
...process.env,
|
|
21302
|
+
...extraEnv
|
|
21303
|
+
}
|
|
21304
|
+
} : { encoding: "utf8" };
|
|
21305
|
+
const { stdout, stderr } = await execFileAsync("xcrun", ["altool", ...args], options);
|
|
21306
|
+
return {
|
|
21307
|
+
exitCode: 0,
|
|
21308
|
+
stdout,
|
|
21309
|
+
stderr
|
|
21310
|
+
};
|
|
21311
|
+
},
|
|
21312
|
+
catch: (error) => {
|
|
21313
|
+
const parsed = Schema.decodeUnknownSync(ExecErrorSchema, { onExcessProperty: "ignore" })(typeof error === "object" && error !== null ? error : {});
|
|
21314
|
+
const stdout = parsed.stdout ?? "";
|
|
21315
|
+
const stderr = parsed.stderr ?? String(error);
|
|
21316
|
+
return {
|
|
21317
|
+
exitCode: parsed.code ?? 1,
|
|
21318
|
+
stdout,
|
|
21319
|
+
stderr: stderr === "" ? String(error) : stderr
|
|
21320
|
+
};
|
|
21321
|
+
}
|
|
21322
|
+
}).pipe(Effect.catchAll((result) => Effect.succeed(result)));
|
|
21323
|
+
const unescapeXml = (value) => value.replaceAll("<", "<").replaceAll(">", ">").replaceAll(""", "\"").replaceAll("'", "'").replaceAll("&", "&");
|
|
21324
|
+
/**
|
|
21325
|
+
* altool with `--output-format xml` writes the real failure detail to a stdout
|
|
21326
|
+
* plist (`product-errors`), leaving stderr with only a generic
|
|
21327
|
+
* "UPLOAD FAILED … ExitFailure (N)" banner. Pull the human-readable messages so
|
|
21328
|
+
* the surfaced error names the actual cause (asset validation, export compliance,
|
|
21329
|
+
* a duplicate build number, a missing app record…).
|
|
21330
|
+
*/
|
|
21331
|
+
const extractAltoolErrors = (xml) => [...xml.matchAll(/<key>message<\/key>\s*<string>(?<message>[\s\S]*?)<\/string>/gu)].flatMap((match) => {
|
|
21332
|
+
const message = match.groups?.["message"];
|
|
21333
|
+
return message === void 0 ? [] : [unescapeXml(message).trim()];
|
|
21334
|
+
});
|
|
21335
|
+
/** Best human-readable altool failure detail: parsed product-errors, else raw streams. */
|
|
21336
|
+
const altoolFailureDetail = (result) => {
|
|
21337
|
+
const messages = extractAltoolErrors(result.stdout);
|
|
21338
|
+
if (messages.length > 0) return messages.join("; ");
|
|
21339
|
+
const combined = `${result.stdout}\n${result.stderr}`.trim();
|
|
21340
|
+
return combined.length > 0 ? combined : "no output";
|
|
21341
|
+
};
|
|
21342
|
+
|
|
21283
21343
|
//#endregion
|
|
21284
21344
|
//#region src/lib/apple-asc-connect.ts
|
|
21285
21345
|
/**
|
|
@@ -21537,32 +21597,6 @@ const applyTestFlightConfig = (inputs) => Effect.gen(function* () {
|
|
|
21537
21597
|
|
|
21538
21598
|
//#endregion
|
|
21539
21599
|
//#region src/application/submit-flow.ts
|
|
21540
|
-
const execFileAsync = promisify(execFile);
|
|
21541
|
-
const ExecErrorSchema = Schema.Struct({
|
|
21542
|
-
code: Schema.optional(Schema.Number),
|
|
21543
|
-
stdout: Schema.optional(Schema.String),
|
|
21544
|
-
stderr: Schema.optional(Schema.String)
|
|
21545
|
-
});
|
|
21546
|
-
const runAltool = (args) => Effect.tryPromise({
|
|
21547
|
-
try: async () => {
|
|
21548
|
-
const { stdout, stderr } = await execFileAsync("xcrun", ["altool", ...args]);
|
|
21549
|
-
return {
|
|
21550
|
-
exitCode: 0,
|
|
21551
|
-
stdout,
|
|
21552
|
-
stderr
|
|
21553
|
-
};
|
|
21554
|
-
},
|
|
21555
|
-
catch: (error) => {
|
|
21556
|
-
const parsed = Schema.decodeUnknownSync(ExecErrorSchema, { onExcessProperty: "ignore" })(typeof error === "object" && error !== null ? error : {});
|
|
21557
|
-
const stdout = parsed.stdout ?? "";
|
|
21558
|
-
const stderr = parsed.stderr ?? String(error);
|
|
21559
|
-
return {
|
|
21560
|
-
exitCode: parsed.code ?? 1,
|
|
21561
|
-
stdout,
|
|
21562
|
-
stderr: stderr === "" ? String(error) : stderr
|
|
21563
|
-
};
|
|
21564
|
-
}
|
|
21565
|
-
}).pipe(Effect.catchAll((result) => Effect.succeed(result)));
|
|
21566
21600
|
var CliSubmitError = class extends Schema.TaggedError()("CliSubmitError", {
|
|
21567
21601
|
code: Schema.String,
|
|
21568
21602
|
message: Schema.String
|
|
@@ -21695,11 +21729,23 @@ const resolveAscUploadCredentials = (params) => Effect.gen(function* () {
|
|
|
21695
21729
|
p8Pem: creds.p8Pem
|
|
21696
21730
|
})), Effect.catchAll((error) => printHuman(`Could not prepare ASC API key ${credsKeyId} (${messageOf(error)}).`).pipe(Effect.as(null))));
|
|
21697
21731
|
});
|
|
21698
|
-
/**
|
|
21699
|
-
|
|
21700
|
-
|
|
21701
|
-
|
|
21702
|
-
|
|
21732
|
+
/**
|
|
21733
|
+
* `altool --apiKey <id>` searches for a file named *exactly* `AuthKey_<id>.p8` in
|
|
21734
|
+
* the standard `private_keys` dirs plus `$API_PRIVATE_KEYS_DIR`. Write the decrypted
|
|
21735
|
+
* `.p8` under that exact name into a fresh private temp dir and return the dir so the
|
|
21736
|
+
* caller can point `API_PRIVATE_KEYS_DIR` at it (and remove it afterward — it holds
|
|
21737
|
+
* the unencrypted signing key).
|
|
21738
|
+
*/
|
|
21739
|
+
const writeP8KeyDir = (credentials) => Effect.promise(async () => {
|
|
21740
|
+
const dir = await mkdtemp(path.join(tmpdir(), "better-update-asc-"));
|
|
21741
|
+
await writeFile(path.join(dir, `AuthKey_${credentials.keyId}.p8`), credentials.p8Pem, "utf8");
|
|
21742
|
+
return dir;
|
|
21743
|
+
});
|
|
21744
|
+
const removeKeyDir = (dir) => Effect.promise(async () => {
|
|
21745
|
+
await rm(dir, {
|
|
21746
|
+
recursive: true,
|
|
21747
|
+
force: true
|
|
21748
|
+
});
|
|
21703
21749
|
});
|
|
21704
21750
|
const baseAltoolArgs = (ipaPath) => [
|
|
21705
21751
|
"--upload-app",
|
|
@@ -21724,15 +21770,12 @@ const buildAltoolArgs = (params) => Effect.gen(function* () {
|
|
|
21724
21770
|
code: "SUBMISSION_ASC_KEY_FETCH_FAILED",
|
|
21725
21771
|
message: "ASC API key is required for an asc-api-key upload but was not resolved."
|
|
21726
21772
|
});
|
|
21727
|
-
const p8Path = yield* writeP8ForAltool(params.ascCredentials);
|
|
21728
21773
|
return [
|
|
21729
21774
|
...baseAltoolArgs(params.ipaPath),
|
|
21730
21775
|
"--apiKey",
|
|
21731
21776
|
params.ascCredentials.keyId,
|
|
21732
21777
|
"--apiIssuer",
|
|
21733
|
-
params.ascCredentials.issuerId
|
|
21734
|
-
"--apiKeyDir",
|
|
21735
|
-
path.dirname(p8Path)
|
|
21778
|
+
params.ascCredentials.issuerId
|
|
21736
21779
|
];
|
|
21737
21780
|
});
|
|
21738
21781
|
const runIosSubmit = (inputs) => Effect.gen(function* () {
|
|
@@ -21758,12 +21801,12 @@ const runIosSubmit = (inputs) => Effect.gen(function* () {
|
|
|
21758
21801
|
ipaPath
|
|
21759
21802
|
});
|
|
21760
21803
|
yield* patchSubmissionStatus(inputs.api, inputs.submissionId, { status: "IN_PROGRESS" });
|
|
21761
|
-
const result = yield* runAltool(altoolArgs);
|
|
21804
|
+
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);
|
|
21762
21805
|
if (result.exitCode !== 0) {
|
|
21763
21806
|
yield* patchSubmissionStatus(inputs.api, inputs.submissionId, {
|
|
21764
21807
|
status: "ERRORED",
|
|
21765
21808
|
errorCode: "SUBMISSION_SERVICE_IOS_ALTOOL_FAILED",
|
|
21766
|
-
errorMessage: `xcrun altool exited ${String(result.exitCode)}: ${result
|
|
21809
|
+
errorMessage: `xcrun altool exited ${String(result.exitCode)}: ${altoolFailureDetail(result)}`
|
|
21767
21810
|
});
|
|
21768
21811
|
return { status: "ERRORED" };
|
|
21769
21812
|
}
|