@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 +19 -0
- package/package.json +8 -4
- package/src/Config.ts +34 -0
- package/src/PublisherERS.ts +153 -0
- package/test/PublisherERS_spec.ts +223 -0
- package/dist/Config.d.ts +0 -34
- package/dist/Config.d.ts.map +0 -1
- package/dist/Config.js +0 -6
- package/dist/PublisherERS.d.ts +0 -10
- package/dist/PublisherERS.d.ts.map +0 -1
- package/dist/PublisherERS.js +0 -150
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.
|
|
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.
|
|
22
|
-
"@electron-forge/shared-types": "6.0.
|
|
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
|
package/dist/Config.d.ts.map
DELETED
|
@@ -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
package/dist/PublisherERS.d.ts
DELETED
|
@@ -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"}
|
package/dist/PublisherERS.js
DELETED
|
@@ -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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
|