@dbx-tools/core 0.6.65 → 0.6.67
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 +92 -1
- package/index.ts +3 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +3 -1
- package/lib/src/bin.d.ts +52 -0
- package/lib/src/bin.js +282 -0
- package/lib/src/process-lock.d.ts +88 -0
- package/lib/src/process-lock.js +355 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -2
- package/src/bin.ts +363 -0
- package/src/process-lock.ts +414 -0
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# @dbx-tools/core
|
|
2
2
|
|
|
3
|
-
Node-only core helpers for process execution and project
|
|
3
|
+
Node-only core helpers for binary installation, process execution, and project
|
|
4
|
+
discovery.
|
|
4
5
|
|
|
5
6
|
Import this package when code needs `node:child_process`, `node:fs`, or
|
|
6
7
|
`node:path`. Browser-safe utilities live in
|
|
@@ -16,6 +17,47 @@ Key features:
|
|
|
16
17
|
the current working directory.
|
|
17
18
|
- Safe filesystem stat and project naming helpers for CLIs and projen synth.
|
|
18
19
|
- YAML/JSON brand-context discovery and loading with shared Zod validation.
|
|
20
|
+
- Idempotent executable downloads with zip/tar extraction and atomic installs.
|
|
21
|
+
- Keyed mutual exclusion across the main thread and its worker threads.
|
|
22
|
+
|
|
23
|
+
## Install A Binary
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { join } from "node:path";
|
|
27
|
+
|
|
28
|
+
import { bin } from "@dbx-tools/core";
|
|
29
|
+
|
|
30
|
+
const executable = await bin.ensure("tool", releaseUrl, {
|
|
31
|
+
autoUnpackage: true,
|
|
32
|
+
minVersion: "1.2",
|
|
33
|
+
selector: ({ source }) => join(source, "tool"),
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`bin.ensure()` installs to `$HOME/.<name>/bin/<name>` and returns its `root`,
|
|
38
|
+
`binDir`, and executable `path`. An existing executable returns immediately, so
|
|
39
|
+
a URL resolver is only called when installation is necessary. Concurrent
|
|
40
|
+
callers use `processLock` with a check-lock-check-load sequence, preventing
|
|
41
|
+
duplicate downloads across the main thread and wired workers. Direct downloads
|
|
42
|
+
are selected by default. Zip, tar, tar.gz, and tgz archives can be unpacked
|
|
43
|
+
automatically; a single-file archive needs no selector, while a selector can
|
|
44
|
+
choose a binary from a larger archive. The selected file is normalized to mode
|
|
45
|
+
`0755` and must report an acceptable version before it is atomically moved into
|
|
46
|
+
place; the final renamed path runs the same validation again before returning.
|
|
47
|
+
|
|
48
|
+
Every candidate runs with `--version` before it is accepted. Set
|
|
49
|
+
`versionArgument` for a different argument and `minVersion` to require a partial
|
|
50
|
+
or complete numeric floor such as `1`, `1.2`, or `1.3.5`. The default parser
|
|
51
|
+
searches stdout first, then stderr. It accepts one to three numeric components
|
|
52
|
+
with common npm/Python suffixes such as `1.2.3-rc.1`, `1.2.3rc1`, or
|
|
53
|
+
`1.2.3.patchdev`; candidates with more components rank first, then the highest
|
|
54
|
+
numeric candidate wins. Supply `versionParser({ stdout, stderr })` for another
|
|
55
|
+
output format.
|
|
56
|
+
|
|
57
|
+
Successful installs log an info event with the source URL and destination path;
|
|
58
|
+
credentials, query parameters, and fragments are removed from the URL. Set
|
|
59
|
+
`LOG_LEVEL=debug` to trace access checks, version decisions, locking, downloads,
|
|
60
|
+
archive extraction, and selection.
|
|
19
61
|
|
|
20
62
|
## Load Brand Context
|
|
21
63
|
|
|
@@ -80,6 +122,50 @@ const argv = exec.shlex('pnpm exec prettier --write "README.md"');
|
|
|
80
122
|
`shlex()` is a small parser for command strings that need to become argv arrays.
|
|
81
123
|
Prefer explicit argv arrays when possible.
|
|
82
124
|
|
|
125
|
+
## Serialize Work Across Threads
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
import { processLock } from "@dbx-tools/core";
|
|
129
|
+
|
|
130
|
+
await processLock.withProcessLock(["cache", name], async () => {
|
|
131
|
+
if (!(await exists(name))) await build(name);
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`withProcessLock()` runs the callback while holding the named lock, releasing it
|
|
136
|
+
when the callback settles. Callers sharing a key are serialized; distinct keys
|
|
137
|
+
run concurrently. The key is any value with a stable identity - a string, a
|
|
138
|
+
`["invoice", id]` tuple, a config object - canonicalized by `object.toStableKey`,
|
|
139
|
+
so structure counts: `["invoice", 7]` and `"invoice_7"` are different locks.
|
|
140
|
+
|
|
141
|
+
Use it instead of a module-level promise chain when worker threads are involved.
|
|
142
|
+
A promise chain only serializes the thread it lives on, so a worker pool runs one
|
|
143
|
+
callback per thread; the coordinator here is shared, so the key admits one
|
|
144
|
+
callback for the whole process.
|
|
145
|
+
|
|
146
|
+
Workers opt in when they are constructed:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
import { Worker } from "node:worker_threads";
|
|
150
|
+
|
|
151
|
+
new Worker(url, processLock.processLockWorkerOptions({ workerData: { tenant } }));
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
That preserves your own `workerData` and `transferList`, and lets the worker lock
|
|
155
|
+
during module initialization. For a worker you did not construct, call
|
|
156
|
+
`attachProcessLock(worker)` and have the worker `await processLockAttached()`
|
|
157
|
+
first - the port arrives by message, so it is not available quite as early.
|
|
158
|
+
|
|
159
|
+
A thread that exits while holding a lock releases it, so a crashed worker cannot
|
|
160
|
+
wedge a key. Locks are held only as long as the callback runs, and an idle lock
|
|
161
|
+
never keeps the process from exiting.
|
|
162
|
+
|
|
163
|
+
The scope is the THREADS of one process - `withProcessLock` shares nothing with a
|
|
164
|
+
second `node` invocation or another replica. When the critical section spans a
|
|
165
|
+
deployment, use
|
|
166
|
+
[`@dbx-tools/postgres`](../postgres)'s `withAdvisoryLock`, which puts the arbiter
|
|
167
|
+
in PostgreSQL where every replica can see it.
|
|
168
|
+
|
|
83
169
|
## Discover Project Roots
|
|
84
170
|
|
|
85
171
|
```ts
|
|
@@ -97,6 +183,11 @@ basename. `project.stat()` returns `undefined` instead of throwing.
|
|
|
97
183
|
## Modules
|
|
98
184
|
|
|
99
185
|
- `exec` - async/sync process spawning, stdio handling, abort wiring, and shlex.
|
|
186
|
+
- `bin` - executable download, optional archive extraction, selection, and
|
|
187
|
+
atomic installation.
|
|
100
188
|
- `project` - root discovery, project naming, git-remote parsing, and safe
|
|
101
189
|
filesystem stat.
|
|
102
190
|
- `brand` - YAML/JSON discovery, parsing, validation, and asset path resolution.
|
|
191
|
+
- `processLock` - keyed mutual exclusion across the main thread and its workers,
|
|
192
|
+
with worker wiring (`processLockWorkerOptions`, `attachProcessLock`,
|
|
193
|
+
`processLockAttached`).
|
package/index.ts
CHANGED
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
// Regenerated from the exporting modules in ./src.
|
|
3
3
|
// Hand edits are overwritten on the next watch; this file is read-only.
|
|
4
4
|
|
|
5
|
+
export * as bin from "./src/bin.ts";
|
|
5
6
|
export * as brand from "./src/brand.ts";
|
|
6
7
|
export * as exec from "./src/exec.ts";
|
|
7
8
|
export * as file from "./src/file.ts";
|
|
9
|
+
export * as processLock from "./src/process-lock.ts";
|
|
8
10
|
export * as project from "./src/project.ts";
|
|
11
|
+
export type { BinContext, BinSelectionContext, BinSelector, BinVersionOutput, BinVersionParser, BinOptions, BinUrl } from "./src/bin.ts";
|
|
9
12
|
export { BrandContextSchema, defaultBrandContext, parseBrandContext, brandContextJsonSchema, brandContextPrompt } from "./src/brand.ts";
|
|
10
13
|
export type { BrandContext, BrandContextInput } from "./src/brand.ts";
|
|
11
14
|
export { COMMAND_NOT_FOUND_EXIT_CODE } from "./src/exec.ts";
|
package/lib/index.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
export * as bin from "./src/bin.ts";
|
|
1
2
|
export * as brand from "./src/brand.ts";
|
|
2
3
|
export * as exec from "./src/exec.ts";
|
|
3
4
|
export * as file from "./src/file.ts";
|
|
5
|
+
export * as processLock from "./src/process-lock.ts";
|
|
4
6
|
export * as project from "./src/project.ts";
|
|
7
|
+
export type { BinContext, BinSelectionContext, BinSelector, BinVersionOutput, BinVersionParser, BinOptions, BinUrl } from "./src/bin.ts";
|
|
5
8
|
export { BrandContextSchema, defaultBrandContext, parseBrandContext, brandContextJsonSchema, brandContextPrompt } from "./src/brand.ts";
|
|
6
9
|
export type { BrandContext, BrandContextInput } from "./src/brand.ts";
|
|
7
10
|
export { COMMAND_NOT_FOUND_EXIT_CODE } from "./src/exec.ts";
|
package/lib/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
// GENERATED by projen watch - DO NOT EDIT.
|
|
2
2
|
// Regenerated from the exporting modules in ./src.
|
|
3
3
|
// Hand edits are overwritten on the next watch; this file is read-only.
|
|
4
|
+
export * as bin from "./src/bin.js";
|
|
4
5
|
export * as brand from "./src/brand.js";
|
|
5
6
|
export * as exec from "./src/exec.js";
|
|
6
7
|
export * as file from "./src/file.js";
|
|
8
|
+
export * as processLock from "./src/process-lock.js";
|
|
7
9
|
export * as project from "./src/project.js";
|
|
8
10
|
export { BrandContextSchema, defaultBrandContext, parseBrandContext, brandContextJsonSchema, brandContextPrompt } from "./src/brand.js";
|
|
9
11
|
export { COMMAND_NOT_FOUND_EXIT_CODE } from "./src/exec.js";
|
|
10
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
12
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwyQ0FBMkM7QUFDM0MsbURBQW1EO0FBQ25ELHdFQUF3RTtBQUV4RSxPQUFPLEtBQUssR0FBRyxNQUFNLGNBQWMsQ0FBQztBQUNwQyxPQUFPLEtBQUssS0FBSyxNQUFNLGdCQUFnQixDQUFDO0FBQ3hDLE9BQU8sS0FBSyxJQUFJLE1BQU0sZUFBZSxDQUFDO0FBQ3RDLE9BQU8sS0FBSyxJQUFJLE1BQU0sZUFBZSxDQUFDO0FBQ3RDLE9BQU8sS0FBSyxXQUFXLE1BQU0sdUJBQXVCLENBQUM7QUFDckQsT0FBTyxLQUFLLE9BQU8sTUFBTSxrQkFBa0IsQ0FBQztBQUU1QyxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsbUJBQW1CLEVBQUUsaUJBQWlCLEVBQUUsc0JBQXNCLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUV4SSxPQUFPLEVBQUUsMkJBQTJCLEVBQUUsTUFBTSxlQUFlLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBHRU5FUkFURUQgYnkgcHJvamVuIHdhdGNoIC0gRE8gTk9UIEVESVQuXG4vLyBSZWdlbmVyYXRlZCBmcm9tIHRoZSBleHBvcnRpbmcgbW9kdWxlcyBpbiAuL3NyYy5cbi8vIEhhbmQgZWRpdHMgYXJlIG92ZXJ3cml0dGVuIG9uIHRoZSBuZXh0IHdhdGNoOyB0aGlzIGZpbGUgaXMgcmVhZC1vbmx5LlxuXG5leHBvcnQgKiBhcyBiaW4gZnJvbSBcIi4vc3JjL2Jpbi50c1wiO1xuZXhwb3J0ICogYXMgYnJhbmQgZnJvbSBcIi4vc3JjL2JyYW5kLnRzXCI7XG5leHBvcnQgKiBhcyBleGVjIGZyb20gXCIuL3NyYy9leGVjLnRzXCI7XG5leHBvcnQgKiBhcyBmaWxlIGZyb20gXCIuL3NyYy9maWxlLnRzXCI7XG5leHBvcnQgKiBhcyBwcm9jZXNzTG9jayBmcm9tIFwiLi9zcmMvcHJvY2Vzcy1sb2NrLnRzXCI7XG5leHBvcnQgKiBhcyBwcm9qZWN0IGZyb20gXCIuL3NyYy9wcm9qZWN0LnRzXCI7XG5leHBvcnQgdHlwZSB7IEJpbkNvbnRleHQsIEJpblNlbGVjdGlvbkNvbnRleHQsIEJpblNlbGVjdG9yLCBCaW5WZXJzaW9uT3V0cHV0LCBCaW5WZXJzaW9uUGFyc2VyLCBCaW5PcHRpb25zLCBCaW5VcmwgfSBmcm9tIFwiLi9zcmMvYmluLnRzXCI7XG5leHBvcnQgeyBCcmFuZENvbnRleHRTY2hlbWEsIGRlZmF1bHRCcmFuZENvbnRleHQsIHBhcnNlQnJhbmRDb250ZXh0LCBicmFuZENvbnRleHRKc29uU2NoZW1hLCBicmFuZENvbnRleHRQcm9tcHQgfSBmcm9tIFwiLi9zcmMvYnJhbmQudHNcIjtcbmV4cG9ydCB0eXBlIHsgQnJhbmRDb250ZXh0LCBCcmFuZENvbnRleHRJbnB1dCB9IGZyb20gXCIuL3NyYy9icmFuZC50c1wiO1xuZXhwb3J0IHsgQ09NTUFORF9OT1RfRk9VTkRfRVhJVF9DT0RFIH0gZnJvbSBcIi4vc3JjL2V4ZWMudHNcIjtcbmV4cG9ydCB0eXBlIHsgRXhlY1N0ZGlvLCBMaW5lSGFuZGxlciwgU3RkaW9PcHRpb24sIEV4ZWNSZXN1bHQsIENoaWxkUHJvY2Vzc1Jlc3VsdCwgRXhlY09wdGlvbnMsIFN5bmNFeGVjU3RkaW8sIFN5bmNFeGVjT3B0aW9ucywgU3Bhd25BcmdzIH0gZnJvbSBcIi4vc3JjL2V4ZWMudHNcIjtcbmV4cG9ydCB0eXBlIHsgUHJvamVjdENvbnRleHQgfSBmcm9tIFwiLi9zcmMvcHJvamVjdC50c1wiO1xuIl19
|
package/lib/src/bin.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** Stable paths for an installed binary. */
|
|
2
|
+
export interface BinContext {
|
|
3
|
+
root: string;
|
|
4
|
+
binDir: string;
|
|
5
|
+
path: string;
|
|
6
|
+
}
|
|
7
|
+
/** Temporary download and extraction paths supplied to a custom selector. */
|
|
8
|
+
export interface BinSelectionContext {
|
|
9
|
+
destination: BinContext;
|
|
10
|
+
downloadPath: string;
|
|
11
|
+
source: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Select the executable from a download or unpacked archive. A selector may
|
|
15
|
+
* also prepare the file, such as applying its executable mode.
|
|
16
|
+
*/
|
|
17
|
+
export type BinSelector = (context: BinSelectionContext) => string | Promise<string>;
|
|
18
|
+
/** Captured output passed to a custom binary version parser. */
|
|
19
|
+
export interface BinVersionOutput {
|
|
20
|
+
stdout: string;
|
|
21
|
+
stderr: string;
|
|
22
|
+
}
|
|
23
|
+
/** Extract a version string from a successful version-command result. */
|
|
24
|
+
export type BinVersionParser = (output: BinVersionOutput) => string | undefined;
|
|
25
|
+
/** Options for {@link ensure}. */
|
|
26
|
+
export interface BinOptions {
|
|
27
|
+
autoUnpackage?: boolean;
|
|
28
|
+
selector?: BinSelector;
|
|
29
|
+
homeDir?: string;
|
|
30
|
+
/** Minimum accepted numeric version, with one to three components. */
|
|
31
|
+
minVersion?: string;
|
|
32
|
+
/** Argument passed to the binary for version detection. Defaults to `--version`. */
|
|
33
|
+
versionArgument?: string;
|
|
34
|
+
/** Version output parser. Defaults to {@link parseVersion}. */
|
|
35
|
+
versionParser?: BinVersionParser;
|
|
36
|
+
}
|
|
37
|
+
/** A URL resolved only when the executable is not already installed. */
|
|
38
|
+
export type BinUrl = string | (() => string | Promise<string>);
|
|
39
|
+
/**
|
|
40
|
+
* Parse the deepest, highest version from stdout, falling back to stderr only
|
|
41
|
+
* when stdout contains no version. Supports one to three numeric components
|
|
42
|
+
* and common suffixes such as `rc1`, `.post1`, and `-dev.2`.
|
|
43
|
+
*/
|
|
44
|
+
export declare function parseVersion({ stdout, stderr }: BinVersionOutput): string | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* Return an existing executable or install it atomically under
|
|
47
|
+
* `$HOME/.<name>/bin/<name>`. Installation uses a check-lock-check-load
|
|
48
|
+
* sequence so concurrent callers resolve and download the binary only once.
|
|
49
|
+
* The downloaded candidate and final renamed executable must both pass the
|
|
50
|
+
* same executable and version checks.
|
|
51
|
+
*/
|
|
52
|
+
export declare function ensure(name: string, url: BinUrl, options?: BinOptions): Promise<BinContext>;
|
package/lib/src/bin.js
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Install and reuse executable binaries under a per-tool home directory.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import { execFile } from "node:child_process";
|
|
7
|
+
import { randomUUID } from "node:crypto";
|
|
8
|
+
import { chmod, copyFile, mkdir, mkdtemp, readdir, rename, rm, stat, writeFile, } from "node:fs/promises";
|
|
9
|
+
import { homedir, tmpdir } from "node:os";
|
|
10
|
+
import { basename, join } from "node:path";
|
|
11
|
+
import { promisify } from "node:util";
|
|
12
|
+
import extractZip from "extract-zip";
|
|
13
|
+
import { x as extractTar } from "tar";
|
|
14
|
+
import { error, log } from "@dbx-tools/shared-core";
|
|
15
|
+
import { withProcessLock } from "./process-lock.js";
|
|
16
|
+
const execFileAsync = promisify(execFile);
|
|
17
|
+
const logger = log.logger("core:bin");
|
|
18
|
+
function context(name, homeDir) {
|
|
19
|
+
if (!name || basename(name) !== name || name === "." || name === "..") {
|
|
20
|
+
throw new TypeError(`invalid binary name: ${name}`);
|
|
21
|
+
}
|
|
22
|
+
const root = join(homeDir, `.${name}`);
|
|
23
|
+
const binDir = join(root, "bin");
|
|
24
|
+
return { root, binDir, path: join(binDir, name) };
|
|
25
|
+
}
|
|
26
|
+
function displayUrl(raw) {
|
|
27
|
+
try {
|
|
28
|
+
const url = new URL(raw);
|
|
29
|
+
if (url.protocol === "data:")
|
|
30
|
+
return "data:";
|
|
31
|
+
url.username = "";
|
|
32
|
+
url.password = "";
|
|
33
|
+
url.search = "";
|
|
34
|
+
url.hash = "";
|
|
35
|
+
return url.toString();
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return raw;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function accessContext(path) {
|
|
42
|
+
try {
|
|
43
|
+
const entry = await stat(path);
|
|
44
|
+
const file = entry.isFile();
|
|
45
|
+
return {
|
|
46
|
+
file,
|
|
47
|
+
executable: file && (process.platform === "win32" || (entry.mode & 0o111) !== 0),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return { file: false, executable: false };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function detectedVersions(output) {
|
|
55
|
+
const versions = [];
|
|
56
|
+
const pattern = /\bv?(\d+(?:\.\d+){0,2})(?:[-+._]?[a-z][0-9a-z.+_-]*)?/gi;
|
|
57
|
+
for (const match of output.matchAll(pattern)) {
|
|
58
|
+
const raw = match[0].replace(/^v/i, "");
|
|
59
|
+
const parts = match[1]?.split(".").map(Number);
|
|
60
|
+
if (parts?.every(Number.isFinite))
|
|
61
|
+
versions.push({ raw, parts });
|
|
62
|
+
}
|
|
63
|
+
return versions.sort((a, b) => {
|
|
64
|
+
if (a.parts.length !== b.parts.length)
|
|
65
|
+
return b.parts.length - a.parts.length;
|
|
66
|
+
for (let index = 0; index < a.parts.length; index += 1) {
|
|
67
|
+
const order = (b.parts[index] ?? 0) - (a.parts[index] ?? 0);
|
|
68
|
+
if (order !== 0)
|
|
69
|
+
return order;
|
|
70
|
+
}
|
|
71
|
+
return 0;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Parse the deepest, highest version from stdout, falling back to stderr only
|
|
76
|
+
* when stdout contains no version. Supports one to three numeric components
|
|
77
|
+
* and common suffixes such as `rc1`, `.post1`, and `-dev.2`.
|
|
78
|
+
*/
|
|
79
|
+
export function parseVersion({ stdout, stderr }) {
|
|
80
|
+
return detectedVersions(stdout).at(0)?.raw ?? detectedVersions(stderr).at(0)?.raw;
|
|
81
|
+
}
|
|
82
|
+
function numericVersion(version, strict) {
|
|
83
|
+
const pattern = strict ? /^\s*v?(\d+(?:\.\d+){0,2})\s*$/i : /\bv?(\d+(?:\.\d+){0,2})/i;
|
|
84
|
+
const match = pattern.exec(version);
|
|
85
|
+
return match?.[1]?.split(".").map(Number);
|
|
86
|
+
}
|
|
87
|
+
function meetsMinVersion(version, minVersion) {
|
|
88
|
+
if (!minVersion)
|
|
89
|
+
return true;
|
|
90
|
+
const actual = numericVersion(version, false);
|
|
91
|
+
const minimum = numericVersion(minVersion, true);
|
|
92
|
+
if (!minimum) {
|
|
93
|
+
throw new TypeError(`invalid minimum binary version: ${minVersion}`);
|
|
94
|
+
}
|
|
95
|
+
if (!actual)
|
|
96
|
+
return false;
|
|
97
|
+
for (let index = 0; index < Math.max(actual.length, minimum.length); index += 1) {
|
|
98
|
+
const difference = (actual[index] ?? 0) - (minimum[index] ?? 0);
|
|
99
|
+
if (difference !== 0)
|
|
100
|
+
return difference > 0;
|
|
101
|
+
}
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
async function isValidBin(path, options) {
|
|
105
|
+
logger.debug("checking binary", {
|
|
106
|
+
path,
|
|
107
|
+
minVersion: options.minVersion,
|
|
108
|
+
versionArgument: options.versionArgument ?? "--version",
|
|
109
|
+
});
|
|
110
|
+
const access = await accessContext(path);
|
|
111
|
+
if (!access.file || !access.executable) {
|
|
112
|
+
logger.debug("binary access check failed", { path, ...access });
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
let stdout;
|
|
116
|
+
let stderr;
|
|
117
|
+
try {
|
|
118
|
+
const result = await execFileAsync(path, [options.versionArgument ?? "--version"], {
|
|
119
|
+
encoding: "utf8",
|
|
120
|
+
});
|
|
121
|
+
stdout = result.stdout;
|
|
122
|
+
stderr = result.stderr;
|
|
123
|
+
}
|
|
124
|
+
catch (cause) {
|
|
125
|
+
logger.debug("binary version command failed", {
|
|
126
|
+
path,
|
|
127
|
+
error: error.errorMessage(cause),
|
|
128
|
+
});
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
const version = (options.versionParser ?? parseVersion)({
|
|
132
|
+
stdout,
|
|
133
|
+
stderr,
|
|
134
|
+
});
|
|
135
|
+
const valid = version !== undefined && meetsMinVersion(version, options.minVersion);
|
|
136
|
+
logger.debug("binary version checked", {
|
|
137
|
+
path,
|
|
138
|
+
version,
|
|
139
|
+
minVersion: options.minVersion,
|
|
140
|
+
valid,
|
|
141
|
+
});
|
|
142
|
+
return valid;
|
|
143
|
+
}
|
|
144
|
+
function downloadName(url, name) {
|
|
145
|
+
try {
|
|
146
|
+
const candidate = basename(decodeURIComponent(new URL(url).pathname));
|
|
147
|
+
return candidate && Buffer.byteLength(candidate) <= 200 ? candidate : name;
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return name;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async function unpack(archive, destination) {
|
|
154
|
+
const filename = archive.toLowerCase();
|
|
155
|
+
if (filename.endsWith(".zip")) {
|
|
156
|
+
await extractZip(archive, { dir: destination });
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (filename.endsWith(".tar") || filename.endsWith(".tar.gz") || filename.endsWith(".tgz")) {
|
|
160
|
+
await extractTar({ file: archive, cwd: destination });
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
throw new Error(`unsupported binary archive: ${basename(archive)}`);
|
|
164
|
+
}
|
|
165
|
+
async function filesUnder(root) {
|
|
166
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
167
|
+
const files = [];
|
|
168
|
+
for (const entry of entries) {
|
|
169
|
+
const path = join(root, entry.name);
|
|
170
|
+
if (entry.isDirectory()) {
|
|
171
|
+
files.push(...(await filesUnder(path)));
|
|
172
|
+
}
|
|
173
|
+
else if (entry.isFile()) {
|
|
174
|
+
files.push(path);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return files;
|
|
178
|
+
}
|
|
179
|
+
async function selectSingleFile(source) {
|
|
180
|
+
const files = await filesUnder(source);
|
|
181
|
+
const selected = files.at(0);
|
|
182
|
+
if (files.length !== 1 || !selected) {
|
|
183
|
+
throw new Error(`binary archive must contain one file, found ${files.length}`);
|
|
184
|
+
}
|
|
185
|
+
return selected;
|
|
186
|
+
}
|
|
187
|
+
async function selectedBin(destination, url, temp, options) {
|
|
188
|
+
const name = downloadName(url, basename(destination.path));
|
|
189
|
+
const downloadPath = join(temp, name);
|
|
190
|
+
logger.debug("downloading binary", {
|
|
191
|
+
from: displayUrl(url),
|
|
192
|
+
to: downloadPath,
|
|
193
|
+
});
|
|
194
|
+
const response = await fetch(url);
|
|
195
|
+
if (!response.ok) {
|
|
196
|
+
throw new Error(`binary download failed (${response.status})`);
|
|
197
|
+
}
|
|
198
|
+
await writeFile(downloadPath, Buffer.from(await response.arrayBuffer()), { mode: 0o755 });
|
|
199
|
+
let source = downloadPath;
|
|
200
|
+
if (options.autoUnpackage) {
|
|
201
|
+
source = join(temp, `unpacked-${randomUUID()}`);
|
|
202
|
+
await mkdir(source);
|
|
203
|
+
logger.debug("unpacking binary archive", {
|
|
204
|
+
archive: downloadPath,
|
|
205
|
+
to: source,
|
|
206
|
+
});
|
|
207
|
+
await unpack(downloadPath, source);
|
|
208
|
+
}
|
|
209
|
+
if (options.selector) {
|
|
210
|
+
const selected = await options.selector({ destination, downloadPath, source });
|
|
211
|
+
logger.debug("selected binary", { path: selected });
|
|
212
|
+
return selected;
|
|
213
|
+
}
|
|
214
|
+
const selected = options.autoUnpackage ? await selectSingleFile(source) : source;
|
|
215
|
+
logger.debug("selected binary", { path: selected });
|
|
216
|
+
return selected;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Return an existing executable or install it atomically under
|
|
220
|
+
* `$HOME/.<name>/bin/<name>`. Installation uses a check-lock-check-load
|
|
221
|
+
* sequence so concurrent callers resolve and download the binary only once.
|
|
222
|
+
* The downloaded candidate and final renamed executable must both pass the
|
|
223
|
+
* same executable and version checks.
|
|
224
|
+
*/
|
|
225
|
+
export async function ensure(name, url, options = {}) {
|
|
226
|
+
if (options.minVersion && !numericVersion(options.minVersion, true)) {
|
|
227
|
+
throw new TypeError(`invalid minimum binary version: ${options.minVersion}`);
|
|
228
|
+
}
|
|
229
|
+
const destination = context(name, options.homeDir ?? homedir());
|
|
230
|
+
if (await isValidBin(destination.path, options)) {
|
|
231
|
+
logger.debug("using installed binary", { name, path: destination.path });
|
|
232
|
+
return destination;
|
|
233
|
+
}
|
|
234
|
+
logger.debug("waiting for binary install lock", { name, path: destination.path });
|
|
235
|
+
return withProcessLock(["bin.ensure", destination.path], async () => {
|
|
236
|
+
if (await isValidBin(destination.path, options)) {
|
|
237
|
+
logger.debug("using binary installed by another caller", {
|
|
238
|
+
name,
|
|
239
|
+
path: destination.path,
|
|
240
|
+
});
|
|
241
|
+
return destination;
|
|
242
|
+
}
|
|
243
|
+
const resolvedUrl = typeof url === "function" ? await url() : url;
|
|
244
|
+
const from = displayUrl(resolvedUrl);
|
|
245
|
+
logger.debug("installing binary", {
|
|
246
|
+
name,
|
|
247
|
+
from,
|
|
248
|
+
to: destination.path,
|
|
249
|
+
minVersion: options.minVersion,
|
|
250
|
+
});
|
|
251
|
+
const temp = await mkdtemp(join(tmpdir(), `${name}-`));
|
|
252
|
+
let staged;
|
|
253
|
+
try {
|
|
254
|
+
const selected = await selectedBin(destination, resolvedUrl, temp, options);
|
|
255
|
+
await chmod(selected, 0o755);
|
|
256
|
+
if (!(await isValidBin(selected, options))) {
|
|
257
|
+
throw new Error(`selected binary has no acceptable version: ${selected}`);
|
|
258
|
+
}
|
|
259
|
+
await mkdir(destination.binDir, { recursive: true });
|
|
260
|
+
staged = join(destination.binDir, `.${name}-${randomUUID()}`);
|
|
261
|
+
await copyFile(selected, staged);
|
|
262
|
+
await chmod(staged, 0o755);
|
|
263
|
+
await rename(staged, destination.path);
|
|
264
|
+
staged = undefined;
|
|
265
|
+
if (!(await isValidBin(destination.path, options))) {
|
|
266
|
+
throw new Error(`installed binary is invalid after rename: ${destination.path}`);
|
|
267
|
+
}
|
|
268
|
+
logger.info("installed binary", {
|
|
269
|
+
name,
|
|
270
|
+
from,
|
|
271
|
+
to: destination.path,
|
|
272
|
+
});
|
|
273
|
+
return destination;
|
|
274
|
+
}
|
|
275
|
+
finally {
|
|
276
|
+
if (staged)
|
|
277
|
+
await rm(staged, { force: true });
|
|
278
|
+
await rm(temp, { recursive: true, force: true });
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYmluLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2Jpbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7OztHQUlHO0FBQ0gsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQzlDLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDekMsT0FBTyxFQUNMLEtBQUssRUFDTCxRQUFRLEVBQ1IsS0FBSyxFQUNMLE9BQU8sRUFDUCxPQUFPLEVBQ1AsTUFBTSxFQUNOLEVBQUUsRUFDRixJQUFJLEVBQ0osU0FBUyxHQUNWLE1BQU0sa0JBQWtCLENBQUM7QUFDMUIsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxTQUFTLENBQUM7QUFDMUMsT0FBTyxFQUFFLFFBQVEsRUFBRSxJQUFJLEVBQUUsTUFBTSxXQUFXLENBQUM7QUFDM0MsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLFdBQVcsQ0FBQztBQUV0QyxPQUFPLFVBQVUsTUFBTSxhQUFhLENBQUM7QUFDckMsT0FBTyxFQUFFLENBQUMsSUFBSSxVQUFVLEVBQUUsTUFBTSxLQUFLLENBQUM7QUFFdEMsT0FBTyxFQUFFLEtBQUssRUFBRSxHQUFHLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUNwRCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFFcEQsTUFBTSxhQUFhLEdBQUcsU0FBUyxDQUFDLFFBQVEsQ0FBQyxDQUFDO0FBQzFDLE1BQU0sTUFBTSxHQUFHLEdBQUcsQ0FBQyxNQUFNLENBQUMsVUFBVSxDQUFDLENBQUM7QUF5RHRDLFNBQVMsT0FBTyxDQUFDLElBQVksRUFBRSxPQUFlO0lBQzVDLElBQUksQ0FBQyxJQUFJLElBQUksUUFBUSxDQUFDLElBQUksQ0FBQyxLQUFLLElBQUksSUFBSSxJQUFJLEtBQUssR0FBRyxJQUFJLElBQUksS0FBSyxJQUFJLEVBQUUsQ0FBQztRQUN0RSxNQUFNLElBQUks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LEVBQUUsSUFBSSxJQUFJLElBQUksVUFBVSxFQUFFLEVBQUUsQ0FBQyxDQUFDO1lBQzlELE1BQU0sUUFBUSxDQUFDLFFBQVEsRUFBRSxNQUFNLENBQUMsQ0FBQztZQUNqQyxNQUFNLEtBQUssQ0FBQyxNQUFNLEVBQUUsS0FBSyxDQUFDLENBQUM7WUFDM0IsTUFBTSxNQUFNLENBQUMsTUFBTSxFQUFFLFdBQVcsQ0FBQyxJQUFJLENBQUMsQ0FBQztZQUN2QyxNQUFNLEdBQUcsU0FBUyxDQUFDO1lBQ25CLElBQUksQ0FBQyxDQUFDLE1BQU0sVUFBVSxDQUFDLFdBQVcsQ0FBQyxJQUFJLEVBQUUsT0FBTyxDQUFDLENBQUMsRUFBRSxDQUFDO2dCQUNuRCxNQUFNLElBQUksS0FBSyxDQUFDLDZDQUE2QyxXQUFXLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQztZQUNuRixDQUFDO1lBQ0QsTUFBTSxDQUFDLElBQUksQ0FBQyxrQkFBa0IsRUFBRTtnQkFDOUIsSUFBSTtnQkFDSixJQUFJO2dCQUNKLEVBQUUsRUFBRSxXQUFXLENBQUMsSUFBSTthQUNyQixDQUFDLENBQUM7WUFDSCxPQUFPLFdBQVcsQ0FBQztRQUNyQixDQUFDO2dCQUFTLENBQUM7WUFDVCxJQUFJLE1BQU07Z0JBQUUsTUFBTSxFQUFFLENBQUMsTUFBTSxFQUFFLEVBQUUsS0FBSyxFQUFFLElBQUksRUFBRSxDQUFDLENBQUM7WUFDOUMsTUFBTSxFQUFFLENBQUMsSUFBSSxFQUFFLEVBQUUsU0FBUyxFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztRQUNuRCxDQUFDO0lBQ0gsQ0FBQyxDQUFDLENBQUM7QUFDTCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBJbnN0YWxsIGFuZCByZXVzZSBleGVjdXRhYmxlIGJpbmFyaWVzIHVuZGVyIGEgcGVyLXRvb2wgaG9tZSBkaXJlY3RvcnkuXG4gKlxuICogQG1vZHVsZVxuICovXG5pbXBvcnQgeyBleGVjRmlsZSB9IGZyb20gXCJub2RlOmNoaWxkX3Byb2Nlc3NcIjtcbmltcG9ydCB7IHJhbmRvbVVVSUQgfSBmcm9tIFwibm9kZTpjcnlwdG9cIjtcbmltcG9ydCB7XG4gIGNobW9kLFxuICBjb3B5RmlsZSxcbiAgbWtkaXIsXG4gIG1rZHRlbXAsXG4gIHJlYWRkaXIsXG4gIHJlbmFtZSxcbiAgcm0sXG4gIHN0YXQsXG4gIHdyaXRlRmlsZSxcbn0gZnJvbSBcIm5vZGU6ZnMvcHJvbWlzZXNcIjtcbmltcG9ydCB7IGhvbWVkaXIsIHRtcGRpciB9IGZyb20gXCJub2RlOm9zXCI7XG5pbXBvcnQgeyBiYXNlbmFtZSwgam9pbiB9IGZyb20gXCJub2RlOnBhdGhcIjtcbmltcG9ydCB7IHByb21pc2lmeSB9IGZyb20gXCJub2RlOnV0aWxcIjtcblxuaW1wb3J0IGV4dHJhY3RaaXAgZnJvbSBcImV4dHJhY3QtemlwXCI7XG5pbXBvcnQgeyB4IGFzIGV4dHJhY3RUYXIgfSBmcm9tIFwidGFyXCI7XG5cbmltcG9ydCB7IGVycm9yLCBsb2cgfSBmcm9tIFwiQGRieC10b29scy9zaGFyZWQtY29yZVwiO1xuaW1wb3J0IHsgd2l0aFByb2Nlc3NMb2NrIH0gZnJvbSBcIi4vcHJvY2Vzcy1sb2NrLnRzXCI7XG5cbmNvbnN0IGV4ZWNGaWxlQXN5bmMgPSBwcm9taXNpZnkoZXhlY0ZpbGUpO1xuY29uc3QgbG9nZ2VyID0gbG9nLmxvZ2dlcihcImNvcmU6YmluXCIpO1xuXG5pbnRlcmZhY2UgUGFyc2VkVmVyc2lvbiB7XG4gIHJhdzogc3RyaW5nO1xuICBwYXJ0czogbnVtYmVyW107XG59XG5cbmludGVyZmFjZSBCaW5BY2Nlc3NDb250ZXh0IHtcbiAgZmlsZTogYm9vbGVhbjtcbiAgZXhlY3V0YWJsZTogYm9vbGVhbjtcbn1cblxuLyoqIFN0YWJsZSBwYXRocyBmb3IgYW4gaW5zdGFsbGVkIGJpbmFyeS4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgQmluQ29udGV4dCB7XG4gIHJvb3Q6IHN0cmluZztcbiAgYmluRGlyOiBzdHJpbmc7XG4gIHBhdGg6IHN0cmluZztcbn1cblxuLyoqIFRlbXBvcmFyeSBkb3dubG9hZCBhbmQgZXh0cmFjdGlvbiBwYXRocyBzdXBwbGllZCB0byBhIGN1c3RvbSBzZWxlY3Rvci4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgQmluU2VsZWN0aW9uQ29udGV4dCB7XG4gIGRlc3RpbmF0aW9uOiBCaW5Db250ZXh0O1xuICBkb3dubG9hZFBhdGg6IHN0cmluZztcbiAgc291cmNlOiBzdHJpbmc7XG59XG5cbi8qKlxuICogU2VsZWN0IHRoZSBleGVjdXRhYmxlIGZyb20gYSBkb3dubG9hZCBvciB1bnBhY2tlZCBhcmNoaXZlLiBBIHNlbGVjdG9yIG1heVxuICogYWxzbyBwcmVwYXJlIHRoZSBmaWxlLCBzdWNoIGFzIGFwcGx5aW5nIGl0cyBleGVjdXRhYmxlIG1vZGUuXG4gKi9cbmV4cG9ydCB0eXBlIEJpblNlbGVjdG9yID0gKGNvbnRleHQ6IEJpblNlbGVjdGlvbkNvbnRleHQpID0+IHN0cmluZyB8IFByb21pc2U8c3RyaW5nPjtcblxuLyoqIENhcHR1cmVkIG91dHB1dCBwYXNzZWQgdG8gYSBjdXN0b20gYmluYXJ5IHZlcnNpb24gcGFyc2VyLiAqL1xuZXhwb3J0IGludGVyZmFjZSBCaW5WZXJzaW9uT3V0cHV0IHtcbiAgc3Rkb3V0OiBzdHJpbmc7XG4gIHN0ZGVycjogc3RyaW5nO1xufVxuXG4vKiogRXh0cmFjdCBhIHZlcnNpb24gc3RyaW5nIGZyb20gYSBzdWNjZXNzZnVsIHZlcnNpb24tY29tbWFuZCByZXN1bHQuICovXG5leHBvcnQgdHlwZSBCaW5WZXJzaW9uUGFyc2VyID0gKG91dHB1dDogQmluVmVyc2lvbk91dHB1dCkgPT4gc3RyaW5nIHwgdW5kZWZpbmVkO1xuXG4vKiogT3B0aW9ucyBmb3Ige0BsaW5rIGVuc3VyZX0uICovXG5leHBvcnQgaW50ZXJmYWNlIEJpbk9wdGlvbnMge1xuICBhdXRvVW5wYWNrYWdlPzogYm9vbGVhbjtcbiAgc2VsZWN0b3I/OiBCaW5TZWxlY3RvcjtcbiAgaG9tZURpcj86IHN0cmluZztcbiAgLyoqIE1pbmltdW0gYWNjZXB0ZWQgbnVtZXJpYyB2ZXJzaW9uLCB3aXRoIG9uZSB0byB0aHJlZSBjb21wb25lbnRzLiAqL1xuICBtaW5WZXJzaW9uPzogc3RyaW5nO1xuICAvKiogQXJndW1lbnQgcGFzc2VkIHRvIHRoZSBiaW5hcnkgZm9yIHZlcnNpb24gZGV0ZWN0aW9uLiBEZWZhdWx0cyB0byBgLS12ZXJzaW9uYC4gKi9cbiAgdmVyc2lvbkFyZ3VtZW50Pzogc3RyaW5nO1xuICAvKiogVmVyc2lvbiBvdXRwdXQgcGFyc2VyLiBEZWZhdWx0cyB0byB7QGxpbmsgcGFyc2VWZXJzaW9ufS4gKi9cbiAgdmVyc2lvblBhcnNlcj86IEJpblZlcnNpb25QYXJzZXI7XG59XG5cbi8qKiBBIFVSTCByZXNvbHZlZCBvbmx5IHdoZW4gdGhlIGV4ZWN1dGFibGUgaXMgbm90IGFscmVhZHkgaW5zdGFsbGVkLiAqL1xuZXhwb3J0IHR5cGUgQmluVXJsID0gc3RyaW5nIHwgKCgpID0+IHN0cmluZyB8IFByb21pc2U8c3RyaW5nPik7XG5cbmZ1bmN0aW9uIGNvbnRleHQobmFtZTogc3RyaW5nLCBob21lRGlyOiBzdHJpbmcpOiBCaW5Db250ZXh0IHtcbiAgaWYgKCFuYW1lIHx8IGJhc2VuYW1lKG5hbWUpICE9PSBuYW1lIHx8IG5hbWUgPT09IFwiLlwiIHx8IG5hbWUgPT09IFwiLi5cIikge1xuICAgIHRocm93IG5ldyBUeXBlRXJyb3IoYGludmFsaWQgYmluYXJ5IG5hbWU6ICR7bmFtZX1gKTtcbiAgfVxuICBjb25zdCByb290ID0gam9pbihob21lRGlyLCBgLiR7bmFtZX1gKTtcbiAgY29uc3QgYmluRGlyID0gam9pbihyb290LCBcImJpblwiKTtcbiAgcmV0dXJuIHsgcm9vdCwgYmluRGlyLCBwYXRoOiBqb2luKGJpbkRpciwgbmFtZSkgfTtcbn1cblxuZnVuY3Rpb24gZGlzcGxheVVybChyYXc6IHN0cmluZyk6IHN0cmluZyB7XG4gIHRyeSB7XG4gICAgY29uc3QgdXJsID0gbmV3IFVSTChyYXcpO1xuICAgIGlmICh1cmwucHJvdG9jb2wgPT09IFwiZGF0YTpcIikgcmV0dXJuIFwiZGF0YTpcIjtcbiAgICB1cmwudXNlcm5hbWUgPSBcIlwiO1xuICAgIHVybC5wYXNzd29yZCA9IFwiXCI7XG4gICAgdXJsLnNlYXJjaCA9IFwiXCI7XG4gICAgdXJsLmhhc2ggPSBcIlwiO1xuICAgIHJldHVybiB1cmwudG9TdHJpbmcoKTtcbiAgfSBjYXRjaCB7XG4gICAgcmV0dXJuIHJhdztcbiAgfVxufVxuXG5hc3luYyBmdW5jdGlvbiBhY2Nlc3NDb250ZXh0KHBhdGg6IHN0cmluZyk6IFByb21pc2U8QmluQWNjZXNzQ29udGV4dD4ge1xuICB0cnkge1xuICAgIGNvbnN0IGVudHJ5ID0gYXdhaXQgc3RhdChwYXRoKTtcbiAgICBjb25zdCBmaWxlID0gZW50cnkuaXNGaWxlKCk7XG4gICAgcmV0dXJuIHtcbiAgICAgIGZpbGUsXG4gICAgICBleGVjdXRhYmxlOiBmaWxlICYmIChwcm9jZXNzLnBsYXRmb3JtID09PSBcIndpbjMyXCIgfHwgKGVudHJ5Lm1vZGUgJiAwbzExMSkgIT09IDApLFxuICAgIH07XG4gIH0gY2F0Y2gge1xuICAgIHJldHVybiB7IGZpbGU6IGZhbHNlLCBleGVjdXRhYmxlOiBmYWxzZSB9O1xuICB9XG59XG5cbmZ1bmN0aW9uIGRldGVjdGVkVmVyc2lvbnMob3V0cHV0OiBzdHJpbmcpOiBQYXJzZWRWZXJzaW9uW10ge1xuICBjb25zdCB2ZXJzaW9uczogUGFyc2VkVmVyc2lvbltdID0gW107XG4gIGNvbnN0IHBhdHRlcm4gPSAvXFxidj8oXFxkKyg/OlxcLlxcZCspezAsMn0pKD86Wy0rLl9dP1thLXpdWzAtOWEtei4rXy1dKik/L2dpO1xuICBmb3IgKGNvbnN0IG1hdGNoIG9mIG91dHB1dC5tYXRjaEFsbChwYXR0ZXJuKSkge1xuICAgIGNvbnN0IHJhdyA9IG1hdGNoWzBdLnJlcGxhY2UoL152L2ksIFwiXCIpO1xuICAgIGNvbnN0IHBhcnRzID0gbWF0Y2hbMV0/LnNwbGl0KFwiLlwiKS5tYXAoTnVtYmVyKTtcbiAgICBpZiAocGFydHM/LmV2ZXJ5KE51bWJlci5pc0Zpbml0ZSkpIHZlcnNpb25zLnB1c2goeyByYXcsIHBhcnRzIH0pO1xuICB9XG4gIHJldHVybiB2ZXJzaW9ucy5zb3J0KChhLCBiKSA9PiB7XG4gICAgaWYgKGEucGFydHMubGVuZ3RoICE9PSBiLnBhcnRzLmxlbmd0aCkgcmV0dXJuIGIucGFydHMubGVuZ3RoIC0gYS5wYXJ0cy5sZW5ndGg7XG4gICAgZm9yIChsZXQgaW5kZXggPSAwOyBpbmRleCA8IGEucGFydHMubGVuZ3RoOyBpbmRleCArPSAxKSB7XG4gICAgICBjb25zdCBvcmRlciA9IChiLnBhcnRzW2luZGV4XSA/PyAwKSAtIChhLnBhcnRzW2luZGV4XSA/PyAwKTtcbiAgICAgIGlmIChvcmRlciAhPT0gMCkgcmV0dXJuIG9yZGVyO1xuICAgIH1cbiAgICByZXR1cm4gMDtcbiAgfSk7XG59XG5cbi8qKlxuICogUGFyc2UgdGhlIGRlZXBlc3QsIGhpZ2hlc3QgdmVyc2lvbiBmcm9tIHN0ZG91dCwgZmFsbGluZyBiYWNrIHRvIHN0ZGVyciBvbmx5XG4gKiB3aGVuIHN0ZG91dCBjb250YWlucyBubyB2ZXJzaW9uLiBTdXBwb3J0cyBvbmUgdG8gdGhyZWUgbnVtZXJpYyBjb21wb25lbnRzXG4gKiBhbmQgY29tbW9uIHN1ZmZpeGVzIHN1Y2ggYXMgYHJjMWAsIGAucG9zdDFgLCBhbmQgYC1kZXYuMmAuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBwYXJzZVZlcnNpb24oeyBzdGRvdXQsIHN0ZGVyciB9OiBCaW5WZXJzaW9uT3V0cHV0KTogc3RyaW5nIHwgdW5kZWZpbmVkIHtcbiAgcmV0dXJuIGRldGVjdGVkVmVyc2lvbnMoc3Rkb3V0KS5hdCgwKT8ucmF3ID8/IGRldGVjdGVkVmVyc2lvbnMoc3RkZXJyKS5hdCgwKT8ucmF3O1xufVxuXG5mdW5jdGlvbiBudW1lcmljVmVyc2lvbih2ZXJzaW9uOiBzdHJpbmcsIHN0cmljdDogYm9vbGVhbik6IG51bWJlcltdIHwgdW5kZWZpbmVkIHtcbiAgY29uc3QgcGF0dGVybiA9IHN0cmljdCA/IC9eXFxzKnY/KFxcZCsoPzpcXC5cXGQrKXswLDJ9KVxccyokL2kgOiAvXFxidj8oXFxkKyg/OlxcLlxcZCspezAsMn0pL2k7XG4gIGNvbnN0IG1hdGNoID0gcGF0dGVybi5leGVjKHZlcnNpb24pO1xuICByZXR1cm4gbWF0Y2g/LlsxXT8uc3BsaXQoXCIuXCIpLm1hcChOdW1iZXIpO1xufVxuXG5mdW5jdGlvbiBtZWV0c01pblZlcnNpb24odmVyc2lvbjogc3RyaW5nLCBtaW5WZXJzaW9uOiBzdHJpbmcgfCB1bmRlZmluZWQpOiBib29sZWFuIHtcbiAgaWYgKCFtaW5WZXJzaW9uKSByZXR1cm4gdHJ1ZTtcbiAgY29uc3QgYWN0dWFsID0gbnVtZXJpY1ZlcnNpb24odmVyc2lvbiwgZmFsc2UpO1xuICBjb25zdCBtaW5pbXVtID0gbnVtZXJpY1ZlcnNpb24obWluVmVyc2lvbiwgdHJ1ZSk7XG4gIGlmICghbWluaW11bSkge1xuICAgIHRocm93IG5ldyBUeXBlRXJyb3IoYGludmFsaWQgbWluaW11bSBiaW5hcnkgdmVyc2lvbjogJHttaW5WZXJzaW9ufWApO1xuICB9XG4gIGlmICghYWN0dWFsKSByZXR1cm4gZmFsc2U7XG4gIGZvciAobGV0IGluZGV4ID0gMDsgaW5kZXggPCBNYXRoLm1heChhY3R1YWwubGVuZ3RoLCBtaW5pbXVtLmxlbmd0aCk7IGluZGV4ICs9IDEpIHtcbiAgICBjb25zdCBkaWZmZXJlbmNlID0gKGFjdHVhbFtpbmRleF0gPz8gMCkgLSAobWluaW11bVtpbmRleF0gPz8gMCk7XG4gICAgaWYgKGRpZmZlcmVuY2UgIT09IDApIHJldHVybiBkaWZmZXJlbmNlID4gMDtcbiAgfVxuICByZXR1cm4gdHJ1ZTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gaXNWYWxpZEJpbihwYXRoOiBzdHJpbmcsIG9wdGlvbnM6IEJpbk9wdGlvbnMpOiBQcm9taXNlPGJvb2xlYW4+IHtcbiAgbG9nZ2VyLmRlYnVnKFwiY2hlY2tpbmcgYmluYXJ5XCIsIHtcbiAgICBwYXRoLFxuICAgIG1pblZlcnNpb246IG9wdGlvbnMubWluVmVyc2lvbixcbiAgICB2ZXJzaW9uQXJndW1lbnQ6IG9wdGlvbnMudmVyc2lvbkFyZ3VtZW50ID8/IFwiLS12ZXJzaW9uXCIsXG4gIH0pO1xuICBjb25zdCBhY2Nlc3MgPSBhd2FpdCBhY2Nlc3NDb250ZXh0KHBhdGgpO1xuICBpZiAoIWFjY2Vzcy5maWxlIHx8ICFhY2Nlc3MuZXhlY3V0YWJsZSkge1xuICAgIGxvZ2dlci5kZWJ1ZyhcImJpbmFyeSBhY2Nlc3MgY2hlY2sgZmFpbGVkXCIsIHsgcGF0aCwgLi4uYWNjZXNzIH0pO1xuICAgIHJldHVybiBmYWxzZTtcbiAgfVxuICBsZXQgc3Rkb3V0OiBzdHJpbmc7XG4gIGxldCBzdGRlcnI6IHN0cmluZztcbiAgdHJ5IHtcbiAgICBjb25zdCByZXN1bHQgPSBhd2FpdCBleGVjRmlsZUFzeW5jKHBhdGgsIFtvcHRpb25zLnZlcnNpb25Bcmd1bWVudCA/PyBcIi0tdmVyc2lvblwiXSwge1xuICAgICAgZW5jb2Rpbmc6IFwidXRmOFwiLFxuICAgIH0pO1xuICAgIHN0ZG91dCA9IHJlc3VsdC5zdGRvdXQ7XG4gICAgc3RkZXJyID0gcmVzdWx0LnN0ZGVycjtcbiAgfSBjYXRjaCAoY2F1c2UpIHtcbiAgICBsb2dnZXIuZGVidWcoXCJiaW5hcnkgdmVyc2lvbiBjb21tYW5kIGZhaWxlZFwiLCB7XG4gICAgICBwYXRoLFxuICAgICAgZXJyb3I6IGVycm9yLmVycm9yTWVzc2FnZShjYXVzZSksXG4gICAgfSk7XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG4gIGNvbnN0IHZlcnNpb24gPSAob3B0aW9ucy52ZXJzaW9uUGFyc2VyID8/IHBhcnNlVmVyc2lvbikoe1xuICAgIHN0ZG91dCxcbiAgICBzdGRlcnIsXG4gIH0pO1xuICBjb25zdCB2YWxpZCA9IHZlcnNpb24gIT09IHVuZGVmaW5lZCAmJiBtZWV0c01pblZlcnNpb24odmVyc2lvbiwgb3B0aW9ucy5taW5WZXJzaW9uKTtcbiAgbG9nZ2VyLmRlYnVnKFwiYmluYXJ5IHZlcnNpb24gY2hlY2tlZFwiLCB7XG4gICAgcGF0aCxcbiAgICB2ZXJzaW9uLFxuICAgIG1pblZlcnNpb246IG9wdGlvbnMubWluVmVyc2lvbixcbiAgICB2YWxpZCxcbiAgfSk7XG4gIHJldHVybiB2YWxpZDtcbn1cblxuZnVuY3Rpb24gZG93bmxvYWROYW1lKHVybDogc3RyaW5nLCBuYW1lOiBzdHJpbmcpOiBzdHJpbmcge1xuICB0cnkge1xuICAgIGNvbnN0IGNhbmRpZGF0ZSA9IGJhc2VuYW1lKGRlY29kZVVSSUNvbXBvbmVudChuZXcgVVJMKHVybCkucGF0aG5hbWUpKTtcbiAgICByZXR1cm4gY2FuZGlkYXRlICYmIEJ1ZmZlci5ieXRlTGVuZ3RoKGNhbmRpZGF0ZSkgPD0gMjAwID8gY2FuZGlkYXRlIDogbmFtZTtcbiAgfSBjYXRjaCB7XG4gICAgcmV0dXJuIG5hbWU7XG4gIH1cbn1cblxuYXN5bmMgZnVuY3Rpb24gdW5wYWNrKGFyY2hpdmU6IHN0cmluZywgZGVzdGluYXRpb246IHN0cmluZyk6IFByb21pc2U8dm9pZD4ge1xuICBjb25zdCBmaWxlbmFtZSA9IGFyY2hpdmUudG9Mb3dlckNhc2UoKTtcbiAgaWYgKGZpbGVuYW1lLmVuZHNXaXRoKFwiLnppcFwiKSkge1xuICAgIGF3YWl0IGV4dHJhY3RaaXAoYXJjaGl2ZSwgeyBkaXI6IGRlc3RpbmF0aW9uIH0pO1xuICAgIHJldHVybjtcbiAgfVxuICBpZiAoZmlsZW5hbWUuZW5kc1dpdGgoXCIudGFyXCIpIHx8IGZpbGVuYW1lLmVuZHNXaXRoKFwiLnRhci5nelwiKSB8fCBmaWxlbmFtZS5lbmRzV2l0aChcIi50Z3pcIikpIHtcbiAgICBhd2FpdCBleHRyYWN0VGFyKHsgZmlsZTogYXJjaGl2ZSwgY3dkOiBkZXN0aW5hdGlvbiB9KTtcbiAgICByZXR1cm47XG4gIH1cbiAgdGhyb3cgbmV3IEVycm9yKGB1bnN1cHBvcnRlZCBiaW5hcnkgYXJjaGl2ZTogJHtiYXNlbmFtZShhcmNoaXZlKX1gKTtcbn1cblxuYXN5bmMgZnVuY3Rpb24gZmlsZXNVbmRlcihyb290OiBzdHJpbmcpOiBQcm9taXNlPHN0cmluZ1tdPiB7XG4gIGNvbnN0IGVudHJpZXMgPSBhd2FpdCByZWFkZGlyKHJvb3QsIHsgd2l0aEZpbGVUeXBlczogdHJ1ZSB9KTtcbiAgY29uc3QgZmlsZXM6IHN0cmluZ1tdID0gW107XG4gIGZvciAoY29uc3QgZW50cnkgb2YgZW50cmllcykge1xuICAgIGNvbnN0IHBhdGggPSBqb2luKHJvb3QsIGVudHJ5Lm5hbWUpO1xuICAgIGlmIChlbnRyeS5pc0RpcmVjdG9yeSgpKSB7XG4gICAgICBmaWxlcy5wdXNoKC4uLihhd2FpdCBmaWxlc1VuZGVyKHBhdGgpKSk7XG4gICAgfSBlbHNlIGlmIChlbnRyeS5pc0ZpbGUoKSkge1xuICAgICAgZmlsZXMucHVzaChwYXRoKTtcbiAgICB9XG4gIH1cbiAgcmV0dXJuIGZpbGVzO1xufVxuXG5hc3luYyBmdW5jdGlvbiBzZWxlY3RTaW5nbGVGaWxlKHNvdXJjZTogc3RyaW5nKTogUHJvbWlzZTxzdHJpbmc+IHtcbiAgY29uc3QgZmlsZXMgPSBhd2FpdCBmaWxlc1VuZGVyKHNvdXJjZSk7XG4gIGNvbnN0IHNlbGVjdGVkID0gZmlsZXMuYXQoMCk7XG4gIGlmIChmaWxlcy5sZW5ndGggIT09IDEgfHwgIXNlbGVjdGVkKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKGBiaW5hcnkgYXJjaGl2ZSBtdXN0IGNvbnRhaW4gb25lIGZpbGUsIGZvdW5kICR7ZmlsZXMubGVuZ3RofWApO1xuICB9XG4gIHJldHVybiBzZWxlY3RlZDtcbn1cblxuYXN5bmMgZnVuY3Rpb24gc2VsZWN0ZWRCaW4oXG4gIGRlc3RpbmF0aW9uOiBCaW5Db250ZXh0LFxuICB1cmw6IHN0cmluZyxcbiAgdGVtcDogc3RyaW5nLFxuICBvcHRpb25zOiBCaW5PcHRpb25zLFxuKTogUHJvbWlzZTxzdHJpbmc+IHtcbiAgY29uc3QgbmFtZSA9IGRvd25sb2FkTmFtZSh1cmwsIGJhc2VuYW1lKGRlc3RpbmF0aW9uLnBhdGgpKTtcbiAgY29uc3QgZG93bmxvYWRQYXRoID0gam9pbih0ZW1wLCBuYW1lKTtcbiAgbG9nZ2VyLmRlYnVnKFwiZG93bmxvYWRpbmcgYmluYXJ5XCIsIHtcbiAgICBmcm9tOiBkaXNwbGF5VXJsKHVybCksXG4gICAgdG86IGRvd25sb2FkUGF0aCxcbiAgfSk7XG4gIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZmV0Y2godXJsKTtcbiAgaWYgKCFyZXNwb25zZS5vaykge1xuICAgIHRocm93IG5ldyBFcnJvcihgYmluYXJ5IGRvd25sb2FkIGZhaWxlZCAoJHtyZXNwb25zZS5zdGF0dXN9KWApO1xuICB9XG4gIGF3YWl0IHdyaXRlRmlsZShkb3dubG9hZFBhdGgsIEJ1ZmZlci5mcm9tKGF3YWl0IHJlc3BvbnNlLmFycmF5QnVmZmVyKCkpLCB7IG1vZGU6IDBvNzU1IH0pO1xuXG4gIGxldCBzb3VyY2UgPSBkb3dubG9hZFBhdGg7XG4gIGlmIChvcHRpb25zLmF1dG9VbnBhY2thZ2UpIHtcbiAgICBzb3VyY2UgPSBqb2luKHRlbXAsIGB1bnBhY2tlZC0ke3JhbmRvbVVVSUQoKX1gKTtcbiAgICBhd2FpdCBta2Rpcihzb3VyY2UpO1xuICAgIGxvZ2dlci5kZWJ1ZyhcInVucGFja2luZyBiaW5hcnkgYXJjaGl2ZVwiLCB7XG4gICAgICBhcmNoaXZlOiBkb3dubG9hZFBhdGgsXG4gICAgICB0bzogc291cmNlLFxuICAgIH0pO1xuICAgIGF3YWl0IHVucGFjayhkb3dubG9hZFBhdGgsIHNvdXJjZSk7XG4gIH1cblxuICBpZiAob3B0aW9ucy5zZWxlY3Rvcikge1xuICAgIGNvbnN0IHNlbGVjdGVkID0gYXdhaXQgb3B0aW9ucy5zZWxlY3Rvcih7IGRlc3RpbmF0aW9uLCBkb3dubG9hZFBhdGgsIHNvdXJjZSB9KTtcbiAgICBsb2dnZXIuZGVidWcoXCJzZWxlY3RlZCBiaW5hcnlcIiwgeyBwYXRoOiBzZWxlY3RlZCB9KTtcbiAgICByZXR1cm4gc2VsZWN0ZWQ7XG4gIH1cbiAgY29uc3Qgc2VsZWN0ZWQgPSBvcHRpb25zLmF1dG9VbnBhY2thZ2UgPyBhd2FpdCBzZWxlY3RTaW5nbGVGaWxlKHNvdXJjZSkgOiBzb3VyY2U7XG4gIGxvZ2dlci5kZWJ1ZyhcInNlbGVjdGVkIGJpbmFyeVwiLCB7IHBhdGg6IHNlbGVjdGVkIH0pO1xuICByZXR1cm4gc2VsZWN0ZWQ7XG59XG5cbi8qKlxuICogUmV0dXJuIGFuIGV4aXN0aW5nIGV4ZWN1dGFibGUgb3IgaW5zdGFsbCBpdCBhdG9taWNhbGx5IHVuZGVyXG4gKiBgJEhPTUUvLjxuYW1lPi9iaW4vPG5hbWU+YC4gSW5zdGFsbGF0aW9uIHVzZXMgYSBjaGVjay1sb2NrLWNoZWNrLWxvYWRcbiAqIHNlcXVlbmNlIHNvIGNvbmN1cnJlbnQgY2FsbGVycyByZXNvbHZlIGFuZCBkb3dubG9hZCB0aGUgYmluYXJ5IG9ubHkgb25jZS5cbiAqIFRoZSBkb3dubG9hZGVkIGNhbmRpZGF0ZSBhbmQgZmluYWwgcmVuYW1lZCBleGVjdXRhYmxlIG11c3QgYm90aCBwYXNzIHRoZVxuICogc2FtZSBleGVjdXRhYmxlIGFuZCB2ZXJzaW9uIGNoZWNrcy5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGVuc3VyZShcbiAgbmFtZTogc3RyaW5nLFxuICB1cmw6IEJpblVybCxcbiAgb3B0aW9uczogQmluT3B0aW9ucyA9IHt9LFxuKTogUHJvbWlzZTxCaW5Db250ZXh0PiB7XG4gIGlmIChvcHRpb25zLm1pblZlcnNpb24gJiYgIW51bWVyaWNWZXJzaW9uKG9wdGlvbnMubWluVmVyc2lvbiwgdHJ1ZSkpIHtcbiAgICB0aHJvdyBuZXcgVHlwZUVycm9yKGBpbnZhbGlkIG1pbmltdW0gYmluYXJ5IHZlcnNpb246ICR7b3B0aW9ucy5taW5WZXJzaW9ufWApO1xuICB9XG4gIGNvbnN0IGRlc3RpbmF0aW9uID0gY29udGV4dChuYW1lLCBvcHRpb25zLmhvbWVEaXIgPz8gaG9tZWRpcigpKTtcbiAgaWYgKGF3YWl0IGlzVmFsaWRCaW4oZGVzdGluYXRpb24ucGF0aCwgb3B0aW9ucykpIHtcbiAgICBsb2dnZXIuZGVidWcoXCJ1c2luZyBpbnN0YWxsZWQgYmluYXJ5XCIsIHsgbmFtZSwgcGF0aDogZGVzdGluYXRpb24ucGF0aCB9KTtcbiAgICByZXR1cm4gZGVzdGluYXRpb247XG4gIH1cblxuICBsb2dnZXIuZGVidWcoXCJ3YWl0aW5nIGZvciBiaW5hcnkgaW5zdGFsbCBsb2NrXCIsIHsgbmFtZSwgcGF0aDogZGVzdGluYXRpb24ucGF0aCB9KTtcbiAgcmV0dXJuIHdpdGhQcm9jZXNzTG9jayhbXCJiaW4uZW5zdXJlXCIsIGRlc3RpbmF0aW9uLnBhdGhdLCBhc3luYyAoKSA9PiB7XG4gICAgaWYgKGF3YWl0IGlzVmFsaWRCaW4oZGVzdGluYXRpb24ucGF0aCwgb3B0aW9ucykpIHtcbiAgICAgIGxvZ2dlci5kZWJ1ZyhcInVzaW5nIGJpbmFyeSBpbnN0YWxsZWQgYnkgYW5vdGhlciBjYWxsZXJcIiwge1xuICAgICAgICBuYW1lLFxuICAgICAgICBwYXRoOiBkZXN0aW5hdGlvbi5wYXRoLFxuICAgICAgfSk7XG4gICAgICByZXR1cm4gZGVzdGluYXRpb247XG4gICAgfVxuXG4gICAgY29uc3QgcmVzb2x2ZWRVcmwgPSB0eXBlb2YgdXJsID09PSBcImZ1bmN0aW9uXCIgPyBhd2FpdCB1cmwoKSA6IHVybDtcbiAgICBjb25zdCBmcm9tID0gZGlzcGxheVVybChyZXNvbHZlZFVybCk7XG4gICAgbG9nZ2VyLmRlYnVnKFwiaW5zdGFsbGluZyBiaW5hcnlcIiwge1xuICAgICAgbmFtZSxcbiAgICAgIGZyb20sXG4gICAgICB0bzogZGVzdGluYXRpb24ucGF0aCxcbiAgICAgIG1pblZlcnNpb246IG9wdGlvbnMubWluVmVyc2lvbixcbiAgICB9KTtcbiAgICBjb25zdCB0ZW1wID0gYXdhaXQgbWtkdGVtcChqb2luKHRtcGRpcigpLCBgJHtuYW1lfS1gKSk7XG4gICAgbGV0IHN0YWdlZDogc3RyaW5nIHwgdW5kZWZpbmVkO1xuICAgIHRyeSB7XG4gICAgICBjb25zdCBzZWxlY3RlZCA9IGF3YWl0IHNlbGVjdGVkQmluKGRlc3RpbmF0aW9uLCByZXNvbHZlZFVybCwgdGVtcCwgb3B0aW9ucyk7XG4gICAgICBhd2FpdCBjaG1vZChzZWxlY3RlZCwgMG83NTUpO1xuICAgICAgaWYgKCEoYXdhaXQgaXNWYWxpZEJpbihzZWxlY3RlZCwgb3B0aW9ucykpKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihgc2VsZWN0ZWQgYmluYXJ5IGhhcyBubyBhY2NlcHRhYmxlIHZlcnNpb246ICR7c2VsZWN0ZWR9YCk7XG4gICAgICB9XG5cbiAgICAgIGF3YWl0IG1rZGlyKGRlc3RpbmF0aW9uLmJpbkRpciwgeyByZWN1cnNpdmU6IHRydWUgfSk7XG4gICAgICBzdGFnZWQgPSBqb2luKGRlc3RpbmF0aW9uLmJpbkRpciwgYC4ke25hbWV9LSR7cmFuZG9tVVVJRCgpfWApO1xuICAgICAgYXdhaXQgY29weUZpbGUoc2VsZWN0ZWQsIHN0YWdlZCk7XG4gICAgICBhd2FpdCBjaG1vZChzdGFnZWQsIDBvNzU1KTtcbiAgICAgIGF3YWl0IHJlbmFtZShzdGFnZWQsIGRlc3RpbmF0aW9uLnBhdGgpO1xuICAgICAgc3RhZ2VkID0gdW5kZWZpbmVkO1xuICAgICAgaWYgKCEoYXdhaXQgaXNWYWxpZEJpbihkZXN0aW5hdGlvbi5wYXRoLCBvcHRpb25zKSkpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBpbnN0YWxsZWQgYmluYXJ5IGlzIGludmFsaWQgYWZ0ZXIgcmVuYW1lOiAke2Rlc3RpbmF0aW9uLnBhdGh9YCk7XG4gICAgICB9XG4gICAgICBsb2dnZXIuaW5mbyhcImluc3RhbGxlZCBiaW5hcnlcIiwge1xuICAgICAgICBuYW1lLFxuICAgICAgICBmcm9tLFxuICAgICAgICB0bzogZGVzdGluYXRpb24ucGF0aCxcbiAgICAgIH0pO1xuICAgICAgcmV0dXJuIGRlc3RpbmF0aW9uO1xuICAgIH0gZmluYWxseSB7XG4gICAgICBpZiAoc3RhZ2VkKSBhd2FpdCBybShzdGFnZWQsIHsgZm9yY2U6IHRydWUgfSk7XG4gICAgICBhd2FpdCBybSh0ZW1wLCB7IHJlY3Vyc2l2ZTogdHJ1ZSwgZm9yY2U6IHRydWUgfSk7XG4gICAgfVxuICB9KTtcbn1cbiJdfQ==
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keyed mutual exclusion across the main thread and its worker threads.
|
|
3
|
+
*
|
|
4
|
+
* {@link withProcessLock} serializes callbacks that share a key: one runs at a
|
|
5
|
+
* time, the rest queue in arrival order, and different keys proceed
|
|
6
|
+
* concurrently. Unlike a plain in-module `Promise` chain, the queue is shared by
|
|
7
|
+
* every thread wired up through {@link processLockWorkerOptions}, so a worker
|
|
8
|
+
* pool cannot run two callbacks for the same key at once.
|
|
9
|
+
*
|
|
10
|
+
* The name says `process`: this coordinates the THREADS of one Node process. It
|
|
11
|
+
* is not cross-process and not cross-host - a second `node` invocation, or a
|
|
12
|
+
* second app replica, has its own coordinator and shares nothing. When the scope
|
|
13
|
+
* is a deployment rather than a process, use
|
|
14
|
+
* `@dbx-tools/postgres`'s `withAdvisoryLock`, which puts the arbiter in
|
|
15
|
+
* PostgreSQL where every replica can see it.
|
|
16
|
+
*
|
|
17
|
+
* The main thread owns the only coordinator. Each participating thread gets one
|
|
18
|
+
* `MessagePort` to it, and a lock is granted by a message back over that port.
|
|
19
|
+
* Consequently:
|
|
20
|
+
*
|
|
21
|
+
* - the lock is ADVISORY, like Postgres advisory locks - it protects a
|
|
22
|
+
* critical section only insofar as every writer takes the same key;
|
|
23
|
+
* - fairness is FIFO per key, since the coordinator queues waiters in the
|
|
24
|
+
* order their requests arrive;
|
|
25
|
+
* - a thread that dies while holding a lock releases it, because its port
|
|
26
|
+
* closing is what hands the key to the next waiter (see
|
|
27
|
+
* {@link LockCoordinator.removePort}).
|
|
28
|
+
*
|
|
29
|
+
* @module
|
|
30
|
+
*/
|
|
31
|
+
import { type Worker, type WorkerOptions } from "node:worker_threads";
|
|
32
|
+
/**
|
|
33
|
+
* Run `fn` while holding the lock named by `key`, releasing it when `fn`
|
|
34
|
+
* settles.
|
|
35
|
+
*
|
|
36
|
+
* Callers sharing a key are serialized across the main thread and every worker
|
|
37
|
+
* started through {@link processLockWorkerOptions}; distinct keys never block
|
|
38
|
+
* each other. Returns whatever `fn` returns and propagates what it throws, so it
|
|
39
|
+
* drops into an existing expression without restructuring.
|
|
40
|
+
*
|
|
41
|
+
* `key` is any value with a stable identity - a string, a `["invoice", id]`
|
|
42
|
+
* tuple, a config object - canonicalized by `object.toStableKey`, the same rule
|
|
43
|
+
* `@dbx-tools/postgres` uses for advisory-lock ids and channel names. Structure
|
|
44
|
+
* is part of the identity: `["invoice", 7]` and `"invoice_7"` are different
|
|
45
|
+
* locks.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* await withProcessLock(["cache", name], async () => {
|
|
49
|
+
* if (!(await exists(name))) await build(name);
|
|
50
|
+
* });
|
|
51
|
+
*/
|
|
52
|
+
export declare function withProcessLock<T>(key: unknown, fn: () => T | Promise<T>): Promise<T>;
|
|
53
|
+
/**
|
|
54
|
+
* Add the coordinator port to a `Worker`'s options so the worker can lock
|
|
55
|
+
* immediately - during module initialization, before any message is handled.
|
|
56
|
+
*
|
|
57
|
+
* Preserves the caller's `workerData` and `transferList`; the port is
|
|
58
|
+
* transferred, as `MessagePort` cannot be cloned.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* new Worker(url, processLockWorkerOptions({ workerData: { tenant } }));
|
|
62
|
+
*/
|
|
63
|
+
export declare function processLockWorkerOptions(options?: WorkerOptions): WorkerOptions;
|
|
64
|
+
/**
|
|
65
|
+
* Wire an ALREADY-RUNNING worker into the lock, for a worker this code did not
|
|
66
|
+
* construct (a pool from a library, say).
|
|
67
|
+
*
|
|
68
|
+
* Prefer {@link processLockWorkerOptions}: the port arrives with a message, so
|
|
69
|
+
* the worker cannot lock during module initialization and must await
|
|
70
|
+
* {@link processLockAttached} first. The worker side needs no other change -
|
|
71
|
+
* `withProcessLock` works normally once the port lands.
|
|
72
|
+
*/
|
|
73
|
+
export declare function attachProcessLock(worker: Worker): void;
|
|
74
|
+
/**
|
|
75
|
+
* In a worker, resolve once the {@link attachProcessLock} port has arrived.
|
|
76
|
+
*
|
|
77
|
+
* Only needed on the attach path, and safe to await regardless: it returns
|
|
78
|
+
* immediately when the worker already has a port (the
|
|
79
|
+
* {@link processLockWorkerOptions} case) or when called on the main thread, so
|
|
80
|
+
* shared worker code does not branch on how it was started.
|
|
81
|
+
*
|
|
82
|
+
* A `parentPort` listener keeps the worker alive, which is CORRECT here and
|
|
83
|
+
* deliberately not unref'd: this promise is pending work, and a worker allowed to
|
|
84
|
+
* exit while awaiting its port would die silently instead of locking. The
|
|
85
|
+
* listener is removed as soon as the port lands, so the worker is free to exit
|
|
86
|
+
* again the moment the wait is over.
|
|
87
|
+
*/
|
|
88
|
+
export declare function processLockAttached(): Promise<void>;
|