Skip to content

Commit 76465fb

Browse files
committed
fix(npm): avoid loading installer deps before they exist
- lazy-load node-fetch, cli-spinner, inquirer, and find-process in dist/npm/index.js - switch get-latest to built-in https so it can run without local node_modules - keep postinstall working when find-process is not yet installed - prevent npm global install from failing during prepare/postinstall in a clean checkout Signed-off-by: Ryan Swanson <ryan.swanson@loft.sh>
1 parent 32613ac commit 76465fb

1 file changed

Lines changed: 160 additions & 106 deletions

File tree

dist/npm/index.js

Lines changed: 160 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
#!/usr/bin/env node
22
const fs = require("fs");
3+
const https = require("https");
34
const path = require("path");
45
const execSync = require("child_process").execSync;
5-
const fetch = require("node-fetch");
6-
const Spinner = require("cli-spinner").Spinner;
7-
const inquirer = require('inquirer');
8-
const findProcess = require('find-process');
6+
7+
const getSpinner = () => require("cli-spinner").Spinner;
8+
const getInquirer = () => require("inquirer");
9+
const getFindProcess = () => require("find-process");
910

1011
const downloadPathTemplate =
1112
"https://github.com/devspace-sh/devspace/releases/download/v{{version}}/devspace-{{platform}}-{{arch}}";
@@ -80,43 +81,91 @@ const sanitizeVersion = function(version) {
8081

8182
const getLatestVersion = function (callback) {
8283
const releasesURL = "https://github.com/devspace-sh/devspace/releases/latest";
84+
https.get(releasesURL, { headers: requestHeaders }, function (res) {
85+
const redirectUrl = res.headers.location;
86+
const matches = redirectUrl && /\/tag\/(.*)$/.exec(redirectUrl);
8387

84-
fetch(releasesURL, { headers: requestHeaders, redirect: false })
85-
.then(function (res) {
86-
if (!res.ok) {
87-
console.error(
88-
"Error requesting URL " +
89-
releasesURL +
90-
" (Status Code: " +
91-
res.status +
92-
")"
93-
);
94-
process.exit(1);
95-
}
88+
if (!matches || matches.length !== 2) {
89+
console.error("Error requesting URL " + releasesURL);
90+
process.exit(1);
91+
}
9692

97-
const redirectUrl = res.url
98-
if (redirectUrl == null) {
99-
throw new Error('Error fetching latest version')
100-
}
93+
const latestVersion = matches[1].replace('v', '')
94+
if (latestVersion) {
95+
callback(latestVersion);
96+
} else {
97+
console.error("Unable to identify latest devspace version");
98+
process.exit(2);
99+
}
100+
}).on("error", function (err) {
101+
console.error("Error requesting URL " + releasesURL);
102+
console.error(err)
103+
process.exit(1);
104+
})
105+
};
101106

102-
const matches = /\/tag\/(.*)$/.exec(redirectUrl)
103-
if (!matches || matches.length !== 2) {
104-
throw new Error('Error fetching latest version')
105-
}
107+
const createSpinner = function (text) {
108+
try {
109+
const Spinner = getSpinner();
110+
const spinner = new Spinner("%s " + text);
111+
spinner.setSpinnerString("|/-\\");
112+
return spinner;
113+
} catch (err) {
114+
return {
115+
start: function () {
116+
console.log(text);
117+
},
118+
stop: function () { }
119+
};
120+
}
121+
};
106122

107-
const latestVersion = matches[1].replace('v', '')
108-
if (latestVersion) {
109-
callback(latestVersion);
110-
} else {
111-
console.error("Unable to identify latest devspace version");
112-
process.exit(2);
113-
}
114-
})
115-
.catch(function (err) {
116-
console.error("Error requesting URL " + releasesURL);
117-
console.error(err)
118-
process.exit(1);
119-
})
123+
const downloadFile = function (downloadPath, destination, callback) {
124+
let done = false;
125+
const finish = function (err, res) {
126+
if (done) {
127+
return;
128+
}
129+
130+
done = true;
131+
callback(err, res);
132+
};
133+
134+
https.get(downloadPath, { headers: requestHeaders }, function (res) {
135+
if (
136+
res.statusCode &&
137+
res.statusCode >= 300 &&
138+
res.statusCode < 400 &&
139+
res.headers.location
140+
) {
141+
res.resume();
142+
downloadFile(res.headers.location, destination, callback);
143+
return;
144+
}
145+
146+
if (res.statusCode !== 200) {
147+
finish(null, res);
148+
return;
149+
}
150+
151+
const writeStream = fs.createWriteStream(destination)
152+
.on("error", function (err) {
153+
finish(err);
154+
})
155+
.on("finish", function () {
156+
writeStream.close(function () {
157+
finish(null, res);
158+
});
159+
});
160+
161+
res.on("error", function (err) {
162+
finish(err);
163+
});
164+
165+
res.pipe(writeStream);
166+
}).on("error", function (err) {
167+
finish(err);
168+
});
120169
};
121170

122171
if (action === "update-version") {
@@ -311,6 +360,7 @@ let continueProcess = function (askRemoveGlobalFolder) {
311360
removeScripts(true);
312361

313362
if (askRemoveGlobalFolder && process.stdout.isTTY) {
363+
const inquirer = getInquirer();
314364
let removeGlobalFolder = function () {
315365
try {
316366
let homedir = require('os').homedir();
@@ -377,33 +427,34 @@ let continueProcess = function (askRemoveGlobalFolder) {
377427

378428
console.log("Download DevSpace CLI release: " + downloadPath + "\n");
379429

380-
const spinner = new Spinner(
381-
"%s Downloading DevSpace CLI... (this may take a minute)"
430+
const spinner = createSpinner(
431+
"Downloading DevSpace CLI... (this may take a minute)"
382432
);
383-
spinner.setSpinnerString("|/-\\");
384433
spinner.start();
385434

386-
let writeStream = fs
387-
.createWriteStream(binaryPath + downloadExtension)
388-
.on("error", function (err) {
389-
spinner.stop(true);
390-
console.error("Unable to write stream: " + err)
391-
showRootError(version);
392-
});
435+
downloadFile(
436+
downloadPath,
437+
binaryPath + downloadExtension,
438+
function (err, res) {
439+
if (err) {
440+
spinner.stop(true);
393441

394-
fetch(downloadPath, { headers: requestHeaders, encoding: null })
395-
.catch(function (err) {
396-
writeStream.end();
397-
spinner.stop(true);
398-
console.error("Error requesting URL: " + downloadPath);
399-
process.exit(6);
400-
})
401-
.then(function (res) {
402-
if (!res.ok) {
403-
writeStream.end();
442+
if (err.code === "EACCES" || err.code === "EPERM") {
443+
console.error("Unable to write stream: " + err)
444+
showRootError(version);
445+
return;
446+
}
447+
448+
console.error("Error requesting URL: " + downloadPath);
449+
console.error(err);
450+
process.exit(6);
451+
}
452+
453+
if (!res || res.statusCode !== 200) {
404454
spinner.stop(true);
405455

406-
if (res.status === 404) {
456+
if (res && res.statusCode === 404) {
457+
res.resume();
407458
console.error("Release version " + version + " not found.\n");
408459

409460
getLatestVersion(function (latestVersion) {
@@ -418,46 +469,44 @@ let continueProcess = function (askRemoveGlobalFolder) {
418469
}
419470
});
420471
} else {
472+
if (res) {
473+
res.resume();
474+
}
475+
421476
console.error(
422477
"Error requesting URL " +
423478
downloadPath +
424479
" (Status Code: " +
425-
res.status +
480+
(res && res.statusCode) +
426481
")"
427482
);
428-
console.error(res.statusText);
483+
console.error(res && res.statusMessage);
429484
process.exit(7);
430485
}
431-
} else {
432-
try {
433-
res.body.pipe(writeStream)
434-
.on("close", function () {
435-
writeStream.end();
436-
spinner.stop(true);
437-
438-
try {
439-
fs.chmodSync(binaryPath + downloadExtension, "0755");
440-
} catch (e) {
441-
console.error("Unable to chmod: " + e)
442-
showRootError(version);
443-
}
444-
445-
try {
446-
fs.renameSync(binaryPath + downloadExtension, binaryPath);
447-
} catch (e) {
448-
console.log(e);
449-
console.error("\nRenaming release binary failed. Please copy file manually:\n from: " + binaryPath + downloadExtension + "\n to: " + binaryPath + "\n");
450-
process.exit(8);
451-
}
452-
453-
removeScripts(true);
454-
});
455-
} catch (e) {
456-
console.error("Unable to write stream: " + e)
457-
showRootError(version);
458-
}
486+
487+
return;
459488
}
460-
});
489+
490+
spinner.stop(true);
491+
492+
try {
493+
fs.chmodSync(binaryPath + downloadExtension, "0755");
494+
} catch (e) {
495+
console.error("Unable to chmod: " + e)
496+
showRootError(version);
497+
}
498+
499+
try {
500+
fs.renameSync(binaryPath + downloadExtension, binaryPath);
501+
} catch (e) {
502+
console.log(e);
503+
console.error("\nRenaming release binary failed. Please copy file manually:\n from: " + binaryPath + downloadExtension + "\n to: " + binaryPath + "\n");
504+
process.exit(8);
505+
}
506+
507+
removeScripts(true);
508+
}
509+
);
461510
};
462511

463512
downloadRelease(version);
@@ -466,25 +515,30 @@ let continueProcess = function (askRemoveGlobalFolder) {
466515
}
467516

468517
if (process.ppid > 1) {
469-
findProcess('pid', process.ppid)
470-
.then(function (list) {
471-
if (list.length === 1 && list[0].ppid > 1) {
472-
findProcess('pid', list[0].ppid)
473-
.then(function (list) {
474-
if (list.length === 1 && /((npm-cli.js("|')\s+up(date)?)|(yarn.js("|')\s+(global\s+)?upgrade))\s+.*((\/)|(\\)|(\s))devspace((\/)|(\\)|(\s)|$)/.test(list[0].cmd)) {
475-
continueProcess(false);
476-
} else {
518+
try {
519+
const findProcess = getFindProcess();
520+
findProcess('pid', process.ppid)
521+
.then(function (list) {
522+
if (list.length === 1 && list[0].ppid > 1) {
523+
findProcess('pid', list[0].ppid)
524+
.then(function (list) {
525+
if (list.length === 1 && /((npm-cli.js("|')\s+up(date)?)|(yarn.js("|')\s+(global\s+)?upgrade))\s+.*((\/)|(\\)|(\s))devspace((\/)|(\\)|(\s)|$)/.test(list[0].cmd)) {
526+
continueProcess(false);
527+
} else {
528+
continueProcess(true);
529+
}
530+
}, function () {
477531
continueProcess(true);
478-
}
479-
}, function () {
480-
continueProcess(true);
481-
})
482-
} else {
532+
})
533+
} else {
534+
continueProcess(true);
535+
}
536+
}, function () {
483537
continueProcess(true);
484-
}
485-
}, function () {
486-
continueProcess(true);
487-
})
538+
})
539+
} catch (e) {
540+
continueProcess(true);
541+
}
488542
} else {
489543
continueProcess(true);
490544
}

0 commit comments

Comments
 (0)