@electron-forge/publisher-electron-release-server 6.0.1 → 6.0.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/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ The MIT License (MIT)
2
+ Copyright (c) 2016 Samuel Attard
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
5
+ this software and associated documentation files (the "Software"), to deal in
6
+ the Software without restriction, including without limitation the rights to
7
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
8
+ the Software, and to permit persons to whom the Software is furnished to do so,
9
+ subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
16
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
17
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
18
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
19
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electron-forge/publisher-electron-release-server",
3
- "version": "6.0.1",
3
+ "version": "6.0.2",
4
4
  "description": "Electron release server publisher for Electron Forge",
5
5
  "repository": "https://github.com/electron/forge",
6
6
  "author": "Samuel Attard",
@@ -18,11 +18,15 @@
18
18
  "node": ">= 14.17.5"
19
19
  },
20
20
  "dependencies": {
21
- "@electron-forge/publisher-base": "6.0.1",
22
- "@electron-forge/shared-types": "6.0.1",
21
+ "@electron-forge/publisher-base": "^6.0.2",
22
+ "@electron-forge/shared-types": "^6.0.2",
23
23
  "debug": "^4.3.1",
24
24
  "form-data": "^4.0.0",
25
25
  "fs-extra": "^10.0.0",
26
26
  "node-fetch": "^2.6.7"
27
- }
27
+ },
28
+ "publishConfig": {
29
+ "access": "public"
30
+ },
31
+ "gitHead": "11cf4b58359c9881c05c06e0d62be575a0ed70d1"
28
32
  }
