Skip to content

Conversation

@matos-ed
Copy link

@matos-ed matos-ed commented Jan 8, 2026

Closes #4124

  • I have read the contribution documentation for this project.
  • I agree to follow the code of conduct that this project follows, as appropriate.
  • The changes are appropriately documented (if applicable).
  • The changes have sufficient test coverage (if applicable).
  • The testsuite passes successfully on my local machine (if applicable).

Add Cloudflare R2 Publisher

This PR adds support for publishing Electron Forge artifacts to Cloudflare R2 storage buckets.

What's New

  • PublisherS3: The publisher-s3 package was updated to allow updates to cloudflare R2 bucket throught the s3 endpoint;
  • Configuration: TypeScript interface for R2 authentication (account ID, access keys) and bucket settings (name, region, folder path, public URL option)
  • Documentation: README with setup instructions and usage examples

Use Case

Provides an alternative to AWS S3 for hosting Electron app artifacts, offering potentially lower costs and better performance through Cloudflare's global network.

Configuration Example

{
  name: '@electron-forge/publisher-s3',
  config: {
    provider: 'r2', // or 's3' (default)
    
    // Common options
    bucket: 'my-bucket',
    accessKeyId: '...',
    secretAccessKey: '...',
    folder: 'releases',
    region: "auto" // auto by default to r2
    
    // R2-specific (only when provider: 'r2')
    accountId: 'your-account-id',
    
    // S3-specific (only when provider: 's3')
    public: true,
    sessionToken: '...',
    s3ForcePathStyle: false,
    releaseFileCacheControlMaxAge: 3600
  }
}

@matos-ed matos-ed requested a review from a team as a code owner January 8, 2026 21:17

d(`executing: npx wrangler ${args.join(' ')}`);

return execa('npx', ['wrangler', ...args], {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems a bit heavy-handed to add execa as a dependency just to run an npx script. Since you're adding wrangler as a dependency anyways, does that library provide an equivalent JavaScript API that we could use?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard agree. Would block this on the usage of npx, it's dangerous

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! I didnt know but, as @MarshallOfSound said we could use the S3 endpoint, preventing the addition of wrangler and execa deps
I'll work on this

Copy link
Member

@MarshallOfSound MarshallOfSound left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't R2 have an S3 compatible endpoint? We should just point the S3 publisher at that and avoid duplicating code and apparently shelling out to wrangler?

@matos-ed
Copy link
Author

matos-ed commented Jan 8, 2026

BTW I've published a package to test the implementation in my app and it's working 🫶

https://www.npmjs.com/package/@et_dev/forge-publisher-r2

Copy link
Member

@malept malept left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this have to be its own package? Can this functionality just be a set of configuration options for the existing publisher-s3 plugin, plus perhaps a guide on the docs site?

@matos-ed
Copy link
Author

matos-ed commented Jan 8, 2026

Does this have to be its own package? Can this functionality just be a set of configuration options for the existing publisher-s3 plugin, plus perhaps a guide on the docs site?

It should work well, since the API is like ~90% the same
What you think about this architecture ?

// Config would become:
{
  name: '@electron-forge/publisher-s3',
  config: {
    provider: 'r2', // or 's3' (default)
    
    // Common options
    bucket: 'my-bucket',
    accessKeyId: '...',
    secretAccessKey: '...',
    folder: 'releases',
    region: "auto" // auto by default to r2
    
    // R2-specific (only when provider: 'r2')
    accountId: 'your-account-id',
    
    // S3-specific (only when provider: 's3')
    public: true,
    sessionToken: '...',
    s3ForcePathStyle: false,
    releaseFileCacheControlMaxAge: 3600
  }
}

@matos-ed matos-ed requested a review from malept January 9, 2026 12:37
@malept malept removed their request for review January 9, 2026 17:06
@erickzhao erickzhao changed the title Create a new publisher to Cloudflare R2 bucket feat(publisher-s3): add Cloudflare R2 provider Jan 12, 2026
Copy link
Member

@erickzhao erickzhao left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @matos-ed, thanks for refactoring your PR. I'm unfamiliar with the interop between R2 and the AWS S3 SDK, so I went reading in the docs. I noticed this from the Cloudflare docs:

JavaScript or TypeScript users may continue to use the @aws-sdk/client-s3 ↗ npm package as per normal. You must pass in the R2 configuration credentials when instantiating your S3 service client.

https://developers.cloudflare.com/r2/examples/aws/aws-sdk-js-v3/

A lot of config options were marked as "S3 only", but I can't tell where the source of truth is for that. Could you help explain a bit further?

@matos-ed
Copy link
Author

A lot of config options were marked as "S3 only", but I can't tell where the source of truth is for that. Could you help explain a bit further?

Hey @erickzhao
First, I would like to say that I'm really happy to be contributing to the forge repo ;)
According to Cloudflare docs, we must integrate to R2 Bucket throught aws SDK using these properties:

const S3 = new S3Client({
  region: "auto", // Required by SDK but not used by R2
  endpoint: `https://${ACCOUNT_ID}.r2.cloudflarestorage.com`,
  credentials: {
    accessKeyId: ACCESS_KEY_ID,
    secretAccessKey: SECRET_ACCESS_KEY,
  },
});

So in our Publisher-S3 integration to S3Client, if the R2 provider is selected, we:

  • Change the endpoint to the cloudflare one;
  • Add region "auto" to R2 provider;
  • Add credentials based on the provider (maintaining backward compatibility with the S3 older impl);
    let endpoint = this.config.endpoint;
    if (provider === 'r2' && !endpoint) {
      endpoint = `https://${this.config.accountId}.r2.cloudflarestorage.com`;
    }

    const region =
      this.config.region || (provider === 'r2' ? 'auto' : undefined);

    const s3Client = new S3Client({
      credentials:
        provider === 'r2'
          ? {
              accessKeyId: this.config.accessKeyId!,
              secretAccessKey: this.config.secretAccessKey!,
            }
          : this.generateCredentials(),
      region,
      endpoint,
      forcePathStyle: !!this.config.s3ForcePathStyle,
    });

All of this params are currently supported by @aws-sdk/client-s3 package
Out of this, the others additions were transforming some s3 specific behavior like setting the cache-control to releases file;
And a additional behavior was added to send the params.contentType when r2 provider is selected

 // R2-specific: Set ContentType
        if (provider === 'r2') {
          params.ContentType =
            mime.lookup(artifact.path) || 'application/octet-stream';
        }

I've published it in @et_dev/publisher-s3 and we have been testing it in some production projects at our company
Let me know if you had any further questions ✌️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create a publisher to Cloudflare R2 Bucket

4 participants