@casomoltd/tooling 0.36.1 → 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/bin/check-listed-files.mjs +127 -0
- package/bin/commit-msg.sh +4 -0
- package/eslint/house-rules.mjs +102 -0
- package/eslint/index.mjs +5 -14
- package/eslint/lib.mjs +13 -11
- package/package.json +10 -6
package/README.md
CHANGED
|
@@ -8,8 +8,8 @@ Shared linting, formatting, commit config, and CLI tools for Casomo Ltd's repos.
|
|
|
8
8
|
|
|
9
9
|
| Export | Description |
|
|
10
10
|
|---|---|
|
|
11
|
-
| `@casomoltd/tooling/eslint` | `createBaseConfig()` — ESLint flat config with Next.js, TypeScript, and
|
|
12
|
-
| `@casomoltd/tooling/eslint-lib` |
|
|
11
|
+
| `@casomoltd/tooling/eslint` | `createBaseConfig()` — ESLint flat config with Next.js, TypeScript, SonarJS and the house rules ([docs/lint.md](docs/lint.md)) |
|
|
12
|
+
| `@casomoltd/tooling/eslint-lib` | `createLibConfig()` — the same house rules for a TypeScript library ([docs/lint.md](docs/lint.md)) |
|
|
13
13
|
| `@casomoltd/tooling/prettier` | Prettier config object |
|
|
14
14
|
| `@casomoltd/tooling/commitlint` | commitlint config with `no-ai-attribution` plugin |
|
|
15
15
|
| `@casomoltd/tooling/knip` | Knip config for unused exports/dependencies |
|
|
@@ -25,7 +25,8 @@ Shared linting, formatting, commit config, and CLI tools for Casomo Ltd's repos.
|
|
|
25
25
|
| `check-linear` | Pre-push guard — rejects a push that would put a merge commit on the remote. History is linear; a branch catches up by rebasing. Only what the push adds is judged, so merges already on the remote stay |
|
|
26
26
|
| `pre-push` | Husky pre-push hook — runs check, then the version, tag and linear-history guards |
|
|
27
27
|
| `pre-commit` | Husky pre-commit hook — runs `npm run check` |
|
|
28
|
-
| `commit-msg` | Husky commit-msg hook — runs commitlint |
|
|
28
|
+
| `commit-msg` | Husky commit-msg hook — runs commitlint, then `check-listed-files` |
|
|
29
|
+
| `check-listed-files` | Refuse a commit that changes a file still listed in `eslint-suppressions.json`, unless a `Lint-deferred:` trailer says why ([docs/lint.md](docs/lint.md)) |
|
|
29
30
|
| `readability` | Measure reading difficulty of page content |
|
|
30
31
|
| `screenshot` | Capture a dev server page via Playwright — an agent's visual feedback loop |
|
|
31
32
|
| `build-report` | Compile a Typst client report to PDF with the house template |
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Refuse a commit that changes a file still listed in the repo's
|
|
4
|
+
* `eslint-suppressions.json`, unless the message says why it is deferred.
|
|
5
|
+
*
|
|
6
|
+
* The house size limits are lint errors, and a file that already broke one
|
|
7
|
+
* when the limit was switched on is listed there instead of failing. The
|
|
8
|
+
* list only shrinks if touching a file is the moment it is tidied: tidy it
|
|
9
|
+
* in its own commit (fix it, then `eslint --prune-suppressions`), and the
|
|
10
|
+
* change that needed it follows. ESLint itself fails while a fixed file
|
|
11
|
+
* keeps its entry, so a tidied file always comes off the list. A commit that
|
|
12
|
+
* has to change a listed file anyway carries a trailer naming why, so the
|
|
13
|
+
* deferral shows in `git log`:
|
|
14
|
+
*
|
|
15
|
+
* Lint-deferred: a one-line hotfix; the tidy follows in its own commit
|
|
16
|
+
*
|
|
17
|
+
* What it reads, and what it assumes:
|
|
18
|
+
*
|
|
19
|
+
* - **The list as staged**, so the commit that tidies a file and drops its
|
|
20
|
+
* entry passes.
|
|
21
|
+
* - **The files staged now**, renames counted under their old path too. An
|
|
22
|
+
* amend is checked on what it newly stages, not on the whole commit.
|
|
23
|
+
* - **One list, at the repo root**, keyed by root-relative path, which is
|
|
24
|
+
* where ESLint writes it when run from the root. A list anywhere else
|
|
25
|
+
* fails the check rather than being silently missed.
|
|
26
|
+
* - **No merge commits:** a merge carries changes each branch already
|
|
27
|
+
* committed and was checked for.
|
|
28
|
+
*
|
|
29
|
+
* Runs from the shared commit-msg hook, the first point the message exists.
|
|
30
|
+
*
|
|
31
|
+
* Usage:
|
|
32
|
+
* check-listed-files <commit-message-file>
|
|
33
|
+
*/
|
|
34
|
+
import {execFileSync} from "node:child_process";
|
|
35
|
+
import {readFileSync, realpathSync} from "node:fs";
|
|
36
|
+
import {pathToFileURL} from "node:url";
|
|
37
|
+
|
|
38
|
+
/** ESLint's default suppressions file, at the repo root. */
|
|
39
|
+
export const SUPPRESSIONS_FILE = "eslint-suppressions.json";
|
|
40
|
+
/** The trailer that lets a listed file through, with a reason on the same
|
|
41
|
+
* line. Git reads trailer keys without regard to case. */
|
|
42
|
+
export const DEFERRED_TRAILER = /^Lint-deferred:[ \t]*\S/im;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The staged files that are listed, each with the rules it is listed for.
|
|
46
|
+
*
|
|
47
|
+
* @param {readonly string[]} staged - repo-relative paths in the commit
|
|
48
|
+
* @param {Record<string, Record<string, {count: number}>>} listed - the
|
|
49
|
+
* staged suppressions file, keyed by repo-relative path
|
|
50
|
+
* @returns {{file: string, rules: string[]}[]}
|
|
51
|
+
*/
|
|
52
|
+
export function listedInCommit(staged, listed) {
|
|
53
|
+
return staged
|
|
54
|
+
.filter((file) => Object.hasOwn(listed, file))
|
|
55
|
+
.map((file) => ({file, rules: Object.keys(listed[file])}));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Whether the message defers the tidy with a reason. */
|
|
59
|
+
export function defers(message) {
|
|
60
|
+
return DEFERRED_TRAILER.test(message);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const git = (...args) => execFileSync("git", args,
|
|
64
|
+
{encoding: "utf8", stdio: ["ignore", "pipe", "pipe"]});
|
|
65
|
+
|
|
66
|
+
/** Whether git can resolve a ref or object, without throwing. */
|
|
67
|
+
function resolves(ref) {
|
|
68
|
+
try {
|
|
69
|
+
git("rev-parse", "-q", "--verify", ref);
|
|
70
|
+
return true;
|
|
71
|
+
} catch {
|
|
72
|
+
// Not resolvable is the answer asked for, not a failure.
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** The repo's suppressions as staged; none where the repo stages no list.
|
|
78
|
+
* A list that is staged but unreadable throws: it is not an empty list. */
|
|
79
|
+
function stagedSuppressions() {
|
|
80
|
+
if (!resolves(`:${SUPPRESSIONS_FILE}`)) return {};
|
|
81
|
+
return JSON.parse(git("show", `:${SUPPRESSIONS_FILE}`));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Suppressions files tracked anywhere but the repo root. */
|
|
85
|
+
function listsBelowRoot() {
|
|
86
|
+
return git("ls-files", `*${SUPPRESSIONS_FILE}`).split("\n")
|
|
87
|
+
.filter((path) => path && path !== SUPPRESSIONS_FILE);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** The paths the commit touches, a rename under both its old and new path
|
|
91
|
+
* and a deletion under its own. */
|
|
92
|
+
function stagedPaths() {
|
|
93
|
+
return git("diff", "--cached", "--name-only", "--no-renames",
|
|
94
|
+
"--diff-filter=ACMRD").split("\n").filter(Boolean);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function main(messageFile) {
|
|
98
|
+
if (resolves("MERGE_HEAD")) return;
|
|
99
|
+
const elsewhere = listsBelowRoot();
|
|
100
|
+
if (elsewhere.length > 0) {
|
|
101
|
+
console.error("check-listed-files: suppressions lists below the repo "
|
|
102
|
+
+ `root are not read, so this check cannot hold: ${elsewhere.join(", ")}`);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
const hits = listedInCommit(stagedPaths(), stagedSuppressions());
|
|
106
|
+
if (hits.length === 0) return;
|
|
107
|
+
if (defers(readFileSync(messageFile, "utf8"))) {
|
|
108
|
+
for (const {file} of hits) {
|
|
109
|
+
console.warn(`check-listed-files: ${file} is listed; tidy deferred`);
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
console.error("check-listed-files: this commit changes files listed in "
|
|
114
|
+
+ `${SUPPRESSIONS_FILE}. Tidy each in its own commit first (fix it, `
|
|
115
|
+
+ "then eslint --prune-suppressions), or add a 'Lint-deferred: <why>' "
|
|
116
|
+
+ "trailer:");
|
|
117
|
+
for (const {file, rules} of hits) {
|
|
118
|
+
console.error(` ${file}: ${rules.join(", ")}`);
|
|
119
|
+
}
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Run through npm's bin symlink, argv[1] is the link and import.meta.url
|
|
124
|
+
// the real file, so compare real paths.
|
|
125
|
+
if (import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
|
|
126
|
+
main(process.argv[2]);
|
|
127
|
+
}
|
package/bin/commit-msg.sh
CHANGED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The house rules every repo's ESLint config shares: line length; the size
|
|
3
|
+
* of a function, its parameter list, a file, its branching and nesting;
|
|
4
|
+
* named numbers; no non-null assertions; and exhaustive switches. Both the
|
|
5
|
+
* Next config (`index.mjs`) and the library config (`lib.mjs`) spread these
|
|
6
|
+
* in, so each rule is stated once. Every rule is on unless a reason is
|
|
7
|
+
* written beside the exception.
|
|
8
|
+
*
|
|
9
|
+
* They are errors, never warnings: a warning nobody has to act on is how a
|
|
10
|
+
* module grows past what anyone can read. A file that already breaks one is
|
|
11
|
+
* recorded in the repo's `eslint-suppressions.json` (`eslint
|
|
12
|
+
* --suppress-all`), and `check-listed-files` fails a commit that touches it
|
|
13
|
+
* until it is tidied, unless the message carries a `Lint-deferred:` reason.
|
|
14
|
+
* A repo whose files are transcribed reference data (published tables,
|
|
15
|
+
* cited at the data) exempts those paths from
|
|
16
|
+
* `@typescript-eslint/no-magic-numbers` in its own config, saying why.
|
|
17
|
+
* What each rule is for, and every exemption, is in `docs/lint.md`.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** Lines a function may run to, blank lines and comments not counted:
|
|
21
|
+
* ESLint's default. */
|
|
22
|
+
const FUNCTION_LINES = 50;
|
|
23
|
+
/** Parameters a function may take; past this, pass a value object.
|
|
24
|
+
* Proposed here; no external source (ESLint's default is 3). */
|
|
25
|
+
const PARAMS = 4;
|
|
26
|
+
/** Lines a file may run to, blank lines and comments not counted.
|
|
27
|
+
* Proposed here; no external source (ESLint's default is 300). */
|
|
28
|
+
const FILE_LINES = 400;
|
|
29
|
+
/** Cyclomatic complexity a function may reach. Proposed here; no external
|
|
30
|
+
* source (ESLint's default is 20). */
|
|
31
|
+
const COMPLEXITY = 10;
|
|
32
|
+
|
|
33
|
+
/** How deep blocks may nest before the code should be flattened.
|
|
34
|
+
* Proposed here; no external source (ESLint's default is 4). */
|
|
35
|
+
const DEPTH = 3;
|
|
36
|
+
|
|
37
|
+
const SKIP_BLANKS_AND_COMMENTS = { skipBlankLines: true, skipComments: true };
|
|
38
|
+
|
|
39
|
+
/** The rules, as one flat-config block for every file. */
|
|
40
|
+
export const HOUSE_RULES = {
|
|
41
|
+
rules: {
|
|
42
|
+
// URLs and regex literals are the deviations from 88: neither can be
|
|
43
|
+
// broken without changing it. No other long line is allowed, so strings
|
|
44
|
+
// and template literals are not exempt.
|
|
45
|
+
'max-len': [
|
|
46
|
+
'error',
|
|
47
|
+
{ code: 88, ignoreUrls: true, ignoreRegExpLiterals: true },
|
|
48
|
+
],
|
|
49
|
+
'max-lines-per-function': [
|
|
50
|
+
'error', { max: FUNCTION_LINES, ...SKIP_BLANKS_AND_COMMENTS },
|
|
51
|
+
],
|
|
52
|
+
// The TypeScript version does not count a `this:` type annotation.
|
|
53
|
+
'max-params': 'off',
|
|
54
|
+
'@typescript-eslint/max-params': ['error', { max: PARAMS }],
|
|
55
|
+
'max-lines': ['error', { max: FILE_LINES, ...SKIP_BLANKS_AND_COMMENTS }],
|
|
56
|
+
complexity: ['error', COMPLEXITY],
|
|
57
|
+
'max-depth': ['error', DEPTH],
|
|
58
|
+
// A number in code is named, so the reader sees what it is. Counting
|
|
59
|
+
// and indexing numbers, enums and type positions are not magic, nor a
|
|
60
|
+
// parameter's default or a readonly class property, which already name
|
|
61
|
+
// the number they hold. The TypeScript version replaces the base rule.
|
|
62
|
+
'no-magic-numbers': 'off',
|
|
63
|
+
'@typescript-eslint/no-magic-numbers': ['error', {
|
|
64
|
+
ignore: [-1, 0, 1],
|
|
65
|
+
ignoreArrayIndexes: true,
|
|
66
|
+
ignoreDefaultValues: true,
|
|
67
|
+
ignoreEnums: true,
|
|
68
|
+
ignoreNumericLiteralTypes: true,
|
|
69
|
+
ignoreReadonlyClassProperties: true,
|
|
70
|
+
ignoreTypeIndexes: true,
|
|
71
|
+
}],
|
|
72
|
+
// A `!` tells the compiler a value exists without checking that it does.
|
|
73
|
+
'@typescript-eslint/no-non-null-assertion': 'error',
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/** Rules that need the type checker, for TypeScript files only. A switch
|
|
78
|
+
* over a union names every member, so a new member is a lint error at each
|
|
79
|
+
* switch rather than a silent fall-through. Type information also brings on the
|
|
80
|
+
* type-aware rules in sonarjs's recommended set, which are silent
|
|
81
|
+
* without it. */
|
|
82
|
+
export const TYPED_RULES = {
|
|
83
|
+
files: ['**/*.ts', '**/*.tsx', '**/*.mts'],
|
|
84
|
+
// A declaration file holds no code to switch on.
|
|
85
|
+
ignores: ['**/*.d.ts', '**/*.d.mts'],
|
|
86
|
+
languageOptions: { parserOptions: { projectService: true } },
|
|
87
|
+
rules: { '@typescript-eslint/switch-exhaustiveness-check': 'error' },
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/** A test file's cases often sit in one top-level function by design (a
|
|
91
|
+
* `describe` block, an assert script), and its expected figures are
|
|
92
|
+
* literals by design, so function length and magic numbers are not held
|
|
93
|
+
* against test files; every other limit is. */
|
|
94
|
+
export const TEST_FILES_EXEMPT = {
|
|
95
|
+
files: [
|
|
96
|
+
'**/*.test.*', '**/*.spec.*', '**/tests/**', '**/test/**', '**/__tests__/**',
|
|
97
|
+
],
|
|
98
|
+
rules: {
|
|
99
|
+
'max-lines-per-function': 'off',
|
|
100
|
+
'@typescript-eslint/no-magic-numbers': 'off',
|
|
101
|
+
},
|
|
102
|
+
};
|
package/eslint/index.mjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import {HOUSE_RULES, TEST_FILES_EXEMPT, TYPED_RULES} from "./house-rules.mjs";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Creates the shared ESLint base config.
|
|
3
5
|
*
|
|
@@ -33,19 +35,8 @@ export function createBaseConfig({
|
|
|
33
35
|
"@typescript-eslint/no-require-imports": "off",
|
|
34
36
|
},
|
|
35
37
|
},
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"error",
|
|
40
|
-
{
|
|
41
|
-
code: 88,
|
|
42
|
-
ignoreUrls: true,
|
|
43
|
-
ignoreStrings: true,
|
|
44
|
-
ignoreTemplateLiterals: true,
|
|
45
|
-
ignoreRegExpLiterals: true,
|
|
46
|
-
},
|
|
47
|
-
],
|
|
48
|
-
},
|
|
49
|
-
},
|
|
38
|
+
HOUSE_RULES,
|
|
39
|
+
TYPED_RULES,
|
|
40
|
+
TEST_FILES_EXEMPT,
|
|
50
41
|
]);
|
|
51
42
|
}
|
package/eslint/lib.mjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import {HOUSE_RULES, TEST_FILES_EXEMPT, TYPED_RULES} from './house-rules.mjs';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Creates a shared ESLint config for TypeScript libraries
|
|
3
5
|
* (non-Next.js projects).
|
|
@@ -22,19 +24,19 @@ export function createLibConfig({tseslint, sonarjs}) {
|
|
|
22
24
|
'@typescript-eslint/no-require-imports': 'off',
|
|
23
25
|
},
|
|
24
26
|
},
|
|
27
|
+
HOUSE_RULES,
|
|
28
|
+
TYPED_RULES,
|
|
25
29
|
{
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
ignoreRegExpLiterals: true,
|
|
35
|
-
},
|
|
36
|
-
],
|
|
30
|
+
// A library's tsconfig includes only its sources and tests, so a root
|
|
31
|
+
// config file (vitest.config.ts) sits outside every project.
|
|
32
|
+
files: TYPED_RULES.files,
|
|
33
|
+
ignores: TYPED_RULES.ignores,
|
|
34
|
+
languageOptions: {
|
|
35
|
+
parserOptions: {
|
|
36
|
+
projectService: {allowDefaultProject: ['*.config.ts', '*.config.mts']},
|
|
37
|
+
},
|
|
37
38
|
},
|
|
38
39
|
},
|
|
40
|
+
TEST_FILES_EXEMPT,
|
|
39
41
|
);
|
|
40
42
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@casomoltd/tooling",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.0",
|
|
4
4
|
"description": "Casomo's shared dev config: ESLint, Prettier, commitlint, knip & jscpd presets and dev CLIs (git hooks, readability, screenshots, design specs)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -22,9 +22,10 @@
|
|
|
22
22
|
"!**/*.test.mjs"
|
|
23
23
|
],
|
|
24
24
|
"scripts": {
|
|
25
|
-
"check": "node bin/check-gates.mjs && node bin/verify-pack.mjs && npm run check:private-refs && npm run check:personal-data && npm run typecheck && npm run test && npm run lint:md",
|
|
25
|
+
"check": "node bin/check-gates.mjs && node bin/verify-pack.mjs && npm run check:private-refs && npm run check:personal-data && npm run lint && npm run typecheck && npm run test && npm run lint:md",
|
|
26
|
+
"lint": "eslint .",
|
|
26
27
|
"typecheck": "tsc --noEmit -p hooks/tsconfig.json",
|
|
27
|
-
"test": "node hooks/test.mts && node bin/check-gates.test.mjs && node bin/check-personal-data.test.mjs && node bin/casomo-config.test.mjs && node bin/buttondown.test.mjs && node commitlint/index.test.mjs && node readability/index.test.mjs",
|
|
28
|
+
"test": "node hooks/test.mts && node bin/check-gates.test.mjs && node bin/check-listed-files.test.mjs && node bin/check-personal-data.test.mjs && node bin/casomo-config.test.mjs && node bin/buttondown.test.mjs && node commitlint/index.test.mjs && node readability/index.test.mjs",
|
|
28
29
|
"lint:md": "remark --frail --quiet --no-stdout --rc-path bin/skills-lint.config.mjs skills agents docs",
|
|
29
30
|
"build": "echo 'no-op: source-only repo, nothing to build'",
|
|
30
31
|
"prepare": "husky || true",
|
|
@@ -33,7 +34,6 @@
|
|
|
33
34
|
},
|
|
34
35
|
"casomo": {
|
|
35
36
|
"gates": {
|
|
36
|
-
"lint": "publishes the shared eslint config but has none of its own yet",
|
|
37
37
|
"knip": "no knip config for this repo's mixed .mts/.mjs layout yet",
|
|
38
38
|
"jscpd": "no jscpd config for this repo yet",
|
|
39
39
|
"spell": "no shared cspell config exists yet",
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
"buttondown": "./bin/buttondown.mjs",
|
|
59
59
|
"check-gates": "./bin/check-gates.mjs",
|
|
60
60
|
"check-hooks": "./bin/check-hooks.sh",
|
|
61
|
+
"check-listed-files": "./bin/check-listed-files.mjs",
|
|
61
62
|
"check-linear": "./bin/check-linear.sh",
|
|
62
63
|
"check-personal-data": "./bin/check-personal-data.mjs",
|
|
63
64
|
"check-private-refs": "./bin/check-private-refs.mjs",
|
|
@@ -80,7 +81,7 @@
|
|
|
80
81
|
},
|
|
81
82
|
"peerDependencies": {
|
|
82
83
|
"@commitlint/cli": ">=20",
|
|
83
|
-
"eslint": ">=9",
|
|
84
|
+
"eslint": ">=9.24",
|
|
84
85
|
"eslint-config-next": ">=16",
|
|
85
86
|
"eslint-plugin-sonarjs": ">=3",
|
|
86
87
|
"jscpd": ">=4",
|
|
@@ -89,12 +90,15 @@
|
|
|
89
90
|
},
|
|
90
91
|
"devDependencies": {
|
|
91
92
|
"@types/node": "^26.0.0",
|
|
93
|
+
"eslint": "^9.39.5",
|
|
94
|
+
"eslint-plugin-sonarjs": "^4.2.2",
|
|
92
95
|
"husky": "^9.1.7",
|
|
93
96
|
"remark-cli": "^12.0.1",
|
|
94
97
|
"remark-frontmatter": "^5.0.0",
|
|
95
98
|
"remark-lint-frontmatter-schema": "^3.15.4",
|
|
96
99
|
"remark-validate-links": "^13.1.0",
|
|
97
|
-
"typescript": "^6.0.3"
|
|
100
|
+
"typescript": "^6.0.3",
|
|
101
|
+
"typescript-eslint": "^8.72.0"
|
|
98
102
|
},
|
|
99
103
|
"overrides": {
|
|
100
104
|
"remark-lint-frontmatter-schema": {
|