@hot-updater/cli-tools 0.36.7 → 1.0.0-rc.1
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 +72 -36
- package/dist/index.mjs +497 -133
- package/package.json +4 -4
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,31 @@ 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 readBundleSigningPublicKeyFile: (publicKeyPath: string, options?: {
|
|
51
|
+
readonly cwd?: string;
|
|
52
|
+
}) => Promise<string>;
|
|
53
|
+
declare const prepareBundleSigning: (signing: SigningConfig | undefined, options?: {
|
|
54
|
+
readonly cwd?: string;
|
|
55
|
+
}) => Promise<BundleSigningSession | null>;
|
|
56
|
+
/** Resolves the public identity of the configured bundle signer. */
|
|
57
|
+
declare const getBundleSigningPublicKey: (signing: SigningConfig | undefined, options?: {
|
|
58
|
+
readonly cwd?: string;
|
|
59
|
+
}) => Promise<string | null>;
|
|
60
|
+
//#endregion
|
|
41
61
|
//#region src/ConfigBuilder.d.ts
|
|
42
62
|
type BuildType = "bare" | "rock" | "expo";
|
|
43
63
|
type ImportInfo = {
|
|
@@ -246,6 +266,7 @@ type ManagedHelperStatement = {
|
|
|
246
266
|
name: string;
|
|
247
267
|
code: string;
|
|
248
268
|
strategy: ManagedHelperStrategy;
|
|
269
|
+
replaceIncompatibleProperties?: string[];
|
|
249
270
|
};
|
|
250
271
|
type CreateHotUpdaterConfigScaffoldOptions = {
|
|
251
272
|
build: BuildType;
|
|
@@ -334,6 +355,31 @@ declare const HotUpdateDirUtil: {
|
|
|
334
355
|
}) => string;
|
|
335
356
|
};
|
|
336
357
|
//#endregion
|
|
358
|
+
//#region src/infrastructureGeneration.d.ts
|
|
359
|
+
type Fetch = typeof fetch;
|
|
360
|
+
declare const assertInfrastructureGenerationPayload: ({
|
|
361
|
+
payload,
|
|
362
|
+
provider,
|
|
363
|
+
resource
|
|
364
|
+
}: {
|
|
365
|
+
readonly payload: unknown;
|
|
366
|
+
readonly provider: string;
|
|
367
|
+
readonly resource: string;
|
|
368
|
+
}) => void;
|
|
369
|
+
declare const assertInfrastructureGenerationAtUrl: ({
|
|
370
|
+
fetchImpl,
|
|
371
|
+
legacyStatuses,
|
|
372
|
+
provider,
|
|
373
|
+
resource,
|
|
374
|
+
versionUrl
|
|
375
|
+
}: {
|
|
376
|
+
readonly fetchImpl?: Fetch;
|
|
377
|
+
readonly legacyStatuses?: readonly number[];
|
|
378
|
+
readonly provider: string;
|
|
379
|
+
readonly resource: string;
|
|
380
|
+
readonly versionUrl: string;
|
|
381
|
+
}) => Promise<void>;
|
|
382
|
+
//#endregion
|
|
337
383
|
//#region src/initProvider.d.ts
|
|
338
384
|
type InitProviderInputPersistence = "always" | "with-consent";
|
|
339
385
|
type InitProviderInputDefinition = {
|
|
@@ -432,6 +478,10 @@ declare class MissingInitInputsError extends InitError {
|
|
|
432
478
|
declare class InitEnvFileError extends InitError {
|
|
433
479
|
readonly name = "InitEnvFileError";
|
|
434
480
|
}
|
|
481
|
+
declare class LegacyInfrastructureError extends InitError {
|
|
482
|
+
readonly name = "LegacyInfrastructureError";
|
|
483
|
+
constructor(provider: string, resource: string);
|
|
484
|
+
}
|
|
435
485
|
declare const assertInitInputs: ({
|
|
436
486
|
inputs,
|
|
437
487
|
strict
|
|
@@ -457,12 +507,19 @@ declare const createLogWriter: ({
|
|
|
457
507
|
logFilePath?: string;
|
|
458
508
|
}) => Promise<HotUpdaterLogWriter>;
|
|
459
509
|
//#endregion
|
|
510
|
+
//#region src/localBundleSigning.d.ts
|
|
511
|
+
declare const normalizeSigningConfig: (signing: SigningConfig | undefined) => BundleSigningPlugin | Extract<LocalSigningConfig, {
|
|
512
|
+
enabled: true;
|
|
513
|
+
}> | undefined;
|
|
514
|
+
//#endregion
|
|
460
515
|
//#region src/loadConfig.d.ts
|
|
461
516
|
type HotUpdaterConfigOptions = {
|
|
462
517
|
platform: Platform;
|
|
463
518
|
channel: string;
|
|
464
519
|
} | null;
|
|
465
|
-
type ConfigResponse = RequiredDeep<ConfigInput
|
|
520
|
+
type ConfigResponse = RequiredDeep<Omit<ConfigInput, "database" | "signing" | "storage">> & Pick<ConfigInput, "database" | "storage"> & {
|
|
521
|
+
signing?: ReturnType<typeof normalizeSigningConfig>;
|
|
522
|
+
};
|
|
466
523
|
declare const loadConfig: (options: HotUpdaterConfigOptions) => Promise<ConfigResponse>;
|
|
467
524
|
//#endregion
|
|
468
525
|
//#region src/log.d.ts
|
|
@@ -486,63 +543,34 @@ declare const makeEnv: (newEnvVars: Record<string, EnvVarValue>, filePath?: stri
|
|
|
486
543
|
}) => Promise<string>;
|
|
487
544
|
//#endregion
|
|
488
545
|
//#region src/promoteBundle.d.ts
|
|
546
|
+
type PromoteStoragePlugin = StoragePluginWith<"get" | "put" | "exists" | "delete">;
|
|
489
547
|
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
548
|
declare function createCopiedBundleArchive({
|
|
502
549
|
bundle,
|
|
503
550
|
config,
|
|
504
551
|
nextBundleId,
|
|
505
|
-
storagePlugin
|
|
506
|
-
targetChannel
|
|
552
|
+
storagePlugin
|
|
507
553
|
}: {
|
|
508
554
|
bundle: Bundle;
|
|
509
555
|
config: ConfigResponse;
|
|
510
556
|
nextBundleId: string;
|
|
511
|
-
storagePlugin:
|
|
512
|
-
targetChannel: string;
|
|
557
|
+
storagePlugin: PromoteStoragePlugin;
|
|
513
558
|
}): Promise<{
|
|
514
559
|
bundle: {
|
|
515
560
|
id: string;
|
|
516
|
-
|
|
561
|
+
archiveByteSize: number;
|
|
517
562
|
storageUri: string;
|
|
518
563
|
fileHash: string;
|
|
519
564
|
metadata: _$_hot_updater_core0.BundleMetadata | undefined;
|
|
520
565
|
assetBaseStorageUri: string;
|
|
521
566
|
patches: never[];
|
|
522
|
-
patchBaseBundleId: null;
|
|
523
567
|
manifestFileHash: string;
|
|
524
568
|
manifestStorageUri: string;
|
|
525
|
-
patchBaseFileHash: null;
|
|
526
|
-
patchFileHash: null;
|
|
527
|
-
patchStorageUri: null;
|
|
528
569
|
platform: _$_hot_updater_core0.Platform;
|
|
529
|
-
shouldForceUpdate: boolean;
|
|
530
|
-
enabled: boolean;
|
|
531
570
|
gitCommitHash: string | null;
|
|
532
|
-
message: string | null;
|
|
533
|
-
targetAppVersion: string | null;
|
|
534
|
-
fingerprintHash: string | null;
|
|
535
|
-
rolloutCohortCount?: number | null;
|
|
536
|
-
targetCohorts?: string[] | null;
|
|
537
571
|
};
|
|
538
572
|
uploadedStorageUris: string[];
|
|
539
573
|
}>;
|
|
540
|
-
declare function promoteBundle({
|
|
541
|
-
action,
|
|
542
|
-
bundleId,
|
|
543
|
-
nextBundleId,
|
|
544
|
-
targetChannel
|
|
545
|
-
}: PromoteBundleInput, deps: PromoteBundleDependencies): Promise<Bundle>;
|
|
546
574
|
//#endregion
|
|
547
575
|
//#region ../../node_modules/.pnpm/@clack+core@1.4.3/node_modules/@clack/core/dist/index.d.mts
|
|
548
576
|
declare const actions: readonly ["up", "down", "left", "right", "space", "enter", "cancel"];
|
|
@@ -1792,6 +1820,14 @@ declare const resolveHotUpdaterServerVersion: (currentPackageName: string, optio
|
|
|
1792
1820
|
searchFrom?: string;
|
|
1793
1821
|
}) => string;
|
|
1794
1822
|
//#endregion
|
|
1823
|
+
//#region src/storageFiles.d.ts
|
|
1824
|
+
declare const getStorageFileByteSize: (filePath: string) => Promise<number>;
|
|
1825
|
+
declare const putStorageFile: (storage: StoragePluginWith<"put">, key: string, filePath: string) => Promise<StoragePutResult & {
|
|
1826
|
+
byteSize: number;
|
|
1827
|
+
}>;
|
|
1828
|
+
declare const writeStorageFile: (storage: StoragePluginWith<"get">, storageUri: string, filePath: string) => Promise<void>;
|
|
1829
|
+
declare const writeStorageResponseFile: (response: Response, filePath: string) => Promise<void>;
|
|
1830
|
+
//#endregion
|
|
1795
1831
|
//#region src/transformEnv.d.ts
|
|
1796
1832
|
declare const transformEnv: <T extends Record<string, string>>(filename: string, env: T) => string;
|
|
1797
1833
|
//#endregion
|
|
@@ -1809,4 +1845,4 @@ type TransformTemplateArgs<T extends string> = { [Key in ExtractPlaceholders<T>]
|
|
|
1809
1845
|
*/
|
|
1810
1846
|
declare function transformTemplate<T extends string>(templateString: T, values: TransformTemplateArgs<T>): string;
|
|
1811
1847
|
//#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,
|
|
1848
|
+
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, readBundleSigningPublicKeyFile, 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,165 @@ 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 }) => {
|
|
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
|
+
async getPublicKey({ cwd } = {}) {
|
|
24540
|
+
const privateKey = await getPrivateKey(cwd);
|
|
24541
|
+
return { publicKey: crypto$1.createPublicKey(privateKey).export({
|
|
24542
|
+
format: "pem",
|
|
24543
|
+
type: "spki"
|
|
24544
|
+
}).toString() };
|
|
24545
|
+
},
|
|
24546
|
+
async sign({ message, cwd }) {
|
|
24547
|
+
if (!(message instanceof Uint8Array) || message.byteLength !== 32) throw new Error("Local bundle signing messages must be exactly 32 bytes.");
|
|
24548
|
+
const privateKey = await getPrivateKey(cwd);
|
|
24549
|
+
return { signature: crypto$1.sign("RSA-SHA256", message, privateKey) };
|
|
24550
|
+
}
|
|
24551
|
+
};
|
|
24552
|
+
};
|
|
24553
|
+
const invalidSigningConfig = () => /* @__PURE__ */ new Error("Bundle signing must be a local key config or signing plugin. Omit signing to disable it.");
|
|
24554
|
+
const normalizeSigningConfig = (signing) => {
|
|
24555
|
+
if (signing === void 0) return void 0;
|
|
24556
|
+
if (typeof signing !== "object" || signing === null) throw invalidSigningConfig();
|
|
24557
|
+
const hasPrivateKeyPath = Reflect.has(signing, "privateKeyPath");
|
|
24558
|
+
const hasEnabled = Reflect.has(signing, "enabled");
|
|
24559
|
+
const hasPluginMembers = [
|
|
24560
|
+
"name",
|
|
24561
|
+
"getPublicKey",
|
|
24562
|
+
"sign"
|
|
24563
|
+
].some((key) => Reflect.has(signing, key));
|
|
24564
|
+
if ((hasPrivateKeyPath || hasEnabled) && hasPluginMembers) throw new Error("Bundle signing config cannot combine local signing fields with signing plugin fields.");
|
|
24565
|
+
if (!hasPluginMembers) {
|
|
24566
|
+
if (Object.keys(signing).some((key) => key !== "enabled" && key !== "privateKeyPath")) throw new Error("Local bundle signing accepts only enabled and privateKeyPath.");
|
|
24567
|
+
const enabled = Reflect.get(signing, "enabled");
|
|
24568
|
+
if (enabled === false) return void 0;
|
|
24569
|
+
if (enabled !== true) throw invalidSigningConfig();
|
|
24570
|
+
const privateKeyPath = Reflect.get(signing, "privateKeyPath");
|
|
24571
|
+
if (typeof privateKeyPath !== "string" || !privateKeyPath.trim()) throw new Error("Enabled local bundle signing requires privateKeyPath.");
|
|
24572
|
+
return {
|
|
24573
|
+
enabled: true,
|
|
24574
|
+
privateKeyPath
|
|
24575
|
+
};
|
|
24576
|
+
}
|
|
24577
|
+
if (typeof Reflect.get(signing, "name") !== "string" || typeof Reflect.get(signing, "getPublicKey") !== "function" || typeof Reflect.get(signing, "sign") !== "function") throw invalidSigningConfig();
|
|
24578
|
+
return signing;
|
|
24579
|
+
};
|
|
24580
|
+
//#endregion
|
|
24581
|
+
//#region src/bundleSigning.ts
|
|
24582
|
+
const FILE_HASH_PATTERN = /^[a-f\d]{64}$/iu;
|
|
24583
|
+
const resolvePath = (cwd, filePath) => path$1.isAbsolute(filePath) ? filePath : path$1.resolve(cwd, filePath);
|
|
24584
|
+
const parseRsaPublicKey = (publicKeyPEM) => {
|
|
24585
|
+
try {
|
|
24586
|
+
const normalizedPublicKey = publicKeyPEM.trim();
|
|
24587
|
+
if (!normalizedPublicKey.startsWith("-----BEGIN PUBLIC KEY-----") || !normalizedPublicKey.endsWith("-----END PUBLIC KEY-----")) throw new Error("not spki");
|
|
24588
|
+
const publicKey = crypto$1.createPublicKey(normalizedPublicKey);
|
|
24589
|
+
if (publicKey.asymmetricKeyType !== "rsa" || (publicKey.asymmetricKeyDetails?.modulusLength ?? 0) < 2048) throw new Error("not rsa");
|
|
24590
|
+
return publicKey;
|
|
24591
|
+
} catch {
|
|
24592
|
+
throw new Error("Bundle signing public key must be a valid RSA SPKI PEM key with a modulus of at least 2048 bits.");
|
|
24593
|
+
}
|
|
24594
|
+
};
|
|
24595
|
+
const exportPublicKey = (publicKey) => publicKey.export({
|
|
24596
|
+
type: "spki",
|
|
24597
|
+
format: "pem"
|
|
24598
|
+
}).toString();
|
|
24599
|
+
const readBundleSigningPublicKeyFile = async (publicKeyPath, options = {}) => {
|
|
24600
|
+
if (!publicKeyPath.trim()) throw new Error("Bundle signing public key path is required.");
|
|
24601
|
+
try {
|
|
24602
|
+
return exportPublicKey(parseRsaPublicKey(await fs$3.readFile(resolvePath(options.cwd ?? getCwd(), publicKeyPath), "utf8")));
|
|
24603
|
+
} catch {
|
|
24604
|
+
throw new Error("Failed to read the bundle signing public key file.");
|
|
24605
|
+
}
|
|
24606
|
+
};
|
|
24607
|
+
const getProviderPublicKey = async (provider, cwd) => {
|
|
24608
|
+
try {
|
|
24609
|
+
const result = await provider.getPublicKey({ cwd });
|
|
24610
|
+
if (!result || typeof result.publicKey !== "string") throw new Error("invalid result");
|
|
24611
|
+
return parseRsaPublicKey(result.publicKey);
|
|
24612
|
+
} catch {
|
|
24613
|
+
throw new Error("Failed to resolve the bundle signing provider public key.");
|
|
24614
|
+
}
|
|
24615
|
+
};
|
|
24616
|
+
const createMemoizedSigner = ({ publicKey, sign }) => {
|
|
24617
|
+
const signatures = /* @__PURE__ */ new Map();
|
|
24618
|
+
return (fileHash) => {
|
|
24619
|
+
if (!FILE_HASH_PATTERN.test(fileHash)) return Promise.reject(/* @__PURE__ */ new Error("Bundle signing requires a 64-character hexadecimal file hash."));
|
|
24620
|
+
const normalizedFileHash = fileHash.toLowerCase();
|
|
24621
|
+
const cached = signatures.get(normalizedFileHash);
|
|
24622
|
+
if (cached) return cached;
|
|
24623
|
+
const pending = (async () => {
|
|
24624
|
+
const message = Buffer.from(normalizedFileHash, "hex");
|
|
24625
|
+
let signature;
|
|
24626
|
+
try {
|
|
24627
|
+
signature = await sign(new Uint8Array(message));
|
|
24628
|
+
} catch {
|
|
24629
|
+
throw new Error("Bundle signing provider failed to sign the file hash.");
|
|
24630
|
+
}
|
|
24631
|
+
if (!(signature instanceof Uint8Array) || signature.byteLength === 0) throw new Error("Bundle signing provider returned an invalid signature.");
|
|
24632
|
+
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.");
|
|
24633
|
+
return Buffer.from(signature).toString("base64");
|
|
24634
|
+
})().catch((error) => {
|
|
24635
|
+
signatures.delete(normalizedFileHash);
|
|
24636
|
+
throw error;
|
|
24637
|
+
});
|
|
24638
|
+
signatures.set(normalizedFileHash, pending);
|
|
24639
|
+
return pending;
|
|
24640
|
+
};
|
|
24641
|
+
};
|
|
24642
|
+
const preparePluginSigning = async (signing, cwd) => {
|
|
24643
|
+
const providerPublicKey = await getProviderPublicKey(signing, cwd);
|
|
24644
|
+
return {
|
|
24645
|
+
name: signing.name,
|
|
24646
|
+
publicKey: exportPublicKey(providerPublicKey),
|
|
24647
|
+
signFileHash: createMemoizedSigner({
|
|
24648
|
+
publicKey: providerPublicKey,
|
|
24649
|
+
sign: async (message) => {
|
|
24650
|
+
return (await signing.sign({
|
|
24651
|
+
cwd,
|
|
24652
|
+
message
|
|
24653
|
+
})).signature;
|
|
24654
|
+
}
|
|
24655
|
+
})
|
|
24656
|
+
};
|
|
24657
|
+
};
|
|
24658
|
+
const prepareBundleSigning = async (signing, options = {}) => {
|
|
24659
|
+
const normalized = normalizeSigningConfig(signing);
|
|
24660
|
+
if (!normalized) return null;
|
|
24661
|
+
const cwd = options.cwd ?? getCwd();
|
|
24662
|
+
return preparePluginSigning("enabled" in normalized ? createLocalSigningPlugin(normalized) : normalized, cwd);
|
|
24663
|
+
};
|
|
24664
|
+
/** Resolves the public identity of the configured bundle signer. */
|
|
24665
|
+
const getBundleSigningPublicKey = async (signing, options = {}) => {
|
|
24666
|
+
const normalized = normalizeSigningConfig(signing);
|
|
24667
|
+
if (!normalized) return null;
|
|
24668
|
+
const cwd = options.cwd ?? getCwd();
|
|
24669
|
+
return exportPublicKey(await getProviderPublicKey("enabled" in normalized ? createLocalSigningPlugin(normalized) : normalized, cwd));
|
|
24670
|
+
};
|
|
24671
|
+
//#endregion
|
|
24510
24672
|
//#region src/ConfigBuilder.ts
|
|
24511
24673
|
const normalizeImportInfos = (imports) => {
|
|
24512
24674
|
const collectedImports = /* @__PURE__ */ new Map();
|
|
@@ -42992,8 +43154,10 @@ const CONFIG_FILE_NAME = "hot-updater.config.ts";
|
|
|
42992
43154
|
const MANAGED_IMPORT_PACKAGES = new Set([
|
|
42993
43155
|
"dotenv",
|
|
42994
43156
|
"firebase-admin",
|
|
43157
|
+
"firebase-admin/app",
|
|
42995
43158
|
"hot-updater",
|
|
42996
43159
|
"@aws-sdk/credential-provider-sso",
|
|
43160
|
+
"@aws-sdk/credential-providers",
|
|
42997
43161
|
"@hot-updater/aws",
|
|
42998
43162
|
"@hot-updater/bare",
|
|
42999
43163
|
"@hot-updater/cloudflare",
|
|
@@ -43002,7 +43166,12 @@ const MANAGED_IMPORT_PACKAGES = new Set([
|
|
|
43002
43166
|
"@hot-updater/rock",
|
|
43003
43167
|
"@hot-updater/supabase"
|
|
43004
43168
|
]);
|
|
43005
|
-
const MANAGED_HELPER_NAMES = new Set([
|
|
43169
|
+
const MANAGED_HELPER_NAMES = new Set([
|
|
43170
|
+
"awsOptions",
|
|
43171
|
+
"commonOptions",
|
|
43172
|
+
"credential",
|
|
43173
|
+
"storageOptions"
|
|
43174
|
+
]);
|
|
43006
43175
|
const KNOWN_BUILD_CALLEES = new Set([
|
|
43007
43176
|
"bare",
|
|
43008
43177
|
"expo",
|
|
@@ -43096,7 +43265,7 @@ const appendMissingProperties = (objectText, propertyTexts, hasExistingPropertie
|
|
|
43096
43265
|
const suffix = `,\n${closingIndent}`;
|
|
43097
43266
|
return `${objectText.slice(0, closeBraceIndex)}${prefix}${formattedProperties}${suffix}${objectText.slice(closeBraceIndex)}`;
|
|
43098
43267
|
};
|
|
43099
|
-
const mergeObjectLiteralText = (existingObject, newObject) => {
|
|
43268
|
+
const mergeObjectLiteralText = (existingObject, newObject, replaceIncompatibleProperties = []) => {
|
|
43100
43269
|
const existingText = getNodeText(existingObject.source, existingObject.objectExpression);
|
|
43101
43270
|
const existingPropertyNames = /* @__PURE__ */ new Set();
|
|
43102
43271
|
const existingSpreadTexts = /* @__PURE__ */ new Set();
|
|
@@ -43111,6 +43280,17 @@ const mergeObjectLiteralText = (existingObject, newObject) => {
|
|
|
43111
43280
|
existingPropertyNames.add(propertyName);
|
|
43112
43281
|
const nextProperty = newObject.objectExpression.properties.find((candidate) => getObjectPropertyName(candidate) === propertyName);
|
|
43113
43282
|
if (!nextProperty || !isDataProperty(property) || !isDataProperty(nextProperty)) continue;
|
|
43283
|
+
const existingCallee = getCallCallee(property.value);
|
|
43284
|
+
const nextCallee = getCallCallee(nextProperty.value);
|
|
43285
|
+
const hasIncompatibleValue = existingCallee !== null && nextCallee !== null && existingCallee !== nextCallee || property.value.type === "ObjectExpression" !== (nextProperty.value.type === "ObjectExpression");
|
|
43286
|
+
if (replaceIncompatibleProperties.includes(propertyName) && hasIncompatibleValue) {
|
|
43287
|
+
edits.push({
|
|
43288
|
+
start: property.value.start - existingObject.objectExpression.start,
|
|
43289
|
+
end: property.value.end - existingObject.objectExpression.start,
|
|
43290
|
+
text: getNodeText(newObject.source, nextProperty.value)
|
|
43291
|
+
});
|
|
43292
|
+
continue;
|
|
43293
|
+
}
|
|
43114
43294
|
if (property.value.type === "ObjectExpression" && nextProperty.value.type === "ObjectExpression") {
|
|
43115
43295
|
const mergedValue = mergeObjectLiteralText({
|
|
43116
43296
|
objectExpression: property.value,
|
|
@@ -43118,7 +43298,7 @@ const mergeObjectLiteralText = (existingObject, newObject) => {
|
|
|
43118
43298
|
}, {
|
|
43119
43299
|
objectExpression: nextProperty.value,
|
|
43120
43300
|
source: newObject.source
|
|
43121
|
-
});
|
|
43301
|
+
}, replaceIncompatibleProperties);
|
|
43122
43302
|
if (!mergedValue) return null;
|
|
43123
43303
|
edits.push({
|
|
43124
43304
|
start: property.value.start - existingObject.objectExpression.start,
|
|
@@ -43175,7 +43355,7 @@ const mergeHelperStatement = (existingStatementText, helper) => {
|
|
|
43175
43355
|
}, {
|
|
43176
43356
|
objectExpression: nextInitializer,
|
|
43177
43357
|
source: nextStatement.source
|
|
43178
|
-
});
|
|
43358
|
+
}, helper.replaceIncompatibleProperties);
|
|
43179
43359
|
if (!mergedInitializer) return null;
|
|
43180
43360
|
return `${existingStatement.statement.kind === "let" || existingStatement.statement.kind === "var" ? existingStatement.statement.kind : "const"} ${helper.name} = ${mergedInitializer};`;
|
|
43181
43361
|
};
|
|
@@ -43230,7 +43410,7 @@ const getManagedHelperName = (statement) => {
|
|
|
43230
43410
|
if (statement.type !== "VariableDeclaration") return null;
|
|
43231
43411
|
const declaration = statement.declarations[0];
|
|
43232
43412
|
if (declaration?.id.type !== "Identifier") return null;
|
|
43233
|
-
return
|
|
43413
|
+
return declaration.id.name;
|
|
43234
43414
|
};
|
|
43235
43415
|
const rebuildImportBlock = (source, scaffold) => {
|
|
43236
43416
|
const importDeclarations = source.program.body.filter((statement) => statement.type === "ImportDeclaration");
|
|
@@ -43247,7 +43427,7 @@ const rebuildImportBlock = (source, scaffold) => {
|
|
|
43247
43427
|
return {
|
|
43248
43428
|
start: getTopLevelFullStart(source, firstImport),
|
|
43249
43429
|
end: lastImport.end,
|
|
43250
|
-
text:
|
|
43430
|
+
text: nextImportBlock
|
|
43251
43431
|
};
|
|
43252
43432
|
};
|
|
43253
43433
|
const rebuildManagedBody = (source, exportStart, scaffold) => {
|
|
@@ -43255,10 +43435,14 @@ const rebuildManagedBody = (source, exportStart, scaffold) => {
|
|
|
43255
43435
|
const managedHelpers = new Map(scaffold.helperStatements.map((statement) => [statement.name, statement]));
|
|
43256
43436
|
const emittedHelpers = /* @__PURE__ */ new Set();
|
|
43257
43437
|
const bodyStatements = [];
|
|
43438
|
+
const configStatements = [];
|
|
43258
43439
|
for (const statement of statementsBeforeExport) {
|
|
43259
|
-
if (isConfigCallStatement(statement))
|
|
43440
|
+
if (isConfigCallStatement(statement)) {
|
|
43441
|
+
configStatements.push(getStatementText(source, statement));
|
|
43442
|
+
continue;
|
|
43443
|
+
}
|
|
43260
43444
|
const helperName = getManagedHelperName(statement);
|
|
43261
|
-
if (!helperName) {
|
|
43445
|
+
if (!helperName || !MANAGED_HELPER_NAMES.has(helperName)) {
|
|
43262
43446
|
bodyStatements.push(getStatementText(source, statement));
|
|
43263
43447
|
continue;
|
|
43264
43448
|
}
|
|
@@ -43271,7 +43455,7 @@ const rebuildManagedBody = (source, exportStart, scaffold) => {
|
|
|
43271
43455
|
}
|
|
43272
43456
|
for (const helper of scaffold.helperStatements) if (!emittedHelpers.has(helper.name)) bodyStatements.push(helper.code.trim());
|
|
43273
43457
|
const bodyText = bodyStatements.filter(Boolean).join("\n\n");
|
|
43274
|
-
const configStatement = `config({ path: ".env.hotupdater" });`;
|
|
43458
|
+
const configStatement = configStatements.join("\n\n") || `config({ path: ".env.hotupdater" });`;
|
|
43275
43459
|
const managedBody = bodyText ? `\n\n${configStatement}\n\n${bodyText}\n\n` : `\n\n${configStatement}\n\n`;
|
|
43276
43460
|
return {
|
|
43277
43461
|
start: source.program.body.filter((statement) => statement.type === "ImportDeclaration").at(-1)?.end ?? 0,
|
|
@@ -43299,7 +43483,9 @@ const mergeHotUpdaterConfigText = (existingText, scaffold) => {
|
|
|
43299
43483
|
source: nextSource
|
|
43300
43484
|
});
|
|
43301
43485
|
if (!nextObjectText) return { reason: "Existing config uses dynamic build/storage/database expressions that cannot be merged safely." };
|
|
43302
|
-
const
|
|
43486
|
+
const exportFullStart = getTopLevelFullStart(existingSource, existingConfig.exportDeclaration);
|
|
43487
|
+
const firstTriviaContent = existingText.slice(exportFullStart, existingConfig.exportDeclaration.start).search(/\S/);
|
|
43488
|
+
const bodyEdit = rebuildManagedBody(existingSource, firstTriviaContent === -1 ? existingConfig.exportDeclaration.start : exportFullStart + firstTriviaContent, scaffold);
|
|
43303
43489
|
if (!bodyEdit) return { reason: "Existing helper declarations could not be merged safely." };
|
|
43304
43490
|
return { text: applyTextEdits(existingText, [
|
|
43305
43491
|
{
|
|
@@ -43386,6 +43572,12 @@ var MissingInitInputsError = class extends InitError {
|
|
|
43386
43572
|
var InitEnvFileError = class extends InitError {
|
|
43387
43573
|
name = "InitEnvFileError";
|
|
43388
43574
|
};
|
|
43575
|
+
var LegacyInfrastructureError = class extends InitError {
|
|
43576
|
+
name = "LegacyInfrastructureError";
|
|
43577
|
+
constructor(provider, resource) {
|
|
43578
|
+
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.`);
|
|
43579
|
+
}
|
|
43580
|
+
};
|
|
43389
43581
|
const assertInitInputs = ({ inputs, strict }) => {
|
|
43390
43582
|
if (!strict) return;
|
|
43391
43583
|
const missingInputs = getMissingInitInputs(inputs);
|
|
@@ -43461,6 +43653,26 @@ const getHotUpdaterEnvValue = (env, key) => {
|
|
|
43461
43653
|
return env[key]?.trim() || void 0;
|
|
43462
43654
|
};
|
|
43463
43655
|
//#endregion
|
|
43656
|
+
//#region src/infrastructureGeneration.ts
|
|
43657
|
+
const assertInfrastructureGenerationPayload = ({ payload, provider, resource }) => {
|
|
43658
|
+
if (typeof payload !== "object" || payload === null || !("infrastructureGeneration" in payload) || payload.infrastructureGeneration !== 1) throw new LegacyInfrastructureError(provider, resource);
|
|
43659
|
+
};
|
|
43660
|
+
const assertInfrastructureGenerationAtUrl = async ({ fetchImpl = fetch, legacyStatuses = [404], provider, resource, versionUrl }) => {
|
|
43661
|
+
let response;
|
|
43662
|
+
try {
|
|
43663
|
+
response = await fetchImpl(versionUrl);
|
|
43664
|
+
} catch (error) {
|
|
43665
|
+
throw new InitError(`Could not verify the ${provider} infrastructure generation at ${resource}: ${error instanceof Error ? error.message : String(error)}`);
|
|
43666
|
+
}
|
|
43667
|
+
if (legacyStatuses.includes(response.status)) throw new LegacyInfrastructureError(provider, resource);
|
|
43668
|
+
if (!response.ok) throw new InitError(`Could not verify the ${provider} infrastructure generation at ${resource}: HTTP ${response.status}`);
|
|
43669
|
+
assertInfrastructureGenerationPayload({
|
|
43670
|
+
payload: await response.json().catch(() => void 0),
|
|
43671
|
+
provider,
|
|
43672
|
+
resource
|
|
43673
|
+
});
|
|
43674
|
+
};
|
|
43675
|
+
//#endregion
|
|
43464
43676
|
//#region src/initProvider.ts
|
|
43465
43677
|
const defineInitProvider = (provider) => provider;
|
|
43466
43678
|
const shouldAutoSelectOnlyInitResource = ({ availableResourceCount, savedIdentifier }) => savedIdentifier === void 0 && availableResourceCount === 1;
|
|
@@ -43635,6 +43847,91 @@ function isMergeableValue(value) {
|
|
|
43635
43847
|
//#endregion
|
|
43636
43848
|
//#region src/loadConfig.ts
|
|
43637
43849
|
var import_out = /* @__PURE__ */ __toESM(require_out(), 1);
|
|
43850
|
+
const missingDatabase = createDatabasePlugin({
|
|
43851
|
+
name: "missingDatabase",
|
|
43852
|
+
models: {
|
|
43853
|
+
bundles: {
|
|
43854
|
+
findById: async () => {
|
|
43855
|
+
throw new Error("database plugin is required");
|
|
43856
|
+
},
|
|
43857
|
+
findMany: async () => {
|
|
43858
|
+
throw new Error("database plugin is required");
|
|
43859
|
+
},
|
|
43860
|
+
count: async () => {
|
|
43861
|
+
throw new Error("database plugin is required");
|
|
43862
|
+
}
|
|
43863
|
+
},
|
|
43864
|
+
bundlePatches: { findByBundleIds: async () => {
|
|
43865
|
+
throw new Error("database plugin is required");
|
|
43866
|
+
} },
|
|
43867
|
+
releases: {
|
|
43868
|
+
findById: async () => {
|
|
43869
|
+
throw new Error("database plugin is required");
|
|
43870
|
+
},
|
|
43871
|
+
findMany: async () => {
|
|
43872
|
+
throw new Error("database plugin is required");
|
|
43873
|
+
},
|
|
43874
|
+
findManyByScope: async () => {
|
|
43875
|
+
throw new Error("database plugin is required");
|
|
43876
|
+
}
|
|
43877
|
+
},
|
|
43878
|
+
releaseCatalogs: {
|
|
43879
|
+
findByScopeKey: async () => {
|
|
43880
|
+
throw new Error("database plugin is required");
|
|
43881
|
+
},
|
|
43882
|
+
findMany: async () => {
|
|
43883
|
+
throw new Error("database plugin is required");
|
|
43884
|
+
}
|
|
43885
|
+
},
|
|
43886
|
+
channels: {
|
|
43887
|
+
insert: async () => {
|
|
43888
|
+
throw new Error("database plugin is required");
|
|
43889
|
+
},
|
|
43890
|
+
list: async () => {
|
|
43891
|
+
throw new Error("database plugin is required");
|
|
43892
|
+
},
|
|
43893
|
+
delete: async () => {
|
|
43894
|
+
throw new Error("database plugin is required");
|
|
43895
|
+
}
|
|
43896
|
+
},
|
|
43897
|
+
insights: {
|
|
43898
|
+
append: async () => {
|
|
43899
|
+
throw new Error("database plugin is required");
|
|
43900
|
+
},
|
|
43901
|
+
scan: async () => {
|
|
43902
|
+
throw new Error("database plugin is required");
|
|
43903
|
+
}
|
|
43904
|
+
},
|
|
43905
|
+
apiKeys: {
|
|
43906
|
+
create: async () => {
|
|
43907
|
+
throw new Error("database plugin is required");
|
|
43908
|
+
},
|
|
43909
|
+
findByHash: async () => {
|
|
43910
|
+
throw new Error("database plugin is required");
|
|
43911
|
+
},
|
|
43912
|
+
list: async () => {
|
|
43913
|
+
throw new Error("database plugin is required");
|
|
43914
|
+
},
|
|
43915
|
+
revoke: async () => {
|
|
43916
|
+
throw new Error("database plugin is required");
|
|
43917
|
+
}
|
|
43918
|
+
}
|
|
43919
|
+
},
|
|
43920
|
+
commit: async () => {
|
|
43921
|
+
throw new Error("database plugin is required");
|
|
43922
|
+
}
|
|
43923
|
+
});
|
|
43924
|
+
const missingStorageError = async () => {
|
|
43925
|
+
throw new Error("storage plugin is required");
|
|
43926
|
+
};
|
|
43927
|
+
const missingStorage = createStoragePlugin({
|
|
43928
|
+
name: "missingStorage",
|
|
43929
|
+
protocol: "missing",
|
|
43930
|
+
put: missingStorageError,
|
|
43931
|
+
get: missingStorageError,
|
|
43932
|
+
exists: missingStorageError,
|
|
43933
|
+
delete: missingStorageError
|
|
43934
|
+
});
|
|
43638
43935
|
const getDefaultPlatformConfig = () => {
|
|
43639
43936
|
let infoPlistPaths = [];
|
|
43640
43937
|
try {
|
|
@@ -43662,27 +43959,14 @@ const getDefaultPlatformConfig = () => {
|
|
|
43662
43959
|
});
|
|
43663
43960
|
if (manifestFiles.length > 0) androidManifestPaths = manifestFiles.map((file) => path.join("android", file));
|
|
43664
43961
|
} 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
43962
|
return {
|
|
43675
|
-
android: {
|
|
43676
|
-
androidManifestPaths,
|
|
43677
|
-
stringResourcePaths
|
|
43678
|
-
},
|
|
43963
|
+
android: { androidManifestPaths },
|
|
43679
43964
|
ios: { infoPlistPaths }
|
|
43680
43965
|
};
|
|
43681
43966
|
};
|
|
43682
43967
|
const getDefaultConfig = () => {
|
|
43683
43968
|
return {
|
|
43684
43969
|
cacheDir: path.join("node_modules", ".hot-updater"),
|
|
43685
|
-
releaseChannel: "production",
|
|
43686
43970
|
updateStrategy: "appVersion",
|
|
43687
43971
|
compressStrategy: "zip",
|
|
43688
43972
|
fingerprint: {},
|
|
@@ -43699,16 +43983,21 @@ const getDefaultConfig = () => {
|
|
|
43699
43983
|
build: () => {
|
|
43700
43984
|
throw new Error("build plugin is required");
|
|
43701
43985
|
},
|
|
43702
|
-
storage:
|
|
43703
|
-
|
|
43704
|
-
},
|
|
43705
|
-
database: () => {
|
|
43706
|
-
throw new Error("database plugin is required");
|
|
43707
|
-
}
|
|
43986
|
+
storage: missingStorage,
|
|
43987
|
+
database: missingDatabase
|
|
43708
43988
|
};
|
|
43709
43989
|
};
|
|
43710
43990
|
const mergeConfigSources = (...sources) => {
|
|
43711
|
-
|
|
43991
|
+
const mergedConfig = sources.reduceRight((mergedConfig, source) => merge(mergedConfig, source ?? {}), {});
|
|
43992
|
+
const database = sources.find((source) => source?.database)?.database;
|
|
43993
|
+
const signing = sources.find((source) => source?.signing)?.signing;
|
|
43994
|
+
const storage = sources.find((source) => source?.storage)?.storage;
|
|
43995
|
+
return {
|
|
43996
|
+
...mergedConfig,
|
|
43997
|
+
...database ? { database } : {},
|
|
43998
|
+
...signing ? { signing } : {},
|
|
43999
|
+
...storage ? { storage } : {}
|
|
44000
|
+
};
|
|
43712
44001
|
};
|
|
43713
44002
|
const getConfigLoaderOptions = (options) => {
|
|
43714
44003
|
const cwd = getCwd();
|
|
@@ -43734,7 +44023,13 @@ const getConfigLoaderOptions = (options) => {
|
|
|
43734
44023
|
};
|
|
43735
44024
|
const loadConfig = async (options) => {
|
|
43736
44025
|
const { config } = await loadConfig$1(getConfigLoaderOptions(options));
|
|
43737
|
-
|
|
44026
|
+
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.`);
|
|
44027
|
+
const mergedConfig = mergeConfigSources(config, getDefaultConfig());
|
|
44028
|
+
const signing = normalizeSigningConfig(mergedConfig.signing);
|
|
44029
|
+
return {
|
|
44030
|
+
...mergedConfig,
|
|
44031
|
+
signing
|
|
44032
|
+
};
|
|
43738
44033
|
};
|
|
43739
44034
|
//#endregion
|
|
43740
44035
|
//#region src/log.ts
|
|
@@ -43849,9 +44144,52 @@ const makeEnv = async (newEnvVars, filePath = ".env.hotupdater", options) => {
|
|
|
43849
44144
|
}
|
|
43850
44145
|
};
|
|
43851
44146
|
//#endregion
|
|
44147
|
+
//#region src/storageFiles.ts
|
|
44148
|
+
const getStorageFileByteSize = async (filePath) => {
|
|
44149
|
+
const { size } = await fs$3.stat(filePath, { bigint: true });
|
|
44150
|
+
if (size < 0n || size > BigInt(Number.MAX_SAFE_INTEGER)) throw new Error("Storage file size must be a non-negative safe integer.");
|
|
44151
|
+
return Number(size);
|
|
44152
|
+
};
|
|
44153
|
+
const putStorageFile = async (storage, key, filePath) => {
|
|
44154
|
+
const byteSize = await getStorageFileByteSize(filePath);
|
|
44155
|
+
const source = createReadStream(filePath);
|
|
44156
|
+
try {
|
|
44157
|
+
return {
|
|
44158
|
+
...await storage.put({
|
|
44159
|
+
key: path$1.posix.join(key, path$1.basename(filePath)),
|
|
44160
|
+
body: Readable$1.toWeb(source),
|
|
44161
|
+
contentLength: byteSize,
|
|
44162
|
+
contentType: getContentType(filePath)
|
|
44163
|
+
}),
|
|
44164
|
+
byteSize
|
|
44165
|
+
};
|
|
44166
|
+
} finally {
|
|
44167
|
+
source.destroy();
|
|
44168
|
+
}
|
|
44169
|
+
};
|
|
44170
|
+
const writeStorageFile = async (storage, storageUri, filePath) => {
|
|
44171
|
+
const { response } = await storage.get({ storageUri });
|
|
44172
|
+
if (response === null) throw new Error(`Storage object not found: ${storageUri}`);
|
|
44173
|
+
await writeStorageResponseFile(response, filePath);
|
|
44174
|
+
};
|
|
44175
|
+
const writeStorageResponseFile = async (response, filePath) => {
|
|
44176
|
+
await fs$3.mkdir(path$1.dirname(filePath), { recursive: true });
|
|
44177
|
+
if (response.body === null) {
|
|
44178
|
+
await fs$3.writeFile(filePath, new Uint8Array());
|
|
44179
|
+
return;
|
|
44180
|
+
}
|
|
44181
|
+
try {
|
|
44182
|
+
await pipeline$1(Readable$1.fromWeb(response.body), createWriteStream$1(filePath));
|
|
44183
|
+
} catch (error) {
|
|
44184
|
+
await fs$3.rm(filePath, { force: true });
|
|
44185
|
+
throw error;
|
|
44186
|
+
}
|
|
44187
|
+
};
|
|
44188
|
+
//#endregion
|
|
43852
44189
|
//#region src/promoteBundle.ts
|
|
43853
44190
|
const LEGACY_BUNDLE_ERROR = "This OTA bundle was created by a version that does not support manifest.json. Copy bundle is not available.";
|
|
43854
44191
|
const SIGNED_HASH_PREFIX = "sig:";
|
|
44192
|
+
const PROMOTE_ASSET_CONCURRENCY = 8;
|
|
43855
44193
|
function isSignedFileHash(fileHash) {
|
|
43856
44194
|
return fileHash.startsWith(SIGNED_HASH_PREFIX);
|
|
43857
44195
|
}
|
|
@@ -43859,16 +44197,27 @@ async function getFileHash(filepath) {
|
|
|
43859
44197
|
const file = await fs$3.readFile(filepath);
|
|
43860
44198
|
return crypto$1.createHash("sha256").update(file).digest("hex");
|
|
43861
44199
|
}
|
|
43862
|
-
async function
|
|
43863
|
-
|
|
43864
|
-
const
|
|
43865
|
-
|
|
43866
|
-
|
|
43867
|
-
|
|
44200
|
+
async function runWithConcurrency(items, concurrency, task) {
|
|
44201
|
+
let nextIndex = 0;
|
|
44202
|
+
const workerCount = Math.min(concurrency, items.length);
|
|
44203
|
+
await Promise.all(Array.from({ length: workerCount }, async () => {
|
|
44204
|
+
while (nextIndex < items.length) {
|
|
44205
|
+
const itemIndex = nextIndex;
|
|
44206
|
+
nextIndex += 1;
|
|
44207
|
+
await task(items[itemIndex]);
|
|
44208
|
+
}
|
|
44209
|
+
}));
|
|
44210
|
+
}
|
|
44211
|
+
function verifySignedFileHash({ actualFileHash, publicKey, signedFileHash }) {
|
|
44212
|
+
try {
|
|
44213
|
+
return crypto$1.verify("RSA-SHA256", Buffer.from(actualFileHash, "hex"), publicKey, Buffer.from(signedFileHash.slice(4), "base64"));
|
|
44214
|
+
} catch {
|
|
44215
|
+
return false;
|
|
44216
|
+
}
|
|
43868
44217
|
}
|
|
43869
44218
|
function getArchiveFilename(storageUri) {
|
|
43870
|
-
const {
|
|
43871
|
-
return path$1.basename(
|
|
44219
|
+
const { key } = parseStorageUri(storageUri, new URL(storageUri).protocol.replace(":", ""));
|
|
44220
|
+
return path$1.posix.basename(key) || "bundle.zip";
|
|
43872
44221
|
}
|
|
43873
44222
|
const getRelativeStorageDir = (relativePath) => {
|
|
43874
44223
|
const normalized = relativePath.replace(/\\/g, "/");
|
|
@@ -43886,7 +44235,7 @@ async function prepareManifestAssetUploadFile({ assetPath, sourcePath, workDir }
|
|
|
43886
44235
|
if (getManifestAssetDownloadPath(assetPath) === assetPath) return sourcePath;
|
|
43887
44236
|
const uploadPath = resolvePreparedUploadPath(workDir, assetPath);
|
|
43888
44237
|
await fs$3.mkdir(path$1.dirname(uploadPath), { recursive: true });
|
|
43889
|
-
await pipeline$1(createReadStream(sourcePath), createBrotliCompress$1(), createWriteStream$1(uploadPath));
|
|
44238
|
+
await pipeline$1(createReadStream(sourcePath), createBrotliCompress$1({ params: { [constants$2.BROTLI_PARAM_QUALITY]: 11 } }), createWriteStream$1(uploadPath));
|
|
43890
44239
|
return uploadPath;
|
|
43891
44240
|
}
|
|
43892
44241
|
async function prepareContentAddressedUploadFile({ sourcePath, storagePath, workDir }) {
|
|
@@ -43897,6 +44246,48 @@ async function prepareContentAddressedUploadFile({ sourcePath, storagePath, work
|
|
|
43897
44246
|
await fs$3.copyFile(sourcePath, uploadPath);
|
|
43898
44247
|
return uploadPath;
|
|
43899
44248
|
}
|
|
44249
|
+
async function prepareManifestAssetUploadTargets({ extractDir, manifest, workDir }) {
|
|
44250
|
+
const targets = /* @__PURE__ */ new Map();
|
|
44251
|
+
const assetPaths = Object.keys(manifest.assets ?? {}).sort((left, right) => left.localeCompare(right));
|
|
44252
|
+
for (const assetPath of assetPaths) {
|
|
44253
|
+
const asset = manifest.assets?.[assetPath];
|
|
44254
|
+
if (!asset?.fileHash) throw new Error(`Manifest file hash not found for ${assetPath}`);
|
|
44255
|
+
const uploadSourcePath = await prepareManifestAssetUploadFile({
|
|
44256
|
+
assetPath,
|
|
44257
|
+
sourcePath: resolveExtractedPath(extractDir, assetPath),
|
|
44258
|
+
workDir
|
|
44259
|
+
});
|
|
44260
|
+
const downloadByteSize = await getStorageFileByteSize(uploadSourcePath);
|
|
44261
|
+
const downloadPath = getManifestAssetDownloadPath(assetPath);
|
|
44262
|
+
const downloadFileHash = downloadPath !== assetPath ? await getFileHash(uploadSourcePath) : void 0;
|
|
44263
|
+
if (downloadFileHash !== void 0 && !isContentAddressedAssetFileHash(downloadFileHash)) throw new Error(`Prepared asset hash must be a lowercase SHA-256 hash: ${assetPath}`);
|
|
44264
|
+
const nextAsset = {
|
|
44265
|
+
...asset,
|
|
44266
|
+
downloadByteSize
|
|
44267
|
+
};
|
|
44268
|
+
delete nextAsset.downloadFileHash;
|
|
44269
|
+
if (downloadFileHash !== void 0) nextAsset.downloadFileHash = downloadFileHash;
|
|
44270
|
+
manifest.assets[assetPath] = nextAsset;
|
|
44271
|
+
const storagePath = getManifestAssetStoragePath({
|
|
44272
|
+
assetPath: downloadPath,
|
|
44273
|
+
downloadFileHash,
|
|
44274
|
+
fileHash: asset.fileHash
|
|
44275
|
+
});
|
|
44276
|
+
const contentAddressedUploadPath = await prepareContentAddressedUploadFile({
|
|
44277
|
+
sourcePath: uploadSourcePath,
|
|
44278
|
+
storagePath,
|
|
44279
|
+
workDir
|
|
44280
|
+
});
|
|
44281
|
+
targets.set(storagePath, {
|
|
44282
|
+
assetPath: downloadPath,
|
|
44283
|
+
downloadFileHash,
|
|
44284
|
+
fileHash: asset.fileHash,
|
|
44285
|
+
storagePath,
|
|
44286
|
+
uploadSourcePath: contentAddressedUploadPath
|
|
44287
|
+
});
|
|
44288
|
+
}
|
|
44289
|
+
return [...targets.values()];
|
|
44290
|
+
}
|
|
43900
44291
|
function resolveExtractedPath(rootDir, entryName) {
|
|
43901
44292
|
const normalizedEntryName = entryName.replaceAll("\\", "/");
|
|
43902
44293
|
const entryPath = path$1.resolve(rootDir, normalizedEntryName);
|
|
@@ -43906,23 +44297,20 @@ function resolveExtractedPath(rootDir, entryName) {
|
|
|
43906
44297
|
}
|
|
43907
44298
|
async function downloadArchive(storageUri, storagePlugin, archivePath) {
|
|
43908
44299
|
const protocol = new URL(storageUri).protocol.replace(":", "");
|
|
44300
|
+
if (storagePlugin?.protocol === protocol) {
|
|
44301
|
+
await writeStorageFile(storagePlugin, storageUri, archivePath);
|
|
44302
|
+
return;
|
|
44303
|
+
}
|
|
43909
44304
|
if (protocol === "http" || protocol === "https") {
|
|
43910
|
-
|
|
43911
|
-
await fs$3.writeFile(archivePath, archiveBuffer);
|
|
44305
|
+
await downloadFromUrl(storageUri, archivePath);
|
|
43912
44306
|
return;
|
|
43913
44307
|
}
|
|
43914
|
-
|
|
44308
|
+
throw new Error(`No storage plugin for protocol: ${protocol}`);
|
|
43915
44309
|
}
|
|
43916
|
-
async function downloadFromUrl(fileUrl) {
|
|
44310
|
+
async function downloadFromUrl(fileUrl, filePath) {
|
|
43917
44311
|
const response = await fetch(fileUrl);
|
|
43918
44312
|
if (!response.ok) throw new Error(`Failed to download bundle archive: ${response.statusText}`);
|
|
43919
|
-
|
|
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);
|
|
44313
|
+
await writeStorageResponseFile(response, filePath);
|
|
43926
44314
|
}
|
|
43927
44315
|
async function extractZipArchive(archivePath, extractDir) {
|
|
43928
44316
|
const zip = await import_lib.default.loadAsync(await fs$3.readFile(archivePath));
|
|
@@ -44002,7 +44390,7 @@ async function createArchiveFromDirectory(sourceDir, archivePath, format) {
|
|
|
44002
44390
|
return;
|
|
44003
44391
|
}
|
|
44004
44392
|
}
|
|
44005
|
-
async function
|
|
44393
|
+
async function readCopiedBundleManifest(extractDir, nextBundleId) {
|
|
44006
44394
|
const manifestPath = path$1.join(extractDir, "manifest.json");
|
|
44007
44395
|
try {
|
|
44008
44396
|
await fs$3.access(manifestPath);
|
|
@@ -44011,13 +44399,12 @@ async function rewriteManifestBundleId(extractDir, nextBundleId) {
|
|
|
44011
44399
|
}
|
|
44012
44400
|
const manifest = JSON.parse(await fs$3.readFile(manifestPath, "utf8"));
|
|
44013
44401
|
manifest.bundleId = nextBundleId;
|
|
44014
|
-
await fs$3.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
44015
44402
|
return {
|
|
44016
44403
|
manifest,
|
|
44017
44404
|
manifestPath
|
|
44018
44405
|
};
|
|
44019
44406
|
}
|
|
44020
|
-
async function createCopiedBundleArchive({ bundle, config, nextBundleId, storagePlugin
|
|
44407
|
+
async function createCopiedBundleArchive({ bundle, config, nextBundleId, storagePlugin }) {
|
|
44021
44408
|
const archiveFilename = getArchiveFilename(bundle.storageUri);
|
|
44022
44409
|
const workDir = await fs$3.mkdtemp(path$1.join(os.tmpdir(), "hot-updater-console-promote-"));
|
|
44023
44410
|
const sourceArchivePath = path$1.join(workDir, archiveFilename);
|
|
@@ -44027,65 +44414,73 @@ async function createCopiedBundleArchive({ bundle, config, nextBundleId, storage
|
|
|
44027
44414
|
await fs$3.mkdir(extractDir, { recursive: true });
|
|
44028
44415
|
try {
|
|
44029
44416
|
await downloadArchive(bundle.storageUri, storagePlugin, sourceArchivePath);
|
|
44417
|
+
const actualSourceFileHash = await getFileHash(sourceArchivePath);
|
|
44418
|
+
const signingSession = await prepareBundleSigning(config.signing);
|
|
44419
|
+
if (isSignedFileHash(bundle.fileHash)) {
|
|
44420
|
+
if (!signingSession) throw new Error("Cannot copy a signed bundle without enabled bundle signing configuration.");
|
|
44421
|
+
if (!verifySignedFileHash({
|
|
44422
|
+
actualFileHash: actualSourceFileHash,
|
|
44423
|
+
publicKey: signingSession.publicKey,
|
|
44424
|
+
signedFileHash: bundle.fileHash
|
|
44425
|
+
})) throw new Error("Source bundle signature verification failed.");
|
|
44426
|
+
} else if (actualSourceFileHash !== bundle.fileHash.toLowerCase()) throw new Error("Source bundle file hash verification failed.");
|
|
44030
44427
|
const format = await extractArchive(sourceArchivePath, extractDir);
|
|
44031
|
-
const { manifest, manifestPath } = await
|
|
44428
|
+
const { manifest, manifestPath } = await readCopiedBundleManifest(extractDir, nextBundleId);
|
|
44429
|
+
const assetPaths = Object.keys(manifest.assets ?? {}).sort((left, right) => left.localeCompare(right));
|
|
44430
|
+
const sourceIsSigned = [bundle.fileHash, getManifestFileHash(bundle)].filter((hash) => Boolean(hash)).some((hash) => isSignedFileHash(hash));
|
|
44431
|
+
const manifestHasSignatures = assetPaths.some((assetPath) => Boolean(manifest.assets?.[assetPath]?.signature));
|
|
44432
|
+
if (!signingSession && (sourceIsSigned || manifestHasSignatures)) throw new Error("Cannot copy a signed bundle without enabled bundle signing configuration.");
|
|
44433
|
+
if (signingSession) {
|
|
44434
|
+
await runWithConcurrency(assetPaths, PROMOTE_ASSET_CONCURRENCY, async (assetPath) => {
|
|
44435
|
+
const asset = manifest.assets?.[assetPath];
|
|
44436
|
+
if (!asset?.fileHash) throw new Error(`Manifest file hash not found for ${assetPath}`);
|
|
44437
|
+
if (await getFileHash(resolveExtractedPath(extractDir, assetPath)) !== asset.fileHash.toLowerCase()) throw new Error(`Manifest file hash mismatch for ${assetPath}`);
|
|
44438
|
+
});
|
|
44439
|
+
await runWithConcurrency(assetPaths, PROMOTE_ASSET_CONCURRENCY, async (assetPath) => {
|
|
44440
|
+
const asset = manifest.assets?.[assetPath];
|
|
44441
|
+
if (!asset?.fileHash) throw new Error(`Manifest file hash not found for ${assetPath}`);
|
|
44442
|
+
asset.signature = await signingSession.signFileHash(asset.fileHash);
|
|
44443
|
+
});
|
|
44444
|
+
}
|
|
44445
|
+
const assetUploadTargets = await prepareManifestAssetUploadTargets({
|
|
44446
|
+
extractDir,
|
|
44447
|
+
manifest,
|
|
44448
|
+
workDir
|
|
44449
|
+
});
|
|
44450
|
+
await fs$3.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
44032
44451
|
await fs$3.rm(sourceArchivePath, { force: true });
|
|
44033
44452
|
await createArchiveFromDirectory(extractDir, outputArchivePath, format);
|
|
44034
44453
|
const fileHash = await getFileHash(outputArchivePath);
|
|
44035
44454
|
const manifestHash = await getFileHash(manifestPath);
|
|
44036
|
-
|
|
44037
|
-
const
|
|
44038
|
-
const
|
|
44039
|
-
const nextManifestFileHash = signingKeyPath ? await signFileHash(manifestHash, signingKeyPath) : manifestHash;
|
|
44040
|
-
const archiveUpload = await storagePlugin.profiles.node.upload(createBundleStorageKey(nextBundleId), outputArchivePath);
|
|
44455
|
+
const nextFileHash = signingSession ? `${SIGNED_HASH_PREFIX}${await signingSession.signFileHash(fileHash)}` : fileHash;
|
|
44456
|
+
const nextManifestFileHash = signingSession ? `${SIGNED_HASH_PREFIX}${await signingSession.signFileHash(manifestHash)}` : manifestHash;
|
|
44457
|
+
const archiveUpload = await putStorageFile(storagePlugin, createBundleStorageKey(nextBundleId), outputArchivePath);
|
|
44041
44458
|
uploadedStorageUris.push(archiveUpload.storageUri);
|
|
44042
|
-
const
|
|
44043
|
-
|
|
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
|
-
});
|
|
44459
|
+
const assetBaseStorageUri = createStorageRootUriWithPath(archiveUpload.storageUri, nextBundleId, "assets");
|
|
44460
|
+
for (const assetUploadTarget of assetUploadTargets) {
|
|
44059
44461
|
const storageUri = resolveManifestAssetStorageUri({
|
|
44060
44462
|
assetBaseStorageUri,
|
|
44061
|
-
assetPath:
|
|
44062
|
-
|
|
44463
|
+
assetPath: assetUploadTarget.assetPath,
|
|
44464
|
+
downloadFileHash: assetUploadTarget.downloadFileHash,
|
|
44465
|
+
fileHash: assetUploadTarget.fileHash
|
|
44063
44466
|
});
|
|
44064
|
-
|
|
44065
|
-
|
|
44066
|
-
sourcePath: uploadPath,
|
|
44067
|
-
storagePath,
|
|
44068
|
-
workDir
|
|
44069
|
-
});
|
|
44070
|
-
await storagePlugin.profiles.node.upload(getRelativeStorageDir(storagePath) ? `assets/${getRelativeStorageDir(storagePath)}` : "assets", contentAddressedUploadPath);
|
|
44071
|
-
}
|
|
44467
|
+
const { exists } = await storagePlugin.exists({ storageUri });
|
|
44468
|
+
if (!exists) await putStorageFile(storagePlugin, getRelativeStorageDir(assetUploadTarget.storagePath) ? `assets/${getRelativeStorageDir(assetUploadTarget.storagePath)}` : "assets", assetUploadTarget.uploadSourcePath);
|
|
44072
44469
|
}
|
|
44470
|
+
const manifestUpload = await putStorageFile(storagePlugin, createBundleStorageKey(nextBundleId), manifestPath);
|
|
44471
|
+
uploadedStorageUris.push(manifestUpload.storageUri);
|
|
44073
44472
|
return {
|
|
44074
44473
|
bundle: {
|
|
44075
44474
|
...bundle,
|
|
44076
44475
|
id: nextBundleId,
|
|
44077
|
-
|
|
44476
|
+
archiveByteSize: archiveUpload.byteSize,
|
|
44078
44477
|
storageUri: archiveUpload.storageUri,
|
|
44079
44478
|
fileHash: nextFileHash,
|
|
44080
44479
|
metadata: stripBundleArtifactMetadata(bundle.metadata),
|
|
44081
44480
|
assetBaseStorageUri,
|
|
44082
44481
|
patches: [],
|
|
44083
|
-
patchBaseBundleId: null,
|
|
44084
44482
|
manifestFileHash: nextManifestFileHash,
|
|
44085
|
-
manifestStorageUri: manifestUpload.storageUri
|
|
44086
|
-
patchBaseFileHash: null,
|
|
44087
|
-
patchFileHash: null,
|
|
44088
|
-
patchStorageUri: null
|
|
44483
|
+
manifestStorageUri: manifestUpload.storageUri
|
|
44089
44484
|
},
|
|
44090
44485
|
uploadedStorageUris
|
|
44091
44486
|
};
|
|
@@ -44102,44 +44497,13 @@ async function createCopiedBundleArchive({ bundle, config, nextBundleId, storage
|
|
|
44102
44497
|
async function deleteUploadedCopy(storagePlugin, storageUris) {
|
|
44103
44498
|
if (storageUris.length === 0) return;
|
|
44104
44499
|
for (const storageUri of new Set(storageUris)) try {
|
|
44105
|
-
|
|
44500
|
+
const protocol = new URL(storageUri).protocol.replace(":", "");
|
|
44501
|
+
if (storagePlugin.protocol === protocol) await storagePlugin.delete({ storageUri });
|
|
44502
|
+
else if (protocol !== "http" && protocol !== "https") throw new Error(`No storage plugin for protocol: ${protocol}`);
|
|
44106
44503
|
} catch (error) {
|
|
44107
44504
|
console.error("Failed to delete uploaded bundle copy:", error);
|
|
44108
44505
|
}
|
|
44109
44506
|
}
|
|
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
44507
|
//#endregion
|
|
44144
44508
|
//#region src/resolvePackageVersion.ts
|
|
44145
44509
|
const require$1 = createRequire(import.meta.url);
|
|
@@ -44206,4 +44570,4 @@ function transformTemplate(templateString, values) {
|
|
|
44206
44570
|
return result;
|
|
44207
44571
|
}
|
|
44208
44572
|
//#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,
|
|
44573
|
+
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, readBundleSigningPublicKeyFile, 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.
|
|
3
|
+
"version": "1.0.0-rc.1",
|
|
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.
|
|
53
|
-
"@hot-updater/
|
|
52
|
+
"@hot-updater/plugin-core": "1.0.0-rc.1",
|
|
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.
|
|
67
|
+
"@hot-updater/test-utils": "1.0.0-rc.1"
|
|
68
68
|
},
|
|
69
69
|
"inlinedDependencies": {
|
|
70
70
|
"@babel/code-frame": "7.29.0",
|