@jesscss/plugin-js 2.0.0-alpha.5 → 2.0.0-alpha.7

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.
@@ -0,0 +1,51 @@
1
+ export type JsBridgeDeclaration = {
2
+ name: string;
3
+ value: JsBridgeValue;
4
+ };
5
+ export type JsBridgeValue = {
6
+ __jessBridge: true;
7
+ kind: 'scalar';
8
+ value: string | number | boolean;
9
+ } | {
10
+ __jessBridge: true;
11
+ kind: 'dimension';
12
+ value: number;
13
+ unit?: string;
14
+ } | {
15
+ __jessBridge: true;
16
+ kind: 'color';
17
+ rgb: [number, number, number];
18
+ alpha?: number;
19
+ format?: string;
20
+ } | {
21
+ __jessBridge: true;
22
+ kind: 'quoted';
23
+ value: string;
24
+ quote?: '"' | '\'';
25
+ escaped?: boolean;
26
+ } | {
27
+ __jessBridge: true;
28
+ kind: 'keyword';
29
+ value: string;
30
+ } | {
31
+ __jessBridge: true;
32
+ kind: 'anonymous';
33
+ value: string;
34
+ } | {
35
+ __jessBridge: true;
36
+ kind: 'list';
37
+ items: JsBridgeValue[];
38
+ separator?: ',' | ';' | '/';
39
+ } | {
40
+ __jessBridge: true;
41
+ kind: 'sequence';
42
+ items: JsBridgeValue[];
43
+ } | {
44
+ __jessBridge: true;
45
+ kind: 'detached';
46
+ rules: JsBridgeDeclaration[];
47
+ };
48
+ export declare function encodeBridgeValue(value: unknown): unknown;
49
+ export declare function decodeBridgeValue(value: unknown): unknown;
50
+ export declare function encodeBridgeArgs(args: unknown[]): unknown[];
51
+ //# sourceMappingURL=bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAcA,MAAM,MAAM,mBAAmB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,aAAa,CAAA;CAAE,CAAC;AAEzE,MAAM,MAAM,aAAa,GACrB;IAAE,YAAY,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;CAAE,GACxE;IAAE,YAAY,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACvE;IAAE,YAAY,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GACrG;IAAE,YAAY,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,GAAG,GAAG,IAAI,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAC5F;IAAE,YAAY,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACtD;IAAE,YAAY,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACxD;IAAE,YAAY,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,aAAa,EAAE,CAAC;IAAC,SAAS,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,CAAA;CAAE,GACzF;IAAE,YAAY,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,aAAa,EAAE,CAAA;CAAE,GAChE;IAAE,YAAY,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,UAAU,CAAC;IAAC,KAAK,EAAE,mBAAmB,EAAE,CAAA;CAAE,CAAC;AAqC3E,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAmEzD;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CA+BzD;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAE3D"}
package/lib/index.cjs ADDED
@@ -0,0 +1,589 @@
1
+ Object.defineProperties(exports, {
2
+ __esModule: { value: true },
3
+ [Symbol.toStringTag]: { value: "Module" }
4
+ });
5
+ //#region \0rolldown/runtime.js
6
+ var __create = Object.create;
7
+ var __defProp = Object.defineProperty;
8
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
9
+ var __getOwnPropNames = Object.getOwnPropertyNames;
10
+ var __getProtoOf = Object.getPrototypeOf;
11
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
14
+ key = keys[i];
15
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
16
+ get: ((k) => from[k]).bind(null, key),
17
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
18
+ });
19
+ }
20
+ return to;
21
+ };
22
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
23
+ value: mod,
24
+ enumerable: true
25
+ }) : target, mod));
26
+ //#endregion
27
+ let _jesscss_core = require("@jesscss/core");
28
+ let node_fs = require("node:fs");
29
+ node_fs = __toESM(node_fs);
30
+ let node_net = require("node:net");
31
+ node_net = __toESM(node_net);
32
+ let node_path = require("node:path");
33
+ node_path = __toESM(node_path);
34
+ let node_url = require("node:url");
35
+ let node_child_process = require("node:child_process");
36
+ //#region src/bridge.ts
37
+ const isBridgeValue = (value) => typeof value === "object" && value !== null && value.__jessBridge === true && typeof value.kind === "string";
38
+ const colorFormatFromString = (value) => {
39
+ if (!value) return;
40
+ if (value in _jesscss_core.ColorFormat) return _jesscss_core.ColorFormat[value];
41
+ if (Object.values(_jesscss_core.ColorFormat).includes(value)) return value;
42
+ };
43
+ function encodeBridgeChildValue(value) {
44
+ const encoded = encodeBridgeValue(value);
45
+ if (isBridgeValue(encoded)) return encoded;
46
+ return {
47
+ __jessBridge: true,
48
+ kind: "scalar",
49
+ value: typeof encoded === "string" || typeof encoded === "number" || typeof encoded === "boolean" ? encoded : String(encoded)
50
+ };
51
+ }
52
+ function encodeBridgeValue(value) {
53
+ if (value instanceof _jesscss_core.Dimension) return {
54
+ __jessBridge: true,
55
+ kind: "dimension",
56
+ value: value.number,
57
+ unit: value.unit
58
+ };
59
+ if (value instanceof _jesscss_core.Color) return {
60
+ __jessBridge: true,
61
+ kind: "color",
62
+ rgb: value.rgb,
63
+ alpha: value.alpha,
64
+ format: value.options.format === void 0 ? void 0 : _jesscss_core.ColorFormat[value.options.format]
65
+ };
66
+ if (value instanceof _jesscss_core.Quoted) return {
67
+ __jessBridge: true,
68
+ kind: "quoted",
69
+ value: String(value.value),
70
+ quote: value.quote,
71
+ escaped: value.escaped
72
+ };
73
+ if (value instanceof _jesscss_core.Any) return {
74
+ __jessBridge: true,
75
+ kind: value.role === "keyword" ? "keyword" : "anonymous",
76
+ value: value.value
77
+ };
78
+ if (value instanceof _jesscss_core.List) return {
79
+ __jessBridge: true,
80
+ kind: "list",
81
+ items: value.value.map(encodeBridgeChildValue),
82
+ separator: value.options.sep
83
+ };
84
+ if (value instanceof _jesscss_core.Sequence) return {
85
+ __jessBridge: true,
86
+ kind: "sequence",
87
+ items: value.value.map(encodeBridgeChildValue)
88
+ };
89
+ if (value instanceof _jesscss_core.Rules) {
90
+ const rules = [];
91
+ for (const rule of value.rules ?? []) if (rule instanceof _jesscss_core.Declaration && typeof rule.name === "string") rules.push({
92
+ name: rule.name,
93
+ value: encodeBridgeChildValue(rule.value)
94
+ });
95
+ return {
96
+ __jessBridge: true,
97
+ kind: "detached",
98
+ rules
99
+ };
100
+ }
101
+ return value;
102
+ }
103
+ function decodeBridgeValue(value) {
104
+ if (!isBridgeValue(value)) return value;
105
+ switch (value.kind) {
106
+ case "scalar": return new _jesscss_core.Any(String(value.value));
107
+ case "dimension": return new _jesscss_core.Dimension({
108
+ number: value.value,
109
+ unit: value.unit
110
+ });
111
+ case "color": return new _jesscss_core.Color({
112
+ rgb: value.rgb,
113
+ alpha: value.alpha
114
+ }, { format: colorFormatFromString(value.format) });
115
+ case "quoted": return new _jesscss_core.Quoted(value.value, {
116
+ quote: value.quote,
117
+ escaped: value.escaped
118
+ });
119
+ case "keyword": return new _jesscss_core.Any(value.value, { role: "keyword" });
120
+ case "anonymous": return new _jesscss_core.Any(value.value);
121
+ case "list": return new _jesscss_core.List(value.items.map((item) => decodeBridgeValue(item)), { sep: value.separator });
122
+ case "sequence": return new _jesscss_core.Sequence(value.items.map((item) => decodeBridgeValue(item)));
123
+ }
124
+ }
125
+ function encodeBridgeArgs(args) {
126
+ return args.map(encodeBridgeValue);
127
+ }
128
+ //#endregion
129
+ //#region src/index.ts
130
+ const SCRIPT_EXTENSIONS = new Set([
131
+ ".js",
132
+ ".mjs",
133
+ ".cjs",
134
+ ".ts",
135
+ ".mts",
136
+ ".cts"
137
+ ]);
138
+ const RUNTIME_MISSING_MESSAGE = [
139
+ "Deno runtime is required for @jesscss/plugin-js, but no usable Deno binary was found.",
140
+ "If using pnpm, approve build scripts for \"deno\" (pnpm approve-builds).",
141
+ "If using npm with ignored scripts, reinstall with lifecycle scripts enabled.",
142
+ "Or install native Deno and ensure \"deno\" is on PATH."
143
+ ].join("\n");
144
+ const BOOT_TIMEOUT_MS = 8e3;
145
+ const REQUEST_TIMEOUT_MS = 1e4;
146
+ const IDLE_SHUTDOWN_MS = 5e3;
147
+ const isPathInside = (candidatePath, rootPath) => {
148
+ const rel = node_path.relative(rootPath, candidatePath);
149
+ return rel === "" || !rel.startsWith("..") && !node_path.isAbsolute(rel);
150
+ };
151
+ const canonicalPath = (p) => {
152
+ try {
153
+ return node_fs.realpathSync.native(p);
154
+ } catch {
155
+ return p;
156
+ }
157
+ };
158
+ const normalizePermissionPath = (value) => {
159
+ if (!value) return null;
160
+ if (value.startsWith("file://")) try {
161
+ return (0, node_url.fileURLToPath)(value);
162
+ } catch {
163
+ return value;
164
+ }
165
+ return value;
166
+ };
167
+ const isFnsPath = (importPath) => {
168
+ const normalized = importPath.replace(/\\/g, "/");
169
+ const isFnsPackagePath = /(^|\/)(@jesscss\/fns|packages\/fns)(\/|$)/.test(normalized);
170
+ return normalized === "@jesscss/fns" || normalized.startsWith("@jesscss/fns/") || normalized === "#less" || normalized.startsWith("#less/") || normalized === "#sass" || normalized.startsWith("#sass/") || isFnsPackagePath;
171
+ };
172
+ const isJsonValue = (value) => {
173
+ try {
174
+ JSON.stringify(value);
175
+ return true;
176
+ } catch {
177
+ return false;
178
+ }
179
+ };
180
+ var JsPlugin = class JsPlugin extends _jesscss_core.AbstractPlugin {
181
+ static cleanupRegistered = false;
182
+ static liveInstances = /* @__PURE__ */ new Set();
183
+ name = "js";
184
+ supportedExtensions = Array.from(SCRIPT_EXTENSIONS);
185
+ runtimeState = { status: "idle" };
186
+ shuttingDown = false;
187
+ brokerServer;
188
+ brokerSocketPath;
189
+ worker;
190
+ workerBuffer = "";
191
+ nextRequestId = 1;
192
+ pending = /* @__PURE__ */ new Map();
193
+ idleTimer;
194
+ constructor(opts = {}) {
195
+ super();
196
+ this.opts = opts;
197
+ JsPlugin.liveInstances.add(this);
198
+ JsPlugin.registerProcessCleanup();
199
+ }
200
+ prewarm() {
201
+ return this.ensureRuntime().catch(() => void 0);
202
+ }
203
+ static registerProcessCleanup() {
204
+ if (JsPlugin.cleanupRegistered) return;
205
+ JsPlugin.cleanupRegistered = true;
206
+ const cleanup = () => {
207
+ for (const instance of JsPlugin.liveInstances) try {
208
+ instance.dispose();
209
+ } catch {}
210
+ };
211
+ process.once("beforeExit", cleanup);
212
+ process.once("exit", cleanup);
213
+ }
214
+ clearIdleTimer() {
215
+ if (this.idleTimer) {
216
+ clearTimeout(this.idleTimer);
217
+ this.idleTimer = void 0;
218
+ }
219
+ }
220
+ scheduleIdleShutdown() {
221
+ this.clearIdleTimer();
222
+ if (this.pending.size > 0 || this.runtimeState.status !== "ready") return;
223
+ this.idleTimer = setTimeout(() => {
224
+ this.shutdown();
225
+ this.runtimeState = { status: "idle" };
226
+ }, IDLE_SHUTDOWN_MS);
227
+ this.idleTimer.unref?.();
228
+ }
229
+ ensureRuntimeAvailable() {
230
+ if ((0, node_child_process.spawnSync)(this.opts.denoCommand ?? "deno", ["--version"], { stdio: "ignore" }).status !== 0) throw new Error(RUNTIME_MISSING_MESSAGE);
231
+ }
232
+ ensureRuntime() {
233
+ if (this.runtimeState.status === "ready") {
234
+ this.clearIdleTimer();
235
+ return Promise.resolve();
236
+ }
237
+ if (this.runtimeState.status === "initializing") return this.runtimeState.promise;
238
+ if (this.runtimeState.status === "failed") return Promise.reject(this.runtimeState.error);
239
+ if (this.runtimeState.status === "disposed") return Promise.reject(/* @__PURE__ */ new Error("Deno worker has been disposed."));
240
+ const promise = this.startRuntime().then(() => {
241
+ this.runtimeState = { status: "ready" };
242
+ this.scheduleIdleShutdown();
243
+ }, (err) => {
244
+ this.runtimeState = {
245
+ status: "failed",
246
+ error: err
247
+ };
248
+ throw err;
249
+ });
250
+ this.runtimeState = {
251
+ status: "initializing",
252
+ promise
253
+ };
254
+ return promise;
255
+ }
256
+ createBrokerPath() {
257
+ if (process.platform === "win32") return `\\\\.\\pipe\\jess-deno-broker-${`${process.pid}-${Date.now()}-${Math.floor(Math.random() * 1e4)}`}`;
258
+ const rand = Math.floor(Math.random() * 1e4);
259
+ return `/tmp/jd-${process.pid}-${Date.now()}-${rand}.sock`;
260
+ }
261
+ isReadAllowed(value) {
262
+ const normalized = normalizePermissionPath(value);
263
+ if (!normalized) return false;
264
+ const requestedPath = canonicalPath(node_path.resolve(normalized));
265
+ const jsReadRoot = this.opts.jsReadRoot ? canonicalPath(node_path.resolve(this.opts.jsReadRoot)) : void 0;
266
+ if (jsReadRoot && isPathInside(requestedPath, jsReadRoot)) return true;
267
+ return requestedPath.includes(`${node_path.sep}node_modules${node_path.sep}`);
268
+ }
269
+ isNetAllowed(value) {
270
+ if (!this.opts.allowHttp) return false;
271
+ const allowHosts = this.opts.allowNetHosts ?? [];
272
+ if (allowHosts.length === 0) return true;
273
+ if (!value) return false;
274
+ const host = value.split(":")[0] ?? value;
275
+ return allowHosts.includes(host);
276
+ }
277
+ handleBrokerRequest(request) {
278
+ const deny = (reason) => ({
279
+ id: request.id,
280
+ result: "deny",
281
+ reason
282
+ });
283
+ switch (request.permission) {
284
+ case "read": return this.isReadAllowed(request.value) ? {
285
+ id: request.id,
286
+ result: "allow"
287
+ } : deny("Read access denied by Jess policy.");
288
+ case "net": return this.isNetAllowed(request.value) ? {
289
+ id: request.id,
290
+ result: "allow"
291
+ } : deny("Network access denied by Jess policy.");
292
+ case "env":
293
+ case "run":
294
+ case "ffi":
295
+ case "sys":
296
+ case "write": return deny(`${request.permission} permission denied by Jess policy.`);
297
+ default: return deny(`Permission "${request.permission}" denied by Jess policy.`);
298
+ }
299
+ }
300
+ async startBroker() {
301
+ const socketPath = this.createBrokerPath();
302
+ if (process.platform !== "win32" && node_fs.existsSync(socketPath)) node_fs.unlinkSync(socketPath);
303
+ const server = node_net.createServer((socket) => {
304
+ socket.unref();
305
+ let buf = "";
306
+ socket.setEncoding("utf8");
307
+ socket.on("data", (chunk) => {
308
+ buf += chunk;
309
+ let idx = buf.indexOf("\n");
310
+ while (idx >= 0) {
311
+ const line = buf.slice(0, idx).trim();
312
+ buf = buf.slice(idx + 1);
313
+ idx = buf.indexOf("\n");
314
+ if (!line) continue;
315
+ let req;
316
+ try {
317
+ req = JSON.parse(line);
318
+ } catch {
319
+ socket.write(JSON.stringify({
320
+ id: -1,
321
+ result: "deny",
322
+ reason: "Malformed permission request."
323
+ }) + "\n");
324
+ continue;
325
+ }
326
+ const response = this.handleBrokerRequest(req);
327
+ socket.write(`${JSON.stringify(response)}\n`);
328
+ }
329
+ });
330
+ });
331
+ await new Promise((resolve, reject) => {
332
+ server.once("error", reject);
333
+ server.listen(socketPath, () => resolve());
334
+ });
335
+ server.unref();
336
+ this.brokerServer = server;
337
+ this.brokerSocketPath = socketPath;
338
+ return socketPath;
339
+ }
340
+ startWorker(socketPath) {
341
+ const denoCommand = this.opts.denoCommand ?? "deno";
342
+ const moduleDir = node_path.dirname((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
343
+ const compiledWorkerPath = node_path.join(moduleDir, "runtime-worker.js");
344
+ const sourceWorkerPath = node_path.join(moduleDir, "runtime-worker.ts");
345
+ const child = (0, node_child_process.spawn)(denoCommand, [
346
+ "run",
347
+ "--no-prompt",
348
+ node_fs.existsSync(compiledWorkerPath) ? compiledWorkerPath : sourceWorkerPath,
349
+ `--runtime-api=${this.opts.runtimeApi ?? "module"}`
350
+ ], {
351
+ stdio: [
352
+ "pipe",
353
+ "pipe",
354
+ "pipe"
355
+ ],
356
+ env: {
357
+ ...process.env,
358
+ DENO_PERMISSION_BROKER_PATH: socketPath
359
+ }
360
+ });
361
+ this.worker = child;
362
+ child.unref();
363
+ child.stdin.unref?.();
364
+ child.stdout.unref?.();
365
+ child.stderr.unref?.();
366
+ child.stdout.setEncoding("utf8");
367
+ child.stderr.setEncoding("utf8");
368
+ child.stdout.on("data", (chunk) => this.onWorkerStdout(chunk));
369
+ child.on("exit", () => {
370
+ this.worker = void 0;
371
+ if (this.shuttingDown) {
372
+ this.shuttingDown = false;
373
+ return;
374
+ }
375
+ const err = /* @__PURE__ */ new Error("Deno worker exited unexpectedly.");
376
+ this.rejectAllPending(err);
377
+ if (this.runtimeState.status !== "failed") this.runtimeState = {
378
+ status: "failed",
379
+ error: err
380
+ };
381
+ });
382
+ return new Promise((resolve, reject) => {
383
+ let stderrText = "";
384
+ const timer = setTimeout(() => {
385
+ reject(/* @__PURE__ */ new Error(stderrText.trim() ? `Timed out waiting for Deno worker startup.\n${stderrText.trim()}` : "Timed out waiting for Deno worker startup."));
386
+ }, BOOT_TIMEOUT_MS);
387
+ const onStderr = (chunk) => {
388
+ stderrText += chunk;
389
+ };
390
+ const onData = (chunk) => {
391
+ this.workerBuffer += chunk;
392
+ let idx = this.workerBuffer.indexOf("\n");
393
+ while (idx >= 0) {
394
+ const line = this.workerBuffer.slice(0, idx).trim();
395
+ this.workerBuffer = this.workerBuffer.slice(idx + 1);
396
+ idx = this.workerBuffer.indexOf("\n");
397
+ if (!line) continue;
398
+ try {
399
+ if (JSON.parse(line).type === "ready") {
400
+ clearTimeout(timer);
401
+ child.stdout.off("data", onData);
402
+ child.stderr.off("data", onStderr);
403
+ resolve();
404
+ return;
405
+ }
406
+ } catch {}
407
+ }
408
+ };
409
+ child.stdout.on("data", onData);
410
+ child.stderr.on("data", onStderr);
411
+ child.once("error", (err) => {
412
+ clearTimeout(timer);
413
+ child.stdout.off("data", onData);
414
+ child.stderr.off("data", onStderr);
415
+ reject(err);
416
+ });
417
+ });
418
+ }
419
+ async startRuntime() {
420
+ this.ensureRuntimeAvailable();
421
+ const socketPath = await this.startBroker();
422
+ try {
423
+ await this.startWorker(socketPath);
424
+ } catch (err) {
425
+ this.shutdown();
426
+ throw err instanceof Error ? err : new Error(String(err));
427
+ }
428
+ }
429
+ onWorkerStdout(chunk) {
430
+ this.workerBuffer += chunk;
431
+ let idx = this.workerBuffer.indexOf("\n");
432
+ while (idx >= 0) {
433
+ const line = this.workerBuffer.slice(0, idx).trim();
434
+ this.workerBuffer = this.workerBuffer.slice(idx + 1);
435
+ idx = this.workerBuffer.indexOf("\n");
436
+ if (!line) continue;
437
+ let parsed;
438
+ try {
439
+ parsed = JSON.parse(line);
440
+ } catch {
441
+ continue;
442
+ }
443
+ if (!parsed || typeof parsed.id !== "number") continue;
444
+ const pending = this.pending.get(parsed.id);
445
+ if (!pending) continue;
446
+ this.pending.delete(parsed.id);
447
+ clearTimeout(pending.timeout);
448
+ pending.resolve(parsed);
449
+ if (this.pending.size === 0) this.scheduleIdleShutdown();
450
+ }
451
+ }
452
+ rejectAllPending(err) {
453
+ for (const pending of this.pending.values()) {
454
+ clearTimeout(pending.timeout);
455
+ pending.reject(err);
456
+ }
457
+ this.pending.clear();
458
+ }
459
+ async callWorker(request) {
460
+ await this.ensureRuntime();
461
+ this.clearIdleTimer();
462
+ if (!this.worker || !this.worker.stdin.writable) throw new Error("Deno worker is not available.");
463
+ const id = this.nextRequestId++;
464
+ const payload = {
465
+ ...request,
466
+ id
467
+ };
468
+ return await new Promise((resolve, reject) => {
469
+ const timeout = setTimeout(() => {
470
+ this.pending.delete(id);
471
+ reject(/* @__PURE__ */ new Error("Timed out waiting for Deno worker response."));
472
+ }, REQUEST_TIMEOUT_MS);
473
+ this.pending.set(id, {
474
+ resolve,
475
+ reject,
476
+ timeout
477
+ });
478
+ this.worker.stdin.write(`${JSON.stringify(payload)}\n`);
479
+ });
480
+ }
481
+ shutdown() {
482
+ this.clearIdleTimer();
483
+ if (this.worker && !this.worker.killed) {
484
+ this.shuttingDown = true;
485
+ this.worker.stdin.destroy();
486
+ this.worker.stdout.destroy();
487
+ this.worker.stderr.destroy();
488
+ this.worker.kill();
489
+ }
490
+ this.worker = void 0;
491
+ if (this.brokerServer) {
492
+ this.brokerServer.close();
493
+ this.brokerServer = void 0;
494
+ }
495
+ if (this.brokerSocketPath && process.platform !== "win32") try {
496
+ if (node_fs.existsSync(this.brokerSocketPath)) node_fs.unlinkSync(this.brokerSocketPath);
497
+ } catch {}
498
+ this.brokerSocketPath = void 0;
499
+ }
500
+ dispose() {
501
+ this.shutdown();
502
+ JsPlugin.liveInstances.delete(this);
503
+ this.runtimeState = { status: "disposed" };
504
+ }
505
+ assertAllowedPath(absoluteFilePath) {
506
+ const resolvedPath = node_path.resolve(absoluteFilePath);
507
+ const jsReadRoot = this.opts.jsReadRoot ? node_path.resolve(this.opts.jsReadRoot) : void 0;
508
+ if (!jsReadRoot) return;
509
+ if (isPathInside(resolvedPath, jsReadRoot)) return;
510
+ if (resolvedPath.includes(`${node_path.sep}node_modules${node_path.sep}`)) return;
511
+ throw new Error(`Script path "${resolvedPath}" is outside jsReadRoot "${jsReadRoot}"`);
512
+ }
513
+ async import(absoluteFilePath) {
514
+ const ext = node_path.extname(absoluteFilePath);
515
+ if (!SCRIPT_EXTENSIONS.has(ext)) throw new Error(`Plugin "${this.name}" cannot import "${absoluteFilePath}"`);
516
+ if (!isFnsPath(absoluteFilePath)) {
517
+ this.assertAllowedPath(absoluteFilePath);
518
+ await this.ensureRuntime();
519
+ const modulePath = node_path.resolve(absoluteFilePath);
520
+ const loadResult = await this.callWorker({
521
+ type: "load",
522
+ modulePath
523
+ });
524
+ if (!loadResult.ok) throw new Error(loadResult.error);
525
+ const moduleObject = {};
526
+ const exported = loadResult.exports ?? [];
527
+ for (const item of exported) if (item.kind === "function") moduleObject[item.name] = async (...args) => {
528
+ const invokeResult = await this.callWorker({
529
+ type: "invoke",
530
+ modulePath,
531
+ exportName: item.name,
532
+ args: encodeBridgeArgs(args)
533
+ });
534
+ if (!invokeResult.ok) throw new Error(invokeResult.error);
535
+ return decodeBridgeValue(invokeResult.value);
536
+ };
537
+ else moduleObject[item.name] = item.value;
538
+ return moduleObject;
539
+ }
540
+ const module = await import((0, node_url.pathToFileURL)(node_path.resolve(absoluteFilePath)).href);
541
+ const safeModule = {};
542
+ for (const [key, value] of Object.entries(module)) if (typeof value === "function" || isJsonValue(value)) safeModule[key] = value;
543
+ return safeModule;
544
+ }
545
+ /**
546
+ * Loads a legacy Less `@plugin` wrapper file in Deno Less-compat mode.
547
+ *
548
+ * @deprecated Less `@plugin` is deprecated. Prefer `@-from` for
549
+ * ESM-style script imports or `@-use` for Sass-module-style namespace imports.
550
+ */
551
+ async importLessPlugin(absoluteFilePath) {
552
+ const ext = node_path.extname(absoluteFilePath);
553
+ if (!SCRIPT_EXTENSIONS.has(ext)) throw new Error(`Plugin "${this.name}" cannot import Less plugin "${absoluteFilePath}"`);
554
+ this.assertAllowedPath(absoluteFilePath);
555
+ await this.ensureRuntime();
556
+ const modulePath = node_path.resolve(absoluteFilePath);
557
+ const loadResult = await this.callWorker({
558
+ type: "loadLessPlugin",
559
+ modulePath
560
+ });
561
+ if (!loadResult.ok) throw new Error(loadResult.error);
562
+ const functions = {};
563
+ for (const functionName of loadResult.functions ?? []) functions[functionName] = async (...args) => {
564
+ const invokeResult = await this.callWorker({
565
+ type: "invokeLessPluginFunction",
566
+ modulePath,
567
+ functionName,
568
+ args: encodeBridgeArgs(args)
569
+ });
570
+ if (!invokeResult.ok) throw new Error(invokeResult.error);
571
+ return decodeBridgeValue(invokeResult.value);
572
+ };
573
+ return { functions };
574
+ }
575
+ };
576
+ /**
577
+ * Global flag set when @jesscss/plugin-js is loaded.
578
+ * less-compat checks this before running @plugin scripts (scripts must be run when plugin-js/Deno is present).
579
+ */
580
+ const JESS_PLUGIN_JS_GLOBAL = "__JESS_PLUGIN_JS_AVAILABLE__";
581
+ if (typeof globalThis !== "undefined") globalThis[JESS_PLUGIN_JS_GLOBAL] = true;
582
+ const jsPlugin = ((opts) => {
583
+ return new JsPlugin(opts);
584
+ });
585
+ //#endregion
586
+ exports.JESS_PLUGIN_JS_GLOBAL = JESS_PLUGIN_JS_GLOBAL;
587
+ exports.JsPlugin = JsPlugin;
588
+ exports.__toESM = __toESM;
589
+ exports.default = jsPlugin;
package/lib/index.d.ts CHANGED
@@ -6,20 +6,34 @@ export type JavaScriptSandboxConfig = {
6
6
  };
7
7
  export interface JsPluginOptions extends JavaScriptSandboxConfig {
8
8
  denoCommand?: string;
9
+ /**
10
+ * Runtime API exposed inside the Deno worker.
11
+ *
12
+ * Jess `@-use`/script imports run as plain ESM modules. Legacy Less
13
+ * `@plugin` loading can opt into the Less-compatible global shape.
14
+ */
15
+ runtimeApi?: 'module' | 'less';
9
16
  }
10
17
  export declare class JsPlugin extends AbstractPlugin {
11
18
  opts: JsPluginOptions;
19
+ private static cleanupRegistered;
20
+ private static liveInstances;
12
21
  name: string;
13
22
  supportedExtensions: string[];
14
23
  private runtimeState;
24
+ private shuttingDown;
15
25
  private brokerServer;
16
26
  private brokerSocketPath;
17
27
  private worker;
18
28
  private workerBuffer;
19
29
  private nextRequestId;
20
30
  private pending;
31
+ private idleTimer;
21
32
  constructor(opts?: JsPluginOptions);
22
33
  prewarm(): Promise<void | undefined>;
34
+ private static registerProcessCleanup;
35
+ private clearIdleTimer;
36
+ private scheduleIdleShutdown;
23
37
  private ensureRuntimeAvailable;
24
38
  private ensureRuntime;
25
39
  private createBrokerPath;
@@ -36,6 +50,15 @@ export declare class JsPlugin extends AbstractPlugin {
36
50
  dispose(): void;
37
51
  private assertAllowedPath;
38
52
  import(absoluteFilePath: string): Promise<Record<string, any>>;
53
+ /**
54
+ * Loads a legacy Less `@plugin` wrapper file in Deno Less-compat mode.
55
+ *
56
+ * @deprecated Less `@plugin` is deprecated. Prefer `@-from` for
57
+ * ESM-style script imports or `@-use` for Sass-module-style namespace imports.
58
+ */
59
+ importLessPlugin(absoluteFilePath: string): Promise<{
60
+ functions: Record<string, (...args: unknown[]) => Promise<unknown>>;
61
+ }>;
39
62
  }
40
63
  /**
41
64
  * Global flag set when @jesscss/plugin-js is loaded.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,cAAc,EACf,MAAM,eAAe,CAAC;AAOvB,MAAM,MAAM,uBAAuB,GAAG;IACpC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,WAAW,eAAgB,SAAQ,uBAAuB;IAC9D,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAoGD,qBAAa,QAAS,SAAQ,cAAc;IAevB,IAAI,EAAE,eAAe;IAdxC,IAAI,SAAQ;IACZ,mBAAmB,WAAiC;IACpD,OAAO,CAAC,YAAY,CAAoC;IACxD,OAAO,CAAC,YAAY,CAAyB;IAC7C,OAAO,CAAC,gBAAgB,CAAqB;IAC7C,OAAO,CAAC,MAAM,CAA6C;IAC3D,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,OAAO,CAIV;gBAEc,IAAI,GAAE,eAAoB;IAI7C,OAAO;IAIP,OAAO,CAAC,sBAAsB;IAU9B,OAAO,CAAC,aAAa;IAuBrB,OAAO,CAAC,gBAAgB;IAUxB,OAAO,CAAC,aAAa;IAarB,OAAO,CAAC,YAAY;IAepB,OAAO,CAAC,mBAAmB;YA0Bb,WAAW;IA2CzB,OAAO,CAAC,WAAW;YAkEL,YAAY;IAW1B,OAAO,CAAC,cAAc;IA6BtB,OAAO,CAAC,gBAAgB;YAQV,UAAU;IAqBxB,OAAO,CAAC,QAAQ;IAqBhB,OAAO;IAKP,OAAO,CAAC,iBAAiB;IAgBnB,MAAM,CAAC,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CA6CrE;AAED;;;GAGG;AACH,eAAO,MAAM,qBAAqB,iCAAiC,CAAC;AAOpE,QAAA,MAAM,QAAQ,UAAY,eAAe,aAEtB,CAAC;AAEpB,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,cAAc,EACf,MAAM,eAAe,CAAC;AAQvB,MAAM,MAAM,uBAAuB,GAAG;IACpC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,WAAW,eAAgB,SAAQ,uBAAuB;IAC9D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;CAChC;AA8GD,qBAAa,QAAS,SAAQ,cAAc;IAoBvB,IAAI,EAAE,eAAe;IAnBxC,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAAS;IACzC,OAAO,CAAC,MAAM,CAAC,aAAa,CAAuB;IACnD,IAAI,SAAQ;IACZ,mBAAmB,WAAiC;IACpD,OAAO,CAAC,YAAY,CAAoC;IACxD,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,YAAY,CAAyB;IAC7C,OAAO,CAAC,gBAAgB,CAAqB;IAC7C,OAAO,CAAC,MAAM,CAA6C;IAC3D,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,OAAO,CAIV;IAEL,OAAO,CAAC,SAAS,CAA6B;gBAE3B,IAAI,GAAE,eAAoB;IAM7C,OAAO;IAIP,OAAO,CAAC,MAAM,CAAC,sBAAsB;IAkBrC,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,oBAAoB;IAY5B,OAAO,CAAC,sBAAsB;IAU9B,OAAO,CAAC,aAAa;IA4BrB,OAAO,CAAC,gBAAgB;IAUxB,OAAO,CAAC,aAAa;IAarB,OAAO,CAAC,YAAY;IAepB,OAAO,CAAC,mBAAmB;YA0Bb,WAAW;IA8CzB,OAAO,CAAC,WAAW;YAwFL,YAAY;IAW1B,OAAO,CAAC,cAAc;IAiCtB,OAAO,CAAC,gBAAgB;YAQV,UAAU;IAwBxB,OAAO,CAAC,QAAQ;IA0BhB,OAAO;IAMP,OAAO,CAAC,iBAAiB;IAgBnB,MAAM,CAAC,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IA+CpE;;;;;OAKG;IACG,gBAAgB,CAAC,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,CAAA;KAAE,CAAC;CA6BnI;AAED;;;GAGG;AACH,eAAO,MAAM,qBAAqB,iCAAiC,CAAC;AAOpE,QAAA,MAAM,QAAQ,UAAY,eAAe,aAEtB,CAAC;AAEpB,eAAe,QAAQ,CAAC"}