markuplint 4.0.0-dev.0 → 4.0.0-dev.12

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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2017-2023 Yusuke Hirao
3
+ Copyright (c) 2017-2024 Yusuke Hirao
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -86,7 +86,6 @@ Options
86
86
  --include-node-modules Include files in node_modules directory. Default: false.
87
87
 
88
88
  --init Initialize settings interactively.
89
- --create-rule Add the scaffold of a custom rule.
90
89
 
91
90
  --help, -h Show help.
92
91
  --version, -v Show version.
@@ -126,6 +125,8 @@ Need [Sponsors❤️‍🔥](https://github.com/sponsors/markuplint)
126
125
  [<img width="36" src="https://avatars.githubusercontent.com/u/6581173" alt="miita" />](https://github.com/mikimhk)
127
126
  [<img width="36" src="https://avatars.githubusercontent.com/u/111797" alt="Yasuo Fukuda" />](https://github.com/sigwyg)
128
127
  [<img width="36" src="https://avatars.githubusercontent.com/u/802921" alt="Hideyuki Saito" />](https://github.com/hideyukisaito)
128
+ [<img width="36" src="https://avatars.githubusercontent.com/u/3056064" alt="tamshow" />](https://github.com/tamshow)
129
+ [<img width="36" src="https://avatars.githubusercontent.com/u/91047157" alt="shamokit" />](https://github.com/shamokit)
129
130
 
130
131
  ## Thanks
131
132
 
@@ -81,7 +81,7 @@ export class MLEngine extends Emitter {
81
81
  throw error;
82
82
  });
83
83
  const sourceCode = await __classPrivateFieldGet(this, _MLEngine_file, "f").getCode();
84
- const fixedCode = core.document.toString();
84
+ const fixedCode = core.document.toString(true);
85
85
  if (violations instanceof Error) {
86
86
  this.emit('lint-error', __classPrivateFieldGet(this, _MLEngine_file, "f").path, sourceCode, violations);
87
87
  const errMessage = violations.stack ?? violations.message;
@@ -1,5 +1,5 @@
1
1
  import type { ReadonlyDeep } from 'type-fest';
2
- export declare const help = "\nUsage\n\t$ markuplint <HTML file paths (glob format)>\n\t$ <stdout> | markuplint\n\nOptions\n\t--config, -c FILE_PATH A configuration file path.\n\t--fix, Fix HTML.\n\t--format, -f FORMAT Output format. Support \"JSON\", \"Simple\", \"GitHub\" and \"Standard\". Default: \"Standard\".\n\t--no-search-config No search a configure file automatically.\n\t--ignore-ext Evaluate files that are received even though the type of extension.\n\t--no-import-preset-rules No import preset rules.\n\t--locale Locale of the message of violation. Default is an OS setting.\n\t--no-color, Output no color.\n\t--problem-only, -p Output only problems, without passeds.\n\t--allow-warnings Return status code 0 even if there are warnings.\n\t--allow-empty-input Return status code 1 even if there are no input files.\n\t--verbose Output with detailed information.\n\t--include-node-modules Include files in node_modules directory. Default: false.\n\n\t--init Initialize settings interactively.\n\t--create-rule Add the scaffold of a custom rule.\n\t--search Search lines of codes that include the target element by selectors.\n\n\t--help, -h Show help.\n\t--version, -v Show version.\n\nExamples\n\t$ markuplint verifyee.html --config path/to/.markuplintrc\n\t$ cat verifyee.html | markuplint\n";
2
+ export declare const help = "\nUsage\n\t$ markuplint <HTML file paths (glob format)>\n\t$ <stdout> | markuplint\n\nOptions\n\t--config, -c FILE_PATH A configuration file path.\n\t--fix, Fix HTML.\n\t--format, -f FORMAT Output format. Support \"JSON\", \"Simple\", \"GitHub\" and \"Standard\". Default: \"Standard\".\n\t--no-search-config No search a configure file automatically.\n\t--ignore-ext Evaluate files that are received even though the type of extension.\n\t--no-import-preset-rules No import preset rules.\n\t--locale Locale of the message of violation. Default is an OS setting.\n\t--no-color, Output no color.\n\t--problem-only, -p Output only problems, without passeds.\n\t--allow-warnings Return status code 0 even if there are warnings.\n\t--allow-empty-input Return status code 1 even if there are no input files.\n\t--verbose Output with detailed information.\n\t--include-node-modules Include files in node_modules directory. Default: false.\n\n\t--init Initialize settings interactively.\n\t--search Search lines of codes that include the target element by selectors.\n\n\t--help, -h Show help.\n\t--version, -v Show version.\n\nExamples\n\t$ markuplint verifyee.html --config path/to/.markuplintrc\n\t$ cat verifyee.html | markuplint\n";
3
3
  export declare const cli: import("meow").Result<{
4
4
  config: {
5
5
  type: "string";
@@ -20,7 +20,6 @@ Options
20
20
  --include-node-modules Include files in node_modules directory. Default: false.
21
21
 
22
22
  --init Initialize settings interactively.
23
- --create-rule Add the scaffold of a custom rule.
24
23
  --search Search lines of codes that include the target element by selectors.
25
24
 
26
25
  --help, -h Show help.
@@ -50,19 +50,16 @@ export async function command(files, options, apiOptions) {
50
50
  if (fix) {
51
51
  log('Overwrite file: %s', result.filePath);
52
52
  await fs.writeFile(result.filePath, result.fixedCode, { encoding: 'utf8' });
53
- process.stdout.write(`markuplint: Fix "${result.filePath}"\n`);
54
53
  }
55
- else {
56
- if (format === 'json') {
57
- jsonOutput.push(...result.violations.map(v => ({
58
- ...v,
59
- filePath: result.filePath,
60
- })));
61
- continue;
62
- }
63
- log('Output reports');
64
- output(result, options);
54
+ if (format === 'json') {
55
+ jsonOutput.push(...result.violations.map(v => ({
56
+ ...v,
57
+ filePath: result.filePath,
58
+ })));
59
+ continue;
65
60
  }
61
+ log('Output reports');
62
+ output(result, options);
66
63
  }
67
64
  if (format === 'json') {
68
65
  process.stdout.write(JSON.stringify(jsonOutput, null, 2) + '\n');
package/lib/cli/index.js CHANGED
@@ -2,7 +2,6 @@ import getStdin from 'get-stdin';
2
2
  import { verbosely } from '../debug.js';
3
3
  import { cli } from './bootstrap.js';
4
4
  import { command } from './command.js';
5
- import { createRule } from './create-rule/index.js';
6
5
  import { initialize } from './init/index.js';
7
6
  import { search } from './search/index.js';
8
7
  /* eslint-disable unicorn/no-process-exit */
@@ -23,11 +22,8 @@ if (cli.flags.init) {
23
22
  process.exit(0);
24
23
  }
25
24
  if (cli.flags.createRule) {
26
- await createRule().catch(error => {
27
- process.stderr.write(error + '\n');
28
- process.exit(1);
29
- });
30
- process.exit(0);
25
+ process.stderr.write("Use to run 'npx @markuplint/create-rule' instead of 'markuplint --create-rule'.\n");
26
+ process.exit(1);
31
27
  }
32
28
  const files = cli.input;
33
29
  if (files.length > 0) {
@@ -2,6 +2,7 @@ const extRExp = {
2
2
  jsx: '\\.[jt]sx?$',
3
3
  vue: '\\.vue$',
4
4
  svelte: '\\.svelte$',
5
+ sveltekit: '\\.html$',
5
6
  astro: '\\.astro$',
6
7
  pug: '\\.pug$',
7
8
  php: '\\.php$',
@@ -16,6 +17,7 @@ export const langs = {
16
17
  jsx: 'React (JSX)',
17
18
  vue: 'Vue',
18
19
  svelte: 'Svelte',
20
+ sveltekit: 'SvelteKit',
19
21
  astro: 'Astro',
20
22
  pug: 'Pug',
21
23
  php: 'PHP',
@@ -34,7 +36,15 @@ export function createConfig(langs, mode, defaultRules) {
34
36
  if (!ext) {
35
37
  continue;
36
38
  }
37
- parser[ext] = `@markuplint/${lang}-parser`;
39
+ switch (lang) {
40
+ case 'sveltekit': {
41
+ parser[ext] = '@markuplint/svelte-parser/kit';
42
+ break;
43
+ }
44
+ default: {
45
+ parser[ext] = `@markuplint/${lang}-parser`;
46
+ }
47
+ }
38
48
  if (lang === 'vue') {
39
49
  config = {
40
50
  ...config,
@@ -1,3 +1,3 @@
1
- export declare function getDefaultRules(version: string): Promise<{
1
+ export declare function getDefaultRules(): {
2
2
  [x: string]: import("./types.js").Rule;
3
- }>;
3
+ };
@@ -1,52 +1,16 @@
1
- import matter from 'gray-matter';
2
- import fetch from 'node-fetch';
3
- const RULES_SCHEMA_URL = 'https://raw.githubusercontent.com/markuplint/markuplint/[VERSION]/packages/@markuplint/rules/schema.json';
4
- const RULES_README_URL = 'https://raw.githubusercontent.com/markuplint/markuplint/[VERSION]/packages/@markuplint/rules/src/[NAME]/README.md';
5
- export async function getDefaultRules(version) {
6
- const json = await safeFetch(RULES_SCHEMA_URL, version);
1
+ import builtinRules from '@markuplint/rules';
2
+ export function getDefaultRules() {
7
3
  const rules = {};
8
- await Promise.all(Object.entries(json.definitions.rules.properties).map(async ([name, rule]) => {
9
- const json = await safeFetch(rule.$ref.replace('/main/', '/[VERSION]/'), version);
10
- let severity = (Array.isArray(json.oneOf) ? json.oneOf : []).find((val) => val.properties)?.properties
11
- ?.severity?.default;
12
- let category = json._category;
13
- if (severity == null || category == null) {
14
- const data = await getCatAndSeverityFromLegacy(version, name);
15
- severity = data.severity;
16
- category = data.category;
4
+ for (const [ruleName, rule] of Object.entries(builtinRules)) {
5
+ const defaultSeverity = rule.defaultSeverity;
6
+ const defaultValue = defaultSeverity === 'warning' ? false : rule.defaultValue ?? true;
7
+ const category = rule.meta?.category;
8
+ if (category) {
9
+ rules[ruleName] = {
10
+ defaultValue,
11
+ category,
12
+ };
17
13
  }
18
- const defaultValue = severity === 'warning' ? false : json.definitions.value.default ?? true;
19
- rules[name] = {
20
- defaultValue,
21
- category,
22
- };
23
- }));
24
- return rules;
25
- }
26
- async function safeFetch(baseUrl, version, type = 'json') {
27
- const url = baseUrl.replace('[VERSION]', `v${version}`);
28
- const res = await fetch(url);
29
- if (!res.ok) {
30
- return safeFetch(baseUrl, '3.0.0', type);
31
- }
32
- if (type === 'json') {
33
- return (await res.json());
34
14
  }
35
- const md = await res.text();
36
- const { data } = matter(md);
37
- return data;
38
- }
39
- /**
40
- * Fallback fetching until 3.0.x
41
- *
42
- * @param version
43
- * @param name
44
- * @returns
45
- */
46
- async function getCatAndSeverityFromLegacy(version, name) {
47
- const data = await safeFetch(RULES_README_URL.replace('[NAME]', name), version, 'md');
48
- return {
49
- category: data.category,
50
- severity: data.severity,
51
- };
15
+ return rules;
52
16
  }
@@ -1,14 +1,9 @@
1
- import fs from 'node:fs';
2
- import module from 'node:module';
1
+ import fs from 'node:fs/promises';
3
2
  import path from 'node:path';
4
- import util from 'node:util';
5
- import { head, write, error } from '../../util.js';
6
- import { confirm, confirmSequence, multiSelect } from '../prompt.js';
3
+ import { installModule, multiSelect, confirm, confirmSequence, header } from '@markuplint/cli-utils';
7
4
  import { createConfig, langs } from './create-config.js';
8
5
  import { getDefaultRules } from './get-default-rules.js';
9
- import { installModule, selectModules } from './install-module.js';
10
- const require = module.createRequire(import.meta.url);
11
- const writeFile = util.promisify(fs.writeFile);
6
+ import { selectModules } from './select-modules.js';
12
7
  const ruleCategories = {
13
8
  validation: {
14
9
  message: 'Are you going to conformance check according to HTML standard?',
@@ -27,8 +22,9 @@ const ruleCategories = {
27
22
  },
28
23
  };
29
24
  export async function initialize() {
30
- write(head('Initialization'));
31
- write.break();
25
+ process.stdout.write(header('Initialization'));
26
+ process.stdout.write('\n');
27
+ process.stdout.write('\n');
32
28
  const selectedLangs = await multiSelect({
33
29
  message: 'Which do you use template engines?',
34
30
  choices: Object.entries(langs).map(([key, name]) => ({ name, value: key })),
@@ -54,26 +50,25 @@ export async function initialize() {
54
50
  }
55
51
  let defaultRules = {};
56
52
  if (ruleSettingMode !== 'recommended') {
57
- const rulesVersion = require('../../../package.json').version;
58
- defaultRules = await getDefaultRules(rulesVersion);
53
+ defaultRules = getDefaultRules();
59
54
  }
60
55
  const config = createConfig(selectedLangs, ruleSettingMode, defaultRules);
61
56
  const filePath = path.resolve(process.cwd(), '.markuplintrc');
62
- await writeFile(filePath, JSON.stringify(config, null, 2), { encoding: 'utf8' });
63
- write(`✨Created: ${filePath}`);
57
+ await fs.writeFile(filePath, JSON.stringify(config, null, 2), { encoding: 'utf8' });
58
+ process.stdout.write(`✨Created: ${filePath}\n`);
64
59
  if (autoInstall) {
65
- write('Install automatically');
60
+ process.stdout.write('Install automatically\n');
66
61
  const modules = selectModules(selectedLangs);
67
62
  const result = await installModule(modules, true).catch(error_ => new Error(error_));
68
63
  if (result instanceof Error) {
69
- error.exit();
70
- return;
64
+ // eslint-disable-next-line unicorn/no-process-exit
65
+ process.exit(1);
71
66
  }
72
67
  if (result.alreadyExists) {
73
- write('Modules are installed already.');
68
+ process.stdout.write('Modules are installed already.\n');
74
69
  }
75
70
  else {
76
- write('✨ Success');
71
+ process.stdout.write('✨ Success\n');
77
72
  }
78
73
  }
79
74
  }
@@ -0,0 +1,2 @@
1
+ import type { Langs } from './types.js';
2
+ export declare function selectModules(selectedLangs: readonly Langs[]): string[];
@@ -0,0 +1,10 @@
1
+ export function selectModules(selectedLangs) {
2
+ const modules = ['markuplint', ...selectedLangs.map(lang => `@markuplint/${lang}-parser`)];
3
+ if (selectedLangs.includes('vue')) {
4
+ modules.push('@markuplint/vue-spec');
5
+ }
6
+ if (selectedLangs.includes('jsx')) {
7
+ modules.push('@markuplint/react-spec');
8
+ }
9
+ return modules;
10
+ }
@@ -1,8 +1,9 @@
1
- export type Langs = 'jsx' | 'vue' | 'svelte' | 'astro' | 'pug' | 'php' | 'smarty' | 'erb' | 'ejs' | 'mustache' | 'nunjucks' | 'liquid';
1
+ import type { RuleConfigValue } from '@markuplint/ml-config';
2
+ export type Langs = 'jsx' | 'vue' | 'svelte' | 'sveltekit' | 'astro' | 'pug' | 'php' | 'smarty' | 'erb' | 'ejs' | 'mustache' | 'nunjucks' | 'liquid';
2
3
  export type Category = 'validation' | 'a11y' | 'naming-convention' | 'style' | 'maintainability';
3
4
  export type RuleSettingMode = readonly Category[] | 'recommended' | 'none';
4
5
  export type DefaultRules = Readonly<Record<string, Rule>>;
5
6
  export type Rule = {
6
7
  readonly category: Category;
7
- readonly defaultValue: boolean | string | number;
8
+ readonly defaultValue: RuleConfigValue;
8
9
  };
@@ -1,4 +1,4 @@
1
- import { messageToString } from '../util.js';
1
+ import { messageToString } from '@markuplint/cli-utils';
2
2
  export function githubReporter(results) {
3
3
  const out = [];
4
4
  for (const violation of results.violations) {
@@ -1,7 +1,7 @@
1
- import c from 'cli-color';
2
- import { markuplint, messageToString, p, w } from '../util.js';
3
- const loggerError = c.red;
4
- const loggerWarning = c.xterm(208);
1
+ import { name, font, pad, getWidth, messageToString } from '@markuplint/cli-utils';
2
+ const commandName = name.toLowerCase();
3
+ const loggerError = font.red;
4
+ const loggerWarning = font.xterm(208);
5
5
  export function simpleReporter(results, options) {
6
6
  const sizes = {
7
7
  line: 0,
@@ -12,19 +12,19 @@ export function simpleReporter(results, options) {
12
12
  sizes.line = Math.max(sizes.line, violation.line.toString(10).length);
13
13
  sizes.col = Math.max(sizes.col, violation.col.toString(10).length);
14
14
  const meg = messageToString(violation.message, violation.reason);
15
- sizes.meg = Math.max(sizes.meg, w(meg));
15
+ sizes.meg = Math.max(sizes.meg, getWidth(meg));
16
16
  }
17
17
  const out = [];
18
18
  if (results.violations.length > 0) {
19
- out.push(`<${markuplint}> ${c.underline(results.filePath)}: ${loggerError('✗')}`);
19
+ out.push(`<${commandName}> ${font.underline(results.filePath)}: ${loggerError('✗')}`);
20
20
  for (const violation of results.violations) {
21
21
  const s = violation.severity === 'error' ? loggerError('✖') : loggerWarning('⚠️');
22
22
  const meg = messageToString(violation.message, violation.reason);
23
- out.push(` ${c.cyan(`${p(violation.line, sizes.line, true)}:${p(violation.col, sizes.col)}`)} ${s} ${p(meg, sizes.meg)} ${c.xterm(8)(violation.ruleId)} `);
23
+ out.push(` ${font.cyan(`${pad(violation.line, sizes.line, true)}:${pad(violation.col, sizes.col)}`)} ${s} ${pad(meg, sizes.meg)} ${font.xterm(8)(violation.ruleId)} `);
24
24
  }
25
25
  }
26
26
  else if (!options.problemOnly) {
27
- out.push(`<${markuplint}> ${c.underline(results.filePath)}: ${c.green('✓')}`);
27
+ out.push(`<${commandName}> ${font.underline(results.filePath)}: ${font.green('✓')}`);
28
28
  }
29
29
  return out;
30
30
  }
@@ -1,7 +1,7 @@
1
- import c from 'cli-color';
2
- import { invisibleSpace, markuplint, messageToString, p, space, w } from '../util.js';
3
- const loggerError = c.red;
4
- const loggerWarning = c.xterm(208);
1
+ import { messageToString, font, name, invisibleSpace, space, pad, getWidth } from '@markuplint/cli-utils';
2
+ const commandName = name.toLowerCase();
3
+ const loggerError = font.red;
4
+ const loggerWarning = font.xterm(208);
5
5
  export function standardReporter(results, options) {
6
6
  const sizes = {
7
7
  line: 0,
@@ -12,32 +12,47 @@ export function standardReporter(results, options) {
12
12
  sizes.line = Math.max(sizes.line, violation.line.toString(10).length);
13
13
  sizes.col = Math.max(sizes.col, violation.col.toString(10).length);
14
14
  const meg = messageToString(violation.message, violation.reason);
15
- sizes.meg = Math.max(sizes.meg, w(meg));
15
+ sizes.meg = Math.max(sizes.meg, getWidth(meg));
16
16
  }
17
17
  const out = [];
18
18
  if (results.violations.length > 0) {
19
19
  const lines = results.sourceCode.split(/\r?\n/);
20
20
  for (const violation of results.violations) {
21
- const prev = lines[violation.line - 2] ?? '';
22
- const line = lines[violation.line - 1] ?? '';
23
- const next = lines[violation.line - 0] ?? '';
24
- const before = line.slice(0, Math.max(0, violation.col - 1));
25
- const after = line.slice(Math.max(0, violation.col - 1 + violation.raw.length));
26
21
  const logger = violation.severity === 'error' ? loggerError : loggerWarning;
27
22
  const meg = messageToString(violation.message, violation.reason);
28
- out.push(`<${markuplint}> ${logger(`${violation.severity}: ${meg} (${violation.ruleId}) ${c.underline(`${results.filePath}:${violation.line}:${violation.col}`)}`)}`);
29
- if (violation.line - 1 > 0) {
30
- out.push(` ${c.cyan(p(violation.line - 1, sizes.col, true))}: ${space(prev)}`);
23
+ const startLine = violation.line - 1;
24
+ // Main message
25
+ out.push(`<${commandName}> ${logger(`${violation.severity}: ${meg} (${violation.ruleId}) ${font.underline(`${results.filePath}:${violation.line}:${violation.col}`)}`)}`);
26
+ // Previous line
27
+ if (startLine > 0) {
28
+ const prev = lines[startLine - 1] ?? '';
29
+ out.push(` ${font.cyan(pad(startLine, sizes.col, true))}: ${space(prev)}`);
31
30
  }
32
- out.push(` ${c.cyan(p(violation.line, sizes.col, true))}: ${space(before)}${c.bgRed(violation.raw)}${space(after)}`);
33
- if (!options.color) {
34
- out.push(` ${invisibleSpace(before)}${'^'.repeat(violation.raw.length)}${invisibleSpace(after)}`);
31
+ // Current line
32
+ const rawLines = violation.raw.split(/\r?\n/);
33
+ const line = lines[startLine] ?? '';
34
+ for (const [i, rawLine] of rawLines.entries()) {
35
+ const currentLine = lines[startLine + i] ?? '';
36
+ const beforeChars = i === 0 ? line.slice(0, Math.max(0, violation.col - 1)) : rawLine.match(/^\s+/)?.[0] ?? '';
37
+ const codeChars = rawLine.trim();
38
+ const afterChars = currentLine.slice(Math.max(0, beforeChars.length + codeChars.length));
39
+ const lineNoChars = pad(violation.line + i, sizes.col, true);
40
+ const lineNo = font.cyan(lineNoChars);
41
+ const before = i === 0 ? space(beforeChars) : font.bgRed(space(beforeChars));
42
+ const code = font.bgRed(codeChars);
43
+ const after = space(afterChars);
44
+ out.push(` ${lineNo}: ${before}${code}${space(after)}`);
45
+ if (!options.color) {
46
+ out.push(` ${invisibleSpace(lineNoChars + ': ' + beforeChars)}${'^'.repeat(codeChars.length)}${invisibleSpace(afterChars)}`);
47
+ }
35
48
  }
36
- out.push(` ${c.cyan(p(violation.line + 1, sizes.col, true))}: ${space(next)}`);
49
+ // Next line
50
+ const next = lines[startLine + rawLines.length] ?? '';
51
+ out.push(` ${font.cyan(pad(startLine + rawLines.length + 1, sizes.col, true))}: ${space(next)}`);
37
52
  }
38
53
  }
39
54
  else if (!options.problemOnly) {
40
- out.push(`<${markuplint}> ${c.green('passed')} ${c.underline(results.filePath)}`);
55
+ out.push(`<${commandName}> ${font.green('passed')} ${font.underline(results.filePath)}`);
41
56
  }
42
57
  return out;
43
58
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "markuplint",
3
- "version": "4.0.0-dev.0+7c596917",
3
+ "version": "4.0.0-dev.12+2275fbeb0",
4
4
  "description": "An HTML linter for all markup developers",
5
5
  "author": "Yusuke Hirao",
6
6
  "license": "MIT",
@@ -25,36 +25,26 @@
25
25
  "clean": "tsc --build --clean"
26
26
  },
27
27
  "dependencies": {
28
- "@markuplint/create-rule-helper": "4.0.0-dev.0+7c596917",
29
- "@markuplint/file-resolver": "4.0.0-dev.0+7c596917",
30
- "@markuplint/html-parser": "4.0.0-dev.0+7c596917",
31
- "@markuplint/html-spec": "4.0.0-dev.0+7c596917",
32
- "@markuplint/i18n": "4.0.0-dev.0+7c596917",
33
- "@markuplint/ml-ast": "4.0.0-dev.0+7c596917",
34
- "@markuplint/ml-config": "4.0.0-dev.0+7c596917",
35
- "@markuplint/ml-core": "4.0.0-dev.0+7c596917",
36
- "@markuplint/ml-spec": "4.0.0-dev.0+7c596917",
37
- "@markuplint/rules": "4.0.0-dev.0+7c596917",
38
- "@markuplint/shared": "4.0.0-dev.0+7c596917",
39
- "@types/cli-color": "^2.0.6",
28
+ "@markuplint/cli-utils": "4.0.0-dev.12+2275fbeb0",
29
+ "@markuplint/file-resolver": "4.0.0-dev.12+2275fbeb0",
30
+ "@markuplint/html-parser": "4.0.0-dev.12+2275fbeb0",
31
+ "@markuplint/html-spec": "4.0.0-dev.12+2275fbeb0",
32
+ "@markuplint/i18n": "4.0.0-dev.12+2275fbeb0",
33
+ "@markuplint/ml-ast": "4.0.0-dev.12+2275fbeb0",
34
+ "@markuplint/ml-config": "4.0.0-dev.12+2275fbeb0",
35
+ "@markuplint/ml-core": "4.0.0-dev.12+2275fbeb0",
36
+ "@markuplint/ml-spec": "4.0.0-dev.12+2275fbeb0",
37
+ "@markuplint/rules": "4.0.0-dev.12+2275fbeb0",
38
+ "@markuplint/shared": "4.0.0-dev.12+2275fbeb0",
40
39
  "@types/debug": "^4.1.12",
41
- "@types/uuid": "^9.0.7",
42
40
  "chokidar": "^3.5.3",
43
- "cli-color": "^2.0.3",
44
41
  "debug": "^4.3.4",
45
- "detect-installed": "^2.0.4",
46
- "eastasianwidth": "^0.2.0",
47
- "enquirer": "^2.4.1",
48
42
  "get-stdin": "^9.0.0",
49
- "gray-matter": "^4.0.3",
50
- "has-yarn": "^3.0.0",
51
- "meow": "^12.1.1",
52
- "node-fetch": "^3.3.2",
43
+ "meow": "^13.1.0",
53
44
  "os-locale": "^6.0.2",
54
45
  "strict-event-emitter": "^0.5.1",
55
46
  "strip-ansi": "^7.1.0",
56
- "type-fest": "^4.8.2",
57
- "uuid": "^9.0.1"
47
+ "type-fest": "^4.9.0"
58
48
  },
59
- "gitHead": "7c59691701465a0fb3a4b69187318e8033c463d4"
49
+ "gitHead": "2275fbeb053605b636f080f4fafd7cd4fc57a9a3"
60
50
  }
@@ -1 +0,0 @@
1
- export declare function createRule(): Promise<void>;
@@ -1,79 +0,0 @@
1
- import { resolve } from 'node:path';
2
- import { isMarkuplintRepo, createRuleHelper } from '@markuplint/create-rule-helper';
3
- import c from 'cli-color';
4
- import { write, head } from '../../util.js';
5
- import { installModule } from '../init/install-module.js';
6
- import { input, select, confirm } from '../prompt.js';
7
- const icons = {
8
- README: '📝',
9
- index: '📜',
10
- schema: '⚙️ ',
11
- package: '🎁',
12
- tsconfig: '💎',
13
- };
14
- export async function createRule() {
15
- write(head('Create a rule'));
16
- write.break();
17
- const firstChoices = [
18
- { name: 'Add the rule to this project', value: 'ADD_TO_PROJECT' },
19
- { name: 'Create the rule and publish it as a package', value: 'PUBLISH_AS_PACKAGE' },
20
- ];
21
- if (await isMarkuplintRepo()) {
22
- firstChoices.push({ name: 'Contribute the new rule to markuplint core rules', value: 'CONTRIBUTE_TO_CORE' });
23
- }
24
- const purpose = await select({
25
- message: 'What purpose do you create the rule for?',
26
- choices: firstChoices,
27
- });
28
- const dirQuestion = purpose === 'ADD_TO_PROJECT' ? 'What is the directory name?' : 'What is the plugin name?';
29
- const pluginName = purpose === 'CONTRIBUTE_TO_CORE' ? '' : await input(dirQuestion, /^[a-z][\da-z]*(?:-[a-z][\da-z]*)*$/i);
30
- const ruleName = await input('What is the rule name?', /^[a-z][\da-z]*(?:-[a-z][\da-z]*)*$/i);
31
- const core = purpose === 'CONTRIBUTE_TO_CORE'
32
- ? {
33
- description: await input('Description:'),
34
- category: await select({
35
- message: 'Category:',
36
- choices: [
37
- { name: 'Conformance checking', value: 'validation' },
38
- { name: 'Accessibility', value: 'a11y' },
39
- { name: 'Naming Convention', value: 'naming-convention' },
40
- { name: 'Maintainability', value: 'maintainability' },
41
- { name: 'Style', value: 'style' },
42
- ],
43
- }),
44
- severity: await select({
45
- message: 'Severity:',
46
- choices: [
47
- { name: 'error', value: 'error' },
48
- { name: 'warning', value: 'warning' },
49
- ],
50
- }),
51
- }
52
- : undefined;
53
- const lang = purpose === 'CONTRIBUTE_TO_CORE'
54
- ? 'TYPESCRIPT'
55
- : await select({
56
- message: 'Which language will you implement?',
57
- choices: [
58
- { name: 'TypeScript', value: 'TYPESCRIPT' },
59
- { name: 'JavaScript', value: 'JAVASCRIPT' },
60
- ],
61
- });
62
- const needTest = purpose === 'CONTRIBUTE_TO_CORE' ? true : await confirm('Do you need the test?', { initial: true });
63
- const result = await createRuleHelper({ purpose, pluginName, ruleName, lang, needTest, core });
64
- for (const file of result.files) {
65
- output(pluginName || 'core', file.test ? '🖍 ' : icons[file.name] ?? '🛡 ', file.fileName, resolve(file.destDir, file.fileName + file.ext));
66
- }
67
- if (result.dependencies.length > 0) {
68
- await installModule(result.dependencies);
69
- }
70
- if (result.devDependencies.length > 0) {
71
- await installModule(result.devDependencies, true);
72
- }
73
- }
74
- function output(name, icon, title, path) {
75
- const _marker = c.xterm(39)('✔') + ' ';
76
- const _title = (icon, title) => `${icon} ` + c.bold(`${name}/${title}`);
77
- const _file = (path) => ' ' + c.cyanBright(path);
78
- write(_marker + _title(icon, title) + _file(path));
79
- }
@@ -1,7 +0,0 @@
1
- import type { Langs } from './types.js';
2
- export type InstallModuleResult = {
3
- success: boolean;
4
- alreadyExists: boolean;
5
- };
6
- export declare function selectModules(selectedLangs: readonly Langs[]): string[];
7
- export declare function installModule(module: readonly string[], dev?: boolean): Promise<InstallModuleResult>;
@@ -1,74 +0,0 @@
1
- import { spawnSync } from 'node:child_process';
2
- import c from 'cli-color';
3
- // @ts-ignore
4
- import detectInstalled from 'detect-installed';
5
- import hasYarn from 'has-yarn';
6
- export function selectModules(selectedLangs) {
7
- const modules = ['markuplint', ...selectedLangs.map(lang => `@markuplint/${lang}-parser`)];
8
- if (selectedLangs.includes('vue')) {
9
- modules.push('@markuplint/vue-spec');
10
- }
11
- if (selectedLangs.includes('jsx')) {
12
- modules.push('@markuplint/react-spec');
13
- }
14
- return modules;
15
- }
16
- export async function installModule(module, dev = false) {
17
- module = module.map(m => m.trim());
18
- const uninstallMods = [];
19
- try {
20
- for (const mod of module) {
21
- const installed = await isInstalled(mod);
22
- if (!installed) {
23
- uninstallMods.push(mod);
24
- }
25
- }
26
- }
27
- catch {
28
- // void
29
- }
30
- if (uninstallMods.length === 0) {
31
- return {
32
- success: true,
33
- alreadyExists: true,
34
- };
35
- }
36
- const mod = hasYarn() ? 'yarn' : 'npm';
37
- const installOpt = hasYarn() ? 'add' : 'install';
38
- const opt = [installOpt];
39
- if (dev) {
40
- opt.push('-D');
41
- }
42
- if (!hasYarn()) {
43
- opt.push('--legacy-peer-deps');
44
- }
45
- opt.push(...uninstallMods);
46
- return new Promise((resolve, reject) => {
47
- process.stdout.write(c.blackBright(`${mod} ${opt.join(' ')}\n`));
48
- const result = spawnSync(mod, opt, { stdio: 'inherit' });
49
- if (result.error || result.status !== 0) {
50
- const message = 'Error running command.';
51
- const error = new Error(message);
52
- error.stack = message;
53
- reject(error);
54
- }
55
- resolve({
56
- success: true,
57
- alreadyExists: false,
58
- });
59
- });
60
- }
61
- function isInstalled(module) {
62
- return new Promise((resolve, reject) => {
63
- try {
64
- detectInstalled(module, {
65
- local: true,
66
- }).then((exists) => {
67
- resolve(exists);
68
- });
69
- }
70
- catch (error) {
71
- reject(error);
72
- }
73
- });
74
- }
@@ -1,18 +0,0 @@
1
- type SelectQuestion<T> = {
2
- readonly message: string;
3
- readonly choices: readonly {
4
- readonly name: string;
5
- readonly value: T;
6
- }[];
7
- };
8
- export declare function select<T>(question: SelectQuestion<T>): Promise<T>;
9
- export declare function multiSelect<T>(question: SelectQuestion<T>): Promise<T[]>;
10
- export declare function input<T extends string = string>(question: string, validation?: Readonly<RegExp>): Promise<T>;
11
- export declare function confirm(question: string, options?: {
12
- readonly initial?: boolean;
13
- }): Promise<boolean>;
14
- export declare function confirmSequence<T extends string = string>(questions: readonly {
15
- readonly message: string;
16
- readonly name: T;
17
- }[]): Promise<Record<T, boolean>>;
18
- export {};
package/lib/cli/prompt.js DELETED
@@ -1,67 +0,0 @@
1
- import c from 'cli-color';
2
- import Enquirer from 'enquirer';
3
- export async function select(question) {
4
- const res = await Enquirer.prompt({
5
- ...question,
6
- name: '__Q__',
7
- type: 'select',
8
- result(resName) {
9
- // @ts-ignore
10
- return this.options.choices.find(c => c.name === resName)?.value;
11
- },
12
- });
13
- // @ts-ignore
14
- return res['__Q__'];
15
- }
16
- export async function multiSelect(question) {
17
- const res = await Enquirer.prompt({
18
- ...question,
19
- name: '__Q__',
20
- type: 'multiselect',
21
- result(names) {
22
- // @ts-ignore
23
- const map = this.map(names);
24
- // @ts-ignore
25
- const values = names.map(name => map[name]);
26
- return values;
27
- },
28
- });
29
- // @ts-ignore
30
- return res['__Q__'];
31
- }
32
- export async function input(question, validation) {
33
- // eslint-disable-next-line no-constant-condition
34
- while (true) {
35
- const _res = await Enquirer.prompt({
36
- message: question,
37
- name: '__Q__',
38
- type: 'input',
39
- });
40
- // @ts-ignore
41
- const res = _res['__Q__'];
42
- if (validation && !validation.test(res)) {
43
- process.stdout.write(c.yellow('Oops! The name that you type is an invalid format.\n'));
44
- continue;
45
- }
46
- return res;
47
- }
48
- }
49
- export async function confirm(question, options) {
50
- const res = await Enquirer.prompt({
51
- message: question,
52
- name: '__Q__',
53
- type: 'confirm',
54
- initial: !!options?.initial,
55
- });
56
- // @ts-ignore
57
- return !!res['__Q__'];
58
- }
59
- export async function confirmSequence(questions) {
60
- const res = await Enquirer.prompt(questions.map(question => {
61
- return {
62
- ...question,
63
- type: 'confirm',
64
- };
65
- }));
66
- return res;
67
- }
package/lib/util.d.ts DELETED
@@ -1,17 +0,0 @@
1
- export declare function uuid(): string;
2
- export declare const markuplint: string;
3
- export declare function write(message: string): void;
4
- export declare namespace write {
5
- var _a: () => boolean;
6
- export { _a as break };
7
- }
8
- export declare function error(message: string): void;
9
- export declare namespace error {
10
- var exit: () => never;
11
- }
12
- export declare const head: (method: string, noColor?: boolean) => string;
13
- export declare function p(s: number | string, pad: number, start?: boolean): string;
14
- export declare function w(s: string): number;
15
- export declare function space(str: string): string;
16
- export declare function invisibleSpace(str: string): string;
17
- export declare function messageToString(message: string, reason?: string): string;
package/lib/util.js DELETED
@@ -1,77 +0,0 @@
1
- import module from 'node:module';
2
- import c from 'cli-color';
3
- // @ts-ignore
4
- import eastasianwidth from 'eastasianwidth';
5
- import stripAnsi from 'strip-ansi';
6
- import { v4 } from 'uuid';
7
- const require = module.createRequire(import.meta.url);
8
- export function uuid() {
9
- return v4();
10
- }
11
- /**
12
- * Brand color (xTerm color)
13
- *
14
- * @see http://www.calmar.ws/vim/256-xterm-24bit-rgb-color-chart.html
15
- */
16
- const PRIMARY_COLOR = 33;
17
- const logo = `/${c.xterm(PRIMARY_COLOR)('✔')}\\`;
18
- const version = require('../package.json').version;
19
- const eaw = eastasianwidth;
20
- const box = (lines, { width = 40, padding = 1, center = false, noColor = false }) => {
21
- const bt = `┌${'─'.repeat(width - 2)}┐`;
22
- const pd = `│${' '.repeat(width - 2)}│`;
23
- const bb = `└${'─'.repeat(width - 2)}┘`;
24
- const texts = lines.map(line => {
25
- const nc = stripAnsi(line);
26
- const length = nc.length;
27
- const pad = width - 2 - 1 - length;
28
- const pad2 = Math.floor(pad / 2);
29
- const padD = pad % 2;
30
- const padL = center ? pad2 : 0;
31
- const padR = center ? pad2 + padD : pad;
32
- const text = noColor ? nc : line;
33
- return `│ ${' '.repeat(padL)}${text}${' '.repeat(padR)}│`;
34
- });
35
- const result = [bt, pd, ...texts, pd, bb];
36
- return result.join('\n');
37
- };
38
- export const markuplint = `markup${c.xterm(PRIMARY_COLOR)('lint')}`;
39
- export function write(message) {
40
- process.stdout.write(message + '\n');
41
- }
42
- write.break = () => process.stdout.write('\n');
43
- export function error(message) {
44
- process.stderr.write(message + '\n');
45
- }
46
- // eslint-disable-next-line unicorn/no-process-exit
47
- error.exit = () => process.exit(1);
48
- export const head = (method, noColor) => box([`${logo} ${markuplint}`, c.blackBright(`v${version}`), '', c.bold(method)], {
49
- center: true,
50
- noColor,
51
- });
52
- export function p(s, pad, start = false) {
53
- const l = w(`${s}`.trim());
54
- const d = pad - l;
55
- const _ = ' '.repeat(d < 0 ? 0 : d);
56
- return start ? `${_}${s}` : `${s}${_}`;
57
- }
58
- export function w(s) {
59
- return s.replaceAll(/./g, _ => '0'.repeat(eaw.characterLength(_))).length;
60
- }
61
- export function space(str) {
62
- return str
63
- .replaceAll(/\s+/g, $0 => {
64
- return c.xterm(8)($0);
65
- })
66
- .replaceAll(' ', $0 => '•')
67
- .replaceAll('\t', $0 => '→ ');
68
- }
69
- export function invisibleSpace(str) {
70
- return str.replaceAll('\t', $0 => ' ').replaceAll(/./g, $0 => ' ');
71
- }
72
- export function messageToString(message, reason) {
73
- if (!reason) {
74
- return message;
75
- }
76
- return `${message} / ${reason}`;
77
- }