@et_dev/forge-publisher-r2 7.10.2 → 7.10.3

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/README.md CHANGED
@@ -17,7 +17,8 @@ module.exports = {
17
17
  config: {
18
18
  bucket: 'my-bucket',
19
19
  accountId: 'your-cloudflare-account-id',
20
- apiToken: 'your-cloudflare-api-token'
20
+ accessKeyId: 'your-r2-access-key-id',
21
+ secretAccessKey: 'your-r2-secret-access-key'
21
22
  }
22
23
  }
23
24
  ]
@@ -28,12 +29,13 @@ If you run publish twice with the same version on the same platform, it is possi
28
29
 
29
30
  ### Authentication
30
31
 
31
- This publisher uses the wrangler npm package to interact with Cloudflare R2. You need to provide your Cloudflare API credentials in the configuration:
32
+ This publisher uses Cloudflare R2's S3-compatible API. You need to provide R2 API credentials:
32
33
 
33
34
  ```javascript
34
35
  config: {
35
36
  accountId: 'your-cloudflare-account-id',
36
- apiToken: 'your-cloudflare-api-token',
37
+ accessKeyId: 'your-r2-access-key-id',
38
+ secretAccessKey: 'your-r2-secret-access-key',
37
39
  bucket: 'my-bucket',
38
40
  // ...
39
41
  }
@@ -41,22 +43,26 @@ config: {
41
43
 
42
44
  #### Getting Your Credentials
43
45
 
44
- - **Account ID**: Found in the Cloudflare dashboard URL or on the R2 overview page
45
- - **API Token**: Create a token with R2 read and write permissions from the [Cloudflare API Tokens page](https://dash.cloudflare.com/profile/api-tokens). Make sure the token has "Account.R2 Storage" permissions.
46
+ 1. **Account ID**: Found in the Cloudflare dashboard URL or on the R2 overview page
47
+ 2. **API Tokens**: Create R2 API tokens from the R2 dashboard:
48
+ - Go to R2 → Manage R2 API Tokens
49
+ - Click "Create API token"
50
+ - Select permissions (Read & Write)
51
+ - Copy the `Access Key ID` and `Secret Access Key`
46
52
 
47
53
  ### Public Access
48
54
 
49
- To make your artifacts publicly accessible, configure a custom domain for your R2 bucket in the Cloudflare dashboard. Public access is managed entirely through Cloudflare's R2 bucket settings, not through this publisher.
55
+ To make your artifacts publicly accessible, configure a custom domain for your R2 bucket in the Cloudflare dashboard under R2 Settings Public Access.
50
56
 
51
57
  ### Custom Key Resolver
52
58
 
53
- You can provide a custom function to determine the key (path) for each artifact:
59
+ You can customize the S3 key for uploaded artifacts by providing a `keyResolver` function:
54
60
 
55
61
  ```javascript
56
62
  config: {
57
63
  bucket: 'my-bucket',
58
64
  keyResolver: (fileName, platform, arch) => {
59
- return `releases/v1.0.0/${platform}/${fileName}`;
65
+ return `releases/${platform}/${arch}/${fileName}`;
60
66
  }
61
67
  }
