@anolilab/multi-semantic-release 1.0.0

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.
@@ -0,0 +1,388 @@
1
+ import { writeFileSync } from "node:fs";
2
+
3
+ import { isEqual, isObject, transform } from "lodash-es";
4
+ import semver from "semver";
5
+
6
+ import getManifest from "./get-manifest.js";
7
+ import logger from "./logger.js";
8
+ import { getHighestVersion, getLatestVersion } from "./utils/get-version.js";
9
+ import recognizeFormat from "./utils/recognize-format.js";
10
+
11
+ const { debug } = logger.withScope("msr:updateDeps");
12
+
13
+ /**
14
+ * Resolve next prerelease comparing bumped tags versions with last version.
15
+ *
16
+ * @param {string|null} latestTag Last released tag from branch or null if non-existent.
17
+ * @param {string} lastVersion Last version released.
18
+ * @param {string} packagePreRelease Prerelease tag from package to-be-released.
19
+ * @returns {string} Next pkg version.
20
+ * @internal
21
+ */
22
+ const _nextPreHighestVersion = (latestTag, lastVersion, packagePreRelease) => {
23
+ const bumpFromTags = latestTag ? semver.inc(latestTag, "prerelease", packagePreRelease) : null;
24
+ const bumpFromLast = semver.inc(lastVersion, "prerelease", packagePreRelease);
25
+
26
+ return bumpFromTags ? getHighestVersion(bumpFromLast, bumpFromTags) : bumpFromLast;
27
+ };
28
+
29
+ /**
30
+ * Resolve next prerelease special cases: highest version from tags or major/minor/patch.#
31
+ *
32
+ * @param {Array<string>} tags - if non-empty, we will use these tags as part fo the comparison
33
+ * @param {string} lastVersionForCurrentMultiRelease Last package version released from multi-semantic-release
34
+ * @param {string} packageNextType Next type evaluated for the next package type.
35
+ * @param {string} packagePreRelease Package prerelease suffix.
36
+ * @returns {string|undefined} Next pkg version.
37
+ * @internal
38
+ */
39
+ const _nextPreVersionCases = (tags, lastVersionForCurrentMultiRelease, packageNextType, packagePreRelease) => {
40
+ // Case 1: Normal release on last version and is now converted to a prerelease
41
+ if (!semver.prerelease(lastVersionForCurrentMultiRelease)) {
42
+ const { major, minor, patch } = semver.parse(lastVersionForCurrentMultiRelease);
43
+
44
+ return `${semver.inc(`${major}.${minor}.${patch}`, packageNextType || "patch")}-${packagePreRelease}.1`;
45
+ }
46
+
47
+ // Case 2: Validates version with tags
48
+ const latestTag = getLatestVersion(tags, true);
49
+
50
+ return _nextPreHighestVersion(latestTag, lastVersionForCurrentMultiRelease, packagePreRelease);
51
+ };
52
+
53
+ /**
54
+ * Get dependent release type by recursive scanning and updating pkg deps.
55
+ *
56
+ * @param {Package} package_ The package with local deps to check.
57
+ * @param {string} bumpStrategy Dependency resolution strategy: override, satisfy, inherit.
58
+ * @param {string} releaseStrategy Release type triggered by deps updating: patch, minor, major, inherit.
59
+ * @param {Package[]} ignore Packages to ignore (to prevent infinite loops).
60
+ * @param {string} prefix Dependency version prefix to be attached if `bumpStrategy='override'`. ^ | ~ | '' (defaults to empty string)
61
+ * @returns {string|undefined} Returns the highest release type if found, undefined otherwise
62
+ * @internal
63
+ */
64
+ const getDependentRelease = (package_, bumpStrategy, releaseStrategy, ignore, prefix) => {
65
+ const severityOrder = ["patch", "minor", "major"];
66
+ const { localDeps, manifest = {} } = package_;
67
+ const lastVersion = package_._lastRelease && package_._lastRelease.version;
68
+ const { dependencies = {}, devDependencies = {}, optionalDependencies = {}, peerDependencies = {} } = manifest;
69
+ const scopes = [dependencies, devDependencies, peerDependencies, optionalDependencies];
70
+ const bumpDependency = (scope, name, nextVersion) => {
71
+ // eslint-disable-next-line security/detect-object-injection
72
+ const currentVersion = scope[name];
73
+
74
+ if (!nextVersion || !currentVersion) {
75
+ return false;
76
+ }
77
+
78
+ // eslint-disable-next-line no-use-before-define
79
+ const resolvedVersion = resolveNextVersion(currentVersion, nextVersion, bumpStrategy, prefix);
80
+
81
+ if (currentVersion !== resolvedVersion) {
82
+ // eslint-disable-next-line no-param-reassign,security/detect-object-injection
83
+ scope[name] = resolvedVersion;
84
+
85
+ return true;
86
+ }
87
+
88
+ return false;
89
+ };
90
+
91
+ return (
92
+ localDeps
93
+ .filter((p) => !ignore.includes(p))
94
+ // eslint-disable-next-line unicorn/no-array-reduce
95
+ .reduce((releaseType, p) => {
96
+ // Has changed if...
97
+ // 1. Any local dep package itself has changed
98
+ // 2. Any local dep package has local deps that have changed.
99
+ // eslint-disable-next-line no-use-before-define
100
+ const nextType = resolveReleaseType(p, bumpStrategy, releaseStrategy, [...ignore, package_], prefix);
101
+ const nextVersion = nextType
102
+ ? // Update the nextVersion only if there is a next type to be bumped
103
+
104
+ p._preRelease
105
+ ? // eslint-disable-next-line no-use-before-define
106
+ getNextPreVersion(p)
107
+ : // eslint-disable-next-line no-use-before-define
108
+ getNextVersion(p)
109
+ : // Set the nextVersion fallback to the last local dependency package last version
110
+ p._lastRelease && p._lastRelease.version;
111
+
112
+ // 3. And this change should correspond to the manifest updating rule.
113
+ const requireRelease = scopes
114
+ // eslint-disable-next-line unicorn/no-array-reduce
115
+ .reduce((result, scope) => bumpDependency(scope, p.name, nextVersion) || result, !lastVersion);
116
+
117
+ return requireRelease && severityOrder.indexOf(nextType) > severityOrder.indexOf(releaseType) ? nextType : releaseType;
118
+ }, undefined)
119
+ );
120
+ };
121
+
122
+ /**
123
+ * Substitute "workspace:" in currentVersion
124
+ * See:
125
+ * {@link https://yarnpkg.com/features/workspaces#publishing-workspaces}
126
+ * {@link https://pnpm.io/workspaces#publishing-workspace-packages}
127
+ *
128
+ * @param {string} currentVersion Current version, may start with "workspace:"
129
+ * @param {string} nextVersion Next version
130
+ * @returns {string} current version without "workspace:"
131
+ */
132
+ const substituteWorkspaceVersion = (currentVersion, nextVersion) => {
133
+ if (currentVersion.startsWith("workspace:")) {
134
+ // eslint-disable-next-line regexp/optimal-quantifier-concatenation
135
+ const [, range, caret] = /^workspace:(([\^~*])?.*)$/u.exec(currentVersion);
136
+
137
+ return caret === range ? (caret === "*" ? nextVersion : caret + nextVersion) : range;
138
+ }
139
+
140
+ return currentVersion;
141
+ };
142
+
143
+ // eslint-disable-next-line no-secrets/no-secrets
144
+ // https://gist.github.com/Yimiprod/7ee176597fef230d1451
145
+ const difference = (object, base) =>
146
+ transform(object, (result, value, key) => {
147
+ // eslint-disable-next-line security/detect-object-injection
148
+ if (!isEqual(value, base[key])) {
149
+ // eslint-disable-next-line security/detect-object-injection,no-param-reassign
150
+ result[key] = isObject(value) && isObject(base[key]) ? difference(value, base[key]) : `${base[key]} → ${value}`;
151
+ }
152
+ });
153
+
154
+ /**
155
+ * Clarify what exactly was changed in manifest file.
156
+ * @param {object} actualManifest manifest object
157
+ * @param {string} path manifest path
158
+ * @returns {boolean} has changed or not
159
+ * @internal
160
+ */
161
+ const auditManifestChanges = (actualManifest, path) => {
162
+ const debugPrefix = `[${actualManifest.name}]`;
163
+ const oldManifest = getManifest(path);
164
+ const depScopes = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];
165
+ // eslint-disable-next-line unicorn/no-array-reduce
166
+ const changes = depScopes.reduce((result, scope) => {
167
+ // eslint-disable-next-line security/detect-object-injection
168
+ const diff = difference(actualManifest[scope], oldManifest[scope]);
169
+
170
+ if (Object.keys(diff).length > 0) {
171
+ // eslint-disable-next-line security/detect-object-injection,no-param-reassign
172
+ result[scope] = diff;
173
+ }
174
+
175
+ return result;
176
+ }, {});
177
+
178
+ debug(debugPrefix, "package.json path=", path);
179
+
180
+ if (Object.keys(changes).length > 0) {
181
+ debug(debugPrefix, "changes=", changes);
182
+
183
+ return true;
184
+ }
185
+
186
+ debug(debugPrefix, "no deps changes");
187
+
188
+ return false;
189
+ };
190
+
191
+ /**
192
+ * Resolve next package version.
193
+ *
194
+ * @param {Package} package_ Package object.
195
+ * @returns {string|undefined} Next pkg version.
196
+ * @internal
197
+ */
198
+ export const getNextVersion = (package_) => {
199
+ const lastVersion = package_._lastRelease && package_._lastRelease.version;
200
+
201
+ return lastVersion && typeof package_._nextType === "string" ? semver.inc(lastVersion, package_._nextType) : lastVersion || "1.0.0";
202
+ };
203
+
204
+ /**
205
+ * Resolve the package version from a tag
206
+ *
207
+ * @param {Package} package_ Package object.
208
+ * @param {string} tag The tag containing the version to resolve
209
+ * @returns {string|null} The version of the package or null if no tag was passed
210
+ * @internal
211
+ */
212
+ export const getVersionFromTag = (package_, tag) => {
213
+ if (!package_.name) {
214
+ return tag || null;
215
+ }
216
+
217
+ if (!tag) {
218
+ return null;
219
+ }
220
+
221
+ // TODO inherit semantic-release/lib/branches/get-tags.js
222
+ const stringMatch = tag.match(/\d.\d.\d[^+]*/u);
223
+ return stringMatch && stringMatch[0] && semver.valid(stringMatch[0]) ? stringMatch[0] : null;
224
+ };
225
+
226
+ /**
227
+ * Parse the prerelease tag from a semver version.
228
+ *
229
+ * @param {string} version Semver version in a string format.
230
+ * @returns {string|null} preReleaseTag Version prerelease tag or null.
231
+ * @internal
232
+ */
233
+ export const getPreReleaseTag = (version) => {
234
+ const parsed = semver.parse(version);
235
+
236
+ if (!parsed) {
237
+ return null;
238
+ }
239
+
240
+ return parsed.prerelease[0] || null;
241
+ };
242
+
243
+ /**
244
+ * Resolve next package version on prereleases.
245
+ *
246
+ * Will resolve highest next version of either:
247
+ *
248
+ * 1. The last release for the package during this multi-release cycle
249
+ * 2. (if tag options provided):
250
+ * a. the highest increment of the tags array provided
251
+ * b. the highest increment of the gitTags for the prerelease
252
+ *
253
+ *
254
+ * @param {Package} package_ Package object.
255
+ * @returns {string|undefined} Next pkg version.
256
+ * @internal
257
+ */
258
+ export const getNextPreVersion = (package_) => {
259
+ // Note: this is only set is a current multi-semantic-release released
260
+ const lastVersionForCurrentRelease = package_._lastRelease && package_._lastRelease.version;
261
+
262
+ const lastPreReleaseTag = getPreReleaseTag(lastVersionForCurrentRelease);
263
+ const isNewPreReleaseTag = lastPreReleaseTag && lastPreReleaseTag !== package_._preRelease;
264
+
265
+ return isNewPreReleaseTag || !lastVersionForCurrentRelease
266
+ ? `1.0.0-${package_._preRelease}.1`
267
+ : _nextPreVersionCases([], lastVersionForCurrentRelease, package_._nextType, package_._preRelease);
268
+ };
269
+
270
+ /**
271
+ * Resolve package release type taking into account the cascading dependency update.
272
+ *
273
+ * @param {Package} package_ Package object.
274
+ * @param {string|undefined} bumpStrategy Dependency resolution strategy: override, satisfy, inherit.
275
+ * @param {string|undefined} releaseStrategy Release type triggered by deps updating: patch, minor, major, inherit.
276
+ * @param {Package[]} ignore Packages to ignore (to prevent infinite loops).
277
+ * @param {string} prefix Dependency version prefix to be attached if `bumpStrategy='override'`. ^ | ~ | '' (defaults to empty string)
278
+ * @returns {string|undefined} Resolved release type.
279
+ * @internal
280
+ */
281
+ export const resolveReleaseType = (package_, bumpStrategy = "override", releaseStrategy = "patch", ignore = [], prefix = "") => {
282
+ // NOTE This fn also updates pkg deps, so it must be invoked anyway.
283
+ const dependentReleaseType = getDependentRelease(package_, bumpStrategy, releaseStrategy, ignore, prefix);
284
+
285
+ // Release type found by commitAnalyzer.
286
+ if (package_._nextType) {
287
+ return package_._nextType;
288
+ }
289
+
290
+ if (!dependentReleaseType) {
291
+ return undefined;
292
+ }
293
+
294
+ // Define release type for dependent package if any of its deps changes.
295
+ // `patch`, `minor`, `major` — strictly declare the release type that occurs when any dependency is updated.
296
+ // `inherit` — applies the "highest" release of updated deps to the package.
297
+ // For example, if any dep has a breaking change, `major` release will be applied to the all dependants up the chain.
298
+
299
+ // eslint-disable-next-line no-param-reassign
300
+ package_._nextType = releaseStrategy === "inherit" ? dependentReleaseType : releaseStrategy;
301
+
302
+ return package_._nextType;
303
+ };
304
+
305
+ /**
306
+ * Resolve next version of dependency.
307
+ *
308
+ * @param {string} currentVersion Current dep version
309
+ * @param {string} nextVersion Next release type: patch, minor, major
310
+ * @param {string|undefined} bumpStrategy Resolution strategy: inherit, override, satisfy
311
+ * @param {string} prefix Dependency version prefix to be attached if `bumpStrategy='override'`. ^ | ~ | '' (defaults to empty string)
312
+ * @returns {string} Next dependency version
313
+ * @internal
314
+ */
315
+ export const resolveNextVersion = (currentVersion, nextVersion, bumpStrategy = "override", prefix = "") => {
316
+ // handle cases of "workspace protocol" defined in yarn and pnpm workspace, whose version starts with "workspace:"
317
+ // eslint-disable-next-line no-param-reassign
318
+ currentVersion = substituteWorkspaceVersion(currentVersion, nextVersion);
319
+
320
+ // if strategy is ignore, return the current version
321
+ if (bumpStrategy === "ignore") {
322
+ return currentVersion;
323
+ }
324
+
325
+ // no change...
326
+ if (currentVersion === nextVersion) {
327
+ return currentVersion;
328
+ }
329
+
330
+ // Check the next pkg version against its current references.
331
+ // If it matches (`*` matches to any, `1.1.0` matches `1.1.x`, `1.5.0` matches to `^1.0.0` and so on)
332
+ // release will not be triggered, if not `override` strategy will be applied instead.
333
+ if ((bumpStrategy === "satisfy" || bumpStrategy === "inherit") && semver.satisfies(nextVersion, currentVersion)) {
334
+ return currentVersion;
335
+ }
336
+
337
+ // `inherit` will try to follow the current declaration version/range.
338
+ // `~1.0.0` + `minor` turns into `~1.1.0`, `1.x` + `major` gives `2.x`,
339
+ // but `1.x` + `minor` gives `1.x` so there will be no release, etc.
340
+ if (bumpStrategy === "inherit") {
341
+ const separator = ".";
342
+ const nextChunks = nextVersion.split(separator);
343
+ const currentChunks = currentVersion.split(separator);
344
+ // eslint-disable-next-line security/detect-object-injection
345
+ const resolvedChunks = currentChunks.map((chunk, index) => (nextChunks[index] ? chunk.replace(/\d+/u, nextChunks[index]) : chunk));
346
+
347
+ return resolvedChunks.join(separator);
348
+ }
349
+
350
+ // "override"
351
+ // By default next package version would be set as is for the all dependants.
352
+ return prefix + nextVersion;
353
+ };
354
+
355
+ /**
356
+ * Update pkg deps.
357
+ *
358
+ * @param {Package} package_ The package this function is being called on.
359
+ * @returns {undefined}
360
+ * @internal
361
+ */
362
+ export const updateManifestDeps = (package_) => {
363
+ const { manifest, path } = package_;
364
+ const { indent, trailingWhitespace } = recognizeFormat(manifest.__contents__);
365
+
366
+ // We need to bump pkg.version for correct yarn.lock update
367
+ // https://github.com/qiwi/multi-semantic-release/issues/58
368
+ manifest.version = package_._nextRelease.version || manifest.version;
369
+
370
+ // Loop through localDeps to verify release consistency.
371
+ package_.localDeps.forEach((d) => {
372
+ // Get version of dependency.
373
+ const release = d._nextRelease || d._lastRelease;
374
+
375
+ // Cannot establish version.
376
+ if (!release || !release.version) {
377
+ throw new Error(`Cannot release ${package_.name} because dependency ${d.name} has not been released yet`);
378
+ }
379
+ });
380
+
381
+ if (!auditManifestChanges(manifest, path)) {
382
+ return;
383
+ }
384
+
385
+ // Write package.json back out.
386
+ // eslint-disable-next-line security/detect-non-literal-fs-filename
387
+ writeFileSync(path, JSON.stringify(manifest, null, indent) + trailingWhitespace);
388
+ };
@@ -0,0 +1,23 @@
1
+ import { existsSync, lstatSync } from "node:fs";
2
+ import { Writable } from "node:stream";
3
+
4
+ import { add, checker } from "blork";
5
+ import { WritableStreamBuffer } from "stream-buffers";
6
+
7
+ // Get some checkers.
8
+ const isAbsolute = checker("absolute");
9
+
10
+ // Add a directory checker.
11
+ // eslint-disable-next-line security/detect-non-literal-fs-filename
12
+ add("directory", (v) => isAbsolute(v) && existsSync(v) && lstatSync(v).isDirectory(), "directory that exists in the filesystem");
13
+
14
+ // Add a writable stream checker.
15
+ add(
16
+ "stream",
17
+ // istanbul ignore next (not important)
18
+ (v) => v instanceof Writable || v instanceof WritableStreamBuffer,
19
+ "instance of stream.Writable or WritableStreamBuffer",
20
+ );
21
+
22
+ // eslint-disable-next-line simple-import-sort/exports
23
+ export { ValueError, check } from "blork";
@@ -0,0 +1,24 @@
1
+ import { isAbsolute, join, normalize } from "node:path";
2
+
3
+ import { check } from "./blork.js";
4
+
5
+ /**
6
+ * Normalize and make a path absolute, optionally using a custom CWD.
7
+ * Trims any trailing slashes from the path.
8
+ *
9
+ * @param {string} path The path to normalize and make absolute.
10
+ * @param {string} cwd=process.cwd() The CWD to prepend to the path to make it absolute.
11
+ * @returns {string} The absolute and normalized path.
12
+ *
13
+ * @internal
14
+ */
15
+ function cleanPath(path, cwd = process.cwd()) {
16
+ check(path, "path: path");
17
+ check(cwd, "cwd: absolute");
18
+
19
+ // Normalize, absolutify, and trim trailing slashes from the path.
20
+ return normalize(isAbsolute(path) ? path : join(cwd, path)).replace(/[/\\]+$/u, "");
21
+ }
22
+
23
+ // Exports.
24
+ export default cleanPath;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Lifted and tweaked from semantic-release because we follow how they bump their packages/dependencies.
3
+ * https://github.com/semantic-release/semantic-release/blob/master/lib/utils.js
4
+ */
5
+
6
+ import { gt, prerelease, rcompare } from "semver";
7
+
8
+ /**
9
+ * HOC that applies highest/lowest semver function.
10
+ * @param {Function} predicate High order function to be called.
11
+ * @param {string|undefined} version1 Version 1 to be compared with.
12
+ * @param {string|undefined} version2 Version 2 to be compared with.
13
+ * @returns {string|undefined} Highest or lowest version.
14
+ * @internal
15
+ */
16
+ const _selectVersionBy = (predicate, version1, version2) => {
17
+ if (predicate && version1 && version2) {
18
+ return predicate(version1, version2) ? version1 : version2;
19
+ }
20
+ return version1 || version2;
21
+ };
22
+
23
+ /**
24
+ * Gets highest semver function binding gt to the HOC selectVersionBy.
25
+ */
26
+ export const getHighestVersion = _selectVersionBy.bind(null, gt);
27
+
28
+ /**
29
+ * Retrieve the latest version from a list of versions.
30
+ * @param {array} versions Versions as string list.
31
+ * @param {boolean|undefined} withPrerelease Prerelease flag.
32
+ * @returns {string|undefined} Latest version.
33
+ * @internal
34
+ */
35
+ export function getLatestVersion(versions, withPrerelease) {
36
+ return versions.filter((version) => withPrerelease || !prerelease(version)).sort(rcompare)[0];
37
+ }
@@ -0,0 +1,21 @@
1
+ // eslint-disable-next-line you-dont-need-lodash-underscore/cast-array
2
+ import { castArray, pickBy } from "lodash-es";
3
+
4
+ const isNil = (value) => value == null;
5
+
6
+ const mergeConfig = (a = {}, b = {}) => {
7
+ return {
8
+ ...a,
9
+ // Remove `null` and `undefined` options so they can be replaced with default ones
10
+ ...pickBy(b, (option) => !isNil(option)),
11
+ // Treat nested objects differently as otherwise we'll loose undefined keys
12
+ deps: {
13
+ ...a.deps,
14
+ ...pickBy(b.deps, (option) => !isNil(option)),
15
+ },
16
+ // Treat arrays differently by merging them
17
+ ignorePackages: [...new Set([...castArray(a.ignorePackages || []), ...castArray(b.ignorePackages || [])])],
18
+ };
19
+ };
20
+
21
+ export default mergeConfig;
@@ -0,0 +1,25 @@
1
+ import detectIndent from "detect-indent";
2
+ import { detectNewline } from "detect-newline";
3
+
4
+ /**
5
+ * Information about the format of a file.
6
+ * @typedef FileFormat
7
+ * @property {string|number} indent Indentation characters
8
+ * @property {string} trailingWhitespace Trailing whitespace at the end of the file
9
+ */
10
+
11
+ /**
12
+ * Detects the indentation and trailing whitespace of a file.
13
+ *
14
+ * @param {string} contents contents of the file
15
+ * @returns {FileFormat} Formatting of the file
16
+ */
17
+ function recognizeFormat(contents) {
18
+ return {
19
+ indent: detectIndent(contents).indent,
20
+ trailingWhitespace: detectNewline(contents) || "",
21
+ };
22
+ }
23
+
24
+ // Exports.
25
+ export default recognizeFormat;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Converts a stream to an array
3
+ *
4
+ * @param {ReadStream} stream
5
+ * @returns {Promise<array>}
6
+ */
7
+ export default function streamToArray(stream) {
8
+ if (!stream.readable) {
9
+ return Promise.resolve([]);
10
+ }
11
+
12
+ // eslint-disable-next-line compat/compat
13
+ return new Promise((resolve, reject) => {
14
+ // stream is already ended
15
+ if (!stream.readable) {
16
+ resolve([]);
17
+
18
+ return;
19
+ }
20
+
21
+ let array = [];
22
+
23
+ function cleanup() {
24
+ array = null;
25
+
26
+ // eslint-disable-next-line no-use-before-define
27
+ stream.removeListener("data", onData);
28
+ // eslint-disable-next-line no-use-before-define
29
+ stream.removeListener("end", onEnd);
30
+ // eslint-disable-next-line no-use-before-define
31
+ stream.removeListener("error", onError);
32
+ // eslint-disable-next-line no-use-before-define
33
+ stream.removeListener("close", onClose);
34
+ }
35
+
36
+ function onData(document_) {
37
+ array.push(document_);
38
+ }
39
+
40
+ function onEnd() {
41
+ resolve(array);
42
+ cleanup();
43
+ }
44
+
45
+ function onError(error) {
46
+ reject(error);
47
+ cleanup();
48
+ }
49
+
50
+ function onClose() {
51
+ resolve(array);
52
+ cleanup();
53
+ }
54
+
55
+ stream.on("data", onData);
56
+ stream.on("end", onEnd);
57
+ stream.on("error", onEnd);
58
+ stream.on("close", onClose);
59
+ });
60
+ }