@hot-updater/cli-tools 0.30.12 → 0.31.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 +29 -25
- package/dist/index.mjs +89 -31
- package/package.json +5 -4
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +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
|
|
4
|
+
import * as _$_hot_updater_core0 from "@hot-updater/core";
|
|
5
|
+
import { Bundle, ConfigInput, DatabasePlugin, NodeStoragePlugin, Platform, RequiredDeep } from "@hot-updater/plugin-core";
|
|
5
6
|
|
|
6
7
|
//#region src/BuildLogger.d.ts
|
|
7
8
|
type LinePattern = string | RegExp;
|
|
@@ -366,14 +367,6 @@ declare const makeEnv: (newEnvVars: Record<string, EnvVarValue>, filePath?: stri
|
|
|
366
367
|
preserveKeys?: string[];
|
|
367
368
|
}) => Promise<string>;
|
|
368
369
|
//#endregion
|
|
369
|
-
//#region ../core/dist/index.d.mts
|
|
370
|
-
//#endregion
|
|
371
|
-
//#region src/types.d.ts
|
|
372
|
-
type Platform$1 = "ios" | "android";
|
|
373
|
-
type BundleMetadata = {
|
|
374
|
-
app_version?: string;
|
|
375
|
-
};
|
|
376
|
-
//#endregion
|
|
377
370
|
//#region src/promoteBundle.d.ts
|
|
378
371
|
declare const LEGACY_BUNDLE_ERROR = "This OTA bundle was created by a version that does not support manifest.json. Copy bundle is not available.";
|
|
379
372
|
interface PromoteBundleInput {
|
|
@@ -385,7 +378,7 @@ interface PromoteBundleInput {
|
|
|
385
378
|
interface PromoteBundleDependencies {
|
|
386
379
|
config: ConfigResponse;
|
|
387
380
|
databasePlugin: DatabasePlugin;
|
|
388
|
-
storagePlugin:
|
|
381
|
+
storagePlugin: NodeStoragePlugin | null;
|
|
389
382
|
}
|
|
390
383
|
declare function createCopiedBundleArchive({
|
|
391
384
|
bundle,
|
|
@@ -397,23 +390,34 @@ declare function createCopiedBundleArchive({
|
|
|
397
390
|
bundle: Bundle;
|
|
398
391
|
config: ConfigResponse;
|
|
399
392
|
nextBundleId: string;
|
|
400
|
-
storagePlugin:
|
|
393
|
+
storagePlugin: NodeStoragePlugin;
|
|
401
394
|
targetChannel: string;
|
|
402
395
|
}): Promise<{
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
396
|
+
bundle: {
|
|
397
|
+
id: string;
|
|
398
|
+
channel: string;
|
|
399
|
+
storageUri: string;
|
|
400
|
+
fileHash: string;
|
|
401
|
+
metadata: _$_hot_updater_core0.BundleMetadata | undefined;
|
|
402
|
+
assetBaseStorageUri: string;
|
|
403
|
+
patches: never[];
|
|
404
|
+
patchBaseBundleId: null;
|
|
405
|
+
manifestFileHash: string;
|
|
406
|
+
manifestStorageUri: string;
|
|
407
|
+
patchBaseFileHash: null;
|
|
408
|
+
patchFileHash: null;
|
|
409
|
+
patchStorageUri: null;
|
|
410
|
+
platform: _$_hot_updater_core0.Platform;
|
|
411
|
+
shouldForceUpdate: boolean;
|
|
412
|
+
enabled: boolean;
|
|
413
|
+
gitCommitHash: string | null;
|
|
414
|
+
message: string | null;
|
|
415
|
+
targetAppVersion: string | null;
|
|
416
|
+
fingerprintHash: string | null;
|
|
417
|
+
rolloutCohortCount?: number | null;
|
|
418
|
+
targetCohorts?: string[] | null;
|
|
419
|
+
};
|
|
420
|
+
uploadedStorageUris: string[];
|
|
417
421
|
}>;
|
|
418
422
|
declare function promoteBundle({
|
|
419
423
|
action,
|
package/dist/index.mjs
CHANGED
|
@@ -34,6 +34,7 @@ import { Buffer as Buffer$2 } from "node:buffer";
|
|
|
34
34
|
import ts from "typescript";
|
|
35
35
|
import { loadConfig as loadConfig$1 } from "unconfig";
|
|
36
36
|
import { brotliDecompressSync } from "node:zlib";
|
|
37
|
+
import { getManifestFileHash, stripBundleArtifactMetadata } from "@hot-updater/core";
|
|
37
38
|
import { createUUIDv7, detectCompressionFormat } from "@hot-updater/plugin-core";
|
|
38
39
|
import { transformSync } from "oxc-transform";
|
|
39
40
|
//#endregion
|
|
@@ -42930,6 +42931,10 @@ const getDefaultConfig = () => {
|
|
|
42930
42931
|
updateStrategy: "appVersion",
|
|
42931
42932
|
compressStrategy: "zip",
|
|
42932
42933
|
fingerprint: { extraSources: [] },
|
|
42934
|
+
patch: {
|
|
42935
|
+
enabled: true,
|
|
42936
|
+
maxBaseBundles: 3
|
|
42937
|
+
},
|
|
42933
42938
|
console: { port: 1422 },
|
|
42934
42939
|
platform: getDefaultPlatformConfig(),
|
|
42935
42940
|
nativeBuild: {
|
|
@@ -43064,6 +43069,18 @@ function getArchiveFilename(storageUri) {
|
|
|
43064
43069
|
const { pathname } = new URL(storageUri);
|
|
43065
43070
|
return path$1.basename(pathname) || "bundle.zip";
|
|
43066
43071
|
}
|
|
43072
|
+
const getRelativeStorageDir = (relativePath) => {
|
|
43073
|
+
const normalized = relativePath.replace(/\\/g, "/");
|
|
43074
|
+
const dirname = path$1.posix.dirname(normalized);
|
|
43075
|
+
return dirname === "." ? "" : dirname;
|
|
43076
|
+
};
|
|
43077
|
+
const replaceStorageUriLeaf = (storageUri, nextLeaf) => {
|
|
43078
|
+
const storageUrl = new URL(storageUri);
|
|
43079
|
+
const normalizedPath = storageUrl.pathname.replace(/\/+$/, "");
|
|
43080
|
+
const lastSlashIndex = normalizedPath.lastIndexOf("/");
|
|
43081
|
+
storageUrl.pathname = `${lastSlashIndex >= 0 ? normalizedPath.slice(0, lastSlashIndex) : ""}/${nextLeaf}`;
|
|
43082
|
+
return storageUrl.toString();
|
|
43083
|
+
};
|
|
43067
43084
|
function resolveExtractedPath(rootDir, entryName) {
|
|
43068
43085
|
const normalizedEntryName = entryName.replaceAll("\\", "/");
|
|
43069
43086
|
const entryPath = path$1.resolve(rootDir, normalizedEntryName);
|
|
@@ -43071,11 +43088,25 @@ function resolveExtractedPath(rootDir, entryName) {
|
|
|
43071
43088
|
if (relativePath.startsWith("..") || path$1.isAbsolute(relativePath) || normalizedEntryName.startsWith("/")) throw new Error(`Invalid archive entry path: ${entryName}`);
|
|
43072
43089
|
return entryPath;
|
|
43073
43090
|
}
|
|
43074
|
-
async function downloadArchive(
|
|
43091
|
+
async function downloadArchive(storageUri, storagePlugin, archivePath) {
|
|
43092
|
+
const protocol = new URL(storageUri).protocol.replace(":", "");
|
|
43093
|
+
if (protocol === "http" || protocol === "https") {
|
|
43094
|
+
const archiveBuffer = await downloadFromUrl(storageUri);
|
|
43095
|
+
await fs$3.writeFile(archivePath, archiveBuffer);
|
|
43096
|
+
return;
|
|
43097
|
+
}
|
|
43098
|
+
await downloadFromStorage(storageUri, storagePlugin, archivePath);
|
|
43099
|
+
}
|
|
43100
|
+
async function downloadFromUrl(fileUrl) {
|
|
43075
43101
|
const response = await fetch(fileUrl);
|
|
43076
43102
|
if (!response.ok) throw new Error(`Failed to download bundle archive: ${response.statusText}`);
|
|
43077
|
-
|
|
43078
|
-
|
|
43103
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
43104
|
+
}
|
|
43105
|
+
async function downloadFromStorage(storageUri, storagePlugin, filePath) {
|
|
43106
|
+
if (!storagePlugin) throw new Error("Storage plugin is not configured");
|
|
43107
|
+
const protocol = new URL(storageUri).protocol.replace(":", "");
|
|
43108
|
+
if (storagePlugin.supportedProtocol !== protocol) throw new Error(`No storage plugin for protocol: ${protocol}`);
|
|
43109
|
+
await storagePlugin.profiles.node.downloadFile(storageUri, filePath);
|
|
43079
43110
|
}
|
|
43080
43111
|
async function extractZipArchive(archivePath, extractDir) {
|
|
43081
43112
|
const zip = await import_lib.default.loadAsync(await fs$3.readFile(archivePath));
|
|
@@ -43165,41 +43196,68 @@ async function rewriteManifestBundleId(extractDir, nextBundleId) {
|
|
|
43165
43196
|
const manifest = JSON.parse(await fs$3.readFile(manifestPath, "utf8"));
|
|
43166
43197
|
manifest.bundleId = nextBundleId;
|
|
43167
43198
|
await fs$3.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
43168
|
-
|
|
43169
|
-
|
|
43170
|
-
|
|
43171
|
-
|
|
43172
|
-
if (!storagePlugin) throw new Error("Storage plugin is not configured");
|
|
43173
|
-
if (storagePlugin.supportedProtocol !== protocol) throw new Error(`No storage plugin for protocol: ${protocol}`);
|
|
43174
|
-
const { fileUrl } = await storagePlugin.getDownloadUrl(storageUri);
|
|
43175
|
-
if (!fileUrl) throw new Error("Storage plugin returned empty fileUrl");
|
|
43176
|
-
return fileUrl;
|
|
43199
|
+
return {
|
|
43200
|
+
manifest,
|
|
43201
|
+
manifestPath
|
|
43202
|
+
};
|
|
43177
43203
|
}
|
|
43178
43204
|
async function createCopiedBundleArchive({ bundle, config, nextBundleId, storagePlugin, targetChannel }) {
|
|
43179
|
-
const downloadUrl = await resolveBundleDownloadUrl(bundle.storageUri, storagePlugin);
|
|
43180
43205
|
const archiveFilename = getArchiveFilename(bundle.storageUri);
|
|
43181
43206
|
const workDir = await fs$3.mkdtemp(path$1.join(os.tmpdir(), "hot-updater-console-promote-"));
|
|
43182
43207
|
const sourceArchivePath = path$1.join(workDir, archiveFilename);
|
|
43183
43208
|
const extractDir = path$1.join(workDir, "bundle");
|
|
43184
43209
|
const outputArchivePath = path$1.join(workDir, archiveFilename);
|
|
43210
|
+
const uploadedStorageUris = [];
|
|
43185
43211
|
await fs$3.mkdir(extractDir, { recursive: true });
|
|
43186
43212
|
try {
|
|
43187
|
-
await downloadArchive(
|
|
43213
|
+
await downloadArchive(bundle.storageUri, storagePlugin, sourceArchivePath);
|
|
43188
43214
|
const format = await extractArchive(sourceArchivePath, extractDir);
|
|
43189
|
-
await rewriteManifestBundleId(extractDir, nextBundleId);
|
|
43215
|
+
const { manifest, manifestPath } = await rewriteManifestBundleId(extractDir, nextBundleId);
|
|
43190
43216
|
await fs$3.rm(sourceArchivePath, { force: true });
|
|
43191
43217
|
await createArchiveFromDirectory(extractDir, outputArchivePath, format);
|
|
43192
43218
|
const fileHash = await getFileHash(outputArchivePath);
|
|
43193
|
-
|
|
43194
|
-
|
|
43195
|
-
const
|
|
43219
|
+
const manifestHash = await getFileHash(manifestPath);
|
|
43220
|
+
if ([bundle.fileHash, getManifestFileHash(bundle)].filter((hash) => Boolean(hash)).some((hash) => isSignedFileHash(hash)) && !config.signing?.privateKeyPath) throw new Error("Cannot copy a signed bundle without signing.privateKeyPath in hot-updater.config.ts");
|
|
43221
|
+
const signingKeyPath = config.signing?.enabled && config.signing.privateKeyPath ? config.signing.privateKeyPath : null;
|
|
43222
|
+
const nextFileHash = signingKeyPath ? await signFileHash(fileHash, signingKeyPath) : fileHash;
|
|
43223
|
+
const nextManifestFileHash = signingKeyPath ? await signFileHash(manifestHash, signingKeyPath) : manifestHash;
|
|
43224
|
+
const archiveUpload = await storagePlugin.profiles.node.upload(nextBundleId, outputArchivePath);
|
|
43225
|
+
uploadedStorageUris.push(archiveUpload.storageUri);
|
|
43226
|
+
const manifestUpload = await storagePlugin.profiles.node.upload(nextBundleId, manifestPath);
|
|
43227
|
+
uploadedStorageUris.push(manifestUpload.storageUri);
|
|
43228
|
+
const assetPaths = Object.keys(manifest.assets ?? {}).sort((left, right) => left.localeCompare(right));
|
|
43229
|
+
for (const assetPath of assetPaths) {
|
|
43230
|
+
const uploadKey = [
|
|
43231
|
+
nextBundleId,
|
|
43232
|
+
"files",
|
|
43233
|
+
getRelativeStorageDir(assetPath)
|
|
43234
|
+
].filter(Boolean).join("/");
|
|
43235
|
+
const assetUpload = await storagePlugin.profiles.node.upload(uploadKey, path$1.join(extractDir, assetPath));
|
|
43236
|
+
uploadedStorageUris.push(assetUpload.storageUri);
|
|
43237
|
+
}
|
|
43238
|
+
const assetBaseStorageUri = replaceStorageUriLeaf(manifestUpload.storageUri, "files");
|
|
43196
43239
|
return {
|
|
43197
|
-
|
|
43198
|
-
|
|
43199
|
-
|
|
43200
|
-
|
|
43201
|
-
|
|
43240
|
+
bundle: {
|
|
43241
|
+
...bundle,
|
|
43242
|
+
id: nextBundleId,
|
|
43243
|
+
channel: targetChannel,
|
|
43244
|
+
storageUri: archiveUpload.storageUri,
|
|
43245
|
+
fileHash: nextFileHash,
|
|
43246
|
+
metadata: stripBundleArtifactMetadata(bundle.metadata),
|
|
43247
|
+
assetBaseStorageUri,
|
|
43248
|
+
patches: [],
|
|
43249
|
+
patchBaseBundleId: null,
|
|
43250
|
+
manifestFileHash: nextManifestFileHash,
|
|
43251
|
+
manifestStorageUri: manifestUpload.storageUri,
|
|
43252
|
+
patchBaseFileHash: null,
|
|
43253
|
+
patchFileHash: null,
|
|
43254
|
+
patchStorageUri: null
|
|
43255
|
+
},
|
|
43256
|
+
uploadedStorageUris
|
|
43202
43257
|
};
|
|
43258
|
+
} catch (error) {
|
|
43259
|
+
await deleteUploadedCopy(storagePlugin, uploadedStorageUris);
|
|
43260
|
+
throw error;
|
|
43203
43261
|
} finally {
|
|
43204
43262
|
await fs$3.rm(workDir, {
|
|
43205
43263
|
recursive: true,
|
|
@@ -43207,10 +43265,10 @@ async function createCopiedBundleArchive({ bundle, config, nextBundleId, storage
|
|
|
43207
43265
|
});
|
|
43208
43266
|
}
|
|
43209
43267
|
}
|
|
43210
|
-
async function deleteUploadedCopy(storagePlugin,
|
|
43211
|
-
if (
|
|
43212
|
-
try {
|
|
43213
|
-
await storagePlugin.delete(storageUri);
|
|
43268
|
+
async function deleteUploadedCopy(storagePlugin, storageUris) {
|
|
43269
|
+
if (storageUris.length === 0) return;
|
|
43270
|
+
for (const storageUri of new Set(storageUris)) try {
|
|
43271
|
+
await storagePlugin.profiles.node.delete(storageUri);
|
|
43214
43272
|
} catch (error) {
|
|
43215
43273
|
console.error("Failed to delete uploaded bundle copy:", error);
|
|
43216
43274
|
}
|
|
@@ -43230,21 +43288,21 @@ async function promoteBundle({ action, bundleId, nextBundleId, targetChannel },
|
|
|
43230
43288
|
}
|
|
43231
43289
|
if (!deps.storagePlugin) throw new Error("Storage plugin is not configured");
|
|
43232
43290
|
const resolvedNextBundleId = nextBundleId?.trim() || createUUIDv7();
|
|
43233
|
-
const copiedBundle = await createCopiedBundleArchive({
|
|
43291
|
+
const { bundle: copiedBundle, uploadedStorageUris } = await createCopiedBundleArchive({
|
|
43234
43292
|
bundle,
|
|
43235
43293
|
config: deps.config,
|
|
43236
43294
|
nextBundleId: resolvedNextBundleId,
|
|
43237
43295
|
storagePlugin: deps.storagePlugin,
|
|
43238
43296
|
targetChannel: normalizedTargetChannel
|
|
43239
43297
|
});
|
|
43240
|
-
let
|
|
43298
|
+
let shouldCleanupUploadedCopy = true;
|
|
43241
43299
|
try {
|
|
43242
43300
|
await deps.databasePlugin.appendBundle(copiedBundle);
|
|
43243
43301
|
await deps.databasePlugin.commitBundle();
|
|
43244
|
-
|
|
43302
|
+
shouldCleanupUploadedCopy = false;
|
|
43245
43303
|
return copiedBundle;
|
|
43246
43304
|
} catch (error) {
|
|
43247
|
-
await deleteUploadedCopy(deps.storagePlugin,
|
|
43305
|
+
if (shouldCleanupUploadedCopy) await deleteUploadedCopy(deps.storagePlugin, uploadedStorageUris);
|
|
43248
43306
|
throw error;
|
|
43249
43307
|
}
|
|
43250
43308
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hot-updater/cli-tools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20.19.0"
|
|
@@ -46,7 +46,8 @@
|
|
|
46
46
|
"oxc-transform": "0.121.0",
|
|
47
47
|
"typescript": "6.0.2",
|
|
48
48
|
"unconfig": "7.5.0",
|
|
49
|
-
"@hot-updater/
|
|
49
|
+
"@hot-updater/core": "0.31.0",
|
|
50
|
+
"@hot-updater/plugin-core": "0.31.0"
|
|
50
51
|
},
|
|
51
52
|
"devDependencies": {
|
|
52
53
|
"@clack/prompts": "1.0.1",
|
|
@@ -61,7 +62,7 @@
|
|
|
61
62
|
"semver": "^7.6.3",
|
|
62
63
|
"tar": "^7.5.1",
|
|
63
64
|
"workspace-tools": "^0.36.4",
|
|
64
|
-
"@hot-updater/test-utils": "0.
|
|
65
|
+
"@hot-updater/test-utils": "0.31.0"
|
|
65
66
|
},
|
|
66
67
|
"inlinedDependencies": {
|
|
67
68
|
"@babel/code-frame": "7.29.0",
|
|
@@ -78,7 +79,7 @@
|
|
|
78
79
|
"ansi-align": "3.0.1",
|
|
79
80
|
"ansi-regex": [
|
|
80
81
|
"5.0.1",
|
|
81
|
-
"6.
|
|
82
|
+
"6.2.2"
|
|
82
83
|
],
|
|
83
84
|
"ansi-styles": "6.2.1",
|
|
84
85
|
"boxen": "8.0.1",
|