@maka/meteor-sdk 0.2.5 → 0.2.7
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 +68 -0
- 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.7",
|
|
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,
|
|
@@ -731,6 +731,8 @@ var updateExistingNpmDirectory = async function (packageName, newPackageNpmDir,
|
|
|
731
731
|
npmTree.dependencies[name] = { version };
|
|
732
732
|
});
|
|
733
733
|
|
|
734
|
+
reconcileGitDependencies(npmTree, installedDependenciesTree);
|
|
735
|
+
|
|
734
736
|
let minInstalledTree;
|
|
735
737
|
try {
|
|
736
738
|
minInstalledTree = minimizeDependencyTree(installedDependenciesTree);
|
|
@@ -838,6 +840,72 @@ var updateExistingNpmDirectory = async function (packageName, newPackageNpmDir,
|
|
|
838
840
|
npmDependencies);
|
|
839
841
|
};
|
|
840
842
|
|
|
843
|
+
// Git-URL dependencies could never satisfy the strict string comparison
|
|
844
|
+
// in updateExistingNpmDirectory: Npm.depends declares the spec the
|
|
845
|
+
// author wrote (e.g. "git+https://github.com/org/repo#v1.2.3") while
|
|
846
|
+
// npm's lockfile records what it resolved (e.g.
|
|
847
|
+
// "git+ssh://git@github.com/org/repo.git#<commit-sha>"). The result was
|
|
848
|
+
// a FULL reinstall of the package's npm deps -- git clone included --
|
|
849
|
+
// on every single build, and during `run` the .npm/package churn could
|
|
850
|
+
// re-fire the file watcher, restarting the app in a loop ("ddp-server:
|
|
851
|
+
// updating npm dependencies ..." flapping forever).
|
|
852
|
+
//
|
|
853
|
+
// Reconcile before comparing: a declared git dep is satisfied by an
|
|
854
|
+
// installed copy from the same repository when the declared ref is
|
|
855
|
+
// consistent with what's installed -- a commit-sha ref must prefix the
|
|
856
|
+
// resolved sha, a "v1.2.3"/"1.2.3" tag must equal the installed
|
|
857
|
+
// package's own version, and a symbolic ref (branch) is accepted as-is
|
|
858
|
+
// (it can move upstream, but reinstalling every build is strictly
|
|
859
|
+
// worse; pin by tag or sha for reproducibility). On a match the
|
|
860
|
+
// declared version is rewritten to the installed resolved URL so the
|
|
861
|
+
// strict subtree comparison passes; on a mismatch (e.g. the tag was
|
|
862
|
+
// bumped) nothing is rewritten and the normal update runs.
|
|
863
|
+
function parseGitUrl(url) {
|
|
864
|
+
if (typeof url !== "string") {
|
|
865
|
+
return null;
|
|
866
|
+
}
|
|
867
|
+
const m = url.match(
|
|
868
|
+
/^git\+(?:https?|ssh|git|file):\/\/(?:[^@/]+@)?([^#?]+?)(?:\.git)?(?:#(.*))?$/i
|
|
869
|
+
);
|
|
870
|
+
if (! m) {
|
|
871
|
+
return null;
|
|
872
|
+
}
|
|
873
|
+
return { repo: m[1].toLowerCase(), ref: m[2] || null };
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
function reconcileGitDependencies(npmTree, installedTree) {
|
|
877
|
+
_.each(npmTree.dependencies, (dep, name) => {
|
|
878
|
+
const declared = parseGitUrl(dep.version);
|
|
879
|
+
if (! declared) {
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
const installed = installedTree.dependencies &&
|
|
883
|
+
installedTree.dependencies[name];
|
|
884
|
+
const resolved = installed && parseGitUrl(installed.resolved);
|
|
885
|
+
if (! resolved || resolved.repo !== declared.repo) {
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
const ref = declared.ref;
|
|
889
|
+
let refSatisfied;
|
|
890
|
+
if (! ref) {
|
|
891
|
+
refSatisfied = true;
|
|
892
|
+
} else if (/^[0-9a-f]{7,40}$/i.test(ref)) {
|
|
893
|
+
refSatisfied = !! resolved.ref &&
|
|
894
|
+
resolved.ref.toLowerCase().startsWith(ref.toLowerCase());
|
|
895
|
+
} else if (/^v?\d+(\.\d+)*([-+.].*)?$/.test(ref)) {
|
|
896
|
+
refSatisfied = !! installed.version &&
|
|
897
|
+
ref.replace(/^v/, "") === installed.version;
|
|
898
|
+
} else {
|
|
899
|
+
refSatisfied = true;
|
|
900
|
+
}
|
|
901
|
+
if (refSatisfied) {
|
|
902
|
+
dep.version = minimizeDependencyTree({
|
|
903
|
+
dependencies: { [name]: installed },
|
|
904
|
+
}).dependencies[name].version;
|
|
905
|
+
}
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
|
|
841
909
|
function isSubtreeOf(subsetTree, supersetTree, predicate) {
|
|
842
910
|
if (subsetTree === supersetTree) {
|
|
843
911
|
return true;
|
|
@@ -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);
|