@hot-updater/cli-tools 0.36.7 → 1.0.0-rc.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.d.mts CHANGED
@@ -1,8 +1,8 @@
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 { Bundle, BundleSigningPlugin, ConfigInput, LocalSigningConfig, Platform, RequiredDeep, SigningConfig, StoragePluginWith, StoragePutResult } from "@hot-updater/plugin-core";
4
5
  import * as _$_hot_updater_core0 from "@hot-updater/core";
5
- import { Bundle, ConfigInput, DatabasePlugin, NodeStoragePlugin, Platform, RequiredDeep } from "@hot-updater/plugin-core";
6
6
 
7
7
  //#region src/BuildLogger.d.ts
8
8
  type LinePattern = string | RegExp;
@@ -33,11 +33,28 @@ declare class BuildLogger {
33
33
  private showLogFileLocation;
34
34
  }
35
35
  //#endregion
36
+ //#region src/apiKeyNote.d.ts
37
+ declare const formatApiKeyNote: (apiKey: string) => string;
38
+ //#endregion
36
39
  //#region src/banner.d.ts
37
40
  declare const link: (url: string) => string;
38
41
  declare const banner: (version?: string) => string;
39
42
  declare const printBanner: (version?: string) => void;
40
43
  //#endregion
44
+ //#region src/bundleSigning.d.ts
45
+ interface BundleSigningSession {
46
+ readonly name: string;
47
+ readonly publicKey: string;
48
+ readonly signFileHash: (fileHash: string) => Promise<string>;
49
+ }
50
+ declare const prepareBundleSigning: (signing: SigningConfig | undefined, options?: {
51
+ readonly cwd?: string;
52
+ }) => Promise<BundleSigningSession | null>;
53
+ /** Resolves the native trust anchor without calling a remote signing provider. */
54
+ declare const getBundleSigningPublicKey: (signing: SigningConfig | undefined, options?: {
55
+ readonly cwd?: string;
56
+ }) => Promise<string | null>;
57
+ //#endregion
41
58
  //#region src/ConfigBuilder.d.ts
42
59
  type BuildType = "bare" | "rock" | "expo";
43
60
  type ImportInfo = {
@@ -246,6 +263,7 @@ type ManagedHelperStatement = {
246
263
  name: string;
247
264
  code: string;
248
265
  strategy: ManagedHelperStrategy;
266
+ replaceIncompatibleProperties?: string[];
249
267
  };
250
268
  type CreateHotUpdaterConfigScaffoldOptions = {
251
269
  build: BuildType;
@@ -334,6 +352,31 @@ declare const HotUpdateDirUtil: {
334
352
  }) => string;
335
353
  };
336
354
  //#endregion
355
+ //#region src/infrastructureGeneration.d.ts
356
+ type Fetch = typeof fetch;
357
+ declare const assertInfrastructureGenerationPayload: ({
358
+ payload,
359
+ provider,
360
+ resource
361
+ }: {
362
+ readonly payload: unknown;
363
+ readonly provider: string;
364
+ readonly resource: string;
365
+ }) => void;
366
+ declare const assertInfrastructureGenerationAtUrl: ({
367
+ fetchImpl,
368
+ legacyStatuses,
369
+ provider,
370
+ resource,
371
+ versionUrl
372
+ }: {
373
+ readonly fetchImpl?: Fetch;
374
+ readonly legacyStatuses?: readonly number[];
375
+ readonly provider: string;
376
+ readonly resource: string;
377
+ readonly versionUrl: string;
378
+ }) => Promise<void>;
379
+ //#endregion
337
380
  //#region src/initProvider.d.ts
338
381
  type InitProviderInputPersistence = "always" | "with-consent";
