@hot-updater/cli-tools 0.30.6 → 0.30.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Readable } from "stream";
2
2
  import { Key as Key$1 } from "node:readline";
3
3
  import { Readable as Readable$1, Writable } from "node:stream";
4
- import { ConfigInput, Platform, RequiredDeep } from "@hot-updater/plugin-core";
4
+ import { Bundle, ConfigInput, DatabasePlugin, Platform, RequiredDeep, StoragePlugin } from "@hot-updater/plugin-core";
5
5
 
6
6
  //#region src/BuildLogger.d.ts
7
7
  type LinePattern = string | RegExp;
@@ -366,6 +366,62 @@ declare const makeEnv: (newEnvVars: Record<string, EnvVarValue>, filePath?: stri
366
366
  preserveKeys?: string[];
367
367
  }) => Promise<string>;
368
368
  //#endregion
369
+ //#region ../core/dist/index.d.mts
370
+ //#endregion
371
+ //#region src/types.d.ts
372
+ type Platform$1 = "ios" | "android";
373
+ type BundleMetadata = {
374
+ app_version?: string;
375
+ };
376
+ //#endregion
377
+ //#region src/promoteBundle.d.ts
378
+ declare const LEGACY_BUNDLE_ERROR = "This OTA bundle was created by a version that does not support manifest.json. Copy bundle is not available.";
379
+ interface PromoteBundleInput {
380
+ action: "copy" | "move";
381
+ bundleId: string;
382
+ nextBundleId?: string;
383
+ targetChannel: string;
384
+ }
385
+ interface PromoteBundleDependencies {
386
+ config: ConfigResponse;
387
+ databasePlugin: DatabasePlugin;
388
+ storagePlugin: StoragePlugin | null;
389
+ }
390
+ declare function createCopiedBundleArchive({
391
+ bundle,
392
+ config,
393
+ nextBundleId,
394
+ storagePlugin,
395
+ targetChannel
396
+ }: {
397
+ bundle: Bundle;
398
+ config: ConfigResponse;
399
+ nextBundleId: string;
400
+ storagePlugin: StoragePlugin;
401
+ targetChannel: string;
402
+ }): Promise<{
403
+ id: string;
404
+ channel: string;
405
+ storageUri: string;
406
+ fileHash: string;
407
+ platform: Platform$1;
408
+ shouldForceUpdate: boolean;
409
+ enabled: boolean;
410
+ gitCommitHash: string | null;
411
+ message: string | null;
412
+ targetAppVersion: string | null;
413
+ fingerprintHash: string | null;
414
+ metadata?: BundleMetadata;
415
+ rolloutCohortCount?: number | null;
416
+ targetCohorts?: string[] | null;
417
+ }>;
418
+ declare function promoteBundle({
419
+ action,
420
+ bundleId,
421
+ nextBundleId,
422
+ targetChannel
423
+ }: PromoteBundleInput, deps: PromoteBundleDependencies): Promise<Bundle>;
424
+ //#endregion
369
425
  //#region ../../node_modules/.pnpm/@clack+core@1.0.1/node_modules/@clack/core/dist/index.d.mts
370
426
  declare const actions: readonly ["up", "down", "left", "right", "space", "enter", "cancel"];
371
427
  type Action = (typeof actions)[number];
