@hot-updater/plugin-core 0.20.15 → 0.21.0

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.js CHANGED
@@ -2,11 +2,14 @@ import { createRequire } from "node:module";
2
2
  import process$1 from "node:process";
3
3
  import os from "node:os";
4
4
  import tty from "node:tty";
5
- import fs from "fs/promises";
5
+ import mime from "mime";
6
6
  import path from "path";
7
+ import fs from "fs/promises";
7
8
  import fs$1 from "fs";
8
9
  import fg from "fast-glob";
9
10
  import semver from "semver";
11
+ import * as tar from "tar";
12
+ import { brotliCompressSync, constants } from "zlib";
10
13
  import { cosmiconfig, cosmiconfigSync } from "cosmiconfig";
11
14
  import { TypeScriptLoader } from "cosmiconfig-typescript-loader";
12
15
  import { transform } from "oxc-transform";
@@ -1553,6 +1556,56 @@ function calculatePagination(total, options) {
1553
1556
  };
1554
1557
  }
1555
1558
 
1559
+ //#endregion
1560
+ //#region src/compressionFormat.ts
1561
+ /**
1562
+ * Compression formats registry
1563
+ * Add new formats here to support additional compression types
1564
+ */
1565
+ const COMPRESSION_FORMATS = {
1566
+ zip: {
1567
+ format: "zip",
1568
+ fileExtension: ".zip",
1569
+ mimeType: "application/zip"
1570
+ },
1571
+ "tar.br": {
1572
+ format: "tar.br",
1573
+ fileExtension: ".tar.br",
1574
+ mimeType: "application/x-tar"
1575
+ },
1576
+ "tar.gz": {
1577
+ format: "tar.gz",
1578
+ fileExtension: ".tar.gz",
1579
+ mimeType: "application/x-tar"
1580
+ }
1581
+ };
1582
+ /**
1583
+ * Detects compression format from filename
1584
+ * @param filename The filename to detect format from
1585
+ * @returns Compression format information
1586
+ */
1587
+ function detectCompressionFormat(filename) {
1588
+ for (const info of Object.values(COMPRESSION_FORMATS)) if (filename.endsWith(info.fileExtension)) return info;
1589
+ return COMPRESSION_FORMATS.zip;
1590
+ }
1591
+ /**
1592
+ * Gets MIME type for a filename
1593
+ * @param filename The filename to get MIME type for
1594
+ * @returns MIME type string
1595
+ */
1596
+ function getCompressionMimeType(filename) {
1597
+ return detectCompressionFormat(filename).mimeType;
1598
+ }
1599
+ /**
1600
+ * Gets Content-Type for a bundle file with 3-tier fallback
1601
+ * @param bundlePath The bundle file path
1602
+ * @returns Content-Type string (never undefined, falls back to application/octet-stream)
1603
+ */
1604
+ function getContentType(bundlePath) {
1605
+ const filename = path.basename(bundlePath);
1606
+ return mime.getType(bundlePath) ?? getCompressionMimeType(filename) ?? "application/octet-stream";
1607
+ }
1608
+
1556
1609
  //#endregion
1557
1610
  //#region ../../node_modules/.pnpm/workspace-tools@0.36.4/node_modules/workspace-tools/lib/graph/getPackageDependencies.js
1558
1611
  var require_getPackageDependencies = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/workspace-tools@0.36.4/node_modules/workspace-tools/lib/graph/getPackageDependencies.js": ((exports) => {
@@ -8462,12 +8515,12 @@ var require_scan = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/picoma
8462
8515
  //#endregion
8463
8516
  //#region ../../node_modules/.pnpm/picomatch@2.3.1/node_modules/picomatch/lib/parse.js
8464
8517
  var require_parse = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/picomatch@2.3.1/node_modules/picomatch/lib/parse.js": ((exports, module) => {
8465
- const constants$2 = require_constants$1();
8518
+ const constants$3 = require_constants$1();
8466
8519
  const utils$30 = require_utils$1();
8467
8520
  /**
8468
8521
  * Constants
8469
8522
  */