62
68
  ```
package/dist/Config.d.ts CHANGED
@@ -6,11 +6,17 @@ export interface PublisherR2Config {
6
6
  */
7
7
  accountId: string;
8
8
  /**
9
- * The Cloudflare API Token
9
+ * The R2 Access Key ID
10
10
  *
11
- * Required. Create a token with R2 read and write permissions.
11
+ * Required. Create an API token from the R2 dashboard.
12
12
  */
13
- apiToken: string;
13
+ accessKeyId: string;
14
+ /**
15
+ * The R2 Secret Access Key
16
+ *
17
+ * Required. Provided when creating an R2 API token.
18
+ */
19
+ secretAccessKey: string;
14
20
  /**
15
21
  * The name of the R2 bucket to upload artifacts to
16
22
  */
@@ -30,8 +36,14 @@ export interface PublisherR2Config {
30
36
  /**
31
37
  * Custom R2 endpoint (optional)
32
38
  *
33
- * Default: Auto-generated from accountId
39
+ * Default: Auto-generated from accountId as https://{accountId}.r2.cloudflarestorage.com
34
40
  */
35
41
  endpoint?: string;
42
+ /**
43
+ * The region to send service requests to.
44
+ *
45
+ * Default: 'auto'
46
+ */
47
+ region?: string;
36
48
  }
37
49
  //# sourceMappingURL=Config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Config.d.ts","sourceRoot":"","sources":["../src/Config.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAC3E;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB"}
1
+ {"version":3,"file":"Config.d.ts","sourceRoot":"","sources":["../src/Config.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAC3E;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
@@ -1,11 +1,16 @@
1
1
  import { PublisherOptions, PublisherStatic } from '@electron-forge/publisher-static';
2
2
  import { PublisherR2Config } from './Config';
3
+ type R2Artifact = {
4
+ path: string;
5
+ keyPrefix: string;
6
+ platform: string;
7
+ arch: string;
8
+ };
3
9
  export default class PublisherR2 extends PublisherStatic<PublisherR2Config> {
4
10
  name: string;
5
11
  private r2KeySafe;
6
- private createWranglerExecutor;
7
12
  publish({ makeResults, setStatusLine, }: PublisherOptions): Promise<void>;
8
- private uploadFile;
13
+ protected keyForArtifact(artifact: R2Artifact): string;
9
14
  }
10
15
  export { PublisherR2, PublisherR2Config };
11
16
  //# sourceMappingURL=PublisherR2.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"PublisherR2.d.ts","sourceRoot":"","sources":["../src/PublisherR2.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,gBAAgB,EAChB,eAAe,EAChB,MAAM,kCAAkC,CAAC;AAK1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAW7C,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,eAAe,CAAC,iBAAiB,CAAC;IACzE,IAAI,SAAQ;IAEZ,OAAO,CAAC,SAAS,CAEf;IAEF,OAAO,CAAC,sBAAsB;IAgBxB,OAAO,CAAC,EACZ,WAAW,EACX,aAAa,GACd,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;YAyDrB,UAAU;CAgDzB;AAED,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,CAAC"}
1
+ {"version":3,"file":"PublisherR2.d.ts","sourceRoot":"","sources":["../src/PublisherR2.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,gBAAgB,EAChB,eAAe,EAChB,MAAM,kCAAkC,CAAC;AAI1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAI7C,KAAK,UAAU,GAAG;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,eAAe,CAAC,iBAAiB,CAAC;IACzE,IAAI,SAAQ;IAEZ,OAAO,CAAC,SAAS,CAEf;IAEI,OAAO,CAAC,EACZ,WAAW,EACX,aAAa,GACd,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAqGnC,SAAS,CAAC,cAAc,CAAC,QAAQ,EAAE,UAAU,GAAG,MAAM;CAavD;AAED,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,CAAC"}
@@ -4,10 +4,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.PublisherR2 = void 0;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
7
8
  const node_path_1 = __importDefault(require("node:path"));
9
+ const client_s3_1 = require("@aws-sdk/client-s3");
10
+ const lib_storage_1 = require("@aws-sdk/lib-storage");
8
11
  const publisher_static_1 = require("@electron-forge/publisher-static");
9
12
  const debug_1 = __importDefault(require("debug"));
10
- const execa_1 = require("execa");
11
13
  const mime_types_1 = __importDefault(require("mime-types"));
12
14
  const d = (0, debug_1.default)('electron-forge:publish:r2');
13
15
  class PublisherR2 extends publisher_static_1.PublisherStatic {
@@ -18,19 +20,6 @@ class PublisherR2 extends publisher_static_1.PublisherStatic {
18
20
  return key.replace(/@/g, '_').replace(/\//g, '_');
19
21
  };
20
22
  }
21
- createWranglerExecutor(accountId, apiToken) {
22
- return async (...args) => {
23
- const env = {
24
- CLOUDFLARE_ACCOUNT_ID: accountId,
25
- CLOUDFLARE_API_TOKEN: apiToken,
26
- };
27
- d(`executing: npx wrangler ${args.join(' ')}`);
28
- return (0, execa_1.execa)('npx', ['wrangler', ...args], {
29
- env,
30
- stdio: 'pipe',
31
- });
32
- };
33
- }
34
23
  async publish({ makeResults, setStatusLine, }) {
35
24
  const artifacts = [];
36
25
  if (!this.config.bucket) {
@@ -39,10 +28,12 @@ class PublisherR2 extends publisher_static_1.PublisherStatic {
39
28
  if (!this.config.accountId) {
40
29
  throw new Error('In order to publish to R2, you must set the "accountId" property in your Forge publisher config.');
41
30
  }
42
- if (!this.config.apiToken) {
43
- throw new Error('In order to publish to R2, you must set the "apiToken" property in your Forge publisher config.');
31
+ if (!this.config.accessKeyId) {
32
+ throw new Error('In order to publish to R2, you must set the "accessKeyId" property in your Forge publisher config.');
33
+ }
34
+ if (!this.config.secretAccessKey) {
35
+ throw new Error('In order to publish to R2, you must set the "secretAccessKey" property in your Forge publisher config.');
44
36
  }
45
- const { accountId, apiToken } = this.config;
46
37
  for (const makeResult of makeResults) {
47
38
  artifacts.push(...makeResult.artifacts.map((artifact) => ({
48
39
  path: artifact,
@@ -51,39 +42,52 @@ class PublisherR2 extends publisher_static_1.PublisherStatic {
51
42
  arch: makeResult.arch,
52
43
  })));
53
44
  }
54
- d('uploading to R2 bucket:', this.config.bucket);
45
+ // Create R2 S3-compatible client
46
+ const endpoint = this.config.endpoint ||
47
+ `https://${this.config.accountId}.r2.cloudflarestorage.com`;
48
+ const s3Client = new client_s3_1.S3Client({
49
+ region: this.config.region || 'auto',
50
+ endpoint,
51
+ credentials: {
52
+ accessKeyId: this.config.accessKeyId,
53
+ secretAccessKey: this.config.secretAccessKey,
54
+ },
55
+ });
56
+ d('creating r2 client with endpoint:', endpoint);
55
57
  let uploaded = 0;
