@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 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.
@@ -0,0 +1,4 @@
1
+ export { spawn } from "./spawn.js";
2
+ export type { SpawnOptions, SpawnedPulsar } from "./spawn.js";
3
+ export * from "@clodocapeo/pulsar-client";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -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"}
@@ -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}`);