8470
- const { MAX_LENGTH, POSIX_REGEX_SOURCE, REGEX_NON_SPECIAL_CHARS, REGEX_SPECIAL_CHARS_BACKREF, REPLACEMENTS } = constants$2;
8523
+ const { MAX_LENGTH, POSIX_REGEX_SOURCE, REGEX_NON_SPECIAL_CHARS, REGEX_SPECIAL_CHARS_BACKREF, REPLACEMENTS } = constants$3;
8471
8524
  /**
8472
8525
  * Helpers
8473
8526
  */
@@ -8509,8 +8562,8 @@ var require_parse = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/picom
8509
8562
  const tokens = [bos];
8510
8563
  const capture = opts.capture ? "" : "?:";
8511
8564
  const win32$1 = utils$30.isWindows(options);
8512
- const PLATFORM_CHARS = constants$2.globChars(win32$1);
8513
- const EXTGLOB_CHARS = constants$2.extglobChars(PLATFORM_CHARS);
8565
+ const PLATFORM_CHARS = constants$3.globChars(win32$1);
8566
+ const EXTGLOB_CHARS = constants$3.extglobChars(PLATFORM_CHARS);
8514
8567
  const { DOT_LITERAL: DOT_LITERAL$1, PLUS_LITERAL: PLUS_LITERAL$1, SLASH_LITERAL: SLASH_LITERAL$1, ONE_CHAR: ONE_CHAR$1, DOTS_SLASH: DOTS_SLASH$1, NO_DOT, NO_DOT_SLASH, NO_DOTS_SLASH, QMARK: QMARK$1, QMARK_NO_DOT, STAR, START_ANCHOR: START_ANCHOR$1 } = PLATFORM_CHARS;
