@shotkit/shotium 0.1.0 → 0.3.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.
- package/README.md +321 -65
- package/dist/daemon_main.js +29 -44
- package/dist/daemon_main.js.map +1 -1
- package/dist/index.d.ts +551 -57
- package/dist/index.js +425 -73
- package/dist/index.js.map +1 -1
- package/dist/protocol-BTeWJDOa.js +474 -0
- package/dist/protocol-BTeWJDOa.js.map +1 -0
- package/native/binding.cc +184 -3
- package/package.json +7 -11
- package/src/index.ts +179 -89
- package/src/lib/binding.ts +110 -0
- package/src/lib/cache.ts +323 -0
- package/src/lib/client.ts +32 -17
- package/src/lib/config.ts +113 -61
- package/src/lib/daemon.ts +97 -62
- package/src/lib/endpoint.ts +19 -12
- package/src/lib/engine.ts +356 -0
- package/src/lib/platform.ts +1 -7
- package/src/lib/request.ts +14 -16
- package/src/types.ts +216 -42
- package/dist/native.d.ts +0 -66
- package/dist/native.js +0 -127
- package/dist/native.js.map +0 -1
- package/dist/platform-DU8DYqmA.js +0 -32
- package/dist/platform-DU8DYqmA.js.map +0 -1
- package/dist/pool-BSgS6vkr.js +0 -356
- package/dist/pool-BSgS6vkr.js.map +0 -1
- package/dist/request-qZXS3N9f.js +0 -43
- package/dist/request-qZXS3N9f.js.map +0 -1
- package/dist/types-x9HtkzeE.d.ts +0 -156
- package/src/lib/pool.ts +0 -243
- package/src/lib/worker.ts +0 -220
- package/src/native.ts +0 -234
package/src/index.ts
CHANGED
|
@@ -1,38 +1,46 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {Cache} from './lib/cache.js';
|
|
3
2
|
import * as client from './lib/client.js';
|
|
4
3
|
import type {DaemonClient} from './lib/client.js';
|
|
5
|
-
import {
|
|
6
|
-
import {Pool} from './lib/pool.js';
|
|
7
|
-
import {SUPERVISOR_MARGIN_MS, timeoutFor, toRequest} from './lib/request.js';
|
|
4
|
+
import {Engine} from './lib/engine.js';
|
|
8
5
|
import type {
|
|
9
6
|
DaemonOptions,
|
|
10
7
|
DaemonStatus,
|
|
8
|
+
ReleaseMemoryOptions,
|
|
11
9
|
ScreenshotOptions,
|
|
10
|
+
ScreenshotResult,
|
|
12
11
|
StartOptions,
|
|
13
|
-
|
|
12
|
+
StartResult,
|
|
14
13
|
} from './types.js';
|
|
15
14
|
|
|
16
15
|
export type {
|
|
16
|
+
CacheClearOptions,
|
|
17
|
+
CacheClearResult,
|
|
18
|
+
CacheEntry,
|
|
19
|
+
CacheMode,
|
|
20
|
+
CacheTarget,
|
|
21
|
+
CaptureStats,
|
|
22
|
+
CaptureTiming,
|
|
17
23
|
Clip,
|
|
18
24
|
DaemonOptions,
|
|
19
25
|
DaemonStatus,
|
|
20
26
|
PageGotoParams,
|
|
21
|
-
|
|
27
|
+
ReleaseMemoryOptions,
|
|
22
28
|
ScreenshotOptions,
|
|
29
|
+
ScreenshotResult,
|
|
23
30
|
StartOptions,
|
|
31
|
+
StartResult,
|
|
24
32
|
Viewport,
|
|
25
|
-
WorkerEvent,
|
|
26
33
|
} from './types.js';
|
|
27
34
|
export type {DaemonClient} from './lib/client.js';
|
|
35
|
+
export {Cache} from './lib/cache.js';
|
|
28
36
|
|
|
29
|
-
/** The five things a caller does with the resident
|
|
37
|
+
/** The five things a caller does with the resident engine. */
|
|
30
38
|
export interface Daemon {
|
|
31
39
|
/** Connects, starting a daemon if none is listening. */
|
|
32
40
|
connect(options?: DaemonOptions): Promise<DaemonClient>;
|
|
33
41
|
/** One screenshot through the daemon, connection and all. */
|
|
34
42
|
screenshot(options: ScreenshotOptions&{daemon?: DaemonOptions}):
|
|
35
|
-
Promise<
|
|
43
|
+
Promise<ScreenshotResult>;
|
|
36
44
|
/** Starts one if it is not up, and reports what is there either way. */
|
|
37
45
|
start(options?: DaemonOptions): Promise<DaemonStatus&{spawned: boolean}>;
|
|
38
46
|
status(options?: DaemonOptions):
|
|
@@ -40,110 +48,174 @@ export interface Daemon {
|
|
|
40
48
|
stop(options?: DaemonOptions): Promise<{stopped: boolean, endpoint: string}>;
|
|
41
49
|
}
|
|
42
50
|
|
|
43
|
-
// The events the pool forwards, and the only ones. Declared as an interface
|
|
44
|
-
// merged into the class below rather than as a catch-all `on(string, ...)`,
|
|
45
|
-
// so that a listener for an event this runtime never emits is a compile error
|
|
46
|
-
// rather than a callback nobody ever calls.
|
|
47
|
-
export interface Runtime {
|
|
48
|
-
on(event: 'ready', listener: (info: {workers: number}) => void): this;
|
|
49
|
-
on(event: 'exit', listener: (event: WorkerEvent) => void): this;
|
|
50
|
-
on(event: 'crash', listener: (event: WorkerEvent) => void): this;
|
|
51
|
-
on(event: 'timeout',
|
|
52
|
-
listener: (event: {worker: number, timeout: number}) => void): this;
|
|
53
|
-
on(event: 'worker-restart',
|
|
54
|
-
listener: (event: {worker: number, reason: string, delay: number}) => void):
|
|
55
|
-
this;
|
|
56
|
-
/** A worker could not be started at all -- a missing or unusable binary. */
|
|
57
|
-
on(event: 'worker-error',
|
|
58
|
-
listener: (event: {worker: number, error: Error}) => void): this;
|
|
59
|
-
on(event: 'stderr',
|
|
60
|
-
listener: (event: {worker: number, line: string}) => void): this;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
51
|
/**
|
|
64
|
-
* The
|
|
52
|
+
* The engine, and its lifecycle, in this process.
|
|
53
|
+
*
|
|
54
|
+
* import shotium from '@shotkit/shotium';
|
|
55
|
+
*
|
|
56
|
+
* shotium.start();
|
|
57
|
+
* const {image, stats} = await shotium.screenshot({
|
|
58
|
+
* file: 'https://example.com',
|
|
59
|
+
* });
|
|
60
|
+
* await shotium.stop();
|
|
65
61
|
*
|
|
66
|
-
* `
|
|
67
|
-
*
|
|
68
|
-
*
|
|
62
|
+
* `start` and `stop` are explicit because starting Blink is the expensive part
|
|
63
|
+
* -- tens of milliseconds and a working set that stays resident -- and only
|
|
64
|
+
* the caller knows whether the next screenshot is coming in a moment or never.
|
|
65
|
+
* Neither call is required: a screenshot starts the engine if it is not up.
|
|
66
|
+
* What they buy is control over when that cost is paid, and the certainty that
|
|
67
|
+
* it has been given back.
|
|
69
68
|
*
|
|
70
|
-
*
|
|
71
|
-
* a
|
|
72
|
-
*
|
|
69
|
+
* Neither is rationed, either. They may be called in any order and as often as
|
|
70
|
+
* a program likes: `stop()` stands the engine down and `start()` picks the
|
|
71
|
+
* same one back up, warm cache and all. What cannot happen is a *second*
|
|
72
|
+
* engine -- Blink is initialised once per process and there is no undo -- but
|
|
73
|
+
* that is a fact about how many there are, not about how many times the one
|
|
74
|
+
* may be asked for.
|
|
75
|
+
*
|
|
76
|
+
* The methods are on the module rather than under a `runtime` namespace, which
|
|
77
|
+
* they were until 0.3. There was never anything else to start, so the word
|
|
78
|
+
* carried nothing; and `runtime.cache` would have been the wrong place for the
|
|
79
|
+
* cache besides, since a cache directory outlives every engine that writes to
|
|
80
|
+
* it and can be read when no engine is running at all.
|
|
81
|
+
*
|
|
82
|
+
* `Runtime` is still exported for a caller who wants to own a lifecycle rather
|
|
83
|
+
* than share the module's. It is a lifecycle and not an engine: there is one
|
|
84
|
+
* engine per process, and a second Runtime that starts adopts the same one
|
|
85
|
+
* rather than building another. Parallelism is more processes, not more
|
|
86
|
+
* Runtimes.
|
|
87
|
+
*
|
|
88
|
+
* `daemon` is the same engine in a process of its own, behind a socket, for
|
|
89
|
+
* callers whose own process does not live long enough to be worth starting
|
|
90
|
+
* one.
|
|
73
91
|
*/
|
|
74
|
-
export class Runtime
|
|
75
|
-
private
|
|
92
|
+
export class Runtime {
|
|
93
|
+
private engine = new Engine();
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The HTTP cache: where it is, what is in it, and how to empty it.
|
|
97
|
+
*
|
|
98
|
+
* On the Runtime as well as on the module because a caller holding their own
|
|
99
|
+
* Runtime needs the engine handle to reach a directory that engine has open:
|
|
100
|
+
* within one process a directory has one backend, so borrowing is the only
|
|
101
|
+
* way in.
|
|
102
|
+
*/
|
|
103
|
+
readonly cache = new Cache(() => this.engine.nativeHandle);
|
|
76
104
|
|
|
77
105
|
get running(): boolean {
|
|
78
|
-
return this.
|
|
106
|
+
return this.engine.running;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Starts the engine, or picks the running one back up.
|
|
111
|
+
*
|
|
112
|
+
* Callable as often as you like, in any order with `stop()`; library code
|
|
113
|
+
* can call it defensively. The first call in a process builds the engine and
|
|
114
|
+
* every later one adopts it -- the same engine, the same warm cache. The one
|
|
115
|
+
* thing it will refuse is a *different* configuration: the options below are
|
|
116
|
+
* fixed when the engine is built, and there is no second build, so naming
|
|
117
|
+
* one that disagrees with what is running throws rather than rendering with
|
|
118
|
+
* a value you did not ask for.
|
|
119
|
+
*
|
|
120
|
+
* Every option has a default. `cacheDir` is the HTTP disk cache and defaults
|
|
121
|
+
* to a per-project directory under `~/.shotium/cache`, and not under the
|
|
122
|
+
* temporary directory, which is defined by not surviving. `null` turns it
|
|
123
|
+
* off. `resourceDir` is where `shotium_data.pak` and
|
|
124
|
+
* `shotium_strings.pak` are, and defaults to the directory the engine was
|
|
125
|
+
* loaded from, which is where they ship.
|
|
126
|
+
*
|
|
127
|
+
* The return value is worth reading once. `cacheActive: false` with a
|
|
128
|
+
* `cacheDir` set means the directory could not be opened and this engine is
|
|
129
|
+
* running without a cache -- correctly, silently, and a round trip slower on
|
|
130
|
+
* everything.
|
|
131
|
+
*/
|
|
132
|
+
start(options: StartOptions = {}): StartResult {
|
|
133
|
+
return this.engine.start(options);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** What `start()` returned, asked again. */
|
|
137
|
+
status(): StartResult {
|
|
138
|
+
return this.engine.status();
|
|
79
139
|
}
|
|
80
140
|
|
|
81
141
|
/**
|
|
82
|
-
*
|
|
83
|
-
*
|
|
142
|
+
* Stands the engine down, after whatever is queued.
|
|
143
|
+
*
|
|
144
|
+
* The queue drains, the memory the engine can rebuild goes back to the OS,
|
|
145
|
+
* and `running` becomes false. Blink itself stays initialised, because there
|
|
146
|
+
* is no way to un-initialise it -- so the disk cache stays where it is, and
|
|
147
|
+
* `start()` or the next `screenshot()` picks the same engine back up.
|
|
84
148
|
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* the system temp, and `null` disables caching.
|
|
149
|
+
* Which makes this a caller saying they are done for now rather than a
|
|
150
|
+
* destructor. It does the same work as `releaseMemory({releaseWorkingSet:
|
|
151
|
+
* true})` and additionally stops accepting captures.
|
|
89
152
|
*/
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
return this;
|
|
93
|
-
}
|
|
94
|
-
const pool = new Pool(resolveStartOptions(options));
|
|
95
|
-
this.pool = pool;
|
|
96
|
-
for (const event
|
|
97
|
-
of ['ready', 'exit', 'crash', 'timeout', 'worker-restart',
|
|
98
|
-
'worker-error', 'stderr']) {
|
|
99
|
-
pool.on(event, (payload) => this.emit(event, payload));
|
|
100
|
-
}
|
|
101
|
-
pool.start();
|
|
102
|
-
return this;
|
|
153
|
+
stop(): Promise<void> {
|
|
154
|
+
return this.engine.stop();
|
|
103
155
|
}
|
|
104
156
|
|
|
105
|
-
/**
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
157
|
+
/**
|
|
158
|
+
* Hands back what the engine is holding but can rebuild: Blink's heap,
|
|
159
|
+
* skia's caches, PartitionAlloc's free lists. Worth calling when a batch has
|
|
160
|
+
* ended and the next one may be a while away.
|
|
161
|
+
*
|
|
162
|
+
* This is memory and nothing else. It was called `purge()` until 0.3, which
|
|
163
|
+
* next to `cache.clear()` read as though it emptied the HTTP cache; it does
|
|
164
|
+
* not touch the disk at all.
|
|
165
|
+
*/
|
|
166
|
+
releaseMemory(options: ReleaseMemoryOptions = {}): void {
|
|
167
|
+
this.engine.releaseMemory(options);
|
|
113
168
|
}
|
|
114
169
|
|
|
115
170
|
/**
|
|
116
|
-
* Renders one screenshot
|
|
117
|
-
*
|
|
171
|
+
* Renders one screenshot, and reports what it cost.
|
|
172
|
+
*
|
|
173
|
+
* `image` is the encoded bytes, or `null` when `path` was given and the
|
|
174
|
+
* engine wrote the file itself. `stats` says how many resources were
|
|
175
|
+
* fetched, how many came from the cache, and where the milliseconds went --
|
|
176
|
+
* which for an `https:` URL is usually the answer to "why did this take so
|
|
177
|
+
* long", because a cold connection costs more than the render does.
|
|
118
178
|
*/
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
// pool of worker processes to discover, and toRequest() is the only check
|
|
122
|
-
// that can be made without one.
|
|
123
|
-
const request = toRequest(options);
|
|
124
|
-
if (!this.pool) {
|
|
125
|
-
this.start();
|
|
126
|
-
}
|
|
127
|
-
const retry = typeof options.retry === 'number' ? options.retry : 0;
|
|
128
|
-
const result = await this.pool!.submit(request, {
|
|
129
|
-
timeout: timeoutFor(options) + SUPERVISOR_MARGIN_MS,
|
|
130
|
-
retry,
|
|
131
|
-
});
|
|
132
|
-
return result.image;
|
|
179
|
+
screenshot(options: ScreenshotOptions): Promise<ScreenshotResult> {
|
|
180
|
+
return this.engine.screenshot(options);
|
|
133
181
|
}
|
|
134
182
|
}
|
|
135
183
|
|
|
136
|
-
/** The shared
|
|
184
|
+
/** The shared engine: one per process, started on first use. */
|
|
137
185
|
const runtime = new Runtime();
|
|
138
186
|
|
|
139
|
-
/** One screenshot through the shared
|
|
140
|
-
const screenshot = (options: ScreenshotOptions): Promise<
|
|
187
|
+
/** One screenshot through the shared engine, starting it if it is not up. */
|
|
188
|
+
const screenshot = (options: ScreenshotOptions): Promise<ScreenshotResult> =>
|
|
141
189
|
runtime.screenshot(options);
|
|
142
190
|
|
|
191
|
+
const start = (options?: StartOptions): StartResult => runtime.start(options);
|
|
192
|
+
const status = (): StartResult => runtime.status();
|
|
193
|
+
const stop = (): Promise<void> => runtime.stop();
|
|
194
|
+
const releaseMemory = (options?: ReleaseMemoryOptions): void =>
|
|
195
|
+
runtime.releaseMemory(options);
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* The HTTP cache.
|
|
199
|
+
*
|
|
200
|
+
* At the top level rather than under the engine because it outlives one: the
|
|
201
|
+
* directory is on disk whether or not anything is running, `getDir()` answers
|
|
202
|
+
* before the first `start()`, and clearing it is something a program may want
|
|
203
|
+
* to do without bringing Blink up at all. When an engine *is* up, these
|
|
204
|
+
* borrow its cache backend, because within one process a directory has one
|
|
205
|
+
* backend and that is the only way in.
|
|
206
|
+
*/
|
|
207
|
+
const cache = runtime.cache;
|
|
208
|
+
|
|
143
209
|
/**
|
|
144
|
-
* The resident
|
|
210
|
+
* The resident engine: a process that outlives the one that started it,
|
|
145
211
|
* reachable over a named pipe on Windows and a unix socket elsewhere. For
|
|
146
212
|
* callers that are short-lived themselves. See lib/daemon.ts.
|
|
213
|
+
*
|
|
214
|
+
* It has no `cache` of its own. A daemon's cache directory is reported by
|
|
215
|
+
* `daemon.status()`, and clearing it is done by pointing `cache.clear()` at
|
|
216
|
+
* that directory or by stopping the daemon -- a cross-process cache protocol
|
|
217
|
+
* would be a second implementation of this module for something nobody does on
|
|
218
|
+
* a request path.
|
|
147
219
|
*/
|
|
148
220
|
const daemon: Daemon = {
|
|
149
221
|
connect: client.connect,
|
|
@@ -153,9 +225,27 @@ const daemon: Daemon = {
|
|
|
153
225
|
stop: client.stop,
|
|
154
226
|
};
|
|
155
227
|
|
|
156
|
-
export {runtime, screenshot,
|
|
228
|
+
export {cache, daemon, releaseMemory, runtime, screenshot, start, status, stop};
|
|
157
229
|
|
|
158
230
|
// A default as well as the names, because `import shotium from` is what a
|
|
159
231
|
// caller coming from `require` writes first, and the two have to be the same
|
|
160
232
|
// object rather than two views that drift.
|
|
161
|
-
export default {
|
|
233
|
+
export default {
|
|
234
|
+
Runtime,
|
|
235
|
+
cache,
|
|
236
|
+
daemon,
|
|
237
|
+
releaseMemory,
|
|
238
|
+
runtime,
|
|
239
|
+
screenshot,
|
|
240
|
+
start,
|
|
241
|
+
status,
|
|
242
|
+
stop,
|
|
243
|
+
// A getter and not a value, because it changes. It is only on the default
|
|
244
|
+
// export: a named `running` would have to be a live binding that something
|
|
245
|
+
// remembered to update, and the two would disagree the first time anybody
|
|
246
|
+
// forgot. Callers who import by name have `status().running`, which is the
|
|
247
|
+
// same answer with the cache directory attached.
|
|
248
|
+
get running(): boolean {
|
|
249
|
+
return runtime.running;
|
|
250
|
+
},
|
|
251
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import {createRequire} from 'node:module';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import {fileURLToPath} from 'node:url';
|
|
5
|
+
|
|
6
|
+
import * as platformPackage from './platform.js';
|
|
7
|
+
|
|
8
|
+
// A .node addon is a CommonJS artefact: there is no ESM loader for one.
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
|
|
11
|
+
// ESM has no __dirname. This is the same thing, from the module's own URL.
|
|
12
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The engine handle the addon hands back. Opaque on purpose: everything that
|
|
16
|
+
* can be done with it is a call on the binding below.
|
|
17
|
+
*/
|
|
18
|
+
export type Engine = unknown;
|
|
19
|
+
|
|
20
|
+
/** One capture's answer, as the addon hands it over. */
|
|
21
|
+
export interface NativeCapture {
|
|
22
|
+
image: Buffer;
|
|
23
|
+
/**
|
|
24
|
+
* CaptureStats as JSON, unparsed. The addon carries JSON between the engine
|
|
25
|
+
* and this layer without reading it -- anything it understood would be a
|
|
26
|
+
* third opinion about the shape, and the third opinion is the one that
|
|
27
|
+
* drifts. Undefined when the engine reported none.
|
|
28
|
+
*/
|
|
29
|
+
stats?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** What native/binding.cc exports. See shot/shot_api.h for the C ABI. */
|
|
33
|
+
export interface NativeBinding {
|
|
34
|
+
create(optionsJson: string): Engine;
|
|
35
|
+
destroy(engine: Engine): void;
|
|
36
|
+
purge(engine: Engine, releaseWorkingSet: boolean): void;
|
|
37
|
+
status(engine: Engine): string;
|
|
38
|
+
capture(engine: Engine, requestJson: string): Promise<NativeCapture>;
|
|
39
|
+
/**
|
|
40
|
+
* List or clear a cache directory. `engine` is nullable and that is the
|
|
41
|
+
* interface: with one, the operation runs on the engine's thread and borrows
|
|
42
|
+
* the backend it already holds; without one, the library opens the directory
|
|
43
|
+
* itself. Resolves to JSON.
|
|
44
|
+
*/
|
|
45
|
+
cache(engine: Engine|null, clearing: boolean, optionsJson: string):
|
|
46
|
+
Promise<string>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Where the addon and the library beside it live.
|
|
50
|
+
//
|
|
51
|
+
// The platform package is what ships -- the .node sits next to the shared
|
|
52
|
+
// library it is linked against, which is the whole reason the two travel in
|
|
53
|
+
// one package rather than two. native/build/Release is where node-gyp puts a
|
|
54
|
+
// local build; it exists in a checkout and not in an install, so the two never
|
|
55
|
+
// compete in practice. Both paths are relative to this file's build output,
|
|
56
|
+
// which is one directory below the package root.
|
|
57
|
+
function candidates(): string[] {
|
|
58
|
+
const found: string[] = [];
|
|
59
|
+
const dir = platformPackage.packageDir();
|
|
60
|
+
if (dir) {
|
|
61
|
+
found.push(path.join(dir, 'shotium.node'));
|
|
62
|
+
}
|
|
63
|
+
found.push(
|
|
64
|
+
path.join(HERE, '..', 'native', 'build', 'Release', 'shotium.node'));
|
|
65
|
+
return found;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let binding: NativeBinding|null = null;
|
|
69
|
+
let loadedFrom: string|null = null;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The addon, loaded once. Throws if there is none for this platform, which is
|
|
73
|
+
* the only failure this package cannot work around: there is nothing else to
|
|
74
|
+
* fall back to.
|
|
75
|
+
*/
|
|
76
|
+
export function load(): NativeBinding {
|
|
77
|
+
if (binding) {
|
|
78
|
+
return binding;
|
|
79
|
+
}
|
|
80
|
+
const tried = candidates();
|
|
81
|
+
for (const candidate of tried) {
|
|
82
|
+
if (!fs.existsSync(candidate)) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
// Not wrapped in a try: a .node that is there and will not load is a
|
|
86
|
+
// broken installation, and the loader's own message -- a missing
|
|
87
|
+
// dependency, an architecture mismatch -- says more than anything that
|
|
88
|
+
// could be substituted for it.
|
|
89
|
+
binding = require(candidate) as NativeBinding;
|
|
90
|
+
loadedFrom = path.dirname(candidate);
|
|
91
|
+
return binding;
|
|
92
|
+
}
|
|
93
|
+
const expected = platformPackage.packageName();
|
|
94
|
+
throw new Error(
|
|
95
|
+
'shotium: no engine for this platform.\n' +
|
|
96
|
+
` looked in:\n ${tried.join('\n ')}\n` +
|
|
97
|
+
(expected ?
|
|
98
|
+
` It ships in ${expected}, which npm installs as an optional ` +
|
|
99
|
+
'dependency of this package. If the install skipped optional ' +
|
|
100
|
+
'dependencies, it is not there.\n' :
|
|
101
|
+
` There is no build for ${process.platform}-${process.arch}.\n`));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The directory the addon came from, or null before the first load(). The
|
|
106
|
+
* resource packs ship beside it, which is what this is for.
|
|
107
|
+
*/
|
|
108
|
+
export function directory(): string|null {
|
|
109
|
+
return loadedFrom;
|
|
110
|
+
}
|