@maka/meteor-sdk 0.2.4 → 0.2.6
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/README.md +25 -1
- package/isopackets/combined/npm/node_modules/meteor/babel-compiler/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/boilerplate-generator/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/constraint-solver/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/ddp-client/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/ecmascript/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/ecmascript-runtime/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/ecmascript-runtime-client/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/ecmascript-runtime-server/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/fetch/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/inter-process-messaging/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/logging/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/logic-solver/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/meteor/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/modules/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/modules-runtime/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/npm-mongo/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/package-version-parser/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/promise/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/react-fast-refresh/node_modules/.package-lock.json +1 -1
- package/isopackets/combined/npm/node_modules/meteor/socket-stream-client/node_modules/.package-lock.json +1 -1
- package/package.json +1 -1
- package/src/console.js +48 -1
- package/src/index.js +10 -0
- package/src/run.js +11 -3
- package/tools/isobuild/meteor-npm.js +65 -6
- package/tools/runners/mongo-download.js +3 -0
- package/tools/utils/http-helpers.js +68 -15
package/README.md
CHANGED
|
@@ -94,7 +94,11 @@ await handle.stop(); // stops the proxy, then the app; no orphaned processes
|
|
|
94
94
|
|
|
95
95
|
**No Mongo lifecycle management.** Unlike `meteor run`, `createAppRunner()`
|
|
96
96
|
does not start or stop a `mongod` for you -- supply a running Mongo's
|
|
97
|
-
connection string via `mongoUrl`.
|
|
97
|
+
connection string via `mongoUrl`. For an app that doesn't use the `mongo`
|
|
98
|
+
package at all, pass `mongoUrl: null` explicitly: no `MONGO_URL` is set
|
|
99
|
+
for the app process. (An app that *does* load the `mongo` package will
|
|
100
|
+
crash at startup without one -- the explicit `null` is how you take
|
|
101
|
+
responsibility for that.)
|
|
98
102
|
|
|
99
103
|
There is no separate `stdout`/`stderr` event -- all app log output is
|
|
100
104
|
observable via `sdk.on("log", ...)` (see below).
|
|
@@ -191,6 +195,26 @@ regardless of whether it hangs, or set `METEOR_MODERN='{"watcher":false}'`
|
|
|
191
195
|
to use the plain polling watcher instead, which doesn't exhibit either
|
|
192
196
|
symptom.
|
|
193
197
|
|
|
198
|
+
### `setConsoleOutput(enabled)`
|
|
199
|
+
|
|
200
|
+
By default the tool's Console renders informational output to this
|
|
201
|
+
process's stdout/stderr *and* emits it as `log` events. An embedder
|
|
202
|
+
with its own progress UI (spinners, status lines) can call
|
|
203
|
+
`sdk.setConsoleOutput(false)` to suppress the terminal rendering --
|
|
204
|
+
newline-terminated Console writes landing between spinner redraws
|
|
205
|
+
garble the terminal otherwise -- and paint the emitted events itself.
|
|
206
|
+
Scoping it to a phase (off around a build, back on after) is the
|
|
207
|
+
intended use; events keep flowing either way.
|
|
208
|
+
|
|
209
|
+
### `setProgressDisplay(enabled)`
|
|
210
|
+
|
|
211
|
+
The tool's own interactive progress bar (the classic `meteor run`
|
|
212
|
+
"Building the application" bar) is disabled by default for embedders.
|
|
213
|
+
`sdk.setProgressDisplay(true)` turns it back on for long build phases --
|
|
214
|
+
it renders only when this process's stdout is a real TTY, and falls back
|
|
215
|
+
to no display otherwise, so it's always safe to call. Turn it off again
|
|
216
|
+
with `false` when your own UI takes over the terminal.
|
|
217
|
+
|
|
194
218
|
### Events
|
|
195
219
|
|
|
196
220
|
```js
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maka/meteor-sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "Programmatic Node API over the Meteor build tool (bundle/run/create/etc.) -- for driving Meteor's build system from plain Node code instead of shelling out to a meteor CLI (there isn't one anymore; see README.md). \"dependencies\" is generated by scripts/write-tool-package-json.js from scripts/dev-bundle-tool-package.js at the monorepo root -- do not hand-edit that field, edit that file and re-run the script instead.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"exports": {
|
package/src/console.js
CHANGED
|
@@ -52,6 +52,13 @@ function patch() {
|
|
|
52
52
|
}
|
|
53
53
|
Console[level] = function (...args) {
|
|
54
54
|
emitter.emit("log", { level, args });
|
|
55
|
+
if (!terminalOutput) {
|
|
56
|
+
// The embedder has claimed the terminal (setConsoleOutput(false)):
|
|
57
|
+
// events still flow above, but the tool's own stdout/stderr
|
|
58
|
+
// rendering is suppressed so it can't collide with the
|
|
59
|
+
// embedder's spinners/progress UI.
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
55
62
|
return original.apply(Console, args);
|
|
56
63
|
};
|
|
57
64
|
});
|
|
@@ -60,4 +67,44 @@ function patch() {
|
|
|
60
67
|
return emitter;
|
|
61
68
|
}
|
|
62
69
|
|
|
63
|
-
|
|
70
|
+
// Whether the tool's Console also renders to this process's
|
|
71
|
+
// stdout/stderr (the default), in addition to emitting "log" events.
|
|
72
|
+
// Embedders that render their own progress UI (spinners, status lines)
|
|
73
|
+
// can turn this off -- newline-terminated Console writes landing between
|
|
74
|
+
// spinner redraws otherwise garble the terminal -- and paint the emitted
|
|
75
|
+
// events however they like. Toggle freely: scoping it to a phase (off
|
|
76
|
+
// around a build, back on afterward) is the intended use.
|
|
77
|
+
let terminalOutput = true;
|
|
78
|
+
function setConsoleOutput(enabled) {
|
|
79
|
+
terminalOutput = !!enabled;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Opt back in to the tool's own interactive progress bar (the classic
|
|
83
|
+
// `meteor run` "Building web.browser [==== ]" display), which patch()
|
|
84
|
+
// disables by default for embedders. Only actually renders a bar when
|
|
85
|
+
// this process's stdout is a real TTY -- otherwise the tool falls back
|
|
86
|
+
// to its no-op display on its own, so this is always safe to call.
|
|
87
|
+
// The ordering below matters: setPretty()/enableProgressDisplay()
|
|
88
|
+
// construct a fresh ProgressDisplay (which re-reads METEOR_HEADLESS,
|
|
89
|
+
// set by setHeadlessEnv()), so setHeadless(false) must come last to
|
|
90
|
+
// propagate onto the newly built display.
|
|
91
|
+
function setProgressDisplay(enabled) {
|
|
92
|
+
const { Console } = require("../tools/console/console.js");
|
|
93
|
+
if (enabled) {
|
|
94
|
+
Console.setPretty(true);
|
|
95
|
+
Console.enableProgressDisplay(true);
|
|
96
|
+
Console.setHeadless(false);
|
|
97
|
+
} else {
|
|
98
|
+
Console.enableProgressDisplay(false);
|
|
99
|
+
Console.setPretty(false);
|
|
100
|
+
Console.setHeadless(true);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = {
|
|
105
|
+
setHeadlessEnv,
|
|
106
|
+
patch,
|
|
107
|
+
emitter,
|
|
108
|
+
setConsoleOutput,
|
|
109
|
+
setProgressDisplay,
|
|
110
|
+
};
|
package/src/index.js
CHANGED
|
@@ -46,4 +46,14 @@ module.exports = {
|
|
|
46
46
|
consoleAdapter.emitter.off(event, listener);
|
|
47
47
|
return module.exports;
|
|
48
48
|
},
|
|
49
|
+
|
|
50
|
+
// Suppress (false) or restore (true, the default) the tool Console's
|
|
51
|
+
// own terminal rendering. "log" events keep flowing either way --
|
|
52
|
+
// embedders with their own progress UI turn this off around a phase
|
|
53
|
+
// and paint the events themselves. See README.
|
|
54
|
+
setConsoleOutput: consoleAdapter.setConsoleOutput,
|
|
55
|
+
|
|
56
|
+
// Opt back in to the tool's own interactive progress bar (disabled by
|
|
57
|
+
// default for embedders); renders only when stdout is a TTY. See README.
|
|
58
|
+
setProgressDisplay: consoleAdapter.setProgressDisplay,
|
|
49
59
|
};
|
package/src/run.js
CHANGED
|
@@ -27,10 +27,15 @@ async function createAppRunner(project, options) {
|
|
|
27
27
|
assertInitialized();
|
|
28
28
|
|
|
29
29
|
options = options || {};
|
|
30
|
-
|
|
30
|
+
// mongoUrl: null is a deliberate "this app doesn't use the mongo
|
|
31
|
+
// package" (no MONGO_URL is set for the app process); undefined still
|
|
32
|
+
// means the caller forgot.
|
|
33
|
+
if (options.mongoUrl === undefined) {
|
|
31
34
|
throw new MeteorSdkError(
|
|
32
35
|
"createAppRunner() requires options.mongoUrl -- meteor-sdk doesn't " +
|
|
33
|
-
"manage a mongod process for you (see README.md)"
|
|
36
|
+
"manage a mongod process for you (see README.md). Pass " +
|
|
37
|
+
"mongoUrl: null explicitly for an app that doesn't use the mongo " +
|
|
38
|
+
"package at all."
|
|
34
39
|
);
|
|
35
40
|
}
|
|
36
41
|
|
|
@@ -64,7 +69,10 @@ async function createAppRunner(project, options) {
|
|
|
64
69
|
projectContext: project._projectContext,
|
|
65
70
|
port: appPort,
|
|
66
71
|
listenHost: options.appHost,
|
|
67
|
-
|
|
72
|
+
// || undefined: a null mongoUrl must not reach the app's environment
|
|
73
|
+
// (env.MONGO_URL = null would be stringified to "null" by spawn;
|
|
74
|
+
// undefined-valued env keys are dropped instead).
|
|
75
|
+
mongoUrl: options.mongoUrl || undefined,
|
|
68
76
|
oplogUrl: options.oplogUrl,
|
|
69
77
|
rootUrl,
|
|
70
78
|
proxy,
|
|
@@ -957,6 +957,36 @@ const npmUserConfigFile = files.pathJoin(
|
|
|
957
957
|
"meteor-npm-userconfig"
|
|
958
958
|
);
|
|
959
959
|
|
|
960
|
+
// The npm major version of the system npm these installs spawn --
|
|
961
|
+
// resolved once per process. Needed because npm 12 changed script
|
|
962
|
+
// execution policy: install-time lifecycle scripts are SKIPPED unless
|
|
963
|
+
// covered by allow-scripts, which silently breaks native-binding deps
|
|
964
|
+
// (@meteorjs/swc-core's postinstall never runs -> no binding -> builds
|
|
965
|
+
// fail much later). --dangerously-allow-all-scripts restores the
|
|
966
|
+
// pre-npm-12 behavior for these tool-controlled installs (the deps come
|
|
967
|
+
// from meteor's own package manifests, same trust model npm<=11 always
|
|
968
|
+
// had) -- but the flag can't be passed unconditionally: npm 12 hard-errors
|
|
969
|
+
// on flags it doesn't recognize, and vice versa older npms warn/err on
|
|
970
|
+
// unknown ones, so it's gated on the detected major.
|
|
971
|
+
let npmMajorVersionPromise = null;
|
|
972
|
+
function getNpmMajorVersion(commandToRun, npmPath, isWindows, env) {
|
|
973
|
+
if (!npmMajorVersionPromise) {
|
|
974
|
+
npmMajorVersionPromise = new Promise(function (resolve) {
|
|
975
|
+
const versionArgs = isWindows
|
|
976
|
+
? ['/c', npmPath, '--version']
|
|
977
|
+
: ['--version'];
|
|
978
|
+
require('child_process').execFile(
|
|
979
|
+
commandToRun, versionArgs, { env },
|
|
980
|
+
function (err, stdout) {
|
|
981
|
+
const major = err ? NaN : parseInt(String(stdout).trim(), 10);
|
|
982
|
+
resolve(Number.isNaN(major) ? 0 : major);
|
|
983
|
+
}
|
|
984
|
+
);
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
return npmMajorVersionPromise;
|
|
988
|
+
}
|
|
989
|
+
|
|
960
990
|
var runNpmCommand = meteorNpm.runNpmCommand =
|
|
961
991
|
Profile("meteorNpm.runNpmCommand", async function (args, cwd) {
|
|
962
992
|
import { getEnv } from "../utils/dev-bundle-env.js";
|
|
@@ -978,6 +1008,33 @@ Profile("meteorNpm.runNpmCommand", async function (args, cwd) {
|
|
|
978
1008
|
// http://nodejs.org/api/child_process.html#child_process_shell_requirements.
|
|
979
1009
|
|
|
980
1010
|
let commandToRun = npmPath;
|
|
1011
|
+
|
|
1012
|
+
const env = await getEnv({devBundle: toolRootDir});
|
|
1013
|
+
// Make sure we don't honor any user-provided configuration files.
|
|
1014
|
+
env.npm_config_userconfig = npmUserConfigFile;
|
|
1015
|
+
|
|
1016
|
+
// npm 12 skips uncovered install scripts (see getNpmMajorVersion's
|
|
1017
|
+
// comment); restore full script execution for the script-running
|
|
1018
|
+
// subcommands only.
|
|
1019
|
+
const npmMajor = await getNpmMajorVersion(
|
|
1020
|
+
isWindows ? (process.env.ComSpec || "cmd.exe") : npmPath,
|
|
1021
|
+
npmPath, isWindows, env
|
|
1022
|
+
);
|
|
1023
|
+
const runsScripts = ['install', 'i', 'ci', 'rebuild', 'update'].includes(args[0]);
|
|
1024
|
+
if (npmMajor >= 12 && runsScripts) {
|
|
1025
|
+
// Also restore git/tarball-URL dependency fetching: npm 12 defaults
|
|
1026
|
+
// allow-git and allow-remote to "none", but core packages
|
|
1027
|
+
// legitimately pin such deps (e.g. ddp-server's uWebSockets.js,
|
|
1028
|
+
// which upstream only distributes via GitHub) -- EALLOWGIT broke
|
|
1029
|
+
// every fresh machine's first build of ddp-server.
|
|
1030
|
+
args = [
|
|
1031
|
+
...args,
|
|
1032
|
+
'--dangerously-allow-all-scripts',
|
|
1033
|
+
'--allow-git=all',
|
|
1034
|
+
'--allow-remote=all',
|
|
1035
|
+
];
|
|
1036
|
+
}
|
|
1037
|
+
|
|
981
1038
|
if (isWindows) {
|
|
982
1039
|
args = ['/c', npmPath, ...args];
|
|
983
1040
|
commandToRun = process.env.ComSpec || "cmd.exe";
|
|
@@ -989,20 +1046,22 @@ Profile("meteorNpm.runNpmCommand", async function (args, cwd) {
|
|
|
989
1046
|
args.join(' ') + ' ...\n');
|
|
990
1047
|
}
|
|
991
1048
|
|
|
992
|
-
const env = await getEnv({devBundle: toolRootDir});
|
|
993
|
-
|
|
994
1049
|
const opts = {
|
|
995
1050
|
env: env,
|
|
996
|
-
maxBuffer: 10 * 1024 * 1024
|
|
1051
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1052
|
+
// stdin 'ignore', NOT the default open pipe: a child npm that decides
|
|
1053
|
+
// to prompt (npm 12's interactive allow-scripts confirmation, auth
|
|
1054
|
+
// prompts, ...) would otherwise block forever on a pipe nobody writes
|
|
1055
|
+
// to -- observed as a hard hang (^C-immune) during a fresh machine's
|
|
1056
|
+
// first `maka create` on an npm 12 box. With stdin closed, npm sees
|
|
1057
|
+
// a non-interactive session and warns/skips instead of waiting.
|
|
1058
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
997
1059
|
};
|
|
998
1060
|
|
|
999
1061
|
if (cwd) {
|
|
1000
1062
|
opts.cwd = files.convertToOSPath(cwd);
|
|
1001
1063
|
}
|
|
1002
1064
|
|
|
1003
|
-
// Make sure we don't honor any user-provided configuration files.
|
|
1004
|
-
env.npm_config_userconfig = npmUserConfigFile;
|
|
1005
|
-
|
|
1006
1065
|
return new Promise(function (resolve) {
|
|
1007
1066
|
require('child_process').execFile(
|
|
1008
1067
|
commandToRun, args, opts, function (err, stdout, stderr) {
|
|
@@ -197,6 +197,9 @@ exports.ensureMongod = async function () {
|
|
|
197
197
|
var buffer = await httpHelpers.getUrlWithResuming({
|
|
198
198
|
url: downloadInfo.url,
|
|
199
199
|
encoding: null,
|
|
200
|
+
// The GitHub release-asset URL (linux x64) always 302s to a storage
|
|
201
|
+
// host; without this the redirect stub itself would be "extracted".
|
|
202
|
+
followRedirects: true,
|
|
200
203
|
});
|
|
201
204
|
|
|
202
205
|
files.mkdir_p(files.pathDirname(cacheDir));
|
|
@@ -126,9 +126,13 @@ Object.assign(exports, {
|
|
|
126
126
|
// - Respects HTTP_PROXY/HTTPS_PROXY, sets a Meteor User-Agent, honors
|
|
127
127
|
// $CAFILE for a custom CA.
|
|
128
128
|
//
|
|
129
|
-
// - Never follows redirects (fetch redirect: "manual"):
|
|
130
|
-
// don't segregate cookies/headers by origin, and callers
|
|
131
|
-
// OAuth flow in auth.js depend on seeing the 302
|
|
129
|
+
// - Never follows redirects by default (fetch redirect: "manual"):
|
|
130
|
+
// redirects don't segregate cookies/headers by origin, and callers
|
|
131
|
+
// like the OAuth flow in auth.js depend on seeing the 302
|
|
132
|
+
// themselves. Credential-less GET downloads (e.g. a GitHub release
|
|
133
|
+
// asset, which always 302s to a storage host) can opt in with
|
|
134
|
+
// followRedirects: true (5 hops) or a hop count; session/auth
|
|
135
|
+
// headers are dropped on any cross-origin hop.
|
|
132
136
|
//
|
|
133
137
|
// - Options: url (or a bare url string instead of the object), method,
|
|
134
138
|
// headers, qs (object appended to the url as a query string),
|
|
@@ -281,19 +285,57 @@ Object.assign(exports, {
|
|
|
281
285
|
fetchBody = options.body;
|
|
282
286
|
}
|
|
283
287
|
|
|
288
|
+
// Opt-in redirect following -- see the doc block. Only safe for
|
|
289
|
+
// requests whose body (if any) isn't a one-shot stream.
|
|
290
|
+
var maxRedirects = 0;
|
|
291
|
+
if (options.followRedirects === true) {
|
|
292
|
+
maxRedirects = 5;
|
|
293
|
+
} else if (typeof options.followRedirects === "number" &&
|
|
294
|
+
options.followRedirects > 0) {
|
|
295
|
+
maxRedirects = options.followRedirects;
|
|
296
|
+
}
|
|
297
|
+
|
|
284
298
|
var res;
|
|
285
299
|
try {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
300
|
+
var currentUrl = url;
|
|
301
|
+
var currentMethod = options.method || 'GET';
|
|
302
|
+
var currentHeaders = headers;
|
|
303
|
+
for (var hop = 0; ; hop++) {
|
|
304
|
+
armIdleTimer();
|
|
305
|
+
res = await undici.fetch(currentUrl, {
|
|
306
|
+
method: currentMethod,
|
|
307
|
+
headers: currentHeaders,
|
|
308
|
+
body: fetchBody,
|
|
309
|
+
// See the redirect comment in this function's doc block.
|
|
310
|
+
redirect: 'manual',
|
|
311
|
+
signal: controller.signal,
|
|
312
|
+
dispatcher: buildDispatcher(currentUrl, undici),
|
|
313
|
+
...(bodyStream ? { duplex: 'half' } : null),
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
var redirectLocation = res.headers.get('location');
|
|
317
|
+
if (hop < maxRedirects && redirectLocation &&
|
|
318
|
+
[301, 302, 303, 307, 308].indexOf(res.status) !== -1) {
|
|
319
|
+
if (res.body) {
|
|
320
|
+
try { await res.body.cancel(); } catch (e) { /* best effort */ }
|
|
321
|
+
}
|
|
322
|
+
var nextUrl = new URL(redirectLocation, currentUrl).href;
|
|
323
|
+
if (new URL(nextUrl).origin !== new URL(url).origin) {
|
|
324
|
+
// Never leak Meteor-services credentials across origins.
|
|
325
|
+
currentHeaders = _.omit(currentHeaders,
|
|
326
|
+
'X-Meteor-Session', 'X-Meteor-Auth', 'Authorization', 'Cookie');
|
|
327
|
+
}
|
|
328
|
+
if (res.status === 303 ||
|
|
329
|
+
((res.status === 301 || res.status === 302) &&
|
|
330
|
+
currentMethod !== 'GET' && currentMethod !== 'HEAD')) {
|
|
331
|
+
currentMethod = 'GET';
|
|
332
|
+
fetchBody = undefined;
|
|
333
|
+
}
|
|
334
|
+
currentUrl = nextUrl;
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
297
339
|
|
|
298
340
|
// Response shape: the subset of the old request-package response
|
|
299
341
|
// object our callers actually read.
|
|
@@ -310,7 +352,7 @@ Object.assign(exports, {
|
|
|
310
352
|
var response = {
|
|
311
353
|
statusCode: res.status,
|
|
312
354
|
headers: headerObj,
|
|
313
|
-
request: { href:
|
|
355
|
+
request: { href: currentUrl },
|
|
314
356
|
};
|
|
315
357
|
|
|
316
358
|
// Stream the response body: to outputStream when given (body
|
|
@@ -493,6 +535,17 @@ Object.assign(exports, {
|
|
|
493
535
|
const href = response.request.href;
|
|
494
536
|
throw Error(`Could not get ${href}; server returned [${response.statusCode}]`);
|
|
495
537
|
}
|
|
538
|
+
// A download must never silently accept a redirect body as the
|
|
539
|
+
// payload (a GitHub release URL 302s; un-gzipping the redirect stub
|
|
540
|
+
// yields "unexpected end of file"). Callers that expect a redirect
|
|
541
|
+
// should pass followRedirects.
|
|
542
|
+
if (response.statusCode >= 300 && response.statusCode < 400) {
|
|
543
|
+
const href = response.request.href;
|
|
544
|
+
throw Error(
|
|
545
|
+
`Could not get ${href}; server redirected [${response.statusCode}] ` +
|
|
546
|
+
`to ${response.headers.location || "(no Location)"} -- pass ` +
|
|
547
|
+
`followRedirects to follow it`);
|
|
548
|
+
}
|
|
496
549
|
|
|
497
550
|
// Really end the stream if we got this far.
|
|
498
551
|
outputStream.end(true);
|