@hot-updater/cloudflare 0.31.4 → 0.33.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 +223 -63
- package/dist/index.d.cts +46 -6
- package/dist/index.d.mts +46 -6
- package/dist/index.mjs +218 -59
- package/dist/worker/index.cjs +9 -12
- package/dist/worker/index.d.cts +4 -3
- package/dist/worker/index.d.mts +3 -2
- package/dist/worker/index.mjs +9 -12
- package/package.json +10 -7
- package/src/cloudflareWorkerDatabase.spec.ts +260 -0
- package/src/cloudflareWorkerDatabase.ts +23 -19
- package/src/d1Database.spec.ts +16 -2
- package/src/d1Database.ts +23 -19
- 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 +249 -58
- package/worker/dist/index.js.map +4 -4
- package/worker/src/index.ts +0 -1
- package/worker/src/getUpdateInfo.ts +0 -194
package/dist/iac/index.mjs
CHANGED
|
@@ -28,7 +28,7 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
28
28
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
29
29
|
var __getProtoOf = Object.getPrototypeOf;
|
|
30
30
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
31
|
-
var __commonJSMin = (cb, mod) => () => (mod ||
|
|
31
|
+
var __commonJSMin = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
32
32
|
var __copyProps = (to, from, except, desc) => {
|
|
33
33
|
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
34
34
|
key = keys[i];
|
|
@@ -10838,7 +10838,10 @@ const getConfigScaffold = (build) => {
|
|
|
10838
10838
|
configString: `r2Storage({
|
|
10839
10839
|
bucketName: process.env.HOT_UPDATER_CLOUDFLARE_R2_BUCKET_NAME!,
|
|
10840
10840
|
accountId: process.env.HOT_UPDATER_CLOUDFLARE_ACCOUNT_ID!,
|
|
10841
|
-
|
|
10841
|
+
credentials: {
|
|
10842
|
+
accessKeyId: process.env.HOT_UPDATER_CLOUDFLARE_R2_ACCESS_KEY_ID!,
|
|
10843
|
+
secretAccessKey: process.env.HOT_UPDATER_CLOUDFLARE_R2_SECRET_ACCESS_KEY!,
|
|
10844
|
+
},
|
|
10842
10845
|
})`
|
|
10843
10846
|
}).setDatabase({
|
|
10844
10847
|
imports: [{
|
|
@@ -10863,7 +10866,50 @@ export default HotUpdater.wrap({
|
|
|
10863
10866
|
baseURL: "%%source%%",
|
|
10864
10867
|
updateStrategy: "appVersion", // or "fingerprint"
|
|
10865
10868
|
})(App);`;
|
|
10866
|
-
const
|
|
10869
|
+
const HOT_UPDATER_ENV_PATH = ".env.hotupdater";
|
|
10870
|
+
const unquoteEnvValue = (value) => {
|
|
10871
|
+
const trimmed = value.trim();
|
|
10872
|
+
if (trimmed.startsWith("\"") && trimmed.endsWith("\"") || trimmed.startsWith("'") && trimmed.endsWith("'")) return trimmed.slice(1, -1);
|
|
10873
|
+
return trimmed;
|
|
10874
|
+
};
|
|
10875
|
+
const readHotUpdaterEnv = async (cwd) => {
|
|
10876
|
+
const envPath = path.join(cwd, HOT_UPDATER_ENV_PATH);
|
|
10877
|
+
const content = await fs.readFile(envPath, "utf-8").catch(() => "");
|
|
10878
|
+
const env = {};
|
|
10879
|
+
for (const line of content.split("\n")) {
|
|
10880
|
+
const trimmed = line.trim();
|
|
10881
|
+
if (!trimmed || trimmed.startsWith("#") || !trimmed.includes("=")) continue;
|
|
10882
|
+
const [key, ...valueParts] = trimmed.split("=");
|
|
10883
|
+
if (!key) continue;
|
|
10884
|
+
env[key.trim()] = unquoteEnvValue(valueParts.join("="));
|
|
10885
|
+
}
|
|
10886
|
+
return env;
|
|
10887
|
+
};
|
|
10888
|
+
const getEnvValue = (env, key) => {
|
|
10889
|
+
return process.env[key]?.trim() || env[key]?.trim() || void 0;
|
|
10890
|
+
};
|
|
10891
|
+
const inputR2ApiCredentials = async ({ accountId, bucketName, accessKeyId, secretAccessKey }) => {
|
|
10892
|
+
p.log.step(`R2 API Tokens dashboard: ${link(`https://dash.cloudflare.com/${accountId}/r2/api-tokens`)}`);
|
|
10893
|
+
p.log.step("Required permission: Object Read & Write");
|
|
10894
|
+
p.log.step(`Target bucket: ${bucketName}`);
|
|
10895
|
+
let resolvedAccessKeyId = accessKeyId;
|
|
10896
|
+
if (!resolvedAccessKeyId) {
|
|
10897
|
+
const inputR2AccessKeyId = await p.password({ message: "Enter the R2 Access Key ID" });
|
|
10898
|
+
if (p.isCancel(inputR2AccessKeyId)) process.exit(1);
|
|
10899
|
+
resolvedAccessKeyId = inputR2AccessKeyId;
|
|
10900
|
+
}
|
|
10901
|
+
let resolvedSecretAccessKey = secretAccessKey;
|
|
10902
|
+
if (!resolvedSecretAccessKey) {
|
|
10903
|
+
const inputR2SecretAccessKey = await p.password({ message: "Enter the R2 Secret Access Key" });
|
|
10904
|
+
if (p.isCancel(inputR2SecretAccessKey)) process.exit(1);
|
|
10905
|
+
resolvedSecretAccessKey = inputR2SecretAccessKey;
|
|
10906
|
+
}
|
|
10907
|
+
return {
|
|
10908
|
+
accessKeyId: resolvedAccessKeyId,
|
|
10909
|
+
secretAccessKey: resolvedSecretAccessKey
|
|
10910
|
+
};
|
|
10911
|
+
};
|
|
10912
|
+
const deployWorker = async (oauth_token, accountId, { d1DatabaseId, d1DatabaseName, r2BucketName, workerName }) => {
|
|
10867
10913
|
const cwd = getCwd();
|
|
10868
10914
|
const cloudflarePackagePath = __require.resolve("@hot-updater/cloudflare/package.json", { paths: [cwd] });
|
|
10869
10915
|
const { tmpDir, removeTmpDir } = await copyDirToTmp(path.dirname(cloudflarePackagePath));
|
|
@@ -10895,14 +10941,19 @@ const deployWorker = async (oauth_token, accountId, { d1DatabaseId, d1DatabaseNa
|
|
|
10895
10941
|
await fs.writeFile(filePath, transformTemplate(content, { BUCKET_NAME: r2BucketName }));
|
|
10896
10942
|
}
|
|
10897
10943
|
await wrangler("d1", "migrations", "apply", d1DatabaseName, "--remote");
|
|
10898
|
-
|
|
10899
|
-
|
|
10900
|
-
|
|
10901
|
-
|
|
10902
|
-
|
|
10903
|
-
|
|
10904
|
-
|
|
10905
|
-
|
|
10944
|
+
let resolvedWorkerName = workerName;
|
|
10945
|
+
if (resolvedWorkerName) p.log.info("Using existing Cloudflare Worker name.");
|
|
10946
|
+
else {
|
|
10947
|
+
const inputWorkerName = await p.text({
|
|
10948
|
+
message: "Enter the name of the worker",
|
|
10949
|
+
defaultValue: "hot-updater",
|
|
10950
|
+
placeholder: "hot-updater"
|
|
10951
|
+
});
|
|
10952
|
+
if (p.isCancel(inputWorkerName)) process.exit(1);
|
|
10953
|
+
resolvedWorkerName = inputWorkerName;
|
|
10954
|
+
}
|
|
10955
|
+
await wrangler("deploy", "--name", resolvedWorkerName);
|
|
10956
|
+
return resolvedWorkerName;
|
|
10906
10957
|
} catch (error) {
|
|
10907
10958
|
throw new Error("Failed to deploy worker", { cause: error });
|
|
10908
10959
|
} finally {
|
|
@@ -10911,6 +10962,7 @@ const deployWorker = async (oauth_token, accountId, { d1DatabaseId, d1DatabaseNa
|
|
|
10911
10962
|
};
|
|
10912
10963
|
const runInit = async ({ build }) => {
|
|
10913
10964
|
const cwd = getCwd();
|
|
10965
|
+
const existingEnv = await readHotUpdaterEnv(cwd);
|
|
10914
10966
|
let auth = getWranglerLoginAuthToken();
|
|
10915
10967
|
if (!auth || (0, import_dayjs_min.default)(auth?.expiration_time).isBefore((0, import_dayjs_min.default)())) {
|
|
10916
10968
|
await execa("npx", [
|
|
@@ -10928,70 +10980,117 @@ const runInit = async ({ build }) => {
|
|
|
10928
10980
|
if (!auth) throw new Error("'npx wrangler login' is required to use this command");
|
|
10929
10981
|
const cf = new Cloudflare({ apiToken: auth.oauth_token });
|
|
10930
10982
|
const createKey = `create/${Math.random().toString(36).substring(2, 15)}`;
|
|
10931
|
-
|
|
10932
|
-
|
|
10933
|
-
|
|
10934
|
-
|
|
10935
|
-
|
|
10936
|
-
|
|
10937
|
-
|
|
10938
|
-
|
|
10939
|
-
|
|
10940
|
-
|
|
10941
|
-
|
|
10942
|
-
|
|
10943
|
-
|
|
10944
|
-
|
|
10983
|
+
let accountId = getEnvValue(existingEnv, "HOT_UPDATER_CLOUDFLARE_ACCOUNT_ID");
|
|
10984
|
+
if (accountId) p.log.info("Using existing Cloudflare account ID.");
|
|
10985
|
+
else {
|
|
10986
|
+
const accounts = [];
|
|
10987
|
+
try {
|
|
10988
|
+
await p.tasks([{
|
|
10989
|
+
title: "Checking Account List...",
|
|
10990
|
+
task: async () => {
|
|
10991
|
+
accounts.push(...(await cf.accounts.list()).result.map((account) => ({
|
|
10992
|
+
id: account.id,
|
|
10993
|
+
name: account.name
|
|
10994
|
+
})));
|
|
10995
|
+
}
|
|
10996
|
+
}]);
|
|
10997
|
+
} catch (e) {
|
|
10998
|
+
if (e instanceof Error) p.log.error(e.message);
|
|
10999
|
+
throw e;
|
|
11000
|
+
}
|
|
11001
|
+
const selectedAccountId = await p.select({
|
|
11002
|
+
message: "Account List",
|
|
11003
|
+
options: accounts.map((account) => ({
|
|
11004
|
+
value: account.id,
|
|
11005
|
+
label: `${account.name} (${account.id})`
|
|
11006
|
+
}))
|
|
11007
|
+
});
|
|
11008
|
+
if (p.isCancel(selectedAccountId)) process.exit(1);
|
|
11009
|
+
accountId = selectedAccountId;
|
|
10945
11010
|
}
|
|
10946
|
-
|
|
10947
|
-
|
|
10948
|
-
|
|
10949
|
-
|
|
10950
|
-
|
|
10951
|
-
|
|
10952
|
-
|
|
10953
|
-
|
|
10954
|
-
|
|
10955
|
-
|
|
10956
|
-
|
|
10957
|
-
|
|
10958
|
-
|
|
10959
|
-
|
|
10960
|
-
|
|
10961
|
-
|
|
10962
|
-
|
|
10963
|
-
|
|
10964
|
-
|
|
10965
|
-
|
|
10966
|
-
|
|
10967
|
-
|
|
10968
|
-
|
|
10969
|
-
|
|
10970
|
-
|
|
11011
|
+
let apiToken = getEnvValue(existingEnv, "HOT_UPDATER_CLOUDFLARE_API_TOKEN");
|
|
11012
|
+
if (apiToken) p.log.info("Using existing Cloudflare API token.");
|
|
11013
|
+
else {
|
|
11014
|
+
p.log.step(`D1 API Token dashboard: ${link(`https://dash.cloudflare.com/${accountId}/api-tokens`)}`);
|
|
11015
|
+
p.log.step("Required permission: D1 Edit");
|
|
11016
|
+
p.log.step("Used for bundle metadata writes after init.");
|
|
11017
|
+
const inputApiToken = await p.password({ message: "Enter the D1 API Token" });
|
|
11018
|
+
if (p.isCancel(inputApiToken)) process.exit(1);
|
|
11019
|
+
apiToken = inputApiToken;
|
|
11020
|
+
if (!apiToken) p.log.warn("Skipping API Token. You can set it later in .env HOT_UPDATER_CLOUDFLARE_API_TOKEN file.");
|
|
11021
|
+
}
|
|
11022
|
+
const existingBucketName = getEnvValue(existingEnv, "HOT_UPDATER_CLOUDFLARE_R2_BUCKET_NAME");
|
|
11023
|
+
let selectedBucketName;
|
|
11024
|
+
if (existingBucketName) {
|
|
11025
|
+
selectedBucketName = existingBucketName;
|
|
11026
|
+
p.log.info("Using existing Cloudflare R2 bucket name.");
|
|
11027
|
+
} else {
|
|
11028
|
+
const availableBuckets = [];
|
|
11029
|
+
try {
|
|
11030
|
+
await p.tasks([{
|
|
11031
|
+
title: "Checking R2 Buckets...",
|
|
11032
|
+
task: async () => {
|
|
11033
|
+
const buckets = (await cf.r2.buckets.list({ account_id: accountId })).buckets ?? [];
|
|
11034
|
+
availableBuckets.push(...buckets.filter((bucket) => bucket.name).map((bucket) => ({ name: bucket.name })));
|
|
11035
|
+
}
|
|
11036
|
+
}]);
|
|
11037
|
+
} catch (e) {
|
|
11038
|
+
if (e instanceof Error) p.log.error(e.message);
|
|
11039
|
+
throw e;
|
|
11040
|
+
}
|
|
11041
|
+
if (availableBuckets.length === 1) {
|
|
11042
|
+
selectedBucketName = availableBuckets[0].name;
|
|
11043
|
+
p.log.info("Using the only Cloudflare R2 bucket.");
|
|
11044
|
+
} else {
|
|
11045
|
+
const selectedR2BucketName = await p.select({
|
|
11046
|
+
message: "R2 List",
|
|
11047
|
+
options: [...availableBuckets.map((bucket) => ({
|
|
11048
|
+
value: bucket.name,
|
|
11049
|
+
label: bucket.name
|
|
11050
|
+
})), {
|
|
11051
|
+
value: createKey,
|
|
11052
|
+
label: "Create New R2 Bucket"
|
|
11053
|
+
}]
|
|
11054
|
+
});
|
|
11055
|
+
if (p.isCancel(selectedR2BucketName)) process.exit(1);
|
|
11056
|
+
selectedBucketName = selectedR2BucketName;
|
|
11057
|
+
}
|
|
11058
|
+
if (selectedBucketName === createKey) {
|
|
11059
|
+
const name = await p.text({ message: "Enter the name of the new R2 Bucket" });
|
|
11060
|
+
if (p.isCancel(name)) process.exit(1);
|
|
11061
|
+
const newR2 = await cf.r2.buckets.create({
|
|
11062
|
+
account_id: accountId,
|
|
11063
|
+
name
|
|
11064
|
+
});
|
|
11065
|
+
if (!newR2.name) throw new Error("Failed to create new R2 Bucket");
|
|
11066
|
+
selectedBucketName = newR2.name;
|
|
11067
|
+
}
|
|
10971
11068
|
}
|
|
10972
|
-
|
|
10973
|
-
|
|
10974
|
-
|
|
10975
|
-
|
|
10976
|
-
|
|
10977
|
-
|
|
10978
|
-
|
|
10979
|
-
|
|
10980
|
-
|
|
10981
|
-
|
|
10982
|
-
|
|
10983
|
-
|
|
10984
|
-
|
|
10985
|
-
|
|
10986
|
-
|
|
10987
|
-
|
|
10988
|
-
|
|
11069
|
+
p.log.info(`Selected R2: ${selectedBucketName}`);
|
|
11070
|
+
const existingR2AccessKeyId = getEnvValue(existingEnv, "HOT_UPDATER_CLOUDFLARE_R2_ACCESS_KEY_ID");
|
|
11071
|
+
const existingR2SecretAccessKey = getEnvValue(existingEnv, "HOT_UPDATER_CLOUDFLARE_R2_SECRET_ACCESS_KEY");
|
|
11072
|
+
let r2AccessKeyId = existingR2AccessKeyId;
|
|
11073
|
+
let r2SecretAccessKey = existingR2SecretAccessKey;
|
|
11074
|
+
if (r2AccessKeyId && r2SecretAccessKey) p.log.info("Using existing Cloudflare R2 API credentials.");
|
|
11075
|
+
else if (r2AccessKeyId || r2SecretAccessKey) {
|
|
11076
|
+
p.log.warn("Existing Cloudflare R2 API credentials are incomplete.");
|
|
11077
|
+
const credentials = await inputR2ApiCredentials({
|
|
11078
|
+
accountId,
|
|
11079
|
+
bucketName: selectedBucketName,
|
|
11080
|
+
accessKeyId: r2AccessKeyId,
|
|
11081
|
+
secretAccessKey: r2SecretAccessKey
|
|
11082
|
+
});
|
|
11083
|
+
r2AccessKeyId = credentials.accessKeyId;
|
|
11084
|
+
r2SecretAccessKey = credentials.secretAccessKey;
|
|
11085
|
+
} else {
|
|
11086
|
+
const credentials = await inputR2ApiCredentials({
|
|
11087
|
+
accountId,
|
|
11088
|
+
bucketName: selectedBucketName
|
|
10989
11089
|
});
|
|
10990
|
-
|
|
10991
|
-
|
|
11090
|
+
r2AccessKeyId = credentials.accessKeyId;
|
|
11091
|
+
r2SecretAccessKey = credentials.secretAccessKey;
|
|
10992
11092
|
}
|
|
10993
|
-
|
|
10994
|
-
if ((await cf.r2.buckets.domains.managed.list(selectedBucketName, { account_id: accountId })).enabled) {
|
|
11093
|
+
if ((existingBucketName ? { enabled: false } : await cf.r2.buckets.domains.managed.list(selectedBucketName, { account_id: accountId })).enabled) {
|
|
10995
11094
|
if (await p.confirm({ message: "Make R2 bucket private?" })) try {
|
|
10996
11095
|
await p.tasks([{
|
|
10997
11096
|
title: "Making R2 bucket private...",
|
|
@@ -11023,17 +11122,27 @@ const runInit = async ({ build }) => {
|
|
|
11023
11122
|
if (e instanceof Error) p.log.error(e.message);
|
|
11024
11123
|
throw e;
|
|
11025
11124
|
}
|
|
11026
|
-
|
|
11027
|
-
|
|
11028
|
-
|
|
11029
|
-
|
|
11030
|
-
|
|
11031
|
-
|
|
11032
|
-
|
|
11033
|
-
|
|
11034
|
-
|
|
11035
|
-
|
|
11036
|
-
|
|
11125
|
+
const existingD1DatabaseId = getEnvValue(existingEnv, "HOT_UPDATER_CLOUDFLARE_D1_DATABASE_ID");
|
|
11126
|
+
const hasExistingD1Database = availableD1List.some((d1) => d1.uuid === existingD1DatabaseId);
|
|
11127
|
+
let selectedD1DatabaseId;
|
|
11128
|
+
if (existingD1DatabaseId && hasExistingD1Database) {
|
|
11129
|
+
selectedD1DatabaseId = existingD1DatabaseId;
|
|
11130
|
+
p.log.info("Using existing Cloudflare D1 database ID.");
|
|
11131
|
+
} else {
|
|
11132
|
+
if (existingD1DatabaseId) p.log.warn("Existing Cloudflare D1 database ID was not found. Select a database again.");
|
|
11133
|
+
const selectedD1 = await p.select({
|
|
11134
|
+
message: "D1 List",
|
|
11135
|
+
options: [...availableD1List.map((d1) => ({
|
|
11136
|
+
value: d1.uuid,
|
|
11137
|
+
label: `${d1.name} (${d1.uuid})`
|
|
11138
|
+
})), {
|
|
11139
|
+
value: createKey,
|
|
11140
|
+
label: "Create New D1 Database"
|
|
11141
|
+
}]
|
|
11142
|
+
});
|
|
11143
|
+
if (p.isCancel(selectedD1)) process.exit(1);
|
|
11144
|
+
selectedD1DatabaseId = selectedD1;
|
|
11145
|
+
}
|
|
11037
11146
|
if (selectedD1DatabaseId === createKey) {
|
|
11038
11147
|
const name = await p.text({ message: "Enter the name of the new D1 Database" });
|
|
11039
11148
|
if (p.isCancel(name)) process.exit(1);
|
|
@@ -11052,17 +11161,22 @@ const runInit = async ({ build }) => {
|
|
|
11052
11161
|
const d1DatabaseName = availableD1List.find((d1) => d1.uuid === selectedD1DatabaseId)?.name;
|
|
11053
11162
|
if (!d1DatabaseName) throw new Error("Failed to get D1 Database name");
|
|
11054
11163
|
const subdomains = await cf.workers.subdomains.get({ account_id: accountId });
|
|
11164
|
+
const existingWorkerName = getEnvValue(existingEnv, "HOT_UPDATER_CLOUDFLARE_WORKER_NAME");
|
|
11055
11165
|
const workerName = await deployWorker(auth.oauth_token, accountId, {
|
|
11056
11166
|
d1DatabaseId: selectedD1DatabaseId,
|
|
11057
11167
|
d1DatabaseName,
|
|
11058
|
-
r2BucketName: selectedBucketName
|
|
11168
|
+
r2BucketName: selectedBucketName,
|
|
11169
|
+
workerName: existingWorkerName
|
|
11059
11170
|
});
|
|
11060
11171
|
const configWriteResult = await writeHotUpdaterConfig(getConfigScaffold(build));
|
|
11061
11172
|
await makeEnv({
|
|
11062
11173
|
HOT_UPDATER_CLOUDFLARE_API_TOKEN: apiToken,
|
|
11063
11174
|
HOT_UPDATER_CLOUDFLARE_ACCOUNT_ID: accountId,
|
|
11064
11175
|
HOT_UPDATER_CLOUDFLARE_R2_BUCKET_NAME: selectedBucketName,
|
|
11065
|
-
|
|
11176
|
+
HOT_UPDATER_CLOUDFLARE_R2_ACCESS_KEY_ID: r2AccessKeyId,
|
|
11177
|
+
HOT_UPDATER_CLOUDFLARE_R2_SECRET_ACCESS_KEY: r2SecretAccessKey,
|
|
11178
|
+
HOT_UPDATER_CLOUDFLARE_D1_DATABASE_ID: selectedD1DatabaseId,
|
|
11179
|
+
HOT_UPDATER_CLOUDFLARE_WORKER_NAME: workerName
|
|
11066
11180
|
});
|
|
11067
11181
|
p.log.success("Generated '.env.hotupdater' file with Cloudflare settings.");
|
|
11068
11182
|
if (configWriteResult.status === "created") p.log.success("Generated 'hot-updater.config.ts' file with Cloudflare settings.");
|