@hot-updater/cli-tools 1.0.0-rc.2 → 1.0.0-rc.21
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 +281 -65
- package/dist/index.mjs +659 -543
- package/package.json +6 -5
package/dist/index.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { createRequire } from "node:module";
|
|
|
3
3
|
import path, { dirname, parse } from "path";
|
|
4
4
|
import { createInterface } from "readline";
|
|
5
5
|
import { PassThrough } from "stream";
|
|
6
|
-
import fs
|
|
6
|
+
import fs from "fs";
|
|
7
7
|
import fs$1 from "fs/promises";
|
|
8
8
|
import $r, { EventEmitter } from "events";
|
|
9
9
|
import Pi from "assert";
|
|
@@ -14,29 +14,29 @@ import process$1, { execArgv, execPath, hrtime, platform, stdin, stdout } from "
|
|
|
14
14
|
import * as l from "node:readline";
|
|
15
15
|
import l__default from "node:readline";
|
|
16
16
|
import tty, { ReadStream } from "node:tty";
|
|
17
|
-
import Ei, { appendFileSync, createReadStream, createWriteStream
|
|
17
|
+
import Ei, { appendFileSync, createReadStream, createWriteStream, existsSync, lstatSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
18
18
|
import path$1, { basename, dirname as dirname$1, join, posix, win32 } from "node:path";
|
|
19
|
+
import { createBundleStorageKey, createMemoryAdapter, createStorageRootUriWithPath, getContentType, getManifestAssetDownloadPath, getManifestAssetStoragePath, isContentAddressedAssetFileHash, isRemoteDatabase, resolveManifestAssetStorageUri, toolingTargetOf } from "@hot-updater/plugin-core";
|
|
20
|
+
import { createHotUpdater } from "@hot-updater/server";
|
|
19
21
|
import os, { constants } from "node:os";
|
|
20
|
-
import crypto$1, { randomBytes, randomUUID } from "node:crypto";
|
|
22
|
+
import crypto$1, { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
21
23
|
import fs$2 from "node:fs/promises";
|
|
22
|
-
import { pipeline } from "stream/promises";
|
|
23
|
-
import * as Ms from "zlib";
|
|
24
|
-
import Jr, { constants as constants$1, createBrotliCompress } from "zlib";
|
|
25
|
-
import { EventEmitter as EventEmitter$1, addAbortListener, on, once, setMaxListeners } from "node:events";
|
|
26
24
|
import As, { Duplex, PassThrough as PassThrough$1, Readable as Readable$1, Transform, Writable, getDefaultHighWaterMark } from "node:stream";
|
|
25
|
+
import { finished, pipeline } from "node:stream/promises";
|
|
26
|
+
import { brotliDecompressSync, constants as constants$1, createBrotliCompress } from "node:zlib";
|
|
27
|
+
import { EventEmitter as EventEmitter$1, addAbortListener, on, once, setMaxListeners } from "node:events";
|
|
27
28
|
import { StringDecoder } from "node:string_decoder";
|
|
29
|
+
import * as Ms from "zlib";
|
|
30
|
+
import Jr from "zlib";
|
|
28
31
|
import no from "node:assert";
|
|
29
32
|
import { fileURLToPath } from "node:url";
|
|
30
33
|
import { ChildProcess, execFile, spawn, spawnSync } from "node:child_process";
|
|
31
34
|
import { scheduler, setImmediate as setImmediate$1, setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
32
35
|
import { serialize } from "node:v8";
|
|
33
|
-
import { finished, pipeline as pipeline$1 } from "node:stream/promises";
|
|
34
36
|
import { Buffer as Buffer$2 } from "node:buffer";
|
|
35
|
-
import { parseSync } from "oxc-parser";
|
|
36
|
-
import { createBundleStorageKey, createDatabasePlugin, createStoragePlugin, createStorageRootUriWithPath, detectCompressionFormat, getContentType, getManifestAssetDownloadPath, getManifestAssetStoragePath, isContentAddressedAssetFileHash, parseStorageUri, resolveManifestAssetStorageUri } from "@hot-updater/plugin-core";
|
|
37
|
+
import { Visitor, parseSync } from "oxc-parser";
|
|
37
38
|
import { loadConfig as loadConfig$1 } from "unconfig";
|
|
38
|
-
import {
|
|
39
|
-
import { getManifestFileHash, stripBundleArtifactMetadata } from "@hot-updater/core";
|
|
39
|
+
import { getManifestFileHash, stripBundleArtifactMetadata } from "@hot-updater/protocol";
|
|
40
40
|
import { transformSync } from "oxc-transform";
|
|
41
41
|
//#endregion
|
|
42
42
|
//#region src/colors.ts
|
|
@@ -22906,8 +22906,99 @@ var BuildLogger = class {
|
|
|
22906
22906
|
}
|
|
22907
22907
|
};
|
|
22908
22908
|
//#endregion
|
|
22909
|
-
//#region src/
|
|
22910
|
-
|
|
22909
|
+
//#region src/appSetup.ts
|
|
22910
|
+
/** The SDK, which exports `HotUpdater` and built-in client plugins such as `insights`. */
|
|
22911
|
+
const SDK_MODULE = "@hot-updater/react-native";
|
|
22912
|
+
/**
|
|
22913
|
+
* The app's import lines for `HotUpdater` and `clientPlugins`: one line per
|
|
22914
|
+
* module, the SDK's first, so a built-in plugin joins `HotUpdater`'s import.
|
|
22915
|
+
* The agent scaffold renders the same lines in hot-updater's
|
|
22916
|
+
* `commands/infra/clientAuth.ts`, which imports no package.
|
|
22917
|
+
*/
|
|
22918
|
+
const renderAppImports = (clientPlugins) => {
|
|
22919
|
+
const namesByModule = /* @__PURE__ */ new Map([[SDK_MODULE, ["HotUpdater"]]]);
|
|
22920
|
+
for (const { module, name } of clientPlugins) {
|
|
22921
|
+
const names = namesByModule.get(module) ?? [];
|
|
22922
|
+
if (!names.includes(name)) names.push(name);
|
|
22923
|
+
namesByModule.set(module, names);
|
|
22924
|
+
}
|
|
22925
|
+
return [...namesByModule].map(([module, names]) => `import { ${names.join(", ")} } from ${JSON.stringify(module)};`);
|
|
22926
|
+
};
|
|
22927
|
+
/**
|
|
22928
|
+
* The App.tsx code init prints: `HotUpdater.init` with the server's URL,
|
|
22929
|
+
* the header that carries the client credential when the server takes one,
|
|
22930
|
+
* and the client plugins the server's plugins ask for.
|
|
22931
|
+
*/
|
|
22932
|
+
const renderAppSetup = ({ baseURL, credential, clientPlugins = [] }) => [
|
|
22933
|
+
"// Add this to your App.tsx",
|
|
22934
|
+
...renderAppImports(clientPlugins),
|
|
22935
|
+
"",
|
|
22936
|
+
"function App() {",
|
|
22937
|
+
" return null; // Replace with your app root.",
|
|
22938
|
+
"}",
|
|
22939
|
+
"",
|
|
22940
|
+
"HotUpdater.init({",
|
|
22941
|
+
` baseURL: ${JSON.stringify(baseURL)},`,
|
|
22942
|
+
...credential === void 0 ? [] : [
|
|
22943
|
+
" requestHeaders: {",
|
|
22944
|
+
` ${JSON.stringify(credential.header)}: ${JSON.stringify(credential.value)},`,
|
|
22945
|
+
" },"
|
|
22946
|
+
],
|
|
22947
|
+
...clientPlugins.length === 0 ? [] : [` plugins: [${clientPlugins.map(({ name }) => `${name}()`).join(", ")}],`],
|
|
22948
|
+
"});",
|
|
22949
|
+
"",
|
|
22950
|
+
"// Call HotUpdater.checkForUpdate({ updateStrategy: \"appVersion\" })",
|
|
22951
|
+
"// when your app is ready to check.",
|
|
22952
|
+
"export default App;"
|
|
22953
|
+
].join("\n");
|
|
22954
|
+
/** Prints the App.tsx code, when the URL is known, and the app's credential. */
|
|
22955
|
+
const printAppSetup = ({ baseURL, ...setup }) => {
|
|
22956
|
+
if (baseURL !== void 0) p.note(renderAppSetup({
|
|
22957
|
+
baseURL,
|
|
22958
|
+
...setup
|
|
22959
|
+
}));
|
|
22960
|
+
const { credential } = setup;
|
|
22961
|
+
if (credential !== void 0) {
|
|
22962
|
+
p.note(credential.value, credential.label);
|
|
22963
|
+
p.log.message(`Store this ${credential.label} separately in a secure place.`);
|
|
22964
|
+
}
|
|
22965
|
+
};
|
|
22966
|
+
//#endregion
|
|
22967
|
+
//#region src/assembleServer.ts
|
|
22968
|
+
/** `createHotUpdater`, with public client routes unless a plugin provides clientAuth. */
|
|
22969
|
+
const createServer = (database, storage, plugins) => createHotUpdater({
|
|
22970
|
+
database,
|
|
22971
|
+
storage,
|
|
22972
|
+
plugins,
|
|
22973
|
+
...plugins.some((plugin) => plugin.provides?.clientAuth) ? {} : { clientAccess: "public" }
|
|
22974
|
+
});
|
|
22975
|
+
function assembleServer({ database, storage, plugins = [] }) {
|
|
22976
|
+
if (isRemoteDatabase(database)) {
|
|
22977
|
+
const shape = createServer({
|
|
22978
|
+
name: "memory",
|
|
22979
|
+
adapter: createMemoryAdapter()
|
|
22980
|
+
}, [], plugins);
|
|
22981
|
+
return {
|
|
22982
|
+
database,
|
|
22983
|
+
core: database.core,
|
|
22984
|
+
storage,
|
|
22985
|
+
plugins: shape.plugins,
|
|
22986
|
+
api: void 0,
|
|
22987
|
+
clientPlugins: shape.clientPlugins,
|
|
22988
|
+
...shape.clientAuth === void 0 ? {} : { clientAuth: shape.clientAuth }
|
|
22989
|
+
};
|
|
22990
|
+
}
|
|
22991
|
+
const server = createServer(database, storage === void 0 ? [] : [storage], plugins);
|
|
22992
|
+
return {
|
|
22993
|
+
database,
|
|
22994
|
+
core: server.core,
|
|
22995
|
+
storage,
|
|
22996
|
+
plugins: server.plugins,
|
|
22997
|
+
api: server.api,
|
|
22998
|
+
clientPlugins: server.clientPlugins,
|
|
22999
|
+
...server.clientAuth === void 0 ? {} : { clientAuth: server.clientAuth }
|
|
23000
|
+
};
|
|
23001
|
+
}
|
|
22911
23002
|
//#endregion
|
|
22912
23003
|
//#region ../../node_modules/.pnpm/ansi-regex@6.2.2/node_modules/ansi-regex/index.js
|
|
22913
23004
|
function ansiRegex({ onlyFirst = false } = {}) {
|
|
@@ -24838,7 +24929,7 @@ const loadPrivateKey = async (privateKeyPath) => {
|
|
|
24838
24929
|
throw new Error("Failed to load the local bundle signing private key.");
|
|
24839
24930
|
}
|
|
24840
24931
|
};
|
|
24841
|
-
const
|
|
24932
|
+
const createLocalSigningAdapter = ({ privateKeyPath }) => {
|
|
24842
24933
|
const privateKeys = /* @__PURE__ */ new Map();
|
|
24843
24934
|
const getPrivateKey = (cwd = process.cwd()) => {
|
|
24844
24935
|
const resolvedPath = resolvePath$1(cwd, privateKeyPath);
|
|
@@ -24867,7 +24958,7 @@ const createLocalSigningPlugin = ({ privateKeyPath }) => {
|
|
|
24867
24958
|
}
|
|
24868
24959
|
};
|
|
24869
24960
|
};
|
|
24870
|
-
const invalidSigningConfig = () => /* @__PURE__ */ new Error("Bundle signing must be a local key config or signing
|
|
24961
|
+
const invalidSigningConfig = () => /* @__PURE__ */ new Error("Bundle signing must be a local key config or signing adapter. Omit signing to disable it.");
|
|
24871
24962
|
const normalizeSigningConfig = (signing) => {
|
|
24872
24963
|
if (signing === void 0) return void 0;
|
|
24873
24964
|
if (typeof signing !== "object" || signing === null) throw invalidSigningConfig();
|
|
@@ -24878,7 +24969,7 @@ const normalizeSigningConfig = (signing) => {
|
|
|
24878
24969
|
"getPublicKey",
|
|
24879
24970
|
"sign"
|
|
24880
24971
|
].some((key) => Reflect.has(signing, key));
|
|
24881
|
-
if ((hasPrivateKeyPath || hasEnabled) && hasPluginMembers) throw new Error("Bundle signing config cannot combine local signing fields with signing
|
|
24972
|
+
if ((hasPrivateKeyPath || hasEnabled) && hasPluginMembers) throw new Error("Bundle signing config cannot combine local signing fields with signing adapter fields.");
|
|
24882
24973
|
if (!hasPluginMembers) {
|
|
24883
24974
|
if (Object.keys(signing).some((key) => key !== "enabled" && key !== "privateKeyPath")) throw new Error("Local bundle signing accepts only enabled and privateKeyPath.");
|
|
24884
24975
|
const enabled = Reflect.get(signing, "enabled");
|
|
@@ -24977,14 +25068,14 @@ const prepareBundleSigning = async (signing, options = {}) => {
|
|
|
24977
25068
|
const normalized = normalizeSigningConfig(signing);
|
|
24978
25069
|
if (!normalized) return null;
|
|
24979
25070
|
const cwd = options.cwd ?? getCwd();
|
|
24980
|
-
return preparePluginSigning("enabled" in normalized ?
|
|
25071
|
+
return preparePluginSigning("enabled" in normalized ? createLocalSigningAdapter(normalized) : normalized, cwd);
|
|
24981
25072
|
};
|
|
24982
25073
|
/** Resolves the public identity of the configured bundle signer. */
|
|
24983
25074
|
const getBundleSigningPublicKey = async (signing, options = {}) => {
|
|
24984
25075
|
const normalized = normalizeSigningConfig(signing);
|
|
24985
25076
|
if (!normalized) return null;
|
|
24986
25077
|
const cwd = options.cwd ?? getCwd();
|
|
24987
|
-
const provider = "enabled" in normalized ?
|
|
25078
|
+
const provider = "enabled" in normalized ? createLocalSigningAdapter(normalized) : normalized;
|
|
24988
25079
|
return exportPublicKey(await getProviderPublicKey(provider, cwd));
|
|
24989
25080
|
};
|
|
24990
25081
|
//#endregion
|
|
@@ -25036,47 +25127,28 @@ const renderImportStatements = (imports) => {
|
|
|
25036
25127
|
}
|
|
25037
25128
|
return importLines.join("\n");
|
|
25038
25129
|
};
|
|
25130
|
+
/**
|
|
25131
|
+
* Renders the hot-updater.config.ts init writes: the build, the server's
|
|
25132
|
+
* storage, database, and plugins, and the deploy settings.
|
|
25133
|
+
*/
|
|
25039
25134
|
var ConfigBuilder = class {
|
|
25040
25135
|
buildType = null;
|
|
25041
25136
|
storageInfo = null;
|
|
25042
25137
|
databaseInfo = null;
|
|
25138
|
+
pluginsInfo = null;
|
|
25043
25139
|
intermediateCode = "";
|
|
25044
|
-
|
|
25045
|
-
|
|
25046
|
-
|
|
25047
|
-
|
|
25048
|
-
|
|
25049
|
-
|
|
25050
|
-
|
|
25051
|
-
|
|
25052
|
-
named: ["defineConfig"]
|
|
25053
|
-
});
|
|
25054
|
-
}
|
|
25140
|
+
imports = [{
|
|
25141
|
+
pkg: "hot-updater",
|
|
25142
|
+
named: ["defineConfig"]
|
|
25143
|
+
}, {
|
|
25144
|
+
pkg: "node:fs",
|
|
25145
|
+
named: ["existsSync"]
|
|
25146
|
+
}];
|
|
25147
|
+
/** Adds an import, such as a credentials helper's. */
|
|
25055
25148
|
addImport(info) {
|
|
25056
|
-
|
|
25057
|
-
const existing = this.collectedImports.get(pkg);
|
|
25058
|
-
if (existing) {
|
|
25059
|
-
if (info.named) for (const n of info.named) existing.named.add(n);
|
|
25060
|
-
if (info.defaultOrNamespace && !existing.defaultOrNamespace) existing.defaultOrNamespace = info.defaultOrNamespace;
|
|
25061
|
-
if (info.sideEffect && !existing.sideEffect) existing.sideEffect = true;
|
|
25062
|
-
} else this.collectedImports.set(pkg, {
|
|
25063
|
-
named: new Set(info.named ?? []),
|
|
25064
|
-
defaultOrNamespace: info.defaultOrNamespace,
|
|
25065
|
-
sideEffect: info.sideEffect ?? false
|
|
25066
|
-
});
|
|
25149
|
+
this.imports.push(info);
|
|
25067
25150
|
return this;
|
|
25068
25151
|
}
|
|
25069
|
-
addImports(imports) {
|
|
25070
|
-
for (const imp of imports) this.addImport(imp);
|
|
25071
|
-
}
|
|
25072
|
-
getImportInfos() {
|
|
25073
|
-
return normalizeImportInfos(Array.from(this.collectedImports.entries()).map(([pkg, info]) => ({
|
|
25074
|
-
pkg,
|
|
25075
|
-
named: Array.from(info.named),
|
|
25076
|
-
defaultOrNamespace: info.defaultOrNamespace,
|
|
25077
|
-
sideEffect: info.sideEffect ?? false
|
|
25078
|
-
})));
|
|
25079
|
-
}
|
|
25080
25152
|
generateBuildConfigString() {
|
|
25081
25153
|
if (!this.buildType) throw new Error("Build type must be set using .setBuildType()");
|
|
25082
25154
|
switch (this.buildType) {
|
|
@@ -25086,33 +25158,34 @@ var ConfigBuilder = class {
|
|
|
25086
25158
|
default: throw new Error(`Invalid build type: ${this.buildType}`);
|
|
25087
25159
|
}
|
|
25088
25160
|
}
|
|
25161
|
+
/** Sets the build type ('bare', 'rock', or 'expo') and its import. */
|
|
25089
25162
|
setBuildType(buildType) {
|
|
25090
|
-
if (this.buildType) console.warn("Build type is being set multiple times. Overwriting previous value.");
|
|
25091
25163
|
this.buildType = buildType;
|
|
25092
|
-
this.
|
|
25164
|
+
this.imports.push({
|
|
25093
25165
|
pkg: `@hot-updater/${buildType}`,
|
|
25094
25166
|
named: [buildType]
|
|
25095
25167
|
});
|
|
25096
25168
|
return this;
|
|
25097
25169
|
}
|
|
25170
|
+
/** Sets the storage the CLI uploads to, which the server lists, and its imports. */
|
|
25098
25171
|
setStorage(storageConfig) {
|
|
25099
25172
|
this.storageInfo = storageConfig;
|
|
25100
|
-
this.
|
|
25101
|
-
if (storageConfig.imports.some((imp) => imp.pkg.includes("firebase"))) this.addImport({
|
|
25102
|
-
pkg: "firebase-admin/app",
|
|
25103
|
-
named: ["applicationDefault"]
|
|
25104
|
-
});
|
|
25173
|
+
this.imports.push(...storageConfig.imports);
|
|
25105
25174
|
return this;
|
|
25106
25175
|
}
|
|
25176
|
+
/** Sets the server's database and its imports. */
|
|
25107
25177
|
setDatabase(databaseConfig) {
|
|
25108
25178
|
this.databaseInfo = databaseConfig;
|
|
25109
|
-
this.
|
|
25110
|
-
|
|
25111
|
-
|
|
25112
|
-
|
|
25113
|
-
|
|
25179
|
+
this.imports.push(...databaseConfig.imports);
|
|
25180
|
+
return this;
|
|
25181
|
+
}
|
|
25182
|
+
/** Sets the plugins the server runs, such as a provider package's `plugins`. */
|
|
25183
|
+
setPlugins(pluginsConfig) {
|
|
25184
|
+
this.pluginsInfo = pluginsConfig;
|
|
25185
|
+
this.imports.push(...pluginsConfig.imports);
|
|
25114
25186
|
return this;
|
|
25115
25187
|
}
|
|
25188
|
+
/** Sets the code between the environment loading and the config, such as a credentials helper. */
|
|
25116
25189
|
setIntermediateCode(code) {
|
|
25117
25190
|
this.intermediateCode = code.trim();
|
|
25118
25191
|
return this;
|
|
@@ -25121,19 +25194,22 @@ var ConfigBuilder = class {
|
|
|
25121
25194
|
if (!this.buildType) throw new Error("Build type must be set using .setBuildType()");
|
|
25122
25195
|
if (!this.storageInfo) throw new Error("Storage config must be set using .setStorage()");
|
|
25123
25196
|
if (!this.databaseInfo) throw new Error("Database config must be set using .setDatabase()");
|
|
25124
|
-
|
|
25125
|
-
const
|
|
25197
|
+
if (!this.pluginsInfo) throw new Error("Plugins config must be set using .setPlugins()");
|
|
25198
|
+
const imports = normalizeImportInfos(this.imports);
|
|
25126
25199
|
const buildConfigString = this.generateBuildConfigString();
|
|
25200
|
+
const plugins = this.pluginsInfo.configString;
|
|
25127
25201
|
const text = `
|
|
25128
|
-
${
|
|
25202
|
+
${renderImportStatements(imports)}
|
|
25129
25203
|
|
|
25130
|
-
|
|
25204
|
+
if (existsSync(".env.hotupdater")) {
|
|
25205
|
+
process.loadEnvFile(".env.hotupdater");
|
|
25206
|
+
}
|
|
25131
25207
|
|
|
25132
|
-
${this.intermediateCode ? `${this.intermediateCode}\n` : ""}
|
|
25133
|
-
export default defineConfig({
|
|
25208
|
+
${this.intermediateCode ? `${this.intermediateCode}\n\n` : ""}export default defineConfig({
|
|
25134
25209
|
build: ${buildConfigString},
|
|
25135
25210
|
storage: ${this.storageInfo.configString},
|
|
25136
25211
|
database: ${this.databaseInfo.configString},
|
|
25212
|
+
${plugins === "plugins" ? "plugins" : `plugins: ${plugins}`},
|
|
25137
25213
|
updateStrategy: "appVersion", // or "fingerprint"
|
|
25138
25214
|
});
|
|
25139
25215
|
`.trim();
|
|
@@ -25142,10 +25218,12 @@ export default defineConfig({
|
|
|
25142
25218
|
buildConfigString,
|
|
25143
25219
|
storageConfigString: this.storageInfo.configString,
|
|
25144
25220
|
databaseConfigString: this.databaseInfo.configString,
|
|
25221
|
+
pluginsConfigString: plugins,
|
|
25145
25222
|
intermediateCode: this.intermediateCode,
|
|
25146
25223
|
text
|
|
25147
25224
|
};
|
|
25148
25225
|
}
|
|
25226
|
+
/** hot-updater.config.ts's text. */
|
|
25149
25227
|
getResult() {
|
|
25150
25228
|
return this.getScaffold().text;
|
|
25151
25229
|
}
|
|
@@ -28325,7 +28403,7 @@ var fo = (s, t) => {
|
|
|
28325
28403
|
});
|
|
28326
28404
|
});
|
|
28327
28405
|
};
|
|
28328
|
-
|
|
28406
|
+
K(co, fo, (s) => new Te(s), (s) => new Xt(s), (s, t) => {
|
|
28329
28407
|
t?.length && Xi(s, t);
|
|
28330
28408
|
});
|
|
28331
28409
|
var mo = (s, t) => {
|
|
@@ -28439,150 +28517,47 @@ var Ro = (s) => {
|
|
|
28439
28517
|
};
|
|
28440
28518
|
//#endregion
|
|
28441
28519
|
//#region src/createTarBr.ts
|
|
28442
|
-
|
|
28443
|
-
|
|
28444
|
-
|
|
28445
|
-
await fs$1.
|
|
28520
|
+
/** Build the one supported bulk transport from the exact manifest file set. */
|
|
28521
|
+
async function createTarBrTargetFiles({ outfile, targetFiles }) {
|
|
28522
|
+
await fs$2.mkdir(path$1.dirname(outfile), { recursive: true });
|
|
28523
|
+
const staging = await fs$2.mkdtemp(path$1.join(path$1.dirname(outfile), ".tar-br-"));
|
|
28446
28524
|
try {
|
|
28525
|
+
const names = /* @__PURE__ */ new Set();
|
|
28447
28526
|
for (const target of targetFiles) {
|
|
28448
|
-
const
|
|
28449
|
-
|
|
28450
|
-
await fs$
|
|
28451
|
-
|
|
28452
|
-
|
|
28453
|
-
|
|
28454
|
-
|
|
28527
|
+
const name = target.name;
|
|
28528
|
+
if (!name || name !== name.trim() || name.includes("\\") || name.includes("\0") || name.startsWith("/") || /^[A-Za-z]:/.test(name) || name.split("/").some((part) => !part || part === "." || part === "..") || name === "manifest.json" || name === "bundle.tar.br" || names.has(name)) throw new Error(`Invalid or duplicate archive asset path: ${name}`);
|
|
28529
|
+
if (!(await fs$2.lstat(target.path)).isFile()) throw new Error(`Archive asset is not a regular file: ${name}`);
|
|
28530
|
+
names.add(name);
|
|
28531
|
+
const destination = path$1.join(staging, name);
|
|
28532
|
+
await fs$2.mkdir(path$1.dirname(destination), { recursive: true });
|
|
28533
|
+
await fs$2.copyFile(target.path, destination);
|
|
28534
|
+
await fs$2.chmod(destination, 420);
|
|
28535
|
+
}
|
|
28536
|
+
let tarByteSize = 0;
|
|
28537
|
+
const countTarBytes = new Transform({ transform(chunk, _encoding, callback) {
|
|
28538
|
+
tarByteSize += chunk.length;
|
|
28539
|
+
callback(null, chunk);
|
|
28540
|
+
} });
|
|
28455
28541
|
await pipeline(Kn({
|
|
28456
|
-
cwd:
|
|
28542
|
+
cwd: staging,
|
|
28457
28543
|
portable: true,
|
|
28458
28544
|
mtime: /* @__PURE__ */ new Date(0),
|
|
28459
28545
|
gzip: false
|
|
28460
|
-
},
|
|
28461
|
-
|
|
28462
|
-
|
|
28463
|
-
|
|
28464
|
-
|
|
28465
|
-
|
|
28466
|
-
|
|
28467
|
-
|
|
28468
|
-
force: true
|
|
28469
|
-
});
|
|
28470
|
-
}
|
|
28471
|
-
};
|
|
28472
|
-
async function copyDir$1(src, dest) {
|
|
28473
|
-
await fs$1.mkdir(dest, { recursive: true });
|
|
28474
|
-
const entries = await fs$1.readdir(src, { withFileTypes: true });
|
|
28475
|
-
for (const entry of entries) {
|
|
28476
|
-
const srcPath = path.join(src, entry.name);
|
|
28477
|
-
const destPath = path.join(dest, entry.name);
|
|
28478
|
-
if (entry.isDirectory()) await copyDir$1(srcPath, destPath);
|
|
28479
|
-
else await fs$1.copyFile(srcPath, destPath);
|
|
28480
|
-
}
|
|
28481
|
-
}
|
|
28482
|
-
const createBrotliTar = async ({ cwd, files, outfile }) => {
|
|
28483
|
-
await fs$1.mkdir(path.dirname(outfile), { recursive: true });
|
|
28484
|
-
await pipeline(Kn({
|
|
28485
|
-
cwd,
|
|
28486
|
-
portable: true,
|
|
28487
|
-
mtime: /* @__PURE__ */ new Date(0),
|
|
28488
|
-
gzip: false
|
|
28489
|
-
}, files), createBrotliCompress({ params: {
|
|
28490
|
-
[constants$1.BROTLI_PARAM_QUALITY]: 11,
|
|
28491
|
-
[constants$1.BROTLI_PARAM_LGWIN]: 24
|
|
28492
|
-
} }), createWriteStream(outfile));
|
|
28493
|
-
};
|
|
28494
|
-
const createTarBr = async ({ outfile, targetDir, excludeExts = [] }) => {
|
|
28495
|
-
await fs$1.rm(outfile, { force: true });
|
|
28496
|
-
async function getFiles(dir, baseDir = "") {
|
|
28497
|
-
const entries = await fs$1.readdir(dir, { withFileTypes: true });
|
|
28498
|
-
const files = [];
|
|
28499
|
-
for (const entry of entries) {
|
|
28500
|
-
if (excludeExts.some((pattern) => entry.name.includes(pattern))) continue;
|
|
28501
|
-
const fullPath = path.join(dir, entry.name);
|
|
28502
|
-
const relativePath = path.join(baseDir, entry.name);
|
|
28503
|
-
if (entry.isDirectory()) {
|
|
28504
|
-
const subFiles = await getFiles(fullPath, relativePath);
|
|
28505
|
-
files.push(...subFiles);
|
|
28506
|
-
} else files.push(relativePath);
|
|
28507
|
-
}
|
|
28508
|
-
return files;
|
|
28509
|
-
}
|
|
28510
|
-
const filesToInclude = await getFiles(targetDir);
|
|
28511
|
-
filesToInclude.sort();
|
|
28512
|
-
await createBrotliTar({
|
|
28513
|
-
cwd: targetDir,
|
|
28514
|
-
files: filesToInclude,
|
|
28515
|
-
outfile
|
|
28516
|
-
});
|
|
28517
|
-
return outfile;
|
|
28518
|
-
};
|
|
28519
|
-
//#endregion
|
|
28520
|
-
//#region src/createTarGz.ts
|
|
28521
|
-
const createTarGzTargetFiles = async ({ outfile, targetFiles }) => {
|
|
28522
|
-
await fs$1.rm(outfile, { force: true });
|
|
28523
|
-
const tmpDir = path.join(path.dirname(outfile), `.tmp-tar-${Date.now()}`);
|
|
28524
|
-
await fs$1.mkdir(tmpDir, { recursive: true });
|
|
28525
|
-
try {
|
|
28526
|
-
for (const target of targetFiles) {
|
|
28527
|
-
const sourcePath = target.path;
|
|
28528
|
-
const destPath = path.join(tmpDir, target.name);
|
|
28529
|
-
await fs$1.mkdir(path.dirname(destPath), { recursive: true });
|
|
28530
|
-
if ((await fs$1.stat(sourcePath)).isDirectory()) await copyDir(sourcePath, destPath);
|
|
28531
|
-
else await fs$1.copyFile(sourcePath, destPath);
|
|
28532
|
-
}
|
|
28533
|
-
await fs$1.mkdir(path.dirname(outfile), { recursive: true });
|
|
28534
|
-
await Kn({
|
|
28535
|
-
file: outfile,
|
|
28536
|
-
cwd: tmpDir,
|
|
28537
|
-
portable: true,
|
|
28538
|
-
mtime: /* @__PURE__ */ new Date(0),
|
|
28539
|
-
gzip: true
|
|
28540
|
-
}, await fs$1.readdir(tmpDir));
|
|
28541
|
-
return outfile;
|
|
28546
|
+
}, [...names].sort()), countTarBytes, createBrotliCompress({ params: { [constants$1.BROTLI_PARAM_QUALITY]: 11 } }), createWriteStream(outfile));
|
|
28547
|
+
const hash = createHash("sha256");
|
|
28548
|
+
for await (const chunk of createReadStream(outfile)) hash.update(chunk);
|
|
28549
|
+
return {
|
|
28550
|
+
downloadFileHash: hash.digest("hex"),
|
|
28551
|
+
downloadByteSize: (await fs$2.stat(outfile)).size,
|
|
28552
|
+
tarByteSize
|
|
28553
|
+
};
|
|
28542
28554
|
} finally {
|
|
28543
|
-
await fs$
|
|
28555
|
+
await fs$2.rm(staging, {
|
|
28544
28556
|
recursive: true,
|
|
28545
28557
|
force: true
|
|
28546
28558
|
});
|
|
28547
28559
|
}
|
|
28548
|
-
};
|
|
28549
|
-
async function copyDir(src, dest) {
|
|
28550
|
-
await fs$1.mkdir(dest, { recursive: true });
|
|
28551
|
-
const entries = await fs$1.readdir(src, { withFileTypes: true });
|
|
28552
|
-
for (const entry of entries) {
|
|
28553
|
-
const srcPath = path.join(src, entry.name);
|
|
28554
|
-
const destPath = path.join(dest, entry.name);
|
|
28555
|
-
if (entry.isDirectory()) await copyDir(srcPath, destPath);
|
|
28556
|
-
else await fs$1.copyFile(srcPath, destPath);
|
|
28557
|
-
}
|
|
28558
28560
|
}
|
|
28559
|
-
const createTarGz = async ({ outfile, targetDir, excludeExts = [] }) => {
|
|
28560
|
-
await fs$1.rm(outfile, { force: true });
|
|
28561
|
-
async function getFiles(dir, baseDir = "") {
|
|
28562
|
-
const entries = await fs$1.readdir(dir, { withFileTypes: true });
|
|
28563
|
-
const files = [];
|
|
28564
|
-
for (const entry of entries) {
|
|
28565
|
-
if (excludeExts.some((pattern) => entry.name.includes(pattern))) continue;
|
|
28566
|
-
const fullPath = path.join(dir, entry.name);
|
|
28567
|
-
const relativePath = path.join(baseDir, entry.name);
|
|
28568
|
-
if (entry.isDirectory()) {
|
|
28569
|
-
const subFiles = await getFiles(fullPath, relativePath);
|
|
28570
|
-
files.push(...subFiles);
|
|
28571
|
-
} else files.push(relativePath);
|
|
28572
|
-
}
|
|
28573
|
-
return files;
|
|
28574
|
-
}
|
|
28575
|
-
const filesToInclude = await getFiles(targetDir);
|
|
28576
|
-
filesToInclude.sort();
|
|
28577
|
-
await Kn({
|
|
28578
|
-
file: outfile,
|
|
28579
|
-
cwd: targetDir,
|
|
28580
|
-
portable: true,
|
|
28581
|
-
mtime: /* @__PURE__ */ new Date(0),
|
|
28582
|
-
gzip: true
|
|
28583
|
-
}, filesToInclude);
|
|
28584
|
-
return outfile;
|
|
28585
|
-
};
|
|
28586
28561
|
//#endregion
|
|
28587
28562
|
//#region ../../node_modules/.pnpm/process-nextick-args@2.0.1/node_modules/process-nextick-args/index.js
|
|
28588
28563
|
var require_process_nextick_args = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
@@ -37664,41 +37639,6 @@ var import_lib = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((export
|
|
|
37664
37639
|
JSZip.external = require_external();
|
|
37665
37640
|
module.exports = JSZip;
|
|
37666
37641
|
})))(), 1);
|
|
37667
|
-
const createZipTargetFiles = async ({ outfile, targetFiles }) => {
|
|
37668
|
-
const zip = new import_lib.default();
|
|
37669
|
-
await fs$1.rm(outfile, { force: true });
|
|
37670
|
-
async function addFiles(dir, zipFolder) {
|
|
37671
|
-
const files = await fs$1.readdir(dir);
|
|
37672
|
-
files.sort();
|
|
37673
|
-
for (const file of files) {
|
|
37674
|
-
const fullPath = path.join(dir, file);
|
|
37675
|
-
if ((await fs$1.stat(fullPath)).isDirectory()) {
|
|
37676
|
-
const folder = zipFolder.folder(file);
|
|
37677
|
-
if (!folder) continue;
|
|
37678
|
-
await addFiles(fullPath, folder);
|
|
37679
|
-
} else {
|
|
37680
|
-
const data = await fs$1.readFile(fullPath);
|
|
37681
|
-
zipFolder.file(file, data);
|
|
37682
|
-
}
|
|
37683
|
-
}
|
|
37684
|
-
}
|
|
37685
|
-
for (const target of targetFiles) if ((await fs$1.stat(target.path)).isDirectory()) {
|
|
37686
|
-
const folder = zip.folder(target.name);
|
|
37687
|
-
if (folder) await addFiles(target.path, folder);
|
|
37688
|
-
} else {
|
|
37689
|
-
const data = await fs$1.readFile(target.path);
|
|
37690
|
-
zip.file(target.name, data);
|
|
37691
|
-
}
|
|
37692
|
-
const content = await zip.generateAsync({
|
|
37693
|
-
type: "nodebuffer",
|
|
37694
|
-
compression: "DEFLATE",
|
|
37695
|
-
compressionOptions: { level: 9 },
|
|
37696
|
-
platform: "UNIX"
|
|
37697
|
-
});
|
|
37698
|
-
await fs$1.mkdir(path.dirname(outfile), { recursive: true });
|
|
37699
|
-
await fs$1.writeFile(outfile, content);
|
|
37700
|
-
return outfile;
|
|
37701
|
-
};
|
|
37702
37642
|
const createZip = async ({ outfile, targetDir, excludeExts = [] }) => {
|
|
37703
37643
|
const zip = new import_lib.default();
|
|
37704
37644
|
await fs$1.rm(outfile, { force: true });
|
|
@@ -37734,13 +37674,13 @@ const createZip = async ({ outfile, targetDir, excludeExts = [] }) => {
|
|
|
37734
37674
|
};
|
|
37735
37675
|
//#endregion
|
|
37736
37676
|
//#region src/isObject.ts
|
|
37737
|
-
const isObject$
|
|
37677
|
+
const isObject$2 = (value) => {
|
|
37738
37678
|
return !!value && value.constructor === Object;
|
|
37739
37679
|
};
|
|
37740
37680
|
//#endregion
|
|
37741
37681
|
//#region src/crypto.ts
|
|
37742
37682
|
const encryptJson = (jsonData, secretKey) => {
|
|
37743
|
-
if (isObject$
|
|
37683
|
+
if (isObject$2(jsonData) === false) throw new Error("jsonData must be an object");
|
|
37744
37684
|
const iv = crypto.randomBytes(16);
|
|
37745
37685
|
const cipher = crypto.createCipheriv("aes-256-cbc", Buffer.from(secretKey, "hex"), iv);
|
|
37746
37686
|
let encrypted = cipher.update(JSON.stringify(jsonData));
|
|
@@ -40943,9 +40883,9 @@ const isReadableStream = (value) => Object.prototype.toString.call(value) === "[
|
|
|
40943
40883
|
const isWritableStream = (value) => Object.prototype.toString.call(value) === "[object WritableStream]";
|
|
40944
40884
|
const isWebStream = (value) => isReadableStream(value) || isWritableStream(value);
|
|
40945
40885
|
const isTransformStream = (value) => isReadableStream(value?.readable) && isWritableStream(value?.writable);
|
|
40946
|
-
const isAsyncIterableObject = (value) => isObject(value) && typeof value[Symbol.asyncIterator] === "function";
|
|
40947
|
-
const isIterableObject = (value) => isObject(value) && typeof value[Symbol.iterator] === "function";
|
|
40948
|
-
const isObject = (value) => typeof value === "object" && value !== null;
|
|
40886
|
+
const isAsyncIterableObject = (value) => isObject$1(value) && typeof value[Symbol.asyncIterator] === "function";
|
|
40887
|
+
const isIterableObject = (value) => isObject$1(value) && typeof value[Symbol.iterator] === "function";
|
|
40888
|
+
const isObject$1 = (value) => typeof value === "object" && value !== null;
|
|
40949
40889
|
const TRANSFORM_TYPES = /* @__PURE__ */ new Set([
|
|
40950
40890
|
"generator",
|
|
40951
40891
|
"asyncGenerator",
|
|
@@ -42298,8 +42238,8 @@ const addPropertiesAsync = {
|
|
|
42298
42238
|
},
|
|
42299
42239
|
output: {
|
|
42300
42240
|
...addProperties,
|
|
42301
|
-
fileUrl: ({ value }) => ({ stream: createWriteStream
|
|
42302
|
-
filePath: ({ value: { file, append } }) => ({ stream: createWriteStream
|
|
42241
|
+
fileUrl: ({ value }) => ({ stream: createWriteStream(value) }),
|
|
42242
|
+
filePath: ({ value: { file, append } }) => ({ stream: createWriteStream(file, append ? { flags: "a" } : {}) }),
|
|
42303
42243
|
webStream: ({ value }) => ({ stream: Writable.fromWeb(value) }),
|
|
42304
42244
|
iterable: forbiddenIfAsync,
|
|
42305
42245
|
asyncIterable: forbiddenIfAsync,
|
|
@@ -44175,7 +44115,6 @@ const getReactNativeMetadatas = (cwd) => {
|
|
|
44175
44115
|
const HOT_UPDATER_CONFIG_PATH = "hot-updater.config.ts";
|
|
44176
44116
|
const CONFIG_FILE_NAME = "hot-updater.config.ts";
|
|
44177
44117
|
const MANAGED_IMPORT_PACKAGES = /* @__PURE__ */ new Set([
|
|
44178
|
-
"dotenv",
|
|
44179
44118
|
"firebase-admin",
|
|
44180
44119
|
"firebase-admin/app",
|
|
44181
44120
|
"hot-updater",
|
|
@@ -44200,6 +44139,9 @@ const KNOWN_BUILD_CALLEES = /* @__PURE__ */ new Set([
|
|
|
44200
44139
|
"expo",
|
|
44201
44140
|
"rock"
|
|
44202
44141
|
]);
|
|
44142
|
+
/** Build adapter packages, whose imports a kept build still needs. */
|
|
44143
|
+
const BUILD_IMPORT_PACKAGES = new Set([...KNOWN_BUILD_CALLEES].map((callee) => `@hot-updater/${callee}`));
|
|
44144
|
+
const importDeclarationsOf = (source) => source.program.body.filter((statement) => statement.type === "ImportDeclaration");
|
|
44203
44145
|
const parseConfigSource = (text) => {
|
|
44204
44146
|
const result = parseSync(CONFIG_FILE_NAME, text, {
|
|
44205
44147
|
astType: "js",
|
|
@@ -44263,13 +44205,6 @@ const getObjectPropertyName = (property) => {
|
|
|
44263
44205
|
return null;
|
|
44264
44206
|
};
|
|
44265
44207
|
const isDataProperty = (property) => property.type === "Property" && property.kind === "init" && !property.method && !property.shorthand;
|
|
44266
|
-
const hasTrailingComma = (text) => {
|
|
44267
|
-
const closeBraceIndex = text.lastIndexOf("}");
|
|
44268
|
-
if (closeBraceIndex === -1) return false;
|
|
44269
|
-
let index = closeBraceIndex - 1;
|
|
44270
|
-
while (index >= 0 && /\s/.test(text[index] ?? "")) index -= 1;
|
|
44271
|
-
return (text[index] ?? "") === ",";
|
|
44272
|
-
};
|
|
44273
44208
|
const dedentBlock = (text) => {
|
|
44274
44209
|
const lines = text.replace(/\s+$/, "").split("\n");
|
|
44275
44210
|
const indents = lines.filter((line) => line.trim() !== "").map((line) => line.match(/^\s*/)?.[0].length ?? 0);
|
|
@@ -44277,16 +44212,55 @@ const dedentBlock = (text) => {
|
|
|
44277
44212
|
return lines.map((line) => line.slice(minIndent)).join("\n");
|
|
44278
44213
|
};
|
|
44279
44214
|
const indentBlock = (text, indent) => dedentBlock(text).split("\n").map((line) => `${indent}${line}`).join("\n");
|
|
44280
|
-
const
|
|
44281
|
-
|
|
44282
|
-
|
|
44283
|
-
|
|
44284
|
-
|
|
44285
|
-
|
|
44286
|
-
|
|
44287
|
-
|
|
44288
|
-
|
|
44289
|
-
|
|
44215
|
+
const lineStartOf = (text, position) => text.lastIndexOf("\n", position - 1) + 1;
|
|
44216
|
+
/** The indentation of the line `position` is on. */
|
|
44217
|
+
const lineIndentAt = (text, position) => /^[ \t]*/.exec(text.slice(lineStartOf(text, position)))?.[0] ?? "";
|
|
44218
|
+
/** Whether `node` is the first thing on its line. */
|
|
44219
|
+
const startsLine = (text, node) => /^[ \t]*$/.test(text.slice(lineStartOf(text, node.start), node.start));
|
|
44220
|
+
/**
|
|
44221
|
+
* A property's text with the indentation of its line, so that a property
|
|
44222
|
+
* spanning lines dedents and indents as one block.
|
|
44223
|
+
*/
|
|
44224
|
+
const getPropertyBlockText = (source, property) => source.text.slice(startsLine(source.text, property) ? lineStartOf(source.text, property.start) : property.start, property.end);
|
|
44225
|
+
/**
|
|
44226
|
+
* The edits, relative to `object`'s start, that add `propertyTexts` after its
|
|
44227
|
+
* last property: each on a line of its own, after that property's comma and
|
|
44228
|
+
* whatever comment shares its line, or after it on the line of a one-line
|
|
44229
|
+
* object.
|
|
44230
|
+
*/
|
|
44231
|
+
const appendPropertiesEdits = (source, object, propertyTexts) => {
|
|
44232
|
+
if (propertyTexts.length === 0) return [];
|
|
44233
|
+
const { text } = source;
|
|
44234
|
+
const closeBrace = object.end - 1;
|
|
44235
|
+
const relative = (position) => position - object.start;
|
|
44236
|
+
const closingIndent = lineIndentAt(text, closeBrace);
|
|
44237
|
+
const last = object.properties.at(-1);
|
|
44238
|
+
if (!last) {
|
|
44239
|
+
const childIndent = `${closingIndent} `;
|
|
44240
|
+
return [{
|
|
44241
|
+
start: relative(object.start + 1),
|
|
44242
|
+
end: relative(closeBrace),
|
|
44243
|
+
text: `\n${propertyTexts.map((propertyText) => indentBlock(propertyText, childIndent)).join(",\n")},\n${closingIndent}`
|
|
44244
|
+
}];
|
|
44245
|
+
}
|
|
44246
|
+
const comma = /^\s*,/.exec(text.slice(last.end, closeBrace));
|
|
44247
|
+
const afterLast = last.end + (comma?.[0].length ?? 0);
|
|
44248
|
+
const lineEnd = text.indexOf("\n", afterLast);
|
|
44249
|
+
if (!startsLine(text, last) || lineEnd === -1 || lineEnd > closeBrace) return [{
|
|
44250
|
+
start: relative(afterLast),
|
|
44251
|
+
end: relative(afterLast),
|
|
44252
|
+
text: `${comma ? " " : ", "}${propertyTexts.map((propertyText) => dedentBlock(propertyText)).join(", ")}`
|
|
44253
|
+
}];
|
|
44254
|
+
const childIndent = lineIndentAt(text, last.start);
|
|
44255
|
+
return [...comma ? [] : [{
|
|
44256
|
+
start: relative(last.end),
|
|
44257
|
+
end: relative(last.end),
|
|
44258
|
+
text: ","
|
|
44259
|
+
}], {
|
|
44260
|
+
start: relative(lineEnd),
|
|
44261
|
+
end: relative(lineEnd),
|
|
44262
|
+
text: `\n${propertyTexts.map((propertyText) => indentBlock(propertyText, childIndent)).join(",\n")},`
|
|
44263
|
+
}];
|
|
44290
44264
|
};
|
|
44291
44265
|
const mergeObjectLiteralText = (existingObject, newObject, replaceIncompatibleProperties = []) => {
|
|
44292
44266
|
const existingText = getNodeText(existingObject.source, existingObject.objectExpression);
|
|
@@ -44330,14 +44304,12 @@ const mergeObjectLiteralText = (existingObject, newObject, replaceIncompatiblePr
|
|
|
44330
44304
|
});
|
|
44331
44305
|
}
|
|
44332
44306
|
}
|
|
44333
|
-
let mergedText = existingText;
|
|
44334
|
-
for (const edit of edits.sort((left, right) => right.start - left.start)) mergedText = mergedText.slice(0, edit.start) + edit.text + mergedText.slice(edit.end);
|
|
44335
44307
|
const missingPropertyTexts = newObject.objectExpression.properties.filter((property) => {
|
|
44336
44308
|
if (property.type === "SpreadElement") return !existingSpreadTexts.has(getNodeText(newObject.source, property.argument).trim());
|
|
44337
44309
|
const propertyName = getObjectPropertyName(property);
|
|
44338
44310
|
return propertyName ? !existingPropertyNames.has(propertyName) : false;
|
|
44339
|
-
}).map((property) =>
|
|
44340
|
-
return
|
|
44311
|
+
}).map((property) => getPropertyBlockText(newObject.source, property));
|
|
44312
|
+
return applyTextEdits(existingText, [...edits, ...appendPropertiesEdits(existingObject.source, existingObject.objectExpression, missingPropertyTexts)]);
|
|
44341
44313
|
};
|
|
44342
44314
|
const buildMergedCallInitializer = (existing, next) => {
|
|
44343
44315
|
const [existingArgument] = existing.callExpression.arguments;
|
|
@@ -44359,6 +44331,11 @@ const findManagedProperty = (objectExpression, propertyName) => {
|
|
|
44359
44331
|
const property = objectExpression.properties.find((candidate) => candidate.type === "Property" && candidate.kind === "init" && !candidate.method && !candidate.shorthand && getObjectPropertyName(candidate) === propertyName);
|
|
44360
44332
|
return property?.type === "Property" ? property : null;
|
|
44361
44333
|
};
|
|
44334
|
+
/** The `plugins` property, written either way: `plugins: [...]` or `plugins`. */
|
|
44335
|
+
const findPluginsProperty = (objectExpression) => {
|
|
44336
|
+
const property = objectExpression.properties.find((candidate) => candidate.type === "Property" && candidate.kind === "init" && !candidate.method && getObjectPropertyName(candidate) === "plugins");
|
|
44337
|
+
return property?.type === "Property" ? property : null;
|
|
44338
|
+
};
|
|
44362
44339
|
const getCallCallee = (expression) => {
|
|
44363
44340
|
if (expression.type !== "CallExpression" || expression.callee.type !== "Identifier") return null;
|
|
44364
44341
|
return expression.callee.name;
|
|
@@ -44382,11 +44359,20 @@ const mergeHelperStatement = (existingStatementText, helper) => {
|
|
|
44382
44359
|
if (!mergedInitializer) return null;
|
|
44383
44360
|
return `${existingStatement.statement.kind === "let" || existingStatement.statement.kind === "var" ? existingStatement.statement.kind : "const"} ${helper.name} = ${mergedInitializer};`;
|
|
44384
44361
|
};
|
|
44362
|
+
/**
|
|
44363
|
+
* The config object with the scaffold's `build`, `storage`, `database`, and
|
|
44364
|
+
* `plugins`: a call to the same adapter keeps the project's arguments and
|
|
44365
|
+
* gains the scaffold's missing ones, and `plugins` is the scaffold's. A build
|
|
44366
|
+
* that is not a plain build adapter, such as `withSentry(bare())`, stays the
|
|
44367
|
+
* project's (`keptBuild`). Null when `build`, `storage`, or `database` is no
|
|
44368
|
+
* call.
|
|
44369
|
+
*/
|
|
44385
44370
|
const updateManagedObject = (existing, next) => {
|
|
44386
44371
|
const objectStart = existing.objectExpression.start;
|
|
44387
44372
|
const objectText = getNodeText(existing.source, existing.objectExpression);
|
|
44388
44373
|
const propertyEdits = [];
|
|
44389
44374
|
const missingPropertyTexts = [];
|
|
44375
|
+
let keptBuild = false;
|
|
44390
44376
|
for (const propertyName of [
|
|
44391
44377
|
"build",
|
|
44392
44378
|
"storage",
|
|
@@ -44396,7 +44382,7 @@ const updateManagedObject = (existing, next) => {
|
|
|
44396
44382
|
const nextProperty = findManagedProperty(next.objectExpression, propertyName);
|
|
44397
44383
|
if (!nextProperty) continue;
|
|
44398
44384
|
if (!existingProperty) {
|
|
44399
|
-
missingPropertyTexts.push(
|
|
44385
|
+
missingPropertyTexts.push(getPropertyBlockText(next.source, nextProperty));
|
|
44400
44386
|
continue;
|
|
44401
44387
|
}
|
|
44402
44388
|
const existingCallee = getCallCallee(existingProperty.value);
|
|
@@ -44404,8 +44390,11 @@ const updateManagedObject = (existing, next) => {
|
|
|
44404
44390
|
if (!existingCallee || !nextCallee) return null;
|
|
44405
44391
|
let nextInitializerText = getNodeText(next.source, nextProperty.value);
|
|
44406
44392
|
if (propertyName === "build") {
|
|
44393
|
+
if (!KNOWN_BUILD_CALLEES.has(existingCallee)) {
|
|
44394
|
+
keptBuild = true;
|
|
44395
|
+
continue;
|
|
44396
|
+
}
|
|
44407
44397
|
if (existingCallee === nextCallee) continue;
|
|
44408
|
-
if (!KNOWN_BUILD_CALLEES.has(existingCallee)) return null;
|
|
44409
44398
|
} else if (existingCallee === nextCallee) {
|
|
44410
44399
|
if (existingProperty.value.type !== "CallExpression" || nextProperty.value.type !== "CallExpression") return null;
|
|
44411
44400
|
const mergedInitializer = buildMergedCallInitializer({
|
|
@@ -44424,29 +44413,96 @@ const updateManagedObject = (existing, next) => {
|
|
|
44424
44413
|
text: nextInitializerText
|
|
44425
44414
|
});
|
|
44426
44415
|
}
|
|
44427
|
-
|
|
44428
|
-
|
|
44429
|
-
|
|
44416
|
+
const existingPlugins = findPluginsProperty(existing.objectExpression);
|
|
44417
|
+
const nextPlugins = findPluginsProperty(next.objectExpression);
|
|
44418
|
+
if (nextPlugins) {
|
|
44419
|
+
const nextPluginsText = getNodeText(next.source, nextPlugins);
|
|
44420
|
+
if (!existingPlugins) missingPropertyTexts.push(getPropertyBlockText(next.source, nextPlugins));
|
|
44421
|
+
else if (getNodeText(existing.source, existingPlugins).replace(/\s+/gu, "") !== nextPluginsText.replace(/\s+/gu, "")) propertyEdits.push({
|
|
44422
|
+
start: existingPlugins.start - objectStart,
|
|
44423
|
+
end: existingPlugins.end - objectStart,
|
|
44424
|
+
text: nextPluginsText
|
|
44425
|
+
});
|
|
44426
|
+
}
|
|
44427
|
+
return {
|
|
44428
|
+
text: applyTextEdits(objectText, [...propertyEdits, ...appendPropertiesEdits(existing.source, existing.objectExpression, missingPropertyTexts)]),
|
|
44429
|
+
keptBuild
|
|
44430
|
+
};
|
|
44430
44431
|
};
|
|
44431
|
-
const isConfigCallStatement = (statement) => statement.type === "ExpressionStatement" && statement.expression.type === "CallExpression" && statement.expression.callee.type === "Identifier" && statement.expression.callee.name === "config";
|
|
44432
44432
|
const getManagedHelperName = (statement) => {
|
|
44433
44433
|
if (statement.type !== "VariableDeclaration") return null;
|
|
44434
44434
|
const declaration = statement.declarations[0];
|
|
44435
44435
|
if (declaration?.id.type !== "Identifier") return null;
|
|
44436
44436
|
return declaration.id.name;
|
|
44437
44437
|
};
|
|
44438
|
-
|
|
44439
|
-
|
|
44438
|
+
/** Whether `text` refers to `name` as an identifier. */
|
|
44439
|
+
const usesIdentifier = (text, name) => new RegExp(`(?<![\\w$])${name.replace(/\$/g, "\\$")}(?![\\w$])`).test(text);
|
|
44440
|
+
/**
|
|
44441
|
+
* What a managed package's existing imports bring that the rebuilt config
|
|
44442
|
+
* still uses and the scaffold doesn't import, such as a project's own `cert`
|
|
44443
|
+
* from firebase-admin/app beside the `credential` helper init keeps. Named
|
|
44444
|
+
* value imports join the scaffold's import of that package; default,
|
|
44445
|
+
* namespace, and type imports keep a declaration of their own.
|
|
44446
|
+
*/
|
|
44447
|
+
const keptManagedImports = (declarations, scaffoldImports, usedText) => {
|
|
44448
|
+
const bound = new Set(scaffoldImports.flatMap((info) => [...(info.named ?? []).map((name) => name.split(/\s+as\s+/).at(-1)), ...info.defaultOrNamespace === void 0 ? [] : [info.defaultOrNamespace.replace(/^\*\s+as\s+/, "")]]));
|
|
44449
|
+
const imports = [];
|
|
44450
|
+
const texts = [];
|
|
44451
|
+
for (const declaration of declarations) {
|
|
44452
|
+
const pkg = declaration.source.value;
|
|
44453
|
+
const named = [];
|
|
44454
|
+
let defaultName;
|
|
44455
|
+
let namespaceName;
|
|
44456
|
+
for (const specifier of declaration.specifiers) {
|
|
44457
|
+
const local = specifier.local.name;
|
|
44458
|
+
if (bound.has(local) || !usesIdentifier(usedText, local)) continue;
|
|
44459
|
+
if (specifier.type === "ImportDefaultSpecifier") defaultName = local;
|
|
44460
|
+
else if (specifier.type === "ImportNamespaceSpecifier") namespaceName = local;
|
|
44461
|
+
else {
|
|
44462
|
+
const imported = specifier.imported.type === "Identifier" ? specifier.imported.name : JSON.stringify(specifier.imported.value);
|
|
44463
|
+
const name = imported === local ? local : `${imported} as ${local}`;
|
|
44464
|
+
named.push(specifier.importKind === "type" ? `type ${name}` : name);
|
|
44465
|
+
}
|
|
44466
|
+
}
|
|
44467
|
+
if (named.length === 0 && !defaultName && !namespaceName) continue;
|
|
44468
|
+
if (declaration.importKind === "type") {
|
|
44469
|
+
texts.push(`import type { ${named.join(", ")} } from "${pkg}";`);
|
|
44470
|
+
continue;
|
|
44471
|
+
}
|
|
44472
|
+
if (named.length > 0) imports.push({
|
|
44473
|
+
pkg,
|
|
44474
|
+
named
|
|
44475
|
+
});
|
|
44476
|
+
if (namespaceName) texts.push(`import * as ${namespaceName} from "${pkg}";`);
|
|
44477
|
+
if (defaultName) texts.push(`import ${defaultName} from "${pkg}";`);
|
|
44478
|
+
}
|
|
44479
|
+
return {
|
|
44480
|
+
imports,
|
|
44481
|
+
texts
|
|
44482
|
+
};
|
|
44483
|
+
};
|
|
44484
|
+
const rebuildImportBlock = (source, scaffold, { keptBuild, usedText }) => {
|
|
44485
|
+
const imports = scaffold.imports.filter((info) => !(keptBuild && BUILD_IMPORT_PACKAGES.has(info.pkg))).map((info) => info.pkg === "node:fs" ? {
|
|
44486
|
+
...info,
|
|
44487
|
+
named: info.named?.filter((name) => name !== "existsSync")
|
|
44488
|
+
} : info);
|
|
44489
|
+
const importDeclarations = importDeclarationsOf(source);
|
|
44440
44490
|
const firstImport = importDeclarations[0];
|
|
44441
44491
|
const lastImport = importDeclarations.at(-1);
|
|
44442
44492
|
if (!firstImport || !lastImport) return {
|
|
44443
44493
|
start: 0,
|
|
44444
44494
|
end: 0,
|
|
44445
|
-
text: `${renderImportStatements(
|
|
44446
|
-
};
|
|
44447
|
-
const
|
|
44448
|
-
const
|
|
44449
|
-
const
|
|
44495
|
+
text: `${renderImportStatements(imports)}\n\n`
|
|
44496
|
+
};
|
|
44497
|
+
const isPreserved = (declaration) => !MANAGED_IMPORT_PACKAGES.has(declaration.source.value) || keptBuild && BUILD_IMPORT_PACKAGES.has(declaration.source.value);
|
|
44498
|
+
const preservedImportTexts = importDeclarations.filter(isPreserved).map((declaration) => source.text.slice(getTopLevelFullStart(source, declaration), declaration.end).trim());
|
|
44499
|
+
const kept = keptManagedImports(importDeclarations.filter((declaration) => !isPreserved(declaration)), imports, usedText);
|
|
44500
|
+
const managedImportText = renderImportStatements([...imports, ...kept.imports]);
|
|
44501
|
+
const nextImportBlock = [
|
|
44502
|
+
...preservedImportTexts,
|
|
44503
|
+
...kept.texts,
|
|
44504
|
+
managedImportText
|
|
44505
|
+
].filter(Boolean).join("\n");
|
|
44450
44506
|
return {
|
|
44451
44507
|
start: getTopLevelFullStart(source, firstImport),
|
|
44452
44508
|
end: lastImport.end,
|
|
@@ -44458,12 +44514,7 @@ const rebuildManagedBody = (source, exportStart, scaffold) => {
|
|
|
44458
44514
|
const managedHelpers = new Map(scaffold.helperStatements.map((statement) => [statement.name, statement]));
|
|
44459
44515
|
const emittedHelpers = /* @__PURE__ */ new Set();
|
|
44460
44516
|
const bodyStatements = [];
|
|
44461
|
-
const configStatements = [];
|
|
44462
44517
|
for (const statement of statementsBeforeExport) {
|
|
44463
|
-
if (isConfigCallStatement(statement)) {
|
|
44464
|
-
configStatements.push(getStatementText(source, statement));
|
|
44465
|
-
continue;
|
|
44466
|
-
}
|
|
44467
44518
|
const helperName = getManagedHelperName(statement);
|
|
44468
44519
|
if (!helperName || !MANAGED_HELPER_NAMES.has(helperName)) {
|
|
44469
44520
|
bodyStatements.push(getStatementText(source, statement));
|
|
@@ -44478,8 +44529,7 @@ const rebuildManagedBody = (source, exportStart, scaffold) => {
|
|
|
44478
44529
|
}
|
|
44479
44530
|
for (const helper of scaffold.helperStatements) if (!emittedHelpers.has(helper.name)) bodyStatements.push(helper.code.trim());
|
|
44480
44531
|
const bodyText = bodyStatements.filter(Boolean).join("\n\n");
|
|
44481
|
-
const
|
|
44482
|
-
const managedBody = bodyText ? `\n\n${configStatement}\n\n${bodyText}\n\n` : `\n\n${configStatement}\n\n`;
|
|
44532
|
+
const managedBody = bodyText ? `\n\n${bodyText}\n\n` : "\n\n";
|
|
44483
44533
|
return {
|
|
44484
44534
|
start: source.program.body.filter((statement) => statement.type === "ImportDeclaration").at(-1)?.end ?? 0,
|
|
44485
44535
|
end: exportStart,
|
|
@@ -44498,27 +44548,49 @@ const mergeHotUpdaterConfigText = (existingText, scaffold) => {
|
|
|
44498
44548
|
const existingConfig = findDefineConfigObject(existingSource);
|
|
44499
44549
|
const nextConfig = findDefineConfigObject(nextSource);
|
|
44500
44550
|
if (!existingConfig || !nextConfig) return { reason: "Existing config is not a supported `export default defineConfig({ ... })` shape." };
|
|
44501
|
-
const
|
|
44551
|
+
const nextObject = updateManagedObject({
|
|
44502
44552
|
objectExpression: existingConfig.objectExpression,
|
|
44503
44553
|
source: existingSource
|
|
44504
44554
|
}, {
|
|
44505
44555
|
objectExpression: nextConfig.objectExpression,
|
|
44506
44556
|
source: nextSource
|
|
44507
44557
|
});
|
|
44508
|
-
if (!
|
|
44558
|
+
if (!nextObject) return { reason: "Existing config uses dynamic build/storage/database expressions that cannot be merged safely." };
|
|
44509
44559
|
const exportFullStart = getTopLevelFullStart(existingSource, existingConfig.exportDeclaration);
|
|
44510
44560
|
const firstTriviaContent = existingText.slice(exportFullStart, existingConfig.exportDeclaration.start).search(/\S/);
|
|
44511
44561
|
const managedBodyEnd = firstTriviaContent === -1 ? existingConfig.exportDeclaration.start : exportFullStart + firstTriviaContent;
|
|
44512
44562
|
const bodyEdit = rebuildManagedBody(existingSource, managedBodyEnd, scaffold);
|
|
44513
44563
|
if (!bodyEdit) return { reason: "Existing helper declarations could not be merged safely." };
|
|
44564
|
+
const scaffoldNames = new Map(scaffold.imports.flatMap((info) => (info.named ?? []).map((name) => [name.split(/\s+as\s+/).at(-1), info.pkg])));
|
|
44565
|
+
for (const declaration of importDeclarationsOf(existingSource)) {
|
|
44566
|
+
const pkg = declaration.source.value;
|
|
44567
|
+
if (MANAGED_IMPORT_PACKAGES.has(pkg) && !(nextObject.keptBuild && BUILD_IMPORT_PACKAGES.has(pkg))) continue;
|
|
44568
|
+
for (const specifier of declaration.specifiers) {
|
|
44569
|
+
const scaffoldPackage = scaffoldNames.get(specifier.local.name);
|
|
44570
|
+
if (scaffoldPackage !== void 0 && scaffoldPackage !== pkg) return { reason: `The import of ${specifier.local.name} from "${pkg}" takes the name init imports from "${scaffoldPackage}".` };
|
|
44571
|
+
}
|
|
44572
|
+
}
|
|
44573
|
+
const objectEdit = {
|
|
44574
|
+
start: existingConfig.objectExpression.start,
|
|
44575
|
+
end: existingConfig.objectExpression.end,
|
|
44576
|
+
text: nextObject.text
|
|
44577
|
+
};
|
|
44578
|
+
const usedText = applyTextEdits(existingText, [
|
|
44579
|
+
objectEdit,
|
|
44580
|
+
bodyEdit,
|
|
44581
|
+
...importDeclarationsOf(existingSource).map((declaration) => ({
|
|
44582
|
+
start: declaration.start,
|
|
44583
|
+
end: declaration.end,
|
|
44584
|
+
text: ""
|
|
44585
|
+
}))
|
|
44586
|
+
]);
|
|
44514
44587
|
return { text: applyTextEdits(existingText, [
|
|
44515
|
-
|
|
44516
|
-
start: existingConfig.objectExpression.start,
|
|
44517
|
-
end: existingConfig.objectExpression.end,
|
|
44518
|
-
text: nextObjectText
|
|
44519
|
-
},
|
|
44588
|
+
objectEdit,
|
|
44520
44589
|
bodyEdit,
|
|
44521
|
-
rebuildImportBlock(existingSource, scaffold
|
|
44590
|
+
rebuildImportBlock(existingSource, scaffold, {
|
|
44591
|
+
keptBuild: nextObject.keptBuild,
|
|
44592
|
+
usedText
|
|
44593
|
+
})
|
|
44522
44594
|
]) };
|
|
44523
44595
|
};
|
|
44524
44596
|
const extractCallIdentifier = (initializer) => {
|
|
@@ -44526,9 +44598,9 @@ const extractCallIdentifier = (initializer) => {
|
|
|
44526
44598
|
if (!match) throw new Error(`Failed to extract call identifier from "${initializer}"`);
|
|
44527
44599
|
return match[1];
|
|
44528
44600
|
};
|
|
44529
|
-
const createHotUpdaterConfigScaffold = ({ build, storage, database, extraImports = [], helperStatements = [], updateStrategy = "appVersion" }) => {
|
|
44601
|
+
const createHotUpdaterConfigScaffold = ({ build, storage, database, plugins, extraImports = [], helperStatements = [], updateStrategy = "appVersion" }) => {
|
|
44530
44602
|
const intermediateCode = helperStatements.map((statement) => statement.code.trim()).filter(Boolean).join("\n\n");
|
|
44531
|
-
const builder = new ConfigBuilder().setBuildType(build).setStorage(storage).setDatabase(database);
|
|
44603
|
+
const builder = new ConfigBuilder().setBuildType(build).setStorage(storage).setDatabase(database).setPlugins(plugins);
|
|
44532
44604
|
for (const extraImport of extraImports) builder.addImport(extraImport);
|
|
44533
44605
|
if (intermediateCode) builder.setIntermediateCode(intermediateCode);
|
|
44534
44606
|
return createHotUpdaterConfigScaffoldFromBuilder(builder, {
|
|
@@ -44553,15 +44625,22 @@ const createHotUpdaterConfigScaffoldFromBuilder = (builder, { helperStatements =
|
|
|
44553
44625
|
initializer: scaffold.databaseConfigString,
|
|
44554
44626
|
callee: extractCallIdentifier(scaffold.databaseConfigString)
|
|
44555
44627
|
},
|
|
44628
|
+
plugins: { initializer: scaffold.pluginsConfigString },
|
|
44556
44629
|
helperStatements,
|
|
44557
44630
|
updateStrategy: `"${updateStrategy}"`
|
|
44558
44631
|
};
|
|
44559
44632
|
};
|
|
44633
|
+
const readTextFile = (filePath) => fs$1.readFile(filePath, "utf-8").catch((error) => {
|
|
44634
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return null;
|
|
44635
|
+
throw error;
|
|
44636
|
+
});
|
|
44637
|
+
/**
|
|
44638
|
+
* Writes hot-updater.config.ts: the scaffold's when there is none, or the
|
|
44639
|
+
* existing config with the scaffold's build, storage, database, plugins, and
|
|
44640
|
+
* helpers merged in, keeping the project's own settings.
|
|
44641
|
+
*/
|
|
44560
44642
|
const writeHotUpdaterConfig = async (scaffold, filePath = HOT_UPDATER_CONFIG_PATH) => {
|
|
44561
|
-
const existingText = await
|
|
44562
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT") return null;
|
|
44563
|
-
throw error;
|
|
44564
|
-
});
|
|
44643
|
+
const existingText = await readTextFile(filePath);
|
|
44565
44644
|
if (existingText === null) {
|
|
44566
44645
|
await fs$1.writeFile(filePath, `${scaffold.text}\n`, "utf-8");
|
|
44567
44646
|
return {
|
|
@@ -44581,6 +44660,63 @@ const writeHotUpdaterConfig = async (scaffold, filePath = HOT_UPDATER_CONFIG_PAT
|
|
|
44581
44660
|
path: filePath
|
|
44582
44661
|
};
|
|
44583
44662
|
};
|
|
44663
|
+
/**
|
|
44664
|
+
* What a config needs for the scaffold's storage, database, and plugins:
|
|
44665
|
+
* their imports, the helpers they read, and the properties themselves.
|
|
44666
|
+
*/
|
|
44667
|
+
const renderServerSettings = (scaffold) => {
|
|
44668
|
+
const imports = scaffold.imports.filter(({ pkg }) => pkg !== "hot-updater" && pkg !== "node:fs" && !BUILD_IMPORT_PACKAGES.has(pkg));
|
|
44669
|
+
const plugins = scaffold.plugins.initializer;
|
|
44670
|
+
return [
|
|
44671
|
+
renderImportStatements(imports),
|
|
44672
|
+
...scaffold.helperStatements.map(({ code }) => code.trim()),
|
|
44673
|
+
[
|
|
44674
|
+
` storage: ${scaffold.storage.initializer},`,
|
|
44675
|
+
` database: ${scaffold.database.initializer},`,
|
|
44676
|
+
` ${plugins === "plugins" ? "plugins" : `plugins: ${plugins}`},`
|
|
44677
|
+
].join("\n")
|
|
44678
|
+
].filter(Boolean).join("\n\n");
|
|
44679
|
+
};
|
|
44680
|
+
/** Where an older init wrote a managed server's plugins, which the config lists now. */
|
|
44681
|
+
const PLUGINS_FILE_PATH = "hotUpdater.plugins.ts";
|
|
44682
|
+
/**
|
|
44683
|
+
* Whether `text` is the plugins file an older init wrote: comments and a
|
|
44684
|
+
* re-export of a provider package's `plugins`, which the config imports now.
|
|
44685
|
+
*/
|
|
44686
|
+
const isGeneratedPluginsFile = (text) => /^export\{plugins\}from(["'])@hot-updater\/[\w-]+\1;?$/u.test(text.replace(/^\s*\/\/.*$/gmu, "").replace(/\s+/gu, ""));
|
|
44687
|
+
/**
|
|
44688
|
+
* Writes hot-updater.config.ts and says what it did. `settings` names the
|
|
44689
|
+
* provider in messages, such as "Supabase". A config it cannot merge is
|
|
44690
|
+
* kept, with what to add to it. The plugins file an older init wrote is
|
|
44691
|
+
* removed; one the project wrote is named, since nothing reads it.
|
|
44692
|
+
*/
|
|
44693
|
+
const writeHotUpdaterFiles = async (scaffold, { cwd = process.cwd(), settings }) => {
|
|
44694
|
+
const config = await writeHotUpdaterConfig(scaffold, path.join(cwd, HOT_UPDATER_CONFIG_PATH));
|
|
44695
|
+
if (config.status === "created") p.log.success(`Generated '${HOT_UPDATER_CONFIG_PATH}' file with ${settings} settings.`);
|
|
44696
|
+
else if (config.status === "merged") p.log.success(`Updated '${HOT_UPDATER_CONFIG_PATH}' file with ${settings} settings.`);
|
|
44697
|
+
else p.log.warn([
|
|
44698
|
+
`Kept existing '${HOT_UPDATER_CONFIG_PATH}' unchanged: ${config.reason}`,
|
|
44699
|
+
`Set storage, database, and plugins in its config, as init writes them for ${settings}:`,
|
|
44700
|
+
"",
|
|
44701
|
+
renderServerSettings(scaffold)
|
|
44702
|
+
].join("\n"));
|
|
44703
|
+
const pluginsPath = path.join(cwd, PLUGINS_FILE_PATH);
|
|
44704
|
+
const pluginsText = await readTextFile(pluginsPath);
|
|
44705
|
+
if (pluginsText === null) return { config };
|
|
44706
|
+
if (isGeneratedPluginsFile(pluginsText)) {
|
|
44707
|
+
await fs$1.rm(pluginsPath);
|
|
44708
|
+
p.log.success(`Removed '${PLUGINS_FILE_PATH}': \`plugins\` in '${HOT_UPDATER_CONFIG_PATH}' lists the plugins the server runs.`);
|
|
44709
|
+
return {
|
|
44710
|
+
config,
|
|
44711
|
+
pluginsFile: "removed"
|
|
44712
|
+
};
|
|
44713
|
+
}
|
|
44714
|
+
p.log.warn(`Nothing reads '${pluginsPath}' anymore: \`plugins\` in '${HOT_UPDATER_CONFIG_PATH}' lists the plugins the server runs. Delete it.`);
|
|
44715
|
+
return {
|
|
44716
|
+
config,
|
|
44717
|
+
pluginsFile: "kept"
|
|
44718
|
+
};
|
|
44719
|
+
};
|
|
44584
44720
|
//#endregion
|
|
44585
44721
|
//#region src/initOptions.ts
|
|
44586
44722
|
var InitError = class extends Error {
|
|
@@ -44873,100 +45009,6 @@ function isMergeableValue(value) {
|
|
|
44873
45009
|
//#endregion
|
|
44874
45010
|
//#region src/loadConfig.ts
|
|
44875
45011
|
var import_out = /* @__PURE__ */ __toESM(require_out(), 1);
|
|
44876
|
-
const missingDatabase = createDatabasePlugin({
|
|
44877
|
-
name: "missingDatabase",
|
|
44878
|
-
models: {
|
|
44879
|
-
bundles: {
|
|
44880
|
-
findById: async () => {
|
|
44881
|
-
throw new Error("database plugin is required");
|
|
44882
|
-
},
|
|
44883
|
-
findMany: async () => {
|
|
44884
|
-
throw new Error("database plugin is required");
|
|
44885
|
-
},
|
|
44886
|
-
count: async () => {
|
|
44887
|
-
throw new Error("database plugin is required");
|
|
44888
|
-
}
|
|
44889
|
-
},
|
|
44890
|
-
bundlePatches: { findByBundleIds: async () => {
|
|
44891
|
-
throw new Error("database plugin is required");
|
|
44892
|
-
} },
|
|
44893
|
-
releases: {
|
|
44894
|
-
findById: async () => {
|
|
44895
|
-
throw new Error("database plugin is required");
|
|
44896
|
-
},
|
|
44897
|
-
findMany: async () => {
|
|
44898
|
-
throw new Error("database plugin is required");
|
|
44899
|
-
},
|
|
44900
|
-
findManyByScope: async () => {
|
|
44901
|
-
throw new Error("database plugin is required");
|
|
44902
|
-
}
|
|
44903
|
-
},
|
|
44904
|
-
releaseCatalogs: {
|
|
44905
|
-
findByScopeKey: async () => {
|
|
44906
|
-
throw new Error("database plugin is required");
|
|
44907
|
-
},
|
|
44908
|
-
findMany: async () => {
|
|
44909
|
-
throw new Error("database plugin is required");
|
|
44910
|
-
}
|
|
44911
|
-
},
|
|
44912
|
-
channels: {
|
|
44913
|
-
insert: async () => {
|
|
44914
|
-
throw new Error("database plugin is required");
|
|
44915
|
-
},
|
|
44916
|
-
list: async () => {
|
|
44917
|
-
throw new Error("database plugin is required");
|
|
44918
|
-
},
|
|
44919
|
-
delete: async () => {
|
|
44920
|
-
throw new Error("database plugin is required");
|
|
44921
|
-
}
|
|
44922
|
-
},
|
|
44923
|
-
insights: {
|
|
44924
|
-
record: async () => {
|
|
44925
|
-
throw new Error("database plugin is required");
|
|
44926
|
-
},
|
|
44927
|
-
listEvents: async () => {
|
|
44928
|
-
throw new Error("database plugin is required");
|
|
44929
|
-
},
|
|
44930
|
-
findInstallations: async () => {
|
|
44931
|
-
throw new Error("database plugin is required");
|
|
44932
|
-
},
|
|
44933
|
-
countInstallations: async () => {
|
|
44934
|
-
throw new Error("database plugin is required");
|
|
44935
|
-
},
|
|
44936
|
-
countEvents: async () => {
|
|
44937
|
-
throw new Error("database plugin is required");
|
|
44938
|
-
}
|
|
44939
|
-
},
|
|
44940
|
-
apiKeys: {
|
|
44941
|
-
create: async () => {
|
|
44942
|
-
throw new Error("database plugin is required");
|
|
44943
|
-
},
|
|
44944
|
-
findByHash: async () => {
|
|
44945
|
-
throw new Error("database plugin is required");
|
|
44946
|
-
},
|
|
44947
|
-
list: async () => {
|
|
44948
|
-
throw new Error("database plugin is required");
|
|
44949
|
-
},
|
|
44950
|
-
revoke: async () => {
|
|
44951
|
-
throw new Error("database plugin is required");
|
|
44952
|
-
}
|
|
44953
|
-
}
|
|
44954
|
-
},
|
|
44955
|
-
commit: async () => {
|
|
44956
|
-
throw new Error("database plugin is required");
|
|
44957
|
-
}
|
|
44958
|
-
});
|
|
44959
|
-
const missingStorageError = async () => {
|
|
44960
|
-
throw new Error("storage plugin is required");
|
|
44961
|
-
};
|
|
44962
|
-
const missingStorage = createStoragePlugin({
|
|
44963
|
-
name: "missingStorage",
|
|
44964
|
-
protocol: "missing",
|
|
44965
|
-
put: missingStorageError,
|
|
44966
|
-
get: missingStorageError,
|
|
44967
|
-
exists: missingStorageError,
|
|
44968
|
-
delete: missingStorageError
|
|
44969
|
-
});
|
|
44970
45012
|
const getDefaultPlatformConfig = () => {
|
|
44971
45013
|
let infoPlistPaths = [];
|
|
44972
45014
|
try {
|
|
@@ -45003,7 +45045,6 @@ const getDefaultConfig = () => {
|
|
|
45003
45045
|
return {
|
|
45004
45046
|
cacheDir: path.join("node_modules", ".hot-updater"),
|
|
45005
45047
|
updateStrategy: "appVersion",
|
|
45006
|
-
compressStrategy: "zip",
|
|
45007
45048
|
fingerprint: {},
|
|
45008
45049
|
patch: {
|
|
45009
45050
|
enabled: true,
|
|
@@ -45016,25 +45057,26 @@ const getDefaultConfig = () => {
|
|
|
45016
45057
|
ios: {}
|
|
45017
45058
|
},
|
|
45018
45059
|
build: () => {
|
|
45019
|
-
throw new Error("build
|
|
45060
|
+
throw new Error("build adapter is required");
|
|
45020
45061
|
},
|
|
45021
|
-
|
|
45022
|
-
database: missingDatabase
|
|
45062
|
+
plugins: []
|
|
45023
45063
|
};
|
|
45024
45064
|
};
|
|
45025
45065
|
const mergeConfigSources = (...sources) => {
|
|
45026
45066
|
const mergedConfig = sources.reduceRight((mergedConfig, source) => merge(mergedConfig, source ?? {}), {});
|
|
45027
45067
|
const database = sources.find((source) => source?.database)?.database;
|
|
45068
|
+
const plugins = sources.find((source) => source?.plugins)?.plugins;
|
|
45028
45069
|
const signing = sources.find((source) => source?.signing)?.signing;
|
|
45029
45070
|
const storage = sources.find((source) => source?.storage)?.storage;
|
|
45030
45071
|
return {
|
|
45031
45072
|
...mergedConfig,
|
|
45032
45073
|
...database ? { database } : {},
|
|
45074
|
+
...plugins ? { plugins } : {},
|
|
45033
45075
|
...signing ? { signing } : {},
|
|
45034
45076
|
...storage ? { storage } : {}
|
|
45035
45077
|
};
|
|
45036
45078
|
};
|
|
45037
|
-
const getConfigLoaderOptions = (
|
|
45079
|
+
const getConfigLoaderOptions = () => {
|
|
45038
45080
|
const cwd = getCwd();
|
|
45039
45081
|
return {
|
|
45040
45082
|
cwd,
|
|
@@ -45049,16 +45091,38 @@ const getConfigLoaderOptions = (options) => {
|
|
|
45049
45091
|
"cts",
|
|
45050
45092
|
"mjs",
|
|
45051
45093
|
"mts"
|
|
45052
|
-
]
|
|
45053
|
-
rewrite: async (config) => {
|
|
45054
|
-
return typeof config === "function" ? config(options) : config;
|
|
45055
|
-
}
|
|
45094
|
+
]
|
|
45056
45095
|
}]
|
|
45057
45096
|
};
|
|
45058
45097
|
};
|
|
45059
|
-
|
|
45060
|
-
|
|
45098
|
+
/** Each load runs the config file again, which creates its adapters again. */
|
|
45099
|
+
const loadConfigFile = async () => {
|
|
45100
|
+
const { config } = await loadConfig$1(getConfigLoaderOptions());
|
|
45101
|
+
return config;
|
|
45102
|
+
};
|
|
45103
|
+
const configFor = async (source, options) => typeof source === "function" ? await source(options) : source;
|
|
45104
|
+
const loadConfig = async (options) => resolveConfig(await configFor(await loadConfigFile(), options));
|
|
45105
|
+
/**
|
|
45106
|
+
* hot-updater.config for each of `platforms`, with the file loaded once. A
|
|
45107
|
+
* config object gives every platform the same database, storage, and
|
|
45108
|
+
* plugins; a config function runs once per platform, so the adapters it
|
|
45109
|
+
* creates are that platform's own.
|
|
45110
|
+
*/
|
|
45111
|
+
const loadPlatformConfigs = async (platforms, { channel }) => {
|
|
45112
|
+
const source = await loadConfigFile();
|
|
45113
|
+
const configs = [];
|
|
45114
|
+
for (const platform of platforms) configs.push({
|
|
45115
|
+
platform,
|
|
45116
|
+
config: resolveConfig(await configFor(source, {
|
|
45117
|
+
channel,
|
|
45118
|
+
platform
|
|
45119
|
+
}))
|
|
45120
|
+
});
|
|
45121
|
+
return configs;
|
|
45122
|
+
};
|
|
45123
|
+
const resolveConfig = (config) => {
|
|
45061
45124
|
for (const key of ["authorityId", "catalogId"]) if (config && Object.hasOwn(config, key)) throw new Error(`Remove ${key} from hot-updater.config. Catalog identity is managed internally.`);
|
|
45125
|
+
if (config && Object.hasOwn(config, "compressStrategy")) throw new Error("Remove compressStrategy from hot-updater.config. OTA artifacts use manifest files with per-file Brotli compression.");
|
|
45062
45126
|
const mergedConfig = mergeConfigSources(config, getDefaultConfig());
|
|
45063
45127
|
const signing = normalizeSigningConfig(mergedConfig.signing);
|
|
45064
45128
|
return {
|
|
@@ -45067,6 +45131,97 @@ const loadConfig = async (options) => {
|
|
|
45067
45131
|
};
|
|
45068
45132
|
};
|
|
45069
45133
|
//#endregion
|
|
45134
|
+
//#region src/serverDefinition.ts
|
|
45135
|
+
/** A server definition tooling cannot use, with what to do about it. */
|
|
45136
|
+
var ServerDefinitionError = class extends Error {
|
|
45137
|
+
name = "ServerDefinitionError";
|
|
45138
|
+
};
|
|
45139
|
+
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
45140
|
+
/** Whether `value` has the public properties tooling reads from a server definition. */
|
|
45141
|
+
const isServerDefinition = (value) => isRecord(value) && isRecord(value.database) && typeof value.database.name === "string" && Array.isArray(value.storage) && Array.isArray(value.plugins) && Array.isArray(value.clientPlugins) && Array.isArray(value.clientEndpoints) && isRecord(value.core) && isRecord(value.api);
|
|
45142
|
+
/**
|
|
45143
|
+
* `value` as a server definition, or a `ServerDefinitionError` that says
|
|
45144
|
+
* what `source`, the module it came from, must export instead.
|
|
45145
|
+
*/
|
|
45146
|
+
const serverDefinitionOf = (value, source = "The server definition") => {
|
|
45147
|
+
if (isServerDefinition(value)) return value;
|
|
45148
|
+
if (isRecord(value) && "handlers" in value) throw new ServerDefinitionError(`${source} exports a hotUpdater from an older @hot-updater/server. Upgrade @hot-updater/server to the version of hot-updater.`);
|
|
45149
|
+
throw new ServerDefinitionError(`${source} must export hotUpdater: the server createHotUpdater({ database, storage, plugins }) returns.`);
|
|
45150
|
+
};
|
|
45151
|
+
const fail = (message) => {
|
|
45152
|
+
throw new ServerDefinitionError(message);
|
|
45153
|
+
};
|
|
45154
|
+
const isObject = (value) => isRecord(value) && !Array.isArray(value);
|
|
45155
|
+
/** The clientAuth plugin's credential contribution, checked. */
|
|
45156
|
+
const credentialOf = ({ plugins, clientAuth }) => {
|
|
45157
|
+
if (clientAuth === void 0) return void 0;
|
|
45158
|
+
const id = clientAuth.plugin;
|
|
45159
|
+
const credential = plugins.find((plugin) => plugin.id === id)?.cli?.clientCredential;
|
|
45160
|
+
if (!isObject(credential) || typeof credential.label !== "string" || typeof credential.header !== "string" || typeof credential.env !== "string" || typeof credential.generate !== "function" || typeof credential.provision !== "function") return fail(`Plugin "${id}" provides clientAuth but no cli.clientCredential with a label, header, env, generate, and provision, so init cannot give an app its credential.`);
|
|
45161
|
+
return {
|
|
45162
|
+
plugin: id,
|
|
45163
|
+
credential
|
|
45164
|
+
};
|
|
45165
|
+
};
|
|
45166
|
+
/**
|
|
45167
|
+
* The client-route policy a definition's plugins set: the plugin that
|
|
45168
|
+
* provides clientAuth, the headers its decision reads, and the credential an
|
|
45169
|
+
* app sends. Undefined when client routes are public.
|
|
45170
|
+
*/
|
|
45171
|
+
const clientAuthOf = (definition) => {
|
|
45172
|
+
const found = credentialOf(definition);
|
|
45173
|
+
if (found === void 0) return void 0;
|
|
45174
|
+
const { label, header, env } = found.credential;
|
|
45175
|
+
return {
|
|
45176
|
+
plugin: found.plugin,
|
|
45177
|
+
varyHeaders: definition.clientAuth?.varyHeaders ?? [],
|
|
45178
|
+
credential: {
|
|
45179
|
+
label,
|
|
45180
|
+
header: header.toLowerCase(),
|
|
45181
|
+
env
|
|
45182
|
+
}
|
|
45183
|
+
};
|
|
45184
|
+
};
|
|
45185
|
+
/**
|
|
45186
|
+
* A new client credential, made without a database, so tooling can save it
|
|
45187
|
+
* before it registers it. Undefined when client routes are public.
|
|
45188
|
+
*/
|
|
45189
|
+
const generateClientCredential = (definition) => credentialOf(definition)?.credential.generate();
|
|
45190
|
+
/**
|
|
45191
|
+
* Provisions the credential an app sends to client routes, through the
|
|
45192
|
+
* plugin that provides clientAuth, on the definition's database: a
|
|
45193
|
+
* credential saved in `env` is registered again rather than replaced.
|
|
45194
|
+
* Undefined when client routes are public.
|
|
45195
|
+
*/
|
|
45196
|
+
const provisionClientCredential = async (definition, input) => {
|
|
45197
|
+
const found = credentialOf(definition);
|
|
45198
|
+
if (found === void 0) return void 0;
|
|
45199
|
+
const { label, header, env, provision } = found.credential;
|
|
45200
|
+
const existing = input.env[env]?.trim();
|
|
45201
|
+
const value = await provision(definition.api[found.plugin], {
|
|
45202
|
+
...existing ? { existing } : {},
|
|
45203
|
+
name: input.name
|
|
45204
|
+
});
|
|
45205
|
+
return {
|
|
45206
|
+
label,
|
|
45207
|
+
header: header.toLowerCase(),
|
|
45208
|
+
env,
|
|
45209
|
+
value
|
|
45210
|
+
};
|
|
45211
|
+
};
|
|
45212
|
+
/** Whether `hot-updater db generate` writes schema files for a definition's database. */
|
|
45213
|
+
const generatesSchema = ({ database }) => database.generateSchema !== void 0;
|
|
45214
|
+
/** The migrator for a definition's database: core's tables and its plugins' tables. */
|
|
45215
|
+
const createMigrator = ({ database, plugins }) => {
|
|
45216
|
+
if (database.createMigrator === void 0) throw new ServerDefinitionError(database.generateSchema === void 0 ? `The ${database.name} database has no migrator; its provider applies the schema.` : `The ${database.name} database applies its schema from migration files: run \`hot-updater db generate\`, then apply the file with the provider's tooling.`);
|
|
45217
|
+
return database.createMigrator(toolingTargetOf(plugins));
|
|
45218
|
+
};
|
|
45219
|
+
/** The schema file `hot-updater db generate` writes for a definition's database. */
|
|
45220
|
+
const generateSchema = ({ database, plugins }, version, name) => {
|
|
45221
|
+
if (database.generateSchema === void 0) throw new ServerDefinitionError(`The ${database.name} database has no schema generator; run \`hot-updater db migrate\` instead.`);
|
|
45222
|
+
return database.generateSchema(version, name, toolingTargetOf(plugins));
|
|
45223
|
+
};
|
|
45224
|
+
//#endregion
|
|
45070
45225
|
//#region src/log.ts
|
|
45071
45226
|
const log = {
|
|
45072
45227
|
normal: (message) => console.log(message),
|
|
@@ -45181,6 +45336,31 @@ const makeEnv = async (newEnvVars, filePath = ".env.hotupdater", options) => {
|
|
|
45181
45336
|
}
|
|
45182
45337
|
};
|
|
45183
45338
|
//#endregion
|
|
45339
|
+
//#region src/moduleSpecifiers.ts
|
|
45340
|
+
/**
|
|
45341
|
+
* The modules `source` imports or re-exports, statically or with an
|
|
45342
|
+
* `import()` of a string literal or a template without substitutions: what a
|
|
45343
|
+
* runtime that loads it unbundled, such as Deno through an import map,
|
|
45344
|
+
* resolves. An `import()` of a computed specifier, such as
|
|
45345
|
+
* `import("dayjs/locale/" + name + ".js")`, names no module to resolve.
|
|
45346
|
+
* Minified code reads the same as formatted code. `fileName` gives the
|
|
45347
|
+
* language, such as `.ts`.
|
|
45348
|
+
*/
|
|
45349
|
+
const moduleSpecifiersOf = (fileName, source) => {
|
|
45350
|
+
const { errors, module, program } = parseSync(fileName, source);
|
|
45351
|
+
if (errors.length > 0) throw new Error(`Could not read the imports of ${fileName}: ${errors[0].message}`);
|
|
45352
|
+
const dynamicImports = [];
|
|
45353
|
+
new Visitor({ ImportExpression({ source: specifier }) {
|
|
45354
|
+
if (specifier.type === "Literal" && typeof specifier.value === "string") dynamicImports.push(specifier.value);
|
|
45355
|
+
else if (specifier.type === "TemplateLiteral" && specifier.expressions.length === 0 && typeof specifier.quasis[0]?.value.cooked === "string") dynamicImports.push(specifier.quasis[0].value.cooked);
|
|
45356
|
+
} }).visit(program);
|
|
45357
|
+
return [.../* @__PURE__ */ new Set([
|
|
45358
|
+
...module.staticImports.map(({ moduleRequest }) => moduleRequest.value),
|
|
45359
|
+
...module.staticExports.flatMap(({ entries }) => entries.flatMap(({ moduleRequest }) => moduleRequest === null ? [] : [moduleRequest.value])),
|
|
45360
|
+
...dynamicImports
|
|
45361
|
+
])];
|
|
45362
|
+
};
|
|
45363
|
+
//#endregion
|
|
45184
45364
|
//#region src/storageFiles.ts
|
|
45185
45365
|
const getStorageFileByteSize = async (filePath) => {
|
|
45186
45366
|
const { size } = await fs$2.stat(filePath, { bigint: true });
|
|
@@ -45216,7 +45396,7 @@ const writeStorageResponseFile = async (response, filePath) => {
|
|
|
45216
45396
|
return;
|
|
45217
45397
|
}
|
|
45218
45398
|
try {
|
|
45219
|
-
await pipeline
|
|
45399
|
+
await pipeline(Readable$1.fromWeb(response.body), createWriteStream(filePath));
|
|
45220
45400
|
} catch (error) {
|
|
45221
45401
|
await fs$2.rm(filePath, { force: true });
|
|
45222
45402
|
throw error;
|
|
@@ -45252,11 +45432,6 @@ function verifySignedFileHash({ actualFileHash, publicKey, signedFileHash }) {
|
|
|
45252
45432
|
return false;
|
|
45253
45433
|
}
|
|
45254
45434
|
}
|
|
45255
|
-
function getArchiveFilename(storageUri) {
|
|
45256
|
-
const protocol = new URL(storageUri).protocol.replace(":", "");
|
|
45257
|
-
const { key } = parseStorageUri(storageUri, protocol);
|
|
45258
|
-
return path$1.posix.basename(key) || "bundle.zip";
|
|
45259
|
-
}
|
|
45260
45435
|
const getRelativeStorageDir = (relativePath) => {
|
|
45261
45436
|
const normalized = relativePath.replace(/\\/g, "/");
|
|
45262
45437
|
const dirname = path$1.posix.dirname(normalized);
|
|
@@ -45273,7 +45448,7 @@ async function prepareManifestAssetUploadFile({ assetPath, sourcePath, workDir }
|
|
|
45273
45448
|
if (getManifestAssetDownloadPath(assetPath) === assetPath) return sourcePath;
|
|
45274
45449
|
const uploadPath = resolvePreparedUploadPath(workDir, assetPath);
|
|
45275
45450
|
await fs$2.mkdir(path$1.dirname(uploadPath), { recursive: true });
|
|
45276
|
-
await pipeline
|
|
45451
|
+
await pipeline(createReadStream(sourcePath), createBrotliCompress({ params: { [constants$1.BROTLI_PARAM_QUALITY]: 11 } }), createWriteStream(uploadPath));
|
|
45277
45452
|
return uploadPath;
|
|
45278
45453
|
}
|
|
45279
45454
|
async function prepareContentAddressedUploadFile({ sourcePath, storagePath, workDir }) {
|
|
@@ -45290,9 +45465,10 @@ async function prepareManifestAssetUploadTargets({ extractDir, manifest, workDir
|
|
|
45290
45465
|
for (const assetPath of assetPaths) {
|
|
45291
45466
|
const asset = manifest.assets?.[assetPath];
|
|
45292
45467
|
if (!asset?.fileHash) throw new Error(`Manifest file hash not found for ${assetPath}`);
|
|
45468
|
+
const sourcePath = resolveExtractedPath(extractDir, assetPath);
|
|
45293
45469
|
const uploadSourcePath = await prepareManifestAssetUploadFile({
|
|
45294
45470
|
assetPath,
|
|
45295
|
-
sourcePath
|
|
45471
|
+
sourcePath,
|
|
45296
45472
|
workDir
|
|
45297
45473
|
});
|
|
45298
45474
|
const downloadByteSize = await getStorageFileByteSize(uploadSourcePath);
|
|
@@ -45301,6 +45477,7 @@ async function prepareManifestAssetUploadTargets({ extractDir, manifest, workDir
|
|
|
45301
45477
|
if (downloadFileHash !== void 0 && !isContentAddressedAssetFileHash(downloadFileHash)) throw new Error(`Prepared asset hash must be a lowercase SHA-256 hash: ${assetPath}`);
|
|
45302
45478
|
const nextAsset = {
|
|
45303
45479
|
...asset,
|
|
45480
|
+
byteSize: (await fs$2.stat(sourcePath)).size,
|
|
45304
45481
|
downloadByteSize
|
|
45305
45482
|
};
|
|
45306
45483
|
delete nextAsset.downloadFileHash;
|
|
@@ -45330,143 +45507,81 @@ function resolveExtractedPath(rootDir, entryName) {
|
|
|
45330
45507
|
const normalizedEntryName = entryName.replaceAll("\\", "/");
|
|
45331
45508
|
const entryPath = path$1.resolve(rootDir, normalizedEntryName);
|
|
45332
45509
|
const relativePath = path$1.relative(rootDir, entryPath);
|
|
45333
|
-
if (relativePath.startsWith("..") || path$1.isAbsolute(relativePath) || normalizedEntryName.startsWith("/")) throw new Error(`Invalid
|
|
45510
|
+
if (relativePath.startsWith("..") || path$1.isAbsolute(relativePath) || normalizedEntryName.startsWith("/")) throw new Error(`Invalid manifest asset path: ${entryName}`);
|
|
45334
45511
|
return entryPath;
|
|
45335
45512
|
}
|
|
45336
|
-
async function
|
|
45513
|
+
async function downloadStorageObject(storageUri, storageAdapter, outputPath) {
|
|
45337
45514
|
const protocol = new URL(storageUri).protocol.replace(":", "");
|
|
45338
|
-
if (
|
|
45339
|
-
await writeStorageFile(
|
|
45515
|
+
if (storageAdapter?.protocol === protocol) {
|
|
45516
|
+
await writeStorageFile(storageAdapter, storageUri, outputPath);
|
|
45340
45517
|
return;
|
|
45341
45518
|
}
|
|
45342
45519
|
if (protocol === "http" || protocol === "https") {
|
|
45343
|
-
await downloadFromUrl(storageUri,
|
|
45520
|
+
await downloadFromUrl(storageUri, outputPath);
|
|
45344
45521
|
return;
|
|
45345
45522
|
}
|
|
45346
|
-
throw new Error(`No storage
|
|
45523
|
+
throw new Error(`No storage adapter for protocol: ${protocol}`);
|
|
45347
45524
|
}
|
|
45348
45525
|
async function downloadFromUrl(fileUrl, filePath) {
|
|
45349
45526
|
const response = await fetch(fileUrl);
|
|
45350
|
-
if (!response.ok) throw new Error(`Failed to download
|
|
45527
|
+
if (!response.ok) throw new Error(`Failed to download storage object: ${response.statusText}`);
|
|
45351
45528
|
await writeStorageResponseFile(response, filePath);
|
|
45352
45529
|
}
|
|
45353
|
-
async function
|
|
45354
|
-
|
|
45355
|
-
|
|
45356
|
-
|
|
45357
|
-
const
|
|
45358
|
-
|
|
45359
|
-
|
|
45360
|
-
|
|
45530
|
+
async function downloadManifestAssets({ bundle, manifest, outputDir, storageAdapter, workDir }) {
|
|
45531
|
+
await runWithConcurrency(Object.keys(manifest.assets ?? {}).sort((left, right) => left.localeCompare(right)), PROMOTE_ASSET_CONCURRENCY, async (assetPath) => {
|
|
45532
|
+
const asset = manifest.assets?.[assetPath];
|
|
45533
|
+
if (!asset?.fileHash) throw new Error(`Manifest file hash not found for ${assetPath}`);
|
|
45534
|
+
const downloadPath = getManifestAssetDownloadPath(assetPath);
|
|
45535
|
+
const storageUri = resolveManifestAssetStorageUri({
|
|
45536
|
+
assetBaseStorageUri: bundle.assetBaseStorageUri,
|
|
45537
|
+
assetPath: downloadPath,
|
|
45538
|
+
downloadFileHash: asset.downloadFileHash,
|
|
45539
|
+
fileHash: asset.fileHash
|
|
45540
|
+
});
|
|
45541
|
+
const transferPath = resolveExtractedPath(workDir, `downloads/${downloadPath}`);
|
|
45542
|
+
await fs$2.mkdir(path$1.dirname(transferPath), { recursive: true });
|
|
45543
|
+
await downloadStorageObject(storageUri, storageAdapter, transferPath);
|
|
45544
|
+
if (asset.downloadFileHash) {
|
|
45545
|
+
if (await getFileHash(transferPath) !== asset.downloadFileHash.toLowerCase()) throw new Error(`Manifest download hash mismatch for ${assetPath}`);
|
|
45361
45546
|
}
|
|
45547
|
+
const outputPath = resolveExtractedPath(outputDir, assetPath);
|
|
45362
45548
|
await fs$2.mkdir(path$1.dirname(outputPath), { recursive: true });
|
|
45363
|
-
await fs$2.
|
|
45364
|
-
|
|
45365
|
-
}
|
|
45366
|
-
|
|
45367
|
-
const tarPath = path$1.join(extractDir, "bundle.tar");
|
|
45368
|
-
const compressedBuffer = await fs$2.readFile(archivePath);
|
|
45369
|
-
const tarBuffer = brotliDecompressSync(compressedBuffer);
|
|
45370
|
-
await fs$2.writeFile(tarPath, tarBuffer);
|
|
45371
|
-
try {
|
|
45372
|
-
await uo({
|
|
45373
|
-
file: tarPath,
|
|
45374
|
-
cwd: extractDir,
|
|
45375
|
-
gzip: false,
|
|
45376
|
-
strict: true
|
|
45377
|
-
});
|
|
45378
|
-
} finally {
|
|
45379
|
-
await fs$2.rm(tarPath, { force: true });
|
|
45380
|
-
}
|
|
45381
|
-
}
|
|
45382
|
-
async function extractArchive(archivePath, extractDir) {
|
|
45383
|
-
const { format } = detectCompressionFormat(path$1.basename(archivePath));
|
|
45384
|
-
switch (format) {
|
|
45385
|
-
case "zip":
|
|
45386
|
-
await extractZipArchive(archivePath, extractDir);
|
|
45387
|
-
return format;
|
|
45388
|
-
case "tar.gz":
|
|
45389
|
-
await uo({
|
|
45390
|
-
file: archivePath,
|
|
45391
|
-
cwd: extractDir,
|
|
45392
|
-
gzip: true,
|
|
45393
|
-
strict: true
|
|
45394
|
-
});
|
|
45395
|
-
return format;
|
|
45396
|
-
case "tar.br":
|
|
45397
|
-
await extractTarBrArchive(archivePath, extractDir);
|
|
45398
|
-
return format;
|
|
45399
|
-
}
|
|
45400
|
-
}
|
|
45401
|
-
async function getArchiveTargetFiles(bundleDir) {
|
|
45402
|
-
const entries = await fs$2.readdir(bundleDir, { withFileTypes: true });
|
|
45403
|
-
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
45404
|
-
return entries.map((entry) => ({
|
|
45405
|
-
path: path$1.join(bundleDir, entry.name),
|
|
45406
|
-
name: entry.name
|
|
45407
|
-
}));
|
|
45408
|
-
}
|
|
45409
|
-
async function createArchiveFromDirectory(sourceDir, archivePath, format) {
|
|
45410
|
-
const targetFiles = await getArchiveTargetFiles(sourceDir);
|
|
45411
|
-
switch (format) {
|
|
45412
|
-
case "zip":
|
|
45413
|
-
await createZipTargetFiles({
|
|
45414
|
-
outfile: archivePath,
|
|
45415
|
-
targetFiles
|
|
45416
|
-
});
|
|
45417
|
-
return;
|
|
45418
|
-
case "tar.gz":
|
|
45419
|
-
await createTarGzTargetFiles({
|
|
45420
|
-
outfile: archivePath,
|
|
45421
|
-
targetFiles
|
|
45422
|
-
});
|
|
45423
|
-
return;
|
|
45424
|
-
case "tar.br":
|
|
45425
|
-
await createTarBrTargetFiles({
|
|
45426
|
-
outfile: archivePath,
|
|
45427
|
-
targetFiles
|
|
45428
|
-
});
|
|
45429
|
-
return;
|
|
45430
|
-
}
|
|
45431
|
-
}
|
|
45432
|
-
async function readCopiedBundleManifest(extractDir, nextBundleId) {
|
|
45433
|
-
const manifestPath = path$1.join(extractDir, "manifest.json");
|
|
45434
|
-
try {
|
|
45435
|
-
await fs$2.access(manifestPath);
|
|
45436
|
-
} catch {
|
|
45437
|
-
throw new Error(LEGACY_BUNDLE_ERROR);
|
|
45438
|
-
}
|
|
45439
|
-
const manifest = JSON.parse(await fs$2.readFile(manifestPath, "utf8"));
|
|
45440
|
-
manifest.bundleId = nextBundleId;
|
|
45441
|
-
return {
|
|
45442
|
-
manifest,
|
|
45443
|
-
manifestPath
|
|
45444
|
-
};
|
|
45549
|
+
if (downloadPath === assetPath) await fs$2.copyFile(transferPath, outputPath);
|
|
45550
|
+
else await fs$2.writeFile(outputPath, brotliDecompressSync(await fs$2.readFile(transferPath)));
|
|
45551
|
+
if (await getFileHash(outputPath) !== asset.fileHash.toLowerCase()) throw new Error(`Manifest file hash mismatch for ${assetPath}`);
|
|
45552
|
+
});
|
|
45445
45553
|
}
|
|
45446
|
-
async function
|
|
45447
|
-
const archiveFilename = getArchiveFilename(bundle.storageUri);
|
|
45554
|
+
async function createCopiedBundleArtifacts({ bundle, config, nextBundleId, storageAdapter }) {
|
|
45448
45555
|
const workDir = await fs$2.mkdtemp(path$1.join(os.tmpdir(), "hot-updater-console-promote-"));
|
|
45449
|
-
const sourceArchivePath = path$1.join(workDir, archiveFilename);
|
|
45450
45556
|
const extractDir = path$1.join(workDir, "bundle");
|
|
45451
|
-
const
|
|
45557
|
+
const sourceManifestPath = path$1.join(workDir, "source-manifest.json");
|
|
45558
|
+
const manifestPath = path$1.join(extractDir, "manifest.json");
|
|
45452
45559
|
const uploadedStorageUris = [];
|
|
45453
45560
|
await fs$2.mkdir(extractDir, { recursive: true });
|
|
45454
45561
|
try {
|
|
45455
|
-
await
|
|
45456
|
-
const
|
|
45562
|
+
await downloadStorageObject(bundle.manifestStorageUri, storageAdapter, sourceManifestPath);
|
|
45563
|
+
const actualManifestHash = await getFileHash(sourceManifestPath);
|
|
45457
45564
|
const signingSession = await prepareBundleSigning(config.signing);
|
|
45458
|
-
if (isSignedFileHash(bundle.
|
|
45565
|
+
if (isSignedFileHash(bundle.manifestFileHash)) {
|
|
45459
45566
|
if (!signingSession) throw new Error("Cannot copy a signed bundle without enabled bundle signing configuration.");
|
|
45460
45567
|
if (!verifySignedFileHash({
|
|
45461
|
-
actualFileHash:
|
|
45568
|
+
actualFileHash: actualManifestHash,
|
|
45462
45569
|
publicKey: signingSession.publicKey,
|
|
45463
|
-
signedFileHash: bundle.
|
|
45464
|
-
})) throw new Error("Source
|
|
45465
|
-
} else if (
|
|
45466
|
-
const
|
|
45467
|
-
|
|
45570
|
+
signedFileHash: bundle.manifestFileHash
|
|
45571
|
+
})) throw new Error("Source manifest signature verification failed.");
|
|
45572
|
+
} else if (actualManifestHash !== bundle.manifestFileHash.toLowerCase()) throw new Error("Source manifest file hash verification failed.");
|
|
45573
|
+
const manifest = JSON.parse(await fs$2.readFile(sourceManifestPath, "utf8"));
|
|
45574
|
+
if (!manifest.assets || typeof manifest.assets !== "object") throw new Error(LEGACY_BUNDLE_ERROR);
|
|
45575
|
+
await downloadManifestAssets({
|
|
45576
|
+
bundle,
|
|
45577
|
+
manifest,
|
|
45578
|
+
outputDir: extractDir,
|
|
45579
|
+
storageAdapter,
|
|
45580
|
+
workDir
|
|
45581
|
+
});
|
|
45582
|
+
manifest.bundleId = nextBundleId;
|
|
45468
45583
|
const assetPaths = Object.keys(manifest.assets ?? {}).sort((left, right) => left.localeCompare(right));
|
|
45469
|
-
const sourceIsSigned =
|
|
45584
|
+
const sourceIsSigned = isSignedFileHash(getManifestFileHash(bundle));
|
|
45470
45585
|
const manifestHasSignatures = assetPaths.some((assetPath) => Boolean(manifest.assets?.[assetPath]?.signature));
|
|
45471
45586
|
if (!signingSession && (sourceIsSigned || manifestHasSignatures)) throw new Error("Cannot copy a signed bundle without enabled bundle signing configuration.");
|
|
45472
45587
|
if (signingSession) {
|
|
@@ -45486,16 +45601,22 @@ async function createCopiedBundleArchive({ bundle, config, nextBundleId, storage
|
|
|
45486
45601
|
manifest,
|
|
45487
45602
|
workDir
|
|
45488
45603
|
});
|
|
45604
|
+
const archivePath = path$1.join(workDir, "bundle.tar.br");
|
|
45605
|
+
manifest.archive = await createTarBrTargetFiles({
|
|
45606
|
+
outfile: archivePath,
|
|
45607
|
+
targetFiles: assetPaths.map((name) => ({
|
|
45608
|
+
name,
|
|
45609
|
+
path: resolveExtractedPath(extractDir, name)
|
|
45610
|
+
}))
|
|
45611
|
+
});
|
|
45489
45612
|
await fs$2.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
45490
|
-
await fs$2.rm(sourceArchivePath, { force: true });
|
|
45491
|
-
await createArchiveFromDirectory(extractDir, outputArchivePath, format);
|
|
45492
|
-
const fileHash = await getFileHash(outputArchivePath);
|
|
45493
45613
|
const manifestHash = await getFileHash(manifestPath);
|
|
45494
|
-
const nextFileHash = signingSession ? `${SIGNED_HASH_PREFIX}${await signingSession.signFileHash(fileHash)}` : fileHash;
|
|
45495
45614
|
const nextManifestFileHash = signingSession ? `${SIGNED_HASH_PREFIX}${await signingSession.signFileHash(manifestHash)}` : manifestHash;
|
|
45496
|
-
const archiveUpload = await putStorageFile(
|
|
45615
|
+
const archiveUpload = await putStorageFile(storageAdapter, createBundleStorageKey(nextBundleId), archivePath);
|
|
45497
45616
|
uploadedStorageUris.push(archiveUpload.storageUri);
|
|
45498
|
-
const
|
|
45617
|
+
const manifestUpload = await putStorageFile(storageAdapter, createBundleStorageKey(nextBundleId), manifestPath);
|
|
45618
|
+
uploadedStorageUris.push(manifestUpload.storageUri);
|
|
45619
|
+
const assetBaseStorageUri = createStorageRootUriWithPath(manifestUpload.storageUri, nextBundleId, "assets");
|
|
45499
45620
|
for (const assetUploadTarget of assetUploadTargets) {
|
|
45500
45621
|
const storageUri = resolveManifestAssetStorageUri({
|
|
45501
45622
|
assetBaseStorageUri,
|
|
@@ -45503,18 +45624,13 @@ async function createCopiedBundleArchive({ bundle, config, nextBundleId, storage
|
|
|
45503
45624
|
downloadFileHash: assetUploadTarget.downloadFileHash,
|
|
45504
45625
|
fileHash: assetUploadTarget.fileHash
|
|
45505
45626
|
});
|
|
45506
|
-
const { exists } = await
|
|
45507
|
-
if (!exists) await putStorageFile(
|
|
45627
|
+
const { exists } = await storageAdapter.exists({ storageUri });
|
|
45628
|
+
if (!exists) await putStorageFile(storageAdapter, getRelativeStorageDir(assetUploadTarget.storagePath) ? `assets/${getRelativeStorageDir(assetUploadTarget.storagePath)}` : "assets", assetUploadTarget.uploadSourcePath);
|
|
45508
45629
|
}
|
|
45509
|
-
const manifestUpload = await putStorageFile(storagePlugin, createBundleStorageKey(nextBundleId), manifestPath);
|
|
45510
|
-
uploadedStorageUris.push(manifestUpload.storageUri);
|
|
45511
45630
|
return {
|
|
45512
45631
|
bundle: {
|
|
45513
45632
|
...bundle,
|
|
45514
45633
|
id: nextBundleId,
|
|
45515
|
-
archiveByteSize: archiveUpload.byteSize,
|
|
45516
|
-
storageUri: archiveUpload.storageUri,
|
|
45517
|
-
fileHash: nextFileHash,
|
|
45518
45634
|
metadata: stripBundleArtifactMetadata(bundle.metadata),
|
|
45519
45635
|
assetBaseStorageUri,
|
|
45520
45636
|
patches: [],
|
|
@@ -45524,7 +45640,7 @@ async function createCopiedBundleArchive({ bundle, config, nextBundleId, storage
|
|
|
45524
45640
|
uploadedStorageUris
|
|
45525
45641
|
};
|
|
45526
45642
|
} catch (error) {
|
|
45527
|
-
await deleteUploadedCopy(
|
|
45643
|
+
await deleteUploadedCopy(storageAdapter, uploadedStorageUris);
|
|
45528
45644
|
throw error;
|
|
45529
45645
|
} finally {
|
|
45530
45646
|
await fs$2.rm(workDir, {
|
|
@@ -45533,12 +45649,12 @@ async function createCopiedBundleArchive({ bundle, config, nextBundleId, storage
|
|
|
45533
45649
|
});
|
|
45534
45650
|
}
|
|
45535
45651
|
}
|
|
45536
|
-
async function deleteUploadedCopy(
|
|
45652
|
+
async function deleteUploadedCopy(storageAdapter, storageUris) {
|
|
45537
45653
|
if (storageUris.length === 0) return;
|
|
45538
45654
|
for (const storageUri of new Set(storageUris)) try {
|
|
45539
45655
|
const protocol = new URL(storageUri).protocol.replace(":", "");
|
|
45540
|
-
if (
|
|
45541
|
-
else if (protocol !== "http" && protocol !== "https") throw new Error(`No storage
|
|
45656
|
+
if (storageAdapter.protocol === protocol) await storageAdapter.delete({ storageUri });
|
|
45657
|
+
else if (protocol !== "http" && protocol !== "https") throw new Error(`No storage adapter for protocol: ${protocol}`);
|
|
45542
45658
|
} catch (error) {
|
|
45543
45659
|
console.error("Failed to delete uploaded bundle copy:", error);
|
|
45544
45660
|
}
|
|
@@ -45611,4 +45727,4 @@ function transformTemplate(templateString, values) {
|
|
|
45611
45727
|
return result;
|
|
45612
45728
|
}
|
|
45613
45729
|
//#endregion
|
|
45614
|
-
export { BuildLogger, ConfigBuilder, HOT_UPDATER_SERVER_PACKAGE_VERSION_ENV, HotUpdateDirUtil, InitEnvFileError, InitError, LEGACY_BUNDLE_ERROR, LegacyInfrastructureError, MissingInitInputsError, assertInfrastructureGenerationAtUrl, assertInfrastructureGenerationPayload, assertInitInputs, assertInitProviderInputs, banner, typedColors as colors, confirmInitInputPersistence, copyDirToTmp,
|
|
45730
|
+
export { BuildLogger, ConfigBuilder, HOT_UPDATER_SERVER_PACKAGE_VERSION_ENV, HotUpdateDirUtil, InitEnvFileError, InitError, LEGACY_BUNDLE_ERROR, LegacyInfrastructureError, MissingInitInputsError, ServerDefinitionError, assembleServer, assertInfrastructureGenerationAtUrl, assertInfrastructureGenerationPayload, assertInitInputs, assertInitProviderInputs, banner, clientAuthOf, typedColors as colors, confirmInitInputPersistence, copyDirToTmp, createCopiedBundleArtifacts, createHotUpdaterConfigScaffold, createHotUpdaterConfigScaffoldFromBuilder, createLogWriter, createMigrator, createTarBrTargetFiles, createZip, decryptJson, defineInitProvider, encryptJson, ensureInstallPackages, generateClientCredential, generateSchema, generatesSchema, getAndroidSdkPath, getBundleSigningPublicKey, getCwd, getHotUpdaterEnvValue, getHotUpdaterInitInputEnv, getInitProviderEnvVars, getInitProviderTextPromptValues, getMissingInitInputs, getMissingInitProviderInputs, getPackageManager, getReactNativeMetadatas, getStorageFileByteSize, isServerDefinition, link, loadConfig, loadPlatformConfigs, log, makeEnv, moduleSpecifiersOf, p, prepareBundleSigning, printAppSetup, printBanner, provisionClientCredential, putStorageFile, readBundleSigningPublicKeyFile, readHotUpdaterEnv, readHotUpdaterInitEnv, readPackageUp, renderAppImports, renderAppSetup, renderImportStatements, resolveHotUpdaterServerVersion, resolveInitProviderInput, resolveInitProviderInputs, resolvePackageVersion, serverDefinitionOf, shouldAutoSelectOnlyInitResource, stripAnsi, transformEnv, transformTemplate, writeHotUpdaterConfig, writeHotUpdaterFiles, writeStorageFile, writeStorageResponseFile };
|