@hot-updater/plugin-core 0.20.14 → 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
@@ -17768,9 +17821,15 @@ function removeBundleInternalKeys(bundle) {
17768
17821
  const { _updateJsonKey, _oldUpdateJsonKey,...pureBundle } = bundle;
17769
17822
  return pureBundle;
17770
17823
  }
17824
+ function normalizeTargetAppVersion(version) {
17825
+ if (!version) return null;
17826
+ return version.replace(/\s+/g, "");
17827
+ }
17771
17828
  function isExactVersion(version) {
17772
17829
  if (!version) return false;
17773
- return semver.valid(version) !== null;
17830
+ const normalized = normalizeTargetAppVersion(version);
17831
+ if (!normalized) return false;
17832
+ return semver.valid(normalized) !== null;
17774
17833
  }
17775
17834
  /**
17776
17835
  * Get all normalized semver versions for a version string.
@@ -17782,8 +17841,9 @@ function isExactVersion(version) {
17782
17841
  * - "1.2.3" generates ["1.2.3"]
17783
17842
  */
17784
17843
  function getSemverNormalizedVersions(version) {
17785
- const coerced = semver.coerce(version);
17786
- if (!coerced) return [version];
17844
+ const normalized = normalizeTargetAppVersion(version) || version;
17845
+ const coerced = semver.coerce(normalized);
17846
+ if (!coerced) return [normalized];
17787
17847
  const versions = /* @__PURE__ */ new Set();
17788
17848
  versions.add(coerced.version);
17789
17849
  if (coerced.patch === 0) versions.add(`${coerced.major}.${coerced.minor}`);
@@ -17903,7 +17963,7 @@ const createBlobDatabasePlugin = ({ name, getContext, listObjects, loadObject, u
17903
17963
  for (const { operation, data } of changedSets) {
17904
17964
  if (data.targetAppVersion !== void 0) isTargetAppVersionChanged = true;
17905
17965
  if (operation === "insert") {
17906
- const target = data.targetAppVersion ?? data.fingerprintHash;
17966
+ const target = normalizeTargetAppVersion(data.targetAppVersion) ?? data.fingerprintHash;
17907
17967
  if (!target) throw new Error("target not found");
17908
17968
  const key = `${data.channel}/${data.platform}/${target}/update.json`;
17909
17969
  const bundleWithKey = {
@@ -17947,7 +18007,7 @@ const createBlobDatabasePlugin = ({ name, getContext, listObjects, loadObject, u
17947
18007
  if (operation === "update") {
17948
18008
  const newChannel = data.channel !== void 0 ? data.channel : bundle.channel;
17949
18009
  const newPlatform = data.platform !== void 0 ? data.platform : bundle.platform;
17950
- const target = data.fingerprintHash ?? bundle.fingerprintHash ?? data.targetAppVersion ?? bundle.targetAppVersion;
18010
+ const target = data.fingerprintHash ?? bundle.fingerprintHash ?? normalizeTargetAppVersion(data.targetAppVersion) ?? normalizeTargetAppVersion(bundle.targetAppVersion);
17951
18011
  if (!target) throw new Error("target not found");
17952
18012
  const newKey = `${newChannel}/${newPlatform}/${target}/update.json`;
17953
18013
  if (newKey !== bundle._updateJsonKey) {
@@ -18047,7 +18107,9 @@ const createBlobDatabasePlugin = ({ name, getContext, listObjects, loadObject, u
18047
18107
  for (const path$4 of updatedPaths) updatedTargetFilePaths.add(path$4);
18048
18108
  }
18049
18109
  for (const path$4 of updatedTargetFilePaths) pathsToInvalidate.add(path$4);
18050
- await invalidatePaths(context, Array.from(pathsToInvalidate));
18110
+ const encondedPaths = /* @__PURE__ */ new Set();
18111
+ for (const path$4 of pathsToInvalidate) encondedPaths.add(encodeURI(path$4));
18112
+ await invalidatePaths(context, Array.from(encondedPaths));
18051
18113
  pendingBundlesMap.clear();
18052
18114
  hooks?.onDatabaseUpdated?.();
18053
18115
  }
@@ -18060,6 +18122,156 @@ const createStorageKeyBuilder = (basePath) => (...args) => {
18060
18122
  return [basePath || "", ...args].filter(Boolean).join("/");
18061
18123
  };
18062
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
+
18063
18275
  //#endregion
18064
18276
  //#region ../../node_modules/.pnpm/process-nextick-args@2.0.1/node_modules/process-nextick-args/index.js
18065
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) => {
@@ -25511,9 +25723,9 @@ var require_pako = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/pako@1
25511
25723
  var assign = require_common().assign;
25512
25724
  var deflate = require_deflate();
25513
25725
  var inflate = require_inflate();
25514
- var constants = require_constants();
25726
+ var constants$1 = require_constants();
25515
25727
  var pako$1 = {};
25516
- assign(pako$1, deflate, inflate, constants);
25728
+ assign(pako$1, deflate, inflate, constants$1);
25517
25729
  module.exports = pako$1;
25518
25730
  }) });
25519
25731
 
@@ -26893,6 +27105,28 @@ const createZip = async ({ outfile, targetDir, excludeExts = [] }) => {
26893
27105
  return outfile;
26894
27106
  };
26895
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
+
26896
27130
  //#endregion
26897
27131
  //#region src/generateMinBundleId.ts
26898
27132
  const generateMinBundleId = () => {
@@ -26939,6 +27173,7 @@ const getDefaultConfig = () => {
26939
27173
  return {
26940
27174
  releaseChannel: "production",
26941
27175
  updateStrategy: "appVersion",
27176
+ compressStrategy: "zip",
26942
27177
  fingerprint: { extraSources: [] },
26943
27178
  console: { port: 1422 },
26944
27179
  platform: getDefaultPlatformConfig(),
@@ -27050,6 +27285,39 @@ const makeEnv = async (newEnvVars, filePath = ".env.hotupdater") => {
27050
27285
  }
27051
27286
  };
27052
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
+
27053
27321
  //#endregion
27054
27322
  //#region src/transformEnv.ts
27055
27323
  const transformEnv = (filename, env$2) => {
@@ -27078,4 +27346,4 @@ function transformTemplate(templateString, values) {
27078
27346
  }
27079
27347
 
27080
27348
  //#endregion
27081
- 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.14",
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.14"
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.14"
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",