@hadden-industries/markdown-quality 0.1.0-alpha.2
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 +661 -0
- package/LICENSES/MIT-universal-ontology.txt +9 -0
- package/README.md +45 -0
- package/THIRD-PARTY-NOTICES.md +34 -0
- package/assets/native-build.json +56 -0
- package/assets/native-rights.json +1832 -0
- package/assets/snapper.toml +4 -0
- package/assets/tool-manifest.json +1897 -0
- package/docs/ci-trust.md +40 -0
- package/docs/consumer-guide.md +87 -0
- package/docs/support-and-release.md +72 -0
- package/package.json +51 -0
- package/schemas/configuration.schema.json +48 -0
- package/schemas/result.schema.json +247 -0
- package/src/analysis.js +119 -0
- package/src/cli.js +85 -0
- package/src/configuration.js +123 -0
- package/src/contracts.js +55 -0
- package/src/document-analysis.js +79 -0
- package/src/document-worker.js +30 -0
- package/src/documents.js +136 -0
- package/src/formatting.js +44 -0
- package/src/native-tool.js +135 -0
- package/src/prose-diagnostics.js +58 -0
- package/src/quality.js +156 -0
- package/src/replacement.js +62 -0
package/docs/ci-trust.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# CI trust and acquisition
|
|
2
|
+
|
|
3
|
+
The package repository, OwlAPI, and WebVOWL are public, as inspected through GitHub on 2026-10-05.
|
|
4
|
+
The owner selected public npm distribution after the private upload was rejected with E402.
|
|
5
|
+
Core and both native packages declare public access; anonymous installation is a required registry qualification.
|
|
6
|
+
Ordinary fork pull requests need no registry credential or paid npm subscription.
|
|
7
|
+
The direct CLI is the canonical integration.
|
|
8
|
+
A private shared Action is optional and does not solve public caller access.
|
|
9
|
+
|
|
10
|
+
| Event | Trusted graph and policy | Acquisition | Candidate processing | Current status |
|
|
11
|
+
| ------------------------ | ----------------------------------------------------- | ----------------------------------------------- | ---------------------------------------------------- | -------------------------------------------- |
|
|
12
|
+
| Package pull request | Public locked dependencies and fixed native manifests | Public upstream inputs, no private secrets | Package tests run without elevated authority | Hosted qualification pending |
|
|
13
|
+
| Pilot protected branch | Reviewed capability release and consumer policy | Public locked graph, lifecycle scripts disabled | Full authored scope without credentials | Public registry qualification pending |
|
|
14
|
+
| Pilot same-repository PR | Reviewed base tooling and policy | Separate trusted acquisition | Candidate Markdown and link target tree only | Architecture selected; qualification pending |
|
|
15
|
+
| Pilot fork PR | Reviewed base tooling and policy | Separate trusted acquisition | Candidate data only; no candidate scripts or graph | Architecture selected; qualification pending |
|
|
16
|
+
| Tool or policy update PR | Existing accepted graph until review | New graph requires owner review | Never treat candidate graph or exclusions as trusted | Required review remains explicit |
|
|
17
|
+
|
|
18
|
+
The package CI has no registry secret or OIDC permission.
|
|
19
|
+
It installs with lifecycle scripts disabled and qualifies both OS targets.
|
|
20
|
+
Packed-consumer tests acquire public JavaScript dependencies before testing offline frozen-lock reinstallation and runtime execution.
|
|
21
|
+
The source lock acquisition alone does not populate metadata for a fresh consumer install.
|
|
22
|
+
It cannot serve as evidence that a pilot's private access works.
|
|
23
|
+
|
|
24
|
+
A pilot integration must acquire the exact public tool graph from a trusted source without registry credentials and process a separately fetched candidate data tree.
|
|
25
|
+
Use reviewed base policy and ignore files at their original relative paths so candidate exclusions cannot silently weaken the required check.
|
|
26
|
+
Candidate checkout must not execute application tooling, Git hooks, package lifecycle scripts, JavaScript configuration, or a candidate CLI.
|
|
27
|
+
The checking process must not inherit checkout write credentials, npm configuration, or OIDC issuance authority.
|
|
28
|
+
Trusted policy review is required for any change to required scope or exclusions.
|
|
29
|
+
|
|
30
|
+
Do not give publication or repository-write credentials to a PR-controlled dependency graph.
|
|
31
|
+
Do not execute candidate source in an elevated `pull_request_target` job.
|
|
32
|
+
Public capability archives are available to all recipients; source/notice and integrity obligations still apply.
|
|
33
|
+
Unqualified event isolation or a missing registry tuple blocks pilot cutover.
|
|
34
|
+
|
|
35
|
+
The owner restored npm login and approved publish authentication on 2026-10-05.
|
|
36
|
+
The private Windows upload was rejected by npm with E402, and no accessible alpha.1 version was found.
|
|
37
|
+
The subsequent owner request selects public distribution and removes the paid-private-plan prerequisite.
|
|
38
|
+
Pilot cutover still requires exact public tuple readback, fresh credential-free installation, event/policy isolation and accepted consumer deltas.
|
|
39
|
+
|
|
40
|
+
References: [GitHub event trust](https://docs.github.com/en/actions/reference/security/securely-using-pull_request_target), [public scoped publication](https://docs.npmjs.com/creating-and-publishing-scoped-public-packages/), and [npm trusted publishing](https://docs.npmjs.com/trusted-publishers/).
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Consumer guide
|
|
2
|
+
|
|
3
|
+
Install an exact coordinated public release after registry availability is verified.
|
|
4
|
+
Acquisition needs no npm account or token; disable lifecycle scripts.
|
|
5
|
+
|
|
6
|
+
```sh
|
|
7
|
+
npm install --save-dev --save-exact --ignore-scripts @hadden-industries/markdown-quality@0.1.0-alpha.2
|
|
8
|
+
npm ci --ignore-scripts
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The version above identifies the candidate; it is not currently a published registry promise.
|
|
12
|
+
Keep the consumer lockfile.
|
|
13
|
+
An isolated `tooling/markdown` npm project supports consumers that do not use Node for their application.
|
|
14
|
+
Its commands must pass the actual repository root.
|
|
15
|
+
|
|
16
|
+
Place this bounded JSON policy at the repository root.
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"schemaVersion": 1,
|
|
21
|
+
"preset": "authored-gfm@1",
|
|
22
|
+
"include": ["*.md", "docs/**/*.md"],
|
|
23
|
+
"exclude": ["docs/reviews/**", "docs/generated/**"],
|
|
24
|
+
"ignoreFiles": [".gitignore", ".prettierignore"],
|
|
25
|
+
"lint": {},
|
|
26
|
+
"links": { "localFiles": true, "rootRelative": "reject" },
|
|
27
|
+
"layout": { "endOfLine": "preserve", "tabWidth": 2 }
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Include and exclude entries are relative slash-separated globs supplied to picomatch.
|
|
32
|
+
Explicit exclusions win.
|
|
33
|
+
Prettier owns the configured ignore files' native semantics.
|
|
34
|
+
Missing optional default ignore files are allowed.
|
|
35
|
+
Dependency, Git, and tooling directories cannot become document inputs.
|
|
36
|
+
Directory traversal never follows symbolic links or Windows junctions.
|
|
37
|
+
Explicit linked paths fail.
|
|
38
|
+
Formatting hard-linked files is unsupported.
|
|
39
|
+
|
|
40
|
+
Unknown fields, versions, and lint rules fail.
|
|
41
|
+
Lint overrides accept only `off`, `warn`, or `error` for installed `markdown/` rules.
|
|
42
|
+
Warnings are unresolved findings and block formatting.
|
|
43
|
+
GitHub alert labels are allowed.
|
|
44
|
+
Consumer JavaScript configs, arbitrary plugins, inline ESLint disable comments, and native executable overrides are not execution authority.
|
|
45
|
+
|
|
46
|
+
Local Markdown links, images, and definitions are parsed with maintained mdast/GFM libraries.
|
|
47
|
+
Percent-encoded filenames resolve after decoding.
|
|
48
|
+
Contained files and directories are valid targets.
|
|
49
|
+
External schemes are classified without network access.
|
|
50
|
+
Same-document fragments use the native ESLint Markdown rule.
|
|
51
|
+
Cross-document heading fragments and remote availability are outside this version's contract.
|
|
52
|
+
|
|
53
|
+
The library exports `runQuality({ root, config, mode, files })`.
|
|
54
|
+
Omit `files` for full discovery.
|
|
55
|
+
An explicit empty array selects nothing.
|
|
56
|
+
CLI literal filenames follow `--`; `--files-json` accepts the JSON array directly.
|
|
57
|
+
Results identify the package, preset, tools, config digest, selection, diagnostics, written paths, unchanged paths, unprocessed paths, and operational errors.
|
|
58
|
+
Text and JSON share the same result.
|
|
59
|
+
|
|
60
|
+
Exit `0` means clean or completed.
|
|
61
|
+
Exit `1` means content findings.
|
|
62
|
+
Exit `2` means configuration, tool, input, or operation failure.
|
|
63
|
+
Machine output follows the shipped result schema even for CLI input failures.
|
|
64
|
+
No excerpts, document bodies, or absolute root paths are emitted by default.
|
|
65
|
+
|
|
66
|
+
Limits are 2 MiB per document, 32 MiB per batch, 10,000 selected documents, 100,000 enumerated entries, and 256 KiB per configuration or ignore file.
|
|
67
|
+
Native invocations have a 15-second deadline and an 8 MiB output cap.
|
|
68
|
+
Document analysis runs in a 128 MiB worker with a 30-second deadline.
|
|
69
|
+
Discovered path bytes are capped at 4 MiB.
|
|
70
|
+
Diagnostics are capped at 1,000 records per document, 10,000 records across the batch, and 4 MiB per document and batch.
|
|
71
|
+
Exceeding a limit returns exit `2` before any formatting writes.
|
|
72
|
+
Text diagnostics escape terminal control characters; JSON retains structured relative filenames.
|
|
73
|
+
Lint messages describe the violated rule without quoting document content.
|
|
74
|
+
These are conservative operational limits; benchmark and platform acceptance remains part of candidate qualification.
|
|
75
|
+
|
|
76
|
+
Formatting compares maintained parsed structure and literals before replacement and verifies convergence.
|
|
77
|
+
The parsed comparison is an additional backstop, not a universal rendered-equivalence guarantee.
|
|
78
|
+
It conservatively refuses unsupported semantic or literal changes.
|
|
79
|
+
Ordinary file permission bits are preserved; ownership changes, alternate streams, ACL inheritance, arbitrary metadata, and adversarial filesystem races are not promised as preserved.
|
|
80
|
+
Use a clean baseline or retained preimages when original uncommitted bytes must be recoverable.
|
|
81
|
+
|
|
82
|
+
CI acquisition uses the exact public release and a trusted reviewed graph without registry credentials.
|
|
83
|
+
The checking process receives no publication, repository-write or OIDC credentials.
|
|
84
|
+
The package itself needs no registry credentials at runtime.
|
|
85
|
+
Fork content cannot supply the executable, dependency graph, workflow, scripts, or trusted policy.
|
|
86
|
+
An unavailable check is a blocked required check, not a successful skip.
|
|
87
|
+
The [CI trust decision](ci-trust.md) records the actual pilot constraints.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Support and release
|
|
2
|
+
|
|
3
|
+
The initial contract targets Node 24.21.0, Windows x64, and Ubuntu 24.04 x64 with glibc.
|
|
4
|
+
Only observed passing platform evidence can establish support.
|
|
5
|
+
Other Node majors, macOS, ARM, and musl are unqualified.
|
|
6
|
+
Controlled Linux builds require glibc 2.39, as supplied by Ubuntu 24.04.
|
|
7
|
+
Windows requires the installed x64 Microsoft Visual C++ runtime (`VCRUNTIME140.dll` and UCRT).
|
|
8
|
+
No system runtime installer or runtime download is shipped.
|
|
9
|
+
Core and platform packages share one immutable release version.
|
|
10
|
+
Consumers pin the package, preset, and lockfile.
|
|
11
|
+
|
|
12
|
+
The public contract includes CLI, library API, schemas, exit meanings, formatter output, and default diagnostics.
|
|
13
|
+
A compatible repair may use a patch version.
|
|
14
|
+
An opt-in compatible feature may use a minor version.
|
|
15
|
+
Changed defaults require a new preset major, and a package major when existing invocations change.
|
|
16
|
+
Dependency upgrades are classified from observed corpus and consumer deltas.
|
|
17
|
+
Never overwrite a published version.
|
|
18
|
+
|
|
19
|
+
The current alpha is not stable v1.0.
|
|
20
|
+
Stable promotion requires the same qualified tuple on both platforms, public registry readback and unauthenticated installation, both pilot acceptances, and a timed restoration exercise.
|
|
21
|
+
Changed/referrer mode and later fleet adoptions remain deferred.
|
|
22
|
+
|
|
23
|
+
Prepare assets explicitly from their frozen manifest.
|
|
24
|
+
Install source dependencies with `npm ci --ignore-scripts`.
|
|
25
|
+
Complete formatting and cheap syntax checks before freezing a candidate.
|
|
26
|
+
Run relevant tests and packed root/isolated consumers.
|
|
27
|
+
Retain independent correctness, security, platform, and rights review.
|
|
28
|
+
Recheck only affected questions after repairs.
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
node scripts/pack.js /approved/evidence/release
|
|
32
|
+
npm sbom --sbom-format cyclonedx
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The packer emits coordinated archives and a digest-bound release manifest.
|
|
36
|
+
Its core manifest replaces development file dependencies with exact release versions.
|
|
37
|
+
The source lock does not become the consumer lock.
|
|
38
|
+
The JS SBOM must be reconciled with every compiled native/runtime component before release.
|
|
39
|
+
An upstream Cargo lock is a superset of features and development inputs and does not prove exact shipped component coverage.
|
|
40
|
+
The original upstream license and per-asset repack identity are retained.
|
|
41
|
+
|
|
42
|
+
The owner selected public distribution after npm rejected the private publication with E402.
|
|
43
|
+
All three packages declare public access and share version 0.1.0-alpha.2.
|
|
44
|
+
The unpublished private alpha.1 candidate and all rejected attempts remain historical evidence.
|
|
45
|
+
Direct first publication uses explicit public access; a staged placeholder is unnecessary.
|
|
46
|
+
OIDC publication is preferred after actual package/workflow eligibility is configured.
|
|
47
|
+
Public installation needs no registry token, subscription, or OIDC authority.
|
|
48
|
+
Public npm provenance is eligible only through a qualified supported publisher; do not claim it for the local bootstrap.
|
|
49
|
+
Retain attributable source/build/repack evidence and qualify attestations separately.
|
|
50
|
+
|
|
51
|
+
Publish native packages first under a pilot tag and publish the core only when the tuple is coherent.
|
|
52
|
+
Read back hashes, public visibility, and fresh unauthenticated archive acquisition for every package.
|
|
53
|
+
Qualify root and isolated consumers on both supported platforms from the registry, without credentials.
|
|
54
|
+
Do not perform registry publication while rights, publisher authentication, independent assurance, or platform evidence remains missing.
|
|
55
|
+
A partial publication stays unpromoted and receives a new corrective version if bytes change.
|
|
56
|
+
|
|
57
|
+
For recovery, retain the exact consumer base revision, old manifest and lock, workflows, selected document preimages, and unrelated-file sentinels.
|
|
58
|
+
Restore only migration-owned changes.
|
|
59
|
+
Verify byte identities and required checks, then record elapsed restoration time.
|
|
60
|
+
Downgrading a package cannot restore formatted document bytes.
|
|
61
|
+
One pilot rehearsal is required before stable promotion.
|
|
62
|
+
|
|
63
|
+
Security reports use GitHub's private vulnerability reporting route when enabled; otherwise contact the repository owner through the project's existing private channel.
|
|
64
|
+
No unattended observation service is assumed.
|
|
65
|
+
During this authorized session, critical/high reports block promotion immediately.
|
|
66
|
+
Durable acknowledgement, triage, mitigation targets, reporting availability, and maintenance ownership require owner acceptance before stable release.
|
|
67
|
+
Retain release source, build inputs, notices, recipient delivery evidence, and recovery inputs for the maintained release lifetime and its accepted retention period.
|
|
68
|
+
|
|
69
|
+
Provide AGPL corresponding source and build materials alongside object-code delivery to actual recipients.
|
|
70
|
+
Preserve third-party license texts and attribution.
|
|
71
|
+
Public registry access does not waive source obligations.
|
|
72
|
+
The repository's original `LICENSE` bytes are verified by the full check.
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hadden-industries/markdown-quality",
|
|
3
|
+
"version": "0.1.0-alpha.2",
|
|
4
|
+
"description": "Shared, bounded Markdown formatting, GFM linting, and local-link checking",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "AGPL-3.0-only",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=24.21.0 <25"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"markdown-quality": "src/cli.js"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
".": "./src/quality.js",
|
|
15
|
+
"./configuration-schema": "./schemas/configuration.schema.json",
|
|
16
|
+
"./result-schema": "./schemas/result.schema.json"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"src",
|
|
20
|
+
"schemas",
|
|
21
|
+
"assets",
|
|
22
|
+
"LICENSE",
|
|
23
|
+
"LICENSES",
|
|
24
|
+
"README.md",
|
|
25
|
+
"THIRD-PARTY-NOTICES.md",
|
|
26
|
+
"docs/consumer-guide.md",
|
|
27
|
+
"docs/ci-trust.md",
|
|
28
|
+
"docs/support-and-release.md"
|
|
29
|
+
],
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/Hadden-Industries/markdown-quality.git"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@eslint/markdown": "8.0.3",
|
|
39
|
+
"ajv": "8.20.0",
|
|
40
|
+
"eslint": "10.12.0",
|
|
41
|
+
"mdast-util-from-markdown": "2.1.0",
|
|
42
|
+
"mdast-util-gfm": "3.1.0",
|
|
43
|
+
"micromark-extension-gfm": "3.0.0",
|
|
44
|
+
"picomatch": "4.0.7",
|
|
45
|
+
"prettier": "3.9.9"
|
|
46
|
+
},
|
|
47
|
+
"optionalDependencies": {
|
|
48
|
+
"@hadden-industries/markdown-quality-win32-x64": "0.1.0-alpha.2",
|
|
49
|
+
"@hadden-industries/markdown-quality-linux-x64": "0.1.0-alpha.2"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"additionalProperties": false,
|
|
5
|
+
"required": ["schemaVersion", "preset", "include"],
|
|
6
|
+
"properties": {
|
|
7
|
+
"schemaVersion": { "const": 1 },
|
|
8
|
+
"preset": { "const": "authored-gfm@1" },
|
|
9
|
+
"include": {
|
|
10
|
+
"type": "array",
|
|
11
|
+
"minItems": 1,
|
|
12
|
+
"maxItems": 100,
|
|
13
|
+
"items": { "type": "string", "minLength": 1, "maxLength": 512 }
|
|
14
|
+
},
|
|
15
|
+
"exclude": {
|
|
16
|
+
"type": "array",
|
|
17
|
+
"maxItems": 100,
|
|
18
|
+
"items": { "type": "string", "minLength": 1, "maxLength": 512 }
|
|
19
|
+
},
|
|
20
|
+
"ignoreFiles": {
|
|
21
|
+
"type": "array",
|
|
22
|
+
"maxItems": 10,
|
|
23
|
+
"uniqueItems": true,
|
|
24
|
+
"items": { "type": "string", "minLength": 1, "maxLength": 512 }
|
|
25
|
+
},
|
|
26
|
+
"lint": {
|
|
27
|
+
"type": "object",
|
|
28
|
+
"maxProperties": 30,
|
|
29
|
+
"additionalProperties": { "enum": ["off", "warn", "error"] }
|
|
30
|
+
},
|
|
31
|
+
"links": {
|
|
32
|
+
"type": "object",
|
|
33
|
+
"additionalProperties": false,
|
|
34
|
+
"properties": {
|
|
35
|
+
"localFiles": { "type": "boolean" },
|
|
36
|
+
"rootRelative": { "enum": ["reject", "root"] }
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"layout": {
|
|
40
|
+
"type": "object",
|
|
41
|
+
"additionalProperties": false,
|
|
42
|
+
"properties": {
|
|
43
|
+
"endOfLine": { "enum": ["lf", "crlf", "preserve"] },
|
|
44
|
+
"tabWidth": { "type": "integer", "minimum": 1, "maximum": 8 }
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"additionalProperties": false,
|
|
5
|
+
"required": [
|
|
6
|
+
"schemaVersion",
|
|
7
|
+
"package",
|
|
8
|
+
"preset",
|
|
9
|
+
"tools",
|
|
10
|
+
"operation",
|
|
11
|
+
"configDigest",
|
|
12
|
+
"selection",
|
|
13
|
+
"diagnostics",
|
|
14
|
+
"outcome",
|
|
15
|
+
"exitCode",
|
|
16
|
+
"written",
|
|
17
|
+
"unchanged",
|
|
18
|
+
"unprocessed",
|
|
19
|
+
"errors",
|
|
20
|
+
"configuration"
|
|
21
|
+
],
|
|
22
|
+
"properties": {
|
|
23
|
+
"schemaVersion": {
|
|
24
|
+
"const": 1
|
|
25
|
+
},
|
|
26
|
+
"package": {
|
|
27
|
+
"type": "object",
|
|
28
|
+
"additionalProperties": false,
|
|
29
|
+
"required": ["name", "version"],
|
|
30
|
+
"properties": {
|
|
31
|
+
"name": {
|
|
32
|
+
"type": "string"
|
|
33
|
+
},
|
|
34
|
+
"version": {
|
|
35
|
+
"type": "string"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"preset": {
|
|
40
|
+
"const": "authored-gfm@1"
|
|
41
|
+
},
|
|
42
|
+
"tools": {
|
|
43
|
+
"type": "object",
|
|
44
|
+
"additionalProperties": {
|
|
45
|
+
"type": "string"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"operation": {
|
|
49
|
+
"enum": ["check", "format", "inspect", "unknown"]
|
|
50
|
+
},
|
|
51
|
+
"configDigest": {
|
|
52
|
+
"type": ["string", "null"]
|
|
53
|
+
},
|
|
54
|
+
"selection": {
|
|
55
|
+
"type": "object",
|
|
56
|
+
"additionalProperties": false,
|
|
57
|
+
"required": ["mode", "files", "exclusions"],
|
|
58
|
+
"properties": {
|
|
59
|
+
"mode": {
|
|
60
|
+
"enum": ["full", "explicit"]
|
|
61
|
+
},
|
|
62
|
+
"files": {
|
|
63
|
+
"$ref": "#/definitions/paths"
|
|
64
|
+
},
|
|
65
|
+
"exclusions": {
|
|
66
|
+
"type": "array",
|
|
67
|
+
"items": {
|
|
68
|
+
"type": "object",
|
|
69
|
+
"required": ["path", "reason"],
|
|
70
|
+
"additionalProperties": false,
|
|
71
|
+
"properties": {
|
|
72
|
+
"path": {
|
|
73
|
+
"type": "string"
|
|
74
|
+
},
|
|
75
|
+
"reason": {
|
|
76
|
+
"enum": ["mandatory", "not-included", "excluded", "ignored"]
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
"diagnostics": {
|
|
84
|
+
"type": "array",
|
|
85
|
+
"items": {
|
|
86
|
+
"type": "object",
|
|
87
|
+
"additionalProperties": false,
|
|
88
|
+
"required": [
|
|
89
|
+
"path",
|
|
90
|
+
"source",
|
|
91
|
+
"rule",
|
|
92
|
+
"line",
|
|
93
|
+
"column",
|
|
94
|
+
"message",
|
|
95
|
+
"severity"
|
|
96
|
+
],
|
|
97
|
+
"properties": {
|
|
98
|
+
"path": {
|
|
99
|
+
"type": "string"
|
|
100
|
+
},
|
|
101
|
+
"source": {
|
|
102
|
+
"enum": ["formatter", "snapper", "eslint", "links"]
|
|
103
|
+
},
|
|
104
|
+
"rule": {
|
|
105
|
+
"type": "string"
|
|
106
|
+
},
|
|
107
|
+
"line": {
|
|
108
|
+
"type": "integer",
|
|
109
|
+
"minimum": 1
|
|
110
|
+
},
|
|
111
|
+
"column": {
|
|
112
|
+
"type": "integer",
|
|
113
|
+
"minimum": 1
|
|
114
|
+
},
|
|
115
|
+
"message": {
|
|
116
|
+
"type": "string"
|
|
117
|
+
},
|
|
118
|
+
"severity": {
|
|
119
|
+
"enum": ["error", "warning"]
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
"outcome": {
|
|
125
|
+
"enum": ["clean", "findings", "error"]
|
|
126
|
+
},
|
|
127
|
+
"exitCode": {
|
|
128
|
+
"enum": [0, 1, 2]
|
|
129
|
+
},
|
|
130
|
+
"written": {
|
|
131
|
+
"$ref": "#/definitions/paths"
|
|
132
|
+
},
|
|
133
|
+
"unchanged": {
|
|
134
|
+
"$ref": "#/definitions/paths"
|
|
135
|
+
},
|
|
136
|
+
"unprocessed": {
|
|
137
|
+
"$ref": "#/definitions/paths"
|
|
138
|
+
},
|
|
139
|
+
"errors": {
|
|
140
|
+
"type": "array",
|
|
141
|
+
"items": {
|
|
142
|
+
"type": "object",
|
|
143
|
+
"additionalProperties": false,
|
|
144
|
+
"required": ["code", "message"],
|
|
145
|
+
"properties": {
|
|
146
|
+
"code": {
|
|
147
|
+
"type": "string"
|
|
148
|
+
},
|
|
149
|
+
"message": {
|
|
150
|
+
"type": "string"
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
"configuration": {
|
|
156
|
+
"anyOf": [
|
|
157
|
+
{
|
|
158
|
+
"type": "null"
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"type": "object",
|
|
162
|
+
"additionalProperties": false,
|
|
163
|
+
"required": ["schemaVersion", "preset", "include"],
|
|
164
|
+
"properties": {
|
|
165
|
+
"schemaVersion": {
|
|
166
|
+
"const": 1
|
|
167
|
+
},
|
|
168
|
+
"preset": {
|
|
169
|
+
"const": "authored-gfm@1"
|
|
170
|
+
},
|
|
171
|
+
"include": {
|
|
172
|
+
"type": "array",
|
|
173
|
+
"minItems": 1,
|
|
174
|
+
"maxItems": 100,
|
|
175
|
+
"items": {
|
|
176
|
+
"type": "string",
|
|
177
|
+
"minLength": 1,
|
|
178
|
+
"maxLength": 512
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
"exclude": {
|
|
182
|
+
"type": "array",
|
|
183
|
+
"maxItems": 100,
|
|
184
|
+
"items": {
|
|
185
|
+
"type": "string",
|
|
186
|
+
"minLength": 1,
|
|
187
|
+
"maxLength": 512
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
"ignoreFiles": {
|
|
191
|
+
"type": "array",
|
|
192
|
+
"maxItems": 10,
|
|
193
|
+
"uniqueItems": true,
|
|
194
|
+
"items": {
|
|
195
|
+
"type": "string",
|
|
196
|
+
"minLength": 1,
|
|
197
|
+
"maxLength": 512
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
"lint": {
|
|
201
|
+
"type": "object",
|
|
202
|
+
"maxProperties": 30,
|
|
203
|
+
"additionalProperties": {
|
|
204
|
+
"enum": ["off", "warn", "error"]
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
"links": {
|
|
208
|
+
"type": "object",
|
|
209
|
+
"additionalProperties": false,
|
|
210
|
+
"properties": {
|
|
211
|
+
"localFiles": {
|
|
212
|
+
"type": "boolean"
|
|
213
|
+
},
|
|
214
|
+
"rootRelative": {
|
|
215
|
+
"enum": ["reject", "root"]
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
"layout": {
|
|
220
|
+
"type": "object",
|
|
221
|
+
"additionalProperties": false,
|
|
222
|
+
"properties": {
|
|
223
|
+
"endOfLine": {
|
|
224
|
+
"enum": ["lf", "crlf", "preserve"]
|
|
225
|
+
},
|
|
226
|
+
"tabWidth": {
|
|
227
|
+
"type": "integer",
|
|
228
|
+
"minimum": 1,
|
|
229
|
+
"maximum": 8
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
]
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
"definitions": {
|
|
239
|
+
"paths": {
|
|
240
|
+
"type": "array",
|
|
241
|
+
"uniqueItems": true,
|
|
242
|
+
"items": {
|
|
243
|
+
"type": "string"
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
package/src/analysis.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
import { ESLint } from "eslint";
|
|
3
|
+
import markdown from "@eslint/markdown";
|
|
4
|
+
import { fromMarkdown } from "mdast-util-from-markdown";
|
|
5
|
+
import { gfm } from "micromark-extension-gfm";
|
|
6
|
+
import { gfmFromMarkdown } from "mdast-util-gfm";
|
|
7
|
+
import { lstatSync } from "node:fs";
|
|
8
|
+
import { dirname, resolve } from "node:path";
|
|
9
|
+
import { safePath } from "./configuration.js";
|
|
10
|
+
import { fail, limits } from "./contracts.js";
|
|
11
|
+
export const parse = (text) =>
|
|
12
|
+
fromMarkdown(text, {
|
|
13
|
+
extensions: [gfm()],
|
|
14
|
+
mdastExtensions: [gfmFromMarkdown()],
|
|
15
|
+
});
|
|
16
|
+
export function createLinter(context) {
|
|
17
|
+
const defaults = {
|
|
18
|
+
...markdown.configs.recommended[0].rules,
|
|
19
|
+
"markdown/table-column-count": "error",
|
|
20
|
+
"markdown/no-missing-label-refs": [
|
|
21
|
+
"error",
|
|
22
|
+
{ allowLabels: ["!NOTE", "!TIP", "!IMPORTANT", "!WARNING", "!CAUTION"] },
|
|
23
|
+
],
|
|
24
|
+
};
|
|
25
|
+
const rules = { ...defaults };
|
|
26
|
+
for (const [rule, severity] of Object.entries(context.config.lint))
|
|
27
|
+
rules[rule] = Array.isArray(defaults[rule])
|
|
28
|
+
? [severity, ...defaults[rule].slice(1)]
|
|
29
|
+
: severity;
|
|
30
|
+
return new ESLint({
|
|
31
|
+
cwd: context.root,
|
|
32
|
+
overrideConfigFile: true,
|
|
33
|
+
ignore: false,
|
|
34
|
+
overrideConfig: [
|
|
35
|
+
{
|
|
36
|
+
files: ["**/*.md"],
|
|
37
|
+
plugins: { markdown },
|
|
38
|
+
language: "markdown/gfm",
|
|
39
|
+
linterOptions: { noInlineConfig: true },
|
|
40
|
+
rules,
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
export async function lintDocument(linter, text, file) {
|
|
46
|
+
const [result] = await linter.lintText(text, { filePath: file });
|
|
47
|
+
if (result.messages.length > limits.documentDiagnostics)
|
|
48
|
+
fail(
|
|
49
|
+
"DIAGNOSTIC_LIMIT",
|
|
50
|
+
"Diagnostic count or output bytes exceed the limit.",
|
|
51
|
+
);
|
|
52
|
+
return result.messages.map((d) => ({
|
|
53
|
+
source: "eslint",
|
|
54
|
+
rule: d.ruleId ?? "parse",
|
|
55
|
+
line: d.line ?? 1,
|
|
56
|
+
column: d.column ?? 1,
|
|
57
|
+
message: d.ruleId
|
|
58
|
+
? (markdown.rules[d.ruleId.slice(9)]?.meta.docs.description ??
|
|
59
|
+
"Markdown rule violation.")
|
|
60
|
+
: "Markdown parsing failed.",
|
|
61
|
+
severity: d.severity === 2 ? "error" : "warning",
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
export function checkLinks(context, text, file) {
|
|
65
|
+
if (!context.config.links.localFiles) return [];
|
|
66
|
+
const diagnostics = [];
|
|
67
|
+
function walk(node) {
|
|
68
|
+
if (["link", "image", "definition"].includes(node.type)) {
|
|
69
|
+
const target = node.url;
|
|
70
|
+
if (
|
|
71
|
+
target &&
|
|
72
|
+
!target.startsWith("#") &&
|
|
73
|
+
!/^[a-zA-Z][a-zA-Z\d+.-]*:/u.test(target) &&
|
|
74
|
+
!target.startsWith("//")
|
|
75
|
+
) {
|
|
76
|
+
try {
|
|
77
|
+
const raw = target.split(/[?#]/u)[0];
|
|
78
|
+
const decoded = decodeURIComponent(raw);
|
|
79
|
+
if (decoded.includes("\\") || decoded.includes("\0"))
|
|
80
|
+
throw new Error("Invalid target.");
|
|
81
|
+
if (
|
|
82
|
+
decoded.startsWith("/") &&
|
|
83
|
+
context.config.links.rootRelative === "reject"
|
|
84
|
+
)
|
|
85
|
+
throw new Error(
|
|
86
|
+
"Root-relative links require explicit root policy.",
|
|
87
|
+
);
|
|
88
|
+
const path = safePath(
|
|
89
|
+
context.root,
|
|
90
|
+
decoded.startsWith("/")
|
|
91
|
+
? resolve(context.root, "." + decoded)
|
|
92
|
+
: resolve(context.root, dirname(file), decoded),
|
|
93
|
+
);
|
|
94
|
+
const stat = lstatSync(path);
|
|
95
|
+
if (!stat.isFile() && !stat.isDirectory())
|
|
96
|
+
throw new Error("Target is not a file or directory.");
|
|
97
|
+
} catch (error) {
|
|
98
|
+
if (diagnostics.length >= limits.documentDiagnostics)
|
|
99
|
+
fail(
|
|
100
|
+
"DIAGNOSTIC_LIMIT",
|
|
101
|
+
"Diagnostic count or output bytes exceed the limit.",
|
|
102
|
+
);
|
|
103
|
+
diagnostics.push({
|
|
104
|
+
source: "links",
|
|
105
|
+
rule: "local-target",
|
|
106
|
+
line: node.position.start.line,
|
|
107
|
+
column: node.position.start.column,
|
|
108
|
+
message:
|
|
109
|
+
"Local target is missing, invalid, linked, or outside the root.",
|
|
110
|
+
severity: "error",
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
for (const child of node.children ?? []) walk(child);
|
|
116
|
+
}
|
|
117
|
+
walk(parse(text));
|
|
118
|
+
return diagnostics;
|
|
119
|
+
}
|