diff --git a/.claude/skills/coding-standards/rules/clean-react-0-compiler.md b/.claude/skills/coding-standards/rules/clean-react-0-compiler.md index 303719f6e2cf..e43d5c7fb94a 100644 --- a/.claude/skills/coding-standards/rules/clean-react-0-compiler.md +++ b/.claude/skills/coding-standards/rules/clean-react-0-compiler.md @@ -17,7 +17,7 @@ Manual memoization is therefore: The codebase enforces this via: - **Babel plugin**: `babel-plugin-react-compiler` in `babel.config.js` -- **ESLint processor**: `eslint-processor-react-compiler-compat` suppresses redundant lint rules when files compile successfully +- **Lint post-processor**: `scripts/lint/processors/ReactCompilerFilter.ts` suppresses redundant lint rules when both React Compilers memoize the file - **CI compliance check**: `scripts/react-compiler-compliance-check.ts` enforces that new components/hooks compile and that existing compiled files don't regress Reference: [React Compiler documentation](https://react.dev/learn/react-compiler) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index bc8bd10c78a9..6d8f0cf30563 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -16,6 +16,7 @@ on: 'config/.editorconfig', 'config/eslint/**', 'scripts/lint.ts', + 'scripts/lint/**', 'scripts/lintChanged.sh', '.watchmanconfig', '.imgbotconfig', diff --git a/CLAUDE.md b/CLAUDE.md index afaa65aac0de..99a61389bc37 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -62,7 +62,7 @@ Do not use `useMemo`, `useCallback`, or `React.memo` in components or hooks that ### Code Quality -- **ESLint**: Linter. Pre-existing violations are grandfathered via [`eslint-seatbelt`](https://github.com/justjake/eslint-seatbelt). +- **ESLint**: Linter. Pre-existing violations are grandfathered via the seatbelt ratchet in `scripts/lint/`. ### Post-Edit Checklist (IMPORTANT) diff --git a/config/eslint/eslint.config.mjs b/config/eslint/eslint.config.mjs index 9815e75d22fb..14c5f1e90788 100644 --- a/config/eslint/eslint.config.mjs +++ b/config/eslint/eslint.config.mjs @@ -13,7 +13,6 @@ import reactNativeA11Y from 'eslint-plugin-react-native-a11y'; import rulesdir from 'eslint-plugin-rulesdir'; import testingLibrary from 'eslint-plugin-testing-library'; import youDontNeedLodashUnderscore from 'eslint-plugin-you-dont-need-lodash-underscore'; -import seatbelt from 'eslint-seatbelt'; import {defineConfig, globalIgnores} from 'eslint/config'; import globals from 'globals'; import {createRequire} from 'node:module'; @@ -22,7 +21,6 @@ import {fileURLToPath} from 'node:url'; import tseslint from 'typescript-eslint'; import reportNameUtilsPlugin from './plugins/eslint-plugin-report-name-utils.mjs'; -import expensifyProcessor from './processors/eslint-processor-expensify.mjs'; const filename = fileURLToPath(import.meta.url); const dirname = path.dirname(filename); @@ -199,7 +197,7 @@ const restrictedReportNameImportPatterns = [ ]; // `isPaidGroupPolicy` is BILLING/paid-only (Collect/Control). Existing usages are grandfathered via -// eslint-seatbelt; this only flags NEW imports so they make a conscious choice: for workspace feature +// the seatbelt baseline; this only flags NEW imports so they make a conscious choice: for workspace feature // gating (violations, report fields, workspace chat, report creation, expense-workspace usability) use // `isGroupPolicy` / `isReportInGroupPolicy` instead, otherwise free group plans like Submit (submit2026) // are wrongly excluded and access bugs return. @@ -240,35 +238,13 @@ const config = defineConfig([ }, }, }, - fileProgress.configs['recommended-ci'], - - // Suppress lint rules that are unnecessary for files successfully compiled by React Compiler. - // The processor runs React Compiler on each file and filters out redundant lint messages. - { - files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '**/*.mjs', '**/*.cjs'], - processor: expensifyProcessor, - }, - - // eslint-seatbelt config. The processor is stitched into `expensifyProcessor` - // above, so we only wire up the plugin, settings, and `configure` rule here. { + ...fileProgress.configs['recommended-ci'], settings: { - seatbelt: { - seatbeltFile: path.join(dirname, 'eslint.seatbelt.tsv'), - threadsafe: true, - // Never persist TSV updates unless we're in CI. In CI, the ephemeral - // write is harmless on PR runs and essential on `push: main`, where - // OSBotify commits the tightened baseline back to main - // (see .github/workflows/lint.yml). SEATBELT_INCREASE overrides this. - readOnly: !process.env.CI, + progress: { + hide: process.env.CI === 'true' || process.env.LINT_PIPELINE === '1', }, }, - plugins: { - 'eslint-seatbelt': seatbelt, - }, - rules: { - 'eslint-seatbelt/configure': 'error', - }, }, { diff --git a/config/eslint/processors/eslint-processor-expensify.mjs b/config/eslint/processors/eslint-processor-expensify.mjs deleted file mode 100644 index d4c128d40359..000000000000 --- a/config/eslint/processors/eslint-processor-expensify.mjs +++ /dev/null @@ -1,27 +0,0 @@ -/** - * ESLint only allows a single processor per file, so this one chains together - * all the custom processors we want to run. - */ -import seatbelt from 'eslint-seatbelt'; - -import reactCompilerCompatProcessor from './eslint-processor-react-compiler-compat.mjs'; -import stratifyNoDeprecatedProcessor from './eslint-processor-stratify-no-deprecated.mjs'; - -const seatbeltProcessor = seatbelt.processors.seatbelt; - -export default { - meta: {name: 'expensify-eslint-processor'}, - supportsAutofix: true, - - preprocess(text, filename) { - const [textResult] = reactCompilerCompatProcessor.preprocess(text, filename); - const [afterStratifyPreprocess] = stratifyNoDeprecatedProcessor.preprocess(textResult, filename); - return seatbeltProcessor.preprocess(afterStratifyPreprocess, filename); - }, - - postprocess(messagesPerBlock, filename) { - const afterCompilerFilter = reactCompilerCompatProcessor.postprocess(messagesPerBlock, filename); - const afterStratify = stratifyNoDeprecatedProcessor.postprocess([afterCompilerFilter], filename); - return seatbeltProcessor.postprocess([afterStratify], filename); - }, -}; diff --git a/config/eslint/processors/eslint-processor-react-compiler-compat.mjs b/config/eslint/processors/eslint-processor-react-compiler-compat.mjs deleted file mode 100644 index 878f23e928f1..000000000000 --- a/config/eslint/processors/eslint-processor-react-compiler-compat.mjs +++ /dev/null @@ -1,75 +0,0 @@ -/** - * ESLint processor that conditionally suppresses lint rules which are unnecessary - * for files that BOTH React Compilers memoize. - * - * React Compiler automatically memoizes components and hooks, making rules like - * `react/jsx-no-constructed-context-values` redundant for memoized files. But the app - * runs two different compilers -- Babel (babel-plugin-react-compiler) on native/Jest and - * OXC (oxc-transform-react) on web -- and they don't always agree. A file that only one compiler - * memoizes still ships without memoization on the other platform, so the manual memoization - * (and the lint rules that enforce it) is still needed there. - * - * This processor therefore: - * 1. Runs BOTH React Compilers on each file during the `preprocess` phase - * 2. Only if BOTH compilers memoize the file, filters out messages from rules that - * React Compiler makes unnecessary in `postprocess` - * 3. Otherwise (either compiler skips memoization, or a file fails to compile) preserves - * all lint messages as-is - */ -import _ from 'lodash'; - -import {didBothCompilersMemoizeFile} from '../../reactCompiler/checkBoth.mjs'; - -// Rules that are entirely unnecessary when React Compiler successfully compiles -// all functions in a file. Add more rules here as needed. -const RULES_SUPPRESSED_BY_REACT_COMPILER = new Set(['react/jsx-no-constructed-context-values', 'rulesdir/no-inline-useOnyx-selector']); - -// react-hooks/exhaustive-deps warnings that suggest useCallback/useMemo are -// false positives in compiled files, since React Compiler auto-memoizes. -// We only suppress the "wrap in useCallback/useMemo" suggestions, NOT warnings -// about genuinely missing dependencies. -const EXHAUSTIVE_DEPS_USECALLBACK_USEMEMO_PATTERN = /\buseCallback\(\) Hook\b|\buseMemo\(\) Hook\b/; - -// Per-file compilation results, populated in preprocess, consumed in postprocess. -const compilationResults = new Map(); - -const processor = { - meta: { - name: 'react-compiler-compat', - version: '1.0.0', - }, - supportsAutofix: true, - - preprocess(text, filename) { - // Skip files that React Compiler wouldn't compile anyway - if (filename.includes('/tests/') || filename.includes('node_modules/')) { - compilationResults.set(filename, false); - } else { - compilationResults.set(filename, didBothCompilersMemoizeFile(text, filename)); - } - - // Pass the source through unchanged as a single code block - return [text]; - }, - - postprocess(messages, filename) { - const bothMemoized = compilationResults.get(filename); - compilationResults.delete(filename); - - if (bothMemoized) { - return _.filter(messages[0], (msg) => { - if (RULES_SUPPRESSED_BY_REACT_COMPILER.has(msg.ruleId)) { - return false; - } - if (msg.ruleId === 'react-hooks/exhaustive-deps' && EXHAUSTIVE_DEPS_USECALLBACK_USEMEMO_PATTERN.test(msg.message)) { - return false; - } - return true; - }); - } - - return messages[0]; - }, -}; - -export default processor; diff --git a/config/eslint/processors/eslint-processor-stratify-no-deprecated.mjs b/config/eslint/processors/eslint-processor-stratify-no-deprecated.mjs deleted file mode 100644 index 84abf23d661b..000000000000 --- a/config/eslint/processors/eslint-processor-stratify-no-deprecated.mjs +++ /dev/null @@ -1,152 +0,0 @@ -/** - * Rewrites `@typescript-eslint/no-deprecated` messages into per-API rule IDs - * (e.g. `@typescript-eslint/no-deprecated/StyleSheet.absoluteFillObject`) so - * eslint-seatbelt can ratchet each deprecated API independently. - */ -import {parse} from '@babel/parser'; - -const NO_DEPRECATED_RULE_ID = '@typescript-eslint/no-deprecated'; - -// AST keys to ignore while walking children: positions, comments, etc. -const NON_CHILD_KEYS = new Set(['loc', 'start', 'end', 'extra', 'leadingComments', 'trailingComments', 'innerComments']); - -// Node types whose children are part of a single dotted/qualified expression -// (e.g. `Foo.bar`, `Foo?.bar`, `Foo.bar` in TS type position). -const MEMBER_LIKE_TYPES = new Set(['MemberExpression', 'OptionalMemberExpression', 'TSQualifiedName']); - -const sourceByFilename = new Map(); - -const isAstNode = (value) => !!value && typeof value === 'object' && typeof value.type === 'string' && typeof value.start === 'number' && typeof value.end === 'number'; - -/** Iterate over a node's direct AST children, skipping non-child metadata. */ -function* astChildren(node) { - for (const [key, value] of Object.entries(node)) { - if (NON_CHILD_KEYS.has(key)) { - continue; - } - for (const child of Array.isArray(value) ? value : [value]) { - if (isAstNode(child)) { - yield child; - } - } - } -} - -/** Convert ESLint's 1-based (line, column) into a 0-based source offset, or -1 if line is out of range. */ -function lineColumnToOffset(source, line, column) { - let lineStart = 0; - for (let currentLine = 1; currentLine < line; currentLine++) { - const nextNewline = source.indexOf('\n', lineStart); - if (nextNewline < 0) { - return -1; - } - lineStart = nextNewline + 1; - } - return lineStart + column - 1; -} - -/** - * Walk down the AST following children whose range contains `offset`. - * Returns the ancestor path (root → deepest) or `null` if the offset is out of range. - */ -function findAstPathAtOffset(root, offset) { - if (offset < 0 || offset < root.start || offset > root.end) { - return null; - } - const path = [root]; - while (true) { - const current = path.at(-1); - let descended = false; - for (const child of astChildren(current)) { - if (offset >= child.start && offset <= child.end) { - path.push(child); - descended = true; - break; - } - } - if (!descended) { - return path; - } - } -} - -/** Walk a path upward through any wrapping member/qualified expression and return the topmost. */ -function topOfMemberChain(path) { - let topIndex = path.length - 1; - while (topIndex > 0 && MEMBER_LIKE_TYPES.has(path.at(topIndex - 1).type)) { - topIndex--; - } - return path.at(topIndex); -} - -function parseSourceOrNull(source) { - try { - return parse(source, {sourceType: 'module', plugins: ['typescript', 'jsx']}); - } catch { - return null; - } -} - -/** Slice the full deprecated expression (e.g. `StyleSheet.absoluteFillObject`) at the lint location, or null on miss. */ -function getDeprecatedExpressionFromSource(source, ast, message) { - const offset = lineColumnToOffset(source, message.line, message.column); - const path = findAstPathAtOffset(ast, offset); - if (!path) { - return null; - } - const top = topOfMemberChain(path); - return source.slice(top.start, top.end); -} - -/** Fallback: parse the symbol name out of the lint message text. */ -function getSymbolNameFromMessage(message) { - const match = /^`([^`]+)`/.exec(message.message); - return match ? match.at(1) : null; -} - -/** Trim; collapse whitespace and `/` to `_`. Preserves `.`, `#`, `$`, `@`. */ -function toRuleIdSuffix(apiName) { - return apiName.trim().replaceAll(/[\s/]+/g, '_'); -} - -/** - * @param {import('eslint').Linter.LintMessage[]} messages - * @param {string | null} source - * @returns {import('eslint').Linter.LintMessage[]} - */ -function stratifyMessages(messages, source) { - const hasNoDeprecatedMessages = messages.some((message) => message.ruleId === NO_DEPRECATED_RULE_ID); - const ast = source && hasNoDeprecatedMessages ? parseSourceOrNull(source) : null; - - return messages.map((message) => { - if (message.ruleId !== NO_DEPRECATED_RULE_ID) { - return message; - } - const apiName = (ast && getDeprecatedExpressionFromSource(source, ast, message)) || getSymbolNameFromMessage(message); - if (!apiName) { - return message; - } - return {...message, ruleId: `${NO_DEPRECATED_RULE_ID}/${toRuleIdSuffix(apiName)}`}; - }); -} - -const processor = { - meta: { - name: 'stratify-no-deprecated', - version: '1.0.0', - }, - supportsAutofix: true, - - preprocess(text, filename) { - sourceByFilename.set(filename, text); - return [text]; - }, - - postprocess(messagesPerBlock, filename) { - const source = sourceByFilename.get(filename) ?? null; - sourceByFilename.delete(filename); - return stratifyMessages(messagesPerBlock[0], source); - }, -}; - -export default processor; diff --git a/contributingGuides/LINTING.md b/contributingGuides/LINTING.md index d7ec02c7a65e..94fbf0043626 100644 --- a/contributingGuides/LINTING.md +++ b/contributingGuides/LINTING.md @@ -1,6 +1,6 @@ # Linting -The App is linted with [ESLint](https://eslint.org) and its configuration lives in [`config/eslint/`](../config/eslint/). The main source of truth is [`config/eslint/eslint.config.mjs`](../config/eslint/eslint.config.mjs) — every other file in that directory (plugins, processors, the seatbelt baseline) is wired up from there. +The App is linted with [ESLint](https://eslint.org). Rule configuration lives in [`config/eslint/`](../config/eslint/); the runner, seatbelt ratchet, and post-process pipeline live in [`scripts/lint/`](../scripts/lint/). ## TL;DR @@ -24,15 +24,19 @@ npm run lint-watch npm run eslint-report ``` -Prefer `npm run lint` (or `lint-changed` / `lint -- `) over raw `npx eslint` invocations. Those wrappers increase the memory allocation to prevent OOM errors, and also include caching and concurrency flags for faster linting. +Put flags before file or directory paths. The CLI stops parsing flags once it starts collecting variadic targets. -By default the wrapper passes `--quiet` to ESLint so only blocking errors are printed — seatbelt-grandfathered violations (which CI does not fail on) are suppressed from the output but still evaluated against the baseline. Pass `--show-warnings` when you want to see them too, e.g. when paying down baselined errors. +Prefer `npm run lint` (or `lint-changed` / `lint -- `) over raw `npx eslint` invocations. The wrapper increases the memory allocation to prevent OOM errors, applies the seatbelt ratchet and the other post-processors, and includes caching and concurrency flags for faster linting. -## eslint-seatbelt -We use [eslint-seatbelt](https://github.com/justjake/eslint-seatbelt) to manage known lint errors. +Editor integrations and bare `npx eslint` still run the rule set from `config/eslint/`, but they do **not** apply the seatbelt ratchet, the React Compiler filter, or no-deprecated stratification. Those run only in `scripts/lint/`. Grandfathered seatbelt rows may therefore show as errors in the editor even though `npm run lint` passes. + +By default the wrapper only prints blocking errors — seatbelt-grandfathered violations (which CI does not fail on) are suppressed from the output but still evaluated against the baseline. Pass `--show-warnings` when you want to see them too, e.g. when paying down baselined errors. + +## Seatbelt +Known lint errors are grandfathered by a seatbelt ratchet implemented in [`scripts/lint/processors/Seatbelt.ts`](../scripts/lint/processors/Seatbelt.ts). The TSV format and env-var names match the previous `eslint-seatbelt` plugin so existing workflows stay the same. 1. **Every rule is an error.** There are no warnings. -2. **Pre-existing errors are grandfathered in via [`eslint-seatbelt`](https://github.com/justjake/eslint-seatbelt).** A per-file / per-rule baseline lives at [`config/eslint/eslint.seatbelt.tsv`](../config/eslint/eslint.seatbelt.tsv). As long as a file's error count for a given rule is **≤** its recorded baseline, seatbelt reclassifies those errors as warnings and the run still passes. +2. **Pre-existing errors are grandfathered via the seatbelt ratchet.** A per-file / per-rule baseline lives at [`config/eslint/eslint.seatbelt.tsv`](../config/eslint/eslint.seatbelt.tsv). As long as a file's error count for a given rule is **≤** its recorded baseline, seatbelt reclassifies those errors as warnings and the run still passes. 3. **The baseline is a ratchet.** The count can only go down over time — never up — unless you (and a reviewer) explicitly allow an increase. 4. **The baseline auto-tightens on `main`.** When a PR merges, the lint job on `main` rewrites `eslint.seatbelt.tsv` to reflect the new (lower) counts and commits it back as OSBotify. You don't need to commit TSV updates yourself during normal development. 5. **You never hand-edit `eslint.seatbelt.tsv`.** Seatbelt rewrites it deterministically based on what it sees during a lint run. @@ -80,7 +84,7 @@ npm run eslint-report ### "I fixed an existing baselined error" -Just run `npm run lint` (or `npm run lint-changed`) locally. Seatbelt notices the count went down and passes. **It does not rewrite `config/eslint/eslint.seatbelt.tsv` locally** — the config sets `readOnly: !process.env.CI`, so the TSV is only rewritten in CI. After your PR merges, the lint job on `main` re-runs, writes the tightened TSV, and OSBotify commits it back to `main` for you. +Just run `npm run lint` (or `npm run lint-changed`) locally. Seatbelt notices the count went down and passes. **It does not rewrite `config/eslint/eslint.seatbelt.tsv` locally** — `readOnly` defaults to on unless `CI` is set, so the TSV is only rewritten in CI. After your PR merges, the lint job on `main` re-runs, writes the tightened TSV, and OSBotify commits it back to `main` for you. No TSV commit required on your end. @@ -124,7 +128,7 @@ Commit the updated `config/eslint/eslint.seatbelt.tsv` alongside the rename. The ## CI behavior -The [`ESLint check`](../.github/workflows/lint.yml) workflow runs `npm run lint`. In CI, `readOnly` is off (so seatbelt can write) and `SEATBELT_FROZEN=0` is exported from [`scripts/lint.ts`](../scripts/lint.ts) so GitHub Actions' auto-set `CI=true` doesn't flip seatbelt into frozen mode. +The [`ESLint check`](../.github/workflows/lint.yml) workflow runs `npm run lint`. In CI, `readOnly` is off (so seatbelt can write) and `SEATBELT_FROZEN` defaults to off so GitHub Actions' auto-set `CI=true` doesn't flip seatbelt into frozen mode. - **PR runs:** counts go down → passes (TSV rewrite is ephemeral and thrown away with the runner). Counts go up without `SEATBELT_INCREASE` → fails with seatbelt's "exceeds allowed count" error. - **`push: main` runs:** same behavior, plus an extra step — if `config/eslint/eslint.seatbelt.tsv` changed, OSBotify commits the tightened baseline straight back to `main`. @@ -143,7 +147,7 @@ Set any of these env vars for a one-off local run when you need to bypass seatbe | `SEATBELT_DISABLE=1` | Skip all seatbelt processing for this run — raw ESLint output, baseline file ignored. | | `SEATBELT_VERBOSE=1` | Log every decrement/increment seatbelt performs. Handy when debugging the baseline. | -Full reference: [eslint-seatbelt README](https://github.com/justjake/eslint-seatbelt#configuration). +The env-var names and TSV format are unchanged from the previous `eslint-seatbelt` plugin. ## Related reading diff --git a/cspell.json b/cspell.json index e57af24e934c..11dd53c450f5 100644 --- a/cspell.json +++ b/cspell.json @@ -727,6 +727,7 @@ "jsbundle", "jumpcloud", "justworks", + "justjake", "kallidus", "keka", "kenjo", @@ -856,6 +857,7 @@ "outform", "outplant", "oxfmt", + "oxlint", "padangle", "parasharrajat", "passcodes", @@ -926,6 +928,7 @@ "remotesync", "removeHiddenElems", "requestee", + "reserialization", "resizeable", "resultsbox", "retryable", @@ -1040,6 +1043,7 @@ "unapproves", "unassigning", "unassignment", + "unbaselined", "unassigns", "uncategorized", "unflushed", diff --git a/package-lock.json b/package-lock.json index 3f3f478bff99..fc4d9b1ecf6a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -250,7 +250,6 @@ "eslint-plugin-storybook": "10.4.6", "eslint-plugin-testing-library": "^7.11.0", "eslint-plugin-you-dont-need-lodash-underscore": "^6.14.0", - "eslint-seatbelt": "^0.1.3", "glob": "^10.4.5", "http-server": "^14.1.1", "jest": "29.7.0", @@ -12764,9 +12763,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -12784,9 +12780,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -12804,9 +12797,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -12824,9 +12814,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -12844,9 +12831,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -12864,9 +12848,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -12884,9 +12865,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -12904,9 +12882,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -19266,6 +19241,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -19282,6 +19258,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -19298,6 +19275,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -19314,6 +19292,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -19330,6 +19309,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -19346,6 +19326,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -19362,6 +19343,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -19378,6 +19360,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -19394,6 +19377,7 @@ "cpu": [ "mips64el" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -19410,6 +19394,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -19426,6 +19411,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -19442,6 +19428,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -19458,6 +19445,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -19474,6 +19462,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -19490,6 +19479,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -19506,6 +19496,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -19522,6 +19513,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -19538,6 +19530,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -19554,6 +19547,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -19570,6 +19564,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -20269,16 +20264,6 @@ "node": ">=0.10.0" } }, - "node_modules/array-back": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", - "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/array-buffer-byte-length": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", @@ -22064,58 +22049,6 @@ "devOptional": true, "license": "MIT" }, - "node_modules/command-line-args": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz", - "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-back": "^3.1.0", - "find-replace": "^3.0.0", - "lodash.camelcase": "^4.3.0", - "typical": "^4.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/command-line-usage": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-6.1.3.tgz", - "integrity": "sha512-sH5ZSPr+7UStsloltmDh7Ce5fb8XPlHyoPzTpyyMuYCtervL65+ubVZ6Q61cFtFl62UyJlc8/JwERRbAFPUqgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-back": "^4.0.2", - "chalk": "^2.4.2", - "table-layout": "^1.0.2", - "typical": "^5.2.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/command-line-usage/node_modules/array-back": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", - "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/command-line-usage/node_modules/typical": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", - "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/commander": { "version": "12.1.0", "license": "MIT", @@ -23425,16 +23358,6 @@ "node": ">=6" } }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/deep-is": { "version": "0.1.4", "dev": true, @@ -24980,31 +24903,6 @@ "node": ">=4.0" } }, - "node_modules/eslint-seatbelt": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/eslint-seatbelt/-/eslint-seatbelt-0.1.3.tgz", - "integrity": "sha512-S8vot6z0Sr4wN1hGVmh3ex8hb7pSm0YNwZNS2Kkqd7CbtwjWQkqXIpoqZSEEW7PaCDMK6G6RBoXXRW/JygxTJg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ts-command-line-args": "^2.5.1" - }, - "bin": { - "eslint-seatbelt": "dist/command.js" - }, - "peerDependencies": { - "@types/eslint": "*", - "eslint": "*" - }, - "peerDependenciesMeta": { - "@types/eslint": { - "optional": true - }, - "eslint": { - "optional": true - } - } - }, "node_modules/eslint-visitor-keys": { "version": "2.1.0", "dev": true, @@ -26289,19 +26187,6 @@ "url": "https://github.com/avajs/find-cache-dir?sponsor=1" } }, - "node_modules/find-replace": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", - "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-back": "^3.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/find-up": { "version": "5.0.0", "devOptional": true, @@ -36712,16 +36597,6 @@ "dev": true, "license": "MIT" }, - "node_modules/reduce-flatten": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz", - "integrity": "sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/reflect-metadata": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", @@ -39050,13 +38925,6 @@ "node": ">=0.6.19" } }, - "node_modules/string-format": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/string-format/-/string-format-2.0.0.tgz", - "integrity": "sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA==", - "dev": true, - "license": "WTFPL OR MIT" - }, "node_modules/string-length": { "version": "4.0.2", "dev": true, @@ -39472,42 +39340,6 @@ "version": "6.2.0", "license": "MIT" }, - "node_modules/table-layout": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-1.0.2.tgz", - "integrity": "sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-back": "^4.0.1", - "deep-extend": "~0.6.0", - "typical": "^5.2.0", - "wordwrapjs": "^4.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/table-layout/node_modules/array-back": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", - "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/table-layout/node_modules/typical": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", - "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/tapable": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", @@ -39972,98 +39804,6 @@ } } }, - "node_modules/ts-command-line-args": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/ts-command-line-args/-/ts-command-line-args-2.5.1.tgz", - "integrity": "sha512-H69ZwTw3rFHb5WYpQya40YAX2/w7Ut75uUECbgBIsLmM+BNuYnxsltfyyLMxy6sEeKxgijLTnQtLd0nKd6+IYw==", - "dev": true, - "license": "ISC", - "dependencies": { - "chalk": "^4.1.0", - "command-line-args": "^5.1.1", - "command-line-usage": "^6.1.0", - "string-format": "^2.0.0" - }, - "bin": { - "write-markdown": "dist/write-markdown.js" - } - }, - "node_modules/ts-command-line-args/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/ts-command-line-args/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/ts-command-line-args/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/ts-command-line-args/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/ts-command-line-args/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ts-command-line-args/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ts-dedent": { "version": "2.2.0", "dev": true, @@ -40344,16 +40084,6 @@ "integrity": "sha512-2SCC/WLzj2SbUwzFOzqMCkz5amXLlxtJqDKTICqg30x+2DZxcfZN2MvQZmGfXWKNWaKK9pBPsvkcwv8bF/gxKg==", "license": "MIT" }, - "node_modules/typical": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", - "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/ua-parser-js": { "version": "0.7.35", "funding": [ @@ -41168,30 +40898,6 @@ "node": ">=0.10.0" } }, - "node_modules/wordwrapjs": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-4.0.1.tgz", - "integrity": "sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA==", - "dev": true, - "license": "MIT", - "dependencies": { - "reduce-flatten": "^2.0.0", - "typical": "^5.2.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/wordwrapjs/node_modules/typical": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", - "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/workbox-background-sync": { "version": "7.4.0", "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-7.4.0.tgz", @@ -41923,6 +41629,15 @@ "engines": { "bun": "1.3.14" } + }, + "server/victory-chart-renderer/node_modules/react": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", + "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } } } } diff --git a/package.json b/package.json index 6acfa79cd244..3c88fccfac53 100644 --- a/package.json +++ b/package.json @@ -47,9 +47,9 @@ "test:debug": "TZ=utc NODE_OPTIONS='--inspect-brk --experimental-vm-modules' jest --runInBand", "perf-test": "NODE_OPTIONS=--experimental-vm-modules npx reassure", "typecheck": "bun scripts/typecheck.ts", - "lint": "bun scripts/lint.ts", + "lint": "bun scripts/lint/index.ts", "lint-changed": "./scripts/lintChanged.sh", - "lint-watch": "onchange '**/*.{js,jsx,ts,tsx,mjs,cjs}' -- bun scripts/lint.ts {{changed}}", + "lint-watch": "onchange '**/*.{js,jsx,ts,tsx,mjs,cjs}' -- bun scripts/lint/index.ts {{changed}}", "eslint-report": "bun scripts/eslint-report.ts", "knip": "KNIP=true knip --include dependencies --exclude unlisted --no-exit-code --reporter compact", "knip:full": "KNIP=true knip --reporter compact", @@ -324,7 +324,6 @@ "eslint-plugin-storybook": "10.4.6", "eslint-plugin-testing-library": "^7.11.0", "eslint-plugin-you-dont-need-lodash-underscore": "^6.14.0", - "eslint-seatbelt": "^0.1.3", "glob": "^10.4.5", "http-server": "^14.1.1", "jest": "29.7.0", diff --git a/patches/eslint-seatbelt/details.md b/patches/eslint-seatbelt/details.md deleted file mode 100644 index 7ae1081af0e5..000000000000 --- a/patches/eslint-seatbelt/details.md +++ /dev/null @@ -1,57 +0,0 @@ -# `eslint-seatbelt` patches - -### [eslint-seatbelt+0.1.3+001+thread-safety.patch](eslint-seatbelt+0.1.3+001+thread-safety.patch) - -- Reason: - - ``` - Without this, running `npm run lint` with `--concurrency=auto` races on - the atomic rename of the TSV and crashes with: - ENOENT: no such file or directory, rename '.../eslint.seatbelt.tsv.wip*.tmp' - -> 'config/eslint/eslint.seatbelt.tsv' - Falling back to `--concurrency=1` is too slow for this repo. - ``` - -- Upstream PR/issue: https://github.com/justjake/eslint-seatbelt/pull/27 -- E/App issue: N/A -- PR introducing patch: https://github.com/Expensify/App/pull/88566 - -### [eslint-seatbelt+0.1.3+002+read-only.patch](eslint-seatbelt+0.1.3+002+read-only.patch) - -- Reason: - - Adds a `readOnly` config option (and `SEATBELT_READ_ONLY` env var) that causes - `eslint-seatbelt` to still read and validate the seatbelt file, but never - write updates back to disk. We enable this in developer worktrees - (`readOnly: !process.env.CI` in `config/eslint/eslint.config.mjs`) so - fixing baselined errors doesn't dirty the worktree with an unrelated TSV - rewrite. In CI, the setting is off so the `push: main` lint job can - auto-commit tightenings back to `main` as OSBotify (see - [`.github/workflows/lint.yml`](../../.github/workflows/lint.yml)). - - Precedence: - - `SEATBELT_READ_ONLY` env var overrides the setting. - - `SEATBELT_INCREASE` overrides `readOnly` (intentional loosening still writes). - - `SEATBELT_DISABLE` short-circuits both. - - `SEATBELT_FROZEN` remains orthogonal. - -- Upstream PR/issue: https://github.com/justjake/eslint-seatbelt/pull/29 -- E/App issue: N/A -- PR introducing patch: https://github.com/Expensify/App/pull/88566 - -### [eslint-seatbelt+0.1.3+003+readonly-type-declarations.patch](eslint-seatbelt+0.1.3+003+readonly-type-declarations.patch) - -- Reason: - - Patch 002 added the `readOnly` config option to the runtime and to `README.md`, - but never added it to the `SeatbeltConfig` TypeScript interface, so `SeatbeltConfig` - and the derived `SeatbeltArgs` type didn't know about it. Any consumer of - `eslint-seatbelt/api` that reads or sets `readOnly` (e.g. `scripts/lint.ts`, which - prunes baseline rows for deleted files using the same readOnly semantics as the rest - of the seatbelt baseline) failed to typecheck. This adds the missing `readOnly?: boolean` - field to both the `.d.ts` and `.d.mts` copies of the interface, matching the JSDoc - already shipped in `README.md` by patch 002. - -- Upstream PR/issue: N/A (the `readOnly` option itself is an Expensify-only patch, not upstream) -- E/App issue: N/A -- PR introducing patch: https://github.com/Expensify/App/pull/98665 diff --git a/patches/eslint-seatbelt/eslint-seatbelt+0.1.3+001+thread-safety.patch b/patches/eslint-seatbelt/eslint-seatbelt+0.1.3+001+thread-safety.patch deleted file mode 100644 index 043263966788..000000000000 --- a/patches/eslint-seatbelt/eslint-seatbelt+0.1.3+001+thread-safety.patch +++ /dev/null @@ -1,4022 +0,0 @@ -diff --git a/node_modules/eslint-seatbelt/README.md b/node_modules/eslint-seatbelt/README.md -index 31e16a9..ddcaa34 100644 ---- a/node_modules/eslint-seatbelt/README.md -+++ b/node_modules/eslint-seatbelt/README.md -@@ -409,7 +409,6 @@ This project uses `pnpm` for package management. - - ### Improvement ideas - --- [ ] Finish SEATBELT_THREADSAFE implementation - - [ ] Set SEATBELT_DISABLE=1 during git merge/rebase events - - [ ] Add SEATBELT_DISABLE_IN_EDITOR config option - - [ ] Integration tests -diff --git a/node_modules/eslint-seatbelt/dist/api.js b/node_modules/eslint-seatbelt/dist/api.js -index e7d5ef9..afa6177 100644 ---- a/node_modules/eslint-seatbelt/dist/api.js -+++ b/node_modules/eslint-seatbelt/dist/api.js -@@ -1,7 +1,7 @@ - "use strict";Object.defineProperty(exports, "__esModule", {value: true}); - - --var _chunkK7UHJBLMjs = require('./chunk-K7UHJBLM.js'); -+var _chunkNTFTCWX7js = require('./chunk-NTFTCWX7.js'); - - - -@@ -23,56 +23,8 @@ var _chunkK7UHJBLMjs = require('./chunk-K7UHJBLM.js'); - - - --var _chunkTVRMUM3Fjs = require('./chunk-TVRMUM3F.js'); -+var _chunkZVY5S6JSjs = require('./chunk-ZVY5S6JS.js'); - --// src/FileLock.ts --var _fs = require('fs'); --var { O_CREAT, O_EXCL, O_RDWR } = _fs.constants; --var waitBuffer = new Int32Array(new SharedArrayBuffer(4)); --var FileLock = class { -- constructor(filename) { -- this.filename = filename; -- } -- -- tryLock() { -- this.assertNotLocked(); -- try { -- this.fd = _fs.openSync.call(void 0, this.filename, O_CREAT | O_EXCL | O_RDWR); -- return true; -- } catch (e) { -- if (_chunkK7UHJBLMjs.isErrno.call(void 0, e, "EEXIST")) { -- return false; -- } -- throw e; -- } -- } -- waitLock(timeoutMs) { -- const deadline = Date.now() + timeoutMs; -- while (!this.tryLock()) { -- if (Date.now() > deadline) { -- throw new Error(`Timed out waiting for lock on ${this.filename}`); -- } -- Atomics.wait(waitBuffer, 0, 0, 1); -- } -- } -- isLocked() { -- return this.fd !== void 0; -- } -- unlock() { -- if (this.fd !== void 0) { -- _fs.closeSync.call(void 0, this.fd); -- _fs.rmSync.call(void 0, this.filename); -- this.fd = void 0; -- } -- } -- assertNotLocked() { -- if (this.fd !== void 0) { -- throw new Error( -- `FileLock "${this.filename}" is already locked by this process [pid ${process.pid}]` -- ); -- } -- } --}; - - - -@@ -95,6 +47,5 @@ var FileLock = class { - - - -- --exports.FileLock = FileLock; exports.SEATBELT_DISABLE = _chunkTVRMUM3Fjs.SEATBELT_DISABLE; exports.SEATBELT_FILE = _chunkTVRMUM3Fjs.SEATBELT_FILE; exports.SEATBELT_FILE_NAME = _chunkTVRMUM3Fjs.SEATBELT_FILE_NAME; exports.SEATBELT_FROZEN = _chunkTVRMUM3Fjs.SEATBELT_FROZEN; exports.SEATBELT_INCREASE = _chunkTVRMUM3Fjs.SEATBELT_INCREASE; exports.SEATBELT_KEEP = _chunkTVRMUM3Fjs.SEATBELT_KEEP; exports.SEATBELT_PWD = _chunkTVRMUM3Fjs.SEATBELT_PWD; exports.SEATBELT_QUIET = _chunkTVRMUM3Fjs.SEATBELT_QUIET; exports.SEATBELT_ROOT = _chunkTVRMUM3Fjs.SEATBELT_ROOT; exports.SEATBELT_THREADSAFE = _chunkTVRMUM3Fjs.SEATBELT_THREADSAFE; exports.SEATBELT_VERBOSE = _chunkTVRMUM3Fjs.SEATBELT_VERBOSE; exports.SeatbeltArgs = _chunkTVRMUM3Fjs.SeatbeltArgs; exports.SeatbeltConfig = _chunkTVRMUM3Fjs.SeatbeltConfig; exports.SeatbeltConfigSchema = _chunkTVRMUM3Fjs.SeatbeltConfigSchema; exports.SeatbeltEnv = _chunkTVRMUM3Fjs.SeatbeltEnv; exports.SeatbeltFile = _chunkK7UHJBLMjs.SeatbeltFile; exports.formatFilename = _chunkTVRMUM3Fjs.formatFilename; exports.formatRuleId = _chunkTVRMUM3Fjs.formatRuleId; exports.logStderr = _chunkTVRMUM3Fjs.logStderr; exports.logStdout = _chunkTVRMUM3Fjs.logStdout; exports.padVarName = _chunkTVRMUM3Fjs.padVarName; -+exports.FileLock = _chunkNTFTCWX7js.FileLock; exports.SEATBELT_DISABLE = _chunkZVY5S6JSjs.SEATBELT_DISABLE; exports.SEATBELT_FILE = _chunkZVY5S6JSjs.SEATBELT_FILE; exports.SEATBELT_FILE_NAME = _chunkZVY5S6JSjs.SEATBELT_FILE_NAME; exports.SEATBELT_FROZEN = _chunkZVY5S6JSjs.SEATBELT_FROZEN; exports.SEATBELT_INCREASE = _chunkZVY5S6JSjs.SEATBELT_INCREASE; exports.SEATBELT_KEEP = _chunkZVY5S6JSjs.SEATBELT_KEEP; exports.SEATBELT_PWD = _chunkZVY5S6JSjs.SEATBELT_PWD; exports.SEATBELT_QUIET = _chunkZVY5S6JSjs.SEATBELT_QUIET; exports.SEATBELT_ROOT = _chunkZVY5S6JSjs.SEATBELT_ROOT; exports.SEATBELT_THREADSAFE = _chunkZVY5S6JSjs.SEATBELT_THREADSAFE; exports.SEATBELT_VERBOSE = _chunkZVY5S6JSjs.SEATBELT_VERBOSE; exports.SeatbeltArgs = _chunkZVY5S6JSjs.SeatbeltArgs; exports.SeatbeltConfig = _chunkZVY5S6JSjs.SeatbeltConfig; exports.SeatbeltConfigSchema = _chunkZVY5S6JSjs.SeatbeltConfigSchema; exports.SeatbeltEnv = _chunkZVY5S6JSjs.SeatbeltEnv; exports.SeatbeltFile = _chunkNTFTCWX7js.SeatbeltFile; exports.formatFilename = _chunkZVY5S6JSjs.formatFilename; exports.formatRuleId = _chunkZVY5S6JSjs.formatRuleId; exports.logStderr = _chunkZVY5S6JSjs.logStderr; exports.logStdout = _chunkZVY5S6JSjs.logStdout; exports.padVarName = _chunkZVY5S6JSjs.padVarName; - //# sourceMappingURL=api.js.map -\ No newline at end of file -diff --git a/node_modules/eslint-seatbelt/dist/api.mjs b/node_modules/eslint-seatbelt/dist/api.mjs -index c4e8548..efa51d7 100644 ---- a/node_modules/eslint-seatbelt/dist/api.mjs -+++ b/node_modules/eslint-seatbelt/dist/api.mjs -@@ -1,7 +1,7 @@ - import { -- SeatbeltFile, -- isErrno --} from "./chunk-OKIIDZIF.mjs"; -+ FileLock, -+ SeatbeltFile -+} from "./chunk-5FFUIU4M.mjs"; - import { - SEATBELT_DISABLE, - SEATBELT_FILE, -@@ -23,56 +23,7 @@ import { - logStderr, - logStdout, - padVarName --} from "./chunk-7ZO4DCZA.mjs"; -- --// src/FileLock.ts --import { openSync, closeSync, constants, rmSync } from "node:fs"; --var { O_CREAT, O_EXCL, O_RDWR } = constants; --var waitBuffer = new Int32Array(new SharedArrayBuffer(4)); --var FileLock = class { -- constructor(filename) { -- this.filename = filename; -- } -- fd; -- tryLock() { -- this.assertNotLocked(); -- try { -- this.fd = openSync(this.filename, O_CREAT | O_EXCL | O_RDWR); -- return true; -- } catch (e) { -- if (isErrno(e, "EEXIST")) { -- return false; -- } -- throw e; -- } -- } -- waitLock(timeoutMs) { -- const deadline = Date.now() + timeoutMs; -- while (!this.tryLock()) { -- if (Date.now() > deadline) { -- throw new Error(`Timed out waiting for lock on ${this.filename}`); -- } -- Atomics.wait(waitBuffer, 0, 0, 1); -- } -- } -- isLocked() { -- return this.fd !== void 0; -- } -- unlock() { -- if (this.fd !== void 0) { -- closeSync(this.fd); -- rmSync(this.filename); -- this.fd = void 0; -- } -- } -- assertNotLocked() { -- if (this.fd !== void 0) { -- throw new Error( -- `FileLock "${this.filename}" is already locked by this process [pid ${process.pid}]` -- ); -- } -- } --}; -+} from "./chunk-ULACHCKT.mjs"; - export { - FileLock, - SEATBELT_DISABLE, -diff --git a/node_modules/eslint-seatbelt/dist/chunk-5FFUIU4M.mjs b/node_modules/eslint-seatbelt/dist/chunk-5FFUIU4M.mjs -new file mode 100644 -index 0000000..025f420 ---- /dev/null -+++ b/node_modules/eslint-seatbelt/dist/chunk-5FFUIU4M.mjs -@@ -0,0 +1,480 @@ -+import { -+ SEATBELT_FROZEN, -+ SEATBELT_KEEP, -+ SeatbeltArgs, -+ formatFilename, -+ formatRuleId, -+ name -+} from "./chunk-ULACHCKT.mjs"; -+ -+// src/FileLock.ts -+import { openSync, writeSync, closeSync, readFileSync, constants, rmSync } from "node:fs"; -+ -+// src/errorHanding.ts -+function appendErrorContext(error, context) { -+ if (error instanceof Error) { -+ error.message += ` -+ ${context}`; -+ } -+} -+function isErrno(error, code) { -+ return error instanceof Error && "code" in error && error.code === code; -+} -+ -+// src/FileLock.ts -+var { O_CREAT, O_EXCL, O_RDWR } = constants; -+var waitBuffer = new Int32Array(new SharedArrayBuffer(4)); -+var heldLocks = /* @__PURE__ */ new Set(); -+var cleanupHooksInstalled = false; -+var SIGNAL_EXIT_CODES = { SIGINT: 130, SIGTERM: 143, SIGHUP: 129 }; -+function installCleanupHooks() { -+ if (cleanupHooksInstalled) return; -+ cleanupHooksInstalled = true; -+ const release = () => { -+ for (const lock of heldLocks) { -+ try { -+ lock.unlock(); -+ } catch { -+ } -+ } -+ }; -+ process.on("exit", release); -+ for (const signal of Object.keys(SIGNAL_EXIT_CODES)) { -+ process.on(signal, () => { -+ release(); -+ process.exit(SIGNAL_EXIT_CODES[signal]); -+ }); -+ } -+} -+var FileLock = class { -+ constructor(filename) { -+ this.filename = filename; -+ } -+ fd; -+ tryLock() { -+ this.assertNotLocked(); -+ try { -+ this.fd = openSync(this.filename, O_CREAT | O_EXCL | O_RDWR); -+ writeSync(this.fd, `${process.pid} -+`); -+ heldLocks.add(this); -+ installCleanupHooks(); -+ return true; -+ } catch (e) { -+ if (isErrno(e, "EEXIST")) { -+ return false; -+ } -+ throw e; -+ } -+ } -+ waitLock(timeoutMs) { -+ const deadline = Date.now() + timeoutMs; -+ let attemptedRecovery = false; -+ while (!this.tryLock()) { -+ if (Date.now() > deadline) { -+ if (!attemptedRecovery && this.reclaimIfStale()) { -+ attemptedRecovery = true; -+ continue; -+ } -+ throw new Error(`Timed out waiting for lock on ${this.filename}`); -+ } -+ Atomics.wait(waitBuffer, 0, 0, 1); -+ } -+ } -+ isLocked() { -+ return this.fd !== void 0; -+ } -+ unlock() { -+ if (this.fd !== void 0) { -+ closeSync(this.fd); -+ try { -+ rmSync(this.filename); -+ } catch (e) { -+ if (!isErrno(e, "ENOENT")) throw e; -+ } -+ this.fd = void 0; -+ heldLocks.delete(this); -+ } -+ } -+ assertNotLocked() { -+ if (this.fd !== void 0) { -+ throw new Error( -+ `FileLock "${this.filename}" is already locked by this process [pid ${process.pid}]` -+ ); -+ } -+ } -+ reclaimIfStale() { -+ let contents; -+ try { -+ contents = readFileSync(this.filename, "utf8"); -+ } catch (e) { -+ if (isErrno(e, "ENOENT")) return true; -+ throw e; -+ } -+ const pid = Number.parseInt(contents.trim(), 10); -+ if (!Number.isFinite(pid) || pid <= 0) return false; -+ try { -+ process.kill(pid, 0); -+ return false; -+ } catch (e) { -+ if (!isErrno(e, "ESRCH")) throw e; -+ } -+ try { -+ rmSync(this.filename); -+ } catch (e) { -+ if (!isErrno(e, "ENOENT")) throw e; -+ } -+ return true; -+ } -+}; -+ -+// src/SeatbeltFile.ts -+import * as os from "node:os"; -+import * as fs from "node:fs"; -+import path, * as nodePath from "node:path"; -+var LOCK_TIMEOUT_MS = 3e4; -+function encodeLine(line) { -+ const { filename, ruleId, maxErrors } = line; -+ return `${JSON.stringify(filename)} ${JSON.stringify(ruleId)} ${maxErrors} -+`; -+} -+function decodeLine(line, index) { -+ try { -+ const lineParts = line.split(" "); -+ if (lineParts.length !== 3) { -+ throw new Error( -+ `Expected 3 tab-separated JSON strings, instead have ${lineParts.length}` -+ ); -+ } -+ let filename; -+ try { -+ filename = JSON.parse(lineParts[0]); -+ } catch (e) { -+ appendErrorContext(e, "at tab-separated column 1 (filename)"); -+ throw e; -+ } -+ let ruleId; -+ try { -+ ruleId = JSON.parse(lineParts[1]); -+ } catch (e) { -+ appendErrorContext(e, "at tab-separated column 2 (RuleId)"); -+ throw e; -+ } -+ let maxErrors; -+ try { -+ maxErrors = JSON.parse(lineParts[2]); -+ } catch (e) { -+ appendErrorContext(e, "at tab-separated column 3 (maxErrors)"); -+ throw e; -+ } -+ return { -+ encoded: line, -+ filename, -+ ruleId, -+ maxErrors -+ }; -+ } catch (e) { -+ appendErrorContext(e, `at line ${index + 1}: \`${line.trim()}\``); -+ throw e; -+ } -+} -+var COMMENT_LINE_REGEX = /^\s*#/; -+var NON_EMPTY_LINE_REGEX = /\S+/; -+var DEFAULT_FILE_HEADER = ` -+# ${name} temporarily allowed errors -+# docs: https://github.com/justjake/${name}#readme -+`.trim(); -+var SeatbeltFile = class _SeatbeltFile { -+ constructor(filename, data, comments = "") { -+ this.filename = filename; -+ this.data = data; -+ this.comments = comments; -+ this.filename = path.resolve(this.filename); -+ this.dirname = path.dirname(this.filename); -+ } -+ static readSync(filename) { -+ const text = fs.readFileSync(filename, "utf8"); -+ try { -+ return _SeatbeltFile.parse(filename, text); -+ } catch (e) { -+ appendErrorContext(e, `in seatbelt file \`${filename}\``); -+ throw e; -+ } -+ } -+ /** -+ * Read `filename` if it exists, otherwise create a new empty seatbelt file object -+ * that will write to that filename. -+ */ -+ static openSync(filename) { -+ try { -+ return _SeatbeltFile.readSync(filename); -+ } catch (e) { -+ if (isErrno(e, "ENOENT")) { -+ return new _SeatbeltFile(filename, /* @__PURE__ */ new Map(), DEFAULT_FILE_HEADER); -+ } -+ throw e; -+ } -+ } -+ static parse(filename, text) { -+ const data = /* @__PURE__ */ new Map(); -+ const split = text.split(/(?<=\n)/); -+ const lines = split.filter( -+ (line) => NON_EMPTY_LINE_REGEX.test(line) && !COMMENT_LINE_REGEX.test(line) -+ ).map(decodeLine); -+ const comments = split.filter((line) => COMMENT_LINE_REGEX.test(line)).join(""); -+ lines.forEach((line) => { -+ let fileState = data.get(line.filename); -+ if (!fileState) { -+ fileState = { maxErrors: void 0, lines: [] }; -+ data.set(line.filename, fileState); -+ } -+ fileState.lines.push(line); -+ }); -+ return new _SeatbeltFile(filename, data, comments.trim()); -+ } -+ static fromJSON(json) { -+ const data = new Map( -+ Object.entries(json.data).map(([filename, maxErrors]) => [ -+ filename, -+ { maxErrors: new Map(Object.entries(maxErrors)), lines: [] } -+ ]) -+ ); -+ return new _SeatbeltFile(json.filename, data); -+ } -+ changed = false; -+ dirname; -+ useTempDirForWrites = true; -+ *filenames() { -+ for (const filename of this.data.keys()) { -+ yield this.toAbsolutePath(filename); -+ } -+ } -+ getMaxErrors(filename) { -+ const fileState = this.data.get(this.toRelativePath(filename)); -+ if (!fileState) { -+ return void 0; -+ } -+ fileState.maxErrors ??= parseMaxErrors(fileState.lines); -+ return fileState.maxErrors; -+ } -+ removeFile(filename, args) { -+ const relativeFilename = this.toRelativePath(filename); -+ if (!this.data.has(relativeFilename)) { -+ return false; -+ } -+ SeatbeltArgs.verboseLog( -+ args, -+ () => args.frozen ? `${formatFilename(filename)}: ${SEATBELT_FROZEN}: didn't remove max errors` : `${formatFilename(filename)}: remove max errors` -+ ); -+ if (args.frozen) { -+ return false; -+ } -+ this.data.delete(relativeFilename); -+ this.changed = true; -+ return true; -+ } -+ updateMaxErrors(filename, args, ruleToErrorCount) { -+ const removedRules = /* @__PURE__ */ new Set(); -+ let increasedRulesCount = 0; -+ let decreasedRulesCount = 0; -+ this.getMaxErrors(filename); -+ const relativeFilename = this.toRelativePath(filename); -+ const maxErrors = this.data.get(relativeFilename)?.maxErrors ?? /* @__PURE__ */ new Map(); -+ ruleToErrorCount.forEach((errorCount, ruleId) => { -+ const maxErrorCount = maxErrors.get(ruleId) ?? 0; -+ if (errorCount === maxErrorCount) { -+ return; -+ } -+ if (errorCount < maxErrorCount || SeatbeltArgs.ruleSetHas(args.allowIncreaseRules, ruleId)) { -+ SeatbeltArgs.verboseLog( -+ args, -+ () => args.frozen ? `${formatFilename(filename)}: ${formatRuleId(ruleId)}: ${SEATBELT_FROZEN}: didn't update max errors ${maxErrorCount} -> ${errorCount}` : `${formatFilename(filename)}: ${formatRuleId(ruleId)}: update max errors ${maxErrorCount} -> ${errorCount}` -+ ); -+ maxErrors.set(ruleId, errorCount); -+ if (errorCount > maxErrorCount) { -+ increasedRulesCount++; -+ } else { -+ decreasedRulesCount++; -+ } -+ } -+ }); -+ if (args.verbose || args.keepRules !== "all") { -+ maxErrors.forEach((maxErrorCount, ruleId) => { -+ const shouldRemove = maxErrorCount === 0 || !ruleToErrorCount.has(ruleId); -+ if (!shouldRemove) { -+ return; -+ } -+ if (SeatbeltArgs.ruleSetHas(args.keepRules, ruleId)) { -+ SeatbeltArgs.verboseLog( -+ args, -+ () => `${formatFilename(filename)}: ${formatRuleId(ruleId)}: ${SEATBELT_KEEP}: didn't update max errors ${maxErrorCount} -> ${0}` -+ ); -+ return; -+ } -+ SeatbeltArgs.verboseLog( -+ args, -+ () => args.frozen ? `${formatFilename(filename)}: ${formatRuleId(ruleId)}: ${SEATBELT_FROZEN}: didn't update max errors ${maxErrorCount} -> ${0}` : `${formatFilename(filename)}: ${formatRuleId(ruleId)}: update max errors ${maxErrorCount} -> ${0}` -+ ); -+ maxErrors.delete(ruleId); -+ removedRules.add(ruleId); -+ }); -+ } -+ const changed = increasedRulesCount > 0 || decreasedRulesCount > 0 || removedRules.size > 0; -+ if (changed && !args.frozen) { -+ const file = this.data.get(relativeFilename); -+ if (file) { -+ file.maxErrors = maxErrors; -+ } else { -+ this.data.set(relativeFilename, { -+ maxErrors, -+ lines: [] -+ }); -+ } -+ this.changed = true; -+ } -+ return { removedRules, increasedRulesCount, decreasedRulesCount }; -+ } -+ /** Atomic read -> apply delta -> write. Takes an exclusive file lock when `args.threadsafe`. */ -+ updateFileMaxErrors(args, filename, ruleToErrorCount) { -+ return this.withOptionalLock(args, () => { -+ const before = this.getMaxErrors(filename); -+ const ruleToMaxErrorCountBefore = before ? new Map(before) : void 0; -+ const result = this.updateMaxErrors(filename, args, ruleToErrorCount); -+ if (!args.frozen) { -+ this.flushChanges(); -+ } -+ return { ruleToMaxErrorCountBefore, ...result }; -+ }); -+ } -+ /** Drop entries whose source file no longer exists. Takes an exclusive file lock when `args.threadsafe`. */ -+ cleanUpRemovedFiles(args) { -+ return this.withOptionalLock(args, () => { -+ let removedFiles = 0; -+ for (const filename of Array.from(this.filenames())) { -+ if (!fs.existsSync(filename)) { -+ if (this.removeFile(filename, args)) { -+ removedFiles++; -+ } -+ } -+ } -+ if (!args.frozen) { -+ this.flushChanges(); -+ } -+ return { removedFiles }; -+ }); -+ } -+ withOptionalLock(args, fn) { -+ if (!args.threadsafe) { -+ return fn(); -+ } -+ const lock = new FileLock(`${this.filename}.lock`); -+ lock.waitLock(LOCK_TIMEOUT_MS); -+ try { -+ this.readSync(); -+ return fn(); -+ } finally { -+ lock.unlock(); -+ } -+ } -+ toDataString() { -+ const lines = []; -+ this.data.forEach((fileState, filename) => { -+ if (fileState.maxErrors) { -+ fileState.lines = []; -+ fileState.maxErrors.forEach((maxErrorCount, ruleId) => { -+ fileState.lines.push({ filename, ruleId, maxErrors: maxErrorCount }); -+ }); -+ fileState.lines.sort( -+ (a, b) => a.ruleId === b.ruleId ? 0 : a.ruleId < b.ruleId ? -1 : 1 -+ ); -+ } -+ fileState.lines.forEach((line) => { -+ const encoded = line.encoded ??= encodeLine(line); -+ lines.push(encoded); -+ }); -+ }); -+ lines.sort(); -+ if (this.comments) { -+ return this.comments + "\n\n" + lines.join(""); -+ } else { -+ return lines.join(""); -+ } -+ } -+ readSync() { -+ const nextStateFile = _SeatbeltFile.openSync(this.filename); -+ if (nextStateFile) { -+ this.data = nextStateFile.data; -+ this.changed = false; -+ return true; -+ } -+ return false; -+ } -+ flushChanges() { -+ if (this.changed) { -+ this.writeSync(); -+ this.changed = false; -+ return { updated: true }; -+ } -+ return { updated: false }; -+ } -+ writeSync() { -+ const dataString = this.toDataString(); -+ const dir = nodePath.dirname(this.filename); -+ const base = nodePath.basename(this.filename); -+ const tempFile = nodePath.join( -+ this.useTempDirForWrites ? os.tmpdir() : dir, -+ `.${base}.wip${process.pid}.${Date.now()}.tmp` -+ ); -+ fs.mkdirSync(dir, { recursive: true }); -+ fs.writeFileSync(tempFile, dataString, "utf8"); -+ try { -+ fs.renameSync(tempFile, this.filename); -+ } catch (error) { -+ if (isErrno(error, "EXDEV")) { -+ this.useTempDirForWrites = false; -+ fs.copyFileSync(tempFile, this.filename); -+ fs.rmSync(tempFile); -+ return; -+ } -+ throw error; -+ } -+ } -+ toJSON() { -+ const data = Object.fromEntries( -+ Array.from(this.data.keys()).map((filename) => { -+ const maxErrors = this.getMaxErrors(filename); -+ if (!maxErrors) { -+ throw new Error(`${name} bug: expected errors for existing key`); -+ } -+ return [filename, Object.fromEntries(maxErrors)]; -+ }) -+ ); -+ return { filename: this.filename, data }; -+ } -+ toRelativePath(filename) { -+ if (!nodePath.isAbsolute(filename)) { -+ return filename; -+ } -+ return nodePath.relative(this.dirname, filename); -+ } -+ toAbsolutePath(filename) { -+ if (nodePath.isAbsolute(filename)) { -+ return filename; -+ } -+ return nodePath.resolve(this.dirname, filename); -+ } -+}; -+function parseMaxErrors(lines) { -+ const maxErrors = /* @__PURE__ */ new Map(); -+ lines.forEach((line) => { -+ maxErrors.set(line.ruleId, line.maxErrors); -+ }); -+ return maxErrors; -+} -+ -+export { -+ appendErrorContext, -+ FileLock, -+ SeatbeltFile -+}; -+//# sourceMappingURL=chunk-5FFUIU4M.mjs.map -\ No newline at end of file -diff --git a/node_modules/eslint-seatbelt/dist/chunk-7ZO4DCZA.mjs b/node_modules/eslint-seatbelt/dist/chunk-7ZO4DCZA.mjs -deleted file mode 100644 -index 3a572f0..0000000 ---- a/node_modules/eslint-seatbelt/dist/chunk-7ZO4DCZA.mjs -+++ /dev/null -@@ -1,427 +0,0 @@ --var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { -- get: (a, b) => (typeof require !== "undefined" ? require : a)[b] --}) : x)(function(x) { -- if (typeof require !== "undefined") return require.apply(this, arguments); -- throw Error('Dynamic require of "' + x + '" is not supported'); --}); -- --// package.json --var name = "eslint-seatbelt"; --var version = "0.1.3"; --var package_default = { -- name, -- version, -- description: "Gradually tighten ESLint rules in your codebase", -- keywords: [ -- "eslint", -- "incremental", -- "gradual", -- "workflow", -- "processor", -- "linting" -- ], -- author: { -- name: "Jake Teton-Landis", -- url: "https://jake.tl" -- }, -- repository: { -- type: "git", -- url: "git+https://github.com/justjake/eslint-seatbelt.git" -- }, -- bugs: { -- url: "https://github.com/justjake/eslint-seatbelt/issues" -- }, -- scripts: { -- build: "./scripts/make-json-schemas.ts && tsc && tsup", -- test: "node --test --require tsx/cjs $(find src -name '*.test.ts')", -- lint: "pnpm build && NODE_OPTIONS='--enable-source-maps' eslint ." -- }, -- types: "dist/index.d.ts", -- import: "./dist/index.mjs", -- main: "dist/index.js", -- exports: { -- ".": { -- types: "./dist/index.d.ts", -- import: "./dist/index.mjs", -- default: "./dist/index.js" -- }, -- "./api": { -- types: "./dist/api.d.ts", -- import: "./dist/api.mjs", -- default: "./dist/api.js" -- } -- }, -- bin: { -- "eslint-seatbelt": "./dist/command.js" -- }, -- files: [ -- "!*.tsbuildinfo", -- "!src/**/*.test.ts", -- "src", -- "dist" -- ], -- license: "MIT", -- peerDependencies: { -- "@types/eslint": "*", -- eslint: "*" -- }, -- peerDependenciesMeta: { -- eslint: { -- optional: true -- }, -- "@types/eslint": { -- optional: true -- } -- }, -- devDependencies: { -- "@eslint/compat": "1.2.3", -- "@eslint/js": "9.15.0", -- "@types/eslint__js": "8.42.3", -- "@types/node": "22.9.0", -- "@typescript-eslint/rule-tester": "8.14.0", -- "@typescript-eslint/utils": "8.14.0", -- eslint: "9.14.0", -- prettier: "3.3.3", -- tsup: "8.3.5", -- tsx: "4.19.2", -- typescript: "5.6.3", -- "typescript-eslint": "8.14.0", -- "typescript-json-schema": "0.65.1" -- }, -- dependencies: { -- "ts-command-line-args": "^2.5.1" -- }, -- packageManager: "pnpm@10.2.1+sha1.48adf39a4ab751eda7b73b99447d1f0b6d227e02" --}; -- --// src/SeatbeltConfig.ts --import path from "node:path"; -- --// src/repoIntegration.ts --import fs from "node:fs"; --import nodePath from "node:path"; --function findAncestorDirectory(path2, predicate) { -- let lastPath = void 0; -- while (path2 !== lastPath) { -- if (predicate(path2)) { -- return path2; -- } -- lastPath = path2; -- path2 = nodePath.dirname(path2); -- } --} --function isGitRoot(dir) { -- return fs.existsSync(nodePath.join(dir, ".git")); --} --function findRepoRoot(path2) { -- return findAncestorDirectory(path2, isGitRoot); --} -- --// src/SeatbeltConfig.ts --var SEATBELT_FILE_NAME = "eslint.seatbelt.tsv"; --var SEATBELT_FROZEN = "SEATBELT_FROZEN"; --var SEATBELT_INCREASE = "SEATBELT_INCREASE"; --var SEATBELT_KEEP = "SEATBELT_KEEP"; --var SEATBELT_FILE = "SEATBELT_FILE"; --var SEATBELT_PWD = "SEATBELT_PWD"; --var SEATBELT_DISABLE = "SEATBELT_DISABLE"; --var SEATBELT_THREADSAFE = "SEATBELT_THREADSAFE"; --var SEATBELT_VERBOSE = "SEATBELT_VERBOSE"; --var SEATBELT_QUIET = "SEATBELT_QUIET"; --var SEATBELT_ROOT = "SEATBELT_ROOT"; --var ENV_VARS = { -- SEATBELT_FROZEN, -- SEATBELT_INCREASE, -- SEATBELT_KEEP, -- SEATBELT_FILE, -- SEATBELT_PWD, -- SEATBELT_DISABLE, -- SEATBELT_THREADSAFE, -- SEATBELT_VERBOSE, -- SEATBELT_QUIET, -- SEATBELT_ROOT, -- CI: "CI", -- JEST_WORKER_ID: "JEST_WORKER_ID" --}; --var SeatbeltConfig = { -- withEnvOverrides(config, env) { -- return { -- ...SeatbeltConfig.fromFallbackEnv(env), -- ...config, -- ...SeatbeltConfig.fromEnvOverrides(env) -- }; -- }, -- fromFallbackEnv(env, log) { -- const config = {}; -- const isCI = SeatbeltEnv.readBooleanEnvVar(env.CI); -- if (isCI) { -- config.frozen = true; -- log?.(`${padVarName("CI")} config.frozen defaults to`, true); -- } -- if (env.JEST_WORKER_ID) { -- config.threadsafe = true; -- log?.( -- `${padVarName("JEST_WORKER_ID")} config.threadsafe defaults to`, -- true -- ); -- } -- return config; -- }, -- fromEnvOverrides(env, log) { -- const config = { -- pwd: env[SEATBELT_PWD] || process.cwd() -- }; -- const verbose = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_VERBOSE]); -- if (verbose !== void 0) { -- config.verbose = verbose; -- log?.(`${padVarName(SEATBELT_VERBOSE)} config.verbose =`, verbose); -- } -- const seatbeltFile = env[SEATBELT_FILE]; -- if (seatbeltFile) { -- const rootRelative = path.isAbsolute(seatbeltFile) ? seatbeltFile : path.join(config.pwd, seatbeltFile); -- config.seatbeltFile = rootRelative; -- log?.(`${padVarName(SEATBELT_FILE)} config.seatbeltFile =`, rootRelative); -- } -- const disable = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_DISABLE]); -- if (disable !== void 0) { -- config.disable = disable; -- log?.(`${padVarName(SEATBELT_DISABLE)} config.disable =`, disable); -- } -- const frozen = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_FROZEN]); -- if (frozen !== void 0) { -- config.frozen = frozen; -- log?.(`${padVarName(SEATBELT_FROZEN)} config.frozen =`, frozen); -- } -- const increase = SeatbeltEnv.parseRuleSetEnvVar(env[SEATBELT_INCREASE]); -- if (increase !== void 0) { -- config.allowIncreaseRules = increase; -- log?.( -- `${padVarName(SEATBELT_INCREASE)} config.allowIncreaseRules =`, -- increase -- ); -- } -- const keep = SeatbeltEnv.parseRuleSetEnvVar(env[SEATBELT_KEEP]); -- if (keep !== void 0) { -- config.keepRules = keep; -- log?.(`${padVarName(SEATBELT_KEEP)} config.keepRules =`, keep); -- } -- const threadsafe = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_THREADSAFE]); -- if (threadsafe !== void 0) { -- config.threadsafe = threadsafe; -- log?.( -- `${padVarName(SEATBELT_THREADSAFE)} config.threadsafe =`, -- threadsafe -- ); -- } -- const quiet = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_QUIET]); -- if (quiet !== void 0) { -- config.quiet = quiet; -- log?.(`${padVarName(SEATBELT_QUIET)} config.quiet =`, quiet); -- } -- const root = env[SEATBELT_ROOT]; -- if (root) { -- config.root = root; -- log?.(`${padVarName(SEATBELT_ROOT)} config.root =`, root); -- } -- return config; -- } --}; --var SeatbeltEnv = { -- parseRuleSetEnvVar(value) { -- if (value === void 0) { -- return void 0; -- } -- if (!value) { -- return []; -- } -- const lower = value.toLowerCase(); -- if (lower === "all" || lower === "1" || lower === "true") { -- return "all"; -- } -- return value.split(/[\s,]+/g).filter(Boolean); -- }, -- readBooleanEnvVar(value) { -- if (value === void 0 || value === "") { -- return void 0; -- } -- const lower = value.toLowerCase(); -- if (lower === "false" || lower === "0" || lower === "no") { -- return false; -- } -- return Boolean(value); -- } --}; --var logStdout = (...message) => ( -- // eslint-disable-next-line no-console -- console.log(`[${name}]:`, ...message) --); --var logStderr = (...message) => ( -- // eslint-disable-next-line no-console -- console.error(`[${name}]:`, ...message) --); --var SeatbeltArgs = { -- fromConfig(config) { -- const cwd = config.pwd ?? process.cwd(); -- const seatbeltFile = config.seatbeltFile ?? SeatbeltArgs.findSeatbeltFile(cwd); -- const root = config.root ?? findRepoRoot(seatbeltFile) ?? path.dirname(seatbeltFile); -- return { -- seatbeltFile, -- root, -- keepRules: typeof config.keepRules === "string" ? config.keepRules : new Set(config.keepRules ?? []), -- allowIncreaseRules: typeof config.allowIncreaseRules === "string" ? config.allowIncreaseRules : new Set(config.allowIncreaseRules ?? []), -- frozen: config.frozen ?? false, -- disable: config.disable ?? false, -- quiet: config.quiet ?? false, -- threadsafe: config.threadsafe ?? false, -- verbose: config.verbose ?? false -- }; -- }, -- getLogger(args) { -- if (typeof args.verbose === "function") { -- return args.verbose; -- } -- if (args.verbose === "stdout") { -- return logStdout; -- } -- return logStderr; -- }, -- ruleSetHas(ruleSet, ruleId) { -- return ruleSet === "all" || ruleSet.has(ruleId); -- }, -- verboseLog(args, makeMessage) { -- if (args.verbose) { -- const message = makeMessage(); -- const log = SeatbeltArgs.getLogger(args); -- if (typeof message === "string") { -- log(message); -- } else { -- log(...message); -- } -- } -- }, -- findSeatbeltFile(cwd) { -- return `${cwd}/${SEATBELT_FILE_NAME}`; -- } --}; --var envVarMaxLength = 0; --function padVarName(name2) { -- envVarMaxLength ||= Math.max( -- ...Object.values(ENV_VARS).map((name3) => name3.length) -- ); -- return `${name2}:`.padEnd(envVarMaxLength + 1); --} --function formatFilename(filename) { -- const relative = path.relative( -- process.env[SEATBELT_PWD] ?? process.cwd(), -- filename -- ); -- return relative ? relative : filename; --} --function formatRuleId(ruleId) { -- if (ruleId === null) { -- return `unknown rule`; -- } -- return `rule ${ruleId}`; --} -- --// src/jsonSchema/SeatbeltConfigSchema.ts --var SeatbeltConfigSchema = { -- description: 'Configuration for seatbelt can be provided in a few ways:\n\n1. Defined in the shared `settings` object in your ESLint config. This\n requires also configuring the `eslint-seatbelt/configure` rule.\n\n ```js\n // in eslint.config.js\n const config = [\n {\n settings: {\n seatbelt: {\n // ...\n }\n },\n rules: {\n "eslint-seatbelt/configure": "error",\n }\n }\n ]\n ```\n\n2. Using the `eslint-seatbelt/configure` rule in your ESLint config.\n This can be used to override settings for specific files in legacy ESLint configs.\n Any configuration provided here will override the shared `settings` object.\n\n ```js\n // in .eslintrc.js\n module.exports = {\n rules: {\n "eslint-seatbelt/configure": "error",\n },\n overrides: [\n {\n files: ["some/path/*"],\n rules: {\n "eslint-seatbelt/configure": ["error", { seatbeltFile: "some/path/eslint.seatbelt.tsv" }]\n },\n },\n ],\n }\n ```\n3. The settings in config files can be overridden with environment variables when running `eslint` or other tools.\n\n ```bash\n SEATBELT_FILE=some/path/eslint.seatbelt.tsv SEATBELT_FROZEN=1 eslint\n ```', -- type: "object", -- properties: { -- seatbeltFile: { -- description: "The seatbelt file stores the max error counts allowed for each file. Should\nbe an absolute path.\n\nIf not provided, $SEATBELT_PWD/eslint.seatbelt.tsv or $PWD/eslint.seatbelt.tsv will be used.\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n // commonjs\n seatbeltFile: `${__dirname}/eslint.seatbelt.tsv`\n // esm\n seatbeltFile: new URL('./eslint.seatbelt.tsv', import.meta.url).pathname\n }\n }\n }\n]\n```\n\nYou can also set this with environment variable `SEATBELT_FILE`:\n\n```bash\nSEATBELT_FILE=.config/custom-seatbelt-file eslint\n```", -- type: "string" -- }, -- keepRules: { -- description: 'By default whenever a file is linted and a rule has no errors, that rule\'s\nmax errors for the file is set to zero.\n\nHowever with typescript-eslint, it can be helpful to have two ESLint configs:\n\n- A default ESLint config with only syntactic rules enabled that don\'t\n require typechecking, that runs on developer machines and in their editor.\n- A CI-only ESLint config with only type-aware rules enabled that requires\n typechecking. Since these rules require typechecking, they can be too\n slow to run in interactive contexts.\n\nTo avoid this, set `keepRules` to the names of *disabled but known rules*\nwhile linting.\n\nExample:\n\n```js\n// Default ESLint config\nmodule.exports = [\n {\n settings: {\n seatbelt: {\n keepRules: require(\'./eslint-typed.config.js\').flatMap(config => Object.keys(config.rules ?? {})),\n }\n },\n rules: {\n "no-unused-vars": "error",\n },\n }\n]\n\n// Typechecking-required ESLint config for CI\nmodule.exports = [\n {\n settings: {\n seatbelt: {\n keepRules: require(\'./eslint.config.js\').flatMap(config => Object.keys(config.rules ?? {})),\n }\n },\n rules: {\n // Requires typechecking (slow)\n "@typescript-eslint/no-floating-promises": "error",\n },\n }\n]\n```\n\nYou can also set this with environment variable `SEATBELT_KEEP`:\n\n```bash\nSEATBELT_KEEP="@typescript-eslint/no-floating-promises', -- anyOf: [ -- { -- type: "array", -- items: { -- type: "string" -- } -- }, -- { -- const: "all", -- type: "string" -- } -- ] -- }, -- allowIncreaseRules: { -- description: 'When you enable a rule for the first time, lint with it in this set to set\nthe initial max error counts.\n\nTypically this should be enabled for one lint run only via an environment\nvariable, but it can also be configured via ESLint settings.\n\n```bash\nSEATBELT_INCREASE="@typescript-eslint/no-floating-promises" eslint\n```\n\nYou can set this to `"ALL"` to enable this setting for ALL rules:\n\n```bash\nSEATBELT_INCREASE=ALL eslint\n```\n\n```js\n// in eslint.config.js\n// maybe you have a use-case for this\nconst config = [\n {\n settings: {\n seatbelt: {\n allowIncreaseRules: ["@typescript-eslint/no-floating-promises"],\n }\n }\n }\n]\n```', -- anyOf: [ -- { -- type: "array", -- items: { -- type: "string" -- } -- }, -- { -- const: "all", -- type: "string" -- } -- ] -- }, -- frozen: { -- description: "Error if there is any change in the number of errors in the seatbelt file.\nThis is useful in CI to ensures that developers keep the seatbelt file up-to-date as they fix errors.\n\nIt is enabled by default when environment variable `CI` is set.\n\n```bash\nCI=1 eslint\n```\n\nThis can be set with the `SEATBELT_FROZEN` environment variable.\n\n```bash\nSEATBELT_FROZEN=1 eslint\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n frozen: true,\n }\n }\n }\n]\n```", -- type: "boolean" -- }, -- disable: { -- description: "Completely disable seatbelt error processing for a lint run while leaving it otherwise configured.\n\nThis can be set with the `SEATBELT_DISABLE` environment variable.\n\n```bash\nSEATBELT_DISABLE=1 eslint\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n disable: true,\n }\n }\n }\n]\n```", -- type: "boolean" -- }, -- quiet: { -- description: 'Suppress seatbelt\'s informational warning messages (e.g. "tend the garden",\n"thank you for fixing"). When enabled, seatbelt still downgrades errors to\nwarnings and updates the seatbelt file, but the warning messages are not\nemitted as ESLint results. Over-limit errors and frozen-mode warnings are\nalways preserved.\n\nThis is useful when seatbelt warnings create noise in CI logs or editor\nintegrations.\n\nThis can be set with the `SEATBELT_QUIET` environment variable.\n\n```bash\nSEATBELT_QUIET=1 eslint\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n quiet: true,\n }\n }\n }\n]\n```', -- type: "boolean" -- }, -- threadsafe: { -- description: "By default seatbelt assumes that only one ESLint process will read and\nwrite to the seatbelt file at a time.\n\nThis should be set to `true` if you use a parallel ESLint runner similar to\njest-runner-eslint to avoid losing updates during parallel writes to the\nseatbelt file.\n\nWhen enabled, seatbelt creates temporary lock files to serialize updates to\nthe seatbelt file. This comes at a small performance cost.\n\nThis is enabled by default when run with Jest (environment variable `JEST_WORKER_ID` is set).\n\nIt can also be set with environment variable `SEATBELT_THREADSAFE`:\n\n```bash\nSEATBELT_THREADSAFE=1 eslint-parallel\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n threadsafe: true,\n }\n }\n }\n]\n```", -- type: "boolean" -- }, -- verbose: { -- description: "Enable verbose logging.\n\nThis can be set with the `SEATBELT_VERBOSE` environment variable.\n\n```bash\nSEATBELT_VERBOSE=1 eslint\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n verbose: true,\n }\n }\n }\n]\n```\n\nIf set to a function (like `console.error`), that function will be called with the log messages.\nThe default logger when set to `true` is `console.error`.", -- anyOf: [ -- { -- enum: [false, "stderr", "stdout", true] -- }, -- { -- type: "object" -- } -- ] -- }, -- root: { -- description: "Repository or project root.\nBy default this is inferred from `seatbeltFile` by checking ancestor directories for `.git`.\nUsed for editor integration to disable seatbelt during git actions like rebase or merge.\n\nThis can be set with the `SEATBELT_ROOT` environment variable.", -- type: "string" -- } -- }, -- $schema: "http://json-schema.org/draft-07/schema#" --}; -- --export { -- __require, -- name, -- version, -- package_default, -- SEATBELT_FILE_NAME, -- SEATBELT_FROZEN, -- SEATBELT_INCREASE, -- SEATBELT_KEEP, -- SEATBELT_FILE, -- SEATBELT_PWD, -- SEATBELT_DISABLE, -- SEATBELT_THREADSAFE, -- SEATBELT_VERBOSE, -- SEATBELT_QUIET, -- SEATBELT_ROOT, -- SeatbeltConfig, -- SeatbeltEnv, -- logStdout, -- logStderr, -- SeatbeltArgs, -- padVarName, -- formatFilename, -- formatRuleId, -- SeatbeltConfigSchema --}; --//# sourceMappingURL=chunk-7ZO4DCZA.mjs.map -\ No newline at end of file -diff --git a/node_modules/eslint-seatbelt/dist/chunk-K7UHJBLM.js b/node_modules/eslint-seatbelt/dist/chunk-K7UHJBLM.js -deleted file mode 100644 -index 17e3bfa..0000000 ---- a/node_modules/eslint-seatbelt/dist/chunk-K7UHJBLM.js -+++ /dev/null -@@ -1,329 +0,0 @@ --"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; -- -- -- -- -- -- --var _chunkTVRMUM3Fjs = require('./chunk-TVRMUM3F.js'); -- --// src/SeatbeltFile.ts --var _os = require('os'); var os = _interopRequireWildcard(_os); --var _fs = require('fs'); var fs = _interopRequireWildcard(_fs); --var _path = require('path'); var nodePath = _interopRequireWildcard(_path); -- --// src/errorHanding.ts --function appendErrorContext(error, context) { -- if (error instanceof Error) { -- error.message += ` -- ${context}`; -- } --} --function isErrno(error, code) { -- return error instanceof Error && "code" in error && error.code === code; --} -- --// src/SeatbeltFile.ts --function encodeLine(line) { -- const { filename, ruleId, maxErrors } = line; -- return `${JSON.stringify(filename)} ${JSON.stringify(ruleId)} ${maxErrors} --`; --} --function decodeLine(line, index) { -- try { -- const lineParts = line.split(" "); -- if (lineParts.length !== 3) { -- throw new Error( -- `Expected 3 tab-separated JSON strings, instead have ${lineParts.length}` -- ); -- } -- let filename; -- try { -- filename = JSON.parse(lineParts[0]); -- } catch (e) { -- appendErrorContext(e, "at tab-separated column 1 (filename)"); -- throw e; -- } -- let ruleId; -- try { -- ruleId = JSON.parse(lineParts[1]); -- } catch (e) { -- appendErrorContext(e, "at tab-separated column 2 (RuleId)"); -- throw e; -- } -- let maxErrors; -- try { -- maxErrors = JSON.parse(lineParts[2]); -- } catch (e) { -- appendErrorContext(e, "at tab-separated column 3 (maxErrors)"); -- throw e; -- } -- return { -- encoded: line, -- filename, -- ruleId, -- maxErrors -- }; -- } catch (e) { -- appendErrorContext(e, `at line ${index + 1}: \`${line.trim()}\``); -- throw e; -- } --} --var COMMENT_LINE_REGEX = /^\s*#/; --var NON_EMPTY_LINE_REGEX = /\S+/; --var DEFAULT_FILE_HEADER = ` --# ${_chunkTVRMUM3Fjs.name} temporarily allowed errors --# docs: https://github.com/justjake/${_chunkTVRMUM3Fjs.name}#readme --`.trim(); --var SeatbeltFile = (_class = class _SeatbeltFile { -- constructor(filename, data, comments = "") {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this); -- this.filename = filename; -- this.data = data; -- this.comments = comments; -- this.filename = nodePath.default.resolve(this.filename); -- this.dirname = nodePath.default.dirname(this.filename); -- } -- static readSync(filename) { -- const text = fs.readFileSync(filename, "utf8"); -- try { -- return _SeatbeltFile.parse(filename, text); -- } catch (e) { -- appendErrorContext(e, `in seatbelt file \`${filename}\``); -- throw e; -- } -- } -- /** -- * Read `filename` if it exists, otherwise create a new empty seatbelt file object -- * that will write to that filename. -- */ -- static openSync(filename) { -- try { -- return _SeatbeltFile.readSync(filename); -- } catch (e) { -- if (isErrno(e, "ENOENT")) { -- return new _SeatbeltFile(filename, /* @__PURE__ */ new Map(), DEFAULT_FILE_HEADER); -- } -- throw e; -- } -- } -- static parse(filename, text) { -- const data = /* @__PURE__ */ new Map(); -- const split = text.split(/(?<=\n)/); -- const lines = split.filter( -- (line) => NON_EMPTY_LINE_REGEX.test(line) && !COMMENT_LINE_REGEX.test(line) -- ).map(decodeLine); -- const comments = split.filter((line) => COMMENT_LINE_REGEX.test(line)).join(""); -- lines.forEach((line) => { -- let fileState = data.get(line.filename); -- if (!fileState) { -- fileState = { maxErrors: void 0, lines: [] }; -- data.set(line.filename, fileState); -- } -- fileState.lines.push(line); -- }); -- return new _SeatbeltFile(filename, data, comments.trim()); -- } -- static fromJSON(json) { -- const data = new Map( -- Object.entries(json.data).map(([filename, maxErrors]) => [ -- filename, -- { maxErrors: new Map(Object.entries(maxErrors)), lines: [] } -- ]) -- ); -- return new _SeatbeltFile(json.filename, data); -- } -- __init() {this.changed = false} -- -- __init2() {this.useTempDirForWrites = true} -- *filenames() { -- for (const filename of this.data.keys()) { -- yield this.toAbsolutePath(filename); -- } -- } -- getMaxErrors(filename) { -- const fileState = this.data.get(this.toRelativePath(filename)); -- if (!fileState) { -- return void 0; -- } -- fileState.maxErrors ??= parseMaxErrors(fileState.lines); -- return fileState.maxErrors; -- } -- removeFile(filename, args) { -- const relativeFilename = this.toRelativePath(filename); -- if (!this.data.has(relativeFilename)) { -- return false; -- } -- _chunkTVRMUM3Fjs.SeatbeltArgs.verboseLog( -- args, -- () => args.frozen ? `${_chunkTVRMUM3Fjs.formatFilename.call(void 0, filename)}: ${_chunkTVRMUM3Fjs.SEATBELT_FROZEN}: didn't remove max errors` : `${_chunkTVRMUM3Fjs.formatFilename.call(void 0, filename)}: remove max errors` -- ); -- if (args.frozen) { -- return false; -- } -- this.data.delete(relativeFilename); -- this.changed = true; -- return true; -- } -- updateMaxErrors(filename, args, ruleToErrorCount) { -- const removedRules = /* @__PURE__ */ new Set(); -- let increasedRulesCount = 0; -- let decreasedRulesCount = 0; -- this.getMaxErrors(filename); -- const relativeFilename = this.toRelativePath(filename); -- const maxErrors = _nullishCoalesce(_optionalChain([this, 'access', _ => _.data, 'access', _2 => _2.get, 'call', _3 => _3(relativeFilename), 'optionalAccess', _4 => _4.maxErrors]), () => ( /* @__PURE__ */ new Map())); -- ruleToErrorCount.forEach((errorCount, ruleId) => { -- const maxErrorCount = _nullishCoalesce(maxErrors.get(ruleId), () => ( 0)); -- if (errorCount === maxErrorCount) { -- return; -- } -- if (errorCount < maxErrorCount || _chunkTVRMUM3Fjs.SeatbeltArgs.ruleSetHas(args.allowIncreaseRules, ruleId)) { -- _chunkTVRMUM3Fjs.SeatbeltArgs.verboseLog( -- args, -- () => args.frozen ? `${_chunkTVRMUM3Fjs.formatFilename.call(void 0, filename)}: ${_chunkTVRMUM3Fjs.formatRuleId.call(void 0, ruleId)}: ${_chunkTVRMUM3Fjs.SEATBELT_FROZEN}: didn't update max errors ${maxErrorCount} -> ${errorCount}` : `${_chunkTVRMUM3Fjs.formatFilename.call(void 0, filename)}: ${_chunkTVRMUM3Fjs.formatRuleId.call(void 0, ruleId)}: update max errors ${maxErrorCount} -> ${errorCount}` -- ); -- maxErrors.set(ruleId, errorCount); -- if (errorCount > maxErrorCount) { -- increasedRulesCount++; -- } else { -- decreasedRulesCount++; -- } -- } -- }); -- if (args.verbose || args.keepRules !== "all") { -- maxErrors.forEach((maxErrorCount, ruleId) => { -- const shouldRemove = maxErrorCount === 0 || !ruleToErrorCount.has(ruleId); -- if (!shouldRemove) { -- return; -- } -- if (_chunkTVRMUM3Fjs.SeatbeltArgs.ruleSetHas(args.keepRules, ruleId)) { -- _chunkTVRMUM3Fjs.SeatbeltArgs.verboseLog( -- args, -- () => `${_chunkTVRMUM3Fjs.formatFilename.call(void 0, filename)}: ${_chunkTVRMUM3Fjs.formatRuleId.call(void 0, ruleId)}: ${_chunkTVRMUM3Fjs.SEATBELT_KEEP}: didn't update max errors ${maxErrorCount} -> ${0}` -- ); -- return; -- } -- _chunkTVRMUM3Fjs.SeatbeltArgs.verboseLog( -- args, -- () => args.frozen ? `${_chunkTVRMUM3Fjs.formatFilename.call(void 0, filename)}: ${_chunkTVRMUM3Fjs.formatRuleId.call(void 0, ruleId)}: ${_chunkTVRMUM3Fjs.SEATBELT_FROZEN}: didn't update max errors ${maxErrorCount} -> ${0}` : `${_chunkTVRMUM3Fjs.formatFilename.call(void 0, filename)}: ${_chunkTVRMUM3Fjs.formatRuleId.call(void 0, ruleId)}: update max errors ${maxErrorCount} -> ${0}` -- ); -- maxErrors.delete(ruleId); -- removedRules.add(ruleId); -- }); -- } -- const changed = increasedRulesCount > 0 || decreasedRulesCount > 0 || removedRules.size > 0; -- if (changed && !args.frozen) { -- const file = this.data.get(relativeFilename); -- if (file) { -- file.maxErrors = maxErrors; -- } else { -- this.data.set(relativeFilename, { -- maxErrors, -- lines: [] -- }); -- } -- this.changed = true; -- } -- return { removedRules, increasedRulesCount, decreasedRulesCount }; -- } -- toDataString() { -- const lines = []; -- this.data.forEach((fileState, filename) => { -- if (fileState.maxErrors) { -- fileState.lines = []; -- fileState.maxErrors.forEach((maxErrorCount, ruleId) => { -- fileState.lines.push({ filename, ruleId, maxErrors: maxErrorCount }); -- }); -- fileState.lines.sort( -- (a, b) => a.ruleId === b.ruleId ? 0 : a.ruleId < b.ruleId ? -1 : 1 -- ); -- } -- fileState.lines.forEach((line) => { -- const encoded = line.encoded ??= encodeLine(line); -- lines.push(encoded); -- }); -- }); -- lines.sort(); -- if (this.comments) { -- return this.comments + "\n\n" + lines.join(""); -- } else { -- return lines.join(""); -- } -- } -- readSync() { -- const nextStateFile = _SeatbeltFile.openSync(this.filename); -- if (nextStateFile) { -- this.data = nextStateFile.data; -- this.changed = false; -- return true; -- } -- return false; -- } -- flushChanges() { -- if (this.changed) { -- this.writeSync(); -- this.changed = false; -- return { updated: true }; -- } -- return { updated: false }; -- } -- writeSync() { -- const dataString = this.toDataString(); -- const dir = nodePath.dirname(this.filename); -- const base = nodePath.basename(this.filename); -- const tempFile = nodePath.join( -- this.useTempDirForWrites ? os.tmpdir() : dir, -- `.${base}.wip${process.pid}.${Date.now()}.tmp` -- ); -- fs.mkdirSync(dir, { recursive: true }); -- fs.writeFileSync(tempFile, dataString, "utf8"); -- try { -- fs.renameSync(tempFile, this.filename); -- } catch (error) { -- if (isErrno(error, "EXDEV")) { -- this.useTempDirForWrites = false; -- fs.copyFileSync(tempFile, this.filename); -- fs.rmSync(tempFile); -- return; -- } -- throw error; -- } -- } -- toJSON() { -- const data = Object.fromEntries( -- Array.from(this.data.keys()).map((filename) => { -- const maxErrors = this.getMaxErrors(filename); -- if (!maxErrors) { -- throw new Error(`${_chunkTVRMUM3Fjs.name} bug: expected errors for existing key`); -- } -- return [filename, Object.fromEntries(maxErrors)]; -- }) -- ); -- return { filename: this.filename, data }; -- } -- toRelativePath(filename) { -- if (!nodePath.isAbsolute(filename)) { -- return filename; -- } -- return nodePath.relative(this.dirname, filename); -- } -- toAbsolutePath(filename) { -- if (nodePath.isAbsolute(filename)) { -- return filename; -- } -- return nodePath.resolve(this.dirname, filename); -- } --}, _class); --function parseMaxErrors(lines) { -- const maxErrors = /* @__PURE__ */ new Map(); -- lines.forEach((line) => { -- maxErrors.set(line.ruleId, line.maxErrors); -- }); -- return maxErrors; --} -- -- -- -- -- --exports.appendErrorContext = appendErrorContext; exports.isErrno = isErrno; exports.SeatbeltFile = SeatbeltFile; --//# sourceMappingURL=chunk-K7UHJBLM.js.map -\ No newline at end of file -diff --git a/node_modules/eslint-seatbelt/dist/chunk-NTFTCWX7.js b/node_modules/eslint-seatbelt/dist/chunk-NTFTCWX7.js -new file mode 100644 -index 0000000..f3e2e3d ---- /dev/null -+++ b/node_modules/eslint-seatbelt/dist/chunk-NTFTCWX7.js -@@ -0,0 +1,480 @@ -+"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; -+ -+ -+ -+ -+ -+ -+var _chunkZVY5S6JSjs = require('./chunk-ZVY5S6JS.js'); -+ -+// src/FileLock.ts -+var _fs = require('fs'); var fs = _interopRequireWildcard(_fs); -+ -+// src/errorHanding.ts -+function appendErrorContext(error, context) { -+ if (error instanceof Error) { -+ error.message += ` -+ ${context}`; -+ } -+} -+function isErrno(error, code) { -+ return error instanceof Error && "code" in error && error.code === code; -+} -+ -+// src/FileLock.ts -+var { O_CREAT, O_EXCL, O_RDWR } = _fs.constants; -+var waitBuffer = new Int32Array(new SharedArrayBuffer(4)); -+var heldLocks = /* @__PURE__ */ new Set(); -+var cleanupHooksInstalled = false; -+var SIGNAL_EXIT_CODES = { SIGINT: 130, SIGTERM: 143, SIGHUP: 129 }; -+function installCleanupHooks() { -+ if (cleanupHooksInstalled) return; -+ cleanupHooksInstalled = true; -+ const release = () => { -+ for (const lock of heldLocks) { -+ try { -+ lock.unlock(); -+ } catch (e2) { -+ } -+ } -+ }; -+ process.on("exit", release); -+ for (const signal of Object.keys(SIGNAL_EXIT_CODES)) { -+ process.on(signal, () => { -+ release(); -+ process.exit(SIGNAL_EXIT_CODES[signal]); -+ }); -+ } -+} -+var FileLock = class { -+ constructor(filename) { -+ this.filename = filename; -+ } -+ -+ tryLock() { -+ this.assertNotLocked(); -+ try { -+ this.fd = _fs.openSync.call(void 0, this.filename, O_CREAT | O_EXCL | O_RDWR); -+ _fs.writeSync.call(void 0, this.fd, `${process.pid} -+`); -+ heldLocks.add(this); -+ installCleanupHooks(); -+ return true; -+ } catch (e) { -+ if (isErrno(e, "EEXIST")) { -+ return false; -+ } -+ throw e; -+ } -+ } -+ waitLock(timeoutMs) { -+ const deadline = Date.now() + timeoutMs; -+ let attemptedRecovery = false; -+ while (!this.tryLock()) { -+ if (Date.now() > deadline) { -+ if (!attemptedRecovery && this.reclaimIfStale()) { -+ attemptedRecovery = true; -+ continue; -+ } -+ throw new Error(`Timed out waiting for lock on ${this.filename}`); -+ } -+ Atomics.wait(waitBuffer, 0, 0, 1); -+ } -+ } -+ isLocked() { -+ return this.fd !== void 0; -+ } -+ unlock() { -+ if (this.fd !== void 0) { -+ _fs.closeSync.call(void 0, this.fd); -+ try { -+ _fs.rmSync.call(void 0, this.filename); -+ } catch (e) { -+ if (!isErrno(e, "ENOENT")) throw e; -+ } -+ this.fd = void 0; -+ heldLocks.delete(this); -+ } -+ } -+ assertNotLocked() { -+ if (this.fd !== void 0) { -+ throw new Error( -+ `FileLock "${this.filename}" is already locked by this process [pid ${process.pid}]` -+ ); -+ } -+ } -+ reclaimIfStale() { -+ let contents; -+ try { -+ contents = _fs.readFileSync.call(void 0, this.filename, "utf8"); -+ } catch (e) { -+ if (isErrno(e, "ENOENT")) return true; -+ throw e; -+ } -+ const pid = Number.parseInt(contents.trim(), 10); -+ if (!Number.isFinite(pid) || pid <= 0) return false; -+ try { -+ process.kill(pid, 0); -+ return false; -+ } catch (e) { -+ if (!isErrno(e, "ESRCH")) throw e; -+ } -+ try { -+ _fs.rmSync.call(void 0, this.filename); -+ } catch (e) { -+ if (!isErrno(e, "ENOENT")) throw e; -+ } -+ return true; -+ } -+}; -+ -+// src/SeatbeltFile.ts -+var _os = require('os'); var os = _interopRequireWildcard(_os); -+ -+var _path = require('path'); var nodePath = _interopRequireWildcard(_path); -+var LOCK_TIMEOUT_MS = 3e4; -+function encodeLine(line) { -+ const { filename, ruleId, maxErrors } = line; -+ return `${JSON.stringify(filename)} ${JSON.stringify(ruleId)} ${maxErrors} -+`; -+} -+function decodeLine(line, index) { -+ try { -+ const lineParts = line.split(" "); -+ if (lineParts.length !== 3) { -+ throw new Error( -+ `Expected 3 tab-separated JSON strings, instead have ${lineParts.length}` -+ ); -+ } -+ let filename; -+ try { -+ filename = JSON.parse(lineParts[0]); -+ } catch (e) { -+ appendErrorContext(e, "at tab-separated column 1 (filename)"); -+ throw e; -+ } -+ let ruleId; -+ try { -+ ruleId = JSON.parse(lineParts[1]); -+ } catch (e) { -+ appendErrorContext(e, "at tab-separated column 2 (RuleId)"); -+ throw e; -+ } -+ let maxErrors; -+ try { -+ maxErrors = JSON.parse(lineParts[2]); -+ } catch (e) { -+ appendErrorContext(e, "at tab-separated column 3 (maxErrors)"); -+ throw e; -+ } -+ return { -+ encoded: line, -+ filename, -+ ruleId, -+ maxErrors -+ }; -+ } catch (e) { -+ appendErrorContext(e, `at line ${index + 1}: \`${line.trim()}\``); -+ throw e; -+ } -+} -+var COMMENT_LINE_REGEX = /^\s*#/; -+var NON_EMPTY_LINE_REGEX = /\S+/; -+var DEFAULT_FILE_HEADER = ` -+# ${_chunkZVY5S6JSjs.name} temporarily allowed errors -+# docs: https://github.com/justjake/${_chunkZVY5S6JSjs.name}#readme -+`.trim(); -+var SeatbeltFile = (_class = class _SeatbeltFile { -+ constructor(filename, data, comments = "") {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this); -+ this.filename = filename; -+ this.data = data; -+ this.comments = comments; -+ this.filename = nodePath.default.resolve(this.filename); -+ this.dirname = nodePath.default.dirname(this.filename); -+ } -+ static readSync(filename) { -+ const text = fs.readFileSync(filename, "utf8"); -+ try { -+ return _SeatbeltFile.parse(filename, text); -+ } catch (e) { -+ appendErrorContext(e, `in seatbelt file \`${filename}\``); -+ throw e; -+ } -+ } -+ /** -+ * Read `filename` if it exists, otherwise create a new empty seatbelt file object -+ * that will write to that filename. -+ */ -+ static openSync(filename) { -+ try { -+ return _SeatbeltFile.readSync(filename); -+ } catch (e) { -+ if (isErrno(e, "ENOENT")) { -+ return new _SeatbeltFile(filename, /* @__PURE__ */ new Map(), DEFAULT_FILE_HEADER); -+ } -+ throw e; -+ } -+ } -+ static parse(filename, text) { -+ const data = /* @__PURE__ */ new Map(); -+ const split = text.split(/(?<=\n)/); -+ const lines = split.filter( -+ (line) => NON_EMPTY_LINE_REGEX.test(line) && !COMMENT_LINE_REGEX.test(line) -+ ).map(decodeLine); -+ const comments = split.filter((line) => COMMENT_LINE_REGEX.test(line)).join(""); -+ lines.forEach((line) => { -+ let fileState = data.get(line.filename); -+ if (!fileState) { -+ fileState = { maxErrors: void 0, lines: [] }; -+ data.set(line.filename, fileState); -+ } -+ fileState.lines.push(line); -+ }); -+ return new _SeatbeltFile(filename, data, comments.trim()); -+ } -+ static fromJSON(json) { -+ const data = new Map( -+ Object.entries(json.data).map(([filename, maxErrors]) => [ -+ filename, -+ { maxErrors: new Map(Object.entries(maxErrors)), lines: [] } -+ ]) -+ ); -+ return new _SeatbeltFile(json.filename, data); -+ } -+ __init() {this.changed = false} -+ -+ __init2() {this.useTempDirForWrites = true} -+ *filenames() { -+ for (const filename of this.data.keys()) { -+ yield this.toAbsolutePath(filename); -+ } -+ } -+ getMaxErrors(filename) { -+ const fileState = this.data.get(this.toRelativePath(filename)); -+ if (!fileState) { -+ return void 0; -+ } -+ fileState.maxErrors ??= parseMaxErrors(fileState.lines); -+ return fileState.maxErrors; -+ } -+ removeFile(filename, args) { -+ const relativeFilename = this.toRelativePath(filename); -+ if (!this.data.has(relativeFilename)) { -+ return false; -+ } -+ _chunkZVY5S6JSjs.SeatbeltArgs.verboseLog( -+ args, -+ () => args.frozen ? `${_chunkZVY5S6JSjs.formatFilename.call(void 0, filename)}: ${_chunkZVY5S6JSjs.SEATBELT_FROZEN}: didn't remove max errors` : `${_chunkZVY5S6JSjs.formatFilename.call(void 0, filename)}: remove max errors` -+ ); -+ if (args.frozen) { -+ return false; -+ } -+ this.data.delete(relativeFilename); -+ this.changed = true; -+ return true; -+ } -+ updateMaxErrors(filename, args, ruleToErrorCount) { -+ const removedRules = /* @__PURE__ */ new Set(); -+ let increasedRulesCount = 0; -+ let decreasedRulesCount = 0; -+ this.getMaxErrors(filename); -+ const relativeFilename = this.toRelativePath(filename); -+ const maxErrors = _nullishCoalesce(_optionalChain([this, 'access', _ => _.data, 'access', _2 => _2.get, 'call', _3 => _3(relativeFilename), 'optionalAccess', _4 => _4.maxErrors]), () => ( /* @__PURE__ */ new Map())); -+ ruleToErrorCount.forEach((errorCount, ruleId) => { -+ const maxErrorCount = _nullishCoalesce(maxErrors.get(ruleId), () => ( 0)); -+ if (errorCount === maxErrorCount) { -+ return; -+ } -+ if (errorCount < maxErrorCount || _chunkZVY5S6JSjs.SeatbeltArgs.ruleSetHas(args.allowIncreaseRules, ruleId)) { -+ _chunkZVY5S6JSjs.SeatbeltArgs.verboseLog( -+ args, -+ () => args.frozen ? `${_chunkZVY5S6JSjs.formatFilename.call(void 0, filename)}: ${_chunkZVY5S6JSjs.formatRuleId.call(void 0, ruleId)}: ${_chunkZVY5S6JSjs.SEATBELT_FROZEN}: didn't update max errors ${maxErrorCount} -> ${errorCount}` : `${_chunkZVY5S6JSjs.formatFilename.call(void 0, filename)}: ${_chunkZVY5S6JSjs.formatRuleId.call(void 0, ruleId)}: update max errors ${maxErrorCount} -> ${errorCount}` -+ ); -+ maxErrors.set(ruleId, errorCount); -+ if (errorCount > maxErrorCount) { -+ increasedRulesCount++; -+ } else { -+ decreasedRulesCount++; -+ } -+ } -+ }); -+ if (args.verbose || args.keepRules !== "all") { -+ maxErrors.forEach((maxErrorCount, ruleId) => { -+ const shouldRemove = maxErrorCount === 0 || !ruleToErrorCount.has(ruleId); -+ if (!shouldRemove) { -+ return; -+ } -+ if (_chunkZVY5S6JSjs.SeatbeltArgs.ruleSetHas(args.keepRules, ruleId)) { -+ _chunkZVY5S6JSjs.SeatbeltArgs.verboseLog( -+ args, -+ () => `${_chunkZVY5S6JSjs.formatFilename.call(void 0, filename)}: ${_chunkZVY5S6JSjs.formatRuleId.call(void 0, ruleId)}: ${_chunkZVY5S6JSjs.SEATBELT_KEEP}: didn't update max errors ${maxErrorCount} -> ${0}` -+ ); -+ return; -+ } -+ _chunkZVY5S6JSjs.SeatbeltArgs.verboseLog( -+ args, -+ () => args.frozen ? `${_chunkZVY5S6JSjs.formatFilename.call(void 0, filename)}: ${_chunkZVY5S6JSjs.formatRuleId.call(void 0, ruleId)}: ${_chunkZVY5S6JSjs.SEATBELT_FROZEN}: didn't update max errors ${maxErrorCount} -> ${0}` : `${_chunkZVY5S6JSjs.formatFilename.call(void 0, filename)}: ${_chunkZVY5S6JSjs.formatRuleId.call(void 0, ruleId)}: update max errors ${maxErrorCount} -> ${0}` -+ ); -+ maxErrors.delete(ruleId); -+ removedRules.add(ruleId); -+ }); -+ } -+ const changed = increasedRulesCount > 0 || decreasedRulesCount > 0 || removedRules.size > 0; -+ if (changed && !args.frozen) { -+ const file = this.data.get(relativeFilename); -+ if (file) { -+ file.maxErrors = maxErrors; -+ } else { -+ this.data.set(relativeFilename, { -+ maxErrors, -+ lines: [] -+ }); -+ } -+ this.changed = true; -+ } -+ return { removedRules, increasedRulesCount, decreasedRulesCount }; -+ } -+ /** Atomic read -> apply delta -> write. Takes an exclusive file lock when `args.threadsafe`. */ -+ updateFileMaxErrors(args, filename, ruleToErrorCount) { -+ return this.withOptionalLock(args, () => { -+ const before = this.getMaxErrors(filename); -+ const ruleToMaxErrorCountBefore = before ? new Map(before) : void 0; -+ const result = this.updateMaxErrors(filename, args, ruleToErrorCount); -+ if (!args.frozen) { -+ this.flushChanges(); -+ } -+ return { ruleToMaxErrorCountBefore, ...result }; -+ }); -+ } -+ /** Drop entries whose source file no longer exists. Takes an exclusive file lock when `args.threadsafe`. */ -+ cleanUpRemovedFiles(args) { -+ return this.withOptionalLock(args, () => { -+ let removedFiles = 0; -+ for (const filename of Array.from(this.filenames())) { -+ if (!fs.existsSync(filename)) { -+ if (this.removeFile(filename, args)) { -+ removedFiles++; -+ } -+ } -+ } -+ if (!args.frozen) { -+ this.flushChanges(); -+ } -+ return { removedFiles }; -+ }); -+ } -+ withOptionalLock(args, fn) { -+ if (!args.threadsafe) { -+ return fn(); -+ } -+ const lock = new FileLock(`${this.filename}.lock`); -+ lock.waitLock(LOCK_TIMEOUT_MS); -+ try { -+ this.readSync(); -+ return fn(); -+ } finally { -+ lock.unlock(); -+ } -+ } -+ toDataString() { -+ const lines = []; -+ this.data.forEach((fileState, filename) => { -+ if (fileState.maxErrors) { -+ fileState.lines = []; -+ fileState.maxErrors.forEach((maxErrorCount, ruleId) => { -+ fileState.lines.push({ filename, ruleId, maxErrors: maxErrorCount }); -+ }); -+ fileState.lines.sort( -+ (a, b) => a.ruleId === b.ruleId ? 0 : a.ruleId < b.ruleId ? -1 : 1 -+ ); -+ } -+ fileState.lines.forEach((line) => { -+ const encoded = line.encoded ??= encodeLine(line); -+ lines.push(encoded); -+ }); -+ }); -+ lines.sort(); -+ if (this.comments) { -+ return this.comments + "\n\n" + lines.join(""); -+ } else { -+ return lines.join(""); -+ } -+ } -+ readSync() { -+ const nextStateFile = _SeatbeltFile.openSync(this.filename); -+ if (nextStateFile) { -+ this.data = nextStateFile.data; -+ this.changed = false; -+ return true; -+ } -+ return false; -+ } -+ flushChanges() { -+ if (this.changed) { -+ this.writeSync(); -+ this.changed = false; -+ return { updated: true }; -+ } -+ return { updated: false }; -+ } -+ writeSync() { -+ const dataString = this.toDataString(); -+ const dir = nodePath.dirname(this.filename); -+ const base = nodePath.basename(this.filename); -+ const tempFile = nodePath.join( -+ this.useTempDirForWrites ? os.tmpdir() : dir, -+ `.${base}.wip${process.pid}.${Date.now()}.tmp` -+ ); -+ fs.mkdirSync(dir, { recursive: true }); -+ fs.writeFileSync(tempFile, dataString, "utf8"); -+ try { -+ fs.renameSync(tempFile, this.filename); -+ } catch (error) { -+ if (isErrno(error, "EXDEV")) { -+ this.useTempDirForWrites = false; -+ fs.copyFileSync(tempFile, this.filename); -+ fs.rmSync(tempFile); -+ return; -+ } -+ throw error; -+ } -+ } -+ toJSON() { -+ const data = Object.fromEntries( -+ Array.from(this.data.keys()).map((filename) => { -+ const maxErrors = this.getMaxErrors(filename); -+ if (!maxErrors) { -+ throw new Error(`${_chunkZVY5S6JSjs.name} bug: expected errors for existing key`); -+ } -+ return [filename, Object.fromEntries(maxErrors)]; -+ }) -+ ); -+ return { filename: this.filename, data }; -+ } -+ toRelativePath(filename) { -+ if (!nodePath.isAbsolute(filename)) { -+ return filename; -+ } -+ return nodePath.relative(this.dirname, filename); -+ } -+ toAbsolutePath(filename) { -+ if (nodePath.isAbsolute(filename)) { -+ return filename; -+ } -+ return nodePath.resolve(this.dirname, filename); -+ } -+}, _class); -+function parseMaxErrors(lines) { -+ const maxErrors = /* @__PURE__ */ new Map(); -+ lines.forEach((line) => { -+ maxErrors.set(line.ruleId, line.maxErrors); -+ }); -+ return maxErrors; -+} -+ -+ -+ -+ -+ -+exports.appendErrorContext = appendErrorContext; exports.FileLock = FileLock; exports.SeatbeltFile = SeatbeltFile; -+//# sourceMappingURL=chunk-NTFTCWX7.js.map -\ No newline at end of file -diff --git a/node_modules/eslint-seatbelt/dist/chunk-OKIIDZIF.mjs b/node_modules/eslint-seatbelt/dist/chunk-OKIIDZIF.mjs -deleted file mode 100644 -index 4e478ef..0000000 ---- a/node_modules/eslint-seatbelt/dist/chunk-OKIIDZIF.mjs -+++ /dev/null -@@ -1,329 +0,0 @@ --import { -- SEATBELT_FROZEN, -- SEATBELT_KEEP, -- SeatbeltArgs, -- formatFilename, -- formatRuleId, -- name --} from "./chunk-7ZO4DCZA.mjs"; -- --// src/SeatbeltFile.ts --import * as os from "node:os"; --import * as fs from "node:fs"; --import path, * as nodePath from "node:path"; -- --// src/errorHanding.ts --function appendErrorContext(error, context) { -- if (error instanceof Error) { -- error.message += ` -- ${context}`; -- } --} --function isErrno(error, code) { -- return error instanceof Error && "code" in error && error.code === code; --} -- --// src/SeatbeltFile.ts --function encodeLine(line) { -- const { filename, ruleId, maxErrors } = line; -- return `${JSON.stringify(filename)} ${JSON.stringify(ruleId)} ${maxErrors} --`; --} --function decodeLine(line, index) { -- try { -- const lineParts = line.split(" "); -- if (lineParts.length !== 3) { -- throw new Error( -- `Expected 3 tab-separated JSON strings, instead have ${lineParts.length}` -- ); -- } -- let filename; -- try { -- filename = JSON.parse(lineParts[0]); -- } catch (e) { -- appendErrorContext(e, "at tab-separated column 1 (filename)"); -- throw e; -- } -- let ruleId; -- try { -- ruleId = JSON.parse(lineParts[1]); -- } catch (e) { -- appendErrorContext(e, "at tab-separated column 2 (RuleId)"); -- throw e; -- } -- let maxErrors; -- try { -- maxErrors = JSON.parse(lineParts[2]); -- } catch (e) { -- appendErrorContext(e, "at tab-separated column 3 (maxErrors)"); -- throw e; -- } -- return { -- encoded: line, -- filename, -- ruleId, -- maxErrors -- }; -- } catch (e) { -- appendErrorContext(e, `at line ${index + 1}: \`${line.trim()}\``); -- throw e; -- } --} --var COMMENT_LINE_REGEX = /^\s*#/; --var NON_EMPTY_LINE_REGEX = /\S+/; --var DEFAULT_FILE_HEADER = ` --# ${name} temporarily allowed errors --# docs: https://github.com/justjake/${name}#readme --`.trim(); --var SeatbeltFile = class _SeatbeltFile { -- constructor(filename, data, comments = "") { -- this.filename = filename; -- this.data = data; -- this.comments = comments; -- this.filename = path.resolve(this.filename); -- this.dirname = path.dirname(this.filename); -- } -- static readSync(filename) { -- const text = fs.readFileSync(filename, "utf8"); -- try { -- return _SeatbeltFile.parse(filename, text); -- } catch (e) { -- appendErrorContext(e, `in seatbelt file \`${filename}\``); -- throw e; -- } -- } -- /** -- * Read `filename` if it exists, otherwise create a new empty seatbelt file object -- * that will write to that filename. -- */ -- static openSync(filename) { -- try { -- return _SeatbeltFile.readSync(filename); -- } catch (e) { -- if (isErrno(e, "ENOENT")) { -- return new _SeatbeltFile(filename, /* @__PURE__ */ new Map(), DEFAULT_FILE_HEADER); -- } -- throw e; -- } -- } -- static parse(filename, text) { -- const data = /* @__PURE__ */ new Map(); -- const split = text.split(/(?<=\n)/); -- const lines = split.filter( -- (line) => NON_EMPTY_LINE_REGEX.test(line) && !COMMENT_LINE_REGEX.test(line) -- ).map(decodeLine); -- const comments = split.filter((line) => COMMENT_LINE_REGEX.test(line)).join(""); -- lines.forEach((line) => { -- let fileState = data.get(line.filename); -- if (!fileState) { -- fileState = { maxErrors: void 0, lines: [] }; -- data.set(line.filename, fileState); -- } -- fileState.lines.push(line); -- }); -- return new _SeatbeltFile(filename, data, comments.trim()); -- } -- static fromJSON(json) { -- const data = new Map( -- Object.entries(json.data).map(([filename, maxErrors]) => [ -- filename, -- { maxErrors: new Map(Object.entries(maxErrors)), lines: [] } -- ]) -- ); -- return new _SeatbeltFile(json.filename, data); -- } -- changed = false; -- dirname; -- useTempDirForWrites = true; -- *filenames() { -- for (const filename of this.data.keys()) { -- yield this.toAbsolutePath(filename); -- } -- } -- getMaxErrors(filename) { -- const fileState = this.data.get(this.toRelativePath(filename)); -- if (!fileState) { -- return void 0; -- } -- fileState.maxErrors ??= parseMaxErrors(fileState.lines); -- return fileState.maxErrors; -- } -- removeFile(filename, args) { -- const relativeFilename = this.toRelativePath(filename); -- if (!this.data.has(relativeFilename)) { -- return false; -- } -- SeatbeltArgs.verboseLog( -- args, -- () => args.frozen ? `${formatFilename(filename)}: ${SEATBELT_FROZEN}: didn't remove max errors` : `${formatFilename(filename)}: remove max errors` -- ); -- if (args.frozen) { -- return false; -- } -- this.data.delete(relativeFilename); -- this.changed = true; -- return true; -- } -- updateMaxErrors(filename, args, ruleToErrorCount) { -- const removedRules = /* @__PURE__ */ new Set(); -- let increasedRulesCount = 0; -- let decreasedRulesCount = 0; -- this.getMaxErrors(filename); -- const relativeFilename = this.toRelativePath(filename); -- const maxErrors = this.data.get(relativeFilename)?.maxErrors ?? /* @__PURE__ */ new Map(); -- ruleToErrorCount.forEach((errorCount, ruleId) => { -- const maxErrorCount = maxErrors.get(ruleId) ?? 0; -- if (errorCount === maxErrorCount) { -- return; -- } -- if (errorCount < maxErrorCount || SeatbeltArgs.ruleSetHas(args.allowIncreaseRules, ruleId)) { -- SeatbeltArgs.verboseLog( -- args, -- () => args.frozen ? `${formatFilename(filename)}: ${formatRuleId(ruleId)}: ${SEATBELT_FROZEN}: didn't update max errors ${maxErrorCount} -> ${errorCount}` : `${formatFilename(filename)}: ${formatRuleId(ruleId)}: update max errors ${maxErrorCount} -> ${errorCount}` -- ); -- maxErrors.set(ruleId, errorCount); -- if (errorCount > maxErrorCount) { -- increasedRulesCount++; -- } else { -- decreasedRulesCount++; -- } -- } -- }); -- if (args.verbose || args.keepRules !== "all") { -- maxErrors.forEach((maxErrorCount, ruleId) => { -- const shouldRemove = maxErrorCount === 0 || !ruleToErrorCount.has(ruleId); -- if (!shouldRemove) { -- return; -- } -- if (SeatbeltArgs.ruleSetHas(args.keepRules, ruleId)) { -- SeatbeltArgs.verboseLog( -- args, -- () => `${formatFilename(filename)}: ${formatRuleId(ruleId)}: ${SEATBELT_KEEP}: didn't update max errors ${maxErrorCount} -> ${0}` -- ); -- return; -- } -- SeatbeltArgs.verboseLog( -- args, -- () => args.frozen ? `${formatFilename(filename)}: ${formatRuleId(ruleId)}: ${SEATBELT_FROZEN}: didn't update max errors ${maxErrorCount} -> ${0}` : `${formatFilename(filename)}: ${formatRuleId(ruleId)}: update max errors ${maxErrorCount} -> ${0}` -- ); -- maxErrors.delete(ruleId); -- removedRules.add(ruleId); -- }); -- } -- const changed = increasedRulesCount > 0 || decreasedRulesCount > 0 || removedRules.size > 0; -- if (changed && !args.frozen) { -- const file = this.data.get(relativeFilename); -- if (file) { -- file.maxErrors = maxErrors; -- } else { -- this.data.set(relativeFilename, { -- maxErrors, -- lines: [] -- }); -- } -- this.changed = true; -- } -- return { removedRules, increasedRulesCount, decreasedRulesCount }; -- } -- toDataString() { -- const lines = []; -- this.data.forEach((fileState, filename) => { -- if (fileState.maxErrors) { -- fileState.lines = []; -- fileState.maxErrors.forEach((maxErrorCount, ruleId) => { -- fileState.lines.push({ filename, ruleId, maxErrors: maxErrorCount }); -- }); -- fileState.lines.sort( -- (a, b) => a.ruleId === b.ruleId ? 0 : a.ruleId < b.ruleId ? -1 : 1 -- ); -- } -- fileState.lines.forEach((line) => { -- const encoded = line.encoded ??= encodeLine(line); -- lines.push(encoded); -- }); -- }); -- lines.sort(); -- if (this.comments) { -- return this.comments + "\n\n" + lines.join(""); -- } else { -- return lines.join(""); -- } -- } -- readSync() { -- const nextStateFile = _SeatbeltFile.openSync(this.filename); -- if (nextStateFile) { -- this.data = nextStateFile.data; -- this.changed = false; -- return true; -- } -- return false; -- } -- flushChanges() { -- if (this.changed) { -- this.writeSync(); -- this.changed = false; -- return { updated: true }; -- } -- return { updated: false }; -- } -- writeSync() { -- const dataString = this.toDataString(); -- const dir = nodePath.dirname(this.filename); -- const base = nodePath.basename(this.filename); -- const tempFile = nodePath.join( -- this.useTempDirForWrites ? os.tmpdir() : dir, -- `.${base}.wip${process.pid}.${Date.now()}.tmp` -- ); -- fs.mkdirSync(dir, { recursive: true }); -- fs.writeFileSync(tempFile, dataString, "utf8"); -- try { -- fs.renameSync(tempFile, this.filename); -- } catch (error) { -- if (isErrno(error, "EXDEV")) { -- this.useTempDirForWrites = false; -- fs.copyFileSync(tempFile, this.filename); -- fs.rmSync(tempFile); -- return; -- } -- throw error; -- } -- } -- toJSON() { -- const data = Object.fromEntries( -- Array.from(this.data.keys()).map((filename) => { -- const maxErrors = this.getMaxErrors(filename); -- if (!maxErrors) { -- throw new Error(`${name} bug: expected errors for existing key`); -- } -- return [filename, Object.fromEntries(maxErrors)]; -- }) -- ); -- return { filename: this.filename, data }; -- } -- toRelativePath(filename) { -- if (!nodePath.isAbsolute(filename)) { -- return filename; -- } -- return nodePath.relative(this.dirname, filename); -- } -- toAbsolutePath(filename) { -- if (nodePath.isAbsolute(filename)) { -- return filename; -- } -- return nodePath.resolve(this.dirname, filename); -- } --}; --function parseMaxErrors(lines) { -- const maxErrors = /* @__PURE__ */ new Map(); -- lines.forEach((line) => { -- maxErrors.set(line.ruleId, line.maxErrors); -- }); -- return maxErrors; --} -- --export { -- appendErrorContext, -- isErrno, -- SeatbeltFile --}; --//# sourceMappingURL=chunk-OKIIDZIF.mjs.map -\ No newline at end of file -diff --git a/node_modules/eslint-seatbelt/dist/chunk-TVRMUM3F.js b/node_modules/eslint-seatbelt/dist/chunk-TVRMUM3F.js -deleted file mode 100644 -index f3a3c0b..0000000 ---- a/node_modules/eslint-seatbelt/dist/chunk-TVRMUM3F.js -+++ /dev/null -@@ -1,427 +0,0 @@ --"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { -- get: (a, b) => (typeof require !== "undefined" ? require : a)[b] --}) : x)(function(x) { -- if (typeof require !== "undefined") return require.apply(this, arguments); -- throw Error('Dynamic require of "' + x + '" is not supported'); --}); -- --// package.json --var name = "eslint-seatbelt"; --var version = "0.1.3"; --var package_default = { -- name, -- version, -- description: "Gradually tighten ESLint rules in your codebase", -- keywords: [ -- "eslint", -- "incremental", -- "gradual", -- "workflow", -- "processor", -- "linting" -- ], -- author: { -- name: "Jake Teton-Landis", -- url: "https://jake.tl" -- }, -- repository: { -- type: "git", -- url: "git+https://github.com/justjake/eslint-seatbelt.git" -- }, -- bugs: { -- url: "https://github.com/justjake/eslint-seatbelt/issues" -- }, -- scripts: { -- build: "./scripts/make-json-schemas.ts && tsc && tsup", -- test: "node --test --require tsx/cjs $(find src -name '*.test.ts')", -- lint: "pnpm build && NODE_OPTIONS='--enable-source-maps' eslint ." -- }, -- types: "dist/index.d.ts", -- import: "./dist/index.mjs", -- main: "dist/index.js", -- exports: { -- ".": { -- types: "./dist/index.d.ts", -- import: "./dist/index.mjs", -- default: "./dist/index.js" -- }, -- "./api": { -- types: "./dist/api.d.ts", -- import: "./dist/api.mjs", -- default: "./dist/api.js" -- } -- }, -- bin: { -- "eslint-seatbelt": "./dist/command.js" -- }, -- files: [ -- "!*.tsbuildinfo", -- "!src/**/*.test.ts", -- "src", -- "dist" -- ], -- license: "MIT", -- peerDependencies: { -- "@types/eslint": "*", -- eslint: "*" -- }, -- peerDependenciesMeta: { -- eslint: { -- optional: true -- }, -- "@types/eslint": { -- optional: true -- } -- }, -- devDependencies: { -- "@eslint/compat": "1.2.3", -- "@eslint/js": "9.15.0", -- "@types/eslint__js": "8.42.3", -- "@types/node": "22.9.0", -- "@typescript-eslint/rule-tester": "8.14.0", -- "@typescript-eslint/utils": "8.14.0", -- eslint: "9.14.0", -- prettier: "3.3.3", -- tsup: "8.3.5", -- tsx: "4.19.2", -- typescript: "5.6.3", -- "typescript-eslint": "8.14.0", -- "typescript-json-schema": "0.65.1" -- }, -- dependencies: { -- "ts-command-line-args": "^2.5.1" -- }, -- packageManager: "pnpm@10.2.1+sha1.48adf39a4ab751eda7b73b99447d1f0b6d227e02" --}; -- --// src/SeatbeltConfig.ts --var _path = require('path'); var _path2 = _interopRequireDefault(_path); -- --// src/repoIntegration.ts --var _fs = require('fs'); var _fs2 = _interopRequireDefault(_fs); -- --function findAncestorDirectory(path2, predicate) { -- let lastPath = void 0; -- while (path2 !== lastPath) { -- if (predicate(path2)) { -- return path2; -- } -- lastPath = path2; -- path2 = _path2.default.dirname(path2); -- } --} --function isGitRoot(dir) { -- return _fs2.default.existsSync(_path2.default.join(dir, ".git")); --} --function findRepoRoot(path2) { -- return findAncestorDirectory(path2, isGitRoot); --} -- --// src/SeatbeltConfig.ts --var SEATBELT_FILE_NAME = "eslint.seatbelt.tsv"; --var SEATBELT_FROZEN = "SEATBELT_FROZEN"; --var SEATBELT_INCREASE = "SEATBELT_INCREASE"; --var SEATBELT_KEEP = "SEATBELT_KEEP"; --var SEATBELT_FILE = "SEATBELT_FILE"; --var SEATBELT_PWD = "SEATBELT_PWD"; --var SEATBELT_DISABLE = "SEATBELT_DISABLE"; --var SEATBELT_THREADSAFE = "SEATBELT_THREADSAFE"; --var SEATBELT_VERBOSE = "SEATBELT_VERBOSE"; --var SEATBELT_QUIET = "SEATBELT_QUIET"; --var SEATBELT_ROOT = "SEATBELT_ROOT"; --var ENV_VARS = { -- SEATBELT_FROZEN, -- SEATBELT_INCREASE, -- SEATBELT_KEEP, -- SEATBELT_FILE, -- SEATBELT_PWD, -- SEATBELT_DISABLE, -- SEATBELT_THREADSAFE, -- SEATBELT_VERBOSE, -- SEATBELT_QUIET, -- SEATBELT_ROOT, -- CI: "CI", -- JEST_WORKER_ID: "JEST_WORKER_ID" --}; --var SeatbeltConfig = { -- withEnvOverrides(config, env) { -- return { -- ...SeatbeltConfig.fromFallbackEnv(env), -- ...config, -- ...SeatbeltConfig.fromEnvOverrides(env) -- }; -- }, -- fromFallbackEnv(env, log) { -- const config = {}; -- const isCI = SeatbeltEnv.readBooleanEnvVar(env.CI); -- if (isCI) { -- config.frozen = true; -- _optionalChain([log, 'optionalCall', _ => _(`${padVarName("CI")} config.frozen defaults to`, true)]); -- } -- if (env.JEST_WORKER_ID) { -- config.threadsafe = true; -- _optionalChain([log, 'optionalCall', _2 => _2( -- `${padVarName("JEST_WORKER_ID")} config.threadsafe defaults to`, -- true -- )]); -- } -- return config; -- }, -- fromEnvOverrides(env, log) { -- const config = { -- pwd: env[SEATBELT_PWD] || process.cwd() -- }; -- const verbose = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_VERBOSE]); -- if (verbose !== void 0) { -- config.verbose = verbose; -- _optionalChain([log, 'optionalCall', _3 => _3(`${padVarName(SEATBELT_VERBOSE)} config.verbose =`, verbose)]); -- } -- const seatbeltFile = env[SEATBELT_FILE]; -- if (seatbeltFile) { -- const rootRelative = _path2.default.isAbsolute(seatbeltFile) ? seatbeltFile : _path2.default.join(config.pwd, seatbeltFile); -- config.seatbeltFile = rootRelative; -- _optionalChain([log, 'optionalCall', _4 => _4(`${padVarName(SEATBELT_FILE)} config.seatbeltFile =`, rootRelative)]); -- } -- const disable = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_DISABLE]); -- if (disable !== void 0) { -- config.disable = disable; -- _optionalChain([log, 'optionalCall', _5 => _5(`${padVarName(SEATBELT_DISABLE)} config.disable =`, disable)]); -- } -- const frozen = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_FROZEN]); -- if (frozen !== void 0) { -- config.frozen = frozen; -- _optionalChain([log, 'optionalCall', _6 => _6(`${padVarName(SEATBELT_FROZEN)} config.frozen =`, frozen)]); -- } -- const increase = SeatbeltEnv.parseRuleSetEnvVar(env[SEATBELT_INCREASE]); -- if (increase !== void 0) { -- config.allowIncreaseRules = increase; -- _optionalChain([log, 'optionalCall', _7 => _7( -- `${padVarName(SEATBELT_INCREASE)} config.allowIncreaseRules =`, -- increase -- )]); -- } -- const keep = SeatbeltEnv.parseRuleSetEnvVar(env[SEATBELT_KEEP]); -- if (keep !== void 0) { -- config.keepRules = keep; -- _optionalChain([log, 'optionalCall', _8 => _8(`${padVarName(SEATBELT_KEEP)} config.keepRules =`, keep)]); -- } -- const threadsafe = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_THREADSAFE]); -- if (threadsafe !== void 0) { -- config.threadsafe = threadsafe; -- _optionalChain([log, 'optionalCall', _9 => _9( -- `${padVarName(SEATBELT_THREADSAFE)} config.threadsafe =`, -- threadsafe -- )]); -- } -- const quiet = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_QUIET]); -- if (quiet !== void 0) { -- config.quiet = quiet; -- _optionalChain([log, 'optionalCall', _10 => _10(`${padVarName(SEATBELT_QUIET)} config.quiet =`, quiet)]); -- } -- const root = env[SEATBELT_ROOT]; -- if (root) { -- config.root = root; -- _optionalChain([log, 'optionalCall', _11 => _11(`${padVarName(SEATBELT_ROOT)} config.root =`, root)]); -- } -- return config; -- } --}; --var SeatbeltEnv = { -- parseRuleSetEnvVar(value) { -- if (value === void 0) { -- return void 0; -- } -- if (!value) { -- return []; -- } -- const lower = value.toLowerCase(); -- if (lower === "all" || lower === "1" || lower === "true") { -- return "all"; -- } -- return value.split(/[\s,]+/g).filter(Boolean); -- }, -- readBooleanEnvVar(value) { -- if (value === void 0 || value === "") { -- return void 0; -- } -- const lower = value.toLowerCase(); -- if (lower === "false" || lower === "0" || lower === "no") { -- return false; -- } -- return Boolean(value); -- } --}; --var logStdout = (...message) => ( -- // eslint-disable-next-line no-console -- console.log(`[${name}]:`, ...message) --); --var logStderr = (...message) => ( -- // eslint-disable-next-line no-console -- console.error(`[${name}]:`, ...message) --); --var SeatbeltArgs = { -- fromConfig(config) { -- const cwd = _nullishCoalesce(config.pwd, () => ( process.cwd())); -- const seatbeltFile = _nullishCoalesce(config.seatbeltFile, () => ( SeatbeltArgs.findSeatbeltFile(cwd))); -- const root = _nullishCoalesce(_nullishCoalesce(config.root, () => ( findRepoRoot(seatbeltFile))), () => ( _path2.default.dirname(seatbeltFile))); -- return { -- seatbeltFile, -- root, -- keepRules: typeof config.keepRules === "string" ? config.keepRules : new Set(_nullishCoalesce(config.keepRules, () => ( []))), -- allowIncreaseRules: typeof config.allowIncreaseRules === "string" ? config.allowIncreaseRules : new Set(_nullishCoalesce(config.allowIncreaseRules, () => ( []))), -- frozen: _nullishCoalesce(config.frozen, () => ( false)), -- disable: _nullishCoalesce(config.disable, () => ( false)), -- quiet: _nullishCoalesce(config.quiet, () => ( false)), -- threadsafe: _nullishCoalesce(config.threadsafe, () => ( false)), -- verbose: _nullishCoalesce(config.verbose, () => ( false)) -- }; -- }, -- getLogger(args) { -- if (typeof args.verbose === "function") { -- return args.verbose; -- } -- if (args.verbose === "stdout") { -- return logStdout; -- } -- return logStderr; -- }, -- ruleSetHas(ruleSet, ruleId) { -- return ruleSet === "all" || ruleSet.has(ruleId); -- }, -- verboseLog(args, makeMessage) { -- if (args.verbose) { -- const message = makeMessage(); -- const log = SeatbeltArgs.getLogger(args); -- if (typeof message === "string") { -- log(message); -- } else { -- log(...message); -- } -- } -- }, -- findSeatbeltFile(cwd) { -- return `${cwd}/${SEATBELT_FILE_NAME}`; -- } --}; --var envVarMaxLength = 0; --function padVarName(name2) { -- envVarMaxLength ||= Math.max( -- ...Object.values(ENV_VARS).map((name3) => name3.length) -- ); -- return `${name2}:`.padEnd(envVarMaxLength + 1); --} --function formatFilename(filename) { -- const relative = _path2.default.relative( -- _nullishCoalesce(process.env[SEATBELT_PWD], () => ( process.cwd())), -- filename -- ); -- return relative ? relative : filename; --} --function formatRuleId(ruleId) { -- if (ruleId === null) { -- return `unknown rule`; -- } -- return `rule ${ruleId}`; --} -- --// src/jsonSchema/SeatbeltConfigSchema.ts --var SeatbeltConfigSchema = { -- description: 'Configuration for seatbelt can be provided in a few ways:\n\n1. Defined in the shared `settings` object in your ESLint config. This\n requires also configuring the `eslint-seatbelt/configure` rule.\n\n ```js\n // in eslint.config.js\n const config = [\n {\n settings: {\n seatbelt: {\n // ...\n }\n },\n rules: {\n "eslint-seatbelt/configure": "error",\n }\n }\n ]\n ```\n\n2. Using the `eslint-seatbelt/configure` rule in your ESLint config.\n This can be used to override settings for specific files in legacy ESLint configs.\n Any configuration provided here will override the shared `settings` object.\n\n ```js\n // in .eslintrc.js\n module.exports = {\n rules: {\n "eslint-seatbelt/configure": "error",\n },\n overrides: [\n {\n files: ["some/path/*"],\n rules: {\n "eslint-seatbelt/configure": ["error", { seatbeltFile: "some/path/eslint.seatbelt.tsv" }]\n },\n },\n ],\n }\n ```\n3. The settings in config files can be overridden with environment variables when running `eslint` or other tools.\n\n ```bash\n SEATBELT_FILE=some/path/eslint.seatbelt.tsv SEATBELT_FROZEN=1 eslint\n ```', -- type: "object", -- properties: { -- seatbeltFile: { -- description: "The seatbelt file stores the max error counts allowed for each file. Should\nbe an absolute path.\n\nIf not provided, $SEATBELT_PWD/eslint.seatbelt.tsv or $PWD/eslint.seatbelt.tsv will be used.\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n // commonjs\n seatbeltFile: `${__dirname}/eslint.seatbelt.tsv`\n // esm\n seatbeltFile: new URL('./eslint.seatbelt.tsv', import.meta.url).pathname\n }\n }\n }\n]\n```\n\nYou can also set this with environment variable `SEATBELT_FILE`:\n\n```bash\nSEATBELT_FILE=.config/custom-seatbelt-file eslint\n```", -- type: "string" -- }, -- keepRules: { -- description: 'By default whenever a file is linted and a rule has no errors, that rule\'s\nmax errors for the file is set to zero.\n\nHowever with typescript-eslint, it can be helpful to have two ESLint configs:\n\n- A default ESLint config with only syntactic rules enabled that don\'t\n require typechecking, that runs on developer machines and in their editor.\n- A CI-only ESLint config with only type-aware rules enabled that requires\n typechecking. Since these rules require typechecking, they can be too\n slow to run in interactive contexts.\n\nTo avoid this, set `keepRules` to the names of *disabled but known rules*\nwhile linting.\n\nExample:\n\n```js\n// Default ESLint config\nmodule.exports = [\n {\n settings: {\n seatbelt: {\n keepRules: require(\'./eslint-typed.config.js\').flatMap(config => Object.keys(config.rules ?? {})),\n }\n },\n rules: {\n "no-unused-vars": "error",\n },\n }\n]\n\n// Typechecking-required ESLint config for CI\nmodule.exports = [\n {\n settings: {\n seatbelt: {\n keepRules: require(\'./eslint.config.js\').flatMap(config => Object.keys(config.rules ?? {})),\n }\n },\n rules: {\n // Requires typechecking (slow)\n "@typescript-eslint/no-floating-promises": "error",\n },\n }\n]\n```\n\nYou can also set this with environment variable `SEATBELT_KEEP`:\n\n```bash\nSEATBELT_KEEP="@typescript-eslint/no-floating-promises', -- anyOf: [ -- { -- type: "array", -- items: { -- type: "string" -- } -- }, -- { -- const: "all", -- type: "string" -- } -- ] -- }, -- allowIncreaseRules: { -- description: 'When you enable a rule for the first time, lint with it in this set to set\nthe initial max error counts.\n\nTypically this should be enabled for one lint run only via an environment\nvariable, but it can also be configured via ESLint settings.\n\n```bash\nSEATBELT_INCREASE="@typescript-eslint/no-floating-promises" eslint\n```\n\nYou can set this to `"ALL"` to enable this setting for ALL rules:\n\n```bash\nSEATBELT_INCREASE=ALL eslint\n```\n\n```js\n// in eslint.config.js\n// maybe you have a use-case for this\nconst config = [\n {\n settings: {\n seatbelt: {\n allowIncreaseRules: ["@typescript-eslint/no-floating-promises"],\n }\n }\n }\n]\n```', -- anyOf: [ -- { -- type: "array", -- items: { -- type: "string" -- } -- }, -- { -- const: "all", -- type: "string" -- } -- ] -- }, -- frozen: { -- description: "Error if there is any change in the number of errors in the seatbelt file.\nThis is useful in CI to ensures that developers keep the seatbelt file up-to-date as they fix errors.\n\nIt is enabled by default when environment variable `CI` is set.\n\n```bash\nCI=1 eslint\n```\n\nThis can be set with the `SEATBELT_FROZEN` environment variable.\n\n```bash\nSEATBELT_FROZEN=1 eslint\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n frozen: true,\n }\n }\n }\n]\n```", -- type: "boolean" -- }, -- disable: { -- description: "Completely disable seatbelt error processing for a lint run while leaving it otherwise configured.\n\nThis can be set with the `SEATBELT_DISABLE` environment variable.\n\n```bash\nSEATBELT_DISABLE=1 eslint\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n disable: true,\n }\n }\n }\n]\n```", -- type: "boolean" -- }, -- quiet: { -- description: 'Suppress seatbelt\'s informational warning messages (e.g. "tend the garden",\n"thank you for fixing"). When enabled, seatbelt still downgrades errors to\nwarnings and updates the seatbelt file, but the warning messages are not\nemitted as ESLint results. Over-limit errors and frozen-mode warnings are\nalways preserved.\n\nThis is useful when seatbelt warnings create noise in CI logs or editor\nintegrations.\n\nThis can be set with the `SEATBELT_QUIET` environment variable.\n\n```bash\nSEATBELT_QUIET=1 eslint\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n quiet: true,\n }\n }\n }\n]\n```', -- type: "boolean" -- }, -- threadsafe: { -- description: "By default seatbelt assumes that only one ESLint process will read and\nwrite to the seatbelt file at a time.\n\nThis should be set to `true` if you use a parallel ESLint runner similar to\njest-runner-eslint to avoid losing updates during parallel writes to the\nseatbelt file.\n\nWhen enabled, seatbelt creates temporary lock files to serialize updates to\nthe seatbelt file. This comes at a small performance cost.\n\nThis is enabled by default when run with Jest (environment variable `JEST_WORKER_ID` is set).\n\nIt can also be set with environment variable `SEATBELT_THREADSAFE`:\n\n```bash\nSEATBELT_THREADSAFE=1 eslint-parallel\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n threadsafe: true,\n }\n }\n }\n]\n```", -- type: "boolean" -- }, -- verbose: { -- description: "Enable verbose logging.\n\nThis can be set with the `SEATBELT_VERBOSE` environment variable.\n\n```bash\nSEATBELT_VERBOSE=1 eslint\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n verbose: true,\n }\n }\n }\n]\n```\n\nIf set to a function (like `console.error`), that function will be called with the log messages.\nThe default logger when set to `true` is `console.error`.", -- anyOf: [ -- { -- enum: [false, "stderr", "stdout", true] -- }, -- { -- type: "object" -- } -- ] -- }, -- root: { -- description: "Repository or project root.\nBy default this is inferred from `seatbeltFile` by checking ancestor directories for `.git`.\nUsed for editor integration to disable seatbelt during git actions like rebase or merge.\n\nThis can be set with the `SEATBELT_ROOT` environment variable.", -- type: "string" -- } -- }, -- $schema: "http://json-schema.org/draft-07/schema#" --}; -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --exports.__require = __require; exports.name = name; exports.version = version; exports.package_default = package_default; exports.SEATBELT_FILE_NAME = SEATBELT_FILE_NAME; exports.SEATBELT_FROZEN = SEATBELT_FROZEN; exports.SEATBELT_INCREASE = SEATBELT_INCREASE; exports.SEATBELT_KEEP = SEATBELT_KEEP; exports.SEATBELT_FILE = SEATBELT_FILE; exports.SEATBELT_PWD = SEATBELT_PWD; exports.SEATBELT_DISABLE = SEATBELT_DISABLE; exports.SEATBELT_THREADSAFE = SEATBELT_THREADSAFE; exports.SEATBELT_VERBOSE = SEATBELT_VERBOSE; exports.SEATBELT_QUIET = SEATBELT_QUIET; exports.SEATBELT_ROOT = SEATBELT_ROOT; exports.SeatbeltConfig = SeatbeltConfig; exports.SeatbeltEnv = SeatbeltEnv; exports.logStdout = logStdout; exports.logStderr = logStderr; exports.SeatbeltArgs = SeatbeltArgs; exports.padVarName = padVarName; exports.formatFilename = formatFilename; exports.formatRuleId = formatRuleId; exports.SeatbeltConfigSchema = SeatbeltConfigSchema; --//# sourceMappingURL=chunk-TVRMUM3F.js.map -\ No newline at end of file -diff --git a/node_modules/eslint-seatbelt/dist/chunk-ULACHCKT.mjs b/node_modules/eslint-seatbelt/dist/chunk-ULACHCKT.mjs -new file mode 100644 -index 0000000..b90ad17 ---- /dev/null -+++ b/node_modules/eslint-seatbelt/dist/chunk-ULACHCKT.mjs -@@ -0,0 +1,435 @@ -+var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { -+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b] -+}) : x)(function(x) { -+ if (typeof require !== "undefined") return require.apply(this, arguments); -+ throw Error('Dynamic require of "' + x + '" is not supported'); -+}); -+ -+// package.json -+var name = "eslint-seatbelt"; -+var version = "0.1.3"; -+var package_default = { -+ name, -+ version, -+ description: "Gradually tighten ESLint rules in your codebase", -+ keywords: [ -+ "eslint", -+ "incremental", -+ "gradual", -+ "workflow", -+ "processor", -+ "linting" -+ ], -+ author: { -+ name: "Jake Teton-Landis", -+ url: "https://jake.tl" -+ }, -+ repository: { -+ type: "git", -+ url: "git+https://github.com/justjake/eslint-seatbelt.git" -+ }, -+ bugs: { -+ url: "https://github.com/justjake/eslint-seatbelt/issues" -+ }, -+ scripts: { -+ build: "./scripts/make-json-schemas.ts && tsc && tsup", -+ test: "node --test --require tsx/cjs $(find src -name '*.test.ts')", -+ lint: "pnpm build && NODE_OPTIONS='--enable-source-maps' eslint ." -+ }, -+ types: "dist/index.d.ts", -+ import: "./dist/index.mjs", -+ main: "dist/index.js", -+ exports: { -+ ".": { -+ types: "./dist/index.d.ts", -+ import: "./dist/index.mjs", -+ default: "./dist/index.js" -+ }, -+ "./api": { -+ types: "./dist/api.d.ts", -+ import: "./dist/api.mjs", -+ default: "./dist/api.js" -+ } -+ }, -+ bin: { -+ "eslint-seatbelt": "./dist/command.js" -+ }, -+ files: [ -+ "!*.tsbuildinfo", -+ "!src/**/*.test.ts", -+ "src", -+ "dist" -+ ], -+ license: "MIT", -+ peerDependencies: { -+ "@types/eslint": "*", -+ eslint: "*" -+ }, -+ peerDependenciesMeta: { -+ eslint: { -+ optional: true -+ }, -+ "@types/eslint": { -+ optional: true -+ } -+ }, -+ devDependencies: { -+ "@eslint/compat": "1.2.3", -+ "@eslint/js": "9.15.0", -+ "@types/eslint__js": "8.42.3", -+ "@types/node": "22.9.0", -+ "@typescript-eslint/rule-tester": "8.14.0", -+ "@typescript-eslint/utils": "8.14.0", -+ eslint: "9.14.0", -+ prettier: "3.3.3", -+ tsup: "8.3.5", -+ tsx: "4.19.2", -+ typescript: "5.6.3", -+ "typescript-eslint": "8.14.0", -+ "typescript-json-schema": "0.65.1" -+ }, -+ dependencies: { -+ "ts-command-line-args": "^2.5.1" -+ }, -+ packageManager: "pnpm@10.2.1+sha1.48adf39a4ab751eda7b73b99447d1f0b6d227e02" -+}; -+ -+// src/SeatbeltConfig.ts -+import path from "node:path"; -+import { isMainThread } from "node:worker_threads"; -+ -+// src/repoIntegration.ts -+import fs from "node:fs"; -+import nodePath from "node:path"; -+function findAncestorDirectory(path2, predicate) { -+ let lastPath = void 0; -+ while (path2 !== lastPath) { -+ if (predicate(path2)) { -+ return path2; -+ } -+ lastPath = path2; -+ path2 = nodePath.dirname(path2); -+ } -+} -+function isGitRoot(dir) { -+ return fs.existsSync(nodePath.join(dir, ".git")); -+} -+function findRepoRoot(path2) { -+ return findAncestorDirectory(path2, isGitRoot); -+} -+ -+// src/SeatbeltConfig.ts -+var SEATBELT_FILE_NAME = "eslint.seatbelt.tsv"; -+var SEATBELT_FROZEN = "SEATBELT_FROZEN"; -+var SEATBELT_INCREASE = "SEATBELT_INCREASE"; -+var SEATBELT_KEEP = "SEATBELT_KEEP"; -+var SEATBELT_FILE = "SEATBELT_FILE"; -+var SEATBELT_PWD = "SEATBELT_PWD"; -+var SEATBELT_DISABLE = "SEATBELT_DISABLE"; -+var SEATBELT_THREADSAFE = "SEATBELT_THREADSAFE"; -+var SEATBELT_VERBOSE = "SEATBELT_VERBOSE"; -+var SEATBELT_QUIET = "SEATBELT_QUIET"; -+var SEATBELT_ROOT = "SEATBELT_ROOT"; -+var ENV_VARS = { -+ SEATBELT_FROZEN, -+ SEATBELT_INCREASE, -+ SEATBELT_KEEP, -+ SEATBELT_FILE, -+ SEATBELT_PWD, -+ SEATBELT_DISABLE, -+ SEATBELT_THREADSAFE, -+ SEATBELT_VERBOSE, -+ SEATBELT_QUIET, -+ SEATBELT_ROOT, -+ CI: "CI", -+ JEST_WORKER_ID: "JEST_WORKER_ID" -+}; -+var SeatbeltConfig = { -+ withEnvOverrides(config, env) { -+ return { -+ ...SeatbeltConfig.fromFallbackEnv(env), -+ ...config, -+ ...SeatbeltConfig.fromEnvOverrides(env) -+ }; -+ }, -+ fromFallbackEnv(env, log) { -+ const config = {}; -+ const isCI = SeatbeltEnv.readBooleanEnvVar(env.CI); -+ if (isCI) { -+ config.frozen = true; -+ log?.(`${padVarName("CI")} config.frozen defaults to`, true); -+ } -+ if (env.JEST_WORKER_ID) { -+ config.threadsafe = true; -+ log?.( -+ `${padVarName("JEST_WORKER_ID")} config.threadsafe defaults to`, -+ true -+ ); -+ } -+ if (!isMainThread) { -+ config.threadsafe = true; -+ log?.( -+ `${padVarName("worker_threads")} config.threadsafe defaults to`, -+ true -+ ); -+ } -+ return config; -+ }, -+ fromEnvOverrides(env, log) { -+ const config = { -+ pwd: env[SEATBELT_PWD] || process.cwd() -+ }; -+ const verbose = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_VERBOSE]); -+ if (verbose !== void 0) { -+ config.verbose = verbose; -+ log?.(`${padVarName(SEATBELT_VERBOSE)} config.verbose =`, verbose); -+ } -+ const seatbeltFile = env[SEATBELT_FILE]; -+ if (seatbeltFile) { -+ const rootRelative = path.isAbsolute(seatbeltFile) ? seatbeltFile : path.join(config.pwd, seatbeltFile); -+ config.seatbeltFile = rootRelative; -+ log?.(`${padVarName(SEATBELT_FILE)} config.seatbeltFile =`, rootRelative); -+ } -+ const disable = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_DISABLE]); -+ if (disable !== void 0) { -+ config.disable = disable; -+ log?.(`${padVarName(SEATBELT_DISABLE)} config.disable =`, disable); -+ } -+ const frozen = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_FROZEN]); -+ if (frozen !== void 0) { -+ config.frozen = frozen; -+ log?.(`${padVarName(SEATBELT_FROZEN)} config.frozen =`, frozen); -+ } -+ const increase = SeatbeltEnv.parseRuleSetEnvVar(env[SEATBELT_INCREASE]); -+ if (increase !== void 0) { -+ config.allowIncreaseRules = increase; -+ log?.( -+ `${padVarName(SEATBELT_INCREASE)} config.allowIncreaseRules =`, -+ increase -+ ); -+ } -+ const keep = SeatbeltEnv.parseRuleSetEnvVar(env[SEATBELT_KEEP]); -+ if (keep !== void 0) { -+ config.keepRules = keep; -+ log?.(`${padVarName(SEATBELT_KEEP)} config.keepRules =`, keep); -+ } -+ const threadsafe = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_THREADSAFE]); -+ if (threadsafe !== void 0) { -+ config.threadsafe = threadsafe; -+ log?.( -+ `${padVarName(SEATBELT_THREADSAFE)} config.threadsafe =`, -+ threadsafe -+ ); -+ } -+ const quiet = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_QUIET]); -+ if (quiet !== void 0) { -+ config.quiet = quiet; -+ log?.(`${padVarName(SEATBELT_QUIET)} config.quiet =`, quiet); -+ } -+ const root = env[SEATBELT_ROOT]; -+ if (root) { -+ config.root = root; -+ log?.(`${padVarName(SEATBELT_ROOT)} config.root =`, root); -+ } -+ return config; -+ } -+}; -+var SeatbeltEnv = { -+ parseRuleSetEnvVar(value) { -+ if (value === void 0) { -+ return void 0; -+ } -+ if (!value) { -+ return []; -+ } -+ const lower = value.toLowerCase(); -+ if (lower === "all" || lower === "1" || lower === "true") { -+ return "all"; -+ } -+ return value.split(/[\s,]+/g).filter(Boolean); -+ }, -+ readBooleanEnvVar(value) { -+ if (value === void 0 || value === "") { -+ return void 0; -+ } -+ const lower = value.toLowerCase(); -+ if (lower === "false" || lower === "0" || lower === "no") { -+ return false; -+ } -+ return Boolean(value); -+ } -+}; -+var logStdout = (...message) => ( -+ // eslint-disable-next-line no-console -+ console.log(`[${name}]:`, ...message) -+); -+var logStderr = (...message) => ( -+ // eslint-disable-next-line no-console -+ console.error(`[${name}]:`, ...message) -+); -+var SeatbeltArgs = { -+ fromConfig(config) { -+ const cwd = config.pwd ?? process.cwd(); -+ const seatbeltFile = config.seatbeltFile ?? SeatbeltArgs.findSeatbeltFile(cwd); -+ const root = config.root ?? findRepoRoot(seatbeltFile) ?? path.dirname(seatbeltFile); -+ return { -+ seatbeltFile, -+ root, -+ keepRules: typeof config.keepRules === "string" ? config.keepRules : new Set(config.keepRules ?? []), -+ allowIncreaseRules: typeof config.allowIncreaseRules === "string" ? config.allowIncreaseRules : new Set(config.allowIncreaseRules ?? []), -+ frozen: config.frozen ?? false, -+ disable: config.disable ?? false, -+ quiet: config.quiet ?? false, -+ threadsafe: config.threadsafe ?? false, -+ verbose: config.verbose ?? false -+ }; -+ }, -+ getLogger(args) { -+ if (typeof args.verbose === "function") { -+ return args.verbose; -+ } -+ if (args.verbose === "stdout") { -+ return logStdout; -+ } -+ return logStderr; -+ }, -+ ruleSetHas(ruleSet, ruleId) { -+ return ruleSet === "all" || ruleSet.has(ruleId); -+ }, -+ verboseLog(args, makeMessage) { -+ if (args.verbose) { -+ const message = makeMessage(); -+ const log = SeatbeltArgs.getLogger(args); -+ if (typeof message === "string") { -+ log(message); -+ } else { -+ log(...message); -+ } -+ } -+ }, -+ findSeatbeltFile(cwd) { -+ return `${cwd}/${SEATBELT_FILE_NAME}`; -+ } -+}; -+var envVarMaxLength = 0; -+function padVarName(name2) { -+ envVarMaxLength ||= Math.max( -+ ...Object.values(ENV_VARS).map((name3) => name3.length) -+ ); -+ return `${name2}:`.padEnd(envVarMaxLength + 1); -+} -+function formatFilename(filename) { -+ const relative = path.relative( -+ process.env[SEATBELT_PWD] ?? process.cwd(), -+ filename -+ ); -+ return relative ? relative : filename; -+} -+function formatRuleId(ruleId) { -+ if (ruleId === null) { -+ return `unknown rule`; -+ } -+ return `rule ${ruleId}`; -+} -+ -+// src/jsonSchema/SeatbeltConfigSchema.ts -+var SeatbeltConfigSchema = { -+ description: 'Configuration for seatbelt can be provided in a few ways:\n\n1. Defined in the shared `settings` object in your ESLint config. This\n requires also configuring the `eslint-seatbelt/configure` rule.\n\n ```js\n // in eslint.config.js\n const config = [\n {\n settings: {\n seatbelt: {\n // ...\n }\n },\n rules: {\n "eslint-seatbelt/configure": "error",\n }\n }\n ]\n ```\n\n2. Using the `eslint-seatbelt/configure` rule in your ESLint config.\n This can be used to override settings for specific files in legacy ESLint configs.\n Any configuration provided here will override the shared `settings` object.\n\n ```js\n // in .eslintrc.js\n module.exports = {\n rules: {\n "eslint-seatbelt/configure": "error",\n },\n overrides: [\n {\n files: ["some/path/*"],\n rules: {\n "eslint-seatbelt/configure": ["error", { seatbeltFile: "some/path/eslint.seatbelt.tsv" }]\n },\n },\n ],\n }\n ```\n3. The settings in config files can be overridden with environment variables when running `eslint` or other tools.\n\n ```bash\n SEATBELT_FILE=some/path/eslint.seatbelt.tsv SEATBELT_FROZEN=1 eslint\n ```', -+ type: "object", -+ properties: { -+ seatbeltFile: { -+ description: "The seatbelt file stores the max error counts allowed for each file. Should\nbe an absolute path.\n\nIf not provided, $SEATBELT_PWD/eslint.seatbelt.tsv or $PWD/eslint.seatbelt.tsv will be used.\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n // commonjs\n seatbeltFile: `${__dirname}/eslint.seatbelt.tsv`\n // esm\n seatbeltFile: new URL('./eslint.seatbelt.tsv', import.meta.url).pathname\n }\n }\n }\n]\n```\n\nYou can also set this with environment variable `SEATBELT_FILE`:\n\n```bash\nSEATBELT_FILE=.config/custom-seatbelt-file eslint\n```", -+ type: "string" -+ }, -+ keepRules: { -+ description: 'By default whenever a file is linted and a rule has no errors, that rule\'s\nmax errors for the file is set to zero.\n\nHowever with typescript-eslint, it can be helpful to have two ESLint configs:\n\n- A default ESLint config with only syntactic rules enabled that don\'t\n require typechecking, that runs on developer machines and in their editor.\n- A CI-only ESLint config with only type-aware rules enabled that requires\n typechecking. Since these rules require typechecking, they can be too\n slow to run in interactive contexts.\n\nTo avoid this, set `keepRules` to the names of *disabled but known rules*\nwhile linting.\n\nExample:\n\n```js\n// Default ESLint config\nmodule.exports = [\n {\n settings: {\n seatbelt: {\n keepRules: require(\'./eslint-typed.config.js\').flatMap(config => Object.keys(config.rules ?? {})),\n }\n },\n rules: {\n "no-unused-vars": "error",\n },\n }\n]\n\n// Typechecking-required ESLint config for CI\nmodule.exports = [\n {\n settings: {\n seatbelt: {\n keepRules: require(\'./eslint.config.js\').flatMap(config => Object.keys(config.rules ?? {})),\n }\n },\n rules: {\n // Requires typechecking (slow)\n "@typescript-eslint/no-floating-promises": "error",\n },\n }\n]\n```\n\nYou can also set this with environment variable `SEATBELT_KEEP`:\n\n```bash\nSEATBELT_KEEP="@typescript-eslint/no-floating-promises', -+ anyOf: [ -+ { -+ type: "array", -+ items: { -+ type: "string" -+ } -+ }, -+ { -+ const: "all", -+ type: "string" -+ } -+ ] -+ }, -+ allowIncreaseRules: { -+ description: 'When you enable a rule for the first time, lint with it in this set to set\nthe initial max error counts.\n\nTypically this should be enabled for one lint run only via an environment\nvariable, but it can also be configured via ESLint settings.\n\n```bash\nSEATBELT_INCREASE="@typescript-eslint/no-floating-promises" eslint\n```\n\nYou can set this to `"ALL"` to enable this setting for ALL rules:\n\n```bash\nSEATBELT_INCREASE=ALL eslint\n```\n\n```js\n// in eslint.config.js\n// maybe you have a use-case for this\nconst config = [\n {\n settings: {\n seatbelt: {\n allowIncreaseRules: ["@typescript-eslint/no-floating-promises"],\n }\n }\n }\n]\n```', -+ anyOf: [ -+ { -+ type: "array", -+ items: { -+ type: "string" -+ } -+ }, -+ { -+ const: "all", -+ type: "string" -+ } -+ ] -+ }, -+ frozen: { -+ description: "Error if there is any change in the number of errors in the seatbelt file.\nThis is useful in CI to ensures that developers keep the seatbelt file up-to-date as they fix errors.\n\nIt is enabled by default when environment variable `CI` is set.\n\n```bash\nCI=1 eslint\n```\n\nThis can be set with the `SEATBELT_FROZEN` environment variable.\n\n```bash\nSEATBELT_FROZEN=1 eslint\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n frozen: true,\n }\n }\n }\n]\n```", -+ type: "boolean" -+ }, -+ disable: { -+ description: "Completely disable seatbelt error processing for a lint run while leaving it otherwise configured.\n\nThis can be set with the `SEATBELT_DISABLE` environment variable.\n\n```bash\nSEATBELT_DISABLE=1 eslint\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n disable: true,\n }\n }\n }\n]\n```", -+ type: "boolean" -+ }, -+ quiet: { -+ description: 'Suppress seatbelt\'s informational warning messages (e.g. "tend the garden",\n"thank you for fixing"). When enabled, seatbelt still downgrades errors to\nwarnings and updates the seatbelt file, but the warning messages are not\nemitted as ESLint results. Over-limit errors and frozen-mode warnings are\nalways preserved.\n\nThis is useful when seatbelt warnings create noise in CI logs or editor\nintegrations.\n\nThis can be set with the `SEATBELT_QUIET` environment variable.\n\n```bash\nSEATBELT_QUIET=1 eslint\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n quiet: true,\n }\n }\n }\n]\n```', -+ type: "boolean" -+ }, -+ threadsafe: { -+ description: "By default seatbelt assumes that only one ESLint process will read and\nwrite to the seatbelt file at a time.\n\nThis should be set to `true` if you use a parallel ESLint runner similar to\njest-runner-eslint to avoid losing updates during parallel writes to the\nseatbelt file.\n\nWhen enabled, seatbelt creates temporary lock files to serialize updates to\nthe seatbelt file. This comes at a small performance cost.\n\nThis is enabled by default when run with Jest (environment variable `JEST_WORKER_ID` is set)\nor inside a Node `worker_threads` worker (e.g. ESLint `--concurrency`).\n\nIt can also be set with environment variable `SEATBELT_THREADSAFE`:\n\n```bash\nSEATBELT_THREADSAFE=1 eslint-parallel\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n threadsafe: true,\n }\n }\n }\n]\n```", -+ type: "boolean" -+ }, -+ verbose: { -+ description: "Enable verbose logging.\n\nThis can be set with the `SEATBELT_VERBOSE` environment variable.\n\n```bash\nSEATBELT_VERBOSE=1 eslint\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n verbose: true,\n }\n }\n }\n]\n```\n\nIf set to a function (like `console.error`), that function will be called with the log messages.\nThe default logger when set to `true` is `console.error`.", -+ anyOf: [ -+ { -+ enum: [false, "stderr", "stdout", true] -+ }, -+ { -+ type: "object" -+ } -+ ] -+ }, -+ root: { -+ description: "Repository or project root.\nBy default this is inferred from `seatbeltFile` by checking ancestor directories for `.git`.\nUsed for editor integration to disable seatbelt during git actions like rebase or merge.\n\nThis can be set with the `SEATBELT_ROOT` environment variable.", -+ type: "string" -+ } -+ }, -+ $schema: "http://json-schema.org/draft-07/schema#" -+}; -+ -+export { -+ __require, -+ name, -+ version, -+ package_default, -+ SEATBELT_FILE_NAME, -+ SEATBELT_FROZEN, -+ SEATBELT_INCREASE, -+ SEATBELT_KEEP, -+ SEATBELT_FILE, -+ SEATBELT_PWD, -+ SEATBELT_DISABLE, -+ SEATBELT_THREADSAFE, -+ SEATBELT_VERBOSE, -+ SEATBELT_QUIET, -+ SEATBELT_ROOT, -+ SeatbeltConfig, -+ SeatbeltEnv, -+ logStdout, -+ logStderr, -+ SeatbeltArgs, -+ padVarName, -+ formatFilename, -+ formatRuleId, -+ SeatbeltConfigSchema -+}; -+//# sourceMappingURL=chunk-ULACHCKT.mjs.map -\ No newline at end of file -diff --git a/node_modules/eslint-seatbelt/dist/chunk-ZVY5S6JS.js b/node_modules/eslint-seatbelt/dist/chunk-ZVY5S6JS.js -new file mode 100644 -index 0000000..ee27681 ---- /dev/null -+++ b/node_modules/eslint-seatbelt/dist/chunk-ZVY5S6JS.js -@@ -0,0 +1,435 @@ -+"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { -+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b] -+}) : x)(function(x) { -+ if (typeof require !== "undefined") return require.apply(this, arguments); -+ throw Error('Dynamic require of "' + x + '" is not supported'); -+}); -+ -+// package.json -+var name = "eslint-seatbelt"; -+var version = "0.1.3"; -+var package_default = { -+ name, -+ version, -+ description: "Gradually tighten ESLint rules in your codebase", -+ keywords: [ -+ "eslint", -+ "incremental", -+ "gradual", -+ "workflow", -+ "processor", -+ "linting" -+ ], -+ author: { -+ name: "Jake Teton-Landis", -+ url: "https://jake.tl" -+ }, -+ repository: { -+ type: "git", -+ url: "git+https://github.com/justjake/eslint-seatbelt.git" -+ }, -+ bugs: { -+ url: "https://github.com/justjake/eslint-seatbelt/issues" -+ }, -+ scripts: { -+ build: "./scripts/make-json-schemas.ts && tsc && tsup", -+ test: "node --test --require tsx/cjs $(find src -name '*.test.ts')", -+ lint: "pnpm build && NODE_OPTIONS='--enable-source-maps' eslint ." -+ }, -+ types: "dist/index.d.ts", -+ import: "./dist/index.mjs", -+ main: "dist/index.js", -+ exports: { -+ ".": { -+ types: "./dist/index.d.ts", -+ import: "./dist/index.mjs", -+ default: "./dist/index.js" -+ }, -+ "./api": { -+ types: "./dist/api.d.ts", -+ import: "./dist/api.mjs", -+ default: "./dist/api.js" -+ } -+ }, -+ bin: { -+ "eslint-seatbelt": "./dist/command.js" -+ }, -+ files: [ -+ "!*.tsbuildinfo", -+ "!src/**/*.test.ts", -+ "src", -+ "dist" -+ ], -+ license: "MIT", -+ peerDependencies: { -+ "@types/eslint": "*", -+ eslint: "*" -+ }, -+ peerDependenciesMeta: { -+ eslint: { -+ optional: true -+ }, -+ "@types/eslint": { -+ optional: true -+ } -+ }, -+ devDependencies: { -+ "@eslint/compat": "1.2.3", -+ "@eslint/js": "9.15.0", -+ "@types/eslint__js": "8.42.3", -+ "@types/node": "22.9.0", -+ "@typescript-eslint/rule-tester": "8.14.0", -+ "@typescript-eslint/utils": "8.14.0", -+ eslint: "9.14.0", -+ prettier: "3.3.3", -+ tsup: "8.3.5", -+ tsx: "4.19.2", -+ typescript: "5.6.3", -+ "typescript-eslint": "8.14.0", -+ "typescript-json-schema": "0.65.1" -+ }, -+ dependencies: { -+ "ts-command-line-args": "^2.5.1" -+ }, -+ packageManager: "pnpm@10.2.1+sha1.48adf39a4ab751eda7b73b99447d1f0b6d227e02" -+}; -+ -+// src/SeatbeltConfig.ts -+var _path = require('path'); var _path2 = _interopRequireDefault(_path); -+var _worker_threads = require('worker_threads'); -+ -+// src/repoIntegration.ts -+var _fs = require('fs'); var _fs2 = _interopRequireDefault(_fs); -+ -+function findAncestorDirectory(path2, predicate) { -+ let lastPath = void 0; -+ while (path2 !== lastPath) { -+ if (predicate(path2)) { -+ return path2; -+ } -+ lastPath = path2; -+ path2 = _path2.default.dirname(path2); -+ } -+} -+function isGitRoot(dir) { -+ return _fs2.default.existsSync(_path2.default.join(dir, ".git")); -+} -+function findRepoRoot(path2) { -+ return findAncestorDirectory(path2, isGitRoot); -+} -+ -+// src/SeatbeltConfig.ts -+var SEATBELT_FILE_NAME = "eslint.seatbelt.tsv"; -+var SEATBELT_FROZEN = "SEATBELT_FROZEN"; -+var SEATBELT_INCREASE = "SEATBELT_INCREASE"; -+var SEATBELT_KEEP = "SEATBELT_KEEP"; -+var SEATBELT_FILE = "SEATBELT_FILE"; -+var SEATBELT_PWD = "SEATBELT_PWD"; -+var SEATBELT_DISABLE = "SEATBELT_DISABLE"; -+var SEATBELT_THREADSAFE = "SEATBELT_THREADSAFE"; -+var SEATBELT_VERBOSE = "SEATBELT_VERBOSE"; -+var SEATBELT_QUIET = "SEATBELT_QUIET"; -+var SEATBELT_ROOT = "SEATBELT_ROOT"; -+var ENV_VARS = { -+ SEATBELT_FROZEN, -+ SEATBELT_INCREASE, -+ SEATBELT_KEEP, -+ SEATBELT_FILE, -+ SEATBELT_PWD, -+ SEATBELT_DISABLE, -+ SEATBELT_THREADSAFE, -+ SEATBELT_VERBOSE, -+ SEATBELT_QUIET, -+ SEATBELT_ROOT, -+ CI: "CI", -+ JEST_WORKER_ID: "JEST_WORKER_ID" -+}; -+var SeatbeltConfig = { -+ withEnvOverrides(config, env) { -+ return { -+ ...SeatbeltConfig.fromFallbackEnv(env), -+ ...config, -+ ...SeatbeltConfig.fromEnvOverrides(env) -+ }; -+ }, -+ fromFallbackEnv(env, log) { -+ const config = {}; -+ const isCI = SeatbeltEnv.readBooleanEnvVar(env.CI); -+ if (isCI) { -+ config.frozen = true; -+ _optionalChain([log, 'optionalCall', _ => _(`${padVarName("CI")} config.frozen defaults to`, true)]); -+ } -+ if (env.JEST_WORKER_ID) { -+ config.threadsafe = true; -+ _optionalChain([log, 'optionalCall', _2 => _2( -+ `${padVarName("JEST_WORKER_ID")} config.threadsafe defaults to`, -+ true -+ )]); -+ } -+ if (!_worker_threads.isMainThread) { -+ config.threadsafe = true; -+ _optionalChain([log, 'optionalCall', _3 => _3( -+ `${padVarName("worker_threads")} config.threadsafe defaults to`, -+ true -+ )]); -+ } -+ return config; -+ }, -+ fromEnvOverrides(env, log) { -+ const config = { -+ pwd: env[SEATBELT_PWD] || process.cwd() -+ }; -+ const verbose = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_VERBOSE]); -+ if (verbose !== void 0) { -+ config.verbose = verbose; -+ _optionalChain([log, 'optionalCall', _4 => _4(`${padVarName(SEATBELT_VERBOSE)} config.verbose =`, verbose)]); -+ } -+ const seatbeltFile = env[SEATBELT_FILE]; -+ if (seatbeltFile) { -+ const rootRelative = _path2.default.isAbsolute(seatbeltFile) ? seatbeltFile : _path2.default.join(config.pwd, seatbeltFile); -+ config.seatbeltFile = rootRelative; -+ _optionalChain([log, 'optionalCall', _5 => _5(`${padVarName(SEATBELT_FILE)} config.seatbeltFile =`, rootRelative)]); -+ } -+ const disable = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_DISABLE]); -+ if (disable !== void 0) { -+ config.disable = disable; -+ _optionalChain([log, 'optionalCall', _6 => _6(`${padVarName(SEATBELT_DISABLE)} config.disable =`, disable)]); -+ } -+ const frozen = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_FROZEN]); -+ if (frozen !== void 0) { -+ config.frozen = frozen; -+ _optionalChain([log, 'optionalCall', _7 => _7(`${padVarName(SEATBELT_FROZEN)} config.frozen =`, frozen)]); -+ } -+ const increase = SeatbeltEnv.parseRuleSetEnvVar(env[SEATBELT_INCREASE]); -+ if (increase !== void 0) { -+ config.allowIncreaseRules = increase; -+ _optionalChain([log, 'optionalCall', _8 => _8( -+ `${padVarName(SEATBELT_INCREASE)} config.allowIncreaseRules =`, -+ increase -+ )]); -+ } -+ const keep = SeatbeltEnv.parseRuleSetEnvVar(env[SEATBELT_KEEP]); -+ if (keep !== void 0) { -+ config.keepRules = keep; -+ _optionalChain([log, 'optionalCall', _9 => _9(`${padVarName(SEATBELT_KEEP)} config.keepRules =`, keep)]); -+ } -+ const threadsafe = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_THREADSAFE]); -+ if (threadsafe !== void 0) { -+ config.threadsafe = threadsafe; -+ _optionalChain([log, 'optionalCall', _10 => _10( -+ `${padVarName(SEATBELT_THREADSAFE)} config.threadsafe =`, -+ threadsafe -+ )]); -+ } -+ const quiet = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_QUIET]); -+ if (quiet !== void 0) { -+ config.quiet = quiet; -+ _optionalChain([log, 'optionalCall', _11 => _11(`${padVarName(SEATBELT_QUIET)} config.quiet =`, quiet)]); -+ } -+ const root = env[SEATBELT_ROOT]; -+ if (root) { -+ config.root = root; -+ _optionalChain([log, 'optionalCall', _12 => _12(`${padVarName(SEATBELT_ROOT)} config.root =`, root)]); -+ } -+ return config; -+ } -+}; -+var SeatbeltEnv = { -+ parseRuleSetEnvVar(value) { -+ if (value === void 0) { -+ return void 0; -+ } -+ if (!value) { -+ return []; -+ } -+ const lower = value.toLowerCase(); -+ if (lower === "all" || lower === "1" || lower === "true") { -+ return "all"; -+ } -+ return value.split(/[\s,]+/g).filter(Boolean); -+ }, -+ readBooleanEnvVar(value) { -+ if (value === void 0 || value === "") { -+ return void 0; -+ } -+ const lower = value.toLowerCase(); -+ if (lower === "false" || lower === "0" || lower === "no") { -+ return false; -+ } -+ return Boolean(value); -+ } -+}; -+var logStdout = (...message) => ( -+ // eslint-disable-next-line no-console -+ console.log(`[${name}]:`, ...message) -+); -+var logStderr = (...message) => ( -+ // eslint-disable-next-line no-console -+ console.error(`[${name}]:`, ...message) -+); -+var SeatbeltArgs = { -+ fromConfig(config) { -+ const cwd = _nullishCoalesce(config.pwd, () => ( process.cwd())); -+ const seatbeltFile = _nullishCoalesce(config.seatbeltFile, () => ( SeatbeltArgs.findSeatbeltFile(cwd))); -+ const root = _nullishCoalesce(_nullishCoalesce(config.root, () => ( findRepoRoot(seatbeltFile))), () => ( _path2.default.dirname(seatbeltFile))); -+ return { -+ seatbeltFile, -+ root, -+ keepRules: typeof config.keepRules === "string" ? config.keepRules : new Set(_nullishCoalesce(config.keepRules, () => ( []))), -+ allowIncreaseRules: typeof config.allowIncreaseRules === "string" ? config.allowIncreaseRules : new Set(_nullishCoalesce(config.allowIncreaseRules, () => ( []))), -+ frozen: _nullishCoalesce(config.frozen, () => ( false)), -+ disable: _nullishCoalesce(config.disable, () => ( false)), -+ quiet: _nullishCoalesce(config.quiet, () => ( false)), -+ threadsafe: _nullishCoalesce(config.threadsafe, () => ( false)), -+ verbose: _nullishCoalesce(config.verbose, () => ( false)) -+ }; -+ }, -+ getLogger(args) { -+ if (typeof args.verbose === "function") { -+ return args.verbose; -+ } -+ if (args.verbose === "stdout") { -+ return logStdout; -+ } -+ return logStderr; -+ }, -+ ruleSetHas(ruleSet, ruleId) { -+ return ruleSet === "all" || ruleSet.has(ruleId); -+ }, -+ verboseLog(args, makeMessage) { -+ if (args.verbose) { -+ const message = makeMessage(); -+ const log = SeatbeltArgs.getLogger(args); -+ if (typeof message === "string") { -+ log(message); -+ } else { -+ log(...message); -+ } -+ } -+ }, -+ findSeatbeltFile(cwd) { -+ return `${cwd}/${SEATBELT_FILE_NAME}`; -+ } -+}; -+var envVarMaxLength = 0; -+function padVarName(name2) { -+ envVarMaxLength ||= Math.max( -+ ...Object.values(ENV_VARS).map((name3) => name3.length) -+ ); -+ return `${name2}:`.padEnd(envVarMaxLength + 1); -+} -+function formatFilename(filename) { -+ const relative = _path2.default.relative( -+ _nullishCoalesce(process.env[SEATBELT_PWD], () => ( process.cwd())), -+ filename -+ ); -+ return relative ? relative : filename; -+} -+function formatRuleId(ruleId) { -+ if (ruleId === null) { -+ return `unknown rule`; -+ } -+ return `rule ${ruleId}`; -+} -+ -+// src/jsonSchema/SeatbeltConfigSchema.ts -+var SeatbeltConfigSchema = { -+ description: 'Configuration for seatbelt can be provided in a few ways:\n\n1. Defined in the shared `settings` object in your ESLint config. This\n requires also configuring the `eslint-seatbelt/configure` rule.\n\n ```js\n // in eslint.config.js\n const config = [\n {\n settings: {\n seatbelt: {\n // ...\n }\n },\n rules: {\n "eslint-seatbelt/configure": "error",\n }\n }\n ]\n ```\n\n2. Using the `eslint-seatbelt/configure` rule in your ESLint config.\n This can be used to override settings for specific files in legacy ESLint configs.\n Any configuration provided here will override the shared `settings` object.\n\n ```js\n // in .eslintrc.js\n module.exports = {\n rules: {\n "eslint-seatbelt/configure": "error",\n },\n overrides: [\n {\n files: ["some/path/*"],\n rules: {\n "eslint-seatbelt/configure": ["error", { seatbeltFile: "some/path/eslint.seatbelt.tsv" }]\n },\n },\n ],\n }\n ```\n3. The settings in config files can be overridden with environment variables when running `eslint` or other tools.\n\n ```bash\n SEATBELT_FILE=some/path/eslint.seatbelt.tsv SEATBELT_FROZEN=1 eslint\n ```', -+ type: "object", -+ properties: { -+ seatbeltFile: { -+ description: "The seatbelt file stores the max error counts allowed for each file. Should\nbe an absolute path.\n\nIf not provided, $SEATBELT_PWD/eslint.seatbelt.tsv or $PWD/eslint.seatbelt.tsv will be used.\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n // commonjs\n seatbeltFile: `${__dirname}/eslint.seatbelt.tsv`\n // esm\n seatbeltFile: new URL('./eslint.seatbelt.tsv', import.meta.url).pathname\n }\n }\n }\n]\n```\n\nYou can also set this with environment variable `SEATBELT_FILE`:\n\n```bash\nSEATBELT_FILE=.config/custom-seatbelt-file eslint\n```", -+ type: "string" -+ }, -+ keepRules: { -+ description: 'By default whenever a file is linted and a rule has no errors, that rule\'s\nmax errors for the file is set to zero.\n\nHowever with typescript-eslint, it can be helpful to have two ESLint configs:\n\n- A default ESLint config with only syntactic rules enabled that don\'t\n require typechecking, that runs on developer machines and in their editor.\n- A CI-only ESLint config with only type-aware rules enabled that requires\n typechecking. Since these rules require typechecking, they can be too\n slow to run in interactive contexts.\n\nTo avoid this, set `keepRules` to the names of *disabled but known rules*\nwhile linting.\n\nExample:\n\n```js\n// Default ESLint config\nmodule.exports = [\n {\n settings: {\n seatbelt: {\n keepRules: require(\'./eslint-typed.config.js\').flatMap(config => Object.keys(config.rules ?? {})),\n }\n },\n rules: {\n "no-unused-vars": "error",\n },\n }\n]\n\n// Typechecking-required ESLint config for CI\nmodule.exports = [\n {\n settings: {\n seatbelt: {\n keepRules: require(\'./eslint.config.js\').flatMap(config => Object.keys(config.rules ?? {})),\n }\n },\n rules: {\n // Requires typechecking (slow)\n "@typescript-eslint/no-floating-promises": "error",\n },\n }\n]\n```\n\nYou can also set this with environment variable `SEATBELT_KEEP`:\n\n```bash\nSEATBELT_KEEP="@typescript-eslint/no-floating-promises', -+ anyOf: [ -+ { -+ type: "array", -+ items: { -+ type: "string" -+ } -+ }, -+ { -+ const: "all", -+ type: "string" -+ } -+ ] -+ }, -+ allowIncreaseRules: { -+ description: 'When you enable a rule for the first time, lint with it in this set to set\nthe initial max error counts.\n\nTypically this should be enabled for one lint run only via an environment\nvariable, but it can also be configured via ESLint settings.\n\n```bash\nSEATBELT_INCREASE="@typescript-eslint/no-floating-promises" eslint\n```\n\nYou can set this to `"ALL"` to enable this setting for ALL rules:\n\n```bash\nSEATBELT_INCREASE=ALL eslint\n```\n\n```js\n// in eslint.config.js\n// maybe you have a use-case for this\nconst config = [\n {\n settings: {\n seatbelt: {\n allowIncreaseRules: ["@typescript-eslint/no-floating-promises"],\n }\n }\n }\n]\n```', -+ anyOf: [ -+ { -+ type: "array", -+ items: { -+ type: "string" -+ } -+ }, -+ { -+ const: "all", -+ type: "string" -+ } -+ ] -+ }, -+ frozen: { -+ description: "Error if there is any change in the number of errors in the seatbelt file.\nThis is useful in CI to ensures that developers keep the seatbelt file up-to-date as they fix errors.\n\nIt is enabled by default when environment variable `CI` is set.\n\n```bash\nCI=1 eslint\n```\n\nThis can be set with the `SEATBELT_FROZEN` environment variable.\n\n```bash\nSEATBELT_FROZEN=1 eslint\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n frozen: true,\n }\n }\n }\n]\n```", -+ type: "boolean" -+ }, -+ disable: { -+ description: "Completely disable seatbelt error processing for a lint run while leaving it otherwise configured.\n\nThis can be set with the `SEATBELT_DISABLE` environment variable.\n\n```bash\nSEATBELT_DISABLE=1 eslint\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n disable: true,\n }\n }\n }\n]\n```", -+ type: "boolean" -+ }, -+ quiet: { -+ description: 'Suppress seatbelt\'s informational warning messages (e.g. "tend the garden",\n"thank you for fixing"). When enabled, seatbelt still downgrades errors to\nwarnings and updates the seatbelt file, but the warning messages are not\nemitted as ESLint results. Over-limit errors and frozen-mode warnings are\nalways preserved.\n\nThis is useful when seatbelt warnings create noise in CI logs or editor\nintegrations.\n\nThis can be set with the `SEATBELT_QUIET` environment variable.\n\n```bash\nSEATBELT_QUIET=1 eslint\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n quiet: true,\n }\n }\n }\n]\n```', -+ type: "boolean" -+ }, -+ threadsafe: { -+ description: "By default seatbelt assumes that only one ESLint process will read and\nwrite to the seatbelt file at a time.\n\nThis should be set to `true` if you use a parallel ESLint runner similar to\njest-runner-eslint to avoid losing updates during parallel writes to the\nseatbelt file.\n\nWhen enabled, seatbelt creates temporary lock files to serialize updates to\nthe seatbelt file. This comes at a small performance cost.\n\nThis is enabled by default when run with Jest (environment variable `JEST_WORKER_ID` is set)\nor inside a Node `worker_threads` worker (e.g. ESLint `--concurrency`).\n\nIt can also be set with environment variable `SEATBELT_THREADSAFE`:\n\n```bash\nSEATBELT_THREADSAFE=1 eslint-parallel\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n threadsafe: true,\n }\n }\n }\n]\n```", -+ type: "boolean" -+ }, -+ verbose: { -+ description: "Enable verbose logging.\n\nThis can be set with the `SEATBELT_VERBOSE` environment variable.\n\n```bash\nSEATBELT_VERBOSE=1 eslint\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n verbose: true,\n }\n }\n }\n]\n```\n\nIf set to a function (like `console.error`), that function will be called with the log messages.\nThe default logger when set to `true` is `console.error`.", -+ anyOf: [ -+ { -+ enum: [false, "stderr", "stdout", true] -+ }, -+ { -+ type: "object" -+ } -+ ] -+ }, -+ root: { -+ description: "Repository or project root.\nBy default this is inferred from `seatbeltFile` by checking ancestor directories for `.git`.\nUsed for editor integration to disable seatbelt during git actions like rebase or merge.\n\nThis can be set with the `SEATBELT_ROOT` environment variable.", -+ type: "string" -+ } -+ }, -+ $schema: "http://json-schema.org/draft-07/schema#" -+}; -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+exports.__require = __require; exports.name = name; exports.version = version; exports.package_default = package_default; exports.SEATBELT_FILE_NAME = SEATBELT_FILE_NAME; exports.SEATBELT_FROZEN = SEATBELT_FROZEN; exports.SEATBELT_INCREASE = SEATBELT_INCREASE; exports.SEATBELT_KEEP = SEATBELT_KEEP; exports.SEATBELT_FILE = SEATBELT_FILE; exports.SEATBELT_PWD = SEATBELT_PWD; exports.SEATBELT_DISABLE = SEATBELT_DISABLE; exports.SEATBELT_THREADSAFE = SEATBELT_THREADSAFE; exports.SEATBELT_VERBOSE = SEATBELT_VERBOSE; exports.SEATBELT_QUIET = SEATBELT_QUIET; exports.SEATBELT_ROOT = SEATBELT_ROOT; exports.SeatbeltConfig = SeatbeltConfig; exports.SeatbeltEnv = SeatbeltEnv; exports.logStdout = logStdout; exports.logStderr = logStderr; exports.SeatbeltArgs = SeatbeltArgs; exports.padVarName = padVarName; exports.formatFilename = formatFilename; exports.formatRuleId = formatRuleId; exports.SeatbeltConfigSchema = SeatbeltConfigSchema; -+//# sourceMappingURL=chunk-ZVY5S6JS.js.map -\ No newline at end of file -diff --git a/node_modules/eslint-seatbelt/dist/command.js b/node_modules/eslint-seatbelt/dist/command.js -index 0a25e80..fa29aa4 100755 ---- a/node_modules/eslint-seatbelt/dist/command.js -+++ b/node_modules/eslint-seatbelt/dist/command.js -@@ -6,14 +6,14 @@ - - - --var _chunkTVRMUM3Fjs = require('./chunk-TVRMUM3F.js'); -+var _chunkZVY5S6JSjs = require('./chunk-ZVY5S6JS.js'); - - // src/command.ts - var _tscommandlineargs = require('ts-command-line-args'); - var ZERO_WIDTH_SPACE = "\u200B"; - function parseArgs() { -- const fallback = _chunkTVRMUM3Fjs.SeatbeltConfig.fromFallbackEnv(process.env); -- const overrides = _chunkTVRMUM3Fjs.SeatbeltConfig.fromEnvOverrides(process.env); -+ const fallback = _chunkZVY5S6JSjs.SeatbeltConfig.fromFallbackEnv(process.env); -+ const overrides = _chunkZVY5S6JSjs.SeatbeltConfig.fromEnvOverrides(process.env); - const env = { ...fallback, ...overrides }; - const escapeForChalk = (s) => s.replaceAll("{", "\\{").replaceAll("}", "\\}").replaceAll(/^(\s)/gm, (match) => `${ZERO_WIDTH_SPACE}${match}`); - return _tscommandlineargs.parse.call(void 0, -@@ -28,7 +28,7 @@ function parseArgs() { - type: String, - alias: "f", - description: escapeForChalk( -- _chunkTVRMUM3Fjs.SeatbeltConfigSchema.properties.seatbeltFile.description -+ _chunkZVY5S6JSjs.SeatbeltConfigSchema.properties.seatbeltFile.description - ), - defaultValue: env.seatbeltFile, - optional: true -@@ -36,7 +36,7 @@ function parseArgs() { - keepRules: { - type: String, - description: escapeForChalk( -- _chunkTVRMUM3Fjs.SeatbeltConfigSchema.properties.keepRules.description -+ _chunkZVY5S6JSjs.SeatbeltConfigSchema.properties.keepRules.description - ), - defaultValue: env.keepRules, - multiple: true, -@@ -46,7 +46,7 @@ function parseArgs() { - alias: "r", - type: String, - description: escapeForChalk( -- _chunkTVRMUM3Fjs.SeatbeltConfigSchema.properties.allowIncreaseRules.description -+ _chunkZVY5S6JSjs.SeatbeltConfigSchema.properties.allowIncreaseRules.description - ), - defaultValue: env.allowIncreaseRules, - multiple: true, -@@ -55,7 +55,7 @@ function parseArgs() { - frozen: { - type: Boolean, - description: escapeForChalk( -- _chunkTVRMUM3Fjs.SeatbeltConfigSchema.properties.frozen.description -+ _chunkZVY5S6JSjs.SeatbeltConfigSchema.properties.frozen.description - ), - defaultValue: env.frozen, - optional: true -@@ -63,7 +63,7 @@ function parseArgs() { - disable: { - type: Boolean, - description: escapeForChalk( -- _chunkTVRMUM3Fjs.SeatbeltConfigSchema.properties.disable.description -+ _chunkZVY5S6JSjs.SeatbeltConfigSchema.properties.disable.description - ), - defaultValue: env.disable, - optional: true -@@ -71,7 +71,7 @@ function parseArgs() { - quiet: { - type: Boolean, - description: escapeForChalk( -- _chunkTVRMUM3Fjs.SeatbeltConfigSchema.properties.quiet.description -+ _chunkZVY5S6JSjs.SeatbeltConfigSchema.properties.quiet.description - ), - defaultValue: env.quiet, - optional: true -@@ -79,7 +79,7 @@ function parseArgs() { - threadsafe: { - type: Boolean, - description: escapeForChalk( -- _chunkTVRMUM3Fjs.SeatbeltConfigSchema.properties.threadsafe.description -+ _chunkZVY5S6JSjs.SeatbeltConfigSchema.properties.threadsafe.description - ), - defaultValue: env.threadsafe, - optional: true -@@ -87,7 +87,7 @@ function parseArgs() { - verbose: { - type: Boolean, - description: escapeForChalk( -- _chunkTVRMUM3Fjs.SeatbeltConfigSchema.properties.verbose.description -+ _chunkZVY5S6JSjs.SeatbeltConfigSchema.properties.verbose.description - ), - defaultValue: env.verbose, - optional: true -@@ -95,7 +95,7 @@ function parseArgs() { - root: { - type: String, - description: escapeForChalk( -- _chunkTVRMUM3Fjs.SeatbeltConfigSchema.properties.root.description -+ _chunkZVY5S6JSjs.SeatbeltConfigSchema.properties.root.description - ), - defaultValue: env.root, - optional: true -@@ -125,8 +125,8 @@ function parseArgs() { - helpArg: "help", - headerContentSections: [ - { -- header: _chunkTVRMUM3Fjs.name, -- content: `Turns command-line arguments into ${_chunkTVRMUM3Fjs.name} environment variables, then call 'eslint' or another command with them.` -+ header: _chunkZVY5S6JSjs.name, -+ content: `Turns command-line arguments into ${_chunkZVY5S6JSjs.name} environment variables, then call 'eslint' or another command with them.` - } - ] - } -@@ -137,16 +137,16 @@ var stderr = (...args) => console.error(...args); - function main() { - const argsConfig = parseArgs(); - if (argsConfig.version) { -- stdout(`v${_chunkTVRMUM3Fjs.version}`); -+ stdout(`v${_chunkZVY5S6JSjs.version}`); - return; - } - if (argsConfig.verbose) { - stderr("Parsed config:", argsConfig); - } -- _chunkTVRMUM3Fjs.logStderr.call(void 0, "command not implemented"); -+ _chunkZVY5S6JSjs.logStderr.call(void 0, "command not implemented"); - process.exit(1); - } --if (_chunkTVRMUM3Fjs.__require.main === module) { -+if (_chunkZVY5S6JSjs.__require.main === module) { - main(); - } - //# sourceMappingURL=command.js.map -\ No newline at end of file -diff --git a/node_modules/eslint-seatbelt/dist/command.mjs b/node_modules/eslint-seatbelt/dist/command.mjs -index f2ef8a6..31b8b7b 100755 ---- a/node_modules/eslint-seatbelt/dist/command.mjs -+++ b/node_modules/eslint-seatbelt/dist/command.mjs -@@ -6,7 +6,7 @@ import { - logStderr, - name, - version --} from "./chunk-7ZO4DCZA.mjs"; -+} from "./chunk-ULACHCKT.mjs"; - - // src/command.ts - import { parse } from "ts-command-line-args"; -diff --git a/node_modules/eslint-seatbelt/dist/index.js b/node_modules/eslint-seatbelt/dist/index.js -index ba9e3a0..2d71ee2 100644 ---- a/node_modules/eslint-seatbelt/dist/index.js -+++ b/node_modules/eslint-seatbelt/dist/index.js -@@ -1,7 +1,7 @@ --"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } -+"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } - - --var _chunkK7UHJBLMjs = require('./chunk-K7UHJBLM.js'); -+var _chunkNTFTCWX7js = require('./chunk-NTFTCWX7.js'); - - - -@@ -14,10 +14,9 @@ var _chunkK7UHJBLMjs = require('./chunk-K7UHJBLM.js'); - - - --var _chunkTVRMUM3Fjs = require('./chunk-TVRMUM3F.js'); -+var _chunkZVY5S6JSjs = require('./chunk-ZVY5S6JS.js'); - - // src/pluginGlobals.ts --var _fs = require('fs'); var _fs2 = _interopRequireDefault(_fs); - var ANY_CONFIG_DISABLED = false; - var LAST_VERBOSE_ARGS; - var VERBOSE_SEATBELT_FILES = /* @__PURE__ */ new Set(); -@@ -33,7 +32,7 @@ var lastLintedFile; - var temporaryFileArgs = /* @__PURE__ */ new Map(); - function getProcessEnvFallbackConfig() { - if (!envFallbackConfig) { -- envFallbackConfig = _chunkTVRMUM3Fjs.SeatbeltConfig.fromFallbackEnv( -+ envFallbackConfig = _chunkZVY5S6JSjs.SeatbeltConfig.fromFallbackEnv( - process.env - ); - hasAnyEnvVars = Object.keys(envFallbackConfig).length > 0; -@@ -42,7 +41,7 @@ function getProcessEnvFallbackConfig() { - } - function getProcessEnvOverrideConfig() { - if (!envOverrideConfig) { -- envOverrideConfig = _chunkTVRMUM3Fjs.SeatbeltConfig.fromEnvOverrides( -+ envOverrideConfig = _chunkZVY5S6JSjs.SeatbeltConfig.fromEnvOverrides( - process.env - ); - ANY_CONFIG_DISABLED ||= _nullishCoalesce(envOverrideConfig.disable, () => ( false)); -@@ -74,7 +73,7 @@ function configToArgs(config) { - ...config, - ...getProcessEnvOverrideConfig() - }; -- args = _chunkTVRMUM3Fjs.SeatbeltArgs.fromConfig(compiledConfig); -+ args = _chunkZVY5S6JSjs.SeatbeltArgs.fromConfig(compiledConfig); - ANY_CONFIG_DISABLED ||= args.disable; - if (args.verbose) { - LAST_VERBOSE_ARGS = args; -@@ -87,9 +86,9 @@ function configToArgs(config) { - } - return args; - } --var configureRuleName = `${_chunkTVRMUM3Fjs.name}/configure`; -+var configureRuleName = `${_chunkZVY5S6JSjs.name}/configure`; - function logRuleSetupHint() { -- _chunkTVRMUM3Fjs.logStderr.call(void 0, -+ _chunkZVY5S6JSjs.logStderr.call(void 0, - ` - Make sure you have rule ${configureRuleName} enabled in your ESLint config for all files: - -@@ -98,16 +97,16 @@ Make sure you have rule ${configureRuleName} enabled in your ESLint config for a - "${configureRuleName}": "error", - } - --Docs: https://github.com/justjake/${_chunkTVRMUM3Fjs.name}#setup` -+Docs: https://github.com/justjake/${_chunkZVY5S6JSjs.name}#setup` - ); - } - function logConfig(args, baseConfig) { -- const log = _chunkTVRMUM3Fjs.SeatbeltArgs.getLogger(args); -- _chunkTVRMUM3Fjs.SeatbeltConfig.fromFallbackEnv(process.env, log); -+ const log = _chunkZVY5S6JSjs.SeatbeltArgs.getLogger(args); -+ _chunkZVY5S6JSjs.SeatbeltConfig.fromFallbackEnv(process.env, log); - for (const [key, value] of Object.entries(baseConfig)) { -- log(`${_chunkTVRMUM3Fjs.padVarName.call(void 0, "ESLint settings")} config.${key} =`, value); -+ log(`${_chunkZVY5S6JSjs.padVarName.call(void 0, "ESLint settings")} config.${key} =`, value); - } -- _chunkTVRMUM3Fjs.SeatbeltConfig.fromEnvOverrides(process.env, log); -+ _chunkZVY5S6JSjs.SeatbeltConfig.fromEnvOverrides(process.env, log); - } - function pushFileArgs(filename, args) { - lastLintedFile = { filename, args }; -@@ -124,13 +123,13 @@ function popFileArgs(filename) { - } - if (!hasAnyEnvVars) { - if (lastLintedFile) { -- _chunkTVRMUM3Fjs.logStderr.call(void 0, -+ _chunkZVY5S6JSjs.logStderr.call(void 0, - `WARNING: last configured by file \`${lastLintedFile.filename}\` but linting file \`${filename}\`. - You may have rule ${configureRuleName} enabled for some files, but not this one. - `.trim() - ); - } else { -- _chunkTVRMUM3Fjs.logStderr.call(void 0, -+ _chunkZVY5S6JSjs.logStderr.call(void 0, - `WARNING: rule ${configureRuleName} not enabled in ESLint config and no SEATBELT environment variables set` - ); - } -@@ -141,7 +140,7 @@ You may have rule ${configureRuleName} enabled for some files, but not this one. - function getSeatbeltFile(filename) { - let seatbeltFile = seatbeltFileCache.get(filename); - if (!seatbeltFile) { -- seatbeltFile = _chunkK7UHJBLMjs.SeatbeltFile.openSync(filename); -+ seatbeltFile = _chunkNTFTCWX7js.SeatbeltFile.openSync(filename); - seatbeltFileCache.set(filename, seatbeltFile); - } - return seatbeltFile; -@@ -187,7 +186,7 @@ function isEslintCli() { - } - - // src/SeatbeltProcessor.ts --var { name: name2, version: version2 } = _chunkTVRMUM3Fjs.package_default; -+var { name: name2, version: version2 } = _chunkZVY5S6JSjs.package_default; - var SeatbeltProcessor = { - supportsAutofix: true, - meta: { -@@ -213,7 +212,7 @@ var SeatbeltProcessor = { - return messages; - } - const seatbeltFile = getSeatbeltFile(args.seatbeltFile); -- if (args.threadsafe || !isEslintCli()) { -+ if (!isEslintCli()) { - seatbeltFile.readSync(); - } - const ruleToErrorCount = countRuleIds(messages); -@@ -268,9 +267,9 @@ function transformMessages(args, seatbeltFile, filename, messages, ruleToErrorCo - } - return messages.flatMap((message) => { - if (message.ruleId === null) { -- _chunkTVRMUM3Fjs.SeatbeltArgs.verboseLog( -+ _chunkZVY5S6JSjs.SeatbeltArgs.verboseLog( - args, -- () => `${_chunkTVRMUM3Fjs.formatFilename.call(void 0, filename)}:${message.line}:${message.column}: cannot transform message with null ruleId` -+ () => `${_chunkZVY5S6JSjs.formatFilename.call(void 0, filename)}:${message.line}:${message.column}: cannot transform message with null ruleId` - ); - return message; - } -@@ -284,7 +283,7 @@ function transformMessages(args, seatbeltFile, filename, messages, ruleToErrorCo - ); - } - const maxErrorCount = _nullishCoalesce(_optionalChain([ruleToMaxErrorCount, 'optionalAccess', _ => _.get, 'call', _2 => _2(message.ruleId)]), () => ( 0)); -- const allowIncrease2 = _chunkTVRMUM3Fjs.SeatbeltArgs.ruleSetHas( -+ const allowIncrease2 = _chunkZVY5S6JSjs.SeatbeltArgs.ruleSetHas( - args.allowIncreaseRules, - message.ruleId - ); -@@ -302,17 +301,17 @@ function transformMessages(args, seatbeltFile, filename, messages, ruleToErrorCo - ); - } - if (verboseOnce(message.ruleId)) { -- _chunkTVRMUM3Fjs.SeatbeltArgs.verboseLog( -+ _chunkZVY5S6JSjs.SeatbeltArgs.verboseLog( - args, -- () => `${_chunkTVRMUM3Fjs.formatFilename.call(void 0, filename)}: ${_chunkTVRMUM3Fjs.formatRuleId.call(void 0, message.ruleId)}: error: ${errorCount} ${pluralErrors(errorCount)} found > max ${maxErrorCount}` -+ () => `${_chunkZVY5S6JSjs.formatFilename.call(void 0, filename)}: ${_chunkZVY5S6JSjs.formatRuleId.call(void 0, message.ruleId)}: error: ${errorCount} ${pluralErrors(errorCount)} found > max ${maxErrorCount}` - ); - } - return messageOverMaxErrorCount(message, errorCount, maxErrorCount); - } else if (errorCount === maxErrorCount) { - if (verboseOnce(message.ruleId)) { -- _chunkTVRMUM3Fjs.SeatbeltArgs.verboseLog( -+ _chunkZVY5S6JSjs.SeatbeltArgs.verboseLog( - args, -- () => `${_chunkTVRMUM3Fjs.formatFilename.call(void 0, filename)}: ${_chunkTVRMUM3Fjs.formatRuleId.call(void 0, message.ruleId)}: ok: ${errorCount} ${pluralErrors(errorCount)} found == max ${maxErrorCount}` -+ () => `${_chunkZVY5S6JSjs.formatFilename.call(void 0, filename)}: ${_chunkZVY5S6JSjs.formatRuleId.call(void 0, message.ruleId)}: ok: ${errorCount} ${pluralErrors(errorCount)} found == max ${maxErrorCount}` - ); - } - if (args.quiet) { -@@ -364,20 +363,10 @@ function maybeWriteStateUpdate(args, stateFile, filename, ruleToErrorCount) { - if (args.disable) { - return; - } -- if (args.threadsafe) { -- stateFile.readSync(); -- } -- const ruleToMaxErrorCount = stateFile.getMaxErrors(filename); -- const { removedRules } = stateFile.updateMaxErrors( -- filename, -- args, -- ruleToErrorCount -- ); -- if (!args.frozen) { -- stateFile.flushChanges(); -- } else if (removedRules && removedRules.size > 0) { -+ const { ruleToMaxErrorCountBefore, removedRules } = stateFile.updateFileMaxErrors(args, filename, ruleToErrorCount); -+ if (args.frozen && removedRules.size > 0) { - return Array.from(removedRules).map((ruleId) => { -- const maxErrorCount = _optionalChain([ruleToMaxErrorCount, 'optionalAccess', _3 => _3.get, 'call', _4 => _4(ruleId)]); -+ const maxErrorCount = _optionalChain([ruleToMaxErrorCountBefore, 'optionalAccess', _3 => _3.get, 'call', _4 => _4(ruleId)]); - if (maxErrorCount === void 0) { - throw new Error( - `${name2} bug: maxErrorCount not found for removed frozen rule ${ruleId}` -@@ -413,7 +402,7 @@ function messageOverMaxErrorCountButIncreaseAllowed(message, errorCount, maxErro - ...message, - severity: 1, - message: `${message.message} --[${name2}]: ${_chunkTVRMUM3Fjs.SEATBELT_INCREASE}: Temporarily allowing ${increaseCount} new ${pluralErrors(increaseCount)} of this type. -+[${name2}]: ${_chunkZVY5S6JSjs.SEATBELT_INCREASE}: Temporarily allowing ${increaseCount} new ${pluralErrors(increaseCount)} of this type. - `.trim() - }; - } -@@ -443,7 +432,7 @@ function messageFrozenUnderMaxErrorCountText(seatbeltFilename, errorCount, maxEr - const fixed = errorCount - maxErrorCount; - const fixedMessage = fixed === 1 ? "error" : "errors"; - return ` --[${name2}]: ${_chunkTVRMUM3Fjs.SEATBELT_FROZEN}: Expected ${maxErrorCount} ${pluralErrors(maxErrorCount)}, found ${errorCount}. -+[${name2}]: ${_chunkZVY5S6JSjs.SEATBELT_FROZEN}: Expected ${maxErrorCount} ${pluralErrors(maxErrorCount)}, found ${errorCount}. - If you fixed ${fixed} ${fixedMessage}, thank you, but you'll need to update the seatbelt file to match. - Try running eslint, then committing ${seatbeltFilename}. - `.trim(); -@@ -460,8 +449,8 @@ var alreadyModifiedError = /* @__PURE__ */ new WeakSet(); - function handleProcessingError(filename, e) { - if (e instanceof Error && !alreadyModifiedError.has(e)) { - alreadyModifiedError.add(e); -- _chunkK7UHJBLMjs.appendErrorContext.call(void 0, e, `while processing \`${filename}\``); -- _chunkK7UHJBLMjs.appendErrorContext.call(void 0, -+ _chunkNTFTCWX7js.appendErrorContext.call(void 0, e, `while processing \`${filename}\``); -+ _chunkNTFTCWX7js.appendErrorContext.call(void 0, - e, - `this may be a bug in ${name2}@${version2} or a problem with your setup` - ); -@@ -476,16 +465,16 @@ function pluralErrors(count) { - var configure = { - meta: { - docs: { -- description: `Applies ${_chunkTVRMUM3Fjs.name} configuration from ESLint config`, -- url: `https://github.com/justjake/${_chunkTVRMUM3Fjs.name}` -+ description: `Applies ${_chunkZVY5S6JSjs.name} configuration from ESLint config`, -+ url: `https://github.com/justjake/${_chunkZVY5S6JSjs.name}` - }, -- schema: [_chunkTVRMUM3Fjs.SeatbeltConfigSchema] -+ schema: [_chunkZVY5S6JSjs.SeatbeltConfigSchema] - }, - create(context) { - const filename = _nullishCoalesce(_optionalChain([context, 'access', _5 => _5.getFilename, 'optionalCall', _6 => _6()]), () => ( context.filename)); - onConfigureRule(filename); - const eslintSharedConfigViaShortName = _optionalChain([context, 'access', _7 => _7.settings, 'optionalAccess', _8 => _8.seatbelt]); -- const eslintSharedConfigViaPackageName = _optionalChain([context, 'access', _9 => _9.settings, 'optionalAccess', _10 => _10[_chunkTVRMUM3Fjs.name]]); -+ const eslintSharedConfigViaPackageName = _optionalChain([context, 'access', _9 => _9.settings, 'optionalAccess', _10 => _10[_chunkZVY5S6JSjs.name]]); - const eslintSharedConfig = _nullishCoalesce(eslintSharedConfigViaShortName, () => ( eslintSharedConfigViaPackageName)); - const fileOverrideConfig = context.options[0]; - const args = ruleOverrideConfigToArgs( -@@ -498,7 +487,7 @@ var configure = { - }; - - // src/index.ts --var { name: name3, version: version3 } = _chunkTVRMUM3Fjs.package_default; -+var { name: name3, version: version3 } = _chunkZVY5S6JSjs.package_default; - var plugin = { - meta: { - name: name3, -diff --git a/node_modules/eslint-seatbelt/dist/index.mjs b/node_modules/eslint-seatbelt/dist/index.mjs -index add8f2e..5b23433 100644 ---- a/node_modules/eslint-seatbelt/dist/index.mjs -+++ b/node_modules/eslint-seatbelt/dist/index.mjs -@@ -1,7 +1,7 @@ - import { - SeatbeltFile, - appendErrorContext --} from "./chunk-OKIIDZIF.mjs"; -+} from "./chunk-5FFUIU4M.mjs"; - import { - SEATBELT_FROZEN, - SEATBELT_INCREASE, -@@ -14,7 +14,7 @@ import { - name, - package_default, - padVarName --} from "./chunk-7ZO4DCZA.mjs"; -+} from "./chunk-ULACHCKT.mjs"; - - // src/pluginGlobals.ts - var ANY_CONFIG_DISABLED = false; -@@ -212,7 +212,7 @@ var SeatbeltProcessor = { - return messages; - } - const seatbeltFile = getSeatbeltFile(args.seatbeltFile); -- if (args.threadsafe || !isEslintCli()) { -+ if (!isEslintCli()) { - seatbeltFile.readSync(); - } - const ruleToErrorCount = countRuleIds(messages); -@@ -363,20 +363,10 @@ function maybeWriteStateUpdate(args, stateFile, filename, ruleToErrorCount) { - if (args.disable) { - return; - } -- if (args.threadsafe) { -- stateFile.readSync(); -- } -- const ruleToMaxErrorCount = stateFile.getMaxErrors(filename); -- const { removedRules } = stateFile.updateMaxErrors( -- filename, -- args, -- ruleToErrorCount -- ); -- if (!args.frozen) { -- stateFile.flushChanges(); -- } else if (removedRules && removedRules.size > 0) { -+ const { ruleToMaxErrorCountBefore, removedRules } = stateFile.updateFileMaxErrors(args, filename, ruleToErrorCount); -+ if (args.frozen && removedRules.size > 0) { - return Array.from(removedRules).map((ruleId) => { -- const maxErrorCount = ruleToMaxErrorCount?.get(ruleId); -+ const maxErrorCount = ruleToMaxErrorCountBefore?.get(ruleId); - if (maxErrorCount === void 0) { - throw new Error( - `${name2} bug: maxErrorCount not found for removed frozen rule ${ruleId}` diff --git a/patches/eslint-seatbelt/eslint-seatbelt+0.1.3+002+read-only.patch b/patches/eslint-seatbelt/eslint-seatbelt+0.1.3+002+read-only.patch deleted file mode 100644 index 88f4e70ab1fe..000000000000 --- a/patches/eslint-seatbelt/eslint-seatbelt+0.1.3+002+read-only.patch +++ /dev/null @@ -1,4319 +0,0 @@ -diff --git a/node_modules/eslint-seatbelt/README.md b/node_modules/eslint-seatbelt/README.md -index ddcaa34..598c0d6 100644 ---- a/node_modules/eslint-seatbelt/README.md -+++ b/node_modules/eslint-seatbelt/README.md -@@ -89,6 +89,37 @@ module.exports = { - - 1. `SEATBELT_FROZEN=1 eslint` or `CI=1 eslint` - -+### Keep the worktree clean during local development (auto-ratchet workflow) -+ -+If you don't want developers to hand-commit changes to `eslint.seatbelt.tsv` -+every time they fix a baselined error, use `SEATBELT_READ_ONLY=1` locally and -+let a post-merge job ratchet the baseline on the default branch. -+ -+1. Local / editor runs: `SEATBELT_READ_ONLY=1 eslint` -+ - Counts go down → lint passes, `eslint.seatbelt.tsv` is NOT rewritten. -+ - Counts go up (no `SEATBELT_INCREASE`) → lint fails as normal. -+ - `SEATBELT_INCREASE=` overrides `SEATBELT_READ_ONLY` so the baseline -+ gets persisted when a developer intentionally loosens a rule. -+2. PR CI: `CI=1 eslint` (frozen). Any drift in `eslint.seatbelt.tsv` fails -+ the build; ephemeral writes during the run are discarded with the runner. -+3. On push to default branch: run `eslint` (no flags) in a bot job. If the -+ lint run produces a diff to `eslint.seatbelt.tsv`, commit and push it. -+ -+Example ESLint config that enables read-only mode outside CI: -+ -+```js -+// in eslint.config.js -+export default [ -+ { -+ settings: { -+ seatbelt: { -+ readOnly: !process.env.CI, -+ }, -+ }, -+ }, -+] -+``` -+ - ### Introduce ESLint to an existing codebase - - eslint-seatbelt makes it easy to introduce ESLint to an existing unlinted codebase. -@@ -336,6 +367,41 @@ export interface SeatbeltConfig { - * ] - */ - disable?: boolean -+ /** -+ * When `true`, seatbelt validates error counts (still reporting increases) -+ * but never writes the seatbelt file. Keeps the worktree clean in local / -+ * editor runs; expect an authoritative updater (e.g. post-merge CI) to run -+ * with `readOnly: false`. -+ * -+ * Unlike `frozen`, does not turn decreases into errors. If both are set, -+ * `frozen` messaging is preserved and no write occurs. -+ * -+ * `SEATBELT_INCREASE` overrides this so intentional loosening is persisted. -+ * -+ * Defaults to `false`. -+ * -+ * Set via `SEATBELT_READ_ONLY` env var: -+ * -+ * ```bash -+ * SEATBELT_READ_ONLY=1 eslint -+ * ``` -+ * -+ * Or in ESLint config: -+ * -+ * ```js -+ * // in eslint.config.js -+ * const config = [ -+ * { -+ * settings: { -+ * seatbelt: { -+ * readOnly: !process.env.CI, -+ * } -+ * } -+ * } -+ * ] -+ * ``` -+ */ -+ readOnly?: boolean - /** - * By default seatbelt assumes that only one ESLint process will read and - * write to the seatbelt file at a time. -diff --git a/node_modules/eslint-seatbelt/dist/api.js b/node_modules/eslint-seatbelt/dist/api.js -index afa6177..b416cf8 100644 ---- a/node_modules/eslint-seatbelt/dist/api.js -+++ b/node_modules/eslint-seatbelt/dist/api.js -@@ -1,7 +1,7 @@ - "use strict";Object.defineProperty(exports, "__esModule", {value: true}); - - --var _chunkNTFTCWX7js = require('./chunk-NTFTCWX7.js'); -+var _chunkF4JJMAJLjs = require('./chunk-F4JJMAJL.js'); - - - -@@ -23,8 +23,8 @@ var _chunkNTFTCWX7js = require('./chunk-NTFTCWX7.js'); - - - --var _chunkZVY5S6JSjs = require('./chunk-ZVY5S6JS.js'); - -+var _chunkTDWD7IZMjs = require('./chunk-TDWD7IZM.js'); - - - -@@ -47,5 +47,7 @@ var _chunkZVY5S6JSjs = require('./chunk-ZVY5S6JS.js'); - - - --exports.FileLock = _chunkNTFTCWX7js.FileLock; exports.SEATBELT_DISABLE = _chunkZVY5S6JSjs.SEATBELT_DISABLE; exports.SEATBELT_FILE = _chunkZVY5S6JSjs.SEATBELT_FILE; exports.SEATBELT_FILE_NAME = _chunkZVY5S6JSjs.SEATBELT_FILE_NAME; exports.SEATBELT_FROZEN = _chunkZVY5S6JSjs.SEATBELT_FROZEN; exports.SEATBELT_INCREASE = _chunkZVY5S6JSjs.SEATBELT_INCREASE; exports.SEATBELT_KEEP = _chunkZVY5S6JSjs.SEATBELT_KEEP; exports.SEATBELT_PWD = _chunkZVY5S6JSjs.SEATBELT_PWD; exports.SEATBELT_QUIET = _chunkZVY5S6JSjs.SEATBELT_QUIET; exports.SEATBELT_ROOT = _chunkZVY5S6JSjs.SEATBELT_ROOT; exports.SEATBELT_THREADSAFE = _chunkZVY5S6JSjs.SEATBELT_THREADSAFE; exports.SEATBELT_VERBOSE = _chunkZVY5S6JSjs.SEATBELT_VERBOSE; exports.SeatbeltArgs = _chunkZVY5S6JSjs.SeatbeltArgs; exports.SeatbeltConfig = _chunkZVY5S6JSjs.SeatbeltConfig; exports.SeatbeltConfigSchema = _chunkZVY5S6JSjs.SeatbeltConfigSchema; exports.SeatbeltEnv = _chunkZVY5S6JSjs.SeatbeltEnv; exports.SeatbeltFile = _chunkNTFTCWX7js.SeatbeltFile; exports.formatFilename = _chunkZVY5S6JSjs.formatFilename; exports.formatRuleId = _chunkZVY5S6JSjs.formatRuleId; exports.logStderr = _chunkZVY5S6JSjs.logStderr; exports.logStdout = _chunkZVY5S6JSjs.logStdout; exports.padVarName = _chunkZVY5S6JSjs.padVarName; -+ -+ -+exports.FileLock = _chunkF4JJMAJLjs.FileLock; exports.SEATBELT_DISABLE = _chunkTDWD7IZMjs.SEATBELT_DISABLE; exports.SEATBELT_FILE = _chunkTDWD7IZMjs.SEATBELT_FILE; exports.SEATBELT_FILE_NAME = _chunkTDWD7IZMjs.SEATBELT_FILE_NAME; exports.SEATBELT_FROZEN = _chunkTDWD7IZMjs.SEATBELT_FROZEN; exports.SEATBELT_INCREASE = _chunkTDWD7IZMjs.SEATBELT_INCREASE; exports.SEATBELT_KEEP = _chunkTDWD7IZMjs.SEATBELT_KEEP; exports.SEATBELT_PWD = _chunkTDWD7IZMjs.SEATBELT_PWD; exports.SEATBELT_QUIET = _chunkTDWD7IZMjs.SEATBELT_QUIET; exports.SEATBELT_READ_ONLY = _chunkTDWD7IZMjs.SEATBELT_READ_ONLY; exports.SEATBELT_ROOT = _chunkTDWD7IZMjs.SEATBELT_ROOT; exports.SEATBELT_THREADSAFE = _chunkTDWD7IZMjs.SEATBELT_THREADSAFE; exports.SEATBELT_VERBOSE = _chunkTDWD7IZMjs.SEATBELT_VERBOSE; exports.SeatbeltArgs = _chunkTDWD7IZMjs.SeatbeltArgs; exports.SeatbeltConfig = _chunkTDWD7IZMjs.SeatbeltConfig; exports.SeatbeltConfigSchema = _chunkTDWD7IZMjs.SeatbeltConfigSchema; exports.SeatbeltEnv = _chunkTDWD7IZMjs.SeatbeltEnv; exports.SeatbeltFile = _chunkF4JJMAJLjs.SeatbeltFile; exports.formatFilename = _chunkTDWD7IZMjs.formatFilename; exports.formatRuleId = _chunkTDWD7IZMjs.formatRuleId; exports.logStderr = _chunkTDWD7IZMjs.logStderr; exports.logStdout = _chunkTDWD7IZMjs.logStdout; exports.padVarName = _chunkTDWD7IZMjs.padVarName; - //# sourceMappingURL=api.js.map -\ No newline at end of file -diff --git a/node_modules/eslint-seatbelt/dist/api.mjs b/node_modules/eslint-seatbelt/dist/api.mjs -index efa51d7..61d1606 100644 ---- a/node_modules/eslint-seatbelt/dist/api.mjs -+++ b/node_modules/eslint-seatbelt/dist/api.mjs -@@ -1,7 +1,7 @@ - import { - FileLock, - SeatbeltFile --} from "./chunk-5FFUIU4M.mjs"; -+} from "./chunk-QGLWIAEE.mjs"; - import { - SEATBELT_DISABLE, - SEATBELT_FILE, -@@ -11,6 +11,7 @@ import { - SEATBELT_KEEP, - SEATBELT_PWD, - SEATBELT_QUIET, -+ SEATBELT_READ_ONLY, - SEATBELT_ROOT, - SEATBELT_THREADSAFE, - SEATBELT_VERBOSE, -@@ -23,7 +24,7 @@ import { - logStderr, - logStdout, - padVarName --} from "./chunk-ULACHCKT.mjs"; -+} from "./chunk-U6KIQG2A.mjs"; - export { - FileLock, - SEATBELT_DISABLE, -@@ -34,6 +35,7 @@ export { - SEATBELT_KEEP, - SEATBELT_PWD, - SEATBELT_QUIET, -+ SEATBELT_READ_ONLY, - SEATBELT_ROOT, - SEATBELT_THREADSAFE, - SEATBELT_VERBOSE, -diff --git a/node_modules/eslint-seatbelt/dist/chunk-5FFUIU4M.mjs b/node_modules/eslint-seatbelt/dist/chunk-5FFUIU4M.mjs -deleted file mode 100644 -index 025f420..0000000 ---- a/node_modules/eslint-seatbelt/dist/chunk-5FFUIU4M.mjs -+++ /dev/null -@@ -1,480 +0,0 @@ --import { -- SEATBELT_FROZEN, -- SEATBELT_KEEP, -- SeatbeltArgs, -- formatFilename, -- formatRuleId, -- name --} from "./chunk-ULACHCKT.mjs"; -- --// src/FileLock.ts --import { openSync, writeSync, closeSync, readFileSync, constants, rmSync } from "node:fs"; -- --// src/errorHanding.ts --function appendErrorContext(error, context) { -- if (error instanceof Error) { -- error.message += ` -- ${context}`; -- } --} --function isErrno(error, code) { -- return error instanceof Error && "code" in error && error.code === code; --} -- --// src/FileLock.ts --var { O_CREAT, O_EXCL, O_RDWR } = constants; --var waitBuffer = new Int32Array(new SharedArrayBuffer(4)); --var heldLocks = /* @__PURE__ */ new Set(); --var cleanupHooksInstalled = false; --var SIGNAL_EXIT_CODES = { SIGINT: 130, SIGTERM: 143, SIGHUP: 129 }; --function installCleanupHooks() { -- if (cleanupHooksInstalled) return; -- cleanupHooksInstalled = true; -- const release = () => { -- for (const lock of heldLocks) { -- try { -- lock.unlock(); -- } catch { -- } -- } -- }; -- process.on("exit", release); -- for (const signal of Object.keys(SIGNAL_EXIT_CODES)) { -- process.on(signal, () => { -- release(); -- process.exit(SIGNAL_EXIT_CODES[signal]); -- }); -- } --} --var FileLock = class { -- constructor(filename) { -- this.filename = filename; -- } -- fd; -- tryLock() { -- this.assertNotLocked(); -- try { -- this.fd = openSync(this.filename, O_CREAT | O_EXCL | O_RDWR); -- writeSync(this.fd, `${process.pid} --`); -- heldLocks.add(this); -- installCleanupHooks(); -- return true; -- } catch (e) { -- if (isErrno(e, "EEXIST")) { -- return false; -- } -- throw e; -- } -- } -- waitLock(timeoutMs) { -- const deadline = Date.now() + timeoutMs; -- let attemptedRecovery = false; -- while (!this.tryLock()) { -- if (Date.now() > deadline) { -- if (!attemptedRecovery && this.reclaimIfStale()) { -- attemptedRecovery = true; -- continue; -- } -- throw new Error(`Timed out waiting for lock on ${this.filename}`); -- } -- Atomics.wait(waitBuffer, 0, 0, 1); -- } -- } -- isLocked() { -- return this.fd !== void 0; -- } -- unlock() { -- if (this.fd !== void 0) { -- closeSync(this.fd); -- try { -- rmSync(this.filename); -- } catch (e) { -- if (!isErrno(e, "ENOENT")) throw e; -- } -- this.fd = void 0; -- heldLocks.delete(this); -- } -- } -- assertNotLocked() { -- if (this.fd !== void 0) { -- throw new Error( -- `FileLock "${this.filename}" is already locked by this process [pid ${process.pid}]` -- ); -- } -- } -- reclaimIfStale() { -- let contents; -- try { -- contents = readFileSync(this.filename, "utf8"); -- } catch (e) { -- if (isErrno(e, "ENOENT")) return true; -- throw e; -- } -- const pid = Number.parseInt(contents.trim(), 10); -- if (!Number.isFinite(pid) || pid <= 0) return false; -- try { -- process.kill(pid, 0); -- return false; -- } catch (e) { -- if (!isErrno(e, "ESRCH")) throw e; -- } -- try { -- rmSync(this.filename); -- } catch (e) { -- if (!isErrno(e, "ENOENT")) throw e; -- } -- return true; -- } --}; -- --// src/SeatbeltFile.ts --import * as os from "node:os"; --import * as fs from "node:fs"; --import path, * as nodePath from "node:path"; --var LOCK_TIMEOUT_MS = 3e4; --function encodeLine(line) { -- const { filename, ruleId, maxErrors } = line; -- return `${JSON.stringify(filename)} ${JSON.stringify(ruleId)} ${maxErrors} --`; --} --function decodeLine(line, index) { -- try { -- const lineParts = line.split(" "); -- if (lineParts.length !== 3) { -- throw new Error( -- `Expected 3 tab-separated JSON strings, instead have ${lineParts.length}` -- ); -- } -- let filename; -- try { -- filename = JSON.parse(lineParts[0]); -- } catch (e) { -- appendErrorContext(e, "at tab-separated column 1 (filename)"); -- throw e; -- } -- let ruleId; -- try { -- ruleId = JSON.parse(lineParts[1]); -- } catch (e) { -- appendErrorContext(e, "at tab-separated column 2 (RuleId)"); -- throw e; -- } -- let maxErrors; -- try { -- maxErrors = JSON.parse(lineParts[2]); -- } catch (e) { -- appendErrorContext(e, "at tab-separated column 3 (maxErrors)"); -- throw e; -- } -- return { -- encoded: line, -- filename, -- ruleId, -- maxErrors -- }; -- } catch (e) { -- appendErrorContext(e, `at line ${index + 1}: \`${line.trim()}\``); -- throw e; -- } --} --var COMMENT_LINE_REGEX = /^\s*#/; --var NON_EMPTY_LINE_REGEX = /\S+/; --var DEFAULT_FILE_HEADER = ` --# ${name} temporarily allowed errors --# docs: https://github.com/justjake/${name}#readme --`.trim(); --var SeatbeltFile = class _SeatbeltFile { -- constructor(filename, data, comments = "") { -- this.filename = filename; -- this.data = data; -- this.comments = comments; -- this.filename = path.resolve(this.filename); -- this.dirname = path.dirname(this.filename); -- } -- static readSync(filename) { -- const text = fs.readFileSync(filename, "utf8"); -- try { -- return _SeatbeltFile.parse(filename, text); -- } catch (e) { -- appendErrorContext(e, `in seatbelt file \`${filename}\``); -- throw e; -- } -- } -- /** -- * Read `filename` if it exists, otherwise create a new empty seatbelt file object -- * that will write to that filename. -- */ -- static openSync(filename) { -- try { -- return _SeatbeltFile.readSync(filename); -- } catch (e) { -- if (isErrno(e, "ENOENT")) { -- return new _SeatbeltFile(filename, /* @__PURE__ */ new Map(), DEFAULT_FILE_HEADER); -- } -- throw e; -- } -- } -- static parse(filename, text) { -- const data = /* @__PURE__ */ new Map(); -- const split = text.split(/(?<=\n)/); -- const lines = split.filter( -- (line) => NON_EMPTY_LINE_REGEX.test(line) && !COMMENT_LINE_REGEX.test(line) -- ).map(decodeLine); -- const comments = split.filter((line) => COMMENT_LINE_REGEX.test(line)).join(""); -- lines.forEach((line) => { -- let fileState = data.get(line.filename); -- if (!fileState) { -- fileState = { maxErrors: void 0, lines: [] }; -- data.set(line.filename, fileState); -- } -- fileState.lines.push(line); -- }); -- return new _SeatbeltFile(filename, data, comments.trim()); -- } -- static fromJSON(json) { -- const data = new Map( -- Object.entries(json.data).map(([filename, maxErrors]) => [ -- filename, -- { maxErrors: new Map(Object.entries(maxErrors)), lines: [] } -- ]) -- ); -- return new _SeatbeltFile(json.filename, data); -- } -- changed = false; -- dirname; -- useTempDirForWrites = true; -- *filenames() { -- for (const filename of this.data.keys()) { -- yield this.toAbsolutePath(filename); -- } -- } -- getMaxErrors(filename) { -- const fileState = this.data.get(this.toRelativePath(filename)); -- if (!fileState) { -- return void 0; -- } -- fileState.maxErrors ??= parseMaxErrors(fileState.lines); -- return fileState.maxErrors; -- } -- removeFile(filename, args) { -- const relativeFilename = this.toRelativePath(filename); -- if (!this.data.has(relativeFilename)) { -- return false; -- } -- SeatbeltArgs.verboseLog( -- args, -- () => args.frozen ? `${formatFilename(filename)}: ${SEATBELT_FROZEN}: didn't remove max errors` : `${formatFilename(filename)}: remove max errors` -- ); -- if (args.frozen) { -- return false; -- } -- this.data.delete(relativeFilename); -- this.changed = true; -- return true; -- } -- updateMaxErrors(filename, args, ruleToErrorCount) { -- const removedRules = /* @__PURE__ */ new Set(); -- let increasedRulesCount = 0; -- let decreasedRulesCount = 0; -- this.getMaxErrors(filename); -- const relativeFilename = this.toRelativePath(filename); -- const maxErrors = this.data.get(relativeFilename)?.maxErrors ?? /* @__PURE__ */ new Map(); -- ruleToErrorCount.forEach((errorCount, ruleId) => { -- const maxErrorCount = maxErrors.get(ruleId) ?? 0; -- if (errorCount === maxErrorCount) { -- return; -- } -- if (errorCount < maxErrorCount || SeatbeltArgs.ruleSetHas(args.allowIncreaseRules, ruleId)) { -- SeatbeltArgs.verboseLog( -- args, -- () => args.frozen ? `${formatFilename(filename)}: ${formatRuleId(ruleId)}: ${SEATBELT_FROZEN}: didn't update max errors ${maxErrorCount} -> ${errorCount}` : `${formatFilename(filename)}: ${formatRuleId(ruleId)}: update max errors ${maxErrorCount} -> ${errorCount}` -- ); -- maxErrors.set(ruleId, errorCount); -- if (errorCount > maxErrorCount) { -- increasedRulesCount++; -- } else { -- decreasedRulesCount++; -- } -- } -- }); -- if (args.verbose || args.keepRules !== "all") { -- maxErrors.forEach((maxErrorCount, ruleId) => { -- const shouldRemove = maxErrorCount === 0 || !ruleToErrorCount.has(ruleId); -- if (!shouldRemove) { -- return; -- } -- if (SeatbeltArgs.ruleSetHas(args.keepRules, ruleId)) { -- SeatbeltArgs.verboseLog( -- args, -- () => `${formatFilename(filename)}: ${formatRuleId(ruleId)}: ${SEATBELT_KEEP}: didn't update max errors ${maxErrorCount} -> ${0}` -- ); -- return; -- } -- SeatbeltArgs.verboseLog( -- args, -- () => args.frozen ? `${formatFilename(filename)}: ${formatRuleId(ruleId)}: ${SEATBELT_FROZEN}: didn't update max errors ${maxErrorCount} -> ${0}` : `${formatFilename(filename)}: ${formatRuleId(ruleId)}: update max errors ${maxErrorCount} -> ${0}` -- ); -- maxErrors.delete(ruleId); -- removedRules.add(ruleId); -- }); -- } -- const changed = increasedRulesCount > 0 || decreasedRulesCount > 0 || removedRules.size > 0; -- if (changed && !args.frozen) { -- const file = this.data.get(relativeFilename); -- if (file) { -- file.maxErrors = maxErrors; -- } else { -- this.data.set(relativeFilename, { -- maxErrors, -- lines: [] -- }); -- } -- this.changed = true; -- } -- return { removedRules, increasedRulesCount, decreasedRulesCount }; -- } -- /** Atomic read -> apply delta -> write. Takes an exclusive file lock when `args.threadsafe`. */ -- updateFileMaxErrors(args, filename, ruleToErrorCount) { -- return this.withOptionalLock(args, () => { -- const before = this.getMaxErrors(filename); -- const ruleToMaxErrorCountBefore = before ? new Map(before) : void 0; -- const result = this.updateMaxErrors(filename, args, ruleToErrorCount); -- if (!args.frozen) { -- this.flushChanges(); -- } -- return { ruleToMaxErrorCountBefore, ...result }; -- }); -- } -- /** Drop entries whose source file no longer exists. Takes an exclusive file lock when `args.threadsafe`. */ -- cleanUpRemovedFiles(args) { -- return this.withOptionalLock(args, () => { -- let removedFiles = 0; -- for (const filename of Array.from(this.filenames())) { -- if (!fs.existsSync(filename)) { -- if (this.removeFile(filename, args)) { -- removedFiles++; -- } -- } -- } -- if (!args.frozen) { -- this.flushChanges(); -- } -- return { removedFiles }; -- }); -- } -- withOptionalLock(args, fn) { -- if (!args.threadsafe) { -- return fn(); -- } -- const lock = new FileLock(`${this.filename}.lock`); -- lock.waitLock(LOCK_TIMEOUT_MS); -- try { -- this.readSync(); -- return fn(); -- } finally { -- lock.unlock(); -- } -- } -- toDataString() { -- const lines = []; -- this.data.forEach((fileState, filename) => { -- if (fileState.maxErrors) { -- fileState.lines = []; -- fileState.maxErrors.forEach((maxErrorCount, ruleId) => { -- fileState.lines.push({ filename, ruleId, maxErrors: maxErrorCount }); -- }); -- fileState.lines.sort( -- (a, b) => a.ruleId === b.ruleId ? 0 : a.ruleId < b.ruleId ? -1 : 1 -- ); -- } -- fileState.lines.forEach((line) => { -- const encoded = line.encoded ??= encodeLine(line); -- lines.push(encoded); -- }); -- }); -- lines.sort(); -- if (this.comments) { -- return this.comments + "\n\n" + lines.join(""); -- } else { -- return lines.join(""); -- } -- } -- readSync() { -- const nextStateFile = _SeatbeltFile.openSync(this.filename); -- if (nextStateFile) { -- this.data = nextStateFile.data; -- this.changed = false; -- return true; -- } -- return false; -- } -- flushChanges() { -- if (this.changed) { -- this.writeSync(); -- this.changed = false; -- return { updated: true }; -- } -- return { updated: false }; -- } -- writeSync() { -- const dataString = this.toDataString(); -- const dir = nodePath.dirname(this.filename); -- const base = nodePath.basename(this.filename); -- const tempFile = nodePath.join( -- this.useTempDirForWrites ? os.tmpdir() : dir, -- `.${base}.wip${process.pid}.${Date.now()}.tmp` -- ); -- fs.mkdirSync(dir, { recursive: true }); -- fs.writeFileSync(tempFile, dataString, "utf8"); -- try { -- fs.renameSync(tempFile, this.filename); -- } catch (error) { -- if (isErrno(error, "EXDEV")) { -- this.useTempDirForWrites = false; -- fs.copyFileSync(tempFile, this.filename); -- fs.rmSync(tempFile); -- return; -- } -- throw error; -- } -- } -- toJSON() { -- const data = Object.fromEntries( -- Array.from(this.data.keys()).map((filename) => { -- const maxErrors = this.getMaxErrors(filename); -- if (!maxErrors) { -- throw new Error(`${name} bug: expected errors for existing key`); -- } -- return [filename, Object.fromEntries(maxErrors)]; -- }) -- ); -- return { filename: this.filename, data }; -- } -- toRelativePath(filename) { -- if (!nodePath.isAbsolute(filename)) { -- return filename; -- } -- return nodePath.relative(this.dirname, filename); -- } -- toAbsolutePath(filename) { -- if (nodePath.isAbsolute(filename)) { -- return filename; -- } -- return nodePath.resolve(this.dirname, filename); -- } --}; --function parseMaxErrors(lines) { -- const maxErrors = /* @__PURE__ */ new Map(); -- lines.forEach((line) => { -- maxErrors.set(line.ruleId, line.maxErrors); -- }); -- return maxErrors; --} -- --export { -- appendErrorContext, -- FileLock, -- SeatbeltFile --}; --//# sourceMappingURL=chunk-5FFUIU4M.mjs.map -\ No newline at end of file -diff --git a/node_modules/eslint-seatbelt/dist/chunk-F4JJMAJL.js b/node_modules/eslint-seatbelt/dist/chunk-F4JJMAJL.js -new file mode 100644 -index 0000000..ae0f5b6 ---- /dev/null -+++ b/node_modules/eslint-seatbelt/dist/chunk-F4JJMAJL.js -@@ -0,0 +1,480 @@ -+"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; -+ -+ -+ -+ -+ -+ -+var _chunkTDWD7IZMjs = require('./chunk-TDWD7IZM.js'); -+ -+// src/FileLock.ts -+var _fs = require('fs'); var fs = _interopRequireWildcard(_fs); -+ -+// src/errorHanding.ts -+function appendErrorContext(error, context) { -+ if (error instanceof Error) { -+ error.message += ` -+ ${context}`; -+ } -+} -+function isErrno(error, code) { -+ return error instanceof Error && "code" in error && error.code === code; -+} -+ -+// src/FileLock.ts -+var { O_CREAT, O_EXCL, O_RDWR } = _fs.constants; -+var waitBuffer = new Int32Array(new SharedArrayBuffer(4)); -+var heldLocks = /* @__PURE__ */ new Set(); -+var cleanupHooksInstalled = false; -+var SIGNAL_EXIT_CODES = { SIGINT: 130, SIGTERM: 143, SIGHUP: 129 }; -+function installCleanupHooks() { -+ if (cleanupHooksInstalled) return; -+ cleanupHooksInstalled = true; -+ const release = () => { -+ for (const lock of heldLocks) { -+ try { -+ lock.unlock(); -+ } catch (e2) { -+ } -+ } -+ }; -+ process.on("exit", release); -+ for (const signal of Object.keys(SIGNAL_EXIT_CODES)) { -+ process.on(signal, () => { -+ release(); -+ process.exit(SIGNAL_EXIT_CODES[signal]); -+ }); -+ } -+} -+var FileLock = class { -+ constructor(filename) { -+ this.filename = filename; -+ } -+ -+ tryLock() { -+ this.assertNotLocked(); -+ try { -+ this.fd = _fs.openSync.call(void 0, this.filename, O_CREAT | O_EXCL | O_RDWR); -+ _fs.writeSync.call(void 0, this.fd, `${process.pid} -+`); -+ heldLocks.add(this); -+ installCleanupHooks(); -+ return true; -+ } catch (e) { -+ if (isErrno(e, "EEXIST")) { -+ return false; -+ } -+ throw e; -+ } -+ } -+ waitLock(timeoutMs) { -+ const deadline = Date.now() + timeoutMs; -+ let attemptedRecovery = false; -+ while (!this.tryLock()) { -+ if (Date.now() > deadline) { -+ if (!attemptedRecovery && this.reclaimIfStale()) { -+ attemptedRecovery = true; -+ continue; -+ } -+ throw new Error(`Timed out waiting for lock on ${this.filename}`); -+ } -+ Atomics.wait(waitBuffer, 0, 0, 1); -+ } -+ } -+ isLocked() { -+ return this.fd !== void 0; -+ } -+ unlock() { -+ if (this.fd !== void 0) { -+ _fs.closeSync.call(void 0, this.fd); -+ try { -+ _fs.rmSync.call(void 0, this.filename); -+ } catch (e) { -+ if (!isErrno(e, "ENOENT")) throw e; -+ } -+ this.fd = void 0; -+ heldLocks.delete(this); -+ } -+ } -+ assertNotLocked() { -+ if (this.fd !== void 0) { -+ throw new Error( -+ `FileLock "${this.filename}" is already locked by this process [pid ${process.pid}]` -+ ); -+ } -+ } -+ reclaimIfStale() { -+ let contents; -+ try { -+ contents = _fs.readFileSync.call(void 0, this.filename, "utf8"); -+ } catch (e) { -+ if (isErrno(e, "ENOENT")) return true; -+ throw e; -+ } -+ const pid = Number.parseInt(contents.trim(), 10); -+ if (!Number.isFinite(pid) || pid <= 0) return false; -+ try { -+ process.kill(pid, 0); -+ return false; -+ } catch (e) { -+ if (!isErrno(e, "ESRCH")) throw e; -+ } -+ try { -+ _fs.rmSync.call(void 0, this.filename); -+ } catch (e) { -+ if (!isErrno(e, "ENOENT")) throw e; -+ } -+ return true; -+ } -+}; -+ -+// src/SeatbeltFile.ts -+var _os = require('os'); var os = _interopRequireWildcard(_os); -+ -+var _path = require('path'); var nodePath = _interopRequireWildcard(_path); -+var LOCK_TIMEOUT_MS = 3e4; -+function encodeLine(line) { -+ const { filename, ruleId, maxErrors } = line; -+ return `${JSON.stringify(filename)} ${JSON.stringify(ruleId)} ${maxErrors} -+`; -+} -+function decodeLine(line, index) { -+ try { -+ const lineParts = line.split(" "); -+ if (lineParts.length !== 3) { -+ throw new Error( -+ `Expected 3 tab-separated JSON strings, instead have ${lineParts.length}` -+ ); -+ } -+ let filename; -+ try { -+ filename = JSON.parse(lineParts[0]); -+ } catch (e) { -+ appendErrorContext(e, "at tab-separated column 1 (filename)"); -+ throw e; -+ } -+ let ruleId; -+ try { -+ ruleId = JSON.parse(lineParts[1]); -+ } catch (e) { -+ appendErrorContext(e, "at tab-separated column 2 (RuleId)"); -+ throw e; -+ } -+ let maxErrors; -+ try { -+ maxErrors = JSON.parse(lineParts[2]); -+ } catch (e) { -+ appendErrorContext(e, "at tab-separated column 3 (maxErrors)"); -+ throw e; -+ } -+ return { -+ encoded: line, -+ filename, -+ ruleId, -+ maxErrors -+ }; -+ } catch (e) { -+ appendErrorContext(e, `at line ${index + 1}: \`${line.trim()}\``); -+ throw e; -+ } -+} -+var COMMENT_LINE_REGEX = /^\s*#/; -+var NON_EMPTY_LINE_REGEX = /\S+/; -+var DEFAULT_FILE_HEADER = ` -+# ${_chunkTDWD7IZMjs.name} temporarily allowed errors -+# docs: https://github.com/justjake/${_chunkTDWD7IZMjs.name}#readme -+`.trim(); -+var SeatbeltFile = (_class = class _SeatbeltFile { -+ constructor(filename, data, comments = "") {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this); -+ this.filename = filename; -+ this.data = data; -+ this.comments = comments; -+ this.filename = nodePath.default.resolve(this.filename); -+ this.dirname = nodePath.default.dirname(this.filename); -+ } -+ static readSync(filename) { -+ const text = fs.readFileSync(filename, "utf8"); -+ try { -+ return _SeatbeltFile.parse(filename, text); -+ } catch (e) { -+ appendErrorContext(e, `in seatbelt file \`${filename}\``); -+ throw e; -+ } -+ } -+ /** -+ * Read `filename` if it exists, otherwise create a new empty seatbelt file object -+ * that will write to that filename. -+ */ -+ static openSync(filename) { -+ try { -+ return _SeatbeltFile.readSync(filename); -+ } catch (e) { -+ if (isErrno(e, "ENOENT")) { -+ return new _SeatbeltFile(filename, /* @__PURE__ */ new Map(), DEFAULT_FILE_HEADER); -+ } -+ throw e; -+ } -+ } -+ static parse(filename, text) { -+ const data = /* @__PURE__ */ new Map(); -+ const split = text.split(/(?<=\n)/); -+ const lines = split.filter( -+ (line) => NON_EMPTY_LINE_REGEX.test(line) && !COMMENT_LINE_REGEX.test(line) -+ ).map(decodeLine); -+ const comments = split.filter((line) => COMMENT_LINE_REGEX.test(line)).join(""); -+ lines.forEach((line) => { -+ let fileState = data.get(line.filename); -+ if (!fileState) { -+ fileState = { maxErrors: void 0, lines: [] }; -+ data.set(line.filename, fileState); -+ } -+ fileState.lines.push(line); -+ }); -+ return new _SeatbeltFile(filename, data, comments.trim()); -+ } -+ static fromJSON(json) { -+ const data = new Map( -+ Object.entries(json.data).map(([filename, maxErrors]) => [ -+ filename, -+ { maxErrors: new Map(Object.entries(maxErrors)), lines: [] } -+ ]) -+ ); -+ return new _SeatbeltFile(json.filename, data); -+ } -+ __init() {this.changed = false} -+ -+ __init2() {this.useTempDirForWrites = true} -+ *filenames() { -+ for (const filename of this.data.keys()) { -+ yield this.toAbsolutePath(filename); -+ } -+ } -+ getMaxErrors(filename) { -+ const fileState = this.data.get(this.toRelativePath(filename)); -+ if (!fileState) { -+ return void 0; -+ } -+ fileState.maxErrors ??= parseMaxErrors(fileState.lines); -+ return fileState.maxErrors; -+ } -+ removeFile(filename, args) { -+ const relativeFilename = this.toRelativePath(filename); -+ if (!this.data.has(relativeFilename)) { -+ return false; -+ } -+ _chunkTDWD7IZMjs.SeatbeltArgs.verboseLog( -+ args, -+ () => args.frozen ? `${_chunkTDWD7IZMjs.formatFilename.call(void 0, filename)}: ${_chunkTDWD7IZMjs.SEATBELT_FROZEN}: didn't remove max errors` : `${_chunkTDWD7IZMjs.formatFilename.call(void 0, filename)}: remove max errors` -+ ); -+ if (args.frozen) { -+ return false; -+ } -+ this.data.delete(relativeFilename); -+ this.changed = true; -+ return true; -+ } -+ updateMaxErrors(filename, args, ruleToErrorCount) { -+ const removedRules = /* @__PURE__ */ new Set(); -+ let increasedRulesCount = 0; -+ let decreasedRulesCount = 0; -+ this.getMaxErrors(filename); -+ const relativeFilename = this.toRelativePath(filename); -+ const maxErrors = _nullishCoalesce(_optionalChain([this, 'access', _ => _.data, 'access', _2 => _2.get, 'call', _3 => _3(relativeFilename), 'optionalAccess', _4 => _4.maxErrors]), () => ( /* @__PURE__ */ new Map())); -+ ruleToErrorCount.forEach((errorCount, ruleId) => { -+ const maxErrorCount = _nullishCoalesce(maxErrors.get(ruleId), () => ( 0)); -+ if (errorCount === maxErrorCount) { -+ return; -+ } -+ if (errorCount < maxErrorCount || _chunkTDWD7IZMjs.SeatbeltArgs.ruleSetHas(args.allowIncreaseRules, ruleId)) { -+ _chunkTDWD7IZMjs.SeatbeltArgs.verboseLog( -+ args, -+ () => args.frozen ? `${_chunkTDWD7IZMjs.formatFilename.call(void 0, filename)}: ${_chunkTDWD7IZMjs.formatRuleId.call(void 0, ruleId)}: ${_chunkTDWD7IZMjs.SEATBELT_FROZEN}: didn't update max errors ${maxErrorCount} -> ${errorCount}` : `${_chunkTDWD7IZMjs.formatFilename.call(void 0, filename)}: ${_chunkTDWD7IZMjs.formatRuleId.call(void 0, ruleId)}: update max errors ${maxErrorCount} -> ${errorCount}` -+ ); -+ maxErrors.set(ruleId, errorCount); -+ if (errorCount > maxErrorCount) { -+ increasedRulesCount++; -+ } else { -+ decreasedRulesCount++; -+ } -+ } -+ }); -+ if (args.verbose || args.keepRules !== "all") { -+ maxErrors.forEach((maxErrorCount, ruleId) => { -+ const shouldRemove = maxErrorCount === 0 || !ruleToErrorCount.has(ruleId); -+ if (!shouldRemove) { -+ return; -+ } -+ if (_chunkTDWD7IZMjs.SeatbeltArgs.ruleSetHas(args.keepRules, ruleId)) { -+ _chunkTDWD7IZMjs.SeatbeltArgs.verboseLog( -+ args, -+ () => `${_chunkTDWD7IZMjs.formatFilename.call(void 0, filename)}: ${_chunkTDWD7IZMjs.formatRuleId.call(void 0, ruleId)}: ${_chunkTDWD7IZMjs.SEATBELT_KEEP}: didn't update max errors ${maxErrorCount} -> ${0}` -+ ); -+ return; -+ } -+ _chunkTDWD7IZMjs.SeatbeltArgs.verboseLog( -+ args, -+ () => args.frozen ? `${_chunkTDWD7IZMjs.formatFilename.call(void 0, filename)}: ${_chunkTDWD7IZMjs.formatRuleId.call(void 0, ruleId)}: ${_chunkTDWD7IZMjs.SEATBELT_FROZEN}: didn't update max errors ${maxErrorCount} -> ${0}` : `${_chunkTDWD7IZMjs.formatFilename.call(void 0, filename)}: ${_chunkTDWD7IZMjs.formatRuleId.call(void 0, ruleId)}: update max errors ${maxErrorCount} -> ${0}` -+ ); -+ maxErrors.delete(ruleId); -+ removedRules.add(ruleId); -+ }); -+ } -+ const changed = increasedRulesCount > 0 || decreasedRulesCount > 0 || removedRules.size > 0; -+ if (changed && !args.frozen) { -+ const file = this.data.get(relativeFilename); -+ if (file) { -+ file.maxErrors = maxErrors; -+ } else { -+ this.data.set(relativeFilename, { -+ maxErrors, -+ lines: [] -+ }); -+ } -+ this.changed = true; -+ } -+ return { removedRules, increasedRulesCount, decreasedRulesCount }; -+ } -+ /** Atomic read -> apply delta -> write. Takes an exclusive file lock when `args.threadsafe`. */ -+ updateFileMaxErrors(args, filename, ruleToErrorCount) { -+ return this.withOptionalLock(args, () => { -+ const before = this.getMaxErrors(filename); -+ const ruleToMaxErrorCountBefore = before ? new Map(before) : void 0; -+ const result = this.updateMaxErrors(filename, args, ruleToErrorCount); -+ if (!args.frozen && !args.readOnly) { -+ this.flushChanges(); -+ } -+ return { ruleToMaxErrorCountBefore, ...result }; -+ }); -+ } -+ /** Drop entries whose source file no longer exists. Takes an exclusive file lock when `args.threadsafe`. */ -+ cleanUpRemovedFiles(args) { -+ return this.withOptionalLock(args, () => { -+ let removedFiles = 0; -+ for (const filename of Array.from(this.filenames())) { -+ if (!fs.existsSync(filename)) { -+ if (this.removeFile(filename, args)) { -+ removedFiles++; -+ } -+ } -+ } -+ if (!args.frozen && !args.readOnly) { -+ this.flushChanges(); -+ } -+ return { removedFiles }; -+ }); -+ } -+ withOptionalLock(args, fn) { -+ if (!args.threadsafe) { -+ return fn(); -+ } -+ const lock = new FileLock(`${this.filename}.lock`); -+ lock.waitLock(LOCK_TIMEOUT_MS); -+ try { -+ this.readSync(); -+ return fn(); -+ } finally { -+ lock.unlock(); -+ } -+ } -+ toDataString() { -+ const lines = []; -+ this.data.forEach((fileState, filename) => { -+ if (fileState.maxErrors) { -+ fileState.lines = []; -+ fileState.maxErrors.forEach((maxErrorCount, ruleId) => { -+ fileState.lines.push({ filename, ruleId, maxErrors: maxErrorCount }); -+ }); -+ fileState.lines.sort( -+ (a, b) => a.ruleId === b.ruleId ? 0 : a.ruleId < b.ruleId ? -1 : 1 -+ ); -+ } -+ fileState.lines.forEach((line) => { -+ const encoded = line.encoded ??= encodeLine(line); -+ lines.push(encoded); -+ }); -+ }); -+ lines.sort(); -+ if (this.comments) { -+ return this.comments + "\n\n" + lines.join(""); -+ } else { -+ return lines.join(""); -+ } -+ } -+ readSync() { -+ const nextStateFile = _SeatbeltFile.openSync(this.filename); -+ if (nextStateFile) { -+ this.data = nextStateFile.data; -+ this.changed = false; -+ return true; -+ } -+ return false; -+ } -+ flushChanges() { -+ if (this.changed) { -+ this.writeSync(); -+ this.changed = false; -+ return { updated: true }; -+ } -+ return { updated: false }; -+ } -+ writeSync() { -+ const dataString = this.toDataString(); -+ const dir = nodePath.dirname(this.filename); -+ const base = nodePath.basename(this.filename); -+ const tempFile = nodePath.join( -+ this.useTempDirForWrites ? os.tmpdir() : dir, -+ `.${base}.wip${process.pid}.${Date.now()}.tmp` -+ ); -+ fs.mkdirSync(dir, { recursive: true }); -+ fs.writeFileSync(tempFile, dataString, "utf8"); -+ try { -+ fs.renameSync(tempFile, this.filename); -+ } catch (error) { -+ if (isErrno(error, "EXDEV")) { -+ this.useTempDirForWrites = false; -+ fs.copyFileSync(tempFile, this.filename); -+ fs.rmSync(tempFile); -+ return; -+ } -+ throw error; -+ } -+ } -+ toJSON() { -+ const data = Object.fromEntries( -+ Array.from(this.data.keys()).map((filename) => { -+ const maxErrors = this.getMaxErrors(filename); -+ if (!maxErrors) { -+ throw new Error(`${_chunkTDWD7IZMjs.name} bug: expected errors for existing key`); -+ } -+ return [filename, Object.fromEntries(maxErrors)]; -+ }) -+ ); -+ return { filename: this.filename, data }; -+ } -+ toRelativePath(filename) { -+ if (!nodePath.isAbsolute(filename)) { -+ return filename; -+ } -+ return nodePath.relative(this.dirname, filename); -+ } -+ toAbsolutePath(filename) { -+ if (nodePath.isAbsolute(filename)) { -+ return filename; -+ } -+ return nodePath.resolve(this.dirname, filename); -+ } -+}, _class); -+function parseMaxErrors(lines) { -+ const maxErrors = /* @__PURE__ */ new Map(); -+ lines.forEach((line) => { -+ maxErrors.set(line.ruleId, line.maxErrors); -+ }); -+ return maxErrors; -+} -+ -+ -+ -+ -+ -+exports.appendErrorContext = appendErrorContext; exports.FileLock = FileLock; exports.SeatbeltFile = SeatbeltFile; -+//# sourceMappingURL=chunk-F4JJMAJL.js.map -\ No newline at end of file -diff --git a/node_modules/eslint-seatbelt/dist/chunk-NTFTCWX7.js b/node_modules/eslint-seatbelt/dist/chunk-NTFTCWX7.js -deleted file mode 100644 -index f3e2e3d..0000000 ---- a/node_modules/eslint-seatbelt/dist/chunk-NTFTCWX7.js -+++ /dev/null -@@ -1,480 +0,0 @@ --"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; -- -- -- -- -- -- --var _chunkZVY5S6JSjs = require('./chunk-ZVY5S6JS.js'); -- --// src/FileLock.ts --var _fs = require('fs'); var fs = _interopRequireWildcard(_fs); -- --// src/errorHanding.ts --function appendErrorContext(error, context) { -- if (error instanceof Error) { -- error.message += ` -- ${context}`; -- } --} --function isErrno(error, code) { -- return error instanceof Error && "code" in error && error.code === code; --} -- --// src/FileLock.ts --var { O_CREAT, O_EXCL, O_RDWR } = _fs.constants; --var waitBuffer = new Int32Array(new SharedArrayBuffer(4)); --var heldLocks = /* @__PURE__ */ new Set(); --var cleanupHooksInstalled = false; --var SIGNAL_EXIT_CODES = { SIGINT: 130, SIGTERM: 143, SIGHUP: 129 }; --function installCleanupHooks() { -- if (cleanupHooksInstalled) return; -- cleanupHooksInstalled = true; -- const release = () => { -- for (const lock of heldLocks) { -- try { -- lock.unlock(); -- } catch (e2) { -- } -- } -- }; -- process.on("exit", release); -- for (const signal of Object.keys(SIGNAL_EXIT_CODES)) { -- process.on(signal, () => { -- release(); -- process.exit(SIGNAL_EXIT_CODES[signal]); -- }); -- } --} --var FileLock = class { -- constructor(filename) { -- this.filename = filename; -- } -- -- tryLock() { -- this.assertNotLocked(); -- try { -- this.fd = _fs.openSync.call(void 0, this.filename, O_CREAT | O_EXCL | O_RDWR); -- _fs.writeSync.call(void 0, this.fd, `${process.pid} --`); -- heldLocks.add(this); -- installCleanupHooks(); -- return true; -- } catch (e) { -- if (isErrno(e, "EEXIST")) { -- return false; -- } -- throw e; -- } -- } -- waitLock(timeoutMs) { -- const deadline = Date.now() + timeoutMs; -- let attemptedRecovery = false; -- while (!this.tryLock()) { -- if (Date.now() > deadline) { -- if (!attemptedRecovery && this.reclaimIfStale()) { -- attemptedRecovery = true; -- continue; -- } -- throw new Error(`Timed out waiting for lock on ${this.filename}`); -- } -- Atomics.wait(waitBuffer, 0, 0, 1); -- } -- } -- isLocked() { -- return this.fd !== void 0; -- } -- unlock() { -- if (this.fd !== void 0) { -- _fs.closeSync.call(void 0, this.fd); -- try { -- _fs.rmSync.call(void 0, this.filename); -- } catch (e) { -- if (!isErrno(e, "ENOENT")) throw e; -- } -- this.fd = void 0; -- heldLocks.delete(this); -- } -- } -- assertNotLocked() { -- if (this.fd !== void 0) { -- throw new Error( -- `FileLock "${this.filename}" is already locked by this process [pid ${process.pid}]` -- ); -- } -- } -- reclaimIfStale() { -- let contents; -- try { -- contents = _fs.readFileSync.call(void 0, this.filename, "utf8"); -- } catch (e) { -- if (isErrno(e, "ENOENT")) return true; -- throw e; -- } -- const pid = Number.parseInt(contents.trim(), 10); -- if (!Number.isFinite(pid) || pid <= 0) return false; -- try { -- process.kill(pid, 0); -- return false; -- } catch (e) { -- if (!isErrno(e, "ESRCH")) throw e; -- } -- try { -- _fs.rmSync.call(void 0, this.filename); -- } catch (e) { -- if (!isErrno(e, "ENOENT")) throw e; -- } -- return true; -- } --}; -- --// src/SeatbeltFile.ts --var _os = require('os'); var os = _interopRequireWildcard(_os); -- --var _path = require('path'); var nodePath = _interopRequireWildcard(_path); --var LOCK_TIMEOUT_MS = 3e4; --function encodeLine(line) { -- const { filename, ruleId, maxErrors } = line; -- return `${JSON.stringify(filename)} ${JSON.stringify(ruleId)} ${maxErrors} --`; --} --function decodeLine(line, index) { -- try { -- const lineParts = line.split(" "); -- if (lineParts.length !== 3) { -- throw new Error( -- `Expected 3 tab-separated JSON strings, instead have ${lineParts.length}` -- ); -- } -- let filename; -- try { -- filename = JSON.parse(lineParts[0]); -- } catch (e) { -- appendErrorContext(e, "at tab-separated column 1 (filename)"); -- throw e; -- } -- let ruleId; -- try { -- ruleId = JSON.parse(lineParts[1]); -- } catch (e) { -- appendErrorContext(e, "at tab-separated column 2 (RuleId)"); -- throw e; -- } -- let maxErrors; -- try { -- maxErrors = JSON.parse(lineParts[2]); -- } catch (e) { -- appendErrorContext(e, "at tab-separated column 3 (maxErrors)"); -- throw e; -- } -- return { -- encoded: line, -- filename, -- ruleId, -- maxErrors -- }; -- } catch (e) { -- appendErrorContext(e, `at line ${index + 1}: \`${line.trim()}\``); -- throw e; -- } --} --var COMMENT_LINE_REGEX = /^\s*#/; --var NON_EMPTY_LINE_REGEX = /\S+/; --var DEFAULT_FILE_HEADER = ` --# ${_chunkZVY5S6JSjs.name} temporarily allowed errors --# docs: https://github.com/justjake/${_chunkZVY5S6JSjs.name}#readme --`.trim(); --var SeatbeltFile = (_class = class _SeatbeltFile { -- constructor(filename, data, comments = "") {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this); -- this.filename = filename; -- this.data = data; -- this.comments = comments; -- this.filename = nodePath.default.resolve(this.filename); -- this.dirname = nodePath.default.dirname(this.filename); -- } -- static readSync(filename) { -- const text = fs.readFileSync(filename, "utf8"); -- try { -- return _SeatbeltFile.parse(filename, text); -- } catch (e) { -- appendErrorContext(e, `in seatbelt file \`${filename}\``); -- throw e; -- } -- } -- /** -- * Read `filename` if it exists, otherwise create a new empty seatbelt file object -- * that will write to that filename. -- */ -- static openSync(filename) { -- try { -- return _SeatbeltFile.readSync(filename); -- } catch (e) { -- if (isErrno(e, "ENOENT")) { -- return new _SeatbeltFile(filename, /* @__PURE__ */ new Map(), DEFAULT_FILE_HEADER); -- } -- throw e; -- } -- } -- static parse(filename, text) { -- const data = /* @__PURE__ */ new Map(); -- const split = text.split(/(?<=\n)/); -- const lines = split.filter( -- (line) => NON_EMPTY_LINE_REGEX.test(line) && !COMMENT_LINE_REGEX.test(line) -- ).map(decodeLine); -- const comments = split.filter((line) => COMMENT_LINE_REGEX.test(line)).join(""); -- lines.forEach((line) => { -- let fileState = data.get(line.filename); -- if (!fileState) { -- fileState = { maxErrors: void 0, lines: [] }; -- data.set(line.filename, fileState); -- } -- fileState.lines.push(line); -- }); -- return new _SeatbeltFile(filename, data, comments.trim()); -- } -- static fromJSON(json) { -- const data = new Map( -- Object.entries(json.data).map(([filename, maxErrors]) => [ -- filename, -- { maxErrors: new Map(Object.entries(maxErrors)), lines: [] } -- ]) -- ); -- return new _SeatbeltFile(json.filename, data); -- } -- __init() {this.changed = false} -- -- __init2() {this.useTempDirForWrites = true} -- *filenames() { -- for (const filename of this.data.keys()) { -- yield this.toAbsolutePath(filename); -- } -- } -- getMaxErrors(filename) { -- const fileState = this.data.get(this.toRelativePath(filename)); -- if (!fileState) { -- return void 0; -- } -- fileState.maxErrors ??= parseMaxErrors(fileState.lines); -- return fileState.maxErrors; -- } -- removeFile(filename, args) { -- const relativeFilename = this.toRelativePath(filename); -- if (!this.data.has(relativeFilename)) { -- return false; -- } -- _chunkZVY5S6JSjs.SeatbeltArgs.verboseLog( -- args, -- () => args.frozen ? `${_chunkZVY5S6JSjs.formatFilename.call(void 0, filename)}: ${_chunkZVY5S6JSjs.SEATBELT_FROZEN}: didn't remove max errors` : `${_chunkZVY5S6JSjs.formatFilename.call(void 0, filename)}: remove max errors` -- ); -- if (args.frozen) { -- return false; -- } -- this.data.delete(relativeFilename); -- this.changed = true; -- return true; -- } -- updateMaxErrors(filename, args, ruleToErrorCount) { -- const removedRules = /* @__PURE__ */ new Set(); -- let increasedRulesCount = 0; -- let decreasedRulesCount = 0; -- this.getMaxErrors(filename); -- const relativeFilename = this.toRelativePath(filename); -- const maxErrors = _nullishCoalesce(_optionalChain([this, 'access', _ => _.data, 'access', _2 => _2.get, 'call', _3 => _3(relativeFilename), 'optionalAccess', _4 => _4.maxErrors]), () => ( /* @__PURE__ */ new Map())); -- ruleToErrorCount.forEach((errorCount, ruleId) => { -- const maxErrorCount = _nullishCoalesce(maxErrors.get(ruleId), () => ( 0)); -- if (errorCount === maxErrorCount) { -- return; -- } -- if (errorCount < maxErrorCount || _chunkZVY5S6JSjs.SeatbeltArgs.ruleSetHas(args.allowIncreaseRules, ruleId)) { -- _chunkZVY5S6JSjs.SeatbeltArgs.verboseLog( -- args, -- () => args.frozen ? `${_chunkZVY5S6JSjs.formatFilename.call(void 0, filename)}: ${_chunkZVY5S6JSjs.formatRuleId.call(void 0, ruleId)}: ${_chunkZVY5S6JSjs.SEATBELT_FROZEN}: didn't update max errors ${maxErrorCount} -> ${errorCount}` : `${_chunkZVY5S6JSjs.formatFilename.call(void 0, filename)}: ${_chunkZVY5S6JSjs.formatRuleId.call(void 0, ruleId)}: update max errors ${maxErrorCount} -> ${errorCount}` -- ); -- maxErrors.set(ruleId, errorCount); -- if (errorCount > maxErrorCount) { -- increasedRulesCount++; -- } else { -- decreasedRulesCount++; -- } -- } -- }); -- if (args.verbose || args.keepRules !== "all") { -- maxErrors.forEach((maxErrorCount, ruleId) => { -- const shouldRemove = maxErrorCount === 0 || !ruleToErrorCount.has(ruleId); -- if (!shouldRemove) { -- return; -- } -- if (_chunkZVY5S6JSjs.SeatbeltArgs.ruleSetHas(args.keepRules, ruleId)) { -- _chunkZVY5S6JSjs.SeatbeltArgs.verboseLog( -- args, -- () => `${_chunkZVY5S6JSjs.formatFilename.call(void 0, filename)}: ${_chunkZVY5S6JSjs.formatRuleId.call(void 0, ruleId)}: ${_chunkZVY5S6JSjs.SEATBELT_KEEP}: didn't update max errors ${maxErrorCount} -> ${0}` -- ); -- return; -- } -- _chunkZVY5S6JSjs.SeatbeltArgs.verboseLog( -- args, -- () => args.frozen ? `${_chunkZVY5S6JSjs.formatFilename.call(void 0, filename)}: ${_chunkZVY5S6JSjs.formatRuleId.call(void 0, ruleId)}: ${_chunkZVY5S6JSjs.SEATBELT_FROZEN}: didn't update max errors ${maxErrorCount} -> ${0}` : `${_chunkZVY5S6JSjs.formatFilename.call(void 0, filename)}: ${_chunkZVY5S6JSjs.formatRuleId.call(void 0, ruleId)}: update max errors ${maxErrorCount} -> ${0}` -- ); -- maxErrors.delete(ruleId); -- removedRules.add(ruleId); -- }); -- } -- const changed = increasedRulesCount > 0 || decreasedRulesCount > 0 || removedRules.size > 0; -- if (changed && !args.frozen) { -- const file = this.data.get(relativeFilename); -- if (file) { -- file.maxErrors = maxErrors; -- } else { -- this.data.set(relativeFilename, { -- maxErrors, -- lines: [] -- }); -- } -- this.changed = true; -- } -- return { removedRules, increasedRulesCount, decreasedRulesCount }; -- } -- /** Atomic read -> apply delta -> write. Takes an exclusive file lock when `args.threadsafe`. */ -- updateFileMaxErrors(args, filename, ruleToErrorCount) { -- return this.withOptionalLock(args, () => { -- const before = this.getMaxErrors(filename); -- const ruleToMaxErrorCountBefore = before ? new Map(before) : void 0; -- const result = this.updateMaxErrors(filename, args, ruleToErrorCount); -- if (!args.frozen) { -- this.flushChanges(); -- } -- return { ruleToMaxErrorCountBefore, ...result }; -- }); -- } -- /** Drop entries whose source file no longer exists. Takes an exclusive file lock when `args.threadsafe`. */ -- cleanUpRemovedFiles(args) { -- return this.withOptionalLock(args, () => { -- let removedFiles = 0; -- for (const filename of Array.from(this.filenames())) { -- if (!fs.existsSync(filename)) { -- if (this.removeFile(filename, args)) { -- removedFiles++; -- } -- } -- } -- if (!args.frozen) { -- this.flushChanges(); -- } -- return { removedFiles }; -- }); -- } -- withOptionalLock(args, fn) { -- if (!args.threadsafe) { -- return fn(); -- } -- const lock = new FileLock(`${this.filename}.lock`); -- lock.waitLock(LOCK_TIMEOUT_MS); -- try { -- this.readSync(); -- return fn(); -- } finally { -- lock.unlock(); -- } -- } -- toDataString() { -- const lines = []; -- this.data.forEach((fileState, filename) => { -- if (fileState.maxErrors) { -- fileState.lines = []; -- fileState.maxErrors.forEach((maxErrorCount, ruleId) => { -- fileState.lines.push({ filename, ruleId, maxErrors: maxErrorCount }); -- }); -- fileState.lines.sort( -- (a, b) => a.ruleId === b.ruleId ? 0 : a.ruleId < b.ruleId ? -1 : 1 -- ); -- } -- fileState.lines.forEach((line) => { -- const encoded = line.encoded ??= encodeLine(line); -- lines.push(encoded); -- }); -- }); -- lines.sort(); -- if (this.comments) { -- return this.comments + "\n\n" + lines.join(""); -- } else { -- return lines.join(""); -- } -- } -- readSync() { -- const nextStateFile = _SeatbeltFile.openSync(this.filename); -- if (nextStateFile) { -- this.data = nextStateFile.data; -- this.changed = false; -- return true; -- } -- return false; -- } -- flushChanges() { -- if (this.changed) { -- this.writeSync(); -- this.changed = false; -- return { updated: true }; -- } -- return { updated: false }; -- } -- writeSync() { -- const dataString = this.toDataString(); -- const dir = nodePath.dirname(this.filename); -- const base = nodePath.basename(this.filename); -- const tempFile = nodePath.join( -- this.useTempDirForWrites ? os.tmpdir() : dir, -- `.${base}.wip${process.pid}.${Date.now()}.tmp` -- ); -- fs.mkdirSync(dir, { recursive: true }); -- fs.writeFileSync(tempFile, dataString, "utf8"); -- try { -- fs.renameSync(tempFile, this.filename); -- } catch (error) { -- if (isErrno(error, "EXDEV")) { -- this.useTempDirForWrites = false; -- fs.copyFileSync(tempFile, this.filename); -- fs.rmSync(tempFile); -- return; -- } -- throw error; -- } -- } -- toJSON() { -- const data = Object.fromEntries( -- Array.from(this.data.keys()).map((filename) => { -- const maxErrors = this.getMaxErrors(filename); -- if (!maxErrors) { -- throw new Error(`${_chunkZVY5S6JSjs.name} bug: expected errors for existing key`); -- } -- return [filename, Object.fromEntries(maxErrors)]; -- }) -- ); -- return { filename: this.filename, data }; -- } -- toRelativePath(filename) { -- if (!nodePath.isAbsolute(filename)) { -- return filename; -- } -- return nodePath.relative(this.dirname, filename); -- } -- toAbsolutePath(filename) { -- if (nodePath.isAbsolute(filename)) { -- return filename; -- } -- return nodePath.resolve(this.dirname, filename); -- } --}, _class); --function parseMaxErrors(lines) { -- const maxErrors = /* @__PURE__ */ new Map(); -- lines.forEach((line) => { -- maxErrors.set(line.ruleId, line.maxErrors); -- }); -- return maxErrors; --} -- -- -- -- -- --exports.appendErrorContext = appendErrorContext; exports.FileLock = FileLock; exports.SeatbeltFile = SeatbeltFile; --//# sourceMappingURL=chunk-NTFTCWX7.js.map -\ No newline at end of file -diff --git a/node_modules/eslint-seatbelt/dist/chunk-QGLWIAEE.mjs b/node_modules/eslint-seatbelt/dist/chunk-QGLWIAEE.mjs -new file mode 100644 -index 0000000..4dd47c5 ---- /dev/null -+++ b/node_modules/eslint-seatbelt/dist/chunk-QGLWIAEE.mjs -@@ -0,0 +1,480 @@ -+import { -+ SEATBELT_FROZEN, -+ SEATBELT_KEEP, -+ SeatbeltArgs, -+ formatFilename, -+ formatRuleId, -+ name -+} from "./chunk-U6KIQG2A.mjs"; -+ -+// src/FileLock.ts -+import { openSync, writeSync, closeSync, readFileSync, constants, rmSync } from "node:fs"; -+ -+// src/errorHanding.ts -+function appendErrorContext(error, context) { -+ if (error instanceof Error) { -+ error.message += ` -+ ${context}`; -+ } -+} -+function isErrno(error, code) { -+ return error instanceof Error && "code" in error && error.code === code; -+} -+ -+// src/FileLock.ts -+var { O_CREAT, O_EXCL, O_RDWR } = constants; -+var waitBuffer = new Int32Array(new SharedArrayBuffer(4)); -+var heldLocks = /* @__PURE__ */ new Set(); -+var cleanupHooksInstalled = false; -+var SIGNAL_EXIT_CODES = { SIGINT: 130, SIGTERM: 143, SIGHUP: 129 }; -+function installCleanupHooks() { -+ if (cleanupHooksInstalled) return; -+ cleanupHooksInstalled = true; -+ const release = () => { -+ for (const lock of heldLocks) { -+ try { -+ lock.unlock(); -+ } catch { -+ } -+ } -+ }; -+ process.on("exit", release); -+ for (const signal of Object.keys(SIGNAL_EXIT_CODES)) { -+ process.on(signal, () => { -+ release(); -+ process.exit(SIGNAL_EXIT_CODES[signal]); -+ }); -+ } -+} -+var FileLock = class { -+ constructor(filename) { -+ this.filename = filename; -+ } -+ fd; -+ tryLock() { -+ this.assertNotLocked(); -+ try { -+ this.fd = openSync(this.filename, O_CREAT | O_EXCL | O_RDWR); -+ writeSync(this.fd, `${process.pid} -+`); -+ heldLocks.add(this); -+ installCleanupHooks(); -+ return true; -+ } catch (e) { -+ if (isErrno(e, "EEXIST")) { -+ return false; -+ } -+ throw e; -+ } -+ } -+ waitLock(timeoutMs) { -+ const deadline = Date.now() + timeoutMs; -+ let attemptedRecovery = false; -+ while (!this.tryLock()) { -+ if (Date.now() > deadline) { -+ if (!attemptedRecovery && this.reclaimIfStale()) { -+ attemptedRecovery = true; -+ continue; -+ } -+ throw new Error(`Timed out waiting for lock on ${this.filename}`); -+ } -+ Atomics.wait(waitBuffer, 0, 0, 1); -+ } -+ } -+ isLocked() { -+ return this.fd !== void 0; -+ } -+ unlock() { -+ if (this.fd !== void 0) { -+ closeSync(this.fd); -+ try { -+ rmSync(this.filename); -+ } catch (e) { -+ if (!isErrno(e, "ENOENT")) throw e; -+ } -+ this.fd = void 0; -+ heldLocks.delete(this); -+ } -+ } -+ assertNotLocked() { -+ if (this.fd !== void 0) { -+ throw new Error( -+ `FileLock "${this.filename}" is already locked by this process [pid ${process.pid}]` -+ ); -+ } -+ } -+ reclaimIfStale() { -+ let contents; -+ try { -+ contents = readFileSync(this.filename, "utf8"); -+ } catch (e) { -+ if (isErrno(e, "ENOENT")) return true; -+ throw e; -+ } -+ const pid = Number.parseInt(contents.trim(), 10); -+ if (!Number.isFinite(pid) || pid <= 0) return false; -+ try { -+ process.kill(pid, 0); -+ return false; -+ } catch (e) { -+ if (!isErrno(e, "ESRCH")) throw e; -+ } -+ try { -+ rmSync(this.filename); -+ } catch (e) { -+ if (!isErrno(e, "ENOENT")) throw e; -+ } -+ return true; -+ } -+}; -+ -+// src/SeatbeltFile.ts -+import * as os from "node:os"; -+import * as fs from "node:fs"; -+import path, * as nodePath from "node:path"; -+var LOCK_TIMEOUT_MS = 3e4; -+function encodeLine(line) { -+ const { filename, ruleId, maxErrors } = line; -+ return `${JSON.stringify(filename)} ${JSON.stringify(ruleId)} ${maxErrors} -+`; -+} -+function decodeLine(line, index) { -+ try { -+ const lineParts = line.split(" "); -+ if (lineParts.length !== 3) { -+ throw new Error( -+ `Expected 3 tab-separated JSON strings, instead have ${lineParts.length}` -+ ); -+ } -+ let filename; -+ try { -+ filename = JSON.parse(lineParts[0]); -+ } catch (e) { -+ appendErrorContext(e, "at tab-separated column 1 (filename)"); -+ throw e; -+ } -+ let ruleId; -+ try { -+ ruleId = JSON.parse(lineParts[1]); -+ } catch (e) { -+ appendErrorContext(e, "at tab-separated column 2 (RuleId)"); -+ throw e; -+ } -+ let maxErrors; -+ try { -+ maxErrors = JSON.parse(lineParts[2]); -+ } catch (e) { -+ appendErrorContext(e, "at tab-separated column 3 (maxErrors)"); -+ throw e; -+ } -+ return { -+ encoded: line, -+ filename, -+ ruleId, -+ maxErrors -+ }; -+ } catch (e) { -+ appendErrorContext(e, `at line ${index + 1}: \`${line.trim()}\``); -+ throw e; -+ } -+} -+var COMMENT_LINE_REGEX = /^\s*#/; -+var NON_EMPTY_LINE_REGEX = /\S+/; -+var DEFAULT_FILE_HEADER = ` -+# ${name} temporarily allowed errors -+# docs: https://github.com/justjake/${name}#readme -+`.trim(); -+var SeatbeltFile = class _SeatbeltFile { -+ constructor(filename, data, comments = "") { -+ this.filename = filename; -+ this.data = data; -+ this.comments = comments; -+ this.filename = path.resolve(this.filename); -+ this.dirname = path.dirname(this.filename); -+ } -+ static readSync(filename) { -+ const text = fs.readFileSync(filename, "utf8"); -+ try { -+ return _SeatbeltFile.parse(filename, text); -+ } catch (e) { -+ appendErrorContext(e, `in seatbelt file \`${filename}\``); -+ throw e; -+ } -+ } -+ /** -+ * Read `filename` if it exists, otherwise create a new empty seatbelt file object -+ * that will write to that filename. -+ */ -+ static openSync(filename) { -+ try { -+ return _SeatbeltFile.readSync(filename); -+ } catch (e) { -+ if (isErrno(e, "ENOENT")) { -+ return new _SeatbeltFile(filename, /* @__PURE__ */ new Map(), DEFAULT_FILE_HEADER); -+ } -+ throw e; -+ } -+ } -+ static parse(filename, text) { -+ const data = /* @__PURE__ */ new Map(); -+ const split = text.split(/(?<=\n)/); -+ const lines = split.filter( -+ (line) => NON_EMPTY_LINE_REGEX.test(line) && !COMMENT_LINE_REGEX.test(line) -+ ).map(decodeLine); -+ const comments = split.filter((line) => COMMENT_LINE_REGEX.test(line)).join(""); -+ lines.forEach((line) => { -+ let fileState = data.get(line.filename); -+ if (!fileState) { -+ fileState = { maxErrors: void 0, lines: [] }; -+ data.set(line.filename, fileState); -+ } -+ fileState.lines.push(line); -+ }); -+ return new _SeatbeltFile(filename, data, comments.trim()); -+ } -+ static fromJSON(json) { -+ const data = new Map( -+ Object.entries(json.data).map(([filename, maxErrors]) => [ -+ filename, -+ { maxErrors: new Map(Object.entries(maxErrors)), lines: [] } -+ ]) -+ ); -+ return new _SeatbeltFile(json.filename, data); -+ } -+ changed = false; -+ dirname; -+ useTempDirForWrites = true; -+ *filenames() { -+ for (const filename of this.data.keys()) { -+ yield this.toAbsolutePath(filename); -+ } -+ } -+ getMaxErrors(filename) { -+ const fileState = this.data.get(this.toRelativePath(filename)); -+ if (!fileState) { -+ return void 0; -+ } -+ fileState.maxErrors ??= parseMaxErrors(fileState.lines); -+ return fileState.maxErrors; -+ } -+ removeFile(filename, args) { -+ const relativeFilename = this.toRelativePath(filename); -+ if (!this.data.has(relativeFilename)) { -+ return false; -+ } -+ SeatbeltArgs.verboseLog( -+ args, -+ () => args.frozen ? `${formatFilename(filename)}: ${SEATBELT_FROZEN}: didn't remove max errors` : `${formatFilename(filename)}: remove max errors` -+ ); -+ if (args.frozen) { -+ return false; -+ } -+ this.data.delete(relativeFilename); -+ this.changed = true; -+ return true; -+ } -+ updateMaxErrors(filename, args, ruleToErrorCount) { -+ const removedRules = /* @__PURE__ */ new Set(); -+ let increasedRulesCount = 0; -+ let decreasedRulesCount = 0; -+ this.getMaxErrors(filename); -+ const relativeFilename = this.toRelativePath(filename); -+ const maxErrors = this.data.get(relativeFilename)?.maxErrors ?? /* @__PURE__ */ new Map(); -+ ruleToErrorCount.forEach((errorCount, ruleId) => { -+ const maxErrorCount = maxErrors.get(ruleId) ?? 0; -+ if (errorCount === maxErrorCount) { -+ return; -+ } -+ if (errorCount < maxErrorCount || SeatbeltArgs.ruleSetHas(args.allowIncreaseRules, ruleId)) { -+ SeatbeltArgs.verboseLog( -+ args, -+ () => args.frozen ? `${formatFilename(filename)}: ${formatRuleId(ruleId)}: ${SEATBELT_FROZEN}: didn't update max errors ${maxErrorCount} -> ${errorCount}` : `${formatFilename(filename)}: ${formatRuleId(ruleId)}: update max errors ${maxErrorCount} -> ${errorCount}` -+ ); -+ maxErrors.set(ruleId, errorCount); -+ if (errorCount > maxErrorCount) { -+ increasedRulesCount++; -+ } else { -+ decreasedRulesCount++; -+ } -+ } -+ }); -+ if (args.verbose || args.keepRules !== "all") { -+ maxErrors.forEach((maxErrorCount, ruleId) => { -+ const shouldRemove = maxErrorCount === 0 || !ruleToErrorCount.has(ruleId); -+ if (!shouldRemove) { -+ return; -+ } -+ if (SeatbeltArgs.ruleSetHas(args.keepRules, ruleId)) { -+ SeatbeltArgs.verboseLog( -+ args, -+ () => `${formatFilename(filename)}: ${formatRuleId(ruleId)}: ${SEATBELT_KEEP}: didn't update max errors ${maxErrorCount} -> ${0}` -+ ); -+ return; -+ } -+ SeatbeltArgs.verboseLog( -+ args, -+ () => args.frozen ? `${formatFilename(filename)}: ${formatRuleId(ruleId)}: ${SEATBELT_FROZEN}: didn't update max errors ${maxErrorCount} -> ${0}` : `${formatFilename(filename)}: ${formatRuleId(ruleId)}: update max errors ${maxErrorCount} -> ${0}` -+ ); -+ maxErrors.delete(ruleId); -+ removedRules.add(ruleId); -+ }); -+ } -+ const changed = increasedRulesCount > 0 || decreasedRulesCount > 0 || removedRules.size > 0; -+ if (changed && !args.frozen) { -+ const file = this.data.get(relativeFilename); -+ if (file) { -+ file.maxErrors = maxErrors; -+ } else { -+ this.data.set(relativeFilename, { -+ maxErrors, -+ lines: [] -+ }); -+ } -+ this.changed = true; -+ } -+ return { removedRules, increasedRulesCount, decreasedRulesCount }; -+ } -+ /** Atomic read -> apply delta -> write. Takes an exclusive file lock when `args.threadsafe`. */ -+ updateFileMaxErrors(args, filename, ruleToErrorCount) { -+ return this.withOptionalLock(args, () => { -+ const before = this.getMaxErrors(filename); -+ const ruleToMaxErrorCountBefore = before ? new Map(before) : void 0; -+ const result = this.updateMaxErrors(filename, args, ruleToErrorCount); -+ if (!args.frozen && !args.readOnly) { -+ this.flushChanges(); -+ } -+ return { ruleToMaxErrorCountBefore, ...result }; -+ }); -+ } -+ /** Drop entries whose source file no longer exists. Takes an exclusive file lock when `args.threadsafe`. */ -+ cleanUpRemovedFiles(args) { -+ return this.withOptionalLock(args, () => { -+ let removedFiles = 0; -+ for (const filename of Array.from(this.filenames())) { -+ if (!fs.existsSync(filename)) { -+ if (this.removeFile(filename, args)) { -+ removedFiles++; -+ } -+ } -+ } -+ if (!args.frozen && !args.readOnly) { -+ this.flushChanges(); -+ } -+ return { removedFiles }; -+ }); -+ } -+ withOptionalLock(args, fn) { -+ if (!args.threadsafe) { -+ return fn(); -+ } -+ const lock = new FileLock(`${this.filename}.lock`); -+ lock.waitLock(LOCK_TIMEOUT_MS); -+ try { -+ this.readSync(); -+ return fn(); -+ } finally { -+ lock.unlock(); -+ } -+ } -+ toDataString() { -+ const lines = []; -+ this.data.forEach((fileState, filename) => { -+ if (fileState.maxErrors) { -+ fileState.lines = []; -+ fileState.maxErrors.forEach((maxErrorCount, ruleId) => { -+ fileState.lines.push({ filename, ruleId, maxErrors: maxErrorCount }); -+ }); -+ fileState.lines.sort( -+ (a, b) => a.ruleId === b.ruleId ? 0 : a.ruleId < b.ruleId ? -1 : 1 -+ ); -+ } -+ fileState.lines.forEach((line) => { -+ const encoded = line.encoded ??= encodeLine(line); -+ lines.push(encoded); -+ }); -+ }); -+ lines.sort(); -+ if (this.comments) { -+ return this.comments + "\n\n" + lines.join(""); -+ } else { -+ return lines.join(""); -+ } -+ } -+ readSync() { -+ const nextStateFile = _SeatbeltFile.openSync(this.filename); -+ if (nextStateFile) { -+ this.data = nextStateFile.data; -+ this.changed = false; -+ return true; -+ } -+ return false; -+ } -+ flushChanges() { -+ if (this.changed) { -+ this.writeSync(); -+ this.changed = false; -+ return { updated: true }; -+ } -+ return { updated: false }; -+ } -+ writeSync() { -+ const dataString = this.toDataString(); -+ const dir = nodePath.dirname(this.filename); -+ const base = nodePath.basename(this.filename); -+ const tempFile = nodePath.join( -+ this.useTempDirForWrites ? os.tmpdir() : dir, -+ `.${base}.wip${process.pid}.${Date.now()}.tmp` -+ ); -+ fs.mkdirSync(dir, { recursive: true }); -+ fs.writeFileSync(tempFile, dataString, "utf8"); -+ try { -+ fs.renameSync(tempFile, this.filename); -+ } catch (error) { -+ if (isErrno(error, "EXDEV")) { -+ this.useTempDirForWrites = false; -+ fs.copyFileSync(tempFile, this.filename); -+ fs.rmSync(tempFile); -+ return; -+ } -+ throw error; -+ } -+ } -+ toJSON() { -+ const data = Object.fromEntries( -+ Array.from(this.data.keys()).map((filename) => { -+ const maxErrors = this.getMaxErrors(filename); -+ if (!maxErrors) { -+ throw new Error(`${name} bug: expected errors for existing key`); -+ } -+ return [filename, Object.fromEntries(maxErrors)]; -+ }) -+ ); -+ return { filename: this.filename, data }; -+ } -+ toRelativePath(filename) { -+ if (!nodePath.isAbsolute(filename)) { -+ return filename; -+ } -+ return nodePath.relative(this.dirname, filename); -+ } -+ toAbsolutePath(filename) { -+ if (nodePath.isAbsolute(filename)) { -+ return filename; -+ } -+ return nodePath.resolve(this.dirname, filename); -+ } -+}; -+function parseMaxErrors(lines) { -+ const maxErrors = /* @__PURE__ */ new Map(); -+ lines.forEach((line) => { -+ maxErrors.set(line.ruleId, line.maxErrors); -+ }); -+ return maxErrors; -+} -+ -+export { -+ appendErrorContext, -+ FileLock, -+ SeatbeltFile -+}; -+//# sourceMappingURL=chunk-QGLWIAEE.mjs.map -\ No newline at end of file -diff --git a/node_modules/eslint-seatbelt/dist/chunk-TDWD7IZM.js b/node_modules/eslint-seatbelt/dist/chunk-TDWD7IZM.js -new file mode 100644 -index 0000000..e136721 ---- /dev/null -+++ b/node_modules/eslint-seatbelt/dist/chunk-TDWD7IZM.js -@@ -0,0 +1,453 @@ -+"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { -+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b] -+}) : x)(function(x) { -+ if (typeof require !== "undefined") return require.apply(this, arguments); -+ throw Error('Dynamic require of "' + x + '" is not supported'); -+}); -+ -+// package.json -+var name = "eslint-seatbelt"; -+var version = "0.1.3"; -+var package_default = { -+ name, -+ version, -+ description: "Gradually tighten ESLint rules in your codebase", -+ keywords: [ -+ "eslint", -+ "incremental", -+ "gradual", -+ "workflow", -+ "processor", -+ "linting" -+ ], -+ author: { -+ name: "Jake Teton-Landis", -+ url: "https://jake.tl" -+ }, -+ repository: { -+ type: "git", -+ url: "git+https://github.com/justjake/eslint-seatbelt.git" -+ }, -+ bugs: { -+ url: "https://github.com/justjake/eslint-seatbelt/issues" -+ }, -+ scripts: { -+ build: "./scripts/make-json-schemas.ts && tsc && tsup", -+ test: "node --test --require tsx/cjs $(find src -name '*.test.ts')", -+ lint: "pnpm build && NODE_OPTIONS='--enable-source-maps' eslint ." -+ }, -+ types: "dist/index.d.ts", -+ import: "./dist/index.mjs", -+ main: "dist/index.js", -+ exports: { -+ ".": { -+ types: "./dist/index.d.ts", -+ import: "./dist/index.mjs", -+ default: "./dist/index.js" -+ }, -+ "./api": { -+ types: "./dist/api.d.ts", -+ import: "./dist/api.mjs", -+ default: "./dist/api.js" -+ } -+ }, -+ bin: { -+ "eslint-seatbelt": "./dist/command.js" -+ }, -+ files: [ -+ "!*.tsbuildinfo", -+ "!src/**/*.test.ts", -+ "src", -+ "dist" -+ ], -+ license: "MIT", -+ peerDependencies: { -+ "@types/eslint": "*", -+ eslint: "*" -+ }, -+ peerDependenciesMeta: { -+ eslint: { -+ optional: true -+ }, -+ "@types/eslint": { -+ optional: true -+ } -+ }, -+ devDependencies: { -+ "@eslint/compat": "1.2.3", -+ "@eslint/js": "9.15.0", -+ "@types/eslint__js": "8.42.3", -+ "@types/node": "22.9.0", -+ "@typescript-eslint/rule-tester": "8.14.0", -+ "@typescript-eslint/utils": "8.14.0", -+ eslint: "9.14.0", -+ prettier: "3.3.3", -+ tsup: "8.3.5", -+ tsx: "4.19.2", -+ typescript: "5.6.3", -+ "typescript-eslint": "8.14.0", -+ "typescript-json-schema": "0.65.1" -+ }, -+ dependencies: { -+ "ts-command-line-args": "^2.5.1" -+ }, -+ packageManager: "pnpm@10.2.1+sha1.48adf39a4ab751eda7b73b99447d1f0b6d227e02" -+}; -+ -+// src/SeatbeltConfig.ts -+var _path = require('path'); var _path2 = _interopRequireDefault(_path); -+var _worker_threads = require('worker_threads'); -+ -+// src/repoIntegration.ts -+var _fs = require('fs'); var _fs2 = _interopRequireDefault(_fs); -+ -+function findAncestorDirectory(path2, predicate) { -+ let lastPath = void 0; -+ while (path2 !== lastPath) { -+ if (predicate(path2)) { -+ return path2; -+ } -+ lastPath = path2; -+ path2 = _path2.default.dirname(path2); -+ } -+} -+function isGitRoot(dir) { -+ return _fs2.default.existsSync(_path2.default.join(dir, ".git")); -+} -+function findRepoRoot(path2) { -+ return findAncestorDirectory(path2, isGitRoot); -+} -+ -+// src/SeatbeltConfig.ts -+var SEATBELT_FILE_NAME = "eslint.seatbelt.tsv"; -+var SEATBELT_FROZEN = "SEATBELT_FROZEN"; -+var SEATBELT_INCREASE = "SEATBELT_INCREASE"; -+var SEATBELT_KEEP = "SEATBELT_KEEP"; -+var SEATBELT_FILE = "SEATBELT_FILE"; -+var SEATBELT_PWD = "SEATBELT_PWD"; -+var SEATBELT_DISABLE = "SEATBELT_DISABLE"; -+var SEATBELT_READ_ONLY = "SEATBELT_READ_ONLY"; -+var SEATBELT_THREADSAFE = "SEATBELT_THREADSAFE"; -+var SEATBELT_VERBOSE = "SEATBELT_VERBOSE"; -+var SEATBELT_QUIET = "SEATBELT_QUIET"; -+var SEATBELT_ROOT = "SEATBELT_ROOT"; -+var ENV_VARS = { -+ SEATBELT_FROZEN, -+ SEATBELT_INCREASE, -+ SEATBELT_KEEP, -+ SEATBELT_FILE, -+ SEATBELT_PWD, -+ SEATBELT_DISABLE, -+ SEATBELT_READ_ONLY, -+ SEATBELT_THREADSAFE, -+ SEATBELT_VERBOSE, -+ SEATBELT_QUIET, -+ SEATBELT_ROOT, -+ CI: "CI", -+ JEST_WORKER_ID: "JEST_WORKER_ID" -+}; -+var SeatbeltConfig = { -+ withEnvOverrides(config, env) { -+ return { -+ ...SeatbeltConfig.fromFallbackEnv(env), -+ ...config, -+ ...SeatbeltConfig.fromEnvOverrides(env) -+ }; -+ }, -+ fromFallbackEnv(env, log) { -+ const config = {}; -+ const isCI = SeatbeltEnv.readBooleanEnvVar(env.CI); -+ if (isCI) { -+ config.frozen = true; -+ _optionalChain([log, 'optionalCall', _ => _(`${padVarName("CI")} config.frozen defaults to`, true)]); -+ } -+ if (env.JEST_WORKER_ID) { -+ config.threadsafe = true; -+ _optionalChain([log, 'optionalCall', _2 => _2( -+ `${padVarName("JEST_WORKER_ID")} config.threadsafe defaults to`, -+ true -+ )]); -+ } -+ if (!_worker_threads.isMainThread) { -+ config.threadsafe = true; -+ _optionalChain([log, 'optionalCall', _3 => _3( -+ `${padVarName("worker_threads")} config.threadsafe defaults to`, -+ true -+ )]); -+ } -+ return config; -+ }, -+ fromEnvOverrides(env, log) { -+ const config = { -+ pwd: env[SEATBELT_PWD] || process.cwd() -+ }; -+ const verbose = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_VERBOSE]); -+ if (verbose !== void 0) { -+ config.verbose = verbose; -+ _optionalChain([log, 'optionalCall', _4 => _4(`${padVarName(SEATBELT_VERBOSE)} config.verbose =`, verbose)]); -+ } -+ const seatbeltFile = env[SEATBELT_FILE]; -+ if (seatbeltFile) { -+ const rootRelative = _path2.default.isAbsolute(seatbeltFile) ? seatbeltFile : _path2.default.join(config.pwd, seatbeltFile); -+ config.seatbeltFile = rootRelative; -+ _optionalChain([log, 'optionalCall', _5 => _5(`${padVarName(SEATBELT_FILE)} config.seatbeltFile =`, rootRelative)]); -+ } -+ const disable = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_DISABLE]); -+ if (disable !== void 0) { -+ config.disable = disable; -+ _optionalChain([log, 'optionalCall', _6 => _6(`${padVarName(SEATBELT_DISABLE)} config.disable =`, disable)]); -+ } -+ const frozen = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_FROZEN]); -+ if (frozen !== void 0) { -+ config.frozen = frozen; -+ _optionalChain([log, 'optionalCall', _7 => _7(`${padVarName(SEATBELT_FROZEN)} config.frozen =`, frozen)]); -+ } -+ const readOnly = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_READ_ONLY]); -+ if (readOnly !== void 0) { -+ config.readOnly = readOnly; -+ _optionalChain([log, 'optionalCall', _8 => _8(`${padVarName(SEATBELT_READ_ONLY)} config.readOnly =`, readOnly)]); -+ } -+ const increase = SeatbeltEnv.parseRuleSetEnvVar(env[SEATBELT_INCREASE]); -+ if (increase !== void 0) { -+ config.allowIncreaseRules = increase; -+ _optionalChain([log, 'optionalCall', _9 => _9( -+ `${padVarName(SEATBELT_INCREASE)} config.allowIncreaseRules =`, -+ increase -+ )]); -+ config.readOnly = false; -+ _optionalChain([log, 'optionalCall', _10 => _10( -+ `${padVarName(SEATBELT_INCREASE)} overrides config.readOnly =`, -+ false -+ )]); -+ } -+ const keep = SeatbeltEnv.parseRuleSetEnvVar(env[SEATBELT_KEEP]); -+ if (keep !== void 0) { -+ config.keepRules = keep; -+ _optionalChain([log, 'optionalCall', _11 => _11(`${padVarName(SEATBELT_KEEP)} config.keepRules =`, keep)]); -+ } -+ const threadsafe = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_THREADSAFE]); -+ if (threadsafe !== void 0) { -+ config.threadsafe = threadsafe; -+ _optionalChain([log, 'optionalCall', _12 => _12( -+ `${padVarName(SEATBELT_THREADSAFE)} config.threadsafe =`, -+ threadsafe -+ )]); -+ } -+ const quiet = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_QUIET]); -+ if (quiet !== void 0) { -+ config.quiet = quiet; -+ _optionalChain([log, 'optionalCall', _13 => _13(`${padVarName(SEATBELT_QUIET)} config.quiet =`, quiet)]); -+ } -+ const root = env[SEATBELT_ROOT]; -+ if (root) { -+ config.root = root; -+ _optionalChain([log, 'optionalCall', _14 => _14(`${padVarName(SEATBELT_ROOT)} config.root =`, root)]); -+ } -+ return config; -+ } -+}; -+var SeatbeltEnv = { -+ parseRuleSetEnvVar(value) { -+ if (value === void 0) { -+ return void 0; -+ } -+ if (!value) { -+ return []; -+ } -+ const lower = value.toLowerCase(); -+ if (lower === "all" || lower === "1" || lower === "true") { -+ return "all"; -+ } -+ return value.split(/[\s,]+/g).filter(Boolean); -+ }, -+ readBooleanEnvVar(value) { -+ if (value === void 0 || value === "") { -+ return void 0; -+ } -+ const lower = value.toLowerCase(); -+ if (lower === "false" || lower === "0" || lower === "no") { -+ return false; -+ } -+ return Boolean(value); -+ } -+}; -+var logStdout = (...message) => ( -+ // eslint-disable-next-line no-console -+ console.log(`[${name}]:`, ...message) -+); -+var logStderr = (...message) => ( -+ // eslint-disable-next-line no-console -+ console.error(`[${name}]:`, ...message) -+); -+var SeatbeltArgs = { -+ fromConfig(config) { -+ const cwd = _nullishCoalesce(config.pwd, () => ( process.cwd())); -+ const seatbeltFile = _nullishCoalesce(config.seatbeltFile, () => ( SeatbeltArgs.findSeatbeltFile(cwd))); -+ const root = _nullishCoalesce(_nullishCoalesce(config.root, () => ( findRepoRoot(seatbeltFile))), () => ( _path2.default.dirname(seatbeltFile))); -+ return { -+ seatbeltFile, -+ root, -+ keepRules: typeof config.keepRules === "string" ? config.keepRules : new Set(_nullishCoalesce(config.keepRules, () => ( []))), -+ allowIncreaseRules: typeof config.allowIncreaseRules === "string" ? config.allowIncreaseRules : new Set(_nullishCoalesce(config.allowIncreaseRules, () => ( []))), -+ frozen: _nullishCoalesce(config.frozen, () => ( false)), -+ disable: _nullishCoalesce(config.disable, () => ( false)), -+ readOnly: _nullishCoalesce(config.readOnly, () => ( false)), -+ quiet: _nullishCoalesce(config.quiet, () => ( false)), -+ threadsafe: _nullishCoalesce(config.threadsafe, () => ( false)), -+ verbose: _nullishCoalesce(config.verbose, () => ( false)) -+ }; -+ }, -+ getLogger(args) { -+ if (typeof args.verbose === "function") { -+ return args.verbose; -+ } -+ if (args.verbose === "stdout") { -+ return logStdout; -+ } -+ return logStderr; -+ }, -+ ruleSetHas(ruleSet, ruleId) { -+ return ruleSet === "all" || ruleSet.has(ruleId); -+ }, -+ verboseLog(args, makeMessage) { -+ if (args.verbose) { -+ const message = makeMessage(); -+ const log = SeatbeltArgs.getLogger(args); -+ if (typeof message === "string") { -+ log(message); -+ } else { -+ log(...message); -+ } -+ } -+ }, -+ findSeatbeltFile(cwd) { -+ return `${cwd}/${SEATBELT_FILE_NAME}`; -+ } -+}; -+var envVarMaxLength = 0; -+function padVarName(name2) { -+ envVarMaxLength ||= Math.max( -+ ...Object.values(ENV_VARS).map((name3) => name3.length) -+ ); -+ return `${name2}:`.padEnd(envVarMaxLength + 1); -+} -+function formatFilename(filename) { -+ const relative = _path2.default.relative( -+ _nullishCoalesce(process.env[SEATBELT_PWD], () => ( process.cwd())), -+ filename -+ ); -+ return relative ? relative : filename; -+} -+function formatRuleId(ruleId) { -+ if (ruleId === null) { -+ return `unknown rule`; -+ } -+ return `rule ${ruleId}`; -+} -+ -+// src/jsonSchema/SeatbeltConfigSchema.ts -+var SeatbeltConfigSchema = { -+ description: 'Configuration for seatbelt can be provided in a few ways:\n\n1. Defined in the shared `settings` object in your ESLint config. This\n requires also configuring the `eslint-seatbelt/configure` rule.\n\n ```js\n // in eslint.config.js\n const config = [\n {\n settings: {\n seatbelt: {\n // ...\n }\n },\n rules: {\n "eslint-seatbelt/configure": "error",\n }\n }\n ]\n ```\n\n2. Using the `eslint-seatbelt/configure` rule in your ESLint config.\n This can be used to override settings for specific files in legacy ESLint configs.\n Any configuration provided here will override the shared `settings` object.\n\n ```js\n // in .eslintrc.js\n module.exports = {\n rules: {\n "eslint-seatbelt/configure": "error",\n },\n overrides: [\n {\n files: ["some/path/*"],\n rules: {\n "eslint-seatbelt/configure": ["error", { seatbeltFile: "some/path/eslint.seatbelt.tsv" }]\n },\n },\n ],\n }\n ```\n3. The settings in config files can be overridden with environment variables when running `eslint` or other tools.\n\n ```bash\n SEATBELT_FILE=some/path/eslint.seatbelt.tsv SEATBELT_FROZEN=1 eslint\n ```', -+ type: "object", -+ properties: { -+ seatbeltFile: { -+ description: "The seatbelt file stores the max error counts allowed for each file. Should\nbe an absolute path.\n\nIf not provided, $SEATBELT_PWD/eslint.seatbelt.tsv or $PWD/eslint.seatbelt.tsv will be used.\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n // commonjs\n seatbeltFile: `${__dirname}/eslint.seatbelt.tsv`\n // esm\n seatbeltFile: new URL('./eslint.seatbelt.tsv', import.meta.url).pathname\n }\n }\n }\n]\n```\n\nYou can also set this with environment variable `SEATBELT_FILE`:\n\n```bash\nSEATBELT_FILE=.config/custom-seatbelt-file eslint\n```", -+ type: "string" -+ }, -+ keepRules: { -+ description: 'By default whenever a file is linted and a rule has no errors, that rule\'s\nmax errors for the file is set to zero.\n\nHowever with typescript-eslint, it can be helpful to have two ESLint configs:\n\n- A default ESLint config with only syntactic rules enabled that don\'t\n require typechecking, that runs on developer machines and in their editor.\n- A CI-only ESLint config with only type-aware rules enabled that requires\n typechecking. Since these rules require typechecking, they can be too\n slow to run in interactive contexts.\n\nTo avoid this, set `keepRules` to the names of *disabled but known rules*\nwhile linting.\n\nExample:\n\n```js\n// Default ESLint config\nmodule.exports = [\n {\n settings: {\n seatbelt: {\n keepRules: require(\'./eslint-typed.config.js\').flatMap(config => Object.keys(config.rules ?? {})),\n }\n },\n rules: {\n "no-unused-vars": "error",\n },\n }\n]\n\n// Typechecking-required ESLint config for CI\nmodule.exports = [\n {\n settings: {\n seatbelt: {\n keepRules: require(\'./eslint.config.js\').flatMap(config => Object.keys(config.rules ?? {})),\n }\n },\n rules: {\n // Requires typechecking (slow)\n "@typescript-eslint/no-floating-promises": "error",\n },\n }\n]\n```\n\nYou can also set this with environment variable `SEATBELT_KEEP`:\n\n```bash\nSEATBELT_KEEP="@typescript-eslint/no-floating-promises', -+ anyOf: [ -+ { -+ type: "array", -+ items: { -+ type: "string" -+ } -+ }, -+ { -+ const: "all", -+ type: "string" -+ } -+ ] -+ }, -+ allowIncreaseRules: { -+ description: 'When you enable a rule for the first time, lint with it in this set to set\nthe initial max error counts.\n\nTypically this should be enabled for one lint run only via an environment\nvariable, but it can also be configured via ESLint settings.\n\n```bash\nSEATBELT_INCREASE="@typescript-eslint/no-floating-promises" eslint\n```\n\nYou can set this to `"ALL"` to enable this setting for ALL rules:\n\n```bash\nSEATBELT_INCREASE=ALL eslint\n```\n\n```js\n// in eslint.config.js\n// maybe you have a use-case for this\nconst config = [\n {\n settings: {\n seatbelt: {\n allowIncreaseRules: ["@typescript-eslint/no-floating-promises"],\n }\n }\n }\n]\n```', -+ anyOf: [ -+ { -+ type: "array", -+ items: { -+ type: "string" -+ } -+ }, -+ { -+ const: "all", -+ type: "string" -+ } -+ ] -+ }, -+ frozen: { -+ description: "Error if there is any change in the number of errors in the seatbelt file.\nThis is useful in CI to ensures that developers keep the seatbelt file up-to-date as they fix errors.\n\nIt is enabled by default when environment variable `CI` is set.\n\n```bash\nCI=1 eslint\n```\n\nThis can be set with the `SEATBELT_FROZEN` environment variable.\n\n```bash\nSEATBELT_FROZEN=1 eslint\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n frozen: true,\n }\n }\n }\n]\n```", -+ type: "boolean" -+ }, -+ disable: { -+ description: "Completely disable seatbelt error processing for a lint run while leaving it otherwise configured.\n\nThis can be set with the `SEATBELT_DISABLE` environment variable.\n\n```bash\nSEATBELT_DISABLE=1 eslint\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n disable: true,\n }\n }\n }\n]\n```", -+ type: "boolean" -+ }, -+ readOnly: { -+ description: "When `true`, seatbelt validates error counts (still reporting increases)\nbut never writes the seatbelt file. Keeps the worktree clean in local /\neditor runs; expect an authoritative updater (e.g. post-merge CI) to run\nwith `readOnly: false`.\n\nUnlike `frozen`, does not turn decreases into errors. If both are set,\n`frozen` messaging is preserved and no write occurs.\n\n`SEATBELT_INCREASE` overrides this so intentional loosening is persisted.\n\nDefaults to `false`.\n\nSet via `SEATBELT_READ_ONLY` env var:\n\n```bash\nSEATBELT_READ_ONLY=1 eslint\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n readOnly: !process.env.CI,\n }\n }\n }\n]\n```", -+ type: "boolean" -+ }, -+ quiet: { -+ description: 'Suppress seatbelt\'s informational warning messages (e.g. "tend the garden",\n"thank you for fixing"). When enabled, seatbelt still downgrades errors to\nwarnings and updates the seatbelt file, but the warning messages are not\nemitted as ESLint results. Over-limit errors and frozen-mode warnings are\nalways preserved.\n\nThis is useful when seatbelt warnings create noise in CI logs or editor\nintegrations.\n\nThis can be set with the `SEATBELT_QUIET` environment variable.\n\n```bash\nSEATBELT_QUIET=1 eslint\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n quiet: true,\n }\n }\n }\n]\n```', -+ type: "boolean" -+ }, -+ threadsafe: { -+ description: "By default seatbelt assumes that only one ESLint process will read and\nwrite to the seatbelt file at a time.\n\nThis should be set to `true` if you use a parallel ESLint runner similar to\njest-runner-eslint to avoid losing updates during parallel writes to the\nseatbelt file.\n\nWhen enabled, seatbelt creates temporary lock files to serialize updates to\nthe seatbelt file. This comes at a small performance cost.\n\nThis is enabled by default when run with Jest (environment variable `JEST_WORKER_ID` is set)\nor inside a Node `worker_threads` worker (e.g. ESLint `--concurrency`).\n\nIt can also be set with environment variable `SEATBELT_THREADSAFE`:\n\n```bash\nSEATBELT_THREADSAFE=1 eslint-parallel\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n threadsafe: true,\n }\n }\n }\n]\n```", -+ type: "boolean" -+ }, -+ verbose: { -+ description: "Enable verbose logging.\n\nThis can be set with the `SEATBELT_VERBOSE` environment variable.\n\n```bash\nSEATBELT_VERBOSE=1 eslint\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n verbose: true,\n }\n }\n }\n]\n```\n\nIf set to a function (like `console.error`), that function will be called with the log messages.\nThe default logger when set to `true` is `console.error`.", -+ anyOf: [ -+ { -+ enum: [false, "stderr", "stdout", true] -+ }, -+ { -+ type: "object" -+ } -+ ] -+ }, -+ root: { -+ description: "Repository or project root.\nBy default this is inferred from `seatbeltFile` by checking ancestor directories for `.git`.\nUsed for editor integration to disable seatbelt during git actions like rebase or merge.\n\nThis can be set with the `SEATBELT_ROOT` environment variable.", -+ type: "string" -+ } -+ }, -+ $schema: "http://json-schema.org/draft-07/schema#" -+}; -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+exports.__require = __require; exports.name = name; exports.version = version; exports.package_default = package_default; exports.SEATBELT_FILE_NAME = SEATBELT_FILE_NAME; exports.SEATBELT_FROZEN = SEATBELT_FROZEN; exports.SEATBELT_INCREASE = SEATBELT_INCREASE; exports.SEATBELT_KEEP = SEATBELT_KEEP; exports.SEATBELT_FILE = SEATBELT_FILE; exports.SEATBELT_PWD = SEATBELT_PWD; exports.SEATBELT_DISABLE = SEATBELT_DISABLE; exports.SEATBELT_READ_ONLY = SEATBELT_READ_ONLY; exports.SEATBELT_THREADSAFE = SEATBELT_THREADSAFE; exports.SEATBELT_VERBOSE = SEATBELT_VERBOSE; exports.SEATBELT_QUIET = SEATBELT_QUIET; exports.SEATBELT_ROOT = SEATBELT_ROOT; exports.SeatbeltConfig = SeatbeltConfig; exports.SeatbeltEnv = SeatbeltEnv; exports.logStdout = logStdout; exports.logStderr = logStderr; exports.SeatbeltArgs = SeatbeltArgs; exports.padVarName = padVarName; exports.formatFilename = formatFilename; exports.formatRuleId = formatRuleId; exports.SeatbeltConfigSchema = SeatbeltConfigSchema; -+//# sourceMappingURL=chunk-TDWD7IZM.js.map -\ No newline at end of file -diff --git a/node_modules/eslint-seatbelt/dist/chunk-U6KIQG2A.mjs b/node_modules/eslint-seatbelt/dist/chunk-U6KIQG2A.mjs -new file mode 100644 -index 0000000..cffef9f ---- /dev/null -+++ b/node_modules/eslint-seatbelt/dist/chunk-U6KIQG2A.mjs -@@ -0,0 +1,453 @@ -+var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { -+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b] -+}) : x)(function(x) { -+ if (typeof require !== "undefined") return require.apply(this, arguments); -+ throw Error('Dynamic require of "' + x + '" is not supported'); -+}); -+ -+// package.json -+var name = "eslint-seatbelt"; -+var version = "0.1.3"; -+var package_default = { -+ name, -+ version, -+ description: "Gradually tighten ESLint rules in your codebase", -+ keywords: [ -+ "eslint", -+ "incremental", -+ "gradual", -+ "workflow", -+ "processor", -+ "linting" -+ ], -+ author: { -+ name: "Jake Teton-Landis", -+ url: "https://jake.tl" -+ }, -+ repository: { -+ type: "git", -+ url: "git+https://github.com/justjake/eslint-seatbelt.git" -+ }, -+ bugs: { -+ url: "https://github.com/justjake/eslint-seatbelt/issues" -+ }, -+ scripts: { -+ build: "./scripts/make-json-schemas.ts && tsc && tsup", -+ test: "node --test --require tsx/cjs $(find src -name '*.test.ts')", -+ lint: "pnpm build && NODE_OPTIONS='--enable-source-maps' eslint ." -+ }, -+ types: "dist/index.d.ts", -+ import: "./dist/index.mjs", -+ main: "dist/index.js", -+ exports: { -+ ".": { -+ types: "./dist/index.d.ts", -+ import: "./dist/index.mjs", -+ default: "./dist/index.js" -+ }, -+ "./api": { -+ types: "./dist/api.d.ts", -+ import: "./dist/api.mjs", -+ default: "./dist/api.js" -+ } -+ }, -+ bin: { -+ "eslint-seatbelt": "./dist/command.js" -+ }, -+ files: [ -+ "!*.tsbuildinfo", -+ "!src/**/*.test.ts", -+ "src", -+ "dist" -+ ], -+ license: "MIT", -+ peerDependencies: { -+ "@types/eslint": "*", -+ eslint: "*" -+ }, -+ peerDependenciesMeta: { -+ eslint: { -+ optional: true -+ }, -+ "@types/eslint": { -+ optional: true -+ } -+ }, -+ devDependencies: { -+ "@eslint/compat": "1.2.3", -+ "@eslint/js": "9.15.0", -+ "@types/eslint__js": "8.42.3", -+ "@types/node": "22.9.0", -+ "@typescript-eslint/rule-tester": "8.14.0", -+ "@typescript-eslint/utils": "8.14.0", -+ eslint: "9.14.0", -+ prettier: "3.3.3", -+ tsup: "8.3.5", -+ tsx: "4.19.2", -+ typescript: "5.6.3", -+ "typescript-eslint": "8.14.0", -+ "typescript-json-schema": "0.65.1" -+ }, -+ dependencies: { -+ "ts-command-line-args": "^2.5.1" -+ }, -+ packageManager: "pnpm@10.2.1+sha1.48adf39a4ab751eda7b73b99447d1f0b6d227e02" -+}; -+ -+// src/SeatbeltConfig.ts -+import path from "node:path"; -+import { isMainThread } from "node:worker_threads"; -+ -+// src/repoIntegration.ts -+import fs from "node:fs"; -+import nodePath from "node:path"; -+function findAncestorDirectory(path2, predicate) { -+ let lastPath = void 0; -+ while (path2 !== lastPath) { -+ if (predicate(path2)) { -+ return path2; -+ } -+ lastPath = path2; -+ path2 = nodePath.dirname(path2); -+ } -+} -+function isGitRoot(dir) { -+ return fs.existsSync(nodePath.join(dir, ".git")); -+} -+function findRepoRoot(path2) { -+ return findAncestorDirectory(path2, isGitRoot); -+} -+ -+// src/SeatbeltConfig.ts -+var SEATBELT_FILE_NAME = "eslint.seatbelt.tsv"; -+var SEATBELT_FROZEN = "SEATBELT_FROZEN"; -+var SEATBELT_INCREASE = "SEATBELT_INCREASE"; -+var SEATBELT_KEEP = "SEATBELT_KEEP"; -+var SEATBELT_FILE = "SEATBELT_FILE"; -+var SEATBELT_PWD = "SEATBELT_PWD"; -+var SEATBELT_DISABLE = "SEATBELT_DISABLE"; -+var SEATBELT_READ_ONLY = "SEATBELT_READ_ONLY"; -+var SEATBELT_THREADSAFE = "SEATBELT_THREADSAFE"; -+var SEATBELT_VERBOSE = "SEATBELT_VERBOSE"; -+var SEATBELT_QUIET = "SEATBELT_QUIET"; -+var SEATBELT_ROOT = "SEATBELT_ROOT"; -+var ENV_VARS = { -+ SEATBELT_FROZEN, -+ SEATBELT_INCREASE, -+ SEATBELT_KEEP, -+ SEATBELT_FILE, -+ SEATBELT_PWD, -+ SEATBELT_DISABLE, -+ SEATBELT_READ_ONLY, -+ SEATBELT_THREADSAFE, -+ SEATBELT_VERBOSE, -+ SEATBELT_QUIET, -+ SEATBELT_ROOT, -+ CI: "CI", -+ JEST_WORKER_ID: "JEST_WORKER_ID" -+}; -+var SeatbeltConfig = { -+ withEnvOverrides(config, env) { -+ return { -+ ...SeatbeltConfig.fromFallbackEnv(env), -+ ...config, -+ ...SeatbeltConfig.fromEnvOverrides(env) -+ }; -+ }, -+ fromFallbackEnv(env, log) { -+ const config = {}; -+ const isCI = SeatbeltEnv.readBooleanEnvVar(env.CI); -+ if (isCI) { -+ config.frozen = true; -+ log?.(`${padVarName("CI")} config.frozen defaults to`, true); -+ } -+ if (env.JEST_WORKER_ID) { -+ config.threadsafe = true; -+ log?.( -+ `${padVarName("JEST_WORKER_ID")} config.threadsafe defaults to`, -+ true -+ ); -+ } -+ if (!isMainThread) { -+ config.threadsafe = true; -+ log?.( -+ `${padVarName("worker_threads")} config.threadsafe defaults to`, -+ true -+ ); -+ } -+ return config; -+ }, -+ fromEnvOverrides(env, log) { -+ const config = { -+ pwd: env[SEATBELT_PWD] || process.cwd() -+ }; -+ const verbose = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_VERBOSE]); -+ if (verbose !== void 0) { -+ config.verbose = verbose; -+ log?.(`${padVarName(SEATBELT_VERBOSE)} config.verbose =`, verbose); -+ } -+ const seatbeltFile = env[SEATBELT_FILE]; -+ if (seatbeltFile) { -+ const rootRelative = path.isAbsolute(seatbeltFile) ? seatbeltFile : path.join(config.pwd, seatbeltFile); -+ config.seatbeltFile = rootRelative; -+ log?.(`${padVarName(SEATBELT_FILE)} config.seatbeltFile =`, rootRelative); -+ } -+ const disable = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_DISABLE]); -+ if (disable !== void 0) { -+ config.disable = disable; -+ log?.(`${padVarName(SEATBELT_DISABLE)} config.disable =`, disable); -+ } -+ const frozen = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_FROZEN]); -+ if (frozen !== void 0) { -+ config.frozen = frozen; -+ log?.(`${padVarName(SEATBELT_FROZEN)} config.frozen =`, frozen); -+ } -+ const readOnly = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_READ_ONLY]); -+ if (readOnly !== void 0) { -+ config.readOnly = readOnly; -+ log?.(`${padVarName(SEATBELT_READ_ONLY)} config.readOnly =`, readOnly); -+ } -+ const increase = SeatbeltEnv.parseRuleSetEnvVar(env[SEATBELT_INCREASE]); -+ if (increase !== void 0) { -+ config.allowIncreaseRules = increase; -+ log?.( -+ `${padVarName(SEATBELT_INCREASE)} config.allowIncreaseRules =`, -+ increase -+ ); -+ config.readOnly = false; -+ log?.( -+ `${padVarName(SEATBELT_INCREASE)} overrides config.readOnly =`, -+ false -+ ); -+ } -+ const keep = SeatbeltEnv.parseRuleSetEnvVar(env[SEATBELT_KEEP]); -+ if (keep !== void 0) { -+ config.keepRules = keep; -+ log?.(`${padVarName(SEATBELT_KEEP)} config.keepRules =`, keep); -+ } -+ const threadsafe = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_THREADSAFE]); -+ if (threadsafe !== void 0) { -+ config.threadsafe = threadsafe; -+ log?.( -+ `${padVarName(SEATBELT_THREADSAFE)} config.threadsafe =`, -+ threadsafe -+ ); -+ } -+ const quiet = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_QUIET]); -+ if (quiet !== void 0) { -+ config.quiet = quiet; -+ log?.(`${padVarName(SEATBELT_QUIET)} config.quiet =`, quiet); -+ } -+ const root = env[SEATBELT_ROOT]; -+ if (root) { -+ config.root = root; -+ log?.(`${padVarName(SEATBELT_ROOT)} config.root =`, root); -+ } -+ return config; -+ } -+}; -+var SeatbeltEnv = { -+ parseRuleSetEnvVar(value) { -+ if (value === void 0) { -+ return void 0; -+ } -+ if (!value) { -+ return []; -+ } -+ const lower = value.toLowerCase(); -+ if (lower === "all" || lower === "1" || lower === "true") { -+ return "all"; -+ } -+ return value.split(/[\s,]+/g).filter(Boolean); -+ }, -+ readBooleanEnvVar(value) { -+ if (value === void 0 || value === "") { -+ return void 0; -+ } -+ const lower = value.toLowerCase(); -+ if (lower === "false" || lower === "0" || lower === "no") { -+ return false; -+ } -+ return Boolean(value); -+ } -+}; -+var logStdout = (...message) => ( -+ // eslint-disable-next-line no-console -+ console.log(`[${name}]:`, ...message) -+); -+var logStderr = (...message) => ( -+ // eslint-disable-next-line no-console -+ console.error(`[${name}]:`, ...message) -+); -+var SeatbeltArgs = { -+ fromConfig(config) { -+ const cwd = config.pwd ?? process.cwd(); -+ const seatbeltFile = config.seatbeltFile ?? SeatbeltArgs.findSeatbeltFile(cwd); -+ const root = config.root ?? findRepoRoot(seatbeltFile) ?? path.dirname(seatbeltFile); -+ return { -+ seatbeltFile, -+ root, -+ keepRules: typeof config.keepRules === "string" ? config.keepRules : new Set(config.keepRules ?? []), -+ allowIncreaseRules: typeof config.allowIncreaseRules === "string" ? config.allowIncreaseRules : new Set(config.allowIncreaseRules ?? []), -+ frozen: config.frozen ?? false, -+ disable: config.disable ?? false, -+ readOnly: config.readOnly ?? false, -+ quiet: config.quiet ?? false, -+ threadsafe: config.threadsafe ?? false, -+ verbose: config.verbose ?? false -+ }; -+ }, -+ getLogger(args) { -+ if (typeof args.verbose === "function") { -+ return args.verbose; -+ } -+ if (args.verbose === "stdout") { -+ return logStdout; -+ } -+ return logStderr; -+ }, -+ ruleSetHas(ruleSet, ruleId) { -+ return ruleSet === "all" || ruleSet.has(ruleId); -+ }, -+ verboseLog(args, makeMessage) { -+ if (args.verbose) { -+ const message = makeMessage(); -+ const log = SeatbeltArgs.getLogger(args); -+ if (typeof message === "string") { -+ log(message); -+ } else { -+ log(...message); -+ } -+ } -+ }, -+ findSeatbeltFile(cwd) { -+ return `${cwd}/${SEATBELT_FILE_NAME}`; -+ } -+}; -+var envVarMaxLength = 0; -+function padVarName(name2) { -+ envVarMaxLength ||= Math.max( -+ ...Object.values(ENV_VARS).map((name3) => name3.length) -+ ); -+ return `${name2}:`.padEnd(envVarMaxLength + 1); -+} -+function formatFilename(filename) { -+ const relative = path.relative( -+ process.env[SEATBELT_PWD] ?? process.cwd(), -+ filename -+ ); -+ return relative ? relative : filename; -+} -+function formatRuleId(ruleId) { -+ if (ruleId === null) { -+ return `unknown rule`; -+ } -+ return `rule ${ruleId}`; -+} -+ -+// src/jsonSchema/SeatbeltConfigSchema.ts -+var SeatbeltConfigSchema = { -+ description: 'Configuration for seatbelt can be provided in a few ways:\n\n1. Defined in the shared `settings` object in your ESLint config. This\n requires also configuring the `eslint-seatbelt/configure` rule.\n\n ```js\n // in eslint.config.js\n const config = [\n {\n settings: {\n seatbelt: {\n // ...\n }\n },\n rules: {\n "eslint-seatbelt/configure": "error",\n }\n }\n ]\n ```\n\n2. Using the `eslint-seatbelt/configure` rule in your ESLint config.\n This can be used to override settings for specific files in legacy ESLint configs.\n Any configuration provided here will override the shared `settings` object.\n\n ```js\n // in .eslintrc.js\n module.exports = {\n rules: {\n "eslint-seatbelt/configure": "error",\n },\n overrides: [\n {\n files: ["some/path/*"],\n rules: {\n "eslint-seatbelt/configure": ["error", { seatbeltFile: "some/path/eslint.seatbelt.tsv" }]\n },\n },\n ],\n }\n ```\n3. The settings in config files can be overridden with environment variables when running `eslint` or other tools.\n\n ```bash\n SEATBELT_FILE=some/path/eslint.seatbelt.tsv SEATBELT_FROZEN=1 eslint\n ```', -+ type: "object", -+ properties: { -+ seatbeltFile: { -+ description: "The seatbelt file stores the max error counts allowed for each file. Should\nbe an absolute path.\n\nIf not provided, $SEATBELT_PWD/eslint.seatbelt.tsv or $PWD/eslint.seatbelt.tsv will be used.\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n // commonjs\n seatbeltFile: `${__dirname}/eslint.seatbelt.tsv`\n // esm\n seatbeltFile: new URL('./eslint.seatbelt.tsv', import.meta.url).pathname\n }\n }\n }\n]\n```\n\nYou can also set this with environment variable `SEATBELT_FILE`:\n\n```bash\nSEATBELT_FILE=.config/custom-seatbelt-file eslint\n```", -+ type: "string" -+ }, -+ keepRules: { -+ description: 'By default whenever a file is linted and a rule has no errors, that rule\'s\nmax errors for the file is set to zero.\n\nHowever with typescript-eslint, it can be helpful to have two ESLint configs:\n\n- A default ESLint config with only syntactic rules enabled that don\'t\n require typechecking, that runs on developer machines and in their editor.\n- A CI-only ESLint config with only type-aware rules enabled that requires\n typechecking. Since these rules require typechecking, they can be too\n slow to run in interactive contexts.\n\nTo avoid this, set `keepRules` to the names of *disabled but known rules*\nwhile linting.\n\nExample:\n\n```js\n// Default ESLint config\nmodule.exports = [\n {\n settings: {\n seatbelt: {\n keepRules: require(\'./eslint-typed.config.js\').flatMap(config => Object.keys(config.rules ?? {})),\n }\n },\n rules: {\n "no-unused-vars": "error",\n },\n }\n]\n\n// Typechecking-required ESLint config for CI\nmodule.exports = [\n {\n settings: {\n seatbelt: {\n keepRules: require(\'./eslint.config.js\').flatMap(config => Object.keys(config.rules ?? {})),\n }\n },\n rules: {\n // Requires typechecking (slow)\n "@typescript-eslint/no-floating-promises": "error",\n },\n }\n]\n```\n\nYou can also set this with environment variable `SEATBELT_KEEP`:\n\n```bash\nSEATBELT_KEEP="@typescript-eslint/no-floating-promises', -+ anyOf: [ -+ { -+ type: "array", -+ items: { -+ type: "string" -+ } -+ }, -+ { -+ const: "all", -+ type: "string" -+ } -+ ] -+ }, -+ allowIncreaseRules: { -+ description: 'When you enable a rule for the first time, lint with it in this set to set\nthe initial max error counts.\n\nTypically this should be enabled for one lint run only via an environment\nvariable, but it can also be configured via ESLint settings.\n\n```bash\nSEATBELT_INCREASE="@typescript-eslint/no-floating-promises" eslint\n```\n\nYou can set this to `"ALL"` to enable this setting for ALL rules:\n\n```bash\nSEATBELT_INCREASE=ALL eslint\n```\n\n```js\n// in eslint.config.js\n// maybe you have a use-case for this\nconst config = [\n {\n settings: {\n seatbelt: {\n allowIncreaseRules: ["@typescript-eslint/no-floating-promises"],\n }\n }\n }\n]\n```', -+ anyOf: [ -+ { -+ type: "array", -+ items: { -+ type: "string" -+ } -+ }, -+ { -+ const: "all", -+ type: "string" -+ } -+ ] -+ }, -+ frozen: { -+ description: "Error if there is any change in the number of errors in the seatbelt file.\nThis is useful in CI to ensures that developers keep the seatbelt file up-to-date as they fix errors.\n\nIt is enabled by default when environment variable `CI` is set.\n\n```bash\nCI=1 eslint\n```\n\nThis can be set with the `SEATBELT_FROZEN` environment variable.\n\n```bash\nSEATBELT_FROZEN=1 eslint\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n frozen: true,\n }\n }\n }\n]\n```", -+ type: "boolean" -+ }, -+ disable: { -+ description: "Completely disable seatbelt error processing for a lint run while leaving it otherwise configured.\n\nThis can be set with the `SEATBELT_DISABLE` environment variable.\n\n```bash\nSEATBELT_DISABLE=1 eslint\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n disable: true,\n }\n }\n }\n]\n```", -+ type: "boolean" -+ }, -+ readOnly: { -+ description: "When `true`, seatbelt validates error counts (still reporting increases)\nbut never writes the seatbelt file. Keeps the worktree clean in local /\neditor runs; expect an authoritative updater (e.g. post-merge CI) to run\nwith `readOnly: false`.\n\nUnlike `frozen`, does not turn decreases into errors. If both are set,\n`frozen` messaging is preserved and no write occurs.\n\n`SEATBELT_INCREASE` overrides this so intentional loosening is persisted.\n\nDefaults to `false`.\n\nSet via `SEATBELT_READ_ONLY` env var:\n\n```bash\nSEATBELT_READ_ONLY=1 eslint\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n readOnly: !process.env.CI,\n }\n }\n }\n]\n```", -+ type: "boolean" -+ }, -+ quiet: { -+ description: 'Suppress seatbelt\'s informational warning messages (e.g. "tend the garden",\n"thank you for fixing"). When enabled, seatbelt still downgrades errors to\nwarnings and updates the seatbelt file, but the warning messages are not\nemitted as ESLint results. Over-limit errors and frozen-mode warnings are\nalways preserved.\n\nThis is useful when seatbelt warnings create noise in CI logs or editor\nintegrations.\n\nThis can be set with the `SEATBELT_QUIET` environment variable.\n\n```bash\nSEATBELT_QUIET=1 eslint\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n quiet: true,\n }\n }\n }\n]\n```', -+ type: "boolean" -+ }, -+ threadsafe: { -+ description: "By default seatbelt assumes that only one ESLint process will read and\nwrite to the seatbelt file at a time.\n\nThis should be set to `true` if you use a parallel ESLint runner similar to\njest-runner-eslint to avoid losing updates during parallel writes to the\nseatbelt file.\n\nWhen enabled, seatbelt creates temporary lock files to serialize updates to\nthe seatbelt file. This comes at a small performance cost.\n\nThis is enabled by default when run with Jest (environment variable `JEST_WORKER_ID` is set)\nor inside a Node `worker_threads` worker (e.g. ESLint `--concurrency`).\n\nIt can also be set with environment variable `SEATBELT_THREADSAFE`:\n\n```bash\nSEATBELT_THREADSAFE=1 eslint-parallel\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n threadsafe: true,\n }\n }\n }\n]\n```", -+ type: "boolean" -+ }, -+ verbose: { -+ description: "Enable verbose logging.\n\nThis can be set with the `SEATBELT_VERBOSE` environment variable.\n\n```bash\nSEATBELT_VERBOSE=1 eslint\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n verbose: true,\n }\n }\n }\n]\n```\n\nIf set to a function (like `console.error`), that function will be called with the log messages.\nThe default logger when set to `true` is `console.error`.", -+ anyOf: [ -+ { -+ enum: [false, "stderr", "stdout", true] -+ }, -+ { -+ type: "object" -+ } -+ ] -+ }, -+ root: { -+ description: "Repository or project root.\nBy default this is inferred from `seatbeltFile` by checking ancestor directories for `.git`.\nUsed for editor integration to disable seatbelt during git actions like rebase or merge.\n\nThis can be set with the `SEATBELT_ROOT` environment variable.", -+ type: "string" -+ } -+ }, -+ $schema: "http://json-schema.org/draft-07/schema#" -+}; -+ -+export { -+ __require, -+ name, -+ version, -+ package_default, -+ SEATBELT_FILE_NAME, -+ SEATBELT_FROZEN, -+ SEATBELT_INCREASE, -+ SEATBELT_KEEP, -+ SEATBELT_FILE, -+ SEATBELT_PWD, -+ SEATBELT_DISABLE, -+ SEATBELT_READ_ONLY, -+ SEATBELT_THREADSAFE, -+ SEATBELT_VERBOSE, -+ SEATBELT_QUIET, -+ SEATBELT_ROOT, -+ SeatbeltConfig, -+ SeatbeltEnv, -+ logStdout, -+ logStderr, -+ SeatbeltArgs, -+ padVarName, -+ formatFilename, -+ formatRuleId, -+ SeatbeltConfigSchema -+}; -+//# sourceMappingURL=chunk-U6KIQG2A.mjs.map -\ No newline at end of file -diff --git a/node_modules/eslint-seatbelt/dist/chunk-ULACHCKT.mjs b/node_modules/eslint-seatbelt/dist/chunk-ULACHCKT.mjs -deleted file mode 100644 -index b90ad17..0000000 ---- a/node_modules/eslint-seatbelt/dist/chunk-ULACHCKT.mjs -+++ /dev/null -@@ -1,435 +0,0 @@ --var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { -- get: (a, b) => (typeof require !== "undefined" ? require : a)[b] --}) : x)(function(x) { -- if (typeof require !== "undefined") return require.apply(this, arguments); -- throw Error('Dynamic require of "' + x + '" is not supported'); --}); -- --// package.json --var name = "eslint-seatbelt"; --var version = "0.1.3"; --var package_default = { -- name, -- version, -- description: "Gradually tighten ESLint rules in your codebase", -- keywords: [ -- "eslint", -- "incremental", -- "gradual", -- "workflow", -- "processor", -- "linting" -- ], -- author: { -- name: "Jake Teton-Landis", -- url: "https://jake.tl" -- }, -- repository: { -- type: "git", -- url: "git+https://github.com/justjake/eslint-seatbelt.git" -- }, -- bugs: { -- url: "https://github.com/justjake/eslint-seatbelt/issues" -- }, -- scripts: { -- build: "./scripts/make-json-schemas.ts && tsc && tsup", -- test: "node --test --require tsx/cjs $(find src -name '*.test.ts')", -- lint: "pnpm build && NODE_OPTIONS='--enable-source-maps' eslint ." -- }, -- types: "dist/index.d.ts", -- import: "./dist/index.mjs", -- main: "dist/index.js", -- exports: { -- ".": { -- types: "./dist/index.d.ts", -- import: "./dist/index.mjs", -- default: "./dist/index.js" -- }, -- "./api": { -- types: "./dist/api.d.ts", -- import: "./dist/api.mjs", -- default: "./dist/api.js" -- } -- }, -- bin: { -- "eslint-seatbelt": "./dist/command.js" -- }, -- files: [ -- "!*.tsbuildinfo", -- "!src/**/*.test.ts", -- "src", -- "dist" -- ], -- license: "MIT", -- peerDependencies: { -- "@types/eslint": "*", -- eslint: "*" -- }, -- peerDependenciesMeta: { -- eslint: { -- optional: true -- }, -- "@types/eslint": { -- optional: true -- } -- }, -- devDependencies: { -- "@eslint/compat": "1.2.3", -- "@eslint/js": "9.15.0", -- "@types/eslint__js": "8.42.3", -- "@types/node": "22.9.0", -- "@typescript-eslint/rule-tester": "8.14.0", -- "@typescript-eslint/utils": "8.14.0", -- eslint: "9.14.0", -- prettier: "3.3.3", -- tsup: "8.3.5", -- tsx: "4.19.2", -- typescript: "5.6.3", -- "typescript-eslint": "8.14.0", -- "typescript-json-schema": "0.65.1" -- }, -- dependencies: { -- "ts-command-line-args": "^2.5.1" -- }, -- packageManager: "pnpm@10.2.1+sha1.48adf39a4ab751eda7b73b99447d1f0b6d227e02" --}; -- --// src/SeatbeltConfig.ts --import path from "node:path"; --import { isMainThread } from "node:worker_threads"; -- --// src/repoIntegration.ts --import fs from "node:fs"; --import nodePath from "node:path"; --function findAncestorDirectory(path2, predicate) { -- let lastPath = void 0; -- while (path2 !== lastPath) { -- if (predicate(path2)) { -- return path2; -- } -- lastPath = path2; -- path2 = nodePath.dirname(path2); -- } --} --function isGitRoot(dir) { -- return fs.existsSync(nodePath.join(dir, ".git")); --} --function findRepoRoot(path2) { -- return findAncestorDirectory(path2, isGitRoot); --} -- --// src/SeatbeltConfig.ts --var SEATBELT_FILE_NAME = "eslint.seatbelt.tsv"; --var SEATBELT_FROZEN = "SEATBELT_FROZEN"; --var SEATBELT_INCREASE = "SEATBELT_INCREASE"; --var SEATBELT_KEEP = "SEATBELT_KEEP"; --var SEATBELT_FILE = "SEATBELT_FILE"; --var SEATBELT_PWD = "SEATBELT_PWD"; --var SEATBELT_DISABLE = "SEATBELT_DISABLE"; --var SEATBELT_THREADSAFE = "SEATBELT_THREADSAFE"; --var SEATBELT_VERBOSE = "SEATBELT_VERBOSE"; --var SEATBELT_QUIET = "SEATBELT_QUIET"; --var SEATBELT_ROOT = "SEATBELT_ROOT"; --var ENV_VARS = { -- SEATBELT_FROZEN, -- SEATBELT_INCREASE, -- SEATBELT_KEEP, -- SEATBELT_FILE, -- SEATBELT_PWD, -- SEATBELT_DISABLE, -- SEATBELT_THREADSAFE, -- SEATBELT_VERBOSE, -- SEATBELT_QUIET, -- SEATBELT_ROOT, -- CI: "CI", -- JEST_WORKER_ID: "JEST_WORKER_ID" --}; --var SeatbeltConfig = { -- withEnvOverrides(config, env) { -- return { -- ...SeatbeltConfig.fromFallbackEnv(env), -- ...config, -- ...SeatbeltConfig.fromEnvOverrides(env) -- }; -- }, -- fromFallbackEnv(env, log) { -- const config = {}; -- const isCI = SeatbeltEnv.readBooleanEnvVar(env.CI); -- if (isCI) { -- config.frozen = true; -- log?.(`${padVarName("CI")} config.frozen defaults to`, true); -- } -- if (env.JEST_WORKER_ID) { -- config.threadsafe = true; -- log?.( -- `${padVarName("JEST_WORKER_ID")} config.threadsafe defaults to`, -- true -- ); -- } -- if (!isMainThread) { -- config.threadsafe = true; -- log?.( -- `${padVarName("worker_threads")} config.threadsafe defaults to`, -- true -- ); -- } -- return config; -- }, -- fromEnvOverrides(env, log) { -- const config = { -- pwd: env[SEATBELT_PWD] || process.cwd() -- }; -- const verbose = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_VERBOSE]); -- if (verbose !== void 0) { -- config.verbose = verbose; -- log?.(`${padVarName(SEATBELT_VERBOSE)} config.verbose =`, verbose); -- } -- const seatbeltFile = env[SEATBELT_FILE]; -- if (seatbeltFile) { -- const rootRelative = path.isAbsolute(seatbeltFile) ? seatbeltFile : path.join(config.pwd, seatbeltFile); -- config.seatbeltFile = rootRelative; -- log?.(`${padVarName(SEATBELT_FILE)} config.seatbeltFile =`, rootRelative); -- } -- const disable = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_DISABLE]); -- if (disable !== void 0) { -- config.disable = disable; -- log?.(`${padVarName(SEATBELT_DISABLE)} config.disable =`, disable); -- } -- const frozen = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_FROZEN]); -- if (frozen !== void 0) { -- config.frozen = frozen; -- log?.(`${padVarName(SEATBELT_FROZEN)} config.frozen =`, frozen); -- } -- const increase = SeatbeltEnv.parseRuleSetEnvVar(env[SEATBELT_INCREASE]); -- if (increase !== void 0) { -- config.allowIncreaseRules = increase; -- log?.( -- `${padVarName(SEATBELT_INCREASE)} config.allowIncreaseRules =`, -- increase -- ); -- } -- const keep = SeatbeltEnv.parseRuleSetEnvVar(env[SEATBELT_KEEP]); -- if (keep !== void 0) { -- config.keepRules = keep; -- log?.(`${padVarName(SEATBELT_KEEP)} config.keepRules =`, keep); -- } -- const threadsafe = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_THREADSAFE]); -- if (threadsafe !== void 0) { -- config.threadsafe = threadsafe; -- log?.( -- `${padVarName(SEATBELT_THREADSAFE)} config.threadsafe =`, -- threadsafe -- ); -- } -- const quiet = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_QUIET]); -- if (quiet !== void 0) { -- config.quiet = quiet; -- log?.(`${padVarName(SEATBELT_QUIET)} config.quiet =`, quiet); -- } -- const root = env[SEATBELT_ROOT]; -- if (root) { -- config.root = root; -- log?.(`${padVarName(SEATBELT_ROOT)} config.root =`, root); -- } -- return config; -- } --}; --var SeatbeltEnv = { -- parseRuleSetEnvVar(value) { -- if (value === void 0) { -- return void 0; -- } -- if (!value) { -- return []; -- } -- const lower = value.toLowerCase(); -- if (lower === "all" || lower === "1" || lower === "true") { -- return "all"; -- } -- return value.split(/[\s,]+/g).filter(Boolean); -- }, -- readBooleanEnvVar(value) { -- if (value === void 0 || value === "") { -- return void 0; -- } -- const lower = value.toLowerCase(); -- if (lower === "false" || lower === "0" || lower === "no") { -- return false; -- } -- return Boolean(value); -- } --}; --var logStdout = (...message) => ( -- // eslint-disable-next-line no-console -- console.log(`[${name}]:`, ...message) --); --var logStderr = (...message) => ( -- // eslint-disable-next-line no-console -- console.error(`[${name}]:`, ...message) --); --var SeatbeltArgs = { -- fromConfig(config) { -- const cwd = config.pwd ?? process.cwd(); -- const seatbeltFile = config.seatbeltFile ?? SeatbeltArgs.findSeatbeltFile(cwd); -- const root = config.root ?? findRepoRoot(seatbeltFile) ?? path.dirname(seatbeltFile); -- return { -- seatbeltFile, -- root, -- keepRules: typeof config.keepRules === "string" ? config.keepRules : new Set(config.keepRules ?? []), -- allowIncreaseRules: typeof config.allowIncreaseRules === "string" ? config.allowIncreaseRules : new Set(config.allowIncreaseRules ?? []), -- frozen: config.frozen ?? false, -- disable: config.disable ?? false, -- quiet: config.quiet ?? false, -- threadsafe: config.threadsafe ?? false, -- verbose: config.verbose ?? false -- }; -- }, -- getLogger(args) { -- if (typeof args.verbose === "function") { -- return args.verbose; -- } -- if (args.verbose === "stdout") { -- return logStdout; -- } -- return logStderr; -- }, -- ruleSetHas(ruleSet, ruleId) { -- return ruleSet === "all" || ruleSet.has(ruleId); -- }, -- verboseLog(args, makeMessage) { -- if (args.verbose) { -- const message = makeMessage(); -- const log = SeatbeltArgs.getLogger(args); -- if (typeof message === "string") { -- log(message); -- } else { -- log(...message); -- } -- } -- }, -- findSeatbeltFile(cwd) { -- return `${cwd}/${SEATBELT_FILE_NAME}`; -- } --}; --var envVarMaxLength = 0; --function padVarName(name2) { -- envVarMaxLength ||= Math.max( -- ...Object.values(ENV_VARS).map((name3) => name3.length) -- ); -- return `${name2}:`.padEnd(envVarMaxLength + 1); --} --function formatFilename(filename) { -- const relative = path.relative( -- process.env[SEATBELT_PWD] ?? process.cwd(), -- filename -- ); -- return relative ? relative : filename; --} --function formatRuleId(ruleId) { -- if (ruleId === null) { -- return `unknown rule`; -- } -- return `rule ${ruleId}`; --} -- --// src/jsonSchema/SeatbeltConfigSchema.ts --var SeatbeltConfigSchema = { -- description: 'Configuration for seatbelt can be provided in a few ways:\n\n1. Defined in the shared `settings` object in your ESLint config. This\n requires also configuring the `eslint-seatbelt/configure` rule.\n\n ```js\n // in eslint.config.js\n const config = [\n {\n settings: {\n seatbelt: {\n // ...\n }\n },\n rules: {\n "eslint-seatbelt/configure": "error",\n }\n }\n ]\n ```\n\n2. Using the `eslint-seatbelt/configure` rule in your ESLint config.\n This can be used to override settings for specific files in legacy ESLint configs.\n Any configuration provided here will override the shared `settings` object.\n\n ```js\n // in .eslintrc.js\n module.exports = {\n rules: {\n "eslint-seatbelt/configure": "error",\n },\n overrides: [\n {\n files: ["some/path/*"],\n rules: {\n "eslint-seatbelt/configure": ["error", { seatbeltFile: "some/path/eslint.seatbelt.tsv" }]\n },\n },\n ],\n }\n ```\n3. The settings in config files can be overridden with environment variables when running `eslint` or other tools.\n\n ```bash\n SEATBELT_FILE=some/path/eslint.seatbelt.tsv SEATBELT_FROZEN=1 eslint\n ```', -- type: "object", -- properties: { -- seatbeltFile: { -- description: "The seatbelt file stores the max error counts allowed for each file. Should\nbe an absolute path.\n\nIf not provided, $SEATBELT_PWD/eslint.seatbelt.tsv or $PWD/eslint.seatbelt.tsv will be used.\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n // commonjs\n seatbeltFile: `${__dirname}/eslint.seatbelt.tsv`\n // esm\n seatbeltFile: new URL('./eslint.seatbelt.tsv', import.meta.url).pathname\n }\n }\n }\n]\n```\n\nYou can also set this with environment variable `SEATBELT_FILE`:\n\n```bash\nSEATBELT_FILE=.config/custom-seatbelt-file eslint\n```", -- type: "string" -- }, -- keepRules: { -- description: 'By default whenever a file is linted and a rule has no errors, that rule\'s\nmax errors for the file is set to zero.\n\nHowever with typescript-eslint, it can be helpful to have two ESLint configs:\n\n- A default ESLint config with only syntactic rules enabled that don\'t\n require typechecking, that runs on developer machines and in their editor.\n- A CI-only ESLint config with only type-aware rules enabled that requires\n typechecking. Since these rules require typechecking, they can be too\n slow to run in interactive contexts.\n\nTo avoid this, set `keepRules` to the names of *disabled but known rules*\nwhile linting.\n\nExample:\n\n```js\n// Default ESLint config\nmodule.exports = [\n {\n settings: {\n seatbelt: {\n keepRules: require(\'./eslint-typed.config.js\').flatMap(config => Object.keys(config.rules ?? {})),\n }\n },\n rules: {\n "no-unused-vars": "error",\n },\n }\n]\n\n// Typechecking-required ESLint config for CI\nmodule.exports = [\n {\n settings: {\n seatbelt: {\n keepRules: require(\'./eslint.config.js\').flatMap(config => Object.keys(config.rules ?? {})),\n }\n },\n rules: {\n // Requires typechecking (slow)\n "@typescript-eslint/no-floating-promises": "error",\n },\n }\n]\n```\n\nYou can also set this with environment variable `SEATBELT_KEEP`:\n\n```bash\nSEATBELT_KEEP="@typescript-eslint/no-floating-promises', -- anyOf: [ -- { -- type: "array", -- items: { -- type: "string" -- } -- }, -- { -- const: "all", -- type: "string" -- } -- ] -- }, -- allowIncreaseRules: { -- description: 'When you enable a rule for the first time, lint with it in this set to set\nthe initial max error counts.\n\nTypically this should be enabled for one lint run only via an environment\nvariable, but it can also be configured via ESLint settings.\n\n```bash\nSEATBELT_INCREASE="@typescript-eslint/no-floating-promises" eslint\n```\n\nYou can set this to `"ALL"` to enable this setting for ALL rules:\n\n```bash\nSEATBELT_INCREASE=ALL eslint\n```\n\n```js\n// in eslint.config.js\n// maybe you have a use-case for this\nconst config = [\n {\n settings: {\n seatbelt: {\n allowIncreaseRules: ["@typescript-eslint/no-floating-promises"],\n }\n }\n }\n]\n```', -- anyOf: [ -- { -- type: "array", -- items: { -- type: "string" -- } -- }, -- { -- const: "all", -- type: "string" -- } -- ] -- }, -- frozen: { -- description: "Error if there is any change in the number of errors in the seatbelt file.\nThis is useful in CI to ensures that developers keep the seatbelt file up-to-date as they fix errors.\n\nIt is enabled by default when environment variable `CI` is set.\n\n```bash\nCI=1 eslint\n```\n\nThis can be set with the `SEATBELT_FROZEN` environment variable.\n\n```bash\nSEATBELT_FROZEN=1 eslint\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n frozen: true,\n }\n }\n }\n]\n```", -- type: "boolean" -- }, -- disable: { -- description: "Completely disable seatbelt error processing for a lint run while leaving it otherwise configured.\n\nThis can be set with the `SEATBELT_DISABLE` environment variable.\n\n```bash\nSEATBELT_DISABLE=1 eslint\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n disable: true,\n }\n }\n }\n]\n```", -- type: "boolean" -- }, -- quiet: { -- description: 'Suppress seatbelt\'s informational warning messages (e.g. "tend the garden",\n"thank you for fixing"). When enabled, seatbelt still downgrades errors to\nwarnings and updates the seatbelt file, but the warning messages are not\nemitted as ESLint results. Over-limit errors and frozen-mode warnings are\nalways preserved.\n\nThis is useful when seatbelt warnings create noise in CI logs or editor\nintegrations.\n\nThis can be set with the `SEATBELT_QUIET` environment variable.\n\n```bash\nSEATBELT_QUIET=1 eslint\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n quiet: true,\n }\n }\n }\n]\n```', -- type: "boolean" -- }, -- threadsafe: { -- description: "By default seatbelt assumes that only one ESLint process will read and\nwrite to the seatbelt file at a time.\n\nThis should be set to `true` if you use a parallel ESLint runner similar to\njest-runner-eslint to avoid losing updates during parallel writes to the\nseatbelt file.\n\nWhen enabled, seatbelt creates temporary lock files to serialize updates to\nthe seatbelt file. This comes at a small performance cost.\n\nThis is enabled by default when run with Jest (environment variable `JEST_WORKER_ID` is set)\nor inside a Node `worker_threads` worker (e.g. ESLint `--concurrency`).\n\nIt can also be set with environment variable `SEATBELT_THREADSAFE`:\n\n```bash\nSEATBELT_THREADSAFE=1 eslint-parallel\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n threadsafe: true,\n }\n }\n }\n]\n```", -- type: "boolean" -- }, -- verbose: { -- description: "Enable verbose logging.\n\nThis can be set with the `SEATBELT_VERBOSE` environment variable.\n\n```bash\nSEATBELT_VERBOSE=1 eslint\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n verbose: true,\n }\n }\n }\n]\n```\n\nIf set to a function (like `console.error`), that function will be called with the log messages.\nThe default logger when set to `true` is `console.error`.", -- anyOf: [ -- { -- enum: [false, "stderr", "stdout", true] -- }, -- { -- type: "object" -- } -- ] -- }, -- root: { -- description: "Repository or project root.\nBy default this is inferred from `seatbeltFile` by checking ancestor directories for `.git`.\nUsed for editor integration to disable seatbelt during git actions like rebase or merge.\n\nThis can be set with the `SEATBELT_ROOT` environment variable.", -- type: "string" -- } -- }, -- $schema: "http://json-schema.org/draft-07/schema#" --}; -- --export { -- __require, -- name, -- version, -- package_default, -- SEATBELT_FILE_NAME, -- SEATBELT_FROZEN, -- SEATBELT_INCREASE, -- SEATBELT_KEEP, -- SEATBELT_FILE, -- SEATBELT_PWD, -- SEATBELT_DISABLE, -- SEATBELT_THREADSAFE, -- SEATBELT_VERBOSE, -- SEATBELT_QUIET, -- SEATBELT_ROOT, -- SeatbeltConfig, -- SeatbeltEnv, -- logStdout, -- logStderr, -- SeatbeltArgs, -- padVarName, -- formatFilename, -- formatRuleId, -- SeatbeltConfigSchema --}; --//# sourceMappingURL=chunk-ULACHCKT.mjs.map -\ No newline at end of file -diff --git a/node_modules/eslint-seatbelt/dist/chunk-ZVY5S6JS.js b/node_modules/eslint-seatbelt/dist/chunk-ZVY5S6JS.js -deleted file mode 100644 -index ee27681..0000000 ---- a/node_modules/eslint-seatbelt/dist/chunk-ZVY5S6JS.js -+++ /dev/null -@@ -1,435 +0,0 @@ --"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { -- get: (a, b) => (typeof require !== "undefined" ? require : a)[b] --}) : x)(function(x) { -- if (typeof require !== "undefined") return require.apply(this, arguments); -- throw Error('Dynamic require of "' + x + '" is not supported'); --}); -- --// package.json --var name = "eslint-seatbelt"; --var version = "0.1.3"; --var package_default = { -- name, -- version, -- description: "Gradually tighten ESLint rules in your codebase", -- keywords: [ -- "eslint", -- "incremental", -- "gradual", -- "workflow", -- "processor", -- "linting" -- ], -- author: { -- name: "Jake Teton-Landis", -- url: "https://jake.tl" -- }, -- repository: { -- type: "git", -- url: "git+https://github.com/justjake/eslint-seatbelt.git" -- }, -- bugs: { -- url: "https://github.com/justjake/eslint-seatbelt/issues" -- }, -- scripts: { -- build: "./scripts/make-json-schemas.ts && tsc && tsup", -- test: "node --test --require tsx/cjs $(find src -name '*.test.ts')", -- lint: "pnpm build && NODE_OPTIONS='--enable-source-maps' eslint ." -- }, -- types: "dist/index.d.ts", -- import: "./dist/index.mjs", -- main: "dist/index.js", -- exports: { -- ".": { -- types: "./dist/index.d.ts", -- import: "./dist/index.mjs", -- default: "./dist/index.js" -- }, -- "./api": { -- types: "./dist/api.d.ts", -- import: "./dist/api.mjs", -- default: "./dist/api.js" -- } -- }, -- bin: { -- "eslint-seatbelt": "./dist/command.js" -- }, -- files: [ -- "!*.tsbuildinfo", -- "!src/**/*.test.ts", -- "src", -- "dist" -- ], -- license: "MIT", -- peerDependencies: { -- "@types/eslint": "*", -- eslint: "*" -- }, -- peerDependenciesMeta: { -- eslint: { -- optional: true -- }, -- "@types/eslint": { -- optional: true -- } -- }, -- devDependencies: { -- "@eslint/compat": "1.2.3", -- "@eslint/js": "9.15.0", -- "@types/eslint__js": "8.42.3", -- "@types/node": "22.9.0", -- "@typescript-eslint/rule-tester": "8.14.0", -- "@typescript-eslint/utils": "8.14.0", -- eslint: "9.14.0", -- prettier: "3.3.3", -- tsup: "8.3.5", -- tsx: "4.19.2", -- typescript: "5.6.3", -- "typescript-eslint": "8.14.0", -- "typescript-json-schema": "0.65.1" -- }, -- dependencies: { -- "ts-command-line-args": "^2.5.1" -- }, -- packageManager: "pnpm@10.2.1+sha1.48adf39a4ab751eda7b73b99447d1f0b6d227e02" --}; -- --// src/SeatbeltConfig.ts --var _path = require('path'); var _path2 = _interopRequireDefault(_path); --var _worker_threads = require('worker_threads'); -- --// src/repoIntegration.ts --var _fs = require('fs'); var _fs2 = _interopRequireDefault(_fs); -- --function findAncestorDirectory(path2, predicate) { -- let lastPath = void 0; -- while (path2 !== lastPath) { -- if (predicate(path2)) { -- return path2; -- } -- lastPath = path2; -- path2 = _path2.default.dirname(path2); -- } --} --function isGitRoot(dir) { -- return _fs2.default.existsSync(_path2.default.join(dir, ".git")); --} --function findRepoRoot(path2) { -- return findAncestorDirectory(path2, isGitRoot); --} -- --// src/SeatbeltConfig.ts --var SEATBELT_FILE_NAME = "eslint.seatbelt.tsv"; --var SEATBELT_FROZEN = "SEATBELT_FROZEN"; --var SEATBELT_INCREASE = "SEATBELT_INCREASE"; --var SEATBELT_KEEP = "SEATBELT_KEEP"; --var SEATBELT_FILE = "SEATBELT_FILE"; --var SEATBELT_PWD = "SEATBELT_PWD"; --var SEATBELT_DISABLE = "SEATBELT_DISABLE"; --var SEATBELT_THREADSAFE = "SEATBELT_THREADSAFE"; --var SEATBELT_VERBOSE = "SEATBELT_VERBOSE"; --var SEATBELT_QUIET = "SEATBELT_QUIET"; --var SEATBELT_ROOT = "SEATBELT_ROOT"; --var ENV_VARS = { -- SEATBELT_FROZEN, -- SEATBELT_INCREASE, -- SEATBELT_KEEP, -- SEATBELT_FILE, -- SEATBELT_PWD, -- SEATBELT_DISABLE, -- SEATBELT_THREADSAFE, -- SEATBELT_VERBOSE, -- SEATBELT_QUIET, -- SEATBELT_ROOT, -- CI: "CI", -- JEST_WORKER_ID: "JEST_WORKER_ID" --}; --var SeatbeltConfig = { -- withEnvOverrides(config, env) { -- return { -- ...SeatbeltConfig.fromFallbackEnv(env), -- ...config, -- ...SeatbeltConfig.fromEnvOverrides(env) -- }; -- }, -- fromFallbackEnv(env, log) { -- const config = {}; -- const isCI = SeatbeltEnv.readBooleanEnvVar(env.CI); -- if (isCI) { -- config.frozen = true; -- _optionalChain([log, 'optionalCall', _ => _(`${padVarName("CI")} config.frozen defaults to`, true)]); -- } -- if (env.JEST_WORKER_ID) { -- config.threadsafe = true; -- _optionalChain([log, 'optionalCall', _2 => _2( -- `${padVarName("JEST_WORKER_ID")} config.threadsafe defaults to`, -- true -- )]); -- } -- if (!_worker_threads.isMainThread) { -- config.threadsafe = true; -- _optionalChain([log, 'optionalCall', _3 => _3( -- `${padVarName("worker_threads")} config.threadsafe defaults to`, -- true -- )]); -- } -- return config; -- }, -- fromEnvOverrides(env, log) { -- const config = { -- pwd: env[SEATBELT_PWD] || process.cwd() -- }; -- const verbose = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_VERBOSE]); -- if (verbose !== void 0) { -- config.verbose = verbose; -- _optionalChain([log, 'optionalCall', _4 => _4(`${padVarName(SEATBELT_VERBOSE)} config.verbose =`, verbose)]); -- } -- const seatbeltFile = env[SEATBELT_FILE]; -- if (seatbeltFile) { -- const rootRelative = _path2.default.isAbsolute(seatbeltFile) ? seatbeltFile : _path2.default.join(config.pwd, seatbeltFile); -- config.seatbeltFile = rootRelative; -- _optionalChain([log, 'optionalCall', _5 => _5(`${padVarName(SEATBELT_FILE)} config.seatbeltFile =`, rootRelative)]); -- } -- const disable = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_DISABLE]); -- if (disable !== void 0) { -- config.disable = disable; -- _optionalChain([log, 'optionalCall', _6 => _6(`${padVarName(SEATBELT_DISABLE)} config.disable =`, disable)]); -- } -- const frozen = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_FROZEN]); -- if (frozen !== void 0) { -- config.frozen = frozen; -- _optionalChain([log, 'optionalCall', _7 => _7(`${padVarName(SEATBELT_FROZEN)} config.frozen =`, frozen)]); -- } -- const increase = SeatbeltEnv.parseRuleSetEnvVar(env[SEATBELT_INCREASE]); -- if (increase !== void 0) { -- config.allowIncreaseRules = increase; -- _optionalChain([log, 'optionalCall', _8 => _8( -- `${padVarName(SEATBELT_INCREASE)} config.allowIncreaseRules =`, -- increase -- )]); -- } -- const keep = SeatbeltEnv.parseRuleSetEnvVar(env[SEATBELT_KEEP]); -- if (keep !== void 0) { -- config.keepRules = keep; -- _optionalChain([log, 'optionalCall', _9 => _9(`${padVarName(SEATBELT_KEEP)} config.keepRules =`, keep)]); -- } -- const threadsafe = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_THREADSAFE]); -- if (threadsafe !== void 0) { -- config.threadsafe = threadsafe; -- _optionalChain([log, 'optionalCall', _10 => _10( -- `${padVarName(SEATBELT_THREADSAFE)} config.threadsafe =`, -- threadsafe -- )]); -- } -- const quiet = SeatbeltEnv.readBooleanEnvVar(env[SEATBELT_QUIET]); -- if (quiet !== void 0) { -- config.quiet = quiet; -- _optionalChain([log, 'optionalCall', _11 => _11(`${padVarName(SEATBELT_QUIET)} config.quiet =`, quiet)]); -- } -- const root = env[SEATBELT_ROOT]; -- if (root) { -- config.root = root; -- _optionalChain([log, 'optionalCall', _12 => _12(`${padVarName(SEATBELT_ROOT)} config.root =`, root)]); -- } -- return config; -- } --}; --var SeatbeltEnv = { -- parseRuleSetEnvVar(value) { -- if (value === void 0) { -- return void 0; -- } -- if (!value) { -- return []; -- } -- const lower = value.toLowerCase(); -- if (lower === "all" || lower === "1" || lower === "true") { -- return "all"; -- } -- return value.split(/[\s,]+/g).filter(Boolean); -- }, -- readBooleanEnvVar(value) { -- if (value === void 0 || value === "") { -- return void 0; -- } -- const lower = value.toLowerCase(); -- if (lower === "false" || lower === "0" || lower === "no") { -- return false; -- } -- return Boolean(value); -- } --}; --var logStdout = (...message) => ( -- // eslint-disable-next-line no-console -- console.log(`[${name}]:`, ...message) --); --var logStderr = (...message) => ( -- // eslint-disable-next-line no-console -- console.error(`[${name}]:`, ...message) --); --var SeatbeltArgs = { -- fromConfig(config) { -- const cwd = _nullishCoalesce(config.pwd, () => ( process.cwd())); -- const seatbeltFile = _nullishCoalesce(config.seatbeltFile, () => ( SeatbeltArgs.findSeatbeltFile(cwd))); -- const root = _nullishCoalesce(_nullishCoalesce(config.root, () => ( findRepoRoot(seatbeltFile))), () => ( _path2.default.dirname(seatbeltFile))); -- return { -- seatbeltFile, -- root, -- keepRules: typeof config.keepRules === "string" ? config.keepRules : new Set(_nullishCoalesce(config.keepRules, () => ( []))), -- allowIncreaseRules: typeof config.allowIncreaseRules === "string" ? config.allowIncreaseRules : new Set(_nullishCoalesce(config.allowIncreaseRules, () => ( []))), -- frozen: _nullishCoalesce(config.frozen, () => ( false)), -- disable: _nullishCoalesce(config.disable, () => ( false)), -- quiet: _nullishCoalesce(config.quiet, () => ( false)), -- threadsafe: _nullishCoalesce(config.threadsafe, () => ( false)), -- verbose: _nullishCoalesce(config.verbose, () => ( false)) -- }; -- }, -- getLogger(args) { -- if (typeof args.verbose === "function") { -- return args.verbose; -- } -- if (args.verbose === "stdout") { -- return logStdout; -- } -- return logStderr; -- }, -- ruleSetHas(ruleSet, ruleId) { -- return ruleSet === "all" || ruleSet.has(ruleId); -- }, -- verboseLog(args, makeMessage) { -- if (args.verbose) { -- const message = makeMessage(); -- const log = SeatbeltArgs.getLogger(args); -- if (typeof message === "string") { -- log(message); -- } else { -- log(...message); -- } -- } -- }, -- findSeatbeltFile(cwd) { -- return `${cwd}/${SEATBELT_FILE_NAME}`; -- } --}; --var envVarMaxLength = 0; --function padVarName(name2) { -- envVarMaxLength ||= Math.max( -- ...Object.values(ENV_VARS).map((name3) => name3.length) -- ); -- return `${name2}:`.padEnd(envVarMaxLength + 1); --} --function formatFilename(filename) { -- const relative = _path2.default.relative( -- _nullishCoalesce(process.env[SEATBELT_PWD], () => ( process.cwd())), -- filename -- ); -- return relative ? relative : filename; --} --function formatRuleId(ruleId) { -- if (ruleId === null) { -- return `unknown rule`; -- } -- return `rule ${ruleId}`; --} -- --// src/jsonSchema/SeatbeltConfigSchema.ts --var SeatbeltConfigSchema = { -- description: 'Configuration for seatbelt can be provided in a few ways:\n\n1. Defined in the shared `settings` object in your ESLint config. This\n requires also configuring the `eslint-seatbelt/configure` rule.\n\n ```js\n // in eslint.config.js\n const config = [\n {\n settings: {\n seatbelt: {\n // ...\n }\n },\n rules: {\n "eslint-seatbelt/configure": "error",\n }\n }\n ]\n ```\n\n2. Using the `eslint-seatbelt/configure` rule in your ESLint config.\n This can be used to override settings for specific files in legacy ESLint configs.\n Any configuration provided here will override the shared `settings` object.\n\n ```js\n // in .eslintrc.js\n module.exports = {\n rules: {\n "eslint-seatbelt/configure": "error",\n },\n overrides: [\n {\n files: ["some/path/*"],\n rules: {\n "eslint-seatbelt/configure": ["error", { seatbeltFile: "some/path/eslint.seatbelt.tsv" }]\n },\n },\n ],\n }\n ```\n3. The settings in config files can be overridden with environment variables when running `eslint` or other tools.\n\n ```bash\n SEATBELT_FILE=some/path/eslint.seatbelt.tsv SEATBELT_FROZEN=1 eslint\n ```', -- type: "object", -- properties: { -- seatbeltFile: { -- description: "The seatbelt file stores the max error counts allowed for each file. Should\nbe an absolute path.\n\nIf not provided, $SEATBELT_PWD/eslint.seatbelt.tsv or $PWD/eslint.seatbelt.tsv will be used.\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n // commonjs\n seatbeltFile: `${__dirname}/eslint.seatbelt.tsv`\n // esm\n seatbeltFile: new URL('./eslint.seatbelt.tsv', import.meta.url).pathname\n }\n }\n }\n]\n```\n\nYou can also set this with environment variable `SEATBELT_FILE`:\n\n```bash\nSEATBELT_FILE=.config/custom-seatbelt-file eslint\n```", -- type: "string" -- }, -- keepRules: { -- description: 'By default whenever a file is linted and a rule has no errors, that rule\'s\nmax errors for the file is set to zero.\n\nHowever with typescript-eslint, it can be helpful to have two ESLint configs:\n\n- A default ESLint config with only syntactic rules enabled that don\'t\n require typechecking, that runs on developer machines and in their editor.\n- A CI-only ESLint config with only type-aware rules enabled that requires\n typechecking. Since these rules require typechecking, they can be too\n slow to run in interactive contexts.\n\nTo avoid this, set `keepRules` to the names of *disabled but known rules*\nwhile linting.\n\nExample:\n\n```js\n// Default ESLint config\nmodule.exports = [\n {\n settings: {\n seatbelt: {\n keepRules: require(\'./eslint-typed.config.js\').flatMap(config => Object.keys(config.rules ?? {})),\n }\n },\n rules: {\n "no-unused-vars": "error",\n },\n }\n]\n\n// Typechecking-required ESLint config for CI\nmodule.exports = [\n {\n settings: {\n seatbelt: {\n keepRules: require(\'./eslint.config.js\').flatMap(config => Object.keys(config.rules ?? {})),\n }\n },\n rules: {\n // Requires typechecking (slow)\n "@typescript-eslint/no-floating-promises": "error",\n },\n }\n]\n```\n\nYou can also set this with environment variable `SEATBELT_KEEP`:\n\n```bash\nSEATBELT_KEEP="@typescript-eslint/no-floating-promises', -- anyOf: [ -- { -- type: "array", -- items: { -- type: "string" -- } -- }, -- { -- const: "all", -- type: "string" -- } -- ] -- }, -- allowIncreaseRules: { -- description: 'When you enable a rule for the first time, lint with it in this set to set\nthe initial max error counts.\n\nTypically this should be enabled for one lint run only via an environment\nvariable, but it can also be configured via ESLint settings.\n\n```bash\nSEATBELT_INCREASE="@typescript-eslint/no-floating-promises" eslint\n```\n\nYou can set this to `"ALL"` to enable this setting for ALL rules:\n\n```bash\nSEATBELT_INCREASE=ALL eslint\n```\n\n```js\n// in eslint.config.js\n// maybe you have a use-case for this\nconst config = [\n {\n settings: {\n seatbelt: {\n allowIncreaseRules: ["@typescript-eslint/no-floating-promises"],\n }\n }\n }\n]\n```', -- anyOf: [ -- { -- type: "array", -- items: { -- type: "string" -- } -- }, -- { -- const: "all", -- type: "string" -- } -- ] -- }, -- frozen: { -- description: "Error if there is any change in the number of errors in the seatbelt file.\nThis is useful in CI to ensures that developers keep the seatbelt file up-to-date as they fix errors.\n\nIt is enabled by default when environment variable `CI` is set.\n\n```bash\nCI=1 eslint\n```\n\nThis can be set with the `SEATBELT_FROZEN` environment variable.\n\n```bash\nSEATBELT_FROZEN=1 eslint\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n frozen: true,\n }\n }\n }\n]\n```", -- type: "boolean" -- }, -- disable: { -- description: "Completely disable seatbelt error processing for a lint run while leaving it otherwise configured.\n\nThis can be set with the `SEATBELT_DISABLE` environment variable.\n\n```bash\nSEATBELT_DISABLE=1 eslint\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n disable: true,\n }\n }\n }\n]\n```", -- type: "boolean" -- }, -- quiet: { -- description: 'Suppress seatbelt\'s informational warning messages (e.g. "tend the garden",\n"thank you for fixing"). When enabled, seatbelt still downgrades errors to\nwarnings and updates the seatbelt file, but the warning messages are not\nemitted as ESLint results. Over-limit errors and frozen-mode warnings are\nalways preserved.\n\nThis is useful when seatbelt warnings create noise in CI logs or editor\nintegrations.\n\nThis can be set with the `SEATBELT_QUIET` environment variable.\n\n```bash\nSEATBELT_QUIET=1 eslint\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n quiet: true,\n }\n }\n }\n]\n```', -- type: "boolean" -- }, -- threadsafe: { -- description: "By default seatbelt assumes that only one ESLint process will read and\nwrite to the seatbelt file at a time.\n\nThis should be set to `true` if you use a parallel ESLint runner similar to\njest-runner-eslint to avoid losing updates during parallel writes to the\nseatbelt file.\n\nWhen enabled, seatbelt creates temporary lock files to serialize updates to\nthe seatbelt file. This comes at a small performance cost.\n\nThis is enabled by default when run with Jest (environment variable `JEST_WORKER_ID` is set)\nor inside a Node `worker_threads` worker (e.g. ESLint `--concurrency`).\n\nIt can also be set with environment variable `SEATBELT_THREADSAFE`:\n\n```bash\nSEATBELT_THREADSAFE=1 eslint-parallel\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n threadsafe: true,\n }\n }\n }\n]\n```", -- type: "boolean" -- }, -- verbose: { -- description: "Enable verbose logging.\n\nThis can be set with the `SEATBELT_VERBOSE` environment variable.\n\n```bash\nSEATBELT_VERBOSE=1 eslint\n```\n\nOr in ESLint config:\n\n```js\n// in eslint.config.js\nconst config = [\n {\n settings: {\n seatbelt: {\n verbose: true,\n }\n }\n }\n]\n```\n\nIf set to a function (like `console.error`), that function will be called with the log messages.\nThe default logger when set to `true` is `console.error`.", -- anyOf: [ -- { -- enum: [false, "stderr", "stdout", true] -- }, -- { -- type: "object" -- } -- ] -- }, -- root: { -- description: "Repository or project root.\nBy default this is inferred from `seatbeltFile` by checking ancestor directories for `.git`.\nUsed for editor integration to disable seatbelt during git actions like rebase or merge.\n\nThis can be set with the `SEATBELT_ROOT` environment variable.", -- type: "string" -- } -- }, -- $schema: "http://json-schema.org/draft-07/schema#" --}; -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --exports.__require = __require; exports.name = name; exports.version = version; exports.package_default = package_default; exports.SEATBELT_FILE_NAME = SEATBELT_FILE_NAME; exports.SEATBELT_FROZEN = SEATBELT_FROZEN; exports.SEATBELT_INCREASE = SEATBELT_INCREASE; exports.SEATBELT_KEEP = SEATBELT_KEEP; exports.SEATBELT_FILE = SEATBELT_FILE; exports.SEATBELT_PWD = SEATBELT_PWD; exports.SEATBELT_DISABLE = SEATBELT_DISABLE; exports.SEATBELT_THREADSAFE = SEATBELT_THREADSAFE; exports.SEATBELT_VERBOSE = SEATBELT_VERBOSE; exports.SEATBELT_QUIET = SEATBELT_QUIET; exports.SEATBELT_ROOT = SEATBELT_ROOT; exports.SeatbeltConfig = SeatbeltConfig; exports.SeatbeltEnv = SeatbeltEnv; exports.logStdout = logStdout; exports.logStderr = logStderr; exports.SeatbeltArgs = SeatbeltArgs; exports.padVarName = padVarName; exports.formatFilename = formatFilename; exports.formatRuleId = formatRuleId; exports.SeatbeltConfigSchema = SeatbeltConfigSchema; --//# sourceMappingURL=chunk-ZVY5S6JS.js.map -\ No newline at end of file -diff --git a/node_modules/eslint-seatbelt/dist/command.js b/node_modules/eslint-seatbelt/dist/command.js -index fa29aa4..4ea504b 100755 ---- a/node_modules/eslint-seatbelt/dist/command.js -+++ b/node_modules/eslint-seatbelt/dist/command.js -@@ -6,14 +6,14 @@ - - - --var _chunkZVY5S6JSjs = require('./chunk-ZVY5S6JS.js'); -+var _chunkTDWD7IZMjs = require('./chunk-TDWD7IZM.js'); - - // src/command.ts - var _tscommandlineargs = require('ts-command-line-args'); - var ZERO_WIDTH_SPACE = "\u200B"; - function parseArgs() { -- const fallback = _chunkZVY5S6JSjs.SeatbeltConfig.fromFallbackEnv(process.env); -- const overrides = _chunkZVY5S6JSjs.SeatbeltConfig.fromEnvOverrides(process.env); -+ const fallback = _chunkTDWD7IZMjs.SeatbeltConfig.fromFallbackEnv(process.env); -+ const overrides = _chunkTDWD7IZMjs.SeatbeltConfig.fromEnvOverrides(process.env); - const env = { ...fallback, ...overrides }; - const escapeForChalk = (s) => s.replaceAll("{", "\\{").replaceAll("}", "\\}").replaceAll(/^(\s)/gm, (match) => `${ZERO_WIDTH_SPACE}${match}`); - return _tscommandlineargs.parse.call(void 0, -@@ -28,7 +28,7 @@ function parseArgs() { - type: String, - alias: "f", - description: escapeForChalk( -- _chunkZVY5S6JSjs.SeatbeltConfigSchema.properties.seatbeltFile.description -+ _chunkTDWD7IZMjs.SeatbeltConfigSchema.properties.seatbeltFile.description - ), - defaultValue: env.seatbeltFile, - optional: true -@@ -36,7 +36,7 @@ function parseArgs() { - keepRules: { - type: String, - description: escapeForChalk( -- _chunkZVY5S6JSjs.SeatbeltConfigSchema.properties.keepRules.description -+ _chunkTDWD7IZMjs.SeatbeltConfigSchema.properties.keepRules.description - ), - defaultValue: env.keepRules, - multiple: true, -@@ -46,7 +46,7 @@ function parseArgs() { - alias: "r", - type: String, - description: escapeForChalk( -- _chunkZVY5S6JSjs.SeatbeltConfigSchema.properties.allowIncreaseRules.description -+ _chunkTDWD7IZMjs.SeatbeltConfigSchema.properties.allowIncreaseRules.description - ), - defaultValue: env.allowIncreaseRules, - multiple: true, -@@ -55,7 +55,7 @@ function parseArgs() { - frozen: { - type: Boolean, - description: escapeForChalk( -- _chunkZVY5S6JSjs.SeatbeltConfigSchema.properties.frozen.description -+ _chunkTDWD7IZMjs.SeatbeltConfigSchema.properties.frozen.description - ), - defaultValue: env.frozen, - optional: true -@@ -63,7 +63,7 @@ function parseArgs() { - disable: { - type: Boolean, - description: escapeForChalk( -- _chunkZVY5S6JSjs.SeatbeltConfigSchema.properties.disable.description -+ _chunkTDWD7IZMjs.SeatbeltConfigSchema.properties.disable.description - ), - defaultValue: env.disable, - optional: true -@@ -71,7 +71,7 @@ function parseArgs() { - quiet: { - type: Boolean, - description: escapeForChalk( -- _chunkZVY5S6JSjs.SeatbeltConfigSchema.properties.quiet.description -+ _chunkTDWD7IZMjs.SeatbeltConfigSchema.properties.quiet.description - ), - defaultValue: env.quiet, - optional: true -@@ -79,15 +79,23 @@ function parseArgs() { - threadsafe: { - type: Boolean, - description: escapeForChalk( -- _chunkZVY5S6JSjs.SeatbeltConfigSchema.properties.threadsafe.description -+ _chunkTDWD7IZMjs.SeatbeltConfigSchema.properties.threadsafe.description - ), - defaultValue: env.threadsafe, - optional: true - }, -+ readOnly: { -+ type: Boolean, -+ description: escapeForChalk( -+ _chunkTDWD7IZMjs.SeatbeltConfigSchema.properties.readOnly.description -+ ), -+ defaultValue: env.readOnly, -+ optional: true -+ }, - verbose: { - type: Boolean, - description: escapeForChalk( -- _chunkZVY5S6JSjs.SeatbeltConfigSchema.properties.verbose.description -+ _chunkTDWD7IZMjs.SeatbeltConfigSchema.properties.verbose.description - ), - defaultValue: env.verbose, - optional: true -@@ -95,7 +103,7 @@ function parseArgs() { - root: { - type: String, - description: escapeForChalk( -- _chunkZVY5S6JSjs.SeatbeltConfigSchema.properties.root.description -+ _chunkTDWD7IZMjs.SeatbeltConfigSchema.properties.root.description - ), - defaultValue: env.root, - optional: true -@@ -125,8 +133,8 @@ function parseArgs() { - helpArg: "help", - headerContentSections: [ - { -- header: _chunkZVY5S6JSjs.name, -- content: `Turns command-line arguments into ${_chunkZVY5S6JSjs.name} environment variables, then call 'eslint' or another command with them.` -+ header: _chunkTDWD7IZMjs.name, -+ content: `Turns command-line arguments into ${_chunkTDWD7IZMjs.name} environment variables, then call 'eslint' or another command with them.` - } - ] - } -@@ -137,16 +145,16 @@ var stderr = (...args) => console.error(...args); - function main() { - const argsConfig = parseArgs(); - if (argsConfig.version) { -- stdout(`v${_chunkZVY5S6JSjs.version}`); -+ stdout(`v${_chunkTDWD7IZMjs.version}`); - return; - } - if (argsConfig.verbose) { - stderr("Parsed config:", argsConfig); - } -- _chunkZVY5S6JSjs.logStderr.call(void 0, "command not implemented"); -+ _chunkTDWD7IZMjs.logStderr.call(void 0, "command not implemented"); - process.exit(1); - } --if (_chunkZVY5S6JSjs.__require.main === module) { -+if (_chunkTDWD7IZMjs.__require.main === module) { - main(); - } - //# sourceMappingURL=command.js.map -\ No newline at end of file -diff --git a/node_modules/eslint-seatbelt/dist/command.mjs b/node_modules/eslint-seatbelt/dist/command.mjs -index 31b8b7b..44c52a0 100755 ---- a/node_modules/eslint-seatbelt/dist/command.mjs -+++ b/node_modules/eslint-seatbelt/dist/command.mjs -@@ -6,7 +6,7 @@ import { - logStderr, - name, - version --} from "./chunk-ULACHCKT.mjs"; -+} from "./chunk-U6KIQG2A.mjs"; - - // src/command.ts - import { parse } from "ts-command-line-args"; -@@ -84,6 +84,14 @@ function parseArgs() { - defaultValue: env.threadsafe, - optional: true - }, -+ readOnly: { -+ type: Boolean, -+ description: escapeForChalk( -+ SeatbeltConfigSchema.properties.readOnly.description -+ ), -+ defaultValue: env.readOnly, -+ optional: true -+ }, - verbose: { - type: Boolean, - description: escapeForChalk( -diff --git a/node_modules/eslint-seatbelt/dist/index.js b/node_modules/eslint-seatbelt/dist/index.js -index 2d71ee2..b28a3e0 100644 ---- a/node_modules/eslint-seatbelt/dist/index.js -+++ b/node_modules/eslint-seatbelt/dist/index.js -@@ -1,7 +1,7 @@ - "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } - - --var _chunkNTFTCWX7js = require('./chunk-NTFTCWX7.js'); -+var _chunkF4JJMAJLjs = require('./chunk-F4JJMAJL.js'); - - - -@@ -14,7 +14,7 @@ var _chunkNTFTCWX7js = require('./chunk-NTFTCWX7.js'); - - - --var _chunkZVY5S6JSjs = require('./chunk-ZVY5S6JS.js'); -+var _chunkTDWD7IZMjs = require('./chunk-TDWD7IZM.js'); - - // src/pluginGlobals.ts - var ANY_CONFIG_DISABLED = false; -@@ -32,7 +32,7 @@ var lastLintedFile; - var temporaryFileArgs = /* @__PURE__ */ new Map(); - function getProcessEnvFallbackConfig() { - if (!envFallbackConfig) { -- envFallbackConfig = _chunkZVY5S6JSjs.SeatbeltConfig.fromFallbackEnv( -+ envFallbackConfig = _chunkTDWD7IZMjs.SeatbeltConfig.fromFallbackEnv( - process.env - ); - hasAnyEnvVars = Object.keys(envFallbackConfig).length > 0; -@@ -41,7 +41,7 @@ function getProcessEnvFallbackConfig() { - } - function getProcessEnvOverrideConfig() { - if (!envOverrideConfig) { -- envOverrideConfig = _chunkZVY5S6JSjs.SeatbeltConfig.fromEnvOverrides( -+ envOverrideConfig = _chunkTDWD7IZMjs.SeatbeltConfig.fromEnvOverrides( - process.env - ); - ANY_CONFIG_DISABLED ||= _nullishCoalesce(envOverrideConfig.disable, () => ( false)); -@@ -73,7 +73,7 @@ function configToArgs(config) { - ...config, - ...getProcessEnvOverrideConfig() - }; -- args = _chunkZVY5S6JSjs.SeatbeltArgs.fromConfig(compiledConfig); -+ args = _chunkTDWD7IZMjs.SeatbeltArgs.fromConfig(compiledConfig); - ANY_CONFIG_DISABLED ||= args.disable; - if (args.verbose) { - LAST_VERBOSE_ARGS = args; -@@ -86,9 +86,9 @@ function configToArgs(config) { - } - return args; - } --var configureRuleName = `${_chunkZVY5S6JSjs.name}/configure`; -+var configureRuleName = `${_chunkTDWD7IZMjs.name}/configure`; - function logRuleSetupHint() { -- _chunkZVY5S6JSjs.logStderr.call(void 0, -+ _chunkTDWD7IZMjs.logStderr.call(void 0, - ` - Make sure you have rule ${configureRuleName} enabled in your ESLint config for all files: - -@@ -97,16 +97,16 @@ Make sure you have rule ${configureRuleName} enabled in your ESLint config for a - "${configureRuleName}": "error", - } - --Docs: https://github.com/justjake/${_chunkZVY5S6JSjs.name}#setup` -+Docs: https://github.com/justjake/${_chunkTDWD7IZMjs.name}#setup` - ); - } - function logConfig(args, baseConfig) { -- const log = _chunkZVY5S6JSjs.SeatbeltArgs.getLogger(args); -- _chunkZVY5S6JSjs.SeatbeltConfig.fromFallbackEnv(process.env, log); -+ const log = _chunkTDWD7IZMjs.SeatbeltArgs.getLogger(args); -+ _chunkTDWD7IZMjs.SeatbeltConfig.fromFallbackEnv(process.env, log); - for (const [key, value] of Object.entries(baseConfig)) { -- log(`${_chunkZVY5S6JSjs.padVarName.call(void 0, "ESLint settings")} config.${key} =`, value); -+ log(`${_chunkTDWD7IZMjs.padVarName.call(void 0, "ESLint settings")} config.${key} =`, value); - } -- _chunkZVY5S6JSjs.SeatbeltConfig.fromEnvOverrides(process.env, log); -+ _chunkTDWD7IZMjs.SeatbeltConfig.fromEnvOverrides(process.env, log); - } - function pushFileArgs(filename, args) { - lastLintedFile = { filename, args }; -@@ -123,13 +123,13 @@ function popFileArgs(filename) { - } - if (!hasAnyEnvVars) { - if (lastLintedFile) { -- _chunkZVY5S6JSjs.logStderr.call(void 0, -+ _chunkTDWD7IZMjs.logStderr.call(void 0, - `WARNING: last configured by file \`${lastLintedFile.filename}\` but linting file \`${filename}\`. - You may have rule ${configureRuleName} enabled for some files, but not this one. - `.trim() - ); - } else { -- _chunkZVY5S6JSjs.logStderr.call(void 0, -+ _chunkTDWD7IZMjs.logStderr.call(void 0, - `WARNING: rule ${configureRuleName} not enabled in ESLint config and no SEATBELT environment variables set` - ); - } -@@ -140,7 +140,7 @@ You may have rule ${configureRuleName} enabled for some files, but not this one. - function getSeatbeltFile(filename) { - let seatbeltFile = seatbeltFileCache.get(filename); - if (!seatbeltFile) { -- seatbeltFile = _chunkNTFTCWX7js.SeatbeltFile.openSync(filename); -+ seatbeltFile = _chunkF4JJMAJLjs.SeatbeltFile.openSync(filename); - seatbeltFileCache.set(filename, seatbeltFile); - } - return seatbeltFile; -@@ -186,7 +186,7 @@ function isEslintCli() { - } - - // src/SeatbeltProcessor.ts --var { name: name2, version: version2 } = _chunkZVY5S6JSjs.package_default; -+var { name: name2, version: version2 } = _chunkTDWD7IZMjs.package_default; - var SeatbeltProcessor = { - supportsAutofix: true, - meta: { -@@ -267,9 +267,9 @@ function transformMessages(args, seatbeltFile, filename, messages, ruleToErrorCo - } - return messages.flatMap((message) => { - if (message.ruleId === null) { -- _chunkZVY5S6JSjs.SeatbeltArgs.verboseLog( -+ _chunkTDWD7IZMjs.SeatbeltArgs.verboseLog( - args, -- () => `${_chunkZVY5S6JSjs.formatFilename.call(void 0, filename)}:${message.line}:${message.column}: cannot transform message with null ruleId` -+ () => `${_chunkTDWD7IZMjs.formatFilename.call(void 0, filename)}:${message.line}:${message.column}: cannot transform message with null ruleId` - ); - return message; - } -@@ -283,7 +283,7 @@ function transformMessages(args, seatbeltFile, filename, messages, ruleToErrorCo - ); - } - const maxErrorCount = _nullishCoalesce(_optionalChain([ruleToMaxErrorCount, 'optionalAccess', _ => _.get, 'call', _2 => _2(message.ruleId)]), () => ( 0)); -- const allowIncrease2 = _chunkZVY5S6JSjs.SeatbeltArgs.ruleSetHas( -+ const allowIncrease2 = _chunkTDWD7IZMjs.SeatbeltArgs.ruleSetHas( - args.allowIncreaseRules, - message.ruleId - ); -@@ -301,17 +301,17 @@ function transformMessages(args, seatbeltFile, filename, messages, ruleToErrorCo - ); - } - if (verboseOnce(message.ruleId)) { -- _chunkZVY5S6JSjs.SeatbeltArgs.verboseLog( -+ _chunkTDWD7IZMjs.SeatbeltArgs.verboseLog( - args, -- () => `${_chunkZVY5S6JSjs.formatFilename.call(void 0, filename)}: ${_chunkZVY5S6JSjs.formatRuleId.call(void 0, message.ruleId)}: error: ${errorCount} ${pluralErrors(errorCount)} found > max ${maxErrorCount}` -+ () => `${_chunkTDWD7IZMjs.formatFilename.call(void 0, filename)}: ${_chunkTDWD7IZMjs.formatRuleId.call(void 0, message.ruleId)}: error: ${errorCount} ${pluralErrors(errorCount)} found > max ${maxErrorCount}` - ); - } - return messageOverMaxErrorCount(message, errorCount, maxErrorCount); - } else if (errorCount === maxErrorCount) { - if (verboseOnce(message.ruleId)) { -- _chunkZVY5S6JSjs.SeatbeltArgs.verboseLog( -+ _chunkTDWD7IZMjs.SeatbeltArgs.verboseLog( - args, -- () => `${_chunkZVY5S6JSjs.formatFilename.call(void 0, filename)}: ${_chunkZVY5S6JSjs.formatRuleId.call(void 0, message.ruleId)}: ok: ${errorCount} ${pluralErrors(errorCount)} found == max ${maxErrorCount}` -+ () => `${_chunkTDWD7IZMjs.formatFilename.call(void 0, filename)}: ${_chunkTDWD7IZMjs.formatRuleId.call(void 0, message.ruleId)}: ok: ${errorCount} ${pluralErrors(errorCount)} found == max ${maxErrorCount}` - ); - } - if (args.quiet) { -@@ -402,7 +402,7 @@ function messageOverMaxErrorCountButIncreaseAllowed(message, errorCount, maxErro - ...message, - severity: 1, - message: `${message.message} --[${name2}]: ${_chunkZVY5S6JSjs.SEATBELT_INCREASE}: Temporarily allowing ${increaseCount} new ${pluralErrors(increaseCount)} of this type. -+[${name2}]: ${_chunkTDWD7IZMjs.SEATBELT_INCREASE}: Temporarily allowing ${increaseCount} new ${pluralErrors(increaseCount)} of this type. - `.trim() - }; - } -@@ -432,7 +432,7 @@ function messageFrozenUnderMaxErrorCountText(seatbeltFilename, errorCount, maxEr - const fixed = errorCount - maxErrorCount; - const fixedMessage = fixed === 1 ? "error" : "errors"; - return ` --[${name2}]: ${_chunkZVY5S6JSjs.SEATBELT_FROZEN}: Expected ${maxErrorCount} ${pluralErrors(maxErrorCount)}, found ${errorCount}. -+[${name2}]: ${_chunkTDWD7IZMjs.SEATBELT_FROZEN}: Expected ${maxErrorCount} ${pluralErrors(maxErrorCount)}, found ${errorCount}. - If you fixed ${fixed} ${fixedMessage}, thank you, but you'll need to update the seatbelt file to match. - Try running eslint, then committing ${seatbeltFilename}. - `.trim(); -@@ -449,8 +449,8 @@ var alreadyModifiedError = /* @__PURE__ */ new WeakSet(); - function handleProcessingError(filename, e) { - if (e instanceof Error && !alreadyModifiedError.has(e)) { - alreadyModifiedError.add(e); -- _chunkNTFTCWX7js.appendErrorContext.call(void 0, e, `while processing \`${filename}\``); -- _chunkNTFTCWX7js.appendErrorContext.call(void 0, -+ _chunkF4JJMAJLjs.appendErrorContext.call(void 0, e, `while processing \`${filename}\``); -+ _chunkF4JJMAJLjs.appendErrorContext.call(void 0, - e, - `this may be a bug in ${name2}@${version2} or a problem with your setup` - ); -@@ -465,16 +465,16 @@ function pluralErrors(count) { - var configure = { - meta: { - docs: { -- description: `Applies ${_chunkZVY5S6JSjs.name} configuration from ESLint config`, -- url: `https://github.com/justjake/${_chunkZVY5S6JSjs.name}` -+ description: `Applies ${_chunkTDWD7IZMjs.name} configuration from ESLint config`, -+ url: `https://github.com/justjake/${_chunkTDWD7IZMjs.name}` - }, -- schema: [_chunkZVY5S6JSjs.SeatbeltConfigSchema] -+ schema: [_chunkTDWD7IZMjs.SeatbeltConfigSchema] - }, - create(context) { - const filename = _nullishCoalesce(_optionalChain([context, 'access', _5 => _5.getFilename, 'optionalCall', _6 => _6()]), () => ( context.filename)); - onConfigureRule(filename); - const eslintSharedConfigViaShortName = _optionalChain([context, 'access', _7 => _7.settings, 'optionalAccess', _8 => _8.seatbelt]); -- const eslintSharedConfigViaPackageName = _optionalChain([context, 'access', _9 => _9.settings, 'optionalAccess', _10 => _10[_chunkZVY5S6JSjs.name]]); -+ const eslintSharedConfigViaPackageName = _optionalChain([context, 'access', _9 => _9.settings, 'optionalAccess', _10 => _10[_chunkTDWD7IZMjs.name]]); - const eslintSharedConfig = _nullishCoalesce(eslintSharedConfigViaShortName, () => ( eslintSharedConfigViaPackageName)); - const fileOverrideConfig = context.options[0]; - const args = ruleOverrideConfigToArgs( -@@ -487,7 +487,7 @@ var configure = { - }; - - // src/index.ts --var { name: name3, version: version3 } = _chunkZVY5S6JSjs.package_default; -+var { name: name3, version: version3 } = _chunkTDWD7IZMjs.package_default; - var plugin = { - meta: { - name: name3, -diff --git a/node_modules/eslint-seatbelt/dist/index.mjs b/node_modules/eslint-seatbelt/dist/index.mjs -index 5b23433..70e7468 100644 ---- a/node_modules/eslint-seatbelt/dist/index.mjs -+++ b/node_modules/eslint-seatbelt/dist/index.mjs -@@ -1,7 +1,7 @@ - import { - SeatbeltFile, - appendErrorContext --} from "./chunk-5FFUIU4M.mjs"; -+} from "./chunk-QGLWIAEE.mjs"; - import { - SEATBELT_FROZEN, - SEATBELT_INCREASE, -@@ -14,7 +14,7 @@ import { - name, - package_default, - padVarName --} from "./chunk-ULACHCKT.mjs"; -+} from "./chunk-U6KIQG2A.mjs"; - - // src/pluginGlobals.ts - var ANY_CONFIG_DISABLED = false; diff --git a/patches/eslint-seatbelt/eslint-seatbelt+0.1.3+003+readonly-type-declarations.patch b/patches/eslint-seatbelt/eslint-seatbelt+0.1.3+003+readonly-type-declarations.patch deleted file mode 100644 index 61fc5db8d6ae..000000000000 --- a/patches/eslint-seatbelt/eslint-seatbelt+0.1.3+003+readonly-type-declarations.patch +++ /dev/null @@ -1,92 +0,0 @@ -diff --git a/node_modules/eslint-seatbelt/dist/SeatbeltConfig-CvuyHBlj.d.mts b/node_modules/eslint-seatbelt/dist/SeatbeltConfig-CvuyHBlj.d.mts -index 9215119..88fa896 100644 ---- a/node_modules/eslint-seatbelt/dist/SeatbeltConfig-CvuyHBlj.d.mts -+++ b/node_modules/eslint-seatbelt/dist/SeatbeltConfig-CvuyHBlj.d.mts -@@ -298,6 +298,41 @@ interface SeatbeltConfig { - * ``` - */ - disable?: boolean; -+ /** -+ * When `true`, seatbelt validates error counts (still reporting increases) -+ * but never writes the seatbelt file. Keeps the worktree clean in local / -+ * editor runs; expect an authoritative updater (e.g. post-merge CI) to run -+ * with `readOnly: false`. -+ * -+ * Unlike `frozen`, does not turn decreases into errors. If both are set, -+ * `frozen` messaging is preserved and no write occurs. -+ * -+ * `SEATBELT_INCREASE` overrides this so intentional loosening is persisted. -+ * -+ * Defaults to `false`. -+ * -+ * Set via `SEATBELT_READ_ONLY` env var: -+ * -+ * ```bash -+ * SEATBELT_READ_ONLY=1 eslint -+ * ``` -+ * -+ * Or in ESLint config: -+ * -+ * ```js -+ * // in eslint.config.js -+ * const config = [ -+ * { -+ * settings: { -+ * seatbelt: { -+ * readOnly: !process.env.CI, -+ * } -+ * } -+ * } -+ * ] -+ * ``` -+ */ -+ readOnly?: boolean; - /** - * Suppress seatbelt's informational warning messages (e.g. "tend the garden", - * "thank you for fixing"). When enabled, seatbelt still downgrades errors to -diff --git a/node_modules/eslint-seatbelt/dist/SeatbeltConfig-CvuyHBlj.d.ts b/node_modules/eslint-seatbelt/dist/SeatbeltConfig-CvuyHBlj.d.ts -index 9215119..88fa896 100644 ---- a/node_modules/eslint-seatbelt/dist/SeatbeltConfig-CvuyHBlj.d.ts -+++ b/node_modules/eslint-seatbelt/dist/SeatbeltConfig-CvuyHBlj.d.ts -@@ -298,6 +298,41 @@ interface SeatbeltConfig { - * ``` - */ - disable?: boolean; -+ /** -+ * When `true`, seatbelt validates error counts (still reporting increases) -+ * but never writes the seatbelt file. Keeps the worktree clean in local / -+ * editor runs; expect an authoritative updater (e.g. post-merge CI) to run -+ * with `readOnly: false`. -+ * -+ * Unlike `frozen`, does not turn decreases into errors. If both are set, -+ * `frozen` messaging is preserved and no write occurs. -+ * -+ * `SEATBELT_INCREASE` overrides this so intentional loosening is persisted. -+ * -+ * Defaults to `false`. -+ * -+ * Set via `SEATBELT_READ_ONLY` env var: -+ * -+ * ```bash -+ * SEATBELT_READ_ONLY=1 eslint -+ * ``` -+ * -+ * Or in ESLint config: -+ * -+ * ```js -+ * // in eslint.config.js -+ * const config = [ -+ * { -+ * settings: { -+ * seatbelt: { -+ * readOnly: !process.env.CI, -+ * } -+ * } -+ * } -+ * ] -+ * ``` -+ */ -+ readOnly?: boolean; - /** - * Suppress seatbelt's informational warning messages (e.g. "tend the garden", - * "thank you for fixing"). When enabled, seatbelt still downgrades errors to diff --git a/scripts/checkOnyxConnectBypass.ts b/scripts/checkOnyxConnectBypass.ts index a5ade47cfe19..7134732ade19 100644 --- a/scripts/checkOnyxConnectBypass.ts +++ b/scripts/checkOnyxConnectBypass.ts @@ -1,66 +1,42 @@ #!/usr/bin/env bun -import type {Rule} from 'eslint'; - +import {file} from 'bun'; /** * Fails the lint run when a new inline `eslint-disable` bypasses the Onyx.connect() ban. * * The ban (`rulesdir/no-onyx-connect`, shipped by eslint-config-expensify) is a normal lint rule, - * so an inline disable can silence it. The ESLint CLI neither surfaces nor fails on such suppressed - * violations, so this runner re-elevates them: it lints with only the ban enabled, reads the - * suppressed violations off the results, and exits non-zero on any that are not grandfathered. - * Because it works from ESLint's suppressed-message data, no disable directive can reach it. + * so an inline disable can silence it. The runner re-elevates those disables by scanning source + * for directives that name the ban or blanket directives that cover a real call — no disable + * comment can reach this check. * - * A real bypass requires a file to contain both an `Onyx.connect` reference and an `eslint-disable` - * directive, so we first narrow the targets to files matching both (via git grep) and only run - * ESLint on those — keeping the check fast even on a whole-repo lint. The `Onyx.connect` match - * deliberately omits the `(` so it stays a superset of the AST rule (e.g. whitespace or a comment - * before the paren); extra matches like `Onyx.connectWithoutView` are harmless, as the rule ignores them. + * A real bypass requires a file to mention `Onyx` and `connect` and contain an `eslint-disable` + * directive, so we first narrow the targets to files matching all three (via git grep). The + * candidate scan does not require the contiguous text `Onyx.connect` — git grep is line-oriented, so a + * spaced or split `Onyx . connect(` would otherwise be skipped. Extra matches like + * `Onyx.connectWithoutView` are harmless: we only fail on disable directives that actually + * suppress the ban. */ -import tsParser from '@typescript-eslint/parser'; -import {ESLint} from 'eslint'; import {execFileSync} from 'node:child_process'; -import {createRequire} from 'node:module'; import path from 'node:path'; -import {BANNED_RULE_ID, collectSuppressedBans, findNewBypasses} from './onyxConnectBypass'; - -const projectRoot = path.resolve(__dirname, '..'); +import {collectDisableDirectivesFromSource, findNewBypasses} from './onyxConnectBypass'; -/** The ban's rule name as registered under the `rulesdir` plugin (i.e. `BANNED_RULE_ID` without the prefix). */ -const RULE_NAME = 'no-onyx-connect'; - -function isRuleModule(value: unknown): value is Rule.RuleModule { - return typeof value === 'object' && value !== null && 'create' in value && typeof value.create === 'function'; -} +const projectRoot = path.resolve(import.meta.dir, '..'); -/** Dynamically import the shipped `no-onyx-connect` rule, which is ESM with relative imports. */ -async function loadNoOnyxConnectRule(): Promise { - const require = createRequire(__filename); - // Resolve the package entry rather than its package.json, since eslint-config-expensify's `exports` map doesn't expose ./package.json. - const expensifyConfigDirectory = path.dirname(require.resolve('eslint-config-expensify')); - const ruleFile = path.join(expensifyConfigDirectory, 'eslint-plugin-expensify', 'no-onyx-connect.js'); - const imported: unknown = await import(ruleFile); - if (isRuleModule(imported)) { - return imported; - } - if (typeof imported === 'object' && imported !== null && 'default' in imported && isRuleModule(imported.default)) { - return imported.default; - } - throw new Error(`Could not load the no-onyx-connect rule from ${ruleFile}`); -} - -/** Files among the lint targets that contain both an Onyx.connect() call and an eslint-disable. */ +/** Files among the lint targets that mention Onyx, connect, and eslint-disable. */ function findCandidateFiles(targets: string[]): string[] { const pathSpecs = targets.length > 0 ? targets : ['.']; try { - const output = execFileSync('git', ['grep', '-lI', '-F', '--all-match', '--untracked', '--no-recurse-submodules', '-e', 'Onyx.connect', '-e', 'eslint-disable', '--', ...pathSpecs], { - cwd: projectRoot, - encoding: 'utf8', - }); + const output = execFileSync( + 'git', + ['grep', '-lI', '--all-match', '--untracked', '--no-recurse-submodules', '-e', 'Onyx', '-e', 'connect', '-e', 'eslint-disable', '--', ...pathSpecs], + { + cwd: projectRoot, + encoding: 'utf8', + }, + ); return output.split('\n').filter(Boolean); } catch (error: unknown) { - // git grep exits 1 when nothing matches; anything else is a real failure. if (typeof error === 'object' && error !== null && 'status' in error && error.status === 1) { return []; } @@ -78,24 +54,16 @@ async function checkOnyxConnectBypass(targets: string[]): Promise { return false; } - const rule = await loadNoOnyxConnectRule(); - const eslint = new ESLint({ - cwd: projectRoot, - warnIgnored: false, - errorOnUnmatchedPattern: false, - overrideConfigFile: true, - overrideConfig: [ - { - files: ['**/*.{js,jsx,ts,tsx,mjs,cjs}'], - languageOptions: {parser: tsParser}, - plugins: {rulesdir: {rules: {[RULE_NAME]: rule}}}, - rules: {[BANNED_RULE_ID]: 'error'}, - }, - ], - }); + const suppressed = ( + await Promise.all( + candidates.map(async (relativePath) => { + const source = await file(path.join(projectRoot, relativePath)).text(); + return collectDisableDirectivesFromSource(source, relativePath.split(path.sep).join('/')); + }), + ) + ).flat(); - const results = await eslint.lintFiles(candidates); - const newBypasses = findNewBypasses(collectSuppressedBans(results, projectRoot)); + const newBypasses = findNewBypasses(suppressed); if (newBypasses.length === 0) { return false; } @@ -108,7 +76,7 @@ async function checkOnyxConnectBypass(targets: string[]): Promise { return true; } -if (require.main === module) { +if (import.meta.main) { checkOnyxConnectBypass(process.argv.slice(2)) .then((failed) => { if (!failed) { diff --git a/scripts/lint.ts b/scripts/lint.ts index 76a5d7eb3488..2a67dde6c9d9 100644 --- a/scripts/lint.ts +++ b/scripts/lint.ts @@ -1,113 +1,6 @@ #!/usr/bin/env bun /** - * Run ESLint with the repo's standard flags (memory ceiling, shared content - * cache, auto concurrency), then finish tightening the eslint-seatbelt baseline. - * Delegate target selection to the caller: - * - * bun scripts/lint.ts -> lint the whole repo - * bun scripts/lint.ts src/foo.ts ... -> lint just the given paths - * bun scripts/lint.ts --show-warnings ... -> include grandfathered seatbelt warnings in the output - * - * By default we pass `--quiet` to ESLint so only blocking errors are printed. - * eslint-seatbelt reclassifies grandfathered violations as warnings, so the - * default output mirrors what CI cares about. Pass `--show-warnings` to - * restore the full output (errors + warnings). + * Compatibility shim. The runner lives in `scripts/lint/index.ts`. */ -import {$, file} from 'bun'; -import {SeatbeltArgs, SeatbeltFile} from 'eslint-seatbelt/api'; - -import checkOnyxConnectBypass from './checkOnyxConnectBypass'; - -const projectRoot = `${import.meta.dir}/..`; - -// parse args -let useCache = true; -let showWarnings = false; -const passthroughArgs: string[] = []; -for (const arg of process.argv.slice(2)) { - if (arg === '--no-cache') { - useCache = false; - } else if (arg === '--show-warnings') { - showWarnings = true; - } else { - passthroughArgs.push(arg); - } -} - -// Preserve default behavior of linting the whole repo when no target is passed. -const lintTargets = passthroughArgs.length > 0 ? passthroughArgs : ['.']; - -// Build ESLint args -const eslintArgs: string[] = []; -if (useCache) { - eslintArgs.push('--cache', '--cache-location=node_modules/.cache/eslint', '--cache-strategy', 'content'); -} -if (!showWarnings) { - eslintArgs.push('--quiet'); -} -// Type-aware linting loads the full TypeScript program in every worker (~12GB heap each on a -// cold cache), so hosts with limited memory need fewer workers with a larger heap rather than -// ESLint's auto worker count. Override via ESLINT_CONCURRENCY and NODE_OPTIONS together. -eslintArgs.push(`--concurrency=${process.env.ESLINT_CONCURRENCY ?? 'auto'}`, '--no-warn-ignored', ...lintTargets); - -const nodeOptions: string = process.env.NODE_OPTIONS ?? '--max_old_space_size=8192'; -const seatbeltFrozenEnv: string = process.env.SEATBELT_FROZEN ?? '0'; - -// Run ESLint with the repo's default memory ceiling and seatbelt behavior. -const eslintResult = await $`npx eslint ${eslintArgs}` - .cwd(projectRoot) - .env({...process.env, NODE_OPTIONS: nodeOptions, SEATBELT_FROZEN: seatbeltFrozenEnv}) - .nothrow(); -if (eslintResult.exitCode !== 0) { - process.exit(eslintResult.exitCode); -} - -/** Mirrors eslint-seatbelt's own boolean env var parsing: unset/empty is unset, "0"/"false"/"no" (case-insensitive) is false, anything else is true. */ -function readSeatbeltBooleanEnvVar(value: string | undefined): boolean | undefined { - if (value === undefined || value === '') { - return undefined; - } - return !['0', 'false', 'no'].includes(value.toLowerCase()); -} - -// eslint-seatbelt only rewrites the row for a file it actually lints, so a deleted or renamed -// file's row is never revisited and lingers in the baseline forever (a dead-code gap in -// eslint-seatbelt itself: https://github.com/justjake/eslint-seatbelt/issues/15). Finish the job -// by dropping rows for files that no longer exist, mirroring the same readOnly default as -// config/eslint/eslint.config.mjs and the same SEATBELT_READ_ONLY/SEATBELT_INCREASE/SEATBELT_DISABLE -// escape hatches eslint-seatbelt itself honors, so pruning never dirties a local worktree. -const seatbeltPath = `${projectRoot}/config/eslint/eslint.seatbelt.tsv`; -const isSeatbeltIncreaseSet = !!process.env.SEATBELT_INCREASE; -const seatbeltArgs = SeatbeltArgs.fromConfig({ - seatbeltFile: seatbeltPath, - disable: readSeatbeltBooleanEnvVar(process.env.SEATBELT_DISABLE) ?? false, - frozen: readSeatbeltBooleanEnvVar(seatbeltFrozenEnv) ?? false, - readOnly: isSeatbeltIncreaseSet ? false : (readSeatbeltBooleanEnvVar(process.env.SEATBELT_READ_ONLY) ?? !process.env.CI), -}); -if (!seatbeltArgs.disable) { - const seatbeltFile = SeatbeltFile.readSync(seatbeltPath); - const filenames = Array.from(seatbeltFile.filenames()); - const missingFilenames = (await Promise.all(filenames.map(async (filename) => ((await file(filename).exists()) ? undefined : filename)))).filter( - (filename): filename is string => filename !== undefined, - ); - - let removedCount = 0; - for (const filename of missingFilenames) { - if (seatbeltFile.removeFile(filename, seatbeltArgs)) { - removedCount++; - } - } - if (removedCount > 0) { - console.log(`eslint-seatbelt: removed ${removedCount} baseline row(s) for deleted files`); - if (!seatbeltArgs.frozen && !seatbeltArgs.readOnly) { - seatbeltFile.writeSync(); - } - } -} - -// Fail if a new inline eslint-disable bypasses the Onyx.connect() ban (rulesdir/no-onyx-connect), -// checking the same targets as ESLint above. Reached only when ESLint itself passes. -if (await checkOnyxConnectBypass(lintTargets)) { - process.exit(1); -} +import './lint/index'; diff --git a/scripts/lint/Formatter.ts b/scripts/lint/Formatter.ts new file mode 100644 index 000000000000..d2b983b2dc2f --- /dev/null +++ b/scripts/lint/Formatter.ts @@ -0,0 +1,12 @@ +import type {FormatterResult, LintMessage} from './types'; + +/** + * Port: turn the final message list into human-readable output and counts. + */ +abstract class Formatter { + abstract readonly name: string; + + abstract format(messages: LintMessage[]): FormatterResult; +} + +export default Formatter; diff --git a/scripts/lint/LintPipeline.ts b/scripts/lint/LintPipeline.ts new file mode 100644 index 000000000000..9aa3d1705db6 --- /dev/null +++ b/scripts/lint/LintPipeline.ts @@ -0,0 +1,65 @@ +import type Formatter from './Formatter'; +import type Linter from './Linter'; +import type Processor from './Processor'; +import type {LintMessage} from './types'; + +import Bench from '../utils/Bench'; + +type PipelineResult = { + messages: LintMessage[]; + errorCount: number; + warningCount: number; + reportText: string; + exitCode: number; +}; + +/** + * Application service: run a Linter, then each Processor in order, then a Formatter. + * Fatal linter exits (`exitCode > 1`) skip processors and surface stderr as the report. + */ +class Pipeline { + constructor( + private readonly projectRoot: string, + private readonly linter: Linter, + private readonly processors: readonly Processor[], + private readonly formatter: Formatter, + private readonly bench = new Bench(), + ) {} + + async run(targets: string[]): Promise { + const linterResult = await this.bench.measure(this.linter.name, () => this.linter.run(targets)); + + if (linterResult.exitCode > 1) { + return { + messages: [], + errorCount: 0, + warningCount: 0, + reportText: linterResult.stderr.trim(), + exitCode: linterResult.exitCode, + }; + } + + let messages = linterResult.files.flatMap((file) => file.messages); + const context = { + projectRoot: this.projectRoot, + lintedFiles: linterResult.files.map((file) => file.filePath), + }; + + for (const processor of this.processors) { + const incoming = messages; + messages = await this.bench.measure(processor.name, () => processor.process(incoming, context)); + } + + const report = this.bench.measureSync(this.formatter.name, () => this.formatter.format(messages)); + return { + messages, + errorCount: report.errorCount, + warningCount: report.warningCount, + reportText: report.text, + exitCode: report.errorCount > 0 ? 1 : 0, + }; + } +} + +export default Pipeline; +export type {PipelineResult}; diff --git a/scripts/lint/Linter.ts b/scripts/lint/Linter.ts new file mode 100644 index 000000000000..fdd7acd3ea83 --- /dev/null +++ b/scripts/lint/Linter.ts @@ -0,0 +1,13 @@ +import type {LinterResult} from './types'; + +/** + * Port: produce diagnostics for a set of paths. Implementations may spawn a + * CLI, call a library, or return a fixture — the pipeline only sees LinterResult. + */ +abstract class Linter { + abstract readonly name: string; + + abstract run(targets: string[]): Promise; +} + +export default Linter; diff --git a/scripts/lint/Processor.ts b/scripts/lint/Processor.ts new file mode 100644 index 000000000000..2c19f54d3155 --- /dev/null +++ b/scripts/lint/Processor.ts @@ -0,0 +1,13 @@ +import type {LintMessage, ProcessorContext} from './types'; + +/** + * Port: transform a message list. A processor may rewrite, drop, or demote + * messages, and may perform side effects (e.g. writing a baseline file). + */ +abstract class Processor { + abstract readonly name: string; + + abstract process(messages: LintMessage[], context: ProcessorContext): Promise; +} + +export default Processor; diff --git a/scripts/lint/eslint/ESLintLinter.ts b/scripts/lint/eslint/ESLintLinter.ts new file mode 100644 index 000000000000..45c1da36b03f --- /dev/null +++ b/scripts/lint/eslint/ESLintLinter.ts @@ -0,0 +1,148 @@ +import {$} from 'bun'; + +import type {LintFileResult, LintMessage, LintSeverity, LinterResult} from '../types'; + +import Linter from '../Linter'; + +const ESLINT_RULE_ID_KEY = 'ruleId' as const; + +type ESLintJSONMessage = { + // ESLint's JSON output uses this key; normalize it to ruleID below. + [ESLINT_RULE_ID_KEY]: string | null; + severity: number; + message: string; + line?: number; + column?: number; + endLine?: number; + endColumn?: number; + suggestions?: unknown; + fix?: unknown; +}; + +type ESLintJSONResult = { + filePath: string; + messages: ESLintJSONMessage[]; + source?: string; + suppressedMessages?: unknown[]; +}; + +type ESLintLinterOptions = { + projectRoot: string; + useCache: boolean; + fix: boolean; + concurrency?: string; + nodeOptions?: string; +}; + +const PARSE_FAILURE_EXIT_CODE = 2; + +function normalizeSeverity(severity: number): LintSeverity { + return severity >= 2 ? 2 : 1; +} + +function isESLintJSONResult(value: unknown): value is ESLintJSONResult { + return typeof value === 'object' && value !== null && 'filePath' in value && 'messages' in value; +} + +function normalizeESLintResults(results: ESLintJSONResult[]): LintFileResult[] { + return results.map((result) => ({ + filePath: result.filePath, + source: result.source, + messages: result.messages.map( + (message): LintMessage => ({ + filePath: result.filePath, + ruleID: message[ESLINT_RULE_ID_KEY], + severity: normalizeSeverity(message.severity), + message: message.message, + line: message.line ?? 0, + column: message.column ?? 0, + endLine: message.endLine, + endColumn: message.endColumn, + suggestions: message.suggestions, + fix: message.fix, + }), + ), + })); +} + +/** Babel / file-progress may write to stdout around the JSON array. */ +function extractJSONArray(text: string): string | null { + const start = text.indexOf('['); + const end = text.lastIndexOf(']'); + if (start < 0 || end <= start) { + return null; + } + return text.slice(start, end + 1); +} + +function parseFailureOutput(stdout: string, stderr: string, exitCode: number): LinterResult { + return { + files: [], + exitCode: Math.max(PARSE_FAILURE_EXIT_CODE, exitCode), + stderr: `${stderr}\nFailed to parse ESLint JSON output.\n${stdout.slice(0, 500)}`, + }; +} + +/** + * Turn ESLint stdout into structured results. A missing/invalid JSON payload is + * fatal (`exitCode > 1`) even when ESLint itself exited 0 or 1 — otherwise + * the pipeline would flatten zero messages and report a clean pass. + */ +function parseESLintStdout(stdout: string, stderr: string, exitCode: number): LinterResult { + const jsonText = extractJSONArray(stdout); + if (!jsonText) { + return parseFailureOutput(stdout, stderr, exitCode); + } + + let parsed: unknown; + try { + parsed = JSON.parse(jsonText); + } catch { + return parseFailureOutput(stdout, stderr, exitCode); + } + if (!Array.isArray(parsed)) { + return parseFailureOutput(stdout, stderr, exitCode); + } + + return {files: normalizeESLintResults(parsed.filter(isESLintJSONResult)), exitCode, stderr}; +} + +/** + * Spawn ESLint as a JSON producer. Processors are not wired in the ESLint + * config — this is the raw linter output the pipeline consumes. + * + * `--no-inline-config` is intentionally *not* passed: disable comments must + * still work. `--quiet` is also not passed here; the formatter filters + * warnings after seatbelt demotes grandfathered errors. + */ +class ESLintLinter extends Linter { + readonly name = 'eslint'; + + constructor(private readonly options: ESLintLinterOptions) { + super(); + } + + async run(targets: string[]): Promise { + const eslintArgs: string[] = ['--format', 'json', '--no-warn-ignored']; + if (this.options.useCache) { + eslintArgs.push('--cache', '--cache-location=node_modules/.cache/eslint', '--cache-strategy', 'content'); + } + if (this.options.fix) { + eslintArgs.push('--fix'); + } + eslintArgs.push(`--concurrency=${this.options.concurrency ?? process.env.ESLINT_CONCURRENCY ?? 'auto'}`, ...targets); + + const nodeOptions = this.options.nodeOptions ?? process.env.NODE_OPTIONS ?? '--max_old_space_size=8192'; + const result = await $`npx eslint ${eslintArgs}` + .cwd(this.options.projectRoot) + .env({...process.env, NODE_OPTIONS: nodeOptions, LINT_PIPELINE: '1'}) + .nothrow() + .quiet(); + + return parseESLintStdout(result.stdout.toString(), result.stderr.toString(), result.exitCode); + } +} + +export default ESLintLinter; +export {normalizeESLintResults, parseESLintStdout}; +export type {ESLintJSONResult, ESLintLinterOptions}; diff --git a/scripts/lint/formatters/StylishFormatter.ts b/scripts/lint/formatters/StylishFormatter.ts new file mode 100644 index 000000000000..be156f0b0c37 --- /dev/null +++ b/scripts/lint/formatters/StylishFormatter.ts @@ -0,0 +1,57 @@ +import path from 'node:path'; + +import type {FormatterResult, LintMessage} from '../types'; + +import Formatter from '../Formatter'; + +function relativePath(projectRoot: string, filePath: string): string { + return path.relative(projectRoot, filePath) || filePath; +} + +function formatMessage(projectRoot: string, message: LintMessage): string { + const loc = `${relativePath(projectRoot, message.filePath)}:${message.line}:${message.column}`; + const level = message.severity >= 2 ? 'error' : 'warning'; + const rule = message.ruleID ? ` ${message.ruleID}` : ''; + return `${loc}\n ${level} ${message.message}${rule}`; +} + +class StylishFormatter extends Formatter { + readonly name = 'stylish'; + + constructor( + private readonly projectRoot: string, + private readonly showWarnings: boolean, + ) { + super(); + } + + format(messages: LintMessage[]): FormatterResult { + const visible = this.showWarnings ? messages : messages.filter((message) => message.severity >= 2); + const errorCount = messages.filter((message) => message.severity >= 2).length; + const warningCount = messages.filter((message) => message.severity < 2).length; + + if (visible.length === 0) { + return {text: '', errorCount, warningCount}; + } + + const byFile = new Map(); + for (const message of visible) { + const list = byFile.get(message.filePath) ?? []; + list.push(message); + byFile.set(message.filePath, list); + } + + const blocks: string[] = []; + for (const fileMessages of byFile.values()) { + blocks.push(fileMessages.map((message) => formatMessage(this.projectRoot, message)).join('\n')); + } + + const summary = this.showWarnings + ? `\n\n${errorCount} error${errorCount === 1 ? '' : 's'}, ${warningCount} warning${warningCount === 1 ? '' : 's'}` + : `\n\n${errorCount} error${errorCount === 1 ? '' : 's'}`; + + return {text: `${blocks.join('\n\n')}${summary}`, errorCount, warningCount}; + } +} + +export default StylishFormatter; diff --git a/scripts/lint/index.ts b/scripts/lint/index.ts new file mode 100644 index 000000000000..ca7fe1fb25a0 --- /dev/null +++ b/scripts/lint/index.ts @@ -0,0 +1,89 @@ +#!/usr/bin/env bun + +/** + * Lint runner: run a Linter, then each Processor, then a Formatter. + * + * bun scripts/lint/index.ts -> lint the whole repo + * bun scripts/lint/index.ts src/foo.ts ... -> lint just the given paths + * bun scripts/lint/index.ts --show-warnings ... -> include grandfathered seatbelt warnings + * bun scripts/lint/index.ts --timings -> print per-stage wall times + */ + +import CLI from 'expensify-common/CLI'; + +import checkOnyxConnectBypass from '../checkOnyxConnectBypass'; +import Bench from '../utils/Bench'; +import ESLintLinter from './eslint/ESLintLinter'; +import StylishFormatter from './formatters/StylishFormatter'; +import Pipeline from './LintPipeline'; +import ReactCompilerFilter from './processors/ReactCompilerFilter'; +import Seatbelt, {resolveSeatbeltOptions} from './processors/Seatbelt'; +import StratifyNoDeprecated from './processors/StratifyNoDeprecated'; + +const projectRoot = `${import.meta.dir}/../..`; + +/* CLI argv uses kebab-case for flags documented in help */ +/* eslint-disable @typescript-eslint/naming-convention */ +const cli = new CLI({ + flags: { + 'no-cache': { + description: 'Disable the ESLint content cache', + }, + 'show-warnings': { + description: 'Include grandfathered seatbelt warnings in the report', + }, + fix: { + description: 'Apply ESLint auto-fixes', + }, + timings: { + description: 'Print per-stage wall times', + }, + }, + positionalArgs: [ + { + name: 'targets', + description: 'Files or directories to lint (default: the whole repo)', + variadic: true, + default: ['.'], + }, + ], +}); +/* eslint-enable @typescript-eslint/naming-convention */ + +const lintTargets = cli.positionalArgs.targets.length > 0 ? cli.positionalArgs.targets : ['.']; +const showTimings = cli.flags.timings || process.env.LINT_TIMINGS === '1'; +const bench = new Bench(); + +const pipeline = new Pipeline( + projectRoot, + new ESLintLinter({ + projectRoot, + useCache: !cli.flags['no-cache'], + fix: cli.flags.fix, + }), + [new ReactCompilerFilter(), new StratifyNoDeprecated(), new Seatbelt(resolveSeatbeltOptions(projectRoot))], + new StylishFormatter(projectRoot, cli.flags['show-warnings']), + bench, +); + +const result = await pipeline.run(lintTargets); + +if (result.reportText) { + if (result.exitCode > 1) { + console.error(result.reportText); + } else { + console.log(result.reportText); + } +} + +if (showTimings) { + console.error(bench.format('lint timings')); +} + +if (result.exitCode !== 0) { + process.exit(result.exitCode); +} + +if (await checkOnyxConnectBypass(lintTargets)) { + process.exit(1); +} diff --git a/scripts/lint/processors/ReactCompilerFilter.ts b/scripts/lint/processors/ReactCompilerFilter.ts new file mode 100644 index 000000000000..cb4545d2169a --- /dev/null +++ b/scripts/lint/processors/ReactCompilerFilter.ts @@ -0,0 +1,226 @@ +import {file} from 'bun'; +import path from 'node:path'; + +import type {LintMessage, ProcessorContext} from '../types'; + +import WorkerPool from '../../utils/WorkerPool'; +import Processor from '../Processor'; + +const RULES_SUPPRESSED_BY_REACT_COMPILER = new Set(['react/jsx-no-constructed-context-values', 'rulesdir/no-inline-useOnyx-selector']); +const EXHAUSTIVE_DEPS_USECALLBACK_USEMEMO_PATTERN = /\buseCallback\(\) Hook\b|\buseMemo\(\) Hook\b/; +const CACHE_DIR = 'node_modules/.cache/react-compiler'; +const REACT_COMPILER_FINGERPRINT_FILES = [ + 'babel.config.js', + 'config/babel/reactCompilerConfig.js', + 'config/reactCompiler/checkBoth.mjs', + 'config/reactCompiler/checkWithBabel.mjs', + 'config/reactCompiler/checkWithOxc.mjs', + 'config/rsbuild/rsbuild.common.ts', + 'package-lock.json', + 'scripts/lint/processors/ReactCompilerFilter.ts', + 'scripts/lint/processors/ReactCompilerWorker.ts', + 'scripts/utils/WorkerPool.ts', +] as const; + +type CompilerCheck = (source: string, filename: string) => boolean | Promise; + +function isSuppressibleMessage(message: LintMessage): boolean { + if (message.ruleID !== null && RULES_SUPPRESSED_BY_REACT_COMPILER.has(message.ruleID)) { + return true; + } + return message.ruleID === 'react-hooks/exhaustive-deps' && EXHAUSTIVE_DEPS_USECALLBACK_USEMEMO_PATTERN.test(message.message); +} + +function shouldSkipCompiler(filename: string): boolean { + return filename.includes('/tests/') || filename.includes('node_modules/'); +} + +function cachePath(projectRoot: string, hash: string): string { + return `${projectRoot}/${CACHE_DIR}/${hash}`; +} + +function cacheKey(fingerprint: string | undefined, filename: string, source: string): string { + return Bun.hash(`${fingerprint}\0${path.extname(filename)}\0${source}`).toString(16); +} + +async function getReactCompilerFingerprint(projectRoot: string): Promise { + const contents = await Promise.all( + REACT_COMPILER_FINGERPRINT_FILES.map(async (relativePath) => { + const content = await file(`${projectRoot}/${relativePath}`) + .text() + .catch(() => ''); + return `${relativePath}\0${content}`; + }), + ); + return Bun.hash(contents.join('\0')).toString(16); +} + +async function readCache(cacheFilePath: string): Promise { + const handle = file(cacheFilePath); + if (!(await handle.exists())) { + return undefined; + } + const text = await handle.text(); + if (text === '1') { + return true; + } + if (text === '0') { + return false; + } + return undefined; +} + +const CACHE_HIT = '1'; +const CACHE_MISS = '0'; + +async function writeCache(cacheFilePath: string, bothMemoized: boolean): Promise { + await Bun.write(cacheFilePath, bothMemoized ? CACHE_HIT : CACHE_MISS); +} + +type Candidate = { + filename: string; + source: string; +}; + +type CompilerWorkerResponse = { + filename: string; + bothMemoized: boolean; +}; + +function conservativeFallback(candidate: Candidate): CompilerWorkerResponse { + return {filename: candidate.filename, bothMemoized: false}; +} + +async function checkCandidatesWithPool(candidates: Candidate[], checkBoth: CompilerCheck | undefined, workerCount: number): Promise> { + const memoized = new Map(); + if (candidates.length === 0) { + return memoized; + } + + if (checkBoth) { + await Promise.all( + candidates.map(async (candidate) => { + try { + memoized.set(candidate.filename, await checkBoth(candidate.source, candidate.filename)); + } catch { + // Conservative: keep the message rather than aborting the whole lint. + memoized.set(candidate.filename, false); + } + }), + ); + return memoized; + } + + const pool = new WorkerPool(new URL('./ReactCompilerWorker.ts', import.meta.url), workerCount); + const responses = await pool.map(candidates, conservativeFallback); + for (const response of responses) { + memoized.set(response.filename, response.bothMemoized); + } + return memoized; +} + +/** + * Drop suppressible React-Compiler-redundant messages, but only after both + * compilers memoize the file. Files with no suppressible message are skipped + * entirely (~60× fewer compiler invocations than the ESLint processor). + * + * Cache is one file per compiler fingerprint and content hash so concurrent + * writes of the same key are benign and need no lock. + */ +class ReactCompilerFilter extends Processor { + readonly name = 'react-compiler-filter'; + + constructor( + private readonly checkBoth?: CompilerCheck, + private readonly workerCount = navigator.hardwareConcurrency || 4, + ) { + super(); + } + + process(messages: LintMessage[], context: ProcessorContext): Promise { + return filterReactCompilerMessages(messages, context.projectRoot, this.checkBoth, this.workerCount); + } +} + +async function filterReactCompilerMessages( + messages: LintMessage[], + projectRoot: string, + checkBoth?: CompilerCheck, + workerCount = navigator.hardwareConcurrency || 4, +): Promise { + const byFile = new Map(); + for (const message of messages) { + const list = byFile.get(message.filePath) ?? []; + list.push(message); + byFile.set(message.filePath, list); + } + + const candidateNames: string[] = []; + for (const [filename, fileMessages] of byFile) { + if (shouldSkipCompiler(filename)) { + continue; + } + if (fileMessages.some(isSuppressibleMessage)) { + candidateNames.push(filename); + } + } + + if (candidateNames.length === 0) { + return messages; + } + + const uncached: Candidate[] = []; + const memoized = new Map(); + const reactCompilerFingerprint = checkBoth ? undefined : await getReactCompilerFingerprint(projectRoot); + + await Promise.all( + candidateNames.map(async (filename) => { + let source = ''; + if (!checkBoth) { + try { + source = await file(filename).text(); + } catch { + memoized.set(filename, false); + return; + } + try { + const cached = await readCache(cachePath(projectRoot, cacheKey(reactCompilerFingerprint, filename, source))); + if (cached !== undefined) { + memoized.set(filename, cached); + return; + } + } catch { + // Conservative: recompute rather than abort the lint. + } + } + uncached.push({filename, source}); + }), + ); + + const computed = await checkCandidatesWithPool(uncached, checkBoth, workerCount); + await Promise.all( + uncached.map(async (candidate) => { + const bothMemoized = computed.get(candidate.filename) ?? false; + memoized.set(candidate.filename, bothMemoized); + if (checkBoth) { + return; + } + try { + await writeCache(cachePath(projectRoot, cacheKey(reactCompilerFingerprint, candidate.filename, candidate.source)), bothMemoized); + } catch { + // Conservative: skip the cache write rather than abort the lint. + } + }), + ); + + return messages.filter((message) => { + if (!memoized.get(message.filePath)) { + return true; + } + return !isSuppressibleMessage(message); + }); +} + +export default ReactCompilerFilter; +export {EXHAUSTIVE_DEPS_USECALLBACK_USEMEMO_PATTERN, filterReactCompilerMessages, isSuppressibleMessage, RULES_SUPPRESSED_BY_REACT_COMPILER}; +export type {CompilerCheck}; diff --git a/scripts/lint/processors/ReactCompilerWorker.ts b/scripts/lint/processors/ReactCompilerWorker.ts new file mode 100644 index 000000000000..bc7b34b789fe --- /dev/null +++ b/scripts/lint/processors/ReactCompilerWorker.ts @@ -0,0 +1,28 @@ +// The compiler helper is ESM (.mjs); Bun resolves it without the extension. +// eslint-disable-next-line import/extensions +import {didBothCompilersMemoizeFile} from '../../../config/reactCompiler/checkBoth.mjs'; + +type WorkerRequest = { + filename: string; + source: string; +}; + +type WorkerResponse = { + filename: string; + bothMemoized: boolean; +}; + +declare const self: Worker; + +self.onmessage = (event: MessageEvent) => { + const {filename, source} = event.data; + let bothMemoized = false; + try { + bothMemoized = didBothCompilersMemoizeFile(source, filename); + } catch { + // Conservative: treat a compiler crash as "not memoized" so this file + // keeps its suppressible messages instead of aborting the whole lint. + } + const response: WorkerResponse = {filename, bothMemoized}; + postMessage(response); +}; diff --git a/scripts/lint/processors/Seatbelt.ts b/scripts/lint/processors/Seatbelt.ts new file mode 100644 index 000000000000..582a11a74c55 --- /dev/null +++ b/scripts/lint/processors/Seatbelt.ts @@ -0,0 +1,583 @@ +import {file} from 'bun'; +import {rename} from 'node:fs/promises'; +import path from 'node:path'; + +import type {LintMessage, ProcessorContext, SeatbeltOptions, SeatbeltRuleSet} from '../types'; + +import Processor from '../Processor'; + +const SEATBELT_NAME = 'eslint-seatbelt'; +const SEATBELT_TSV_RELATIVE = 'config/eslint/eslint.seatbelt.tsv'; +const COMMENT_LINE_REGEX = /^\s*#/; +const NON_EMPTY_LINE_REGEX = /\S+/; +const DEFAULT_FILE_HEADER = `# ${SEATBELT_NAME} temporarily allowed errors +# docs: https://github.com/justjake/${SEATBELT_NAME}#readme`; + +type SeatbeltFileLine = { + encoded?: string; + filename: string; + ruleID: string; + maxErrors: number; +}; + +type SeatbeltFileData = { + maxErrors?: Map; + lines: SeatbeltFileLine[]; +}; + +type SeatbeltApplyResult = { + messages: LintMessage[]; + tsv: string; + wrote: boolean; + changed: boolean; +}; + +function ruleSetHas(ruleSet: SeatbeltRuleSet, ruleID: string): boolean { + return ruleSet === 'all' || ruleSet.has(ruleID); +} + +function encodeLine(line: SeatbeltFileLine): string { + return `${JSON.stringify(line.filename)}\t${JSON.stringify(line.ruleID)}\t${line.maxErrors}\n`; +} + +function parseJSONString(value: string | undefined, column: string, index: number): string { + if (value === undefined) { + throw new Error(`Missing ${column} at line ${index + 1}`); + } + const parsed: unknown = JSON.parse(value); + if (typeof parsed !== 'string') { + throw new Error(`Expected ${column} to be a JSON string at line ${index + 1}`); + } + return parsed; +} + +function parseJSONNumber(value: string | undefined, column: string, index: number): number { + if (value === undefined) { + throw new Error(`Missing ${column} at line ${index + 1}`); + } + const parsed: unknown = JSON.parse(value); + if (typeof parsed !== 'number') { + throw new Error(`Expected ${column} to be a JSON number at line ${index + 1}`); + } + return parsed; +} + +function decodeLine(line: string, index: number): SeatbeltFileLine { + const lineParts = line.split('\t'); + if (lineParts.length !== 3) { + throw new Error(`Expected 3 tab-separated JSON strings at line ${index + 1}, instead have ${lineParts.length}`); + } + return { + encoded: line, + filename: parseJSONString(lineParts.at(0), 'filename', index), + ruleID: parseJSONString(lineParts.at(1), 'ruleID', index), + maxErrors: parseJSONNumber(lineParts.at(2), 'maxErrors', index), + }; +} + +function parseMaxErrors(lines: SeatbeltFileLine[]): Map { + const maxErrors = new Map(); + for (const row of lines) { + maxErrors.set(row.ruleID, row.maxErrors); + } + return maxErrors; +} + +function toRelativePath(seatbeltFile: string, filename: string): string { + if (!path.isAbsolute(filename)) { + return filename; + } + return path.relative(path.dirname(seatbeltFile), filename); +} + +function toAbsolutePath(seatbeltFile: string, filename: string): string { + if (path.isAbsolute(filename)) { + return filename; + } + return path.resolve(path.dirname(seatbeltFile), filename); +} + +function parseSeatbeltTSV(text: string): {data: Map; comments: string} { + const data = new Map(); + const split = text.split(/(?<=\n)/); + const lines = split.filter((line) => NON_EMPTY_LINE_REGEX.test(line) && !COMMENT_LINE_REGEX.test(line)).map(decodeLine); + const comments = split.filter((line) => COMMENT_LINE_REGEX.test(line)).join(''); + for (const line of lines) { + let fileState = data.get(line.filename); + if (!fileState) { + fileState = {maxErrors: undefined, lines: []}; + data.set(line.filename, fileState); + } + fileState.lines.push(line); + } + return {data, comments: comments.trim()}; +} + +function serializeSeatbeltTSV(data: Map, comments: string): string { + const lines: string[] = []; + for (const [filename, fileState] of data) { + if (fileState.maxErrors) { + fileState.lines = []; + for (const [ruleID, maxErrorCount] of fileState.maxErrors) { + fileState.lines.push({filename, ruleID, maxErrors: maxErrorCount}); + } + } + for (const line of fileState.lines) { + line.encoded ??= encodeLine(line); + lines.push(line.encoded); + } + } + lines.sort(); + return comments ? `${comments}\n\n${lines.join('')}` : lines.join(''); +} + +function getMaxErrors(data: Map, relativeFilename: string): Map | undefined { + const fileState = data.get(relativeFilename); + if (!fileState) { + return undefined; + } + fileState.maxErrors ??= parseMaxErrors(fileState.lines); + return fileState.maxErrors; +} + +function isCountableError(message: LintMessage): message is LintMessage & {ruleID: string} { + return message.severity >= 2 && !!message.ruleID; +} + +function countRuleIDs(messages: readonly LintMessage[]): Map { + const counts = new Map(); + for (const message of messages) { + if (!isCountableError(message)) { + continue; + } + counts.set(message.ruleID, (counts.get(message.ruleID) ?? 0) + 1); + } + return counts; +} + +function pluralErrors(count: number): string { + return count === 1 ? 'error' : 'errors'; +} + +function compareMessages(a: LintMessage, b: LintMessage): number { + if (a.filePath !== b.filePath) { + return a.filePath < b.filePath ? -1 : 1; + } + if (a.ruleID === null) { + return b.ruleID === null ? 0 : -1; + } + if (b.ruleID === null) { + return 1; + } + if (a.ruleID !== b.ruleID) { + return a.ruleID < b.ruleID ? -1 : 1; + } + if (a.line !== b.line) { + return a.line - b.line; + } + return a.column - b.column; +} + +function verboseLog(options: SeatbeltOptions, makeMessage: () => string): void { + if (!options.verbose) { + return; + } + console.error(`[${SEATBELT_NAME}]:`, makeMessage()); +} + +function messageOverMaxErrorCount(message: LintMessage, errorCount: number, maxErrorCount: number): LintMessage { + return { + ...message, + message: `${message.message} +[${SEATBELT_NAME}]: There are ${errorCount} ${pluralErrors(errorCount)} of this type, but only ${maxErrorCount} are allowed. +Remove ${errorCount - maxErrorCount} to turn these errors into warnings.`.trim(), + }; +} + +function messageOverMaxErrorCountButIncreaseAllowed(message: LintMessage, errorCount: number, maxErrorCount: number): LintMessage { + const increaseCount = errorCount - maxErrorCount; + return { + ...message, + severity: 1, + message: `${message.message} +[${SEATBELT_NAME}]: SEATBELT_INCREASE: Temporarily allowing ${increaseCount} new ${pluralErrors(increaseCount)} of this type.`.trim(), + }; +} + +function messageAtMaxErrorCount(message: LintMessage, errorCount: number): LintMessage { + return { + ...message, + severity: 1, + message: `${message.message} +[${SEATBELT_NAME}]: This file is temporarily allowed to have ${errorCount} ${pluralErrors(errorCount)} of this type. +Please tend the garden by fixing if you have the time.`.trim(), + }; +} + +function messageUnderMaxErrorCount(message: LintMessage, errorCount: number, maxErrorCount: number): LintMessage { + const fixed = maxErrorCount - errorCount; + const fixedMessage = fixed === 1 ? 'one' : `${fixed} errors`; + return { + ...message, + severity: 1, + message: `${message.message} +[${SEATBELT_NAME}]: This file is temporarily allowed to have ${maxErrorCount} ${pluralErrors(maxErrorCount)} of this type. +Thank you for fixing ${fixedMessage}, it really helps.`.trim(), + }; +} + +function messageFrozenUnderMaxErrorCountText(seatbeltFilename: string, errorCount: number, maxErrorCount: number): string { + const fixed = maxErrorCount - errorCount; + const fixedMessage = fixed === 1 ? 'error' : 'errors'; + return `[${SEATBELT_NAME}]: SEATBELT_FROZEN: Expected ${maxErrorCount} ${pluralErrors(maxErrorCount)}, found ${errorCount}. +If you fixed ${fixed} ${fixedMessage}, thank you, but you'll need to update the seatbelt file to match. +Try running eslint, then committing ${seatbeltFilename}.`.trim(); +} + +function messageFrozenUnderMaxErrorCount(message: LintMessage, seatbeltFilename: string, errorCount: number, maxErrorCount: number): LintMessage { + return { + ...message, + severity: 1, + message: `${message.message}\n${messageFrozenUnderMaxErrorCountText(seatbeltFilename, errorCount, maxErrorCount)}`, + }; +} + +function transformMessages(options: SeatbeltOptions, data: Map, filename: string, messages: LintMessage[]): LintMessage[] { + const relativeFilename = toRelativePath(options.seatbeltFile, filename); + const ruleToMaxErrorCount = getMaxErrors(data, relativeFilename); + const allowIncrease = options.allowIncreaseRules === 'all' || options.allowIncreaseRules.size > 0; + if (!ruleToMaxErrorCount && !allowIncrease) { + return messages; + } + + const ruleToErrorCount = countRuleIDs(messages); + const demoteRemaining = new Map(); + const seenVerbose = new Set(); + + return messages.flatMap((message) => { + if (message.ruleID === null) { + verboseLog(options, () => `${filename}:${message.line}:${message.column}: cannot transform message with null ruleID`); + return message; + } + if (!isCountableError(message)) { + return message; + } + + const errorCount = ruleToErrorCount.get(message.ruleID); + if (errorCount === undefined) { + throw new Error(`${SEATBELT_NAME} bug: errorCount not found for rule ${message.ruleID}`); + } + + const maxErrorCount = ruleToMaxErrorCount?.get(message.ruleID) ?? 0; + const allowThisIncrease = ruleSetHas(options.allowIncreaseRules, message.ruleID); + if (maxErrorCount === 0 && !allowThisIncrease) { + return message; + } + + if (errorCount > maxErrorCount) { + if (allowThisIncrease) { + if (options.quiet) { + return []; + } + return messageOverMaxErrorCountButIncreaseAllowed(message, errorCount, maxErrorCount); + } + if (options.verbose && !seenVerbose.has(message.ruleID)) { + seenVerbose.add(message.ruleID); + verboseLog(options, () => `${filename}: rule ${message.ruleID}: error: ${errorCount} ${pluralErrors(errorCount)} found > max ${maxErrorCount}`); + } + const remaining = demoteRemaining.get(message.ruleID) ?? maxErrorCount; + if (remaining > 0) { + demoteRemaining.set(message.ruleID, remaining - 1); + if (options.quiet) { + return []; + } + return messageAtMaxErrorCount(message, maxErrorCount); + } + return messageOverMaxErrorCount(message, errorCount, maxErrorCount); + } + + if (errorCount === maxErrorCount) { + if (options.verbose && !seenVerbose.has(message.ruleID)) { + seenVerbose.add(message.ruleID); + verboseLog(options, () => `${filename}: rule ${message.ruleID}: ok: ${errorCount} ${pluralErrors(errorCount)} found == max ${maxErrorCount}`); + } + if (options.quiet) { + return []; + } + return messageAtMaxErrorCount(message, errorCount); + } + + if (options.frozen) { + return messageFrozenUnderMaxErrorCount(message, options.seatbeltFile, errorCount, maxErrorCount); + } + if (options.quiet) { + return []; + } + return messageUnderMaxErrorCount(message, errorCount, maxErrorCount); + }); +} + +function updateMaxErrors( + options: SeatbeltOptions, + data: Map, + filename: string, + ruleToErrorCount: ReadonlyMap, +): {removedRules: Set; changed: boolean} { + const removedRules = new Set(); + let increasedRulesCount = 0; + let decreasedRulesCount = 0; + const relativeFilename = toRelativePath(options.seatbeltFile, filename); + getMaxErrors(data, relativeFilename); + const existing = data.get(relativeFilename)?.maxErrors; + const maxErrors = new Map(existing ?? []); + + for (const [ruleID, errorCount] of ruleToErrorCount) { + const maxErrorCount = maxErrors.get(ruleID) ?? 0; + if (errorCount === maxErrorCount) { + continue; + } + if (errorCount < maxErrorCount || ruleSetHas(options.allowIncreaseRules, ruleID)) { + verboseLog(options, () => + options.frozen + ? `${filename}: rule ${ruleID}: SEATBELT_FROZEN: didn't update max errors ${maxErrorCount} -> ${errorCount}` + : `${filename}: rule ${ruleID}: update max errors ${maxErrorCount} -> ${errorCount}`, + ); + maxErrors.set(ruleID, errorCount); + if (errorCount > maxErrorCount) { + increasedRulesCount++; + } else { + decreasedRulesCount++; + } + } + } + + if (options.verbose || options.keepRules !== 'all') { + for (const [ruleID, maxErrorCount] of [...maxErrors]) { + const shouldRemove = maxErrorCount === 0 || !ruleToErrorCount.has(ruleID); + if (!shouldRemove) { + continue; + } + if (ruleSetHas(options.keepRules, ruleID)) { + verboseLog(options, () => `${filename}: rule ${ruleID}: SEATBELT_KEEP: didn't update max errors ${maxErrorCount} -> 0`); + continue; + } + verboseLog(options, () => + options.frozen + ? `${filename}: rule ${ruleID}: SEATBELT_FROZEN: didn't update max errors ${maxErrorCount} -> 0` + : `${filename}: rule ${ruleID}: update max errors ${maxErrorCount} -> 0`, + ); + maxErrors.delete(ruleID); + removedRules.add(ruleID); + } + } + + const changed = increasedRulesCount > 0 || decreasedRulesCount > 0 || removedRules.size > 0; + if (changed && !options.frozen) { + const fileState = data.get(relativeFilename); + if (fileState) { + fileState.maxErrors = maxErrors; + } else { + data.set(relativeFilename, {maxErrors, lines: []}); + } + } + + return {removedRules, changed: changed && !options.frozen}; +} + +function frozenRemovedRuleMessages(filename: string, seatbeltFile: string, removedRules: Set, maxErrorsBefore: ReadonlyMap | undefined): LintMessage[] { + if (removedRules.size === 0) { + return []; + } + return [...removedRules].map((ruleID) => { + const maxErrorCount = maxErrorsBefore?.get(ruleID); + if (maxErrorCount === undefined) { + throw new Error(`${SEATBELT_NAME} bug: maxErrorCount not found for removed frozen rule ${ruleID}`); + } + return { + filePath: filename, + ruleID, + column: 0, + line: 1, + severity: 2 as const, + message: messageFrozenUnderMaxErrorCountText(seatbeltFile, 0, maxErrorCount), + }; + }); +} + +/** + * Sort by (filename, ruleID, line, column) so "the first N of M" demotions are + * deterministic regardless of the linter's thread order. + */ +function canonicalizeMessages(messages: LintMessage[]): LintMessage[] { + return [...messages].sort(compareMessages); +} + +async function writeTSVAtomically(seatbeltFile: string, tsv: string): Promise { + const tempPath = `${seatbeltFile}.${process.pid}.${Date.now()}.tmp`; + await Bun.write(tempPath, tsv); + try { + await rename(tempPath, seatbeltFile); + } catch (error) { + if (error instanceof Error && 'code' in error && error.code === 'EXDEV') { + await Bun.write(seatbeltFile, tsv); + await file(tempPath) + .unlink() + .catch(() => undefined); + return; + } + throw error; + } +} + +class Seatbelt extends Processor { + readonly name = 'seatbelt'; + + constructor(private readonly options: SeatbeltOptions) { + super(); + } + + async process(messages: LintMessage[], context: ProcessorContext): Promise { + const result = await applySeatbelt(messages, this.options, context.lintedFiles); + return result.messages; + } +} + +async function applySeatbelt(messages: LintMessage[], options: SeatbeltOptions, lintedFilenames: Iterable): Promise { + if (options.disable) { + const existing = await file(options.seatbeltFile) + .text() + .catch(() => DEFAULT_FILE_HEADER); + return {messages, tsv: existing.endsWith('\n') || existing.length === 0 ? existing : `${existing}\n`, wrote: false, changed: false}; + } + + const existingText = await file(options.seatbeltFile) + .text() + .catch(() => ''); + const {data, comments} = existingText ? parseSeatbeltTSV(existingText) : {data: new Map(), comments: DEFAULT_FILE_HEADER}; + + const canonical = canonicalizeMessages(messages); + const byFile = new Map(); + for (const message of canonical) { + const list = byFile.get(message.filePath) ?? []; + list.push(message); + byFile.set(message.filePath, list); + } + + for (const filename of lintedFilenames) { + if (!byFile.has(filename)) { + byFile.set(filename, []); + } + } + + const transformed: LintMessage[] = []; + let anyChanged = false; + + for (const [filename, fileMessages] of byFile) { + const relativeFilename = toRelativePath(options.seatbeltFile, filename); + const maxErrorsBefore = getMaxErrors(data, relativeFilename); + const maxErrorsBeforeCopy = maxErrorsBefore ? new Map(maxErrorsBefore) : undefined; + const after = transformMessages(options, data, filename, fileMessages); + const ruleToErrorCount = countRuleIDs(fileMessages); + const {removedRules, changed} = updateMaxErrors(options, data, filename, ruleToErrorCount); + anyChanged ||= changed; + if (options.frozen && removedRules.size > 0) { + transformed.push(...after, ...frozenRemovedRuleMessages(filename, options.seatbeltFile, removedRules, maxErrorsBeforeCopy)); + } else { + transformed.push(...after); + } + } + + // Dead-row prune: drop baseline rows whose source file no longer exists. + // Native fix for justjake/eslint-seatbelt#15 — previously a post-hoc pass in scripts/lint.ts. + let pruned = 0; + for (const relativeFilename of [...data.keys()]) { + const absolute = toAbsolutePath(options.seatbeltFile, relativeFilename); + if (await file(absolute).exists()) { + continue; + } + if (options.frozen) { + verboseLog(options, () => `${relativeFilename}: SEATBELT_FROZEN: didn't remove max errors`); + continue; + } + verboseLog(options, () => `${relativeFilename}: remove max errors`); + data.delete(relativeFilename); + anyChanged = true; + pruned++; + } + const tsv = serializeSeatbeltTSV(data, comments || DEFAULT_FILE_HEADER); + const shouldWrite = anyChanged && !options.frozen && !options.readOnly; + if (pruned > 0) { + const verb = shouldWrite ? 'removed' : 'would remove'; + console.log(`eslint-seatbelt: ${verb} ${pruned} baseline row(s) for deleted files`); + } + if (shouldWrite) { + await writeTSVAtomically(options.seatbeltFile, tsv); + } + + return {messages: canonicalizeMessages(transformed), tsv, wrote: shouldWrite, changed: anyChanged}; +} + +/** + * Mirrors eslint-seatbelt's boolean env parsing: unset/empty is unset, "0"/"false"/"no" + * (case-insensitive) is false, anything else is true. + */ +function readBooleanEnvVar(value: string | undefined): boolean | undefined { + if (value === undefined || value === '') { + return undefined; + } + return !['0', 'false', 'no'].includes(value.toLowerCase()); +} + +/** + * Mirrors eslint-seatbelt's rule-set env parsing: unset is unset, empty is [], + * "all"/"1"/"true" is "all", otherwise a whitespace-or-comma-separated list. + */ +function parseRuleSetEnvVar(value: string | undefined): SeatbeltRuleSet | undefined { + if (value === undefined) { + return undefined; + } + if (!value) { + return new Set(); + } + const lower = value.toLowerCase(); + if (lower === 'all' || lower === '1' || lower === 'true') { + return 'all'; + } + return new Set(value.split(/[\s,]+/g).filter(Boolean)); +} + +/** + * Seatbelt env/config: + * - `SEATBELT_FROZEN` defaults to false (the wrapper forces `0` so `CI=true` does not freeze). + * - `readOnly` defaults to `!CI`; `SEATBELT_INCREASE` forces writes. + */ +function resolveSeatbeltOptions(projectRoot: string, env: NodeJS.ProcessEnv = process.env): SeatbeltOptions { + const allowIncreaseRules = parseRuleSetEnvVar(env.SEATBELT_INCREASE) ?? new Set(); + const isIncreaseSet = allowIncreaseRules === 'all' || allowIncreaseRules.size > 0; + return { + seatbeltFile: `${projectRoot}/${SEATBELT_TSV_RELATIVE}`, + projectRoot, + disable: readBooleanEnvVar(env.SEATBELT_DISABLE) ?? false, + frozen: readBooleanEnvVar(env.SEATBELT_FROZEN) ?? false, + readOnly: isIncreaseSet ? false : (readBooleanEnvVar(env.SEATBELT_READ_ONLY) ?? !env.CI), + allowIncreaseRules, + keepRules: parseRuleSetEnvVar(env.SEATBELT_KEEP) ?? new Set(), + quiet: readBooleanEnvVar(env.SEATBELT_QUIET) ?? false, + verbose: readBooleanEnvVar(env.SEATBELT_VERBOSE) ?? false, + }; +} + +export default Seatbelt; +export { + applySeatbelt, + canonicalizeMessages, + compareMessages, + countRuleIDs, + parseSeatbeltTSV, + resolveSeatbeltOptions, + serializeSeatbeltTSV, + toRelativePath, + transformMessages, + updateMaxErrors, +}; +export type {SeatbeltApplyResult, SeatbeltFileData}; diff --git a/scripts/lint/processors/StratifyNoDeprecated.ts b/scripts/lint/processors/StratifyNoDeprecated.ts new file mode 100644 index 000000000000..8ff5b87ea2d4 --- /dev/null +++ b/scripts/lint/processors/StratifyNoDeprecated.ts @@ -0,0 +1,185 @@ +import {parse} from '@babel/parser'; +import {file} from 'bun'; + +import type {LintMessage} from '../types'; + +import Processor from '../Processor'; + +const NO_DEPRECATED_RULE_ID = '@typescript-eslint/no-deprecated'; +const NON_CHILD_KEYS = new Set(['loc', 'start', 'end', 'extra', 'leadingComments', 'trailingComments', 'innerComments']); +const MEMBER_LIKE_TYPES = new Set(['MemberExpression', 'OptionalMemberExpression', 'TSQualifiedName']); + +type ASTNode = { + type: string; + start: number; + end: number; + [key: string]: unknown; +}; + +function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null; +} + +const isASTNode = (value: unknown): value is ASTNode => { + if (!isRecord(value)) { + return false; + } + return typeof value.type === 'string' && typeof value.start === 'number' && typeof value.end === 'number'; +}; + +function* astChildren(node: ASTNode): Generator { + for (const [key, value] of Object.entries(node)) { + if (NON_CHILD_KEYS.has(key)) { + continue; + } + for (const child of Array.isArray(value) ? value : [value]) { + if (isASTNode(child)) { + yield child; + } + } + } +} + +function lineColumnToOffset(source: string, line: number, column: number): number { + let lineStart = 0; + for (let currentLine = 1; currentLine < line; currentLine++) { + const nextNewline = source.indexOf('\n', lineStart); + if (nextNewline < 0) { + return -1; + } + lineStart = nextNewline + 1; + } + return lineStart + column - 1; +} + +function findASTPathAtOffset(root: ASTNode, offset: number): ASTNode[] | null { + if (offset < 0 || offset < root.start || offset > root.end) { + return null; + } + const path = [root]; + while (true) { + const current = path.at(-1); + if (!current) { + return path; + } + let descended = false; + for (const child of astChildren(current)) { + if (offset >= child.start && offset <= child.end) { + path.push(child); + descended = true; + break; + } + } + if (!descended) { + return path; + } + } +} + +function topOfMemberChain(path: ASTNode[]): ASTNode { + let topIndex = path.length - 1; + while (topIndex > 0 && MEMBER_LIKE_TYPES.has(path.at(topIndex - 1)?.type ?? '')) { + topIndex--; + } + const top = path.at(topIndex); + if (!top) { + throw new Error('empty AST path'); + } + return top; +} + +function parseSourceOrNull(source: string): ASTNode | null { + try { + const parsed: unknown = parse(source, {sourceType: 'module', plugins: ['typescript', 'jsx']}); + return isASTNode(parsed) ? parsed : null; + } catch { + return null; + } +} + +function getDeprecatedExpressionFromSource(source: string, ast: ASTNode, message: LintMessage): string | null { + const offset = lineColumnToOffset(source, message.line, message.column); + const path = findASTPathAtOffset(ast, offset); + if (!path) { + return null; + } + const top = topOfMemberChain(path); + return source.slice(top.start, top.end); +} + +function getSymbolNameFromMessage(message: LintMessage): string | null { + const match = /^`([^`]+)`/.exec(message.message); + return match ? (match.at(1) ?? null) : null; +} + +function toRuleIDSuffix(apiName: string): string { + return apiName.trim().replaceAll(/[\s/]+/g, '_'); +} + +function stratifyMessages(messages: LintMessage[], source: string | null): LintMessage[] { + const hasNoDeprecatedMessages = messages.some((message) => message.ruleID === NO_DEPRECATED_RULE_ID); + const ast = source && hasNoDeprecatedMessages ? parseSourceOrNull(source) : null; + + return messages.map((message) => { + if (message.ruleID !== NO_DEPRECATED_RULE_ID) { + return message; + } + const apiName = (source !== null && ast !== null ? getDeprecatedExpressionFromSource(source, ast, message) : null) ?? getSymbolNameFromMessage(message); + if (!apiName) { + return message; + } + return {...message, ruleID: `${NO_DEPRECATED_RULE_ID}/${toRuleIDSuffix(apiName)}`}; + }); +} + +/** + * Rewrite `@typescript-eslint/no-deprecated` into per-API rule IDs so the + * ratchet can tighten each deprecated symbol independently. Only files that + * actually carry a `no-deprecated` message are re-parsed. + */ +class StratifyNoDeprecated extends Processor { + readonly name = 'stratify-no-deprecated'; + + process(messages: LintMessage[]): Promise { + return stratifyNoDeprecated(messages); + } +} + +async function stratifyNoDeprecated(messages: LintMessage[]): Promise { + const filesNeedingSource = new Set(); + for (const message of messages) { + if (message.ruleID === NO_DEPRECATED_RULE_ID) { + filesNeedingSource.add(message.filePath); + } + } + if (filesNeedingSource.size === 0) { + return messages; + } + + const sources = new Map(); + await Promise.all( + [...filesNeedingSource].map(async (filename) => { + try { + sources.set(filename, await file(filename).text()); + } catch { + sources.set(filename, null); + } + }), + ); + + const byFile = new Map(); + for (const message of messages) { + const list = byFile.get(message.filePath) ?? []; + list.push(message); + byFile.set(message.filePath, list); + } + + const rewritten: LintMessage[] = []; + for (const [filename, fileMessages] of byFile) { + rewritten.push(...stratifyMessages(fileMessages, sources.get(filename) ?? null)); + } + return rewritten; +} + +export default StratifyNoDeprecated; +export {NO_DEPRECATED_RULE_ID, stratifyMessages, stratifyNoDeprecated, toRuleIDSuffix}; diff --git a/scripts/lint/types.ts b/scripts/lint/types.ts new file mode 100644 index 000000000000..971f82330e62 --- /dev/null +++ b/scripts/lint/types.ts @@ -0,0 +1,62 @@ +/** + * Linter-agnostic diagnostic produced by a Linter (ESLint today, Oxlint later) + * and consumed by processors and the formatter. + * + * `severity` matches ESLint: 2 = error, 1 = warning. Seatbelt only ratchets + * countable errors (severity 2, non-null ruleID). + */ +type LintSeverity = 1 | 2; + +type LintMessage = { + filePath: string; + ruleID: string | null; + severity: LintSeverity; + message: string; + line: number; + column: number; + endLine?: number; + endColumn?: number; + suggestions?: unknown; + fix?: unknown; +}; + +type LintFileResult = { + filePath: string; + messages: LintMessage[]; + source?: string; +}; + +type LinterResult = { + files: LintFileResult[]; + /** Non-zero when the linter itself crashed or rejected the config. */ + exitCode: number; + /** Stderr from the linter process, for surfacing crashes. */ + stderr: string; +}; + +type ProcessorContext = { + projectRoot: string; + lintedFiles: string[]; +}; + +type FormatterResult = { + text: string; + errorCount: number; + warningCount: number; +}; + +type SeatbeltRuleSet = 'all' | Set; + +type SeatbeltOptions = { + seatbeltFile: string; + projectRoot: string; + disable: boolean; + frozen: boolean; + readOnly: boolean; + allowIncreaseRules: SeatbeltRuleSet; + keepRules: SeatbeltRuleSet; + quiet: boolean; + verbose: boolean; +}; + +export type {FormatterResult, LintFileResult, LintMessage, LintSeverity, LinterResult, ProcessorContext, SeatbeltOptions, SeatbeltRuleSet}; diff --git a/scripts/lintChanged.sh b/scripts/lintChanged.sh index 997e2b7bc3fb..63419206ff3e 100755 --- a/scripts/lintChanged.sh +++ b/scripts/lintChanged.sh @@ -27,10 +27,10 @@ if ! GIT_DIFF_OUTPUT="$(git diff --diff-filter=AMR --name-only "$MERGE_BASE_SHA_ exit 1 fi -# Run eslint on the changed files, forwarding any user-provided flags +# Run lint on the changed files. Flags must precede paths; this script puts "$@" first. if [[ -n "$GIT_DIFF_OUTPUT" ]] ; then # shellcheck disable=SC2086 # For multiple files in variable - exec bun "${TOP}/scripts/lint.ts" "$@" $GIT_DIFF_OUTPUT + exec bun "${TOP}/scripts/lint/index.ts" "$@" $GIT_DIFF_OUTPUT else info "No lintable files changed" fi diff --git a/scripts/onyxConnectBypass.ts b/scripts/onyxConnectBypass.ts index 444ebfd53bb4..18a7c6d906b0 100644 --- a/scripts/onyxConnectBypass.ts +++ b/scripts/onyxConnectBypass.ts @@ -2,18 +2,23 @@ * Detection logic for new `eslint-disable` bypasses of the Onyx.connect() ban. * * `rulesdir/no-onyx-connect` (shipped by eslint-config-expensify) is a normal lint rule, so an - * inline `eslint-disable` can silence it. ESLint records such silenced violations as "suppressed - * messages". This module finds suppressed `rulesdir/no-onyx-connect` violations and flags any that - * go beyond the disables already present on `main`, so a new bypass can be re-elevated to an error - * at the runner level — where no disable directive can reach it. + * inline `eslint-disable` can silence it. The lint runner re-elevates those disables by scanning + * source for disable directives that name the ban or blanket directives that cover a real call. No + * disable directive can reach this check because it does not go through ESLint's message pipeline. + * + * Blanket `eslint-disable` / `eslint-disable-next-line` with no rule list counts only when it + * covers a real Onyx.connect() call. Unrelated blanket comments (e.g. around ReportUtils) remain + * ignored. Call sites are found via the TypeScript AST so comments and grouping parens cannot + * hide a banned member access from a source scan. */ -import type {ESLint} from 'eslint'; -import path from 'node:path'; +import ts from 'typescript'; /** Rule id of the Onyx.connect() ban, as exposed through eslint-plugin-rulesdir. */ const BANNED_RULE_ID = 'rulesdir/no-onyx-connect'; +const BANNED_RULE_NAME = 'no-onyx-connect'; + /** * Disables of the ban that already exist on `main`, keyed by repo-relative path with the number of * occurrences in each file. Migrating these call sites to useOnyx() is already in progress; any @@ -30,20 +35,136 @@ type SuppressedBan = { line: number; }; -/** The fields of an ESLint result this module reads; real `ESLint.LintResult`s satisfy it. */ -type ResultWithSuppressed = Pick; +type DirectiveMatch = { + index: number; + text: string; + kind?: string; + args: string; +}; -/** Pull suppressed `no-onyx-connect` violations out of ESLint results, keyed by repo-relative path. */ -function collectSuppressedBans(results: readonly ResultWithSuppressed[], projectRoot: string): SuppressedBan[] { - const bans: SuppressedBan[] = []; - for (const result of results) { - for (const message of result.suppressedMessages ?? []) { - if (message.ruleId !== BANNED_RULE_ID) { - continue; +function collectDirectiveMatches(source: string, directive: 'disable' | 'enable'): DirectiveMatch[] { + const matches: DirectiveMatch[] = []; + const scanner = ts.createScanner(ts.ScriptTarget.Latest, false, ts.LanguageVariant.Standard, source); + while (scanner.scan() !== ts.SyntaxKind.EndOfFileToken) { + const token = scanner.getToken(); + if (token !== ts.SyntaxKind.SingleLineCommentTrivia && token !== ts.SyntaxKind.MultiLineCommentTrivia) { + continue; + } + const index = scanner.getTokenStart(); + const text = scanner.getTokenText(); + const body = text.startsWith('//') ? text.slice(2) : text.slice(2, -2); + const directiveMatch = body.match(new RegExp(`^\\s*eslint-${directive}(?-next-line|-line)?(?[\\s\\S]*)$`)); + if (!directiveMatch) { + continue; + } + matches.push({index, text, kind: directiveMatch.groups?.kind, args: directiveMatch.groups?.args ?? ''}); + } + return matches; +} + +function directiveKind(match: DirectiveMatch): string | undefined { + return match.kind; +} + +function directiveArgs(match: DirectiveMatch): string { + return match.args; +} + +function unwrapExpression(node: ts.Expression): ts.Expression { + let current = node; + while (ts.isParenthesizedExpression(current) || ts.isAsExpression(current) || ts.isSatisfiesExpression(current) || ts.isNonNullExpression(current)) { + current = current.expression; + } + return current; +} + +function collectOnyxConnectCallOffsets(source: string, file: string): number[] { + const sourceFile = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX); + const offsets: number[] = []; + const visit = (node: ts.Node) => { + if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && !node.expression.questionDotToken && node.expression.name.text === 'connect') { + const object = unwrapExpression(node.expression.expression); + if (ts.isIdentifier(object) && object.text === 'Onyx') { + offsets.push(node.getStart(sourceFile)); + } + } + ts.forEachChild(node, visit); + }; + visit(sourceFile); + return offsets; +} + +function normalizedDirectiveArgs(args: string): string { + return args + .replace(/--[\s\S]*$/, '') + .replaceAll(/[\s*]+/g, ' ') + .trim(); +} + +function directiveTargetsBan(args: string): boolean { + const trimmed = normalizedDirectiveArgs(args); + if (trimmed.length === 0) { + return false; + } + return trimmed.split(',').some((part) => { + const rule = part.trim(); + return rule === BANNED_RULE_ID || rule === BANNED_RULE_NAME || rule.endsWith(`/${BANNED_RULE_NAME}`); + }); +} + +function isBlanketDirective(args: string): boolean { + return normalizedDirectiveArgs(args).length === 0; +} + +function lineNumberAtOffset(source: string, offset: number): number { + return source.slice(0, offset).split('\n').length; +} + +function blanketDirectiveCoversCall(source: string, match: DirectiveMatch, callOffsets: number[], enableMatches: DirectiveMatch[]): boolean { + const directiveLine = lineNumberAtOffset(source, match.index ?? 0); + const kind = directiveKind(match); + const directiveEnd = match.index + match.text.length; + return callOffsets.some((callOffset) => { + const callLine = lineNumberAtOffset(source, callOffset); + if (kind === '-line') { + return callLine === directiveLine; + } + if (kind === '-next-line') { + return callLine === directiveLine + 1; + } + if (callOffset <= directiveEnd) { + return false; + } + const reenabled = enableMatches.some((enableMatch) => { + const enableOffset = enableMatch.index; + if (enableOffset <= directiveEnd || enableOffset >= callOffset) { + return false; } - const file = path.relative(projectRoot, result.filePath).split(path.sep).join('/'); - bans.push({file, line: message.line}); + const enableArgs = directiveArgs(enableMatch); + return isBlanketDirective(enableArgs) || directiveTargetsBan(enableArgs); + }); + return !reenabled; + }); +} + +/** + * Find disable directives in `source` that suppress `rulesdir/no-onyx-connect`. + * Line numbers are 1-based. Matches both full-line and trailing `eslint-disable-line`. + */ +function collectDisableDirectivesFromSource(source: string, file: string): SuppressedBan[] { + const bans: SuppressedBan[] = []; + const callOffsets = collectOnyxConnectCallOffsets(source, file); + const enableMatches = collectDirectiveMatches(source, 'enable'); + for (const match of collectDirectiveMatches(source, 'disable')) { + const args = directiveArgs(match); + const targetsBan = directiveTargetsBan(args); + const coversBan = isBlanketDirective(args) && blanketDirectiveCoversCall(source, match, callOffsets, enableMatches); + if (!targetsBan && !coversBan) { + continue; } + const prefix = source.slice(0, match.index); + const line = prefix.split('\n').length; + bans.push({file, line}); } return bans; } @@ -69,5 +190,5 @@ function findNewBypasses(suppressedBans: readonly SuppressedBan[]): SuppressedBa return newBypasses; } -export {BANNED_RULE_ID, GRANDFATHERED_BYPASSES, collectSuppressedBans, findNewBypasses}; -export type {SuppressedBan, ResultWithSuppressed}; +export {BANNED_RULE_ID, BANNED_RULE_NAME, GRANDFATHERED_BYPASSES, collectDisableDirectivesFromSource, findNewBypasses}; +export type {SuppressedBan}; diff --git a/scripts/utils/Bench.ts b/scripts/utils/Bench.ts new file mode 100644 index 000000000000..41cdf2cf6129 --- /dev/null +++ b/scripts/utils/Bench.ts @@ -0,0 +1,66 @@ +type BenchMark = { + name: string; + ms: number; +}; + +/** + * Lightweight wall-clock instrumentation. Marks are recorded in insertion + * order so a printed summary matches the run. + */ +class Bench { + private readonly starts = new Map(); + + private readonly marks: BenchMark[] = []; + + start(name: string): void { + this.starts.set(name, performance.now()); + } + + end(name: string): number { + const startedAt = this.starts.get(name); + if (startedAt === undefined) { + throw new Error(`Bench.end(${name}) called without a matching start`); + } + this.starts.delete(name); + const ms = performance.now() - startedAt; + this.marks.push({name, ms}); + return ms; + } + + async measure(name: string, fn: () => Promise): Promise { + this.start(name); + try { + return await fn(); + } finally { + this.end(name); + } + } + + measureSync(name: string, fn: () => T): T { + this.start(name); + try { + return fn(); + } finally { + this.end(name); + } + } + + getMarks(): readonly BenchMark[] { + return this.marks; + } + + format(label = 'timings'): string { + if (this.marks.length === 0) { + return `${label}: (none)`; + } + const total = this.marks.reduce((sum, mark) => sum + mark.ms, 0); + const lines = this.marks.map((mark) => { + const percent = total === 0 ? 0 : (mark.ms / total) * 100; + return ` ${mark.name.padEnd(28)} ${mark.ms.toFixed(1).padStart(10)} ms ${percent.toFixed(1).padStart(5)}%`; + }); + return [`${label} (wall):`, ...lines, ` ${'total'.padEnd(28)} ${total.toFixed(1).padStart(10)} ms`].join('\n'); + } +} + +export default Bench; +export type {BenchMark}; diff --git a/scripts/utils/WorkerPool.ts b/scripts/utils/WorkerPool.ts new file mode 100644 index 000000000000..9d96d6184355 --- /dev/null +++ b/scripts/utils/WorkerPool.ts @@ -0,0 +1,98 @@ +type QueuedItem = { + item: TRequest; + index: number; +}; + +/** + * Fixed-size Bun/Web Worker pool. One request is in flight per worker; a dead + * worker falls back for its in-flight item and leaves the rest of the queue + * for surviving workers. Leftover items also use the fallback so a total + * pool crash cannot drop work on the floor. + */ +class WorkerPool { + private readonly workerURL: URL; + + private readonly concurrency: number; + + constructor(workerURL: URL, concurrency = 4) { + this.workerURL = workerURL; + this.concurrency = concurrency; + } + + async map(items: readonly TRequest[], fallback: (item: TRequest) => TResponse): Promise { + if (items.length === 0) { + return []; + } + + const results = new Map(); + const queue: Array> = items.map((item, index) => ({item, index})); + const poolSize = Math.max(1, Math.min(this.concurrency, items.length)); + const workers = Array.from({length: poolSize}, () => new Worker(this.workerURL)); + + try { + await Promise.all(workers.map((worker) => this.drain(worker, queue, results, fallback))); + } finally { + for (const worker of workers) { + worker.terminate(); + } + } + + for (const leftover of queue) { + results.set(leftover.index, fallback(leftover.item)); + } + + return items.map((item, index) => results.get(index) ?? fallback(item)); + } + + private drain(worker: Worker, queue: Array>, results: Map, fallback: (item: TRequest) => TResponse): Promise { + return new Promise((resolve) => { + let inFlight: QueuedItem | undefined; + let settled = false; + + const pump = () => { + if (settled) { + return; + } + const next = queue.pop(); + if (!next) { + settled = true; + resolve(); + return; + } + inFlight = next; + worker.postMessage(next.item); + }; + + const fail = () => { + if (settled) { + return; + } + settled = true; + if (inFlight) { + results.set(inFlight.index, fallback(inFlight.item)); + inFlight = undefined; + } + resolve(); + }; + + worker.addEventListener('message', (event: MessageEvent) => { + if (settled) { + return; + } + if (inFlight) { + results.set(inFlight.index, event.data); + inFlight = undefined; + } + pump(); + }); + + worker.addEventListener('error', fail); + worker.addEventListener('messageerror', fail); + worker.addEventListener('close', fail); + + pump(); + }); + } +} + +export default WorkerPool; diff --git a/tests/tooling/WorkerPool.test.ts b/tests/tooling/WorkerPool.test.ts new file mode 100644 index 000000000000..cd1e4ef7aae7 --- /dev/null +++ b/tests/tooling/WorkerPool.test.ts @@ -0,0 +1,53 @@ +import {describe, expect, it} from 'bun:test'; + +import WorkerPool from '../../scripts/utils/WorkerPool'; + +type EchoRequest = { + n: number; +}; + +type EchoResponse = { + n: number; +}; + +describe('WorkerPool', () => { + it('maps items across workers in input order', async () => { + const pool = new WorkerPool(new URL('./workerPoolEchoWorker.ts', import.meta.url), 2); + const result = await pool.map([{n: 1}, {n: 2}, {n: 3}, {n: 4}], (item) => ({n: item.n})); + expect(result).toEqual([{n: 2}, {n: 4}, {n: 6}, {n: 8}]); + }); + + it('uses the fallback when a worker dies', async () => { + const pool = new WorkerPool(new URL('./workerPoolCrashWorker.ts', import.meta.url), 2); + const result = await pool.map([{n: 1}, {n: 2}], (item) => ({n: -item.n})); + expect(result).toEqual([{n: -1}, {n: -2}]); + }); + + it('uses the fallback for work left after all workers die', async () => { + const pool = new WorkerPool(new URL('./workerPoolCrashWorker.ts', import.meta.url), 2); + const result = await pool.map([{n: 1}, {n: 2}, {n: 3}, {n: 4}], (item) => ({n: -item.n})); + expect(result).toEqual([{n: -1}, {n: -2}, {n: -3}, {n: -4}]); + }); + + it('continues processing queued work when one worker dies', async () => { + const pool = new WorkerPool(new URL('./workerPoolSelectiveCrashWorker.ts', import.meta.url), 2); + const result = await pool.map([{n: 1}, {n: 2}, {n: 3}, {n: 4}], (item) => ({n: -item.n})); + expect(result).toEqual([{n: 2}, {n: 4}, {n: -3}, {n: 8}]); + }); + + it('uses the fallback when a worker exits without an error event', async () => { + const pool = new WorkerPool(new URL('./workerPoolExitWorker.ts', import.meta.url), 1); + const result = await Promise.race([ + pool.map([{n: 1}, {n: 2}], (item) => ({n: -item.n})), + new Promise((_, reject) => { + setTimeout(() => reject(new Error('WorkerPool hung after worker exit')), 5_000); + }), + ]); + expect(result).toEqual([{n: -1}, {n: -2}]); + }); + + it('returns an empty array for no items', async () => { + const pool = new WorkerPool(new URL('./workerPoolEchoWorker.ts', import.meta.url), 2); + expect(await pool.map([], (item) => ({n: item.n}))).toEqual([]); + }); +}); diff --git a/tests/tooling/lintPipeline.test.ts b/tests/tooling/lintPipeline.test.ts new file mode 100644 index 000000000000..fbc180263c3e --- /dev/null +++ b/tests/tooling/lintPipeline.test.ts @@ -0,0 +1,218 @@ +import {describe, expect, it} from 'bun:test'; + +import type {ESLintJSONResult} from '../../scripts/lint/eslint/ESLintLinter'; +import type {LintMessage, LinterResult} from '../../scripts/lint/types'; + +import {normalizeESLintResults, parseESLintStdout} from '../../scripts/lint/eslint/ESLintLinter'; +import StylishFormatter from '../../scripts/lint/formatters/StylishFormatter'; +import Linter from '../../scripts/lint/Linter'; +import Pipeline from '../../scripts/lint/LintPipeline'; +import {filterReactCompilerMessages} from '../../scripts/lint/processors/ReactCompilerFilter'; +import Seatbelt, {resolveSeatbeltOptions} from '../../scripts/lint/processors/Seatbelt'; +import {stratifyMessages} from '../../scripts/lint/processors/StratifyNoDeprecated'; + +function makeMessage(overrides: Partial = {}): LintMessage { + return { + filePath: '/tmp/src/file.ts', + ruleID: 'no-console', + severity: 2, + message: 'x', + line: 1, + column: 1, + ...overrides, + }; +} + +class StubLinter extends Linter { + readonly name = 'stub'; + + constructor(private readonly result: LinterResult) { + super(); + } + + run(): Promise { + return Promise.resolve(this.result); + } +} + +describe('resolveSeatbeltOptions', () => { + const root = '/repo'; + + it('defaults readOnly on when CI is unset', () => { + const options = resolveSeatbeltOptions(root, {}); + expect(options.readOnly).toBe(true); + expect(options.frozen).toBe(false); + expect(options.disable).toBe(false); + }); + + it('defaults readOnly off in CI', () => { + expect(resolveSeatbeltOptions(root, {CI: 'true'}).readOnly).toBe(false); + }); + + it('lets SEATBELT_INCREASE force writes even locally', () => { + const options = resolveSeatbeltOptions(root, {SEATBELT_INCREASE: 'no-console'}); + expect(options.readOnly).toBe(false); + expect(options.allowIncreaseRules).toEqual(new Set(['no-console'])); + }); + + it('lets SEATBELT_INCREASE override SEATBELT_READ_ONLY', () => { + const options = resolveSeatbeltOptions(root, {SEATBELT_INCREASE: 'no-console', SEATBELT_READ_ONLY: '1'}); + expect(options.readOnly).toBe(false); + expect(options.allowIncreaseRules).toEqual(new Set(['no-console'])); + }); + + it('parses SEATBELT_INCREASE=ALL', () => { + expect(resolveSeatbeltOptions(root, {SEATBELT_INCREASE: 'ALL'}).allowIncreaseRules).toBe('all'); + }); + + it('treats 0/false/no as false for boolean env vars', () => { + expect(resolveSeatbeltOptions(root, {SEATBELT_DISABLE: '0'}).disable).toBe(false); + expect(resolveSeatbeltOptions(root, {SEATBELT_DISABLE: 'false'}).disable).toBe(false); + expect(resolveSeatbeltOptions(root, {SEATBELT_FROZEN: '1'}).frozen).toBe(true); + }); +}); + +describe('extractJSONArray via runESLint stdout', () => { + it('normalizes results even when babel logs wrap the JSON array', () => { + const wrapped = `babel.config.js\n - running in: undefined\n${JSON.stringify([ + {filePath: '/repo/src/a.ts', messages: [{ruleId: 'no-console', severity: 2, message: 'nope', line: 3, column: 4}]}, + ])}\n`; + const start = wrapped.indexOf('['); + const end = wrapped.lastIndexOf(']'); + const parsed: unknown = JSON.parse(wrapped.slice(start, end + 1)); + if (!Array.isArray(parsed)) { + throw new Error('expected JSON array'); + } + const [first] = normalizeESLintResults( + parsed.filter((value): value is ESLintJSONResult => { + return typeof value === 'object' && value !== null && 'filePath' in value && 'messages' in value; + }), + ); + expect(first?.messages.at(0)?.ruleID).toBe('no-console'); + }); +}); + +describe('normalizeESLintResults', () => { + it('copies ESLint JSON into the linter-agnostic message shape', () => { + const [result] = normalizeESLintResults([ + { + filePath: '/repo/src/a.ts', + messages: [{ruleId: 'no-console', severity: 2, message: 'nope', line: 3, column: 4}], + }, + ]); + expect(result.filePath).toBe('/repo/src/a.ts'); + expect(result.messages).toEqual([ + { + filePath: '/repo/src/a.ts', + ruleID: 'no-console', + severity: 2, + message: 'nope', + line: 3, + column: 4, + endLine: undefined, + endColumn: undefined, + suggestions: undefined, + fix: undefined, + }, + ]); + }); +}); + +describe('filterReactCompilerMessages', () => { + it('skips the compiler for files with no suppressible message', async () => { + let called = 0; + const messages = [makeMessage({ruleID: 'no-console'})]; + const result = await filterReactCompilerMessages(messages, '/tmp', () => { + called++; + return true; + }); + expect(called).toBe(0); + expect(result).toEqual(messages); + }); + + it('drops suppressible messages when both compilers memoize the file', async () => { + const messages = [ + makeMessage({ruleID: 'react/jsx-no-constructed-context-values'}), + makeMessage({ruleID: 'react-hooks/exhaustive-deps', message: 'React Hook useCallback() Hook is missing a dependency'}), + makeMessage({ruleID: 'no-console'}), + ]; + const result = await filterReactCompilerMessages(messages, '/tmp', () => true); + expect(result.map((message) => message.ruleID)).toEqual(['no-console']); + }); + + it('keeps suppressible messages when either compiler skips memoization', async () => { + const messages = [makeMessage({ruleID: 'react/jsx-no-constructed-context-values'})]; + const result = await filterReactCompilerMessages(messages, '/tmp', () => false); + expect(result).toEqual(messages); + }); + + it('does not suppress genuine exhaustive-deps missing-deps warnings', async () => { + const messages = [makeMessage({ruleID: 'react-hooks/exhaustive-deps', message: 'React Hook useEffect has a missing dependency: "foo"'})]; + const result = await filterReactCompilerMessages(messages, '/tmp', () => true); + expect(result).toEqual(messages); + }); + + it('keeps suppressible messages when a compiler check throws', async () => { + const messages = [makeMessage({ruleID: 'react/jsx-no-constructed-context-values'})]; + const result = await filterReactCompilerMessages(messages, '/tmp', () => { + throw new Error('compiler boom'); + }); + expect(result).toEqual(messages); + }); + + it('keeps suppressible messages when the source file cannot be read', async () => { + const messages = [makeMessage({filePath: '/tmp/does-not-exist.tsx', ruleID: 'react/jsx-no-constructed-context-values'})]; + const result = await filterReactCompilerMessages(messages, '/tmp'); + expect(result).toEqual(messages); + }); +}); + +describe('stratifyMessages', () => { + it('rewrites no-deprecated using the source expression at the lint location', () => { + const source = 'const x = StyleSheet.absoluteFillObject;\n'; + const messages = [makeMessage({ruleID: '@typescript-eslint/no-deprecated', message: '`absoluteFillObject` is deprecated.', line: 1, column: 11})]; + const result = stratifyMessages(messages, source); + expect(result.at(0)?.ruleID).toBe('@typescript-eslint/no-deprecated/StyleSheet.absoluteFillObject'); + }); + + it('falls back to the backtick symbol in the message when there is no source', () => { + const messages = [makeMessage({ruleID: '@typescript-eslint/no-deprecated', message: '`Foo.bar` is deprecated.'})]; + expect(stratifyMessages(messages, null).at(0)?.ruleID).toBe('@typescript-eslint/no-deprecated/Foo.bar'); + }); + + it('leaves other rules alone', () => { + const messages = [makeMessage({ruleID: 'no-console'})]; + expect(stratifyMessages(messages, 'console.log(1)\n')).toEqual(messages); + }); +}); + +describe('Pipeline', () => { + it('returns the linter exit code when the linter itself crashed', async () => { + const pipeline = new Pipeline( + '/tmp', + new StubLinter({files: [], exitCode: 2, stderr: 'oops'}), + [new Seatbelt(resolveSeatbeltOptions('/tmp', {SEATBELT_DISABLE: '1'}))], + new StylishFormatter('/tmp', false), + ); + const result = await pipeline.run(['.']); + expect(result.exitCode).toBe(2); + expect(result.reportText).toBe('oops'); + }); + + it('treats a JSON parse failure with ESLint exit 0 or 1 as fatal', async () => { + const parsed = parseESLintStdout('not json', '', 1); + expect(parsed.exitCode).toBe(2); + expect(parsed.files).toEqual([]); + expect(parsed.stderr).toContain('Failed to parse ESLint JSON output'); + + const pipeline = new Pipeline('/tmp', new StubLinter(parsed), [new Seatbelt(resolveSeatbeltOptions('/tmp', {SEATBELT_DISABLE: '1'}))], new StylishFormatter('/tmp', false)); + const result = await pipeline.run(['.']); + expect(result.exitCode).toBe(2); + expect(result.reportText).toContain('Failed to parse ESLint JSON output'); + expect(parseESLintStdout('', '', 0).exitCode).toBe(2); + }); + + it('preserves a linter crash exit code above 2 on parse failure', () => { + expect(parseESLintStdout('', 'oom', 137).exitCode).toBe(137); + }); +}); diff --git a/tests/tooling/lintSeatbelt.test.ts b/tests/tooling/lintSeatbelt.test.ts new file mode 100644 index 000000000000..a13dc3842149 --- /dev/null +++ b/tests/tooling/lintSeatbelt.test.ts @@ -0,0 +1,257 @@ +import {afterEach, describe, expect, it} from 'bun:test'; + +import {mkdtemp, rm, writeFile} from 'node:fs/promises'; +import {tmpdir} from 'node:os'; +import path from 'node:path'; + +import type {LintMessage, SeatbeltOptions} from '../../scripts/lint/types'; + +import {applySeatbelt, canonicalizeMessages, parseSeatbeltTSV, serializeSeatbeltTSV, transformMessages} from '../../scripts/lint/processors/Seatbelt'; + +function makeOptions(overrides: Partial = {}): SeatbeltOptions { + return { + seatbeltFile: '/tmp/config/eslint/eslint.seatbelt.tsv', + projectRoot: '/tmp', + disable: false, + frozen: false, + readOnly: true, + allowIncreaseRules: new Set(), + keepRules: new Set(), + quiet: false, + verbose: false, + ...overrides, + }; +} + +function makeMessage(ruleID: string, overrides: Partial = {}): LintMessage { + return { + filePath: '/tmp/src/file.ts', + ruleID, + severity: 2, + message: `Original: ${ruleID}`, + line: 1, + column: 1, + ...overrides, + }; +} + +describe('canonicalizeMessages', () => { + it('sorts by filename, ruleID, line, then column', () => { + const messages = [ + makeMessage('b', {filePath: '/z.ts', line: 2, column: 1}), + makeMessage('a', {filePath: '/a.ts', line: 9, column: 1}), + makeMessage('a', {filePath: '/a.ts', line: 1, column: 9}), + makeMessage('a', {filePath: '/a.ts', line: 1, column: 1}), + ]; + expect(canonicalizeMessages(messages).map((message) => `${message.filePath}:${message.ruleID}:${message.line}:${message.column}`)).toEqual([ + '/a.ts:a:1:1', + '/a.ts:a:1:9', + '/a.ts:a:9:1', + '/z.ts:b:2:1', + ]); + }); +}); + +describe('transformMessages', () => { + it('demotes errors at the baseline to warnings', () => { + const {data} = parseSeatbeltTSV(`"../../src/file.ts"\t"no-console"\t2\n`); + const result = transformMessages(makeOptions(), data, '/tmp/src/file.ts', [makeMessage('no-console'), makeMessage('no-console', {line: 2})]); + expect(result).toHaveLength(2); + expect(result.at(0)?.severity).toBe(1); + expect(result.at(0)?.message).toContain('tend the garden'); + }); + + it('keeps overflow errors as errors', () => { + const {data} = parseSeatbeltTSV(`"../../src/file.ts"\t"no-console"\t1\n`); + const result = transformMessages(makeOptions(), data, '/tmp/src/file.ts', [ + makeMessage('no-console', {line: 1}), + makeMessage('no-console', {line: 2}), + makeMessage('no-console', {line: 3}), + ]); + expect(result.filter((message) => message.severity === 2)).toHaveLength(2); + expect(result.filter((message) => message.severity === 1)).toHaveLength(1); + expect(result.at(-1)?.message).toContain('Remove'); + }); + + it('demotes the first N overflow occurrences, not an arbitrary subset', () => { + const {data} = parseSeatbeltTSV(`"../../src/file.ts"\t"no-console"\t1\n`); + const result = transformMessages(makeOptions(), data, '/tmp/src/file.ts', [makeMessage('no-console', {line: 10}), makeMessage('no-console', {line: 20})]); + expect(result.at(0)?.severity).toBe(1); + expect(result.at(0)?.line).toBe(10); + expect(result.at(1)?.severity).toBe(2); + expect(result.at(1)?.line).toBe(20); + }); + + it('quiet suppresses at-max warnings but keeps overflow errors', () => { + const {data} = parseSeatbeltTSV(`"../../src/file.ts"\t"no-console"\t1\n`); + const result = transformMessages(makeOptions({quiet: true}), data, '/tmp/src/file.ts', [makeMessage('no-console', {line: 1}), makeMessage('no-console', {line: 2})]); + expect(result).toHaveLength(1); + expect(result.at(0)?.severity).toBe(2); + }); + + it('frozen turns a decrease into a warning rather than writing', () => { + const {data} = parseSeatbeltTSV(`"../../src/file.ts"\t"no-console"\t5\n`); + const result = transformMessages(makeOptions({frozen: true}), data, '/tmp/src/file.ts', [makeMessage('no-console'), makeMessage('no-console', {line: 2})]); + expect(result.at(0)?.severity).toBe(1); + expect(result.at(0)?.message).toContain('SEATBELT_FROZEN'); + expect(result.at(0)?.message).toContain('eslint.seatbelt.tsv'); + expect(result.at(0)?.message).not.toContain('/tmp/src/file.ts'); + }); + + it('leaves unbaselined rules untouched', () => { + const {data} = parseSeatbeltTSV(`"../../src/file.ts"\t"no-console"\t1\n`); + const result = transformMessages(makeOptions(), data, '/tmp/src/file.ts', [makeMessage('no-debugger')]); + expect(result).toEqual([makeMessage('no-debugger')]); + }); +}); + +describe('serializeSeatbeltTSV', () => { + it('round-trips the committed header and row format', () => { + const original = `# eslint-seatbelt temporarily allowed errors +# docs: https://github.com/justjake/eslint-seatbelt#readme + +"../../src/a.ts" "no-console" 1 +"../../src/b.ts" "@typescript-eslint/no-deprecated/Foo" 2 +`; + const {data, comments} = parseSeatbeltTSV(original); + // Force reserialization through the maxErrors map (the write path). + for (const fileState of data.values()) { + fileState.maxErrors = new Map(fileState.lines.map((line) => [line.ruleID, line.maxErrors])); + } + expect(serializeSeatbeltTSV(data, comments)).toBe(original); + }); +}); + +describe('applySeatbelt', () => { + const dirs: string[] = []; + + afterEach(async () => { + await Promise.all(dirs.splice(0).map((dir) => rm(dir, {recursive: true, force: true}))); + }); + + async function tempDir(): Promise { + const dir = await mkdtemp(path.join(tmpdir(), 'lint-seatbelt-')); + dirs.push(dir); + return dir; + } + + it('does not write in readOnly mode when counts go down', async () => { + const dir = await tempDir(); + await writeFile(path.join(dir, 'src.ts'), 'x\n'); + const tsvPath = path.join(dir, 'eslint.seatbelt.tsv'); + const tsv = `"src.ts"\t"no-console"\t2\n`; + await writeFile(tsvPath, tsv); + + const result = await applySeatbelt([makeMessage('no-console', {filePath: path.join(dir, 'src.ts')})], makeOptions({seatbeltFile: tsvPath, projectRoot: dir, readOnly: true}), [ + path.join(dir, 'src.ts'), + ]); + + expect(result.wrote).toBe(false); + expect(result.changed).toBe(true); + expect(await Bun.file(tsvPath).text()).toBe(tsv); + }); + + it('writes a tightened TSV when readOnly is off', async () => { + const dir = await tempDir(); + await writeFile(path.join(dir, 'src.ts'), 'x\n'); + const tsvPath = path.join(dir, 'eslint.seatbelt.tsv'); + await writeFile(tsvPath, `"src.ts"\t"no-console"\t2\n`); + + const result = await applySeatbelt([makeMessage('no-console', {filePath: path.join(dir, 'src.ts')})], makeOptions({seatbeltFile: tsvPath, projectRoot: dir, readOnly: false}), [ + path.join(dir, 'src.ts'), + ]); + + expect(result.wrote).toBe(true); + expect(result.tsv).toContain('"src.ts"\t"no-console"\t1'); + expect(await Bun.file(tsvPath).text()).toBe(result.tsv); + }); + + it('fails an increase without SEATBELT_INCREASE and does not write', async () => { + const dir = await tempDir(); + await writeFile(path.join(dir, 'src.ts'), 'x\n'); + const tsvPath = path.join(dir, 'eslint.seatbelt.tsv'); + const original = `"src.ts"\t"no-console"\t1\n`; + await writeFile(tsvPath, original); + + const result = await applySeatbelt( + [makeMessage('no-console', {filePath: path.join(dir, 'src.ts'), line: 1}), makeMessage('no-console', {filePath: path.join(dir, 'src.ts'), line: 2})], + makeOptions({seatbeltFile: tsvPath, projectRoot: dir, readOnly: false}), + [path.join(dir, 'src.ts')], + ); + + expect(result.wrote).toBe(false); + expect(result.messages.some((message) => message.severity === 2 && message.message.includes('Remove'))).toBe(true); + expect(await Bun.file(tsvPath).text()).toBe(original); + }); + + it('allows an increase when SEATBELT_INCREASE names the rule', async () => { + const dir = await tempDir(); + await writeFile(path.join(dir, 'src.ts'), 'x\n'); + const tsvPath = path.join(dir, 'eslint.seatbelt.tsv'); + await writeFile(tsvPath, `"src.ts"\t"no-console"\t1\n`); + + const result = await applySeatbelt( + [makeMessage('no-console', {filePath: path.join(dir, 'src.ts'), line: 1}), makeMessage('no-console', {filePath: path.join(dir, 'src.ts'), line: 2})], + makeOptions({seatbeltFile: tsvPath, projectRoot: dir, readOnly: false, allowIncreaseRules: new Set(['no-console'])}), + [path.join(dir, 'src.ts')], + ); + + expect(result.wrote).toBe(true); + expect(result.tsv).toContain('"src.ts"\t"no-console"\t2'); + expect(result.messages.every((message) => message.severity === 1)).toBe(true); + }); + + it('prunes rows for deleted files on the write path', async () => { + const dir = await tempDir(); + await writeFile(path.join(dir, 'kept.ts'), 'x\n'); + const tsvPath = path.join(dir, 'eslint.seatbelt.tsv'); + await writeFile(tsvPath, `"gone.ts"\t"no-console"\t1\n"kept.ts"\t"no-console"\t1\n`); + + const result = await applySeatbelt([makeMessage('no-console', {filePath: path.join(dir, 'kept.ts')})], makeOptions({seatbeltFile: tsvPath, projectRoot: dir, readOnly: false}), [ + path.join(dir, 'kept.ts'), + ]); + + expect(result.wrote).toBe(true); + expect(result.tsv).not.toContain('gone.ts'); + expect(result.tsv).toContain('kept.ts'); + }); + + it('does not prune deleted-file rows when frozen', async () => { + const dir = await tempDir(); + await writeFile(path.join(dir, 'kept.ts'), 'x\n'); + const tsvPath = path.join(dir, 'eslint.seatbelt.tsv'); + const original = `"gone.ts"\t"no-console"\t1\n"kept.ts"\t"no-console"\t1\n`; + await writeFile(tsvPath, original); + + const result = await applySeatbelt( + [makeMessage('no-console', {filePath: path.join(dir, 'kept.ts')})], + makeOptions({seatbeltFile: tsvPath, projectRoot: dir, frozen: true, readOnly: false}), + [path.join(dir, 'kept.ts')], + ); + + expect(result.wrote).toBe(false); + expect(await Bun.file(tsvPath).text()).toBe(original); + }); + + it('names the TSV path when a frozen run drops a rule entirely', async () => { + const dir = await tempDir(); + await writeFile(path.join(dir, 'kept.ts'), 'x\n'); + const tsvPath = path.join(dir, 'eslint.seatbelt.tsv'); + await writeFile(tsvPath, `"kept.ts"\t"no-console"\t1\n`); + + const result = await applySeatbelt([], makeOptions({seatbeltFile: tsvPath, projectRoot: dir, frozen: true, readOnly: false}), [path.join(dir, 'kept.ts')]); + + expect(result.messages.some((message) => message.message.includes(tsvPath) && message.message.includes('SEATBELT_FROZEN'))).toBe(true); + expect(result.messages.some((message) => message.message.includes('kept.ts') && !message.message.includes(tsvPath))).toBe(false); + }); + + it('is a no-op when SEATBELT_DISABLE is set', async () => { + const dir = await tempDir(); + const tsvPath = path.join(dir, 'eslint.seatbelt.tsv'); + await writeFile(tsvPath, `"src.ts"\t"no-console"\t1\n`); + const incoming = [makeMessage('no-console')]; + const result = await applySeatbelt(incoming, makeOptions({seatbeltFile: tsvPath, disable: true}), ['/tmp/src/file.ts']); + expect(result.messages).toBe(incoming); + expect(result.wrote).toBe(false); + }); +}); diff --git a/tests/tooling/workerPoolCrashWorker.ts b/tests/tooling/workerPoolCrashWorker.ts new file mode 100644 index 000000000000..2a0665f5c576 --- /dev/null +++ b/tests/tooling/workerPoolCrashWorker.ts @@ -0,0 +1,5 @@ +declare const self: Worker; + +self.onmessage = () => { + throw new Error('boom'); +}; diff --git a/tests/tooling/workerPoolEchoWorker.ts b/tests/tooling/workerPoolEchoWorker.ts new file mode 100644 index 000000000000..870a1bee44db --- /dev/null +++ b/tests/tooling/workerPoolEchoWorker.ts @@ -0,0 +1,14 @@ +type EchoRequest = { + n: number; +}; + +type EchoResponse = { + n: number; +}; + +declare const self: Worker; + +self.onmessage = (event: MessageEvent) => { + const response: EchoResponse = {n: event.data.n * 2}; + postMessage(response); +}; diff --git a/tests/tooling/workerPoolExitWorker.ts b/tests/tooling/workerPoolExitWorker.ts new file mode 100644 index 000000000000..41a2fb8d8c45 --- /dev/null +++ b/tests/tooling/workerPoolExitWorker.ts @@ -0,0 +1,5 @@ +declare const self: Worker; + +self.onmessage = () => { + process.exit(1); +}; diff --git a/tests/tooling/workerPoolSelectiveCrashWorker.ts b/tests/tooling/workerPoolSelectiveCrashWorker.ts new file mode 100644 index 000000000000..c6b754057208 --- /dev/null +++ b/tests/tooling/workerPoolSelectiveCrashWorker.ts @@ -0,0 +1,17 @@ +type EchoRequest = { + n: number; +}; + +type EchoResponse = { + n: number; +}; + +declare const self: Worker; + +self.onmessage = (event: MessageEvent) => { + if (event.data.n === 3) { + throw new Error('boom'); + } + const response: EchoResponse = {n: event.data.n * 2}; + postMessage(response); +}; diff --git a/tests/unit/OnyxConnectBypassTest.ts b/tests/unit/OnyxConnectBypassTest.ts index 1930c13de48d..b0d3208c69bf 100644 --- a/tests/unit/OnyxConnectBypassTest.ts +++ b/tests/unit/OnyxConnectBypassTest.ts @@ -1,31 +1,84 @@ -import type {ESLint} from 'eslint'; +import {BANNED_RULE_ID, collectDisableDirectivesFromSource, findNewBypasses} from '../../scripts/onyxConnectBypass'; -import type {ResultWithSuppressed} from '../../scripts/onyxConnectBypass'; +const ONYX_CONNECT_CALL = `Onyx${'.connect'}`; +const onyxConnectCall = (key: string): string => `${ONYX_CONNECT_CALL}({key: "${key}"});`; -import {BANNED_RULE_ID, collectSuppressedBans, findNewBypasses} from '../../scripts/onyxConnectBypass'; +describe('collectDisableDirectivesFromSource', () => { + it('keeps only disable directives that name the no-onyx-connect ban', () => { + const source = [ + '// eslint-disable-next-line no-console', + 'console.log(1);', + `// eslint-disable-next-line ${BANNED_RULE_ID}`, + onyxConnectCall('x'), + '/* eslint-disable no-console */', + ].join('\n'); -const PROJECT_ROOT = '/repo'; + expect(collectDisableDirectivesFromSource(source, 'src/libs/Foo.ts')).toEqual([{file: 'src/libs/Foo.ts', line: 3}]); + }); + + it('flags a blanket eslint-disable that covers a banned call', () => { + const source = ['/* eslint-disable */', onyxConnectCall('x'), '// eslint-disable-next-line'].join('\n'); + + expect(collectDisableDirectivesFromSource(source, 'src/libs/Foo.ts')).toEqual([{file: 'src/libs/Foo.ts', line: 1}]); + }); + + it('flags a blanket disable covering a spaced or split Onyx.connect call', () => { + const spaced = ['/* eslint-disable */', `Onyx${' . connect'} ({key: "x"});`].join('\n'); + const split = ['/* eslint-disable */', `Onyx${'.'}`, ' connect({key: "x"});'].join('\n'); + + expect(collectDisableDirectivesFromSource(spaced, 'src/libs/Foo.ts')).toEqual([{file: 'src/libs/Foo.ts', line: 1}]); + expect(collectDisableDirectivesFromSource(split, 'src/libs/Foo.ts')).toEqual([{file: 'src/libs/Foo.ts', line: 1}]); + }); -function makeResult(relativePath: string, suppressedMessages: ESLint.LintResult['suppressedMessages']): ResultWithSuppressed { - return {filePath: `${PROJECT_ROOT}/${relativePath}`, suppressedMessages}; -} + it('flags a blanket disable covering commented or parenthesized Onyx.connect calls', () => { + const commented = ['/* eslint-disable */', `Onyx${' /* x */ . connect'}({key: "x"});`].join('\n'); + const parenthesized = ['/* eslint-disable */', `(Onyx)${'.connect'}({key: "x"});`].join('\n'); -function suppressed(ruleId: string, line: number): ESLint.LintResult['suppressedMessages'][number] { - return {ruleId, line, column: 1, message: 'x', severity: 2, suppressions: [{kind: 'directive', justification: ''}]}; -} + expect(collectDisableDirectivesFromSource(commented, 'src/libs/Foo.ts')).toEqual([{file: 'src/libs/Foo.ts', line: 1}]); + expect(collectDisableDirectivesFromSource(parenthesized, 'src/libs/Foo.ts')).toEqual([{file: 'src/libs/Foo.ts', line: 1}]); + }); -describe('collectSuppressedBans', () => { - it('keeps only suppressed no-onyx-connect violations and relativizes their paths', () => { - const results = [makeResult('src/libs/Foo.ts', [suppressed(BANNED_RULE_ID, 12), suppressed('no-console', 3)]), makeResult('src/libs/Bar.ts', [suppressed(BANNED_RULE_ID, 7)])]; + it('flags blanket line and next-line disables only when they cover a call', () => { + const source = [`${onyxConnectCall('line')} // eslint-disable-line`, '// eslint-disable-next-line', onyxConnectCall('next')].join('\n'); - expect(collectSuppressedBans(results, PROJECT_ROOT)).toEqual([ - {file: 'src/libs/Foo.ts', line: 12}, - {file: 'src/libs/Bar.ts', line: 7}, + expect(collectDisableDirectivesFromSource(source, 'src/libs/Foo.ts')).toEqual([ + {file: 'src/libs/Foo.ts', line: 1}, + {file: 'src/libs/Foo.ts', line: 2}, ]); }); - it('returns nothing when there are no suppressed messages', () => { - expect(collectSuppressedBans([makeResult('src/libs/Foo.ts', [])], PROJECT_ROOT)).toEqual([]); + it('ignores blanket disables that do not cover a banned call', () => { + const source = ['/* eslint-disable */', 'console.log(1);', '// eslint-disable-next-line', 'console.log(2);'].join('\n'); + + expect(collectDisableDirectivesFromSource(source, 'src/libs/Foo.ts')).toEqual([]); + }); + + it('ignores a blanket disable after the ban is re-enabled', () => { + const source = ['/* eslint-disable */', '/* eslint-enable */', onyxConnectCall('x')].join('\n'); + + expect(collectDisableDirectivesFromSource(source, 'src/libs/Foo.ts')).toEqual([]); + }); + + it('matches a trailing eslint-disable-line that names the ban', () => { + const source = `${onyxConnectCall('x')} // eslint-disable-line ${BANNED_RULE_ID}\n`; + + expect(collectDisableDirectivesFromSource(source, 'src/libs/Foo.ts')).toEqual([{file: 'src/libs/Foo.ts', line: 1}]); + }); + + it('matches a multiline block disable that names the ban after the first line', () => { + const source = ['/* eslint-disable no-console,', ` ${BANNED_RULE_ID} */`, onyxConnectCall('x')].join('\n'); + + expect(collectDisableDirectivesFromSource(source, 'src/libs/Foo.ts')).toEqual([{file: 'src/libs/Foo.ts', line: 1}]); + }); + + it('does not treat directive text in a string as an eslint-enable', () => { + const source = ['/* eslint-disable */', 'const text = "/* eslint-enable */";', onyxConnectCall('x')].join('\n'); + + expect(collectDisableDirectivesFromSource(source, 'src/libs/Foo.ts')).toEqual([{file: 'src/libs/Foo.ts', line: 1}]); + }); + + it('returns nothing when there are no matching directives', () => { + expect(collectDisableDirectivesFromSource('const x = 1;\n', 'src/libs/Foo.ts')).toEqual([]); }); });