@jesscss/plugin-js 2.0.0-alpha.6 → 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 CHANGED
@@ -33,6 +33,99 @@ let node_path = require("node:path");
33
33
  node_path = __toESM(node_path);
34
34
  let node_url = require("node:url");
35
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
36
129
  //#region src/index.ts
37
130
  const SCRIPT_EXTENSIONS = new Set([
38
131
  ".js",
@@ -90,6 +183,7 @@ var JsPlugin = class JsPlugin extends _jesscss_core.AbstractPlugin {
90
183
  name = "js";
91
184
  supportedExtensions = Array.from(SCRIPT_EXTENSIONS);
92
185
  runtimeState = { status: "idle" };
186
+ shuttingDown = false;
93
187
  brokerServer;
94
188
  brokerSocketPath;
95
189
  worker;
@@ -142,6 +236,7 @@ var JsPlugin = class JsPlugin extends _jesscss_core.AbstractPlugin {
142
236
  }
143
237
  if (this.runtimeState.status === "initializing") return this.runtimeState.promise;
144
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."));
145
240
  const promise = this.startRuntime().then(() => {
146
241
  this.runtimeState = { status: "ready" };
147
242
  this.scheduleIdleShutdown();
@@ -206,6 +301,7 @@ var JsPlugin = class JsPlugin extends _jesscss_core.AbstractPlugin {
206
301
  const socketPath = this.createBrokerPath();
207
302
  if (process.platform !== "win32" && node_fs.existsSync(socketPath)) node_fs.unlinkSync(socketPath);
208
303
  const server = node_net.createServer((socket) => {
304
+ socket.unref();
209
305
  let buf = "";
210
306
  socket.setEncoding("utf8");
211
307
  socket.on("data", (chunk) => {
@@ -236,6 +332,7 @@ var JsPlugin = class JsPlugin extends _jesscss_core.AbstractPlugin {
236
332
  server.once("error", reject);
237
333
  server.listen(socketPath, () => resolve());
238
334
  });
335
+ server.unref();
239
336
  this.brokerServer = server;
240
337
  this.brokerSocketPath = socketPath;
241
338
  return socketPath;
@@ -248,7 +345,8 @@ var JsPlugin = class JsPlugin extends _jesscss_core.AbstractPlugin {
248
345
  const child = (0, node_child_process.spawn)(denoCommand, [
249
346
  "run",
250
347
  "--no-prompt",
251
- node_fs.existsSync(compiledWorkerPath) ? compiledWorkerPath : sourceWorkerPath
348
+ node_fs.existsSync(compiledWorkerPath) ? compiledWorkerPath : sourceWorkerPath,
349
+ `--runtime-api=${this.opts.runtimeApi ?? "module"}`
252
350
  ], {
253
351
  stdio: [
254
352
  "pipe",
@@ -261,10 +359,19 @@ var JsPlugin = class JsPlugin extends _jesscss_core.AbstractPlugin {
261
359
  }
262
360
  });
263
361
  this.worker = child;
362
+ child.unref();
363
+ child.stdin.unref?.();
364
+ child.stdout.unref?.();
365
+ child.stderr.unref?.();
264
366
  child.stdout.setEncoding("utf8");
265
367
  child.stderr.setEncoding("utf8");
266
368
  child.stdout.on("data", (chunk) => this.onWorkerStdout(chunk));
267
369
  child.on("exit", () => {
370
+ this.worker = void 0;
371
+ if (this.shuttingDown) {
372
+ this.shuttingDown = false;
373
+ return;
374
+ }
268
375
  const err = /* @__PURE__ */ new Error("Deno worker exited unexpectedly.");
269
376
  this.rejectAllPending(err);
270
377
  if (this.runtimeState.status !== "failed") this.runtimeState = {
@@ -273,9 +380,13 @@ var JsPlugin = class JsPlugin extends _jesscss_core.AbstractPlugin {
273
380
  };
274
381
  });
275
382
  return new Promise((resolve, reject) => {
383
+ let stderrText = "";
276
384
  const timer = setTimeout(() => {
277
- reject(/* @__PURE__ */ new Error("Timed out waiting for Deno worker startup."));
385
+ reject(/* @__PURE__ */ new Error(stderrText.trim() ? `Timed out waiting for Deno worker startup.\n${stderrText.trim()}` : "Timed out waiting for Deno worker startup."));
278
386
  }, BOOT_TIMEOUT_MS);
387
+ const onStderr = (chunk) => {
388
+ stderrText += chunk;
389
+ };
279
390
  const onData = (chunk) => {
280
391
  this.workerBuffer += chunk;
281
392
  let idx = this.workerBuffer.indexOf("\n");
@@ -288,6 +399,7 @@ var JsPlugin = class JsPlugin extends _jesscss_core.AbstractPlugin {
288
399
  if (JSON.parse(line).type === "ready") {
289
400
  clearTimeout(timer);
290
401
  child.stdout.off("data", onData);
402
+ child.stderr.off("data", onStderr);
291
403
  resolve();
292
404
  return;
293
405
  }
@@ -295,9 +407,11 @@ var JsPlugin = class JsPlugin extends _jesscss_core.AbstractPlugin {
295
407
  }
296
408
  };
297
409
  child.stdout.on("data", onData);
410
+ child.stderr.on("data", onStderr);
298
411
  child.once("error", (err) => {
299
412
  clearTimeout(timer);
300
413
  child.stdout.off("data", onData);
414
+ child.stderr.off("data", onStderr);
301
415
  reject(err);
302
416
  });
303
417
  });
@@ -366,7 +480,13 @@ var JsPlugin = class JsPlugin extends _jesscss_core.AbstractPlugin {
366
480
  }
367
481
  shutdown() {
368
482
  this.clearIdleTimer();
369
- if (this.worker && !this.worker.killed) this.worker.kill();
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
+ }
370
490
  this.worker = void 0;
371
491
  if (this.brokerServer) {
372
492
  this.brokerServer.close();
@@ -379,7 +499,8 @@ var JsPlugin = class JsPlugin extends _jesscss_core.AbstractPlugin {
379
499
  }
380
500
  dispose() {
381
501
  this.shutdown();
382
- this.runtimeState = { status: "idle" };
502
+ JsPlugin.liveInstances.delete(this);
503
+ this.runtimeState = { status: "disposed" };
383
504
  }
384
505
  assertAllowedPath(absoluteFilePath) {
385
506
  const resolvedPath = node_path.resolve(absoluteFilePath);
@@ -408,10 +529,10 @@ var JsPlugin = class JsPlugin extends _jesscss_core.AbstractPlugin {
408
529
  type: "invoke",
409
530
  modulePath,
410
531
  exportName: item.name,
411
- args
532
+ args: encodeBridgeArgs(args)
412
533
  });
413
534
  if (!invokeResult.ok) throw new Error(invokeResult.error);
414
- return invokeResult.value;
535
+ return decodeBridgeValue(invokeResult.value);
415
536
  };
416
537
  else moduleObject[item.name] = item.value;
417
538
  return moduleObject;
@@ -421,6 +542,36 @@ var JsPlugin = class JsPlugin extends _jesscss_core.AbstractPlugin {
421
542
  for (const [key, value] of Object.entries(module)) if (typeof value === "function" || isJsonValue(value)) safeModule[key] = value;
422
543
  return safeModule;
423
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
+ }
424
575
  };
425
576
  /**
426
577
  * Global flag set when @jesscss/plugin-js is loaded.
@@ -434,4 +585,5 @@ const jsPlugin = ((opts) => {
434
585
  //#endregion
435
586
  exports.JESS_PLUGIN_JS_GLOBAL = JESS_PLUGIN_JS_GLOBAL;
436
587
  exports.JsPlugin = JsPlugin;
588
+ exports.__toESM = __toESM;
437
589
  exports.default = jsPlugin;
package/lib/index.d.ts CHANGED
@@ -6,6 +6,13 @@ 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;
@@ -14,6 +21,7 @@ export declare class JsPlugin extends AbstractPlugin {
14
21
  name: string;
15
22
  supportedExtensions: string[];
16
23
  private runtimeState;
24
+ private shuttingDown;
17
25
  private brokerServer;
18
26
  private brokerSocketPath;
19
27
  private worker;
@@ -42,6 +50,15 @@ export declare class JsPlugin extends AbstractPlugin {
42
50
  dispose(): void;
43
51
  private assertAllowedPath;
44
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
+ }>;
45
62
  }
46
63
  /**
47
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;AAqGD,qBAAa,QAAS,SAAQ,cAAc;IAmBvB,IAAI,EAAE,eAAe;IAlBxC,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,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;IAyBrB,OAAO,CAAC,gBAAgB;IAUxB,OAAO,CAAC,aAAa;IAarB,OAAO,CAAC,YAAY;IAepB,OAAO,CAAC,mBAAmB;YA0Bb,WAAW;IA4CzB,OAAO,CAAC,WAAW;YAmEL,YAAY;IAW1B,OAAO,CAAC,cAAc;IAiCtB,OAAO,CAAC,gBAAgB;YAQV,UAAU;IAsBxB,OAAO,CAAC,QAAQ;IAsBhB,OAAO;IAKP,OAAO,CAAC,iBAAiB;IAgBnB,MAAM,CAAC,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CA8CrE;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"}
package/lib/index.js CHANGED
@@ -1,9 +1,102 @@
1
- import { AbstractPlugin } from "@jesscss/core";
1
+ import { AbstractPlugin, Any, Color, ColorFormat, Declaration, Dimension, List, Quoted, Rules, Sequence } from "@jesscss/core";
2
2
  import * as fs from "node:fs";
3
3
  import * as net from "node:net";
4
4
  import * as path from "node:path";
5
5
  import { fileURLToPath, pathToFileURL } from "node:url";
6
6
  import { spawn, spawnSync } from "node:child_process";
7
+ //#region src/bridge.ts
8
+ const isBridgeValue = (value) => typeof value === "object" && value !== null && value.__jessBridge === true && typeof value.kind === "string";
9
+ const colorFormatFromString = (value) => {
10
+ if (!value) return;
11
+ if (value in ColorFormat) return ColorFormat[value];
12
+ if (Object.values(ColorFormat).includes(value)) return value;
13
+ };
14
+ function encodeBridgeChildValue(value) {
15
+ const encoded = encodeBridgeValue(value);
16
+ if (isBridgeValue(encoded)) return encoded;
17
+ return {
18
+ __jessBridge: true,
19
+ kind: "scalar",
20
+ value: typeof encoded === "string" || typeof encoded === "number" || typeof encoded === "boolean" ? encoded : String(encoded)
21
+ };
22
+ }
23
+ function encodeBridgeValue(value) {
24
+ if (value instanceof Dimension) return {
25
+ __jessBridge: true,
26
+ kind: "dimension",
27
+ value: value.number,
28
+ unit: value.unit
29
+ };
30
+ if (value instanceof Color) return {
31
+ __jessBridge: true,
32
+ kind: "color",
33
+ rgb: value.rgb,
34
+ alpha: value.alpha,
35
+ format: value.options.format === void 0 ? void 0 : ColorFormat[value.options.format]
36
+ };
37
+ if (value instanceof Quoted) return {
38
+ __jessBridge: true,
39
+ kind: "quoted",
40
+ value: String(value.value),
41
+ quote: value.quote,
42
+ escaped: value.escaped
43
+ };
44
+ if (value instanceof Any) return {
45
+ __jessBridge: true,
46
+ kind: value.role === "keyword" ? "keyword" : "anonymous",
47
+ value: value.value
48
+ };
49
+ if (value instanceof List) return {
50
+ __jessBridge: true,
51
+ kind: "list",
52
+ items: value.value.map(encodeBridgeChildValue),
53
+ separator: value.options.sep
54
+ };
55
+ if (value instanceof Sequence) return {
56
+ __jessBridge: true,
57
+ kind: "sequence",
58
+ items: value.value.map(encodeBridgeChildValue)
59
+ };
60
+ if (value instanceof Rules) {
61
+ const rules = [];
62
+ for (const rule of value.rules ?? []) if (rule instanceof Declaration && typeof rule.name === "string") rules.push({
63
+ name: rule.name,
64
+ value: encodeBridgeChildValue(rule.value)
65
+ });
66
+ return {
67
+ __jessBridge: true,
68
+ kind: "detached",
69
+ rules
70
+ };
71
+ }
72
+ return value;
73
+ }
74
+ function decodeBridgeValue(value) {
75
+ if (!isBridgeValue(value)) return value;
76
+ switch (value.kind) {
77
+ case "scalar": return new Any(String(value.value));
78
+ case "dimension": return new Dimension({
79
+ number: value.value,
80
+ unit: value.unit
81
+ });
82
+ case "color": return new Color({
83
+ rgb: value.rgb,
84
+ alpha: value.alpha
85
+ }, { format: colorFormatFromString(value.format) });
86
+ case "quoted": return new Quoted(value.value, {
87
+ quote: value.quote,
88
+ escaped: value.escaped
89
+ });
90
+ case "keyword": return new Any(value.value, { role: "keyword" });
91
+ case "anonymous": return new Any(value.value);
92
+ case "list": return new List(value.items.map((item) => decodeBridgeValue(item)), { sep: value.separator });
93
+ case "sequence": return new Sequence(value.items.map((item) => decodeBridgeValue(item)));
94
+ }
95
+ }
96
+ function encodeBridgeArgs(args) {
97
+ return args.map(encodeBridgeValue);
98
+ }
99
+ //#endregion
7
100
  //#region src/index.ts
8
101
  const SCRIPT_EXTENSIONS = new Set([
9
102
  ".js",
@@ -61,6 +154,7 @@ var JsPlugin = class JsPlugin extends AbstractPlugin {
61
154
  name = "js";
62
155
  supportedExtensions = Array.from(SCRIPT_EXTENSIONS);
63
156
  runtimeState = { status: "idle" };
157
+ shuttingDown = false;
64
158
  brokerServer;
65
159
  brokerSocketPath;
66
160
  worker;
@@ -113,6 +207,7 @@ var JsPlugin = class JsPlugin extends AbstractPlugin {
113
207
  }
114
208
  if (this.runtimeState.status === "initializing") return this.runtimeState.promise;
115
209
  if (this.runtimeState.status === "failed") return Promise.reject(this.runtimeState.error);
210
+ if (this.runtimeState.status === "disposed") return Promise.reject(/* @__PURE__ */ new Error("Deno worker has been disposed."));
116
211
  const promise = this.startRuntime().then(() => {
117
212
  this.runtimeState = { status: "ready" };
118
213
  this.scheduleIdleShutdown();
@@ -177,6 +272,7 @@ var JsPlugin = class JsPlugin extends AbstractPlugin {
177
272
  const socketPath = this.createBrokerPath();
178
273
  if (process.platform !== "win32" && fs.existsSync(socketPath)) fs.unlinkSync(socketPath);
179
274
  const server = net.createServer((socket) => {
275
+ socket.unref();
180
276
  let buf = "";
181
277
  socket.setEncoding("utf8");
182
278
  socket.on("data", (chunk) => {
@@ -207,6 +303,7 @@ var JsPlugin = class JsPlugin extends AbstractPlugin {
207
303
  server.once("error", reject);
208
304
  server.listen(socketPath, () => resolve());
209
305
  });
306
+ server.unref();
210
307
  this.brokerServer = server;
211
308
  this.brokerSocketPath = socketPath;
212
309
  return socketPath;
@@ -219,7 +316,8 @@ var JsPlugin = class JsPlugin extends AbstractPlugin {
219
316
  const child = spawn(denoCommand, [
220
317
  "run",
221
318
  "--no-prompt",
222
- fs.existsSync(compiledWorkerPath) ? compiledWorkerPath : sourceWorkerPath
319
+ fs.existsSync(compiledWorkerPath) ? compiledWorkerPath : sourceWorkerPath,
320
+ `--runtime-api=${this.opts.runtimeApi ?? "module"}`
223
321
  ], {
224
322
  stdio: [
225
323
  "pipe",
@@ -232,10 +330,19 @@ var JsPlugin = class JsPlugin extends AbstractPlugin {
232
330
  }
233
331
  });
234
332
  this.worker = child;
333
+ child.unref();
334
+ child.stdin.unref?.();
335
+ child.stdout.unref?.();
336
+ child.stderr.unref?.();
235
337
  child.stdout.setEncoding("utf8");
236
338
  child.stderr.setEncoding("utf8");
237
339
  child.stdout.on("data", (chunk) => this.onWorkerStdout(chunk));
238
340
  child.on("exit", () => {
341
+ this.worker = void 0;
342
+ if (this.shuttingDown) {
343
+ this.shuttingDown = false;
344
+ return;
345
+ }
239
346
  const err = /* @__PURE__ */ new Error("Deno worker exited unexpectedly.");
240
347
  this.rejectAllPending(err);
241
348
  if (this.runtimeState.status !== "failed") this.runtimeState = {
@@ -244,9 +351,13 @@ var JsPlugin = class JsPlugin extends AbstractPlugin {
244
351
  };
245
352
  });
246
353
  return new Promise((resolve, reject) => {
354
+ let stderrText = "";
247
355
  const timer = setTimeout(() => {
248
- reject(/* @__PURE__ */ new Error("Timed out waiting for Deno worker startup."));
356
+ reject(/* @__PURE__ */ new Error(stderrText.trim() ? `Timed out waiting for Deno worker startup.\n${stderrText.trim()}` : "Timed out waiting for Deno worker startup."));
249
357
  }, BOOT_TIMEOUT_MS);
358
+ const onStderr = (chunk) => {
359
+ stderrText += chunk;
360
+ };
250
361
  const onData = (chunk) => {
251
362
  this.workerBuffer += chunk;
252
363
  let idx = this.workerBuffer.indexOf("\n");
@@ -259,6 +370,7 @@ var JsPlugin = class JsPlugin extends AbstractPlugin {
259
370
  if (JSON.parse(line).type === "ready") {
260
371
  clearTimeout(timer);
261
372
  child.stdout.off("data", onData);
373
+ child.stderr.off("data", onStderr);
262
374
  resolve();
263
375
  return;
264
376
  }
@@ -266,9 +378,11 @@ var JsPlugin = class JsPlugin extends AbstractPlugin {
266
378
  }
267
379
  };
268
380
  child.stdout.on("data", onData);
381
+ child.stderr.on("data", onStderr);
269
382
  child.once("error", (err) => {
270
383
  clearTimeout(timer);
271
384
  child.stdout.off("data", onData);
385
+ child.stderr.off("data", onStderr);
272
386
  reject(err);
273
387
  });
274
388
  });
@@ -337,7 +451,13 @@ var JsPlugin = class JsPlugin extends AbstractPlugin {
337
451
  }
338
452
  shutdown() {
339
453
  this.clearIdleTimer();
340
- if (this.worker && !this.worker.killed) this.worker.kill();
454
+ if (this.worker && !this.worker.killed) {
455
+ this.shuttingDown = true;
456
+ this.worker.stdin.destroy();
457
+ this.worker.stdout.destroy();
458
+ this.worker.stderr.destroy();
459
+ this.worker.kill();
460
+ }
341
461
  this.worker = void 0;
342
462
  if (this.brokerServer) {
343
463
  this.brokerServer.close();
@@ -350,7 +470,8 @@ var JsPlugin = class JsPlugin extends AbstractPlugin {
350
470
  }
351
471
  dispose() {
352
472
  this.shutdown();
353
- this.runtimeState = { status: "idle" };
473
+ JsPlugin.liveInstances.delete(this);
474
+ this.runtimeState = { status: "disposed" };
354
475
  }
355
476
  assertAllowedPath(absoluteFilePath) {
356
477
  const resolvedPath = path.resolve(absoluteFilePath);
@@ -379,10 +500,10 @@ var JsPlugin = class JsPlugin extends AbstractPlugin {
379
500
  type: "invoke",
380
501
  modulePath,
381
502
  exportName: item.name,
382
- args
503
+ args: encodeBridgeArgs(args)
383
504
  });
384
505
  if (!invokeResult.ok) throw new Error(invokeResult.error);
385
- return invokeResult.value;
506
+ return decodeBridgeValue(invokeResult.value);
386
507
  };
387
508
  else moduleObject[item.name] = item.value;
388
509
  return moduleObject;
@@ -392,6 +513,36 @@ var JsPlugin = class JsPlugin extends AbstractPlugin {
392
513
  for (const [key, value] of Object.entries(module)) if (typeof value === "function" || isJsonValue(value)) safeModule[key] = value;
393
514
  return safeModule;
394
515
  }
516
+ /**
517
+ * Loads a legacy Less `@plugin` wrapper file in Deno Less-compat mode.
518
+ *
519
+ * @deprecated Less `@plugin` is deprecated. Prefer `@-from` for
520
+ * ESM-style script imports or `@-use` for Sass-module-style namespace imports.
521
+ */
522
+ async importLessPlugin(absoluteFilePath) {
523
+ const ext = path.extname(absoluteFilePath);
524
+ if (!SCRIPT_EXTENSIONS.has(ext)) throw new Error(`Plugin "${this.name}" cannot import Less plugin "${absoluteFilePath}"`);
525
+ this.assertAllowedPath(absoluteFilePath);
526
+ await this.ensureRuntime();
527
+ const modulePath = path.resolve(absoluteFilePath);
528
+ const loadResult = await this.callWorker({
529
+ type: "loadLessPlugin",
530
+ modulePath
531
+ });
532
+ if (!loadResult.ok) throw new Error(loadResult.error);
533
+ const functions = {};
534
+ for (const functionName of loadResult.functions ?? []) functions[functionName] = async (...args) => {
535
+ const invokeResult = await this.callWorker({
536
+ type: "invokeLessPluginFunction",
537
+ modulePath,
538
+ functionName,
539
+ args: encodeBridgeArgs(args)
540
+ });
541
+ if (!invokeResult.ok) throw new Error(invokeResult.error);
542
+ return decodeBridgeValue(invokeResult.value);
543
+ };
544
+ return { functions };
545
+ }
395
546
  };
396
547
  /**
397
548
  * Global flag set when @jesscss/plugin-js is loaded.