markuplint 4.0.0-alpha.2 → 4.0.0-dev.28

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.
@@ -1,5 +1,15 @@
1
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
2
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
3
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
4
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5
+ };
6
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
7
+ if (kind === "m") throw new TypeError("Private method is not writable");
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
9
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
10
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
11
+ };
1
12
  var _MLEngine_configProvider, _MLEngine_core, _MLEngine_file, _MLEngine_options, _MLEngine_watcher;
2
- import { __classPrivateFieldGet, __classPrivateFieldSet } from "tslib";
3
13
  import { ConfigProvider, resolveFiles, resolveParser, resolveRules, resolveSpecs } from '@markuplint/file-resolver';
4
14
  import { MLCore, convertRuleset } from '@markuplint/ml-core';
5
15
  import { FSWatcher } from 'chokidar';
@@ -11,6 +21,10 @@ const fileLog = log.extend('file');
11
21
  const configLog = log.extend('config');
12
22
  class MLEngine extends Emitter {
13
23
  static async fromCode(sourceCode, options) {
24
+ if (options?.debug) {
25
+ verbosely();
26
+ }
27
+ log('[fromCode] Creates: %O', options);
14
28
  const file = await MLEngine.toMLFile({
15
29
  sourceCode,
16
30
  name: options?.name,
@@ -19,7 +33,9 @@ class MLEngine extends Emitter {
19
33
  if (!file) {
20
34
  throw new Error('Never reach error');
21
35
  }
22
- return new MLEngine(file, options);
36
+ log('[fromCode] Created file: %s', file.path);
37
+ const engine = new MLEngine(file, options);
38
+ return engine;
23
39
  }
24
40
  static async toMLFile(target) {
25
41
  const files = await resolveFiles([target]);
@@ -32,13 +48,14 @@ class MLEngine extends Emitter {
32
48
  _MLEngine_file.set(this, void 0);
33
49
  _MLEngine_options.set(this, void 0);
34
50
  _MLEngine_watcher.set(this, new FSWatcher());
51
+ if (__classPrivateFieldGet(this, _MLEngine_options, "f")?.debug) {
52
+ verbosely();
53
+ }
35
54
  __classPrivateFieldSet(this, _MLEngine_file, file, "f");
36
55
  __classPrivateFieldSet(this, _MLEngine_options, options, "f");
37
56
  __classPrivateFieldSet(this, _MLEngine_configProvider, new ConfigProvider(), "f");
38
57
  this.watchMode(!!__classPrivateFieldGet(this, _MLEngine_options, "f")?.watch);
39
- if (__classPrivateFieldGet(this, _MLEngine_options, "f")?.debug) {
40
- verbosely();
41
- }
58
+ log('[MLEngine] Initialized: %s', __classPrivateFieldGet(this, _MLEngine_file, "f").path);
42
59
  }
43
60
  get document() {
44
61
  if (__classPrivateFieldGet(this, _MLEngine_core, "f")?.document instanceof Error) {
@@ -57,11 +74,11 @@ class MLEngine extends Emitter {
57
74
  log('exec: cancel (unsetuped yet)');
58
75
  return null;
59
76
  }
60
- const violations = await core.verify(__classPrivateFieldGet(this, _MLEngine_options, "f")?.fix).catch(e => {
61
- if (e instanceof Error) {
62
- return e;
77
+ const violations = await core.verify(__classPrivateFieldGet(this, _MLEngine_options, "f")?.fix).catch(error => {
78
+ if (error instanceof Error) {
79
+ return error;
63
80
  }
64
- throw e;
81
+ throw error;
65
82
  });
66
83
  const sourceCode = await __classPrivateFieldGet(this, _MLEngine_file, "f").getCode();
67
84
  const fixedCode = core.document.toString();
@@ -225,12 +242,16 @@ class MLEngine extends Emitter {
225
242
  };
226
243
  }
227
244
  async resolveConfig(cache) {
245
+ this.emit('log', 'resolveConfig', JSON.stringify(__classPrivateFieldGet(this, _MLEngine_configProvider, "f"), null, 2));
246
+ configLog('configProvider: %s', __classPrivateFieldGet(this, _MLEngine_configProvider, "f"));
228
247
  const defaultConfigKey = __classPrivateFieldGet(this, _MLEngine_options, "f")?.defaultConfig && __classPrivateFieldGet(this, _MLEngine_configProvider, "f").set(__classPrivateFieldGet(this, _MLEngine_options, "f")?.defaultConfig);
229
248
  configLog('defaultConfigKey: %s', defaultConfigKey ?? 'N/A');
230
249
  this.emit('log', 'defaultConfigKey', defaultConfigKey ?? 'N/A');
250
+ const targetConfig = await __classPrivateFieldGet(this, _MLEngine_configProvider, "f").search(__classPrivateFieldGet(this, _MLEngine_file, "f"));
251
+ this.emit('log', 'targetConfig', targetConfig ?? 'N/A');
231
252
  const configFilePathsFromTarget = __classPrivateFieldGet(this, _MLEngine_options, "f")?.noSearchConfig
232
253
  ? defaultConfigKey ?? null
233
- : (await __classPrivateFieldGet(this, _MLEngine_configProvider, "f").search(__classPrivateFieldGet(this, _MLEngine_file, "f"))) ?? defaultConfigKey;
254
+ : targetConfig ?? defaultConfigKey;
234
255
  configLog('configFilePathsFromTarget: %s', configFilePathsFromTarget ?? 'N/A');
235
256
  this.emit('log', 'configFilePathsFromTarget', configFilePathsFromTarget ?? 'N/A');
236
257
  const configKey = __classPrivateFieldGet(this, _MLEngine_options, "f")?.config && __classPrivateFieldGet(this, _MLEngine_configProvider, "f").set(__classPrivateFieldGet(this, _MLEngine_options, "f").config);
@@ -248,7 +269,7 @@ class MLEngine extends Emitter {
248
269
  this.emit('config', __classPrivateFieldGet(this, _MLEngine_file, "f").path, configSet);
249
270
  if (__classPrivateFieldGet(this, _MLEngine_options, "f")?.watch) {
250
271
  // It doesn't watch the main HTML file because it may is watched and managed by a language server or text editor or more.
251
- __classPrivateFieldGet(this, _MLEngine_watcher, "f").add(Array.from(configSet.files));
272
+ __classPrivateFieldGet(this, _MLEngine_watcher, "f").add([...configSet.files]);
252
273
  }
253
274
  return configSet;
254
275
  }
@@ -26,8 +26,8 @@ export async function createRule() {
26
26
  choices: firstChoices,
27
27
  });
28
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][a-z0-9]*(?:-[a-z][a-z0-9]*)*$/i);
30
- const ruleName = await input('What is the rule name?', /^[a-z][a-z0-9]*(?:-[a-z][a-z0-9]*)*$/i);
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
31
  const core = purpose === 'CONTRIBUTE_TO_CORE'
32
32
  ? {
33
33
  description: await input('Description:'),
package/lib/cli/index.js CHANGED
@@ -5,71 +5,66 @@ import { command } from './command.js';
5
5
  import { createRule } from './create-rule/index.js';
6
6
  import { initialize } from './init/index.js';
7
7
  import search from './search/index.js';
8
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
9
- (async () => {
10
- if (cli.flags.v) {
11
- cli.showVersion(); // And exit successfully.
12
- }
13
- if (cli.flags.h) {
14
- cli.showHelp(0); // And exit successfully.
15
- }
16
- if (cli.flags.verbose) {
17
- verbosely();
18
- }
19
- if (cli.flags.init) {
20
- await initialize().catch(err => {
21
- process.stderr.write(err + '\n');
22
- process.exit(1);
23
- });
24
- return;
25
- }
26
- if (cli.flags.createRule) {
27
- await createRule().catch(err => {
28
- process.stderr.write(err + '\n');
29
- process.exit(1);
30
- });
31
- return;
32
- }
33
- const files = cli.input;
34
- if (files.length > 0) {
35
- if (cli.flags.search) {
36
- await search(files, cli.flags, cli.flags.search).catch(err => {
37
- process.stderr.write(err + '\n');
38
- process.exit(1);
39
- });
40
- return;
41
- }
42
- const hasError = await command(files, cli.flags).catch(err => {
43
- throw err;
44
- });
45
- process.exit(hasError ? 1 : 0);
46
- }
47
- if (usePipe()) {
48
- getStdin()
49
- .then(async (stdin) => {
50
- if (stdin) {
51
- const hasError = await command([{ sourceCode: stdin }], {
52
- ...cli.flags,
53
- ignoreExt: true,
54
- }).catch(err => {
55
- process.stderr.write(err + '\n');
56
- process.exit(1);
57
- });
58
- process.exit(hasError ? 1 : 0);
59
- }
60
- // result is empty
61
- cli.showHelp(1);
62
- })
63
- .catch(reason => {
64
- // eslint-disable-next-line no-console
65
- console.warn(reason);
8
+ /* eslint-disable unicorn/no-process-exit */
9
+ if (cli.flags.v) {
10
+ cli.showVersion(); // And exit successfully.
11
+ }
12
+ if (cli.flags.h) {
13
+ cli.showHelp(0); // And exit successfully.
14
+ }
15
+ if (cli.flags.verbose) {
16
+ verbosely();
17
+ }
18
+ if (cli.flags.init) {
19
+ await initialize().catch(error => {
20
+ process.stderr.write(error + '\n');
21
+ process.exit(1);
22
+ });
23
+ process.exit(0);
24
+ }
25
+ if (cli.flags.createRule) {
26
+ await createRule().catch(error => {
27
+ process.stderr.write(error + '\n');
28
+ process.exit(1);
29
+ });
30
+ process.exit(0);
31
+ }
32
+ const files = cli.input;
33
+ if (files.length > 0) {
34
+ if (cli.flags.search) {
35
+ await search(files, cli.flags, cli.flags.search).catch(error => {
36
+ process.stderr.write(error + '\n');
66
37
  process.exit(1);
67
38
  });
39
+ process.exit(0);
68
40
  }
69
- else {
41
+ const hasError = await command(files, cli.flags).catch(error => {
42
+ throw error;
43
+ });
44
+ process.exit(hasError ? 1 : 0);
45
+ }
46
+ if (usePipe()) {
47
+ const stdin = await getStdin().catch(error => {
48
+ // eslint-disable-next-line no-console
49
+ console.warn(error);
50
+ process.exit(1);
51
+ });
52
+ if (!stdin) {
53
+ // result is empty
70
54
  cli.showHelp(1);
71
55
  }
72
- })();
56
+ const hasError = await command([{ sourceCode: stdin }], {
57
+ ...cli.flags,
58
+ ignoreExt: true,
59
+ }).catch(error => {
60
+ process.stderr.write(error + '\n');
61
+ process.exit(1);
62
+ });
63
+ process.exit(hasError ? 1 : 0);
64
+ }
65
+ // No arguments
66
+ cli.showHelp(1);
67
+ /* eslint-enable unicorn/no-process-exit */
73
68
  function usePipe() {
74
69
  return !process.stdin.isTTY && process.stdout.isTTY;
75
70
  }
@@ -59,12 +59,12 @@ export async function initialize() {
59
59
  }
60
60
  const config = createConfig(selectedLangs, ruleSettingMode, defaultRules);
61
61
  const filePath = path.resolve(process.cwd(), '.markuplintrc');
62
- await writeFile(filePath, JSON.stringify(config, null, 2), { encoding: 'utf-8' });
62
+ await writeFile(filePath, JSON.stringify(config, null, 2), { encoding: 'utf8' });
63
63
  write(`✨Created: ${filePath}`);
64
64
  if (autoInstall) {
65
65
  write('Install automatically');
66
66
  const modules = selectModules(selectedLangs);
67
- const result = await installModule(modules, true).catch(e => new Error(e));
67
+ const result = await installModule(modules, true).catch(error_ => new Error(error_));
68
68
  if (result instanceof Error) {
69
69
  error.exit();
70
70
  return;
@@ -24,7 +24,7 @@ export async function installModule(module, dev = false) {
24
24
  }
25
25
  }
26
26
  }
27
- catch (_) {
27
+ catch {
28
28
  // void
29
29
  }
30
30
  if (uninstallMods.length === 0) {
@@ -67,8 +67,8 @@ function isInstalled(module) {
67
67
  resolve(exists);
68
68
  });
69
69
  }
70
- catch (err) {
71
- reject(err);
70
+ catch (error) {
71
+ reject(error);
72
72
  }
73
73
  });
74
74
  }
@@ -14,7 +14,7 @@ export default async function (files, options, selectors) {
14
14
  ...createRule({
15
15
  verify({ document }) {
16
16
  const nodes = document.querySelectorAll(selectors);
17
- locations.push(...Array.from(nodes).map(node => {
17
+ locations.push(...[...nodes].map(node => {
18
18
  return {
19
19
  file: document.filename ?? '_NO_FILE_',
20
20
  line: node.startLine,
@@ -31,7 +31,7 @@ export default async function (files, options, selectors) {
31
31
  },
32
32
  },
33
33
  });
34
- locations.forEach(loc => {
34
+ for (const loc of locations) {
35
35
  process.stdout.write(`${loc.file}:${loc.line}:${loc.col}\n`);
36
- });
36
+ }
37
37
  }
package/lib/debug.js CHANGED
@@ -3,8 +3,9 @@ import debug from 'debug';
3
3
  export const log = debug('markuplint-cli');
4
4
  export function verbosely() {
5
5
  if (!log.enabled) {
6
- debug.enable(`${log.namespace}*`);
7
- log(`Debug enable: ${log.namespace}`);
6
+ const namespace = `${log.namespace}*,ml-*`;
7
+ debug.enable(namespace);
8
+ log(`[Debug] Enable: "${namespace}"`);
8
9
  }
9
10
  enableDebug();
10
11
  }
@@ -4,7 +4,7 @@ export function getJsonModule(modulePath) {
4
4
  try {
5
5
  return require(modulePath);
6
6
  }
7
- catch (_) {
7
+ catch {
8
8
  return null;
9
9
  }
10
10
  }
package/lib/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { MLEngine, FromCodeOptions } from './api/index.js';
2
+ export { MLEngineEventMap } from './api/types.js';
2
3
  export * from './i18n.js';
3
4
  export * from './testing-tool/index.js';
4
5
  export * from './types.js';
@@ -10,10 +10,12 @@ export function githubReporter(results) {
10
10
  }
11
11
  function severityToCommand(severity) {
12
12
  switch (severity) {
13
- case 'info':
13
+ case 'info': {
14
14
  return 'notice';
15
+ }
15
16
  case 'error':
16
- case 'warning':
17
+ case 'warning': {
17
18
  return severity;
19
+ }
18
20
  }
19
21
  }
@@ -21,8 +21,8 @@ export function standardReporter(results, options) {
21
21
  const prev = lines[violation.line - 2] ?? '';
22
22
  const line = lines[violation.line - 1] ?? '';
23
23
  const next = lines[violation.line - 0] ?? '';
24
- const before = line.substring(0, violation.col - 1);
25
- const after = line.substring(violation.col - 1 + violation.raw.length);
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
26
  const logger = violation.severity === 'error' ? loggerError : loggerWarning;
27
27
  const meg = messageToString(violation.message, violation.reason);
28
28
  out.push(`<${markuplint}> ${logger(`${violation.severity}: ${meg} (${violation.ruleId}) ${c.underline(`${results.filePath}:${violation.line}:${violation.col}`)}`)}`);
@@ -18,38 +18,40 @@ export async function mlTest(sourceCode, config, rules, locale = 'en', fix = fal
18
18
  fixedCode: result?.fixedCode ?? sourceCode,
19
19
  };
20
20
  }
21
- export async function mlRuleTest(rule, sourceCode, config = { rule: true }, fix = false, locale = 'en') {
21
+ export async function mlRuleTest(rule, sourceCode,
22
+ // eslint-disable-next-line unicorn/no-object-as-default-parameter
23
+ config = { rule: true }, fix = false, locale = 'en') {
22
24
  const _config = {
23
25
  ...config,
24
- rules: config.rule !== undefined
25
- ? {
26
- '<current-rule>': config.rule,
27
- }
28
- : config.rule === undefined && config.nodeRule === undefined && config.childNodeRule === undefined
26
+ rules: config.rule === undefined
27
+ ? config.rule === undefined && config.nodeRule === undefined && config.childNodeRule === undefined
29
28
  ? {
30
29
  '<current-rule>': true,
31
30
  }
32
- : undefined,
33
- nodeRules: config.nodeRule !== undefined
34
- ? config.nodeRule.map(nodeConfig => ({
31
+ : undefined
32
+ : {
33
+ '<current-rule>': config.rule,
34
+ },
35
+ nodeRules: config.nodeRule === undefined
36
+ ? undefined
37
+ : config.nodeRule.map(nodeConfig => ({
35
38
  ...nodeConfig,
36
- rules: nodeConfig.rule !== undefined
37
- ? {
39
+ rules: nodeConfig.rule === undefined
40
+ ? undefined
41
+ : {
38
42
  '<current-rule>': nodeConfig.rule,
39
- }
40
- : undefined,
41
- }))
42
- : undefined,
43
- childNodeRules: config.childNodeRule !== undefined
44
- ? config.childNodeRule.map(childNodeConfig => ({
43
+ },
44
+ })),
45
+ childNodeRules: config.childNodeRule === undefined
46
+ ? undefined
47
+ : config.childNodeRule.map(childNodeConfig => ({
45
48
  ...childNodeConfig,
46
- rules: childNodeConfig.rule !== undefined
47
- ? {
49
+ rules: childNodeConfig.rule === undefined
50
+ ? undefined
51
+ : {
48
52
  '<current-rule>': childNodeConfig.rule,
49
- }
50
- : undefined,
51
- }))
52
- : undefined,
53
+ },
54
+ })),
53
55
  };
54
56
  const res = await mlTest(sourceCode, _config, [
55
57
  new MLRule({
package/lib/util.js CHANGED
@@ -43,6 +43,7 @@ write.break = () => process.stdout.write('\n');
43
43
  export function error(message) {
44
44
  process.stderr.write(message + '\n');
45
45
  }
46
+ // eslint-disable-next-line unicorn/no-process-exit
46
47
  error.exit = () => process.exit(1);
47
48
  export const head = (method, noColor) => box([`${logo} ${markuplint}`, c.blackBright(`v${version}`), '', c.bold(method)], {
48
49
  center: true,
@@ -55,18 +56,18 @@ export function p(s, pad, start = false) {
55
56
  return start ? `${_}${s}` : `${s}${_}`;
56
57
  }
57
58
  export function w(s) {
58
- return s.replace(/./g, _ => '0'.repeat(eaw.characterLength(_))).length;
59
+ return s.replaceAll(/./g, _ => '0'.repeat(eaw.characterLength(_))).length;
59
60
  }
60
61
  export function space(str) {
61
62
  return str
62
- .replace(/\s+/g, $0 => {
63
+ .replaceAll(/\s+/g, $0 => {
63
64
  return c.xterm(8)($0);
64
65
  })
65
- .replace(/ /g, $0 => '•')
66
- .replace(/\t/g, $0 => '→ ');
66
+ .replaceAll(' ', $0 => '•')
67
+ .replaceAll('\t', $0 => '→ ');
67
68
  }
68
69
  export function invisibleSpace(str) {
69
- return str.replace(/\t/g, $0 => ' ').replace(/./g, $0 => ' ');
70
+ return str.replaceAll('\t', $0 => ' ').replaceAll(/./g, $0 => ' ');
70
71
  }
71
72
  export function messageToString(message, reason) {
72
73
  if (!reason) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "markuplint",
3
- "version": "4.0.0-alpha.2",
3
+ "version": "4.0.0-dev.28+0131de5e",
4
4
  "description": "An HTML linter for all markup developers",
5
5
  "author": "Yusuke Hirao",
6
6
  "license": "MIT",
@@ -24,26 +24,21 @@
24
24
  "dev": "tsc --build --watch",
25
25
  "clean": "tsc --build --clean"
26
26
  },
27
- "devDependencies": {
28
- "execa": "^8.0.1"
29
- },
30
27
  "dependencies": {
31
- "@markuplint/create-rule-helper": "4.0.0-alpha.2",
32
- "@markuplint/file-resolver": "4.0.0-alpha.2",
33
- "@markuplint/html-parser": "4.0.0-alpha.2",
34
- "@markuplint/html-spec": "4.0.0-alpha.2",
35
- "@markuplint/i18n": "4.0.0-alpha.2",
36
- "@markuplint/ml-ast": "4.0.0-alpha.2",
37
- "@markuplint/ml-config": "4.0.0-alpha.2",
38
- "@markuplint/ml-core": "4.0.0-alpha.2",
39
- "@markuplint/ml-spec": "4.0.0-alpha.2",
40
- "@markuplint/rules": "4.0.0-alpha.2",
41
- "@markuplint/shared": "4.0.0-alpha.2",
42
- "@types/cli-color": "^2.0.3",
43
- "@types/debug": "^4.1.9",
44
- "@types/has-yarn": "^1.0.1",
45
- "@types/meow": "^6.0.0",
46
- "@types/uuid": "^9.0.4",
28
+ "@markuplint/create-rule-helper": "4.0.0-dev.28+0131de5e",
29
+ "@markuplint/file-resolver": "4.0.0-dev.28+0131de5e",
30
+ "@markuplint/html-parser": "4.0.0-dev.28+0131de5e",
31
+ "@markuplint/html-spec": "4.0.0-dev.28+0131de5e",
32
+ "@markuplint/i18n": "4.0.0-dev.28+0131de5e",
33
+ "@markuplint/ml-ast": "4.0.0-dev.28+0131de5e",
34
+ "@markuplint/ml-config": "4.0.0-dev.28+0131de5e",
35
+ "@markuplint/ml-core": "4.0.0-dev.28+0131de5e",
36
+ "@markuplint/ml-spec": "4.0.0-dev.28+0131de5e",
37
+ "@markuplint/rules": "4.0.0-dev.28+0131de5e",
38
+ "@markuplint/shared": "4.0.0-dev.28+0131de5e",
39
+ "@types/cli-color": "^2.0.4",
40
+ "@types/debug": "^4.1.10",
41
+ "@types/uuid": "^9.0.6",
47
42
  "chokidar": "^3.5.3",
48
43
  "cli-color": "^2.0.3",
49
44
  "debug": "^4.3.4",
@@ -58,9 +53,8 @@
58
53
  "os-locale": "^6.0.2",
59
54
  "strict-event-emitter": "^0.5.1",
60
55
  "strip-ansi": "^7.1.0",
61
- "tslib": "^2.6.2",
62
- "type-fest": "^4.3.1",
56
+ "type-fest": "^4.5.0",
63
57
  "uuid": "^9.0.1"
64
58
  },
65
- "gitHead": "51ad52c760435641f9bff8685707d8178b0787eb"
59
+ "gitHead": "0131de5ea9dd6d3fd5472d7b414b66644c758881"
66
60
  }