@shotkit/shotium 0.1.0 → 0.2.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 +38 -39
- package/dist/daemon_main.js +25 -42
- package/dist/daemon_main.js.map +1 -1
- package/dist/engine-Xe7nH-1i.js +267 -0
- package/dist/engine-Xe7nH-1i.js.map +1 -0
- package/dist/index.d.ts +181 -48
- package/dist/index.js +60 -56
- package/dist/index.js.map +1 -1
- package/package.json +7 -11
- package/src/index.ts +59 -81
- package/src/lib/binding.ts +89 -0
- package/src/lib/client.ts +8 -12
- package/src/lib/config.ts +11 -66
- package/src/lib/daemon.ts +70 -58
- package/src/lib/endpoint.ts +19 -12
- package/src/lib/engine.ts +168 -0
- package/src/lib/platform.ts +1 -7
- package/src/lib/request.ts +4 -15
- package/src/types.ts +19 -45
- 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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shotkit/shotium",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Static screenshots from a stripped Chromium: DOM, CSS, layout, paint, no JavaScript engine.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"screenshot",
|
|
@@ -24,10 +24,6 @@
|
|
|
24
24
|
"types": "./dist/index.d.ts",
|
|
25
25
|
"default": "./dist/index.js"
|
|
26
26
|
},
|
|
27
|
-
"./native": {
|
|
28
|
-
"types": "./dist/native.d.ts",
|
|
29
|
-
"default": "./dist/native.js"
|
|
30
|
-
},
|
|
31
27
|
"./package.json": "./package.json"
|
|
32
28
|
},
|
|
33
29
|
"files": [
|
|
@@ -42,12 +38,12 @@
|
|
|
42
38
|
"node": ">=18"
|
|
43
39
|
},
|
|
44
40
|
"optionalDependencies": {
|
|
45
|
-
"@shotkit/shotium-darwin-arm64": "0.
|
|
46
|
-
"@shotkit/shotium-darwin-x64": "0.
|
|
47
|
-
"@shotkit/shotium-linux-arm64": "0.
|
|
48
|
-
"@shotkit/shotium-linux-x64": "0.
|
|
49
|
-
"@shotkit/shotium-win32-arm64": "0.
|
|
50
|
-
"@shotkit/shotium-win32-x64": "0.
|
|
41
|
+
"@shotkit/shotium-darwin-arm64": "0.2.0",
|
|
42
|
+
"@shotkit/shotium-darwin-x64": "0.2.0",
|
|
43
|
+
"@shotkit/shotium-linux-arm64": "0.2.0",
|
|
44
|
+
"@shotkit/shotium-linux-x64": "0.2.0",
|
|
45
|
+
"@shotkit/shotium-win32-arm64": "0.2.0",
|
|
46
|
+
"@shotkit/shotium-win32-x64": "0.2.0"
|
|
51
47
|
},
|
|
52
48
|
"scripts": {
|
|
53
49
|
"build": "tsdown",
|
package/src/index.ts
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
|
-
import {EventEmitter} from 'node:events';
|
|
2
|
-
|
|
3
1
|
import * as client from './lib/client.js';
|
|
4
2
|
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';
|
|
3
|
+
import {Engine} from './lib/engine.js';
|
|
8
4
|
import type {
|
|
9
5
|
DaemonOptions,
|
|
10
6
|
DaemonStatus,
|
|
7
|
+
PurgeOptions,
|
|
11
8
|
ScreenshotOptions,
|
|
12
9
|
StartOptions,
|
|
13
|
-
WorkerEvent,
|
|
14
10
|
} from './types.js';
|
|
15
11
|
|
|
16
12
|
export type {
|
|
@@ -22,11 +18,10 @@ export type {
|
|
|
22
18
|
ScreenshotOptions,
|
|
23
19
|
StartOptions,
|
|
24
20
|
Viewport,
|
|
25
|
-
WorkerEvent,
|
|
26
21
|
} from './types.js';
|
|
27
22
|
export type {DaemonClient} from './lib/client.js';
|
|
28
23
|
|
|
29
|
-
/** The five things a caller does with the resident
|
|
24
|
+
/** The five things a caller does with the resident engine. */
|
|
30
25
|
export interface Daemon {
|
|
31
26
|
/** Connects, starting a daemon if none is listening. */
|
|
32
27
|
connect(options?: DaemonOptions): Promise<DaemonClient>;
|
|
@@ -40,108 +35,91 @@ export interface Daemon {
|
|
|
40
35
|
stop(options?: DaemonOptions): Promise<{stopped: boolean, endpoint: string}>;
|
|
41
36
|
}
|
|
42
37
|
|
|
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
38
|
/**
|
|
64
|
-
* The
|
|
39
|
+
* The engine, and its lifecycle, in this process.
|
|
40
|
+
*
|
|
41
|
+
* import shotium from '@shotkit/shotium';
|
|
42
|
+
*
|
|
43
|
+
* shotium.runtime.start();
|
|
44
|
+
* const png = await shotium.screenshot({file: 'https://example.com'});
|
|
45
|
+
* await shotium.runtime.stop();
|
|
46
|
+
*
|
|
47
|
+
* `start` and `stop` are explicit because starting Blink is the expensive part
|
|
48
|
+
* -- tens of milliseconds and a working set that stays resident -- and only
|
|
49
|
+
* the caller knows whether the next screenshot is coming in a moment or never.
|
|
50
|
+
* Neither call is required: a screenshot starts the engine if it is not up.
|
|
51
|
+
* What they buy is control over when that cost is paid, and the certainty that
|
|
52
|
+
* it has been given back.
|
|
65
53
|
*
|
|
66
|
-
* `runtime` below is the singleton
|
|
67
|
-
*
|
|
68
|
-
*
|
|
54
|
+
* `runtime` below is the singleton because there is nothing else it could be:
|
|
55
|
+
* Blink starts once per process and cannot be restarted, so a second Runtime
|
|
56
|
+
* in the same process has no engine to have. Construct one directly only to
|
|
57
|
+
* own the lifecycle yourself instead of using `runtime`. Parallelism is more
|
|
58
|
+
* processes, not more Runtimes.
|
|
69
59
|
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
60
|
+
* `daemon` is the same engine in a process of its own, behind a socket, for
|
|
61
|
+
* callers whose own process does not live long enough to be worth starting
|
|
62
|
+
* one.
|
|
73
63
|
*/
|
|
74
|
-
export class Runtime
|
|
75
|
-
private
|
|
64
|
+
export class Runtime {
|
|
65
|
+
private engine = new Engine();
|
|
76
66
|
|
|
77
67
|
get running(): boolean {
|
|
78
|
-
return this.
|
|
68
|
+
return this.engine.running;
|
|
79
69
|
}
|
|
80
70
|
|
|
81
71
|
/**
|
|
82
|
-
* Starts the
|
|
83
|
-
* library code can call it defensively.
|
|
72
|
+
* Starts the engine. Safe to call twice; the second call is a no-op, so that
|
|
73
|
+
* library code can call it defensively. Not safe after `stop()` -- see there.
|
|
84
74
|
*
|
|
85
|
-
* Every option has a default
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
75
|
+
* Every option has a default. `cacheDir` is the HTTP disk cache and `null`
|
|
76
|
+
* disables it; `resourceDir` is where `shotium_data.pak` and
|
|
77
|
+
* `shotium_strings.pak` are, and defaults to the directory the engine was
|
|
78
|
+
* loaded from, which is where they ship.
|
|
89
79
|
*/
|
|
90
80
|
start(options: StartOptions = {}): this {
|
|
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();
|
|
81
|
+
this.engine.start(options);
|
|
102
82
|
return this;
|
|
103
83
|
}
|
|
104
84
|
|
|
105
|
-
/**
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
85
|
+
/**
|
|
86
|
+
* Stops the engine, after whatever is queued.
|
|
87
|
+
*
|
|
88
|
+
* Final for this process. Blink writes process-wide state that it has no
|
|
89
|
+
* path to undo, so starting again -- here or on another Runtime -- throws
|
|
90
|
+
* rather than quietly handing back something that cannot render. A program
|
|
91
|
+
* that wants another screenshot later should stay started and `purge()`.
|
|
92
|
+
*/
|
|
93
|
+
stop(): Promise<void> {
|
|
94
|
+
return this.engine.stop();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Hands back what the engine is holding but can rebuild. Worth calling when
|
|
99
|
+
* a batch has ended and the next one may be a while away.
|
|
100
|
+
*/
|
|
101
|
+
purge(options: PurgeOptions = {}): void {
|
|
102
|
+
this.engine.purge(options);
|
|
113
103
|
}
|
|
114
104
|
|
|
115
105
|
/**
|
|
116
106
|
* Renders one screenshot. Resolves to the encoded image, or to `null` when
|
|
117
|
-
* `path` was given and the
|
|
107
|
+
* `path` was given and the engine wrote the file itself.
|
|
118
108
|
*/
|
|
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;
|
|
109
|
+
screenshot(options: ScreenshotOptions): Promise<Buffer|null> {
|
|
110
|
+
return this.engine.screenshot(options);
|
|
133
111
|
}
|
|
134
112
|
}
|
|
135
113
|
|
|
136
|
-
/** The shared
|
|
114
|
+
/** The shared engine: one per process, started on first use. */
|
|
137
115
|
const runtime = new Runtime();
|
|
138
116
|
|
|
139
|
-
/** One screenshot through the shared
|
|
117
|
+
/** One screenshot through the shared engine, starting it if it is not up. */
|
|
140
118
|
const screenshot = (options: ScreenshotOptions): Promise<Buffer|null> =>
|
|
141
119
|
runtime.screenshot(options);
|
|
142
120
|
|
|
143
121
|
/**
|
|
144
|
-
* The resident
|
|
122
|
+
* The resident engine: a process that outlives the one that started it,
|
|
145
123
|
* reachable over a named pipe on Windows and a unix socket elsewhere. For
|
|
146
124
|
* callers that are short-lived themselves. See lib/daemon.ts.
|
|
147
125
|
*/
|
|
@@ -0,0 +1,89 @@
|
|
|
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
|
+
/** What native/binding.cc exports. See shot/shot_api.h for the C ABI. */
|
|
21
|
+
export interface NativeBinding {
|
|
22
|
+
create(optionsJson: string): Engine;
|
|
23
|
+
destroy(engine: Engine): void;
|
|
24
|
+
purge(engine: Engine, releaseWorkingSet: boolean): void;
|
|
25
|
+
capture(engine: Engine, requestJson: string): Promise<Buffer>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Where the addon and the library beside it live.
|
|
29
|
+
//
|
|
30
|
+
// The platform package is what ships -- the .node sits next to the shared
|
|
31
|
+
// library it is linked against, which is the whole reason the two travel in
|
|
32
|
+
// one package rather than two. native/build/Release is where node-gyp puts a
|
|
33
|
+
// local build; it exists in a checkout and not in an install, so the two never
|
|
34
|
+
// compete in practice. Both paths are relative to this file's build output,
|
|
35
|
+
// which is one directory below the package root.
|
|
36
|
+
function candidates(): string[] {
|
|
37
|
+
const found: string[] = [];
|
|
38
|
+
const dir = platformPackage.packageDir();
|
|
39
|
+
if (dir) {
|
|
40
|
+
found.push(path.join(dir, 'shotium.node'));
|
|
41
|
+
}
|
|
42
|
+
found.push(
|
|
43
|
+
path.join(HERE, '..', 'native', 'build', 'Release', 'shotium.node'));
|
|
44
|
+
return found;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let binding: NativeBinding|null = null;
|
|
48
|
+
let loadedFrom: string|null = null;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The addon, loaded once. Throws if there is none for this platform, which is
|
|
52
|
+
* the only failure this package cannot work around: there is nothing else to
|
|
53
|
+
* fall back to.
|
|
54
|
+
*/
|
|
55
|
+
export function load(): NativeBinding {
|
|
56
|
+
if (binding) {
|
|
57
|
+
return binding;
|
|
58
|
+
}
|
|
59
|
+
const tried = candidates();
|
|
60
|
+
for (const candidate of tried) {
|
|
61
|
+
if (!fs.existsSync(candidate)) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
// Not wrapped in a try: a .node that is there and will not load is a
|
|
65
|
+
// broken installation, and the loader's own message -- a missing
|
|
66
|
+
// dependency, an architecture mismatch -- says more than anything that
|
|
67
|
+
// could be substituted for it.
|
|
68
|
+
binding = require(candidate) as NativeBinding;
|
|
69
|
+
loadedFrom = path.dirname(candidate);
|
|
70
|
+
return binding;
|
|
71
|
+
}
|
|
72
|
+
const expected = platformPackage.packageName();
|
|
73
|
+
throw new Error(
|
|
74
|
+
'shotium: no engine for this platform.\n' +
|
|
75
|
+
` looked in:\n ${tried.join('\n ')}\n` +
|
|
76
|
+
(expected ?
|
|
77
|
+
` It ships in ${expected}, which npm installs as an optional ` +
|
|
78
|
+
'dependency of this package. If the install skipped optional ' +
|
|
79
|
+
'dependencies, it is not there.\n' :
|
|
80
|
+
` There is no build for ${process.platform}-${process.arch}.\n`));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The directory the addon came from, or null before the first load(). The
|
|
85
|
+
* resource packs ship beside it, which is what this is for.
|
|
86
|
+
*/
|
|
87
|
+
export function directory(): string|null {
|
|
88
|
+
return loadedFrom;
|
|
89
|
+
}
|
package/src/lib/client.ts
CHANGED
|
@@ -48,10 +48,9 @@ interface Pending {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
interface ResolvedDaemonOptions {
|
|
51
|
-
binary: string;
|
|
52
|
-
workers: number;
|
|
53
51
|
cacheDir: string|null;
|
|
54
|
-
|
|
52
|
+
userAgent?: string;
|
|
53
|
+
resourceDir?: string;
|
|
55
54
|
name: string|undefined;
|
|
56
55
|
endpoint: string;
|
|
57
56
|
idleTimeoutMs: number|undefined;
|
|
@@ -61,11 +60,11 @@ interface ResolvedDaemonOptions {
|
|
|
61
60
|
|
|
62
61
|
// The client half of the resident daemon.
|
|
63
62
|
//
|
|
64
|
-
// One connection can carry several requests at once
|
|
65
|
-
// between this and the worker protocol underneath: every message carries an
|
|
63
|
+
// One connection can carry several requests at once: every message carries an
|
|
66
64
|
// `id` and the answers are matched back by it, so a caller can fire ten
|
|
67
|
-
// screenshots down one socket
|
|
68
|
-
//
|
|
65
|
+
// screenshots down one socket without waiting between them. They still come
|
|
66
|
+
// back one at a time -- there is one renderer on the other side -- so this
|
|
67
|
+
// saves the round trips, not the renders.
|
|
69
68
|
class DaemonClient extends EventEmitter {
|
|
70
69
|
private readonly socket: net.Socket;
|
|
71
70
|
private readonly endpointPath: string;
|
|
@@ -155,12 +154,10 @@ class DaemonClient extends EventEmitter {
|
|
|
155
154
|
/** Resolves to the image, or to null when `path` was given. */
|
|
156
155
|
async screenshot(options: ScreenshotOptions): Promise<Buffer|null> {
|
|
157
156
|
const request = toRequest(options);
|
|
158
|
-
const retry = typeof options.retry === 'number' ? options.retry : 0;
|
|
159
157
|
const result = await this.send({
|
|
160
158
|
op: 'screenshot',
|
|
161
159
|
request,
|
|
162
160
|
timeout: timeoutFor(options),
|
|
163
|
-
retry,
|
|
164
161
|
});
|
|
165
162
|
return result.image;
|
|
166
163
|
}
|
|
@@ -219,10 +216,9 @@ function resolveDaemonOptions(options: DaemonOptions = {}):
|
|
|
219
216
|
|
|
220
217
|
function spawnDaemon(options: ResolvedDaemonOptions): void {
|
|
221
218
|
const config = {
|
|
222
|
-
binary: options.binary,
|
|
223
|
-
workers: options.workers,
|
|
224
219
|
cacheDir: options.cacheDir,
|
|
225
|
-
|
|
220
|
+
userAgent: options.userAgent,
|
|
221
|
+
resourceDir: options.resourceDir,
|
|
226
222
|
endpoint: options.endpoint,
|
|
227
223
|
idleTimeoutMs: options.idleTimeoutMs,
|
|
228
224
|
prewarm: options.prewarm,
|
package/src/lib/config.ts
CHANGED
|
@@ -1,21 +1,11 @@
|
|
|
1
|
-
import os from 'node:os';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import {fileURLToPath} from 'node:url';
|
|
4
|
-
|
|
5
1
|
import type {StartOptions} from '../types.js';
|
|
6
2
|
|
|
7
|
-
import * as platform from './platform.js';
|
|
8
|
-
|
|
9
|
-
// ESM has no __dirname. This is the same thing, from the module's own URL.
|
|
10
|
-
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
-
|
|
12
3
|
// StartOptions with every hole filled in. `cacheDir` is still nullable here
|
|
13
4
|
// because null is an answer -- "no disk cache" -- and not an absent one.
|
|
14
5
|
export interface ResolvedStartOptions {
|
|
15
|
-
binary: string;
|
|
16
|
-
workers: number;
|
|
17
6
|
cacheDir: string|null;
|
|
18
|
-
|
|
7
|
+
userAgent?: string;
|
|
8
|
+
resourceDir?: string;
|
|
19
9
|
}
|
|
20
10
|
|
|
21
11
|
// The one place that decides what "no options" means.
|
|
@@ -23,64 +13,19 @@ export interface ResolvedStartOptions {
|
|
|
23
13
|
// It is shared rather than duplicated because the daemon's address is a hash of
|
|
24
14
|
// its configuration: if two callers filled in defaults even slightly
|
|
25
15
|
// differently, one would compute an address no daemon is listening on and
|
|
26
|
-
// start a second
|
|
16
|
+
// start a second engine next to the first one that was already warm. See
|
|
27
17
|
// endpoint.ts.
|
|
28
18
|
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
if (process.env.SHOTIUM_BINARY) {
|
|
34
|
-
return process.env.SHOTIUM_BINARY;
|
|
35
|
-
}
|
|
36
|
-
const dir = platform.packageDir();
|
|
37
|
-
if (dir) {
|
|
38
|
-
return path.join(dir, platform.binaryName());
|
|
39
|
-
}
|
|
40
|
-
// No platform package: an archive from the releases page, unpacked into
|
|
41
|
-
// bin/ beside this file. This is also the path a checkout takes, where
|
|
42
|
-
// nothing was installed from a registry at all.
|
|
43
|
-
return path.join(HERE, '..', 'bin', platform.binaryName());
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// How many worker processes, when nobody said.
|
|
47
|
-
//
|
|
48
|
-
// Half the cores, capped. The cap is there because a worker is a process with
|
|
49
|
-
// blink in it, not a thread: measured on this tree it settles around 14 MB of
|
|
50
|
-
// private working set and holds a further ~30 MB of shotium.exe resident, so
|
|
51
|
-
// "half the cores" on a 32-core machine is sixteen of them and most of a
|
|
52
|
-
// gigabyte for a queue that is almost never sixteen deep. Four is past the
|
|
53
|
-
// point where a screenshot workload gets much from another one -- the corpus
|
|
54
|
-
// runs at 41 pages/s on four -- and anyone who has measured otherwise passes
|
|
55
|
-
// `workers`.
|
|
56
|
-
const MAXIMUM_DEFAULT_WORKERS = 4;
|
|
57
|
-
|
|
58
|
-
function defaultWorkers(): number {
|
|
59
|
-
const half = Math.floor((os.cpus().length || 2) / 2);
|
|
60
|
-
return Math.max(1, Math.min(MAXIMUM_DEFAULT_WORKERS, half));
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function defaultCacheDir(): string {
|
|
64
|
-
return path.join(os.tmpdir(), 'shotium-cache');
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// binary / workers / cacheDir / args, filled in and normalised. `cacheDir:
|
|
68
|
-
// null` survives as null -- it means "no disk cache", which is not the same
|
|
69
|
-
// request as "use the default one".
|
|
19
|
+
// The default for `cacheDir` is null -- no disk cache. A program holding the
|
|
20
|
+
// engine is often short-lived, and a cache it never reads twice is a directory
|
|
21
|
+
// it leaves behind. The daemon, which is the case where a cache does pay for
|
|
22
|
+
// itself, is also the case where the caller is already passing options.
|
|
70
23
|
function resolveStartOptions(options: StartOptions = {}): ResolvedStartOptions {
|
|
71
24
|
return {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
null :
|
|
76
|
-
(options.cacheDir || defaultCacheDir()),
|
|
77
|
-
args: options.args || [],
|
|
25
|
+
cacheDir: options.cacheDir ?? null,
|
|
26
|
+
userAgent: options.userAgent,
|
|
27
|
+
resourceDir: options.resourceDir,
|
|
78
28
|
};
|
|
79
29
|
}
|
|
80
30
|
|
|
81
|
-
export {
|
|
82
|
-
defaultBinary,
|
|
83
|
-
defaultCacheDir,
|
|
84
|
-
defaultWorkers,
|
|
85
|
-
resolveStartOptions,
|
|
86
|
-
};
|
|
31
|
+
export {resolveStartOptions};
|