@hot-updater/cli-tools 1.2.0-rc.8 → 1.2.0-rc.9

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
@@ -25310,6 +25310,103 @@ const printBanner = (version) => {
25310
25310
  console.log(banner(version));
25311
25311
  };
25312
25312
  //#endregion
25313
+ //#region src/credentialEnvNames.ts
25314
+ /**
25315
+ * The environment variables that override each kind, one entry per field.
25316
+ * A field with two names takes a file path or the file's contents; the path
25317
+ * wins when both are set.
25318
+ */
25319
+ const CREDENTIAL_ENV_NAMES = {
25320
+ "asc-api-key": [
25321
+ ["HOT_UPDATER_ASC_KEY_PATH", "HOT_UPDATER_ASC_KEY"],
25322
+ ["HOT_UPDATER_ASC_KEY_ID"],
25323
+ ["HOT_UPDATER_ASC_ISSUER_ID"]
25324
+ ],
25325
+ "android-keystore": [
25326
+ ["HOT_UPDATER_ANDROID_KEYSTORE_PATH", "HOT_UPDATER_ANDROID_KEYSTORE_BASE64"],
25327
+ ["HOT_UPDATER_ANDROID_KEYSTORE_PASSWORD"],
25328
+ ["HOT_UPDATER_ANDROID_KEY_ALIAS"],
25329
+ ["HOT_UPDATER_ANDROID_KEY_PASSWORD"]
25330
+ ],
25331
+ "play-service-account": [["HOT_UPDATER_PLAY_SERVICE_ACCOUNT_PATH", "HOT_UPDATER_PLAY_SERVICE_ACCOUNT_JSON"]]
25332
+ };
25333
+ //#endregion
25334
+ //#region src/credentialFiles.ts
25335
+ const TERMINATION_SIGNALS = [
25336
+ "SIGHUP",
25337
+ "SIGINT",
25338
+ "SIGTERM"
25339
+ ];
25340
+ const liveDirectories = /* @__PURE__ */ new Set();
25341
+ const onSignal = (signal) => {
25342
+ removeLiveDirectories();
25343
+ if (process.listenerCount(signal) === 0) process.kill(process.pid, signal);
25344
+ };
25345
+ const removeLiveDirectories = () => {
25346
+ for (const directory of liveDirectories) rmSync(directory, {
25347
+ force: true,
25348
+ recursive: true
25349
+ });
25350
+ liveDirectories.clear();
25351
+ stopListening();
25352
+ };
25353
+ const startListening = () => {
25354
+ process.on("exit", removeLiveDirectories);
25355
+ for (const signal of TERMINATION_SIGNALS) process.on(signal, onSignal);
25356
+ };
25357
+ const stopListening = () => {
25358
+ process.off("exit", removeLiveDirectories);
25359
+ for (const signal of TERMINATION_SIGNALS) process.off(signal, onSignal);
25360
+ };
25361
+ const trackDirectory = (directory) => {
25362
+ if (liveDirectories.size === 0) startListening();
25363
+ liveDirectories.add(directory);
25364
+ };
25365
+ const untrackDirectory = (directory) => {
25366
+ if (liveDirectories.delete(directory) && liveDirectories.size === 0) stopListening();
25367
+ };
25368
+ /**
25369
+ * Creates the temporary files of one credential resolver: a `mkdtemp`
25370
+ * directory, created on the first write and removed by `dispose` or, if the
25371
+ * process exits first, on `exit` or a termination signal.
25372
+ */
25373
+ const createCredentialFiles = () => {
25374
+ let directory;
25375
+ let disposal;
25376
+ const ensureDirectory = () => {
25377
+ directory ??= fs$2.mkdtemp(path$1.join(os.tmpdir(), "hot-updater-credentials-")).then((created) => {
25378
+ trackDirectory(created);
25379
+ return created;
25380
+ });
25381
+ return directory;
25382
+ };
25383
+ return {
25384
+ write: async (fileName, contents) => {
25385
+ if (disposal) throw new Error("The credential files have been disposed.");
25386
+ if (typeof contents === "string") registerSecretValue(contents);
25387
+ const fileDirectory = await fs$2.mkdtemp(path$1.join(await ensureDirectory(), "item-"));
25388
+ const filePath = path$1.join(fileDirectory, path$1.basename(fileName));
25389
+ await fs$2.writeFile(filePath, contents, {
25390
+ flag: "wx",
25391
+ mode: 384
25392
+ });
25393
+ return filePath;
25394
+ },
25395
+ dispose: () => {
25396
+ disposal ??= (async () => {
25397
+ const created = await directory?.catch(() => void 0);
25398
+ if (!created) return;
25399
+ await fs$2.rm(created, {
25400
+ force: true,
25401
+ recursive: true
25402
+ });
25403
+ untrackDirectory(created);
25404
+ })();
25405
+ return disposal;
25406
+ }
25407
+ };
25408
+ };
25409
+ //#endregion
25313
25410
  //#region src/initOptions.ts
25314
25411
  var InitError = class extends Error {
25315
25412
  name = "InitError";
@@ -25535,7 +25632,7 @@ const assertPrivateDirectory = async (directory) => {
25535
25632
  if (isMissing(error)) return void 0;
25536
25633
  throw error;
25537
25634
  });
25538
- if (stats && !stats.isDirectory()) throw new Error(`localSecrets() keeps secrets in a directory, and ${directory} is not one. Set its path to a directory.`);
25635
+ if (stats && !stats.isDirectory()) throw new Error([`localSecrets() can't keep secrets in ${directory}.`, "It is not a directory: set its path to one."].join("\n"));
25539
25636
  const real = realpathThroughParents(directory);
25540
25637
  let probe = real;
25541
25638
  while (!existsSync(probe) && path$1.dirname(probe) !== probe) probe = path$1.dirname(probe);
@@ -25543,7 +25640,11 @@ const assertPrivateDirectory = async (directory) => {
25543
25640
  if (!root) return;
25544
25641
  const top = realpath(root);
25545
25642
  if (isIgnoredByGit(top, real)) return;
25546
- throw new Error(`localSecrets() refuses ${directory}: it is in the git repository ${top}, which does not ignore it, so the secrets in it could be committed. Add ${path$1.relative(top, real).split(path$1.sep).join("/")}/ to that repository's .gitignore, or keep the secrets outside it with localSecrets({ path }).`);
25643
+ throw new Error([
25644
+ `localSecrets() refuses ${directory}: its secrets could be committed.`,
25645
+ `The git repository ${top} doesn't ignore it.`,
25646
+ `Add ${path$1.relative(top, real).split(path$1.sep).join("/")}/ to its .gitignore, or set localSecrets({ path }).`
25647
+ ].join("\n"));
25547
25648
  };
25548
25649
  /** Makes `directory` 0700, and every directory it creates on the way. */
