@hot-updater/aws 0.35.12 → 0.36.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/iac/index.cjs +1 -1
- package/dist/iac/index.mjs +1 -1
- package/dist/index.cjs +64 -4
- package/dist/index.mjs +64 -4
- package/dist/lambda/index.cjs +114 -25
- package/package.json +8 -8
package/dist/iac/index.cjs
CHANGED
|
@@ -7951,7 +7951,7 @@ const SOURCE_TEMPLATE = `// Add this to your App.tsx
|
|
|
7951
7951
|
import { HotUpdater } from "@hot-updater/react-native";
|
|
7952
7952
|
|
|
7953
7953
|
function App() {
|
|
7954
|
-
return
|
|
7954
|
+
return null; // Replace with your app root
|
|
7955
7955
|
}
|
|
7956
7956
|
|
|
7957
7957
|
export default HotUpdater.wrap({
|
package/dist/iac/index.mjs
CHANGED
|
@@ -7946,7 +7946,7 @@ const SOURCE_TEMPLATE = `// Add this to your App.tsx
|
|
|
7946
7946
|
import { HotUpdater } from "@hot-updater/react-native";
|
|
7947
7947
|
|
|
7948
7948
|
function App() {
|
|
7949
|
-
return
|
|
7949
|
+
return null; // Replace with your app root
|
|
7950
7950
|
}
|
|
7951
7951
|
|
|
7952
7952
|
export default HotUpdater.wrap({
|
package/dist/index.cjs
CHANGED
|
@@ -1515,9 +1515,17 @@ function isPlatformDirectoryPrefix(prefix) {
|
|
|
1515
1515
|
const lastSegment = getLastDirectorySegment(prefix);
|
|
1516
1516
|
return lastSegment === "ios" || lastSegment === "android";
|
|
1517
1517
|
}
|
|
1518
|
+
function isLegacyBundleStorageDirectoryPrefix(prefix) {
|
|
1519
|
+
const lastSegment = getLastDirectorySegment(prefix);
|
|
1520
|
+
return lastSegment !== void 0 && /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(lastSegment);
|
|
1521
|
+
}
|
|
1518
1522
|
function isUpdateJsonKey(key) {
|
|
1519
1523
|
return key.endsWith(`${S3_DIRECTORY_DELIMITER}update.json`);
|
|
1520
1524
|
}
|
|
1525
|
+
function isUuidChannelUpdateJsonKey(key, rootPrefix, uuidChannels) {
|
|
1526
|
+
const [channel, platform, target, fileName, ...rest] = getRelativeDirectoryPrefix(key, rootPrefix).split(S3_DIRECTORY_DELIMITER);
|
|
1527
|
+
return channel !== void 0 && uuidChannels.has(channel) && (platform === "ios" || platform === "android") && target !== void 0 && target.length > 0 && fileName === "update.json" && rest.length === 0;
|
|
1528
|
+
}
|
|
1521
1529
|
async function mapWithConcurrency(items, concurrency, mapper) {
|
|
1522
1530
|
const results = [];
|
|
1523
1531
|
let nextIndex = 0;
|
|
@@ -1572,7 +1580,7 @@ async function uploadJsonToS3(client, bucket, key, data) {
|
|
|
1572
1580
|
}
|
|
1573
1581
|
async function listObjectsInS3(client, bucketName, prefix, rootPrefix = "") {
|
|
1574
1582
|
const normalizedRootPrefix = normalizeDirectoryPrefix(rootPrefix);
|
|
1575
|
-
const listPrefix = async (currentPrefix) => {
|
|
1583
|
+
const listPrefix = async (currentPrefix, delimiter = S3_DIRECTORY_DELIMITER) => {
|
|
1576
1584
|
let continuationToken;
|
|
1577
1585
|
const keys = [];
|
|
1578
1586
|
const commonPrefixes = /* @__PURE__ */ new Set();
|
|
@@ -1580,7 +1588,7 @@ async function listObjectsInS3(client, bucketName, prefix, rootPrefix = "") {
|
|
|
1580
1588
|
const response = await client.send(new _aws_sdk_client_s3.ListObjectsV2Command({
|
|
1581
1589
|
Bucket: bucketName,
|
|
1582
1590
|
Prefix: currentPrefix,
|
|
1583
|
-
Delimiter:
|
|
1591
|
+
Delimiter: delimiter ?? void 0,
|
|
1584
1592
|
ContinuationToken: continuationToken
|
|
1585
1593
|
}));
|
|
1586
1594
|
const found = (response.Contents ?? []).map((item) => item.Key).filter((key) => !!key);
|
|
@@ -1597,7 +1605,12 @@ async function listObjectsInS3(client, bucketName, prefix, rootPrefix = "") {
|
|
|
1597
1605
|
const { commonPrefixes, keys } = await listPrefix(currentPrefix);
|
|
1598
1606
|
const depth = getDirectoryDepth(currentPrefix, normalizedRootPrefix);
|
|
1599
1607
|
if (depth >= 2) return [...keys.filter(isUpdateJsonKey), ...commonPrefixes.map((commonPrefix) => `${commonPrefix}update.json`)];
|
|
1600
|
-
|
|
1608
|
+
const uuidPrefixes = depth === 0 ? commonPrefixes.filter(isLegacyBundleStorageDirectoryPrefix) : [];
|
|
1609
|
+
const uuidPrefixSet = new Set(uuidPrefixes);
|
|
1610
|
+
const nestedKeys = await mapWithConcurrency(depth === 0 ? commonPrefixes.filter((commonPrefix) => !uuidPrefixSet.has(commonPrefix)) : depth === 1 ? commonPrefixes.filter(isPlatformDirectoryPrefix) : commonPrefixes, S3_LIST_OBJECTS_CONCURRENCY, (nextPrefix) => collectUpdateJsonKeys(nextPrefix));
|
|
1611
|
+
const uuidChannels = new Set(uuidPrefixes.map(getLastDirectorySegment).filter((segment) => !!segment));
|
|
1612
|
+
const uuidChannelKeys = (await mapWithConcurrency(Array.from(new Set(Array.from(uuidChannels, (channel) => `${normalizedRootPrefix}${channel[0]}`))), S3_LIST_OBJECTS_CONCURRENCY, async (scanPrefix) => (await listPrefix(scanPrefix, null)).keys)).flat().filter((key) => isUuidChannelUpdateJsonKey(key, normalizedRootPrefix, uuidChannels));
|
|
1613
|
+
return [...nestedKeys.flat(), ...uuidChannelKeys];
|
|
1601
1614
|
};
|
|
1602
1615
|
const normalizedPrefix = normalizeDirectoryPrefix(prefix);
|
|
1603
1616
|
return Array.from(new Set(await collectUpdateJsonKeys(normalizedPrefix)));
|
|
@@ -1682,9 +1695,56 @@ const s3Storage = (0, _hot_updater_plugin_core.createUniversalStoragePlugin)({
|
|
|
1682
1695
|
factory: (config) => {
|
|
1683
1696
|
const { bucketName, ...s3Config } = config;
|
|
1684
1697
|
const client = new _aws_sdk_client_s3.S3Client(applyS3RuntimeAwsConfig(s3Config));
|
|
1685
|
-
const
|
|
1698
|
+
const normalizedBasePath = config.basePath?.replace(/^\/+|\/+$/g, "") ?? "";
|
|
1699
|
+
const getStorageKey = (0, _hot_updater_plugin_core.createStorageKeyBuilder)(normalizedBasePath);
|
|
1700
|
+
const getListPrefix = (prefix = "") => {
|
|
1701
|
+
const value = [normalizedBasePath, prefix.replace(/^\/+|\/+$/g, "")].filter(Boolean).join("/");
|
|
1702
|
+
return value ? `${value}/` : "";
|
|
1703
|
+
};
|
|
1704
|
+
const getRelativeKey = (key) => {
|
|
1705
|
+
if (!normalizedBasePath) return key;
|
|
1706
|
+
const basePrefix = `${normalizedBasePath}/`;
|
|
1707
|
+
return key.startsWith(basePrefix) ? key.slice(basePrefix.length) : key;
|
|
1708
|
+
};
|
|
1686
1709
|
return {
|
|
1687
1710
|
node: {
|
|
1711
|
+
async listObjects(prefix) {
|
|
1712
|
+
const objects = [];
|
|
1713
|
+
let continuationToken;
|
|
1714
|
+
do {
|
|
1715
|
+
const response = await client.send(new _aws_sdk_client_s3.ListObjectsV2Command({
|
|
1716
|
+
Bucket: bucketName,
|
|
1717
|
+
ContinuationToken: continuationToken,
|
|
1718
|
+
Prefix: getListPrefix(prefix)
|
|
1719
|
+
}));
|
|
1720
|
+
for (const object of response.Contents ?? []) {
|
|
1721
|
+
if (!object.Key) continue;
|
|
1722
|
+
objects.push({
|
|
1723
|
+
key: getRelativeKey(object.Key),
|
|
1724
|
+
lastModifiedAt: object.LastModified,
|
|
1725
|
+
size: object.Size ?? 0,
|
|
1726
|
+
storageUri: `s3://${bucketName}/${object.Key}`
|
|
1727
|
+
});
|
|
1728
|
+
}
|
|
1729
|
+
continuationToken = response.NextContinuationToken;
|
|
1730
|
+
} while (continuationToken);
|
|
1731
|
+
return objects;
|
|
1732
|
+
},
|
|
1733
|
+
async deleteObjects(relativeKeys) {
|
|
1734
|
+
const keys = relativeKeys.map((key) => getStorageKey(key));
|
|
1735
|
+
for (let offset = 0; offset < keys.length; offset += 1e3) {
|
|
1736
|
+
const batch = keys.slice(offset, offset + 1e3);
|
|
1737
|
+
if (batch.length === 0) continue;
|
|
1738
|
+
const response = await client.send(new _aws_sdk_client_s3.DeleteObjectsCommand({
|
|
1739
|
+
Bucket: bucketName,
|
|
1740
|
+
Delete: {
|
|
1741
|
+
Objects: batch.map((Key) => ({ Key })),
|
|
1742
|
+
Quiet: true
|
|
1743
|
+
}
|
|
1744
|
+
}));
|
|
1745
|
+
if (response.Errors && response.Errors.length > 0) throw new Error(`Failed to delete ${response.Errors.length} S3 object(s).`);
|
|
1746
|
+
}
|
|
1747
|
+
},
|
|
1688
1748
|
async delete(storageUri) {
|
|
1689
1749
|
const { bucket, key } = (0, _hot_updater_plugin_core.parseStorageUri)(storageUri, "s3");
|
|
1690
1750
|
if (bucket !== bucketName) throw new Error(`Bucket name mismatch: expected "${bucketName}", but found "${bucket}".`);
|
package/dist/index.mjs
CHANGED
|
@@ -1490,9 +1490,17 @@ function isPlatformDirectoryPrefix(prefix) {
|
|
|
1490
1490
|
const lastSegment = getLastDirectorySegment(prefix);
|
|
1491
1491
|
return lastSegment === "ios" || lastSegment === "android";
|
|
1492
1492
|
}
|
|
1493
|
+
function isLegacyBundleStorageDirectoryPrefix(prefix) {
|
|
1494
|
+
const lastSegment = getLastDirectorySegment(prefix);
|
|
1495
|
+
return lastSegment !== void 0 && /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(lastSegment);
|
|
1496
|
+
}
|
|
1493
1497
|
function isUpdateJsonKey(key) {
|
|
1494
1498
|
return key.endsWith(`${S3_DIRECTORY_DELIMITER}update.json`);
|
|
1495
1499
|
}
|
|
1500
|
+
function isUuidChannelUpdateJsonKey(key, rootPrefix, uuidChannels) {
|
|
1501
|
+
const [channel, platform, target, fileName, ...rest] = getRelativeDirectoryPrefix(key, rootPrefix).split(S3_DIRECTORY_DELIMITER);
|
|
1502
|
+
return channel !== void 0 && uuidChannels.has(channel) && (platform === "ios" || platform === "android") && target !== void 0 && target.length > 0 && fileName === "update.json" && rest.length === 0;
|
|
1503
|
+
}
|
|
1496
1504
|
async function mapWithConcurrency(items, concurrency, mapper) {
|
|
1497
1505
|
const results = [];
|
|
1498
1506
|
let nextIndex = 0;
|
|
@@ -1547,7 +1555,7 @@ async function uploadJsonToS3(client, bucket, key, data) {
|
|
|
1547
1555
|
}
|
|
1548
1556
|
async function listObjectsInS3(client, bucketName, prefix, rootPrefix = "") {
|
|
1549
1557
|
const normalizedRootPrefix = normalizeDirectoryPrefix(rootPrefix);
|
|
1550
|
-
const listPrefix = async (currentPrefix) => {
|
|
1558
|
+
const listPrefix = async (currentPrefix, delimiter = S3_DIRECTORY_DELIMITER) => {
|
|
1551
1559
|
let continuationToken;
|
|
1552
1560
|
const keys = [];
|
|
1553
1561
|
const commonPrefixes = /* @__PURE__ */ new Set();
|
|
@@ -1555,7 +1563,7 @@ async function listObjectsInS3(client, bucketName, prefix, rootPrefix = "") {
|
|
|
1555
1563
|
const response = await client.send(new ListObjectsV2Command({
|
|
1556
1564
|
Bucket: bucketName,
|
|
1557
1565
|
Prefix: currentPrefix,
|
|
1558
|
-
Delimiter:
|
|
1566
|
+
Delimiter: delimiter ?? void 0,
|
|
1559
1567
|
ContinuationToken: continuationToken
|
|
1560
1568
|
}));
|
|
1561
1569
|
const found = (response.Contents ?? []).map((item) => item.Key).filter((key) => !!key);
|
|
@@ -1572,7 +1580,12 @@ async function listObjectsInS3(client, bucketName, prefix, rootPrefix = "") {
|
|
|
1572
1580
|
const { commonPrefixes, keys } = await listPrefix(currentPrefix);
|
|
1573
1581
|
const depth = getDirectoryDepth(currentPrefix, normalizedRootPrefix);
|
|
1574
1582
|
if (depth >= 2) return [...keys.filter(isUpdateJsonKey), ...commonPrefixes.map((commonPrefix) => `${commonPrefix}update.json`)];
|
|
1575
|
-
|
|
1583
|
+
const uuidPrefixes = depth === 0 ? commonPrefixes.filter(isLegacyBundleStorageDirectoryPrefix) : [];
|
|
1584
|
+
const uuidPrefixSet = new Set(uuidPrefixes);
|
|
1585
|
+
const nestedKeys = await mapWithConcurrency(depth === 0 ? commonPrefixes.filter((commonPrefix) => !uuidPrefixSet.has(commonPrefix)) : depth === 1 ? commonPrefixes.filter(isPlatformDirectoryPrefix) : commonPrefixes, S3_LIST_OBJECTS_CONCURRENCY, (nextPrefix) => collectUpdateJsonKeys(nextPrefix));
|
|
1586
|
+
const uuidChannels = new Set(uuidPrefixes.map(getLastDirectorySegment).filter((segment) => !!segment));
|
|
1587
|
+
const uuidChannelKeys = (await mapWithConcurrency(Array.from(new Set(Array.from(uuidChannels, (channel) => `${normalizedRootPrefix}${channel[0]}`))), S3_LIST_OBJECTS_CONCURRENCY, async (scanPrefix) => (await listPrefix(scanPrefix, null)).keys)).flat().filter((key) => isUuidChannelUpdateJsonKey(key, normalizedRootPrefix, uuidChannels));
|
|
1588
|
+
return [...nestedKeys.flat(), ...uuidChannelKeys];
|
|
1576
1589
|
};
|
|
1577
1590
|
const normalizedPrefix = normalizeDirectoryPrefix(prefix);
|
|
1578
1591
|
return Array.from(new Set(await collectUpdateJsonKeys(normalizedPrefix)));
|
|
@@ -1657,9 +1670,56 @@ const s3Storage = createUniversalStoragePlugin({
|
|
|
1657
1670
|
factory: (config) => {
|
|
1658
1671
|
const { bucketName, ...s3Config } = config;
|
|
1659
1672
|
const client = new S3Client(applyS3RuntimeAwsConfig(s3Config));
|
|
1660
|
-
const
|
|
1673
|
+
const normalizedBasePath = config.basePath?.replace(/^\/+|\/+$/g, "") ?? "";
|
|
1674
|
+
const getStorageKey = createStorageKeyBuilder(normalizedBasePath);
|
|
1675
|
+
const getListPrefix = (prefix = "") => {
|
|
1676
|
+
const value = [normalizedBasePath, prefix.replace(/^\/+|\/+$/g, "")].filter(Boolean).join("/");
|
|
1677
|
+
return value ? `${value}/` : "";
|
|
1678
|
+
};
|
|
1679
|
+
const getRelativeKey = (key) => {
|
|
1680
|
+
if (!normalizedBasePath) return key;
|
|
1681
|
+
const basePrefix = `${normalizedBasePath}/`;
|
|
1682
|
+
return key.startsWith(basePrefix) ? key.slice(basePrefix.length) : key;
|
|
1683
|
+
};
|
|
1661
1684
|
return {
|
|
1662
1685
|
node: {
|
|
1686
|
+
async listObjects(prefix) {
|
|
1687
|
+
const objects = [];
|
|
1688
|
+
let continuationToken;
|
|
1689
|
+
do {
|
|
1690
|
+
const response = await client.send(new ListObjectsV2Command({
|
|
1691
|
+
Bucket: bucketName,
|
|
1692
|
+
ContinuationToken: continuationToken,
|
|
1693
|
+
Prefix: getListPrefix(prefix)
|
|
1694
|
+
}));
|
|
1695
|
+
for (const object of response.Contents ?? []) {
|
|
1696
|
+
if (!object.Key) continue;
|
|
1697
|
+
objects.push({
|
|
1698
|
+
key: getRelativeKey(object.Key),
|
|
1699
|
+
lastModifiedAt: object.LastModified,
|
|
1700
|
+
size: object.Size ?? 0,
|
|
1701
|
+
storageUri: `s3://${bucketName}/${object.Key}`
|
|
1702
|
+
});
|
|
1703
|
+
}
|
|
1704
|
+
continuationToken = response.NextContinuationToken;
|
|
1705
|
+
} while (continuationToken);
|
|
1706
|
+
return objects;
|
|
1707
|
+
},
|
|
1708
|
+
async deleteObjects(relativeKeys) {
|
|
1709
|
+
const keys = relativeKeys.map((key) => getStorageKey(key));
|
|
1710
|
+
for (let offset = 0; offset < keys.length; offset += 1e3) {
|
|
1711
|
+
const batch = keys.slice(offset, offset + 1e3);
|
|
1712
|
+
if (batch.length === 0) continue;
|
|
1713
|
+
const response = await client.send(new DeleteObjectsCommand({
|
|
1714
|
+
Bucket: bucketName,
|
|
1715
|
+
Delete: {
|
|
1716
|
+
Objects: batch.map((Key) => ({ Key })),
|
|
1717
|
+
Quiet: true
|
|
1718
|
+
}
|
|
1719
|
+
}));
|
|
1720
|
+
if (response.Errors && response.Errors.length > 0) throw new Error(`Failed to delete ${response.Errors.length} S3 object(s).`);
|
|
1721
|
+
}
|
|
1722
|
+
},
|
|
1663
1723
|
async delete(storageUri) {
|
|
1664
1724
|
const { bucket, key } = parseStorageUri(storageUri, "s3");
|
|
1665
1725
|
if (bucket !== bucketName) throw new Error(`Bucket name mismatch: expected "${bucketName}", but found "${bucket}".`);
|
package/dist/lambda/index.cjs
CHANGED
|
@@ -35,7 +35,7 @@ let _aws_sdk_client_ssm = require("@aws-sdk/client-ssm");
|
|
|
35
35
|
let _aws_sdk_cloudfront_signer = require("@aws-sdk/cloudfront-signer");
|
|
36
36
|
//#endregion
|
|
37
37
|
//#region ../../packages/server/dist/version.mjs
|
|
38
|
-
const HOT_UPDATER_SERVER_VERSION = "0.
|
|
38
|
+
const HOT_UPDATER_SERVER_VERSION = "0.36.1";
|
|
39
39
|
//#endregion
|
|
40
40
|
//#region ../../packages/server/dist/internalRouter.mjs
|
|
41
41
|
const normalizePath = (path) => {
|
|
@@ -2340,6 +2340,8 @@ const getContentAddressedAssetStoragePath = ({ assetPath, fileHash }) => {
|
|
|
2340
2340
|
const getLegacyManifestAssetStoragePath = ({ assetPath }) => assetPath;
|
|
2341
2341
|
//#endregion
|
|
2342
2342
|
//#region ../plugin-core/dist/assetStorageLayout.mjs
|
|
2343
|
+
const isBrotliManifestAssetPath = (assetPath) => /(^|\/)index\.[^/]+\.bundle$/.test(assetPath.replace(/\\/g, "/"));
|
|
2344
|
+
const getManifestAssetDownloadPath = (assetPath) => isBrotliManifestAssetPath(assetPath) ? `${assetPath}.br` : assetPath;
|
|
2343
2345
|
const createStorageUriWithRelativePath = ({ baseStorageUri, relativePath }) => {
|
|
2344
2346
|
const storageUrl = new URL(baseStorageUri);
|
|
2345
2347
|
storageUrl.pathname = `${storageUrl.pathname.replace(/\/+$/, "")}/${relativePath.replace(/\\/g, "/").split("/").filter(Boolean).map((segment) => encodeURIComponent(segment)).join("/")}`;
|
|
@@ -3681,10 +3683,17 @@ function addTargetVersionRemoval(mutations, bundle) {
|
|
|
3681
3683
|
getTargetVersionMutation(mutations, bundle).removals.add(targetAppVersion);
|
|
3682
3684
|
}
|
|
3683
3685
|
function getManagementListPrefixes(where) {
|
|
3686
|
+
if (where?.channel && where.platform && typeof where.targetAppVersion === "string") {
|
|
3687
|
+
const targetAppVersion = normalizeTargetAppVersion(where.targetAppVersion);
|
|
3688
|
+
if (targetAppVersion) return [`${where.channel}/${where.platform}/${targetAppVersion}/`];
|
|
3689
|
+
}
|
|
3684
3690
|
if (where?.channel && where.platform) return [`${where.channel}/${where.platform}/`];
|
|
3685
3691
|
if (where?.channel) return [`${where.channel}/`];
|
|
3686
3692
|
return [""];
|
|
3687
3693
|
}
|
|
3694
|
+
function getChannelFromUpdateJsonKey(key) {
|
|
3695
|
+
return key.match(/^([^/]+)\/(?:ios|android)\/[^/]+\/update\.json$/)?.[1] ?? null;
|
|
3696
|
+
}
|
|
3688
3697
|
const DEFAULT_DESC_ORDER = {
|
|
3689
3698
|
field: "id",
|
|
3690
3699
|
direction: "desc"
|
|
@@ -3701,7 +3710,7 @@ function sortManagedBundles(bundles, orderBy = DEFAULT_DESC_ORDER) {
|
|
|
3701
3710
|
*/
|
|
3702
3711
|
const createBlobDatabasePlugin = ({ name, factory }) => {
|
|
3703
3712
|
return (config, hooks) => {
|
|
3704
|
-
const { listObjects, loadObject, uploadObject, deleteObject, shouldSkipLoadObjectError, invalidatePaths, apiBasePath } = factory(config);
|
|
3713
|
+
const { listObjects, loadObject, uploadObject, deleteObject, shouldSkipLoadObjectError, validateChannel, invalidatePaths, apiBasePath } = factory(config);
|
|
3705
3714
|
const bundlesMap = /* @__PURE__ */ new Map();
|
|
3706
3715
|
const pendingBundlesMap = /* @__PURE__ */ new Map();
|
|
3707
3716
|
const locallyDeletedBundleIds = /* @__PURE__ */ new Set();
|
|
@@ -3837,10 +3846,12 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
|
|
|
3837
3846
|
});
|
|
3838
3847
|
},
|
|
3839
3848
|
async getChannels() {
|
|
3840
|
-
|
|
3849
|
+
const channels = (await listObjects("")).map(getChannelFromUpdateJsonKey).filter((channel) => channel !== null);
|
|
3850
|
+
return [...new Set(channels)].sort();
|
|
3841
3851
|
},
|
|
3842
3852
|
async commitBundle({ changedSets }) {
|
|
3843
3853
|
if (changedSets.length === 0) return;
|
|
3854
|
+
for (const { operation, data } of changedSets) if (operation === "insert" || operation === "update" && data.channel !== void 0) validateChannel?.(data.channel);
|
|
3844
3855
|
const changedBundlesByKey = {};
|
|
3845
3856
|
const removalsByKey = {};
|
|
3846
3857
|
const targetVersionRemovalsByKey = {};
|
|
@@ -4003,21 +4014,39 @@ const createProfiledStoragePlugin = ({ createProfiles, name, profileShape, suppo
|
|
|
4003
4014
|
return runtime;
|
|
4004
4015
|
};
|
|
4005
4016
|
const profiles = {};
|
|
4006
|
-
if (profileShape?.node)
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4017
|
+
if (profileShape?.node) {
|
|
4018
|
+
const nodeProfile = {
|
|
4019
|
+
async delete(storageUri) {
|
|
4020
|
+
return requireNodeProfile().delete(storageUri);
|
|
4021
|
+
},
|
|
4022
|
+
async downloadFile(storageUri, filePath) {
|
|
4023
|
+
return requireNodeProfile().downloadFile(storageUri, filePath);
|
|
4024
|
+
},
|
|
4025
|
+
async exists(storageUri) {
|
|
4026
|
+
return requireNodeProfile().exists(storageUri);
|
|
4027
|
+
},
|
|
4028
|
+
async upload(key, filePath) {
|
|
4029
|
+
return requireNodeProfile().upload(key, filePath);
|
|
4030
|
+
}
|
|
4031
|
+
};
|
|
4032
|
+
Object.defineProperties(nodeProfile, {
|
|
4033
|
+
deleteObjects: {
|
|
4034
|
+
enumerable: true,
|
|
4035
|
+
get: () => {
|
|
4036
|
+
const deleteObjects = requireNodeProfile().deleteObjects;
|
|
4037
|
+
return deleteObjects ? (keys) => deleteObjects(keys) : void 0;
|
|
4038
|
+
}
|
|
4039
|
+
},
|
|
4040
|
+
listObjects: {
|
|
4041
|
+
enumerable: true,
|
|
4042
|
+
get: () => {
|
|
4043
|
+
const listObjects = requireNodeProfile().listObjects;
|
|
4044
|
+
return listObjects ? (prefix) => listObjects(prefix) : void 0;
|
|
4045
|
+
}
|
|
4046
|
+
}
|
|
4047
|
+
});
|
|
4048
|
+
profiles.node = nodeProfile;
|
|
4049
|
+
} else if (profileShape?.node !== false) Object.defineProperty(profiles, "node", {
|
|
4021
4050
|
enumerable: true,
|
|
4022
4051
|
get: getNodeProfile
|
|
4023
4052
|
});
|
|
@@ -4098,7 +4127,6 @@ function assertRuntimeStoragePlugin(plugin) {
|
|
|
4098
4127
|
//#endregion
|
|
4099
4128
|
//#region ../../packages/server/dist/db/updateArtifacts.mjs
|
|
4100
4129
|
const HBC_ASSET_PATH_RE = /\.bundle$/;
|
|
4101
|
-
const BR_COMPRESSED_ASSET_PATH_RE = /(^|\/)index\.[^/]+\.bundle$/;
|
|
4102
4130
|
const resolveUniqueHbcAssetPath = (manifest) => {
|
|
4103
4131
|
const candidates = Object.keys(manifest.assets).sort((left, right) => left.localeCompare(right)).filter((candidate) => HBC_ASSET_PATH_RE.test(candidate));
|
|
4104
4132
|
return candidates.length === 1 ? candidates[0] : null;
|
|
@@ -4140,10 +4168,11 @@ async function resolveChangedAssets({ assetBaseStorageUri, currentManifest, curr
|
|
|
4140
4168
|
});
|
|
4141
4169
|
const changedEntries = await Promise.all(Object.entries(targetManifest.assets).map(async ([assetPath, asset]) => {
|
|
4142
4170
|
if ((currentManifest?.assets[assetPath])?.fileHash === asset.fileHash) return null;
|
|
4143
|
-
const
|
|
4171
|
+
const downloadPath = getManifestAssetDownloadPath(assetPath);
|
|
4172
|
+
const usesBrotliAsset = downloadPath !== assetPath;
|
|
4144
4173
|
const storageUri = resolveManifestAssetStorageUri({
|
|
4145
4174
|
assetBaseStorageUri,
|
|
4146
|
-
assetPath:
|
|
4175
|
+
assetPath: downloadPath,
|
|
4147
4176
|
fileHash: asset.fileHash
|
|
4148
4177
|
});
|
|
4149
4178
|
const patch = patchDescriptor?.assetPath === assetPath ? patchDescriptor.patch : null;
|
|
@@ -6528,9 +6557,17 @@ function isPlatformDirectoryPrefix(prefix) {
|
|
|
6528
6557
|
const lastSegment = getLastDirectorySegment(prefix);
|
|
6529
6558
|
return lastSegment === "ios" || lastSegment === "android";
|
|
6530
6559
|
}
|
|
6560
|
+
function isLegacyBundleStorageDirectoryPrefix(prefix) {
|
|
6561
|
+
const lastSegment = getLastDirectorySegment(prefix);
|
|
6562
|
+
return lastSegment !== void 0 && /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(lastSegment);
|
|
6563
|
+
}
|
|
6531
6564
|
function isUpdateJsonKey(key) {
|
|
6532
6565
|
return key.endsWith(`${S3_DIRECTORY_DELIMITER}update.json`);
|
|
6533
6566
|
}
|
|
6567
|
+
function isUuidChannelUpdateJsonKey(key, rootPrefix, uuidChannels) {
|
|
6568
|
+
const [channel, platform, target, fileName, ...rest] = getRelativeDirectoryPrefix(key, rootPrefix).split(S3_DIRECTORY_DELIMITER);
|
|
6569
|
+
return channel !== void 0 && uuidChannels.has(channel) && (platform === "ios" || platform === "android") && target !== void 0 && target.length > 0 && fileName === "update.json" && rest.length === 0;
|
|
6570
|
+
}
|
|
6534
6571
|
async function mapWithConcurrency(items, concurrency, mapper) {
|
|
6535
6572
|
const results = [];
|
|
6536
6573
|
let nextIndex = 0;
|
|
@@ -6585,7 +6622,7 @@ async function uploadJsonToS3(client, bucket, key, data) {
|
|
|
6585
6622
|
}
|
|
6586
6623
|
async function listObjectsInS3(client, bucketName, prefix, rootPrefix = "") {
|
|
6587
6624
|
const normalizedRootPrefix = normalizeDirectoryPrefix(rootPrefix);
|
|
6588
|
-
const listPrefix = async (currentPrefix) => {
|
|
6625
|
+
const listPrefix = async (currentPrefix, delimiter = S3_DIRECTORY_DELIMITER) => {
|
|
6589
6626
|
let continuationToken;
|
|
6590
6627
|
const keys = [];
|
|
6591
6628
|
const commonPrefixes = /* @__PURE__ */ new Set();
|
|
@@ -6593,7 +6630,7 @@ async function listObjectsInS3(client, bucketName, prefix, rootPrefix = "") {
|
|
|
6593
6630
|
const response = await client.send(new _aws_sdk_client_s3.ListObjectsV2Command({
|
|
6594
6631
|
Bucket: bucketName,
|
|
6595
6632
|
Prefix: currentPrefix,
|
|
6596
|
-
Delimiter:
|
|
6633
|
+
Delimiter: delimiter ?? void 0,
|
|
6597
6634
|
ContinuationToken: continuationToken
|
|
6598
6635
|
}));
|
|
6599
6636
|
const found = (response.Contents ?? []).map((item) => item.Key).filter((key) => !!key);
|
|
@@ -6610,7 +6647,12 @@ async function listObjectsInS3(client, bucketName, prefix, rootPrefix = "") {
|
|
|
6610
6647
|
const { commonPrefixes, keys } = await listPrefix(currentPrefix);
|
|
6611
6648
|
const depth = getDirectoryDepth(currentPrefix, normalizedRootPrefix);
|
|
6612
6649
|
if (depth >= 2) return [...keys.filter(isUpdateJsonKey), ...commonPrefixes.map((commonPrefix) => `${commonPrefix}update.json`)];
|
|
6613
|
-
|
|
6650
|
+
const uuidPrefixes = depth === 0 ? commonPrefixes.filter(isLegacyBundleStorageDirectoryPrefix) : [];
|
|
6651
|
+
const uuidPrefixSet = new Set(uuidPrefixes);
|
|
6652
|
+
const nestedKeys = await mapWithConcurrency(depth === 0 ? commonPrefixes.filter((commonPrefix) => !uuidPrefixSet.has(commonPrefix)) : depth === 1 ? commonPrefixes.filter(isPlatformDirectoryPrefix) : commonPrefixes, S3_LIST_OBJECTS_CONCURRENCY, (nextPrefix) => collectUpdateJsonKeys(nextPrefix));
|
|
6653
|
+
const uuidChannels = new Set(uuidPrefixes.map(getLastDirectorySegment).filter((segment) => !!segment));
|
|
6654
|
+
const uuidChannelKeys = (await mapWithConcurrency(Array.from(new Set(Array.from(uuidChannels, (channel) => `${normalizedRootPrefix}${channel[0]}`))), S3_LIST_OBJECTS_CONCURRENCY, async (scanPrefix) => (await listPrefix(scanPrefix, null)).keys)).flat().filter((key) => isUuidChannelUpdateJsonKey(key, normalizedRootPrefix, uuidChannels));
|
|
6655
|
+
return [...nestedKeys.flat(), ...uuidChannelKeys];
|
|
6614
6656
|
};
|
|
6615
6657
|
const normalizedPrefix = normalizeDirectoryPrefix(prefix);
|
|
6616
6658
|
return Array.from(new Set(await collectUpdateJsonKeys(normalizedPrefix)));
|
|
@@ -6695,9 +6737,56 @@ const s3Storage = createUniversalStoragePlugin({
|
|
|
6695
6737
|
factory: (config) => {
|
|
6696
6738
|
const { bucketName, ...s3Config } = config;
|
|
6697
6739
|
const client = new _aws_sdk_client_s3.S3Client(applyS3RuntimeAwsConfig(s3Config));
|
|
6698
|
-
const
|
|
6740
|
+
const normalizedBasePath = config.basePath?.replace(/^\/+|\/+$/g, "") ?? "";
|
|
6741
|
+
const getStorageKey = createStorageKeyBuilder(normalizedBasePath);
|
|
6742
|
+
const getListPrefix = (prefix = "") => {
|
|
6743
|
+
const value = [normalizedBasePath, prefix.replace(/^\/+|\/+$/g, "")].filter(Boolean).join("/");
|
|
6744
|
+
return value ? `${value}/` : "";
|
|
6745
|
+
};
|
|
6746
|
+
const getRelativeKey = (key) => {
|
|
6747
|
+
if (!normalizedBasePath) return key;
|
|
6748
|
+
const basePrefix = `${normalizedBasePath}/`;
|
|
6749
|
+
return key.startsWith(basePrefix) ? key.slice(basePrefix.length) : key;
|
|
6750
|
+
};
|
|
6699
6751
|
return {
|
|
6700
6752
|
node: {
|
|
6753
|
+
async listObjects(prefix) {
|
|
6754
|
+
const objects = [];
|
|
6755
|
+
let continuationToken;
|
|
6756
|
+
do {
|
|
6757
|
+
const response = await client.send(new _aws_sdk_client_s3.ListObjectsV2Command({
|
|
6758
|
+
Bucket: bucketName,
|
|
6759
|
+
ContinuationToken: continuationToken,
|
|
6760
|
+
Prefix: getListPrefix(prefix)
|
|
6761
|
+
}));
|
|
6762
|
+
for (const object of response.Contents ?? []) {
|
|
6763
|
+
if (!object.Key) continue;
|
|
6764
|
+
objects.push({
|
|
6765
|
+
key: getRelativeKey(object.Key),
|
|
6766
|
+
lastModifiedAt: object.LastModified,
|
|
6767
|
+
size: object.Size ?? 0,
|
|
6768
|
+
storageUri: `s3://${bucketName}/${object.Key}`
|
|
6769
|
+
});
|
|
6770
|
+
}
|
|
6771
|
+
continuationToken = response.NextContinuationToken;
|
|
6772
|
+
} while (continuationToken);
|
|
6773
|
+
return objects;
|
|
6774
|
+
},
|
|
6775
|
+
async deleteObjects(relativeKeys) {
|
|
6776
|
+
const keys = relativeKeys.map((key) => getStorageKey(key));
|
|
6777
|
+
for (let offset = 0; offset < keys.length; offset += 1e3) {
|
|
6778
|
+
const batch = keys.slice(offset, offset + 1e3);
|
|
6779
|
+
if (batch.length === 0) continue;
|
|
6780
|
+
const response = await client.send(new _aws_sdk_client_s3.DeleteObjectsCommand({
|
|
6781
|
+
Bucket: bucketName,
|
|
6782
|
+
Delete: {
|
|
6783
|
+
Objects: batch.map((Key) => ({ Key })),
|
|
6784
|
+
Quiet: true
|
|
6785
|
+
}
|
|
6786
|
+
}));
|
|
6787
|
+
if (response.Errors && response.Errors.length > 0) throw new Error(`Failed to delete ${response.Errors.length} S3 object(s).`);
|
|
6788
|
+
}
|
|
6789
|
+
},
|
|
6701
6790
|
async delete(storageUri) {
|
|
6702
6791
|
const { bucket, key } = parseStorageUri(storageUri, "s3");
|
|
6703
6792
|
if (bucket !== bucketName) throw new Error(`Bucket name mismatch: expected "${bucketName}", but found "${bucket}".`);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hot-updater/aws",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.36.1",
|
|
5
5
|
"description": "React Native OTA solution for self-hosted",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
7
7
|
"module": "dist/index.mjs",
|
|
@@ -50,10 +50,10 @@
|
|
|
50
50
|
"es-toolkit": "^1.32.0",
|
|
51
51
|
"execa": "9.5.2",
|
|
52
52
|
"mime": "^4.0.4",
|
|
53
|
-
"@hot-updater/core": "0.
|
|
54
|
-
"@hot-updater/js": "0.
|
|
55
|
-
"@hot-updater/mock": "0.
|
|
56
|
-
"@hot-updater/test-utils": "0.
|
|
53
|
+
"@hot-updater/core": "0.36.1",
|
|
54
|
+
"@hot-updater/js": "0.36.1",
|
|
55
|
+
"@hot-updater/mock": "0.36.1",
|
|
56
|
+
"@hot-updater/test-utils": "0.36.1"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"@aws-sdk/client-cloudfront": "3.1066.0",
|
|
@@ -68,9 +68,9 @@
|
|
|
68
68
|
"@aws-sdk/lib-storage": "3.1066.0",
|
|
69
69
|
"hono": "4.12.9",
|
|
70
70
|
"aws-lambda": "1.0.7",
|
|
71
|
-
"@hot-updater/cli-tools": "0.
|
|
72
|
-
"@hot-updater/plugin-core": "0.
|
|
73
|
-
"@hot-updater/server": "0.
|
|
71
|
+
"@hot-updater/cli-tools": "0.36.1",
|
|
72
|
+
"@hot-updater/plugin-core": "0.36.1",
|
|
73
|
+
"@hot-updater/server": "0.36.1"
|
|
74
74
|
},
|
|
75
75
|
"scripts": {
|
|
76
76
|
"build": "tsdown",
|