@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 +69 -36
- package/dist/index.mjs +519 -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,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:
|
|
512
|
-
targetChannel: string;
|
|
554
|
+
storagePlugin: PromoteStoragePlugin;
|
|
513
555
|
}): Promise<{
|
|
514
556
|
bundle: {
|
|
515
557
|
id: string;
|
|
516
|
-
|
|
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,
|
|
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([
|
|
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
|
|
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:
|
|
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))
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
43863
|
-
|
|
43864
|
-
const
|
|
43865
|
-
|
|
43866
|
-
|
|
43867
|
-
|
|
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 {
|
|
43871
|
-
return path$1.basename(
|
|
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
|
-
|
|
43911
|
-
await fs$3.writeFile(archivePath, archiveBuffer);
|
|
44327
|
+
await downloadFromUrl(storageUri, archivePath);
|
|
43912
44328
|
return;
|
|
43913
44329
|
}
|
|
43914
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
44037
|
-
const
|
|
44038
|
-
const
|
|
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
|
|
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
|
-
});
|
|
44481
|
+
const assetBaseStorageUri = createStorageRootUriWithPath(archiveUpload.storageUri, nextBundleId, "assets");
|
|
44482
|
+
for (const assetUploadTarget of assetUploadTargets) {
|
|
44059
44483
|
const storageUri = resolveManifestAssetStorageUri({
|
|
44060
44484
|
assetBaseStorageUri,
|
|
44061
|
-
assetPath:
|
|
44062
|
-
|
|
44485
|
+
assetPath: assetUploadTarget.assetPath,
|
|
44486
|
+
downloadFileHash: assetUploadTarget.downloadFileHash,
|
|
44487
|
+
fileHash: assetUploadTarget.fileHash
|
|
44063
44488
|
});
|
|
44064
|
-
|
|
44065
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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.
|
|
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.
|
|
53
|
-
"@hot-updater/
|
|
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.
|
|
67
|
+
"@hot-updater/test-utils": "1.0.0-rc.0"
|
|
68
68
|
},
|
|
69
69
|
"inlinedDependencies": {
|
|
70
70
|
"@babel/code-frame": "7.29.0",
|