@@ -919,4 +975,4 @@ type TransformTemplateArgs<T extends string> = { [Key in ExtractPlaceholders<T>]
919
975
  */
920
976
  declare function transformTemplate<T extends string>(templateString: T, values: TransformTemplateArgs<T>): string;
921
977
  //#endregion
922
- export { BuildLogger, BuildLoggerConfig, BuildType, ConfigBuilder, ConfigBuilderScaffold, ConfigResponse, CreateHotUpdaterConfigScaffoldFromBuilderOptions, CreateHotUpdaterConfigScaffoldOptions, HOT_UPDATER_SERVER_PACKAGE_VERSION_ENV, HotUpdateDirUtil, HotUpdaterConfigOptions, HotUpdaterConfigScaffold, HotUpdaterLogWriter, IConfigBuilder, ImportInfo, ManagedHelperStatement, ManagedHelperStrategy, PromptProgress, PromptSpinner, ProviderConfig, ReactNativeMetadata, WriteHotUpdaterConfigResult, banner, picocolors as colors, copyDirToTmp, createHotUpdaterConfigScaffold, createHotUpdaterConfigScaffoldFromBuilder, createLogWriter, createTarBr, createTarBrTargetFiles, createTarGz, createTarGzTargetFiles, createZip, createZipTargetFiles, decryptJson, encryptJson, ensureInstallPackages, getAndroidSdkPath, getCwd, getPackageManager, getReactNativeMetadatas, link, loadConfig, log, makeEnv, index_d_exports as p, printBanner, renderImportStatements, resolveHotUpdaterServerVersion, resolvePackageVersion, stripAnsi, transformEnv, transformTemplate, writeHotUpdaterConfig };
978
+ export { BuildLogger, BuildLoggerConfig, BuildType, ConfigBuilder, ConfigBuilderScaffold, ConfigResponse, CreateHotUpdaterConfigScaffoldFromBuilderOptions, CreateHotUpdaterConfigScaffoldOptions, HOT_UPDATER_SERVER_PACKAGE_VERSION_ENV, HotUpdateDirUtil, HotUpdaterConfigOptions, HotUpdaterConfigScaffold, HotUpdaterLogWriter, IConfigBuilder, ImportInfo, LEGACY_BUNDLE_ERROR, ManagedHelperStatement, ManagedHelperStrategy, PromoteBundleDependencies, PromoteBundleInput, PromptProgress, PromptSpinner, ProviderConfig, ReactNativeMetadata, WriteHotUpdaterConfigResult, banner, picocolors as colors, copyDirToTmp, createCopiedBundleArchive, createHotUpdaterConfigScaffold, createHotUpdaterConfigScaffoldFromBuilder, createLogWriter, createTarBr, createTarBrTargetFiles, createTarGz, createTarGzTargetFiles, createZip, createZipTargetFiles, decryptJson, encryptJson, ensureInstallPackages, getAndroidSdkPath, getCwd, getPackageManager, getReactNativeMetadatas, link, loadConfig, log, makeEnv, index_d_exports as p, printBanner, promoteBundle, renderImportStatements, resolveHotUpdaterServerVersion, resolvePackageVersion, stripAnsi, transformEnv, transformTemplate, writeHotUpdaterConfig };
package/dist/index.mjs CHANGED
@@ -23,8 +23,8 @@ import { EventEmitter as EventEmitter$1, addAbortListener, on, once, setMaxListe
23
23
  import Stream, { Duplex, PassThrough as PassThrough$1, Readable as Readable$1, Transform, Writable, getDefaultHighWaterMark } from "node:stream";
24
24
  import { StringDecoder } from "node:string_decoder";
25
25
  import assert$1 from "node:assert";
26
- import { randomBytes } from "node:crypto";
27
- import fsp from "node:fs/promises";
26
+ import crypto$1, { randomBytes } from "node:crypto";
27
+ import fs$3 from "node:fs/promises";
28
28
  import { fileURLToPath } from "node:url";
29
29
  import { ChildProcess, execFile, spawn, spawnSync } from "node:child_process";
30
30
  import { scheduler, setImmediate as setImmediate$1, setTimeout as setTimeout$1 } from "node:timers/promises";
@@ -33,6 +33,8 @@ import { finished } from "node:stream/promises";
33
33
  import { Buffer as Buffer$2 } from "node:buffer";
34
34
  import ts from "typescript";
35
35
  import { loadConfig as loadConfig$1 } from "unconfig";
36
+ import { brotliDecompressSync } from "node:zlib";
37
+ import { createUUIDv7, detectCompressionFormat } from "@hot-updater/plugin-core";
36
38
  import { transformSync } from "oxc-transform";
37
39
  //#region ../../node_modules/.pnpm/picocolors@1.1.1/node_modules/picocolors/picocolors.js
38
40
  var require_picocolors = /* @__PURE__ */ __commonJSMin(((exports, module) => {
@@ -5408,12 +5410,12 @@ var require_sync$5 = /* @__PURE__ */ __commonJSMin(((exports) => {
5408
5410
  var require_fs$2 = /* @__PURE__ */ __commonJSMin(((exports) => {
5409
5411
  Object.defineProperty(exports, "__esModule", { value: true });
5410
5412
  exports.createFileSystemAdapter = exports.FILE_SYSTEM_ADAPTER = void 0;
5411
- const fs$8 = __require("fs");
5413
+ const fs$9 = __require("fs");
5412
5414
  exports.FILE_SYSTEM_ADAPTER = {
5413
- lstat: fs$8.lstat,
5414
- stat: fs$8.stat,
5415
- lstatSync: fs$8.lstatSync,
5416
- statSync: fs$8.statSync
5415
+ lstat: fs$9.lstat,
5416
+ stat: fs$9.stat,
5417
+ lstatSync: fs$9.lstatSync,
5418
+ statSync: fs$9.statSync
5417
5419
  };
5418
5420
  function createFileSystemAdapter(fsMethods) {
5419
5421
  if (fsMethods === void 0) return exports.FILE_SYSTEM_ADAPTER;
@@ -5731,14 +5733,14 @@ var require_sync$4 = /* @__PURE__ */ __commonJSMin(((exports) => {
5731
5733
  var require_fs = /* @__PURE__ */ __commonJSMin(((exports) => {
5732
5734
  Object.defineProperty(exports, "__esModule", { value: true });
5733
5735
  exports.createFileSystemAdapter = exports.FILE_SYSTEM_ADAPTER = void 0;
5734
- const fs$7 = __require("fs");
5736
+ const fs$8 = __require("fs");
5735
5737
  exports.FILE_SYSTEM_ADAPTER = {
5736
- lstat: fs$7.lstat,
5737
- stat: fs$7.stat,
5738
- lstatSync: fs$7.lstatSync,
5739
- statSync: fs$7.statSync,
5740
- readdir: fs$7.readdir,
5741
- readdirSync: fs$7.readdirSync
5738
+ lstat: fs$8.lstat,
5739
+ stat: fs$8.stat,
5740
+ lstatSync: fs$8.lstatSync,
5741
+ statSync: fs$8.statSync,
5742
+ readdir: fs$8.readdir,
5743
+ readdirSync: fs$8.readdirSync
5742
5744
  };
5743
5745
  function createFileSystemAdapter(fsMethods) {
5744
5746
  if (fsMethods === void 0) return exports.FILE_SYSTEM_ADAPTER;
@@ -6908,7 +6910,7 @@ var require_sync = /* @__PURE__ */ __commonJSMin(((exports) => {
6908
6910
  var require_settings = /* @__PURE__ */ __commonJSMin(((exports) => {
6909
6911
  Object.defineProperty(exports, "__esModule", { value: true });
6910
6912
  exports.DEFAULT_FILE_SYSTEM_ADAPTER = void 0;
6911
- const fs$6 = __require("fs");
6913
+ const fs$7 = __require("fs");
6912
6914
  const os$1 = __require("os");
6913
6915
  /**
6914
6916
  * The `os.cpus` method can return zero. We expect the number of cores to be greater than zero.
@@ -6916,12 +6918,12 @@ var require_settings = /* @__PURE__ */ __commonJSMin(((exports) => {
6916
6918
  */
6917
6919
  const CPU_COUNT = Math.max(os$1.cpus().length, 1);
6918
6920
  exports.DEFAULT_FILE_SYSTEM_ADAPTER = {
6919
- lstat: fs$6.lstat,
6920
- lstatSync: fs$6.lstatSync,
6921
- stat: fs$6.stat,
6922
- statSync: fs$6.statSync,
6923
- readdir: fs$6.readdir,
6924
- readdirSync: fs$6.readdirSync
6921
+ lstat: fs$7.lstat,
6922
+ lstatSync: fs$7.lstatSync,
6923
+ stat: fs$7.stat,
6924
+ statSync: fs$7.statSync,
6925
+ readdir: fs$7.readdir,
6926
+ readdirSync: fs$7.readdirSync
6925
6927
  };
6926
6928
  var Settings = class {
6927
6929
  constructor(_options = {}) {
@@ -25988,7 +25990,7 @@ const mkdir = (dir, opt, cb) => {
25988
25990
  else cb();
25989
25991
  };
25990
25992
  if (dir === cwd) return checkCwd(dir, done);
25991
- if (preserve) return fsp.mkdir(dir, {
25993
+ if (preserve) return fs$3.mkdir(dir, {
25992
25994
  mode,
25993
25995
  recursive: true
25994
25996
  }).then((made) => done(null, made ?? void 0), done);
@@ -26799,7 +26801,7 @@ const extractFile = (opt, _) => {
26799
26801
  });
26800
26802
  });
26801
26803
  };
26802
- makeCommand(extractFileSync, extractFile, (opt) => new UnpackSync(opt), (opt) => new Unpack(opt), (opt, files) => {
26804
+ const extract = makeCommand(extractFileSync, extractFile, (opt) => new UnpackSync(opt), (opt) => new Unpack(opt), (opt, files) => {
26803
26805
  if (files?.length) filesFilter(opt, files);
26804
26806
  });
26805
26807
  //#endregion
@@ -36656,7 +36658,7 @@ const handleCommand = (filePath, rawArguments, rawOptions) => {
36656
36658
  var require_windows = /* @__PURE__ */ __commonJSMin(((exports, module) => {
36657
36659
  module.exports = isexe;
36658
36660
  isexe.sync = sync;
36659
- var fs$5 = __require("fs");
36661
+ var fs$6 = __require("fs");
36660
36662
  function checkPathExt(path, options) {
36661
36663
  var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT;
36662
36664
  if (!pathext) return true;
@@ -36673,12 +36675,12 @@ var require_windows = /* @__PURE__ */ __commonJSMin(((exports, module) => {
36673
36675
  return checkPathExt(path, options);
36674
36676
  }
36675
36677
  function isexe(path, options, cb) {
36676
- fs$5.stat(path, function(er, stat) {
36678
+ fs$6.stat(path, function(er, stat) {
36677
36679
  cb(er, er ? false : checkStat(stat, path, options));
36678
36680
  });
36679
36681
  }
36680
36682
  function sync(path, options) {
36681
- return checkStat(fs$5.statSync(path), path, options);
36683
+ return checkStat(fs$6.statSync(path), path, options);
36682
36684
  }
36683
36685
  }));
36684
36686
  //#endregion
@@ -36686,14 +36688,14 @@ var require_windows = /* @__PURE__ */ __commonJSMin(((exports, module) => {
36686
36688
  var require_mode = /* @__PURE__ */ __commonJSMin(((exports, module) => {
36687
36689
  module.exports = isexe;
36688
36690
  isexe.sync = sync;
36689
- var fs$4 = __require("fs");
36691
+ var fs$5 = __require("fs");
36690
36692
  function isexe(path, options, cb) {
36691
- fs$4.stat(path, function(er, stat) {
36693
+ fs$5.stat(path, function(er, stat) {
36692
36694
  cb(er, er ? false : checkStat(stat, options));
36693
36695
  });
36694
36696
  }
36695
36697
  function sync(path, options) {
36696
- return checkStat(fs$4.statSync(path), options);
36698
+ return checkStat(fs$5.statSync(path), options);
36697
36699
  }
36698
36700
  function checkStat(stat, options) {
36699
36701
  return stat.isFile() && checkMode(stat, options);
@@ -36908,16 +36910,16 @@ var require_shebang_command = /* @__PURE__ */ __commonJSMin(((exports, module) =
36908
36910
  //#endregion
36909
36911
  //#region ../../node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/readShebang.js
36910
36912
  var require_readShebang = /* @__PURE__ */ __commonJSMin(((exports, module) => {
36911
- const fs$3 = __require("fs");
36913
+ const fs$4 = __require("fs");
36912
36914
  const shebangCommand = require_shebang_command();
36913
36915
  function readShebang(command) {
36914
36916
  const size = 150;
36915
36917
  const buffer = Buffer.alloc(size);
36916
36918
  let fd;
36917
36919
  try {
36918
- fd = fs$3.openSync(command, "r");
36919
- fs$3.readSync(fd, buffer, 0, size, 0);
36920
- fs$3.closeSync(fd);
36920
+ fd = fs$4.openSync(command, "r");
36921
+ fs$4.readSync(fd, buffer, 0, size, 0);
36922
+ fs$4.closeSync(fd);
36921
36923
  } catch (e) {}
36922
36924
  return shebangCommand(buffer.toString());
36923
36925
  }
@@ -42055,7 +42057,7 @@ async function findUp(name, { cwd = process$1.cwd(), type = "file", stopAt } = {
42055
42057
  while (directory) {
42056
42058
  const filePath = isAbsoluteName ? name : path$1.join(directory, name);
42057
42059
  try {
42058
- const stats = await fsp.stat(filePath);
42060
+ const stats = await fs$3.stat(filePath);
42059
42061
  if (type === "file" && stats.isFile() || type === "directory" && stats.isDirectory()) return filePath;
42060
42062
  } catch {}
42061
42063
  if (directory === stopAt || directory === root) break;
@@ -47124,7 +47126,7 @@ const _readPackage = (file, normalize) => {
47124
47126
  return json;
47125
47127
  };
47126
47128
  async function readPackage({ cwd, normalize = true } = {}) {
47127
- return _readPackage(await fsp.readFile(getPackagePath(cwd), "utf8"), normalize);
47129
+ return _readPackage(await fs$3.readFile(getPackagePath(cwd), "utf8"), normalize);
47128
47130
  }
47129
47131
  //#endregion
47130
47132
  //#region ../../node_modules/.pnpm/read-package-up@11.0.0/node_modules/read-package-up/index.js
@@ -48116,6 +48118,212 @@ const makeEnv = async (newEnvVars, filePath = ".env.hotupdater", options) => {
48116
48118
  }
48117
48119
  };
48118
48120
  //#endregion
48121
+ //#region src/promoteBundle.ts
48122
+ const LEGACY_BUNDLE_ERROR = "This OTA bundle was created by a version that does not support manifest.json. Copy bundle is not available.";
48123
+ const SIGNED_HASH_PREFIX = "sig:";
48124
+ function isSignedFileHash(fileHash) {
48125
+ return fileHash.startsWith(SIGNED_HASH_PREFIX);
48126
+ }
48127
+ async function getFileHash(filepath) {
48128
+ const file = await fs$3.readFile(filepath);
48129
+ return crypto$1.createHash("sha256").update(file).digest("hex");
48130
+ }
48131
+ async function signFileHash(fileHash, privateKeyPath) {
48132
+ const privateKeyPEM = await fs$3.readFile(privateKeyPath, "utf8");
48133
+ const sign = crypto$1.createSign("RSA-SHA256");
48134
+ sign.update(Buffer.from(fileHash, "hex"));
48135
+ sign.end();
48136
+ return `${SIGNED_HASH_PREFIX}${sign.sign(privateKeyPEM).toString("base64")}`;
48137
+ }
48138
+ function getArchiveFilename(storageUri) {
48139
+ const { pathname } = new URL(storageUri);
48140
+ return path$1.basename(pathname) || "bundle.zip";
48141
+ }
48142
+ function resolveExtractedPath(rootDir, entryName) {
48143
+ const normalizedEntryName = entryName.replaceAll("\\", "/");
48144
+ const entryPath = path$1.resolve(rootDir, normalizedEntryName);
48145
+ const relativePath = path$1.relative(rootDir, entryPath);
48146
+ if (relativePath.startsWith("..") || path$1.isAbsolute(relativePath) || normalizedEntryName.startsWith("/")) throw new Error(`Invalid archive entry path: ${entryName}`);
48147
+ return entryPath;
48148
+ }
48149
+ async function downloadArchive(fileUrl, archivePath) {
48150
+ const response = await fetch(fileUrl);
48151
+ if (!response.ok) throw new Error(`Failed to download bundle archive: ${response.statusText}`);
48152
+ const archiveBuffer = Buffer.from(await response.arrayBuffer());
48153
+ await fs$3.writeFile(archivePath, archiveBuffer);
48154
+ }
48155
+ async function extractZipArchive(archivePath, extractDir) {
48156
+ const zip = await import_lib$1.default.loadAsync(await fs$3.readFile(archivePath));
48157
+ const entries = Object.values(zip.files).sort((left, right) => left.name.localeCompare(right.name));
48158
+ for (const entry of entries) {
48159
+ const outputPath = resolveExtractedPath(extractDir, entry.name);
48160
+ if (entry.dir) {
48161
+ await fs$3.mkdir(outputPath, { recursive: true });
48162
+ continue;
48163
+ }
48164
+ await fs$3.mkdir(path$1.dirname(outputPath), { recursive: true });
48165
+ await fs$3.writeFile(outputPath, await entry.async("nodebuffer"));
48166
+ }
48167
+ }
48168
+ async function extractTarBrArchive(archivePath, extractDir) {
48169
+ const tarPath = path$1.join(extractDir, "bundle.tar");
48170
+ const tarBuffer = brotliDecompressSync(await fs$3.readFile(archivePath));
48171
+ await fs$3.writeFile(tarPath, tarBuffer);
48172
+ try {
48173
+ await extract({
48174
+ file: tarPath,
48175
+ cwd: extractDir,
48176
+ gzip: false,
48177
+ strict: true
48178
+ });
48179
+ } finally {
48180
+ await fs$3.rm(tarPath, { force: true });
48181
+ }
48182
+ }
48183
+ async function extractArchive(archivePath, extractDir) {
48184
+ const { format } = detectCompressionFormat(path$1.basename(archivePath));
48185
+ switch (format) {
48186
+ case "zip":
48187
+ await extractZipArchive(archivePath, extractDir);
48188
+ return format;
48189
+ case "tar.gz":
48190
+ await extract({
48191
+ file: archivePath,
48192
+ cwd: extractDir,
48193
+ gzip: true,
48194
+ strict: true
48195
+ });
48196
+ return format;
48197
+ case "tar.br":
48198
+ await extractTarBrArchive(archivePath, extractDir);
48199
+ return format;
48200
+ }
48201
+ }
48202
+ async function getArchiveTargetFiles(bundleDir) {
48203
+ const entries = await fs$3.readdir(bundleDir, { withFileTypes: true });
48204
+ entries.sort((left, right) => left.name.localeCompare(right.name));
48205
+ return entries.map((entry) => ({
48206
+ path: path$1.join(bundleDir, entry.name),
48207
+ name: entry.name
48208
+ }));
48209
+ }
48210
+ async function createArchiveFromDirectory(sourceDir, archivePath, format) {
48211
+ const targetFiles = await getArchiveTargetFiles(sourceDir);
48212
+ switch (format) {
48213
+ case "zip":
48214
+ await createZipTargetFiles({
48215
+ outfile: archivePath,
48216
+ targetFiles
48217
+ });
48218
+ return;
48219
+ case "tar.gz":
48220
+ await createTarGzTargetFiles({
48221
+ outfile: archivePath,
48222
+ targetFiles
48223
+ });
48224
+ return;
48225
+ case "tar.br":
48226
+ await createTarBrTargetFiles({
48227
+ outfile: archivePath,
48228
+ targetFiles
48229
+ });
48230
+ return;
48231
+ }
48232
+ }
48233
+ async function rewriteManifestBundleId(extractDir, nextBundleId) {
48234
+ const manifestPath = path$1.join(extractDir, "manifest.json");
48235
+ try {
48236
+ await fs$3.access(manifestPath);
48237
+ } catch {
48238
+ throw new Error(LEGACY_BUNDLE_ERROR);
48239
+ }
48240
+ const manifest = JSON.parse(await fs$3.readFile(manifestPath, "utf8"));
48241
+ manifest.bundleId = nextBundleId;
48242
+ await fs$3.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
48243
+ }
48244
+ async function resolveBundleDownloadUrl(storageUri, storagePlugin) {
48245
+ const protocol = new URL(storageUri).protocol.replace(":", "");
48246
+ if (protocol === "http" || protocol === "https") return storageUri;
48247
+ if (!storagePlugin) throw new Error("Storage plugin is not configured");
48248
+ if (storagePlugin.supportedProtocol !== protocol) throw new Error(`No storage plugin for protocol: ${protocol}`);
48249
+ const { fileUrl } = await storagePlugin.getDownloadUrl(storageUri);
48250
+ if (!fileUrl) throw new Error("Storage plugin returned empty fileUrl");
48251
+ return fileUrl;
48252
+ }
48253
+ async function createCopiedBundleArchive({ bundle, config, nextBundleId, storagePlugin, targetChannel }) {
48254
+ const downloadUrl = await resolveBundleDownloadUrl(bundle.storageUri, storagePlugin);
48255
+ const archiveFilename = getArchiveFilename(bundle.storageUri);
48256
+ const workDir = await fs$3.mkdtemp(path$1.join(os.tmpdir(), "hot-updater-console-promote-"));
48257
+ const sourceArchivePath = path$1.join(workDir, archiveFilename);
48258
+ const extractDir = path$1.join(workDir, "bundle");
48259
+ const outputArchivePath = path$1.join(workDir, archiveFilename);
48260
+ await fs$3.mkdir(extractDir, { recursive: true });
48261
+ try {
48262
+ await downloadArchive(downloadUrl, sourceArchivePath);
48263
+ const format = await extractArchive(sourceArchivePath, extractDir);
48264
+ await rewriteManifestBundleId(extractDir, nextBundleId);
48265
+ await fs$3.rm(sourceArchivePath, { force: true });
48266
+ await createArchiveFromDirectory(extractDir, outputArchivePath, format);
48267
+ const fileHash = await getFileHash(outputArchivePath);
48268
+ if (isSignedFileHash(bundle.fileHash) && !config.signing?.enabled) throw new Error("Cannot copy a signed bundle without signing.privateKeyPath in hot-updater.config.ts");
48269
+ const nextFileHash = config.signing?.enabled && config.signing.privateKeyPath ? await signFileHash(fileHash, config.signing.privateKeyPath) : fileHash;
48270
+ const { storageUri } = await storagePlugin.upload(nextBundleId, outputArchivePath);
48271
+ return {
48272
+ ...bundle,
48273
+ id: nextBundleId,
48274
+ channel: targetChannel,
48275
+ storageUri,
48276
+ fileHash: nextFileHash
48277
+ };
48278
+ } finally {
48279
+ await fs$3.rm(workDir, {
48280
+ recursive: true,
48281
+ force: true
48282
+ });
48283
+ }
48284
+ }
48285
+ async function deleteUploadedCopy(storagePlugin, storageUri) {
48286
+ if (!storageUri) return;
48287
+ try {
48288
+ await storagePlugin.delete(storageUri);
48289
+ } catch (error) {
48290
+ console.error("Failed to delete uploaded bundle copy:", error);
48291
+ }
48292
+ }
48293
+ async function promoteBundle({ action, bundleId, nextBundleId, targetChannel }, deps) {
48294
+ const normalizedTargetChannel = targetChannel.trim();
48295
+ if (!normalizedTargetChannel) throw new Error("Target channel is required");
48296
+ const bundle = await deps.databasePlugin.getBundleById(bundleId);
48297
+ if (!bundle) throw new Error("Bundle not found");
48298
+ if (bundle.channel === normalizedTargetChannel) throw new Error("Target channel must be different from the current channel");
48299
+ if (action === "move") {
48300
+ await deps.databasePlugin.updateBundle(bundleId, { channel: normalizedTargetChannel });
48301
+ await deps.databasePlugin.commitBundle();
48302
+ const updatedBundle = await deps.databasePlugin.getBundleById(bundleId);
48303
+ if (!updatedBundle) throw new Error("Promoted bundle not found");
48304
+ return updatedBundle;
48305
+ }
48306
+ if (!deps.storagePlugin) throw new Error("Storage plugin is not configured");
48307
+ const resolvedNextBundleId = nextBundleId?.trim() || createUUIDv7();
48308
+ const copiedBundle = await createCopiedBundleArchive({
48309
+ bundle,
48310
+ config: deps.config,
48311
+ nextBundleId: resolvedNextBundleId,
48312
+ storagePlugin: deps.storagePlugin,
48313
+ targetChannel: normalizedTargetChannel
48314
+ });
48315
+ let uploadedStorageUri = copiedBundle.storageUri;
48316
+ try {
48317
+ await deps.databasePlugin.appendBundle(copiedBundle);
48318
+ await deps.databasePlugin.commitBundle();
48319
+ uploadedStorageUri = null;
48320
+ return copiedBundle;
48321
+ } catch (error) {
48322
+ await deleteUploadedCopy(deps.storagePlugin, uploadedStorageUri);
48323
+ throw error;
48324
+ }
48325
+ }
48326
+ //#endregion
48119
48327
  //#region src/resolvePackageVersion.ts
48120
48328
  const require$1 = createRequire(import.meta.url);
48121
48329
  const HOT_UPDATER_SERVER_PACKAGE_VERSION_ENV = "HOT_UPDATER_SERVER_PACKAGE_VERSION";
@@ -48182,4 +48390,4 @@ function transformTemplate(templateString, values) {
48182
48390
  }
48183
48391
  //#endregion
48184
48392
  var colors = import_picocolors.default;
48185
- export { BuildLogger, ConfigBuilder, HOT_UPDATER_SERVER_PACKAGE_VERSION_ENV, HotUpdateDirUtil, banner, colors, copyDirToTmp, createHotUpdaterConfigScaffold, createHotUpdaterConfigScaffoldFromBuilder, createLogWriter, createTarBr, createTarBrTargetFiles, createTarGz, createTarGzTargetFiles, createZip, createZipTargetFiles, decryptJson, encryptJson, ensureInstallPackages, getAndroidSdkPath, getCwd, getPackageManager, getReactNativeMetadatas, link, loadConfig, log, makeEnv, dist_exports as p, printBanner, renderImportStatements, resolveHotUpdaterServerVersion, resolvePackageVersion, stripAnsi, transformEnv, transformTemplate, writeHotUpdaterConfig };
48393
+ export { BuildLogger, ConfigBuilder, HOT_UPDATER_SERVER_PACKAGE_VERSION_ENV, HotUpdateDirUtil, LEGACY_BUNDLE_ERROR, banner, colors, copyDirToTmp, createCopiedBundleArchive, createHotUpdaterConfigScaffold, createHotUpdaterConfigScaffoldFromBuilder, createLogWriter, createTarBr, createTarBrTargetFiles, createTarGz, createTarGzTargetFiles, createZip, createZipTargetFiles, decryptJson, encryptJson, ensureInstallPackages, getAndroidSdkPath, getCwd, getPackageManager, getReactNativeMetadatas, link, loadConfig, log, makeEnv, dist_exports as p, printBanner, promoteBundle, renderImportStatements, resolveHotUpdaterServerVersion, resolvePackageVersion, stripAnsi, transformEnv, transformTemplate, writeHotUpdaterConfig };
package/package.json CHANGED
@@ -1,16 +1,20 @@
1
1
  {
2
2
  "name": "@hot-updater/cli-tools",
3
- "version": "0.30.6",
3
+ "version": "0.30.8",
4
4
  "type": "module",
5
+ "engines": {
6
+ "node": ">=20.19.0"
7
+ },
5
8
  "description": "CLI utilities for Hot Updater",
6
9
  "sideEffects": false,
7
- "main": "./dist/index.cjs",
10
+ "main": "./dist/index.mjs",
8
11
  "module": "./dist/index.mjs",
9
- "types": "./dist/index.d.cts",
12
+ "types": "./dist/index.d.mts",
10
13
  "exports": {
11
14
  ".": {
15
+ "types": "./dist/index.d.mts",
12
16
  "import": "./dist/index.mjs",
13
- "require": "./dist/index.cjs"
17
+ "require": "./dist/index.mjs"
14
18
  },
15
19
  "./package.json": "./package.json"
16
20
  },
@@ -42,7 +46,7 @@
42
46
  "oxc-transform": "0.121.0",
43
47
  "typescript": "6.0.2",
44
48
  "unconfig": "7.5.0",
45
- "@hot-updater/plugin-core": "0.30.6"
49
+ "@hot-updater/plugin-core": "0.30.8"
46
50
  },
47
51
  "devDependencies": {
48
52
  "@clack/prompts": "1.0.1",
@@ -58,7 +62,7 @@
58
62
  "semver": "^7.6.3",
59
63
  "tar": "^7.5.1",
60
64
  "workspace-tools": "^0.36.4",
61
- "@hot-updater/test-utils": "0.30.6"
65
+ "@hot-updater/test-utils": "0.30.8"
62
66
  },
63
67
  "inlinedDependencies": {
64
68
  "@babel/code-frame": "7.29.0",