This repository was archived by the owner on Apr 16, 2026. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathpostinstall.js
More file actions
executable file
·387 lines (346 loc) · 12.4 KB
/
Copy pathpostinstall.js
File metadata and controls
executable file
·387 lines (346 loc) · 12.4 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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
// Copyright 2016 Martin Reinhardt
/*
* This simply downloads Galen
*/
'use strict';
const requestProgress = require('request-progress');
const replace = require('replace-in-file');
const Progress = require('progress');
const AdmZip = require('adm-zip');
const cp = require('child_process');
const fs = require('fs-extra');
const helper = require('./lib/helper');
const kew = require('kew');
const npmconf = require('npmconf');
const path = require('path');
const request = require('request');
const url = require('url');
const which = require('which');
const log = require('npmlog');
const cdnUrl = process.env.npm_config_galen_url || process.env.GALEN_CDNURL || 'https://github.com/galenframework/galen/releases/download/';
const downloadUrl = cdnUrl + '/galen-' + helper.version + '/galen-bin-' + helper.version + '.zip';
const originalPath = process.env.PATH;
// If the process exits without going through exit(), then we did not complete.
var validExit = false;
process.on('exit', function () {
if (!validExit) {
log.error('Install exited unexpectedly');
exit(1);
}
});
// NPM adds bin directories to the path, which will cause `which` to find the
// bin for this package not the actual galenframework bin. Also help out people who
// put ./bin on their path
process.env.PATH = helper.cleanPath(originalPath);
const libPath = path.join(__dirname, 'lib');
const pkgPath = path.join(libPath, 'galen');
let galenPath = null;
let tmpPath = null;
// If the user manually installed galen, we want
// to use the existing version.
//
// Do not re-use a manually-installed galen with
// a different version.
//
// Do not re-use an npm-installed galen, because
// that can lead to weird circular dependencies between
// local versions and global versions.
const whichDeferred = kew.defer();
which('galen', whichDeferred.makeNodeResolver());
whichDeferred.promise
.then((result) => {
galenPath = result;
// Horrible hack to avoid problems during global install. We check to see if
// the file `which` found is our own bin script.
if (galenPath.indexOf(path.join('npm', 'galenframework')) !== -1) {
log.info('Looks like an `npm install -g` on windows; unable to check for already installed version.');
throw new Error('Global install');
}
const contents = fs.readFileSync(galenPath, 'utf8');
if (/NPM_INSTALL_MARKER/.test(contents)) {
log.info('Looks like an `npm install -g`; unable to check for already installed version.');
throw new Error('Global install');
} else {
const checkVersionDeferred = kew.defer();
cp.execFile(galenPath, ['--version'], checkVersionDeferred.makeNodeResolver());
return checkVersionDeferred.promise;
}
})
.then((stdout) => {
const regex = /^Version: ([0-9\.]+)$/;
const result = stdout.trim().match(regex);
const version = result[1];
if (helper.version === version) {
writeLocationFile(galenPath);
log.info('galenframework is already installed at', galenPath + '.');
exit(0);
} else {
log.info('galenframework detected, but wrong version', stdout.trim(), '@', galenPath + '.');
downloadAndInstallGalen();
}
})
.fail(downloadAndInstallGalen);
function downloadAndInstallGalen() {
// Trying to use a local file failed, so initiate download and install
// steps instead.
const npmconfDeferred = kew.defer();
npmconf.load(npmconfDeferred.makeNodeResolver());
return npmconfDeferred.promise.then((conf) => {
tmpPath = findSuitableTempDirectory(conf);
const fileName = downloadUrl.split('/').pop();
const downloadedFile = path.join(tmpPath, fileName);
log.info('Running at platform: ' + process.platform);
// Start the install.
if (!fs.existsSync(downloadedFile)) {
log.info('Downloading', downloadUrl);
log.info('Saving to', downloadedFile);
return requestBinary(getRequestOptions(conf), downloadedFile);
} else {
log.info('Download already available at', downloadedFile);
return {
requestOptions: getRequestOptions(conf),
downloadedFile: downloadedFile
};
}
})
.then((response) => extractDownload(response.downloadedFile, response.requestOptions, false))
.then((extractedPath) => copyIntoPlace(extractedPath, pkgPath))
.then(() => {
const location = libPath;
writeLocationFile(location);
log.info('Done. galen binary available at ', location);
// Ensure executable is executable by all users
fs.chmodSync(location, '755');
fs.chmodSync(location + '/galen/galen', '755');
fs.chmodSync(location + '/galen/galen.bat', '755');
replace({
files: location + '/galen/galen.bat',
replace: 'com.galenframework.GalenMain %*',
with: 'com.galenframework.GalenMain %* -Djna.nosys=true'
}, (error, changedFiles) => {
//Catch errors
if (error) {
log.error('Error occurred:', error);
}
//List changed files
log.info('Modified files:', changedFiles.join(', '));
exit(0);
});
})
.fail(function (err) {
log.error('Galen installation failed', err, err.stack);
exit(1);
});
}
/**
* Save the destination directory back
* @param {string} location - path of the directory
*/
function writeLocationFile(location) {
log.info('Writing location.js file');
if (process.platform === 'win32') {
location = location.replace(/\\/g, '\\\\');
}
fs.writeFileSync(path.join(libPath, 'location.js'),
'module.exports.location = \'' + location + '\';');
}
/**
* Exit helper function
* @param {int} code - exit code
* @function
*/
function exit(code) {
validExit = true;
process.env.PATH = originalPath;
process.exit(code || 0);
}
/**
* Function to find an writable temp directory
* @param {object} npmConf - current NPM configuration
* @returns {string} representing suitable temp directory
* @function
*/
function findSuitableTempDirectory(npmConf) {
const now = Date.now();
const candidateTmpDirs = [
process.env.TMPDIR || process.env.TEMP || npmConf.get('tmp'),
path.join(process.cwd(), 'tmp',
'/tmp')
];
for (let i = 0; i < candidateTmpDirs.length; i++) {
const candidatePath = path.join(candidateTmpDirs[i], 'galenframework');
try {
fs.mkdirsSync(candidatePath, '0777');
// Make double sure we have 0777 permissions; some operating systems
// default umask does not allow write by default.
fs.chmodSync(candidatePath, '0777');
const testFile = path.join(candidatePath, now + '.tmp');
fs.writeFileSync(testFile, 'test');
fs.unlinkSync(testFile);
return candidatePath;
} catch (e) {
log.info(candidatePath, 'is not writable:', e.message);
}
}
log.error('Can not find a writable tmp directory.');
exit(1);
}
/**
* Create request opions object
* @param {object} conf - current NPM config
* @returns {{uri: string, encoding: null, followRedirect: boolean, headers: {}, strictSSL: *}}
* @function
*/
function getRequestOptions(conf) {
const strictSSL = conf.get('strict-ssl');
let options = {
uri: downloadUrl,
encoding: null, // Get response as a buffer
followRedirect: true, // The default download path redirects to a CDN URL.
headers: {},
strictSSL: strictSSL
};
let proxyUrl = conf.get('https-proxy') || conf.get('http-proxy') || conf.get('proxy');
if (proxyUrl) {
// Print using proxy
let proxy = url.parse(proxyUrl);
if (proxy.auth) {
// Mask password
proxy.auth = proxy.auth.replace(/:.*$/, ':******');
}
log.info('Using proxy ' + url.format(proxy));
// Enable proxy
options.proxy = proxyUrl;
// If going through proxy, use the user-agent string from the npm config
options.headers['User-Agent'] = conf.get('user-agent');
}
// Use certificate authority settings from npm
const ca = conf.get('ca');
if (ca) {
log.info('Using npmconf ca');
options.ca = ca;
}
return options;
}
/**
* Downloads binary file
* @param {object} requestOptions - to use for HTTP call
* @param {string} filePath - download URL
* @returns {*}
* @function
*/
function requestBinary(requestOptions, filePath) {
const deferred = kew.defer();
const writePath = filePath + '-download-' + Date.now();
log.info('Receiving...');
let bar = null;
requestProgress(request(requestOptions, (error, response, body) => {
log.info('');
if (!error && response.statusCode === 200) {
fs.writeFileSync(writePath, body);
log.info('Received ' + Math.floor(body.length / 1024) + 'K total.');
fs.renameSync(writePath, filePath);
deferred.resolve({
requestOptions: requestOptions,
downloadedFile: filePath
});
} else if (response) {
log.error('Error requesting archive.\n' +
'Status: ' + response.statusCode + '\n' +
'Request options: ' + JSON.stringify(requestOptions, null, 2) + '\n' +
'Response headers: ' + JSON.stringify(response.headers, null, 2) + '\n' +
'Make sure your network and proxy settings are correct.\n\n');
exit(1);
} else if (error && error.stack && error.stack.indexOf('SELF_SIGNED_CERT_IN_CHAIN') != -1) {
log.error('Error making request.');
exit(1);
} else if (error) {
log.error('Error making request.\n' + error.stack + '\n\n' +
'Please report this full log at https://github.com/hypery2k/galenframework-cli/issues');
exit(1);
} else {
log.error('Something unexpected happened, please report this full ' +
'log at https://github.com/hypery2k/galenframework-cli/issues');
exit(1);
}
})).on('progress', (state) => {
if (!bar) {
bar = new Progress(' [:bar] :percent :etas', { total: state.total, width: 40 });
}
bar.curr = state.received;
bar.tick(0);
});
return deferred.promise;
}
/**
* Extracts the given Archive
* @param {string} filePath - path of the ZIP archive to extract
* @param {object} requestOptions - request options for retry attempt
* @param {boolean} retry - set to true if it's already an retry attempt
* @returns {*} - path of the extracted archive content
* @function
*/
function extractDownload(filePath, requestOptions, retry) {
const deferred = kew.defer();
// extract to a unique directory in case multiple processes are
// installing and extracting at once
const extractedPath = filePath + '-extract-' + Date.now();
let options = { cwd: extractedPath };
fs.mkdirsSync(extractedPath, '0777');
// Make double sure we have 0777 permissions; some operating systems
// default umask does not allow write by default.
fs.chmodSync(extractedPath, '0777');
if (filePath.substr(-4) === '.zip') {
log.info('Extracting zip contents');
try {
let zip = new AdmZip(filePath);
zip.extractAllTo(extractedPath, true);
deferred.resolve(extractedPath);
} catch (err) {
log.error('Error extracting zip');
deferred.reject(err);
}
} else {
log.info('Extracting tar contents (via spawned process)');
cp.execFile('tar', ['jxf', filePath], options, function (err) {
if (err) {
if (!retry) {
log.info('Error during extracting. Trying to download again.');
fs.unlinkSync(filePath);
return requestBinary(requestOptions, filePath).then(function (downloadedFile) {
return extractDownload(downloadedFile, requestOptions, true);
});
} else {
deferred.reject(err);
log.error('Error extracting archive');
}
} else {
deferred.resolve(extractedPath);
}
});
}
return deferred.promise;
}
/**
* Helper function to move folder contents to target directory
* @param {string} extractedPath - source directory path
* @param {string} targetPath - target directory path
* @returns {string} {!Promise.<RESULT>} promise for chaing
* @function
*/
function copyIntoPlace(extractedPath, targetPath) {
log.info('Removing', targetPath);
return kew.nfcall(fs.remove, targetPath).then(function () {
// Look for the extracted directory, so we can rename it.
const files = fs.readdirSync(extractedPath);
for (let i = 0; i < files.length; i++) {
const file = path.join(extractedPath, files[i]);
if (fs.statSync(file).isDirectory() && file.indexOf(helper.version) !== -1) {
log.info('Copying extracted folder', file, '->', targetPath);
return kew.nfcall(fs.move, file, targetPath);
}
}
log.info('Could not find extracted file', files);
throw new Error('Could not find extracted file');
});
}