339
382
  type InitProviderInputDefinition = {
@@ -432,6 +475,10 @@ declare class MissingInitInputsError extends InitError {
432
475
  declare class InitEnvFileError extends InitError {
433
476
  readonly name = "InitEnvFileError";
434
477
  }
478
+ declare class LegacyInfrastructureError extends InitError {
479
+ readonly name = "LegacyInfrastructureError";
480
+ constructor(provider: string, resource: string);
481
+ }
435
482
  declare const assertInitInputs: ({
436
483
  inputs,
437
484
  strict
@@ -457,12 +504,19 @@ declare const createLogWriter: ({
457
504
  logFilePath?: string;
458
505
  }) => Promise<HotUpdaterLogWriter>;
459
506
  //#endregion
507
+ //#region src/localBundleSigning.d.ts
508
+ declare const normalizeSigningConfig: (signing: SigningConfig | undefined) => BundleSigningPlugin | Extract<LocalSigningConfig, {
509
+ enabled: true;
510
+ }> | undefined;
511
+ //#endregion
460
512
  //#region src/loadConfig.d.ts
461
513
  type HotUpdaterConfigOptions = {
462
514
  platform: Platform;
463
515
  channel: string;
464
516
  } | null;
465
- type ConfigResponse = RequiredDeep<ConfigInput>;
517
+ type ConfigResponse = RequiredDeep<Omit<ConfigInput, "database" | "signing" | "storage">> & Pick<ConfigInput, "database" | "storage"> & {
518
+ signing?: ReturnType<typeof normalizeSigningConfig>;
519
+ };
466
520
  declare const loadConfig: (options: HotUpdaterConfigOptions) => Promise<ConfigResponse>;
467
521
  //#endregion
468
522
  //#region src/log.d.ts
@@ -486,63 +540,34 @@ declare const makeEnv: (newEnvVars: Record<string, EnvVarValue>, filePath?: stri
486
540
  }) => Promise<string>;
487
541
  //#endregion
488
542
  //#region src/promoteBundle.d.ts
543
+ type PromoteStoragePlugin = StoragePluginWith<"get" | "put" | "exists" | "delete">;
489
544
  declare const LEGACY_BUNDLE_ERROR = "This OTA bundle was created by a version that does not support manifest.json. Copy bundle is not available.";
490
- interface PromoteBundleInput {
491
- action: "copy" | "move";
492
- bundleId: string;
493
- nextBundleId?: string;
494
- targetChannel: string;
495
- }
496
- interface PromoteBundleDependencies {
497
- config: ConfigResponse;
498
- databasePlugin: DatabasePlugin;
499
- storagePlugin: NodeStoragePlugin | null;
500
- }
501
545
  declare function createCopiedBundleArchive({
502
546
  bundle,
503
547
  config,
504
548
  nextBundleId,
505
- storagePlugin,
506
- targetChannel
549
+ storagePlugin
507
550
  }: {
508
551
  bundle: Bundle;
509
552
  config: ConfigResponse;
510
553
  nextBundleId: string;
511
- storagePlugin: NodeStoragePlugin;
512
- targetChannel: string;
554
+ storagePlugin: PromoteStoragePlugin;
513
555
  }): Promise<{
514
556
  bundle: {
515
557
  id: string;
516
- channel: string;
558
+ archiveByteSize: number;
517
559
  storageUri: string;
518
560
  fileHash: string;
519
561
  metadata: _$_hot_updater_core0.BundleMetadata | undefined;
520
562
  assetBaseStorageUri: string;
521
563
  patches: never[];
522
- patchBaseBundleId: null;
523
564
  manifestFileHash: string;
524
565
  manifestStorageUri: string;
525
- patchBaseFileHash: null;
526
- patchFileHash: null;
527
- patchStorageUri: null;
528
566
  platform: _$_hot_updater_core0.Platform;
529
- shouldForceUpdate: boolean;
530
- enabled: boolean;
531
567
  gitCommitHash: string | null;
532
- message: string | null;
533
- targetAppVersion: string | null;
534
- fingerprintHash: string | null;
535
- rolloutCohortCount?: number | null;
536
- targetCohorts?: string[] | null;
537
568
  };
538
569
  uploadedStorageUris: string[];
539
570
  }>;
540
- declare function promoteBundle({
541
- action,
542
- bundleId,
543
- nextBundleId,
544
- targetChannel
545
- }: PromoteBundleInput, deps: PromoteBundleDependencies): Promise<Bundle>;
546
571
  //#endregion
547
572
  //#region ../../node_modules/.pnpm/@clack+core@1.4.3/node_modules/@clack/core/dist/index.d.mts
548
573
  declare const actions: readonly ["up", "down", "left", "right", "space", "enter", "cancel"];
@@ -1792,6 +1817,14 @@ declare const resolveHotUpdaterServerVersion: (currentPackageName: string, optio
1792
1817
  searchFrom?: string;
1793
1818
  }) => string;
1794
1819
  //#endregion
1820
+ //#region src/storageFiles.d.ts
1821
+ declare const getStorageFileByteSize: (filePath: string) => Promise<number>;
1822
+ declare const putStorageFile: (storage: StoragePluginWith<"put">, key: string, filePath: string) => Promise<StoragePutResult & {
1823
+ byteSize: number;
1824
+ }>;
1825
+ declare const writeStorageFile: (storage: StoragePluginWith<"get">, storageUri: string, filePath: string) => Promise<void>;
1826
+ declare const writeStorageResponseFile: (response: Response, filePath: string) => Promise<void>;
1827
+ //#endregion
1795
1828
  //#region src/transformEnv.d.ts
1796
1829
  declare const transformEnv: <T extends Record<string, string>>(filename: string, env: T) => string;
1797
1830
  //#endregion
@@ -1809,4 +1842,4 @@ type TransformTemplateArgs<T extends string> = { [Key in ExtractPlaceholders<T>]
1809
1842
  */
1810
1843
  declare function transformTemplate<T extends string>(templateString: T, values: TransformTemplateArgs<T>): string;
1811
1844
  //#endregion
1812
- export { BuildLogger, BuildLoggerConfig, BuildType, ConfigBuilder, ConfigBuilderScaffold, ConfigResponse, CreateHotUpdaterConfigScaffoldFromBuilderOptions, CreateHotUpdaterConfigScaffoldOptions, HOT_UPDATER_SERVER_PACKAGE_VERSION_ENV, HotUpdateDirUtil, HotUpdaterConfigOptions, HotUpdaterConfigScaffold, HotUpdaterInitEnv, HotUpdaterLogWriter, IConfigBuilder, ImportInfo, InitEnvFileError, InitError, InitProviderDefinition, InitProviderInputDefinition, InitProviderInputPersistence, LEGACY_BUNDLE_ERROR, ManagedHelperStatement, ManagedHelperStrategy, MissingInitInputsError, PromoteBundleDependencies, PromoteBundleInput, PromptProgress, PromptSpinner, ProviderConfig, ReactNativeMetadata, ReadPackageUpResult, RunInitOptions, WriteHotUpdaterConfigResult, assertInitInputs, assertInitProviderInputs, banner, typedColors as colors, confirmInitInputPersistence, copyDirToTmp, createCopiedBundleArchive, createHotUpdaterConfigScaffold, createHotUpdaterConfigScaffoldFromBuilder, createLogWriter, createTarBr, createTarBrTargetFiles, createTarGz, createTarGzTargetFiles, createZip, createZipTargetFiles, decryptJson, defineInitProvider, encryptJson, ensureInstallPackages, getAndroidSdkPath, getCwd, getHotUpdaterEnvValue, getHotUpdaterInitInputEnv, getInitProviderEnvVars, getInitProviderTextPromptValues, getMissingInitInputs, getMissingInitProviderInputs, getPackageManager, getReactNativeMetadatas, link, loadConfig, log, makeEnv, p, printBanner, promoteBundle, readHotUpdaterEnv, readHotUpdaterInitEnv, readPackageUp, renderImportStatements, resolveHotUpdaterServerVersion, resolveInitProviderInput, resolveInitProviderInputs, resolvePackageVersion, shouldAutoSelectOnlyInitResource, stripAnsi, transformEnv, transformTemplate, writeHotUpdaterConfig };
1845
+ export { BuildLogger, BuildLoggerConfig, BuildType, BundleSigningSession, ConfigBuilder, ConfigBuilderScaffold, ConfigResponse, CreateHotUpdaterConfigScaffoldFromBuilderOptions, CreateHotUpdaterConfigScaffoldOptions, HOT_UPDATER_SERVER_PACKAGE_VERSION_ENV, HotUpdateDirUtil, HotUpdaterConfigOptions, HotUpdaterConfigScaffold, HotUpdaterInitEnv, HotUpdaterLogWriter, IConfigBuilder, ImportInfo, InitEnvFileError, InitError, InitProviderDefinition, InitProviderInputDefinition, InitProviderInputPersistence, LEGACY_BUNDLE_ERROR, LegacyInfrastructureError, ManagedHelperStatement, ManagedHelperStrategy, MissingInitInputsError, PromptProgress, PromptSpinner, ProviderConfig, ReactNativeMetadata, ReadPackageUpResult, RunInitOptions, WriteHotUpdaterConfigResult, assertInfrastructureGenerationAtUrl, assertInfrastructureGenerationPayload, assertInitInputs, assertInitProviderInputs, banner, typedColors as colors, confirmInitInputPersistence, copyDirToTmp, createCopiedBundleArchive, createHotUpdaterConfigScaffold, createHotUpdaterConfigScaffoldFromBuilder, createLogWriter, createTarBr, createTarBrTargetFiles, createTarGz, createTarGzTargetFiles, createZip, createZipTargetFiles, decryptJson, defineInitProvider, encryptJson, ensureInstallPackages, formatApiKeyNote, getAndroidSdkPath, getBundleSigningPublicKey, getCwd, getHotUpdaterEnvValue, getHotUpdaterInitInputEnv, getInitProviderEnvVars, getInitProviderTextPromptValues, getMissingInitInputs, getMissingInitProviderInputs, getPackageManager, getReactNativeMetadatas, getStorageFileByteSize, link, loadConfig, log, makeEnv, p, prepareBundleSigning, printBanner, putStorageFile, readHotUpdaterEnv, readHotUpdaterInitEnv, readPackageUp, renderImportStatements, resolveHotUpdaterServerVersion, resolveInitProviderInput, resolveInitProviderInputs, resolvePackageVersion, shouldAutoSelectOnlyInitResource, stripAnsi, transformEnv, transformTemplate, writeHotUpdaterConfig, writeStorageFile, writeStorageResponseFile };
package/dist/index.mjs CHANGED
@@ -17,6 +17,8 @@ import tty, { ReadStream } from "node:tty";
17
17
  import fs$2, { appendFileSync, createReadStream, createWriteStream as createWriteStream$1, 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
19
  import os, { constants } from "node:os";
20
+ import crypto$1, { randomBytes, randomUUID } from "node:crypto";
21
+ import fs$3 from "node:fs/promises";
20
22
  import { pipeline } from "stream/promises";
21
23
  import * as Ms from "zlib";
22
24
  import Jr, { constants as constants$1, createBrotliCompress } from "zlib";
@@ -24,8 +26,6 @@ import { EventEmitter as EventEmitter$1, addAbortListener, on, once, setMaxListe
24
26
  import As, { Duplex, PassThrough as PassThrough$1, Readable as Readable$1, Transform, Writable, getDefaultHighWaterMark } from "node:stream";
25
27
  import { StringDecoder } from "node:string_decoder";
26
28
  import no from "node:assert";
27
- import crypto$1, { randomBytes, randomUUID } from "node:crypto";
28
- import fs$3 from "node:fs/promises";
29
29
  import { fileURLToPath } from "node:url";
30
30
  import { ChildProcess, execFile, spawn, spawnSync } from "node:child_process";
31
31
  import { scheduler, setImmediate as setImmediate$1, setTimeout as setTimeout$1 } from "node:timers/promises";
@@ -33,10 +33,10 @@ import { serialize } from "node:v8";
33
33
  import { finished, pipeline as pipeline$1 } from "node:stream/promises";
34
34
  import { Buffer as Buffer$2 } from "node:buffer";
35
35
  import { parseSync } from "oxc-parser";
36
+ import { createBundleStorageKey, createDatabasePlugin, createStoragePlugin, createStorageRootUriWithPath, detectCompressionFormat, getContentType, getManifestAssetDownloadPath, getManifestAssetStoragePath, isContentAddressedAssetFileHash, parseStorageUri, resolveManifestAssetStorageUri } from "@hot-updater/plugin-core";
36
37
  import { loadConfig as loadConfig$1 } from "unconfig";
37
- import { brotliDecompressSync, createBrotliCompress as createBrotliCompress$1 } from "node:zlib";
38
+ import { brotliDecompressSync, constants as constants$2, createBrotliCompress as createBrotliCompress$1 } from "node:zlib";
38
39
  import { getManifestFileHash, stripBundleArtifactMetadata } from "@hot-updater/core";
39
- import { createBundleStorageKey, createStorageRootUriWithPath, createUUIDv7, detectCompressionFormat, getContentAddressedAssetStoragePath, getManifestAssetDownloadPath, resolveManifestAssetStorageUri } from "@hot-updater/plugin-core";
40
40
  import { transformSync } from "oxc-transform";
41
41
  //#endregion
42
42
  //#region src/colors.ts
@@ -22587,6 +22587,9 @@ var BuildLogger = class {
22587
22587
  }
22588
22588
  };
22589
22589
  //#endregion
22590
+ //#region src/apiKeyNote.ts
22591
+ const formatApiKeyNote = (apiKey) => apiKey;
22592
+ //#endregion
22590
22593
  //#region ../../node_modules/.pnpm/ansi-regex@6.2.2/node_modules/ansi-regex/index.js
22591
22594
  function ansiRegex({ onlyFirst = false } = {}) {
22592
22595
  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");
@@ -24507,6 +24510,187 @@ const printBanner = (version) => {
24507
24510
  console.log(banner(version));
24508
24511
  };
24509
24512
  //#endregion
24513
+ //#region src/localBundleSigning.ts
24514
+ const resolvePath$1 = (cwd, filePath) => path$1.isAbsolute(filePath) ? filePath : path$1.resolve(cwd, filePath);
24515
+ const loadPrivateKey = async (privateKeyPath) => {
24516
+ try {
24517
+ const privateKey = crypto$1.createPrivateKey(await fs$3.readFile(privateKeyPath, "utf8"));
24518
+ if (privateKey.asymmetricKeyType !== "rsa" || (privateKey.asymmetricKeyDetails?.modulusLength ?? 0) < 2048) throw new Error("not rsa");
24519
+ return privateKey;
24520
+ } catch {
24521
+ throw new Error("Failed to load the local bundle signing private key.");
24522
+ }
24523
+ };
24524
+ const createLocalSigningPlugin = ({ privateKeyPath, publicKeyPath }) => {
24525
+ const privateKeys = /* @__PURE__ */ new Map();
24526
+ const getPrivateKey = (cwd = process.cwd()) => {
24527
+ const resolvedPath = resolvePath$1(cwd, privateKeyPath);
24528
+ const cached = privateKeys.get(resolvedPath);
24529
+ if (cached) return cached;
24530
+ const pending = loadPrivateKey(resolvedPath).catch((error) => {
24531
+ privateKeys.delete(resolvedPath);
24532
+ throw error;
24533
+ });
24534
+ privateKeys.set(resolvedPath, pending);
24535
+ return pending;
24536
+ };
24537
+ return {
24538
+ name: "localSigning",
24539
+ publicKeyPath,
24540
+ async getPublicKey({ cwd } = {}) {
24541
+ const privateKey = await getPrivateKey(cwd);
24542
+ return { publicKey: crypto$1.createPublicKey(privateKey).export({
24543
+ format: "pem",
24544
+ type: "spki"
24545
+ }).toString() };
24546
+ },
24547
+ async sign({ message, cwd }) {
24548
+ if (!(message instanceof Uint8Array) || message.byteLength !== 32) throw new Error("Local bundle signing messages must be exactly 32 bytes.");
24549
+ const privateKey = await getPrivateKey(cwd);
24550
+ return { signature: crypto$1.sign("RSA-SHA256", message, privateKey) };
24551
+ }
24552
+ };
24553
+ };
24554
+ const invalidSigningConfig = () => /* @__PURE__ */ new Error("Bundle signing must be a local key config or signing plugin. Omit signing to disable it.");
24555
+ const normalizeSigningConfig = (signing) => {
24556
+ if (signing === void 0) return void 0;
24557
+ if (typeof signing !== "object" || signing === null) throw invalidSigningConfig();
24558
+ const hasPrivateKeyPath = Reflect.has(signing, "privateKeyPath");
24559
+ const hasEnabled = Reflect.has(signing, "enabled");
24560
+ const hasPluginMembers = [
24561
+ "name",
24562
+ "getPublicKey",
24563
+ "sign"
24564
+ ].some((key) => Reflect.has(signing, key));
24565
+ if ((hasPrivateKeyPath || hasEnabled) && hasPluginMembers) throw new Error("Bundle signing config cannot combine local signing fields with signing plugin fields.");
24566
+ if (!hasPluginMembers) {
24567
+ if (Object.keys(signing).some((key) => key !== "enabled" && key !== "privateKeyPath" && key !== "publicKeyPath")) throw new Error("Local bundle signing accepts only enabled, privateKeyPath and publicKeyPath.");
24568
+ const enabled = Reflect.get(signing, "enabled");
24569
+ if (enabled === false || enabled === void 0) return void 0;
24570
+ if (enabled !== true) throw invalidSigningConfig();
24571
+ const privateKeyPath = Reflect.get(signing, "privateKeyPath");
24572
+ const publicKeyPath = Reflect.get(signing, "publicKeyPath");
24573
+ if (typeof privateKeyPath !== "string" || !privateKeyPath.trim()) throw new Error("Enabled local bundle signing requires privateKeyPath.");
24574
+ if (publicKeyPath !== void 0 && (typeof publicKeyPath !== "string" || !publicKeyPath.trim())) throw new Error("Local bundle signing publicKeyPath must be a non-empty path when provided.");
24575
+ return {
24576
+ enabled: true,
24577
+ privateKeyPath,
24578
+ ...publicKeyPath === void 0 ? {} : { publicKeyPath }
24579
+ };
24580
+ }
24581
+ if (typeof Reflect.get(signing, "name") !== "string" || typeof Reflect.get(signing, "publicKeyPath") !== "string" || !Reflect.get(signing, "publicKeyPath").trim() || typeof Reflect.get(signing, "getPublicKey") !== "function" || typeof Reflect.get(signing, "sign") !== "function") throw invalidSigningConfig();
24582
+ return signing;
24583
+ };
24584
+ //#endregion
24585
+ //#region src/bundleSigning.ts
24586
+ const FILE_HASH_PATTERN = /^[a-f\d]{64}$/iu;
24587
+ const resolvePath = (cwd, filePath) => path$1.isAbsolute(filePath) ? filePath : path$1.resolve(cwd, filePath);
24588
+ const parseRsaPublicKey = (publicKeyPEM) => {
24589
+ try {
24590
+ const normalizedPublicKey = publicKeyPEM.trim();
24591
+ if (!normalizedPublicKey.startsWith("-----BEGIN PUBLIC KEY-----") || !normalizedPublicKey.endsWith("-----END PUBLIC KEY-----")) throw new Error("not spki");
24592
+ const publicKey = crypto$1.createPublicKey(normalizedPublicKey);
24593
+ if (publicKey.asymmetricKeyType !== "rsa" || (publicKey.asymmetricKeyDetails?.modulusLength ?? 0) < 2048) throw new Error("not rsa");
24594
+ return publicKey;
24595
+ } catch {
24596
+ throw new Error("Bundle signing public key must be a valid RSA SPKI PEM key with a modulus of at least 2048 bits.");
24597
+ }
24598
+ };
24599
+ const exportPublicKey = (publicKey) => publicKey.export({
24600
+ type: "spki",
24601
+ format: "pem"
24602
+ }).toString();
24603
+ const publicKeysMatch = (left, right) => {
24604
+ const leftDer = left.export({
24605
+ type: "spki",
24606
+ format: "der"
24607
+ });
24608
+ const rightDer = right.export({
24609
+ type: "spki",
24610
+ format: "der"
24611
+ });
24612
+ return leftDer.byteLength === rightDer.byteLength && crypto$1.timingSafeEqual(leftDer, rightDer);
24613
+ };
24614
+ const readKeyFile = async (cwd, filePath) => {
24615
+ try {
24616
+ return await fs$3.readFile(resolvePath(cwd, filePath), "utf8");
24617
+ } catch {
24618
+ throw new Error("Failed to read the bundle signing public key file.");
24619
+ }
24620
+ };
24621
+ const getProviderPublicKey = async (provider, cwd) => {
24622
+ try {
24623
+ const result = await provider.getPublicKey({ cwd });
24624
+ if (!result || typeof result.publicKey !== "string") throw new Error("invalid result");
24625
+ return parseRsaPublicKey(result.publicKey);
24626
+ } catch {
24627
+ throw new Error("Failed to resolve the bundle signing provider public key.");
24628
+ }
24629
+ };
24630
+ const createMemoizedSigner = ({ publicKey, sign }) => {
24631
+ const signatures = /* @__PURE__ */ new Map();
24632
+ return (fileHash) => {
24633
+ if (!FILE_HASH_PATTERN.test(fileHash)) return Promise.reject(/* @__PURE__ */ new Error("Bundle signing requires a 64-character hexadecimal file hash."));
24634
+ const normalizedFileHash = fileHash.toLowerCase();
24635
+ const cached = signatures.get(normalizedFileHash);
24636
+ if (cached) return cached;
24637
+ const pending = (async () => {
24638
+ const message = Buffer.from(normalizedFileHash, "hex");
24639
+ let signature;
24640
+ try {
24641
+ signature = await sign(new Uint8Array(message));
24642
+ } catch {
24643
+ throw new Error("Bundle signing provider failed to sign the file hash.");
24644
+ }
24645
+ if (!(signature instanceof Uint8Array) || signature.byteLength === 0) throw new Error("Bundle signing provider returned an invalid signature.");
24646
+ if (!crypto$1.verify("RSA-SHA256", message, publicKey, signature)) throw new Error("Bundle signing provider returned a signature that does not match the configured public key.");
24647
+ return Buffer.from(signature).toString("base64");
24648
+ })().catch((error) => {
24649
+ signatures.delete(normalizedFileHash);
24650
+ throw error;
24651
+ });
24652
+ signatures.set(normalizedFileHash, pending);
24653
+ return pending;
24654
+ };
24655
+ };
24656
+ const preparePluginSigning = async (signing, cwd) => {
24657
+ const [configuredPublicKeyPEM, providerPublicKey] = await Promise.all([signing.publicKeyPath === void 0 ? void 0 : readKeyFile(cwd, signing.publicKeyPath), getProviderPublicKey(signing, cwd)]);
24658
+ if (!publicKeysMatch(configuredPublicKeyPEM === void 0 ? providerPublicKey : parseRsaPublicKey(configuredPublicKeyPEM), providerPublicKey)) throw new Error("Bundle signing provider public key does not match publicKeyPath.");
24659
+ return {
24660
+ name: signing.name,
24661
+ publicKey: exportPublicKey(providerPublicKey),
24662
+ signFileHash: createMemoizedSigner({
24663
+ publicKey: providerPublicKey,
24664
+ sign: async (message) => {
24665
+ return (await signing.sign({
24666
+ cwd,
24667
+ message
24668
+ })).signature;
24669
+ }
24670
+ })
24671
+ };
24672
+ };
24673
+ const prepareBundleSigning = async (signing, options = {}) => {
24674
+ const normalized = normalizeSigningConfig(signing);
24675
+ if (!normalized) return null;
24676
+ const cwd = options.cwd ?? getCwd();
24677
+ return preparePluginSigning("enabled" in normalized ? createLocalSigningPlugin(normalized) : normalized, cwd);
24678
+ };
24679
+ /** Resolves the native trust anchor without calling a remote signing provider. */
24680
+ const getBundleSigningPublicKey = async (signing, options = {}) => {
24681
+ const normalized = normalizeSigningConfig(signing);
24682
+ if (!normalized) return null;
24683
+ const cwd = options.cwd ?? getCwd();
24684
+ if (normalized.publicKeyPath !== void 0) return exportPublicKey(parseRsaPublicKey(await readKeyFile(cwd, normalized.publicKeyPath)));
24685
+ if ("enabled" in normalized) try {
24686
+ const { publicKey } = await createLocalSigningPlugin(normalized).getPublicKey({ cwd });
24687
+ return exportPublicKey(parseRsaPublicKey(publicKey));
24688
+ } catch {
24689
+ return exportPublicKey(parseRsaPublicKey(await readKeyFile(cwd, path$1.join(path$1.dirname(normalized.privateKeyPath), "public-key.pem"))));
24690
+ }
24691
+ throw new Error("Bundle signing plugins require publicKeyPath.");
24692
+ };
24693
+ //#endregion
24510
24694
  //#region src/ConfigBuilder.ts
24511
24695
  const normalizeImportInfos = (imports) => {
24512
24696
  const collectedImports = /* @__PURE__ */ new Map();
@@ -42992,8 +43176,10 @@ const CONFIG_FILE_NAME = "hot-updater.config.ts";
42992
43176
  const MANAGED_IMPORT_PACKAGES = new Set([
42993
43177
  "dotenv",
42994
43178
  "firebase-admin",
43179
+ "firebase-admin/app",
42995
43180
  "hot-updater",
42996
43181
  "@aws-sdk/credential-provider-sso",
43182
+ "@aws-sdk/credential-providers",
42997
43183
  "@hot-updater/aws",
42998
43184
  "@hot-updater/bare",
42999
43185
  "@hot-updater/cloudflare",
@@ -43002,7 +43188,12 @@ const MANAGED_IMPORT_PACKAGES = new Set([
43002
43188
  "@hot-updater/rock",
43003
43189
  "@hot-updater/supabase"
43004
43190
  ]);
43005
- const MANAGED_HELPER_NAMES = new Set(["commonOptions", "credential"]);
43191
+ const MANAGED_HELPER_NAMES = new Set([
43192
+ "awsOptions",
43193
+ "commonOptions",
43194
+ "credential",
43195
+ "storageOptions"
43196
+ ]);
43006
43197
  const KNOWN_BUILD_CALLEES = new Set([
43007
43198
  "bare",
43008
43199
  "expo",
@@ -43096,7 +43287,7 @@ const appendMissingProperties = (objectText, propertyTexts, hasExistingPropertie
43096
43287
  const suffix = `,\n${closingIndent}`;
43097
43288
  return `${objectText.slice(0, closeBraceIndex)}${prefix}${formattedProperties}${suffix}${objectText.slice(closeBraceIndex)}`;
43098
43289
  };
43099
- const mergeObjectLiteralText = (existingObject, newObject) => {
43290
+ const mergeObjectLiteralText = (existingObject, newObject, replaceIncompatibleProperties = []) => {
43100
43291
  const existingText = getNodeText(existingObject.source, existingObject.objectExpression);
43101
43292
  const existingPropertyNames = /* @__PURE__ */ new Set();
43102
43293
  const existingSpreadTexts = /* @__PURE__ */ new Set();
@@ -43111,6 +43302,17 @@ const mergeObjectLiteralText = (existingObject, newObject) => {
43111
43302
  existingPropertyNames.add(propertyName);
43112
43303
  const nextProperty = newObject.objectExpression.properties.find((candidate) => getObjectPropertyName(candidate) === propertyName);
43113
43304
  if (!nextProperty || !isDataProperty(property) || !isDataProperty(nextProperty)) continue;
43305
+ const existingCallee = getCallCallee(property.value);
43306
+ const nextCallee = getCallCallee(nextProperty.value);
43307
+ const hasIncompatibleValue = existingCallee !== null && nextCallee !== null && existingCallee !== nextCallee || property.value.type === "ObjectExpression" !== (nextProperty.value.type === "ObjectExpression");
43308
+ if (replaceIncompatibleProperties.includes(propertyName) && hasIncompatibleValue) {
43309
+ edits.push({
43310
+ start: property.value.start - existingObject.objectExpression.start,
43311
+ end: property.value.end - existingObject.objectExpression.start,
43312
+ text: getNodeText(newObject.source, nextProperty.value)
43313
+ });
43314
+ continue;
43315
+ }
43114
43316
  if (property.value.type === "ObjectExpression" && nextProperty.value.type === "ObjectExpression") {
43115
43317
  const mergedValue = mergeObjectLiteralText({
43116
43318
  objectExpression: property.value,
@@ -43118,7 +43320,7 @@ const mergeObjectLiteralText = (existingObject, newObject) => {
43118
43320
  }, {
43119
43321
  objectExpression: nextProperty.value,
43120
43322
  source: newObject.source
43121
- });
43323
+ }, replaceIncompatibleProperties);
43122
43324
  if (!mergedValue) return null;
43123
43325
  edits.push({
43124
43326
  start: property.value.start - existingObject.objectExpression.start,
@@ -43175,7 +43377,7 @@ const mergeHelperStatement = (existingStatementText, helper) => {
43175
43377
  }, {
43176
43378
  objectExpression: nextInitializer,
43177
43379
  source: nextStatement.source
43178
- });
43380
+ }, helper.replaceIncompatibleProperties);
43179
43381
  if (!mergedInitializer) return null;
43180
43382
  return `${existingStatement.statement.kind === "let" || existingStatement.statement.kind === "var" ? existingStatement.statement.kind : "const"} ${helper.name} = ${mergedInitializer};`;
43181
43383
  };
@@ -43230,7 +43432,7 @@ const getManagedHelperName = (statement) => {
43230
43432
  if (statement.type !== "VariableDeclaration") return null;
43231
43433
  const declaration = statement.declarations[0];
43232
43434
  if (declaration?.id.type !== "Identifier") return null;
43233
- return MANAGED_HELPER_NAMES.has(declaration.id.name) ? declaration.id.name : null;
43435
+ return declaration.id.name;
43234
43436
  };
43235
43437
  const rebuildImportBlock = (source, scaffold) => {
43236
43438
  const importDeclarations = source.program.body.filter((statement) => statement.type === "ImportDeclaration");
@@ -43247,7 +43449,7 @@ const rebuildImportBlock = (source, scaffold) => {
43247
43449
  return {
43248
43450
  start: getTopLevelFullStart(source, firstImport),
43249
43451
  end: lastImport.end,
43250
- text: `${nextImportBlock}\n\n`
43452
+ text: nextImportBlock
43251
43453
  };
43252
43454
  };
43253
43455
  const rebuildManagedBody = (source, exportStart, scaffold) => {
@@ -43255,10 +43457,14 @@ const rebuildManagedBody = (source, exportStart, scaffold) => {
43255
43457
  const managedHelpers = new Map(scaffold.helperStatements.map((statement) => [statement.name, statement]));
43256
43458
  const emittedHelpers = /* @__PURE__ */ new Set();
43257
43459
  const bodyStatements = [];
43460
+ const configStatements = [];
43258
43461
  for (const statement of statementsBeforeExport) {
43259
- if (isConfigCallStatement(statement)) continue;
43462
+ if (isConfigCallStatement(statement)) {
43463
+ configStatements.push(getStatementText(source, statement));
43464
+ continue;
43465
+ }
43260
43466
  const helperName = getManagedHelperName(statement);
43261
- if (!helperName) {
43467
+ if (!helperName || !MANAGED_HELPER_NAMES.has(helperName)) {
43262
43468
  bodyStatements.push(getStatementText(source, statement));
43263
43469
  continue;
43264
43470
  }
@@ -43271,7 +43477,7 @@ const rebuildManagedBody = (source, exportStart, scaffold) => {
43271
43477
  }
43272
43478
  for (const helper of scaffold.helperStatements) if (!emittedHelpers.has(helper.name)) bodyStatements.push(helper.code.trim());
43273
43479
  const bodyText = bodyStatements.filter(Boolean).join("\n\n");
43274
- const configStatement = `config({ path: ".env.hotupdater" });`;
43480
+ const configStatement = configStatements.join("\n\n") || `config({ path: ".env.hotupdater" });`;
43275
43481
  const managedBody = bodyText ? `\n\n${configStatement}\n\n${bodyText}\n\n` : `\n\n${configStatement}\n\n`;
43276
43482
  return {
43277
43483
  start: source.program.body.filter((statement) => statement.type === "ImportDeclaration").at(-1)?.end ?? 0,
@@ -43299,7 +43505,9 @@ const mergeHotUpdaterConfigText = (existingText, scaffold) => {
43299
43505
  source: nextSource
43300
43506
  });
43301
43507
  if (!nextObjectText) return { reason: "Existing config uses dynamic build/storage/database expressions that cannot be merged safely." };
43302
- const bodyEdit = rebuildManagedBody(existingSource, getTopLevelFullStart(existingSource, existingConfig.exportDeclaration), scaffold);
43508
+ const exportFullStart = getTopLevelFullStart(existingSource, existingConfig.exportDeclaration);
43509
+ const firstTriviaContent = existingText.slice(exportFullStart, existingConfig.exportDeclaration.start).search(/\S/);
43510
+ const bodyEdit = rebuildManagedBody(existingSource, firstTriviaContent === -1 ? existingConfig.exportDeclaration.start : exportFullStart + firstTriviaContent, scaffold);
43303
43511
  if (!bodyEdit) return { reason: "Existing helper declarations could not be merged safely." };
43304
43512
  return { text: applyTextEdits(existingText, [
43305
43513
  {
@@ -43386,6 +43594,12 @@ var MissingInitInputsError = class extends InitError {
43386
43594
  var InitEnvFileError = class extends InitError {
43387
43595
  name = "InitEnvFileError";
43388
43596
  };
43597
+ var LegacyInfrastructureError = class extends InitError {
43598
+ name = "LegacyInfrastructureError";
43599
+ constructor(provider, resource) {
43600
+ super(`${provider} v0 infrastructure was detected at ${resource}. Hot Updater v1 cannot upgrade it in place. Run init with new provider resources and ship the new endpoint in a new native build. The existing infrastructure was not changed.`);
43601
+ }
43602
+ };
43389
43603
  const assertInitInputs = ({ inputs, strict }) => {
43390
43604
  if (!strict) return;
43391
43605
  const missingInputs = getMissingInitInputs(inputs);
@@ -43461,6 +43675,26 @@ const getHotUpdaterEnvValue = (env, key) => {
43461
43675
  return env[key]?.trim() || void 0;
43462
43676
  };
43463
43677
  //#endregion
43678
+ //#region src/infrastructureGeneration.ts
43679
+ const assertInfrastructureGenerationPayload = ({ payload, provider, resource }) => {
43680
+ if (typeof payload !== "object" || payload === null || !("infrastructureGeneration" in payload) || payload.infrastructureGeneration !== 1) throw new LegacyInfrastructureError(provider, resource);
43681
+ };
43682
+ const assertInfrastructureGenerationAtUrl = async ({ fetchImpl = fetch, legacyStatuses = [404], provider, resource, versionUrl }) => {
43683
+ let response;
43684
+ try {
43685
+ response = await fetchImpl(versionUrl);
43686
+ } catch (error) {
43687
+ throw new InitError(`Could not verify the ${provider} infrastructure generation at ${resource}: ${error instanceof Error ? error.message : String(error)}`);
43688
+ }
43689
+ if (legacyStatuses.includes(response.status)) throw new LegacyInfrastructureError(provider, resource);
43690
+ if (!response.ok) throw new InitError(`Could not verify the ${provider} infrastructure generation at ${resource}: HTTP ${response.status}`);
43691
+ assertInfrastructureGenerationPayload({
43692
+ payload: await response.json().catch(() => void 0),
43693
+ provider,
43694
+ resource
43695
+ });
43696
+ };
43697
+ //#endregion
43464
43698
  //#region src/initProvider.ts
43465
43699
  const defineInitProvider = (provider) => provider;
43466
43700
  const shouldAutoSelectOnlyInitResource = ({ availableResourceCount, savedIdentifier }) => savedIdentifier === void 0 && availableResourceCount === 1;
@@ -43635,6 +43869,91 @@ function isMergeableValue(value) {
43635
43869
  //#endregion
43636
43870
  //#region src/loadConfig.ts
43637
43871
  var import_out = /* @__PURE__ */ __toESM(require_out(), 1);
43872
+ const missingDatabase = createDatabasePlugin({
43873
+ name: "missingDatabase",
43874
+ models: {
43875
+ bundles: {
43876
+ findById: async () => {
43877
+ throw new Error("database plugin is required");
43878
+ },
43879
+ findMany: async () => {
43880
+ throw new Error("database plugin is required");
43881
+ },
43882
+ count: async () => {
43883
+ throw new Error("database plugin is required");
43884
+ }
43885
+ },
43886
+ bundlePatches: { findByBundleIds: async () => {
43887
+ throw new Error("database plugin is required");
43888
+ } },
43889
+ releases: {
43890
+ findById: async () => {
43891
+ throw new Error("database plugin is required");
43892
+ },
43893
+ findMany: async () => {
43894
+ throw new Error("database plugin is required");
43895
+ },
43896
+ findManyByScope: async () => {
43897
+ throw new Error("database plugin is required");
43898
+ }
43899
+ },
43900
+ releaseCatalogs: {
43901
+ findByScopeKey: async () => {
43902
+ throw new Error("database plugin is required");
43903
+ },
43904
+ findMany: async () => {
43905
+ throw new Error("database plugin is required");
43906
+ }
43907
+ },
43908
+ channels: {
43909
+ insert: async () => {
43910
+ throw new Error("database plugin is required");
43911
+ },
43912
+ list: async () => {
43913
+ throw new Error("database plugin is required");
43914
+ },
43915
+ delete: async () => {
43916
+ throw new Error("database plugin is required");
43917
+ }
43918
+ },
43919
+ analytics: {
43920
+ append: async () => {
43921
+ throw new Error("database plugin is required");
43922
+ },
43923
+ scan: async () => {
43924
+ throw new Error("database plugin is required");
43925
+ }
43926
+ },
43927
+ apiKeys: {
43928
+ create: async () => {
43929
+ throw new Error("database plugin is required");
43930
+ },
43931
+ findByHash: async () => {
43932
+ throw new Error("database plugin is required");
43933
+ },
43934
+ list: async () => {
43935
+ throw new Error("database plugin is required");
43936
+ },
43937
+ revoke: async () => {
43938
+ throw new Error("database plugin is required");
43939
+ }
43940
+ }
43941
+ },
43942
+ commit: async () => {
43943
+ throw new Error("database plugin is required");
43944
+ }
43945
+ });
43946
+ const missingStorageError = async () => {
43947
+ throw new Error("storage plugin is required");
43948
+ };
43949
+ const missingStorage = createStoragePlugin({
43950
+ name: "missingStorage",
43951
+ protocol: "missing",
43952
+ put: missingStorageError,
43953
+ get: missingStorageError,
43954
+ exists: missingStorageError,
43955
+ delete: missingStorageError
43956
+ });
43638
43957
  const getDefaultPlatformConfig = () => {
43639
43958
  let infoPlistPaths = [];
43640
43959
  try {
@@ -43662,27 +43981,14 @@ const getDefaultPlatformConfig = () => {
43662
43981
  });
43663
43982
  if (manifestFiles.length > 0) androidManifestPaths = manifestFiles.map((file) => path.join("android", file));
43664
43983
  } catch {}
43665
- let stringResourcePaths = [];
43666
- try {
43667
- const stringsFiles = import_out.default.sync(path.join("**", "strings.xml"), {
43668
- cwd: path.join(getCwd(), "android"),
43669
- absolute: false,
43670
- onlyFiles: true
43671
- });
43672
- if (stringsFiles.length > 0) stringResourcePaths = stringsFiles.map((file) => path.join("android", file));
43673
- } catch {}
43674
43984
  return {
43675
- android: {
43676
- androidManifestPaths,
43677
- stringResourcePaths
43678
- },
43985
+ android: { androidManifestPaths },
43679
43986
  ios: { infoPlistPaths }
43680
43987
  };
43681
43988
  };
43682
43989
  const getDefaultConfig = () => {
43683
43990
  return {
43684
43991
  cacheDir: path.join("node_modules", ".hot-updater"),
43685
- releaseChannel: "production",
43686
43992
  updateStrategy: "appVersion",
43687
43993
  compressStrategy: "zip",
43688
43994
  fingerprint: {},
@@ -43699,16 +44005,21 @@ const getDefaultConfig = () => {
43699
44005
  build: () => {
43700
44006
  throw new Error("build plugin is required");
43701
44007
  },
43702
- storage: () => {
43703
- throw new Error("storage plugin is required");
43704
- },
43705
- database: () => {
43706
- throw new Error("database plugin is required");
43707
- }
44008
+ storage: missingStorage,
44009
+ database: missingDatabase
43708
44010
  };
43709
44011
  };
43710
44012
  const mergeConfigSources = (...sources) => {
43711
- return sources.reduceRight((mergedConfig, source) => merge(mergedConfig, source ?? {}), {});
44013
+ const mergedConfig = sources.reduceRight((mergedConfig, source) => merge(mergedConfig, source ?? {}), {});
44014
+ const database = sources.find((source) => source?.database)?.database;
44015
+ const signing = sources.find((source) => source?.signing)?.signing;
44016
+ const storage = sources.find((source) => source?.storage)?.storage;
44017
+ return {
44018
+ ...mergedConfig,
44019
+ ...database ? { database } : {},
44020
+ ...signing ? { signing } : {},
44021
+ ...storage ? { storage } : {}
44022
+ };
43712
44023
  };
43713
44024
  const getConfigLoaderOptions = (options) => {
43714
44025
  const cwd = getCwd();
@@ -43734,7 +44045,13 @@ const getConfigLoaderOptions = (options) => {
43734
44045
  };
43735
44046
  const loadConfig = async (options) => {
43736
44047
  const { config } = await loadConfig$1(getConfigLoaderOptions(options));
43737
- return mergeConfigSources(config, getDefaultConfig());
44048
+ 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.`);
44049
+ const mergedConfig = mergeConfigSources(config, getDefaultConfig());
44050
+ const signing = normalizeSigningConfig(mergedConfig.signing);
44051
+ return {
44052
+ ...mergedConfig,
44053
+ signing
44054
+ };
43738
44055
  };
43739
44056
  //#endregion
43740
44057
  //#region src/log.ts
@@ -43849,9 +44166,52 @@ const makeEnv = async (newEnvVars, filePath = ".env.hotupdater", options) => {
43849
44166
  }
43850
44167
  };
43851
44168
  //#endregion
44169
+ //#region src/storageFiles.ts
44170
+ const getStorageFileByteSize = async (filePath) => {
44171
+ const { size } = await fs$3.stat(filePath, { bigint: true });
44172
+ if (size < 0n || size > BigInt(Number.MAX_SAFE_INTEGER)) throw new Error("Storage file size must be a non-negative safe integer.");
44173
+ return Number(size);
44174
+ };
44175
+ const putStorageFile = async (storage, key, filePath) => {
44176
+ const byteSize = await getStorageFileByteSize(filePath);
44177
+ const source = createReadStream(filePath);
44178
+ try {
44179
+ return {
44180
+ ...await storage.put({
44181
+ key: path$1.posix.join(key, path$1.basename(filePath)),
44182
+ body: Readable$1.toWeb(source),
44183
+ contentLength: byteSize,
44184
+ contentType: getContentType(filePath)
44185
+ }),
44186
+ byteSize
44187
+ };
44188
+ } finally {
44189
+ source.destroy();
44190
+ }
44191
+ };
44192
+ const writeStorageFile = async (storage, storageUri, filePath) => {
44193
+ const { response } = await storage.get({ storageUri });
44194
+ if (response === null) throw new Error(`Storage object not found: ${storageUri}`);
44195
+ await writeStorageResponseFile(response, filePath);
44196
+ };
44197
+ const writeStorageResponseFile = async (response, filePath) => {
44198
+ await fs$3.mkdir(path$1.dirname(filePath), { recursive: true });
44199
+ if (response.body === null) {
44200
+ await fs$3.writeFile(filePath, new Uint8Array());
44201
+ return;
44202
+ }
44203
+ try {
44204
+ await pipeline$1(Readable$1.fromWeb(response.body), createWriteStream$1(filePath));
44205
+ } catch (error) {
44206
+ await fs$3.rm(filePath, { force: true });
44207
+ throw error;
44208
+ }
44209
+ };
44210
+ //#endregion
43852
44211
  //#region src/promoteBundle.ts
43853
44212
  const LEGACY_BUNDLE_ERROR = "This OTA bundle was created by a version that does not support manifest.json. Copy bundle is not available.";
43854
44213
  const SIGNED_HASH_PREFIX = "sig:";
44214
+ const PROMOTE_ASSET_CONCURRENCY = 8;
43855
44215
  function isSignedFileHash(fileHash) {
43856
44216
  return fileHash.startsWith(SIGNED_HASH_PREFIX);
43857
44217
  }
@@ -43859,16 +44219,27 @@ async function getFileHash(filepath) {
43859
44219
  const file = await fs$3.readFile(filepath);
43860
44220
  return crypto$1.createHash("sha256").update(file).digest("hex");
43861
44221
  }
43862
- async function signFileHash(fileHash, privateKeyPath) {
43863
- const privateKeyPEM = await fs$3.readFile(privateKeyPath, "utf8");
43864
- const sign = crypto$1.createSign("RSA-SHA256");
43865
- sign.update(Buffer.from(fileHash, "hex"));
43866
- sign.end();
43867
- return `${SIGNED_HASH_PREFIX}${sign.sign(privateKeyPEM).toString("base64")}`;
44222
+ async function runWithConcurrency(items, concurrency, task) {
44223
+ let nextIndex = 0;
44224
+ const workerCount = Math.min(concurrency, items.length);
44225
+ await Promise.all(Array.from({ length: workerCount }, async () => {
44226
+ while (nextIndex < items.length) {
44227
+ const itemIndex = nextIndex;
44228
+ nextIndex += 1;
44229
+ await task(items[itemIndex]);
44230
+ }
44231
+ }));
44232
+ }
44233
+ function verifySignedFileHash({ actualFileHash, publicKey, signedFileHash }) {
44234
+ try {
44235
+ return crypto$1.verify("RSA-SHA256", Buffer.from(actualFileHash, "hex"), publicKey, Buffer.from(signedFileHash.slice(4), "base64"));
44236
+ } catch {
44237
+ return false;
44238
+ }
43868
44239
  }
43869
44240
  function getArchiveFilename(storageUri) {
43870
- const { pathname } = new URL(storageUri);
43871
- return path$1.basename(pathname) || "bundle.zip";
44241
+ const { key } = parseStorageUri(storageUri, new URL(storageUri).protocol.replace(":", ""));
44242
+ return path$1.posix.basename(key) || "bundle.zip";
43872
44243
  }
43873
44244
  const getRelativeStorageDir = (relativePath) => {
43874
44245
  const normalized = relativePath.replace(/\\/g, "/");
@@ -43886,7 +44257,7 @@ async function prepareManifestAssetUploadFile({ assetPath, sourcePath, workDir }
43886
44257
  if (getManifestAssetDownloadPath(assetPath) === assetPath) return sourcePath;
43887
44258
  const uploadPath = resolvePreparedUploadPath(workDir, assetPath);
43888
44259
  await fs$3.mkdir(path$1.dirname(uploadPath), { recursive: true });
43889
- await pipeline$1(createReadStream(sourcePath), createBrotliCompress$1(), createWriteStream$1(uploadPath));
44260
+ await pipeline$1(createReadStream(sourcePath), createBrotliCompress$1({ params: { [constants$2.BROTLI_PARAM_QUALITY]: 11 } }), createWriteStream$1(uploadPath));
43890
44261
  return uploadPath;
43891
44262
  }
43892
44263
  async function prepareContentAddressedUploadFile({ sourcePath, storagePath, workDir }) {
@@ -43897,6 +44268,48 @@ async function prepareContentAddressedUploadFile({ sourcePath, storagePath, work
43897
44268
  await fs$3.copyFile(sourcePath, uploadPath);
43898
44269
  return uploadPath;
43899
44270
  }
44271
+ async function prepareManifestAssetUploadTargets({ extractDir, manifest, workDir }) {
44272
+ const targets = /* @__PURE__ */ new Map();
44273
+ const assetPaths = Object.keys(manifest.assets ?? {}).sort((left, right) => left.localeCompare(right));
44274
+ for (const assetPath of assetPaths) {
44275
+ const asset = manifest.assets?.[assetPath];
44276
+ if (!asset?.fileHash) throw new Error(`Manifest file hash not found for ${assetPath}`);
44277
+ const uploadSourcePath = await prepareManifestAssetUploadFile({
44278
+ assetPath,
44279
+ sourcePath: resolveExtractedPath(extractDir, assetPath),
44280
+ workDir
44281
+ });
44282
+ const downloadByteSize = await getStorageFileByteSize(uploadSourcePath);
44283
+ const downloadPath = getManifestAssetDownloadPath(assetPath);
44284
+ const downloadFileHash = downloadPath !== assetPath ? await getFileHash(uploadSourcePath) : void 0;
44285
+ if (downloadFileHash !== void 0 && !isContentAddressedAssetFileHash(downloadFileHash)) throw new Error(`Prepared asset hash must be a lowercase SHA-256 hash: ${assetPath}`);
44286
+ const nextAsset = {
44287
+ ...asset,
44288
+ downloadByteSize
44289
+ };
44290
+ delete nextAsset.downloadFileHash;
44291
+ if (downloadFileHash !== void 0) nextAsset.downloadFileHash = downloadFileHash;
44292
+ manifest.assets[assetPath] = nextAsset;
44293
+ const storagePath = getManifestAssetStoragePath({
44294
+ assetPath: downloadPath,
44295
+ downloadFileHash,
44296
+ fileHash: asset.fileHash
44297
+ });
44298
+ const contentAddressedUploadPath = await prepareContentAddressedUploadFile({
44299
+ sourcePath: uploadSourcePath,
44300
+ storagePath,
44301
+ workDir
44302
+ });
44303
+ targets.set(storagePath, {
44304
+ assetPath: downloadPath,
44305
+ downloadFileHash,
44306
+ fileHash: asset.fileHash,
44307
+ storagePath,
44308
+ uploadSourcePath: contentAddressedUploadPath
44309
+ });
44310
+ }
44311
+ return [...targets.values()];
44312
+ }
43900
44313
  function resolveExtractedPath(rootDir, entryName) {
43901
44314
  const normalizedEntryName = entryName.replaceAll("\\", "/");
43902
44315
  const entryPath = path$1.resolve(rootDir, normalizedEntryName);
@@ -43906,23 +44319,20 @@ function resolveExtractedPath(rootDir, entryName) {
43906
44319
  }
43907
44320
  async function downloadArchive(storageUri, storagePlugin, archivePath) {
43908
44321
  const protocol = new URL(storageUri).protocol.replace(":", "");
44322
+ if (storagePlugin?.protocol === protocol) {
44323
+ await writeStorageFile(storagePlugin, storageUri, archivePath);
44324
+ return;
44325
+ }
43909
44326
  if (protocol === "http" || protocol === "https") {
43910
- const archiveBuffer = await downloadFromUrl(storageUri);
43911
- await fs$3.writeFile(archivePath, archiveBuffer);
44327
+ await downloadFromUrl(storageUri, archivePath);
43912
44328
  return;
43913
44329
  }
43914
- await downloadFromStorage(storageUri, storagePlugin, archivePath);
44330
+ throw new Error(`No storage plugin for protocol: ${protocol}`);
43915
44331
  }
43916
- async function downloadFromUrl(fileUrl) {
44332
+ async function downloadFromUrl(fileUrl, filePath) {
43917
44333
  const response = await fetch(fileUrl);
43918
44334
  if (!response.ok) throw new Error(`Failed to download bundle archive: ${response.statusText}`);
43919
- return new Uint8Array(await response.arrayBuffer());
43920
- }
43921
- async function downloadFromStorage(storageUri, storagePlugin, filePath) {
43922
- if (!storagePlugin) throw new Error("Storage plugin is not configured");
43923
- const protocol = new URL(storageUri).protocol.replace(":", "");
43924
- if (storagePlugin.supportedProtocol !== protocol) throw new Error(`No storage plugin for protocol: ${protocol}`);
43925
- await storagePlugin.profiles.node.downloadFile(storageUri, filePath);
44335
+ await writeStorageResponseFile(response, filePath);
43926
44336
  }
43927
44337
  async function extractZipArchive(archivePath, extractDir) {
43928
44338
  const zip = await import_lib.default.loadAsync(await fs$3.readFile(archivePath));
@@ -44002,7 +44412,7 @@ async function createArchiveFromDirectory(sourceDir, archivePath, format) {
44002
44412
  return;
44003
44413
  }
44004
44414
  }
44005
- async function rewriteManifestBundleId(extractDir, nextBundleId) {
44415
+ async function readCopiedBundleManifest(extractDir, nextBundleId) {
44006
44416
  const manifestPath = path$1.join(extractDir, "manifest.json");
44007
44417
  try {
44008
44418
  await fs$3.access(manifestPath);
@@ -44011,13 +44421,12 @@ async function rewriteManifestBundleId(extractDir, nextBundleId) {
44011
44421
  }
44012
44422
  const manifest = JSON.parse(await fs$3.readFile(manifestPath, "utf8"));
44013
44423
  manifest.bundleId = nextBundleId;
44014
- await fs$3.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
44015
44424
  return {
44016
44425
  manifest,
44017
44426
  manifestPath
44018
44427
  };
44019
44428
  }
44020
- async function createCopiedBundleArchive({ bundle, config, nextBundleId, storagePlugin, targetChannel }) {
44429
+ async function createCopiedBundleArchive({ bundle, config, nextBundleId, storagePlugin }) {
44021
44430
  const archiveFilename = getArchiveFilename(bundle.storageUri);
44022
44431
  const workDir = await fs$3.mkdtemp(path$1.join(os.tmpdir(), "hot-updater-console-promote-"));
44023
44432
  const sourceArchivePath = path$1.join(workDir, archiveFilename);
@@ -44027,65 +44436,73 @@ async function createCopiedBundleArchive({ bundle, config, nextBundleId, storage
44027
44436
  await fs$3.mkdir(extractDir, { recursive: true });
44028
44437
  try {
44029
44438
  await downloadArchive(bundle.storageUri, storagePlugin, sourceArchivePath);
44439
+ const actualSourceFileHash = await getFileHash(sourceArchivePath);
44440
+ const signingSession = await prepareBundleSigning(config.signing);
44441
+ if (isSignedFileHash(bundle.fileHash)) {
44442
+ if (!signingSession) throw new Error("Cannot copy a signed bundle without enabled bundle signing configuration.");
44443
+ if (!verifySignedFileHash({
44444
+ actualFileHash: actualSourceFileHash,
44445
+ publicKey: signingSession.publicKey,
44446
+ signedFileHash: bundle.fileHash
44447
+ })) throw new Error("Source bundle signature verification failed.");
44448
+ } else if (actualSourceFileHash !== bundle.fileHash.toLowerCase()) throw new Error("Source bundle file hash verification failed.");
44030
44449
  const format = await extractArchive(sourceArchivePath, extractDir);
44031
- const { manifest, manifestPath } = await rewriteManifestBundleId(extractDir, nextBundleId);
44450
+ const { manifest, manifestPath } = await readCopiedBundleManifest(extractDir, nextBundleId);
44451
+ const assetPaths = Object.keys(manifest.assets ?? {}).sort((left, right) => left.localeCompare(right));
44452
+ const sourceIsSigned = [bundle.fileHash, getManifestFileHash(bundle)].filter((hash) => Boolean(hash)).some((hash) => isSignedFileHash(hash));
44453
+ const manifestHasSignatures = assetPaths.some((assetPath) => Boolean(manifest.assets?.[assetPath]?.signature));
44454
+ if (!signingSession && (sourceIsSigned || manifestHasSignatures)) throw new Error("Cannot copy a signed bundle without enabled bundle signing configuration.");
44455
+ if (signingSession) {
44456
+ await runWithConcurrency(assetPaths, PROMOTE_ASSET_CONCURRENCY, async (assetPath) => {
44457
+ const asset = manifest.assets?.[assetPath];
44458
+ if (!asset?.fileHash) throw new Error(`Manifest file hash not found for ${assetPath}`);
44459
+ if (await getFileHash(resolveExtractedPath(extractDir, assetPath)) !== asset.fileHash.toLowerCase()) throw new Error(`Manifest file hash mismatch for ${assetPath}`);
44460
+ });
44461
+ await runWithConcurrency(assetPaths, PROMOTE_ASSET_CONCURRENCY, async (assetPath) => {
44462
+ const asset = manifest.assets?.[assetPath];
44463
+ if (!asset?.fileHash) throw new Error(`Manifest file hash not found for ${assetPath}`);
44464
+ asset.signature = await signingSession.signFileHash(asset.fileHash);
44465
+ });
44466
+ }
44467
+ const assetUploadTargets = await prepareManifestAssetUploadTargets({
44468
+ extractDir,
44469
+ manifest,
44470
+ workDir
44471
+ });
44472
+ await fs$3.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
44032
44473
  await fs$3.rm(sourceArchivePath, { force: true });
44033
44474
  await createArchiveFromDirectory(extractDir, outputArchivePath, format);
44034
44475
  const fileHash = await getFileHash(outputArchivePath);
44035
44476
  const manifestHash = await getFileHash(manifestPath);
44036
- if ([bundle.fileHash, getManifestFileHash(bundle)].filter((hash) => Boolean(hash)).some((hash) => isSignedFileHash(hash)) && !config.signing?.privateKeyPath) throw new Error("Cannot copy a signed bundle without signing.privateKeyPath in hot-updater.config.ts");
44037
- const signingKeyPath = config.signing?.enabled && config.signing.privateKeyPath ? config.signing.privateKeyPath : null;
44038
- const nextFileHash = signingKeyPath ? await signFileHash(fileHash, signingKeyPath) : fileHash;
44039
- const nextManifestFileHash = signingKeyPath ? await signFileHash(manifestHash, signingKeyPath) : manifestHash;
44040
- const archiveUpload = await storagePlugin.profiles.node.upload(createBundleStorageKey(nextBundleId), outputArchivePath);
44477
+ const nextFileHash = signingSession ? `${SIGNED_HASH_PREFIX}${await signingSession.signFileHash(fileHash)}` : fileHash;
44478
+ const nextManifestFileHash = signingSession ? `${SIGNED_HASH_PREFIX}${await signingSession.signFileHash(manifestHash)}` : manifestHash;
44479
+ const archiveUpload = await putStorageFile(storagePlugin, createBundleStorageKey(nextBundleId), outputArchivePath);
44041
44480
  uploadedStorageUris.push(archiveUpload.storageUri);
44042
- const manifestUpload = await storagePlugin.profiles.node.upload(createBundleStorageKey(nextBundleId), manifestPath);
44043
- uploadedStorageUris.push(manifestUpload.storageUri);
44044
- const assetBaseStorageUri = createStorageRootUriWithPath(manifestUpload.storageUri, nextBundleId, "assets");
44045
- const assetPaths = Object.keys(manifest.assets ?? {}).sort((left, right) => left.localeCompare(right));
44046
- for (const assetPath of assetPaths) {
44047
- const asset = manifest.assets?.[assetPath];
44048
- if (!asset?.fileHash) throw new Error(`Manifest file hash not found for ${assetPath}`);
44049
- const uploadPath = await prepareManifestAssetUploadFile({
44050
- assetPath,
44051
- sourcePath: path$1.join(extractDir, assetPath),
44052
- workDir
44053
- });
44054
- const uploadName = getManifestAssetDownloadPath(assetPath);
44055
- const storagePath = getContentAddressedAssetStoragePath({
44056
- assetPath: uploadName,
44057
- fileHash: asset.fileHash
44058
- });
44481
+ const assetBaseStorageUri = createStorageRootUriWithPath(archiveUpload.storageUri, nextBundleId, "assets");
44482
+ for (const assetUploadTarget of assetUploadTargets) {
44059
44483
  const storageUri = resolveManifestAssetStorageUri({
44060
44484
  assetBaseStorageUri,
44061
- assetPath: uploadName,
44062
- fileHash: asset.fileHash
44485
+ assetPath: assetUploadTarget.assetPath,
44486
+ downloadFileHash: assetUploadTarget.downloadFileHash,
44487
+ fileHash: assetUploadTarget.fileHash
44063
44488
  });
44064
- if (!await storagePlugin.profiles.node.exists(storageUri)) {
44065
- const contentAddressedUploadPath = await prepareContentAddressedUploadFile({
44066
- sourcePath: uploadPath,
44067
- storagePath,
44068
- workDir
44069
- });
44070
- await storagePlugin.profiles.node.upload(getRelativeStorageDir(storagePath) ? `assets/${getRelativeStorageDir(storagePath)}` : "assets", contentAddressedUploadPath);
44071
- }
44489
+ const { exists } = await storagePlugin.exists({ storageUri });
44490
+ if (!exists) await putStorageFile(storagePlugin, getRelativeStorageDir(assetUploadTarget.storagePath) ? `assets/${getRelativeStorageDir(assetUploadTarget.storagePath)}` : "assets", assetUploadTarget.uploadSourcePath);
44072
44491
  }
44492
+ const manifestUpload = await putStorageFile(storagePlugin, createBundleStorageKey(nextBundleId), manifestPath);
44493
+ uploadedStorageUris.push(manifestUpload.storageUri);
44073
44494
  return {
44074
44495
  bundle: {
44075
44496
  ...bundle,
44076
44497
  id: nextBundleId,
44077
- channel: targetChannel,
44498
+ archiveByteSize: archiveUpload.byteSize,
44078
44499
  storageUri: archiveUpload.storageUri,
44079
44500
  fileHash: nextFileHash,
44080
44501
  metadata: stripBundleArtifactMetadata(bundle.metadata),
44081
44502
  assetBaseStorageUri,
44082
44503
  patches: [],
44083
- patchBaseBundleId: null,
44084
44504
  manifestFileHash: nextManifestFileHash,
44085
- manifestStorageUri: manifestUpload.storageUri,
44086
- patchBaseFileHash: null,
44087
- patchFileHash: null,
44088
- patchStorageUri: null
44505
+ manifestStorageUri: manifestUpload.storageUri
44089
44506
  },
44090
44507
  uploadedStorageUris
44091
44508
  };
@@ -44102,44 +44519,13 @@ async function createCopiedBundleArchive({ bundle, config, nextBundleId, storage
44102
44519
  async function deleteUploadedCopy(storagePlugin, storageUris) {
44103
44520
  if (storageUris.length === 0) return;
44104
44521
  for (const storageUri of new Set(storageUris)) try {
44105
- await storagePlugin.profiles.node.delete(storageUri);
44522
+ const protocol = new URL(storageUri).protocol.replace(":", "");
44523
+ if (storagePlugin.protocol === protocol) await storagePlugin.delete({ storageUri });
44524
+ else if (protocol !== "http" && protocol !== "https") throw new Error(`No storage plugin for protocol: ${protocol}`);
44106
44525
  } catch (error) {
44107
44526
  console.error("Failed to delete uploaded bundle copy:", error);
44108
44527
  }
44109
44528
  }
44110
- async function promoteBundle({ action, bundleId, nextBundleId, targetChannel }, deps) {
44111
- const normalizedTargetChannel = targetChannel.trim();
44112
- if (!normalizedTargetChannel) throw new Error("Target channel is required");
44113
- const bundle = await deps.databasePlugin.getBundleById(bundleId);
44114
- if (!bundle) throw new Error("Bundle not found");
44115
- if (bundle.channel === normalizedTargetChannel) throw new Error("Target channel must be different from the current channel");
44116
- if (action === "move") {
44117
- await deps.databasePlugin.updateBundle(bundleId, { channel: normalizedTargetChannel });
44118
- await deps.databasePlugin.commitBundle();
44119
- const updatedBundle = await deps.databasePlugin.getBundleById(bundleId);
44120
- if (!updatedBundle) throw new Error("Promoted bundle not found");
44121
- return updatedBundle;
44122
- }
44123
- if (!deps.storagePlugin) throw new Error("Storage plugin is not configured");
44124
- const resolvedNextBundleId = nextBundleId?.trim() || createUUIDv7();
44125
- const { bundle: copiedBundle, uploadedStorageUris } = await createCopiedBundleArchive({
44126
- bundle,
44127
- config: deps.config,
44128
- nextBundleId: resolvedNextBundleId,
44129
- storagePlugin: deps.storagePlugin,
44130
- targetChannel: normalizedTargetChannel
44131
- });
44132
- let shouldCleanupUploadedCopy = true;
44133
- try {
44134
- await deps.databasePlugin.appendBundle(copiedBundle);
44135
- await deps.databasePlugin.commitBundle();
44136
- shouldCleanupUploadedCopy = false;
44137
- return copiedBundle;
44138
- } catch (error) {
44139
- if (shouldCleanupUploadedCopy) await deleteUploadedCopy(deps.storagePlugin, uploadedStorageUris);
44140
- throw error;
44141
- }
44142
- }
44143
44529
  //#endregion
44144
44530
  //#region src/resolvePackageVersion.ts
44145
44531
  const require$1 = createRequire(import.meta.url);
@@ -44206,4 +44592,4 @@ function transformTemplate(templateString, values) {
44206
44592
  return result;
44207
44593
  }
44208
44594
  //#endregion
44209
- export { BuildLogger, ConfigBuilder, HOT_UPDATER_SERVER_PACKAGE_VERSION_ENV, HotUpdateDirUtil, InitEnvFileError, InitError, LEGACY_BUNDLE_ERROR, MissingInitInputsError, assertInitInputs, assertInitProviderInputs, banner, typedColors as colors, confirmInitInputPersistence, copyDirToTmp, createCopiedBundleArchive, createHotUpdaterConfigScaffold, createHotUpdaterConfigScaffoldFromBuilder, createLogWriter, createTarBr, createTarBrTargetFiles, createTarGz, createTarGzTargetFiles, createZip, createZipTargetFiles, decryptJson, defineInitProvider, encryptJson, ensureInstallPackages, getAndroidSdkPath, getCwd, getHotUpdaterEnvValue, getHotUpdaterInitInputEnv, getInitProviderEnvVars, getInitProviderTextPromptValues, getMissingInitInputs, getMissingInitProviderInputs, getPackageManager, getReactNativeMetadatas, link, loadConfig, log, makeEnv, p, printBanner, promoteBundle, readHotUpdaterEnv, readHotUpdaterInitEnv, readPackageUp, renderImportStatements, resolveHotUpdaterServerVersion, resolveInitProviderInput, resolveInitProviderInputs, resolvePackageVersion, shouldAutoSelectOnlyInitResource, stripAnsi, transformEnv, transformTemplate, writeHotUpdaterConfig };
44595
+ export { BuildLogger, ConfigBuilder, HOT_UPDATER_SERVER_PACKAGE_VERSION_ENV, HotUpdateDirUtil, InitEnvFileError, InitError, LEGACY_BUNDLE_ERROR, LegacyInfrastructureError, MissingInitInputsError, assertInfrastructureGenerationAtUrl, assertInfrastructureGenerationPayload, assertInitInputs, assertInitProviderInputs, banner, typedColors as colors, confirmInitInputPersistence, copyDirToTmp, createCopiedBundleArchive, createHotUpdaterConfigScaffold, createHotUpdaterConfigScaffoldFromBuilder, createLogWriter, createTarBr, createTarBrTargetFiles, createTarGz, createTarGzTargetFiles, createZip, createZipTargetFiles, decryptJson, defineInitProvider, encryptJson, ensureInstallPackages, formatApiKeyNote, getAndroidSdkPath, getBundleSigningPublicKey, getCwd, getHotUpdaterEnvValue, getHotUpdaterInitInputEnv, getInitProviderEnvVars, getInitProviderTextPromptValues, getMissingInitInputs, getMissingInitProviderInputs, getPackageManager, getReactNativeMetadatas, getStorageFileByteSize, link, loadConfig, log, makeEnv, p, prepareBundleSigning, printBanner, putStorageFile, readHotUpdaterEnv, readHotUpdaterInitEnv, readPackageUp, renderImportStatements, resolveHotUpdaterServerVersion, resolveInitProviderInput, resolveInitProviderInputs, resolvePackageVersion, shouldAutoSelectOnlyInitResource, stripAnsi, transformEnv, transformTemplate, writeHotUpdaterConfig, writeStorageFile, writeStorageResponseFile };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hot-updater/cli-tools",
3
- "version": "0.36.7",
3
+ "version": "1.0.0-rc.0",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=20.19.0"
@@ -49,8 +49,8 @@
49
49
  "oxc-parser": "0.141.0",
50
50
  "oxc-transform": "0.141.0",
51
51
  "unconfig": "7.5.0",
52
- "@hot-updater/core": "0.36.7",
53
- "@hot-updater/plugin-core": "0.36.7"
52
+ "@hot-updater/plugin-core": "1.0.0-rc.0",
53
+ "@hot-updater/core": "1.0.0-rc.0"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@clack/prompts": "1.7.0",
@@ -64,7 +64,7 @@
64
64
  "tar": "^7.5.16",
65
65
  "verkit": "0.3.2",
66
66
  "workspace-tools": "^0.41.7",
67
- "@hot-updater/test-utils": "0.36.7"
67
+ "@hot-updater/test-utils": "1.0.0-rc.0"
68
68
  },
69
69
  "inlinedDependencies": {
70
70
  "@babel/code-frame": "7.29.0",