@jesscss/plugin-js 2.0.0-alpha.7 → 2.0.0-alpha.9
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 +47 -3
- package/lib/bridge.d.ts +4 -9
- package/lib/bridge.d.ts.map +1 -1
- package/lib/index.cjs +211 -76
- package/lib/index.d.ts +12 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +212 -78
- package/lib/runtime-worker.cjs +47 -25
- package/lib/runtime-worker.js +47 -25
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,50 @@
|
|
|
1
1
|
# @jesscss/plugin-js
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Import bridge for JavaScript/TypeScript modules in stylesheets — a seed of the
|
|
4
|
+
JavaScript-execution / CSS-in-JS story.**
|
|
4
5
|
|
|
5
|
-
This
|
|
6
|
-
|
|
6
|
+
This plugin lets a stylesheet pull in JavaScript/TypeScript modules
|
|
7
|
+
(`.js`, `.mjs`, `.cjs`, `.ts`, `.mts`, `.cts`) — the mechanism behind `@use` /
|
|
8
|
+
`@-from` script imports and legacy Less `@plugin` loading. When installed, it is
|
|
9
|
+
auto-loaded by `jess`.
|
|
10
|
+
|
|
11
|
+
## Sandboxed execution
|
|
12
|
+
|
|
13
|
+
`plugin-js` does **not** run untrusted module code in your Node process. Before
|
|
14
|
+
executing anything, it checks for a usable **Deno** runtime (`deno --version`)
|
|
15
|
+
and runs the module in a Deno subprocess behind a permission broker:
|
|
16
|
+
|
|
17
|
+
- **read** is limited to `node_modules` (and an optional `jsReadRoot`),
|
|
18
|
+
- **net** is denied unless you opt in (`allowHttp`, optionally scoped to
|
|
19
|
+
`allowNetHosts`),
|
|
20
|
+
- **env / write / run / ffi / sys** are denied outright.
|
|
21
|
+
|
|
22
|
+
Values cross the boundary through a small typed bridge (dimensions, colors,
|
|
23
|
+
quoted strings, lists, detached rules, …). Built-in `@jesscss/fns` modules are
|
|
24
|
+
trusted and imported directly, without the worker. If no Deno binary is found,
|
|
25
|
+
the plugin fails with a clear message instead of falling back to unsandboxed
|
|
26
|
+
execution.
|
|
27
|
+
|
|
28
|
+
## Why it exists — the convergence angle
|
|
29
|
+
|
|
30
|
+
One of the four tools [Jess](https://github.com/jesscss/jess) aims to converge is
|
|
31
|
+
**CSS-in-JS**: running real JavaScript inside stylesheets so styles can be
|
|
32
|
+
dynamic without leaving CSS files. This plugin — together with
|
|
33
|
+
[`@jesscss/plugin-node-modules`](../jess-plugin-node-modules), which resolves the
|
|
34
|
+
packages — is a seed of that story.
|
|
35
|
+
|
|
36
|
+
That convergence is **roadmap — being proven through the alpha, not claimed as
|
|
37
|
+
done.** Legacy Less `@plugin` is supported for compatibility but deprecated in
|
|
38
|
+
favor of `@-from` / `@-use`.
|
|
39
|
+
|
|
40
|
+
## Status
|
|
41
|
+
|
|
42
|
+
**Alpha.** Part of Jess. Requires a Deno runtime for script execution. The
|
|
43
|
+
programmatic plugin/compiler API is **not yet stabilized** — the `jess` CLI is
|
|
44
|
+
the documented public surface for the alpha. Watch the
|
|
45
|
+
[docs site](https://jesscss.github.io/) for the API once it settles.
|
|
46
|
+
|
|
47
|
+
- Project overview & positioning: <https://github.com/jesscss/jess#readme>
|
|
48
|
+
- Docs: <https://jesscss.github.io/> (currently pre-alpha content)
|
|
49
|
+
- Issues: <https://github.com/jesscss/jess/issues>
|
|
50
|
+
- License: MIT
|
package/lib/bridge.d.ts
CHANGED
|
@@ -16,17 +16,12 @@ export type JsBridgeValue = {
|
|
|
16
16
|
kind: 'color';
|
|
17
17
|
rgb: [number, number, number];
|
|
18
18
|
alpha?: number;
|
|
19
|
-
format?: string;
|
|
20
19
|
} | {
|
|
21
20
|
__jessBridge: true;
|
|
22
21
|
kind: 'quoted';
|
|
23
22
|
value: string;
|
|
24
23
|
quote?: '"' | '\'';
|
|
25
24
|
escaped?: boolean;
|
|
26
|
-
} | {
|
|
27
|
-
__jessBridge: true;
|
|
28
|
-
kind: 'keyword';
|
|
29
|
-
value: string;
|
|
30
25
|
} | {
|
|
31
26
|
__jessBridge: true;
|
|
32
27
|
kind: 'anonymous';
|
|
@@ -35,17 +30,17 @@ export type JsBridgeValue = {
|
|
|
35
30
|
__jessBridge: true;
|
|
36
31
|
kind: 'list';
|
|
37
32
|
items: JsBridgeValue[];
|
|
38
|
-
separator
|
|
33
|
+
separator: ',' | '/' | ';';
|
|
39
34
|
} | {
|
|
40
35
|
__jessBridge: true;
|
|
41
|
-
kind: '
|
|
36
|
+
kind: 'expression';
|
|
42
37
|
items: JsBridgeValue[];
|
|
43
38
|
} | {
|
|
44
39
|
__jessBridge: true;
|
|
45
|
-
kind: '
|
|
40
|
+
kind: 'mixin';
|
|
46
41
|
rules: JsBridgeDeclaration[];
|
|
47
42
|
};
|
|
48
43
|
export declare function encodeBridgeValue(value: unknown): unknown;
|
|
49
44
|
export declare function decodeBridgeValue(value: unknown): unknown;
|
|
50
|
-
export declare function encodeBridgeArgs(args: unknown[]): unknown[];
|
|
45
|
+
export declare function encodeBridgeArgs(args: readonly unknown[]): unknown[];
|
|
51
46
|
//# sourceMappingURL=bridge.d.ts.map
|
package/lib/bridge.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAaA,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,CAAA;CAAE,GACpF;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,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,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,CAAA;CAAE,GACxF;IAAE,YAAY,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,aAAa,EAAE,CAAA;CAAE,GAClE;IAAE,YAAY,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,mBAAmB,EAAE,CAAA;CAAE,CAAC;AAiGxE,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CA8BzD;AAqCD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAKzD;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,EAAE,CAEpE"}
|
package/lib/index.cjs
CHANGED
|
@@ -33,100 +33,198 @@ 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
|
+
let _jesscss_core_value = require("@jesscss/core/value");
|
|
36
37
|
//#region src/bridge.ts
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (Object.values(_jesscss_core.ColorFormat).includes(value)) return value;
|
|
42
|
-
};
|
|
38
|
+
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
39
|
+
const isBridgeValue = (value) => isRecord(value) && value.__jessBridge === true && typeof value.kind === "string";
|
|
40
|
+
const isValueObj = (value) => isRecord(value) && typeof value.type === "string" && typeof value.bytes === "string";
|
|
41
|
+
const isDetached = (value) => isRecord(value) && value.type === "DetachedRuleset" && Array.isArray(value.rules);
|
|
43
42
|
function encodeBridgeChildValue(value) {
|
|
44
43
|
const encoded = encodeBridgeValue(value);
|
|
45
44
|
if (isBridgeValue(encoded)) return encoded;
|
|
46
45
|
return {
|
|
47
46
|
__jessBridge: true,
|
|
48
47
|
kind: "scalar",
|
|
49
|
-
value:
|
|
48
|
+
value: String(encoded)
|
|
50
49
|
};
|
|
51
50
|
}
|
|
51
|
+
function encodeFacadeValue(value) {
|
|
52
|
+
switch (value.type) {
|
|
53
|
+
case "Dimension": return typeof value.value === "number" ? {
|
|
54
|
+
__jessBridge: true,
|
|
55
|
+
kind: "dimension",
|
|
56
|
+
value: value.value,
|
|
57
|
+
unit: typeof value.unit === "string" ? value.unit : void 0
|
|
58
|
+
} : void 0;
|
|
59
|
+
case "Color": return Array.isArray(value.rgb) && value.rgb.length === 3 ? {
|
|
60
|
+
__jessBridge: true,
|
|
61
|
+
kind: "color",
|
|
62
|
+
rgb: [
|
|
63
|
+
value.rgb[0],
|
|
64
|
+
value.rgb[1],
|
|
65
|
+
value.rgb[2]
|
|
66
|
+
],
|
|
67
|
+
alpha: typeof value.alpha === "number" ? value.alpha : void 0
|
|
68
|
+
} : void 0;
|
|
69
|
+
case "Quoted": return typeof value.value === "string" ? {
|
|
70
|
+
__jessBridge: true,
|
|
71
|
+
kind: "quoted",
|
|
72
|
+
value: value.value,
|
|
73
|
+
quote: value.quote === "'" ? "'" : "\"",
|
|
74
|
+
escaped: value.escaped === true
|
|
75
|
+
} : void 0;
|
|
76
|
+
case "Anonymous":
|
|
77
|
+
case "Keyword": return typeof value.value === "string" ? {
|
|
78
|
+
__jessBridge: true,
|
|
79
|
+
kind: "anonymous",
|
|
80
|
+
value: value.value
|
|
81
|
+
} : void 0;
|
|
82
|
+
case "Expression": return Array.isArray(value.value) ? {
|
|
83
|
+
__jessBridge: true,
|
|
84
|
+
kind: "expression",
|
|
85
|
+
items: value.value.map(encodeBridgeChildValue)
|
|
86
|
+
} : void 0;
|
|
87
|
+
case "Value": return Array.isArray(value.value) ? {
|
|
88
|
+
__jessBridge: true,
|
|
89
|
+
kind: "list",
|
|
90
|
+
items: value.value.map(encodeBridgeChildValue),
|
|
91
|
+
separator: value.separator === "/" || value.separator === ";" ? value.separator : ","
|
|
92
|
+
} : void 0;
|
|
93
|
+
case "Mixin": {
|
|
94
|
+
const ruleset = value.ruleset;
|
|
95
|
+
if (!isRecord(ruleset) || !Array.isArray(ruleset.rules)) return;
|
|
96
|
+
const rules = [];
|
|
97
|
+
for (const rule of ruleset.rules) {
|
|
98
|
+
if (!isRecord(rule)) continue;
|
|
99
|
+
if (rule.type === "Declaration" && typeof rule.name === "string") rules.push({
|
|
100
|
+
name: rule.name,
|
|
101
|
+
value: encodeBridgeChildValue(rule.value)
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
__jessBridge: true,
|
|
106
|
+
kind: "mixin",
|
|
107
|
+
rules
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
default: return;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
52
113
|
function encodeBridgeValue(value) {
|
|
53
|
-
if (value
|
|
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 {
|
|
114
|
+
if (Array.isArray(value)) return {
|
|
67
115
|
__jessBridge: true,
|
|
68
|
-
kind: "
|
|
69
|
-
|
|
70
|
-
quote: value.quote,
|
|
71
|
-
escaped: value.escaped
|
|
116
|
+
kind: "expression",
|
|
117
|
+
items: value.map(encodeBridgeChildValue)
|
|
72
118
|
};
|
|
73
|
-
if (value
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
value:
|
|
94
|
-
|
|
95
|
-
|
|
119
|
+
if (isValueObj(value)) switch (value.type) {
|
|
120
|
+
case "Dimension": return {
|
|
121
|
+
__jessBridge: true,
|
|
122
|
+
kind: "dimension",
|
|
123
|
+
value: value.number,
|
|
124
|
+
unit: value.unit
|
|
125
|
+
};
|
|
126
|
+
case "Color": return {
|
|
127
|
+
__jessBridge: true,
|
|
128
|
+
kind: "color",
|
|
129
|
+
rgb: [
|
|
130
|
+
value.rgb[0],
|
|
131
|
+
value.rgb[1],
|
|
132
|
+
value.rgb[2]
|
|
133
|
+
],
|
|
134
|
+
alpha: value.alpha
|
|
135
|
+
};
|
|
136
|
+
case "Quoted": return {
|
|
137
|
+
__jessBridge: true,
|
|
138
|
+
kind: "quoted",
|
|
139
|
+
value: value.value,
|
|
140
|
+
quote: value.quote === "'" ? "'" : "\"",
|
|
141
|
+
escaped: value.escaped
|
|
142
|
+
};
|
|
143
|
+
case "List": return value.sep === "," || value.sep === "/" ? {
|
|
144
|
+
__jessBridge: true,
|
|
145
|
+
kind: "list",
|
|
146
|
+
items: value.value.map(encodeBridgeChildValue),
|
|
147
|
+
separator: value.sep
|
|
148
|
+
} : {
|
|
96
149
|
__jessBridge: true,
|
|
97
|
-
kind: "
|
|
98
|
-
|
|
150
|
+
kind: "anonymous",
|
|
151
|
+
value: value.bytes
|
|
152
|
+
};
|
|
153
|
+
case "Block": return {
|
|
154
|
+
__jessBridge: true,
|
|
155
|
+
kind: "anonymous",
|
|
156
|
+
value: value.bytes
|
|
157
|
+
};
|
|
158
|
+
default: return {
|
|
159
|
+
__jessBridge: true,
|
|
160
|
+
kind: "anonymous",
|
|
161
|
+
value: value.bytes
|
|
99
162
|
};
|
|
100
163
|
}
|
|
164
|
+
if (isDetached(value)) return {
|
|
165
|
+
__jessBridge: true,
|
|
166
|
+
kind: "mixin",
|
|
167
|
+
rules: value.rules.map((rule) => ({
|
|
168
|
+
name: rule.name,
|
|
169
|
+
value: encodeBridgeChildValue(rule.value)
|
|
170
|
+
}))
|
|
171
|
+
};
|
|
172
|
+
if (isRecord(value)) return encodeFacadeValue(value) ?? value;
|
|
101
173
|
return value;
|
|
102
174
|
}
|
|
103
|
-
function
|
|
104
|
-
if (!isBridgeValue(value)) return value;
|
|
175
|
+
function decodeValue(value) {
|
|
105
176
|
switch (value.kind) {
|
|
106
|
-
case "scalar": return
|
|
107
|
-
case "dimension": return
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
case "
|
|
112
|
-
|
|
113
|
-
|
|
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)));
|
|
177
|
+
case "scalar": return (0, _jesscss_core_value.makeKeyword)(String(value.value));
|
|
178
|
+
case "dimension": return (0, _jesscss_core_value.makeDimension)(value.value, value.unit ?? "");
|
|
179
|
+
case "color": return (0, _jesscss_core_value.makeColorRgb)(value.rgb, value.alpha ?? 1, _jesscss_core_value.RGB);
|
|
180
|
+
case "quoted": return (0, _jesscss_core_value.makeQuoted)(value.value, value.quote ?? "\"", value.escaped === true);
|
|
181
|
+
case "anonymous": return (0, _jesscss_core_value.makeKeyword)(value.value);
|
|
182
|
+
case "expression": return value.items.map(decodeValue);
|
|
183
|
+
case "list": return (0, _jesscss_core_value.makeList)(value.items.map(decodeValue), value.separator === ";" ? "," : value.separator);
|
|
184
|
+
case "mixin": return (0, _jesscss_core_value.makeKeyword)("");
|
|
123
185
|
}
|
|
124
186
|
}
|
|
187
|
+
function decodeMixin(value) {
|
|
188
|
+
const nil = {
|
|
189
|
+
type: "Nil",
|
|
190
|
+
value: ""
|
|
191
|
+
};
|
|
192
|
+
const mixin = {
|
|
193
|
+
type: "Mixin",
|
|
194
|
+
name: nil,
|
|
195
|
+
args: nil,
|
|
196
|
+
ruleset: { rules: value.rules.map((rule) => ({
|
|
197
|
+
type: "Declaration",
|
|
198
|
+
name: rule.name,
|
|
199
|
+
value: decodeValue(rule.value),
|
|
200
|
+
eval() {
|
|
201
|
+
return this;
|
|
202
|
+
}
|
|
203
|
+
})) },
|
|
204
|
+
eval() {
|
|
205
|
+
return mixin;
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
return mixin;
|
|
209
|
+
}
|
|
210
|
+
function decodeBridgeValue(value) {
|
|
211
|
+
if (!isBridgeValue(value)) return value;
|
|
212
|
+
return value.kind === "mixin" ? decodeMixin(value) : decodeValue(value);
|
|
213
|
+
}
|
|
125
214
|
function encodeBridgeArgs(args) {
|
|
126
215
|
return args.map(encodeBridgeValue);
|
|
127
216
|
}
|
|
128
217
|
//#endregion
|
|
129
218
|
//#region src/index.ts
|
|
219
|
+
/**
|
|
220
|
+
* Child-process stdio streams are typed as bare `Writable`/`Readable`, which do
|
|
221
|
+
* not declare `unref`, but the underlying pipe sockets expose it at runtime.
|
|
222
|
+
* The optional structural member lets any stream be passed without an unsafe
|
|
223
|
+
* cast; the runtime `?.` guard covers streams that genuinely lack the method.
|
|
224
|
+
*/
|
|
225
|
+
const unrefStream = (stream) => {
|
|
226
|
+
if (typeof stream === "object" && stream !== null && "unref" in stream && typeof stream.unref === "function") stream.unref();
|
|
227
|
+
};
|
|
130
228
|
const SCRIPT_EXTENSIONS = new Set([
|
|
131
229
|
".js",
|
|
132
230
|
".mjs",
|
|
@@ -144,6 +242,33 @@ const RUNTIME_MISSING_MESSAGE = [
|
|
|
144
242
|
const BOOT_TIMEOUT_MS = 8e3;
|
|
145
243
|
const REQUEST_TIMEOUT_MS = 1e4;
|
|
146
244
|
const IDLE_SHUTDOWN_MS = 5e3;
|
|
245
|
+
/**
|
|
246
|
+
* Environment variables that inject a Node.js debugger/inspector bootloader
|
|
247
|
+
* into child processes (VS Code / Cursor "Auto Attach", `node --inspect`, etc.).
|
|
248
|
+
*
|
|
249
|
+
* These must never leak into the sandboxed Deno worker: the injected bootloader
|
|
250
|
+
* tries to read the environment, the Jess Deno sandbox denies `env` permission,
|
|
251
|
+
* and the worker never signals ready — producing a spurious
|
|
252
|
+
* "Timed out waiting for Deno worker startup" failure. Deno does not use any of
|
|
253
|
+
* these vars, so stripping them is safe.
|
|
254
|
+
*/
|
|
255
|
+
const DEBUG_ENV_KEY_RE = /^(NODE_OPTIONS|NODE_INSPECT|VSCODE_INSPECTOR_OPTIONS)/i;
|
|
256
|
+
const DEBUG_ENV_VALUE_RE = /js-debug|bootloader/i;
|
|
257
|
+
/**
|
|
258
|
+
* Returns a copy of `env` with Node debugger/inspector-attach variables removed,
|
|
259
|
+
* so a spawned Deno subprocess starts regardless of the parent's debug env.
|
|
260
|
+
* The Deno permission sandbox is untouched; this only stops leaking the debugger
|
|
261
|
+
* into it.
|
|
262
|
+
*/
|
|
263
|
+
const sanitizeSpawnEnv = (env = process.env) => {
|
|
264
|
+
const clean = {};
|
|
265
|
+
for (const [key, value] of Object.entries(env)) {
|
|
266
|
+
if (DEBUG_ENV_KEY_RE.test(key)) continue;
|
|
267
|
+
if (typeof value === "string" && DEBUG_ENV_VALUE_RE.test(value)) continue;
|
|
268
|
+
clean[key] = value;
|
|
269
|
+
}
|
|
270
|
+
return clean;
|
|
271
|
+
};
|
|
147
272
|
const isPathInside = (candidatePath, rootPath) => {
|
|
148
273
|
const rel = node_path.relative(rootPath, candidatePath);
|
|
149
274
|
return rel === "" || !rel.startsWith("..") && !node_path.isAbsolute(rel);
|
|
@@ -227,7 +352,10 @@ var JsPlugin = class JsPlugin extends _jesscss_core.AbstractPlugin {
|
|
|
227
352
|
this.idleTimer.unref?.();
|
|
228
353
|
}
|
|
229
354
|
ensureRuntimeAvailable() {
|
|
230
|
-
if ((0, node_child_process.spawnSync)(this.opts.denoCommand ?? "deno", ["--version"], {
|
|
355
|
+
if ((0, node_child_process.spawnSync)(this.opts.denoCommand ?? "deno", ["--version"], {
|
|
356
|
+
stdio: "ignore",
|
|
357
|
+
env: sanitizeSpawnEnv(process.env)
|
|
358
|
+
}).status !== 0) throw new Error(RUNTIME_MISSING_MESSAGE);
|
|
231
359
|
}
|
|
232
360
|
ensureRuntime() {
|
|
233
361
|
if (this.runtimeState.status === "ready") {
|
|
@@ -354,15 +482,15 @@ var JsPlugin = class JsPlugin extends _jesscss_core.AbstractPlugin {
|
|
|
354
482
|
"pipe"
|
|
355
483
|
],
|
|
356
484
|
env: {
|
|
357
|
-
...process.env,
|
|
485
|
+
...sanitizeSpawnEnv(process.env),
|
|
358
486
|
DENO_PERMISSION_BROKER_PATH: socketPath
|
|
359
487
|
}
|
|
360
488
|
});
|
|
361
489
|
this.worker = child;
|
|
362
490
|
child.unref();
|
|
363
|
-
child.stdin
|
|
364
|
-
child.stdout
|
|
365
|
-
child.stderr
|
|
491
|
+
unrefStream(child.stdin);
|
|
492
|
+
unrefStream(child.stdout);
|
|
493
|
+
unrefStream(child.stderr);
|
|
366
494
|
child.stdout.setEncoding("utf8");
|
|
367
495
|
child.stderr.setEncoding("utf8");
|
|
368
496
|
child.stdout.on("data", (chunk) => this.onWorkerStdout(chunk));
|
|
@@ -548,7 +676,7 @@ var JsPlugin = class JsPlugin extends _jesscss_core.AbstractPlugin {
|
|
|
548
676
|
* @deprecated Less `@plugin` is deprecated. Prefer `@-from` for
|
|
549
677
|
* ESM-style script imports or `@-use` for Sass-module-style namespace imports.
|
|
550
678
|
*/
|
|
551
|
-
async importLessPlugin(absoluteFilePath) {
|
|
679
|
+
async importLessPlugin(absoluteFilePath, options = null) {
|
|
552
680
|
const ext = node_path.extname(absoluteFilePath);
|
|
553
681
|
if (!SCRIPT_EXTENSIONS.has(ext)) throw new Error(`Plugin "${this.name}" cannot import Less plugin "${absoluteFilePath}"`);
|
|
554
682
|
this.assertAllowedPath(absoluteFilePath);
|
|
@@ -556,7 +684,8 @@ var JsPlugin = class JsPlugin extends _jesscss_core.AbstractPlugin {
|
|
|
556
684
|
const modulePath = node_path.resolve(absoluteFilePath);
|
|
557
685
|
const loadResult = await this.callWorker({
|
|
558
686
|
type: "loadLessPlugin",
|
|
559
|
-
modulePath
|
|
687
|
+
modulePath,
|
|
688
|
+
options
|
|
560
689
|
});
|
|
561
690
|
if (!loadResult.ok) throw new Error(loadResult.error);
|
|
562
691
|
const functions = {};
|
|
@@ -564,6 +693,7 @@ var JsPlugin = class JsPlugin extends _jesscss_core.AbstractPlugin {
|
|
|
564
693
|
const invokeResult = await this.callWorker({
|
|
565
694
|
type: "invokeLessPluginFunction",
|
|
566
695
|
modulePath,
|
|
696
|
+
options,
|
|
567
697
|
functionName,
|
|
568
698
|
args: encodeBridgeArgs(args)
|
|
569
699
|
});
|
|
@@ -572,6 +702,10 @@ var JsPlugin = class JsPlugin extends _jesscss_core.AbstractPlugin {
|
|
|
572
702
|
};
|
|
573
703
|
return { functions };
|
|
574
704
|
}
|
|
705
|
+
/** Context-facing executable-plugin capability used by the direct AST path. */
|
|
706
|
+
async importPlugin(absoluteFilePath, options = null) {
|
|
707
|
+
return this.importLessPlugin(absoluteFilePath, options);
|
|
708
|
+
}
|
|
575
709
|
};
|
|
576
710
|
/**
|
|
577
711
|
* Global flag set when @jesscss/plugin-js is loaded.
|
|
@@ -587,3 +721,4 @@ exports.JESS_PLUGIN_JS_GLOBAL = JESS_PLUGIN_JS_GLOBAL;
|
|
|
587
721
|
exports.JsPlugin = JsPlugin;
|
|
588
722
|
exports.__toESM = __toESM;
|
|
589
723
|
exports.default = jsPlugin;
|
|
724
|
+
exports.sanitizeSpawnEnv = sanitizeSpawnEnv;
|
package/lib/index.d.ts
CHANGED
|
@@ -14,6 +14,13 @@ export interface JsPluginOptions extends JavaScriptSandboxConfig {
|
|
|
14
14
|
*/
|
|
15
15
|
runtimeApi?: 'module' | 'less';
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Returns a copy of `env` with Node debugger/inspector-attach variables removed,
|
|
19
|
+
* so a spawned Deno subprocess starts regardless of the parent's debug env.
|
|
20
|
+
* The Deno permission sandbox is untouched; this only stops leaking the debugger
|
|
21
|
+
* into it.
|
|
22
|
+
*/
|
|
23
|
+
export declare const sanitizeSpawnEnv: (env?: NodeJS.ProcessEnv) => NodeJS.ProcessEnv;
|
|
17
24
|
export declare class JsPlugin extends AbstractPlugin {
|
|
18
25
|
opts: JsPluginOptions;
|
|
19
26
|
private static cleanupRegistered;
|
|
@@ -56,7 +63,11 @@ export declare class JsPlugin extends AbstractPlugin {
|
|
|
56
63
|
* @deprecated Less `@plugin` is deprecated. Prefer `@-from` for
|
|
57
64
|
* ESM-style script imports or `@-use` for Sass-module-style namespace imports.
|
|
58
65
|
*/
|
|
59
|
-
importLessPlugin(absoluteFilePath: string): Promise<{
|
|
66
|
+
importLessPlugin(absoluteFilePath: string, options?: string | null): Promise<{
|
|
67
|
+
functions: Record<string, (...args: unknown[]) => Promise<unknown>>;
|
|
68
|
+
}>;
|
|
69
|
+
/** Context-facing executable-plugin capability used by the direct AST path. */
|
|
70
|
+
importPlugin(absoluteFilePath: string, options?: string | null): Promise<{
|
|
60
71
|
functions: Record<string, (...args: unknown[]) => Promise<unknown>>;
|
|
61
72
|
}>;
|
|
62
73
|
}
|
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,cAAc,EACf,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,cAAc,EACf,MAAM,eAAe,CAAC;AAyBvB,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;AAmCD;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAC3B,MAAK,MAAM,CAAC,UAAwB,KACnC,MAAM,CAAC,UAYT,CAAC;AA0FF,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;IAW9B,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,EAAE,OAAO,GAAE,MAAM,GAAG,IAAW,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;IA+BjK,+EAA+E;IACzE,YAAY,CAAC,gBAAgB,EAAE,MAAM,EAAE,OAAO,GAAE,MAAM,GAAG,IAAW,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;CAG9J;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,103 +1,201 @@
|
|
|
1
|
-
import { AbstractPlugin
|
|
1
|
+
import { AbstractPlugin } 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
|
+
import { RGB, makeColorRgb, makeDimension, makeKeyword, makeList, makeQuoted } from "@jesscss/core/value";
|
|
7
8
|
//#region src/bridge.ts
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if (Object.values(ColorFormat).includes(value)) return value;
|
|
13
|
-
};
|
|
9
|
+
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
10
|
+
const isBridgeValue = (value) => isRecord(value) && value.__jessBridge === true && typeof value.kind === "string";
|
|
11
|
+
const isValueObj = (value) => isRecord(value) && typeof value.type === "string" && typeof value.bytes === "string";
|
|
12
|
+
const isDetached = (value) => isRecord(value) && value.type === "DetachedRuleset" && Array.isArray(value.rules);
|
|
14
13
|
function encodeBridgeChildValue(value) {
|
|
15
14
|
const encoded = encodeBridgeValue(value);
|
|
16
15
|
if (isBridgeValue(encoded)) return encoded;
|
|
17
16
|
return {
|
|
18
17
|
__jessBridge: true,
|
|
19
18
|
kind: "scalar",
|
|
20
|
-
value:
|
|
19
|
+
value: String(encoded)
|
|
21
20
|
};
|
|
22
21
|
}
|
|
22
|
+
function encodeFacadeValue(value) {
|
|
23
|
+
switch (value.type) {
|
|
24
|
+
case "Dimension": return typeof value.value === "number" ? {
|
|
25
|
+
__jessBridge: true,
|
|
26
|
+
kind: "dimension",
|
|
27
|
+
value: value.value,
|
|
28
|
+
unit: typeof value.unit === "string" ? value.unit : void 0
|
|
29
|
+
} : void 0;
|
|
30
|
+
case "Color": return Array.isArray(value.rgb) && value.rgb.length === 3 ? {
|
|
31
|
+
__jessBridge: true,
|
|
32
|
+
kind: "color",
|
|
33
|
+
rgb: [
|
|
34
|
+
value.rgb[0],
|
|
35
|
+
value.rgb[1],
|
|
36
|
+
value.rgb[2]
|
|
37
|
+
],
|
|
38
|
+
alpha: typeof value.alpha === "number" ? value.alpha : void 0
|
|
39
|
+
} : void 0;
|
|
40
|
+
case "Quoted": return typeof value.value === "string" ? {
|
|
41
|
+
__jessBridge: true,
|
|
42
|
+
kind: "quoted",
|
|
43
|
+
value: value.value,
|
|
44
|
+
quote: value.quote === "'" ? "'" : "\"",
|
|
45
|
+
escaped: value.escaped === true
|
|
46
|
+
} : void 0;
|
|
47
|
+
case "Anonymous":
|
|
48
|
+
case "Keyword": return typeof value.value === "string" ? {
|
|
49
|
+
__jessBridge: true,
|
|
50
|
+
kind: "anonymous",
|
|
51
|
+
value: value.value
|
|
52
|
+
} : void 0;
|
|
53
|
+
case "Expression": return Array.isArray(value.value) ? {
|
|
54
|
+
__jessBridge: true,
|
|
55
|
+
kind: "expression",
|
|
56
|
+
items: value.value.map(encodeBridgeChildValue)
|
|
57
|
+
} : void 0;
|
|
58
|
+
case "Value": return Array.isArray(value.value) ? {
|
|
59
|
+
__jessBridge: true,
|
|
60
|
+
kind: "list",
|
|
61
|
+
items: value.value.map(encodeBridgeChildValue),
|
|
62
|
+
separator: value.separator === "/" || value.separator === ";" ? value.separator : ","
|
|
63
|
+
} : void 0;
|
|
64
|
+
case "Mixin": {
|
|
65
|
+
const ruleset = value.ruleset;
|
|
66
|
+
if (!isRecord(ruleset) || !Array.isArray(ruleset.rules)) return;
|
|
67
|
+
const rules = [];
|
|
68
|
+
for (const rule of ruleset.rules) {
|
|
69
|
+
if (!isRecord(rule)) continue;
|
|
70
|
+
if (rule.type === "Declaration" && typeof rule.name === "string") rules.push({
|
|
71
|
+
name: rule.name,
|
|
72
|
+
value: encodeBridgeChildValue(rule.value)
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
__jessBridge: true,
|
|
77
|
+
kind: "mixin",
|
|
78
|
+
rules
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
default: return;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
23
84
|
function encodeBridgeValue(value) {
|
|
24
|
-
if (value
|
|
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 {
|
|
85
|
+
if (Array.isArray(value)) return {
|
|
38
86
|
__jessBridge: true,
|
|
39
|
-
kind: "
|
|
40
|
-
|
|
41
|
-
quote: value.quote,
|
|
42
|
-
escaped: value.escaped
|
|
87
|
+
kind: "expression",
|
|
88
|
+
items: value.map(encodeBridgeChildValue)
|
|
43
89
|
};
|
|
44
|
-
if (value
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
value:
|
|
65
|
-
|
|
66
|
-
|
|
90
|
+
if (isValueObj(value)) switch (value.type) {
|
|
91
|
+
case "Dimension": return {
|
|
92
|
+
__jessBridge: true,
|
|
93
|
+
kind: "dimension",
|
|
94
|
+
value: value.number,
|
|
95
|
+
unit: value.unit
|
|
96
|
+
};
|
|
97
|
+
case "Color": return {
|
|
98
|
+
__jessBridge: true,
|
|
99
|
+
kind: "color",
|
|
100
|
+
rgb: [
|
|
101
|
+
value.rgb[0],
|
|
102
|
+
value.rgb[1],
|
|
103
|
+
value.rgb[2]
|
|
104
|
+
],
|
|
105
|
+
alpha: value.alpha
|
|
106
|
+
};
|
|
107
|
+
case "Quoted": return {
|
|
108
|
+
__jessBridge: true,
|
|
109
|
+
kind: "quoted",
|
|
110
|
+
value: value.value,
|
|
111
|
+
quote: value.quote === "'" ? "'" : "\"",
|
|
112
|
+
escaped: value.escaped
|
|
113
|
+
};
|
|
114
|
+
case "List": return value.sep === "," || value.sep === "/" ? {
|
|
115
|
+
__jessBridge: true,
|
|
116
|
+
kind: "list",
|
|
117
|
+
items: value.value.map(encodeBridgeChildValue),
|
|
118
|
+
separator: value.sep
|
|
119
|
+
} : {
|
|
67
120
|
__jessBridge: true,
|
|
68
|
-
kind: "
|
|
69
|
-
|
|
121
|
+
kind: "anonymous",
|
|
122
|
+
value: value.bytes
|
|
123
|
+
};
|
|
124
|
+
case "Block": return {
|
|
125
|
+
__jessBridge: true,
|
|
126
|
+
kind: "anonymous",
|
|
127
|
+
value: value.bytes
|
|
128
|
+
};
|
|
129
|
+
default: return {
|
|
130
|
+
__jessBridge: true,
|
|
131
|
+
kind: "anonymous",
|
|
132
|
+
value: value.bytes
|
|
70
133
|
};
|
|
71
134
|
}
|
|
135
|
+
if (isDetached(value)) return {
|
|
136
|
+
__jessBridge: true,
|
|
137
|
+
kind: "mixin",
|
|
138
|
+
rules: value.rules.map((rule) => ({
|
|
139
|
+
name: rule.name,
|
|
140
|
+
value: encodeBridgeChildValue(rule.value)
|
|
141
|
+
}))
|
|
142
|
+
};
|
|
143
|
+
if (isRecord(value)) return encodeFacadeValue(value) ?? value;
|
|
72
144
|
return value;
|
|
73
145
|
}
|
|
74
|
-
function
|
|
75
|
-
if (!isBridgeValue(value)) return value;
|
|
146
|
+
function decodeValue(value) {
|
|
76
147
|
switch (value.kind) {
|
|
77
|
-
case "scalar": return
|
|
78
|
-
case "dimension": return
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
case "
|
|
83
|
-
|
|
84
|
-
|
|
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)));
|
|
148
|
+
case "scalar": return makeKeyword(String(value.value));
|
|
149
|
+
case "dimension": return makeDimension(value.value, value.unit ?? "");
|
|
150
|
+
case "color": return makeColorRgb(value.rgb, value.alpha ?? 1, RGB);
|
|
151
|
+
case "quoted": return makeQuoted(value.value, value.quote ?? "\"", value.escaped === true);
|
|
152
|
+
case "anonymous": return makeKeyword(value.value);
|
|
153
|
+
case "expression": return value.items.map(decodeValue);
|
|
154
|
+
case "list": return makeList(value.items.map(decodeValue), value.separator === ";" ? "," : value.separator);
|
|
155
|
+
case "mixin": return makeKeyword("");
|
|
94
156
|
}
|
|
95
157
|
}
|
|
158
|
+
function decodeMixin(value) {
|
|
159
|
+
const nil = {
|
|
160
|
+
type: "Nil",
|
|
161
|
+
value: ""
|
|
162
|
+
};
|
|
163
|
+
const mixin = {
|
|
164
|
+
type: "Mixin",
|
|
165
|
+
name: nil,
|
|
166
|
+
args: nil,
|
|
167
|
+
ruleset: { rules: value.rules.map((rule) => ({
|
|
168
|
+
type: "Declaration",
|
|
169
|
+
name: rule.name,
|
|
170
|
+
value: decodeValue(rule.value),
|
|
171
|
+
eval() {
|
|
172
|
+
return this;
|
|
173
|
+
}
|
|
174
|
+
})) },
|
|
175
|
+
eval() {
|
|
176
|
+
return mixin;
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
return mixin;
|
|
180
|
+
}
|
|
181
|
+
function decodeBridgeValue(value) {
|
|
182
|
+
if (!isBridgeValue(value)) return value;
|
|
183
|
+
return value.kind === "mixin" ? decodeMixin(value) : decodeValue(value);
|
|
184
|
+
}
|
|
96
185
|
function encodeBridgeArgs(args) {
|
|
97
186
|
return args.map(encodeBridgeValue);
|
|
98
187
|
}
|
|
99
188
|
//#endregion
|
|
100
189
|
//#region src/index.ts
|
|
190
|
+
/**
|
|
191
|
+
* Child-process stdio streams are typed as bare `Writable`/`Readable`, which do
|
|
192
|
+
* not declare `unref`, but the underlying pipe sockets expose it at runtime.
|
|
193
|
+
* The optional structural member lets any stream be passed without an unsafe
|
|
194
|
+
* cast; the runtime `?.` guard covers streams that genuinely lack the method.
|
|
195
|
+
*/
|
|
196
|
+
const unrefStream = (stream) => {
|
|
197
|
+
if (typeof stream === "object" && stream !== null && "unref" in stream && typeof stream.unref === "function") stream.unref();
|
|
198
|
+
};
|
|
101
199
|
const SCRIPT_EXTENSIONS = new Set([
|
|
102
200
|
".js",
|
|
103
201
|
".mjs",
|
|
@@ -115,6 +213,33 @@ const RUNTIME_MISSING_MESSAGE = [
|
|
|
115
213
|
const BOOT_TIMEOUT_MS = 8e3;
|
|
116
214
|
const REQUEST_TIMEOUT_MS = 1e4;
|
|
117
215
|
const IDLE_SHUTDOWN_MS = 5e3;
|
|
216
|
+
/**
|
|
217
|
+
* Environment variables that inject a Node.js debugger/inspector bootloader
|
|
218
|
+
* into child processes (VS Code / Cursor "Auto Attach", `node --inspect`, etc.).
|
|
219
|
+
*
|
|
220
|
+
* These must never leak into the sandboxed Deno worker: the injected bootloader
|
|
221
|
+
* tries to read the environment, the Jess Deno sandbox denies `env` permission,
|
|
222
|
+
* and the worker never signals ready — producing a spurious
|
|
223
|
+
* "Timed out waiting for Deno worker startup" failure. Deno does not use any of
|
|
224
|
+
* these vars, so stripping them is safe.
|
|
225
|
+
*/
|
|
226
|
+
const DEBUG_ENV_KEY_RE = /^(NODE_OPTIONS|NODE_INSPECT|VSCODE_INSPECTOR_OPTIONS)/i;
|
|
227
|
+
const DEBUG_ENV_VALUE_RE = /js-debug|bootloader/i;
|
|
228
|
+
/**
|
|
229
|
+
* Returns a copy of `env` with Node debugger/inspector-attach variables removed,
|
|
230
|
+
* so a spawned Deno subprocess starts regardless of the parent's debug env.
|
|
231
|
+
* The Deno permission sandbox is untouched; this only stops leaking the debugger
|
|
232
|
+
* into it.
|
|
233
|
+
*/
|
|
234
|
+
const sanitizeSpawnEnv = (env = process.env) => {
|
|
235
|
+
const clean = {};
|
|
236
|
+
for (const [key, value] of Object.entries(env)) {
|
|
237
|
+
if (DEBUG_ENV_KEY_RE.test(key)) continue;
|
|
238
|
+
if (typeof value === "string" && DEBUG_ENV_VALUE_RE.test(value)) continue;
|
|
239
|
+
clean[key] = value;
|
|
240
|
+
}
|
|
241
|
+
return clean;
|
|
242
|
+
};
|
|
118
243
|
const isPathInside = (candidatePath, rootPath) => {
|
|
119
244
|
const rel = path.relative(rootPath, candidatePath);
|
|
120
245
|
return rel === "" || !rel.startsWith("..") && !path.isAbsolute(rel);
|
|
@@ -198,7 +323,10 @@ var JsPlugin = class JsPlugin extends AbstractPlugin {
|
|
|
198
323
|
this.idleTimer.unref?.();
|
|
199
324
|
}
|
|
200
325
|
ensureRuntimeAvailable() {
|
|
201
|
-
if (spawnSync(this.opts.denoCommand ?? "deno", ["--version"], {
|
|
326
|
+
if (spawnSync(this.opts.denoCommand ?? "deno", ["--version"], {
|
|
327
|
+
stdio: "ignore",
|
|
328
|
+
env: sanitizeSpawnEnv(process.env)
|
|
329
|
+
}).status !== 0) throw new Error(RUNTIME_MISSING_MESSAGE);
|
|
202
330
|
}
|
|
203
331
|
ensureRuntime() {
|
|
204
332
|
if (this.runtimeState.status === "ready") {
|
|
@@ -325,15 +453,15 @@ var JsPlugin = class JsPlugin extends AbstractPlugin {
|
|
|
325
453
|
"pipe"
|
|
326
454
|
],
|
|
327
455
|
env: {
|
|
328
|
-
...process.env,
|
|
456
|
+
...sanitizeSpawnEnv(process.env),
|
|
329
457
|
DENO_PERMISSION_BROKER_PATH: socketPath
|
|
330
458
|
}
|
|
331
459
|
});
|
|
332
460
|
this.worker = child;
|
|
333
461
|
child.unref();
|
|
334
|
-
child.stdin
|
|
335
|
-
child.stdout
|
|
336
|
-
child.stderr
|
|
462
|
+
unrefStream(child.stdin);
|
|
463
|
+
unrefStream(child.stdout);
|
|
464
|
+
unrefStream(child.stderr);
|
|
337
465
|
child.stdout.setEncoding("utf8");
|
|
338
466
|
child.stderr.setEncoding("utf8");
|
|
339
467
|
child.stdout.on("data", (chunk) => this.onWorkerStdout(chunk));
|
|
@@ -519,7 +647,7 @@ var JsPlugin = class JsPlugin extends AbstractPlugin {
|
|
|
519
647
|
* @deprecated Less `@plugin` is deprecated. Prefer `@-from` for
|
|
520
648
|
* ESM-style script imports or `@-use` for Sass-module-style namespace imports.
|
|
521
649
|
*/
|
|
522
|
-
async importLessPlugin(absoluteFilePath) {
|
|
650
|
+
async importLessPlugin(absoluteFilePath, options = null) {
|
|
523
651
|
const ext = path.extname(absoluteFilePath);
|
|
524
652
|
if (!SCRIPT_EXTENSIONS.has(ext)) throw new Error(`Plugin "${this.name}" cannot import Less plugin "${absoluteFilePath}"`);
|
|
525
653
|
this.assertAllowedPath(absoluteFilePath);
|
|
@@ -527,7 +655,8 @@ var JsPlugin = class JsPlugin extends AbstractPlugin {
|
|
|
527
655
|
const modulePath = path.resolve(absoluteFilePath);
|
|
528
656
|
const loadResult = await this.callWorker({
|
|
529
657
|
type: "loadLessPlugin",
|
|
530
|
-
modulePath
|
|
658
|
+
modulePath,
|
|
659
|
+
options
|
|
531
660
|
});
|
|
532
661
|
if (!loadResult.ok) throw new Error(loadResult.error);
|
|
533
662
|
const functions = {};
|
|
@@ -535,6 +664,7 @@ var JsPlugin = class JsPlugin extends AbstractPlugin {
|
|
|
535
664
|
const invokeResult = await this.callWorker({
|
|
536
665
|
type: "invokeLessPluginFunction",
|
|
537
666
|
modulePath,
|
|
667
|
+
options,
|
|
538
668
|
functionName,
|
|
539
669
|
args: encodeBridgeArgs(args)
|
|
540
670
|
});
|
|
@@ -543,6 +673,10 @@ var JsPlugin = class JsPlugin extends AbstractPlugin {
|
|
|
543
673
|
};
|
|
544
674
|
return { functions };
|
|
545
675
|
}
|
|
676
|
+
/** Context-facing executable-plugin capability used by the direct AST path. */
|
|
677
|
+
async importPlugin(absoluteFilePath, options = null) {
|
|
678
|
+
return this.importLessPlugin(absoluteFilePath, options);
|
|
679
|
+
}
|
|
546
680
|
};
|
|
547
681
|
/**
|
|
548
682
|
* Global flag set when @jesscss/plugin-js is loaded.
|
|
@@ -554,4 +688,4 @@ const jsPlugin = ((opts) => {
|
|
|
554
688
|
return new JsPlugin(opts);
|
|
555
689
|
});
|
|
556
690
|
//#endregion
|
|
557
|
-
export { JESS_PLUGIN_JS_GLOBAL, JsPlugin, jsPlugin as default };
|
|
691
|
+
export { JESS_PLUGIN_JS_GLOBAL, JsPlugin, jsPlugin as default, sanitizeSpawnEnv };
|
package/lib/runtime-worker.cjs
CHANGED
|
@@ -78,8 +78,17 @@ var Declaration = class {
|
|
|
78
78
|
return `${this.name}: ${v}`;
|
|
79
79
|
}
|
|
80
80
|
};
|
|
81
|
-
var
|
|
82
|
-
type = "
|
|
81
|
+
var Nil = class {
|
|
82
|
+
type = "Nil";
|
|
83
|
+
value = "";
|
|
84
|
+
eval() {
|
|
85
|
+
return this;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
var Mixin = class {
|
|
89
|
+
type = "Mixin";
|
|
90
|
+
name = new Nil();
|
|
91
|
+
args = new Nil();
|
|
83
92
|
ruleset;
|
|
84
93
|
constructor(rules) {
|
|
85
94
|
this.ruleset = { rules };
|
|
@@ -115,10 +124,11 @@ const lessFacade = {
|
|
|
115
124
|
Anonymous,
|
|
116
125
|
Color,
|
|
117
126
|
Declaration,
|
|
118
|
-
DetachedRuleset,
|
|
119
127
|
Dimension,
|
|
120
128
|
Expression,
|
|
121
129
|
Keyword,
|
|
130
|
+
Mixin,
|
|
131
|
+
Nil,
|
|
122
132
|
Quoted,
|
|
123
133
|
Value
|
|
124
134
|
},
|
|
@@ -150,11 +160,10 @@ const decodeBridgeValue = (value) => {
|
|
|
150
160
|
case "dimension": return new Dimension(value.value, value.unit ?? "");
|
|
151
161
|
case "color": return new Color(value.rgb, value.alpha ?? 1);
|
|
152
162
|
case "quoted": return new Quoted(value.quote ?? "\"", value.value, value.escaped === true);
|
|
153
|
-
case "keyword": return new Keyword(value.value);
|
|
154
163
|
case "anonymous": return new Anonymous(value.value);
|
|
155
164
|
case "list": return new Value((value.items ?? []).map(decodeBridgeValue), value.separator ?? ",");
|
|
156
|
-
case "
|
|
157
|
-
case "
|
|
165
|
+
case "expression": return new Expression((value.items ?? []).map(decodeBridgeValue));
|
|
166
|
+
case "mixin": return new Mixin((value.rules ?? []).map((decl) => {
|
|
158
167
|
const decoded = decodeBridgeValue(decl.value);
|
|
159
168
|
const cssText = decoded?.toCSS ? decoded.toCSS() : String(decoded);
|
|
160
169
|
return new Declaration(decl.name, new Anonymous(cssText));
|
|
@@ -191,21 +200,29 @@ const encodeBridgeValue = (value) => {
|
|
|
191
200
|
quote: value.quote,
|
|
192
201
|
escaped: value.escaped
|
|
193
202
|
};
|
|
194
|
-
if (value instanceof Keyword) return {
|
|
203
|
+
if (value instanceof Keyword || value instanceof Anonymous) return {
|
|
195
204
|
__jessBridge: true,
|
|
196
|
-
kind: "
|
|
205
|
+
kind: "anonymous",
|
|
197
206
|
value: String(value.value)
|
|
198
207
|
};
|
|
199
|
-
if (value instanceof
|
|
208
|
+
if (value instanceof Expression) return {
|
|
200
209
|
__jessBridge: true,
|
|
201
|
-
kind: "
|
|
202
|
-
|
|
210
|
+
kind: "expression",
|
|
211
|
+
items: value.value.map(encodeBridgeChildValue)
|
|
203
212
|
};
|
|
204
|
-
if (value instanceof Value
|
|
213
|
+
if (value instanceof Value) return {
|
|
205
214
|
__jessBridge: true,
|
|
206
|
-
kind:
|
|
215
|
+
kind: "list",
|
|
207
216
|
items: value.value.map(encodeBridgeChildValue),
|
|
208
|
-
separator: value
|
|
217
|
+
separator: value.separator === "/" || value.separator === ";" ? value.separator : ","
|
|
218
|
+
};
|
|
219
|
+
if (value instanceof Mixin) return {
|
|
220
|
+
__jessBridge: true,
|
|
221
|
+
kind: "mixin",
|
|
222
|
+
rules: value.ruleset.rules.filter((rule) => rule instanceof Declaration && typeof rule.name === "string").map((rule) => ({
|
|
223
|
+
name: rule.name,
|
|
224
|
+
value: encodeBridgeChildValue(rule.value)
|
|
225
|
+
}))
|
|
209
226
|
};
|
|
210
227
|
return value;
|
|
211
228
|
};
|
|
@@ -243,7 +260,7 @@ const loadModule = async (modulePath) => {
|
|
|
243
260
|
}
|
|
244
261
|
return exports;
|
|
245
262
|
};
|
|
246
|
-
const createLegacyLessPluginRuntime = (modulePath) => {
|
|
263
|
+
const createLegacyLessPluginRuntime = (modulePath, options) => {
|
|
247
264
|
const localFunctions = /* @__PURE__ */ new Map();
|
|
248
265
|
const functions = {
|
|
249
266
|
add(name, fn) {
|
|
@@ -280,7 +297,9 @@ const createLegacyLessPluginRuntime = (modulePath) => {
|
|
|
280
297
|
return plugin;
|
|
281
298
|
}
|
|
282
299
|
})() : plugin;
|
|
300
|
+
if (candidate && options != null && typeof candidate.setOptions === "function") candidate.setOptions(options);
|
|
283
301
|
if (candidate && typeof candidate.install === "function") candidate.install(less, manager, functions);
|
|
302
|
+
if (candidate && options != null && typeof candidate.setOptions === "function") candidate.setOptions(options);
|
|
284
303
|
};
|
|
285
304
|
const require = (specifier) => {
|
|
286
305
|
throw new Error(`Less @plugin require("${specifier}") is not supported in the Deno sandbox yet.`);
|
|
@@ -298,11 +317,13 @@ const createLegacyLessPluginRuntime = (modulePath) => {
|
|
|
298
317
|
fileInfo: { filename: modulePath }
|
|
299
318
|
};
|
|
300
319
|
};
|
|
301
|
-
const
|
|
302
|
-
|
|
320
|
+
const lessPluginCacheKey = (modulePath, options) => `${modulePath}\u0000${options ?? ""}`;
|
|
321
|
+
const loadLessPlugin = async (modulePath, options = null) => {
|
|
322
|
+
const cacheKey = lessPluginCacheKey(modulePath, options);
|
|
323
|
+
let functions = lessPluginFunctionCache.get(cacheKey);
|
|
303
324
|
if (functions) return Array.from(functions.keys());
|
|
304
325
|
const source = await Deno.readTextFile(modulePath);
|
|
305
|
-
const runtime = createLegacyLessPluginRuntime(modulePath);
|
|
326
|
+
const runtime = createLegacyLessPluginRuntime(modulePath, options);
|
|
306
327
|
const module = { exports: runtime.exports };
|
|
307
328
|
new Function("module", "exports", "require", "registerPlugin", "functions", "tree", "less", "fileInfo", "process", source)(module, module.exports, runtime.require, runtime.registerPlugin, runtime.functions, lessFacade.tree, {
|
|
308
329
|
...lessFacade,
|
|
@@ -311,14 +332,15 @@ const loadLessPlugin = async (modulePath) => {
|
|
|
311
332
|
const exported = module.exports?.default ?? module.exports;
|
|
312
333
|
if (typeof exported === "function" || exported && typeof exported.install === "function") runtime.registerPlugin(exported);
|
|
313
334
|
functions = runtime.localFunctions;
|
|
314
|
-
lessPluginFunctionCache.set(
|
|
335
|
+
lessPluginFunctionCache.set(cacheKey, functions);
|
|
315
336
|
return Array.from(functions.keys());
|
|
316
337
|
};
|
|
317
|
-
const invokeLessPluginFunction = async (modulePath, functionName, args) => {
|
|
318
|
-
|
|
338
|
+
const invokeLessPluginFunction = async (modulePath, functionName, args, options = null) => {
|
|
339
|
+
const cacheKey = lessPluginCacheKey(modulePath, options);
|
|
340
|
+
let functions = lessPluginFunctionCache.get(cacheKey);
|
|
319
341
|
if (!functions) {
|
|
320
|
-
await loadLessPlugin(modulePath);
|
|
321
|
-
functions = lessPluginFunctionCache.get(
|
|
342
|
+
await loadLessPlugin(modulePath, options);
|
|
343
|
+
functions = lessPluginFunctionCache.get(cacheKey);
|
|
322
344
|
}
|
|
323
345
|
const fn = functions?.get(String(functionName).toLowerCase());
|
|
324
346
|
if (typeof fn !== "function") throw new Error(`Less @plugin function "${functionName}" is not registered.`);
|
|
@@ -357,7 +379,7 @@ const handleRequest = async (req) => {
|
|
|
357
379
|
return;
|
|
358
380
|
}
|
|
359
381
|
if (req.type === "loadLessPlugin") {
|
|
360
|
-
const functions = await loadLessPlugin(req.modulePath);
|
|
382
|
+
const functions = await loadLessPlugin(req.modulePath, req.options ?? null);
|
|
361
383
|
send({
|
|
362
384
|
id: req.id,
|
|
363
385
|
ok: true,
|
|
@@ -366,7 +388,7 @@ const handleRequest = async (req) => {
|
|
|
366
388
|
return;
|
|
367
389
|
}
|
|
368
390
|
if (req.type === "invokeLessPluginFunction") {
|
|
369
|
-
const value = await invokeLessPluginFunction(req.modulePath, req.functionName, req.args ?? []);
|
|
391
|
+
const value = await invokeLessPluginFunction(req.modulePath, req.functionName, req.args ?? [], req.options ?? null);
|
|
370
392
|
send({
|
|
371
393
|
id: req.id,
|
|
372
394
|
ok: true,
|
package/lib/runtime-worker.js
CHANGED
|
@@ -76,8 +76,17 @@ var Declaration = class {
|
|
|
76
76
|
return `${this.name}: ${v}`;
|
|
77
77
|
}
|
|
78
78
|
};
|
|
79
|
-
var
|
|
80
|
-
type = "
|
|
79
|
+
var Nil = class {
|
|
80
|
+
type = "Nil";
|
|
81
|
+
value = "";
|
|
82
|
+
eval() {
|
|
83
|
+
return this;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
var Mixin = class {
|
|
87
|
+
type = "Mixin";
|
|
88
|
+
name = new Nil();
|
|
89
|
+
args = new Nil();
|
|
81
90
|
ruleset;
|
|
82
91
|
constructor(rules) {
|
|
83
92
|
this.ruleset = { rules };
|
|
@@ -113,10 +122,11 @@ const lessFacade = {
|
|
|
113
122
|
Anonymous,
|
|
114
123
|
Color,
|
|
115
124
|
Declaration,
|
|
116
|
-
DetachedRuleset,
|
|
117
125
|
Dimension,
|
|
118
126
|
Expression,
|
|
119
127
|
Keyword,
|
|
128
|
+
Mixin,
|
|
129
|
+
Nil,
|
|
120
130
|
Quoted,
|
|
121
131
|
Value
|
|
122
132
|
},
|
|
@@ -148,11 +158,10 @@ const decodeBridgeValue = (value) => {
|
|
|
148
158
|
case "dimension": return new Dimension(value.value, value.unit ?? "");
|
|
149
159
|
case "color": return new Color(value.rgb, value.alpha ?? 1);
|
|
150
160
|
case "quoted": return new Quoted(value.quote ?? "\"", value.value, value.escaped === true);
|
|
151
|
-
case "keyword": return new Keyword(value.value);
|
|
152
161
|
case "anonymous": return new Anonymous(value.value);
|
|
153
162
|
case "list": return new Value((value.items ?? []).map(decodeBridgeValue), value.separator ?? ",");
|
|
154
|
-
case "
|
|
155
|
-
case "
|
|
163
|
+
case "expression": return new Expression((value.items ?? []).map(decodeBridgeValue));
|
|
164
|
+
case "mixin": return new Mixin((value.rules ?? []).map((decl) => {
|
|
156
165
|
const decoded = decodeBridgeValue(decl.value);
|
|
157
166
|
const cssText = decoded?.toCSS ? decoded.toCSS() : String(decoded);
|
|
158
167
|
return new Declaration(decl.name, new Anonymous(cssText));
|
|
@@ -189,21 +198,29 @@ const encodeBridgeValue = (value) => {
|
|
|
189
198
|
quote: value.quote,
|
|
190
199
|
escaped: value.escaped
|
|
191
200
|
};
|
|
192
|
-
if (value instanceof Keyword) return {
|
|
201
|
+
if (value instanceof Keyword || value instanceof Anonymous) return {
|
|
193
202
|
__jessBridge: true,
|
|
194
|
-
kind: "
|
|
203
|
+
kind: "anonymous",
|
|
195
204
|
value: String(value.value)
|
|
196
205
|
};
|
|
197
|
-
if (value instanceof
|
|
206
|
+
if (value instanceof Expression) return {
|
|
198
207
|
__jessBridge: true,
|
|
199
|
-
kind: "
|
|
200
|
-
|
|
208
|
+
kind: "expression",
|
|
209
|
+
items: value.value.map(encodeBridgeChildValue)
|
|
201
210
|
};
|
|
202
|
-
if (value instanceof Value
|
|
211
|
+
if (value instanceof Value) return {
|
|
203
212
|
__jessBridge: true,
|
|
204
|
-
kind:
|
|
213
|
+
kind: "list",
|
|
205
214
|
items: value.value.map(encodeBridgeChildValue),
|
|
206
|
-
separator: value
|
|
215
|
+
separator: value.separator === "/" || value.separator === ";" ? value.separator : ","
|
|
216
|
+
};
|
|
217
|
+
if (value instanceof Mixin) return {
|
|
218
|
+
__jessBridge: true,
|
|
219
|
+
kind: "mixin",
|
|
220
|
+
rules: value.ruleset.rules.filter((rule) => rule instanceof Declaration && typeof rule.name === "string").map((rule) => ({
|
|
221
|
+
name: rule.name,
|
|
222
|
+
value: encodeBridgeChildValue(rule.value)
|
|
223
|
+
}))
|
|
207
224
|
};
|
|
208
225
|
return value;
|
|
209
226
|
};
|
|
@@ -241,7 +258,7 @@ const loadModule = async (modulePath) => {
|
|
|
241
258
|
}
|
|
242
259
|
return exports;
|
|
243
260
|
};
|
|
244
|
-
const createLegacyLessPluginRuntime = (modulePath) => {
|
|
261
|
+
const createLegacyLessPluginRuntime = (modulePath, options) => {
|
|
245
262
|
const localFunctions = /* @__PURE__ */ new Map();
|
|
246
263
|
const functions = {
|
|
247
264
|
add(name, fn) {
|
|
@@ -278,7 +295,9 @@ const createLegacyLessPluginRuntime = (modulePath) => {
|
|
|
278
295
|
return plugin;
|
|
279
296
|
}
|
|
280
297
|
})() : plugin;
|
|
298
|
+
if (candidate && options != null && typeof candidate.setOptions === "function") candidate.setOptions(options);
|
|
281
299
|
if (candidate && typeof candidate.install === "function") candidate.install(less, manager, functions);
|
|
300
|
+
if (candidate && options != null && typeof candidate.setOptions === "function") candidate.setOptions(options);
|
|
282
301
|
};
|
|
283
302
|
const require = (specifier) => {
|
|
284
303
|
throw new Error(`Less @plugin require("${specifier}") is not supported in the Deno sandbox yet.`);
|
|
@@ -296,11 +315,13 @@ const createLegacyLessPluginRuntime = (modulePath) => {
|
|
|
296
315
|
fileInfo: { filename: modulePath }
|
|
297
316
|
};
|
|
298
317
|
};
|
|
299
|
-
const
|
|
300
|
-
|
|
318
|
+
const lessPluginCacheKey = (modulePath, options) => `${modulePath}\u0000${options ?? ""}`;
|
|
319
|
+
const loadLessPlugin = async (modulePath, options = null) => {
|
|
320
|
+
const cacheKey = lessPluginCacheKey(modulePath, options);
|
|
321
|
+
let functions = lessPluginFunctionCache.get(cacheKey);
|
|
301
322
|
if (functions) return Array.from(functions.keys());
|
|
302
323
|
const source = await Deno.readTextFile(modulePath);
|
|
303
|
-
const runtime = createLegacyLessPluginRuntime(modulePath);
|
|
324
|
+
const runtime = createLegacyLessPluginRuntime(modulePath, options);
|
|
304
325
|
const module = { exports: runtime.exports };
|
|
305
326
|
new Function("module", "exports", "require", "registerPlugin", "functions", "tree", "less", "fileInfo", "process", source)(module, module.exports, runtime.require, runtime.registerPlugin, runtime.functions, lessFacade.tree, {
|
|
306
327
|
...lessFacade,
|
|
@@ -309,14 +330,15 @@ const loadLessPlugin = async (modulePath) => {
|
|
|
309
330
|
const exported = module.exports?.default ?? module.exports;
|
|
310
331
|
if (typeof exported === "function" || exported && typeof exported.install === "function") runtime.registerPlugin(exported);
|
|
311
332
|
functions = runtime.localFunctions;
|
|
312
|
-
lessPluginFunctionCache.set(
|
|
333
|
+
lessPluginFunctionCache.set(cacheKey, functions);
|
|
313
334
|
return Array.from(functions.keys());
|
|
314
335
|
};
|
|
315
|
-
const invokeLessPluginFunction = async (modulePath, functionName, args) => {
|
|
316
|
-
|
|
336
|
+
const invokeLessPluginFunction = async (modulePath, functionName, args, options = null) => {
|
|
337
|
+
const cacheKey = lessPluginCacheKey(modulePath, options);
|
|
338
|
+
let functions = lessPluginFunctionCache.get(cacheKey);
|
|
317
339
|
if (!functions) {
|
|
318
|
-
await loadLessPlugin(modulePath);
|
|
319
|
-
functions = lessPluginFunctionCache.get(
|
|
340
|
+
await loadLessPlugin(modulePath, options);
|
|
341
|
+
functions = lessPluginFunctionCache.get(cacheKey);
|
|
320
342
|
}
|
|
321
343
|
const fn = functions?.get(String(functionName).toLowerCase());
|
|
322
344
|
if (typeof fn !== "function") throw new Error(`Less @plugin function "${functionName}" is not registered.`);
|
|
@@ -355,7 +377,7 @@ const handleRequest = async (req) => {
|
|
|
355
377
|
return;
|
|
356
378
|
}
|
|
357
379
|
if (req.type === "loadLessPlugin") {
|
|
358
|
-
const functions = await loadLessPlugin(req.modulePath);
|
|
380
|
+
const functions = await loadLessPlugin(req.modulePath, req.options ?? null);
|
|
359
381
|
send({
|
|
360
382
|
id: req.id,
|
|
361
383
|
ok: true,
|
|
@@ -364,7 +386,7 @@ const handleRequest = async (req) => {
|
|
|
364
386
|
return;
|
|
365
387
|
}
|
|
366
388
|
if (req.type === "invokeLessPluginFunction") {
|
|
367
|
-
const value = await invokeLessPluginFunction(req.modulePath, req.functionName, req.args ?? []);
|
|
389
|
+
const value = await invokeLessPluginFunction(req.modulePath, req.functionName, req.args ?? [], req.options ?? null);
|
|
368
390
|
send({
|
|
369
391
|
id: req.id,
|
|
370
392
|
ok: true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jesscss/plugin-js",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.9",
|
|
4
4
|
"description": "Jess plugin for JavaScript/TypeScript module imports with Deno runtime checks",
|
|
5
5
|
"main": "lib/index.cjs",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"deno": "^2.6.8",
|
|
21
|
-
"@jesscss/core": "2.0.0-alpha.
|
|
21
|
+
"@jesscss/core": "2.0.0-alpha.9"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@types/node": "^20.0.0",
|