@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.d.mts +7 -1
- package/dist/index.mjs +1270 -1254
- package/package.json +5 -5
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()
|
|
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(
|
|
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()
|
|
25887
|
-
if (isReservedVariableName(name)) throw new Error(`secret() refuses ${name}
|
|
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(
|
|
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(
|
|
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/
|
|
25934
|
-
/**
|
|
25935
|
-
const
|
|
25936
|
-
|
|
25937
|
-
const
|
|
25938
|
-
|
|
25939
|
-
|
|
25940
|
-
|
|
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
|
|
25986
|
-
*
|
|
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
|
-
|
|
25989
|
-
|
|
25990
|
-
|
|
25991
|
-
|
|
25992
|
-
|
|
25993
|
-
|
|
25994
|
-
|
|
25995
|
-
|
|
25996
|
-
|
|
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
|
-
*
|
|
26035
|
-
*
|
|
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
|
|
26040
|
-
|
|
26041
|
-
|
|
26042
|
-
|
|
26043
|
-
|
|
26044
|
-
|
|
26045
|
-
|
|
26046
|
-
|
|
26047
|
-
|
|
26048
|
-
|
|
26049
|
-
|
|
26050
|
-
|
|
26051
|
-
|
|
26052
|
-
|
|
26053
|
-
|
|
26054
|
-
|
|
26055
|
-
|
|
26056
|
-
|
|
26057
|
-
|
|
26058
|
-
|
|
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
|
|
26063
|
-
*
|
|
26064
|
-
*
|
|
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
|
|
26083
|
+
const credentialPutCommand = (kind, name = kind) => `npx hot-updater secrets put ${name}${name === kind ? "" : ` --kind ${kind}`} ${PUT_FILES[kind]}`;
|
|
26067
26084
|
/**
|
|
26068
|
-
* The
|
|
26069
|
-
*
|
|
26070
|
-
*
|
|
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
|
|
26073
|
-
|
|
26074
|
-
|
|
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
|
-
*
|
|
26092
|
-
*
|
|
26093
|
-
*
|
|
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
|
|
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
|
|
26120
|
-
*
|
|
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
|
|
26123
|
-
|
|
26124
|
-
|
|
26125
|
-
|
|
26126
|
-
|
|
26127
|
-
|
|
26128
|
-
|
|
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
|
-
|
|
26132
|
-
|
|
26133
|
-
const
|
|
26134
|
-
|
|
26135
|
-
|
|
26136
|
-
|
|
26137
|
-
|
|
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
|
|
26163
|
-
const
|
|
26164
|
-
|
|
26165
|
-
|
|
26166
|
-
|
|
26167
|
-
|
|
26168
|
-
|
|
26169
|
-
|
|
26170
|
-
|
|
26171
|
-
|
|
26172
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
26180
|
-
*
|
|
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
|
-
|
|
26183
|
-
|
|
26184
|
-
|
|
26185
|
-
|
|
26186
|
-
|
|
26187
|
-
|
|
26188
|
-
|
|
26189
|
-
|
|
26190
|
-
|
|
26191
|
-
|
|
26192
|
-
|
|
26193
|
-
|
|
26194
|
-
|
|
26195
|
-
|
|
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
|
-
|
|
26281
|
-
|
|
26282
|
-
|
|
26283
|
-
|
|
26284
|
-
|
|
26285
|
-
|
|
26286
|
-
|
|
26287
|
-
|
|
26288
|
-
|
|
26289
|
-
|
|
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
|
-
|
|
26302
|
-
|
|
26303
|
-
|
|
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
|
-
|
|
26321
|
-
|
|
26322
|
-
|
|
26323
|
-
|
|
26324
|
-
|
|
26325
|
-
|
|
26326
|
-
|
|
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
|
-
|
|
26330
|
-
|
|
26331
|
-
|
|
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
|
-
|
|
26334
|
-
|
|
26335
|
-
|
|
26336
|
-
|
|
26337
|
-
|
|
26338
|
-
|
|
26339
|
-
|
|
26340
|
-
|
|
26341
|
-
|
|
26342
|
-
|
|
26343
|
-
|
|
26344
|
-
|
|
26345
|
-
|
|
26346
|
-
|
|
26347
|
-
|
|
26348
|
-
|
|
26349
|
-
|
|
26350
|
-
|
|
26351
|
-
|
|
26352
|
-
|
|
26353
|
-
|
|
26354
|
-
|
|
26355
|
-
|
|
26356
|
-
|
|
26357
|
-
|
|
26358
|
-
|
|
26359
|
-
|
|
26360
|
-
|
|
26361
|
-
|
|
26362
|
-
|
|
26363
|
-
|
|
26364
|
-
|
|
26365
|
-
|
|
26366
|
-
|
|
26367
|
-
|
|
26368
|
-
|
|
26369
|
-
|
|
26370
|
-
|
|
26371
|
-
|
|
26372
|
-
|
|
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
|
-
|
|
26381
|
-
|
|
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
|
-
|
|
26384
|
-
|
|
26385
|
-
|
|
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
|
-
|
|
26389
|
-
|
|
26390
|
-
|
|
26391
|
-
|
|
26392
|
-
|
|
26393
|
-
|
|
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
|
-
|
|
26397
|
-
|
|
26398
|
-
|
|
26399
|
-
|
|
26400
|
-
|
|
26401
|
-
|
|
26402
|
-
|
|
26403
|
-
|
|
26404
|
-
|
|
26405
|
-
|
|
26406
|
-
|
|
26407
|
-
|
|
26408
|
-
|
|
26409
|
-
|
|
26410
|
-
|
|
26411
|
-
|
|
26412
|
-
|
|
26413
|
-
|
|
26414
|
-
|
|
26415
|
-
|
|
26416
|
-
|
|
26417
|
-
|
|
26418
|
-
|
|
26419
|
-
|
|
26420
|
-
|
|
26421
|
-
|
|
26422
|
-
|
|
26423
|
-
|
|
26424
|
-
|
|
26425
|
-
|
|
26426
|
-
|
|
26427
|
-
|
|
26428
|
-
|
|
26429
|
-
|
|
26430
|
-
|
|
26431
|
-
|
|
26432
|
-
|
|
26433
|
-
|
|
26434
|
-
|
|
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
|
-
|
|
26437
|
-
|
|
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
|
-
|
|
26440
|
-
|
|
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
|
-
|
|
26443
|
-
|
|
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
|
-
|
|
26446
|
-
|
|
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
|
-
|
|
26449
|
-
|
|
26450
|
-
|
|
26451
|
-
|
|
26452
|
-
|
|
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
|
-
|
|
26455
|
-
|
|
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
|
-
|
|
26458
|
-
|
|
26459
|
-
|
|
26460
|
-
|
|
26461
|
-
|
|
26462
|
-
|
|
26463
|
-
|
|
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
|
-
|
|
26472
|
-
|
|
26473
|
-
|
|
26474
|
-
|
|
26475
|
-
|
|
26476
|
-
|
|
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
|
-
|
|
26479
|
-
|
|
26480
|
-
|
|
26481
|
-
|
|
26482
|
-
|
|
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
|
-
|
|
26485
|
-
|
|
26486
|
-
|
|
26487
|
-
|
|
26488
|
-
|
|
26489
|
-
|
|
26490
|
-
|
|
26491
|
-
|
|
26492
|
-
|
|
26493
|
-
|
|
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
|
-
|
|
26496
|
-
|
|
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
|
-
|
|
26499
|
-
|
|
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
|
-
|
|
26502
|
-
|
|
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
|
-
|
|
26505
|
-
|
|
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
|
-
|
|
26508
|
-
|
|
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
|
-
|
|
26511
|
-
|
|
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
|
-
|
|
26514
|
-
|
|
26515
|
-
|
|
26516
|
-
|
|
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
|
-
|
|
26519
|
-
|
|
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
|
-
|
|
26522
|
-
|
|
26523
|
-
this
|
|
26524
|
-
|
|
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
|
-
|
|
26528
|
-
|
|
26529
|
-
|
|
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
|
-
|
|
26532
|
-
|
|
26740
|
+
/** hot-updater.config.ts's text. */
|
|
26741
|
+
getResult() {
|
|
26742
|
+
return this.getScaffold().text;
|
|
26533
26743
|
}
|
|
26534
|
-
|
|
26535
|
-
|
|
26536
|
-
|
|
26537
|
-
|
|
26538
|
-
|
|
26539
|
-
|
|
26540
|
-
|
|
26541
|
-
|
|
26542
|
-
|
|
26543
|
-
|
|
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
|
-
|
|
26546
|
-
|
|
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
|
-
|
|
26549
|
-
|
|
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
|
-
|
|
26553
|
-
|
|
26554
|
-
|
|
26848
|
+
proxyErrors(t) {}
|
|
26849
|
+
end() {
|
|
26850
|
+
this.unpipe(), this.opts.end && this.dest.end();
|
|
26555
26851
|
}
|
|
26556
|
-
|
|
26557
|
-
|
|
26852
|
+
};
|
|
26853
|
+
var xi = class extends Fe {
|
|
26854
|
+
unpipe() {
|
|
26855
|
+
this.src.removeListener("error", this.proxyErrors), super.unpipe();
|
|
26558
26856
|
}
|
|
26559
|
-
|
|
26560
|
-
|
|
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
|
-
|
|
26563
|
-
|
|
26564
|
-
|
|
26565
|
-
|
|
26566
|
-
|
|
26567
|
-
|
|
26568
|
-
|
|
26569
|
-
|
|
26570
|
-
|
|
26571
|
-
|
|
26572
|
-
|
|
26573
|
-
|
|
26574
|
-
|
|
26575
|
-
|
|
26576
|
-
|
|
26577
|
-
|
|
26578
|
-
|
|
26579
|
-
|
|
26580
|
-
|
|
26581
|
-
|
|
26582
|
-
|
|
26583
|
-
|
|
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
|
-
|
|
26586
|
-
|
|
26587
|
-
let e = this[F] ? !1 : super.emit("data", t);
|
|
26588
|
-
return this[Q](), e;
|
|
26892
|
+
get bufferLength() {
|
|
26893
|
+
return this[_];
|
|
26589
26894
|
}
|
|
26590
|
-
|
|
26591
|
-
return this[
|
|
26895
|
+
get encoding() {
|
|
26896
|
+
return this[z];
|
|
26592
26897
|
}
|
|
26593
|
-
|
|
26594
|
-
|
|
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
|
-
|
|
26606
|
-
|
|
26607
|
-
|
|
26608
|
-
|
|
26609
|
-
return this
|
|
26610
|
-
|
|
26611
|
-
|
|
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
|
-
|
|
39074
|
-
|
|
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
|
-
|
|
39078
|
-
|
|
39079
|
-
|
|
39080
|
-
|
|
39081
|
-
|
|
39082
|
-
|
|
39083
|
-
|
|
39084
|
-
|
|
39085
|
-
|
|
39086
|
-
|
|
39087
|
-
|
|
39088
|
-
|
|
39089
|
-
|
|
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
|
-
|
|
39092
|
-
|
|
39093
|
-
|
|
39094
|
-
|
|
39095
|
-
|
|
39096
|
-
|
|
39097
|
-
|
|
39098
|
-
|
|
39099
|
-
|
|
39100
|
-
|
|
39101
|
-
|
|
39102
|
-
|
|
39103
|
-
|
|
39104
|
-
|
|
39105
|
-
|
|
39106
|
-
|
|
39107
|
-
|
|
39108
|
-
|
|
39109
|
-
|
|
39110
|
-
|
|
39111
|
-
|
|
39112
|
-
|
|
39113
|
-
|
|
39114
|
-
|
|
39115
|
-
|
|
39116
|
-
|
|
39117
|
-
|
|
39118
|
-
|
|
39119
|
-
|
|
39120
|
-
|
|
39121
|
-
|
|
39122
|
-
|
|
39123
|
-
|
|
39124
|
-
|
|
39125
|
-
|
|
39126
|
-
|
|
39127
|
-
|
|
39128
|
-
|
|
39129
|
-
|
|
39130
|
-
|
|
39131
|
-
|
|
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
|
-
|
|
39140
|
-
|
|
39141
|
-
|
|
39142
|
-
|
|
39143
|
-
|
|
39144
|
-
|
|
39145
|
-
|
|
39146
|
-
|
|
39147
|
-
|
|
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 };
|