@clodocapeo/pulsar-bundle-full 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 +104 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/spawn.d.ts +64 -0
- package/dist/spawn.d.ts.map +1 -0
- package/dist/spawn.js +153 -0
- package/dist/spawn.js.map +1 -0
- package/package.json +53 -0
- package/scripts/postinstall.mjs +106 -0
package/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# @clodocapeo/pulsar-bundle-full
|
|
2
|
+
|
|
3
|
+
Full Pulsar bundle for Node consumers — `pulsar.exe` with **browser sources**, **text sources**, and **VLC-backed media sources** included on top of the standard light bundle. Same `spawn()` API as [`@clodocapeo/pulsar-bundle`](../pulsar-bundle).
|
|
4
|
+
|
|
5
|
+
Use this package when your scenes need any of:
|
|
6
|
+
|
|
7
|
+
- HTML / CSS / JS overlays (browser_source via CEF)
|
|
8
|
+
- Native text overlays (text_gdiplus / text_ft2_source)
|
|
9
|
+
- Local video file playback (vlc_source)
|
|
10
|
+
|
|
11
|
+
If you only need game capture + window/monitor capture + audio + recording + RTMP, the smaller [`@clodocapeo/pulsar-bundle`](../pulsar-bundle) (~40 MB zip) is enough.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @clodocapeo/pulsar-bundle-full
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Windows x64 only — `os: ["win32"]` in `package.json` makes `npm install` skip the binary on every other platform.
|
|
20
|
+
|
|
21
|
+
A `postinstall` step downloads the matching version's `pulsar-windows-x64-full-vX.Y.Z.zip` from the Pulsar GitHub Releases (~250 MB) and extracts it into `node_modules/@clodocapeo/pulsar-bundle-full/binaries/`. Cached: re-running `npm install` against an unchanged version is a no-op.
|
|
22
|
+
|
|
23
|
+
The 200 MB inflation over the light bundle is **CEF** (Chromium Embedded Framework, the runtime obs-browser links against). It is what makes browser sources possible.
|
|
24
|
+
|
|
25
|
+
### Environment overrides
|
|
26
|
+
|
|
27
|
+
Same as `@clodocapeo/pulsar-bundle`:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
PULSAR_BUNDLE_SKIP_POSTINSTALL=1 # skip download (CI / offline)
|
|
31
|
+
PULSAR_BUNDLE_DOWNLOAD_URL=<url> # mirror or internal CDN
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { spawn } from "@clodocapeo/pulsar-bundle-full";
|
|
38
|
+
|
|
39
|
+
const pulsar = await spawn();
|
|
40
|
+
|
|
41
|
+
// HTML overlay via browser_source (the headline feature of -full)
|
|
42
|
+
await pulsar.client.obs.call("CreateInput", {
|
|
43
|
+
sceneName: "Default",
|
|
44
|
+
inputName: "Overlay",
|
|
45
|
+
inputKind: "browser_source",
|
|
46
|
+
inputSettings: {
|
|
47
|
+
url: "https://example.com/my-overlay",
|
|
48
|
+
width: 1920,
|
|
49
|
+
height: 1080,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Native text source
|
|
54
|
+
await pulsar.client.obs.call("CreateInput", {
|
|
55
|
+
sceneName: "Default",
|
|
56
|
+
inputName: "Title",
|
|
57
|
+
inputKind: "text_gdiplus",
|
|
58
|
+
inputSettings: { text: "LIVE", font: { face: "Segoe UI", size: 64 } },
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// VLC-backed local video file playback
|
|
62
|
+
await pulsar.client.obs.call("CreateInput", {
|
|
63
|
+
sceneName: "Default",
|
|
64
|
+
inputName: "Bumper",
|
|
65
|
+
inputKind: "vlc_source",
|
|
66
|
+
inputSettings: { playlist: ["C:/path/to/clip.mp4"] },
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
await pulsar.shutdown();
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`spawn()` returns the same handle shape as the light bundle (`{ client, child, port, libobsVersion, shutdown }`). All the higher-level namespaces (`destinations`, `video`, `adaptive`, `record`, `stream`) are inherited from `@clodocapeo/pulsar-client` and work identically.
|
|
73
|
+
|
|
74
|
+
## Trade-off vs the light bundle
|
|
75
|
+
|
|
76
|
+
| | `pulsar-bundle` (light) | `pulsar-bundle-full` |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| Zip download | ~40 MB | ~250 MB |
|
|
79
|
+
| Extracted | ~100 MB | ~600 MB |
|
|
80
|
+
| Plugins | game capture, window, monitor, WASAPI, dshow webcam, x264/NVENC/QSV/AMF, ffmpeg, filters, transitions, multi-stream + websocket | + obs-browser (CEF) + obs-text + text-freetype2 + vlc-video |
|
|
81
|
+
| Best for | gaming streams, simple capture flows | composed scenes, HTML overlays, multi-source layouts |
|
|
82
|
+
|
|
83
|
+
Both bundles ship the same set of always-stripped plugins (obs-vst, obs-webrtc, decklink, frontend-tools, obs-libfdk) — those are deliberate omissions, see the repo's main README for the rationale.
|
|
84
|
+
|
|
85
|
+
## Versioning
|
|
86
|
+
|
|
87
|
+
Locked to `pulsar-client` and `pulsar.exe` in lockstep. `0.2.0` of this package downloads `pulsar-windows-x64-full-v0.2.0.zip` and depends on `@clodocapeo/pulsar-client@0.2.0`.
|
|
88
|
+
|
|
89
|
+
## Development
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# from the monorepo root
|
|
93
|
+
npm install # workspaces resolve, postinstall soft-fails
|
|
94
|
+
# on offline / unreleased versions
|
|
95
|
+
npm run build # tsc -> dist/
|
|
96
|
+
npm run test # vitest with the same fake-pulsar fixture
|
|
97
|
+
# as @clodocapeo/pulsar-bundle
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The vitest suite uses `tests/fake-pulsar.mjs` (a Node script that mimics pulsar.exe's boot) and exercises the same 5 lifecycle tests as `pulsar-bundle`. Browser-source-specific behaviour requires the real `pulsar.exe` with CEF; that's covered in the repo's integration smoke test, not the package's unit suite.
|
|
101
|
+
|
|
102
|
+
## License
|
|
103
|
+
|
|
104
|
+
MIT.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC9D,cAAc,2BAA2B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// @clodocapeo/pulsar-bundle-full
|
|
2
|
+
//
|
|
3
|
+
// Spawns the full Pulsar bundle (with obs-browser / CEF + text + vlc)
|
|
4
|
+
// and returns a connected PulsarClient. Identical spawn() API to
|
|
5
|
+
// @clodocapeo/pulsar-bundle; the difference is purely the binary
|
|
6
|
+
// payload downloaded by postinstall. Use this package when consumers
|
|
7
|
+
// need browser sources (HTML overlays), native text sources, or
|
|
8
|
+
// VLC-backed media sources -- e.g. Prism's composed scenes.
|
|
9
|
+
export { spawn } from "./spawn.js";
|
|
10
|
+
export * from "@clodocapeo/pulsar-client";
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,iCAAiC;AACjC,EAAE;AACF,sEAAsE;AACtE,iEAAiE;AACjE,iEAAiE;AACjE,qEAAqE;AACrE,gEAAgE;AAChE,4DAA4D;AAE5D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,cAAc,2BAA2B,CAAC"}
|
package/dist/spawn.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { type ChildProcess } from "node:child_process";
|
|
2
|
+
import { PulsarClient } from "@clodocapeo/pulsar-client";
|
|
3
|
+
export interface SpawnOptions {
|
|
4
|
+
/** Override the directory containing bin/64bit/pulsar.exe.
|
|
5
|
+
* Default: <package>/binaries (populated by postinstall). */
|
|
6
|
+
binariesPath?: string;
|
|
7
|
+
/** Extra env vars for pulsar.exe. Common ones:
|
|
8
|
+
* PULSAR_CAPTURE_WINDOW "<title>:<class>:<exe>" for window_capture
|
|
9
|
+
* PULSAR_FPS 30 / 60 / etc.
|
|
10
|
+
* PULSAR_VIDEO_BITRATE kbps
|
|
11
|
+
* PULSAR_AUDIO_BITRATE kbps
|
|
12
|
+
* PULSAR_RECORD_DIR output dir for the singleton recorder
|
|
13
|
+
* PULSAR_PROCESS_AUDIO_NAME exe name for per-process audio
|
|
14
|
+
* PULSAR_ADAPTIVE_BITRATE off to disable the worker
|
|
15
|
+
*/
|
|
16
|
+
env?: Record<string, string>;
|
|
17
|
+
/** How long to wait for "ready, idling" on stdout. Default 30 s. */
|
|
18
|
+
readyTimeoutMs?: number;
|
|
19
|
+
/** Optional log forwarder. Receives one stdout/stderr line at a time. */
|
|
20
|
+
onLog?: (stream: "stdout" | "stderr", line: string) => void;
|
|
21
|
+
/** Test-only escape hatch: spawn a different executable instead of
|
|
22
|
+
* the bundled pulsar.exe. The launched process must still print the
|
|
23
|
+
* ready marker on stdout and create obs-websocket/config.json under
|
|
24
|
+
* the cwd. Used by the package's own vitest suite -- not a public
|
|
25
|
+
* API for normal callers.
|
|
26
|
+
* @internal */
|
|
27
|
+
launchCommand?: {
|
|
28
|
+
exe: string;
|
|
29
|
+
args?: string[];
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export interface SpawnedPulsar {
|
|
33
|
+
/** Connected PulsarClient ready for vendor calls. */
|
|
34
|
+
client: PulsarClient;
|
|
35
|
+
/** Underlying child process. Most callers should use shutdown() instead
|
|
36
|
+
* of touching this directly, but it's exposed for advanced use cases
|
|
37
|
+
* (sending custom signals, reading stdio, etc.). */
|
|
38
|
+
child: ChildProcess;
|
|
39
|
+
/** WebSocket port the obs-websocket server is bound to. */
|
|
40
|
+
port: number;
|
|
41
|
+
/** libobs version string parsed from the boot log
|
|
42
|
+
* (e.g. "32.1.2-1-g8c23ba721-pulsar"). */
|
|
43
|
+
libobsVersion: string;
|
|
44
|
+
/** Disconnect the WS client and terminate pulsar.exe. Resolves once
|
|
45
|
+
* the process has exited. Idempotent. */
|
|
46
|
+
shutdown(): Promise<void>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Spawn pulsar.exe and return a connected PulsarClient.
|
|
50
|
+
*
|
|
51
|
+
* The child process is started with its working directory set to the
|
|
52
|
+
* binary's location so that the loader resolves obs.dll and the libobs
|
|
53
|
+
* plugin scanner finds obs-plugins/64bit/ via the relative path
|
|
54
|
+
* convention add_default_module_paths uses.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* const pulsar = await spawn({
|
|
58
|
+
* env: { PULSAR_CAPTURE_WINDOW: "Untitled - Notepad:Notepad:notepad.exe" },
|
|
59
|
+
* });
|
|
60
|
+
* await pulsar.client.destinations.create({ kind: "twitch", key });
|
|
61
|
+
* await pulsar.shutdown();
|
|
62
|
+
*/
|
|
63
|
+
export declare function spawn(opts?: SpawnOptions): Promise<SpawnedPulsar>;
|
|
64
|
+
//# sourceMappingURL=spawn.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spawn.d.ts","sourceRoot":"","sources":["../src/spawn.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAK5E,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAQzD,MAAM,WAAW,YAAY;IAC3B;kEAC8D;IAC9D,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;;;;;OAQG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAE7B,oEAAoE;IACpE,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,yEAAyE;IACzE,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAE5D;;;;;oBAKgB;IAChB,aAAa,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;CAClD;AAED,MAAM,WAAW,aAAa;IAC5B,qDAAqD;IACrD,MAAM,EAAE,YAAY,CAAC;IAErB;;yDAEqD;IACrD,KAAK,EAAE,YAAY,CAAC;IAEpB,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IAEb;+CAC2C;IAC3C,aAAa,EAAE,MAAM,CAAC;IAEtB;8CAC0C;IAC1C,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAUD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,KAAK,CAAC,IAAI,GAAE,YAAiB,GAAG,OAAO,CAAC,aAAa,CAAC,CAiH3E"}
|
package/dist/spawn.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { spawn as childSpawn } from "node:child_process";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { PulsarClient } from "@clodocapeo/pulsar-client";
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = dirname(__filename);
|
|
8
|
+
/** Default location of the bundled binaries inside this package. */
|
|
9
|
+
const DEFAULT_BINARIES = resolve(__dirname, "..", "binaries");
|
|
10
|
+
const READY_MARKER = "pulsar-headless: libobs ";
|
|
11
|
+
const IDLE_MARKER = "ready, idling";
|
|
12
|
+
/**
|
|
13
|
+
* Spawn pulsar.exe and return a connected PulsarClient.
|
|
14
|
+
*
|
|
15
|
+
* The child process is started with its working directory set to the
|
|
16
|
+
* binary's location so that the loader resolves obs.dll and the libobs
|
|
17
|
+
* plugin scanner finds obs-plugins/64bit/ via the relative path
|
|
18
|
+
* convention add_default_module_paths uses.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* const pulsar = await spawn({
|
|
22
|
+
* env: { PULSAR_CAPTURE_WINDOW: "Untitled - Notepad:Notepad:notepad.exe" },
|
|
23
|
+
* });
|
|
24
|
+
* await pulsar.client.destinations.create({ kind: "twitch", key });
|
|
25
|
+
* await pulsar.shutdown();
|
|
26
|
+
*/
|
|
27
|
+
export async function spawn(opts = {}) {
|
|
28
|
+
const binariesPath = opts.binariesPath ?? DEFAULT_BINARIES;
|
|
29
|
+
const cwd = join(binariesPath, "bin", "64bit");
|
|
30
|
+
const configPath = join(cwd, "obs-websocket", "config.json");
|
|
31
|
+
// Resolve the executable. Default = bundled pulsar.exe; tests can pass
|
|
32
|
+
// launchCommand to swap in a fake.
|
|
33
|
+
let exe;
|
|
34
|
+
let args;
|
|
35
|
+
if (opts.launchCommand) {
|
|
36
|
+
exe = opts.launchCommand.exe;
|
|
37
|
+
args = opts.launchCommand.args ?? [];
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
exe = join(cwd, "pulsar.exe");
|
|
41
|
+
args = [];
|
|
42
|
+
if (!existsSync(exe)) {
|
|
43
|
+
throw new Error(`pulsar.exe not found at ${exe}. ` +
|
|
44
|
+
`Run \`npm install\` to trigger the postinstall download, ` +
|
|
45
|
+
`or set binariesPath to a local checkout.`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const child = childSpawn(exe, args, {
|
|
49
|
+
cwd,
|
|
50
|
+
env: { ...process.env, ...opts.env },
|
|
51
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
52
|
+
windowsHide: true,
|
|
53
|
+
});
|
|
54
|
+
const readyTimeout = opts.readyTimeoutMs ?? 30_000;
|
|
55
|
+
let libobsVersion = "";
|
|
56
|
+
let resolveReady;
|
|
57
|
+
let rejectReady;
|
|
58
|
+
const ready = new Promise((res, rej) => {
|
|
59
|
+
resolveReady = res;
|
|
60
|
+
rejectReady = rej;
|
|
61
|
+
});
|
|
62
|
+
const watchdog = setTimeout(() => {
|
|
63
|
+
rejectReady(new Error(`pulsar.exe did not signal ready within ${readyTimeout}ms`));
|
|
64
|
+
}, readyTimeout);
|
|
65
|
+
const handleLine = (stream, line) => {
|
|
66
|
+
if (line.length === 0)
|
|
67
|
+
return;
|
|
68
|
+
if (opts.onLog)
|
|
69
|
+
opts.onLog(stream, line);
|
|
70
|
+
// pulsar-headless prints exactly:
|
|
71
|
+
// "pulsar-headless: libobs <version> ready, idling (Ctrl+C to exit)"
|
|
72
|
+
// when boot is complete.
|
|
73
|
+
if (stream === "stdout" && line.includes(IDLE_MARKER) && line.includes(READY_MARKER)) {
|
|
74
|
+
const slice = line.slice(line.indexOf(READY_MARKER) + READY_MARKER.length);
|
|
75
|
+
libobsVersion = slice.split(" ", 1)[0] ?? "";
|
|
76
|
+
clearTimeout(watchdog);
|
|
77
|
+
resolveReady();
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
attachLineReader(child, "stdout", (l) => handleLine("stdout", l));
|
|
81
|
+
attachLineReader(child, "stderr", (l) => handleLine("stderr", l));
|
|
82
|
+
child.on("exit", (code, signal) => {
|
|
83
|
+
clearTimeout(watchdog);
|
|
84
|
+
rejectReady(new Error(`pulsar.exe exited prematurely (code=${code}, signal=${signal})`));
|
|
85
|
+
});
|
|
86
|
+
await ready;
|
|
87
|
+
// obs-websocket writes its config.json on first run -- after the
|
|
88
|
+
// ready marker, the file is guaranteed to exist with port + password.
|
|
89
|
+
if (!existsSync(configPath)) {
|
|
90
|
+
child.kill();
|
|
91
|
+
throw new Error(`obs-websocket config not found at ${configPath} (boot incomplete?)`);
|
|
92
|
+
}
|
|
93
|
+
const config = JSON.parse(readFileSync(configPath, "utf8"));
|
|
94
|
+
const port = config.server_port ?? 4455;
|
|
95
|
+
const password = config.server_password;
|
|
96
|
+
const client = new PulsarClient();
|
|
97
|
+
await client.connect({
|
|
98
|
+
url: `ws://127.0.0.1:${port}`,
|
|
99
|
+
...(password !== undefined ? { password } : {}),
|
|
100
|
+
});
|
|
101
|
+
let shutdownPromise = null;
|
|
102
|
+
const shutdown = async () => {
|
|
103
|
+
if (shutdownPromise)
|
|
104
|
+
return shutdownPromise;
|
|
105
|
+
shutdownPromise = (async () => {
|
|
106
|
+
try {
|
|
107
|
+
await client.disconnect();
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// Already closed -- swallow.
|
|
111
|
+
}
|
|
112
|
+
if (child.exitCode === null && child.signalCode === null) {
|
|
113
|
+
child.kill();
|
|
114
|
+
await new Promise((res) => {
|
|
115
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
116
|
+
return res();
|
|
117
|
+
child.once("exit", () => res());
|
|
118
|
+
// Safety net: a stuck process gets a 5 s grace period before
|
|
119
|
+
// force-kill so a pending muxer trailer write can land.
|
|
120
|
+
setTimeout(() => {
|
|
121
|
+
if (child.exitCode === null && child.signalCode === null) {
|
|
122
|
+
child.kill("SIGKILL");
|
|
123
|
+
}
|
|
124
|
+
}, 5_000);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
})();
|
|
128
|
+
return shutdownPromise;
|
|
129
|
+
};
|
|
130
|
+
return { client, child, port, libobsVersion, shutdown };
|
|
131
|
+
}
|
|
132
|
+
/** Reads `child[stream]` line-by-line and dispatches each completed line. */
|
|
133
|
+
function attachLineReader(child, stream, onLine) {
|
|
134
|
+
const out = child[stream];
|
|
135
|
+
if (!out)
|
|
136
|
+
return;
|
|
137
|
+
let buf = "";
|
|
138
|
+
out.setEncoding("utf8");
|
|
139
|
+
out.on("data", (chunk) => {
|
|
140
|
+
buf += chunk;
|
|
141
|
+
let idx;
|
|
142
|
+
while ((idx = buf.indexOf("\n")) >= 0) {
|
|
143
|
+
const line = buf.slice(0, idx).replace(/\r$/, "");
|
|
144
|
+
buf = buf.slice(idx + 1);
|
|
145
|
+
onLine(line);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
out.on("end", () => {
|
|
149
|
+
if (buf.length > 0)
|
|
150
|
+
onLine(buf.replace(/\r$/, ""));
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=spawn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spawn.js","sourceRoot":"","sources":["../src/spawn.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@clodocapeo/pulsar-bundle-full",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Full Pulsar bundle: pulsar.exe + DLLs + browser sources (CEF) + text + VLC. Same Node spawn() API as @clodocapeo/pulsar-bundle.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/ZabLaboratory/Pulsar.git",
|
|
9
|
+
"directory": "packages/pulsar-bundle-full"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"import": "./dist/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist/",
|
|
22
|
+
"scripts/",
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE"
|
|
25
|
+
],
|
|
26
|
+
"os": ["win32"],
|
|
27
|
+
"cpu": ["x64"],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsc -p tsconfig.build.json",
|
|
30
|
+
"test": "vitest run",
|
|
31
|
+
"test:watch": "vitest",
|
|
32
|
+
"lint": "tsc -p tsconfig.json --noEmit",
|
|
33
|
+
"postinstall": "node scripts/postinstall.mjs",
|
|
34
|
+
"prepublishOnly": "npm run build"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@clodocapeo/pulsar-client": "0.2.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@msgpack/msgpack": "^3.0.0",
|
|
41
|
+
"@types/node": "^22.0.0",
|
|
42
|
+
"@types/ws": "^8.5.10",
|
|
43
|
+
"typescript": "^5.6.0",
|
|
44
|
+
"vitest": "^2.1.0",
|
|
45
|
+
"ws": "^8.18.0"
|
|
46
|
+
},
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=18"
|
|
49
|
+
},
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// @clodocapeo/pulsar-bundle postinstall.
|
|
2
|
+
//
|
|
3
|
+
// Downloads the matching pulsar-windows-x64-v<VERSION>.zip from the
|
|
4
|
+
// Pulsar GitHub Releases and extracts it into ./binaries/. Idempotent:
|
|
5
|
+
// a stamp file under binaries/.version records what's already on disk
|
|
6
|
+
// so re-running npm install on an unchanged version is a no-op.
|
|
7
|
+
//
|
|
8
|
+
// Skip with `PULSAR_BUNDLE_SKIP_POSTINSTALL=1` (useful for CI matrix
|
|
9
|
+
// runs that don't actually need the binary, or for offline builds with
|
|
10
|
+
// a vendored copy).
|
|
11
|
+
//
|
|
12
|
+
// Override the download URL with `PULSAR_BUNDLE_DOWNLOAD_URL=<url>` if
|
|
13
|
+
// the binary is hosted somewhere else (private mirror, internal CDN).
|
|
14
|
+
|
|
15
|
+
import { execFileSync } from "node:child_process";
|
|
16
|
+
import { createWriteStream } from "node:fs";
|
|
17
|
+
import { mkdir, readFile, rm, writeFile, stat } from "node:fs/promises";
|
|
18
|
+
import { dirname, join, resolve } from "node:path";
|
|
19
|
+
import { fileURLToPath } from "node:url";
|
|
20
|
+
import { Readable } from "node:stream";
|
|
21
|
+
import { pipeline } from "node:stream/promises";
|
|
22
|
+
|
|
23
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
const pkgRoot = resolve(__dirname, "..");
|
|
25
|
+
|
|
26
|
+
if (process.env.PULSAR_BUNDLE_SKIP_POSTINSTALL === "1") {
|
|
27
|
+
console.log("[pulsar-bundle] PULSAR_BUNDLE_SKIP_POSTINSTALL=1 -- skipping binary download");
|
|
28
|
+
process.exit(0);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (process.platform !== "win32" || process.arch !== "x64") {
|
|
32
|
+
// package.json's "os" / "cpu" guards prevent this on a normal install,
|
|
33
|
+
// but a cross-platform `npm install --force` could end up here.
|
|
34
|
+
console.log(`[pulsar-bundle] platform=${process.platform} arch=${process.arch} unsupported; skipping`);
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const pkg = JSON.parse(await readFile(join(pkgRoot, "package.json"), "utf8"));
|
|
39
|
+
const version = pkg.version;
|
|
40
|
+
// pulsar-bundle-full pulls the full distribution variant: light bundle
|
|
41
|
+
// + obs-browser (CEF runtime) + obs-text + text-freetype2 + vlc-video.
|
|
42
|
+
// ~250 MB zipped vs ~40 MB for the light bundle.
|
|
43
|
+
const zipName = `pulsar-windows-x64-full-v${version}.zip`;
|
|
44
|
+
const url =
|
|
45
|
+
process.env.PULSAR_BUNDLE_DOWNLOAD_URL ??
|
|
46
|
+
`https://github.com/ZabLaboratory/Pulsar/releases/download/v${version}/${zipName}`;
|
|
47
|
+
|
|
48
|
+
const binariesDir = join(pkgRoot, "binaries");
|
|
49
|
+
const stampFile = join(binariesDir, ".version-stamp");
|
|
50
|
+
|
|
51
|
+
// Cache hit?
|
|
52
|
+
try {
|
|
53
|
+
const stamp = await readFile(stampFile, "utf8");
|
|
54
|
+
if (stamp.trim() === version) {
|
|
55
|
+
console.log(`[pulsar-bundle] binaries/ already at v${version}, skipping download`);
|
|
56
|
+
process.exit(0);
|
|
57
|
+
}
|
|
58
|
+
} catch {
|
|
59
|
+
// Missing stamp -> proceed
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
await rm(binariesDir, { recursive: true, force: true });
|
|
63
|
+
await mkdir(binariesDir, { recursive: true });
|
|
64
|
+
|
|
65
|
+
console.log(`[pulsar-bundle] downloading ${url}`);
|
|
66
|
+
let response;
|
|
67
|
+
try {
|
|
68
|
+
response = await fetch(url, { redirect: "follow" });
|
|
69
|
+
} catch (err) {
|
|
70
|
+
console.warn(`[pulsar-bundle] WARN: network error fetching binary: ${err.message}`);
|
|
71
|
+
console.warn("[pulsar-bundle] WARN: pulsar.exe will be unavailable until you re-install with network access");
|
|
72
|
+
process.exit(0);
|
|
73
|
+
}
|
|
74
|
+
if (!response.ok || !response.body) {
|
|
75
|
+
console.warn(`[pulsar-bundle] WARN: download failed: ${response.status} ${response.statusText}`);
|
|
76
|
+
console.warn(`[pulsar-bundle] WARN: is the v${version} release published?`);
|
|
77
|
+
console.warn("[pulsar-bundle] https://github.com/ZabLaboratory/Pulsar/releases");
|
|
78
|
+
console.warn("[pulsar-bundle] WARN: pulsar.exe will be unavailable; spawn() will throw a clear error");
|
|
79
|
+
// Soft-fail so npm install completes -- the bundle is still usable in
|
|
80
|
+
// monorepo dev (where binariesPath is overridden) and the failure is
|
|
81
|
+
// surfaced loudly enough that production callers will fix it.
|
|
82
|
+
process.exit(0);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const zipPath = join(binariesDir, zipName);
|
|
86
|
+
await pipeline(Readable.fromWeb(response.body), createWriteStream(zipPath));
|
|
87
|
+
const sizeMb = ((await stat(zipPath)).size / 1024 / 1024).toFixed(1);
|
|
88
|
+
console.log(`[pulsar-bundle] downloaded ${sizeMb} MB`);
|
|
89
|
+
|
|
90
|
+
console.log("[pulsar-bundle] extracting...");
|
|
91
|
+
// Expand-Archive is built into PowerShell since Windows 10. Avoids
|
|
92
|
+
// adding a native unzip dependency to the package.
|
|
93
|
+
execFileSync(
|
|
94
|
+
"powershell.exe",
|
|
95
|
+
[
|
|
96
|
+
"-NoProfile",
|
|
97
|
+
"-NonInteractive",
|
|
98
|
+
"-Command",
|
|
99
|
+
`Expand-Archive -Path "${zipPath}" -DestinationPath "${binariesDir}" -Force`,
|
|
100
|
+
],
|
|
101
|
+
{ stdio: "inherit" },
|
|
102
|
+
);
|
|
103
|
+
await rm(zipPath, { force: true });
|
|
104
|
+
|
|
105
|
+
await writeFile(stampFile, `${version}\n`, "utf8");
|
|
106
|
+
console.log(`[pulsar-bundle] binaries ready at ${binariesDir}`);
|