@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.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 { createBundleStorageKey, createStoragePlugin, createStorageRootUriWithPath, getContentType, getManifestAssetDownloadPath, getManifestAssetStoragePath, isContentAddressedAssetFileHash, resolveManifestAssetStorageUri } from "@hot-updater/plugin-core";
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
- "import { HotUpdater } from \"@hot-updater/react-native\";",
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 createLocalSigningPlugin = ({ privateKeyPath }) => {
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 plugin. Omit signing to disable it.");
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 plugin fields.");
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 ? createLocalSigningPlugin(normalized) : normalized, cwd);
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 ? createLocalSigningPlugin(normalized) : 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
- collectedImports = /* @__PURE__ */ new Map();
25083
- constructor() {
25084
- this.addImport({
25085
- pkg: "hot-updater",
25086
- named: ["defineConfig"]
25087
- });
25088
- this.addImport({
25089
- pkg: "node:fs",
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
- const pkg = info.pkg;
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.addImport({
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.addImports(storageConfig.imports);
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.addImports(databaseConfig.imports);
25148
- if (databaseConfig.imports.some((imp) => imp.pkg.includes("firebase"))) this.addImport({
25149
- pkg: "firebase-admin/app",
25150
- named: ["applicationDefault"]
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
- const imports = this.getImportInfos();
25163
- const importStatements = renderImportStatements(imports);
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
- ${importStatements}
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$1 = (value) => {
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$1(jsonData) === false) throw new Error("jsonData must be an object");
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 appendMissingProperties = (objectText, propertyTexts, hasExistingProperties) => {
44182
- if (propertyTexts.length === 0) return objectText;
44183
- const closingIndent = /\n([ \t]*)\}$/.exec(objectText)?.[1] ?? "";
44184
- const childIndent = objectText.match(/\n([ \t]+)[^\s]/)?.[1] ?? `${closingIndent} `;
44185
- const formattedProperties = propertyTexts.map((propertyText) => indentBlock(propertyText, childIndent)).join(",\n");
44186
- const closeBraceIndex = objectText.lastIndexOf("}");
44187
- if (closeBraceIndex === -1) return objectText;
44188
- const prefix = hasExistingProperties ? hasTrailingComma(objectText) ? "\n" : ",\n" : "\n";
44189
- const suffix = `,\n${closingIndent}`;
44190
- return `${objectText.slice(0, closeBraceIndex)}${prefix}${formattedProperties}${suffix}${objectText.slice(closeBraceIndex)}`;
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) => getNodeText(newObject.source, property));
44241
- return appendMissingProperties(mergedText, missingPropertyTexts, existingObject.objectExpression.properties.length > 0);
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(getNodeText(next.source, nextProperty));
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
- let mergedText = objectText;
44329
- for (const edit of propertyEdits.sort((left, right) => right.start - left.start)) mergedText = mergedText.slice(0, edit.start) + edit.text + mergedText.slice(edit.end);
44330
- return appendMissingProperties(mergedText, missingPropertyTexts, existing.objectExpression.properties.length > 0);
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
- const rebuildImportBlock = (source, scaffold) => {
44339
- const imports = scaffold.imports.map((info) => info.pkg === "node:fs" ? {
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.program.body.filter((statement) => statement.type === "ImportDeclaration");
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 preservedImportTexts = importDeclarations.filter((declaration) => !MANAGED_IMPORT_PACKAGES.has(declaration.source.value)).map((declaration) => source.text.slice(getTopLevelFullStart(source, declaration), declaration.end).trim());
44352
- const managedImportText = renderImportStatements(imports);
44353
- const nextImportBlock = [...preservedImportTexts, managedImportText].filter(Boolean).join("\n");
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 nextObjectText = updateManagedObject({
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 (!nextObjectText) return { reason: "Existing config uses dynamic build/storage/database expressions that cannot be merged safely." };
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 fs$1.readFile(filePath, "utf-8").catch((error) => {
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 plugin is required");
45060
+ throw new Error("build adapter is required");
44920
45061
  },
44921
- storage: missingStorage,
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 = (options) => {
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
- const loadConfig = async (options) => {
44960
- const { config } = await loadConfig$1(getConfigLoaderOptions(options));
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, storagePlugin, outputPath) {
45513
+ async function downloadStorageObject(storageUri, storageAdapter, outputPath) {
45235
45514
  const protocol = new URL(storageUri).protocol.replace(":", "");
45236
- if (storagePlugin?.protocol === protocol) {
45237
- await writeStorageFile(storagePlugin, storageUri, outputPath);
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 plugin for protocol: ${protocol}`);
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, storagePlugin, workDir }) {
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, storagePlugin, transferPath);
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, storagePlugin }) {
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, storagePlugin, sourceManifestPath);
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
- storagePlugin,
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(storagePlugin, createBundleStorageKey(nextBundleId), archivePath);
45615
+ const archiveUpload = await putStorageFile(storageAdapter, createBundleStorageKey(nextBundleId), archivePath);
45337
45616
  uploadedStorageUris.push(archiveUpload.storageUri);
45338
- const manifestUpload = await putStorageFile(storagePlugin, createBundleStorageKey(nextBundleId), manifestPath);
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 storagePlugin.exists({ storageUri });
45349
- if (!exists) await putStorageFile(storagePlugin, getRelativeStorageDir(assetUploadTarget.storagePath) ? `assets/${getRelativeStorageDir(assetUploadTarget.storagePath)}` : "assets", assetUploadTarget.uploadSourcePath);
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(storagePlugin, uploadedStorageUris);
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(storagePlugin, storageUris) {
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 (storagePlugin.protocol === protocol) await storagePlugin.delete({ storageUri });
45378
- else if (protocol !== "http" && protocol !== "https") throw new Error(`No storage plugin for protocol: ${protocol}`);
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, HOT_UPDATER_PLUGINS_PATH, HOT_UPDATER_SERVER_PACKAGE_VERSION_ENV, HotUpdateDirUtil, InitEnvFileError, InitError, LEGACY_BUNDLE_ERROR, LegacyInfrastructureError, MissingInitInputsError, assertInfrastructureGenerationAtUrl, assertInfrastructureGenerationPayload, assertInitInputs, assertInitProviderInputs, banner, typedColors as colors, confirmInitInputPersistence, copyDirToTmp, createCopiedBundleArtifacts, createHotUpdaterConfigScaffold, createHotUpdaterConfigScaffoldFromBuilder, createLogWriter, createTarBrTargetFiles, createZip, decryptJson, defineInitProvider, encryptJson, ensureInstallPackages, generateHotUpdaterPlugins, getAndroidSdkPath, getBundleSigningPublicKey, getCwd, getHotUpdaterEnvValue, getHotUpdaterInitInputEnv, getInitProviderEnvVars, getInitProviderTextPromptValues, getMissingInitInputs, getMissingInitProviderInputs, getPackageManager, getReactNativeMetadatas, getStorageFileByteSize, link, loadConfig, loadHotUpdaterPlugins, log, makeEnv, p, prepareBundleSigning, printAppSetup, printBanner, putStorageFile, readBundleSigningPublicKeyFile, readHotUpdaterEnv, readHotUpdaterInitEnv, readPackageUp, renderAppSetup, renderHotUpdaterPlugins, renderImportStatements, resolveHotUpdaterServerVersion, resolveInitProviderInput, resolveInitProviderInputs, resolvePackageVersion, shouldAutoSelectOnlyInitResource, stripAnsi, transformEnv, transformTemplate, writeHotUpdaterConfig, writeHotUpdaterPlugins, writeStorageFile, writeStorageResponseFile };
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 };