56
58
  const updateStatusLine = () => setStatusLine(`Uploading distributable (${uploaded}/${artifacts.length})`);
57
59
  updateStatusLine();
58
- const wrangler = this.createWranglerExecutor(accountId, apiToken);
59
60
  await Promise.all(artifacts.map(async (artifact) => {
60
61
  d('uploading:', artifact.path);
61
- await this.uploadFile(artifact, wrangler);
62
+ const key = this.keyForArtifact(artifact);
63
+ const contentType = mime_types_1.default.lookup(artifact.path) || 'application/octet-stream';
64
+ const params = {
65
+ Body: node_fs_1.default.createReadStream(artifact.path),
66
+ Bucket: this.config.bucket,
67
+ Key: key,
68
+ ContentType: contentType,
69
+ };
70
+ const upload = new lib_storage_1.Upload({
71
+ client: s3Client,
72
+ params,
73
+ });
74
+ upload.on('httpUploadProgress', (progress) => {
75
+ d(`upload progress for ${node_path_1.default.basename(artifact.path)}:`, progress.loaded, '/', progress.total);
76
+ });
77
+ await upload.done();
78
+ d(`successfully uploaded: ${node_path_1.default.basename(artifact.path)}`);
62
79
  uploaded += 1;
63
80
  updateStatusLine();
64
81
  }));
65
82
  }
