mdrefine 0.1.0-beta.1

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.
Files changed (59) hide show
  1. package/CONTRIBUTING.md +10 -0
  2. package/LICENSE +21 -0
  3. package/README.md +133 -0
  4. package/SECURITY.md +16 -0
  5. package/dist/cli/main.d.ts +2 -0
  6. package/dist/cli/main.js +158 -0
  7. package/dist/cli/main.js.map +1 -0
  8. package/dist/config/load.d.ts +9 -0
  9. package/dist/config/load.js +75 -0
  10. package/dist/config/load.js.map +1 -0
  11. package/dist/config/resolve.d.ts +93 -0
  12. package/dist/config/resolve.js +110 -0
  13. package/dist/config/resolve.js.map +1 -0
  14. package/dist/core/directives.d.ts +3 -0
  15. package/dist/core/directives.js +48 -0
  16. package/dist/core/directives.js.map +1 -0
  17. package/dist/core/engine.d.ts +8 -0
  18. package/dist/core/engine.js +192 -0
  19. package/dist/core/engine.js.map +1 -0
  20. package/dist/core/types.d.ts +92 -0
  21. package/dist/core/types.js +2 -0
  22. package/dist/core/types.js.map +1 -0
  23. package/dist/index.d.ts +7 -0
  24. package/dist/index.js +5 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/rules/dialects.d.ts +2 -0
  27. package/dist/rules/dialects.js +105 -0
  28. package/dist/rules/dialects.js.map +1 -0
  29. package/dist/rules/links.d.ts +2 -0
  30. package/dist/rules/links.js +238 -0
  31. package/dist/rules/links.js.map +1 -0
  32. package/dist/rules/style.d.ts +12 -0
  33. package/dist/rules/style.js +244 -0
  34. package/dist/rules/style.js.map +1 -0
  35. package/dist/syntax/obsidian.d.ts +33 -0
  36. package/dist/syntax/obsidian.js +126 -0
  37. package/dist/syntax/obsidian.js.map +1 -0
  38. package/dist/syntax/parse.d.ts +5 -0
  39. package/dist/syntax/parse.js +45 -0
  40. package/dist/syntax/parse.js.map +1 -0
  41. package/dist/workspace/files.d.ts +8 -0
  42. package/dist/workspace/files.js +98 -0
  43. package/dist/workspace/files.js.map +1 -0
  44. package/dist/workspace/index.d.ts +10 -0
  45. package/dist/workspace/index.js +100 -0
  46. package/dist/workspace/index.js.map +1 -0
  47. package/docs/cli.md +71 -0
  48. package/docs/configuration.md +171 -0
  49. package/docs/contributing.md +103 -0
  50. package/docs/design.md +79 -0
  51. package/docs/index.md +48 -0
  52. package/docs/obsidian.md +69 -0
  53. package/docs/plugins.md +114 -0
  54. package/docs/quick-start.md +101 -0
  55. package/docs/releases/0.1.0-beta.1.md +39 -0
  56. package/docs/releases.md +90 -0
  57. package/examples/github.jsonc +7 -0
  58. package/examples/obsidian.jsonc +8 -0
  59. package/package.json +101 -0
