@jesscss/plugin-js 2.0.0-alpha.1 → 2.0.0-alpha.11
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 +46 -0
- package/lib/bridge.d.ts.map +1 -0
- package/lib/index.cjs +817 -0
- package/lib/index.d.ts +107 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +766 -425
- package/lib/runtime-worker.cjs +892 -0
- package/lib/runtime-worker.js +879 -81
- package/package.json +14 -8
- package/lib/index.js.map +0 -1
- package/lib/runtime-worker.js.map +0 -1
package/lib/runtime-worker.js
CHANGED
|
@@ -1,93 +1,891 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { pathToFileURL } from "node:url";
|
|
2
|
+
//#region src/runtime-worker.ts
|
|
3
3
|
const encoder = new TextEncoder();
|
|
4
4
|
const decoder = new TextDecoder();
|
|
5
|
-
const moduleCache = new Map();
|
|
5
|
+
const moduleCache = /* @__PURE__ */ new Map();
|
|
6
|
+
const lessPluginFunctionCache = /* @__PURE__ */ new Map();
|
|
7
|
+
const runtimeApi = Deno.args.includes("--runtime-api=less") ? "less" : "module";
|
|
8
|
+
/**
|
|
9
|
+
* A deliberate, attributable failure for a `less.tree` member the Jess
|
|
10
|
+
* compatibility shim cannot honour. Structural Less 4 nodes (rulesets,
|
|
11
|
+
* selectors, at-rules, imports, extends) have no representation on the value
|
|
12
|
+
* bridge that carries plugin results back to the engine, so exposing a
|
|
13
|
+
* look-alike constructor would silently produce wrong CSS. Throwing names the
|
|
14
|
+
* member and points at the supported surface instead.
|
|
15
|
+
*/
|
|
16
|
+
var UnsupportedTreeNodeError = class extends Error {
|
|
17
|
+
constructor(name, reason) {
|
|
18
|
+
super(`Less @plugin: "tree.${name}" is not supported by the Jess less-compat shim.\n${reason}\nSupported members: Node, Anonymous, Keyword, Quoted, Dimension, Unit, Color, Expression, Value, Declaration, Variable, Property, Operation, Paren, Negative, Call, URL, Comment, Assignment, UnicodeDescriptor, Ruleset, DetachedRuleset, Mixin, Nil.`);
|
|
19
|
+
this.name = "UnsupportedTreeNodeError";
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* The Less 4 `Node` base. `find` is the member bootstrap-era plugins reach for
|
|
24
|
+
* through `tree.Variable.prototype.find(frames, cb)`; it is defined here (and
|
|
25
|
+
* inherited) exactly as less.js defines it, so the prototype lookup that those
|
|
26
|
+
* plugins perform resolves to the real helper rather than `undefined`.
|
|
27
|
+
*/
|
|
28
|
+
var Node = class {
|
|
29
|
+
eval() {
|
|
30
|
+
return this;
|
|
31
|
+
}
|
|
32
|
+
find(obj, fun) {
|
|
33
|
+
for (let index = 0, result; index < obj.length; index++) {
|
|
34
|
+
result = fun.call(obj, obj[index]);
|
|
35
|
+
if (result) return result;
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
toCSS() {
|
|
40
|
+
return String(this.value ?? "");
|
|
41
|
+
}
|
|
42
|
+
toString() {
|
|
43
|
+
return this.toCSS();
|
|
44
|
+
}
|
|
45
|
+
valueOf() {
|
|
46
|
+
return this.value;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Less's unit model, reduced to the single fact plugin authors read or pass on:
|
|
51
|
+
* the unit string. `Dimension` accepts either a plain string or a `Unit`, which
|
|
52
|
+
* is how `new tree.Dimension(next.value - 0.02, next.unit)` keeps its unit.
|
|
53
|
+
*/
|
|
54
|
+
var Unit = class Unit extends Node {
|
|
55
|
+
type = "Unit";
|
|
56
|
+
constructor(numerator) {
|
|
57
|
+
super();
|
|
58
|
+
this.numerator = typeof numerator === "string" ? numerator ? [numerator] : [] : Array.isArray(numerator) ? numerator.slice() : [];
|
|
59
|
+
this.denominator = [];
|
|
60
|
+
}
|
|
61
|
+
clone() {
|
|
62
|
+
return new Unit(this.numerator);
|
|
63
|
+
}
|
|
64
|
+
isEmpty() {
|
|
65
|
+
return this.numerator.length === 0 && this.denominator.length === 0;
|
|
66
|
+
}
|
|
67
|
+
toString() {
|
|
68
|
+
return this.numerator.join("*");
|
|
69
|
+
}
|
|
70
|
+
toCSS() {
|
|
71
|
+
return this.toString();
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const unitOf = (unit) => {
|
|
75
|
+
if (unit instanceof Unit) return unit.clone();
|
|
76
|
+
return new Unit(unit == null ? "" : String(unit));
|
|
77
|
+
};
|
|
78
|
+
var Dimension = class Dimension extends Node {
|
|
79
|
+
type = "Dimension";
|
|
80
|
+
/**
|
|
81
|
+
* Mirrors less.js: the numeric part is `parseFloat`d off `value` and the unit
|
|
82
|
+
* comes only from the second argument. `new tree.Dimension('25%')` is
|
|
83
|
+
* therefore the unitless `25` — the same value less.js produces — not `25%`.
|
|
84
|
+
*/
|
|
85
|
+
constructor(value, unit) {
|
|
86
|
+
super();
|
|
87
|
+
this.value = parseFloat(value);
|
|
88
|
+
if (Number.isNaN(this.value)) throw new Error(`Less @plugin: tree.Dimension received a non-numeric value (${JSON.stringify(value)}).`);
|
|
89
|
+
this._unit = unitOf(unit);
|
|
90
|
+
}
|
|
91
|
+
get unit() {
|
|
92
|
+
return this._unit.toString();
|
|
93
|
+
}
|
|
94
|
+
set unit(next) {
|
|
95
|
+
this._unit = unitOf(next);
|
|
96
|
+
}
|
|
97
|
+
valueOf() {
|
|
98
|
+
return this.unit ? `${this.value}${this.unit}` : this.value;
|
|
99
|
+
}
|
|
100
|
+
toString() {
|
|
101
|
+
return String(this.valueOf());
|
|
102
|
+
}
|
|
103
|
+
toCSS() {
|
|
104
|
+
return `${this.value}${this.unit}`;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* less.js `Dimension.compare`: numbers are comparable when their units match
|
|
108
|
+
* or when either side is unitless. Anything else is `undefined`, which is the
|
|
109
|
+
* signal bootstrap's `valid-calc` plugin uses to emit a `calc()` instead.
|
|
110
|
+
*/
|
|
111
|
+
compare(other) {
|
|
112
|
+
if (!(other instanceof Dimension)) return;
|
|
113
|
+
const a = this.unit;
|
|
114
|
+
const b = other.unit;
|
|
115
|
+
if (a !== b && a !== "" && b !== "") return;
|
|
116
|
+
if (other.value > this.value) return -1;
|
|
117
|
+
return other.value < this.value ? 1 : 0;
|
|
118
|
+
}
|
|
119
|
+
operate(context, op, other) {
|
|
120
|
+
if (!(other instanceof Dimension)) throw new Error(`Less @plugin: tree.Dimension.operate("${op}") needs a Dimension operand.`);
|
|
121
|
+
const a = this.unit;
|
|
122
|
+
const b = other.unit;
|
|
123
|
+
if ((op === "+" || op === "-") && a !== b && a !== "" && b !== "") throw new Error(`Less @plugin: incompatible units "${a}" and "${b}" in a "${op}" operation.`);
|
|
124
|
+
const value = op === "+" ? this.value + other.value : op === "-" ? this.value - other.value : op === "*" ? this.value * other.value : op === "/" ? this.value / other.value : void 0;
|
|
125
|
+
if (value === void 0) throw new Error(`Less @plugin: tree.Dimension.operate does not implement "${op}".`);
|
|
126
|
+
return new Dimension(value, a || b);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
const clampByte = (n) => Math.min(255, Math.max(0, Math.round(n)));
|
|
130
|
+
const hexPair = (n) => clampByte(n).toString(16).padStart(2, "0");
|
|
131
|
+
var Color = class extends Node {
|
|
132
|
+
type = "Color";
|
|
133
|
+
/**
|
|
134
|
+
* less.js accepts an RGB triple OR a hex string WITHOUT the leading `#`
|
|
135
|
+
* (`new tree.Color(someColor.toCSS().substr(1))`), which is precisely the
|
|
136
|
+
* form bootstrap's `theme-color-level` plugin round-trips through.
|
|
137
|
+
*/
|
|
138
|
+
constructor(rgb, alpha = 1) {
|
|
139
|
+
super();
|
|
140
|
+
if (Array.isArray(rgb)) {
|
|
141
|
+
this.rgb = rgb.slice(0, 3).map(clampByte);
|
|
142
|
+
this.alpha = typeof alpha === "number" ? alpha : 1;
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const text = String(rgb).replace(/^#/, "");
|
|
146
|
+
const expanded = text.length >= 6 ? [
|
|
147
|
+
text.slice(0, 2),
|
|
148
|
+
text.slice(2, 4),
|
|
149
|
+
text.slice(4, 6),
|
|
150
|
+
text.slice(6, 8)
|
|
151
|
+
] : [
|
|
152
|
+
text[0] + text[0],
|
|
153
|
+
text[1] + text[1],
|
|
154
|
+
text[2] + text[2],
|
|
155
|
+
text[3] ? text[3] + text[3] : void 0
|
|
156
|
+
];
|
|
157
|
+
const channels = expanded.slice(0, 3).map((pair) => parseInt(pair, 16));
|
|
158
|
+
if (channels.some(Number.isNaN)) throw new Error(`Less @plugin: tree.Color received an unparsable hex value (${JSON.stringify(rgb)}).`);
|
|
159
|
+
this.rgb = channels;
|
|
160
|
+
this.alpha = expanded[3] === void 0 ? typeof alpha === "number" ? alpha : 1 : parseInt(expanded[3], 16) / 255;
|
|
161
|
+
}
|
|
162
|
+
get value() {
|
|
163
|
+
return this.toCSS();
|
|
164
|
+
}
|
|
165
|
+
/** less.js renders an opaque colour as hex and a translucent one as `rgba()`. */
|
|
166
|
+
toCSS() {
|
|
167
|
+
const [r, g, b] = this.rgb;
|
|
168
|
+
return this.alpha === 1 ? `#${hexPair(r)}${hexPair(g)}${hexPair(b)}` : `rgba(${clampByte(r)}, ${clampByte(g)}, ${clampByte(b)}, ${this.alpha})`;
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
var Quoted = class extends Node {
|
|
172
|
+
type = "Quoted";
|
|
173
|
+
constructor(quote, value, escaped = false) {
|
|
174
|
+
super();
|
|
175
|
+
this.quote = quote === void 0 ? "\"" : String(quote);
|
|
176
|
+
this.value = value === void 0 || value === null ? "" : String(value);
|
|
177
|
+
this.escaped = escaped;
|
|
178
|
+
}
|
|
179
|
+
toCSS() {
|
|
180
|
+
return `${this.escaped ? "~" : ""}${this.quote}${this.value}${this.quote}`;
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
var Anonymous = class extends Node {
|
|
184
|
+
type = "Anonymous";
|
|
185
|
+
constructor(value) {
|
|
186
|
+
super();
|
|
187
|
+
this.value = value;
|
|
188
|
+
}
|
|
189
|
+
toCSS() {
|
|
190
|
+
return String(this.value);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
var Keyword = class extends Anonymous {
|
|
194
|
+
type = "Keyword";
|
|
195
|
+
};
|
|
196
|
+
var Comment = class extends Anonymous {
|
|
197
|
+
type = "Comment";
|
|
198
|
+
};
|
|
199
|
+
var UnicodeDescriptor = class extends Anonymous {
|
|
200
|
+
type = "UnicodeDescriptor";
|
|
201
|
+
};
|
|
202
|
+
var Property = class extends Node {
|
|
203
|
+
type = "Property";
|
|
204
|
+
constructor(name) {
|
|
205
|
+
super();
|
|
206
|
+
this.name = name;
|
|
207
|
+
this.value = name;
|
|
208
|
+
}
|
|
209
|
+
toCSS() {
|
|
210
|
+
return String(this.name);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
/**
|
|
214
|
+
* `tree.Variable` exists so plugins can (a) reach `Variable.prototype.find` —
|
|
215
|
+
* the `Node` helper every bootstrap-era scope lookup goes through — and (b)
|
|
216
|
+
* name a variable for the shim to resolve against the live evaluation frames.
|
|
217
|
+
*/
|
|
218
|
+
var Variable = class extends Node {
|
|
219
|
+
type = "Variable";
|
|
220
|
+
constructor(name, index, currentFileInfo) {
|
|
221
|
+
super();
|
|
222
|
+
this.name = name;
|
|
223
|
+
this.index = index;
|
|
224
|
+
this.currentFileInfo = currentFileInfo;
|
|
225
|
+
}
|
|
226
|
+
eval(context) {
|
|
227
|
+
const frames = context?.frames ?? [];
|
|
228
|
+
for (const frame of frames) {
|
|
229
|
+
const hit = frame.variable?.(this.name);
|
|
230
|
+
if (hit?.value !== void 0) return hit.value;
|
|
231
|
+
}
|
|
232
|
+
throw new Error(`Less @plugin: variable "${this.name}" is undefined.`);
|
|
233
|
+
}
|
|
234
|
+
toCSS() {
|
|
235
|
+
return String(this.name);
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
var Declaration = class extends Node {
|
|
239
|
+
type = "Declaration";
|
|
240
|
+
constructor(name, value, important = "") {
|
|
241
|
+
super();
|
|
242
|
+
this.name = name;
|
|
243
|
+
this.value = value;
|
|
244
|
+
this.important = important;
|
|
245
|
+
}
|
|
246
|
+
eval() {
|
|
247
|
+
return this;
|
|
248
|
+
}
|
|
249
|
+
toCSS() {
|
|
250
|
+
const v = this.value?.toCSS ? this.value.toCSS() : String(this.value);
|
|
251
|
+
return `${this.name}: ${v}${this.important}`;
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
var Nil = class extends Node {
|
|
255
|
+
type = "Nil";
|
|
256
|
+
value = "";
|
|
257
|
+
};
|
|
258
|
+
/**
|
|
259
|
+
* A read-only view of a declaration list. Less plugins reach `ruleset.rules`
|
|
260
|
+
* (and `instanceof tree.Declaration` on each entry); nothing on the value
|
|
261
|
+
* bridge can express a ruleset's selectors or nesting, so the shim exposes the
|
|
262
|
+
* declaration list only and refuses anything that would need more.
|
|
263
|
+
*/
|
|
264
|
+
var Ruleset = class extends Node {
|
|
265
|
+
type = "Ruleset";
|
|
266
|
+
constructor(selectors, rules) {
|
|
267
|
+
super();
|
|
268
|
+
if (selectors !== void 0 && selectors !== null && (!Array.isArray(selectors) || selectors.length > 0)) throw new UnsupportedTreeNodeError("Ruleset", "A selector-bearing ruleset cannot cross the plugin value boundary; only an anonymous declaration list can.");
|
|
269
|
+
this.selectors = [];
|
|
270
|
+
this.rules = Array.isArray(rules) ? rules : [];
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
var DetachedRuleset = class extends Node {
|
|
274
|
+
type = "DetachedRuleset";
|
|
275
|
+
constructor(ruleset) {
|
|
276
|
+
super();
|
|
277
|
+
this.ruleset = ruleset ?? new Ruleset(null, []);
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
var Mixin = class extends Node {
|
|
281
|
+
type = "Mixin";
|
|
282
|
+
name = new Nil();
|
|
283
|
+
args = new Nil();
|
|
284
|
+
ruleset;
|
|
285
|
+
constructor(rules) {
|
|
286
|
+
super();
|
|
287
|
+
this.ruleset = { rules };
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
var Value = class extends Node {
|
|
291
|
+
type = "Value";
|
|
292
|
+
constructor(value, separator = ",") {
|
|
293
|
+
super();
|
|
294
|
+
this.value = value;
|
|
295
|
+
this.separator = separator;
|
|
296
|
+
}
|
|
297
|
+
toCSS() {
|
|
298
|
+
const sep = this.separator === " " ? " " : `${this.separator} `;
|
|
299
|
+
return this.value.map((item) => item?.toCSS ? item.toCSS() : String(item)).join(sep);
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
var Expression = class extends Value {
|
|
303
|
+
type = "Expression";
|
|
304
|
+
constructor(value) {
|
|
305
|
+
super(value, " ");
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
var Paren = class extends Node {
|
|
309
|
+
type = "Paren";
|
|
310
|
+
constructor(value) {
|
|
311
|
+
super();
|
|
312
|
+
this.value = value;
|
|
313
|
+
}
|
|
314
|
+
toCSS() {
|
|
315
|
+
return `(${this.value?.toCSS ? this.value.toCSS() : String(this.value)})`;
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
var Negative = class extends Node {
|
|
319
|
+
type = "Negative";
|
|
320
|
+
constructor(value) {
|
|
321
|
+
super();
|
|
322
|
+
this.value = value;
|
|
323
|
+
}
|
|
324
|
+
toCSS() {
|
|
325
|
+
return `-${this.value?.toCSS ? this.value.toCSS() : String(this.value)}`;
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
const cssOf = (item) => item?.toCSS ? item.toCSS() : String(item);
|
|
329
|
+
var Operation = class extends Node {
|
|
330
|
+
type = "Operation";
|
|
331
|
+
constructor(op, operands) {
|
|
332
|
+
super();
|
|
333
|
+
this.op = String(op).trim();
|
|
334
|
+
this.operands = operands ?? [];
|
|
335
|
+
}
|
|
336
|
+
eval(context) {
|
|
337
|
+
const [left, right] = this.operands;
|
|
338
|
+
if (left?.operate) return left.operate(context, this.op, right);
|
|
339
|
+
throw new Error(`Less @plugin: tree.Operation cannot evaluate "${this.op}" on this operand.`);
|
|
340
|
+
}
|
|
341
|
+
toCSS() {
|
|
342
|
+
return this.operands.map(cssOf).join(` ${this.op} `);
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
var Call = class extends Node {
|
|
346
|
+
type = "Call";
|
|
347
|
+
constructor(name, args) {
|
|
348
|
+
super();
|
|
349
|
+
this.name = name;
|
|
350
|
+
this.args = args ?? [];
|
|
351
|
+
}
|
|
352
|
+
toCSS() {
|
|
353
|
+
return `${this.name}(${this.args.map(cssOf).join(", ")})`;
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
var URL = class extends Node {
|
|
357
|
+
type = "Url";
|
|
358
|
+
constructor(value) {
|
|
359
|
+
super();
|
|
360
|
+
this.value = value;
|
|
361
|
+
}
|
|
362
|
+
toCSS() {
|
|
363
|
+
return `url(${cssOf(this.value)})`;
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
var Assignment = class extends Node {
|
|
367
|
+
type = "Assignment";
|
|
368
|
+
constructor(key, value) {
|
|
369
|
+
super();
|
|
370
|
+
this.key = key;
|
|
371
|
+
this.value = value;
|
|
372
|
+
}
|
|
373
|
+
toCSS() {
|
|
374
|
+
return `${this.key}=${cssOf(this.value)}`;
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
/**
|
|
378
|
+
* Structural Less 4 nodes with no value-bridge representation. Each is exposed
|
|
379
|
+
* as a constructor that fails loudly and names itself, so a 4.x plugin reaching
|
|
380
|
+
* for one gets an attributable error instead of a silently wrong value.
|
|
381
|
+
*/
|
|
382
|
+
const UNSUPPORTED_TREE_NODES = {
|
|
383
|
+
AtRule: "At-rules are statements, not values; a plugin function can only return a value.",
|
|
384
|
+
Attribute: "Attribute selectors are part of selector structure, which the value bridge does not carry.",
|
|
385
|
+
Combinator: "Combinators are part of selector structure, which the value bridge does not carry.",
|
|
386
|
+
Condition: "Guard conditions are evaluated by the engine, not reconstructed by a plugin.",
|
|
387
|
+
Element: "Selector elements are part of selector structure, which the value bridge does not carry.",
|
|
388
|
+
Extend: ":extend is resolved by the engine's extend pass, not by plugin values.",
|
|
389
|
+
Import: "@import is resolved during document loading, before plugin functions run.",
|
|
390
|
+
JavaScript: "Inline JavaScript evaluation was removed in Jess; use an @plugin function instead.",
|
|
391
|
+
Media: "@media is a statement, not a value; a plugin function can only return a value.",
|
|
392
|
+
MixinCall: "Mixin calls are dispatched by the engine, not constructed by plugin values.",
|
|
393
|
+
MixinDefinition: "Mixin definitions are statements, not values.",
|
|
394
|
+
NamespaceValue: "Namespace lookups are resolved by the engine's scope walk.",
|
|
395
|
+
Selector: "Selectors are part of statement structure, which the value bridge does not carry.",
|
|
396
|
+
VariableCall: "Detached-ruleset calls are dispatched by the engine, not by plugin values."
|
|
397
|
+
};
|
|
398
|
+
const treeNamespace = {
|
|
399
|
+
Anonymous,
|
|
400
|
+
Assignment,
|
|
401
|
+
Call,
|
|
402
|
+
Color,
|
|
403
|
+
Comment,
|
|
404
|
+
Declaration,
|
|
405
|
+
DetachedRuleset,
|
|
406
|
+
Dimension,
|
|
407
|
+
Expression,
|
|
408
|
+
Keyword,
|
|
409
|
+
Mixin,
|
|
410
|
+
Negative,
|
|
411
|
+
Nil,
|
|
412
|
+
Node,
|
|
413
|
+
Operation,
|
|
414
|
+
Paren,
|
|
415
|
+
Property,
|
|
416
|
+
Quoted,
|
|
417
|
+
Ruleset,
|
|
418
|
+
UnicodeDescriptor,
|
|
419
|
+
Unit,
|
|
420
|
+
URL,
|
|
421
|
+
Value,
|
|
422
|
+
Variable
|
|
423
|
+
};
|
|
424
|
+
for (const [name, reason] of Object.entries(UNSUPPORTED_TREE_NODES)) Object.defineProperty(treeNamespace, name, {
|
|
425
|
+
enumerable: true,
|
|
426
|
+
configurable: false,
|
|
427
|
+
get() {
|
|
428
|
+
throw new UnsupportedTreeNodeError(name, reason);
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
/**
|
|
432
|
+
* `less.logger` is captured by a plugin at LOAD time but must reach the host
|
|
433
|
+
* diagnostics of whichever CALL is in flight, so the facade's logger forwards to
|
|
434
|
+
* a per-invocation sink installed around each plugin function call.
|
|
435
|
+
*/
|
|
436
|
+
let activeLogSink = null;
|
|
437
|
+
const lessFacade = {
|
|
438
|
+
tree: treeNamespace,
|
|
439
|
+
logger: {
|
|
440
|
+
warn: (message) => activeLogSink?.push({
|
|
441
|
+
level: "warn",
|
|
442
|
+
message: String(message)
|
|
443
|
+
}),
|
|
444
|
+
error: (message) => activeLogSink?.push({
|
|
445
|
+
level: "error",
|
|
446
|
+
message: String(message)
|
|
447
|
+
}),
|
|
448
|
+
info: (message) => activeLogSink?.push({
|
|
449
|
+
level: "info",
|
|
450
|
+
message: String(message)
|
|
451
|
+
}),
|
|
452
|
+
debug: (message) => activeLogSink?.push({
|
|
453
|
+
level: "debug",
|
|
454
|
+
message: String(message)
|
|
455
|
+
})
|
|
456
|
+
},
|
|
457
|
+
dimension(value, unit) {
|
|
458
|
+
return new Dimension(value, unit);
|
|
459
|
+
},
|
|
460
|
+
value(values, separator = ",") {
|
|
461
|
+
return new Value(values, separator);
|
|
462
|
+
},
|
|
463
|
+
anonymous(value) {
|
|
464
|
+
return new Anonymous(value);
|
|
465
|
+
},
|
|
466
|
+
color(rgb, alpha) {
|
|
467
|
+
return new Color(rgb, alpha);
|
|
468
|
+
},
|
|
469
|
+
quoted(quote, value, escaped = false) {
|
|
470
|
+
return new Quoted(quote, value, escaped);
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
if (runtimeApi === "less") {
|
|
474
|
+
globalThis.less ??= lessFacade;
|
|
475
|
+
globalThis.Less ??= lessFacade;
|
|
476
|
+
}
|
|
477
|
+
const isBridgeValue = (value) => value && typeof value === "object" && value.__jessBridge === true && typeof value.kind === "string";
|
|
478
|
+
const decodeBridgeValue = (value) => {
|
|
479
|
+
if (!isBridgeValue(value)) return value;
|
|
480
|
+
switch (value.kind) {
|
|
481
|
+
case "scalar": return value.value;
|
|
482
|
+
case "dimension": return new Dimension(value.value, value.unit ?? "");
|
|
483
|
+
case "color": return new Color(value.rgb, value.alpha ?? 1);
|
|
484
|
+
case "quoted": return new Quoted(value.quote ?? "\"", value.value, value.escaped === true);
|
|
485
|
+
case "anonymous": return new Anonymous(value.value);
|
|
486
|
+
case "list": return new Value((value.items ?? []).map(decodeBridgeValue), value.separator ?? ",");
|
|
487
|
+
case "expression": return new Expression((value.items ?? []).map(decodeBridgeValue));
|
|
488
|
+
case "mixin": return new Mixin((value.rules ?? []).map((decl) => {
|
|
489
|
+
const decoded = decodeBridgeValue(decl.value);
|
|
490
|
+
const cssText = decoded?.toCSS ? decoded.toCSS() : String(decoded);
|
|
491
|
+
return new Declaration(decl.name, new Anonymous(cssText));
|
|
492
|
+
}));
|
|
493
|
+
default: return value;
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
const encodeBridgeChildValue = (value) => {
|
|
497
|
+
const encoded = encodeBridgeValue(value);
|
|
498
|
+
if (isBridgeValue(encoded)) return encoded;
|
|
499
|
+
return {
|
|
500
|
+
__jessBridge: true,
|
|
501
|
+
kind: "scalar",
|
|
502
|
+
value: typeof encoded === "string" || typeof encoded === "number" || typeof encoded === "boolean" ? encoded : String(encoded)
|
|
503
|
+
};
|
|
504
|
+
};
|
|
505
|
+
const encodeBridgeValue = (value) => {
|
|
506
|
+
if (value instanceof Dimension) return {
|
|
507
|
+
__jessBridge: true,
|
|
508
|
+
kind: "dimension",
|
|
509
|
+
value: value.value,
|
|
510
|
+
unit: value.unit || void 0
|
|
511
|
+
};
|
|
512
|
+
if (value instanceof Color) return {
|
|
513
|
+
__jessBridge: true,
|
|
514
|
+
kind: "color",
|
|
515
|
+
rgb: value.rgb,
|
|
516
|
+
alpha: value.alpha
|
|
517
|
+
};
|
|
518
|
+
if (value instanceof Quoted) return value.quote === "" ? {
|
|
519
|
+
__jessBridge: true,
|
|
520
|
+
kind: "anonymous",
|
|
521
|
+
value: String(value.value)
|
|
522
|
+
} : {
|
|
523
|
+
__jessBridge: true,
|
|
524
|
+
kind: "quoted",
|
|
525
|
+
value: String(value.value),
|
|
526
|
+
quote: value.quote,
|
|
527
|
+
escaped: value.escaped
|
|
528
|
+
};
|
|
529
|
+
if (value instanceof Keyword || value instanceof Anonymous) return {
|
|
530
|
+
__jessBridge: true,
|
|
531
|
+
kind: "anonymous",
|
|
532
|
+
value: String(value.value)
|
|
533
|
+
};
|
|
534
|
+
if (value instanceof Expression) return {
|
|
535
|
+
__jessBridge: true,
|
|
536
|
+
kind: "expression",
|
|
537
|
+
items: value.value.map(encodeBridgeChildValue)
|
|
538
|
+
};
|
|
539
|
+
if (value instanceof Value) return {
|
|
540
|
+
__jessBridge: true,
|
|
541
|
+
kind: "list",
|
|
542
|
+
items: value.value.map(encodeBridgeChildValue),
|
|
543
|
+
separator: value.separator === "/" || value.separator === ";" ? value.separator : ","
|
|
544
|
+
};
|
|
545
|
+
if (value instanceof Mixin || value instanceof Ruleset || value instanceof DetachedRuleset) return {
|
|
546
|
+
__jessBridge: true,
|
|
547
|
+
kind: "mixin",
|
|
548
|
+
rules: (value instanceof Mixin ? value.ruleset.rules : value instanceof DetachedRuleset ? value.ruleset?.rules ?? [] : value.rules).filter((rule) => rule instanceof Declaration && typeof rule.name === "string").map((rule) => ({
|
|
549
|
+
name: rule.name,
|
|
550
|
+
value: encodeBridgeChildValue(rule.value)
|
|
551
|
+
}))
|
|
552
|
+
};
|
|
553
|
+
if (value instanceof Node) return {
|
|
554
|
+
__jessBridge: true,
|
|
555
|
+
kind: "anonymous",
|
|
556
|
+
value: value.toCSS()
|
|
557
|
+
};
|
|
558
|
+
return value;
|
|
559
|
+
};
|
|
6
560
|
const isJsonValue = (value) => {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
561
|
+
try {
|
|
562
|
+
JSON.stringify(value);
|
|
563
|
+
return true;
|
|
564
|
+
} catch {
|
|
565
|
+
return false;
|
|
566
|
+
}
|
|
14
567
|
};
|
|
15
568
|
const send = (payload) => {
|
|
16
|
-
|
|
569
|
+
Deno.stdout.writeSync(encoder.encode(`${JSON.stringify(payload)}\n`));
|
|
17
570
|
};
|
|
18
571
|
const loadModule = async (modulePath) => {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
572
|
+
let mod = moduleCache.get(modulePath);
|
|
573
|
+
if (!mod) {
|
|
574
|
+
mod = await import(pathToFileURL(modulePath).href);
|
|
575
|
+
moduleCache.set(modulePath, mod);
|
|
576
|
+
}
|
|
577
|
+
const exports = [];
|
|
578
|
+
for (const [name, value] of Object.entries(mod)) {
|
|
579
|
+
if (typeof value === "function") {
|
|
580
|
+
exports.push({
|
|
581
|
+
name,
|
|
582
|
+
kind: "function"
|
|
583
|
+
});
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
if (isJsonValue(value)) exports.push({
|
|
587
|
+
name,
|
|
588
|
+
kind: "value",
|
|
589
|
+
value
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
return exports;
|
|
593
|
+
};
|
|
594
|
+
const createLegacyLessPluginRuntime = (modulePath, options) => {
|
|
595
|
+
const localFunctions = /* @__PURE__ */ new Map();
|
|
596
|
+
const functions = {
|
|
597
|
+
add(name, fn) {
|
|
598
|
+
if (typeof name === "string" && typeof fn === "function") localFunctions.set(name.toLowerCase(), fn);
|
|
599
|
+
},
|
|
600
|
+
addMultiple(items) {
|
|
601
|
+
for (const [name, fn] of Object.entries(items ?? {})) this.add(name, fn);
|
|
602
|
+
},
|
|
603
|
+
get(name) {
|
|
604
|
+
return localFunctions.get(String(name).toLowerCase());
|
|
605
|
+
}
|
|
606
|
+
};
|
|
607
|
+
const manager = {
|
|
608
|
+
visitors: [],
|
|
609
|
+
addVisitor(visitor) {
|
|
610
|
+
this.visitors.push(visitor);
|
|
611
|
+
},
|
|
612
|
+
addPreProcessor() {},
|
|
613
|
+
addPostProcessor() {},
|
|
614
|
+
registerPlugin(plugin) {
|
|
615
|
+
installPlugin(plugin);
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
const less = {
|
|
619
|
+
...lessFacade,
|
|
620
|
+
functions: { functionRegistry: functions }
|
|
621
|
+
};
|
|
622
|
+
const installPlugin = (plugin) => {
|
|
623
|
+
if (!plugin) return;
|
|
624
|
+
const candidate = typeof plugin === "function" ? (() => {
|
|
625
|
+
try {
|
|
626
|
+
return new plugin();
|
|
627
|
+
} catch {
|
|
628
|
+
return plugin;
|
|
629
|
+
}
|
|
630
|
+
})() : plugin;
|
|
631
|
+
if (candidate && options != null && typeof candidate.setOptions === "function") candidate.setOptions(options);
|
|
632
|
+
if (candidate && typeof candidate.install === "function") candidate.install(less, manager, functions);
|
|
633
|
+
if (candidate && options != null && typeof candidate.setOptions === "function") candidate.setOptions(options);
|
|
634
|
+
if (candidate && typeof candidate.use === "function") candidate.use(less);
|
|
635
|
+
if (candidate && typeof candidate.eval === "function") candidate.eval(less);
|
|
636
|
+
};
|
|
637
|
+
const require = (specifier) => {
|
|
638
|
+
throw new Error(`Less @plugin require("${specifier}") is not supported in the Deno sandbox yet.`);
|
|
639
|
+
};
|
|
640
|
+
const registerPlugin = (plugin) => {
|
|
641
|
+
installPlugin(plugin);
|
|
642
|
+
};
|
|
643
|
+
return {
|
|
644
|
+
exports: {},
|
|
645
|
+
functions,
|
|
646
|
+
localFunctions,
|
|
647
|
+
manager,
|
|
648
|
+
require,
|
|
649
|
+
registerPlugin,
|
|
650
|
+
fileInfo: { filename: modulePath }
|
|
651
|
+
};
|
|
652
|
+
};
|
|
653
|
+
const lessPluginCacheKey = (modulePath, options) => `${modulePath}\u0000${options ?? ""}`;
|
|
654
|
+
const loadLessPlugin = async (modulePath, options = null) => {
|
|
655
|
+
const cacheKey = lessPluginCacheKey(modulePath, options);
|
|
656
|
+
let functions = lessPluginFunctionCache.get(cacheKey);
|
|
657
|
+
if (functions) return Array.from(functions.keys());
|
|
658
|
+
const source = await Deno.readTextFile(modulePath);
|
|
659
|
+
const runtime = createLegacyLessPluginRuntime(modulePath, options);
|
|
660
|
+
const module = { exports: runtime.exports };
|
|
661
|
+
new Function("module", "exports", "require", "registerPlugin", "functions", "tree", "less", "fileInfo", "process", source)(module, module.exports, runtime.require, runtime.registerPlugin, runtime.functions, lessFacade.tree, {
|
|
662
|
+
...lessFacade,
|
|
663
|
+
functions: { functionRegistry: runtime.functions }
|
|
664
|
+
}, runtime.fileInfo, void 0);
|
|
665
|
+
const exported = module.exports?.default ?? module.exports;
|
|
666
|
+
if (typeof exported === "function" || exported && typeof exported.install === "function") runtime.registerPlugin(exported);
|
|
667
|
+
functions = runtime.localFunctions;
|
|
668
|
+
lessPluginFunctionCache.set(cacheKey, functions);
|
|
669
|
+
return Array.from(functions.keys());
|
|
670
|
+
};
|
|
671
|
+
/**
|
|
672
|
+
* The sandbox cannot call back into the compiler synchronously, but a Less 4
|
|
673
|
+
* plugin body reads scope (`frames[i].variable('@x')`) and built-in functions
|
|
674
|
+
* (`functionRegistry.get('mix')`) synchronously. Rather than eagerly shipping
|
|
675
|
+
* the whole variable scope on every call, an unsatisfied read raises this
|
|
676
|
+
* signal: the worker reports exactly what it needs, the host resolves it
|
|
677
|
+
* against the LIVE evaluation frame, and the call is replayed with the answer
|
|
678
|
+
* in hand. The host remembers which names a given function asked for, so after
|
|
679
|
+
* the first call the needed facts travel with the request and no replay occurs.
|
|
680
|
+
*/
|
|
681
|
+
var HostFactNeeded = class {
|
|
682
|
+
constructor(need) {
|
|
683
|
+
this.need = need;
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
const hostCallKey = (name, args) => `${String(name).toLowerCase()}${JSON.stringify(args)}`;
|
|
687
|
+
/**
|
|
688
|
+
* Builds the `this` a Less 4 plugin function body expects: `this.context` with
|
|
689
|
+
* `frames` / `importantScope` / `pluginManager`, plus `this.currentFileInfo`.
|
|
690
|
+
* Without this the body's `this` is the sandbox global and every `this.context`
|
|
691
|
+
* read is `undefined`.
|
|
692
|
+
*/
|
|
693
|
+
const createPluginCallContext = (facts, logs) => {
|
|
694
|
+
const importantScope = [{ important: "" }];
|
|
695
|
+
const frame = {
|
|
696
|
+
variable(name) {
|
|
697
|
+
const key = String(name);
|
|
698
|
+
if (!Object.prototype.hasOwnProperty.call(facts.vars, key)) throw new HostFactNeeded({
|
|
699
|
+
kind: "variable",
|
|
700
|
+
name: key
|
|
701
|
+
});
|
|
702
|
+
const entry = facts.vars[key];
|
|
703
|
+
if (entry === null || entry === void 0) return null;
|
|
704
|
+
return {
|
|
705
|
+
value: decodeBridgeValue(entry.value),
|
|
706
|
+
important: entry.important ? "!important" : ""
|
|
707
|
+
};
|
|
708
|
+
},
|
|
709
|
+
property() {
|
|
710
|
+
return null;
|
|
711
|
+
},
|
|
712
|
+
functionRegistry: null
|
|
713
|
+
};
|
|
714
|
+
const functionRegistry = {
|
|
715
|
+
get(name) {
|
|
716
|
+
const fnName = String(name);
|
|
717
|
+
return (...callArgs) => {
|
|
718
|
+
const encoded = callArgs.map(encodeBridgeChildValue);
|
|
719
|
+
const key = hostCallKey(fnName, encoded);
|
|
720
|
+
if (!Object.prototype.hasOwnProperty.call(facts.calls, key)) throw new HostFactNeeded({
|
|
721
|
+
kind: "call",
|
|
722
|
+
name: fnName,
|
|
723
|
+
args: encoded,
|
|
724
|
+
key
|
|
725
|
+
});
|
|
726
|
+
const answer = facts.calls[key];
|
|
727
|
+
if (answer === null || answer === void 0) throw new Error(`Less @plugin: built-in function "${fnName}" could not be evaluated with these arguments.`);
|
|
728
|
+
return decodeBridgeValue(answer);
|
|
729
|
+
};
|
|
730
|
+
},
|
|
731
|
+
add() {
|
|
732
|
+
throw new Error("Less @plugin: functions cannot be registered from inside a function body.");
|
|
733
|
+
}
|
|
734
|
+
};
|
|
735
|
+
const pluginManager = {
|
|
736
|
+
less: {
|
|
737
|
+
...lessFacade,
|
|
738
|
+
functions: { functionRegistry }
|
|
739
|
+
},
|
|
740
|
+
getPreProcessors: () => [],
|
|
741
|
+
getPostProcessors: () => [],
|
|
742
|
+
getVisitors: () => [],
|
|
743
|
+
getFileManagers: () => []
|
|
744
|
+
};
|
|
745
|
+
return {
|
|
746
|
+
context: {
|
|
747
|
+
frames: [frame],
|
|
748
|
+
importantScope,
|
|
749
|
+
pluginManager,
|
|
750
|
+
compress: false,
|
|
751
|
+
numPrecision: 8
|
|
752
|
+
},
|
|
753
|
+
currentFileInfo: facts.fileInfo ?? {
|
|
754
|
+
filename: "",
|
|
755
|
+
entryPath: ""
|
|
756
|
+
},
|
|
757
|
+
index: 0,
|
|
758
|
+
importantScope,
|
|
759
|
+
pluginManager
|
|
760
|
+
};
|
|
761
|
+
};
|
|
762
|
+
const invokeLessPluginFunction = async (modulePath, functionName, args, options = null, facts = null) => {
|
|
763
|
+
const cacheKey = lessPluginCacheKey(modulePath, options);
|
|
764
|
+
let functions = lessPluginFunctionCache.get(cacheKey);
|
|
765
|
+
if (!functions) {
|
|
766
|
+
await loadLessPlugin(modulePath, options);
|
|
767
|
+
functions = lessPluginFunctionCache.get(cacheKey);
|
|
768
|
+
}
|
|
769
|
+
const fn = functions?.get(String(functionName).toLowerCase());
|
|
770
|
+
if (typeof fn !== "function") throw new Error(`Less @plugin function "${functionName}" is not registered.`);
|
|
771
|
+
const logs = [];
|
|
772
|
+
const callContext = createPluginCallContext(facts ?? {
|
|
773
|
+
vars: {},
|
|
774
|
+
calls: {},
|
|
775
|
+
fileInfo: null
|
|
776
|
+
}, logs);
|
|
777
|
+
const previousSink = activeLogSink;
|
|
778
|
+
activeLogSink = logs;
|
|
779
|
+
let raw;
|
|
780
|
+
try {
|
|
781
|
+
raw = await fn.apply(callContext, args.map(decodeBridgeValue));
|
|
782
|
+
} catch (err) {
|
|
783
|
+
if (err instanceof HostFactNeeded) return {
|
|
784
|
+
need: err.need,
|
|
785
|
+
logs
|
|
786
|
+
};
|
|
787
|
+
throw err;
|
|
788
|
+
} finally {
|
|
789
|
+
activeLogSink = previousSink;
|
|
790
|
+
}
|
|
791
|
+
const result = encodeBridgeValue(raw);
|
|
792
|
+
if (!isJsonValue(result)) throw new Error(`Result for Less @plugin function "${functionName}" is not JSON-serializable.`);
|
|
793
|
+
return {
|
|
794
|
+
value: result,
|
|
795
|
+
logs,
|
|
796
|
+
important: callContext.importantScope.some((entry) => Boolean(entry?.important))
|
|
797
|
+
};
|
|
36
798
|
};
|
|
37
799
|
const invokeExport = async (modulePath, exportName, args) => {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
800
|
+
const mod = moduleCache.get(modulePath) ?? await import(pathToFileURL(modulePath).href);
|
|
801
|
+
moduleCache.set(modulePath, mod);
|
|
802
|
+
const target = mod?.[exportName];
|
|
803
|
+
if (typeof target !== "function") throw new Error(`Export "${exportName}" is not callable.`);
|
|
804
|
+
const result = encodeBridgeValue(await target(...args.map(decodeBridgeValue)));
|
|
805
|
+
if (!isJsonValue(result)) throw new Error(`Result for "${exportName}" is not JSON-serializable.`);
|
|
806
|
+
return result;
|
|
807
|
+
};
|
|
808
|
+
const computeResponse = async (req) => {
|
|
809
|
+
try {
|
|
810
|
+
if (req.type === "load") {
|
|
811
|
+
const exports = await loadModule(req.modulePath);
|
|
812
|
+
return {
|
|
813
|
+
id: req.id,
|
|
814
|
+
ok: true,
|
|
815
|
+
exports
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
if (req.type === "invoke") {
|
|
819
|
+
const value = await invokeExport(req.modulePath, req.exportName, req.args ?? []);
|
|
820
|
+
return {
|
|
821
|
+
id: req.id,
|
|
822
|
+
ok: true,
|
|
823
|
+
value
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
if (req.type === "loadLessPlugin") {
|
|
827
|
+
const functions = await loadLessPlugin(req.modulePath, req.options ?? null);
|
|
828
|
+
return {
|
|
829
|
+
id: req.id,
|
|
830
|
+
ok: true,
|
|
831
|
+
functions
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
if (req.type === "invokeLessPluginFunction") {
|
|
835
|
+
const outcome = await invokeLessPluginFunction(req.modulePath, req.functionName, req.args ?? [], req.options ?? null, req.facts ?? null);
|
|
836
|
+
return outcome.need ? {
|
|
837
|
+
id: req.id,
|
|
838
|
+
ok: false,
|
|
839
|
+
need: outcome.need,
|
|
840
|
+
logs: outcome.logs
|
|
841
|
+
} : {
|
|
842
|
+
id: req.id,
|
|
843
|
+
ok: true,
|
|
844
|
+
value: outcome.value,
|
|
845
|
+
logs: outcome.logs,
|
|
846
|
+
important: outcome.important
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
return {
|
|
850
|
+
id: req.id,
|
|
851
|
+
ok: false,
|
|
852
|
+
error: `Unknown request type "${req.type}".`
|
|
853
|
+
};
|
|
854
|
+
} catch (err) {
|
|
855
|
+
return {
|
|
856
|
+
id: req.id,
|
|
857
|
+
ok: false,
|
|
858
|
+
error: err?.message ?? String(err),
|
|
859
|
+
errorName: err?.name ?? "Error",
|
|
860
|
+
stack: typeof err?.stack === "string" ? err.stack : void 0
|
|
861
|
+
};
|
|
862
|
+
}
|
|
49
863
|
};
|
|
50
864
|
const handleRequest = async (req) => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
buffer += decoder.decode(chunk, { stream: true });
|
|
75
|
-
let idx = buffer.indexOf('\n');
|
|
76
|
-
while (idx >= 0) {
|
|
77
|
-
const line = buffer.slice(0, idx).trim();
|
|
78
|
-
buffer = buffer.slice(idx + 1);
|
|
79
|
-
idx = buffer.indexOf('\n');
|
|
80
|
-
if (!line) {
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
let req;
|
|
84
|
-
try {
|
|
85
|
-
req = JSON.parse(line);
|
|
86
|
-
}
|
|
87
|
-
catch {
|
|
88
|
-
continue;
|
|
89
|
-
}
|
|
90
|
-
await handleRequest(req);
|
|
91
|
-
}
|
|
865
|
+
if (!req || typeof req.id !== "number" || typeof req.type !== "string") return;
|
|
866
|
+
send(await computeResponse(req));
|
|
867
|
+
};
|
|
868
|
+
async function main() {
|
|
869
|
+
send({ type: "ready" });
|
|
870
|
+
let buffer = "";
|
|
871
|
+
for await (const chunk of Deno.stdin.readable) {
|
|
872
|
+
buffer += decoder.decode(chunk, { stream: true });
|
|
873
|
+
let idx = buffer.indexOf("\n");
|
|
874
|
+
while (idx >= 0) {
|
|
875
|
+
const line = buffer.slice(0, idx).trim();
|
|
876
|
+
buffer = buffer.slice(idx + 1);
|
|
877
|
+
idx = buffer.indexOf("\n");
|
|
878
|
+
if (!line) continue;
|
|
879
|
+
let req;
|
|
880
|
+
try {
|
|
881
|
+
req = JSON.parse(line);
|
|
882
|
+
} catch {
|
|
883
|
+
continue;
|
|
884
|
+
}
|
|
885
|
+
await handleRequest(req);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
92
888
|
}
|
|
93
|
-
|
|
889
|
+
main();
|
|
890
|
+
//#endregion
|
|
891
|
+
export {};
|