66
- async uploadFile(artifact, wrangler) {
67
- const key = this.keyForArtifact(artifact);
68
- const contentType = mime_types_1.default.lookup(artifact.path) || 'application/octet-stream';
69
- d(`uploading ${node_path_1.default.basename(artifact.path)} with content-type: ${contentType}`);
70
- try {
71
- const { stderr, exitCode } = await wrangler('r2', 'object', 'put', `${this.config.bucket}/${key}`, '--file', artifact.path, '--content-type', contentType, '--remote');
72
- if (exitCode !== 0 && stderr) {
73
- throw new Error(stderr);
74
- }
75
- d(`successfully uploaded: ${node_path_1.default.basename(artifact.path)}`);
76
- }
77
- catch (error) {
78
- if (error instanceof execa_1.ExecaError) {
79
- const errorMessage = error.stderr || error.stdout || error.message;
80
- d(`upload failed for ${node_path_1.default.basename(artifact.path)}: ${errorMessage}`);
81
- throw new Error(`Failed to upload ${node_path_1.default.basename(artifact.path)} to R2: ${errorMessage}`);
82
- }
83
- throw new Error(`Failed to upload ${node_path_1.default.basename(artifact.path)} to R2: ${error instanceof Error ? error.message : String(error)}`);
83
+ keyForArtifact(artifact) {
84
+ const { keyPrefix, platform, arch, path: artifactPath } = artifact;
85
+ if (this.config.keyResolver) {
86
+ return this.config.keyResolver(node_path_1.default.basename(artifactPath), platform, arch);
84
87
  }
88
+ return `${keyPrefix}/${platform}/${arch}/${node_path_1.default.basename(artifactPath)}`;
85
89
  }
86
90
  }
87
91
  exports.default = PublisherR2;
88
92
  exports.PublisherR2 = PublisherR2;
89
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUHVibGlzaGVyUjIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvUHVibGlzaGVyUjIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQUEsMERBQTZCO0FBRTdCLHVFQUcwQztBQUMxQyxrREFBMEI7QUFDMUIsaUNBQWtEO0FBQ2xELDREQUE4QjtBQUk5QixNQUFNLENBQUMsR0FBRyxJQUFBLGVBQUssRUFBQywyQkFBMkIsQ0FBQyxDQUFDO0FBUzdDLE1BQXFCLFdBQVksU0FBUSxrQ0FBa0M7SUFBM0U7O1FBQ0UsU0FBSSxHQUFHLElBQUksQ0FBQztRQUVKLGNBQVMsR0FBRyxDQUFDLEdBQVcsRUFBRSxFQUFFO1lBQ2xDLE9BQU8sR0FBRyxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsR0FBRyxDQUFDLENBQUMsT0FBTyxDQUFDLEtBQUssRUFBRSxHQUFHLENBQUMsQ0FBQztRQUNwRCxDQUFDLENBQUM7SUE4SEosQ0FBQztJQTVIUyxzQkFBc0IsQ0FBQyxTQUFpQixFQUFFLFFBQWdCO1FBQ2hFLE9BQU8sS0FBSyxFQUFFLEdBQUcsSUFBYyxFQUFFLEVBQUU7WUFDakMsTUFBTSxHQUFHLEdBQUc7Z0JBQ1YscUJBQXFCLEVBQUUsU0FBUztnQkFDaEMsb0JBQW9CLEVBQUUsUUFBUTthQUMvQixDQUFDO1lBRUYsQ0FBQyxDQUFDLDJCQUEyQixJQUFJLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsQ0FBQztZQUUvQyxPQUFPLElBQUEsYUFBSyxFQUFDLEtBQUssRUFBRSxDQUFDLFVBQVUsRUFBRSxHQUFHLElBQUksQ0FBQyxFQUFFO2dCQUN6QyxHQUFHO2dCQUNILEtBQUssRUFBRSxNQUFNO2FBQ2QsQ0FBQyxDQUFDO1FBQ0wsQ0FBQyxDQUFDO0lBQ0osQ0FBQztJQUVELEtBQUssQ0FBQyxPQUFPLENBQUMsRUFDWixXQUFXLEVBQ1gsYUFBYSxHQUNJO1FBQ2pCLE1BQU0sU0FBUyxHQUFpQixFQUFFLENBQUM7UUFF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
93
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUHVibGlzaGVyUjIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvUHVibGlzaGVyUjIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQUEsc0RBQXlCO0FBQ3pCLDBEQUE2QjtBQUU3QixrREFBcUU7QUFDckUsc0RBQThDO0FBQzlDLHVFQUcwQztBQUMxQyxrREFBMEI7QUFDMUIsNERBQThCO0FBSTlCLE1BQU0sQ0FBQyxHQUFHLElBQUEsZUFBSyxFQUFDLDJCQUEyQixDQUFDLENBQUM7QUFTN0MsTUFBcUIsV0FBWSxTQUFRLGtDQUFrQztJQUEzRTs7UUFDRSxTQUFJLEdBQUcsSUFBSSxDQUFDO1FBRUosY0FBUyxHQUFHLENBQUMsR0FBVyxFQUFFLEVBQUU7WUFDbEMsT0FBTyxHQUFHLENBQUMsT0FBTyxDQUFDLElBQUksRUFBRSxHQUFHLENBQUMsQ0FBQyxPQUFPLENBQUMsS0FBSyxFQUFFLEdBQUcsQ0FBQyxDQUFDO1FBQ3BELENBQUMsQ0FBQztJQXVISixDQUFDO0lBckhDLEtBQUssQ0FBQyxPQUFPLENBQUMsRUFDWixXQUFXLEVBQ1gsYUFBYSxHQUNJO1FBQ2pCLE1BQU0sU0FBUyxHQUFpQixFQUFFLENBQUM7UUFFbkMsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDeEIsTUFBTSxJQUFJLEtBQUssQ0FDYiwwSEFBMEgsQ0FDM0gsQ0FBQztRQUNKLENBQUM7UUFFRCxJQUFJLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxTQUFTLEVBQUUsQ0FBQztZQUMzQixNQUFNLElBQUksS0FBSyxDQUNiLGtHQUFrRyxDQUNuRyxDQUFDO1FBQ0osQ0FBQztRQUVELElBQUksQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLFdBQVcsRUFBRSxDQUFDO1lBQzdCLE1BQU0sSUFBSSxLQUFLLENBQ2Isb0dBQW9HLENBQ3JHLENBQUM7UUFDSixDQUFDO1FBRUQsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsZUFBZSxFQUFFLENBQUM7WUFDakMsTUFBTSxJQUFJLEtBQUssQ0FDYix3R0FBd0csQ0FDekcsQ0FBQztRQUNKLENBQUM7UUFFRCxLQUFLLE1BQU0sVUFBVSxJQUFJLFdBQVcsRUFBRSxDQUFDO1lBQ3JDLFNBQVMsQ0FBQyxJQUFJLENBQ1osR0FBRyxVQUFVLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxDQUFDLFFBQVEsRUFBRSxFQUFFLENBQUMsQ0FBQztnQkFDekMsSUFBSSxFQUFFLFFBQVE7Z0JBQ2QsU0FBUyxFQUNQLElBQUksQ0FBQyxNQUFNLENBQUMsTUFBTSxJQUFJLElBQUksQ0FBQyxTQUFTLENBQUMsVUFBVSxDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUM7Z0JBQ25FLFFBQVEsRUFBRSxVQUFVLENBQUMsUUFBUTtnQkFDN0IsSUFBSSxFQUFFLFVBQVUsQ0FBQyxJQUFJO2FBQ3RCLENBQUMsQ0FBQyxDQUNKLENBQUM7UUFDSixDQUFDO1FBRUQsaUNBQWlDO1FBQ2pDLE1BQU0sUUFBUSxHQUNaLElBQUksQ0FBQyxNQUFNLENBQUMsUUFBUTtZQUNwQixXQUFXLElBQUksQ0FBQyxNQUFNLENBQUMsU0FBUywyQkFBMkIsQ0FBQztRQUU5RCxNQUFNLFFBQVEsR0FBRyxJQUFJLG9CQUFRLENBQUM7WUFDNUIsTUFBTSxFQUFFLElBQUksQ0FBQyxNQUFNLENBQUMsTUFBTSxJQUFJLE1BQU07WUFDcEMsUUFBUTtZQUNSLFdBQVcsRUFBRTtnQkFDWCxXQUFXLEVBQUUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxXQUFXO2dCQUNwQyxlQUFlLEVBQUUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxlQUFlO2FBQzdDO1NBQ0YsQ0FBQyxDQUFDO1FBRUgsQ0FBQyxDQUFDLG1DQUFtQyxFQUFFLFFBQVEsQ0FBQyxDQUFDO1FBRWpELElBQUksUUFBUSxHQUFHLENBQUMsQ0FBQztRQUNqQixNQUFNLGdCQUFnQixHQUFHLEdBQUcsRUFBRSxDQUM1QixhQUFhLENBQ1gsNEJBQTRCLFFBQVEsSUFBSSxTQUFTLENBQUMsTUFBTSxHQUFHLENBQzVELENBQUM7UUFFSixnQkFBZ0IsRUFBRSxDQUFDO1FBRW5CLE1BQU0sT0FBTyxDQUFDLEdBQUcsQ0FDZixTQUFTLENBQUMsR0FBRyxDQUFDLEtBQUssRUFBRSxRQUFRLEVBQUUsRUFBRTtZQUMvQixDQUFDLENBQUMsWUFBWSxFQUFFLFFBQVEsQ0FBQyxJQUFJLENBQUMsQ0FBQztZQUUvQixNQUFNLEdBQUcsR0FBRyxJQUFJLENBQUMsY0FBYyxDQUFDLFFBQVEsQ0FBQyxDQUFDO1lBQzFDLE1BQU0sV0FBVyxHQUNmLG9CQUFJLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsSUFBSSwwQkFBMEIsQ0FBQztZQUUzRCxNQUFNLE1BQU0sR0FBMEI7Z0JBQ3BDLElBQUksRUFBRSxpQkFBRSxDQUFDLGdCQUFnQixDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUM7Z0JBQ3hDLE1BQU0sRUFBRSxJQUFJLENBQUMsTUFBTSxDQUFDLE1BQU07Z0JBQzFCLEdBQUcsRUFBRSxHQUFHO2dCQUNSLFdBQVcsRUFBRSxXQUFXO2FBQ3pCLENBQUM7WUFFRixNQUFNLE1BQU0sR0FBRyxJQUFJLG9CQUFNLENBQUM7Z0JBQ3hCLE1BQU0sRUFBRSxRQUFRO2dCQUNoQixNQUFNO2FBQ1AsQ0FBQyxDQUFDO1lBRUgsTUFBTSxDQUFDLEVBQUUsQ0FBQyxvQkFBb0IsRUFBRSxDQUFDLFFBQVEsRUFBRSxFQUFFO2dCQUMzQyxDQUFDLENBQ0MsdUJBQXVCLG1CQUFJLENBQUMsUUFBUSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsR0FBRyxFQUN0RCxRQUFRLENBQUMsTUFBTSxFQUNmLEdBQUcsRUFDSCxRQUFRLENBQUMsS0FBSyxDQUNmLENBQUM7WUFDSixDQUFDLENBQUMsQ0FBQztZQUVILE1BQU0sTUFBTSxDQUFDLElBQUksRUFBRSxDQUFDO1lBRXBCLENBQUMsQ0FBQywwQkFBMEIsbUJBQUksQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQztZQUM1RCxRQUFRLElBQUksQ0FBQyxDQUFDO1lBQ2QsZ0JBQWdCLEVBQUUsQ0FBQztRQUNyQixDQUFDLENBQUMsQ0FDSCxDQUFDO0lBQ0osQ0FBQztJQUVTLGNBQWMsQ0FBQyxRQUFvQjtRQUMzQyxNQUFNLEVBQUUsU0FBUyxFQUFFLFFBQVEsRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLFlBQVksRUFBRSxHQUFHLFFBQVEsQ0FBQztRQUVuRSxJQUFJLElBQUksQ0FBQyxNQUFNLENBQUMsV0FBVyxFQUFFLENBQUM7WUFDNUIsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFDLFdBQVcsQ0FDNUIsbUJBQUksQ0FBQyxRQUFRLENBQUMsWUFBWSxDQUFDLEVBQzNCLFFBQVEsRUFDUixJQUFJLENBQ0wsQ0FBQztRQUNKLENBQUM7UUFFRCxPQUFPLEdBQUcsU0FBUyxJQUFJLFFBQVEsSUFBSSxJQUFJLElBQUksbUJBQUksQ0FBQyxRQUFRLENBQUMsWUFBWSxDQUFDLEVBQUUsQ0FBQztJQUMzRSxDQUFDO0NBQ0Y7QUE1SEQsOEJBNEhDO0FBRVEsa0NBQVcifQ==
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@et_dev/forge-publisher-r2",
3
- "version": "7.10.2",
3
+ "version": "7.10.3",
4
4
  "description": "Cloudflare R2 publisher for Electron Forge",
5
5
  "repository": "https://github.com/electron/forge",
6
6
  "author": "Electron Forge",
@@ -15,12 +15,12 @@
15
15
  "prepublishOnly": "npm run build"
16
16
  },