@@ -0,0 +1,10 @@
1
+ # Contributing
2
+
3
+ Human and agent-authored issues and pull requests are welcome. Read the
4
+ [contribution guide](https://viell-dev.github.io/slop-markdown-tools/contributing.html)
5
+ for defect reports, development, verification, and PR policy. This project is
6
+ agent-maintained, as described in [README.md](README.md).
7
+
8
+ Changes to document rendering need a minimal synthetic regression example.
9
+ Remove private vault content and credentials from all reports. Report security
10
+ vulnerabilities through the channel in [SECURITY.md](SECURITY.md).
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 viell
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,133 @@
1
+ # mdrefine — Markdown Tools
2
+
3
+ Configurable linting and formatting for CommonMark, GitHub Markdown, and
4
+ Obsidian. Separate `lint` and `format` commands share syntax, style settings,
5
+ and an extensible rule engine.
6
+
7
+ ## Maintenance
8
+
9
+ This project is developed and maintained by AI agents. It is
10
+ **not human-maintained**; do not assume that a person has reviewed generated
11
+ changes. Human and agent-authored issues and pull requests are welcome.
12
+ Reproducible examples, automated checks, and regression tests guide maintenance.
13
+
14
+ The project is currently an early prerelease. Report document corruption or
15
+ changed rendering with a minimal input, the configuration, actual output, and
16
+ expected output. Remove private content before posting examples. Agents using
17
+ the tool should report reproducible defects when their operator has authorized
18
+ publishing an issue.
19
+
20
+ ## Documentation
21
+
22
+ Read the
23
+ [quick start](https://viell-dev.github.io/slop-markdown-tools/quick-start.html)
24
+ or browse the
25
+ [documentation site](https://viell-dev.github.io/slop-markdown-tools/). It
26
+ covers CLI workflows, configuration and rules, Obsidian vaults, plugins, and
27
+ current limitations. The Markdown source lives in `docs/`.
28
+
29
+ ## Getting started
30
+
31
+ The CLI requires Node.js 22.12 or newer. The npm package is `mdrefine`; the
32
+ executable is `mdtools`. To install the beta in your document workspace:
33
+
34
+ ```sh
35
+ npm install --save-dev --save-exact mdrefine@0.1.0-beta.1
36
+ npx --no-install mdtools --help
37
+ ```
38
+
39
+ See the
40
+ [release procedure](https://viell-dev.github.io/slop-markdown-tools/releases.html)
41
+ for registry availability, preparation, and authentication requirements.
42
+
43
+ Inspect your documents from the workspace where you installed the package:
44
+
45
+ ```sh
46
+ npx --no-install mdtools lint --root /path/to/documents --json
47
+ npx --no-install mdtools format --root /path/to/documents --diff
48
+ npx --no-install mdtools format --root /path/to/documents --write
49
+ npx --no-install mdtools format --root /path/to/documents --check
50
+ ```
51
+
52
+ `lint` never writes documents. `format` previews a diff by default for file
53
+ inputs and writes only with `--write`. For stdin, `format -` returns formatted
54
+ Markdown on stdout. Diagnostics use stderr unless `--json` requests a structured
55
+ report. `--no-install` prevents fetching a different package if the local
56
+ executable is missing.
57
+
58
+ ## Build from source
59
+
60
+ For development, use Node.js 22.22.2+ or 24.15.0+ and npm 10 or newer; Node.js
61
+ 24 LTS is preferred.
62
+
63
+ ```sh
64
+ git clone https://github.com/viell-dev/slop-markdown-tools.git
65
+ cd slop-markdown-tools
66
+ npm ci
67
+ npm run build
68
+ node dist/cli/main.js --help
69
+ ```
70
+
71
+ From the checkout, use `node dist/cli/main.js` in place of
72
+ `npx --no-install mdtools` in the examples above. You can also install the built
73
+ checkout as a local dependency.
74
+
75
+ ## Contributions and verification
76
+
77
+ All changes to `main` require a pull request and passing CI, including
78
+ prerelease maintenance. Published npm versions and release tags are immutable;
79
+ corrections get a new version. See [CONTRIBUTING.md](CONTRIBUTING.md) and
80
+ [SECURITY.md](SECURITY.md) for contribution and vulnerability reporting.
81
+
82
+ Issues and PRs from people and agents are welcome. For defects, include the
83
+ version, command, configuration, minimal Markdown input, actual output, and
84
+ expected rendering. A screenshot can help explain a visible defect, but the
85
+ Markdown source is needed to reproduce it. For agents, include the JSON
86
+ diagnostic output when available. Automatic issue reporting is not built into
87
+ the tool.
88
+
89
+ ```sh
90
+ npm run check
91
+ npm run test:package
92
+ ```
93
+
94
+ The project uses Markdown Tools to lint and format its own Markdown at the
95
+ default 80-column width. `npm run format:docs` formats Markdown;
96
+ `npm run format:docs:check` checks it without writing. Prettier formats
97
+ implementation and configuration files and excludes Markdown. `npm run format`
98
+ runs both formatters. Markdown fixtures under `tests/fixtures/` are excluded
99
+ from documentation passes.
100
+
101
+ `npm-run-all2` coordinates scripts, running independent checks in parallel and
102
+ building the CLI before documentation checks and tests.
103
+
104
+ `check` runs strict TypeScript checks, ESLint, Prettier for non-Markdown files,
105
+ Markdown Tools for documentation, the VitePress site build, and
106
+ regression/property/CLI tests. GitHub Actions runs checks on Linux with Node.js
107
+ 22 and 24, plus macOS and Windows with Node.js 24. Changes to formatting need
108
+ tests for expected output, idempotence, and preserved semantics; defects should
109
+ add a regression example. Keep fixes scoped and describe remaining limitations
110
+ in the PR.
111
+
112
+ Agent contributors should read
113
+ [AGENTS.md](https://github.com/viell-dev/slop-markdown-tools/blob/main/AGENTS.md)
114
+ for maintenance-specific instructions. `test:package` packs and installs a
115
+ temporary consumer project, then exercises the installed CLI and library; it can
116
+ require access to the npm registry.
117
+
118
+ ## Documentation website
119
+
120
+ ```sh
121
+ npm run docs:dev
122
+ npm run docs:build
123
+ npm run docs:preview
124
+ ```
125
+
126
+ VitePress renders `docs/`; Markdown Tools formats its Markdown sources. The
127
+ Pages workflow builds from `main` and deploys the generated artifact. No
128
+ publishing branch or committed build output is needed. See the
129
+ [documentation workflow](https://viell-dev.github.io/slop-markdown-tools/contributing.html#write-and-preview-documentation).
130
+
131
+ ## License
132
+
133
+ [MIT](LICENSE).
package/SECURITY.md ADDED
@@ -0,0 +1,16 @@
1
+ # Security policy
2
+
3
+ Report suspected vulnerabilities through
4
+ [GitHub private vulnerability reporting](https://github.com/viell-dev/slop-markdown-tools/security/advisories/new).
5
+ Include the affected version, configuration, and a minimal synthetic example. Do
6
+ not disclose exploitable details or private documents in a public issue.
7
+
8
+ This project is maintained by agents, without a guaranteed response time.
9
+ Security fixes target the latest prerelease until a stable release exists; older
10
+ prereleases do not receive separate backports.
11
+
12
+ JavaScript configuration and plugins execute with the caller's permissions. Only
13
+ load trusted code. JSON configuration is data, but its plugin entries can still
14
+ load executable code. Link validation does not fetch external URLs. Document
15
+ rendering defects without a security impact belong in ordinary issues; remove
16
+ private content before posting.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,158 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { createTwoFilesPatch } from "diff";
4
+ import { readFile } from "node:fs/promises";
5
+ import path from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import { format, lint, ruleRegistry } from "../core/engine.js";
8
+ import { loadConfig } from "../config/load.js";
9
+ import { resolveConfig } from "../config/resolve.js";
10
+ import { discover, writeAtomic } from "../workspace/files.js";
11
+ import { createWorkspace } from "../workspace/index.js";
12
+ const manifest = JSON.parse(await readFile(fileURLToPath(new URL("../../package.json", import.meta.url)), "utf8"));
13
+ const program = new Command()
14
+ .name("mdtools")
15
+ .description("Lint and format Markdown with explicit dialects and configurable rules.")
16
+ .version(manifest.version);
17
+ function common(command) {
18
+ return command
19
+ .option("--config <file>", "Explicit JSON, JSONC, or .mjs configuration")
20
+ .option("--root <directory>", "Workspace root (default: configuration directory or cwd)")
21
+ .option("--dialect <dialect>", "commonmark, github, or obsidian")
22
+ .option("--json", "Machine-readable output")
23
+ .option("--stdin-filepath <file>", "Filename context for stdin")
24
+ .option("--max-warnings <count>", "Fail when warning count exceeds this nonnegative integer", (value) => {
25
+ const number = Number(value);
26
+ if (!Number.isInteger(number) || number < 0)
27
+ throw new Error("--max-warnings must be a nonnegative integer.");
28
+ return number;
29
+ });
30
+ }
31
+ async function run(mode, inputs, flags) {
32
+ if ([flags.check, flags.diff, flags.write].filter(Boolean).length > 1)
33
+ throw new Error("Choose only one of --check, --diff, and --write.");
34
+ const loaded = await loadConfig(path.resolve(flags.root ?? "."), flags.config);
35
+ const root = path.resolve(flags.root ?? loaded.root);
36
+ if (flags.dialect)
37
+ loaded.config.dialect = flags.dialect;
38
+ const resolved = resolveConfig(loaded.config, "document.md", loaded.plugins);
39
+ const stdin = inputs.includes("-");
40
+ if (stdin && (inputs.length !== 1 || flags.write))
41
+ throw new Error("stdin must be the only input and cannot be combined with --write.");
42
+ const set = await discover(root, stdin ? [] : inputs, resolved.ignore);
43
+ if (stdin) {
44
+ const name = path
45
+ .relative(root, path.resolve(flags.stdinFilepath ?? path.join(root, "stdin.md")))
46
+ .split(path.sep)
47
+ .join("/");
48
+ if (name.startsWith("../") || path.isAbsolute(name))
49
+ throw new Error("--stdin-filepath must be within the workspace.");
50
+ let source = "";
51
+ process.stdin.setEncoding("utf8");
52
+ for await (const chunk of process.stdin)
53
+ source += String(chunk);
54
+ set.files[name] = source;
55
+ set.selected = [name];
56
+ }
57
+ const reports = [];
58
+ const changes = [];
59
+ // One index per active dialect; build lazily for mixed documentation workspaces.
60
+ const indexes = new Map();
61
+ for (const name of set.selected) {
62
+ const config = resolveConfig(loaded.config, name, loaded.plugins);
63
+ if (!indexes.has(config.dialect))
64
+ indexes.set(config.dialect, createWorkspace(set.files, {
65
+ dialect: config.dialect,
66
+ ...(set.strictLineBreaks !== undefined ? { strictLineBreaks: set.strictLineBreaks } : {}),
67
+ }));
68
+ const options = {
69
+ path: name,
70
+ config: loaded.config,
71
+ plugins: loaded.plugins,
72
+ workspace: indexes.get(config.dialect),
73
+ };
74
+ const source = set.files[name];
75
+ if (source === null)
76
+ continue;
77
+ if (mode === "lint")
78
+ reports.push({ path: name, diagnostics: lint(source, options) });
79
+ else {
80
+ const result = format(source, options);
81
+ reports.push({
82
+ path: name,
83
+ changed: result.changed,
84
+ diagnostics: result.diagnostics,
85
+ ...(stdin && !flags.check ? { output: result.output } : {}),
86
+ });
87
+ if (result.changed)
88
+ changes.push({ file: name, before: source, after: result.output });
89
+ }
90
+ }
91
+ const unsafe = reports.some((report) => report.diagnostics.some((item) => item.rule === "engine/unsafe-format"));
92
+ if (flags.write && !unsafe)
93
+ for (const change of changes)
94
+ await writeAtomic(path.join(set.root, change.file), change.before, change.after);
95
+ if (flags.json)
96
+ process.stdout.write(JSON.stringify({
97
+ version: manifest.version,
98
+ mode,
99
+ files: reports,
100
+ written: flags.write === true && !unsafe,
101
+ }, null, 2) + "\n");
102
+ else {
103
+ if (stdin && mode === "format" && !flags.check && !flags.diff)
104
+ process.stdout.write(reports[0]?.output ?? "");
105
+ else if (mode === "format" && !flags.check && !flags.write)
106
+ for (const change of changes)
107
+ process.stdout.write(createTwoFilesPatch(`a/${change.file}`, `b/${change.file}`, change.before, change.after));
108
+ for (const report of reports)
109
+ for (const item of report.diagnostics)
110
+ process.stderr.write(`${report.path}:${item.line}:${item.column}: ${item.severity} ${item.rule}: ${item.message}\n`);
111
+ if (!stdin)
112
+ process.stderr.write(`${set.selected.length} file(s) ${mode === "lint" ? "linted" : "processed"}; ${changes.length} ${flags.write && !unsafe ? "written" : "would change"}.\n`);
113
+ }
114
+ const diagnostics = reports.flatMap((report) => report.diagnostics);
115
+ const errors = diagnostics.some((item) => item.severity === "error");
116
+ const tooManyWarnings = flags.maxWarnings !== undefined &&
117
+ diagnostics.filter((item) => item.severity === "warn").length > flags.maxWarnings;
118
+ process.exitCode = unsafe
119
+ ? 2
120
+ : errors || tooManyWarnings || (flags.check && changes.length > 0)
121
+ ? 1
122
+ : 0;
123
+ }
124
+ common(program.command("lint").argument("[paths...]", "Markdown files/directories, or - for stdin")).action((inputs, flags) => run("lint", inputs, flags));
125
+ common(program.command("format").argument("[paths...]", "Markdown files/directories, or - for stdin"))
126
+ .option("--check", "Fail if formatting changes would be needed")
127
+ .option("--diff", "Preview the complete diff (default for file inputs)")
128
+ .option("--write", "Write validated formatting changes")
129
+ .action((inputs, flags) => run("format", inputs, flags));
130
+ common(program.command("config").description("Inspect effective configuration"))
131
+ .command("explain <file>")
132
+ .action(async (file, _flags, command) => {
133
+ const flags = command.parent.opts();
134
+ const loaded = await loadConfig(path.resolve(flags.root ?? "."), flags.config);
135
+ if (flags.dialect)
136
+ loaded.config.dialect = flags.dialect;
137
+ const root = path.resolve(flags.root ?? loaded.root);
138
+ process.stdout.write(JSON.stringify({
139
+ root,
140
+ file: loaded.file ?? null,
141
+ effective: resolveConfig(loaded.config, path.relative(root, path.resolve(file)).split(path.sep).join("/"), loaded.plugins),
142
+ }, null, 2) + "\n");
143
+ });
144
+ program
145
+ .command("rules")
146
+ .description("List built-in rules")
147
+ .action(() => {
148
+ for (const [name, rule] of Object.entries(ruleRegistry()))
149
+ process.stdout.write(`${name}\t${rule.kind}\t${rule.description}\n`);
150
+ });
151
+ try {
152
+ await program.parseAsync();
153
+ }
154
+ catch (error) {
155
+ process.stderr.write(`mdtools: ${error instanceof Error ? error.message : String(error)}\n`);
156
+ process.exitCode = 2;
157
+ }
158
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport { createTwoFilesPatch } from \"diff\";\nimport { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { format, lint, ruleRegistry } from \"../core/engine.js\";\nimport { loadConfig } from \"../config/load.js\";\nimport { resolveConfig } from \"../config/resolve.js\";\nimport { discover, writeAtomic } from \"../workspace/files.js\";\nimport { createWorkspace } from \"../workspace/index.js\";\nimport type { Diagnostic, Dialect, ProcessOptions } from \"../core/types.js\";\n\ninterface Flags {\n config?: string;\n root?: string;\n dialect?: Dialect;\n json?: boolean;\n check?: boolean;\n diff?: boolean;\n write?: boolean;\n stdinFilepath?: string;\n maxWarnings?: number;\n}\nconst manifest = JSON.parse(\n await readFile(fileURLToPath(new URL(\"../../package.json\", import.meta.url)), \"utf8\"),\n) as { version: string };\nconst program = new Command()\n .name(\"mdtools\")\n .description(\"Lint and format Markdown with explicit dialects and configurable rules.\")\n .version(manifest.version);\nfunction common(command: Command): Command {\n return command\n .option(\"--config <file>\", \"Explicit JSON, JSONC, or .mjs configuration\")\n .option(\"--root <directory>\", \"Workspace root (default: configuration directory or cwd)\")\n .option(\"--dialect <dialect>\", \"commonmark, github, or obsidian\")\n .option(\"--json\", \"Machine-readable output\")\n .option(\"--stdin-filepath <file>\", \"Filename context for stdin\")\n .option(\n \"--max-warnings <count>\",\n \"Fail when warning count exceeds this nonnegative integer\",\n (value: string) => {\n const number = Number(value);\n if (!Number.isInteger(number) || number < 0)\n throw new Error(\"--max-warnings must be a nonnegative integer.\");\n return number;\n },\n );\n}\ninterface Report {\n path: string;\n changed?: boolean;\n diagnostics: Diagnostic[];\n output?: string;\n}\nasync function run(mode: \"lint\" | \"format\", inputs: string[], flags: Flags) {\n if ([flags.check, flags.diff, flags.write].filter(Boolean).length > 1)\n throw new Error(\"Choose only one of --check, --diff, and --write.\");\n const loaded = await loadConfig(path.resolve(flags.root ?? \".\"), flags.config);\n const root = path.resolve(flags.root ?? loaded.root);\n if (flags.dialect) loaded.config.dialect = flags.dialect;\n const resolved = resolveConfig(loaded.config, \"document.md\", loaded.plugins);\n const stdin = inputs.includes(\"-\");\n if (stdin && (inputs.length !== 1 || flags.write))\n throw new Error(\"stdin must be the only input and cannot be combined with --write.\");\n const set = await discover(root, stdin ? [] : inputs, resolved.ignore);\n if (stdin) {\n const name = path\n .relative(root, path.resolve(flags.stdinFilepath ?? path.join(root, \"stdin.md\")))\n .split(path.sep)\n .join(\"/\");\n if (name.startsWith(\"../\") || path.isAbsolute(name))\n throw new Error(\"--stdin-filepath must be within the workspace.\");\n let source = \"\";\n process.stdin.setEncoding(\"utf8\");\n for await (const chunk of process.stdin) source += String(chunk);\n set.files[name] = source;\n set.selected = [name];\n }\n const reports: Report[] = [];\n const changes: { file: string; before: string; after: string }[] = [];\n // One index per active dialect; build lazily for mixed documentation workspaces.\n const indexes = new Map<Dialect, ReturnType<typeof createWorkspace>>();\n for (const name of set.selected) {\n const config = resolveConfig(loaded.config, name, loaded.plugins);\n if (!indexes.has(config.dialect))\n indexes.set(\n config.dialect,\n createWorkspace(set.files, {\n dialect: config.dialect,\n ...(set.strictLineBreaks !== undefined ? { strictLineBreaks: set.strictLineBreaks } : {}),\n }),\n );\n const options: ProcessOptions = {\n path: name,\n config: loaded.config,\n plugins: loaded.plugins,\n workspace: indexes.get(config.dialect)!,\n };\n const source = set.files[name]!;\n if (source === null) continue;\n if (mode === \"lint\") reports.push({ path: name, diagnostics: lint(source, options) });\n else {\n const result = format(source, options);\n reports.push({\n path: name,\n changed: result.changed,\n diagnostics: result.diagnostics,\n ...(stdin && !flags.check ? { output: result.output } : {}),\n });\n if (result.changed) changes.push({ file: name, before: source, after: result.output });\n }\n }\n const unsafe = reports.some((report) =>\n report.diagnostics.some((item) => item.rule === \"engine/unsafe-format\"),\n );\n if (flags.write && !unsafe)\n for (const change of changes)\n await writeAtomic(path.join(set.root, change.file), change.before, change.after);\n if (flags.json)\n process.stdout.write(\n JSON.stringify(\n {\n version: manifest.version,\n mode,\n files: reports,\n written: flags.write === true && !unsafe,\n },\n null,\n 2,\n ) + \"\\n\",\n );\n else {\n if (stdin && mode === \"format\" && !flags.check && !flags.diff)\n process.stdout.write(reports[0]?.output ?? \"\");\n else if (mode === \"format\" && !flags.check && !flags.write)\n for (const change of changes)\n process.stdout.write(\n createTwoFilesPatch(`a/${change.file}`, `b/${change.file}`, change.before, change.after),\n );\n for (const report of reports)\n for (const item of report.diagnostics)\n process.stderr.write(\n `${report.path}:${item.line}:${item.column}: ${item.severity} ${item.rule}: ${item.message}\\n`,\n );\n if (!stdin)\n process.stderr.write(\n `${set.selected.length} file(s) ${mode === \"lint\" ? \"linted\" : \"processed\"}; ${changes.length} ${flags.write && !unsafe ? \"written\" : \"would change\"}.\\n`,\n );\n }\n const diagnostics = reports.flatMap((report) => report.diagnostics);\n const errors = diagnostics.some((item) => item.severity === \"error\");\n const tooManyWarnings =\n flags.maxWarnings !== undefined &&\n diagnostics.filter((item) => item.severity === \"warn\").length > flags.maxWarnings;\n process.exitCode = unsafe\n ? 2\n : errors || tooManyWarnings || (flags.check && changes.length > 0)\n ? 1\n : 0;\n}\ncommon(\n program.command(\"lint\").argument(\"[paths...]\", \"Markdown files/directories, or - for stdin\"),\n).action((inputs: string[], flags: Flags) => run(\"lint\", inputs, flags));\ncommon(\n program.command(\"format\").argument(\"[paths...]\", \"Markdown files/directories, or - for stdin\"),\n)\n .option(\"--check\", \"Fail if formatting changes would be needed\")\n .option(\"--diff\", \"Preview the complete diff (default for file inputs)\")\n .option(\"--write\", \"Write validated formatting changes\")\n .action((inputs: string[], flags: Flags) => run(\"format\", inputs, flags));\ncommon(program.command(\"config\").description(\"Inspect effective configuration\"))\n .command(\"explain <file>\")\n .action(async (file: string, _flags: unknown, command: Command) => {\n const flags = command.parent!.opts<Flags>();\n const loaded = await loadConfig(path.resolve(flags.root ?? \".\"), flags.config);\n if (flags.dialect) loaded.config.dialect = flags.dialect;\n const root = path.resolve(flags.root ?? loaded.root);\n process.stdout.write(\n JSON.stringify(\n {\n root,\n file: loaded.file ?? null,\n effective: resolveConfig(\n loaded.config,\n path.relative(root, path.resolve(file)).split(path.sep).join(\"/\"),\n loaded.plugins,\n ),\n },\n null,\n 2,\n ) + \"\\n\",\n );\n });\nprogram\n .command(\"rules\")\n .description(\"List built-in rules\")\n .action(() => {\n for (const [name, rule] of Object.entries(ruleRegistry()))\n process.stdout.write(`${name}\\t${rule.kind}\\t${rule.description}\\n`);\n });\ntry {\n await program.parseAsync();\n} catch (error) {\n process.stderr.write(`mdtools: ${error instanceof Error ? error.message : String(error)}\\n`);\n process.exitCode = 2;\n}\n"]}
@@ -0,0 +1,9 @@
1
+ import type { Config, Plugin } from "../core/types.js";
2
+ export interface LoadedConfig {
3
+ config: Config;
4
+ plugins: Plugin[];
5
+ root: string;
6
+ file?: string;
7
+ }
8
+ export declare function exists(file: string): Promise<boolean>;
9
+ export declare function loadConfig(start: string, explicit?: string): Promise<LoadedConfig>;
@@ -0,0 +1,75 @@
1
+ import { readFile, stat } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import { createRequire } from "node:module";
5
+ import { parse, printParseErrorCode } from "jsonc-parser";
6
+ import { validateConfig } from "./resolve.js";
7
+ export async function exists(file) {
8
+ try {
9
+ await stat(file);
10
+ return true;
11
+ }
12
+ catch (error) {
13
+ if (error.code === "ENOENT")
14
+ return false;
15
+ throw error;
16
+ }
17
+ }
18
+ export async function loadConfig(start, explicit) {
19
+ let file = explicit ? path.resolve(explicit) : undefined;
20
+ let fallbackRoot = path.resolve(start);
21
+ if (!file) {
22
+ let directory = path.resolve(start);
23
+ for (;;) {
24
+ const found = [];
25
+ for (const name of ["mdtools.config.jsonc", "mdtools.config.json", "mdtools.config.mjs"])
26
+ if (await exists(path.join(directory, name)))
27
+ found.push(path.join(directory, name));
28
+ if (found.length > 1)
29
+ throw new Error(`Multiple mdtools configuration files in ${directory}. Use --config.`);
30
+ if (found.length) {
31
+ file = found[0];
32
+ break;
33
+ }
34
+ if ((await exists(path.join(directory, ".git"))) ||
35
+ (await exists(path.join(directory, ".obsidian")))) {
36
+ fallbackRoot = directory;
37
+ break;
38
+ }
39
+ if (path.dirname(directory) === directory)
40
+ break;
41
+ directory = path.dirname(directory);
42
+ }
43
+ }
44
+ let value = {};
45
+ if (file?.endsWith(".mjs"))
46
+ value = (await import(pathToFileURL(file).href)).default;
47
+ else if (file) {
48
+ const errors = [];
49
+ value = parse(await readFile(file, "utf8"), errors, {
50
+ allowTrailingComma: true,
51
+ disallowComments: file.endsWith(".json"),
52
+ });
53
+ if (errors.length)
54
+ throw new Error(`Invalid JSON configuration: ${errors.map((error) => `${printParseErrorCode(error.error)} at offset ${error.offset}`).join(", ")}`);
55
+ }
56
+ validateConfig(value);
57
+ const root = file ? path.dirname(file) : fallbackRoot;
58
+ const plugins = [];
59
+ const require = createRequire(path.join(root, "mdtools.config.mjs"));
60
+ for (const specifier of value.plugins ?? []) {
61
+ const location = specifier.startsWith(".") || path.isAbsolute(specifier)
62
+ ? path.resolve(root, specifier)
63
+ : require.resolve(specifier);
64
+ const plugin = (await import(pathToFileURL(location).href))
65
+ .default;
66
+ if (!plugin ||
67
+ typeof plugin !== "object" ||
68
+ !("name" in plugin) ||
69
+ typeof plugin.name !== "string")
70
+ throw new Error(`Invalid plugin export: ${specifier}`);
71
+ plugins.push(plugin);
72
+ }
73
+ return { config: value, plugins, root, ...(file ? { file } : {}) };
74
+ }
75
+ //# sourceMappingURL=load.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load.js","sourceRoot":"","sources":["../../src/config/load.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFile, stat } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { createRequire } from \"node:module\";\nimport { parse, printParseErrorCode } from \"jsonc-parser\";\nimport type { ParseError } from \"jsonc-parser\";\nimport type { Config, Plugin } from \"../core/types.js\";\nimport { validateConfig } from \"./resolve.js\";\n\nexport interface LoadedConfig {\n config: Config;\n plugins: Plugin[];\n root: string;\n file?: string;\n}\nexport async function exists(file: string): Promise<boolean> {\n try {\n await stat(file);\n return true;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return false;\n throw error;\n }\n}\nexport async function loadConfig(start: string, explicit?: string): Promise<LoadedConfig> {\n let file = explicit ? path.resolve(explicit) : undefined;\n let fallbackRoot = path.resolve(start);\n if (!file) {\n let directory = path.resolve(start);\n for (;;) {\n const found: string[] = [];\n for (const name of [\"mdtools.config.jsonc\", \"mdtools.config.json\", \"mdtools.config.mjs\"])\n if (await exists(path.join(directory, name))) found.push(path.join(directory, name));\n if (found.length > 1)\n throw new Error(`Multiple mdtools configuration files in ${directory}. Use --config.`);\n if (found.length) {\n file = found[0];\n break;\n }\n if (\n (await exists(path.join(directory, \".git\"))) ||\n (await exists(path.join(directory, \".obsidian\")))\n ) {\n fallbackRoot = directory;\n break;\n }\n if (path.dirname(directory) === directory) break;\n directory = path.dirname(directory);\n }\n }\n let value: unknown = {};\n if (file?.endsWith(\".mjs\"))\n value = ((await import(pathToFileURL(file).href)) as { default: unknown }).default;\n else if (file) {\n const errors: ParseError[] = [];\n value = parse(await readFile(file, \"utf8\"), errors, {\n allowTrailingComma: true,\n disallowComments: file.endsWith(\".json\"),\n });\n if (errors.length)\n throw new Error(\n `Invalid JSON configuration: ${errors.map((error) => `${printParseErrorCode(error.error)} at offset ${error.offset}`).join(\", \")}`,\n );\n }\n validateConfig(value);\n const root = file ? path.dirname(file) : fallbackRoot;\n const plugins: Plugin[] = [];\n const require = createRequire(path.join(root, \"mdtools.config.mjs\"));\n for (const specifier of value.plugins ?? []) {\n const location =\n specifier.startsWith(\".\") || path.isAbsolute(specifier)\n ? path.resolve(root, specifier)\n : require.resolve(specifier);\n const plugin: unknown = ((await import(pathToFileURL(location).href)) as { default: unknown })\n .default;\n if (\n !plugin ||\n typeof plugin !== \"object\" ||\n !(\"name\" in plugin) ||\n typeof plugin.name !== \"string\"\n )\n throw new Error(`Invalid plugin export: ${specifier}`);\n plugins.push(plugin as Plugin);\n }\n return { config: value, plugins, root, ...(file ? { file } : {}) };\n}\n"]}
@@ -0,0 +1,93 @@
1
+ import type { Config, Plugin, ResolvedConfig, RuleSetting } from "../core/types.js";
2
+ export declare const presets: Record<string, Config>;
3
+ export declare const configSchema: {
4
+ $schema: string;
5
+ type: string;
6
+ additionalProperties: boolean;
7
+ properties: {
8
+ $schema: {
9
+ type: string;
10
+ };
11
+ extends: {
12
+ type: string;
13
+ items: {
14
+ type: string;
15
+ };
16
+ };
17
+ dialect: {
18
+ enum: string[];
19
+ };
20
+ rules: {
21
+ type: string;
22
+ additionalProperties: {
23
+ anyOf: ({
24
+ enum: string[];
25
+ } | {
26
+ type: string;
27
+ items: ({
28
+ enum: string[];
29
+ } | {
30
+ type: string;
31
+ })[];
32
+ minItems: number;
33
+ maxItems: number;
34
+ })[];
35
+ };
36
+ };
37
+ ignore: {
38
+ type: string;
39
+ items: {
40
+ type: string;
41
+ };
42
+ };
43
+ plugins: {
44
+ type: string;
45
+ items: {
46
+ type: string;
47
+ };
48
+ };
49
+ overrides: {
50
+ type: string;
51
+ items: {
52
+ type: string;
53
+ required: string[];
54
+ additionalProperties: boolean;
55
+ properties: {
56
+ files: {
57
+ type: string;
58
+ items: {
59
+ type: string;
60
+ };
61
+ minItems: number;
62
+ };
63
+ dialect: {
64
+ enum: string[];
65
+ };
66
+ rules: {
67
+ type: string;
68
+ additionalProperties: {
69
+ anyOf: ({
70
+ enum: string[];
71
+ } | {
72
+ type: string;
73
+ items: ({
74
+ enum: string[];
75
+ } | {
76
+ type: string;
77
+ })[];
78
+ minItems: number;
79
+ maxItems: number;
80
+ })[];
81
+ };
82
+ };
83
+ };
84
+ };
85
+ };
86
+ };
87
+ };
88
+ export declare function validateConfig(value: unknown): asserts value is Config;
89
+ export declare function setting(value: RuleSetting): {
90
+ severity: "off" | "warn" | "error";
91
+ options: Record<string, unknown>;
92
+ };
93
+ export declare function resolveConfig(config?: Config, path?: string, plugins?: Plugin[]): ResolvedConfig;