-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinstall.js
More file actions
188 lines (164 loc) · 5.87 KB
/
install.js
File metadata and controls
188 lines (164 loc) · 5.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
import fs from 'fs';
import url from 'url';
import path from 'path';
import https from 'https';
import logger from '@percy/logger';
import cp from 'child_process';
import { ProxyHttpsAgent, formatBytes } from '@percy/client/utils';
// Formats milliseconds as "MM:SS"
function formatTime(ms) {
let minutes = (ms / 1000 / 60).toString().split('.')[0].padStart(2, '0');
let seconds = (ms / 1000 % 60).toFixed().padStart(2, '0');
return `${minutes}:${seconds}`;
}
// Formats progress as ":prefix [:bar] :ratio :percent :eta"
function formatProgress(prefix, total, start, progress) {
let width = 20;
let ratio = progress === total ? 1 : Math.min(Math.max(progress / total, 0), 1);
let percent = Math.floor(ratio * 100).toFixed(0);
let barLen = Math.round(width * ratio);
let barContent = Array(Math.max(0, barLen + 1)).join('=') + (
Array(Math.max(0, width - barLen + 1)).join(' '));
let elapsed = Date.now() - start;
let eta = (ratio >= 1) ? 0 : elapsed * (total / progress - 1);
return (
`${prefix} [${barContent}] ` +
`${formatBytes(progress)}/${formatBytes(total)} ` +
`${percent}% ${formatTime(eta)}`
);
}
// Returns an item from the map keyed by the current platform
export function selectByPlatform(map) {
let { platform, arch } = process;
if (platform === 'win32' && arch === 'x64') platform = 'win64';
if (platform === 'darwin' && arch === 'arm64') platform = 'darwinArm';
return map[platform];
}
// Downloads and extracts an executable from a url into a local directory, returning the full path
// to the extracted binary. Skips installation if the executable already exists at the binary path.
export async function download({
name,
revision,
url,
extract,
directory,
executable
}) {
let outdir = path.join(directory, revision);
if (process.env.NODE_ENV === 'executable') {
if (outdir.charAt(0) === '/') {
outdir = outdir.replace('/', '');
}
}
let command = 'pwd';
let archive = path.join(outdir, decodeURIComponent(url.split('/').pop()));
if (process.env.NODE_ENV === 'executable') {
/* istanbul ignore next */
if (process.platform.startsWith('win')) {
command = 'cd';
}
outdir = outdir.replace('C:\\', '');
archive = archive.replace('C:\\', '');
}
let exec = path.join(outdir, executable);
if (!fs.existsSync(exec)) {
let log = logger('core:install');
let premsg = `Downloading ${name} ${revision}`;
log.progress(`${premsg}...`);
try {
// ensure the out directory exists
await fs.promises.mkdir(outdir, { recursive: true });
// download the file at the given URL
await new Promise((resolve, reject) => https.get(url, {
agent: new ProxyHttpsAgent() // allow proxied requests
}, response => {
// on failure, resume the response before rejecting
if (response.statusCode !== 200) {
response.resume();
reject(new Error(`Download failed: ${response.statusCode} - ${url}`));
return;
}
// log progress
if (log.shouldLog('info') && logger.stdout.isTTY) {
let total = parseInt(response.headers['content-length'], 10);
let start, progress;
response.on('data', chunk => {
start ??= Date.now();
progress = (progress ?? 0) + chunk.length;
log.progress(formatProgress(premsg, total, start, progress));
});
}
// pipe the response directly to a file
response.pipe(
fs.createWriteStream(archive)
.on('finish', resolve)
.on('error', reject)
);
}).on('error', reject));
if (process.env.NODE_ENV === 'executable') {
let output = cp.execSync(command, { encoding: 'utf-8' }).trim();
let prefix = null;
if (process.platform.startsWith('win')) {
prefix = '\\';
} else {
prefix = '/';
}
archive = output.concat(prefix, archive);
outdir = output.concat(prefix, outdir);
}
// extract the downloaded file
await extract(archive, outdir);
// log success
log.info(`Successfully downloaded ${name} ${revision}`);
} finally {
// always cleanup the archive
if (fs.existsSync(archive)) {
await fs.promises.unlink(archive);
}
}
}
// return the path to the executable
return exec;
}
// Installs a revision of Chromium to a local directory
export function chromium({
// default directory is within @percy/core package root
directory = path.resolve(url.fileURLToPath(import.meta.url), '../../.local-chromium'),
// default chromium revision by platform (see chromium.revisions)
revision = selectByPlatform(chromium.revisions)
} = {}) {
let extract = (i, o) => import('extract-zip').then(ex => ex.default(i, { dir: o }));
let url = (process.env.PERCY_CHROMIUM_BASE_URL || 'https://storage.googleapis.com/chromium-browser-snapshots/') +
selectByPlatform({
linux: `Linux_x64/${revision}/chrome-linux.zip`,
darwin: `Mac/${revision}/chrome-mac.zip`,
darwinArm: `Mac_Arm/${revision}/chrome-mac.zip`,
win64: `Win_x64/${revision}/chrome-win.zip`,
win32: `Win/${revision}/chrome-win.zip`
});
let executable = selectByPlatform({
linux: path.join('chrome-linux', 'chrome'),
win64: path.join('chrome-win', 'chrome.exe'),
win32: path.join('chrome-win', 'chrome.exe'),
darwin: path.join('chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium'),
darwinArm: path.join('chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium')
});
return download({
name: 'Chromium',
revision,
url,
extract,
directory,
executable
});
}
// default chromium revisions corresponds to v126.0.6478.184
chromium.revisions = {
linux: '1300309',
win64: '1300297',
win32: '1300295',
darwin: '1300293',
darwinArm: '1300314'
};
// export the namespace by default
export * as default from './install.js';