markuplint 5.3.0 → 5.5.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/CHANGELOG.md +25 -0
- package/README.md +8 -6
- package/lib/api/ml-engine.js +72 -13
- package/lib/api/types.d.ts +5 -0
- package/lib/cli/bootstrap.d.ts +1 -1
- package/lib/cli/bootstrap.js +8 -3
- package/lib/cli/command.d.ts +18 -0
- package/lib/cli/command.js +173 -115
- package/lib/cli/index.d.ts +36 -0
- package/lib/cli/index.js +38 -66
- package/lib/cli/init/index.js +3 -3
- package/lib/cli/main.d.ts +14 -0
- package/lib/cli/main.js +171 -0
- package/lib/cli/output.js +3 -1
- package/lib/cli/search/index.js +2 -1
- package/lib/suppressions/compute-scope.d.ts +7 -2
- package/lib/suppressions/compute-scope.js +7 -2
- package/lib/suppressions/suppressions-file.d.ts +5 -0
- package/lib/suppressions/suppressions-file.js +10 -1
- package/package.json +49 -13
package/lib/cli/main.js
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module cli/main
|
|
3
|
+
*
|
|
4
|
+
* The CLI run itself. `index.ts` is the entry point and owns the process boundary
|
|
5
|
+
* (the exit code and why `process.exit()` is not used); this module is separate so a spec
|
|
6
|
+
* can call `main()` without starting the process.
|
|
7
|
+
*/
|
|
8
|
+
import { text } from 'node:stream/consumers';
|
|
9
|
+
import { isFatalError } from '@markuplint/shared';
|
|
10
|
+
import { verbosely } from '../debug.js';
|
|
11
|
+
import { cli } from './bootstrap.js';
|
|
12
|
+
import { command } from './command.js';
|
|
13
|
+
import { initialize } from './init/index.js';
|
|
14
|
+
import { search } from './search/index.js';
|
|
15
|
+
/** The operand that means standard input. */
|
|
16
|
+
const STDIN_OPERAND = '-';
|
|
17
|
+
/**
|
|
18
|
+
* Runs the CLI and resolves to its exit code.
|
|
19
|
+
*
|
|
20
|
+
* Every branch returns a number: a branch that forgets to leaves `undefined`,
|
|
21
|
+
* and the `process.exitCode` that `output.ts` set (1) would leak out.
|
|
22
|
+
*/
|
|
23
|
+
export async function main() {
|
|
24
|
+
// Same condition as meow's automatic handling: the flag has to be the only argument.
|
|
25
|
+
const isSoleArgument = cli.input.length === 0 && process.argv.length === 3;
|
|
26
|
+
if (cli.flags.v || (isSoleArgument && cli.flags.version === true)) {
|
|
27
|
+
return showVersion();
|
|
28
|
+
}
|
|
29
|
+
if (cli.flags.h || (isSoleArgument && cli.flags.help === true)) {
|
|
30
|
+
return showHelp(0);
|
|
31
|
+
}
|
|
32
|
+
if (cli.flags.verbose) {
|
|
33
|
+
verbosely();
|
|
34
|
+
}
|
|
35
|
+
if (cli.flags.init) {
|
|
36
|
+
try {
|
|
37
|
+
await initialize();
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
if (isFatalError(error)) {
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
process.stderr.write(error + '\n');
|
|
44
|
+
return 1;
|
|
45
|
+
}
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
if (cli.flags.createRule) {
|
|
49
|
+
process.stderr.write("Use to run 'npx @markuplint/create-rule' instead of 'markuplint --create-rule'.\n");
|
|
50
|
+
return 1;
|
|
51
|
+
}
|
|
52
|
+
const files = cli.input;
|
|
53
|
+
if (files.includes(STDIN_OPERAND)) {
|
|
54
|
+
const fileFlags = givenFileFlags();
|
|
55
|
+
if (files.length > 1 || fileFlags.length > 0) {
|
|
56
|
+
const conflicts = files.length > 1 ? ['file paths', ...fileFlags] : fileFlags;
|
|
57
|
+
process.stderr.write(`The \`${STDIN_OPERAND}\` operand (standard input) cannot be combined with ${conflicts.join(', ')}.\n`);
|
|
58
|
+
return 1;
|
|
59
|
+
}
|
|
60
|
+
const stdin = await readStdin();
|
|
61
|
+
if (stdin === null) {
|
|
62
|
+
return 1;
|
|
63
|
+
}
|
|
64
|
+
// An empty input is an empty source, as `markuplint empty.html` is.
|
|
65
|
+
return lintStdin(stdin);
|
|
66
|
+
}
|
|
67
|
+
if (files.length > 0) {
|
|
68
|
+
if (cli.flags.search) {
|
|
69
|
+
try {
|
|
70
|
+
await search(files, cli.flags, cli.flags.search);
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
if (isFatalError(error)) {
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
process.stderr.write(error + '\n');
|
|
77
|
+
return 1;
|
|
78
|
+
}
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
return lint(files, cli.flags);
|
|
82
|
+
}
|
|
83
|
+
if (usePipe()) {
|
|
84
|
+
const fileFlags = givenFileFlags();
|
|
85
|
+
if (fileFlags.length > 0) {
|
|
86
|
+
process.stderr.write(`Standard input cannot be combined with ${fileFlags.join(', ')}.\n`);
|
|
87
|
+
return 1;
|
|
88
|
+
}
|
|
89
|
+
const stdin = await readStdin();
|
|
90
|
+
if (stdin === null) {
|
|
91
|
+
return 1;
|
|
92
|
+
}
|
|
93
|
+
if (!stdin) {
|
|
94
|
+
// result is empty
|
|
95
|
+
return showHelp(1);
|
|
96
|
+
}
|
|
97
|
+
return lintStdin(stdin);
|
|
98
|
+
}
|
|
99
|
+
// No arguments
|
|
100
|
+
return showHelp(1);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The options that act on a file that are given, by their names: `--fix` writes the fixed source
|
|
104
|
+
* to it (with nothing to write to, it created a file named `<AnonymousFile>`, #4560), `--search`
|
|
105
|
+
* and the suppressions options report or record by file. The standard input has none, so both
|
|
106
|
+
* ways of reading it (the `-` operand and the no-operand pipe) reject them, and name them in the
|
|
107
|
+
* message, from this one list: an option that acts on a file is added here, and nowhere else.
|
|
108
|
+
* `--fix-dry-run` is not one: it writes nothing and prints the diff.
|
|
109
|
+
*/
|
|
110
|
+
function givenFileFlags() {
|
|
111
|
+
const { search, fix, suppress, suppressRule, pruneSuppressions } = cli.flags;
|
|
112
|
+
// Each is given by the condition of the code that consumes it, not by one shared test:
|
|
113
|
+
// `--search` is a search when it is not empty (the file branch below tests it so), and
|
|
114
|
+
// `--suppress-rule` turns the suppressions mode on when it is set at all (`command.ts`).
|
|
115
|
+
const given = [
|
|
116
|
+
['--search', Boolean(search)],
|
|
117
|
+
['--fix', Boolean(fix)],
|
|
118
|
+
['--suppress', Boolean(suppress)],
|
|
119
|
+
['--suppress-rule', suppressRule != null],
|
|
120
|
+
['--prune-suppressions', Boolean(pruneSuppressions)],
|
|
121
|
+
];
|
|
122
|
+
return given.filter(([, isGiven]) => isGiven).map(([name]) => name);
|
|
123
|
+
}
|
|
124
|
+
function usePipe() {
|
|
125
|
+
return !process.stdin.isTTY && process.stdout.isTTY;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Reports a read failure on stderr and returns `null` instead of throwing,
|
|
129
|
+
* so the caller can return the exit code.
|
|
130
|
+
*/
|
|
131
|
+
async function readStdin() {
|
|
132
|
+
try {
|
|
133
|
+
return await text(process.stdin);
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
if (isFatalError(error)) {
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
process.stderr.write(error + '\n');
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function lintStdin(sourceCode) {
|
|
144
|
+
return lint([{ sourceCode }], { ...cli.flags, ignoreExt: true });
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Runs `command()` and resolves to its exit code. A failure that is not fatal is reported on
|
|
148
|
+
* stderr with exit code 1, whatever the input is (file paths or the standard input).
|
|
149
|
+
*/
|
|
150
|
+
async function lint(...args) {
|
|
151
|
+
try {
|
|
152
|
+
return (await command(...args)) ? 1 : 0;
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
if (isFatalError(error)) {
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
process.stderr.write(error + '\n');
|
|
159
|
+
return 1;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/** Prints what meow's `showVersion()` printed (`console.log` of the version). */
|
|
163
|
+
function showVersion() {
|
|
164
|
+
process.stdout.write(cli.pkg.version + '\n');
|
|
165
|
+
return 0;
|
|
166
|
+
}
|
|
167
|
+
/** Prints what meow's `showHelp()` printed (`console.log` of the help, which ends with a newline). */
|
|
168
|
+
function showHelp(exitCode) {
|
|
169
|
+
process.stdout.write(cli.help + '\n');
|
|
170
|
+
return exitCode;
|
|
171
|
+
}
|
package/lib/cli/output.js
CHANGED
|
@@ -28,6 +28,8 @@ export function output(results, options) {
|
|
|
28
28
|
let msg = `${out.join('\n')}\n`;
|
|
29
29
|
msg = options.color ? msg : stripAnsi(msg);
|
|
30
30
|
// If it has errors, Write to `stderr` and failure and exit.
|
|
31
|
+
// `index.ts` overwrites this `exitCode` with the verdict `command()` returns, which is the one
|
|
32
|
+
// that counts (warnings are written here too, yet only `--no-allow-warnings` fails on them).
|
|
31
33
|
if (results.violations.length > 0) {
|
|
32
34
|
process.stderr.write(msg);
|
|
33
35
|
process.exitCode = 1;
|
|
@@ -78,7 +80,7 @@ function createSummaryLines(summary) {
|
|
|
78
80
|
out.push(`${logger('✖')} ${problemCount} problem${problemCount === 1 ? '' : 's'} (${problemDetail}) in ${pluralize(summary.checkedFileCount, 'file')}`, `${pluralize(summary.checkedFileCount, 'file')} checked: ${passedFileCount} passed, ${summary.failedFileCount} failed`);
|
|
79
81
|
}
|
|
80
82
|
if (summary.skippedFileCount > 0) {
|
|
81
|
-
out.push(`${font.yellow('⚠')} ${pluralize(summary.skippedFileCount, 'file')}
|
|
83
|
+
out.push(`${font.yellow('⚠')} ${pluralize(summary.skippedFileCount, 'file')} not reported because --max-count was reached (violations in ${summary.skippedFileCount === 1 ? 'it' : 'them'} still count toward the exit code)`);
|
|
82
84
|
}
|
|
83
85
|
return out;
|
|
84
86
|
}
|
package/lib/cli/search/index.js
CHANGED
|
@@ -13,7 +13,8 @@ export async function search(files, options, selectors) {
|
|
|
13
13
|
name,
|
|
14
14
|
...createRule({
|
|
15
15
|
verify({ document }) {
|
|
16
|
-
|
|
16
|
+
// Every tree is searched: what is in the content of a `template` element is found too
|
|
17
|
+
const nodes = document.deepQuerySelectorAll(selectors);
|
|
17
18
|
locations.push(...[...nodes].map(node => {
|
|
18
19
|
return {
|
|
19
20
|
file: document.filename ?? '_NO_FILE_',
|
|
@@ -57,7 +57,7 @@ export interface PositionedNode extends ScopedNode {
|
|
|
57
57
|
* (the one `CSS.escape()` is specified by), in which a code point that is not an ident code point of CSS
|
|
58
58
|
* Syntax is escaped, so the output is always one identifier. A NUL is written as U+FFFD, as the algorithm
|
|
59
59
|
* has it, so it does not read back as NUL. A scope is a CSS selector, and CSS lets any code point be part
|
|
60
|
-
* of an identifier by escaping it,
|
|
60
|
+
* of an identifier by escaping it, even one that CSS uses in its syntax. So `Tabs.Root` (a member tag of JSX, MDX or Svelte) is `Tabs\.Root` and not the element
|
|
61
61
|
* `Tabs` with the class `Root`, `py-1.5` is `py-1\.5`, `hover:bg-x` is `hover\:bg-x`, `2xl:flex` is
|
|
62
62
|
* `\32 xl\:flex`, and a class that ends with `:nth-of-type(2)` is no position. What this writes and what
|
|
63
63
|
* {@link unescapeIdentifier} reads are one form, so a scope matches the element it was made for.
|
|
@@ -69,7 +69,12 @@ export interface PositionedNode extends ScopedNode {
|
|
|
69
69
|
* with `:nth-of-type(n)` (`div.a:nth-of-type(2)`) never matched, and is still read as a position. A `\`
|
|
70
70
|
* that such a file holds is read as CSS reads it: `\ba` is the hex escape U+00BA.
|
|
71
71
|
*
|
|
72
|
+
* CSS Syntax says of an identifier:
|
|
73
|
+
*
|
|
74
|
+
* > You can include any code point at all, even ones that CSS uses in its syntax, by escaping it.
|
|
75
|
+
*
|
|
72
76
|
* @see https://drafts.csswg.org/cssom/#serialize-an-identifier
|
|
77
|
+
* @see https://www.w3.org/TR/css-syntax-3/#syntax-description
|
|
73
78
|
*/
|
|
74
79
|
export declare function serializeIdentifier(name: string): string;
|
|
75
80
|
/**
|
|
@@ -104,7 +109,7 @@ export declare function splitScopeAttr(segment: string): {
|
|
|
104
109
|
* class `a\` at the position 2, and `a\:nth-of-type(2)` is the class `a:nth-of-type(2)`. The escapes
|
|
105
110
|
* are scanned from the start, as {@link splitScopeIdents} does.
|
|
106
111
|
*
|
|
107
|
-
* @see https://www.w3.org/TR/css-syntax-3/#
|
|
112
|
+
* @see https://www.w3.org/TR/css-syntax-3/#syntax-description
|
|
108
113
|
*/
|
|
109
114
|
export declare function splitScopeNth(segment: string): {
|
|
110
115
|
readonly base: string;
|
|
@@ -40,7 +40,7 @@ const NON_ASCII_IDENT_CODE_POINT = /^[\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u0
|
|
|
40
40
|
* (the one `CSS.escape()` is specified by), in which a code point that is not an ident code point of CSS
|
|
41
41
|
* Syntax is escaped, so the output is always one identifier. A NUL is written as U+FFFD, as the algorithm
|
|
42
42
|
* has it, so it does not read back as NUL. A scope is a CSS selector, and CSS lets any code point be part
|
|
43
|
-
* of an identifier by escaping it,
|
|
43
|
+
* of an identifier by escaping it, even one that CSS uses in its syntax. So `Tabs.Root` (a member tag of JSX, MDX or Svelte) is `Tabs\.Root` and not the element
|
|
44
44
|
* `Tabs` with the class `Root`, `py-1.5` is `py-1\.5`, `hover:bg-x` is `hover\:bg-x`, `2xl:flex` is
|
|
45
45
|
* `\32 xl\:flex`, and a class that ends with `:nth-of-type(2)` is no position. What this writes and what
|
|
46
46
|
* {@link unescapeIdentifier} reads are one form, so a scope matches the element it was made for.
|
|
@@ -52,7 +52,12 @@ const NON_ASCII_IDENT_CODE_POINT = /^[\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u0
|
|
|
52
52
|
* with `:nth-of-type(n)` (`div.a:nth-of-type(2)`) never matched, and is still read as a position. A `\`
|
|
53
53
|
* that such a file holds is read as CSS reads it: `\ba` is the hex escape U+00BA.
|
|
54
54
|
*
|
|
55
|
+
* CSS Syntax says of an identifier:
|
|
56
|
+
*
|
|
57
|
+
* > You can include any code point at all, even ones that CSS uses in its syntax, by escaping it.
|
|
58
|
+
*
|
|
55
59
|
* @see https://drafts.csswg.org/cssom/#serialize-an-identifier
|
|
60
|
+
* @see https://www.w3.org/TR/css-syntax-3/#syntax-description
|
|
56
61
|
*/
|
|
57
62
|
export function serializeIdentifier(name) {
|
|
58
63
|
let result = '';
|
|
@@ -187,7 +192,7 @@ export function splitScopeAttr(segment) {
|
|
|
187
192
|
* class `a\` at the position 2, and `a\:nth-of-type(2)` is the class `a:nth-of-type(2)`. The escapes
|
|
188
193
|
* are scanned from the start, as {@link splitScopeIdents} does.
|
|
189
194
|
*
|
|
190
|
-
* @see https://www.w3.org/TR/css-syntax-3/#
|
|
195
|
+
* @see https://www.w3.org/TR/css-syntax-3/#syntax-description
|
|
191
196
|
*/
|
|
192
197
|
export function splitScopeNth(segment) {
|
|
193
198
|
// Only an unescaped `:` is looked at: an escaped one is read with its `\` before it, as one character
|
|
@@ -12,6 +12,11 @@ export declare function resolveSuppressionsPath(customPath?: string): string;
|
|
|
12
12
|
* Reads and parses a suppressions JSON file.
|
|
13
13
|
* Returns an empty object if the file does not exist.
|
|
14
14
|
*
|
|
15
|
+
* Only the top level is checked: valid JSON that is not an object (`null`, an
|
|
16
|
+
* array, a string, a number) is not suppressions data and fails like
|
|
17
|
+
* unparsable JSON does, so that every caller gets the same readable error
|
|
18
|
+
* instead of a `TypeError` from the first `Object.keys` on it.
|
|
19
|
+
*
|
|
15
20
|
* @param filePath - Absolute path to the suppressions file.
|
|
16
21
|
* @returns The parsed suppressions data, or an empty object if the file does not exist.
|
|
17
22
|
*/
|
|
@@ -20,13 +20,22 @@ export function resolveSuppressionsPath(customPath) {
|
|
|
20
20
|
* Reads and parses a suppressions JSON file.
|
|
21
21
|
* Returns an empty object if the file does not exist.
|
|
22
22
|
*
|
|
23
|
+
* Only the top level is checked: valid JSON that is not an object (`null`, an
|
|
24
|
+
* array, a string, a number) is not suppressions data and fails like
|
|
25
|
+
* unparsable JSON does, so that every caller gets the same readable error
|
|
26
|
+
* instead of a `TypeError` from the first `Object.keys` on it.
|
|
27
|
+
*
|
|
23
28
|
* @param filePath - Absolute path to the suppressions file.
|
|
24
29
|
* @returns The parsed suppressions data, or an empty object if the file does not exist.
|
|
25
30
|
*/
|
|
26
31
|
export async function readSuppressionsFile(filePath) {
|
|
27
32
|
try {
|
|
28
33
|
const content = await fs.readFile(filePath, 'utf8');
|
|
29
|
-
|
|
34
|
+
const parsed = JSON.parse(content);
|
|
35
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
36
|
+
throw new Error(`Failed to parse suppressions file "${filePath}": the top level must be a JSON object`);
|
|
37
|
+
}
|
|
38
|
+
return parsed;
|
|
30
39
|
}
|
|
31
40
|
catch (error) {
|
|
32
41
|
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "markuplint",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.5.0",
|
|
4
4
|
"description": "An HTML linter for all markup developers",
|
|
5
5
|
"author": "Yusuke Hirao",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,20 +33,28 @@
|
|
|
33
33
|
"dev": "tsc --watch --project tsconfig.build.json",
|
|
34
34
|
"clean": "tsc --build --clean tsconfig.build.json"
|
|
35
35
|
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@markuplint/astro-parser": "5.5.0",
|
|
38
|
+
"@markuplint/jsx-parser": "5.5.0",
|
|
39
|
+
"@markuplint/mdx-parser": "5.5.0",
|
|
40
|
+
"@markuplint/pretenders": "5.5.0",
|
|
41
|
+
"@markuplint/svelte-parser": "5.5.0",
|
|
42
|
+
"@markuplint/vue-parser": "5.5.0"
|
|
43
|
+
},
|
|
36
44
|
"dependencies": {
|
|
37
45
|
"@markuplint/cli-utils": "5.3.0",
|
|
38
|
-
"@markuplint/file-resolver": "5.
|
|
39
|
-
"@markuplint/html-parser": "5.
|
|
40
|
-
"@markuplint/html-spec": "5.
|
|
41
|
-
"@markuplint/i18n": "5.
|
|
42
|
-
"@markuplint/ml-ast": "5.
|
|
43
|
-
"@markuplint/ml-config": "5.
|
|
44
|
-
"@markuplint/ml-core": "5.
|
|
45
|
-
"@markuplint/ml-spec": "5.
|
|
46
|
-
"@markuplint/rules": "5.
|
|
47
|
-
"@markuplint/shared": "5.
|
|
46
|
+
"@markuplint/file-resolver": "5.5.0",
|
|
47
|
+
"@markuplint/html-parser": "5.5.0",
|
|
48
|
+
"@markuplint/html-spec": "5.5.0",
|
|
49
|
+
"@markuplint/i18n": "5.5.0",
|
|
50
|
+
"@markuplint/ml-ast": "5.5.0",
|
|
51
|
+
"@markuplint/ml-config": "5.5.0",
|
|
52
|
+
"@markuplint/ml-core": "5.5.0",
|
|
53
|
+
"@markuplint/ml-spec": "5.5.0",
|
|
54
|
+
"@markuplint/rules": "5.5.0",
|
|
55
|
+
"@markuplint/shared": "5.5.0",
|
|
48
56
|
"@types/debug": "^4.1.13",
|
|
49
|
-
"chokidar": "^
|
|
57
|
+
"chokidar": "^6.0.0",
|
|
50
58
|
"debug": "^4.4.3",
|
|
51
59
|
"meow": "^14.1.0",
|
|
52
60
|
"os-locale": "^8.0.0",
|
|
@@ -54,5 +62,33 @@
|
|
|
54
62
|
"strip-ansi": "^7.2.0",
|
|
55
63
|
"type-fest": "^5.10.0"
|
|
56
64
|
},
|
|
57
|
-
"
|
|
65
|
+
"peerDependencies": {
|
|
66
|
+
"@markuplint/astro-parser": "^5.4.0",
|
|
67
|
+
"@markuplint/jsx-parser": "^5.4.0",
|
|
68
|
+
"@markuplint/mdx-parser": "^5.4.0",
|
|
69
|
+
"@markuplint/pretenders": "^5.4.0",
|
|
70
|
+
"@markuplint/svelte-parser": "^5.4.0",
|
|
71
|
+
"@markuplint/vue-parser": "^5.4.0"
|
|
72
|
+
},
|
|
73
|
+
"peerDependenciesMeta": {
|
|
74
|
+
"@markuplint/astro-parser": {
|
|
75
|
+
"optional": true
|
|
76
|
+
},
|
|
77
|
+
"@markuplint/jsx-parser": {
|
|
78
|
+
"optional": true
|
|
79
|
+
},
|
|
80
|
+
"@markuplint/mdx-parser": {
|
|
81
|
+
"optional": true
|
|
82
|
+
},
|
|
83
|
+
"@markuplint/pretenders": {
|
|
84
|
+
"optional": true
|
|
85
|
+
},
|
|
86
|
+
"@markuplint/svelte-parser": {
|
|
87
|
+
"optional": true
|
|
88
|
+
},
|
|
89
|
+
"@markuplint/vue-parser": {
|
|
90
|
+
"optional": true
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
"gitHead": "fedebcfd56eaec4642e68643b2f9e10750d810e1"
|
|
58
94
|
}
|