25549
25650
  const makePrivateDirectory = async (directory) => {
@@ -25611,27 +25712,6 @@ const readLocalKey = (name, cwd) => defaultStore.read(name, cwd);
25611
25712
  /** Stores `value` as `name` in `localSecrets()`'s default store. */
25612
25713
  const writeLocalKey = (name, value, cwd) => defaultStore.write(name, value, cwd);
25613
25714
  //#endregion
25614
- //#region src/credentialEnvNames.ts
25615
- /**
25616
- * The environment variables that override each kind, one entry per field.
25617
- * A field with two names takes a file path or the file's contents; the path
25618
- * wins when both are set.
25619
- */
25620
- const CREDENTIAL_ENV_NAMES = {
25621
- "asc-api-key": [
25622
- ["HOT_UPDATER_ASC_KEY_PATH", "HOT_UPDATER_ASC_KEY"],
25623
- ["HOT_UPDATER_ASC_KEY_ID"],
25624
- ["HOT_UPDATER_ASC_ISSUER_ID"]
25625
- ],
25626
- "android-keystore": [
25627
- ["HOT_UPDATER_ANDROID_KEYSTORE_PATH", "HOT_UPDATER_ANDROID_KEYSTORE_BASE64"],
25628
- ["HOT_UPDATER_ANDROID_KEYSTORE_PASSWORD"],
25629
- ["HOT_UPDATER_ANDROID_KEY_ALIAS"],
25630
- ["HOT_UPDATER_ANDROID_KEY_PASSWORD"]
25631
- ],
25632
- "play-service-account": [["HOT_UPDATER_PLAY_SERVICE_ACCOUNT_PATH", "HOT_UPDATER_PLAY_SERVICE_ACCOUNT_JSON"]]
25633
- };
25634
- //#endregion
25635
25715
  //#region src/reservedVariables.ts
25636
25716
  /**
25637
25717
  * Variables that change how a process loads code or which credentials Hot
@@ -25883,8 +25963,8 @@ const list$1 = (names) => names.length <= 2 ? names.join(" and ") : `${names.sli
25883
25963
  * });
25884
25964
  */
25885
25965
  const secret = (name) => {
25886
- if (typeof name !== "string" || !isSecretName(name)) throw new Error(`secret() takes up to 64 letters, digits, ".", "_", and "-", starting with a letter or digit, and ${JSON.stringify(name)} is not that.`);
25887
- if (isReservedVariableName(name)) throw new Error(`secret() refuses ${name}: it changes how a process loads code or which credentials Hot Updater uses.`);
25966
+ if (typeof name !== "string" || !isSecretName(name)) throw new Error([`secret() can't take the name ${JSON.stringify(name)}.`, "Use up to 64 letters, digits, \".\", \"_\", \"-\"; start with a letter or digit."].join("\n"));
25967
+ if (isReservedVariableName(name)) throw new Error([`secret() refuses ${name}.`, "It changes how a process loads code, or the credentials Hot Updater uses."].join("\n"));
25888
25968
  const scope = globalThis[LOADING_SCOPE];
25889
25969
  if (scope) (scope.names ??= /* @__PURE__ */ new Set()).add(name);
25890
25970
  let read;
@@ -25914,7 +25994,11 @@ const readSecretRef = async (name, scope) => {
25914
25994
  return fromEnv;
25915
25995
  }
25916
25996
  const secrets = getConfiguredAdapter(scope?.secrets ?? getConfiguredSecrets());
25917
- if (secrets === void 0) throw new Error(`secret("${name}") has no value: set ${variable} in the environment or .env.hotupdater, or set the config's secrets to a team store.`);
25997
+ if (secrets === void 0) throw new Error([
25998
+ `secret("${name}") has no value.`,
25999
+ `Set ${variable} in the environment or .env.hotupdater.`,
26000
+ "Or set the config's secrets to a team store."
26001
+ ].join("\n"));
25918
26002
  let values;
25919
26003
  if (scope) {
25920
26004
  scope.read ??= readSecrets(secrets, batchedNames(scope, name, fileEnv), cwd).catch((error) => {
@@ -25925,690 +26009,1071 @@ const readSecretRef = async (name, scope) => {
25925
26009
  }
25926
26010
  if (!values || !Object.hasOwn(values, name)) values = await readSecrets(secrets, [name], cwd);
25927
26011
  const value = Object.hasOwn(values, name) ? values[name] : void 0;
25928
- if (typeof value !== "string" || value === "") throw new Error(`secret("${name}") has no value: ${variable} is not set in the environment or .env.hotupdater, and the ${secrets.name} secrets adapter does not hold it (${describeSecret(secrets, name)}).`);
26012
+ if (typeof value !== "string" || value === "") throw new Error([
26013
+ `secret("${name}") has no value.`,
26014
+ `It isn't in ${describeSecret(secrets, name)}.`,
26015
+ `Store it there, or set ${variable}.`
26016
+ ].join("\n"));
25929
26017
  registerSecretValue(value);
25930
26018
  return value;
25931
26019
  };
25932
26020
  //#endregion
25933
- //#region src/bundleSigning.ts
25934
- /** The secret `signing: secret("bundle-signing-key")` names, which `secrets issue` creates. */
25935
- const BUNDLE_SIGNING_KEY_SECRET = "bundle-signing-key";
25936
- /** The command that creates and stores a bundle signing key. */
25937
- const ISSUE_SIGNING_KEY_COMMAND = `npx hot-updater secrets issue ${BUNDLE_SIGNING_KEY_SECRET}`;
25938
- /** The command that stores a bundle signing key you have. */
25939
- const PUT_SIGNING_KEY_COMMAND = `npx hot-updater secrets put ${BUNDLE_SIGNING_KEY_SECRET} <key.pem>`;
25940
- /** What deploy and doctor say when the config sets no `signing`. */
25941
- const UNSIGNED_BUNDLES_WARNING = "Bundles are unsigned.";
25942
- /** The next step after `UNSIGNED_BUNDLES_WARNING`. */
25943
- const UNSIGNED_BUNDLES_NEXT_STEP = "To sign them, run:";
25944
- /** What deploy and doctor say when `signing` is no shape it takes. */
25945
- const INVALID_SIGNING_MESSAGE = `signing takes secret("${BUNDLE_SIGNING_KEY_SECRET}").`;
25946
- /** The next step after `INVALID_SIGNING_MESSAGE`. */
25947
- const INVALID_SIGNING_NEXT_STEP = "Store your key:";
25948
- const invalid = (message) => ({
25949
- status: "invalid",
25950
- message
25951
- });
25952
- const LOCAL_FIELDS = [
25953
- "enabled",
25954
- "privateKeyPath",
25955
- "privateKey"
25956
- ];
25957
- const readBundleSigningState = (signing) => {
25958
- if (signing === void 0) return { status: "unsigned" };
25959
- if (isSecretRef(signing)) return {
25960
- status: "on",
25961
- key: { secret: signing.name }
25962
- };
25963
- if (typeof signing !== "object" || signing === null || !Object.keys(signing).every((key) => LOCAL_FIELDS.includes(key))) return invalid(INVALID_SIGNING_MESSAGE);
25964
- const { enabled, privateKey, privateKeyPath } = signing;
25965
- if (enabled === false) return { status: "off" };
25966
- if (enabled !== true) return invalid(INVALID_SIGNING_MESSAGE);
25967
- if (privateKey !== void 0 && privateKeyPath !== void 0) return invalid("signing takes privateKey or privateKeyPath, not both.");
25968
- if (privateKey !== void 0) return isSecretRef(privateKey) ? {
25969
- status: "on",
25970
- key: { secret: privateKey.name }
25971
- } : invalid("signing.privateKey takes secret(\"<name>\").");
25972
- if (typeof privateKeyPath !== "string" || !privateKeyPath.trim()) return invalid("signing takes privateKeyPath, or privateKey as secret(\"<name>\").");
25973
- return {
25974
- status: "on",
25975
- key: { privateKeyPath }
25976
- };
25977
- };
25978
- /** The `secret()` the config's `signing` reads its private key from, if any. */
25979
- const bundleSigningSecretRef = (signing) => {
25980
- if (isSecretRef(signing)) return signing;
25981
- const privateKey = typeof signing === "object" && signing !== null ? Reflect.get(signing, "privateKey") : void 0;
25982
- return isSecretRef(privateKey) ? privateKey : void 0;
26021
+ //#region src/credentialResolver.ts
26022
+ /** Passwords are read as set: trimming would change them. */
26023
+ const UNTRIMMED_ENV_NAMES = /* @__PURE__ */ new Set(["HOT_UPDATER_ANDROID_KEYSTORE_PASSWORD", "HOT_UPDATER_ANDROID_KEY_PASSWORD"]);
26024
+ const describeField = ([name, alternative]) => alternative ? `${name} (or ${alternative})` : name;
26025
+ const describeFields = (fields) => {
26026
+ const described = fields.map(describeField);
26027
+ if (described.length <= 2) return described.join(" and ");
26028
+ return `${described.slice(0, -1).join(", ")}, and ${described.at(-1)}`;
25983
26029
  };
25984
26030
  /**
25985
- * A bundle signing failure whose message the CLI prints as it is: it names
25986
- * the secret and the next step, and quotes no key material.
26031
+ * A command to print, broken at spaces with ` \` once a line would pass
26032
+ * 74 columns, each continuation indented two spaces, so a terminal never
26033
+ * wraps it mid-word and it still pastes as one command.
25987
26034
  */
25988
- var BundleSigningKeyError = class extends Error {
25989
- constructor(message, options) {
25990
- super(message, options);
25991
- this.name = "HotUpdaterBundleSigningKeyError";
25992
- }
25993
- };
25994
- const FILE_HASH_PATTERN = /^[a-f\d]{64}$/iu;
25995
- const resolvePath = (cwd, filePath) => path$1.isAbsolute(filePath) ? filePath : path$1.resolve(cwd, filePath);
25996
- const parseRsaPublicKey = (publicKeyPEM) => {
25997
- try {
25998
- const normalizedPublicKey = publicKeyPEM.trim();
25999
- if (!normalizedPublicKey.startsWith("-----BEGIN PUBLIC KEY-----") || !normalizedPublicKey.endsWith("-----END PUBLIC KEY-----")) throw new Error("not spki");
26000
- const publicKey = crypto$1.createPublicKey(normalizedPublicKey);
26001
- if (publicKey.asymmetricKeyType !== "rsa" || (publicKey.asymmetricKeyDetails?.modulusLength ?? 0) < 2048) throw new Error("not rsa");
26002
- return publicKey;
26003
- } catch {
26004
- throw new Error("Bundle signing public key must be a valid RSA SPKI PEM key with a modulus of at least 2048 bits.");
26005
- }
26006
- };
26007
- /** The SPKI PEM of `key`, a public key or the private key it derives from. */
26008
- const exportPublicKey = (key) => (key.type === "private" ? crypto$1.createPublicKey(key) : key).export({
26009
- type: "spki",
26010
- format: "pem"
26011
- }).toString();
26012
- const readBundleSigningPublicKeyFile = async (publicKeyPath, options = {}) => {
26013
- if (!publicKeyPath.trim()) throw new Error("Bundle signing public key path is required.");
26014
- try {
26015
- const publicKeyPEM = await fs$2.readFile(resolvePath(options.cwd ?? getCwd(), publicKeyPath), "utf8");
26016
- return exportPublicKey(parseRsaPublicKey(publicKeyPEM));
26017
- } catch {
26018
- throw new Error("Failed to read the bundle signing public key file.");
26019
- }
26020
- };
26021
- const parsePrivateKey = (pem) => {
26022
- const privateKey = crypto$1.createPrivateKey(pem);
26023
- if (privateKey.asymmetricKeyType !== "rsa" || (privateKey.asymmetricKeyDetails?.modulusLength ?? 0) < 2048) throw new Error("not rsa");
26024
- return privateKey;
26025
- };
26026
- const loadPrivateKeyFile = async (file) => {
26027
- try {
26028
- return parsePrivateKey(await fs$2.readFile(file, "utf8"));
26029
- } catch {
26030
- throw new BundleSigningKeyError("Failed to load the bundle signing private key from privateKeyPath.");
26035
+ const formatCommand = (command) => {
26036
+ const lines = [];
26037
+ let line = "";
26038
+ for (const word of command.split(" ")) {
26039
+ const next = line ? `${line} ${word}` : word;
26040
+ if (line && next.length > 72) {
26041
+ lines.push(`${line} \\`);
26042
+ line = ` ${word}`;
26043
+ } else line = next;
26031
26044
  }
26045
+ lines.push(line);
26046
+ return lines.join("\n");
26032
26047
  };
26033
26048
  /**
26034
- * The private key the `secrets` adapter holds as `name`: masked in the CLI's
26035
- * output before it is parsed, and kept only as a key object. A PEM written
26036
- * on one line, with `\n` for its line breaks, as a `.env.hotupdater` value
26037
- * or a CI variable may hold it, reads as the PEM.
26049
+ * `lead` and the command it names on one line when they fit 77 columns,
26050
+ * else the command on the lines after it.
26038
26051
  */
26039
- const loadPrivateKeySecret = async (secrets, name, cwd) => {
26040
- let value;
26041
- try {
26042
- const read = await secrets.get([name], { cwd });
26043
- value = Object.hasOwn(read, name) ? read[name] : void 0;
26044
- } catch (error) {
26045
- const reason = error instanceof Error ? error.message : String(error);
26046
- throw new BundleSigningKeyError(`The ${secrets.name} secrets adapter could not read the bundle signing key ${name}: ${reason}`, { cause: error });
26047
- }
26048
- if (typeof value !== "string" || !value.trim()) {
26049
- const issue = `npx hot-updater secrets issue ${BUNDLE_SIGNING_KEY_SECRET}${name === "bundle-signing-key" ? "" : ` ${name}`}`;
26050
- throw new BundleSigningKeyError(`signing reads the bundle signing key ${name}, which is not in the ${secrets.name} secrets adapter (${describeSecret(secrets, name)}).\nTo create one, run: ${issue}`);
26051
- }
26052
- registerSecretValue(value);
26053
- const pem = value.includes("\n") ? value : value.replaceAll("\\n", "\n");
26054
- if (pem !== value) registerSecretValue(pem);
26055
- try {
26056
- return parsePrivateKey(pem);
26057
- } catch {
26058
- throw new BundleSigningKeyError(`The bundle signing key ${name} in the ${secrets.name} secrets adapter is not an RSA private key in PEM of at least 2048 bits.`);
26059
- }
26052
+ const withLead = (lead, command) => lead.length + 1 + command.length <= 77 ? `${lead} ${command}` : `${lead}\n${formatCommand(command)}`;
26053
+ /**
26054
+ * Why a build has no upload keystore, steering away from a second key:
26055
+ * Play rejects builds signed with a key it does not have. Under a team's
26056
+ * adapter the keystore is someone's already, so it is imported, never
26057
+ * created; only `localSecrets()` alone offers a new one, for an app with
26058
+ * no `.aab` on Play yet.
26059
+ */
26060
+ const describeMissingKeystore = (store, item, name) => {
26061
+ const configured = getConfiguredAdapter(store);
26062
+ if (configured) return [
26063
+ `No ${item} in ${describeSecret(configured, name)}.`,
26064
+ "Ask whoever set it up to store it there, or import it with:",
26065
+ formatCommand(credentialPutCommand("android-keystore", name))
26066
+ ].join("\n");
26067
+ return [
26068
+ `No ${item} in the ${store.name} secrets adapter.`,
26069
+ withLead("Store the app's upload key:", credentialPutCommand("android-keystore", name)),
26070
+ withLead("No .aab on Play yet? Create one:", credentialIssueCommand("android-keystore", name))
26071
+ ].join("\n");
26072
+ };
26073
+ /** The file each `secrets put <name> <file>` command names. */
26074
+ const PUT_FILES = {
26075
+ "android-keystore": "<upload.jks>",
26076
+ "play-service-account": "<key.json>"
26060
26077
  };
26061
26078
  /**
26062
- * The private keys each `signing` value has read, by file or by secrets
26063
- * adapter, so the commands of one run that sign and derive the public key
26064
- * read the key once.
26079
+ * The command that stores the file you have as `name`: the service
26080
+ * account's JSON key, or the app's upload keystore. `--kind` is named only
26081
+ * for a name that is not the kind.
26065
26082
  */
26066
- const privateKeys = /* @__PURE__ */ new WeakMap();
26083
+ const credentialPutCommand = (kind, name = kind) => `npx hot-updater secrets put ${name}${name === kind ? "" : ` --kind ${kind}`} ${PUT_FILES[kind]}`;
26067
26084
  /**
26068
- * The private key `signing` signs with, or null when it signs nothing.
26069
- * Throws `INVALID_SIGNING_MESSAGE`, with the next step, for a value
26070
- * `signing` doesn't take.
26085
+ * The command that stores the key Hot Updater issues as `name`: an App
26086
+ * Store Connect API key after an Apple Account sign-in, and an upload
26087
+ * keystore it creates.
26071
26088
  */
26072
- const loadSigningKey = (signing, { cwd = getCwd(), secrets }) => {
26073
- const state = readBundleSigningState(signing);
26074
- if (state.status === "invalid") throw new BundleSigningKeyError(`${state.message}\n${INVALID_SIGNING_NEXT_STEP} ${PUT_SIGNING_KEY_COMMAND}`);
26075
- if (state.status !== "on") return null;
26076
- const store = secrets ?? getConfiguredSecrets();
26077
- const cacheKey = "secret" in state.key ? store : resolvePath(cwd, state.key.privateKeyPath);
26078
- const cache = privateKeys.get(signing) ?? /* @__PURE__ */ new Map();
26079
- privateKeys.set(signing, cache);
26080
- const cached = cache.get(cacheKey);
26081
- if (cached) return cached;
26082
- const pending = ("secret" in state.key ? loadPrivateKeySecret(store, state.key.secret, cwd) : loadPrivateKeyFile(cacheKey)).catch((error) => {
26083
- cache.delete(cacheKey);
26084
- throw error;
26085
- });
26086
- cache.set(cacheKey, pending);
26087
- return pending;
26088
- };
26089
- const signingFailure = (summary, error) => new Error(error instanceof BundleSigningKeyError ? error.message : `${summary} ${error instanceof Error ? error.message : String(error)}`, { cause: error });
26089
+ const credentialIssueCommand = (kind, name = kind) => `npx hot-updater secrets issue ${kind}${name === kind ? "" : ` ${name}`}`;
26090
+ /** The command group of each issued kind's shortcut, `<group> login`. */
26091
+ const CREDENTIAL_LOGIN_GROUPS = { "asc-api-key": "apple" };
26090
26092
  /**
26091
- * Prepares the run's bundle signing: the public key the app must embed, and
26092
- * a signer of SHA-256 file hashes that signs each hash once. Null when
26093
- * `signing` is omitted or `{ enabled: false }`.
26093
+ * The command people meet first that issues `kind` as `name`: its shortcut,
26094
+ * `apple login` for an App Store Connect API key, which takes nothing and
26095
+ * stores the kind's own name; for another name, `credentialIssueCommand`.
26094
26096
  */
26095
- const prepareBundleSigning = async (signing, options = {}) => {
26096
- const loading = loadSigningKey(signing, options);
26097
- if (!loading) return null;
26098
- let privateKey;
26099
- try {
26100
- privateKey = await loading;
26101
- } catch (error) {
26102
- throw signingFailure("Failed to read the bundle signing key.", error);
26103
- }
26104
- const signatures = /* @__PURE__ */ new Map();
26105
- return {
26106
- publicKey: exportPublicKey(privateKey),
26107
- signFileHash: async (fileHash) => {
26108
- if (!FILE_HASH_PATTERN.test(fileHash)) throw new Error("Bundle signing requires a 64-character hexadecimal file hash.");
26109
- const normalized = fileHash.toLowerCase();
26110
- const cached = signatures.get(normalized);
26111
- if (cached) return cached;
26112
- const signature = crypto$1.sign("RSA-SHA256", Buffer.from(normalized, "hex"), privateKey).toString("base64");
26113
- signatures.set(normalized, signature);
26114
- return signature;
26115
- }
26116
- };
26117
- };
26097
+ const credentialLoginCommand = (kind, name = kind) => name === kind ? `npx hot-updater ${CREDENTIAL_LOGIN_GROUPS[kind]} login` : credentialIssueCommand(kind, name);
26118
26098
  /**
26119
- * The public key of the configured bundle signing key, in SPKI PEM; null
26120
- * when `signing` signs nothing.
26099
+ * The command that stores a missing credential of `kind` as `name`: the
26100
+ * key Hot Updater issues after an Apple sign-in, or the file you have.
26121
26101
  */
26122
- const getBundleSigningPublicKey = async (signing, options = {}) => {
26123
- const loading = loadSigningKey(signing, options);
26124
- if (!loading) return null;
26125
- try {
26126
- return exportPublicKey(await loading);
26127
- } catch (error) {
26128
- throw signingFailure("Failed to read the bundle signing key.", error);
26102
+ const credentialStoreCommand = (kind, name = kind) => kind === "asc-api-key" ? credentialLoginCommand(kind, name) : credentialPutCommand(kind, name);
26103
+ var CredentialUnavailableError = class extends Error {
26104
+ kind;
26105
+ /** The environment variables that can provide this credential. */
26106
+ envNames;
26107
+ constructor(kind, message, options) {
26108
+ super(message, options);
26109
+ this.name = "CredentialUnavailableError";
26110
+ this.kind = kind;
26111
+ this.envNames = CREDENTIAL_ENV_NAMES[kind].flat();
26129
26112
  }
26130
26113
  };
26131
- //#endregion
26132
- //#region src/ConfigBuilder.ts
26133
- const normalizeImportInfos = (imports) => {
26134
- const collectedImports = /* @__PURE__ */ new Map();
26135
- for (const info of imports) {
26136
- const existing = collectedImports.get(info.pkg);
26137
- if (existing) {
26138
- if (info.named) for (const namedImport of info.named) existing.named.add(namedImport);
26139
- if (info.defaultOrNamespace && !existing.defaultOrNamespace) existing.defaultOrNamespace = info.defaultOrNamespace;
26140
- if (info.sideEffect && !existing.sideEffect) existing.sideEffect = true;
26141
- continue;
26142
- }
26143
- collectedImports.set(info.pkg, {
26144
- named: new Set(info.named ?? []),
26145
- defaultOrNamespace: info.defaultOrNamespace,
26146
- sideEffect: info.sideEffect ?? false
26147
- });
26114
+ const readEnvField = (env, field) => {
26115
+ for (const name of field) {
26116
+ const value = UNTRIMMED_ENV_NAMES.has(name) ? (process.env[name] ?? env[name]) || void 0 : getHotUpdaterEnvValue(env, name);
26117
+ if (value !== void 0) return {
26118
+ name,
26119
+ value
26120
+ };
26148
26121
  }
26149
- return Array.from(collectedImports.entries()).sort(([a], [b]) => {
26150
- const isABuild = a.startsWith("@hot-updater/");
26151
- if (isABuild !== b.startsWith("@hot-updater/")) return isABuild ? -1 : 1;
26152
- const isAdminA = a === "firebase-admin";
26153
- if (isAdminA !== (b === "firebase-admin")) return isAdminA ? -1 : 1;
26154
- return a.localeCompare(b);
26155
- }).map(([pkg, info]) => ({
26156
- pkg,
26157
- named: Array.from(info.named).sort(),
26158
- defaultOrNamespace: info.defaultOrNamespace,
26159
- sideEffect: info.sideEffect ?? false
26160
- }));
26161
26122
  };
26162
- const renderImportStatements = (imports) => {
26163
- const importLines = [];
26164
- for (const info of normalizeImportInfos(imports)) {
26165
- if (info.sideEffect) {
26166
- importLines.push(`import "${info.pkg}";`);
26167
- continue;
26168
- }
26169
- if (info.defaultOrNamespace) {
26170
- if (info.pkg === "firebase-admin" && (info.named?.length ?? 0) > 0) importLines.push(`import ${info.defaultOrNamespace}, { ${info.named.join(", ")} } from "${info.pkg}";`);
26171
- else importLines.push(`import ${info.defaultOrNamespace} from "${info.pkg}";`);
26172
- continue;
26123
+ const readFileField = async (kind, { name, value }) => {
26124
+ const filePath = path$1.resolve(value);
26125
+ if (!await fs$2.stat(filePath).then(async (stats) => {
26126
+ await fs$2.access(filePath, fs$2.constants.R_OK);
26127
+ return stats.isFile();
26128
+ }).catch(() => false)) throw new CredentialUnavailableError(kind, `${name} does not point to a readable file.`);
26129
+ return filePath;
26130
+ };
26131
+ const parseServiceAccount = (json) => {
26132
+ try {
26133
+ return JSON.parse(json);
26134
+ } catch {
26135
+ return;
26136
+ }
26137
+ };
26138
+ const fromEnv = {
26139
+ "asc-api-key": async ([key, keyId, issuerId], { files }) => ({
26140
+ keyPath: key.name === "HOT_UPDATER_ASC_KEY_PATH" ? await readFileField("asc-api-key", key) : await files.write(`AuthKey_${keyId.value}.p8`, key.value.includes("\n") ? key.value : key.value.replaceAll("\\n", "\n")),
26141
+ keyId: keyId.value,
26142
+ issuerId: issuerId.value
26143
+ }),
26144
+ "android-keystore": async ([keystore, storePassword, keyAlias, keyPassword], { files, name }) => {
26145
+ let storeFile;
26146
+ if (keystore.name === "HOT_UPDATER_ANDROID_KEYSTORE_PATH") storeFile = await readFileField("android-keystore", keystore);
26147
+ else {
26148
+ registerSecretValue(keystore.value);
26149
+ const contents = decodeBase64(keystore.value);
26150
+ if (!contents) throw new CredentialUnavailableError("android-keystore", `${keystore.name} is not a base64-encoded keystore.`);
26151
+ storeFile = await files.write(`${name}.keystore`, contents);
26173
26152
  }
26174
- if ((info.named?.length ?? 0) > 0) importLines.push(`import { ${info.named.join(", ")} } from "${info.pkg}";`);
26153
+ return {
26154
+ storeFile,
26155
+ storePassword: storePassword.value,
26156
+ keyAlias: keyAlias.value,
26157
+ keyPassword: keyPassword.value
26158
+ };
26159
+ },
26160
+ "play-service-account": async ([serviceAccount]) => {
26161
+ const json = serviceAccount.name === "HOT_UPDATER_PLAY_SERVICE_ACCOUNT_PATH" ? await fs$2.readFile(await readFileField("play-service-account", serviceAccount), "utf-8") : serviceAccount.value;
26162
+ const parsed = parseServiceAccount(json);
26163
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new CredentialUnavailableError("play-service-account", `${serviceAccount.name} is not a service account JSON object.`);
26164
+ return { json };
26175
26165
  }
26176
- return importLines.join("\n");
26177
26166
  };
26167
+ /** Fields of a service account that are no secret: its type and Google's endpoints. */
26178
26168
  /**
26179
- * Renders the hot-updater.config.ts init writes: the build, the server's
26180
- * storage, database, and plugins, and the deploy settings.
26169
+ * Fields of a JSON credential that identify it rather than grant access:
26170
+ * its IDs, key alias, and a service account's email, project, type, and
26171
+ * Google's endpoints. They stay readable, as messages name them, and a
26172
+ * short one, such as the alias `release`, would mask every word it is in.
26181
26173
  */
26182
- var ConfigBuilder = class {
26183
- buildType = null;
26184
- storageInfo = null;
26185
- databaseInfo = null;
26186
- pluginsInfo = null;
26187
- intermediateCode = "";
26188
- imports = [{
26189
- pkg: "hot-updater",
26190
- named: ["defineConfig", "localSecrets"]
26191
- }, {
26192
- pkg: "node:fs",
26193
- named: ["existsSync"]
26194
- }];
26195
- /** Adds an import, such as a credentials helper's. */
26196
- addImport(info) {
26197
- this.imports.push(info);
26198
- return this;
26199
- }
26200
- generateBuildConfigString() {
26201
- if (!this.buildType) throw new Error("Build type must be set using .setBuildType()");
26202
- switch (this.buildType) {
26203
- case "bare": return "bare({ enableHermes: true })";
26204
- case "rock": return "rock()";
26205
- case "expo": return "expo()";
26206
- default: throw new Error(`Invalid build type: ${this.buildType}`);
26207
- }
26208
- }
26209
- /** Sets the build type ('bare', 'rock', or 'expo') and its import. */
26210
- setBuildType(buildType) {
26211
- this.buildType = buildType;
26212
- this.imports.push({
26213
- pkg: `@hot-updater/${buildType}`,
26214
- named: [buildType]
26215
- });
26216
- return this;
26217
- }
26218
- /** Sets the storage the CLI uploads to, which the server lists, and its imports. */
26219
- setStorage(storageConfig) {
26220
- this.storageInfo = storageConfig;
26221
- this.imports.push(...storageConfig.imports);
26222
- return this;
26223
- }
26224
- /** Sets the server's database and its imports. */
26225
- setDatabase(databaseConfig) {
26226
- this.databaseInfo = databaseConfig;
26227
- this.imports.push(...databaseConfig.imports);
26228
- return this;
26229
- }
26230
- /** Sets the server's plugin factory array and its imports. */
26231
- setPlugins(pluginsConfig) {
26232
- this.pluginsInfo = pluginsConfig;
26233
- this.imports.push(...pluginsConfig.imports);
26234
- return this;
26235
- }
26236
- /** Sets the code between the environment loading and the config, such as a credentials helper. */
26237
- setIntermediateCode(code) {
26238
- this.intermediateCode = code.trim();
26239
- return this;
26240
- }
26241
- getScaffold() {
26242
- if (!this.buildType) throw new Error("Build type must be set using .setBuildType()");
26243
- if (!this.storageInfo) throw new Error("Storage config must be set using .setStorage()");
26244
- if (!this.databaseInfo) throw new Error("Database config must be set using .setDatabase()");
26245
- if (!this.pluginsInfo) throw new Error("Plugins config must be set using .setPlugins()");
26246
- const imports = normalizeImportInfos(this.imports);
26247
- const buildConfigString = this.generateBuildConfigString();
26248
- const plugins = this.pluginsInfo.configString;
26249
- const text = `
26250
- ${renderImportStatements(imports)}
26251
-
26252
- if (existsSync(".env.hotupdater")) {
26253
- process.loadEnvFile(".env.hotupdater");
26254
- }
26255
-
26256
- ${this.intermediateCode ? `${this.intermediateCode}\n\n` : ""}export default defineConfig({
26257
- build: ${buildConfigString},
26258
- storage: ${this.storageInfo.configString},
26259
- database: ${this.databaseInfo.configString},
26260
- ${plugins === "plugins" ? "plugins" : `plugins: ${plugins}`},
26261
- secrets: localSecrets(),
26262
- updateStrategy: "appVersion", // or "fingerprint"
26263
- });
26264
- `.trim();
26265
- return {
26266
- imports,
26267
- buildConfigString,
26268
- storageConfigString: this.storageInfo.configString,
26269
- databaseConfigString: this.databaseInfo.configString,
26270
- pluginsConfigString: plugins,
26271
- intermediateCode: this.intermediateCode,
26272
- text
26273
- };
26274
- }
26275
- /** hot-updater.config.ts's text. */
26276
- getResult() {
26277
- return this.getScaffold().text;
26278
- }
26174
+ const isIdentifierField = (field) => [
26175
+ "keyId",
26176
+ "issuerId",
26177
+ "keyAlias",
26178
+ "type",
26179
+ "project_id",
26180
+ "client_email",
26181
+ "client_id",
26182
+ "private_key_id",
26183
+ "universe_domain"
26184
+ ].includes(field) || field.endsWith("_uri") || field.endsWith("_url");
26185
+ const registerJsonSecrets = (value, fields) => {
26186
+ registerSecretValue(value);
26187
+ for (const [field, entry] of Object.entries(fields)) if (typeof entry === "string" && !isIdentifierField(field)) registerSecretValue(entry);
26279
26188
  };
26280
- //#endregion
26281
- //#region src/HotUpdateDirUtil.ts
26282
- const HOT_UPDATE_DIR_NAME = ".hot-updater";
26283
- const HOT_UPDATE_OUTPUT_DIR_NAME = "output";
26284
- const HOT_UPDATE_LOG_DIR_NAME = "log";
26285
- const HotUpdateDirUtil = {
26286
- dirName: HOT_UPDATE_DIR_NAME,
26287
- outputDirName: HOT_UPDATE_OUTPUT_DIR_NAME,
26288
- logDirName: HOT_UPDATE_LOG_DIR_NAME,
26289
- outputGitignorePath: `${HOT_UPDATE_DIR_NAME}/${HOT_UPDATE_OUTPUT_DIR_NAME}`,
26290
- logGitignorePath: `${HOT_UPDATE_DIR_NAME}/${HOT_UPDATE_LOG_DIR_NAME}`,
26291
- getDirPath: ({ cwd }) => {
26292
- return path$1.join(cwd, HOT_UPDATE_DIR_NAME);
26293
- },
26294
- getDefaultOutputPath: ({ cwd }) => {
26295
- return path$1.join(cwd, HOT_UPDATE_DIR_NAME, HOT_UPDATE_OUTPUT_DIR_NAME);
26296
- },
26297
- getLogDirPath: ({ cwd }) => {
26298
- return path$1.join(cwd, HOT_UPDATE_DIR_NAME, HOT_UPDATE_LOG_DIR_NAME);
26299
- }
26189
+ /**
26190
+ * Masks a credential's JSON value in the CLI's output: the whole value, and
26191
+ * each field but those that identify it, such as `keyId` and `keyAlias`,
26192
+ * so a password or a key a log prints alone stays masked. A value that is
26193
+ * no JSON object is masked whole.
26194
+ */
26195
+ const registerCredentialValue = (value) => {
26196
+ const parsed = parseObject(value);
26197
+ if (parsed) registerJsonSecrets(value, parsed);
26198
+ else registerSecretValue(value);
26300
26199
  };
26301
- //#endregion
26302
- //#region src/copyDirToTmp.ts
26303
- const copyDirToTmp = async (dir, childDirname) => {
26304
- const cwd = getCwd();
26305
- const hotUpdaterDir = HotUpdateDirUtil.getDirPath({ cwd });
26306
- const tmpDir = childDirname ? path.join(hotUpdaterDir, childDirname) : hotUpdaterDir;
26307
- try {
26308
- await fs$1.rm(tmpDir, {
26309
- recursive: true,
26310
- force: true
26311
- });
26312
- } catch {}
26313
- await fs$1.mkdir(tmpDir, { recursive: true });
26314
- await fs$1.cp(dir, tmpDir, { recursive: true });
26315
- return {
26316
- tmpDir: hotUpdaterDir,
26317
- removeTmpDir: () => fs$1.rm(hotUpdaterDir, { recursive: true })
26318
- };
26200
+ const parseObject = (value) => {
26201
+ const parsed = parseServiceAccount(value);
26202
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
26319
26203
  };
26320
- //#endregion
26321
- //#region ../../node_modules/.pnpm/tar@7.5.16/node_modules/tar/dist/esm/index.min.js
26322
- var Mr = Object.defineProperty;
26323
- var Br = (s, t) => {
26324
- for (var e in t) Mr(s, e, {
26325
- get: t[e],
26326
- enumerable: !0
26327
- });
26204
+ /**
26205
+ * The fields of a JSON credential, each a non-empty string, or an error
26206
+ * naming the shape `kind` takes.
26207
+ */
26208
+ const readJsonFields = (kind, item, value, fields) => {
26209
+ const parsed = parseObject(value);
26210
+ const shape = `{${fields.map((field) => `"${field}"`).join(", ")}}`;
26211
+ if (!parsed) throw new CredentialUnavailableError(kind, `${item} is not a JSON object.\n${capitalize(aKind(kind))} is one JSON value: ${shape}`);
26212
+ registerJsonSecrets(value, parsed);
26213
+ const missing = fields.filter((field) => typeof parsed[field] !== "string" || !parsed[field]);
26214
+ if (missing.length > 0) throw new CredentialUnavailableError(kind, `${item} has no ${missing.join(", ")}.\n${capitalize(aKind(kind))} is one JSON value: ${shape}`);
26215
+ return parsed;
26328
26216
  };
26329
- var xs = typeof process == "object" && process ? process : {
26330
- stdout: null,
26331
- stderr: null
26217
+ const aKind = (kind) => `${/^[aeiou]/u.test(kind) ? "an" : "a"} ${kind}`;
26218
+ const capitalize = (text) => text.charAt(0).toUpperCase() + text.slice(1);
26219
+ /** A base64 value's bytes, or undefined when it is not base64. */
26220
+ const decodeBase64 = (value) => {
26221
+ const base64 = value.replace(/\s/g, "");
26222
+ const contents = Buffer.from(base64, "base64");
26223
+ return contents.length > 0 && contents.toString("base64").replace(/=+$/, "") === base64.replace(/=+$/, "") ? contents : void 0;
26332
26224
  };
26333
- var zr = (s) => !!s && typeof s == "object" && (s instanceof A || s instanceof As || Ur(s) || Hr(s));
26334
- var Ur = (s) => !!s && typeof s == "object" && s instanceof EventEmitter$1 && typeof s.pipe == "function" && s.pipe !== As.Writable.prototype.pipe;
26335
- var Hr = (s) => !!s && typeof s == "object" && s instanceof EventEmitter$1 && typeof s.write == "function" && typeof s.end == "function";
26336
- var q = Symbol("EOF");
26337
- var Q = Symbol("maybeEmitEnd");
26338
- var rt = Symbol("emittedEnd");
26339
- var Le = Symbol("emittingEnd");
26340
- var qt = Symbol("emittedError");
26341
- var Ne = Symbol("closed");
26342
- var Ls = Symbol("read");
26343
- var De = Symbol("flush");
26344
- var Ns = Symbol("flushChunk");
26345
- var z = Symbol("encoding");
26346
- var Mt = Symbol("decoder");
26347
- var g = Symbol("flowing");
26348
- var Qt = Symbol("paused");
26349
- var Bt = Symbol("resume");
26350
- var b = Symbol("buffer");
26351
- var D = Symbol("pipes");
26352
- var _ = Symbol("bufferLength");
26353
- var gi = Symbol("bufferPush");
26354
- var Ae = Symbol("bufferShift");
26355
- var L = Symbol("objectMode");
26356
- var w = Symbol("destroyed");
26357
- var bi = Symbol("error");
26358
- var _i = Symbol("emitData");
26359
- var Ds = Symbol("emitEnd");
26360
- var Oi = Symbol("emitEnd2");
26361
- var Z = Symbol("async");
26362
- var Ti = Symbol("abort");
26363
- var Ie = Symbol("aborted");
26364
- var Jt = Symbol("signal");
26365
- var Rt = Symbol("dataListeners");
26366
- var F = Symbol("discarded");
26367
- var jt = (s) => Promise.resolve().then(s);
26368
- var Wr = (s) => s();
26369
- var Gr = (s) => s === "end" || s === "finish" || s === "prefinish";
26370
- var Zr = (s) => s instanceof ArrayBuffer || !!s && typeof s == "object" && s.constructor && s.constructor.name === "ArrayBuffer" && s.byteLength >= 0;
26371
- var Yr = (s) => !Buffer.isBuffer(s) && ArrayBuffer.isView(s);
26372
- var Fe = class {
26373
- src;
26374
- dest;
26375
- opts;
26376
- ondrain;
26377
- constructor(t, e, i) {
26378
- this.src = t, this.dest = e, this.opts = i, this.ondrain = () => t[Bt](), this.dest.on("drain", this.ondrain);
26225
+ /**
26226
+ * Each kind from the one string a secrets adapter holds for it: a JSON
26227
+ * object for the App Store Connect key (`p8` is the PEM) and the keystore
26228
+ * (`keystore` is base64), and the service account's own JSON. File values
26229
+ * are written with `files`; every value is masked.
26230
+ */
26231
+ const fromSecretValue = {
26232
+ "asc-api-key": async (value, { files, item }) => {
26233
+ const { keyId, issuerId, p8 } = readJsonFields("asc-api-key", item, value, [
26234
+ "keyId",
26235
+ "issuerId",
26236
+ "p8"
26237
+ ]);
26238
+ return {
26239
+ keyPath: await files.write(`AuthKey_${keyId}.p8`, p8.includes("\n") ? p8 : p8.replaceAll("\\n", "\n")),
26240
+ keyId,
26241
+ issuerId
26242
+ };
26243
+ },
26244
+ "android-keystore": async (value, { files, name, item }) => {
26245
+ const { keystore, storePassword, keyAlias, keyPassword } = readJsonFields("android-keystore", item, value, [
26246
+ "keystore",
26247
+ "storePassword",
26248
+ "keyAlias",
26249
+ "keyPassword"
26250
+ ]);
26251
+ const contents = decodeBase64(keystore);
26252
+ if (!contents) throw new CredentialUnavailableError("android-keystore", `${item}'s keystore is not a base64-encoded keystore.`);
26253
+ return {
26254
+ storeFile: await files.write(`${name}.keystore`, contents),
26255
+ storePassword,
26256
+ keyAlias,
26257
+ keyPassword
26258
+ };
26259
+ },
26260
+ "play-service-account": async (value, { item }) => {
26261
+ const parsed = parseObject(value);
26262
+ if (!parsed) throw new CredentialUnavailableError("play-service-account", `${item} is not a service account JSON object.`);
26263
+ registerJsonSecrets(value, parsed);
26264
+ return { json: value };
26379
26265
  }
26380
- unpipe() {
26381
- this.dest.removeListener("drain", this.ondrain);
26266
+ };
26267
+ const registerCredentialSecrets = (kind, credential) => {
26268
+ if (kind === "android-keystore") {
26269
+ const keystore = credential;
26270
+ registerSecretValue(keystore.storePassword);
26271
+ registerSecretValue(keystore.keyPassword);
26382
26272
  }
26383
- proxyErrors(t) {}
26384
- end() {
26385
- this.unpipe(), this.opts.end && this.dest.end();
26273
+ if (kind === "play-service-account") {
26274
+ const { json } = credential;
26275
+ registerSecretValue(json);
26276
+ const privateKey = parseServiceAccount(json)?.private_key;
26277
+ if (typeof privateKey === "string") registerSecretValue(privateKey);
26386
26278
  }
26387
26279
  };
26388
- var xi = class extends Fe {
26389
- unpipe() {
26390
- this.src.removeListener("error", this.proxyErrors), super.unpipe();
26391
- }
26392
- constructor(t, e, i) {
26393
- super(t, e, i), this.proxyErrors = (r) => this.dest.emit("error", r), t.on("error", this.proxyErrors);
26394
- }
26280
+ /**
26281
+ * Reads names from `secrets`, the names asked for in one turn of the event
26282
+ * loop, such as a build's credentials, in one `get`. Nothing is cached.
26283
+ */
26284
+ const createSecretLoader = (secrets, cwd) => {
26285
+ let batch;
26286
+ const flush = async (names) => {
26287
+ try {
26288
+ const read = await secrets().get([...names.keys()], { cwd: cwd() });
26289
+ for (const [name, waiters] of names) {
26290
+ const value = Object.hasOwn(read, name) ? read[name] : void 0;
26291
+ for (const waiter of waiters) waiter.resolve(typeof value === "string" && value ? value : void 0);
26292
+ }
26293
+ } catch (error) {
26294
+ for (const waiters of names.values()) for (const waiter of waiters) waiter.reject(error);
26295
+ }
26296
+ };
26297
+ return (name) => new Promise((resolve, reject) => {
26298
+ if (batch === void 0) {
26299
+ const names = /* @__PURE__ */ new Map();
26300
+ batch = names;
26301
+ setTimeout(() => {
26302
+ batch = void 0;
26303
+ flush(names);
26304
+ }, 0);
26305
+ }
26306
+ const waiters = batch.get(name) ?? [];
26307
+ waiters.push({
26308
+ resolve,
26309
+ reject
26310
+ });
26311
+ batch.set(name, waiters);
26312
+ });
26395
26313
  };
26396
- var Kr = (s) => !!s.objectMode;
26397
- var Vr = (s) => !s.objectMode && !!s.encoding && s.encoding !== "buffer";
26398
- var A = class extends EventEmitter$1 {
26399
- [g] = !1;
26400
- [Qt] = !1;
26401
- [D] = [];
26402
- [b] = [];
26403
- [L];
26404
- [z];
26405
- [Z];
26406
- [Mt];
26407
- [q] = !1;
26408
- [rt] = !1;
26409
- [Le] = !1;
26410
- [Ne] = !1;
26411
- [qt] = null;
26412
- [_] = 0;
26413
- [w] = !1;
26414
- [Jt];
26415
- [Ie] = !1;
26416
- [Rt] = 0;
26417
- [F] = !1;
26418
- writable = !0;
26419
- readable = !0;
26420
- constructor(...t) {
26421
- let e = t[0] || {};
26422
- if (super(), e.objectMode && typeof e.encoding == "string") throw new TypeError("Encoding and objectMode may not be used together");
26423
- Kr(e) ? (this[L] = !0, this[z] = null) : Vr(e) ? (this[z] = e.encoding, this[L] = !1) : (this[L] = !1, this[z] = null), this[Z] = !!e.async, this[Mt] = this[z] ? new StringDecoder(this[z]) : null, e && e.debugExposeBuffer === !0 && Object.defineProperty(this, "buffer", { get: () => this[b] }), e && e.debugExposePipes === !0 && Object.defineProperty(this, "pipes", { get: () => this[D] });
26424
- let { signal: i } = e;
26425
- i && (this[Jt] = i, i.aborted ? this[Ti]() : i.addEventListener("abort", () => this[Ti]()));
26426
- }
26427
- get bufferLength() {
26428
- return this[_];
26429
- }
26430
- get encoding() {
26431
- return this[z];
26432
- }
26433
- set encoding(t) {
26434
- throw new Error("Encoding must be set at instantiation time");
26314
+ /**
26315
+ * Resolves each item from its environment overrides when any is set, and
26316
+ * otherwise from the config's `secrets` adapter as one value named by the
26317
+ * item, so overrides never reach the store. When the adapter fails or holds
26318
+ * no such value, the error names the environment variables that can
26319
+ * provide the item instead. Nothing is cached, values are never written to
26320
+ * disk except as 0600 temporary files, are never set in `process.env`, and
26321
+ * every secret is masked in logs.
26322
+ */
26323
+ const createCredentialResolver = ({ env = {}, secrets, cwd } = {}) => {
26324
+ const files = createCredentialFiles();
26325
+ const resolved = /* @__PURE__ */ new Map();
26326
+ let disposal;
26327
+ const adapter = () => typeof secrets === "function" ? secrets() : secrets ?? getConfiguredSecrets();
26328
+ const load = createSecretLoader(adapter, () => cwd ?? getCwd());
26329
+ const resolveItem = async (kind, name) => {
26330
+ const fields = CREDENTIAL_ENV_NAMES[kind];
26331
+ const values = fields.map((field) => readEnvField(env, field));
26332
+ if (values.some((value) => value !== void 0)) {
26333
+ const missing = fields.filter((_, index) => !values[index]);
26334
+ if (missing.length > 0) throw new CredentialUnavailableError(kind, `The environment overrides ${kind} but ${describeFields(missing)} ${missing.length === 1 ? "is" : "are"} not set.`);
26335
+ const credential = await fromEnv[kind](values, {
26336
+ files,
26337
+ name
26338
+ });
26339
+ registerCredentialSecrets(kind, credential);
26340
+ return credential;
26341
+ }
26342
+ const store = adapter();
26343
+ const item = name === kind ? kind : `${name} (${kind})`;
26344
+ let value;
26345
+ try {
26346
+ value = await load(name);
26347
+ } catch (error) {
26348
+ const reason = error instanceof Error ? error.message : String(error);
26349
+ throw new CredentialUnavailableError(kind, `Couldn't read ${item} from the ${store.name} secrets adapter.\n${reason}`, { cause: error });
26350
+ }
26351
+ if (value === void 0) throw new CredentialUnavailableError(kind, kind === "android-keystore" ? describeMissingKeystore(store, item, name) : `No ${item} in the ${store.name} secrets adapter.\nStore it once with:\n${formatCommand(credentialStoreCommand(kind, name))}`);
26352
+ registerSecretValue(value);
26353
+ return fromSecretValue[kind](value, {
26354
+ files,
26355
+ name,
26356
+ item: `The ${store.name} secrets adapter's ${item}`
26357
+ });
26358
+ };
26359
+ return {
26360
+ resolve: ({ kind, name = kind }) => {
26361
+ if (disposal) return Promise.reject(/* @__PURE__ */ new Error("The credential resolver has been disposed."));
26362
+ const key = `${kind}\u0000${name}`;
26363
+ let pending = resolved.get(key);
26364
+ if (!pending) {
26365
+ pending = resolveItem(kind, name);
26366
+ resolved.set(key, pending);
26367
+ pending.catch(() => resolved.delete(key));
26368
+ }
26369
+ return pending;
26370
+ },
26371
+ dispose: () => {
26372
+ disposal ??= (async () => {
26373
+ await Promise.allSettled(resolved.values());
26374
+ resolved.clear();
26375
+ await files.dispose();
26376
+ })();
26377
+ return disposal;
26378
+ }
26379
+ };
26380
+ };
26381
+ /**
26382
+ * Runs `run` with a credential resolver and deletes its temporary files
26383
+ * when `run` settles.
26384
+ */
26385
+ const withCredentialResolver = async (run, options) => {
26386
+ const resolver = createCredentialResolver(options);
26387
+ try {
26388
+ return await run(resolver);
26389
+ } finally {
26390
+ await resolver.dispose();
26435
26391
  }
26436
- setEncoding(t) {
26437
- throw new Error("Encoding must be set at instantiation time");
26392
+ };
26393
+ //#endregion
26394
+ //#region src/bundleSigning.ts
26395
+ /** The secret `signing: secret("bundle-signing-key")` names, which `secrets issue` creates. */
26396
+ const BUNDLE_SIGNING_KEY_SECRET = "bundle-signing-key";
26397
+ /** The command that creates and stores a bundle signing key. */
26398
+ const ISSUE_SIGNING_KEY_COMMAND = `npx hot-updater secrets issue ${BUNDLE_SIGNING_KEY_SECRET}`;
26399
+ /** The command that stores a bundle signing key you have. */
26400
+ const PUT_SIGNING_KEY_COMMAND = `npx hot-updater secrets put ${BUNDLE_SIGNING_KEY_SECRET} <key.pem>`;
26401
+ /** What deploy and doctor say when the config sets no `signing`. */
26402
+ const UNSIGNED_BUNDLES_WARNING = "Bundles are unsigned.";
26403
+ /** The next step after `UNSIGNED_BUNDLES_WARNING`. */
26404
+ const UNSIGNED_BUNDLES_NEXT_STEP = "To sign them, run:";
26405
+ /** What deploy and doctor say when `signing` is no shape it takes. */
26406
+ const INVALID_SIGNING_MESSAGE = `signing takes secret("${BUNDLE_SIGNING_KEY_SECRET}").`;
26407
+ /** The next step after `INVALID_SIGNING_MESSAGE`. */
26408
+ const INVALID_SIGNING_NEXT_STEP = "Store your key:";
26409
+ const invalid = (message) => ({
26410
+ status: "invalid",
26411
+ message
26412
+ });
26413
+ const LOCAL_FIELDS = [
26414
+ "enabled",
26415
+ "privateKeyPath",
26416
+ "privateKey"
26417
+ ];
26418
+ const readBundleSigningState = (signing) => {
26419
+ if (signing === void 0) return { status: "unsigned" };
26420
+ if (isSecretRef(signing)) return {
26421
+ status: "on",
26422
+ key: { secret: signing.name }
26423
+ };
26424
+ if (typeof signing !== "object" || signing === null || !Object.keys(signing).every((key) => LOCAL_FIELDS.includes(key))) return invalid(INVALID_SIGNING_MESSAGE);
26425
+ const { enabled, privateKey, privateKeyPath } = signing;
26426
+ if (enabled === false) return { status: "off" };
26427
+ if (enabled !== true) return invalid(INVALID_SIGNING_MESSAGE);
26428
+ if (privateKey !== void 0 && privateKeyPath !== void 0) return invalid("signing takes privateKey or privateKeyPath, not both.");
26429
+ if (privateKey !== void 0) return isSecretRef(privateKey) ? {
26430
+ status: "on",
26431
+ key: { secret: privateKey.name }
26432
+ } : invalid("signing.privateKey takes secret(\"<name>\").");
26433
+ if (typeof privateKeyPath !== "string" || !privateKeyPath.trim()) return invalid("signing takes privateKeyPath, or privateKey as secret(\"<name>\").");
26434
+ return {
26435
+ status: "on",
26436
+ key: { privateKeyPath }
26437
+ };
26438
+ };
26439
+ /** The `secret()` the config's `signing` reads its private key from, if any. */
26440
+ const bundleSigningSecretRef = (signing) => {
26441
+ if (isSecretRef(signing)) return signing;
26442
+ const privateKey = typeof signing === "object" && signing !== null ? Reflect.get(signing, "privateKey") : void 0;
26443
+ return isSecretRef(privateKey) ? privateKey : void 0;
26444
+ };
26445
+ /**
26446
+ * A bundle signing failure whose message the CLI prints as it is: it names
26447
+ * the secret and the next step, and quotes no key material.
26448
+ */
26449
+ var BundleSigningKeyError = class extends Error {
26450
+ constructor(message, options) {
26451
+ super(message, options);
26452
+ this.name = "HotUpdaterBundleSigningKeyError";
26438
26453
  }
26439
- get objectMode() {
26440
- return this[L];
26454
+ };
26455
+ const FILE_HASH_PATTERN = /^[a-f\d]{64}$/iu;
26456
+ const resolvePath = (cwd, filePath) => path$1.isAbsolute(filePath) ? filePath : path$1.resolve(cwd, filePath);
26457
+ const parseRsaPublicKey = (publicKeyPEM) => {
26458
+ try {
26459
+ const normalizedPublicKey = publicKeyPEM.trim();
26460
+ if (!normalizedPublicKey.startsWith("-----BEGIN PUBLIC KEY-----") || !normalizedPublicKey.endsWith("-----END PUBLIC KEY-----")) throw new Error("not spki");
26461
+ const publicKey = crypto$1.createPublicKey(normalizedPublicKey);
26462
+ if (publicKey.asymmetricKeyType !== "rsa" || (publicKey.asymmetricKeyDetails?.modulusLength ?? 0) < 2048) throw new Error("not rsa");
26463
+ return publicKey;
26464
+ } catch {
26465
+ throw new Error("Bundle signing public key must be a valid RSA SPKI PEM key with a modulus of at least 2048 bits.");
26441
26466
  }
26442
- set objectMode(t) {
26443
- throw new Error("objectMode must be set at instantiation time");
26467
+ };
26468
+ /** The SPKI PEM of `key`, a public key or the private key it derives from. */
26469
+ const exportPublicKey = (key) => (key.type === "private" ? crypto$1.createPublicKey(key) : key).export({
26470
+ type: "spki",
26471
+ format: "pem"
26472
+ }).toString();
26473
+ const readBundleSigningPublicKeyFile = async (publicKeyPath, options = {}) => {
26474
+ if (!publicKeyPath.trim()) throw new Error("Bundle signing public key path is required.");
26475
+ try {
26476
+ const publicKeyPEM = await fs$2.readFile(resolvePath(options.cwd ?? getCwd(), publicKeyPath), "utf8");
26477
+ return exportPublicKey(parseRsaPublicKey(publicKeyPEM));
26478
+ } catch {
26479
+ throw new Error("Failed to read the bundle signing public key file.");
26444
26480
  }
26445
- get async() {
26446
- return this[Z];
26481
+ };
26482
+ const parsePrivateKey = (pem) => {
26483
+ const privateKey = crypto$1.createPrivateKey(pem);
26484
+ if (privateKey.asymmetricKeyType !== "rsa" || (privateKey.asymmetricKeyDetails?.modulusLength ?? 0) < 2048) throw new Error("not rsa");
26485
+ return privateKey;
26486
+ };
26487
+ const loadPrivateKeyFile = async (file) => {
26488
+ try {
26489
+ return parsePrivateKey(await fs$2.readFile(file, "utf8"));
26490
+ } catch {
26491
+ throw new BundleSigningKeyError("Failed to load the bundle signing private key from privateKeyPath.");
26447
26492
  }
26448
- set async(t) {
26449
- this[Z] = this[Z] || !!t;
26450
- }
26451
- [Ti]() {
26452
- this[Ie] = !0, this.emit("abort", this[Jt]?.reason), this.destroy(this[Jt]?.reason);
26493
+ };
26494
+ /**
26495
+ * The private key the `secrets` adapter holds as `name`: masked in the CLI's
26496
+ * output before it is parsed, and kept only as a key object. A PEM written
26497
+ * on one line, with `\n` for its line breaks, as a `.env.hotupdater` value
26498
+ * or a CI variable may hold it, reads as the PEM.
26499
+ */
26500
+ const loadPrivateKeySecret = async (secrets, name, cwd) => {
26501
+ let value;
26502
+ try {
26503
+ const read = await secrets.get([name], { cwd });
26504
+ value = Object.hasOwn(read, name) ? read[name] : void 0;
26505
+ } catch (error) {
26506
+ const reason = error instanceof Error ? error.message : String(error);
26507
+ throw new BundleSigningKeyError(`Couldn't read ${name} from the ${secrets.name} secrets adapter.\n${reason}`, { cause: error });
26453
26508
  }
26454
- get aborted() {
26455
- return this[Ie];
26509
+ if (typeof value !== "string" || !value.trim()) {
26510
+ const [missing, check] = getConfiguredAdapter(secrets) ? [`${name} isn't in the ${secrets.name} secrets adapter.`, "Check the name and your access, or import your key:"] : [`${name} isn't on this machine.`, `Check the name, set ${secretVariableName(name)}, or import your key:`];
26511
+ throw new BundleSigningKeyError([
26512
+ missing,
26513
+ check,
26514
+ formatCommand(`npx hot-updater secrets put ${name}${name === "bundle-signing-key" ? "" : ` --kind ${BUNDLE_SIGNING_KEY_SECRET}`} <key.pem>`)
26515
+ ].join("\n"));
26456
26516
  }
26457
- set aborted(t) {}
26458
- write(t, e, i) {
26459
- if (this[Ie]) return !1;
26460
- if (this[q]) throw new Error("write after end");
26461
- if (this[w]) return this.emit("error", Object.assign(/* @__PURE__ */ new Error("Cannot call write after a stream was destroyed"), { code: "ERR_STREAM_DESTROYED" })), !0;
26462
- typeof e == "function" && (i = e, e = "utf8"), e || (e = "utf8");
26463
- let r = this[Z] ? jt : Wr;
26464
- if (!this[L] && !Buffer.isBuffer(t)) {
26465
- if (Yr(t)) t = Buffer.from(t.buffer, t.byteOffset, t.byteLength);
26466
- else if (Zr(t)) t = Buffer.from(t);
26467
- else if (typeof t != "string") throw new Error("Non-contiguous data written to non-objectMode stream");
26468
- }
26469
- return this[L] ? (this[g] && this[_] !== 0 && this[De](!0), this[g] ? this.emit("data", t) : this[gi](t), this[_] !== 0 && this.emit("readable"), i && r(i), this[g]) : t.length ? (typeof t == "string" && !(e === this[z] && !this[Mt]?.lastNeed) && (t = Buffer.from(t, e)), Buffer.isBuffer(t) && this[z] && (t = this[Mt].write(t)), this[g] && this[_] !== 0 && this[De](!0), this[g] ? this.emit("data", t) : this[gi](t), this[_] !== 0 && this.emit("readable"), i && r(i), this[g]) : (this[_] !== 0 && this.emit("readable"), i && r(i), this[g]);
26517
+ registerSecretValue(value);
26518
+ const pem = value.includes("\n") ? value : value.replaceAll("\\n", "\n");
26519
+ if (pem !== value) registerSecretValue(pem);
26520
+ try {
26521
+ return parsePrivateKey(pem);
26522
+ } catch {
26523
+ throw new BundleSigningKeyError([`${name} in the ${secrets.name} secrets adapter is no signing key.`, "Store an RSA private key in PEM, of 2048 bits or more."].join("\n"));
26470
26524
  }
26471
- read(t) {
26472
- if (this[w]) return null;
26473
- if (this[F] = !1, this[_] === 0 || t === 0 || t && t > this[_]) return this[Q](), null;
26474
- this[L] && (t = null), this[b].length > 1 && !this[L] && (this[b] = [this[z] ? this[b].join("") : Buffer.concat(this[b], this[_])]);
26475
- let e = this[Ls](t || null, this[b][0]);
26476
- return this[Q](), e;
26525
+ };
26526
+ /**
26527
+ * The private keys each `signing` value has read, by file or by secrets
26528
+ * adapter, so the commands of one run that sign and derive the public key
26529
+ * read the key once.
26530
+ */
26531
+ const privateKeys = /* @__PURE__ */ new WeakMap();
26532
+ /**
26533
+ * The private key `signing` signs with, or null when it signs nothing.
26534
+ * Throws `INVALID_SIGNING_MESSAGE`, with the next step, for a value
26535
+ * `signing` doesn't take.
26536
+ */
26537
+ const loadSigningKey = (signing, { cwd = getCwd(), secrets }) => {
26538
+ const state = readBundleSigningState(signing);
26539
+ if (state.status === "invalid") throw new BundleSigningKeyError(`${state.message}\n${INVALID_SIGNING_NEXT_STEP} ${PUT_SIGNING_KEY_COMMAND}`);
26540
+ if (state.status !== "on") return null;
26541
+ const store = secrets ?? getConfiguredSecrets();
26542
+ const cacheKey = "secret" in state.key ? store : resolvePath(cwd, state.key.privateKeyPath);
26543
+ const cache = privateKeys.get(signing) ?? /* @__PURE__ */ new Map();
26544
+ privateKeys.set(signing, cache);
26545
+ const cached = cache.get(cacheKey);
26546
+ if (cached) return cached;
26547
+ const pending = ("secret" in state.key ? loadPrivateKeySecret(store, state.key.secret, cwd) : loadPrivateKeyFile(cacheKey)).catch((error) => {
26548
+ cache.delete(cacheKey);
26549
+ throw error;
26550
+ });
26551
+ cache.set(cacheKey, pending);
26552
+ return pending;
26553
+ };
26554
+ const signingFailure = (summary, error) => new Error(error instanceof BundleSigningKeyError ? error.message : `${summary} ${error instanceof Error ? error.message : String(error)}`, { cause: error });
26555
+ /**
26556
+ * Prepares the run's bundle signing: the public key the app must embed, and
26557
+ * a signer of SHA-256 file hashes that signs each hash once. Null when
26558
+ * `signing` is omitted or `{ enabled: false }`.
26559
+ */
26560
+ const prepareBundleSigning = async (signing, options = {}) => {
26561
+ const loading = loadSigningKey(signing, options);
26562
+ if (!loading) return null;
26563
+ let privateKey;
26564
+ try {
26565
+ privateKey = await loading;
26566
+ } catch (error) {
26567
+ throw signingFailure("Failed to read the bundle signing key.", error);
26477
26568
  }
26478
- [Ls](t, e) {
26479
- if (this[L]) this[Ae]();
26480
- else {
26481
- let i = e;
26482
- t === i.length || t === null ? this[Ae]() : typeof i == "string" ? (this[b][0] = i.slice(t), e = i.slice(0, t), this[_] -= t) : (this[b][0] = i.subarray(t), e = i.subarray(0, t), this[_] -= t);
26569
+ const signatures = /* @__PURE__ */ new Map();
26570
+ return {
26571
+ publicKey: exportPublicKey(privateKey),
26572
+ signFileHash: async (fileHash) => {
26573
+ if (!FILE_HASH_PATTERN.test(fileHash)) throw new Error("Bundle signing requires a 64-character hexadecimal file hash.");
26574
+ const normalized = fileHash.toLowerCase();
26575
+ const cached = signatures.get(normalized);
26576
+ if (cached) return cached;
26577
+ const signature = crypto$1.sign("RSA-SHA256", Buffer.from(normalized, "hex"), privateKey).toString("base64");
26578
+ signatures.set(normalized, signature);
26579
+ return signature;
26483
26580
  }
26484
- return this.emit("data", e), !this[b].length && !this[q] && this.emit("drain"), e;
26485
- }
26486
- end(t, e, i) {
26487
- return typeof t == "function" && (i = t, t = void 0), typeof e == "function" && (i = e, e = "utf8"), t !== void 0 && this.write(t, e), i && this.once("end", i), this[q] = !0, this.writable = !1, (this[g] || !this[Qt]) && this[Q](), this;
26488
- }
26489
- [Bt]() {
26490
- this[w] || (!this[Rt] && !this[D].length && (this[F] = !0), this[Qt] = !1, this[g] = !0, this.emit("resume"), this[b].length ? this[De]() : this[q] ? this[Q]() : this.emit("drain"));
26491
- }
26492
- resume() {
26493
- return this[Bt]();
26581
+ };
26582
+ };
26583
+ /**
26584
+ * The public key of the configured bundle signing key, in SPKI PEM; null
26585
+ * when `signing` signs nothing.
26586
+ */
26587
+ const getBundleSigningPublicKey = async (signing, options = {}) => {
26588
+ const loading = loadSigningKey(signing, options);
26589
+ if (!loading) return null;
26590
+ try {
26591
+ return exportPublicKey(await loading);
26592
+ } catch (error) {
26593
+ throw signingFailure("Failed to read the bundle signing key.", error);
26494
26594
  }
26495
- pause() {
26496
- this[g] = !1, this[Qt] = !0, this[F] = !1;
26595
+ };
26596
+ //#endregion
26597
+ //#region src/ConfigBuilder.ts
26598
+ const normalizeImportInfos = (imports) => {
26599
+ const collectedImports = /* @__PURE__ */ new Map();
26600
+ for (const info of imports) {
26601
+ const existing = collectedImports.get(info.pkg);
26602
+ if (existing) {
26603
+ if (info.named) for (const namedImport of info.named) existing.named.add(namedImport);
26604
+ if (info.defaultOrNamespace && !existing.defaultOrNamespace) existing.defaultOrNamespace = info.defaultOrNamespace;
26605
+ if (info.sideEffect && !existing.sideEffect) existing.sideEffect = true;
26606
+ continue;
26607
+ }
26608
+ collectedImports.set(info.pkg, {
26609
+ named: new Set(info.named ?? []),
26610
+ defaultOrNamespace: info.defaultOrNamespace,
26611
+ sideEffect: info.sideEffect ?? false
26612
+ });
26497
26613
  }
26498
- get destroyed() {
26499
- return this[w];
26614
+ return Array.from(collectedImports.entries()).sort(([a], [b]) => {
26615
+ const isABuild = a.startsWith("@hot-updater/");
26616
+ if (isABuild !== b.startsWith("@hot-updater/")) return isABuild ? -1 : 1;
26617
+ const isAdminA = a === "firebase-admin";
26618
+ if (isAdminA !== (b === "firebase-admin")) return isAdminA ? -1 : 1;
26619
+ return a.localeCompare(b);
26620
+ }).map(([pkg, info]) => ({
26621
+ pkg,
26622
+ named: Array.from(info.named).sort(),
26623
+ defaultOrNamespace: info.defaultOrNamespace,
26624
+ sideEffect: info.sideEffect ?? false
26625
+ }));
26626
+ };
26627
+ const renderImportStatements = (imports) => {
26628
+ const importLines = [];
26629
+ for (const info of normalizeImportInfos(imports)) {
26630
+ if (info.sideEffect) {
26631
+ importLines.push(`import "${info.pkg}";`);
26632
+ continue;
26633
+ }
26634
+ if (info.defaultOrNamespace) {
26635
+ if (info.pkg === "firebase-admin" && (info.named?.length ?? 0) > 0) importLines.push(`import ${info.defaultOrNamespace}, { ${info.named.join(", ")} } from "${info.pkg}";`);
26636
+ else importLines.push(`import ${info.defaultOrNamespace} from "${info.pkg}";`);
26637
+ continue;
26638
+ }
26639
+ if ((info.named?.length ?? 0) > 0) importLines.push(`import { ${info.named.join(", ")} } from "${info.pkg}";`);
26500
26640
  }
26501
- get flowing() {
26502
- return this[g];
26641
+ return importLines.join("\n");
26642
+ };
26643
+ /**
26644
+ * Renders the hot-updater.config.ts init writes: the build, the server's
26645
+ * storage, database, and plugins, and the deploy settings.
26646
+ */
26647
+ var ConfigBuilder = class {
26648
+ buildType = null;
26649
+ storageInfo = null;
26650
+ databaseInfo = null;
26651
+ pluginsInfo = null;
26652
+ intermediateCode = "";
26653
+ imports = [{
26654
+ pkg: "hot-updater",
26655
+ named: ["defineConfig", "localSecrets"]
26656
+ }, {
26657
+ pkg: "node:fs",
26658
+ named: ["existsSync"]
26659
+ }];
26660
+ /** Adds an import, such as a credentials helper's. */
26661
+ addImport(info) {
26662
+ this.imports.push(info);
26663
+ return this;
26503
26664
  }
26504
- get paused() {
26505
- return this[Qt];
26665
+ generateBuildConfigString() {
26666
+ if (!this.buildType) throw new Error("Build type must be set using .setBuildType()");
26667
+ switch (this.buildType) {
26668
+ case "bare": return "bare({ enableHermes: true })";
26669
+ case "rock": return "rock()";
26670
+ case "expo": return "expo()";
26671
+ default: throw new Error(`Invalid build type: ${this.buildType}`);
26672
+ }
26506
26673
  }
26507
- [gi](t) {
26508
- this[L] ? this[_] += 1 : this[_] += t.length, this[b].push(t);
26674
+ /** Sets the build type ('bare', 'rock', or 'expo') and its import. */
26675
+ setBuildType(buildType) {
26676
+ this.buildType = buildType;
26677
+ this.imports.push({
26678
+ pkg: `@hot-updater/${buildType}`,
26679
+ named: [buildType]
26680
+ });
26681
+ return this;
26509
26682
  }
26510
- [Ae]() {
26511
- return this[L] ? this[_] -= 1 : this[_] -= this[b][0].length, this[b].shift();
26683
+ /** Sets the storage the CLI uploads to, which the server lists, and its imports. */
26684
+ setStorage(storageConfig) {
26685
+ this.storageInfo = storageConfig;
26686
+ this.imports.push(...storageConfig.imports);
26687
+ return this;
26512
26688
  }
26513
- [De](t = !1) {
26514
- do ;
26515
- while (this[Ns](this[Ae]()) && this[b].length);
26516
- !t && !this[b].length && !this[q] && this.emit("drain");
26689
+ /** Sets the server's database and its imports. */
26690
+ setDatabase(databaseConfig) {
26691
+ this.databaseInfo = databaseConfig;
26692
+ this.imports.push(...databaseConfig.imports);
26693
+ return this;
26517
26694
  }
26518
- [Ns](t) {
26519
- return this.emit("data", t), this[g];
26695
+ /** Sets the server's plugin factory array and its imports. */
26696
+ setPlugins(pluginsConfig) {
26697
+ this.pluginsInfo = pluginsConfig;
26698
+ this.imports.push(...pluginsConfig.imports);
26699
+ return this;
26520
26700
  }
26521
- pipe(t, e) {
26522
- if (this[w]) return t;
26523
- this[F] = !1;
26524
- let i = this[rt];
26525
- return e = e || {}, t === xs.stdout || t === xs.stderr ? e.end = !1 : e.end = e.end !== !1, e.proxyErrors = !!e.proxyErrors, i ? e.end && t.end() : (this[D].push(e.proxyErrors ? new xi(this, t, e) : new Fe(this, t, e)), this[Z] ? jt(() => this[Bt]()) : this[Bt]()), t;
26701
+ /** Sets the code between the environment loading and the config, such as a credentials helper. */
26702
+ setIntermediateCode(code) {
26703
+ this.intermediateCode = code.trim();
26704
+ return this;
26526
26705
  }
26527
- unpipe(t) {
26528
- let e = this[D].find((i) => i.dest === t);
26529
- e && (this[D].length === 1 ? (this[g] && this[Rt] === 0 && (this[g] = !1), this[D] = []) : this[D].splice(this[D].indexOf(e), 1), e.unpipe());
26706
+ getScaffold() {
26707
+ if (!this.buildType) throw new Error("Build type must be set using .setBuildType()");
26708
+ if (!this.storageInfo) throw new Error("Storage config must be set using .setStorage()");
26709
+ if (!this.databaseInfo) throw new Error("Database config must be set using .setDatabase()");
26710
+ if (!this.pluginsInfo) throw new Error("Plugins config must be set using .setPlugins()");
26711
+ const imports = normalizeImportInfos(this.imports);
26712
+ const buildConfigString = this.generateBuildConfigString();
26713
+ const plugins = this.pluginsInfo.configString;
26714
+ const text = `
26715
+ ${renderImportStatements(imports)}
26716
+
26717
+ if (existsSync(".env.hotupdater")) {
26718
+ process.loadEnvFile(".env.hotupdater");
26719
+ }
26720
+
26721
+ ${this.intermediateCode ? `${this.intermediateCode}\n\n` : ""}export default defineConfig({
26722
+ build: ${buildConfigString},
26723
+ storage: ${this.storageInfo.configString},
26724
+ database: ${this.databaseInfo.configString},
26725
+ ${plugins === "plugins" ? "plugins" : `plugins: ${plugins}`},
26726
+ secrets: localSecrets(),
26727
+ updateStrategy: "appVersion", // or "fingerprint"
26728
+ });
26729
+ `.trim();
26730
+ return {
26731
+ imports,
26732
+ buildConfigString,
26733
+ storageConfigString: this.storageInfo.configString,
26734
+ databaseConfigString: this.databaseInfo.configString,
26735
+ pluginsConfigString: plugins,
26736
+ intermediateCode: this.intermediateCode,
26737
+ text
26738
+ };
26530
26739
  }
26531
- addListener(t, e) {
26532
- return this.on(t, e);
26740
+ /** hot-updater.config.ts's text. */
26741
+ getResult() {
26742
+ return this.getScaffold().text;
26533
26743
  }
26534
- on(t, e) {
26535
- let i = super.on(t, e);
26536
- if (t === "data") this[F] = !1, this[Rt]++, !this[D].length && !this[g] && this[Bt]();
26537
- else if (t === "readable" && this[_] !== 0) super.emit("readable");
26538
- else if (Gr(t) && this[rt]) super.emit(t), this.removeAllListeners(t);
26539
- else if (t === "error" && this[qt]) {
26540
- let r = e;
26541
- this[Z] ? jt(() => r.call(this, this[qt])) : r.call(this, this[qt]);
26542
- }
26543
- return i;
26744
+ };
26745
+ //#endregion
26746
+ //#region src/HotUpdateDirUtil.ts
26747
+ const HOT_UPDATE_DIR_NAME = ".hot-updater";
26748
+ const HOT_UPDATE_OUTPUT_DIR_NAME = "output";
26749
+ const HOT_UPDATE_LOG_DIR_NAME = "log";
26750
+ const HotUpdateDirUtil = {
26751
+ dirName: HOT_UPDATE_DIR_NAME,
26752
+ outputDirName: HOT_UPDATE_OUTPUT_DIR_NAME,
26753
+ logDirName: HOT_UPDATE_LOG_DIR_NAME,
26754
+ outputGitignorePath: `${HOT_UPDATE_DIR_NAME}/${HOT_UPDATE_OUTPUT_DIR_NAME}`,
26755
+ logGitignorePath: `${HOT_UPDATE_DIR_NAME}/${HOT_UPDATE_LOG_DIR_NAME}`,
26756
+ getDirPath: ({ cwd }) => {
26757
+ return path$1.join(cwd, HOT_UPDATE_DIR_NAME);
26758
+ },
26759
+ getDefaultOutputPath: ({ cwd }) => {
26760
+ return path$1.join(cwd, HOT_UPDATE_DIR_NAME, HOT_UPDATE_OUTPUT_DIR_NAME);
26761
+ },
26762
+ getLogDirPath: ({ cwd }) => {
26763
+ return path$1.join(cwd, HOT_UPDATE_DIR_NAME, HOT_UPDATE_LOG_DIR_NAME);
26544
26764
  }
26545
- removeListener(t, e) {
26546
- return this.off(t, e);
26765
+ };
26766
+ //#endregion
26767
+ //#region src/copyDirToTmp.ts
26768
+ const copyDirToTmp = async (dir, childDirname) => {
26769
+ const cwd = getCwd();
26770
+ const hotUpdaterDir = HotUpdateDirUtil.getDirPath({ cwd });
26771
+ const tmpDir = childDirname ? path.join(hotUpdaterDir, childDirname) : hotUpdaterDir;
26772
+ try {
26773
+ await fs$1.rm(tmpDir, {
26774
+ recursive: true,
26775
+ force: true
26776
+ });
26777
+ } catch {}
26778
+ await fs$1.mkdir(tmpDir, { recursive: true });
26779
+ await fs$1.cp(dir, tmpDir, { recursive: true });
26780
+ return {
26781
+ tmpDir: hotUpdaterDir,
26782
+ removeTmpDir: () => fs$1.rm(hotUpdaterDir, { recursive: true })
26783
+ };
26784
+ };
26785
+ //#endregion
26786
+ //#region ../../node_modules/.pnpm/tar@7.5.16/node_modules/tar/dist/esm/index.min.js
26787
+ var Mr = Object.defineProperty;
26788
+ var Br = (s, t) => {
26789
+ for (var e in t) Mr(s, e, {
26790
+ get: t[e],
26791
+ enumerable: !0
26792
+ });
26793
+ };
26794
+ var xs = typeof process == "object" && process ? process : {
26795
+ stdout: null,
26796
+ stderr: null
26797
+ };
26798
+ var zr = (s) => !!s && typeof s == "object" && (s instanceof A || s instanceof As || Ur(s) || Hr(s));
26799
+ var Ur = (s) => !!s && typeof s == "object" && s instanceof EventEmitter$1 && typeof s.pipe == "function" && s.pipe !== As.Writable.prototype.pipe;
26800
+ var Hr = (s) => !!s && typeof s == "object" && s instanceof EventEmitter$1 && typeof s.write == "function" && typeof s.end == "function";
26801
+ var q = Symbol("EOF");
26802
+ var Q = Symbol("maybeEmitEnd");
26803
+ var rt = Symbol("emittedEnd");
26804
+ var Le = Symbol("emittingEnd");
26805
+ var qt = Symbol("emittedError");
26806
+ var Ne = Symbol("closed");
26807
+ var Ls = Symbol("read");
26808
+ var De = Symbol("flush");
26809
+ var Ns = Symbol("flushChunk");
26810
+ var z = Symbol("encoding");
26811
+ var Mt = Symbol("decoder");
26812
+ var g = Symbol("flowing");
26813
+ var Qt = Symbol("paused");
26814
+ var Bt = Symbol("resume");
26815
+ var b = Symbol("buffer");
26816
+ var D = Symbol("pipes");
26817
+ var _ = Symbol("bufferLength");
26818
+ var gi = Symbol("bufferPush");
26819
+ var Ae = Symbol("bufferShift");
26820
+ var L = Symbol("objectMode");
26821
+ var w = Symbol("destroyed");
26822
+ var bi = Symbol("error");
26823
+ var _i = Symbol("emitData");
26824
+ var Ds = Symbol("emitEnd");
26825
+ var Oi = Symbol("emitEnd2");
26826
+ var Z = Symbol("async");
26827
+ var Ti = Symbol("abort");
26828
+ var Ie = Symbol("aborted");
26829
+ var Jt = Symbol("signal");
26830
+ var Rt = Symbol("dataListeners");
26831
+ var F = Symbol("discarded");
26832
+ var jt = (s) => Promise.resolve().then(s);
26833
+ var Wr = (s) => s();
26834
+ var Gr = (s) => s === "end" || s === "finish" || s === "prefinish";
26835
+ var Zr = (s) => s instanceof ArrayBuffer || !!s && typeof s == "object" && s.constructor && s.constructor.name === "ArrayBuffer" && s.byteLength >= 0;
26836
+ var Yr = (s) => !Buffer.isBuffer(s) && ArrayBuffer.isView(s);
26837
+ var Fe = class {
26838
+ src;
26839
+ dest;
26840
+ opts;
26841
+ ondrain;
26842
+ constructor(t, e, i) {
26843
+ this.src = t, this.dest = e, this.opts = i, this.ondrain = () => t[Bt](), this.dest.on("drain", this.ondrain);
26547
26844
  }
26548
- off(t, e) {
26549
- let i = super.off(t, e);
26550
- return t === "data" && (this[Rt] = this.listeners("data").length, this[Rt] === 0 && !this[F] && !this[D].length && (this[g] = !1)), i;
26845
+ unpipe() {
26846
+ this.dest.removeListener("drain", this.ondrain);
26551
26847
  }
26552
- removeAllListeners(t) {
26553
- let e = super.removeAllListeners(t);
26554
- return (t === "data" || t === void 0) && (this[Rt] = 0, !this[F] && !this[D].length && (this[g] = !1)), e;
26848
+ proxyErrors(t) {}
26849
+ end() {
26850
+ this.unpipe(), this.opts.end && this.dest.end();
26555
26851
  }
26556
- get emittedEnd() {
26557
- return this[rt];
26852
+ };
26853
+ var xi = class extends Fe {
26854
+ unpipe() {
26855
+ this.src.removeListener("error", this.proxyErrors), super.unpipe();
26558
26856
  }
26559
- [Q]() {
26560
- !this[Le] && !this[rt] && !this[w] && this[b].length === 0 && this[q] && (this[Le] = !0, this.emit("end"), this.emit("prefinish"), this.emit("finish"), this[Ne] && this.emit("close"), this[Le] = !1);
26857
+ constructor(t, e, i) {
26858
+ super(t, e, i), this.proxyErrors = (r) => this.dest.emit("error", r), t.on("error", this.proxyErrors);
26561
26859
  }
26562
- emit(t, ...e) {
26563
- let i = e[0];
26564
- if (t !== "error" && t !== "close" && t !== w && this[w]) return !1;
26565
- if (t === "data") return !this[L] && !i ? !1 : this[Z] ? (jt(() => this[_i](i)), !0) : this[_i](i);
26566
- if (t === "end") return this[Ds]();
26567
- if (t === "close") {
26568
- if (this[Ne] = !0, !this[rt] && !this[w]) return !1;
26569
- let n = super.emit("close");
26570
- return this.removeAllListeners("close"), n;
26571
- } else if (t === "error") {
26572
- this[qt] = i, super.emit(bi, i);
26573
- let n = !this[Jt] || this.listeners("error").length ? super.emit("error", i) : !1;
26574
- return this[Q](), n;
26575
- } else if (t === "resume") {
26576
- let n = super.emit("resume");
26577
- return this[Q](), n;
26578
- } else if (t === "finish" || t === "prefinish") {
26579
- let n = super.emit(t);
26580
- return this.removeAllListeners(t), n;
26581
- }
26582
- let r = super.emit(t, ...e);
26583
- return this[Q](), r;
26860
+ };
26861
+ var Kr = (s) => !!s.objectMode;
26862
+ var Vr = (s) => !s.objectMode && !!s.encoding && s.encoding !== "buffer";
26863
+ var A = class extends EventEmitter$1 {
26864
+ [g] = !1;
26865
+ [Qt] = !1;
26866
+ [D] = [];
26867
+ [b] = [];
26868
+ [L];
26869
+ [z];
26870
+ [Z];
26871
+ [Mt];
26872
+ [q] = !1;
26873
+ [rt] = !1;
26874
+ [Le] = !1;
26875
+ [Ne] = !1;
26876
+ [qt] = null;
26877
+ [_] = 0;
26878
+ [w] = !1;
26879
+ [Jt];
26880
+ [Ie] = !1;
26881
+ [Rt] = 0;
26882
+ [F] = !1;
26883
+ writable = !0;
26884
+ readable = !0;
26885
+ constructor(...t) {
26886
+ let e = t[0] || {};
26887
+ if (super(), e.objectMode && typeof e.encoding == "string") throw new TypeError("Encoding and objectMode may not be used together");
26888
+ Kr(e) ? (this[L] = !0, this[z] = null) : Vr(e) ? (this[z] = e.encoding, this[L] = !1) : (this[L] = !1, this[z] = null), this[Z] = !!e.async, this[Mt] = this[z] ? new StringDecoder(this[z]) : null, e && e.debugExposeBuffer === !0 && Object.defineProperty(this, "buffer", { get: () => this[b] }), e && e.debugExposePipes === !0 && Object.defineProperty(this, "pipes", { get: () => this[D] });
26889
+ let { signal: i } = e;
26890
+ i && (this[Jt] = i, i.aborted ? this[Ti]() : i.addEventListener("abort", () => this[Ti]()));
26584
26891
  }
26585
- [_i](t) {
26586
- for (let i of this[D]) i.dest.write(t) === !1 && this.pause();
26587
- let e = this[F] ? !1 : super.emit("data", t);
26588
- return this[Q](), e;
26892
+ get bufferLength() {
26893
+ return this[_];
26589
26894
  }
26590
- [Ds]() {
26591
- return this[rt] ? !1 : (this[rt] = !0, this.readable = !1, this[Z] ? (jt(() => this[Oi]()), !0) : this[Oi]());
26895
+ get encoding() {
26896
+ return this[z];
26592
26897
  }
26593
- [Oi]() {
26594
- if (this[Mt]) {
26595
- let e = this[Mt].end();
26596
- if (e) {
26597
- for (let i of this[D]) i.dest.write(e);
26598
- this[F] || super.emit("data", e);
26599
- }
26600
- }
26601
- for (let e of this[D]) e.end();
26602
- let t = super.emit("end");
26603
- return this.removeAllListeners("end"), t;
26898
+ set encoding(t) {
26899
+ throw new Error("Encoding must be set at instantiation time");
26604
26900
  }
26605
- async collect() {
26606
- let t = Object.assign([], { dataLength: 0 });
26607
- this[L] || (t.dataLength = 0);
26608
- let e = this.promise();
26609
- return this.on("data", (i) => {
26610
- t.push(i), this[L] || (t.dataLength += i.length);
26611
- }), await e, t;
26901
+ setEncoding(t) {
26902
+ throw new Error("Encoding must be set at instantiation time");
26903
+ }
26904
+ get objectMode() {
26905
+ return this[L];
26906
+ }
26907
+ set objectMode(t) {
26908
+ throw new Error("objectMode must be set at instantiation time");
26909
+ }
26910
+ get async() {
26911
+ return this[Z];
26912
+ }
26913
+ set async(t) {
26914
+ this[Z] = this[Z] || !!t;
26915
+ }
26916
+ [Ti]() {
26917
+ this[Ie] = !0, this.emit("abort", this[Jt]?.reason), this.destroy(this[Jt]?.reason);
26918
+ }
26919
+ get aborted() {
26920
+ return this[Ie];
26921
+ }
26922
+ set aborted(t) {}
26923
+ write(t, e, i) {
26924
+ if (this[Ie]) return !1;
26925
+ if (this[q]) throw new Error("write after end");
26926
+ if (this[w]) return this.emit("error", Object.assign(/* @__PURE__ */ new Error("Cannot call write after a stream was destroyed"), { code: "ERR_STREAM_DESTROYED" })), !0;
26927
+ typeof e == "function" && (i = e, e = "utf8"), e || (e = "utf8");
26928
+ let r = this[Z] ? jt : Wr;
26929
+ if (!this[L] && !Buffer.isBuffer(t)) {
26930
+ if (Yr(t)) t = Buffer.from(t.buffer, t.byteOffset, t.byteLength);
26931
+ else if (Zr(t)) t = Buffer.from(t);
26932
+ else if (typeof t != "string") throw new Error("Non-contiguous data written to non-objectMode stream");
26933
+ }
26934
+ return this[L] ? (this[g] && this[_] !== 0 && this[De](!0), this[g] ? this.emit("data", t) : this[gi](t), this[_] !== 0 && this.emit("readable"), i && r(i), this[g]) : t.length ? (typeof t == "string" && !(e === this[z] && !this[Mt]?.lastNeed) && (t = Buffer.from(t, e)), Buffer.isBuffer(t) && this[z] && (t = this[Mt].write(t)), this[g] && this[_] !== 0 && this[De](!0), this[g] ? this.emit("data", t) : this[gi](t), this[_] !== 0 && this.emit("readable"), i && r(i), this[g]) : (this[_] !== 0 && this.emit("readable"), i && r(i), this[g]);
26935
+ }
26936
+ read(t) {
26937
+ if (this[w]) return null;
26938
+ if (this[F] = !1, this[_] === 0 || t === 0 || t && t > this[_]) return this[Q](), null;
26939
+ this[L] && (t = null), this[b].length > 1 && !this[L] && (this[b] = [this[z] ? this[b].join("") : Buffer.concat(this[b], this[_])]);
26940
+ let e = this[Ls](t || null, this[b][0]);
26941
+ return this[Q](), e;
26942
+ }
26943
+ [Ls](t, e) {
26944
+ if (this[L]) this[Ae]();
26945
+ else {
26946
+ let i = e;
26947
+ t === i.length || t === null ? this[Ae]() : typeof i == "string" ? (this[b][0] = i.slice(t), e = i.slice(0, t), this[_] -= t) : (this[b][0] = i.subarray(t), e = i.subarray(0, t), this[_] -= t);
26948
+ }
26949
+ return this.emit("data", e), !this[b].length && !this[q] && this.emit("drain"), e;
26950
+ }
26951
+ end(t, e, i) {
26952
+ return typeof t == "function" && (i = t, t = void 0), typeof e == "function" && (i = e, e = "utf8"), t !== void 0 && this.write(t, e), i && this.once("end", i), this[q] = !0, this.writable = !1, (this[g] || !this[Qt]) && this[Q](), this;
26953
+ }
26954
+ [Bt]() {
26955
+ this[w] || (!this[Rt] && !this[D].length && (this[F] = !0), this[Qt] = !1, this[g] = !0, this.emit("resume"), this[b].length ? this[De]() : this[q] ? this[Q]() : this.emit("drain"));
26956
+ }
26957
+ resume() {
26958
+ return this[Bt]();
26959
+ }
26960
+ pause() {
26961
+ this[g] = !1, this[Qt] = !0, this[F] = !1;
26962
+ }
26963
+ get destroyed() {
26964
+ return this[w];
26965
+ }
26966
+ get flowing() {
26967
+ return this[g];
26968
+ }
26969
+ get paused() {
26970
+ return this[Qt];
26971
+ }
26972
+ [gi](t) {
26973
+ this[L] ? this[_] += 1 : this[_] += t.length, this[b].push(t);
26974
+ }
26975
+ [Ae]() {
26976
+ return this[L] ? this[_] -= 1 : this[_] -= this[b][0].length, this[b].shift();
26977
+ }
26978
+ [De](t = !1) {
26979
+ do ;
26980
+ while (this[Ns](this[Ae]()) && this[b].length);
26981
+ !t && !this[b].length && !this[q] && this.emit("drain");
26982
+ }
26983
+ [Ns](t) {
26984
+ return this.emit("data", t), this[g];
26985
+ }
26986
+ pipe(t, e) {
26987
+ if (this[w]) return t;
26988
+ this[F] = !1;
26989
+ let i = this[rt];
26990
+ return e = e || {}, t === xs.stdout || t === xs.stderr ? e.end = !1 : e.end = e.end !== !1, e.proxyErrors = !!e.proxyErrors, i ? e.end && t.end() : (this[D].push(e.proxyErrors ? new xi(this, t, e) : new Fe(this, t, e)), this[Z] ? jt(() => this[Bt]()) : this[Bt]()), t;
26991
+ }
26992
+ unpipe(t) {
26993
+ let e = this[D].find((i) => i.dest === t);
26994
+ e && (this[D].length === 1 ? (this[g] && this[Rt] === 0 && (this[g] = !1), this[D] = []) : this[D].splice(this[D].indexOf(e), 1), e.unpipe());
26995
+ }
26996
+ addListener(t, e) {
26997
+ return this.on(t, e);
26998
+ }
26999
+ on(t, e) {
27000
+ let i = super.on(t, e);
27001
+ if (t === "data") this[F] = !1, this[Rt]++, !this[D].length && !this[g] && this[Bt]();
27002
+ else if (t === "readable" && this[_] !== 0) super.emit("readable");
27003
+ else if (Gr(t) && this[rt]) super.emit(t), this.removeAllListeners(t);
27004
+ else if (t === "error" && this[qt]) {
27005
+ let r = e;
27006
+ this[Z] ? jt(() => r.call(this, this[qt])) : r.call(this, this[qt]);
27007
+ }
27008
+ return i;
27009
+ }
27010
+ removeListener(t, e) {
27011
+ return this.off(t, e);
27012
+ }
27013
+ off(t, e) {
27014
+ let i = super.off(t, e);
27015
+ return t === "data" && (this[Rt] = this.listeners("data").length, this[Rt] === 0 && !this[F] && !this[D].length && (this[g] = !1)), i;
27016
+ }
27017
+ removeAllListeners(t) {
27018
+ let e = super.removeAllListeners(t);
27019
+ return (t === "data" || t === void 0) && (this[Rt] = 0, !this[F] && !this[D].length && (this[g] = !1)), e;
27020
+ }
27021
+ get emittedEnd() {
27022
+ return this[rt];
27023
+ }
27024
+ [Q]() {
27025
+ !this[Le] && !this[rt] && !this[w] && this[b].length === 0 && this[q] && (this[Le] = !0, this.emit("end"), this.emit("prefinish"), this.emit("finish"), this[Ne] && this.emit("close"), this[Le] = !1);
27026
+ }
27027
+ emit(t, ...e) {
27028
+ let i = e[0];
27029
+ if (t !== "error" && t !== "close" && t !== w && this[w]) return !1;
27030
+ if (t === "data") return !this[L] && !i ? !1 : this[Z] ? (jt(() => this[_i](i)), !0) : this[_i](i);
27031
+ if (t === "end") return this[Ds]();
27032
+ if (t === "close") {
27033
+ if (this[Ne] = !0, !this[rt] && !this[w]) return !1;
27034
+ let n = super.emit("close");
27035
+ return this.removeAllListeners("close"), n;
27036
+ } else if (t === "error") {
27037
+ this[qt] = i, super.emit(bi, i);
27038
+ let n = !this[Jt] || this.listeners("error").length ? super.emit("error", i) : !1;
27039
+ return this[Q](), n;
27040
+ } else if (t === "resume") {
27041
+ let n = super.emit("resume");
27042
+ return this[Q](), n;
27043
+ } else if (t === "finish" || t === "prefinish") {
27044
+ let n = super.emit(t);
27045
+ return this.removeAllListeners(t), n;
27046
+ }
27047
+ let r = super.emit(t, ...e);
27048
+ return this[Q](), r;
27049
+ }
27050
+ [_i](t) {
27051
+ for (let i of this[D]) i.dest.write(t) === !1 && this.pause();
27052
+ let e = this[F] ? !1 : super.emit("data", t);
27053
+ return this[Q](), e;
27054
+ }
27055
+ [Ds]() {
27056
+ return this[rt] ? !1 : (this[rt] = !0, this.readable = !1, this[Z] ? (jt(() => this[Oi]()), !0) : this[Oi]());
27057
+ }
27058
+ [Oi]() {
27059
+ if (this[Mt]) {
27060
+ let e = this[Mt].end();
27061
+ if (e) {
27062
+ for (let i of this[D]) i.dest.write(e);
27063
+ this[F] || super.emit("data", e);
27064
+ }
27065
+ }
27066
+ for (let e of this[D]) e.end();
27067
+ let t = super.emit("end");
27068
+ return this.removeAllListeners("end"), t;
27069
+ }
27070
+ async collect() {
27071
+ let t = Object.assign([], { dataLength: 0 });
27072
+ this[L] || (t.dataLength = 0);
27073
+ let e = this.promise();
27074
+ return this.on("data", (i) => {
27075
+ t.push(i), this[L] || (t.dataLength += i.length);
27076
+ }), await e, t;
26612
27077
  }
26613
27078
  async concat() {
26614
27079
  if (this[L]) throw new Error("cannot concat in objectMode");
@@ -38546,629 +39011,180 @@ var require_zipEntries = /* @__PURE__ */ __commonJSMin(((exports, module) => {
38546
39011
  else throw new Error("Corrupted zip: can't find end of central directory");
38547
39012
  }
38548
39013
  this.reader.setIndex(offset);
38549
- var endOfCentralDirOffset = offset;
38550
- this.checkSignature(sig.CENTRAL_DIRECTORY_END);
38551
- this.readBlockEndOfCentral();
38552
- if (this.diskNumber === utils.MAX_VALUE_16BITS || this.diskWithCentralDirStart === utils.MAX_VALUE_16BITS || this.centralDirRecordsOnThisDisk === utils.MAX_VALUE_16BITS || this.centralDirRecords === utils.MAX_VALUE_16BITS || this.centralDirSize === utils.MAX_VALUE_32BITS || this.centralDirOffset === utils.MAX_VALUE_32BITS) {
38553
- this.zip64 = true;
38554
- offset = this.reader.lastIndexOfSignature(sig.ZIP64_CENTRAL_DIRECTORY_LOCATOR);
38555
- if (offset < 0) throw new Error("Corrupted zip: can't find the ZIP64 end of central directory locator");
38556
- this.reader.setIndex(offset);
38557
- this.checkSignature(sig.ZIP64_CENTRAL_DIRECTORY_LOCATOR);
38558
- this.readBlockZip64EndOfCentralLocator();
38559
- if (!this.isSignature(this.relativeOffsetEndOfZip64CentralDir, sig.ZIP64_CENTRAL_DIRECTORY_END)) {
38560
- this.relativeOffsetEndOfZip64CentralDir = this.reader.lastIndexOfSignature(sig.ZIP64_CENTRAL_DIRECTORY_END);
38561
- if (this.relativeOffsetEndOfZip64CentralDir < 0) throw new Error("Corrupted zip: can't find the ZIP64 end of central directory");
38562
- }
38563
- this.reader.setIndex(this.relativeOffsetEndOfZip64CentralDir);
38564
- this.checkSignature(sig.ZIP64_CENTRAL_DIRECTORY_END);
38565
- this.readBlockZip64EndOfCentral();
38566
- }
38567
- var expectedEndOfCentralDirOffset = this.centralDirOffset + this.centralDirSize;
38568
- if (this.zip64) {
38569
- expectedEndOfCentralDirOffset += 20;
38570
- expectedEndOfCentralDirOffset += 12 + this.zip64EndOfCentralSize;
38571
- }
38572
- var extraBytes = endOfCentralDirOffset - expectedEndOfCentralDirOffset;
38573
- if (extraBytes > 0) {
38574
- if (this.isSignature(endOfCentralDirOffset, sig.CENTRAL_FILE_HEADER)) {} else this.reader.zero = extraBytes;
38575
- } else if (extraBytes < 0) throw new Error("Corrupted zip: missing " + Math.abs(extraBytes) + " bytes.");
38576
- },
38577
- prepareReader: function(data) {
38578
- this.reader = readerFor(data);
38579
- },
38580
- /**
38581
- * Read a zip file and create ZipEntries.
38582
- * @param {String|ArrayBuffer|Uint8Array|Buffer} data the binary string representing a zip file.
38583
- */
38584
- load: function(data) {
38585
- this.prepareReader(data);
38586
- this.readEndOfCentral();
38587
- this.readCentralDir();
38588
- this.readLocalFiles();
38589
- }
38590
- };
38591
- module.exports = ZipEntries;
38592
- }));
38593
- //#endregion
38594
- //#region ../../node_modules/.pnpm/jszip@3.10.1/node_modules/jszip/lib/load.js
38595
- var require_load = /* @__PURE__ */ __commonJSMin(((exports, module) => {
38596
- var utils = require_utils();
38597
- var external = require_external();
38598
- var utf8 = require_utf8();
38599
- var ZipEntries = require_zipEntries();
38600
- var Crc32Probe = require_Crc32Probe();
38601
- var nodejsUtils = require_nodejsUtils();
38602
- /**
38603
- * Check the CRC32 of an entry.
38604
- * @param {ZipEntry} zipEntry the zip entry to check.
38605
- * @return {Promise} the result.
38606
- */
38607
- function checkEntryCRC32(zipEntry) {
38608
- return new external.Promise(function(resolve, reject) {
38609
- var worker = zipEntry.decompressed.getContentWorker().pipe(new Crc32Probe());
38610
- worker.on("error", function(e) {
38611
- reject(e);
38612
- }).on("end", function() {
38613
- if (worker.streamInfo.crc32 !== zipEntry.decompressed.crc32) reject(/* @__PURE__ */ new Error("Corrupted zip : CRC32 mismatch"));
38614
- else resolve();
38615
- }).resume();
38616
- });
38617
- }
38618
- module.exports = function(data, options) {
38619
- var zip = this;
38620
- options = utils.extend(options || {}, {
38621
- base64: false,
38622
- checkCRC32: false,
38623
- optimizedBinaryString: false,
38624
- createFolders: false,
38625
- decodeFileName: utf8.utf8decode
38626
- });
38627
- if (nodejsUtils.isNode && nodejsUtils.isStream(data)) return external.Promise.reject(/* @__PURE__ */ new Error("JSZip can't accept a stream when loading a zip file."));
38628
- return utils.prepareContent("the loaded zip file", data, true, options.optimizedBinaryString, options.base64).then(function(data) {
38629
- var zipEntries = new ZipEntries(options);
38630
- zipEntries.load(data);
38631
- return zipEntries;
38632
- }).then(function checkCRC32(zipEntries) {
38633
- var promises = [external.Promise.resolve(zipEntries)];
38634
- var files = zipEntries.files;
38635
- if (options.checkCRC32) for (var i = 0; i < files.length; i++) promises.push(checkEntryCRC32(files[i]));
38636
- return external.Promise.all(promises);
38637
- }).then(function addFiles(results) {
38638
- var zipEntries = results.shift();
38639
- var files = zipEntries.files;
38640
- for (var i = 0; i < files.length; i++) {
38641
- var input = files[i];
38642
- var unsafeName = input.fileNameStr;
38643
- var safeName = utils.resolve(input.fileNameStr);
38644
- zip.file(safeName, input.decompressed, {
38645
- binary: true,
38646
- optimizedBinaryString: true,
38647
- date: input.date,
38648
- dir: input.dir,
38649
- comment: input.fileCommentStr.length ? input.fileCommentStr : null,
38650
- unixPermissions: input.unixPermissions,
38651
- dosPermissions: input.dosPermissions,
38652
- createFolders: options.createFolders
38653
- });
38654
- if (!input.dir) zip.file(safeName).unsafeOriginalName = unsafeName;
38655
- }
38656
- if (zipEntries.zipComment.length) zip.comment = zipEntries.zipComment;
38657
- return zip;
38658
- });
38659
- };
38660
- }));
38661
- //#endregion
38662
- //#region src/createZip.ts
38663
- var import_lib = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
38664
- /**
38665
- * Representation a of zip file in js
38666
- * @constructor
38667
- */
38668
- function JSZip() {
38669
- if (!(this instanceof JSZip)) return new JSZip();
38670
- if (arguments.length) throw new Error("The constructor with parameters has been removed in JSZip 3.0, please check the upgrade guide.");
38671
- this.files = Object.create(null);
38672
- this.comment = null;
38673
- this.root = "";
38674
- this.clone = function() {
38675
- var newObj = new JSZip();
38676
- for (var i in this) if (typeof this[i] !== "function") newObj[i] = this[i];
38677
- return newObj;
38678
- };
38679
- }
38680
- JSZip.prototype = require_object();
38681
- JSZip.prototype.loadAsync = require_load();
38682
- JSZip.support = require_support();
38683
- JSZip.defaults = require_defaults();
38684
- JSZip.version = "3.10.1";
38685
- JSZip.loadAsync = function(content, options) {
38686
- return new JSZip().loadAsync(content, options);
38687
- };
38688
- JSZip.external = require_external();
38689
- module.exports = JSZip;
38690
- })))(), 1);
38691
- const createZip = async ({ outfile, targetDir, excludeExts = [] }) => {
38692
- const zip = new import_lib.default();
38693
- await fs$1.rm(outfile, { force: true });
38694
- async function addFiles(dir, zipFolder) {
38695
- const files = await fs$1.readdir(dir);
38696
- files.sort();
38697
- for (const file of files) {
38698
- if (excludeExts.some((pattern) => file.includes(pattern))) continue;
38699
- const fullPath = path.join(dir, file);
38700
- if ((await fs$1.stat(fullPath)).isDirectory()) {
38701
- const folder = zipFolder.folder(file);
38702
- if (!folder) continue;
38703
- await addFiles(fullPath, folder);
38704
- } else {
38705
- const data = await fs$1.readFile(fullPath);
38706
- zipFolder.file(file, data);
38707
- }
38708
- }
38709
- }
38710
- await addFiles(targetDir, zip);
38711
- zip.forEach((_, file) => {
38712
- file.date = /* @__PURE__ */ new Date(0);
38713
- });
38714
- const content = await zip.generateAsync({
38715
- type: "nodebuffer",
38716
- compression: "DEFLATE",
38717
- compressionOptions: { level: 9 },
38718
- platform: "UNIX"
38719
- });
38720
- await fs$1.mkdir(path.dirname(outfile), { recursive: true });
38721
- await fs$1.writeFile(outfile, content);
38722
- return outfile;
38723
- };
38724
- //#endregion
38725
- //#region src/credentialFiles.ts
38726
- const TERMINATION_SIGNALS = [
38727
- "SIGHUP",
38728
- "SIGINT",
38729
- "SIGTERM"
38730
- ];
38731
- const liveDirectories = /* @__PURE__ */ new Set();
38732
- const onSignal = (signal) => {
38733
- removeLiveDirectories();
38734
- if (process.listenerCount(signal) === 0) process.kill(process.pid, signal);
38735
- };
38736
- const removeLiveDirectories = () => {
38737
- for (const directory of liveDirectories) rmSync(directory, {
38738
- force: true,
38739
- recursive: true
38740
- });
38741
- liveDirectories.clear();
38742
- stopListening();
38743
- };
38744
- const startListening = () => {
38745
- process.on("exit", removeLiveDirectories);
38746
- for (const signal of TERMINATION_SIGNALS) process.on(signal, onSignal);
38747
- };
38748
- const stopListening = () => {
38749
- process.off("exit", removeLiveDirectories);
38750
- for (const signal of TERMINATION_SIGNALS) process.off(signal, onSignal);
38751
- };
38752
- const trackDirectory = (directory) => {
38753
- if (liveDirectories.size === 0) startListening();
38754
- liveDirectories.add(directory);
38755
- };
38756
- const untrackDirectory = (directory) => {
38757
- if (liveDirectories.delete(directory) && liveDirectories.size === 0) stopListening();
38758
- };
38759
- /**
38760
- * Creates the temporary files of one credential resolver: a `mkdtemp`
38761
- * directory, created on the first write and removed by `dispose` or, if the
38762
- * process exits first, on `exit` or a termination signal.
38763
- */
38764
- const createCredentialFiles = () => {
38765
- let directory;
38766
- let disposal;
38767
- const ensureDirectory = () => {
38768
- directory ??= fs$2.mkdtemp(path$1.join(os.tmpdir(), "hot-updater-credentials-")).then((created) => {
38769
- trackDirectory(created);
38770
- return created;
38771
- });
38772
- return directory;
38773
- };
38774
- return {
38775
- write: async (fileName, contents) => {
38776
- if (disposal) throw new Error("The credential files have been disposed.");
38777
- if (typeof contents === "string") registerSecretValue(contents);
38778
- const fileDirectory = await fs$2.mkdtemp(path$1.join(await ensureDirectory(), "item-"));
38779
- const filePath = path$1.join(fileDirectory, path$1.basename(fileName));
38780
- await fs$2.writeFile(filePath, contents, {
38781
- flag: "wx",
38782
- mode: 384
38783
- });
38784
- return filePath;
38785
- },
38786
- dispose: () => {
38787
- disposal ??= (async () => {
38788
- const created = await directory?.catch(() => void 0);
38789
- if (!created) return;
38790
- await fs$2.rm(created, {
38791
- force: true,
38792
- recursive: true
38793
- });
38794
- untrackDirectory(created);
38795
- })();
38796
- return disposal;
38797
- }
38798
- };
38799
- };
38800
- //#endregion
38801
- //#region src/credentialResolver.ts
38802
- /** Passwords are read as set: trimming would change them. */
38803
- const UNTRIMMED_ENV_NAMES = /* @__PURE__ */ new Set(["HOT_UPDATER_ANDROID_KEYSTORE_PASSWORD", "HOT_UPDATER_ANDROID_KEY_PASSWORD"]);
38804
- const describeField = ([name, alternative]) => alternative ? `${name} (or ${alternative})` : name;
38805
- const describeFields = (fields) => {
38806
- const described = fields.map(describeField);
38807
- if (described.length <= 2) return described.join(" and ");
38808
- return `${described.slice(0, -1).join(", ")}, and ${described.at(-1)}`;
38809
- };
38810
- /**
38811
- * A command to print, broken at spaces with ` \` once a line would pass
38812
- * 74 columns, each continuation indented two spaces, so a terminal never
38813
- * wraps it mid-word and it still pastes as one command.
38814
- */
38815
- const formatCommand = (command) => {
38816
- const lines = [];
38817
- let line = "";
38818
- for (const word of command.split(" ")) {
38819
- const next = line ? `${line} ${word}` : word;
38820
- if (line && next.length > 72) {
38821
- lines.push(`${line} \\`);
38822
- line = ` ${word}`;
38823
- } else line = next;
38824
- }
38825
- lines.push(line);
38826
- return lines.join("\n");
38827
- };
38828
- /**
38829
- * `lead` and the command it names on one line when they fit 77 columns,
38830
- * else the command on the lines after it.
38831
- */
38832
- const withLead = (lead, command) => lead.length + 1 + command.length <= 77 ? `${lead} ${command}` : `${lead}\n${formatCommand(command)}`;
38833
- /**
38834
- * Why a build has no upload keystore, steering away from a second key:
38835
- * Play rejects builds signed with a key it does not have. Under a team's
38836
- * adapter the keystore is someone's already, so it is imported, never
38837
- * created; only `localSecrets()` alone offers a new one, for an app with
38838
- * no `.aab` on Play yet.
38839
- */
38840
- const describeMissingKeystore = (store, item, name) => {
38841
- const configured = getConfiguredAdapter(store);
38842
- if (configured) return [
38843
- `No ${item} in ${describeSecret(configured, name)}.`,
38844
- "Ask whoever set it up to store it there, or import it with:",
38845
- formatCommand(credentialPutCommand("android-keystore", name))
38846
- ].join("\n");
38847
- return [
38848
- `No ${item} in the ${store.name} secrets adapter.`,
38849
- withLead("Store the app's upload key:", credentialPutCommand("android-keystore", name)),
38850
- withLead("No .aab on Play yet? Create one:", credentialIssueCommand("android-keystore", name))
38851
- ].join("\n");
38852
- };
38853
- /** The file each `secrets put <name> <file>` command names. */
38854
- const PUT_FILES = {
38855
- "android-keystore": "<upload.jks>",
38856
- "play-service-account": "<key.json>"
38857
- };
38858
- /**
38859
- * The command that stores the file you have as `name`: the service
38860
- * account's JSON key, or the app's upload keystore. `--kind` is named only
38861
- * for a name that is not the kind.
38862
- */
38863
- const credentialPutCommand = (kind, name = kind) => `npx hot-updater secrets put ${name}${name === kind ? "" : ` --kind ${kind}`} ${PUT_FILES[kind]}`;
38864
- /**
38865
- * The command that stores the key Hot Updater issues as `name`: an App
38866
- * Store Connect API key after an Apple Account sign-in, and an upload
38867
- * keystore it creates.
38868
- */
38869
- const credentialIssueCommand = (kind, name = kind) => `npx hot-updater secrets issue ${kind}${name === kind ? "" : ` ${name}`}`;
38870
- /** The command group of each issued kind's shortcut, `<group> login`. */
38871
- const CREDENTIAL_LOGIN_GROUPS = { "asc-api-key": "apple" };
38872
- /**
38873
- * The command people meet first that issues `kind` as `name`: its shortcut,
38874
- * `apple login` for an App Store Connect API key, which takes nothing and
38875
- * stores the kind's own name; for another name, `credentialIssueCommand`.
38876
- */
38877
- const credentialLoginCommand = (kind, name = kind) => name === kind ? `npx hot-updater ${CREDENTIAL_LOGIN_GROUPS[kind]} login` : credentialIssueCommand(kind, name);
38878
- /**
38879
- * The command that stores a missing credential of `kind` as `name`: the
38880
- * key Hot Updater issues after an Apple sign-in, or the file you have.
38881
- */
38882
- const credentialStoreCommand = (kind, name = kind) => kind === "asc-api-key" ? credentialLoginCommand(kind, name) : credentialPutCommand(kind, name);
38883
- var CredentialUnavailableError = class extends Error {
38884
- kind;
38885
- /** The environment variables that can provide this credential. */
38886
- envNames;
38887
- constructor(kind, message, options) {
38888
- super(message, options);
38889
- this.name = "CredentialUnavailableError";
38890
- this.kind = kind;
38891
- this.envNames = CREDENTIAL_ENV_NAMES[kind].flat();
38892
- }
38893
- };
38894
- const readEnvField = (env, field) => {
38895
- for (const name of field) {
38896
- const value = UNTRIMMED_ENV_NAMES.has(name) ? (process.env[name] ?? env[name]) || void 0 : getHotUpdaterEnvValue(env, name);
38897
- if (value !== void 0) return {
38898
- name,
38899
- value
38900
- };
38901
- }
38902
- };
38903
- const readFileField = async (kind, { name, value }) => {
38904
- const filePath = path$1.resolve(value);
38905
- if (!await fs$2.stat(filePath).then(async (stats) => {
38906
- await fs$2.access(filePath, fs$2.constants.R_OK);
38907
- return stats.isFile();
38908
- }).catch(() => false)) throw new CredentialUnavailableError(kind, `${name} does not point to a readable file.`);
38909
- return filePath;
38910
- };
38911
- const parseServiceAccount = (json) => {
38912
- try {
38913
- return JSON.parse(json);
38914
- } catch {
38915
- return;
38916
- }
38917
- };
38918
- const fromEnv = {
38919
- "asc-api-key": async ([key, keyId, issuerId], { files }) => ({
38920
- keyPath: key.name === "HOT_UPDATER_ASC_KEY_PATH" ? await readFileField("asc-api-key", key) : await files.write(`AuthKey_${keyId.value}.p8`, key.value.includes("\n") ? key.value : key.value.replaceAll("\\n", "\n")),
38921
- keyId: keyId.value,
38922
- issuerId: issuerId.value
38923
- }),
38924
- "android-keystore": async ([keystore, storePassword, keyAlias, keyPassword], { files, name }) => {
38925
- let storeFile;
38926
- if (keystore.name === "HOT_UPDATER_ANDROID_KEYSTORE_PATH") storeFile = await readFileField("android-keystore", keystore);
38927
- else {
38928
- registerSecretValue(keystore.value);
38929
- const contents = decodeBase64(keystore.value);
38930
- if (!contents) throw new CredentialUnavailableError("android-keystore", `${keystore.name} is not a base64-encoded keystore.`);
38931
- storeFile = await files.write(`${name}.keystore`, contents);
38932
- }
38933
- return {
38934
- storeFile,
38935
- storePassword: storePassword.value,
38936
- keyAlias: keyAlias.value,
38937
- keyPassword: keyPassword.value
38938
- };
38939
- },
38940
- "play-service-account": async ([serviceAccount]) => {
38941
- const json = serviceAccount.name === "HOT_UPDATER_PLAY_SERVICE_ACCOUNT_PATH" ? await fs$2.readFile(await readFileField("play-service-account", serviceAccount), "utf-8") : serviceAccount.value;
38942
- const parsed = parseServiceAccount(json);
38943
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new CredentialUnavailableError("play-service-account", `${serviceAccount.name} is not a service account JSON object.`);
38944
- return { json };
38945
- }
38946
- };
38947
- /** Fields of a service account that are no secret: its type and Google's endpoints. */
38948
- /**
38949
- * Fields of a JSON credential that identify it rather than grant access:
38950
- * its IDs, key alias, and a service account's email, project, type, and
38951
- * Google's endpoints. They stay readable, as messages name them, and a
38952
- * short one, such as the alias `release`, would mask every word it is in.
38953
- */
38954
- const isIdentifierField = (field) => [
38955
- "keyId",
38956
- "issuerId",
38957
- "keyAlias",
38958
- "type",
38959
- "project_id",
38960
- "client_email",
38961
- "client_id",
38962
- "private_key_id",
38963
- "universe_domain"
38964
- ].includes(field) || field.endsWith("_uri") || field.endsWith("_url");
38965
- const registerJsonSecrets = (value, fields) => {
38966
- registerSecretValue(value);
38967
- for (const [field, entry] of Object.entries(fields)) if (typeof entry === "string" && !isIdentifierField(field)) registerSecretValue(entry);
38968
- };
38969
- /**
38970
- * Masks a credential's JSON value in the CLI's output: the whole value, and
38971
- * each field but those that identify it, such as `keyId` and `keyAlias`,
38972
- * so a password or a key a log prints alone stays masked. A value that is
38973
- * no JSON object is masked whole.
38974
- */
38975
- const registerCredentialValue = (value) => {
38976
- const parsed = parseObject(value);
38977
- if (parsed) registerJsonSecrets(value, parsed);
38978
- else registerSecretValue(value);
38979
- };
38980
- const parseObject = (value) => {
38981
- const parsed = parseServiceAccount(value);
38982
- return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
38983
- };
38984
- /**
38985
- * The fields of a JSON credential, each a non-empty string, or an error
38986
- * naming the shape `kind` takes.
38987
- */
38988
- const readJsonFields = (kind, item, value, fields) => {
38989
- const parsed = parseObject(value);
38990
- const shape = `{${fields.map((field) => `"${field}"`).join(", ")}}`;
38991
- if (!parsed) throw new CredentialUnavailableError(kind, `${item} is not a JSON object.\n${capitalize(aKind(kind))} is one JSON value: ${shape}`);
38992
- registerJsonSecrets(value, parsed);
38993
- const missing = fields.filter((field) => typeof parsed[field] !== "string" || !parsed[field]);
38994
- if (missing.length > 0) throw new CredentialUnavailableError(kind, `${item} has no ${missing.join(", ")}.\n${capitalize(aKind(kind))} is one JSON value: ${shape}`);
38995
- return parsed;
38996
- };
38997
- const aKind = (kind) => `${/^[aeiou]/u.test(kind) ? "an" : "a"} ${kind}`;
38998
- const capitalize = (text) => text.charAt(0).toUpperCase() + text.slice(1);
38999
- /** A base64 value's bytes, or undefined when it is not base64. */
39000
- const decodeBase64 = (value) => {
39001
- const base64 = value.replace(/\s/g, "");
39002
- const contents = Buffer.from(base64, "base64");
39003
- return contents.length > 0 && contents.toString("base64").replace(/=+$/, "") === base64.replace(/=+$/, "") ? contents : void 0;
39004
- };
39005
- /**
39006
- * Each kind from the one string a secrets adapter holds for it: a JSON
39007
- * object for the App Store Connect key (`p8` is the PEM) and the keystore
39008
- * (`keystore` is base64), and the service account's own JSON. File values
39009
- * are written with `files`; every value is masked.
39010
- */
39011
- const fromSecretValue = {
39012
- "asc-api-key": async (value, { files, item }) => {
39013
- const { keyId, issuerId, p8 } = readJsonFields("asc-api-key", item, value, [
39014
- "keyId",
39015
- "issuerId",
39016
- "p8"
39017
- ]);
39018
- return {
39019
- keyPath: await files.write(`AuthKey_${keyId}.p8`, p8.includes("\n") ? p8 : p8.replaceAll("\\n", "\n")),
39020
- keyId,
39021
- issuerId
39022
- };
39023
- },
39024
- "android-keystore": async (value, { files, name, item }) => {
39025
- const { keystore, storePassword, keyAlias, keyPassword } = readJsonFields("android-keystore", item, value, [
39026
- "keystore",
39027
- "storePassword",
39028
- "keyAlias",
39029
- "keyPassword"
39030
- ]);
39031
- const contents = decodeBase64(keystore);
39032
- if (!contents) throw new CredentialUnavailableError("android-keystore", `${item}'s keystore is not a base64-encoded keystore.`);
39033
- return {
39034
- storeFile: await files.write(`${name}.keystore`, contents),
39035
- storePassword,
39036
- keyAlias,
39037
- keyPassword
39038
- };
39039
- },
39040
- "play-service-account": async (value, { item }) => {
39041
- const parsed = parseObject(value);
39042
- if (!parsed) throw new CredentialUnavailableError("play-service-account", `${item} is not a service account JSON object.`);
39043
- registerJsonSecrets(value, parsed);
39044
- return { json: value };
39045
- }
39046
- };
39047
- const registerCredentialSecrets = (kind, credential) => {
39048
- if (kind === "android-keystore") {
39049
- const keystore = credential;
39050
- registerSecretValue(keystore.storePassword);
39051
- registerSecretValue(keystore.keyPassword);
39052
- }
39053
- if (kind === "play-service-account") {
39054
- const { json } = credential;
39055
- registerSecretValue(json);
39056
- const privateKey = parseServiceAccount(json)?.private_key;
39057
- if (typeof privateKey === "string") registerSecretValue(privateKey);
39058
- }
39059
- };
39060
- /**
39061
- * Reads names from `secrets`, the names asked for in one turn of the event
39062
- * loop, such as a build's credentials, in one `get`. Nothing is cached.
39063
- */
39064
- const createSecretLoader = (secrets, cwd) => {
39065
- let batch;
39066
- const flush = async (names) => {
39067
- try {
39068
- const read = await secrets().get([...names.keys()], { cwd: cwd() });
39069
- for (const [name, waiters] of names) {
39070
- const value = Object.hasOwn(read, name) ? read[name] : void 0;
39071
- for (const waiter of waiters) waiter.resolve(typeof value === "string" && value ? value : void 0);
39014
+ var endOfCentralDirOffset = offset;
39015
+ this.checkSignature(sig.CENTRAL_DIRECTORY_END);
39016
+ this.readBlockEndOfCentral();
39017
+ if (this.diskNumber === utils.MAX_VALUE_16BITS || this.diskWithCentralDirStart === utils.MAX_VALUE_16BITS || this.centralDirRecordsOnThisDisk === utils.MAX_VALUE_16BITS || this.centralDirRecords === utils.MAX_VALUE_16BITS || this.centralDirSize === utils.MAX_VALUE_32BITS || this.centralDirOffset === utils.MAX_VALUE_32BITS) {
39018
+ this.zip64 = true;
39019
+ offset = this.reader.lastIndexOfSignature(sig.ZIP64_CENTRAL_DIRECTORY_LOCATOR);
39020
+ if (offset < 0) throw new Error("Corrupted zip: can't find the ZIP64 end of central directory locator");
39021
+ this.reader.setIndex(offset);
39022
+ this.checkSignature(sig.ZIP64_CENTRAL_DIRECTORY_LOCATOR);
39023
+ this.readBlockZip64EndOfCentralLocator();
39024
+ if (!this.isSignature(this.relativeOffsetEndOfZip64CentralDir, sig.ZIP64_CENTRAL_DIRECTORY_END)) {
39025
+ this.relativeOffsetEndOfZip64CentralDir = this.reader.lastIndexOfSignature(sig.ZIP64_CENTRAL_DIRECTORY_END);
39026
+ if (this.relativeOffsetEndOfZip64CentralDir < 0) throw new Error("Corrupted zip: can't find the ZIP64 end of central directory");
39027
+ }
39028
+ this.reader.setIndex(this.relativeOffsetEndOfZip64CentralDir);
39029
+ this.checkSignature(sig.ZIP64_CENTRAL_DIRECTORY_END);
39030
+ this.readBlockZip64EndOfCentral();
39072
39031
  }
39073
- } catch (error) {
39074
- for (const waiters of names.values()) for (const waiter of waiters) waiter.reject(error);
39032
+ var expectedEndOfCentralDirOffset = this.centralDirOffset + this.centralDirSize;
39033
+ if (this.zip64) {
39034
+ expectedEndOfCentralDirOffset += 20;
39035
+ expectedEndOfCentralDirOffset += 12 + this.zip64EndOfCentralSize;
39036
+ }
39037
+ var extraBytes = endOfCentralDirOffset - expectedEndOfCentralDirOffset;
39038
+ if (extraBytes > 0) {
39039
+ if (this.isSignature(endOfCentralDirOffset, sig.CENTRAL_FILE_HEADER)) {} else this.reader.zero = extraBytes;
39040
+ } else if (extraBytes < 0) throw new Error("Corrupted zip: missing " + Math.abs(extraBytes) + " bytes.");
39041
+ },
39042
+ prepareReader: function(data) {
39043
+ this.reader = readerFor(data);
39044
+ },
39045
+ /**
39046
+ * Read a zip file and create ZipEntries.
39047
+ * @param {String|ArrayBuffer|Uint8Array|Buffer} data the binary string representing a zip file.
39048
+ */
39049
+ load: function(data) {
39050
+ this.prepareReader(data);
39051
+ this.readEndOfCentral();
39052
+ this.readCentralDir();
39053
+ this.readLocalFiles();
39075
39054
  }
39076
39055
  };
39077
- return (name) => new Promise((resolve, reject) => {
39078
- if (batch === void 0) {
39079
- const names = /* @__PURE__ */ new Map();
39080
- batch = names;
39081
- setTimeout(() => {
39082
- batch = void 0;
39083
- flush(names);
39084
- }, 0);
39085
- }
39086
- const waiters = batch.get(name) ?? [];
39087
- waiters.push({
39088
- resolve,
39089
- reject
39056
+ module.exports = ZipEntries;
39057
+ }));
39058
+ //#endregion
39059
+ //#region ../../node_modules/.pnpm/jszip@3.10.1/node_modules/jszip/lib/load.js
39060
+ var require_load = /* @__PURE__ */ __commonJSMin(((exports, module) => {
39061
+ var utils = require_utils();
39062
+ var external = require_external();
39063
+ var utf8 = require_utf8();
39064
+ var ZipEntries = require_zipEntries();
39065
+ var Crc32Probe = require_Crc32Probe();
39066
+ var nodejsUtils = require_nodejsUtils();
39067
+ /**
39068
+ * Check the CRC32 of an entry.
39069
+ * @param {ZipEntry} zipEntry the zip entry to check.
39070
+ * @return {Promise} the result.
39071
+ */
39072
+ function checkEntryCRC32(zipEntry) {
39073
+ return new external.Promise(function(resolve, reject) {
39074
+ var worker = zipEntry.decompressed.getContentWorker().pipe(new Crc32Probe());
39075
+ worker.on("error", function(e) {
39076
+ reject(e);
39077
+ }).on("end", function() {
39078
+ if (worker.streamInfo.crc32 !== zipEntry.decompressed.crc32) reject(/* @__PURE__ */ new Error("Corrupted zip : CRC32 mismatch"));
39079
+ else resolve();
39080
+ }).resume();
39090
39081
  });
39091
- batch.set(name, waiters);
39092
- });
39093
- };
39094
- /**
39095
- * Resolves each item from its environment overrides when any is set, and
39096
- * otherwise from the config's `secrets` adapter as one value named by the
39097
- * item, so overrides never reach the store. When the adapter fails or holds
39098
- * no such value, the error names the environment variables that can
39099
- * provide the item instead. Nothing is cached, values are never written to
39100
- * disk except as 0600 temporary files, are never set in `process.env`, and
39101
- * every secret is masked in logs.
39102
- */
39103
- const createCredentialResolver = ({ env = {}, secrets, cwd } = {}) => {
39104
- const files = createCredentialFiles();
39105
- const resolved = /* @__PURE__ */ new Map();
39106
- let disposal;
39107
- const adapter = () => typeof secrets === "function" ? secrets() : secrets ?? getConfiguredSecrets();
39108
- const load = createSecretLoader(adapter, () => cwd ?? getCwd());
39109
- const resolveItem = async (kind, name) => {
39110
- const fields = CREDENTIAL_ENV_NAMES[kind];
39111
- const values = fields.map((field) => readEnvField(env, field));
39112
- if (values.some((value) => value !== void 0)) {
39113
- const missing = fields.filter((_, index) => !values[index]);
39114
- if (missing.length > 0) throw new CredentialUnavailableError(kind, `The environment overrides ${kind} but ${describeFields(missing)} ${missing.length === 1 ? "is" : "are"} not set.`);
39115
- const credential = await fromEnv[kind](values, {
39116
- files,
39117
- name
39118
- });
39119
- registerCredentialSecrets(kind, credential);
39120
- return credential;
39121
- }
39122
- const store = adapter();
39123
- const item = name === kind ? kind : `${name} (${kind})`;
39124
- let value;
39125
- try {
39126
- value = await load(name);
39127
- } catch (error) {
39128
- const reason = error instanceof Error ? error.message : String(error);
39129
- throw new CredentialUnavailableError(kind, `Couldn't read ${item} from the ${store.name} secrets adapter.\n${reason}`, { cause: error });
39130
- }
39131
- if (value === void 0) throw new CredentialUnavailableError(kind, kind === "android-keystore" ? describeMissingKeystore(store, item, name) : `No ${item} in the ${store.name} secrets adapter.\nStore it once with:\n${formatCommand(credentialStoreCommand(kind, name))}`);
39132
- registerSecretValue(value);
39133
- return fromSecretValue[kind](value, {
39134
- files,
39135
- name,
39136
- item: `The ${store.name} secrets adapter's ${item}`
39082
+ }
39083
+ module.exports = function(data, options) {
39084
+ var zip = this;
39085
+ options = utils.extend(options || {}, {
39086
+ base64: false,
39087
+ checkCRC32: false,
39088
+ optimizedBinaryString: false,
39089
+ createFolders: false,
39090
+ decodeFileName: utf8.utf8decode
39091
+ });
39092
+ if (nodejsUtils.isNode && nodejsUtils.isStream(data)) return external.Promise.reject(/* @__PURE__ */ new Error("JSZip can't accept a stream when loading a zip file."));
39093
+ return utils.prepareContent("the loaded zip file", data, true, options.optimizedBinaryString, options.base64).then(function(data) {
39094
+ var zipEntries = new ZipEntries(options);
39095
+ zipEntries.load(data);
39096
+ return zipEntries;
39097
+ }).then(function checkCRC32(zipEntries) {
39098
+ var promises = [external.Promise.resolve(zipEntries)];
39099
+ var files = zipEntries.files;
39100
+ if (options.checkCRC32) for (var i = 0; i < files.length; i++) promises.push(checkEntryCRC32(files[i]));
39101
+ return external.Promise.all(promises);
39102
+ }).then(function addFiles(results) {
39103
+ var zipEntries = results.shift();
39104
+ var files = zipEntries.files;
39105
+ for (var i = 0; i < files.length; i++) {
39106
+ var input = files[i];
39107
+ var unsafeName = input.fileNameStr;
39108
+ var safeName = utils.resolve(input.fileNameStr);
39109
+ zip.file(safeName, input.decompressed, {
39110
+ binary: true,
39111
+ optimizedBinaryString: true,
39112
+ date: input.date,
39113
+ dir: input.dir,
39114
+ comment: input.fileCommentStr.length ? input.fileCommentStr : null,
39115
+ unixPermissions: input.unixPermissions,
39116
+ dosPermissions: input.dosPermissions,
39117
+ createFolders: options.createFolders
39118
+ });
39119
+ if (!input.dir) zip.file(safeName).unsafeOriginalName = unsafeName;
39120
+ }
39121
+ if (zipEntries.zipComment.length) zip.comment = zipEntries.zipComment;
39122
+ return zip;
39137
39123
  });
39138
39124
  };
39139
- return {
39140
- resolve: ({ kind, name = kind }) => {
39141
- if (disposal) return Promise.reject(/* @__PURE__ */ new Error("The credential resolver has been disposed."));
39142
- const key = `${kind}\u0000${name}`;
39143
- let pending = resolved.get(key);
39144
- if (!pending) {
39145
- pending = resolveItem(kind, name);
39146
- resolved.set(key, pending);
39147
- pending.catch(() => resolved.delete(key));
39125
+ }));
39126
+ //#endregion
39127
+ //#region src/createZip.ts
39128
+ var import_lib = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
39129
+ /**
39130
+ * Representation a of zip file in js
39131
+ * @constructor
39132
+ */
39133
+ function JSZip() {
39134
+ if (!(this instanceof JSZip)) return new JSZip();
39135
+ if (arguments.length) throw new Error("The constructor with parameters has been removed in JSZip 3.0, please check the upgrade guide.");
39136
+ this.files = Object.create(null);
39137
+ this.comment = null;
39138
+ this.root = "";
39139
+ this.clone = function() {
39140
+ var newObj = new JSZip();
39141
+ for (var i in this) if (typeof this[i] !== "function") newObj[i] = this[i];
39142
+ return newObj;
39143
+ };
39144
+ }
39145
+ JSZip.prototype = require_object();
39146
+ JSZip.prototype.loadAsync = require_load();
39147
+ JSZip.support = require_support();
39148
+ JSZip.defaults = require_defaults();
39149
+ JSZip.version = "3.10.1";
39150
+ JSZip.loadAsync = function(content, options) {
39151
+ return new JSZip().loadAsync(content, options);
39152
+ };
39153
+ JSZip.external = require_external();
39154
+ module.exports = JSZip;
39155
+ })))(), 1);
39156
+ const createZip = async ({ outfile, targetDir, excludeExts = [] }) => {
39157
+ const zip = new import_lib.default();
39158
+ await fs$1.rm(outfile, { force: true });
39159
+ async function addFiles(dir, zipFolder) {
39160
+ const files = await fs$1.readdir(dir);
39161
+ files.sort();
39162
+ for (const file of files) {
39163
+ if (excludeExts.some((pattern) => file.includes(pattern))) continue;
39164
+ const fullPath = path.join(dir, file);
39165
+ if ((await fs$1.stat(fullPath)).isDirectory()) {
39166
+ const folder = zipFolder.folder(file);
39167
+ if (!folder) continue;
39168
+ await addFiles(fullPath, folder);
39169
+ } else {
39170
+ const data = await fs$1.readFile(fullPath);
39171
+ zipFolder.file(file, data);
39148
39172
  }
39149
- return pending;
39150
- },
39151
- dispose: () => {
39152
- disposal ??= (async () => {
39153
- await Promise.allSettled(resolved.values());
39154
- resolved.clear();
39155
- await files.dispose();
39156
- })();
39157
- return disposal;
39158
39173
  }
39159
- };
39160
- };
39161
- /**
39162
- * Runs `run` with a credential resolver and deletes its temporary files
39163
- * when `run` settles.
39164
- */
39165
- const withCredentialResolver = async (run, options) => {
39166
- const resolver = createCredentialResolver(options);
39167
- try {
39168
- return await run(resolver);
39169
- } finally {
39170
- await resolver.dispose();
39171
39174
  }
39175
+ await addFiles(targetDir, zip);
39176
+ zip.forEach((_, file) => {
39177
+ file.date = /* @__PURE__ */ new Date(0);
39178
+ });
39179
+ const content = await zip.generateAsync({
39180
+ type: "nodebuffer",
39181
+ compression: "DEFLATE",
39182
+ compressionOptions: { level: 9 },
39183
+ platform: "UNIX"
39184
+ });
39185
+ await fs$1.mkdir(path.dirname(outfile), { recursive: true });
39186
+ await fs$1.writeFile(outfile, content);
39187
+ return outfile;
39172
39188
  };
39173
39189
  //#endregion
39174
39190
  //#region src/isObject.ts
@@ -47745,4 +47761,4 @@ function transformTemplate(templateString, values) {
47745
47761
  return result;
47746
47762
  }
47747
47763
  //#endregion
47748
- export { BUNDLE_SIGNING_KEY_SECRET, BuildFailedError, BuildLogger, BundleSigningKeyError, CREDENTIAL_ENV_NAMES, CREDENTIAL_LOGIN_GROUPS, ConfigBuilder, CredentialUnavailableError, HOT_UPDATER_HOME_ENV, HOT_UPDATER_SERVER_PACKAGE_VERSION_ENV, HotUpdateDirUtil, INTERFACE_FINGERPRINT_ENGINE, INVALID_SIGNING_MESSAGE, INVALID_SIGNING_NEXT_STEP, ISSUE_SIGNING_KEY_COMMAND, InitEnvFileError, InitError, LOCAL_SECRETS_NAME, LegacyInfrastructureError, MANAGED_CLIENT_CREDENTIAL_ENV, MissingInitInputsError, PUT_SIGNING_KEY_COMMAND, ServerDefinitionError, UNSIGNED_BUNDLES_NEXT_STEP, UNSIGNED_BUNDLES_WARNING, addSigningSecretToConfig, addSigningSecretToConfigText, assembleServer, assertInfrastructureGenerationAtUrl, assertInfrastructureGenerationPayload, assertInitInputs, assertInitProviderInputs, assertUnreservedEnv, autolinkingConfigCommand, banner, bundleSigningSecretRef, clientAuthOf, typedColors as colors, configSetsTeamSecrets, confirmInitInputPersistence, copyDirToTmp, createCredentialResolver, createHotUpdaterConfigScaffold, createHotUpdaterConfigScaffoldFromBuilder, createInterfaceFingerprint, createLocalSecretsStore, createLogWriter, createMigrator, createMinBundleId, createStep, createTarBrTargetFiles, createZip, credentialIssueCommand, credentialLoginCommand, credentialPutCommand, credentialStoreCommand, decryptJson, defineInitProvider, describeSecret, describeStoredSecret, encryptJson, ensureInstallPackages, failActiveStep, formatDuration, generateClientCredential, generateSchema, generatesSchema, getAndroidSdkPath, getBundleSigningPublicKey, getConfiguredAdapter, getConfiguredSecrets, getCwd, getDeploySecretNames, getHotUpdaterEnvValue, getHotUpdaterHome, getHotUpdaterInitInputEnv, getInitProviderEnvVars, getInitProviderTextPromptValues, getLocalKeyFile, getLocalKeyStoreDir, getLocalSecretsStore, getMissingInitInputs, getMissingInitProviderInputs, getPackageManager, getProjectKey, getReactNativeMetadatas, getStorageFileByteSize, isCI, isCodegenSpecFile, isReservedVariableName, isSecretName, isSecretRef, isServerDefinition, keepsKeysOnThisMachine, link, loadConfig, loadPlatformConfigs, loadTeamSecrets, localSecrets, log, makeEnv, maskSecretValues, moduleSpecifiersOf, nonInteractiveReason, p, parseLoadedEnv, parseSecretReference, prepareBundleSigning, printAppSetup, printBanner, provisionClientCredential, putStorageFile, reactNativeCliFingerprint, reactNativeConfigCommand, readBundleSigningPublicKeyFile, readBundleSigningState, readHotUpdaterInitEnv, readLoadedEnvFile, readLoadedHotUpdaterEnv, readLocalKey, readMissingFromTeamSecrets, readPackageUp, readReactNativeInterface, readSecretVariables, readSecretWithSource, readSecrets, registerCredentialValue, registerSecretValue, renderAppImports, renderAppSetup, renderImportStatements, resolveExpoAutolinking, resolveHotUpdaterServerVersion, resolveInitProviderInput, resolveInitProviderInputs, resolveLocalSecretsPath, resolvePackageVersion, resolveSecretsAdapter, secret, secretVariableName, serverDefinitionOf, setConfiguredSecrets, shouldAutoSelectOnlyInitResource, startStep, storeEnvFileSecrets, stripAnsi, transformEnv, transformTemplate, truncateLine, withCredentialResolver, withDuration, withSecretsScope, withStep, writeHotUpdaterConfig, writeHotUpdaterFiles, writeLocalKey };
47764
+ export { BUNDLE_SIGNING_KEY_SECRET, BuildFailedError, BuildLogger, BundleSigningKeyError, CREDENTIAL_ENV_NAMES, CREDENTIAL_LOGIN_GROUPS, ConfigBuilder, CredentialUnavailableError, HOT_UPDATER_HOME_ENV, HOT_UPDATER_SERVER_PACKAGE_VERSION_ENV, HotUpdateDirUtil, INTERFACE_FINGERPRINT_ENGINE, INVALID_SIGNING_MESSAGE, INVALID_SIGNING_NEXT_STEP, ISSUE_SIGNING_KEY_COMMAND, InitEnvFileError, InitError, LOCAL_SECRETS_NAME, LegacyInfrastructureError, MANAGED_CLIENT_CREDENTIAL_ENV, MissingInitInputsError, PUT_SIGNING_KEY_COMMAND, ServerDefinitionError, UNSIGNED_BUNDLES_NEXT_STEP, UNSIGNED_BUNDLES_WARNING, addSigningSecretToConfig, addSigningSecretToConfigText, assembleServer, assertInfrastructureGenerationAtUrl, assertInfrastructureGenerationPayload, assertInitInputs, assertInitProviderInputs, assertUnreservedEnv, autolinkingConfigCommand, banner, bundleSigningSecretRef, clientAuthOf, typedColors as colors, configSetsTeamSecrets, confirmInitInputPersistence, copyDirToTmp, createCredentialResolver, createHotUpdaterConfigScaffold, createHotUpdaterConfigScaffoldFromBuilder, createInterfaceFingerprint, createLocalSecretsStore, createLogWriter, createMigrator, createMinBundleId, createStep, createTarBrTargetFiles, createZip, credentialIssueCommand, credentialLoginCommand, credentialPutCommand, credentialStoreCommand, decryptJson, defineInitProvider, describeSecret, describeStoredSecret, encryptJson, ensureInstallPackages, failActiveStep, formatCommand, formatDuration, generateClientCredential, generateSchema, generatesSchema, getAndroidSdkPath, getBundleSigningPublicKey, getConfiguredAdapter, getConfiguredSecrets, getCwd, getDeploySecretNames, getHotUpdaterEnvValue, getHotUpdaterHome, getHotUpdaterInitInputEnv, getInitProviderEnvVars, getInitProviderTextPromptValues, getLocalKeyFile, getLocalKeyStoreDir, getLocalSecretsStore, getMissingInitInputs, getMissingInitProviderInputs, getPackageManager, getProjectKey, getReactNativeMetadatas, getStorageFileByteSize, isCI, isCodegenSpecFile, isReservedVariableName, isSecretName, isSecretRef, isServerDefinition, keepsKeysOnThisMachine, link, loadConfig, loadPlatformConfigs, loadTeamSecrets, localSecrets, log, makeEnv, maskSecretValues, moduleSpecifiersOf, nonInteractiveReason, p, parseLoadedEnv, parseSecretReference, prepareBundleSigning, printAppSetup, printBanner, provisionClientCredential, putStorageFile, reactNativeCliFingerprint, reactNativeConfigCommand, readBundleSigningPublicKeyFile, readBundleSigningState, readHotUpdaterInitEnv, readLoadedEnvFile, readLoadedHotUpdaterEnv, readLocalKey, readMissingFromTeamSecrets, readPackageUp, readReactNativeInterface, readSecretVariables, readSecretWithSource, readSecrets, registerCredentialValue, registerSecretValue, renderAppImports, renderAppSetup, renderImportStatements, resolveExpoAutolinking, resolveHotUpdaterServerVersion, resolveInitProviderInput, resolveInitProviderInputs, resolveLocalSecretsPath, resolvePackageVersion, resolveSecretsAdapter, secret, secretVariableName, serverDefinitionOf, setConfiguredSecrets, shouldAutoSelectOnlyInitResource, startStep, storeEnvFileSecrets, stripAnsi, transformEnv, transformTemplate, truncateLine, withCredentialResolver, withDuration, withSecretsScope, withStep, writeHotUpdaterConfig, writeHotUpdaterFiles, writeLocalKey };