@et_dev/forge-publisher-r2 7.10.2
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 +62 -0
- package/dist/Config.d.ts +37 -0
- package/dist/Config.d.ts.map +1 -0
- package/dist/Config.js +3 -0
- package/dist/PublisherR2.d.ts +11 -0
- package/dist/PublisherR2.d.ts.map +1 -0
- package/dist/PublisherR2.js +89 -0
- package/package.json +32 -0
- package/src/Config.ts +36 -0
- package/src/PublisherR2.ts +155 -0
package/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
## publisher-r2
|
|
2
|
+
|
|
3
|
+
`@electron-forge/publisher-r2` publishes all your artifacts to a Cloudflare R2 bucket where users will be able to download them.
|
|
4
|
+
|
|
5
|
+
By default, all files are positioned at the following key:
|
|
6
|
+
|
|
7
|
+
${config.folder || appVersion}/${platform}/${arch}/${artifactName}
|
|
8
|
+
|
|
9
|
+
Configuration options are documented in [PublisherR2Config](https://js.electronforge.io/interfaces/_electron_forge_publisher_r2.PublisherR2Config.html).
|
|
10
|
+
|
|
11
|
+
```javascript title=forge.config.js
|
|
12
|
+
module.exports = {
|
|
13
|
+
// ...
|
|
14
|
+
publishers: [
|
|
15
|
+
{
|
|
16
|
+
name: '@electron-forge/publisher-r2',
|
|
17
|
+
config: {
|
|
18
|
+
bucket: 'my-bucket',
|
|
19
|
+
accountId: 'your-cloudflare-account-id',
|
|
20
|
+
apiToken: 'your-cloudflare-api-token'
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
};
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
If you run publish twice with the same version on the same platform, it is possible for your old artifacts to get overwritten in R2. It is your responsibility to ensure that you don't overwrite your own releases.
|
|
28
|
+
|
|
29
|
+
### Authentication
|
|
30
|
+
|
|
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
|
+
|
|
33
|
+
```javascript
|
|
34
|
+
config: {
|
|
35
|
+
accountId: 'your-cloudflare-account-id',
|
|
36
|
+
apiToken: 'your-cloudflare-api-token',
|
|
37
|
+
bucket: 'my-bucket',
|
|
38
|
+
// ...
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
#### Getting Your Credentials
|
|
43
|
+
|
|
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
|
+
|
|
47
|
+
### Public Access
|
|
48
|
+
|
|
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.
|
|
50
|
+
|
|
51
|
+
### Custom Key Resolver
|
|
52
|
+
|
|
53
|
+
You can provide a custom function to determine the key (path) for each artifact:
|
|
54
|
+
|
|
55
|
+
```javascript
|
|
56
|
+
config: {
|
|
57
|
+
bucket: 'my-bucket',
|
|
58
|
+
keyResolver: (fileName, platform, arch) => {
|
|
59
|
+
return `releases/v1.0.0/${platform}/${fileName}`;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
package/dist/Config.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface PublisherR2Config {
|
|
2
|
+
/**
|
|
3
|
+
* The Cloudflare Account ID
|
|
4
|
+
*
|
|
5
|
+
* Required. Can be found in the Cloudflare dashboard.
|
|
6
|
+
*/
|
|
7
|
+
accountId: string;
|
|
8
|
+
/**
|
|
9
|
+
* The Cloudflare API Token
|
|
10
|
+
*
|
|
11
|
+
* Required. Create a token with R2 read and write permissions.
|
|
12
|
+
*/
|
|
13
|
+
apiToken: string;
|
|
14
|
+
/**
|
|
15
|
+
* The name of the R2 bucket to upload artifacts to
|
|
16
|
+
*/
|
|
17
|
+
bucket?: string;
|
|
18
|
+
/**
|
|
19
|
+
* The key prefix to upload artifacts to.
|
|
20
|
+
*
|
|
21
|
+
* E.g. `my/prefix`
|
|
22
|
+
*
|
|
23
|
+
* Default: appVersion
|
|
24
|
+
*/
|
|
25
|
+
folder?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Custom function to provide the key to upload a given file to
|
|
28
|
+
*/
|
|
29
|
+
keyResolver?: (fileName: string, platform: string, arch: string) => string;
|
|
30
|
+
/**
|
|
31
|
+
* Custom R2 endpoint (optional)
|
|
32
|
+
*
|
|
33
|
+
* Default: Auto-generated from accountId
|
|
34
|
+
*/
|
|
35
|
+
endpoint?: string;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=Config.d.ts.map
|
|
@@ -0,0 +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"}
|
package/dist/Config.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { PublisherOptions, PublisherStatic } from '@electron-forge/publisher-static';
|
|
2
|
+
import { PublisherR2Config } from './Config';
|
|
3
|
+
export default class PublisherR2 extends PublisherStatic<PublisherR2Config> {
|
|
4
|
+
name: string;
|
|
5
|
+
private r2KeySafe;
|
|
6
|
+
private createWranglerExecutor;
|
|
7
|
+
publish({ makeResults, setStatusLine, }: PublisherOptions): Promise<void>;
|
|
8
|
+
private uploadFile;
|
|
9
|
+
}
|
|
10
|
+
export { PublisherR2, PublisherR2Config };
|
|
11
|
+
//# sourceMappingURL=PublisherR2.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.PublisherR2 = void 0;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const publisher_static_1 = require("@electron-forge/publisher-static");
|
|
9
|
+
const debug_1 = __importDefault(require("debug"));
|
|
10
|
+
const execa_1 = require("execa");
|
|
11
|
+
const mime_types_1 = __importDefault(require("mime-types"));
|
|
12
|
+
const d = (0, debug_1.default)('electron-forge:publish:r2');
|
|
13
|
+
class PublisherR2 extends publisher_static_1.PublisherStatic {
|
|
14
|
+
constructor() {
|
|
15
|
+
super(...arguments);
|
|
16
|
+
this.name = 'r2';
|
|
17
|
+
this.r2KeySafe = (key) => {
|
|
18
|
+
return key.replace(/@/g, '_').replace(/\//g, '_');
|
|
19
|
+
};
|
|
20
|
+
}
|
|
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
|
+
async publish({ makeResults, setStatusLine, }) {
|
|
35
|
+
const artifacts = [];
|
|
36
|
+
if (!this.config.bucket) {
|
|
37
|
+
throw new Error('In order to publish to R2, you must set the "bucket" property in your Forge publisher config. See the docs for more info');
|
|
38
|
+
}
|
|
39
|
+
if (!this.config.accountId) {
|
|
40
|
+
throw new Error('In order to publish to R2, you must set the "accountId" property in your Forge publisher config.');
|
|
41
|
+
}
|
|
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.');
|
|
44
|
+
}
|
|
45
|
+
const { accountId, apiToken } = this.config;
|
|
46
|
+
for (const makeResult of makeResults) {
|
|
47
|
+
artifacts.push(...makeResult.artifacts.map((artifact) => ({
|
|
48
|
+
path: artifact,
|
|
49
|
+
keyPrefix: this.config.folder || this.r2KeySafe(makeResult.packageJSON.name),
|
|
50
|
+
platform: makeResult.platform,
|
|
51
|
+
arch: makeResult.arch,
|
|
52
|
+
})));
|
|
53
|
+
}
|
|
54
|
+
d('uploading to R2 bucket:', this.config.bucket);
|
|
55
|
+
let uploaded = 0;
|
|
56
|
+
const updateStatusLine = () => setStatusLine(`Uploading distributable (${uploaded}/${artifacts.length})`);
|
|
57
|
+
updateStatusLine();
|
|
58
|
+
const wrangler = this.createWranglerExecutor(accountId, apiToken);
|
|
59
|
+
await Promise.all(artifacts.map(async (artifact) => {
|
|
60
|
+
d('uploading:', artifact.path);
|
|
61
|
+
await this.uploadFile(artifact, wrangler);
|
|
62
|
+
uploaded += 1;
|
|
63
|
+
updateStatusLine();
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
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)}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
exports.default = PublisherR2;
|
|
88
|
+
exports.PublisherR2 = PublisherR2;
|
|
89
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUHVibGlzaGVyUjIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvUHVibGlzaGVyUjIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQUEsMERBQTZCO0FBRTdCLHVFQUcwQztBQUMxQyxrREFBMEI7QUFDMUIsaUNBQWtEO0FBQ2xELDREQUE4QjtBQUk5QixNQUFNLENBQUMsR0FBRyxJQUFBLGVBQUssRUFBQywyQkFBMkIsQ0FBQyxDQUFDO0FBUzdDLE1BQXFCLFdBQVksU0FBUSxrQ0FBa0M7SUFBM0U7O1FBQ0UsU0FBSSxHQUFHLElBQUksQ0FBQztRQUVKLGNBQVMsR0FBRyxDQUFDLEdBQVcsRUFBRSxFQUFFO1lBQ2xDLE9BQU8sR0FBRyxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsR0FBRyxDQUFDLENBQUMsT0FBTyxDQUFDLEtBQUssRUFBRSxHQUFHLENBQUMsQ0FBQztRQUNwRCxDQUFDLENBQUM7SUE4SEosQ0FBQztJQTVIUyxzQkFBc0IsQ0FBQyxTQUFpQixFQUFFLFFBQWdCO1FBQ2hFLE9BQU8sS0FBSyxFQUFFLEdBQUcsSUFBYyxFQUFFLEVBQUU7WUFDakMsTUFBTSxHQUFHLEdBQUc7Z0JBQ1YscUJBQXFCLEVBQUUsU0FBUztnQkFDaEMsb0JBQW9CLEVBQUUsUUFBUTthQUMvQixDQUFDO1lBRUYsQ0FBQyxDQUFDLDJCQUEyQixJQUFJLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsQ0FBQztZQUUvQyxPQUFPLElBQUEsYUFBSyxFQUFDLEtBQUssRUFBRSxDQUFDLFVBQVUsRUFBRSxHQUFHLElBQUksQ0FBQyxFQUFFO2dCQUN6QyxHQUFHO2dCQUNILEtBQUssRUFBRSxNQUFNO2FBQ2QsQ0FBQyxDQUFDO1FBQ0wsQ0FBQyxDQUFDO0lBQ0osQ0FBQztJQUVELEtBQUssQ0FBQyxPQUFPLENBQUMsRUFDWixXQUFXLEVBQ1gsYUFBYSxHQUNJO1FBQ2pCLE1BQU0sU0FBUyxHQUFpQixFQUFFLENBQUM7UUFF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
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@et_dev/forge-publisher-r2",
|
|
3
|
+
"version": "7.10.2",
|
|
4
|
+
"description": "Cloudflare R2 publisher for Electron Forge",
|
|
5
|
+
"repository": "https://github.com/electron/forge",
|
|
6
|
+
"author": "Electron Forge",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"main": "dist/PublisherR2.js",
|
|
9
|
+
"typings": "dist/PublisherR2.d.ts",
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">= 16.4.0"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"prepublishOnly": "npm run build"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@electron-forge/publisher-static": "7.10.2",
|
|
19
|
+
"@electron-forge/shared-types": "7.10.2",
|
|
20
|
+
"debug": "^4.3.1",
|
|
21
|
+
"execa": "^9.0.0",
|
|
22
|
+
"mime-types": "^2.1.25",
|
|
23
|
+
"wrangler": "^4.54.0"
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"src"
|
|
31
|
+
]
|
|
32
|
+
}
|
package/src/Config.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface PublisherR2Config {
|
|
2
|
+
/**
|
|
3
|
+
* The Cloudflare Account ID
|
|
4
|
+
*
|
|
5
|
+
* Required. Can be found in the Cloudflare dashboard.
|
|
6
|
+
*/
|
|
7
|
+
accountId: string;
|
|
8
|
+
/**
|
|
9
|
+
* The Cloudflare API Token
|
|
10
|
+
*
|
|
11
|
+
* Required. Create a token with R2 read and write permissions.
|
|
12
|
+
*/
|
|
13
|
+
apiToken: string;
|
|
14
|
+
/**
|
|
15
|
+
* The name of the R2 bucket to upload artifacts to
|
|
16
|
+
*/
|
|
17
|
+
bucket?: string;
|
|
18
|
+
/**
|
|
19
|
+
* The key prefix to upload artifacts to.
|
|
20
|
+
*
|
|
21
|
+
* E.g. `my/prefix`
|
|
22
|
+
*
|
|
23
|
+
* Default: appVersion
|
|
24
|
+
*/
|
|
25
|
+
folder?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Custom function to provide the key to upload a given file to
|
|
28
|
+
*/
|
|
29
|
+
keyResolver?: (fileName: string, platform: string, arch: string) => string;
|
|
30
|
+
/**
|
|
31
|
+
* Custom R2 endpoint (optional)
|
|
32
|
+
*
|
|
33
|
+
* Default: Auto-generated from accountId
|
|
34
|
+
*/
|
|
35
|
+
endpoint?: string;
|
|
36
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
PublisherOptions,
|
|
5
|
+
PublisherStatic,
|
|
6
|
+
} from '@electron-forge/publisher-static';
|
|
7
|
+
import debug from 'debug';
|
|
8
|
+
import { execa, ExecaError, Result } from 'execa';
|
|
9
|
+
import mime from 'mime-types';
|
|
10
|
+
|
|
11
|
+
import { PublisherR2Config } from './Config';
|
|
12
|
+
|
|
13
|
+
const d = debug('electron-forge:publish:r2');
|
|
14
|
+
|
|
15
|
+
type R2Artifact = {
|
|
16
|
+
path: string;
|
|
17
|
+
keyPrefix: string;
|
|
18
|
+
platform: string;
|
|
19
|
+
arch: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default class PublisherR2 extends PublisherStatic<PublisherR2Config> {
|
|
23
|
+
name = 'r2';
|
|
24
|
+
|
|
25
|
+
private r2KeySafe = (key: string) => {
|
|
26
|
+
return key.replace(/@/g, '_').replace(/\//g, '_');
|
|
27
|
+
};
|
|
28
|
+
|
|
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
|
+
async publish({
|
|
46
|
+
makeResults,
|
|
47
|
+
setStatusLine,
|
|
48
|
+
}: PublisherOptions): Promise<void> {
|
|
49
|
+
const artifacts: R2Artifact[] = [];
|
|
50
|
+
|
|
51
|
+
if (!this.config.bucket) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
'In order to publish to R2, you must set the "bucket" property in your Forge publisher config. See the docs for more info',
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!this.config.accountId) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
'In order to publish to R2, you must set the "accountId" property in your Forge publisher config.',
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!this.config.apiToken) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
'In order to publish to R2, you must set the "apiToken" property in your Forge publisher config.',
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const { accountId, apiToken } = this.config;
|
|
70
|
+
|
|
71
|
+
for (const makeResult of makeResults) {
|
|
72
|
+
artifacts.push(
|
|
73
|
+
...makeResult.artifacts.map((artifact) => ({
|
|
74
|
+
path: artifact,
|
|
75
|
+
keyPrefix:
|
|
76
|
+
this.config.folder || this.r2KeySafe(makeResult.packageJSON.name),
|
|
77
|
+
platform: makeResult.platform,
|
|
78
|
+
arch: makeResult.arch,
|
|
79
|
+
})),
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
d('uploading to R2 bucket:', this.config.bucket);
|
|
84
|
+
|
|
85
|
+
let uploaded = 0;
|
|
86
|
+
const updateStatusLine = () =>
|
|
87
|
+
setStatusLine(
|
|
88
|
+
`Uploading distributable (${uploaded}/${artifacts.length})`,
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
updateStatusLine();
|
|
92
|
+
|
|
93
|
+
const wrangler = this.createWranglerExecutor(accountId, apiToken);
|
|
94
|
+
|
|
95
|
+
await Promise.all(
|
|
96
|
+
artifacts.map(async (artifact) => {
|
|
97
|
+
d('uploading:', artifact.path);
|
|
98
|
+
await this.uploadFile(artifact, wrangler);
|
|
99
|
+
uploaded += 1;
|
|
100
|
+
updateStatusLine();
|
|
101
|
+
}),
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
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
|
+
);
|
|
134
|
+
|
|
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)}`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export { PublisherR2, PublisherR2Config };
|