17
17
  "dependencies": {
18
+ "@aws-sdk/client-s3": "^3.654.0",
19
+ "@aws-sdk/lib-storage": "^3.654.0",
18
20
  "@electron-forge/publisher-static": "7.10.2",
19
21
  "@electron-forge/shared-types": "7.10.2",
20
22
  "debug": "^4.3.1",
21
- "execa": "^9.0.0",
22
- "mime-types": "^2.1.25",
23
- "wrangler": "^4.54.0"
23
+ "mime-types": "^2.1.25"
24
24
  },
25
25
  "publishConfig": {
26
26
  "access": "public"
package/src/Config.ts CHANGED
@@ -6,11 +6,17 @@ export interface PublisherR2Config {
6
6
  */
7
7
  accountId: string;
8
8
  /**
9
- * The Cloudflare API Token
9
+ * The R2 Access Key ID
10
10
  *
11
- * Required. Create a token with R2 read and write permissions.
11
+ * Required. Create an API token from the R2 dashboard.
12
12
  */
13
- apiToken: string;
13
+ accessKeyId: string;
14
+ /**
15
+ * The R2 Secret Access Key
16
+ *
17
+ * Required. Provided when creating an R2 API token.
18
+ */
19
+ secretAccessKey: string;
14
20
  /**
15
21
  * The name of the R2 bucket to upload artifacts to
16
22
  */
@@ -30,7 +36,13 @@ export interface PublisherR2Config {
30
36
  /**
31
37
  * Custom R2 endpoint (optional)
32
38
  *
33
- * Default: Auto-generated from accountId
39
+ * Default: Auto-generated from accountId as https://{accountId}.r2.cloudflarestorage.com
34
40
  */
35
41
  endpoint?: string;
42
+ /**
43
+ * The region to send service requests to.
44
+ *
45
+ * Default: 'auto'
46
+ */
47
+ region?: string;
36
48
  }
@@ -1,11 +1,13 @@
1
+ import fs from 'node:fs';
1
2
  import path from 'node:path';
2
3
 
4
+ import { PutObjectCommandInput, S3Client } from '@aws-sdk/client-s3';
5
+ import { Upload } from '@aws-sdk/lib-storage';
3
6
  import {
4
7
  PublisherOptions,
5
8
  PublisherStatic,
6
9
  } from '@electron-forge/publisher-static';
7
10
  import debug from 'debug';
8
- import { execa, ExecaError, Result } from 'execa';
9
11
  import mime from 'mime-types';
10
12
 
11
13
  import { PublisherR2Config } from './Config';
@@ -26,22 +28,6 @@ export default class PublisherR2 extends PublisherStatic<PublisherR2Config> {
26
28
  return key.replace(/@/g, '_').replace(/\//g, '_');
27
29
  };
28
30
 
29
- private createWranglerExecutor(accountId: string, apiToken: string) {
30
- return async (...args: string[]) => {
31
- const env = {
32
- CLOUDFLARE_ACCOUNT_ID: accountId,
33
- CLOUDFLARE_API_TOKEN: apiToken,
34
- };
35
-
36
- d(`executing: npx wrangler ${args.join(' ')}`);
37
-
38
- return execa('npx', ['wrangler', ...args], {
39
- env,
40
- stdio: 'pipe',
41
- });
42
- };
43
- }
44
-
45
31
  async publish({
46
32
  makeResults,
47
33
  setStatusLine,
@@ -60,13 +46,17 @@ export default class PublisherR2 extends PublisherStatic<PublisherR2Config> {
60
46
  );
61
47
  }
62
48
 
63
- if (!this.config.apiToken) {
49
+ if (!this.config.accessKeyId) {
64
50
  throw new Error(
65
- 'In order to publish to R2, you must set the "apiToken" property in your Forge publisher config.',
51
+ 'In order to publish to R2, you must set the "accessKeyId" property in your Forge publisher config.',
66
52
  );
67
53
  }
68
54
 
69
- const { accountId, apiToken } = this.config;
55
+ if (!this.config.secretAccessKey) {
56
+ throw new Error(
57
+ 'In order to publish to R2, you must set the "secretAccessKey" property in your Forge publisher config.',
58
+ );
59
+ }
70
60
 
71
61
  for (const makeResult of makeResults) {
72
62
  artifacts.push(
@@ -80,7 +70,21 @@ export default class PublisherR2 extends PublisherStatic<PublisherR2Config> {
80
70
  );
81
71
  }
82
72
 
83
- d('uploading to R2 bucket:', this.config.bucket);
73
+ // Create R2 S3-compatible client
74
+ const endpoint =
75
+ this.config.endpoint ||
76
+ `https://${this.config.accountId}.r2.cloudflarestorage.com`;
77
+
78
+ const s3Client = new S3Client({
79
+ region: this.config.region || 'auto',
80
+ endpoint,
81
+ credentials: {
82
+ accessKeyId: this.config.accessKeyId,
83
+ secretAccessKey: this.config.secretAccessKey,
84
+ },
85
+ });
86
+
87
+ d('creating r2 client with endpoint:', endpoint);
84
88
 
85
89
  let uploaded = 0;
86
90
  const updateStatusLine = () =>
@@ -90,65 +94,56 @@ export default class PublisherR2 extends PublisherStatic<PublisherR2Config> {
90
94
 
91
95
  updateStatusLine();
92
96
 
93
- const wrangler = this.createWranglerExecutor(accountId, apiToken);
94
-
95
97
  await Promise.all(
96
98
  artifacts.map(async (artifact) => {
97
99
  d('uploading:', artifact.path);
98
- await this.uploadFile(artifact, wrangler);
100
+
101
+ const key = this.keyForArtifact(artifact);
102
+ const contentType =
103
+ mime.lookup(artifact.path) || 'application/octet-stream';
104
+
105
+ const params: PutObjectCommandInput = {
106
+ Body: fs.createReadStream(artifact.path),
107
+ Bucket: this.config.bucket,
108
+ Key: key,
109
+ ContentType: contentType,
110
+ };
111
+
112
+ const upload = new Upload({
113
+ client: s3Client,
114
+ params,
115
+ });
116
+
117
+ upload.on('httpUploadProgress', (progress) => {
118
+ d(
119
+ `upload progress for ${path.basename(artifact.path)}:`,
120
+ progress.loaded,
121
+ '/',
122
+ progress.total,
123
+ );
124
+ });
125
+
126
+ await upload.done();
127
+
128
+ d(`successfully uploaded: ${path.basename(artifact.path)}`);
99
129
  uploaded += 1;
100
130
  updateStatusLine();
101
131
  }),
102
132
  );
103
133
  }
104
134
 
105
- private async uploadFile(
106
- artifact: R2Artifact,
107
- wrangler: (...args: string[]) => Promise<
108
- Result<{
109
- env: { [key: string]: string };
110
- stdio: 'pipe';
111
- }>
112
- >,
113
- ): Promise<void> {
114
- const key = this.keyForArtifact(artifact);
115
- const contentType =
116
- mime.lookup(artifact.path) || 'application/octet-stream';
117
-
118
- d(
119
- `uploading ${path.basename(artifact.path)} with content-type: ${contentType}`,
120
- );
121
-
122
- try {
123
- const { stderr, exitCode } = await wrangler(
124
- 'r2',
125
- 'object',
126
- 'put',
127
- `${this.config.bucket}/${key}`,
128
- '--file',
129
- artifact.path,
130
- '--content-type',
131
- contentType,
132
- '--remote',
133
- );
135
+ protected keyForArtifact(artifact: R2Artifact): string {
136
+ const { keyPrefix, platform, arch, path: artifactPath } = artifact;
134
137
 
135
- if (exitCode !== 0 && stderr) {
136
- throw new Error(stderr);
137
- }
138
-
139
- d(`successfully uploaded: ${path.basename(artifact.path)}`);
140
- } catch (error) {
141
- if (error instanceof ExecaError) {
142
- const errorMessage = error.stderr || error.stdout || error.message;
143
- d(`upload failed for ${path.basename(artifact.path)}: ${errorMessage}`);
144
- throw new Error(
145
- `Failed to upload ${path.basename(artifact.path)} to R2: ${errorMessage}`,
146
- );
147
- }
148
- throw new Error(
149
- `Failed to upload ${path.basename(artifact.path)} to R2: ${error instanceof Error ? error.message : String(error)}`,
138
+ if (this.config.keyResolver) {
139
+ return this.config.keyResolver(
140
+ path.basename(artifactPath),
141
+ platform,
142
+ arch,
150
143
  );
151
144
  }
145
+
146
+ return `${keyPrefix}/${platform}/${arch}/${path.basename(artifactPath)}`;
152
147
  }
153
148
  }
154
149