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])
+ })
+ })
+})