@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 +14 -8
- package/dist/Config.d.ts +16 -4
- package/dist/Config.d.ts.map +1 -1
- package/dist/PublisherR2.d.ts +7 -2
- package/dist/PublisherR2.d.ts.map +1 -1
- package/dist/PublisherR2.js +43 -39
- package/package.json +4 -4
- package/src/Config.ts +16 -4
- package/src/PublisherR2.ts +63 -68
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
9
|
+
* The R2 Access Key ID
|
|
10
10
|
*
|
|
11
|
-
* Required. Create
|
|
11
|
+
* Required. Create an API token from the R2 dashboard.
|
|
12
12
|
*/
|
|
13
|
-
|
|
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
|
package/dist/Config.d.ts.map
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/PublisherR2.d.ts
CHANGED
|
@@ -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
|
-
|
|
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":"
|
|
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"}
|
package/dist/PublisherR2.js
CHANGED
|
@@ -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.
|
|
43
|
-
throw new Error('In order to publish to R2, you must set the "
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
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,
|
|
93
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUHVibGlzaGVyUjIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvUHVibGlzaGVyUjIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQUEsc0RBQXlCO0FBQ3pCLDBEQUE2QjtBQUU3QixrREFBcUU7QUFDckUsc0RBQThDO0FBQzlDLHVFQUcwQztBQUMxQyxrREFBMEI7QUFDMUIsNERBQThCO0FBSTlCLE1BQU0sQ0FBQyxHQUFHLElBQUEsZUFBSyxFQUFDLDJCQUEyQixDQUFDLENBQUM7QUFTN0MsTUFBcUIsV0FBWSxTQUFRLGtDQUFrQztJQUEzRTs7UUFDRSxTQUFJLEdBQUcsSUFBSSxDQUFDO1FBRUosY0FBUyxHQUFHLENBQUMsR0FBVyxFQUFFLEVBQUU7WUFDbEMsT0FBTyxHQUFHLENBQUMsT0FBTyxDQUFDLElBQUksRUFBRSxHQUFHLENBQUMsQ0FBQyxPQUFPLENBQUMsS0FBSyxFQUFFLEdBQUcsQ0FBQyxDQUFDO1FBQ3BELENBQUMsQ0FBQztJQXVISixDQUFDO0lBckhDLEtBQUssQ0FBQyxPQUFPLENBQUMsRUFDWixXQUFXLEVBQ1gsYUFBYSxHQUNJO1FBQ2pCLE1BQU0sU0FBUyxHQUFpQixFQUFFLENBQUM7UUFFbkMsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDeEIsTUFBTSxJQUFJLEtBQUssQ0FDYiwwSEFBMEgsQ0FDM0gsQ0FBQztRQUNKLENBQUM7UUFFRCxJQUFJLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxTQUFTLEVBQUUsQ0FBQztZQUMzQixNQUFNLElBQUksS0FBSyxDQUNiLGtHQUFrRyxDQUNuRyxDQUFDO1FBQ0osQ0FBQztRQUVELElBQUksQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLFdBQVcsRUFBRSxDQUFDO1lBQzdCLE1BQU0sSUFBSSxLQUFLLENBQ2Isb0dBQW9HLENBQ3JHLENBQUM7UUFDSixDQUFDO1FBRUQsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFN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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@et_dev/forge-publisher-r2",
|
|
3
|
-
"version": "7.10.
|
|
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
|
-
"
|
|
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
|
|
9
|
+
* The R2 Access Key ID
|
|
10
10
|
*
|
|
11
|
-
* Required. Create
|
|
11
|
+
* Required. Create an API token from the R2 dashboard.
|
|
12
12
|
*/
|
|
13
|
-
|
|
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
|
}
|
package/src/PublisherR2.ts
CHANGED
|
@@ -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.
|
|
49
|
+
if (!this.config.accessKeyId) {
|
|
64
50
|
throw new Error(
|
|
65
|
-
'In order to publish to R2, you must set the "
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
106
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
|