@hot-updater/cloudflare 0.31.3 → 0.32.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/iac/index.cjs +206 -92
- package/dist/iac/index.mjs +198 -84
- package/dist/index.cjs +214 -51
- package/dist/index.d.cts +46 -6
- package/dist/index.d.mts +46 -6
- package/dist/index.mjs +209 -47
- package/dist/worker/index.d.cts +4 -3
- package/dist/worker/index.d.mts +3 -2
- package/package.json +10 -7
- package/src/r2S3Storage.ts +197 -0
- package/src/r2Storage.spec.ts +316 -2
- package/src/r2Storage.ts +50 -110
- package/src/r2WranglerStorage.ts +193 -0
- package/worker/dist/README.md +1 -1
- package/worker/dist/index.js +85 -24
- package/worker/dist/index.js.map +4 -4
- package/worker/src/index.ts +0 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,24 +1,64 @@
|
|
|
1
|
+
import * as _$_hot_updater_plugin_core0 from "@hot-updater/plugin-core";
|
|
2
|
+
import { StoragePluginHooks, UniversalStoragePlugin } from "@hot-updater/plugin-core";
|
|
3
|
+
import { S3ClientConfig } from "@aws-sdk/client-s3";
|
|
4
|
+
|
|
1
5
|
//#region src/d1Database.d.ts
|
|
2
6
|
interface D1DatabaseConfig {
|
|
3
7
|
databaseId: string;
|
|
4
8
|
accountId: string;
|
|
5
9
|
cloudflareApiToken: string;
|
|
6
10
|
}
|
|
7
|
-
declare const d1Database: (config: D1DatabaseConfig, hooks?:
|
|
11
|
+
declare const d1Database: (config: D1DatabaseConfig, hooks?: _$_hot_updater_plugin_core0.DatabasePluginHooks) => () => _$_hot_updater_plugin_core0.DatabasePlugin<unknown>;
|
|
8
12
|
//#endregion
|
|
9
|
-
//#region src/
|
|
10
|
-
interface
|
|
11
|
-
|
|
13
|
+
//#region src/r2S3Storage.d.ts
|
|
14
|
+
interface R2S3StorageConfig extends S3ClientConfig {
|
|
15
|
+
accountId: string;
|
|
16
|
+
bucketName: string;
|
|
17
|
+
credentials: NonNullable<S3ClientConfig["credentials"]>;
|
|
18
|
+
/**
|
|
19
|
+
* Base path where bundles will be stored in the bucket
|
|
20
|
+
*/
|
|
21
|
+
basePath?: string;
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/r2WranglerStorage.d.ts
|
|
25
|
+
/**
|
|
26
|
+
* @deprecated `cloudflareApiToken` uses the Wrangler CLI for R2 operations,
|
|
27
|
+
* which is slower than direct S3-compatible API access. Create R2
|
|
28
|
+
* S3-compatible credentials in the Cloudflare dashboard and pass them with
|
|
29
|
+
* `r2Storage({ credentials })` instead.
|
|
30
|
+
*/
|
|
31
|
+
interface R2WranglerStorageConfig {
|
|
12
32
|
accountId: string;
|
|
13
33
|
bucketName: string;
|
|
34
|
+
/**
|
|
35
|
+
* @deprecated This token keeps R2 access on the slower Wrangler CLI path.
|
|
36
|
+
* Create R2 S3-compatible credentials in the Cloudflare dashboard and use
|
|
37
|
+
* `credentials` instead.
|
|
38
|
+
*/
|
|
39
|
+
cloudflareApiToken: string;
|
|
14
40
|
/**
|
|
15
41
|
* Base path where bundles will be stored in the bucket
|
|
16
42
|
*/
|
|
17
43
|
basePath?: string;
|
|
44
|
+
credentials?: never;
|
|
18
45
|
}
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/r2Storage.d.ts
|
|
48
|
+
type R2StorageConfig = R2S3StorageConfig | R2WranglerStorageConfig;
|
|
19
49
|
/**
|
|
20
50
|
* Cloudflare R2 storage plugin for Hot Updater.
|
|
21
51
|
*/
|
|
22
|
-
|
|
52
|
+
interface R2Storage {
|
|
53
|
+
(config: R2S3StorageConfig, hooks?: StoragePluginHooks): () => UniversalStoragePlugin;
|
|
54
|
+
/**
|
|
55
|
+
* @deprecated `cloudflareApiToken` uses the Wrangler CLI for R2 operations,
|
|
56
|
+
* which is slower than direct S3-compatible API access. Create R2
|
|
57
|
+
* S3-compatible credentials in the Cloudflare dashboard and pass them with
|
|
58
|
+
* `r2Storage({ credentials })` instead.
|
|
59
|
+
*/
|
|
60
|
+
(config: R2WranglerStorageConfig, hooks?: StoragePluginHooks): () => UniversalStoragePlugin;
|
|
61
|
+
}
|
|
62
|
+
declare const r2Storage: R2Storage;
|
|
23
63
|
//#endregion
|
|
24
|
-
export { D1DatabaseConfig, R2StorageConfig, d1Database, r2Storage };
|
|
64
|
+
export { D1DatabaseConfig, type R2S3StorageConfig, R2StorageConfig, type R2WranglerStorageConfig, d1Database, r2Storage };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,24 +1,64 @@
|
|
|
1
|
+
import * as _$_hot_updater_plugin_core0 from "@hot-updater/plugin-core";
|
|
2
|
+
import { StoragePluginHooks, UniversalStoragePlugin } from "@hot-updater/plugin-core";
|
|
3
|
+
import { S3ClientConfig } from "@aws-sdk/client-s3";
|
|
4
|
+
|
|
1
5
|
//#region src/d1Database.d.ts
|
|
2
6
|
interface D1DatabaseConfig {
|
|
3
7
|
databaseId: string;
|
|
4
8
|
accountId: string;
|
|
5
9
|
cloudflareApiToken: string;
|
|
6
10
|
}
|
|
7
|
-
declare const d1Database: (config: D1DatabaseConfig, hooks?:
|
|
11
|
+
declare const d1Database: (config: D1DatabaseConfig, hooks?: _$_hot_updater_plugin_core0.DatabasePluginHooks) => () => _$_hot_updater_plugin_core0.DatabasePlugin<unknown>;
|
|
8
12
|
//#endregion
|
|
9
|
-
//#region src/
|
|
10
|
-
interface
|
|
11
|
-
|
|
13
|
+
//#region src/r2S3Storage.d.ts
|
|
14
|
+
interface R2S3StorageConfig extends S3ClientConfig {
|
|
15
|
+
accountId: string;
|
|
16
|
+
bucketName: string;
|
|
17
|
+
credentials: NonNullable<S3ClientConfig["credentials"]>;
|
|
18
|
+
/**
|
|
19
|
+
* Base path where bundles will be stored in the bucket
|
|
20
|
+
*/
|
|
21
|
+
basePath?: string;
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/r2WranglerStorage.d.ts
|
|
25
|
+
/**
|
|
26
|
+
* @deprecated `cloudflareApiToken` uses the Wrangler CLI for R2 operations,
|
|
27
|
+
* which is slower than direct S3-compatible API access. Create R2
|
|
28
|
+
* S3-compatible credentials in the Cloudflare dashboard and pass them with
|
|
29
|
+
* `r2Storage({ credentials })` instead.
|
|
30
|
+
*/
|
|
31
|
+
interface R2WranglerStorageConfig {
|
|
12
32
|
accountId: string;
|
|
13
33
|
bucketName: string;
|
|
34
|
+
/**
|
|
35
|
+
* @deprecated This token keeps R2 access on the slower Wrangler CLI path.
|
|
36
|
+
* Create R2 S3-compatible credentials in the Cloudflare dashboard and use
|
|
37
|
+
* `credentials` instead.
|
|
38
|
+
*/
|
|
39
|
+
cloudflareApiToken: string;
|
|
14
40
|
/**
|
|
15
41
|
* Base path where bundles will be stored in the bucket
|
|
16
42
|
*/
|
|
17
43
|
basePath?: string;
|
|
44
|
+
credentials?: never;
|
|
18
45
|
}
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/r2Storage.d.ts
|
|
48
|
+
type R2StorageConfig = R2S3StorageConfig | R2WranglerStorageConfig;
|
|
19
49
|
/**
|
|
20
50
|
* Cloudflare R2 storage plugin for Hot Updater.
|
|
21
51
|
*/
|
|
22
|
-
|
|
52
|
+
interface R2Storage {
|
|
53
|
+
(config: R2S3StorageConfig, hooks?: StoragePluginHooks): () => UniversalStoragePlugin;
|
|
54
|
+
/**
|
|
55
|
+
* @deprecated `cloudflareApiToken` uses the Wrangler CLI for R2 operations,
|
|
56
|
+
* which is slower than direct S3-compatible API access. Create R2
|
|
57
|
+
* S3-compatible credentials in the Cloudflare dashboard and pass them with
|
|
58
|
+
* `r2Storage({ credentials })` instead.
|
|
59
|
+
*/
|
|
60
|
+
(config: R2WranglerStorageConfig, hooks?: StoragePluginHooks): () => UniversalStoragePlugin;
|
|
61
|
+
}
|
|
62
|
+
declare const r2Storage: R2Storage;
|
|
23
63
|
//#endregion
|
|
24
|
-
export { D1DatabaseConfig, R2StorageConfig, d1Database, r2Storage };
|
|
64
|
+
export { D1DatabaseConfig, type R2S3StorageConfig, R2StorageConfig, type R2WranglerStorageConfig, d1Database, r2Storage };
|
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { DEFAULT_ROLLOUT_COHORT_COUNT, getAssetBaseStorageUri, getBundlePatches, getManifestFileHash, getManifestStorageUri, stripBundleArtifactMetadata } from "@hot-updater/core";
|
|
3
|
-
import { calculatePagination, createDatabasePlugin, createDatabasePluginGetUpdateInfo,
|
|
3
|
+
import { calculatePagination, createDatabasePlugin, createDatabasePluginGetUpdateInfo, createStorageKeyBuilder, createUniversalStoragePlugin, getContentType, parseStorageUri } from "@hot-updater/plugin-core";
|
|
4
4
|
import Cloudflare from "cloudflare";
|
|
5
5
|
import fs from "node:fs/promises";
|
|
6
6
|
import path from "node:path";
|
|
7
|
+
import { DeleteObjectCommand, GetObjectCommand, HeadObjectCommand, S3Client } from "@aws-sdk/client-s3";
|
|
8
|
+
import { Upload } from "@aws-sdk/lib-storage";
|
|
9
|
+
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
10
|
+
import os, { constants } from "node:os";
|
|
7
11
|
import { fileURLToPath } from "node:url";
|
|
8
12
|
import { ChildProcess, execFile, spawn, spawnSync } from "node:child_process";
|
|
9
13
|
import { StringDecoder } from "node:string_decoder";
|
|
@@ -11,7 +15,6 @@ import { aborted, callbackify, debuglog, inspect, promisify, stripVTControlChara
|
|
|
11
15
|
import process$1, { execArgv, execPath, hrtime, platform } from "node:process";
|
|
12
16
|
import tty from "node:tty";
|
|
13
17
|
import { scheduler, setImmediate, setTimeout } from "node:timers/promises";
|
|
14
|
-
import { constants } from "node:os";
|
|
15
18
|
import { EventEmitter, addAbortListener, on, once, setMaxListeners } from "node:events";
|
|
16
19
|
import { serialize } from "node:v8";
|
|
17
20
|
import { appendFileSync, createReadStream, createWriteStream, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
@@ -25,7 +28,7 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
25
28
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
26
29
|
var __getProtoOf = Object.getPrototypeOf;
|
|
27
30
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
28
|
-
var __commonJSMin = (cb, mod) => () => (mod ||
|
|
31
|
+
var __commonJSMin = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
29
32
|
var __copyProps = (to, from, except, desc) => {
|
|
30
33
|
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
31
34
|
key = keys[i];
|
|
@@ -665,6 +668,112 @@ const d1Database = createDatabasePlugin({
|
|
|
665
668
|
}
|
|
666
669
|
});
|
|
667
670
|
//#endregion
|
|
671
|
+
//#region src/r2S3Storage.ts
|
|
672
|
+
const ensureExpectedR2Bucket$1 = (bucket, bucketName) => {
|
|
673
|
+
if (bucket !== bucketName) throw new Error(`Bucket name mismatch: expected "${bucketName}", but found "${bucket}".`);
|
|
674
|
+
};
|
|
675
|
+
const isS3ObjectNotFoundError = (error) => {
|
|
676
|
+
if (error instanceof Error) return error.name === "NotFound" || error.name === "NoSuchKey";
|
|
677
|
+
if (typeof error === "object" && error !== null && "$metadata" in error) return error.$metadata?.httpStatusCode === 404;
|
|
678
|
+
return false;
|
|
679
|
+
};
|
|
680
|
+
const createS3Client = (config) => {
|
|
681
|
+
const { accountId, basePath: _basePath, bucketName: _bucketName, endpoint, forcePathStyle, region, ...s3Config } = config;
|
|
682
|
+
return new S3Client({
|
|
683
|
+
...s3Config,
|
|
684
|
+
endpoint: endpoint ?? `https://${accountId}.r2.cloudflarestorage.com`,
|
|
685
|
+
forcePathStyle: forcePathStyle ?? true,
|
|
686
|
+
region: region ?? "auto"
|
|
687
|
+
});
|
|
688
|
+
};
|
|
689
|
+
const createS3StorageProfile = (config) => {
|
|
690
|
+
const { bucketName } = config;
|
|
691
|
+
const client = createS3Client(config);
|
|
692
|
+
const getStorageKey = createStorageKeyBuilder(config.basePath);
|
|
693
|
+
return {
|
|
694
|
+
async delete(storageUri) {
|
|
695
|
+
const { bucket, key } = parseStorageUri(storageUri, "r2");
|
|
696
|
+
ensureExpectedR2Bucket$1(bucket, bucketName);
|
|
697
|
+
await client.send(new DeleteObjectCommand({
|
|
698
|
+
Bucket: bucketName,
|
|
699
|
+
Key: key
|
|
700
|
+
}));
|
|
701
|
+
},
|
|
702
|
+
async upload(key, filePath) {
|
|
703
|
+
const Body = await fs.readFile(filePath);
|
|
704
|
+
const ContentType = getContentType(filePath);
|
|
705
|
+
const Key = getStorageKey(key, path.basename(filePath));
|
|
706
|
+
await new Upload({
|
|
707
|
+
client,
|
|
708
|
+
params: {
|
|
709
|
+
Body,
|
|
710
|
+
Bucket: bucketName,
|
|
711
|
+
CacheControl: "max-age=31536000",
|
|
712
|
+
ContentType,
|
|
713
|
+
Key
|
|
714
|
+
}
|
|
715
|
+
}).done();
|
|
716
|
+
return { storageUri: `r2://${bucketName}/${Key}` };
|
|
717
|
+
},
|
|
718
|
+
async exists(storageUri) {
|
|
719
|
+
const { bucket, key } = parseStorageUri(storageUri, "r2");
|
|
720
|
+
ensureExpectedR2Bucket$1(bucket, bucketName);
|
|
721
|
+
try {
|
|
722
|
+
await client.send(new HeadObjectCommand({
|
|
723
|
+
Bucket: bucketName,
|
|
724
|
+
Key: key
|
|
725
|
+
}));
|
|
726
|
+
return true;
|
|
727
|
+
} catch (error) {
|
|
728
|
+
if (isS3ObjectNotFoundError(error)) return false;
|
|
729
|
+
throw error;
|
|
730
|
+
}
|
|
731
|
+
},
|
|
732
|
+
async downloadFile(storageUri, filePath) {
|
|
733
|
+
const { bucket, key } = parseStorageUri(storageUri, "r2");
|
|
734
|
+
ensureExpectedR2Bucket$1(bucket, bucketName);
|
|
735
|
+
const response = await client.send(new GetObjectCommand({
|
|
736
|
+
Bucket: bucketName,
|
|
737
|
+
Key: key
|
|
738
|
+
}));
|
|
739
|
+
if (!response.Body) throw new Error("R2 object body is empty");
|
|
740
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
741
|
+
await fs.writeFile(filePath, await response.Body.transformToByteArray());
|
|
742
|
+
}
|
|
743
|
+
};
|
|
744
|
+
};
|
|
745
|
+
const createS3RuntimeStorageProfile = (config) => {
|
|
746
|
+
const { bucketName } = config;
|
|
747
|
+
const client = createS3Client(config);
|
|
748
|
+
return {
|
|
749
|
+
async readText(storageUri) {
|
|
750
|
+
const { bucket, key } = parseStorageUri(storageUri, "r2");
|
|
751
|
+
ensureExpectedR2Bucket$1(bucket, bucketName);
|
|
752
|
+
try {
|
|
753
|
+
const response = await client.send(new GetObjectCommand({
|
|
754
|
+
Bucket: bucketName,
|
|
755
|
+
Key: key
|
|
756
|
+
}));
|
|
757
|
+
if (!response.Body) return null;
|
|
758
|
+
return response.Body.transformToString();
|
|
759
|
+
} catch (error) {
|
|
760
|
+
if (isS3ObjectNotFoundError(error)) return null;
|
|
761
|
+
throw error;
|
|
762
|
+
}
|
|
763
|
+
},
|
|
764
|
+
async getDownloadUrl(storageUri) {
|
|
765
|
+
const { bucket, key } = parseStorageUri(storageUri, "r2");
|
|
766
|
+
ensureExpectedR2Bucket$1(bucket, bucketName);
|
|
767
|
+
const signedUrl = await getSignedUrl(client, new GetObjectCommand({
|
|
768
|
+
Bucket: bucketName,
|
|
769
|
+
Key: key
|
|
770
|
+
}), { expiresIn: 3600 });
|
|
771
|
+
if (!signedUrl) throw new Error("Failed to presign R2 URL");
|
|
772
|
+
return { fileUrl: signedUrl };
|
|
773
|
+
}
|
|
774
|
+
};
|
|
775
|
+
};
|
|
776
|
+
//#endregion
|
|
668
777
|
//#region ../../node_modules/.pnpm/is-plain-obj@4.1.0/node_modules/is-plain-obj/index.js
|
|
669
778
|
function isPlainObject(value) {
|
|
670
779
|
if (typeof value !== "object" || value === null) return false;
|
|
@@ -6838,55 +6947,108 @@ const createWrangler = ({ stdio, accountId, cloudflareApiToken, cwd }) => {
|
|
|
6838
6947
|
return (...command) => $("npx", ["wrangler", ...command]);
|
|
6839
6948
|
};
|
|
6840
6949
|
//#endregion
|
|
6950
|
+
//#region src/r2WranglerStorage.ts
|
|
6951
|
+
const ensureExpectedR2Bucket = (bucket, bucketName) => {
|
|
6952
|
+
if (bucket !== bucketName) throw new Error(`Bucket name mismatch: expected "${bucketName}", but found "${bucket}".`);
|
|
6953
|
+
};
|
|
6954
|
+
const isR2ObjectNotFoundError = (error) => {
|
|
6955
|
+
const output = [
|
|
6956
|
+
error.stderr,
|
|
6957
|
+
error.stdout,
|
|
6958
|
+
error.shortMessage,
|
|
6959
|
+
error.message
|
|
6960
|
+
].filter(Boolean).join("\n").toLowerCase();
|
|
6961
|
+
return output.includes("not found") || output.includes("no such object") || output.includes("does not exist");
|
|
6962
|
+
};
|
|
6963
|
+
const createWranglerStorageProfile = (config) => {
|
|
6964
|
+
const { bucketName, cloudflareApiToken, accountId } = config;
|
|
6965
|
+
const wrangler = createWrangler({
|
|
6966
|
+
accountId,
|
|
6967
|
+
cloudflareApiToken,
|
|
6968
|
+
cwd: process.cwd()
|
|
6969
|
+
});
|
|
6970
|
+
const getStorageKey = createStorageKeyBuilder(config.basePath);
|
|
6971
|
+
return {
|
|
6972
|
+
async delete(storageUri) {
|
|
6973
|
+
const { bucket, key } = parseStorageUri(storageUri, "r2");
|
|
6974
|
+
ensureExpectedR2Bucket(bucket, bucketName);
|
|
6975
|
+
try {
|
|
6976
|
+
await wrangler("r2", "object", "delete", [bucketName, key].join("/"), "--remote");
|
|
6977
|
+
} catch {
|
|
6978
|
+
throw new Error("Can not delete bundle");
|
|
6979
|
+
}
|
|
6980
|
+
},
|
|
6981
|
+
async upload(key, filePath) {
|
|
6982
|
+
const contentType = getContentType(filePath);
|
|
6983
|
+
const Key = getStorageKey(key, path.basename(filePath));
|
|
6984
|
+
try {
|
|
6985
|
+
const { stderr, exitCode } = await wrangler("r2", "object", "put", [bucketName, Key].join("/"), "--file", filePath, "--content-type", contentType, "--remote");
|
|
6986
|
+
if (exitCode !== 0 && stderr) throw new Error(stderr);
|
|
6987
|
+
} catch (error) {
|
|
6988
|
+
if (error instanceof ExecaError) throw new Error(error.stderr || error.stdout);
|
|
6989
|
+
throw error;
|
|
6990
|
+
}
|
|
6991
|
+
return { storageUri: `r2://${bucketName}/${Key}` };
|
|
6992
|
+
},
|
|
6993
|
+
async exists(storageUri) {
|
|
6994
|
+
const { bucket, key } = parseStorageUri(storageUri, "r2");
|
|
6995
|
+
ensureExpectedR2Bucket(bucket, bucketName);
|
|
6996
|
+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "hot-updater-r2-exists-"));
|
|
6997
|
+
const tempFilePath = path.join(tempDir, "object");
|
|
6998
|
+
try {
|
|
6999
|
+
await wrangler("r2", "object", "get", [bucketName, key].join("/"), "--file", tempFilePath, "--remote");
|
|
7000
|
+
return true;
|
|
7001
|
+
} catch (error) {
|
|
7002
|
+
if (error instanceof ExecaError && isR2ObjectNotFoundError(error)) return false;
|
|
7003
|
+
throw error;
|
|
7004
|
+
} finally {
|
|
7005
|
+
await fs.rm(tempDir, {
|
|
7006
|
+
force: true,
|
|
7007
|
+
recursive: true
|
|
7008
|
+
});
|
|
7009
|
+
}
|
|
7010
|
+
},
|
|
7011
|
+
async downloadFile(storageUri, filePath) {
|
|
7012
|
+
const { bucket, key } = parseStorageUri(storageUri, "r2");
|
|
7013
|
+
ensureExpectedR2Bucket(bucket, bucketName);
|
|
7014
|
+
try {
|
|
7015
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
7016
|
+
const { stderr, exitCode } = await wrangler("r2", "object", "get", [bucketName, key].join("/"), "--file", filePath, "--remote");
|
|
7017
|
+
if (exitCode !== 0 && stderr) throw new Error(stderr);
|
|
7018
|
+
} catch (error) {
|
|
7019
|
+
if (error instanceof ExecaError) throw new Error(error.stderr || error.stdout);
|
|
7020
|
+
throw error;
|
|
7021
|
+
}
|
|
7022
|
+
}
|
|
7023
|
+
};
|
|
7024
|
+
};
|
|
7025
|
+
const createWranglerRuntimeStorageProfile = () => {
|
|
7026
|
+
const error = /* @__PURE__ */ new Error("r2Storage runtime profile requires R2 S3 credentials. Wrangler-based R2 access is only supported by the node profile.");
|
|
7027
|
+
return {
|
|
7028
|
+
async readText() {
|
|
7029
|
+
throw error;
|
|
7030
|
+
},
|
|
7031
|
+
async getDownloadUrl() {
|
|
7032
|
+
throw error;
|
|
7033
|
+
}
|
|
7034
|
+
};
|
|
7035
|
+
};
|
|
7036
|
+
//#endregion
|
|
6841
7037
|
//#region src/r2Storage.ts
|
|
6842
|
-
|
|
6843
|
-
|
|
6844
|
-
|
|
6845
|
-
const r2Storage =
|
|
7038
|
+
const hasS3Credentials = (config) => {
|
|
7039
|
+
return Boolean(config.credentials);
|
|
7040
|
+
};
|
|
7041
|
+
const r2Storage = createUniversalStoragePlugin({
|
|
6846
7042
|
name: "r2Storage",
|
|
6847
7043
|
supportedProtocol: "r2",
|
|
6848
7044
|
factory: (config) => {
|
|
6849
|
-
|
|
6850
|
-
|
|
6851
|
-
|
|
6852
|
-
|
|
6853
|
-
cwd: process.cwd()
|
|
6854
|
-
});
|
|
6855
|
-
const getStorageKey = createStorageKeyBuilder(config.basePath);
|
|
7045
|
+
if (hasS3Credentials(config)) return {
|
|
7046
|
+
node: createS3StorageProfile(config),
|
|
7047
|
+
runtime: createS3RuntimeStorageProfile(config)
|
|
7048
|
+
};
|
|
6856
7049
|
return {
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
if (bucket !== bucketName) throw new Error(`Bucket name mismatch: expected "${bucketName}", but found "${bucket}".`);
|
|
6860
|
-
try {
|
|
6861
|
-
await wrangler("r2", "object", "delete", [bucketName, key].join("/"), "--remote");
|
|
6862
|
-
} catch {
|
|
6863
|
-
throw new Error("Can not delete bundle");
|
|
6864
|
-
}
|
|
6865
|
-
},
|
|
6866
|
-
async upload(key, filePath) {
|
|
6867
|
-
const contentType = getContentType(filePath);
|
|
6868
|
-
const Key = getStorageKey(key, path.basename(filePath));
|
|
6869
|
-
try {
|
|
6870
|
-
const { stderr, exitCode } = await wrangler("r2", "object", "put", [bucketName, Key].join("/"), "--file", filePath, "--content-type", contentType, "--remote");
|
|
6871
|
-
if (exitCode !== 0 && stderr) throw new Error(stderr);
|
|
6872
|
-
} catch (error) {
|
|
6873
|
-
if (error instanceof ExecaError) throw new Error(error.stderr || error.stdout);
|
|
6874
|
-
throw error;
|
|
6875
|
-
}
|
|
6876
|
-
return { storageUri: `r2://${bucketName}/${Key}` };
|
|
6877
|
-
},
|
|
6878
|
-
async downloadFile(storageUri, filePath) {
|
|
6879
|
-
const { bucket, key } = parseStorageUri(storageUri, "r2");
|
|
6880
|
-
if (bucket !== bucketName) throw new Error(`Bucket name mismatch: expected "${bucketName}", but found "${bucket}".`);
|
|
6881
|
-
try {
|
|
6882
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
6883
|
-
const { stderr, exitCode } = await wrangler("r2", "object", "get", [bucketName, key].join("/"), "--file", filePath, "--remote");
|
|
6884
|
-
if (exitCode !== 0 && stderr) throw new Error(stderr);
|
|
6885
|
-
} catch (error) {
|
|
6886
|
-
if (error instanceof ExecaError) throw new Error(error.stderr || error.stdout);
|
|
6887
|
-
throw error;
|
|
6888
|
-
}
|
|
6889
|
-
}
|
|
7050
|
+
node: createWranglerStorageProfile(config),
|
|
7051
|
+
runtime: createWranglerRuntimeStorageProfile()
|
|
6890
7052
|
};
|
|
6891
7053
|
}
|
|
6892
7054
|
});
|
package/dist/worker/index.d.cts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as _$_hot_updater_plugin_core0 from "@hot-updater/plugin-core";
|
|
2
2
|
import { HotUpdaterContext, RequestEnvContext as RequestEnvContext$1 } from "@hot-updater/plugin-core";
|
|
3
|
+
import { verifyJwtSignedUrl } from "@hot-updater/js";
|
|
3
4
|
|
|
4
5
|
//#region src/cloudflareWorkerDatabase.d.ts
|
|
5
6
|
type D1Result<T> = {
|
|
@@ -37,7 +38,7 @@ interface CloudflareWorkerStorageConfig<TContext extends RequestEnvContext$1<Clo
|
|
|
37
38
|
//#region src/worker/index.d.ts
|
|
38
39
|
interface CloudflareWorkerRuntimeEnv extends CloudflareWorkerDatabaseEnv, CloudflareWorkerStorageEnv {}
|
|
39
40
|
type RequestEnvContext<TEnv = CloudflareWorkerRuntimeEnv> = RequestEnvContext$1<TEnv>;
|
|
40
|
-
declare const d1Database: <TContext extends RequestEnvContext<CloudflareWorkerRuntimeEnv> = RequestEnvContext<CloudflareWorkerRuntimeEnv>>() => () =>
|
|
41
|
-
declare const r2Storage: <TContext extends RequestEnvContext<CloudflareWorkerRuntimeEnv> = RequestEnvContext<CloudflareWorkerRuntimeEnv>>(config: CloudflareWorkerStorageConfig<TContext>) => () =>
|
|
41
|
+
declare const d1Database: <TContext extends RequestEnvContext<CloudflareWorkerRuntimeEnv> = RequestEnvContext<CloudflareWorkerRuntimeEnv>>() => () => _$_hot_updater_plugin_core0.DatabasePlugin<TContext>;
|
|
42
|
+
declare const r2Storage: <TContext extends RequestEnvContext<CloudflareWorkerRuntimeEnv> = RequestEnvContext<CloudflareWorkerRuntimeEnv>>(config: CloudflareWorkerStorageConfig<TContext>) => () => _$_hot_updater_plugin_core0.RuntimeStoragePlugin<TContext>;
|
|
42
43
|
//#endregion
|
|
43
44
|
export { type CloudflareWorkerDatabaseEnv, CloudflareWorkerRuntimeEnv, type CloudflareWorkerStorageEnv, RequestEnvContext, d1Database, r2Storage, verifyJwtSignedUrl };
|
package/dist/worker/index.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { verifyJwtSignedUrl } from "@hot-updater/js";
|
|
2
|
+
import * as _$_hot_updater_plugin_core0 from "@hot-updater/plugin-core";
|
|
2
3
|
import { HotUpdaterContext, RequestEnvContext as RequestEnvContext$1 } from "@hot-updater/plugin-core";
|
|
3
4
|
|
|
4
5
|
//#region src/cloudflareWorkerDatabase.d.ts
|
|
@@ -37,7 +38,7 @@ interface CloudflareWorkerStorageConfig<TContext extends RequestEnvContext$1<Clo
|
|
|
37
38
|
//#region src/worker/index.d.ts
|
|
38
39
|
interface CloudflareWorkerRuntimeEnv extends CloudflareWorkerDatabaseEnv, CloudflareWorkerStorageEnv {}
|
|
39
40
|
type RequestEnvContext<TEnv = CloudflareWorkerRuntimeEnv> = RequestEnvContext$1<TEnv>;
|
|
40
|
-
declare const d1Database: <TContext extends RequestEnvContext<CloudflareWorkerRuntimeEnv> = RequestEnvContext<CloudflareWorkerRuntimeEnv>>() => () =>
|
|
41
|
-
declare const r2Storage: <TContext extends RequestEnvContext<CloudflareWorkerRuntimeEnv> = RequestEnvContext<CloudflareWorkerRuntimeEnv>>(config: CloudflareWorkerStorageConfig<TContext>) => () =>
|
|
41
|
+
declare const d1Database: <TContext extends RequestEnvContext<CloudflareWorkerRuntimeEnv> = RequestEnvContext<CloudflareWorkerRuntimeEnv>>() => () => _$_hot_updater_plugin_core0.DatabasePlugin<TContext>;
|
|
42
|
+
declare const r2Storage: <TContext extends RequestEnvContext<CloudflareWorkerRuntimeEnv> = RequestEnvContext<CloudflareWorkerRuntimeEnv>>(config: CloudflareWorkerStorageConfig<TContext>) => () => _$_hot_updater_plugin_core0.RuntimeStoragePlugin<TContext>;
|
|
42
43
|
//#endregion
|
|
43
44
|
export { type CloudflareWorkerDatabaseEnv, CloudflareWorkerRuntimeEnv, type CloudflareWorkerStorageEnv, RequestEnvContext, d1Database, r2Storage, verifyJwtSignedUrl };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hot-updater/cloudflare",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.32.0",
|
|
5
5
|
"description": "React Native OTA solution for self-hosted",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
7
7
|
"module": "dist/index.mjs",
|
|
@@ -47,14 +47,17 @@
|
|
|
47
47
|
"package.json"
|
|
48
48
|
],
|
|
49
49
|
"dependencies": {
|
|
50
|
+
"@aws-sdk/client-s3": "3.1008.0",
|
|
51
|
+
"@aws-sdk/lib-storage": "3.1008.0",
|
|
52
|
+
"@aws-sdk/s3-request-presigner": "3.1008.0",
|
|
50
53
|
"cloudflare": "4.2.0",
|
|
51
54
|
"hono": "4.12.9",
|
|
52
55
|
"uuidv7": "^1.0.2",
|
|
53
|
-
"@hot-updater/
|
|
54
|
-
"@hot-updater/js": "0.
|
|
55
|
-
"@hot-updater/core": "0.
|
|
56
|
-
"@hot-updater/
|
|
57
|
-
"@hot-updater/
|
|
56
|
+
"@hot-updater/core": "0.32.0",
|
|
57
|
+
"@hot-updater/js": "0.32.0",
|
|
58
|
+
"@hot-updater/plugin-core": "0.32.0",
|
|
59
|
+
"@hot-updater/server": "0.32.0",
|
|
60
|
+
"@hot-updater/cli-tools": "0.32.0"
|
|
58
61
|
},
|
|
59
62
|
"devDependencies": {
|
|
60
63
|
"@cloudflare/vitest-pool-workers": "0.13.0",
|
|
@@ -71,7 +74,7 @@
|
|
|
71
74
|
"vitest": "4.1.4",
|
|
72
75
|
"wrangler": "^4.5.0",
|
|
73
76
|
"xdg-app-paths": "^8.3.0",
|
|
74
|
-
"@hot-updater/test-utils": "0.
|
|
77
|
+
"@hot-updater/test-utils": "0.32.0"
|
|
75
78
|
},
|
|
76
79
|
"scripts": {
|
|
77
80
|
"build": "tsdown && pnpm build:worker",
|