8515
8568
  const globstar = (opts$1) => {
8516
8569
  return `(${capture}(?:(?!${START_ANCHOR$1}${opts$1.dot ? DOTS_SLASH$1 : DOT_LITERAL$1}).)*?)`;
@@ -9281,7 +9334,7 @@ var require_parse = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/picom
9281
9334
  if (len > max) throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`);
9282
9335
  input = REPLACEMENTS[input] || input;
9283
9336
  const win32$1 = utils$30.isWindows(options);
9284
- const { DOT_LITERAL: DOT_LITERAL$1, SLASH_LITERAL: SLASH_LITERAL$1, ONE_CHAR: ONE_CHAR$1, DOTS_SLASH: DOTS_SLASH$1, NO_DOT, NO_DOTS, NO_DOTS_SLASH, STAR, START_ANCHOR: START_ANCHOR$1 } = constants$2.globChars(win32$1);
9337
+ const { DOT_LITERAL: DOT_LITERAL$1, SLASH_LITERAL: SLASH_LITERAL$1, ONE_CHAR: ONE_CHAR$1, DOTS_SLASH: DOTS_SLASH$1, NO_DOT, NO_DOTS, NO_DOTS_SLASH, STAR, START_ANCHOR: START_ANCHOR$1 } = constants$3.globChars(win32$1);
9285
9338
  const nodot = opts.dot ? NO_DOTS : NO_DOT;
9286
9339
  const slashDot = opts.dot ? NO_DOTS_SLASH : NO_DOT;
9287
9340
  const capture = opts.capture ? "" : "?:";
@@ -9328,7 +9381,7 @@ var require_picomatch$1 = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm
9328
9381
  const scan = require_scan();
9329
9382
  const parse = require_parse();
9330
9383
  const utils$29 = require_utils$1();
9331
- const constants$1 = require_constants$1();
9384
+ const constants$2 = require_constants$1();
9332
9385
  const isObject$1 = (val) => val && typeof val === "object" && !Array.isArray(val);
9333
9386
  /**
9334
9387
  * Creates a matcher function from one or more glob patterns. The
@@ -9608,7 +9661,7 @@ var require_picomatch$1 = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm
9608
9661
  * Picomatch constants.
9609
9662
  * @return {Object}
9610
9663
  */
9611
- picomatch$1.constants = constants$1;
9664
+ picomatch$1.constants = constants$2;
9612
9665
  /**
9613
9666
  * Expose "picomatch"
9614
9667
  */
@@ -11058,7 +11111,7 @@ var require_lockfile$1 = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/
11058
11111
  };
11059
11112
  })();
11060
11113
  exports$1.getFirstSuitableFolder = (() => {
11061
- var _ref36 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (paths, mode = constants$3.W_OK | constants$3.X_OK) {
11114
+ var _ref36 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (paths, mode = constants$4.W_OK | constants$4.X_OK) {
11062
11115
  const result = {
11063
11116
  skipped: [],
11064
11117
  folder: null
@@ -11146,7 +11199,7 @@ var require_lockfile$1 = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/
11146
11199
  function _interopRequireDefault(obj) {
11147
11200
  return obj && obj.__esModule ? obj : { default: obj };
11148
11201
  }
11149
- const constants$3 = exports$1.constants = typeof (_fs || _load_fs()).default.constants !== "undefined" ? (_fs || _load_fs()).default.constants : {
11202
+ const constants$4 = exports$1.constants = typeof (_fs || _load_fs()).default.constants !== "undefined" ? (_fs || _load_fs()).default.constants : {
11150
11203
  R_OK: (_fs || _load_fs()).default.R_OK,
11151
11204
  W_OK: (_fs || _load_fs()).default.W_OK,
11152
11205
  X_OK: (_fs || _load_fs()).default.X_OK
@@ -18069,6 +18122,156 @@ const createStorageKeyBuilder = (basePath) => (...args) => {
18069
18122
  return [basePath || "", ...args].filter(Boolean).join("/");
18070
18123
  };
18071
18124
 
18125
+ //#endregion
18126
+ //#region src/createTarBr.ts
18127
+ const createTarBrTargetFiles = async ({ outfile, targetFiles }) => {
18128
+ await fs.rm(outfile, { force: true });
18129
+ const tmpDir = path.join(path.dirname(outfile), `.tmp-tar-${Date.now()}`);
18130
+ await fs.mkdir(tmpDir, { recursive: true });
18131
+ try {
18132
+ for (const target of targetFiles) {
18133
+ const sourcePath = target.path;
18134
+ const destPath = path.join(tmpDir, target.name);
18135
+ await fs.mkdir(path.dirname(destPath), { recursive: true });
18136
+ if ((await fs.stat(sourcePath)).isDirectory()) await copyDir$1(sourcePath, destPath);
18137
+ else await fs.copyFile(sourcePath, destPath);
18138
+ }
18139
+ const tmpTarFile = outfile.replace(/\.tar\.br$/, ".tar");
18140
+ await tar.create({
18141
+ file: tmpTarFile,
18142
+ cwd: tmpDir,
18143
+ portable: true,
18144
+ mtime: /* @__PURE__ */ new Date(0),
18145
+ gzip: false
18146
+ }, await fs.readdir(tmpDir));
18147
+ const compressedData = brotliCompressSync(await fs.readFile(tmpTarFile), { params: {
18148
+ [constants.BROTLI_PARAM_QUALITY]: 11,
18149
+ [constants.BROTLI_PARAM_LGWIN]: 24
18150
+ } });
18151
+ await fs.mkdir(path.dirname(outfile), { recursive: true });
18152
+ await fs.writeFile(outfile, compressedData);
18153
+ await fs.rm(tmpTarFile, { force: true });
18154
+ return outfile;
18155
+ } finally {
18156
+ await fs.rm(tmpDir, {
18157
+ recursive: true,
18158
+ force: true
18159
+ });
18160
+ }
18161
+ };
18162
+ async function copyDir$1(src, dest) {
18163
+ await fs.mkdir(dest, { recursive: true });
18164
+ const entries = await fs.readdir(src, { withFileTypes: true });
18165
+ for (const entry of entries) {
18166
+ const srcPath = path.join(src, entry.name);
18167
+ const destPath = path.join(dest, entry.name);
18168
+ if (entry.isDirectory()) await copyDir$1(srcPath, destPath);
18169
+ else await fs.copyFile(srcPath, destPath);
18170
+ }
18171
+ }
18172
+ const createTarBr = async ({ outfile, targetDir, excludeExts = [] }) => {
18173
+ await fs.rm(outfile, { force: true });
18174
+ const tmpTarFile = outfile.replace(/\.tar\.br$/, ".tar");
18175
+ async function getFiles(dir, baseDir = "") {
18176
+ const entries = await fs.readdir(dir, { withFileTypes: true });
18177
+ const files = [];
18178
+ for (const entry of entries) {
18179
+ if (excludeExts.some((pattern) => entry.name.includes(pattern))) continue;
18180
+ const fullPath = path.join(dir, entry.name);
18181
+ const relativePath = path.join(baseDir, entry.name);
18182
+ if (entry.isDirectory()) {
18183
+ const subFiles = await getFiles(fullPath, relativePath);
18184
+ files.push(...subFiles);
18185
+ } else files.push(relativePath);
18186
+ }
18187
+ return files;
18188
+ }
18189
+ const filesToInclude = await getFiles(targetDir);
18190
+ filesToInclude.sort();
18191
+ await tar.create({
18192
+ file: tmpTarFile,
18193
+ cwd: targetDir,
18194
+ portable: true,
18195
+ mtime: /* @__PURE__ */ new Date(0),
18196
+ gzip: false
18197
+ }, filesToInclude);
18198
+ const compressedData = brotliCompressSync(await fs.readFile(tmpTarFile), { params: {
18199
+ [constants.BROTLI_PARAM_QUALITY]: 11,
18200
+ [constants.BROTLI_PARAM_LGWIN]: 24
18201
+ } });
18202
+ await fs.writeFile(outfile, compressedData);
18203
+ await fs.rm(tmpTarFile, { force: true });
18204
+ return outfile;
18205
+ };
18206
+
18207
+ //#endregion
18208
+ //#region src/createTarGz.ts
18209
+ const createTarGzTargetFiles = async ({ outfile, targetFiles }) => {
18210
+ await fs.rm(outfile, { force: true });
18211
+ const tmpDir = path.join(path.dirname(outfile), `.tmp-tar-${Date.now()}`);
18212
+ await fs.mkdir(tmpDir, { recursive: true });
18213
+ try {
18214
+ for (const target of targetFiles) {
18215
+ const sourcePath = target.path;
18216
+ const destPath = path.join(tmpDir, target.name);
18217
+ await fs.mkdir(path.dirname(destPath), { recursive: true });
18218
+ if ((await fs.stat(sourcePath)).isDirectory()) await copyDir(sourcePath, destPath);
18219
+ else await fs.copyFile(sourcePath, destPath);
18220
+ }
18221
+ await fs.mkdir(path.dirname(outfile), { recursive: true });
18222
+ await tar.create({
18223
+ file: outfile,
18224
+ cwd: tmpDir,
18225
+ portable: true,
18226
+ mtime: /* @__PURE__ */ new Date(0),
18227
+ gzip: true
18228
+ }, await fs.readdir(tmpDir));
18229
+ return outfile;
18230
+ } finally {
18231
+ await fs.rm(tmpDir, {
18232
+ recursive: true,
18233
+ force: true
18234
+ });
18235
+ }
18236
+ };
18237
+ async function copyDir(src, dest) {
18238
+ await fs.mkdir(dest, { recursive: true });
18239
+ const entries = await fs.readdir(src, { withFileTypes: true });
18240
+ for (const entry of entries) {
18241
+ const srcPath = path.join(src, entry.name);
18242
+ const destPath = path.join(dest, entry.name);
18243
+ if (entry.isDirectory()) await copyDir(srcPath, destPath);
18244
+ else await fs.copyFile(srcPath, destPath);
18245
+ }
18246
+ }
18247
+ const createTarGz = async ({ outfile, targetDir, excludeExts = [] }) => {
18248
+ await fs.rm(outfile, { force: true });
18249
+ async function getFiles(dir, baseDir = "") {
18250
+ const entries = await fs.readdir(dir, { withFileTypes: true });
18251
+ const files = [];
18252
+ for (const entry of entries) {
18253
+ if (excludeExts.some((pattern) => entry.name.includes(pattern))) continue;
18254
+ const fullPath = path.join(dir, entry.name);
18255
+ const relativePath = path.join(baseDir, entry.name);
18256
+ if (entry.isDirectory()) {
18257
+ const subFiles = await getFiles(fullPath, relativePath);
18258
+ files.push(...subFiles);
18259
+ } else files.push(relativePath);
18260
+ }
18261
+ return files;
18262
+ }
18263
+ const filesToInclude = await getFiles(targetDir);
18264
+ filesToInclude.sort();
18265
+ await tar.create({
18266
+ file: outfile,
18267
+ cwd: targetDir,
18268
+ portable: true,
18269
+ mtime: /* @__PURE__ */ new Date(0),
18270
+ gzip: true
18271
+ }, filesToInclude);
18272
+ return outfile;
18273
+ };
18274
+
18072
18275
  //#endregion
18073
18276
  //#region ../../node_modules/.pnpm/process-nextick-args@2.0.1/node_modules/process-nextick-args/index.js
18074
18277
  var require_process_nextick_args = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/process-nextick-args@2.0.1/node_modules/process-nextick-args/index.js": ((exports, module) => {
@@ -25520,9 +25723,9 @@ var require_pako = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/pako@1
25520
25723
  var assign = require_common().assign;
25521
25724
  var deflate = require_deflate();
25522
25725
  var inflate = require_inflate();
25523
- var constants = require_constants();
25726
+ var constants$1 = require_constants();
25524
25727
  var pako$1 = {};
25525
- assign(pako$1, deflate, inflate, constants);
25728
+ assign(pako$1, deflate, inflate, constants$1);
25526
25729
  module.exports = pako$1;
25527
25730
  }) });
25528
25731
 
@@ -26902,6 +27105,28 @@ const createZip = async ({ outfile, targetDir, excludeExts = [] }) => {
26902
27105
  return outfile;
26903
27106
  };
26904
27107
 
27108
+ //#endregion
27109
+ //#region src/semverSatisfies.ts
27110
+ const semverSatisfies = (targetAppVersion, currentVersion) => {
27111
+ const currentCoerce = semver.coerce(currentVersion);
27112
+ if (!currentCoerce) return false;
27113
+ return semver.satisfies(currentCoerce.version, targetAppVersion);
27114
+ };
27115
+
27116
+ //#endregion
27117
+ //#region src/filterCompatibleAppVersions.ts
27118
+ /**
27119
+ * Filters target app versions that are compatible with the current app version.
27120
+ * Returns only versions that are compatible with the current version according to semver rules.
27121
+ *
27122
+ * @param targetAppVersionList - List of target app versions to filter
27123
+ * @param currentVersion - Current app version
27124
+ * @returns Array of target app versions compatible with the current version
27125
+ */
27126
+ const filterCompatibleAppVersions = (targetAppVersionList, currentVersion) => {
27127
+ return targetAppVersionList.filter((version) => semverSatisfies(version, currentVersion)).sort((a, b) => b.localeCompare(a));
27128
+ };
27129
+
26905
27130
  //#endregion
26906
27131
  //#region src/generateMinBundleId.ts
26907
27132
  const generateMinBundleId = () => {
@@ -26948,6 +27173,7 @@ const getDefaultConfig = () => {
26948
27173
  return {
26949
27174
  releaseChannel: "production",
26950
27175
  updateStrategy: "appVersion",
27176
+ compressStrategy: "zip",
26951
27177
  fingerprint: { extraSources: [] },
26952
27178
  console: { port: 1422 },
26953
27179
  platform: getDefaultPlatformConfig(),
@@ -27059,6 +27285,39 @@ const makeEnv = async (newEnvVars, filePath = ".env.hotupdater") => {
27059
27285
  }
27060
27286
  };
27061
27287
 
27288
+ //#endregion
27289
+ //#region src/parseStorageUri.ts
27290
+ /**
27291
+ * Parses a storage URI and validates the protocol.
27292
+ *
27293
+ * @param storageUri - The storage URI to parse (e.g., "s3://bucket/path/to/file")
27294
+ * @param expectedProtocol - The expected protocol without colon (e.g., "s3", "r2", "gs")
27295
+ * @returns Parsed storage URI components
27296
+ * @throws Error if the URI is invalid or protocol doesn't match
27297
+ *
27298
+ * @example
27299
+ * ```typescript
27300
+ * const { bucket, key } = parseStorageUri("s3://my-bucket/path/to/file.zip", "s3");
27301
+ * // bucket: "my-bucket"
27302
+ * // key: "path/to/file.zip"
27303
+ * ```
27304
+ */
27305
+ function parseStorageUri(storageUri, expectedProtocol) {
27306
+ try {
27307
+ const url = new URL(storageUri);
27308
+ const protocol = url.protocol.replace(":", "");
27309
+ if (protocol !== expectedProtocol) throw new Error(`Invalid storage URI protocol. Expected ${expectedProtocol}, got ${protocol}`);
27310
+ return {
27311
+ protocol,
27312
+ bucket: url.hostname,
27313
+ key: url.pathname.slice(1)
27314
+ };
27315
+ } catch (error) {
27316
+ if (error instanceof TypeError) throw new Error(`Invalid storage URI format: ${storageUri}`);
27317
+ throw error;
27318
+ }
27319
+ }
27320
+
27062
27321
  //#endregion
27063
27322
  //#region src/transformEnv.ts
27064
27323
  const transformEnv = (filename, env$2) => {
@@ -27087,4 +27346,4 @@ function transformTemplate(templateString, values) {
27087
27346
  }
27088
27347
 
27089
27348
  //#endregion
27090
- export { ConfigBuilder, banner, calculatePagination, copyDirToTmp, createBlobDatabasePlugin, createDatabasePlugin, createStorageKeyBuilder, createZip, createZipTargetFiles, generateMinBundleId, getCwd, link, loadConfig, loadConfigSync, log, makeEnv, printBanner, transformEnv, transformTemplate };
27349
+ export { ConfigBuilder, banner, calculatePagination, copyDirToTmp, createBlobDatabasePlugin, createDatabasePlugin, createStorageKeyBuilder, createTarBr, createTarBrTargetFiles, createTarGz, createTarGzTargetFiles, createZip, createZipTargetFiles, detectCompressionFormat, filterCompatibleAppVersions, generateMinBundleId, getCompressionMimeType, getContentType, getCwd, link, loadConfig, loadConfigSync, log, makeEnv, parseStorageUri, printBanner, semverSatisfies, transformEnv, transformTemplate };
package/package.json CHANGED
@@ -1,21 +1,18 @@
1
1
  {
2
2
  "name": "@hot-updater/plugin-core",
3
- "version": "0.20.15",
3
+ "version": "0.21.0",
4
4
  "type": "module",
5
5
  "description": "React Native OTA solution for self-hosted",
6
6
  "sideEffects": false,
7
- "main": "dist/index.cjs",
8
- "module": "dist/index.js",
9
- "types": "dist/index.d.ts",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.cts",
10
10
  "exports": {
11
11
  ".": {
12
12
  "import": "./dist/index.js",
13
13
  "require": "./dist/index.cjs"
14
14
  },
15
- "./test-utils": {
16
- "import": "./src/test-utils/index.ts",
17
- "require": "./src/test-utils/index.ts"
18
- }
15
+ "./package.json": "./package.json"
19
16
  },
20
17
  "files": [
21
18
  "dist",
@@ -45,9 +42,12 @@
45
42
  "cosmiconfig": "9.0.0",
46
43
  "cosmiconfig-typescript-loader": "5.0.0",
47
44
  "fast-glob": "3.3.3",
45
+ "mime": "^4.0.4",
48
46
  "oxc-transform": "0.82.1",
49
47
  "semver": "^7.7.2",
50
- "@hot-updater/core": "0.20.15"
48
+ "tar": "^7.5.1",
49
+ "zlib": "^1.0.5",
50
+ "@hot-updater/core": "0.21.0"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@types/node": "^20",
@@ -58,7 +58,8 @@
58
58
  "picocolors": "1.1.1",
59
59
  "typescript": "5.8.2",
60
60
  "workspace-tools": "^0.36.4",
61
- "@hot-updater/plugin-core": "0.20.15"
61
+ "@hot-updater/plugin-core": "0.21.0",
62
+ "@hot-updater/test-utils": "0.21.0"
62
63
  },
63
64
  "scripts": {
64
65
  "build": "tsdown",