package/src/Config.ts ADDED
@@ -0,0 +1,34 @@
1
+ export interface PublisherERSConfig {
2
+ /**
3
+ * The base URL of your instance of ERS.
4
+ *
5
+ * E.g. https://my-update.server.com
6
+ */
7
+ baseUrl: string;
8
+ /**
9
+ * The username you use to sign in to ERS
10
+ */
11
+ username: string;
12
+ /**
13
+ * The password you use to sign in to ERS
14
+ */
15
+ password: string;
16
+ /**
17
+ * The release channel you want to send artifacts to, normally something like
18
+ * "stable", "beta" or "alpha".
19
+ *
20
+ * If left unspecified we will try to infer the channel from your version
21
+ * field in your package.json.
22
+ *
23
+ * Default: stable
24
+ */
25
+ channel?: string;
26
+
27
+ /**
28
+ * The "flavor" of the binary that you want to release to.
29
+ * This is useful if you want to provide multiple versions
30
+ * of the same application version (e.g. full and lite)
31
+ * to end users.
32
+ */
33
+ flavor?: string;
34
+ }
@@ -0,0 +1,153 @@
1
+ import path from 'path';
2
+
3
+ import { PublisherBase, PublisherOptions } from '@electron-forge/publisher-base';
4
+ import { ForgeArch, ForgePlatform } from '@electron-forge/shared-types';
5
+ import debug from 'debug';
6
+ import FormData from 'form-data';
7
+ import fs from 'fs-extra';
8
+ import fetch, { RequestInfo, RequestInit, Response } from 'node-fetch';
9
+
10
+ import { PublisherERSConfig } from './Config';
11
+
12
+ const d = debug('electron-forge:publish:ers');
13
+
14
+ interface ERSVersion {
15
+ name: string;
16
+ assets: { name: string }[];
17
+ flavor?: string;
18
+ }
19
+
20
+ const fetchAndCheckStatus = async (url: RequestInfo, init?: RequestInit): Promise<Response> => {
21
+ const result = await fetch(url, init);
22
+ if (result.ok) {
23
+ // res.status >= 200 && res.status < 300
24
+ return result;
25
+ }
26
+ throw new Error(`ERS publish failed with status code: ${result.status} (${result.url})`);
27
+ };
28
+
29
+ export const ersPlatform = (platform: ForgePlatform, arch: ForgeArch): string => {
30
+ switch (platform) {
31
+ case 'darwin':
32
+ return 'osx_64';
33
+ case 'linux':
34
+ return arch === 'ia32' ? 'linux_32' : 'linux_64';
35
+ case 'win32':
36
+ return arch === 'ia32' ? 'windows_32' : 'windows_64';
37
+ default:
38
+ return platform;
39
+ }
40
+ };
41
+
42
+ export default class PublisherERS extends PublisherBase<PublisherERSConfig> {
43
+ name = 'electron-release-server';
44
+
45
+ async publish({ makeResults, setStatusLine }: PublisherOptions): Promise<void> {
46
+ const { config } = this;
47
+
48
+ if (!(config.baseUrl && config.username && config.password)) {
49
+ throw new Error(
50
+ 'In order to publish to ERS you must set the "electronReleaseServer.baseUrl", "electronReleaseServer.username" and "electronReleaseServer.password" properties in your Forge config. See the docs for more info'
51
+ );
52
+ }
53
+
54
+ d('attempting to authenticate to ERS');
55
+
56
+ const api = (apiPath: string) => `${config.baseUrl}/${apiPath}`;
57
+
58
+ const { token } = await (
59
+ await fetchAndCheckStatus(api('api/auth/login'), {
60
+ method: 'POST',
61
+ body: JSON.stringify({
62
+ username: config.username,
63
+ password: config.password,
64
+ }),
65
+ headers: {
66
+ 'Content-Type': 'application/json',
67
+ },
68
+ })
69
+ ).json();
70
+
71
+ const authFetch = (apiPath: string, options?: RequestInit) =>
72
+ fetchAndCheckStatus(api(apiPath), { ...(options || {}), headers: { ...(options || {}).headers, Authorization: `Bearer ${token}` } });
73
+
74
+ const versions: ERSVersion[] = await (await authFetch('api/version')).json();
75
+ const flavor = config.flavor || 'default';
76
+
77
+ for (const makeResult of makeResults) {
78
+ const { packageJSON } = makeResult;
79
+ const artifacts = makeResult.artifacts.filter((artifactPath) => path.basename(artifactPath).toLowerCase() !== 'releases');
80
+
81
+ const existingVersion = versions.find((version) => {
82
+ return version.name === packageJSON.version && (!version.flavor || version.flavor === flavor);
83
+ });
84
+
85
+ let channel = 'stable';
86
+ if (config.channel) {
87
+ channel = config.channel;
88
+ } else if (packageJSON.version.includes('beta')) {
89
+ channel = 'beta';
90
+ } else if (packageJSON.version.includes('alpha')) {
91
+ channel = 'alpha';
92
+ }
93
+
94
+ if (!existingVersion) {
95
+ await authFetch('api/version', {
96
+ method: 'POST',
97
+ body: JSON.stringify({
98
+ channel: {
99
+ name: channel,
100
+ },
101
+ flavor: config.flavor,
102
+ name: packageJSON.version,
103
+ notes: '',
104
+ }),
105
+ headers: {
106
+ 'Content-Type': 'application/json',
107
+ },
108
+ });
109
+ }
110
+
111
+ let uploaded = 0;
112
+ const updateStatusLine = () => setStatusLine(`Uploading distributable (${uploaded}/${artifacts.length})`);
113
+ updateStatusLine();
114
+
115
+ await Promise.all(
116
+ artifacts.map(async (artifactPath) => {
117
+ if (existingVersion) {
118
+ const existingAsset = existingVersion.assets.find((asset) => asset.name === path.basename(artifactPath));
119
+
120
+ if (existingAsset) {
121
+ d('asset at path:', artifactPath, 'already exists on server');
122
+ uploaded += 1;
123
+ updateStatusLine();
124
+ return;
125
+ }
126
+ }
127
+ d('attempting to upload asset:', artifactPath);
128
+ const artifactForm = new FormData();
129
+ artifactForm.append('token', token);
130
+ artifactForm.append('version', packageJSON.version);
131
+ artifactForm.append('platform', ersPlatform(makeResult.platform, makeResult.arch));
132
+
133
+ // see https://github.com/form-data/form-data/issues/426
134
+ const fileOptions = {
135
+ knownLength: fs.statSync(artifactPath).size,
136
+ };
137
+ artifactForm.append('file', fs.createReadStream(artifactPath), fileOptions);
138
+
139
+ await authFetch('api/asset', {
140
+ method: 'POST',
141
+ body: artifactForm,
142
+ headers: artifactForm.getHeaders(),
143
+ });
144
+ d('upload successful for asset:', artifactPath);
145
+ uploaded += 1;
146
+ updateStatusLine();
147
+ })
148
+ );
149
+ }
150
+ }
151
+ }
152
+
153
+ export { PublisherERS, PublisherERSConfig };
@@ -0,0 +1,223 @@
1
+ import { ForgeMakeResult, ResolvedForgeConfig } from '@electron-forge/shared-types';
2
+ import { expect } from 'chai';
3
+ import fetchMock from 'fetch-mock';
4
+ import proxyquire from 'proxyquire';
5
+ import { stub } from 'sinon';
6
+
7
+ import type { PublisherERS as PublisherERSType } from '../src/PublisherERS';
8
+
9
+ const noop = () => void 0;
10
+
11
+ describe('PublisherERS', () => {
12
+ let fetch: typeof fetchMock;
13
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
14
+ let PublisherERS: typeof PublisherERSType;
15
+
16
+ beforeEach(() => {
17
+ fetch = fetchMock.sandbox();
18
+ PublisherERS = proxyquire.noCallThru().load('../src/PublisherERS', {
19
+ 'node-fetch': fetch,
20
+ 'fs-extra': { createReadStream: stub().returns(''), statSync: stub().returns({ size: 100 }) },
21
+ }).default;
22
+ });
23
+
24
+ afterEach(() => {
25
+ fetch.restore();
26
+ });
27
+
28
+ describe('new version', () => {
29
+ it('can publish a new version to ERS', async () => {
30
+ const baseUrl = 'https://example.com';
31
+ const token = 'FAKE_TOKEN';
32
+ const flavor = 'lite';
33
+ const version = '3.0.0';
34
+
35
+ // mock login
36
+ fetch.postOnce('path:/api/auth/login', { body: { token }, status: 200 });
37
+ // mock fetch all existing versions
38
+ fetch.getOnce('path:/api/version', { body: [{ name: '2.0.0', assets: [], flavor: 'default' }], status: 200 });
39
+ // mock creating a new version
40
+ fetch.postOnce('path:/api/version', { status: 200 });
41
+ // mock asset upload
42
+ fetch.post('path:/api/asset', { status: 200 });
43
+
44
+ const publisher = new PublisherERS({
45
+ baseUrl,
46
+ username: 'test',
47
+ password: 'test',
48
+ flavor,
49
+ });
50
+
51
+ const makeResults: ForgeMakeResult[] = [
52
+ {
53
+ artifacts: ['/path/to/artifact'],
54
+ packageJSON: {
55
+ version,
56
+ },
57
+ platform: 'linux',
58
+ arch: 'x64',
59
+ },
60
+ ];
61
+
62
+ await publisher.publish({ makeResults, dir: '', forgeConfig: {} as ResolvedForgeConfig, setStatusLine: noop });
63
+
64
+ const calls = fetch.calls();
65
+
66
+ // creates a new version with the correct flavor, name, and channel
67
+ expect(calls[2][0]).to.equal(`${baseUrl}/api/version`);
68
+ expect(calls[2][1]?.body).to.equal(`{"channel":{"name":"stable"},"flavor":"${flavor}","name":"${version}","notes":""}`);
69
+
70
+ // uploads asset successfully
71
+ expect(calls[3][0]).to.equal(`${baseUrl}/api/asset`);
72
+ });
73
+ });
74
+
75
+ describe('existing version', () => {
76
+ it('can add new assets', async () => {
77
+ const baseUrl = 'https://example.com';
78
+ const token = 'FAKE_TOKEN';
79
+ const channel = 'stable';
80
+ const flavor = 'lite';
81
+ const version = '2.0.0';
82
+
83
+ // mock login
84
+ fetch.postOnce('path:/api/auth/login', { body: { token }, status: 200 });
85
+ // mock fetch all existing versions
86
+ fetch.getOnce('path:/api/version', { body: [{ name: '2.0.0', assets: [], flavor: 'lite' }], status: 200 });
87
+ // mock asset upload
88
+ fetch.post('path:/api/asset', { status: 200 });
89
+
90
+ const publisher = new PublisherERS({
91
+ baseUrl,
92
+ username: 'test',
93
+ password: 'test',
94
+ channel,
95
+ flavor,
96
+ });
97
+
98
+ const makeResults: ForgeMakeResult[] = [
99
+ {
100
+ artifacts: ['/path/to/artifact'],
101
+ packageJSON: {
102
+ version,
103
+ },
104
+ platform: 'linux',
105
+ arch: 'x64',
106
+ },
107
+ ];
108
+
109
+ await publisher.publish({ makeResults, dir: '', forgeConfig: {} as ResolvedForgeConfig, setStatusLine: noop });
110
+
111
+ const calls = fetch.calls();
112
+
113
+ // uploads asset successfully
114
+ expect(calls[2][0]).to.equal(`${baseUrl}/api/asset`);
115
+ });
116
+
117
+ it('does not replace assets for existing version', async () => {
118
+ const baseUrl = 'https://example.com';
119
+ const token = 'FAKE_TOKEN';
120
+ const channel = 'stable';
121
+ const version = '2.0.0';
122
+
123
+ // mock login
124
+ fetch.postOnce('path:/api/auth/login', { body: { token }, status: 200 });
125
+ // mock fetch all existing versions
126
+ fetch.getOnce('path:/api/version', { body: [{ name: '2.0.0', assets: [{ name: 'existing-artifact' }], flavor: 'default' }], status: 200 });
127
+
128
+ const publisher = new PublisherERS({
129
+ baseUrl,
130
+ username: 'test',
131
+ password: 'test',
132
+ channel,
133
+ });
134
+
135
+ const makeResults: ForgeMakeResult[] = [
136
+ {
137
+ artifacts: ['/path/to/existing-artifact'],
138
+ packageJSON: {
139
+ version,
140
+ },
141
+ platform: 'linux',
142
+ arch: 'x64',
143
+ },
144
+ ];
145
+
146
+ await publisher.publish({ makeResults, dir: '', forgeConfig: {} as ResolvedForgeConfig, setStatusLine: noop });
147
+
148
+ const calls = fetch.calls();
149
+ expect(calls).to.have.length(2);
150
+ });
151
+
152
+ it('can upload a new flavor for an existing version', async () => {
153
+ const baseUrl = 'https://example.com';
154
+ const token = 'FAKE_TOKEN';
155
+ const version = '2.0.0';
156
+ const flavor = 'lite';
157
+
158
+ // mock login
159
+ fetch.postOnce('path:/api/auth/login', { body: { token }, status: 200 });
160
+ // mock fetch all existing versions
161
+ fetch.getOnce('path:/api/version', { body: [{ name: '2.0.0', assets: [{ name: 'existing-artifact' }], flavor: 'default' }], status: 200 });
162
+ // mock creating a new version
163
+ fetch.postOnce('path:/api/version', { status: 200 });
164
+ // mock asset upload
165
+ fetch.post('path:/api/asset', { status: 200 });
166
+
167
+ const publisher = new PublisherERS({
168
+ baseUrl,
169
+ username: 'test',
170
+ password: 'test',
171
+ flavor,
172
+ });
173
+
174
+ const makeResults: ForgeMakeResult[] = [
175
+ {
176
+ artifacts: ['/path/to/artifact'],
177
+ packageJSON: {
178
+ version,
179
+ },
180
+ platform: 'linux',
181
+ arch: 'x64',
182
+ },
183
+ ];
184
+
185
+ await publisher.publish({ makeResults, dir: '', forgeConfig: {} as ResolvedForgeConfig, setStatusLine: noop });
186
+
187
+ const calls = fetch.calls();
188
+
189
+ // creates a new version with the correct flavor, name, and channel
190
+ expect(calls[2][0]).to.equal(`${baseUrl}/api/version`);
191
+ expect(calls[2][1]?.body).to.equal(`{"channel":{"name":"stable"},"flavor":"${flavor}","name":"${version}","notes":""}`);
192
+
193
+ // uploads asset successfully
194
+ expect(calls[3][0]).to.equal(`${baseUrl}/api/asset`);
195
+ });
196
+
197
+ // TODO: implement edge cases
198
+ it('can read the channel from the package.json version');
199
+ it('does not upload the RELEASES file');
200
+ });
201
+
202
+ it('fails if username and password are not provided', () => {
203
+ // @ts-expect-error testing invalid options
204
+ const publisher = new PublisherERS({});
205
+
206
+ expect(publisher.publish({ makeResults: [], dir: '', forgeConfig: {} as ResolvedForgeConfig, setStatusLine: noop })).to.eventually.be.rejectedWith(
207
+ 'In order to publish to ERS you must set the "electronReleaseServer.baseUrl", "electronReleaseServer.username" and "electronReleaseServer.password" properties in your Forge config. See the docs for more info'
208
+ );
209
+ });
210
+
211
+ it('fails if the server returns 4xx', async () => {
212
+ fetch.mock('begin:http://example.com', { body: {}, status: 400 });
213
+
214
+ const publisher = new PublisherERS({
215
+ baseUrl: 'http://example.com',
216
+ username: 'test',
217
+ password: 'test',
218
+ });
219
+ return expect(publisher.publish({ makeResults: [], dir: '', forgeConfig: {} as ResolvedForgeConfig, setStatusLine: noop })).to.eventually.be.rejectedWith(
220
+ 'ERS publish failed with status code: 400 (http://example.com/api/auth/login)'
221
+ );
222
+ });
223
+ });
package/dist/Config.d.ts DELETED
@@ -1,34 +0,0 @@
1
- export interface PublisherERSConfig {
2
- /**
3
- * The base URL of your instance of ERS.
4
- *
5
- * E.g. https://my-update.server.com
6
- */
7
- baseUrl: string;
8
- /**
9
- * The username you use to sign in to ERS
10
- */
11
- username: string;
12
- /**
13
- * The password you use to sign in to ERS
14
- */
15
- password: string;
16
- /**
17
- * The release channel you want to send artifacts to, normally something like
18
- * "stable", "beta" or "alpha".
19
- *
20
- * If left unspecified we will try to infer the channel from your version
21
- * field in your package.json.
22
- *
23
- * Default: stable
24
- */
25
- channel?: string;
26
- /**
27
- * The "flavor" of the binary that you want to release to.
28
- * This is useful if you want to provide multiple versions
29
- * of the same application version (e.g. full and lite)
30
- * to end users.
31
- */
32
- flavor?: string;
33
- }
34
- //# sourceMappingURL=Config.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Config.d.ts","sourceRoot":"","sources":["../src/Config.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
package/dist/Config.js DELETED
@@ -1,6 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", {
3
- value: true
4
- });
5
-
6
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiJ9
@@ -1,10 +0,0 @@
1
- import { PublisherBase, PublisherOptions } from '@electron-forge/publisher-base';
2
- import { ForgeArch, ForgePlatform } from '@electron-forge/shared-types';
3
- import { PublisherERSConfig } from './Config';
4
- export declare const ersPlatform: (platform: ForgePlatform, arch: ForgeArch) => string;
5
- export default class PublisherERS extends PublisherBase<PublisherERSConfig> {
6
- name: string;
7
- publish({ makeResults, setStatusLine }: PublisherOptions): Promise<void>;
8
- }
9
- export { PublisherERS, PublisherERSConfig };
10
- //# sourceMappingURL=PublisherERS.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"PublisherERS.d.ts","sourceRoot":"","sources":["../src/PublisherERS.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AACjF,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAMxE,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAmB9C,eAAO,MAAM,WAAW,aAAc,aAAa,QAAQ,SAAS,KAAG,MAWtE,CAAC;AAEF,MAAM,CAAC,OAAO,OAAO,YAAa,SAAQ,aAAa,CAAC,kBAAkB,CAAC;IACzE,IAAI,SAA6B;IAE3B,OAAO,CAAC,EAAE,WAAW,EAAE,aAAa,EAAE,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;CA0G/E;AAED,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,CAAC"}
@@ -1,150 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", {
3
- value: true
4
- });
5
- Object.defineProperty(exports, "PublisherERSConfig", {
6
- enumerable: true,
7
- get: function() {
8
- return _config.PublisherERSConfig;
9
- }
10
- });
11
- exports.PublisherERS = exports.default = exports.ersPlatform = void 0;
12
- var _path = _interopRequireDefault(require("path"));
13
- var _publisherBase = require("@electron-forge/publisher-base");
14
- var _debug = _interopRequireDefault(require("debug"));
15
- var _formData = _interopRequireDefault(require("form-data"));
16
- var _fsExtra = _interopRequireDefault(require("fs-extra"));
17
- var _nodeFetch = _interopRequireDefault(require("node-fetch"));
18
- var _config = require("./Config");
19
- function _interopRequireDefault(obj) {
20
- return obj && obj.__esModule ? obj : {
21
- default: obj
22
- };
23
- }
24
- const d = (0, _debug).default('electron-forge:publish:ers');
25
- const fetchAndCheckStatus = async (url, init)=>{
26
- const result = await (0, _nodeFetch).default(url, init);
27
- if (result.ok) {
28
- // res.status >= 200 && res.status < 300
29
- return result;
30
- }
31
- throw new Error(`ERS publish failed with status code: ${result.status} (${result.url})`);
32
- };
33
- const ersPlatform = (platform, arch)=>{
34
- switch(platform){
35
- case 'darwin':
36
- return 'osx_64';
37
- case 'linux':
38
- return arch === 'ia32' ? 'linux_32' : 'linux_64';
39
- case 'win32':
40
- return arch === 'ia32' ? 'windows_32' : 'windows_64';
41
- default:
42
- return platform;
43
- }
44
- };
45
- exports.ersPlatform = ersPlatform;
46
- class PublisherERS extends _publisherBase.PublisherBase {
47
- async publish({ makeResults , setStatusLine }) {
48
- const { config } = this;
49
- if (!(config.baseUrl && config.username && config.password)) {
50
- throw new Error('In order to publish to ERS you must set the "electronReleaseServer.baseUrl", "electronReleaseServer.username" and "electronReleaseServer.password" properties in your Forge config. See the docs for more info');
51
- }
52
- d('attempting to authenticate to ERS');
53
- const api = (apiPath)=>`${config.baseUrl}/${apiPath}`
54
- ;
55
- const { token } = await (await fetchAndCheckStatus(api('api/auth/login'), {
56
- method: 'POST',
57
- body: JSON.stringify({
58
- username: config.username,
59
- password: config.password
60
- }),
61
- headers: {
62
- 'Content-Type': 'application/json'
63
- }
64
- })).json();
65
- const authFetch = (apiPath, options)=>fetchAndCheckStatus(api(apiPath), {
66
- ...options || {},
67
- headers: {
68
- ...(options || {}).headers,
69
- Authorization: `Bearer ${token}`
70
- }
71
- })
72
- ;
73
- const versions = await (await authFetch('api/version')).json();
74
- const flavor = config.flavor || 'default';
75
- for (const makeResult of makeResults){
76
- const { packageJSON } = makeResult;
77
- const artifacts = makeResult.artifacts.filter((artifactPath)=>_path.default.basename(artifactPath).toLowerCase() !== 'releases'
78
- );
79
- const existingVersion = versions.find((version)=>{
80
- return version.name === packageJSON.version && (!version.flavor || version.flavor === flavor);
81
- });
82
- let channel = 'stable';
83
- if (config.channel) {
84
- channel = config.channel;
85
- } else if (packageJSON.version.includes('beta')) {
86
- channel = 'beta';
87
- } else if (packageJSON.version.includes('alpha')) {
88
- channel = 'alpha';
89
- }
90
- if (!existingVersion) {
91
- await authFetch('api/version', {
92
- method: 'POST',
93
- body: JSON.stringify({
94
- channel: {
95
- name: channel
96
- },
97
- flavor: config.flavor,
98
- name: packageJSON.version,
99
- notes: ''
100
- }),
101
- headers: {
102
- 'Content-Type': 'application/json'
103
- }
104
- });
105
- }
106
- let uploaded = 0;
107
- const updateStatusLine = ()=>setStatusLine(`Uploading distributable (${uploaded}/${artifacts.length})`)
108
- ;
109
- updateStatusLine();
110
- await Promise.all(artifacts.map(async (artifactPath)=>{
111
- if (existingVersion) {
112
- const existingAsset = existingVersion.assets.find((asset)=>asset.name === _path.default.basename(artifactPath)
113
- );
114
- if (existingAsset) {
115
- d('asset at path:', artifactPath, 'already exists on server');
116
- uploaded += 1;
117
- updateStatusLine();
118
- return;
119
- }
120
- }
121
- d('attempting to upload asset:', artifactPath);
122
- const artifactForm = new _formData.default();
123
- artifactForm.append('token', token);
124
- artifactForm.append('version', packageJSON.version);
125
- artifactForm.append('platform', ersPlatform(makeResult.platform, makeResult.arch));
126
- // see https://github.com/form-data/form-data/issues/426
127
- const fileOptions = {
128
- knownLength: _fsExtra.default.statSync(artifactPath).size
129
- };
130
- artifactForm.append('file', _fsExtra.default.createReadStream(artifactPath), fileOptions);
131
- await authFetch('api/asset', {
132
- method: 'POST',
133
- body: artifactForm,
134
- headers: artifactForm.getHeaders()
135
- });
136
- d('upload successful for asset:', artifactPath);
137
- uploaded += 1;
138
- updateStatusLine();
139
- }));
140
- }
141
- }
142
- constructor(...args){
143
- super(...args);
144
- this.name = 'electron-release-server';
145
- }
146
- }
147
- exports.default = PublisherERS;
148
- exports.PublisherERS = PublisherERS;
149
-
150
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy9QdWJsaXNoZXJFUlMudHMiXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHBhdGggZnJvbSAncGF0aCc7XG5cbmltcG9ydCB7IFB1Ymxpc2hlckJhc2UsIFB1Ymxpc2hlck9wdGlvbnMgfSBmcm9tICdAZWxlY3Ryb24tZm9yZ2UvcHVibGlzaGVyLWJhc2UnO1xuaW1wb3J0IHsgRm9yZ2VBcmNoLCBGb3JnZVBsYXRmb3JtIH0gZnJvbSAnQGVsZWN0cm9uLWZvcmdlL3NoYXJlZC10eXBlcyc7XG5pbXBvcnQgZGVidWcgZnJvbSAnZGVidWcnO1xuaW1wb3J0IEZvcm1EYXRhIGZyb20gJ2Zvcm0tZGF0YSc7XG5pbXBvcnQgZnMgZnJvbSAnZnMtZXh0cmEnO1xuaW1wb3J0IGZldGNoLCB7IFJlcXVlc3RJbmZvLCBSZXF1ZXN0SW5pdCwgUmVzcG9uc2UgfSBmcm9tICdub2RlLWZldGNoJztcblxuaW1wb3J0IHsgUHVibGlzaGVyRVJTQ29uZmlnIH0gZnJvbSAnLi9Db25maWcnO1xuXG5jb25zdCBkID0gZGVidWcoJ2VsZWN0cm9uLWZvcmdlOnB1Ymxpc2g6ZXJzJyk7XG5cbmludGVyZmFjZSBFUlNWZXJzaW9uIHtcbiAgbmFtZTogc3RyaW5nO1xuICBhc3NldHM6IHsgbmFtZTogc3RyaW5nIH1bXTtcbiAgZmxhdm9yPzogc3RyaW5nO1xufVxuXG5jb25zdCBmZXRjaEFuZENoZWNrU3RhdHVzID0gYXN5bmMgKHVybDogUmVxdWVzdEluZm8sIGluaXQ/OiBSZXF1ZXN0SW5pdCk6IFByb21pc2U8UmVzcG9uc2U+ID0+IHtcbiAgY29uc3QgcmVzdWx0ID0gYXdhaXQgZmV0Y2godXJsLCBpbml0KTtcbiAgaWYgKHJlc3VsdC5vaykge1xuICAgIC8vIHJlcy5zdGF0dXMgPj0gMjAwICYmIHJlcy5zdGF0dXMgPCAzMDBcbiAgICByZXR1cm4gcmVzdWx0O1xuICB9XG4gIHRocm93IG5ldyBFcnJvcihgRVJTIHB1Ymxpc2ggZmFpbGVkIHdpdGggc3RhdHVzIGNvZGU6ICR7cmVzdWx0LnN0YXR1c30gKCR7cmVzdWx0LnVybH0pYCk7XG59O1xuXG5leHBvcnQgY29uc3QgZXJzUGxhdGZvcm0gPSAocGxhdGZvcm06IEZvcmdlUGxhdGZvcm0sIGFyY2g6IEZvcmdlQXJjaCk6IHN0cmluZyA9PiB7XG4gIHN3aXRjaCAocGxhdGZvcm0pIHtcbiAgICBjYXNlICdkYXJ3aW4nOlxuICAgICAgcmV0dXJuICdvc3hfNjQnO1xuICAgIGNhc2UgJ2xpbnV4JzpcbiAgICAgIHJldHVybiBhcmNoID09PSAnaWEzMicgPyAnbGludXhfMzInIDogJ2xpbnV4XzY0JztcbiAgICBjYXNlICd3aW4zMic6XG4gICAgICByZXR1cm4gYXJjaCA9PT0gJ2lhMzInID8gJ3dpbmRvd3NfMzInIDogJ3dpbmRvd3NfNjQnO1xuICAgIGRlZmF1bHQ6XG4gICAgICByZXR1cm4gcGxhdGZvcm07XG4gIH1cbn07XG5cbmV4cG9ydCBkZWZhdWx0IGNsYXNzIFB1Ymxpc2hlckVSUyBleHRlbmRzIFB1Ymxpc2hlckJhc2U8UHVibGlzaGVyRVJTQ29uZmlnPiB7XG4gIG5hbWUgPSAnZWxlY3Ryb24tcmVsZWFzZS1zZXJ2ZXInO1xuXG4gIGFzeW5jIHB1Ymxpc2goeyBtYWtlUmVzdWx0cywgc2V0U3RhdHVzTGluZSB9OiBQdWJsaXNoZXJPcHRpb25zKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgY29uc3QgeyBjb25maWcgfSA9IHRoaXM7XG5cbiAgICBpZiAoIShjb25maWcuYmFzZVVybCAmJiBjb25maWcudXNlcm5hbWUgJiYgY29uZmlnLnBhc3N3b3JkKSkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAnSW4gb3JkZXIgdG8gcHVibGlzaCB0byBFUlMgeW91IG11c3Qgc2V0IHRoZSBcImVsZWN0cm9uUmVsZWFzZVNlcnZlci5iYXNlVXJsXCIsIFwiZWxlY3Ryb25SZWxlYXNlU2VydmVyLnVzZXJuYW1lXCIgYW5kIFwiZWxlY3Ryb25SZWxlYXNlU2VydmVyLnBhc3N3b3JkXCIgcHJvcGVydGllcyBpbiB5b3VyIEZvcmdlIGNvbmZpZy4gU2VlIHRoZSBkb2NzIGZvciBtb3JlIGluZm8nXG4gICAgICApO1xuICAgIH1cblxuICAgIGQoJ2F0dGVtcHRpbmcgdG8gYXV0aGVudGljYXRlIHRvIEVSUycpO1xuXG4gICAgY29uc3QgYXBpID0gKGFwaVBhdGg6IHN0cmluZykgPT4gYCR7Y29uZmlnLmJhc2VVcmx9LyR7YXBpUGF0aH1gO1xuXG4gICAgY29uc3QgeyB0b2tlbiB9ID0gYXdhaXQgKFxuICAgICAgYXdhaXQgZmV0Y2hBbmRDaGVja1N0YXR1cyhhcGkoJ2FwaS9hdXRoL2xvZ2luJyksIHtcbiAgICAgICAgbWV0aG9kOiAnUE9TVCcsXG4gICAgICAgIGJvZHk6IEpTT04uc3RyaW5naWZ5KHtcbiAgICAgICAgICB1c2VybmFtZTogY29uZmlnLnVzZXJuYW1lLFxuICAgICAgICAgIHBhc3N3b3JkOiBjb25maWcucGFzc3dvcmQsXG4gICAgICAgIH0pLFxuICAgICAgICBoZWFkZXJzOiB7XG4gICAgICAgICAgJ0NvbnRlbnQtVHlwZSc6ICdhcHBsaWNhdGlvbi9qc29uJyxcbiAgICAgICAgfSxcbiAgICAgIH0pXG4gICAgKS5qc29uKCk7XG5cbiAgICBjb25zdCBhdXRoRmV0Y2ggPSAoYXBpUGF0aDogc3RyaW5nLCBvcHRpb25zPzogUmVxdWVzdEluaXQpID0+XG4gICAgICBmZXRjaEFuZENoZWNrU3RhdHVzKGFwaShhcGlQYXRoKSwgeyAuLi4ob3B0aW9ucyB8fCB7fSksIGhlYWRlcnM6IHsgLi4uKG9wdGlvbnMgfHwge30pLmhlYWRlcnMsIEF1dGhvcml6YXRpb246IGBCZWFyZXIgJHt0b2tlbn1gIH0gfSk7XG5cbiAgICBjb25zdCB2ZXJzaW9uczogRVJTVmVyc2lvbltdID0gYXdhaXQgKGF3YWl0IGF1dGhGZXRjaCgnYXBpL3ZlcnNpb24nKSkuanNvbigpO1xuICAgIGNvbnN0IGZsYXZvciA9IGNvbmZpZy5mbGF2b3IgfHwgJ2RlZmF1bHQnO1xuXG4gICAgZm9yIChjb25zdCBtYWtlUmVzdWx0IG9mIG1ha2VSZXN1bHRzKSB7XG4gICAgICBjb25zdCB7IHBhY2thZ2VKU09OIH0gPSBtYWtlUmVzdWx0O1xuICAgICAgY29uc3QgYXJ0aWZhY3RzID0gbWFrZVJlc3VsdC5hcnRpZmFjdHMuZmlsdGVyKChhcnRpZmFjdFBhdGgpID0+IHBhdGguYmFzZW5hbWUoYXJ0aWZhY3RQYXRoKS50b0xvd2VyQ2FzZSgpICE9PSAncmVsZWFzZXMnKTtcblxuICAgICAgY29uc3QgZXhpc3RpbmdWZXJzaW9uID0gdmVyc2lvbnMuZmluZCgodmVyc2lvbikgPT4ge1xuICAgICAgICByZXR1cm4gdmVyc2lvbi5uYW1lID09PSBwYWNrYWdlSlNPTi52ZXJzaW9uICYmICghdmVyc2lvbi5mbGF2b3IgfHwgdmVyc2lvbi5mbGF2b3IgPT09IGZsYXZvcik7XG4gICAgICB9KTtcblxuICAgICAgbGV0IGNoYW5uZWwgPSAnc3RhYmxlJztcbiAgICAgIGlmIChjb25maWcuY2hhbm5lbCkge1xuICAgICAgICBjaGFubmVsID0gY29uZmlnLmNoYW5uZWw7XG4gICAgICB9IGVsc2UgaWYgKHBhY2thZ2VKU09OLnZlcnNpb24uaW5jbHVkZXMoJ2JldGEnKSkge1xuICAgICAgICBjaGFubmVsID0gJ2JldGEnO1xuICAgICAgfSBlbHNlIGlmIChwYWNrYWdlSlNPTi52ZXJzaW9uLmluY2x1ZGVzKCdhbHBoYScpKSB7XG4gICAgICAgIGNoYW5uZWwgPSAnYWxwaGEnO1xuICAgICAgfVxuXG4gICAgICBpZiAoIWV4aXN0aW5nVmVyc2lvbikge1xuICAgICAgICBhd2FpdCBhdXRoRmV0Y2goJ2FwaS92ZXJzaW9uJywge1xuICAgICAgICAgIG1ldGhvZDogJ1BPU1QnLFxuICAgICAgICAgIGJvZHk6IEpTT04uc3RyaW5naWZ5KHtcbiAgICAgICAgICAgIGNoYW5uZWw6IHtcbiAgICAgICAgICAgICAgbmFtZTogY2hhbm5lbCxcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBmbGF2b3I6IGNvbmZpZy5mbGF2b3IsXG4gICAgICAgICAgICBuYW1lOiBwYWNrYWdlSlNPTi52ZXJzaW9uLFxuICAgICAgICAgICAgbm90ZXM6ICcnLFxuICAgICAgICAgIH0pLFxuICAgICAgICAgIGhlYWRlcnM6IHtcbiAgICAgICAgICAgICdDb250ZW50LVR5cGUnOiAnYXBwbGljYXRpb24vanNvbicsXG4gICAgICAgICAgfSxcbiAgICAgICAgfSk7XG4gICAgICB9XG5cbiAgICAgIGxldCB1cGxvYWRlZCA9IDA7XG4gICAgICBjb25zdCB1cGRhdGVTdGF0dXNMaW5lID0gKCkgPT4gc2V0U3RhdHVzTGluZShgVXBsb2FkaW5nIGRpc3RyaWJ1dGFibGUgKCR7dXBsb2FkZWR9LyR7YXJ0aWZhY3RzLmxlbmd0aH0pYCk7XG4gICAgICB1cGRhdGVTdGF0dXNMaW5lKCk7XG5cbiAgICAgIGF3YWl0IFByb21pc2UuYWxsKFxuICAgICAgICBhcnRpZmFjdHMubWFwKGFzeW5jIChhcnRpZmFjdFBhdGgpID0+IHtcbiAgICAgICAgICBpZiAoZXhpc3RpbmdWZXJzaW9uKSB7XG4gICAgICAgICAgICBjb25zdCBleGlzdGluZ0Fzc2V0ID0gZXhpc3RpbmdWZXJzaW9uLmFzc2V0cy5maW5kKChhc3NldCkgPT4gYXNzZXQubmFtZSA9PT0gcGF0aC5iYXNlbmFtZShhcnRpZmFjdFBhdGgpKTtcblxuICAgICAgICAgICAgaWYgKGV4aXN0aW5nQXNzZXQpIHtcbiAgICAgICAgICAgICAgZCgnYXNzZXQgYXQgcGF0aDonLCBhcnRpZmFjdFBhdGgsICdhbHJlYWR5IGV4aXN0cyBvbiBzZXJ2ZXInKTtcbiAgICAgICAgICAgICAgdXBsb2FkZWQgKz0gMTtcbiAgICAgICAgICAgICAgdXBkYXRlU3RhdHVzTGluZSgpO1xuICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICAgIGQoJ2F0dGVtcHRpbmcgdG8gdXBsb2FkIGFzc2V0OicsIGFydGlmYWN0UGF0aCk7XG4gICAgICAgICAgY29uc3QgYXJ0aWZhY3RGb3JtID0gbmV3IEZvcm1EYXRhKCk7XG4gICAgICAgICAgYXJ0aWZhY3RGb3JtLmFwcGVuZCgndG9rZW4nLCB0b2tlbik7XG4gICAgICAgICAgYXJ0aWZhY3RGb3JtLmFwcGVuZCgndmVyc2lvbicsIHBhY2thZ2VKU09OLnZlcnNpb24pO1xuICAgICAgICAgIGFydGlmYWN0Rm9ybS5hcHBlbmQoJ3BsYXRmb3JtJywgZXJzUGxhdGZvcm0obWFrZVJlc3VsdC5wbGF0Zm9ybSwgbWFrZVJlc3VsdC5hcmNoKSk7XG5cbiAgICAgICAgICAvLyBzZWUgaHR0cHM6Ly9naXRodWIuY29tL2Zvcm0tZGF0YS9mb3JtLWRhdGEvaXNzdWVzLzQyNlxuICAgICAgICAgIGNvbnN0IGZpbGVPcHRpb25zID0ge1xuICAgICAgICAgICAga25vd25MZW5ndGg6IGZzLnN0YXRTeW5jKGFydGlmYWN0UGF0aCkuc2l6ZSxcbiAgICAgICAgICB9O1xuICAgICAgICAgIGFydGlmYWN0Rm9ybS5hcHBlbmQoJ2ZpbGUnLCBmcy5jcmVhdGVSZWFkU3RyZWFtKGFydGlmYWN0UGF0aCksIGZpbGVPcHRpb25zKTtcblxuICAgICAgICAgIGF3YWl0IGF1dGhGZXRjaCgnYXBpL2Fzc2V0Jywge1xuICAgICAgICAgICAgbWV0aG9kOiAnUE9TVCcsXG4gICAgICAgICAgICBib2R5OiBhcnRpZmFjdEZvcm0sXG4gICAgICAgICAgICBoZWFkZXJzOiBhcnRpZmFjdEZvcm0uZ2V0SGVhZGVycygpLFxuICAgICAgICAgIH0pO1xuICAgICAgICAgIGQoJ3VwbG9hZCBzdWNjZXNzZnVsIGZvciBhc3NldDonLCBhcnRpZmFjdFBhdGgpO1xuICAgICAgICAgIHVwbG9hZGVkICs9IDE7XG4gICAgICAgICAgdXBkYXRlU3RhdHVzTGluZSgpO1xuICAgICAgICB9KVxuICAgICAgKTtcbiAgICB9XG4gIH1cbn1cblxuZXhwb3J0IHsgUHVibGlzaGVyRVJTLCBQdWJsaXNoZXJFUlNDb25maWcgfTtcbiJdLCJuYW1lcyI6WyJQdWJsaXNoZXJFUlNDb25maWciLCJkIiwiZGVidWciLCJmZXRjaEFuZENoZWNrU3RhdHVzIiwidXJsIiwiaW5pdCIsInJlc3VsdCIsImZldGNoIiwib2siLCJFcnJvciIsInN0YXR1cyIsImVyc1BsYXRmb3JtIiwicGxhdGZvcm0iLCJhcmNoIiwiUHVibGlzaGVyRVJTIiwiUHVibGlzaGVyQmFzZSIsInB1Ymxpc2giLCJtYWtlUmVzdWx0cyIsInNldFN0YXR1c0xpbmUiLCJjb25maWciLCJiYXNlVXJsIiwidXNlcm5hbWUiLCJwYXNzd29yZCIsImFwaSIsImFwaVBhdGgiLCJ0b2tlbiIsIm1ldGhvZCIsImJvZHkiLCJKU09OIiwic3RyaW5naWZ5IiwiaGVhZGVycyIsImpzb24iLCJhdXRoRmV0Y2giLCJvcHRpb25zIiwiQXV0aG9yaXphdGlvbiIsInZlcnNpb25zIiwiZmxhdm9yIiwibWFrZVJlc3VsdCIsInBhY2thZ2VKU09OIiwiYXJ0aWZhY3RzIiwiZmlsdGVyIiwiYXJ0aWZhY3RQYXRoIiwicGF0aCIsImJhc2VuYW1lIiwidG9Mb3dlckNhc2UiLCJleGlzdGluZ1ZlcnNpb24iLCJmaW5kIiwidmVyc2lvbiIsIm5hbWUiLCJjaGFubmVsIiwiaW5jbHVkZXMiLCJub3RlcyIsInVwbG9hZGVkIiwidXBkYXRlU3RhdHVzTGluZSIsImxlbmd0aCIsIlByb21pc2UiLCJhbGwiLCJtYXAiLCJleGlzdGluZ0Fzc2V0IiwiYXNzZXRzIiwiYXNzZXQiLCJhcnRpZmFjdEZvcm0iLCJGb3JtRGF0YSIsImFwcGVuZCIsImZpbGVPcHRpb25zIiwia25vd25MZW5ndGgiLCJmcyIsInN0YXRTeW5jIiwic2l6ZSIsImNyZWF0ZVJlYWRTdHJlYW0iLCJnZXRIZWFkZXJzIl0sIm1hcHBpbmdzIjoiOzs7OytCQXdKdUJBLENBQWtCOzs7ZUFBbEJBLE9BQWtCOzs7O0FBeEp4QixHQUFNLENBQU4sS0FBTTtBQUV5QixHQUFnQyxDQUFoQyxjQUFnQztBQUU5RCxHQUFPLENBQVAsTUFBTztBQUNKLEdBQVcsQ0FBWCxTQUFXO0FBQ2pCLEdBQVUsQ0FBVixRQUFVO0FBQ2lDLEdBQVksQ0FB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