@hot-updater/cli-tools 1.0.0-rc.17 → 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 +244 -61
- package/dist/index.mjs +515 -236
- package/package.json +5 -4
package/dist/index.mjs
CHANGED
|
@@ -16,6 +16,8 @@ import l__default from "node:readline";
|
|
|
16
16
|
import tty, { ReadStream } from "node:tty";
|
|
17
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
22
|
import crypto$1, { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
21
23
|
import fs$2 from "node:fs/promises";
|
|
@@ -32,10 +34,9 @@ import { ChildProcess, execFile, spawn, spawnSync } from "node:child_process";
|
|
|
32
34
|
import { scheduler, setImmediate as setImmediate$1, setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
33
35
|
import { serialize } from "node:v8";
|
|
34
36
|
import { Buffer as Buffer$2 } from "node:buffer";
|
|
35
|
-
import { parseSync } from "oxc-parser";
|
|
37
|
+
import { Visitor, parseSync } from "oxc-parser";
|
|
36
38
|
import { loadConfig as loadConfig$1 } from "unconfig";
|
|
37
|
-
import {
|
|
38
|
-
import { getManifestFileHash, stripBundleArtifactMetadata } from "@hot-updater/core";
|
|
39
|
+
import { getManifestFileHash, stripBundleArtifactMetadata } from "@hot-updater/protocol";
|
|
39
40
|
import { transformSync } from "oxc-transform";
|
|
40
41
|
//#endregion
|
|
41
42
|
//#region src/colors.ts
|
|
@@ -22906,6 +22907,23 @@ var BuildLogger = class {
|
|
|
22906
22907
|
};
|
|
22907
22908
|
//#endregion
|
|
22908
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
|
+
};
|
|
22909
22927
|
/**
|
|
22910
22928
|
* The App.tsx code init prints: `HotUpdater.init` with the server's URL,
|
|
22911
22929
|
* the header that carries the client credential when the server takes one,
|
|
@@ -22913,8 +22931,7 @@ var BuildLogger = class {
|
|
|
22913
22931
|
*/
|
|
22914
22932
|
const renderAppSetup = ({ baseURL, credential, clientPlugins = [] }) => [
|
|
22915
22933
|
"// Add this to your App.tsx",
|
|
22916
|
-
|
|
22917
|
-
...clientPlugins.map(({ module, name }) => `import { ${name} } from ${JSON.stringify(module)};`),
|
|
22934
|
+
...renderAppImports(clientPlugins),
|
|
22918
22935
|
"",
|
|
22919
22936
|
"function App() {",
|
|
22920
22937
|
" return null; // Replace with your app root.",
|
|
@@ -22947,6 +22964,42 @@ const printAppSetup = ({ baseURL, ...setup }) => {
|
|
|
22947
22964
|
}
|
|
22948
22965
|
};
|
|
22949
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
|
+
}
|
|
23002
|
+
//#endregion
|
|
22950
23003
|
//#region ../../node_modules/.pnpm/ansi-regex@6.2.2/node_modules/ansi-regex/index.js
|
|
22951
23004
|
function ansiRegex({ onlyFirst = false } = {}) {
|
|
22952
23005
|
return new RegExp(`(?:\\u001B\\][\\s\\S]*?(?:\\u0007|\\u001B\\u005C|\\u009C))|[\\u001B\\u009B][[\\]()#;?]*(?:\\d{1,4}(?:[;:]\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]`, onlyFirst ? void 0 : "g");
|
|
@@ -24876,7 +24929,7 @@ const loadPrivateKey = async (privateKeyPath) => {
|
|
|
24876
24929
|
throw new Error("Failed to load the local bundle signing private key.");
|
|
24877
24930
|
}
|
|
24878
24931
|
};
|
|
24879
|
-
const
|
|
24932
|
+
const createLocalSigningAdapter = ({ privateKeyPath }) => {
|
|
24880
24933
|
const privateKeys = /* @__PURE__ */ new Map();
|
|
24881
24934
|
const getPrivateKey = (cwd = process.cwd()) => {
|
|
24882
24935
|
const resolvedPath = resolvePath$1(cwd, privateKeyPath);
|
|
@@ -24905,7 +24958,7 @@ const createLocalSigningPlugin = ({ privateKeyPath }) => {
|
|
|
24905
24958
|
}
|
|
24906
24959
|
};
|
|
24907
24960
|
};
|
|
24908
|
-
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.");
|
|
24909
24962
|
const normalizeSigningConfig = (signing) => {
|
|
24910
24963
|
if (signing === void 0) return void 0;
|
|
24911
24964
|
if (typeof signing !== "object" || signing === null) throw invalidSigningConfig();
|
|
@@ -24916,7 +24969,7 @@ const normalizeSigningConfig = (signing) => {
|
|
|
24916
24969
|
"getPublicKey",
|
|
24917
24970
|
"sign"
|
|
24918
24971
|
].some((key) => Reflect.has(signing, key));
|
|
24919
|
-
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.");
|
|
24920
24973
|
if (!hasPluginMembers) {
|
|
24921
24974
|
if (Object.keys(signing).some((key) => key !== "enabled" && key !== "privateKeyPath")) throw new Error("Local bundle signing accepts only enabled and privateKeyPath.");
|
|
24922
24975
|
const enabled = Reflect.get(signing, "enabled");
|
|
@@ -25015,14 +25068,14 @@ const prepareBundleSigning = async (signing, options = {}) => {
|
|
|
25015
25068
|
const normalized = normalizeSigningConfig(signing);
|
|
25016
25069
|
if (!normalized) return null;
|
|
25017
25070
|
const cwd = options.cwd ?? getCwd();
|
|
25018
|
-
return preparePluginSigning("enabled" in normalized ?
|
|
25071
|
+
return preparePluginSigning("enabled" in normalized ? createLocalSigningAdapter(normalized) : normalized, cwd);
|
|
25019
25072
|
};
|
|
25020
25073
|
/** Resolves the public identity of the configured bundle signer. */
|
|
25021
25074
|
const getBundleSigningPublicKey = async (signing, options = {}) => {
|
|
25022
25075
|
const normalized = normalizeSigningConfig(signing);
|
|
25023
25076
|
if (!normalized) return null;
|
|
25024
25077
|
const cwd = options.cwd ?? getCwd();
|
|
25025
|
-
const provider = "enabled" in normalized ?
|
|
25078
|
+
const provider = "enabled" in normalized ? createLocalSigningAdapter(normalized) : normalized;
|
|
25026
25079
|
return exportPublicKey(await getProviderPublicKey(provider, cwd));
|
|
25027
25080
|
};
|
|
25028
25081
|
//#endregion
|
|
@@ -25074,47 +25127,28 @@ const renderImportStatements = (imports) => {
|
|
|
25074
25127
|
}
|
|
25075
25128
|
return importLines.join("\n");
|
|
25076
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
|
+
*/
|
|
25077
25134
|
var ConfigBuilder = class {
|
|
25078
25135
|
buildType = null;
|
|
25079
25136
|
storageInfo = null;
|
|
25080
25137
|
databaseInfo = null;
|
|
25138
|
+
pluginsInfo = null;
|
|
25081
25139
|
intermediateCode = "";
|
|
25082
|
-
|
|
25083
|
-
|
|
25084
|
-
|
|
25085
|
-
|
|
25086
|
-
|
|
25087
|
-
|
|
25088
|
-
|
|
25089
|
-
|
|
25090
|
-
named: ["existsSync"]
|
|
25091
|
-
});
|
|
25092
|
-
}
|
|
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. */
|
|
25093
25148
|
addImport(info) {
|
|
25094
|
-
|
|
25095
|
-
const existing = this.collectedImports.get(pkg);
|
|
25096
|
-
if (existing) {
|
|
25097
|
-
if (info.named) for (const n of info.named) existing.named.add(n);
|
|
25098
|
-
if (info.defaultOrNamespace && !existing.defaultOrNamespace) existing.defaultOrNamespace = info.defaultOrNamespace;
|
|
25099
|
-
if (info.sideEffect && !existing.sideEffect) existing.sideEffect = true;
|
|
25100
|
-
} else this.collectedImports.set(pkg, {
|
|
25101
|
-
named: new Set(info.named ?? []),
|
|
25102
|
-
defaultOrNamespace: info.defaultOrNamespace,
|
|
25103
|
-
sideEffect: info.sideEffect ?? false
|
|
25104
|
-
});
|
|
25149
|
+
this.imports.push(info);
|
|
25105
25150
|
return this;
|
|
25106
25151
|
}
|
|
25107
|
-
addImports(imports) {
|
|
25108
|
-
for (const imp of imports) this.addImport(imp);
|
|
25109
|
-
}
|
|
25110
|
-
getImportInfos() {
|
|
25111
|
-
return normalizeImportInfos(Array.from(this.collectedImports.entries()).map(([pkg, info]) => ({
|
|
25112
|
-
pkg,
|
|
25113
|
-
named: Array.from(info.named),
|
|
25114
|
-
defaultOrNamespace: info.defaultOrNamespace,
|
|
25115
|
-
sideEffect: info.sideEffect ?? false
|
|
25116
|
-
})));
|
|
25117
|
-
}
|
|
25118
25152
|
generateBuildConfigString() {
|
|
25119
25153
|
if (!this.buildType) throw new Error("Build type must be set using .setBuildType()");
|
|
25120
25154
|
switch (this.buildType) {
|
|
@@ -25124,33 +25158,34 @@ var ConfigBuilder = class {
|
|
|
25124
25158
|
default: throw new Error(`Invalid build type: ${this.buildType}`);
|
|
25125
25159
|
}
|
|
25126
25160
|
}
|
|
25161
|
+
/** Sets the build type ('bare', 'rock', or 'expo') and its import. */
|
|
25127
25162
|
setBuildType(buildType) {
|
|
25128
|
-
if (this.buildType) console.warn("Build type is being set multiple times. Overwriting previous value.");
|
|
25129
25163
|
this.buildType = buildType;
|
|
25130
|
-
this.
|
|
25164
|
+
this.imports.push({
|
|
25131
25165
|
pkg: `@hot-updater/${buildType}`,
|
|
25132
25166
|
named: [buildType]
|
|
25133
25167
|
});
|
|
25134
25168
|
return this;
|
|
25135
25169
|
}
|
|
25170
|
+
/** Sets the storage the CLI uploads to, which the server lists, and its imports. */
|
|
25136
25171
|
setStorage(storageConfig) {
|
|
25137
25172
|
this.storageInfo = storageConfig;
|
|
25138
|
-
this.
|
|
25139
|
-
if (storageConfig.imports.some((imp) => imp.pkg.includes("firebase"))) this.addImport({
|
|
25140
|
-
pkg: "firebase-admin/app",
|
|
25141
|
-
named: ["applicationDefault"]
|
|
25142
|
-
});
|
|
25173
|
+
this.imports.push(...storageConfig.imports);
|
|
25143
25174
|
return this;
|
|
25144
25175
|
}
|
|
25176
|
+
/** Sets the server's database and its imports. */
|
|
25145
25177
|
setDatabase(databaseConfig) {
|
|
25146
25178
|
this.databaseInfo = databaseConfig;
|
|
25147
|
-
this.
|
|
25148
|
-
|
|
25149
|
-
|
|
25150
|
-
|
|
25151
|
-
|
|
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);
|
|
25152
25186
|
return this;
|
|
25153
25187
|
}
|
|
25188
|
+
/** Sets the code between the environment loading and the config, such as a credentials helper. */
|
|
25154
25189
|
setIntermediateCode(code) {
|
|
25155
25190
|
this.intermediateCode = code.trim();
|
|
25156
25191
|
return this;
|
|
@@ -25159,21 +25194,22 @@ var ConfigBuilder = class {
|
|
|
25159
25194
|
if (!this.buildType) throw new Error("Build type must be set using .setBuildType()");
|
|
25160
25195
|
if (!this.storageInfo) throw new Error("Storage config must be set using .setStorage()");
|
|
25161
25196
|
if (!this.databaseInfo) throw new Error("Database config must be set using .setDatabase()");
|
|
25162
|
-
|
|
25163
|
-
const
|
|
25197
|
+
if (!this.pluginsInfo) throw new Error("Plugins config must be set using .setPlugins()");
|
|
25198
|
+
const imports = normalizeImportInfos(this.imports);
|
|
25164
25199
|
const buildConfigString = this.generateBuildConfigString();
|
|
25200
|
+
const plugins = this.pluginsInfo.configString;
|
|
25165
25201
|
const text = `
|
|
25166
|
-
${
|
|
25202
|
+
${renderImportStatements(imports)}
|
|
25167
25203
|
|
|
25168
25204
|
if (existsSync(".env.hotupdater")) {
|
|
25169
25205
|
process.loadEnvFile(".env.hotupdater");
|
|
25170
25206
|
}
|
|
25171
25207
|
|
|
25172
|
-
${this.intermediateCode ? `${this.intermediateCode}\n` : ""}
|
|
25173
|
-
export default defineConfig({
|
|
25208
|
+
${this.intermediateCode ? `${this.intermediateCode}\n\n` : ""}export default defineConfig({
|
|
25174
25209
|
build: ${buildConfigString},
|
|
25175
25210
|
storage: ${this.storageInfo.configString},
|
|
25176
25211
|
database: ${this.databaseInfo.configString},
|
|
25212
|
+
${plugins === "plugins" ? "plugins" : `plugins: ${plugins}`},
|
|
25177
25213
|
updateStrategy: "appVersion", // or "fingerprint"
|
|
25178
25214
|
});
|
|
25179
25215
|
`.trim();
|
|
@@ -25182,10 +25218,12 @@ export default defineConfig({
|
|
|
25182
25218
|
buildConfigString,
|
|
25183
25219
|
storageConfigString: this.storageInfo.configString,
|
|
25184
25220
|
databaseConfigString: this.databaseInfo.configString,
|
|
25221
|
+
pluginsConfigString: plugins,
|
|
25185
25222
|
intermediateCode: this.intermediateCode,
|
|
25186
25223
|
text
|
|
25187
25224
|
};
|
|
25188
25225
|
}
|
|
25226
|
+
/** hot-updater.config.ts's text. */
|
|
25189
25227
|
getResult() {
|
|
25190
25228
|
return this.getScaffold().text;
|
|
25191
25229
|
}
|
|
@@ -37636,13 +37674,13 @@ const createZip = async ({ outfile, targetDir, excludeExts = [] }) => {
|
|
|
37636
37674
|
};
|
|
37637
37675
|
//#endregion
|
|
37638
37676
|
//#region src/isObject.ts
|
|
37639
|
-
const isObject$
|
|
37677
|
+
const isObject$2 = (value) => {
|
|
37640
37678
|
return !!value && value.constructor === Object;
|
|
37641
37679
|
};
|
|
37642
37680
|
//#endregion
|
|
37643
37681
|
//#region src/crypto.ts
|
|
37644
37682
|
const encryptJson = (jsonData, secretKey) => {
|
|
37645
|
-
if (isObject$
|
|
37683
|
+
if (isObject$2(jsonData) === false) throw new Error("jsonData must be an object");
|
|
37646
37684
|
const iv = crypto.randomBytes(16);
|
|
37647
37685
|
const cipher = crypto.createCipheriv("aes-256-cbc", Buffer.from(secretKey, "hex"), iv);
|
|
37648
37686
|
let encrypted = cipher.update(JSON.stringify(jsonData));
|
|
@@ -40845,9 +40883,9 @@ const isReadableStream = (value) => Object.prototype.toString.call(value) === "[
|
|
|
40845
40883
|
const isWritableStream = (value) => Object.prototype.toString.call(value) === "[object WritableStream]";
|
|
40846
40884
|
const isWebStream = (value) => isReadableStream(value) || isWritableStream(value);
|
|
40847
40885
|
const isTransformStream = (value) => isReadableStream(value?.readable) && isWritableStream(value?.writable);
|
|
40848
|
-
const isAsyncIterableObject = (value) => isObject(value) && typeof value[Symbol.asyncIterator] === "function";
|
|
40849
|
-
const isIterableObject = (value) => isObject(value) && typeof value[Symbol.iterator] === "function";
|
|
40850
|
-
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;
|
|
40851
40889
|
const TRANSFORM_TYPES = /* @__PURE__ */ new Set([
|
|
40852
40890
|
"generator",
|
|
40853
40891
|
"asyncGenerator",
|
|
@@ -44101,6 +44139,9 @@ const KNOWN_BUILD_CALLEES = /* @__PURE__ */ new Set([
|
|
|
44101
44139
|
"expo",
|
|
44102
44140
|
"rock"
|
|
44103
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");
|
|
44104
44145
|
const parseConfigSource = (text) => {
|
|
44105
44146
|
const result = parseSync(CONFIG_FILE_NAME, text, {
|
|
44106
44147
|
astType: "js",
|
|
@@ -44164,13 +44205,6 @@ const getObjectPropertyName = (property) => {
|
|
|
44164
44205
|
return null;
|
|
44165
44206
|
};
|
|
44166
44207
|
const isDataProperty = (property) => property.type === "Property" && property.kind === "init" && !property.method && !property.shorthand;
|
|
44167
|
-
const hasTrailingComma = (text) => {
|
|
44168
|
-
const closeBraceIndex = text.lastIndexOf("}");
|
|
44169
|
-
if (closeBraceIndex === -1) return false;
|
|
44170
|
-
let index = closeBraceIndex - 1;
|
|
44171
|
-
while (index >= 0 && /\s/.test(text[index] ?? "")) index -= 1;
|
|
44172
|
-
return (text[index] ?? "") === ",";
|
|
44173
|
-
};
|
|
44174
44208
|
const dedentBlock = (text) => {
|
|
44175
44209
|
const lines = text.replace(/\s+$/, "").split("\n");
|
|
44176
44210
|
const indents = lines.filter((line) => line.trim() !== "").map((line) => line.match(/^\s*/)?.[0].length ?? 0);
|
|
@@ -44178,16 +44212,55 @@ const dedentBlock = (text) => {
|
|
|
44178
44212
|
return lines.map((line) => line.slice(minIndent)).join("\n");
|
|
44179
44213
|
};
|
|
44180
44214
|
const indentBlock = (text, indent) => dedentBlock(text).split("\n").map((line) => `${indent}${line}`).join("\n");
|
|
44181
|
-
const
|
|
44182
|
-
|
|
44183
|
-
|
|
44184
|
-
|
|
44185
|
-
|
|
44186
|
-
|
|
44187
|
-
|
|
44188
|
-
|
|
44189
|
-
|
|
44190
|
-
|
|
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
|
+
}];
|
|
44191
44264
|
};
|
|
44192
44265
|
const mergeObjectLiteralText = (existingObject, newObject, replaceIncompatibleProperties = []) => {
|
|
44193
44266
|
const existingText = getNodeText(existingObject.source, existingObject.objectExpression);
|
|
@@ -44231,14 +44304,12 @@ const mergeObjectLiteralText = (existingObject, newObject, replaceIncompatiblePr
|
|
|
44231
44304
|
});
|
|
44232
44305
|
}
|
|
44233
44306
|
}
|
|
44234
|
-
let mergedText = existingText;
|
|
44235
|
-
for (const edit of edits.sort((left, right) => right.start - left.start)) mergedText = mergedText.slice(0, edit.start) + edit.text + mergedText.slice(edit.end);
|
|
44236
44307
|
const missingPropertyTexts = newObject.objectExpression.properties.filter((property) => {
|
|
44237
44308
|
if (property.type === "SpreadElement") return !existingSpreadTexts.has(getNodeText(newObject.source, property.argument).trim());
|
|
44238
44309
|
const propertyName = getObjectPropertyName(property);
|
|
44239
44310
|
return propertyName ? !existingPropertyNames.has(propertyName) : false;
|
|
44240
|
-
}).map((property) =>
|
|
44241
|
-
return
|
|
44311
|
+
}).map((property) => getPropertyBlockText(newObject.source, property));
|
|
44312
|
+
return applyTextEdits(existingText, [...edits, ...appendPropertiesEdits(existingObject.source, existingObject.objectExpression, missingPropertyTexts)]);
|
|
44242
44313
|
};
|
|
44243
44314
|
const buildMergedCallInitializer = (existing, next) => {
|
|
44244
44315
|
const [existingArgument] = existing.callExpression.arguments;
|
|
@@ -44260,6 +44331,11 @@ const findManagedProperty = (objectExpression, propertyName) => {
|
|
|
44260
44331
|
const property = objectExpression.properties.find((candidate) => candidate.type === "Property" && candidate.kind === "init" && !candidate.method && !candidate.shorthand && getObjectPropertyName(candidate) === propertyName);
|
|
44261
44332
|
return property?.type === "Property" ? property : null;
|
|
44262
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
|
+
};
|
|
44263
44339
|
const getCallCallee = (expression) => {
|
|
44264
44340
|
if (expression.type !== "CallExpression" || expression.callee.type !== "Identifier") return null;
|
|
44265
44341
|
return expression.callee.name;
|
|
@@ -44283,11 +44359,20 @@ const mergeHelperStatement = (existingStatementText, helper) => {
|
|
|
44283
44359
|
if (!mergedInitializer) return null;
|
|
44284
44360
|
return `${existingStatement.statement.kind === "let" || existingStatement.statement.kind === "var" ? existingStatement.statement.kind : "const"} ${helper.name} = ${mergedInitializer};`;
|
|
44285
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
|
+
*/
|
|
44286
44370
|
const updateManagedObject = (existing, next) => {
|
|
44287
44371
|
const objectStart = existing.objectExpression.start;
|
|
44288
44372
|
const objectText = getNodeText(existing.source, existing.objectExpression);
|
|
44289
44373
|
const propertyEdits = [];
|
|
44290
44374
|
const missingPropertyTexts = [];
|
|
44375
|
+
let keptBuild = false;
|
|
44291
44376
|
for (const propertyName of [
|
|
44292
44377
|
"build",
|
|
44293
44378
|
"storage",
|
|
@@ -44297,7 +44382,7 @@ const updateManagedObject = (existing, next) => {
|
|
|
44297
44382
|
const nextProperty = findManagedProperty(next.objectExpression, propertyName);
|
|
44298
44383
|
if (!nextProperty) continue;
|
|
44299
44384
|
if (!existingProperty) {
|
|
44300
|
-
missingPropertyTexts.push(
|
|
44385
|
+
missingPropertyTexts.push(getPropertyBlockText(next.source, nextProperty));
|
|
44301
44386
|
continue;
|
|
44302
44387
|
}
|
|
44303
44388
|
const existingCallee = getCallCallee(existingProperty.value);
|
|
@@ -44305,8 +44390,11 @@ const updateManagedObject = (existing, next) => {
|
|
|
44305
44390
|
if (!existingCallee || !nextCallee) return null;
|
|
44306
44391
|
let nextInitializerText = getNodeText(next.source, nextProperty.value);
|
|
44307
44392
|
if (propertyName === "build") {
|
|
44393
|
+
if (!KNOWN_BUILD_CALLEES.has(existingCallee)) {
|
|
44394
|
+
keptBuild = true;
|
|
44395
|
+
continue;
|
|
44396
|
+
}
|
|
44308
44397
|
if (existingCallee === nextCallee) continue;
|
|
44309
|
-
if (!KNOWN_BUILD_CALLEES.has(existingCallee)) return null;
|
|
44310
44398
|
} else if (existingCallee === nextCallee) {
|
|
44311
44399
|
if (existingProperty.value.type !== "CallExpression" || nextProperty.value.type !== "CallExpression") return null;
|
|
44312
44400
|
const mergedInitializer = buildMergedCallInitializer({
|
|
@@ -44325,9 +44413,21 @@ const updateManagedObject = (existing, next) => {
|
|
|
44325
44413
|
text: nextInitializerText
|
|
44326
44414
|
});
|
|
44327
44415
|
}
|
|
44328
|
-
|
|
44329
|
-
|
|
44330
|
-
|
|
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
|
+
};
|
|
44331
44431
|
};
|
|
44332
44432
|
const getManagedHelperName = (statement) => {
|
|
44333
44433
|
if (statement.type !== "VariableDeclaration") return null;
|
|
@@ -44335,12 +44435,58 @@ const getManagedHelperName = (statement) => {
|
|
|
44335
44435
|
if (declaration?.id.type !== "Identifier") return null;
|
|
44336
44436
|
return declaration.id.name;
|
|
44337
44437
|
};
|
|
44338
|
-
|
|
44339
|
-
|
|
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" ? {
|
|
44340
44486
|
...info,
|
|
44341
44487
|
named: info.named?.filter((name) => name !== "existsSync")
|
|
44342
44488
|
} : info);
|
|
44343
|
-
const importDeclarations = source
|
|
44489
|
+
const importDeclarations = importDeclarationsOf(source);
|
|
44344
44490
|
const firstImport = importDeclarations[0];
|
|
44345
44491
|
const lastImport = importDeclarations.at(-1);
|
|
44346
44492
|
if (!firstImport || !lastImport) return {
|
|
@@ -44348,9 +44494,15 @@ const rebuildImportBlock = (source, scaffold) => {
|
|
|
44348
44494
|
end: 0,
|
|
44349
44495
|
text: `${renderImportStatements(imports)}\n\n`
|
|
44350
44496
|
};
|
|
44351
|
-
const
|
|
44352
|
-
const
|
|
44353
|
-
const
|
|
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");
|
|
44354
44506
|
return {
|
|
44355
44507
|
start: getTopLevelFullStart(source, firstImport),
|
|
44356
44508
|
end: lastImport.end,
|
|
@@ -44396,27 +44548,49 @@ const mergeHotUpdaterConfigText = (existingText, scaffold) => {
|
|
|
44396
44548
|
const existingConfig = findDefineConfigObject(existingSource);
|
|
44397
44549
|
const nextConfig = findDefineConfigObject(nextSource);
|
|
44398
44550
|
if (!existingConfig || !nextConfig) return { reason: "Existing config is not a supported `export default defineConfig({ ... })` shape." };
|
|
44399
|
-
const
|
|
44551
|
+
const nextObject = updateManagedObject({
|
|
44400
44552
|
objectExpression: existingConfig.objectExpression,
|
|
44401
44553
|
source: existingSource
|
|
44402
44554
|
}, {
|
|
44403
44555
|
objectExpression: nextConfig.objectExpression,
|
|
44404
44556
|
source: nextSource
|
|
44405
44557
|
});
|
|
44406
|
-
if (!
|
|
44558
|
+
if (!nextObject) return { reason: "Existing config uses dynamic build/storage/database expressions that cannot be merged safely." };
|
|
44407
44559
|
const exportFullStart = getTopLevelFullStart(existingSource, existingConfig.exportDeclaration);
|
|
44408
44560
|
const firstTriviaContent = existingText.slice(exportFullStart, existingConfig.exportDeclaration.start).search(/\S/);
|
|
44409
44561
|
const managedBodyEnd = firstTriviaContent === -1 ? existingConfig.exportDeclaration.start : exportFullStart + firstTriviaContent;
|
|
44410
44562
|
const bodyEdit = rebuildManagedBody(existingSource, managedBodyEnd, scaffold);
|
|
44411
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
|
+
]);
|
|
44412
44587
|
return { text: applyTextEdits(existingText, [
|
|
44413
|
-
|
|
44414
|
-
start: existingConfig.objectExpression.start,
|
|
44415
|
-
end: existingConfig.objectExpression.end,
|
|
44416
|
-
text: nextObjectText
|
|
44417
|
-
},
|
|
44588
|
+
objectEdit,
|
|
44418
44589
|
bodyEdit,
|
|
44419
|
-
rebuildImportBlock(existingSource, scaffold
|
|
44590
|
+
rebuildImportBlock(existingSource, scaffold, {
|
|
44591
|
+
keptBuild: nextObject.keptBuild,
|
|
44592
|
+
usedText
|
|
44593
|
+
})
|
|
44420
44594
|
]) };
|
|
44421
44595
|
};
|
|
44422
44596
|
const extractCallIdentifier = (initializer) => {
|
|
@@ -44424,9 +44598,9 @@ const extractCallIdentifier = (initializer) => {
|
|
|
44424
44598
|
if (!match) throw new Error(`Failed to extract call identifier from "${initializer}"`);
|
|
44425
44599
|
return match[1];
|
|
44426
44600
|
};
|
|
44427
|
-
const createHotUpdaterConfigScaffold = ({ build, storage, database, extraImports = [], helperStatements = [], updateStrategy = "appVersion" }) => {
|
|
44601
|
+
const createHotUpdaterConfigScaffold = ({ build, storage, database, plugins, extraImports = [], helperStatements = [], updateStrategy = "appVersion" }) => {
|
|
44428
44602
|
const intermediateCode = helperStatements.map((statement) => statement.code.trim()).filter(Boolean).join("\n\n");
|
|
44429
|
-
const builder = new ConfigBuilder().setBuildType(build).setStorage(storage).setDatabase(database);
|
|
44603
|
+
const builder = new ConfigBuilder().setBuildType(build).setStorage(storage).setDatabase(database).setPlugins(plugins);
|
|
44430
44604
|
for (const extraImport of extraImports) builder.addImport(extraImport);
|
|
44431
44605
|
if (intermediateCode) builder.setIntermediateCode(intermediateCode);
|
|
44432
44606
|
return createHotUpdaterConfigScaffoldFromBuilder(builder, {
|
|
@@ -44451,15 +44625,22 @@ const createHotUpdaterConfigScaffoldFromBuilder = (builder, { helperStatements =
|
|
|
44451
44625
|
initializer: scaffold.databaseConfigString,
|
|
44452
44626
|
callee: extractCallIdentifier(scaffold.databaseConfigString)
|
|
44453
44627
|
},
|
|
44628
|
+
plugins: { initializer: scaffold.pluginsConfigString },
|
|
44454
44629
|
helperStatements,
|
|
44455
44630
|
updateStrategy: `"${updateStrategy}"`
|
|
44456
44631
|
};
|
|
44457
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
|
+
*/
|
|
44458
44642
|
const writeHotUpdaterConfig = async (scaffold, filePath = HOT_UPDATER_CONFIG_PATH) => {
|
|
44459
|
-
const existingText = await
|
|
44460
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT") return null;
|
|
44461
|
-
throw error;
|
|
44462
|
-
});
|
|
44643
|
+
const existingText = await readTextFile(filePath);
|
|
44463
44644
|
if (existingText === null) {
|
|
44464
44645
|
await fs$1.writeFile(filePath, `${scaffold.text}\n`, "utf-8");
|
|
44465
44646
|
return {
|
|
@@ -44479,6 +44660,63 @@ const writeHotUpdaterConfig = async (scaffold, filePath = HOT_UPDATER_CONFIG_PAT
|
|
|
44479
44660
|
path: filePath
|
|
44480
44661
|
};
|
|
44481
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
|
+
};
|
|
44482
44720
|
//#endregion
|
|
44483
44721
|
//#region src/initOptions.ts
|
|
44484
44722
|
var InitError = class extends Error {
|
|
@@ -44576,78 +44814,6 @@ const getHotUpdaterEnvValue = (env, key) => {
|
|
|
44576
44814
|
return env[key]?.trim() || void 0;
|
|
44577
44815
|
};
|
|
44578
44816
|
//#endregion
|
|
44579
|
-
//#region src/hotUpdaterPlugins.ts
|
|
44580
|
-
/** Where `hot-updater init` writes a managed server's plugin list. */
|
|
44581
|
-
const HOT_UPDATER_PLUGINS_PATH = "hotUpdater.plugins.ts";
|
|
44582
|
-
/**
|
|
44583
|
-
* The plugins file for a managed provider: a re-export of the fixed list its
|
|
44584
|
-
* package ships, which its prebuilt server runs too.
|
|
44585
|
-
*/
|
|
44586
|
-
const renderHotUpdaterPlugins = (pkg) => [
|
|
44587
|
-
"// Generated by `hot-updater init`. The managed server runs these plugins,",
|
|
44588
|
-
`// and ${pkg} fixes the list, so the server, CLI, and console agree.`,
|
|
44589
|
-
`export { plugins } from "${pkg}";`,
|
|
44590
|
-
""
|
|
44591
|
-
].join("\n");
|
|
44592
|
-
/** Writes the plugins file; an existing file that differs is kept. */
|
|
44593
|
-
const writeHotUpdaterPlugins = async (pkg, filePath = HOT_UPDATER_PLUGINS_PATH) => {
|
|
44594
|
-
const text = renderHotUpdaterPlugins(pkg);
|
|
44595
|
-
const existingText = await fs$1.readFile(filePath, "utf-8").catch((error) => {
|
|
44596
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT") return null;
|
|
44597
|
-
throw error;
|
|
44598
|
-
});
|
|
44599
|
-
if (existingText === null) {
|
|
44600
|
-
await fs$1.writeFile(filePath, text, "utf-8");
|
|
44601
|
-
return {
|
|
44602
|
-
status: "created",
|
|
44603
|
-
path: filePath
|
|
44604
|
-
};
|
|
44605
|
-
}
|
|
44606
|
-
if (existingText === text) return {
|
|
44607
|
-
status: "unchanged",
|
|
44608
|
-
path: filePath
|
|
44609
|
-
};
|
|
44610
|
-
return {
|
|
44611
|
-
status: "skipped",
|
|
44612
|
-
path: filePath,
|
|
44613
|
-
reason: `it does not re-export the plugins ${pkg} runs; replace it with \`export { plugins } from "${pkg}";\``
|
|
44614
|
-
};
|
|
44615
|
-
};
|
|
44616
|
-
/** Writes the plugins file for `pkg` and reports what happened. */
|
|
44617
|
-
const generateHotUpdaterPlugins = async (pkg, filePath = HOT_UPDATER_PLUGINS_PATH) => {
|
|
44618
|
-
const result = await writeHotUpdaterPlugins(pkg, filePath);
|
|
44619
|
-
if (result.status === "created") p.log.success(`Generated '${filePath}' with the plugins the server runs.`);
|
|
44620
|
-
else if (result.status === "skipped") p.log.warn(`Kept existing '${filePath}' unchanged: ${result.reason}`);
|
|
44621
|
-
return result;
|
|
44622
|
-
};
|
|
44623
|
-
/**
|
|
44624
|
-
* The plugins a project's `hotUpdater.plugins.ts` exports, which its server
|
|
44625
|
-
* runs; undefined when the project has no such file. The console and the CLI
|
|
44626
|
-
* assemble them in process as the server does, for the features and commands
|
|
44627
|
-
* the plugins add.
|
|
44628
|
-
*/
|
|
44629
|
-
const loadHotUpdaterPlugins = async (cwd = getCwd()) => {
|
|
44630
|
-
const { config, sources } = await loadConfig$1({
|
|
44631
|
-
cwd,
|
|
44632
|
-
stopAt: path.dirname(cwd),
|
|
44633
|
-
merge: false,
|
|
44634
|
-
sources: [{
|
|
44635
|
-
files: "hotUpdater.plugins",
|
|
44636
|
-
extensions: [
|
|
44637
|
-
"ts",
|
|
44638
|
-
"mts",
|
|
44639
|
-
"cts",
|
|
44640
|
-
"js",
|
|
44641
|
-
"mjs",
|
|
44642
|
-
"cjs"
|
|
44643
|
-
]
|
|
44644
|
-
}]
|
|
44645
|
-
});
|
|
44646
|
-
if (sources.length === 0) return void 0;
|
|
44647
|
-
if (!Array.isArray(config?.plugins)) throw new Error(`${path.basename(sources[0])} must export plugins, an array of plugins.`);
|
|
44648
|
-
return config.plugins;
|
|
44649
|
-
};
|
|
44650
|
-
//#endregion
|
|
44651
44817
|
//#region src/infrastructureGeneration.ts
|
|
44652
44818
|
const assertInfrastructureGenerationPayload = ({ payload, provider, resource }) => {
|
|
44653
44819
|
if (typeof payload !== "object" || payload === null || !("infrastructureGeneration" in payload) || payload.infrastructureGeneration !== 1) throw new LegacyInfrastructureError(provider, resource);
|
|
@@ -44843,31 +45009,6 @@ function isMergeableValue(value) {
|
|
|
44843
45009
|
//#endregion
|
|
44844
45010
|
//#region src/loadConfig.ts
|
|
44845
45011
|
var import_out = /* @__PURE__ */ __toESM(require_out(), 1);
|
|
44846
|
-
const missingDatabaseError = async () => {
|
|
44847
|
-
throw new Error("database is required");
|
|
44848
|
-
};
|
|
44849
|
-
/** The default until the config names a database: every read and write refuses. */
|
|
44850
|
-
const missingDatabase = {
|
|
44851
|
-
name: "missingDatabase",
|
|
44852
|
-
adapter: {
|
|
44853
|
-
id: "missing",
|
|
44854
|
-
fits: () => true,
|
|
44855
|
-
get: missingDatabaseError,
|
|
44856
|
-
query: missingDatabaseError,
|
|
44857
|
-
write: missingDatabaseError
|
|
44858
|
-
}
|
|
44859
|
-
};
|
|
44860
|
-
const missingStorageError = async () => {
|
|
44861
|
-
throw new Error("storage plugin is required");
|
|
44862
|
-
};
|
|
44863
|
-
const missingStorage = createStoragePlugin({
|
|
44864
|
-
name: "missingStorage",
|
|
44865
|
-
protocol: "missing",
|
|
44866
|
-
put: missingStorageError,
|
|
44867
|
-
get: missingStorageError,
|
|
44868
|
-
exists: missingStorageError,
|
|
44869
|
-
delete: missingStorageError
|
|
44870
|
-
});
|
|
44871
45012
|
const getDefaultPlatformConfig = () => {
|
|
44872
45013
|
let infoPlistPaths = [];
|
|
44873
45014
|
try {
|
|
@@ -44916,25 +45057,26 @@ const getDefaultConfig = () => {
|
|
|
44916
45057
|
ios: {}
|
|
44917
45058
|
},
|
|
44918
45059
|
build: () => {
|
|
44919
|
-
throw new Error("build
|
|
45060
|
+
throw new Error("build adapter is required");
|
|
44920
45061
|
},
|
|
44921
|
-
|
|
44922
|
-
database: missingDatabase
|
|
45062
|
+
plugins: []
|
|
44923
45063
|
};
|
|
44924
45064
|
};
|
|
44925
45065
|
const mergeConfigSources = (...sources) => {
|
|
44926
45066
|
const mergedConfig = sources.reduceRight((mergedConfig, source) => merge(mergedConfig, source ?? {}), {});
|
|
44927
45067
|
const database = sources.find((source) => source?.database)?.database;
|
|
45068
|
+
const plugins = sources.find((source) => source?.plugins)?.plugins;
|
|
44928
45069
|
const signing = sources.find((source) => source?.signing)?.signing;
|
|
44929
45070
|
const storage = sources.find((source) => source?.storage)?.storage;
|
|
44930
45071
|
return {
|
|
44931
45072
|
...mergedConfig,
|
|
44932
45073
|
...database ? { database } : {},
|
|
45074
|
+
...plugins ? { plugins } : {},
|
|
44933
45075
|
...signing ? { signing } : {},
|
|
44934
45076
|
...storage ? { storage } : {}
|
|
44935
45077
|
};
|
|
44936
45078
|
};
|
|
44937
|
-
const getConfigLoaderOptions = (
|
|
45079
|
+
const getConfigLoaderOptions = () => {
|
|
44938
45080
|
const cwd = getCwd();
|
|
44939
45081
|
return {
|
|
44940
45082
|
cwd,
|
|
@@ -44949,15 +45091,36 @@ const getConfigLoaderOptions = (options) => {
|
|
|
44949
45091
|
"cts",
|
|
44950
45092
|
"mjs",
|
|
44951
45093
|
"mts"
|
|
44952
|
-
]
|
|
44953
|
-
rewrite: async (config) => {
|
|
44954
|
-
return typeof config === "function" ? config(options) : config;
|
|
44955
|
-
}
|
|
45094
|
+
]
|
|
44956
45095
|
}]
|
|
44957
45096
|
};
|
|
44958
45097
|
};
|
|
44959
|
-
|
|
44960
|
-
|
|
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) => {
|
|
44961
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.`);
|
|
44962
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.");
|
|
44963
45126
|
const mergedConfig = mergeConfigSources(config, getDefaultConfig());
|
|
@@ -44968,6 +45131,97 @@ const loadConfig = async (options) => {
|
|
|
44968
45131
|
};
|
|
44969
45132
|
};
|
|
44970
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
|
|
44971
45225
|
//#region src/log.ts
|
|
44972
45226
|
const log = {
|
|
44973
45227
|
normal: (message) => console.log(message),
|
|
@@ -45082,6 +45336,31 @@ const makeEnv = async (newEnvVars, filePath = ".env.hotupdater", options) => {
|
|
|
45082
45336
|
}
|
|
45083
45337
|
};
|
|
45084
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
|
|
45085
45364
|
//#region src/storageFiles.ts
|
|
45086
45365
|
const getStorageFileByteSize = async (filePath) => {
|
|
45087
45366
|
const { size } = await fs$2.stat(filePath, { bigint: true });
|
|
@@ -45231,24 +45510,24 @@ function resolveExtractedPath(rootDir, entryName) {
|
|
|
45231
45510
|
if (relativePath.startsWith("..") || path$1.isAbsolute(relativePath) || normalizedEntryName.startsWith("/")) throw new Error(`Invalid manifest asset path: ${entryName}`);
|
|
45232
45511
|
return entryPath;
|
|
45233
45512
|
}
|
|
45234
|
-
async function downloadStorageObject(storageUri,
|
|
45513
|
+
async function downloadStorageObject(storageUri, storageAdapter, outputPath) {
|
|
45235
45514
|
const protocol = new URL(storageUri).protocol.replace(":", "");
|
|
45236
|
-
if (
|
|
45237
|
-
await writeStorageFile(
|
|
45515
|
+
if (storageAdapter?.protocol === protocol) {
|
|
45516
|
+
await writeStorageFile(storageAdapter, storageUri, outputPath);
|
|
45238
45517
|
return;
|
|
45239
45518
|
}
|
|
45240
45519
|
if (protocol === "http" || protocol === "https") {
|
|
45241
45520
|
await downloadFromUrl(storageUri, outputPath);
|
|
45242
45521
|
return;
|
|
45243
45522
|
}
|
|
45244
|
-
throw new Error(`No storage
|
|
45523
|
+
throw new Error(`No storage adapter for protocol: ${protocol}`);
|
|
45245
45524
|
}
|
|
45246
45525
|
async function downloadFromUrl(fileUrl, filePath) {
|
|
45247
45526
|
const response = await fetch(fileUrl);
|
|
45248
45527
|
if (!response.ok) throw new Error(`Failed to download storage object: ${response.statusText}`);
|
|
45249
45528
|
await writeStorageResponseFile(response, filePath);
|
|
45250
45529
|
}
|
|
45251
|
-
async function downloadManifestAssets({ bundle, manifest, outputDir,
|
|
45530
|
+
async function downloadManifestAssets({ bundle, manifest, outputDir, storageAdapter, workDir }) {
|
|
45252
45531
|
await runWithConcurrency(Object.keys(manifest.assets ?? {}).sort((left, right) => left.localeCompare(right)), PROMOTE_ASSET_CONCURRENCY, async (assetPath) => {
|
|
45253
45532
|
const asset = manifest.assets?.[assetPath];
|
|
45254
45533
|
if (!asset?.fileHash) throw new Error(`Manifest file hash not found for ${assetPath}`);
|
|
@@ -45261,7 +45540,7 @@ async function downloadManifestAssets({ bundle, manifest, outputDir, storagePlug
|
|
|
45261
45540
|
});
|
|
45262
45541
|
const transferPath = resolveExtractedPath(workDir, `downloads/${downloadPath}`);
|
|
45263
45542
|
await fs$2.mkdir(path$1.dirname(transferPath), { recursive: true });
|
|
45264
|
-
await downloadStorageObject(storageUri,
|
|
45543
|
+
await downloadStorageObject(storageUri, storageAdapter, transferPath);
|
|
45265
45544
|
if (asset.downloadFileHash) {
|
|
45266
45545
|
if (await getFileHash(transferPath) !== asset.downloadFileHash.toLowerCase()) throw new Error(`Manifest download hash mismatch for ${assetPath}`);
|
|
45267
45546
|
}
|
|
@@ -45272,7 +45551,7 @@ async function downloadManifestAssets({ bundle, manifest, outputDir, storagePlug
|
|
|
45272
45551
|
if (await getFileHash(outputPath) !== asset.fileHash.toLowerCase()) throw new Error(`Manifest file hash mismatch for ${assetPath}`);
|
|
45273
45552
|
});
|
|
45274
45553
|
}
|
|
45275
|
-
async function createCopiedBundleArtifacts({ bundle, config, nextBundleId,
|
|
45554
|
+
async function createCopiedBundleArtifacts({ bundle, config, nextBundleId, storageAdapter }) {
|
|
45276
45555
|
const workDir = await fs$2.mkdtemp(path$1.join(os.tmpdir(), "hot-updater-console-promote-"));
|
|
45277
45556
|
const extractDir = path$1.join(workDir, "bundle");
|
|
45278
45557
|
const sourceManifestPath = path$1.join(workDir, "source-manifest.json");
|
|
@@ -45280,7 +45559,7 @@ async function createCopiedBundleArtifacts({ bundle, config, nextBundleId, stora
|
|
|
45280
45559
|
const uploadedStorageUris = [];
|
|
45281
45560
|
await fs$2.mkdir(extractDir, { recursive: true });
|
|
45282
45561
|
try {
|
|
45283
|
-
await downloadStorageObject(bundle.manifestStorageUri,
|
|
45562
|
+
await downloadStorageObject(bundle.manifestStorageUri, storageAdapter, sourceManifestPath);
|
|
45284
45563
|
const actualManifestHash = await getFileHash(sourceManifestPath);
|
|
45285
45564
|
const signingSession = await prepareBundleSigning(config.signing);
|
|
45286
45565
|
if (isSignedFileHash(bundle.manifestFileHash)) {
|
|
@@ -45297,7 +45576,7 @@ async function createCopiedBundleArtifacts({ bundle, config, nextBundleId, stora
|
|
|
45297
45576
|
bundle,
|
|
45298
45577
|
manifest,
|
|
45299
45578
|
outputDir: extractDir,
|
|
45300
|
-
|
|
45579
|
+
storageAdapter,
|
|
45301
45580
|
workDir
|
|
45302
45581
|
});
|
|
45303
45582
|
manifest.bundleId = nextBundleId;
|
|
@@ -45333,9 +45612,9 @@ async function createCopiedBundleArtifacts({ bundle, config, nextBundleId, stora
|
|
|
45333
45612
|
await fs$2.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
45334
45613
|
const manifestHash = await getFileHash(manifestPath);
|
|
45335
45614
|
const nextManifestFileHash = signingSession ? `${SIGNED_HASH_PREFIX}${await signingSession.signFileHash(manifestHash)}` : manifestHash;
|
|
45336
|
-
const archiveUpload = await putStorageFile(
|
|
45615
|
+
const archiveUpload = await putStorageFile(storageAdapter, createBundleStorageKey(nextBundleId), archivePath);
|
|
45337
45616
|
uploadedStorageUris.push(archiveUpload.storageUri);
|
|
45338
|
-
const manifestUpload = await putStorageFile(
|
|
45617
|
+
const manifestUpload = await putStorageFile(storageAdapter, createBundleStorageKey(nextBundleId), manifestPath);
|
|
45339
45618
|
uploadedStorageUris.push(manifestUpload.storageUri);
|
|
45340
45619
|
const assetBaseStorageUri = createStorageRootUriWithPath(manifestUpload.storageUri, nextBundleId, "assets");
|
|
45341
45620
|
for (const assetUploadTarget of assetUploadTargets) {
|
|
@@ -45345,8 +45624,8 @@ async function createCopiedBundleArtifacts({ bundle, config, nextBundleId, stora
|
|
|
45345
45624
|
downloadFileHash: assetUploadTarget.downloadFileHash,
|
|
45346
45625
|
fileHash: assetUploadTarget.fileHash
|
|
45347
45626
|
});
|
|
45348
|
-
const { exists } = await
|
|
45349
|
-
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);
|
|
45350
45629
|
}
|
|
45351
45630
|
return {
|
|
45352
45631
|
bundle: {
|
|
@@ -45361,7 +45640,7 @@ async function createCopiedBundleArtifacts({ bundle, config, nextBundleId, stora
|
|
|
45361
45640
|
uploadedStorageUris
|
|
45362
45641
|
};
|
|
45363
45642
|
} catch (error) {
|
|
45364
|
-
await deleteUploadedCopy(
|
|
45643
|
+
await deleteUploadedCopy(storageAdapter, uploadedStorageUris);
|
|
45365
45644
|
throw error;
|
|
45366
45645
|
} finally {
|
|
45367
45646
|
await fs$2.rm(workDir, {
|
|
@@ -45370,12 +45649,12 @@ async function createCopiedBundleArtifacts({ bundle, config, nextBundleId, stora
|
|
|
45370
45649
|
});
|
|
45371
45650
|
}
|
|
45372
45651
|
}
|
|
45373
|
-
async function deleteUploadedCopy(
|
|
45652
|
+
async function deleteUploadedCopy(storageAdapter, storageUris) {
|
|
45374
45653
|
if (storageUris.length === 0) return;
|
|
45375
45654
|
for (const storageUri of new Set(storageUris)) try {
|
|
45376
45655
|
const protocol = new URL(storageUri).protocol.replace(":", "");
|
|
45377
|
-
if (
|
|
45378
|
-
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}`);
|
|
45379
45658
|
} catch (error) {
|
|
45380
45659
|
console.error("Failed to delete uploaded bundle copy:", error);
|
|
45381
45660
|
}
|
|
@@ -45448,4 +45727,4 @@ function transformTemplate(templateString, values) {
|
|
|
45448
45727
|
return result;
|
|
45449
45728
|
}
|
|
45450
45729
|
//#endregion
|
|
45451
|
-
export { BuildLogger, ConfigBuilder,
|
|
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 };
|