@hot-updater/aws 0.35.11 → 0.36.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.cjs +64 -4
- package/dist/index.mjs +64 -4
- package/dist/lambda/index.cjs +887 -2815
- package/package.json +8 -8
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}".`);
|