htmljs-parser 3.3.5 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -14
- package/dist/core/Parser.d.ts +1 -1
- package/dist/index.js +21 -15
- package/dist/index.mjs +21 -15
- package/dist/util/constants.d.ts +5 -8
- package/package.json +16 -16
package/README.md
CHANGED
|
@@ -155,20 +155,29 @@ const parser = createParser({
|
|
|
155
155
|
range.value; // Another range that includes only the value itself without the leading $ or surrounding braces (if applicable).
|
|
156
156
|
},
|
|
157
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Called when we're about to begin an HTML open tag (before the tag name).
|
|
160
|
+
* Note: This is only called for HTML mode tags and can be used to track if you are in concise mode.
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* 1╭─ <div>Hi</div>
|
|
164
|
+
* ╰─ ╰─ openTagStart
|
|
165
|
+
*/
|
|
166
|
+
onOpenTagStart(range) {}
|
|
167
|
+
|
|
158
168
|
/**
|
|
159
169
|
* Called when a tag name, which can include placeholders, has been parsed.
|
|
160
170
|
*
|
|
161
171
|
* @example
|
|
162
172
|
* 1╭─ <div/>
|
|
163
|
-
* ╰─ ╰─
|
|
173
|
+
* ╰─ ╰─ openTagName "div"
|
|
164
174
|
* 2╭─ <hello-${test}-again/>
|
|
165
|
-
* │ │ │ ╰─
|
|
166
|
-
* │ │ ╰─
|
|
167
|
-
* │ ├─
|
|
168
|
-
* ╰─ ╰─
|
|
175
|
+
* │ │ │ ╰─ openTagName.quasis[1] "-again"
|
|
176
|
+
* │ │ ╰─ openTagName.expressions[0] "${test}"
|
|
177
|
+
* │ ├─ openTagName.quasis[0] "hello-"
|
|
178
|
+
* ╰─ ╰─ openTagName "hello-${test}-again"
|
|
169
179
|
*/
|
|
170
|
-
|
|
171
|
-
range.concise; // true if this tag is a concise mode tag.
|
|
180
|
+
onOpenTagName(range) {
|
|
172
181
|
range.quasis; // An array of ranges that indicate the string literal parts of the tag name.
|
|
173
182
|
range.expressions; // A list of placeholder ranges (similar to whats emitted via onPlaceholder).
|
|
174
183
|
|
|
@@ -353,21 +362,38 @@ const parser = createParser({
|
|
|
353
362
|
* ╰─ ╰─ openTagEnd ">"
|
|
354
363
|
*/
|
|
355
364
|
onOpenTagEnd(range) {
|
|
356
|
-
range.selfClosed; // true if this tag was self closed (onCloseTag
|
|
365
|
+
range.selfClosed; // true if this tag was self closed (the onCloseTag* handlers will not be called if so).
|
|
357
366
|
},
|
|
358
367
|
|
|
359
368
|
/**
|
|
360
|
-
* Called
|
|
369
|
+
* Called when we start parsing and html closing tag.
|
|
370
|
+
* Note this is not emitted for concise, selfClosed, void or statement tags.
|
|
371
|
+
*
|
|
372
|
+
* @example
|
|
373
|
+
* 1╭─ <div><span/></div>
|
|
374
|
+
* ╰─ ╰─ closeTagStart "</"
|
|
375
|
+
*/
|
|
376
|
+
onCloseTagStart(range) {},
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Called after the content within the brackets of an html closing tag has been parsed.
|
|
380
|
+
* Note this is not emitted for concise, selfClosed, void or statement tags.
|
|
381
|
+
*
|
|
382
|
+
* @example
|
|
383
|
+
* 1╭─ <div><span/></div>
|
|
384
|
+
* ╰─ ╰─ closeTagName "div"
|
|
385
|
+
*/
|
|
386
|
+
onCloseTagName(range) {},
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Called once the closing tag has finished parsing, or in concise mode we hit an outdent or eof.
|
|
361
390
|
* Note this is not called for selfClosed, void or statement tags.
|
|
362
391
|
*
|
|
363
392
|
* @example
|
|
364
393
|
* 1╭─ <div><span/></div>
|
|
365
|
-
*
|
|
366
|
-
* ╰─ ╰─ closeTag(div) "</div>"
|
|
394
|
+
* ╰─ ╰─ closeTagEnd ">"
|
|
367
395
|
*/
|
|
368
|
-
|
|
369
|
-
range.value; // The raw content of the closing tag (undefined in concise mode).
|
|
370
|
-
},
|
|
396
|
+
onCloseTagEnd(range) {},
|
|
371
397
|
});
|
|
372
398
|
```
|
|
373
399
|
|
package/dist/core/Parser.d.ts
CHANGED
|
@@ -57,7 +57,7 @@ export declare class Parser {
|
|
|
57
57
|
*/
|
|
58
58
|
beginHtmlBlock(delimiter: string | undefined, singleLine: boolean): void;
|
|
59
59
|
emitError(range: number | Range, code: ErrorCode, message: string): void;
|
|
60
|
-
|
|
60
|
+
closeTagEnd(start: number, end: number, name: Range | undefined): void;
|
|
61
61
|
consumeWhitespaceIfBefore(str: string, start?: number): boolean;
|
|
62
62
|
getPreviousNonWhitespaceCharCode(start?: number): number;
|
|
63
63
|
onlyWhitespaceRemainsOnLine(start?: number): boolean;
|
package/dist/index.js
CHANGED
|
@@ -89,10 +89,10 @@ function getPos(lines, startLine, index) {
|
|
|
89
89
|
};
|
|
90
90
|
}
|
|
91
91
|
function getLines(src) {
|
|
92
|
-
const lines = [
|
|
92
|
+
const lines = [0];
|
|
93
93
|
for (let i = 0; i < src.length; i++) {
|
|
94
94
|
if (src.charCodeAt(i) === 10 /* NEWLINE */) {
|
|
95
|
-
lines.push(i);
|
|
95
|
+
lines.push(i + 1);
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
return lines;
|
|
@@ -101,7 +101,7 @@ function htmlEOF() {
|
|
|
101
101
|
this.endText();
|
|
102
102
|
while (this.activeTag) {
|
|
103
103
|
if (this.activeTag.concise) {
|
|
104
|
-
this.
|
|
104
|
+
this.closeTagEnd(this.pos, this.pos, void 0);
|
|
105
105
|
} else {
|
|
106
106
|
return this.emitError(this.activeTag, 22 /* MISSING_END_TAG */, 'Missing ending "' + this.read(this.activeTag.tagName) + '" tag');
|
|
107
107
|
}
|
|
@@ -231,17 +231,15 @@ var Parser = class {
|
|
|
231
231
|
});
|
|
232
232
|
this.pos = this.maxPos + 1;
|
|
233
233
|
}
|
|
234
|
-
|
|
235
|
-
var _a, _b;
|
|
234
|
+
closeTagEnd(start, end, name) {
|
|
235
|
+
var _a, _b, _c, _d;
|
|
236
236
|
const { beginMixedMode, parentTag } = this.activeTag;
|
|
237
237
|
if (beginMixedMode)
|
|
238
238
|
this.endingMixedModeAtEOL = true;
|
|
239
239
|
this.activeTag = parentTag;
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
value
|
|
244
|
-
});
|
|
240
|
+
if (name)
|
|
241
|
+
(_b = (_a = this.options).onCloseTagName) == null ? void 0 : _b.call(_a, name);
|
|
242
|
+
(_d = (_c = this.options).onCloseTagEnd) == null ? void 0 : _d.call(_c, { start, end });
|
|
245
243
|
}
|
|
246
244
|
consumeWhitespaceIfBefore(str, start = 0) {
|
|
247
245
|
const { pos, data } = this;
|
|
@@ -778,7 +776,7 @@ function ensureExpectedCloseTag(parser, closeTag) {
|
|
|
778
776
|
}
|
|
779
777
|
}
|
|
780
778
|
}
|
|
781
|
-
parser.
|
|
779
|
+
parser.closeTagEnd(closeTagNameEnd, closeTag.end, closeTagNamePos);
|
|
782
780
|
return true;
|
|
783
781
|
}
|
|
784
782
|
|
|
@@ -805,7 +803,7 @@ var CONCISE_HTML_CONTENT = {
|
|
|
805
803
|
const indentStart = this.pos - curIndent - 1;
|
|
806
804
|
let parentTag = this.activeTag;
|
|
807
805
|
while (parentTag && parentTag.indent.length >= curIndent) {
|
|
808
|
-
this.
|
|
806
|
+
this.closeTagEnd(indentStart, indentStart, void 0);
|
|
809
807
|
parentTag = this.activeTag;
|
|
810
808
|
}
|
|
811
809
|
if (!parentTag && curIndent) {
|
|
@@ -1210,6 +1208,7 @@ var HTML_CONTENT = {
|
|
|
1210
1208
|
exit() {
|
|
1211
1209
|
},
|
|
1212
1210
|
char(code) {
|
|
1211
|
+
var _a, _b, _c, _d;
|
|
1213
1212
|
if (code === 60 /* OPEN_ANGLE_BRACKET */) {
|
|
1214
1213
|
if (states_exports.checkForCDATA(this))
|
|
1215
1214
|
return;
|
|
@@ -1226,11 +1225,19 @@ var HTML_CONTENT = {
|
|
|
1226
1225
|
this.enterState(states_exports.DECLARATION);
|
|
1227
1226
|
this.pos++;
|
|
1228
1227
|
} else if (nextCode === 47 /* FORWARD_SLASH */) {
|
|
1228
|
+
(_b = (_a = this.options).onCloseTagStart) == null ? void 0 : _b.call(_a, {
|
|
1229
|
+
start: this.pos,
|
|
1230
|
+
end: this.pos + 2
|
|
1231
|
+
});
|
|
1229
1232
|
this.enterState(states_exports.CLOSE_TAG);
|
|
1230
1233
|
this.pos++;
|
|
1231
1234
|
} else if (nextCode === 62 /* CLOSE_ANGLE_BRACKET */ || nextCode === 60 /* OPEN_ANGLE_BRACKET */ || isWhitespaceCode(nextCode)) {
|
|
1232
1235
|
this.startText();
|
|
1233
1236
|
} else {
|
|
1237
|
+
(_d = (_c = this.options).onOpenTagStart) == null ? void 0 : _d.call(_c, {
|
|
1238
|
+
start: this.pos,
|
|
1239
|
+
end: this.pos + 1
|
|
1240
|
+
});
|
|
1234
1241
|
this.enterState(states_exports.OPEN_TAG);
|
|
1235
1242
|
}
|
|
1236
1243
|
} else if (code === 36 /* DOLLAR */ && isWhitespaceCode(this.lookAtCharCodeAhead(1)) && isBeginningOfLine(this)) {
|
|
@@ -1632,12 +1639,11 @@ var TAG_NAME = {
|
|
|
1632
1639
|
break;
|
|
1633
1640
|
default: {
|
|
1634
1641
|
const tag = this.activeTag;
|
|
1635
|
-
const tagType = (_f = (_e = this.options).
|
|
1642
|
+
const tagType = (_f = (_e = this.options).onOpenTagName) == null ? void 0 : _f.call(_e, {
|
|
1636
1643
|
start,
|
|
1637
1644
|
end,
|
|
1638
1645
|
quasis,
|
|
1639
|
-
expressions
|
|
1640
|
-
concise: this.isConcise
|
|
1646
|
+
expressions
|
|
1641
1647
|
});
|
|
1642
1648
|
tag.tagName = tagName;
|
|
1643
1649
|
if (tagType) {
|
package/dist/index.mjs
CHANGED
|
@@ -68,10 +68,10 @@ function getPos(lines, startLine, index) {
|
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
70
|
function getLines(src) {
|
|
71
|
-
const lines = [
|
|
71
|
+
const lines = [0];
|
|
72
72
|
for (let i = 0; i < src.length; i++) {
|
|
73
73
|
if (src.charCodeAt(i) === 10 /* NEWLINE */) {
|
|
74
|
-
lines.push(i);
|
|
74
|
+
lines.push(i + 1);
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
return lines;
|
|
@@ -80,7 +80,7 @@ function htmlEOF() {
|
|
|
80
80
|
this.endText();
|
|
81
81
|
while (this.activeTag) {
|
|
82
82
|
if (this.activeTag.concise) {
|
|
83
|
-
this.
|
|
83
|
+
this.closeTagEnd(this.pos, this.pos, void 0);
|
|
84
84
|
} else {
|
|
85
85
|
return this.emitError(this.activeTag, 22 /* MISSING_END_TAG */, 'Missing ending "' + this.read(this.activeTag.tagName) + '" tag');
|
|
86
86
|
}
|
|
@@ -210,17 +210,15 @@ var Parser = class {
|
|
|
210
210
|
});
|
|
211
211
|
this.pos = this.maxPos + 1;
|
|
212
212
|
}
|
|
213
|
-
|
|
214
|
-
var _a, _b;
|
|
213
|
+
closeTagEnd(start, end, name) {
|
|
214
|
+
var _a, _b, _c, _d;
|
|
215
215
|
const { beginMixedMode, parentTag } = this.activeTag;
|
|
216
216
|
if (beginMixedMode)
|
|
217
217
|
this.endingMixedModeAtEOL = true;
|
|
218
218
|
this.activeTag = parentTag;
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
value
|
|
223
|
-
});
|
|
219
|
+
if (name)
|
|
220
|
+
(_b = (_a = this.options).onCloseTagName) == null ? void 0 : _b.call(_a, name);
|
|
221
|
+
(_d = (_c = this.options).onCloseTagEnd) == null ? void 0 : _d.call(_c, { start, end });
|
|
224
222
|
}
|
|
225
223
|
consumeWhitespaceIfBefore(str, start = 0) {
|
|
226
224
|
const { pos, data } = this;
|
|
@@ -757,7 +755,7 @@ function ensureExpectedCloseTag(parser, closeTag) {
|
|
|
757
755
|
}
|
|
758
756
|
}
|
|
759
757
|
}
|
|
760
|
-
parser.
|
|
758
|
+
parser.closeTagEnd(closeTagNameEnd, closeTag.end, closeTagNamePos);
|
|
761
759
|
return true;
|
|
762
760
|
}
|
|
763
761
|
|
|
@@ -784,7 +782,7 @@ var CONCISE_HTML_CONTENT = {
|
|
|
784
782
|
const indentStart = this.pos - curIndent - 1;
|
|
785
783
|
let parentTag = this.activeTag;
|
|
786
784
|
while (parentTag && parentTag.indent.length >= curIndent) {
|
|
787
|
-
this.
|
|
785
|
+
this.closeTagEnd(indentStart, indentStart, void 0);
|
|
788
786
|
parentTag = this.activeTag;
|
|
789
787
|
}
|
|
790
788
|
if (!parentTag && curIndent) {
|
|
@@ -1189,6 +1187,7 @@ var HTML_CONTENT = {
|
|
|
1189
1187
|
exit() {
|
|
1190
1188
|
},
|
|
1191
1189
|
char(code) {
|
|
1190
|
+
var _a, _b, _c, _d;
|
|
1192
1191
|
if (code === 60 /* OPEN_ANGLE_BRACKET */) {
|
|
1193
1192
|
if (states_exports.checkForCDATA(this))
|
|
1194
1193
|
return;
|
|
@@ -1205,11 +1204,19 @@ var HTML_CONTENT = {
|
|
|
1205
1204
|
this.enterState(states_exports.DECLARATION);
|
|
1206
1205
|
this.pos++;
|
|
1207
1206
|
} else if (nextCode === 47 /* FORWARD_SLASH */) {
|
|
1207
|
+
(_b = (_a = this.options).onCloseTagStart) == null ? void 0 : _b.call(_a, {
|
|
1208
|
+
start: this.pos,
|
|
1209
|
+
end: this.pos + 2
|
|
1210
|
+
});
|
|
1208
1211
|
this.enterState(states_exports.CLOSE_TAG);
|
|
1209
1212
|
this.pos++;
|
|
1210
1213
|
} else if (nextCode === 62 /* CLOSE_ANGLE_BRACKET */ || nextCode === 60 /* OPEN_ANGLE_BRACKET */ || isWhitespaceCode(nextCode)) {
|
|
1211
1214
|
this.startText();
|
|
1212
1215
|
} else {
|
|
1216
|
+
(_d = (_c = this.options).onOpenTagStart) == null ? void 0 : _d.call(_c, {
|
|
1217
|
+
start: this.pos,
|
|
1218
|
+
end: this.pos + 1
|
|
1219
|
+
});
|
|
1213
1220
|
this.enterState(states_exports.OPEN_TAG);
|
|
1214
1221
|
}
|
|
1215
1222
|
} else if (code === 36 /* DOLLAR */ && isWhitespaceCode(this.lookAtCharCodeAhead(1)) && isBeginningOfLine(this)) {
|
|
@@ -1611,12 +1618,11 @@ var TAG_NAME = {
|
|
|
1611
1618
|
break;
|
|
1612
1619
|
default: {
|
|
1613
1620
|
const tag = this.activeTag;
|
|
1614
|
-
const tagType = (_f = (_e = this.options).
|
|
1621
|
+
const tagType = (_f = (_e = this.options).onOpenTagName) == null ? void 0 : _f.call(_e, {
|
|
1615
1622
|
start,
|
|
1616
1623
|
end,
|
|
1617
1624
|
quasis,
|
|
1618
|
-
expressions
|
|
1619
|
-
concise: this.isConcise
|
|
1625
|
+
expressions
|
|
1620
1626
|
});
|
|
1621
1627
|
tag.tagName = tagName;
|
|
1622
1628
|
if (tagType) {
|
package/dist/util/constants.d.ts
CHANGED
|
@@ -79,9 +79,6 @@ export declare namespace Ranges {
|
|
|
79
79
|
interface Placeholder extends Value {
|
|
80
80
|
escape: boolean;
|
|
81
81
|
}
|
|
82
|
-
interface TagName extends Template {
|
|
83
|
-
concise: boolean;
|
|
84
|
-
}
|
|
85
82
|
interface AttrValue extends Value {
|
|
86
83
|
bound: boolean;
|
|
87
84
|
}
|
|
@@ -92,9 +89,6 @@ export declare namespace Ranges {
|
|
|
92
89
|
interface OpenTagEnd extends Range {
|
|
93
90
|
selfClosed: boolean;
|
|
94
91
|
}
|
|
95
|
-
interface CloseTag extends Range {
|
|
96
|
-
value: Range | undefined;
|
|
97
|
-
}
|
|
98
92
|
}
|
|
99
93
|
export declare enum ErrorCode {
|
|
100
94
|
EXTRA_CLOSING_TAG = 0,
|
|
@@ -139,7 +133,8 @@ export interface ParserOptions {
|
|
|
139
133
|
onDeclaration?(data: Ranges.Value): void;
|
|
140
134
|
onDoctype?(data: Ranges.Value): void;
|
|
141
135
|
onScriptlet?(data: Ranges.Scriptlet): void;
|
|
142
|
-
|
|
136
|
+
onOpenTagStart?(data: Range): void;
|
|
137
|
+
onOpenTagName?(data: Ranges.Template): TagType | void;
|
|
143
138
|
onTagShorthandId?(data: Ranges.Template): void;
|
|
144
139
|
onTagShorthandClass?(data: Ranges.Template): void;
|
|
145
140
|
onTagVar?(data: Ranges.Value): void;
|
|
@@ -151,5 +146,7 @@ export interface ParserOptions {
|
|
|
151
146
|
onAttrMethod?(data: Ranges.AttrMethod): void;
|
|
152
147
|
onAttrSpread?(data: Ranges.Value): void;
|
|
153
148
|
onOpenTagEnd?(data: Ranges.OpenTagEnd): void;
|
|
154
|
-
|
|
149
|
+
onCloseTagStart?(data: Range): void;
|
|
150
|
+
onCloseTagName?(data: Range): void;
|
|
151
|
+
onCloseTagEnd?(data: Range): void;
|
|
155
152
|
}
|
package/package.json
CHANGED
|
@@ -1,32 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "htmljs-parser",
|
|
3
3
|
"description": "An HTML parser recognizes content and string placeholders and allows JavaScript expressions as attribute values",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "5.0.0",
|
|
5
5
|
"devDependencies": {
|
|
6
6
|
"@changesets/changelog-github": "^0.4.4",
|
|
7
7
|
"@changesets/cli": "^2.22.0",
|
|
8
|
-
"@types/benchmark": "^2.1.1",
|
|
9
8
|
"@types/degit": "^2.8.3",
|
|
10
9
|
"@types/mocha": "^9.1.1",
|
|
11
|
-
"@types/node": "^17.0.
|
|
12
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
|
13
|
-
"@typescript-eslint/parser": "^5.
|
|
14
|
-
"benchmark": "^2.1.4",
|
|
10
|
+
"@types/node": "^17.0.38",
|
|
11
|
+
"@typescript-eslint/eslint-plugin": "^5.27.0",
|
|
12
|
+
"@typescript-eslint/parser": "^5.27.0",
|
|
15
13
|
"cross-env": "^7.0.3",
|
|
16
14
|
"degit": "^2.8.4",
|
|
17
|
-
"esbuild": "0.14.
|
|
18
|
-
"
|
|
19
|
-
"eslint": "^8.14.0",
|
|
15
|
+
"esbuild": "0.14.42",
|
|
16
|
+
"eslint": "^8.16.0",
|
|
20
17
|
"eslint-config-prettier": "^8.5.0",
|
|
21
18
|
"fast-glob": "^3.2.11",
|
|
22
19
|
"fixpack": "^4.0.0",
|
|
23
|
-
"husky": "^
|
|
24
|
-
"lint-staged": "^
|
|
25
|
-
"
|
|
20
|
+
"husky": "^8.0.1",
|
|
21
|
+
"lint-staged": "^13.0.0",
|
|
22
|
+
"mitata": "^0.1.1",
|
|
23
|
+
"mocha": "^10.0.0",
|
|
26
24
|
"mocha-snap": "^4.3.0",
|
|
27
25
|
"nyc": "^15.1.0",
|
|
28
26
|
"prettier": "^2.6.2",
|
|
29
|
-
"
|
|
27
|
+
"tsx": "^3.4.2",
|
|
28
|
+
"typescript": "^4.7.2"
|
|
30
29
|
},
|
|
31
30
|
"exports": {
|
|
32
31
|
".": {
|
|
@@ -59,8 +58,9 @@
|
|
|
59
58
|
"url": "https://github.com/marko-js/htmljs-parser.git"
|
|
60
59
|
},
|
|
61
60
|
"scripts": {
|
|
62
|
-
"bench": "
|
|
63
|
-
"build": "tsc -b &&
|
|
61
|
+
"bench": "tsx bench.mts",
|
|
62
|
+
"build": "tsc -b && tsx build.mts",
|
|
63
|
+
"change": "changeset add",
|
|
64
64
|
"ci:test": "nyc npm run mocha -- --forbid-only",
|
|
65
65
|
"format": "npm run lint:eslint -- --fix && npm run lint:prettier -- --write && (fixpack || true)",
|
|
66
66
|
"lint": "tsc -b && npm run lint:eslint && npm run lint:prettier -- -l && fixpack",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"lint:prettier": "prettier \"./**/*{.ts,.js,.json,.md,.yml,rc}\"",
|
|
69
69
|
"mocha": "cross-env NODE_ENV=test mocha \"./src/**/__tests__/*.test.ts\"",
|
|
70
70
|
"prepare": "husky install",
|
|
71
|
-
"
|
|
71
|
+
"release": "npm run build && changeset publish",
|
|
72
72
|
"report": "open ./coverage/lcov-report/index.html",
|
|
73
73
|
"test": "npm run mocha -- --watch",
|
|
74
74
|
"test:inspect": "npm test -- --inspect",
|