@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.
@@ -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 || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
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
- cloudflareApiToken: process.env.HOT_UPDATER_CLOUDFLARE_API_TOKEN!,
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 deployWorker = async (oauth_token, accountId, { d1DatabaseId, d1DatabaseName, r2BucketName }) => {
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
- const workerName = await p.text({
10899
- message: "Enter the name of the worker",
10900
- defaultValue: "hot-updater",
10901
- placeholder: "hot-updater"
10902
- });
10903
- if (p.isCancel(workerName)) process.exit(1);
10904
- await wrangler("deploy", "--name", workerName);
10905
- return workerName;
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
- const accounts = [];
10932
- try {
10933
- await p.tasks([{
10934
- title: "Checking Account List...",
10935
- task: async () => {
10936
- accounts.push(...(await cf.accounts.list()).result.map((account) => ({
10937
- id: account.id,
10938
- name: account.name
10939
- })));
10940
- }
10941
- }]);
10942
- } catch (e) {
10943
- if (e instanceof Error) p.log.error(e.message);
10944
- throw e;
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
- const accountId = await p.select({
10947
- message: "Account List",
10948
- options: accounts.map((account) => ({
10949
- value: account.id,
10950
- label: `${account.name} (${account.id})`
10951
- }))
10952
- });
10953
- if (p.isCancel(accountId)) process.exit(1);
10954
- p.log.step(`Please visit this link to create an API Token: ${link(`https://dash.cloudflare.com/${accountId}/api-tokens`)}`);
10955
- p.log.step("You need edit permissions for both D1 and R2");
10956
- const apiToken = await p.password({ message: "Enter the API Token" });
10957
- if (!apiToken) p.log.warn("Skipping API Token. You can set it later in .env HOT_UPDATER_CLOUDFLARE_API_TOKEN file.");
10958
- if (p.isCancel(apiToken)) process.exit(1);
10959
- const availableBuckets = [];
10960
- try {
10961
- await p.tasks([{
10962
- title: "Checking R2 Buckets...",
10963
- task: async () => {
10964
- const buckets = (await cf.r2.buckets.list({ account_id: accountId })).buckets ?? [];
10965
- availableBuckets.push(...buckets.filter((bucket) => bucket.name).map((bucket) => ({ name: bucket.name })));
10966
- }
10967
- }]);
10968
- } catch (e) {
10969
- if (e instanceof Error) p.log.error(e.message);
10970
- throw e;
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
- let selectedBucketName = await p.select({
10973
- message: "R2 List",
10974
- options: [...availableBuckets.map((bucket) => ({
10975
- value: bucket.name,
10976
- label: bucket.name
10977
- })), {
10978
- value: createKey,
10979
- label: "Create New R2 Bucket"
10980
- }]
10981
- });
10982
- if (p.isCancel(selectedBucketName)) process.exit(1);
10983
- if (selectedBucketName === createKey) {
10984
- const name = await p.text({ message: "Enter the name of the new R2 Bucket" });
10985
- if (p.isCancel(name)) process.exit(1);
10986
- const newR2 = await cf.r2.buckets.create({
10987
- account_id: accountId,
10988
- name
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
- if (!newR2.name) throw new Error("Failed to create new R2 Bucket");
10991
- selectedBucketName = newR2.name;
11090
+ r2AccessKeyId = credentials.accessKeyId;
11091
+ r2SecretAccessKey = credentials.secretAccessKey;
10992
11092
  }
10993
- p.log.info(`Selected R2: ${selectedBucketName}`);
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
- let selectedD1DatabaseId = await p.select({
11027
- message: "D1 List",
11028
- options: [...availableD1List.map((d1) => ({
11029
- value: d1.uuid,
11030
- label: `${d1.name} (${d1.uuid})`
11031
- })), {
11032
- value: createKey,
11033
- label: "Create New D1 Database"
11034
- }]
11035
- });
11036
- if (p.isCancel(selectedD1DatabaseId)) process.exit(1);
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
- HOT_UPDATER_CLOUDFLARE_D1_DATABASE_ID: selectedD1DatabaseId
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.");