diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0ef33ba..7a6c76a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,5 +64,5 @@ jobs: - name: Build run: nr build - # - name: Test - # run: nr test + - name: Test + run: nr test diff --git a/app/pages/configs.vue b/app/pages/configs.vue index 0be98ae..767bc0b 100644 --- a/app/pages/configs.vue +++ b/app/pages/configs.vue @@ -32,7 +32,7 @@ watchEffect(() => { fileMatchResult.value = matchFile( filters.filepath, payload.value.configs, - payload.value.configsIgnoreOnly, + payload.value.meta.basePath, ) if (fileMatchResult.value.configs.length) { configs = Array.from(new Set([ @@ -339,7 +339,7 @@ onMounted(async () => {
Ignored by globs:
} + */ +-const META_FIELDS = new Set(["name"]); ++const META_FIELDS = new Set(["name", "index"]); + + /** + * A schema containing just files and ignores for early validation. +diff --git a/dist/esm/index.js b/dist/esm/index.js +index 1e68f33e3d641d856ab25b8206be085b6c205067..7dd46e0b8872078bba033ac75c79a9046020d589 100644 +--- a/dist/esm/index.js ++++ b/dist/esm/index.js +@@ -221,7 +221,7 @@ const CONFIG_TYPES = new Set(["array", "function"]); + * Fields that are considered metadata and not part of the config object. + * @type {Set} + */ +-const META_FIELDS = new Set(["name"]); ++const META_FIELDS = new Set(["name", "index"]); + + /** + * A schema containing just files and ignores for early validation. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d01c862..0691365 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -130,7 +130,7 @@ catalogs: specifier: ^0.0.1 version: 0.0.1 typescript: - specifier: ~5.6.3 + specifier: ^5.6.3 version: 5.6.3 unbuild: specifier: ^2.0.0 @@ -145,16 +145,21 @@ catalogs: overrides: nitropack: 2.8.1 +patchedDependencies: + '@eslint/config-array@0.19.1': + hash: x7vaku5tfstwhfkemiablef5bi + path: patches/@eslint__config-array@0.19.1.patch + importers: .: dependencies: '@eslint/config-array': specifier: 'catalog:' - version: 0.19.1 + version: 0.19.1(patch_hash=x7vaku5tfstwhfkemiablef5bi) '@voxpelli/config-array-find-files': specifier: 'catalog:' - version: 1.2.1(@eslint/config-array@0.19.1) + version: 1.2.1(@eslint/config-array@0.19.1(patch_hash=x7vaku5tfstwhfkemiablef5bi)) bundle-require: specifier: 'catalog:' version: 5.0.0(esbuild@0.24.0) @@ -200,7 +205,7 @@ importers: devDependencies: '@antfu/eslint-config': specifier: 'catalog:' - version: 3.11.2(@typescript-eslint/utils@8.18.0(eslint@9.16.0(jiti@2.4.1))(typescript@5.6.3))(@unocss/eslint-plugin@0.65.1(eslint@9.16.0(jiti@2.4.1))(typescript@5.6.3))(@vue/compiler-sfc@3.5.13)(eslint@9.16.0(jiti@2.4.1))(typescript@5.6.3) + version: 3.11.2(@typescript-eslint/utils@8.18.0(eslint@9.16.0(jiti@2.4.1))(typescript@5.6.3))(@unocss/eslint-plugin@0.65.1(eslint@9.16.0(jiti@2.4.1))(typescript@5.6.3))(@vue/compiler-sfc@3.5.13)(eslint@9.16.0(jiti@2.4.1))(typescript@5.6.3)(vitest@2.1.8(@types/node@20.12.11)(terser@5.31.0)) '@iconify-json/carbon': specifier: 'catalog:' version: 1.2.4 @@ -282,6 +287,9 @@ importers: unbuild: specifier: 'catalog:' version: 2.0.0(typescript@5.6.3)(vue-tsc@2.1.10(typescript@5.6.3)) + vitest: + specifier: ^2.1.8 + version: 2.1.8(@types/node@20.12.11)(terser@5.31.0) vue-tsc: specifier: 'catalog:' version: 2.1.10(typescript@5.6.3) @@ -2132,6 +2140,35 @@ packages: vitest: optional: true + '@vitest/expect@2.1.8': + resolution: {integrity: sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw==} + + '@vitest/mocker@2.1.8': + resolution: {integrity: sha512-7guJ/47I6uqfttp33mgo6ga5Gr1VnL58rcqYKyShoRK9ebu8T5Rs6HN3s1NABiBeVTdWNrwUMcHH54uXZBN4zA==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@2.1.8': + resolution: {integrity: sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==} + + '@vitest/runner@2.1.8': + resolution: {integrity: sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg==} + + '@vitest/snapshot@2.1.8': + resolution: {integrity: sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg==} + + '@vitest/spy@2.1.8': + resolution: {integrity: sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg==} + + '@vitest/utils@2.1.8': + resolution: {integrity: sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==} + '@volar/language-core@2.4.10': resolution: {integrity: sha512-hG3Z13+nJmGaT+fnQzAkS0hjJRa2FCeqZt6Bd+oGNhUkQ+mTFsDETg5rqUTxyzIh5pSOGY7FHCWUS8G82AzLCA==} @@ -2380,6 +2417,10 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + ast-kit@1.1.0: resolution: {integrity: sha512-RlNqd4u6c/rJ5R+tN/ZTtyNrH8X0NHCvyt6gD8RHa3JjzxxHWoyaU0Ujk3Zjbh7IZqrYl1Sxm6XzZifmVxXxHQ==} engines: {node: '>=16.14.0'} @@ -2491,6 +2532,10 @@ packages: ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chai@5.1.2: + resolution: {integrity: sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==} + engines: {node: '>=12'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -2508,6 +2553,10 @@ packages: character-entities@2.0.2: resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -2750,6 +2799,10 @@ packages: decode-named-character-reference@1.0.2: resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -3169,6 +3222,10 @@ packages: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} + expect-type@1.1.0: + resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==} + engines: {node: '>=12.0.0'} + externality@1.0.2: resolution: {integrity: sha512-LyExtJWKxtgVzmgtEHyQtLFpw1KFhQphF9nTG8TpAIVkiI/xQ3FJh75tRFLYl4hkn7BNIIdLJInuDAavX35pMw==} @@ -3765,6 +3822,9 @@ packages: longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + loupe@3.1.2: + resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -4283,6 +4343,10 @@ packages: pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} + perfect-debounce@1.0.0: resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} @@ -4727,6 +4791,9 @@ packages: shiki@1.24.2: resolution: {integrity: sha512-TR1fi6mkRrzW+SKT5G6uKuc32Dj2EEa7Kj0k8kGqiBINb+C1TiflVOiT9ta6GqOJtC4fraxO5SLUaKBcSY38Fg==} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -4814,6 +4881,9 @@ packages: stable-hash@0.0.4: resolution: {integrity: sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + standard-as-callback@2.1.0: resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} @@ -4958,6 +5028,9 @@ packages: tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinyexec@0.3.1: resolution: {integrity: sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==} @@ -4965,6 +5038,18 @@ packages: resolution: {integrity: sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==} engines: {node: '>=12.0.0'} + tinypool@1.0.2: + resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -4996,8 +5081,8 @@ packages: tslib@2.6.3: resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} - tsx@4.16.3: - resolution: {integrity: sha512-MP8AEUxVnboD2rCC6kDLxnpDBNWN9k3BSVU/0/nNxgm70bPBnfn+yCKcnOsIVPQwdkbKYoFOlKjjWZWJ2XCXUg==} + tsx@4.19.2: + resolution: {integrity: sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==} engines: {node: '>=18.0.0'} hasBin: true @@ -5286,6 +5371,31 @@ packages: terser: optional: true + vitest@2.1.8: + resolution: {integrity: sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.1.8 + '@vitest/ui': 2.1.8 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + vscode-jsonrpc@6.0.0: resolution: {integrity: sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==} engines: {node: '>=8.0.0 || >=10.0.0'} @@ -5388,6 +5498,11 @@ packages: engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + wide-align@1.1.5: resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} @@ -5474,7 +5589,7 @@ snapshots: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 - '@antfu/eslint-config@3.11.2(@typescript-eslint/utils@8.18.0(eslint@9.16.0(jiti@2.4.1))(typescript@5.6.3))(@unocss/eslint-plugin@0.65.1(eslint@9.16.0(jiti@2.4.1))(typescript@5.6.3))(@vue/compiler-sfc@3.5.13)(eslint@9.16.0(jiti@2.4.1))(typescript@5.6.3)': + '@antfu/eslint-config@3.11.2(@typescript-eslint/utils@8.18.0(eslint@9.16.0(jiti@2.4.1))(typescript@5.6.3))(@unocss/eslint-plugin@0.65.1(eslint@9.16.0(jiti@2.4.1))(typescript@5.6.3))(@vue/compiler-sfc@3.5.13)(eslint@9.16.0(jiti@2.4.1))(typescript@5.6.3)(vitest@2.1.8(@types/node@20.12.11)(terser@5.31.0))': dependencies: '@antfu/install-pkg': 0.5.0 '@clack/prompts': 0.8.2 @@ -5483,7 +5598,7 @@ snapshots: '@stylistic/eslint-plugin': 2.11.0(eslint@9.16.0(jiti@2.4.1))(typescript@5.6.3) '@typescript-eslint/eslint-plugin': 8.17.0(@typescript-eslint/parser@8.17.0(eslint@9.16.0(jiti@2.4.1))(typescript@5.6.3))(eslint@9.16.0(jiti@2.4.1))(typescript@5.6.3) '@typescript-eslint/parser': 8.17.0(eslint@9.16.0(jiti@2.4.1))(typescript@5.6.3) - '@vitest/eslint-plugin': 1.1.14(@typescript-eslint/utils@8.18.0(eslint@9.16.0(jiti@2.4.1))(typescript@5.6.3))(eslint@9.16.0(jiti@2.4.1))(typescript@5.6.3) + '@vitest/eslint-plugin': 1.1.14(@typescript-eslint/utils@8.18.0(eslint@9.16.0(jiti@2.4.1))(typescript@5.6.3))(eslint@9.16.0(jiti@2.4.1))(typescript@5.6.3)(vitest@2.1.8(@types/node@20.12.11)(terser@5.31.0)) eslint: 9.16.0(jiti@2.4.1) eslint-config-flat-gitignore: 0.3.0(eslint@9.16.0(jiti@2.4.1)) eslint-flat-config-utils: 0.4.0 @@ -6132,7 +6247,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/config-array@0.19.1': + '@eslint/config-array@0.19.1(patch_hash=x7vaku5tfstwhfkemiablef5bi)': dependencies: '@eslint/object-schema': 2.1.5 debug: 4.3.7 @@ -7461,12 +7576,53 @@ snapshots: vite: 5.4.11(@types/node@20.12.11)(terser@5.31.0) vue: 3.5.13(typescript@5.6.3) - '@vitest/eslint-plugin@1.1.14(@typescript-eslint/utils@8.18.0(eslint@9.16.0(jiti@2.4.1))(typescript@5.6.3))(eslint@9.16.0(jiti@2.4.1))(typescript@5.6.3)': + '@vitest/eslint-plugin@1.1.14(@typescript-eslint/utils@8.18.0(eslint@9.16.0(jiti@2.4.1))(typescript@5.6.3))(eslint@9.16.0(jiti@2.4.1))(typescript@5.6.3)(vitest@2.1.8(@types/node@20.12.11)(terser@5.31.0))': dependencies: '@typescript-eslint/utils': 8.18.0(eslint@9.16.0(jiti@2.4.1))(typescript@5.6.3) eslint: 9.16.0(jiti@2.4.1) optionalDependencies: typescript: 5.6.3 + vitest: 2.1.8(@types/node@20.12.11)(terser@5.31.0) + + '@vitest/expect@2.1.8': + dependencies: + '@vitest/spy': 2.1.8 + '@vitest/utils': 2.1.8 + chai: 5.1.2 + tinyrainbow: 1.2.0 + + '@vitest/mocker@2.1.8(vite@5.4.11(@types/node@20.12.11)(terser@5.31.0))': + dependencies: + '@vitest/spy': 2.1.8 + estree-walker: 3.0.3 + magic-string: 0.30.14 + optionalDependencies: + vite: 5.4.11(@types/node@20.12.11)(terser@5.31.0) + + '@vitest/pretty-format@2.1.8': + dependencies: + tinyrainbow: 1.2.0 + + '@vitest/runner@2.1.8': + dependencies: + '@vitest/utils': 2.1.8 + pathe: 1.1.2 + + '@vitest/snapshot@2.1.8': + dependencies: + '@vitest/pretty-format': 2.1.8 + magic-string: 0.30.14 + pathe: 1.1.2 + + '@vitest/spy@2.1.8': + dependencies: + tinyspy: 3.0.2 + + '@vitest/utils@2.1.8': + dependencies: + '@vitest/pretty-format': 2.1.8 + loupe: 3.1.2 + tinyrainbow: 1.2.0 '@volar/language-core@2.4.10': dependencies: @@ -7485,9 +7641,9 @@ snapshots: '@eslint/config-array': 0.18.0 '@nodelib/fs.walk': 2.0.0 - '@voxpelli/config-array-find-files@1.2.1(@eslint/config-array@0.19.1)': + '@voxpelli/config-array-find-files@1.2.1(@eslint/config-array@0.19.1(patch_hash=x7vaku5tfstwhfkemiablef5bi))': dependencies: - '@eslint/config-array': 0.19.1 + '@eslint/config-array': 0.19.1(patch_hash=x7vaku5tfstwhfkemiablef5bi) '@nodelib/fs.walk': 2.0.0 '@vue-macros/common@1.14.0(rollup@3.29.4)(vue@3.5.13(typescript@5.6.3))': @@ -7830,6 +7986,8 @@ snapshots: argparse@2.0.1: {} + assertion-error@2.0.1: {} + ast-kit@1.1.0: dependencies: '@babel/parser': 7.26.3 @@ -7960,6 +8118,14 @@ snapshots: ccount@2.0.1: {} + chai@5.1.2: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.2 + pathval: 2.0.0 + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -7973,6 +8139,8 @@ snapshots: character-entities@2.0.2: {} + check-error@2.1.1: {} + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -8204,6 +8372,8 @@ snapshots: dependencies: character-entities: 2.0.2 + deep-eql@5.0.2: {} + deep-is@0.1.4: {} deepmerge@4.3.1: {} @@ -8710,7 +8880,7 @@ snapshots: dependencies: '@eslint-community/eslint-utils': 4.4.1(eslint@9.16.0(jiti@2.4.1)) '@eslint-community/regexpp': 4.12.1 - '@eslint/config-array': 0.19.1 + '@eslint/config-array': 0.19.1(patch_hash=x7vaku5tfstwhfkemiablef5bi) '@eslint/core': 0.9.0 '@eslint/eslintrc': 3.2.0 '@eslint/js': 9.16.0 @@ -8809,6 +8979,8 @@ snapshots: signal-exit: 4.1.0 strip-final-newline: 3.0.0 + expect-type@1.1.0: {} + externality@1.0.2: dependencies: enhanced-resolve: 5.17.1 @@ -9129,7 +9301,7 @@ snapshots: jiti-v1: jiti@1.21.6 pathe: 1.1.2 pkg-types: 1.2.1 - tsx: 4.16.3 + tsx: 4.19.2 transitivePeerDependencies: - supports-color @@ -9430,6 +9602,8 @@ snapshots: longest-streak@3.1.0: {} + loupe@3.1.2: {} + lru-cache@10.4.3: {} lru-cache@5.1.1: @@ -10285,6 +10459,8 @@ snapshots: pathe@1.1.2: {} + pathval@2.0.0: {} + perfect-debounce@1.0.0: {} picocolors@1.1.1: {} @@ -10744,6 +10920,8 @@ snapshots: '@shikijs/vscode-textmate': 9.3.0 '@types/hast': 3.0.4 + siginfo@2.0.0: {} + signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -10826,6 +11004,8 @@ snapshots: stable-hash@0.0.4: {} + stackback@0.0.2: {} + standard-as-callback@2.1.0: optional: true @@ -10973,6 +11153,8 @@ snapshots: tiny-invariant@1.3.3: {} + tinybench@2.9.0: {} + tinyexec@0.3.1: {} tinyglobby@0.2.10: @@ -10980,6 +11162,12 @@ snapshots: fdir: 6.4.2(picomatch@4.0.2) picomatch: 4.0.2 + tinypool@1.0.2: {} + + tinyrainbow@1.2.0: {} + + tinyspy@3.0.2: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -11002,9 +11190,9 @@ snapshots: tslib@2.6.3: {} - tsx@4.16.3: + tsx@4.19.2: dependencies: - esbuild: 0.21.5 + esbuild: 0.23.1 get-tsconfig: 4.8.1 optionalDependencies: fsevents: 2.3.3 @@ -11374,6 +11562,41 @@ snapshots: fsevents: 2.3.3 terser: 5.31.0 + vitest@2.1.8(@types/node@20.12.11)(terser@5.31.0): + dependencies: + '@vitest/expect': 2.1.8 + '@vitest/mocker': 2.1.8(vite@5.4.11(@types/node@20.12.11)(terser@5.31.0)) + '@vitest/pretty-format': 2.1.8 + '@vitest/runner': 2.1.8 + '@vitest/snapshot': 2.1.8 + '@vitest/spy': 2.1.8 + '@vitest/utils': 2.1.8 + chai: 5.1.2 + debug: 4.3.7 + expect-type: 1.1.0 + magic-string: 0.30.14 + pathe: 1.1.2 + std-env: 3.8.0 + tinybench: 2.9.0 + tinyexec: 0.3.1 + tinypool: 1.0.2 + tinyrainbow: 1.2.0 + vite: 5.4.11(@types/node@20.12.11)(terser@5.31.0) + vite-node: 2.1.8(@types/node@20.12.11)(terser@5.31.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 20.12.11 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vscode-jsonrpc@6.0.0: {} vscode-languageclient@7.0.0: @@ -11501,6 +11724,11 @@ snapshots: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + wide-align@1.1.5: dependencies: string-width: 4.2.3 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index dc13076..236a493 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -13,6 +13,7 @@ catalog: '@nuxt/eslint': ^0.7.2 '@shikijs/transformers': ^1.24.2 '@types/connect': ^3.4.38 + '@types/mocha': ^10.0.10 '@types/ws': ^8.5.13 '@typescript-eslint/utils': ^8.18.0 '@unocss/eslint-config': ^0.65.1 @@ -34,6 +35,7 @@ catalog: # v10.0 requires Node.js v20, so we stick with v9.0 minimatch: ^9.0.5 mlly: ^1.7.3 + mocha: ^10.4.0 mrmime: ^2.0.0 nitropack: 2.8.1 nuxt: ^3.14.1592 @@ -43,7 +45,8 @@ catalog: shiki: ^1.24.2 simple-git-hooks: ^2.11.1 textmate-grammar-glob: ^0.0.1 - typescript: ~5.6.3 + tsx: ^4.19.2 + typescript: ^5.6.3 unbuild: ^2.0.0 vue-tsc: ^2.1.10 ws: ^8.18.0 diff --git a/shared/configs.ts b/shared/configs.ts index a3999f0..c409d23 100644 --- a/shared/configs.ts +++ b/shared/configs.ts @@ -1,4 +1,6 @@ +import type { Linter } from 'eslint' import type { FlatConfigItem, MatchedFile } from './types' +import { ConfigArray } from '@eslint/config-array' import { Minimatch } from 'minimatch' const minimatchOpts = { dot: true } @@ -15,7 +17,11 @@ function minimatch(file: string, pattern: string) { export function getMatchedGlobs(file: string, glob: (string | string[])[]) { const globs = (Array.isArray(glob) ? glob : [glob]).flat() - return globs.filter(glob => minimatch(file, glob)).flat() + return globs.filter((glob) => { + const matchResult = minimatch(file, glob) + // for unignore glob, we need to flip back the match + return glob.startsWith('!') ? !matchResult : matchResult + }).flat() } const META_KEYS = new Set(['name', 'index']) @@ -38,31 +44,61 @@ export function isGeneralConfig(config: FlatConfigItem) { export function matchFile( filepath: string, configs: FlatConfigItem[], - ignoreOnlyConfigs: FlatConfigItem[], + basePath: string, ): MatchedFile { - const globalIgnored = ignoreOnlyConfigs.flatMap(config => getMatchedGlobs(filepath, config.ignores!)) - if (globalIgnored.length) { - return { - filepath, - globs: globalIgnored, - configs: [], - } - } - const result: MatchedFile = { filepath, globs: [], configs: [], } - configs.forEach((config, index) => { + + const { + config: globalMatchedConfig = {}, + status: globalMatchStatus, + } = buildConfigArray(configs, basePath).getConfigWithStatus(filepath) + configs.forEach((config) => { const positive = getMatchedGlobs(filepath, config.files || []) const negative = getMatchedGlobs(filepath, config.ignores || []) - if (!negative.length && positive.length) - result.configs.push(index) - result.globs.push( - ...positive, - ...negative, - ) + + if (globalMatchStatus === 'matched' && globalMatchedConfig.index?.includes(config.index) && positive.length > 0) { + result.configs.push(config.index) + // push positive globs only when there are configs matched + result.globs.push(...positive) + } + + result.globs.push(...negative) }) + + result.globs = [...new Set(result.globs)] + return result } + +const NOOP_SCHEMA = { + merge: 'replace', + validate() {}, +} + +const FLAT_CONFIG_NOOP_SCHEMA = { + settings: NOOP_SCHEMA, + linterOptions: NOOP_SCHEMA, + language: NOOP_SCHEMA, + languageOptions: NOOP_SCHEMA, + processor: NOOP_SCHEMA, + plugins: NOOP_SCHEMA, + index: { + ...NOOP_SCHEMA, + // accumulate the matched config index to an array + merge(v1: number, v2: number) { + return [v1].concat(v2).flat() + }, + }, + rules: NOOP_SCHEMA, +} + +export function buildConfigArray(configs: Linter.Config[], basePath: string) { + return new ConfigArray(configs, { + basePath, + schema: FLAT_CONFIG_NOOP_SCHEMA, + }).normalizeSync() +} diff --git a/shared/types.ts b/shared/types.ts index 022e9ec..f62327e 100644 --- a/shared/types.ts +++ b/shared/types.ts @@ -1,11 +1,8 @@ import type { RuleMetaData } from '@typescript-eslint/utils/ts-eslint' import type { Linter } from 'eslint' -export interface FlatConfigItem extends Omit { - name?: string +export interface FlatConfigItem extends Linter.Config { index: number - files?: (string | string[])[] - ignores?: string[] } export type RuleLevel = 'off' | 'warn' | 'error' diff --git a/src/configs.ts b/src/configs.ts index 2098d30..fdc761e 100644 --- a/src/configs.ts +++ b/src/configs.ts @@ -1,13 +1,13 @@ +import type { Linter } from 'eslint' import type { FlatConfigItem, MatchedFile, Payload, RuleInfo } from '../shared/types' import { basename, dirname, relative, resolve } from 'node:path' import process from 'node:process' -import { ConfigArray } from '@eslint/config-array' import { configArrayFindFiles } from '@voxpelli/config-array-find-files' import { bundleRequire } from 'bundle-require' import { findUp } from 'find-up' import { resolve as resolveModule } from 'mlly' import c from 'picocolors' -import { isIgnoreOnlyConfig, matchFile } from '../shared/configs' +import { buildConfigArray, matchFile } from '../shared/configs' import { configFilenames, legacyConfigFilenames, MARK_CHECK, MARK_INFO } from './constants' import { ConfigPathError, ConfigPathLegacyError } from './errors' @@ -219,7 +219,7 @@ export async function readConfig( configs, rules, files: globFiles - ? await globMatchedFiles(basePath, rawConfigs) + ? await globMatchedFiles(basePath, configs, rawConfigs) : undefined, meta: { lastUpdate: Date.now(), @@ -235,56 +235,22 @@ export async function readConfig( } } -const noopSchema = { - merge: 'replace', - validate() {}, -} - -const flatConfigNoopSchema = { - settings: noopSchema, - linterOptions: noopSchema, - language: noopSchema, - languageOptions: noopSchema, - processor: noopSchema, - plugins: noopSchema, - rules: noopSchema, -} - export async function globMatchedFiles( basePath: string, configs: FlatConfigItem[], + rawConfigs: Linter.Config[], ): Promise { console.log(MARK_INFO, 'Globing matched files') - const configArray = new ConfigArray(configs, { + const files = (await configArrayFindFiles({ basePath, - schema: flatConfigNoopSchema, - }) - - await configArray.normalize() - - const files = await configArrayFindFiles({ - basePath, - configs: configArray, - }) - - files.sort() - - const ignoreOnlyConfigs = configs.filter(isIgnoreOnlyConfig) - // const functionalGlobMap = new Map() - // function stringifyGlob(glob: string) { - // if (typeof glob === 'function') { - // if (!functionalGlobMap.has(glob)) - // functionalGlobMap.set(glob, ``) - // return functionalGlobMap.get(glob)! - // } - // return glob - // } + configs: buildConfigArray(rawConfigs, basePath), + })).toSorted() return files .map((filepath) => { filepath = relative(basePath, filepath) - const result = matchFile(filepath, configs, ignoreOnlyConfigs) + const result = matchFile(filepath, configs, basePath) if (!result.configs.length) return undefined return result diff --git a/tests/shared_configs.test.ts b/tests/shared_configs.test.ts new file mode 100644 index 0000000..ec1b073 --- /dev/null +++ b/tests/shared_configs.test.ts @@ -0,0 +1,98 @@ +import { describe, expect, it } from 'vitest' +import { matchFile } from '../shared/configs' + +describe('matchFile', () => { + describe('global ignored', () => { + const testGlobalIgnores = (ignores: string[]) => { + const configs = [{ index: 0, files: ['tests/folder/foo.test.ts'] }, { index: 1, ignores }] + return matchFile('tests/folder/foo.test.ts', configs, process.cwd()) + } + it('should match no configs', () => { + const result = testGlobalIgnores(['tests/**']) + expect(result).toEqual({ + filepath: 'tests/folder/foo.test.ts', + globs: ['tests/**'], + configs: [], + }) + }) + + it('should match when final matched glob is not an unignore', () => { + const result = testGlobalIgnores(['tests/**', '!tests/folder/**', 'tests/**/*.test.ts']) + expect(result).toEqual({ + filepath: 'tests/folder/foo.test.ts', + globs: ['tests/**', '!tests/folder/**', 'tests/**/*.test.ts'], + configs: [], + }) + }) + + it('should match when irrelevant unignores included', () => { + const result = testGlobalIgnores(['tests/**', '!tests/other/**', '!tests/*.test.ts']) + expect(result).toEqual({ + filepath: 'tests/folder/foo.test.ts', + globs: ['tests/**'], + configs: [], + }) + }) + + it('should match when file is unignored but directory is ignored', () => { + const result = testGlobalIgnores(['tests/**', '!tests/**/*.test.ts', 'tests/other/**']) + expect(result).toEqual({ + filepath: 'tests/folder/foo.test.ts', + globs: ['tests/**', '!tests/**/*.test.ts'], + configs: [], + }) + }) + + it('should not match when file is unignored and directory is ignored but sub directory is unignored', () => { + const result = testGlobalIgnores(['tests/**/*', '!tests/**/*/', '!tests/**/*.test.ts', 'tests/other/**']) + expect(result).toEqual({ + filepath: 'tests/folder/foo.test.ts', + globs: ['tests/folder/foo.test.ts', 'tests/**/*', '!tests/**/*.test.ts'], + configs: [0], + }) + }) + }) + + describe('config matching', () => { + it('should match a basic config', () => { + const result = matchFile('tests/folder/foo.test.ts', [{ index: 0, files: ['**'], ignores: [] }], process.cwd()) + expect(result).toEqual({ + filepath: 'tests/folder/foo.test.ts', + globs: ['**'], + configs: [0], + }) + }) + + it('should be ignored when final matched glob is not an unignore', () => { + const result = matchFile('tests/folder/foo.test.ts', [{ index: 0, files: ['**'], ignores: ['tests/**', '!tests/folder/**', 'tests/**/*.test.ts'] }], process.cwd()) + expect(result).toEqual({ + filepath: 'tests/folder/foo.test.ts', + globs: ['tests/**', '!tests/folder/**', 'tests/**/*.test.ts'], + configs: [], + }) + }) + + it('should match when last matching glob is an unignore', () => { + const result = matchFile('tests/folder/foo.test.ts', [{ index: 0, files: ['**'], ignores: ['tests/**', '!tests/folder/**', 'tests/other/**'] }], process.cwd()) + expect(result).toEqual({ + filepath: 'tests/folder/foo.test.ts', + globs: ['**', 'tests/**', '!tests/folder/**'], + configs: [0], + }) + }) + + it ('should match multiple configs / a complex case', () => { + const result = matchFile('tests/folder/foo.test.ts', [ + { index: 0, files: ['**'], ignores: [] }, + { index: 1, files: ['**/*.miss.ts'], ignores: ['tests/**'] }, + { index: 2, files: ['**/*.miss.ts'], ignores: [] }, + { index: 3, files: ['**'], ignores: ['tests/**', '!**/*.ts'] }, + { index: 4, files: ['**'], ignores: ['tests/**.miss'] }, + { index: 5, files: ['**'], ignores: ['tests/**', '!tests/folder/*.foo', '!tests/folder/*.bar'] }, + { index: 6, files: ['**'], ignores: ['tests/**', '!tests/*.test.ts', 'tests/folder/*'] }, + { index: 7, files: ['**'], ignores: ['tests/**', '!tests/*.test.ts', 'tests/folder/*', '!tests/folder/*.ts', 'tests/other/**'] }, + ], process.cwd()) + expect(result.configs).toEqual([0, 3, 4, 7]) + }) + }) +})