diff --git a/.changeset/neat-rabbits-clap.md b/.changeset/neat-rabbits-clap.md
new file mode 100644
index 0000000..f965f9d
--- /dev/null
+++ b/.changeset/neat-rabbits-clap.md
@@ -0,0 +1,5 @@
+---
+"readie": patch
+---
+
+Added ultracte to readie
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 3af9d82..edccfb4 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -31,7 +31,7 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: 22
- registry-url: 'https://registry.npmjs.org'
+ registry-url: "https://registry.npmjs.org"
- name: Install dependencies
run: bun install --frozen-lockfile
diff --git a/.oxfmtrc.jsonc b/.oxfmtrc.jsonc
new file mode 100644
index 0000000..01f9593
--- /dev/null
+++ b/.oxfmtrc.jsonc
@@ -0,0 +1,23 @@
+// Ultracite oxfmt Configuration
+// https://oxc.rs/docs/guide/usage/formatter/config-file-reference.html
+{
+ "$schema": "./node_modules/oxfmt/configuration_schema.json",
+ "printWidth": 80,
+ "tabWidth": 2,
+ "useTabs": true,
+ "semi": true,
+ "singleQuote": false,
+ "quoteProps": "as-needed",
+ "jsxSingleQuote": false,
+ "trailingComma": "es5",
+ "bracketSpacing": true,
+ "bracketSameLine": false,
+ "arrowParens": "always",
+ "endOfLine": "lf",
+ "experimentalSortPackageJson": true,
+ "experimentalSortImports": {
+ "ignoreCase": true,
+ "newlinesBetween": true,
+ "order": "asc",
+ },
+}
diff --git a/.oxlintrc.json b/.oxlintrc.json
new file mode 100644
index 0000000..c5a5e50
--- /dev/null
+++ b/.oxlintrc.json
@@ -0,0 +1,4 @@
+{
+ "$schema": "./node_modules/oxlint/configuration_schema.json",
+ "extends": ["./node_modules/ultracite/config/oxlint/core/.oxlintrc.json"]
+}
diff --git a/README.md b/README.md
index c0a2eea..e11e829 100644
--- a/README.md
+++ b/README.md
@@ -67,8 +67,8 @@ You can also run it without global install via `npx readie`.
```json
{
- "banner": "
{{title}}
",
- "footer": "https://example.com?ref={{packageNameEncoded}}"
+ "banner": "{{title}}
",
+ "footer": "https://example.com?ref={{packageNameEncoded}}"
}
```
diff --git a/bun.lock b/bun.lock
index 0ef64ee..241dc61 100644
--- a/bun.lock
+++ b/bun.lock
@@ -5,20 +5,28 @@
"": {
"name": "readie",
"dependencies": {
- "@effect/cli": "^0.73.2",
- "@effect/platform": "^0.94.5",
- "@effect/platform-node": "^0.104.1",
- "@effect/printer": "^0.47.0",
- "@effect/printer-ansi": "^0.47.0",
- "effect": "^3.19.16",
+ "@effect/cli": "latest",
+ "@effect/platform": "latest",
+ "@effect/platform-node": "latest",
+ "@effect/printer": "latest",
+ "@effect/printer-ansi": "latest",
+ "effect": "latest",
},
"devDependencies": {
- "@changesets/changelog-github": "^0.5.1",
- "@changesets/cli": "^2.29.7",
- "@types/node": "^24.3.0",
- "changeset-conventional-commits": "^0.2.5",
- "typescript": "^5.9.2",
- "vitest": "^3.2.4",
+ "@changesets/changelog-github": "latest",
+ "@changesets/cli": "latest",
+ "@types/fs-extra": "latest",
+ "@types/node": "latest",
+ "changeset-conventional-commits": "latest",
+ "fs-extra": "latest",
+ "lefthook": "latest",
+ "oxfmt": "latest",
+ "oxlint": "latest",
+ "pathe": "latest",
+ "tempy": "latest",
+ "typescript": "latest",
+ "ultracite": "latest",
+ "vitest": "latest",
},
},
},
@@ -63,6 +71,10 @@
"@changesets/write": ["@changesets/write@0.4.0", "", { "dependencies": { "@changesets/types": "^6.1.0", "fs-extra": "^7.0.1", "human-id": "^4.1.1", "prettier": "^2.7.1" } }, "sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q=="],
+ "@clack/core": ["@clack/core@1.0.1", "", { "dependencies": { "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-WKeyK3NOBwDOzagPR5H08rFk9D/WuN705yEbuZvKqlkmoLM2woKtXb10OO2k1NoSU4SFG947i2/SCYh+2u5e4g=="],
+
+ "@clack/prompts": ["@clack/prompts@1.0.1", "", { "dependencies": { "@clack/core": "1.0.1", "picocolors": "^1.0.0", "sisteransi": "^1.0.5" } }, "sha512-/42G73JkuYdyWZ6m8d/CJtBrGl1Hegyc7Fy78m5Ob+jF85TOUmLR5XLce/U3LxYAw0kJ8CT5aI99RIvPHcGp/Q=="],
+
"@effect/cli": ["@effect/cli@0.73.2", "", { "dependencies": { "ini": "^4.1.3", "toml": "^3.0.0", "yaml": "^2.5.0" }, "peerDependencies": { "@effect/platform": "^0.94.3", "@effect/printer": "^0.47.0", "@effect/printer-ansi": "^0.47.0", "effect": "^3.19.16" } }, "sha512-K8IJo81+qa1LU8dhxcDU4QO/bIjL/dPd3zUOSCpLiuUNz8Y3/T+WNs3GqIXEhMfCFMSlRZERN0YgmtRlEZUREA=="],
"@effect/cluster": ["@effect/cluster@0.48.16", "", { "peerDependencies": { "@effect/platform": "^0.90.10", "@effect/rpc": "^0.69.4", "@effect/sql": "^0.44.2", "@effect/workflow": "^0.9.6", "effect": "^3.17.14" } }, "sha512-ZZkrSMVetOvlRDD8mPCX3IcVJtvUZBp6++lUKNGIT6LRIObRP4lVwtei85Z+4g49WpeLvJnSdH0zjPtGieFDHQ=="],
@@ -167,6 +179,82 @@
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
+ "@oxfmt/binding-android-arm-eabi": ["@oxfmt/binding-android-arm-eabi@0.34.0", "", { "os": "android", "cpu": "arm" }, "sha512-sqkqjh/Z38l+duOb1HtVqJTAj1grt2ttkobCopC/72+a4Xxz4xUgZPFyQ4HxrYMvyqO/YA0tvM1QbfOu70Gk1Q=="],
+
+ "@oxfmt/binding-android-arm64": ["@oxfmt/binding-android-arm64@0.34.0", "", { "os": "android", "cpu": "arm64" }, "sha512-1KRCtasHcVcGOMwfOP9d5Bus2NFsN8yAYM5cBwi8LBg5UtXC3C49WHKrlEa8iF1BjOS6CR2qIqiFbGoA0DJQNQ=="],
+
+ "@oxfmt/binding-darwin-arm64": ["@oxfmt/binding-darwin-arm64@0.34.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-b+Rmw9Bva6e/7PBES2wLO8sEU7Mi0+/Kv+pXSe/Y8i4fWNftZZlGwp8P01eECaUqpXATfSgNxdEKy7+ssVNz7g=="],
+
+ "@oxfmt/binding-darwin-x64": ["@oxfmt/binding-darwin-x64@0.34.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-QGjpevWzf1T9COEokZEWt80kPOtthW1zhRbo7x4Qoz646eTTfi6XsHG2uHeDWJmTbgBoJZPMgj2TAEV/ppEZaA=="],
+
+ "@oxfmt/binding-freebsd-x64": ["@oxfmt/binding-freebsd-x64@0.34.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-VMSaC02cG75qL59M9M/szEaqq/RsLfgpzQ4nqUu8BUnX1zkiZIW2gTpUv3ZJ6qpWnHxIlAXiRZjQwmcwpvtbcg=="],
+
+ "@oxfmt/binding-linux-arm-gnueabihf": ["@oxfmt/binding-linux-arm-gnueabihf@0.34.0", "", { "os": "linux", "cpu": "arm" }, "sha512-Klm367PFJhH6vYK3vdIOxFepSJZHPaBfIuqwxdkOcfSQ4qqc/M8sgK0UTFnJWWTA/IkhMIh1kW6uEqiZ/xtQqg=="],
+
+ "@oxfmt/binding-linux-arm-musleabihf": ["@oxfmt/binding-linux-arm-musleabihf@0.34.0", "", { "os": "linux", "cpu": "arm" }, "sha512-nqn0QueVXRfbN9m58/E9Zij0Ap8lzayx591eWBYn0sZrGzY1IRv9RYS7J/1YUXbb0Ugedo0a8qIWzUHU9bWQuA=="],
+
+ "@oxfmt/binding-linux-arm64-gnu": ["@oxfmt/binding-linux-arm64-gnu@0.34.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-DDn+dcqW+sMTCEjvLoQvC/VWJjG7h8wcdN/J+g7ZTdf/3/Dx730pQElxPPGsCXPhprb11OsPyMp5FwXjMY3qvA=="],
+
+ "@oxfmt/binding-linux-arm64-musl": ["@oxfmt/binding-linux-arm64-musl@0.34.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-H+F8+71gHQoGTFPPJ6z4dD0Fzfzi0UP8Zx94h5kUmIFThLvMq5K1Y/bUUubiXwwHfwb5C3MPjUpYijiy0rj51Q=="],
+
+ "@oxfmt/binding-linux-ppc64-gnu": ["@oxfmt/binding-linux-ppc64-gnu@0.34.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-dIGnzTNhCXqQD5pzBwduLg8pClm+t8R53qaE9i5h8iua1iaFAJyLffh4847CNZSlASb7gn1Ofuv7KoG/EpoGZg=="],
+
+ "@oxfmt/binding-linux-riscv64-gnu": ["@oxfmt/binding-linux-riscv64-gnu@0.34.0", "", { "os": "linux", "cpu": "none" }, "sha512-FGQ2GTTooilDte/ogwWwkHuuL3lGtcE3uKM2EcC7kOXNWdUfMY6Jx3JCodNVVbFoybv4A+HuCj8WJji2uu1Ceg=="],
+
+ "@oxfmt/binding-linux-riscv64-musl": ["@oxfmt/binding-linux-riscv64-musl@0.34.0", "", { "os": "linux", "cpu": "none" }, "sha512-2dGbGneJ7ptOIVKMwEIHdCkdZEomh74X3ggo4hCzEXL/rl9HwfsZDR15MkqfQqAs6nVXMvtGIOMxjDYa5lwKaA=="],
+
+ "@oxfmt/binding-linux-s390x-gnu": ["@oxfmt/binding-linux-s390x-gnu@0.34.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-cCtGgmrTrxq3OeSG0UAO+w6yLZTMeOF4XM9SAkNrRUxYhRQELSDQ/iNPCLyHhYNi38uHJQbS5RQweLUDpI4ajA=="],
+
+ "@oxfmt/binding-linux-x64-gnu": ["@oxfmt/binding-linux-x64-gnu@0.34.0", "", { "os": "linux", "cpu": "x64" }, "sha512-7AvMzmeX+k7GdgitXp99GQoIV/QZIpAS7rwxQvC/T541yWC45nwvk4mpnU8N+V6dE5SPEObnqfhCjO80s7qIsg=="],
+
+ "@oxfmt/binding-linux-x64-musl": ["@oxfmt/binding-linux-x64-musl@0.34.0", "", { "os": "linux", "cpu": "x64" }, "sha512-uNiglhcmivJo1oDMh3hoN/Z0WsbEXOpRXZdQ3W/IkOpyV8WF308jFjSC1ZxajdcNRXWej0zgge9QXba58Owt+g=="],
+
+ "@oxfmt/binding-openharmony-arm64": ["@oxfmt/binding-openharmony-arm64@0.34.0", "", { "os": "none", "cpu": "arm64" }, "sha512-5eFsTjCyji25j6zznzlMc+wQAZJoL9oWy576xhqd2efv+N4g1swIzuSDcb1dz4gpcVC6veWe9pAwD7HnrGjLwg=="],
+
+ "@oxfmt/binding-win32-arm64-msvc": ["@oxfmt/binding-win32-arm64-msvc@0.34.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-6id8kK0t5hKfbV6LHDzRO21wRTA6ctTlKGTZIsG/mcoir0rssvaYsedUymF4HDj7tbCUlnxCX/qOajKlEuqbIw=="],
+
+ "@oxfmt/binding-win32-ia32-msvc": ["@oxfmt/binding-win32-ia32-msvc@0.34.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-QHaz+w673mlYqn9v/+fuiKZpjkmagleXQ+NygShDv8tdHpRYX2oYhTJwwt9j1ZfVhRgza1EIUW3JmzCXmtPdhQ=="],
+
+ "@oxfmt/binding-win32-x64-msvc": ["@oxfmt/binding-win32-x64-msvc@0.34.0", "", { "os": "win32", "cpu": "x64" }, "sha512-CXKQM/VaF+yuvGru8ktleHLJoBdjBtTFmAsLGePiESiTN0NjCI/PiaiOCfHMJ1HdP1LykvARUwMvgaN3tDhcrg=="],
+
+ "@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.49.0", "", { "os": "android", "cpu": "arm" }, "sha512-2WPoh/2oK9r/i2R4o4J18AOrm3HVlWiHZ8TnuCaS4dX8m5ZzRmHW0I3eLxEurQLHWVruhQN7fHgZnah+ag5iQg=="],
+
+ "@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.49.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YqJAGvNB11EzoKm1euVhZntb79alhMvWW/j12bYqdvVxn6xzEQWrEDCJg9BPo3A3tBCSUBKH7bVkAiCBqK/L1w=="],
+
+ "@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.49.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-WFocCRlvVkMhChCJ2qpJfp1Gj/IjvyjuifH9Pex8m8yHonxxQa3d8DZYreuDQU3T4jvSY8rqhoRqnpc61Nlbxw=="],
+
+ "@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.49.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-BN0KniwvehbUfYztOMwEDkYoojGm/narf5oJf+/ap+6PnzMeWLezMaVARNIS0j3OdMkjHTEP8s3+GdPJ7WDywQ=="],
+
+ "@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.49.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-SnkAc/DPIY6joMCiP/+53Q+N2UOGMU6ULvbztpmvPJNF/jYPGhNbKtN982uj2Gs6fpbxYkmyj08QnpkD4fbHJA=="],
+
+ "@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.49.0", "", { "os": "linux", "cpu": "arm" }, "sha512-6Z3EzRvpQVIpO7uFhdiGhdE8Mh3S2VWKLL9xuxVqD6fzPhyI3ugthpYXlCChXzO8FzcYIZ3t1+Kau+h2NY1hqA=="],
+
+ "@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.49.0", "", { "os": "linux", "cpu": "arm" }, "sha512-wdjXaQYAL/L25732mLlngfst4Jdmi/HLPVHb3yfCoP5mE3lO/pFFrmOJpqWodgv29suWY74Ij+RmJ/YIG5VuzQ=="],
+
+ "@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.49.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-oSHpm8zmSvAG1BWUumbDRSg7moJbnwoEXKAkwDf/xTQJOzvbUknq95NVQdw/AduZr5dePftalB8rzJNGBogUMg=="],
+
+ "@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.49.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-xeqkMOARgGBlEg9BQuPDf6ZW711X6BT5qjDyeM5XNowCJeTSdmMhpePJjTEiVbbr3t21sIlK8RE6X5bc04nWyQ=="],
+
+ "@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.49.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-uvcqRO6PnlJGbL7TeePhTK5+7/JXbxGbN+C6FVmfICDeeRomgQqrfVjf0lUrVpUU8ii8TSkIbNdft3M+oNlOsQ=="],
+
+ "@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.49.0", "", { "os": "linux", "cpu": "none" }, "sha512-Dw1HkdXAwHNH+ZDserHP2RzXQmhHtpsYYI0hf8fuGAVCIVwvS6w1+InLxpPMY25P8ASRNiFN3hADtoh6lI+4lg=="],
+
+ "@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.49.0", "", { "os": "linux", "cpu": "none" }, "sha512-EPlMYaA05tJ9km/0dI9K57iuMq3Tw+nHst7TNIegAJZrBPtsOtYaMFZEaWj02HA8FI5QvSnRHMt+CI+RIhXJBQ=="],
+
+ "@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.49.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-yZiQL9qEwse34aMbnMb5VqiAWfDY+fLFuoJbHOuzB1OaJZbN1MRF9Nk+W89PIpGr5DNPDipwjZb8+Q7wOywoUQ=="],
+
+ "@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.49.0", "", { "os": "linux", "cpu": "x64" }, "sha512-CcCDwMMXSchNkhdgvhVn3DLZ4EnBXAD8o8+gRzahg+IdSt/72y19xBgShJgadIRF0TsRcV/MhDUMwL5N/W54aQ=="],
+
+ "@oxlint/binding-linux-x64-musl": ["@oxlint/binding-linux-x64-musl@1.49.0", "", { "os": "linux", "cpu": "x64" }, "sha512-u3HfKV8BV6t6UCCbN0RRiyqcymhrnpunVmLFI8sEa5S/EBu+p/0bJ3D7LZ2KT6PsBbrB71SWq4DeFrskOVgIZg=="],
+
+ "@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.49.0", "", { "os": "none", "cpu": "arm64" }, "sha512-dRDpH9fw+oeUMpM4br0taYCFpW6jQtOuEIec89rOgDA1YhqwmeRcx0XYeCv7U48p57qJ1XZHeMGM9LdItIjfzA=="],
+
+ "@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.49.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-6rrKe/wL9tn0qnOy76i1/0f4Dc3dtQnibGlU4HqR/brVHlVjzLSoaH0gAFnLnznh9yQ6gcFTBFOPrcN/eKPDGA=="],
+
+ "@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.49.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-CXHLWAtLs2xG/aVy1OZiYJzrULlq0QkYpI6cd7VKMrab+qur4fXVE/B1Bp1m0h1qKTj5/FTGg6oU4qaXMjS/ug=="],
+
+ "@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.49.0", "", { "os": "win32", "cpu": "x64" }, "sha512-VteIelt78kwzSglOozaQcs6BCS4Lk0j+QA+hGV0W8UeyaqQ3XpbZRhDU55NW1PPvCy1tg4VXsTlEaPovqto7nQ=="],
+
"@parcel/watcher": ["@parcel/watcher@2.5.6", "", { "dependencies": { "detect-libc": "^2.0.3", "is-glob": "^4.0.3", "node-addon-api": "^7.0.0", "picomatch": "^4.0.3" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.6", "@parcel/watcher-darwin-arm64": "2.5.6", "@parcel/watcher-darwin-x64": "2.5.6", "@parcel/watcher-freebsd-x64": "2.5.6", "@parcel/watcher-linux-arm-glibc": "2.5.6", "@parcel/watcher-linux-arm-musl": "2.5.6", "@parcel/watcher-linux-arm64-glibc": "2.5.6", "@parcel/watcher-linux-arm64-musl": "2.5.6", "@parcel/watcher-linux-x64-glibc": "2.5.6", "@parcel/watcher-linux-x64-musl": "2.5.6", "@parcel/watcher-win32-arm64": "2.5.6", "@parcel/watcher-win32-ia32": "2.5.6", "@parcel/watcher-win32-x64": "2.5.6" } }, "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ=="],
"@parcel/watcher-android-arm64": ["@parcel/watcher-android-arm64@2.5.6", "", { "os": "android", "cpu": "arm64" }, "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A=="],
@@ -253,21 +341,25 @@
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
- "@types/node": ["@types/node@24.10.13", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg=="],
+ "@types/fs-extra": ["@types/fs-extra@11.0.4", "", { "dependencies": { "@types/jsonfile": "*", "@types/node": "*" } }, "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ=="],
+
+ "@types/jsonfile": ["@types/jsonfile@6.1.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ=="],
+
+ "@types/node": ["@types/node@25.3.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A=="],
- "@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="],
+ "@vitest/expect": ["@vitest/expect@4.0.18", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.0.18", "@vitest/utils": "4.0.18", "chai": "^6.2.1", "tinyrainbow": "^3.0.3" } }, "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ=="],
- "@vitest/mocker": ["@vitest/mocker@3.2.4", "", { "dependencies": { "@vitest/spy": "3.2.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ=="],
+ "@vitest/mocker": ["@vitest/mocker@4.0.18", "", { "dependencies": { "@vitest/spy": "4.0.18", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ=="],
- "@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="],
+ "@vitest/pretty-format": ["@vitest/pretty-format@4.0.18", "", { "dependencies": { "tinyrainbow": "^3.0.3" } }, "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw=="],
- "@vitest/runner": ["@vitest/runner@3.2.4", "", { "dependencies": { "@vitest/utils": "3.2.4", "pathe": "^2.0.3", "strip-literal": "^3.0.0" } }, "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ=="],
+ "@vitest/runner": ["@vitest/runner@4.0.18", "", { "dependencies": { "@vitest/utils": "4.0.18", "pathe": "^2.0.3" } }, "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw=="],
- "@vitest/snapshot": ["@vitest/snapshot@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "magic-string": "^0.30.17", "pathe": "^2.0.3" } }, "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ=="],
+ "@vitest/snapshot": ["@vitest/snapshot@4.0.18", "", { "dependencies": { "@vitest/pretty-format": "4.0.18", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA=="],
- "@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="],
+ "@vitest/spy": ["@vitest/spy@4.0.18", "", {}, "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw=="],
- "@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="],
+ "@vitest/utils": ["@vitest/utils@4.0.18", "", { "dependencies": { "@vitest/pretty-format": "4.0.18", "tinyrainbow": "^3.0.3" } }, "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA=="],
"ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="],
@@ -281,13 +373,15 @@
"assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
+ "balanced-match": ["balanced-match@4.0.3", "", {}, "sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g=="],
+
"better-path-resolve": ["better-path-resolve@1.0.0", "", { "dependencies": { "is-windows": "^1.0.0" } }, "sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g=="],
- "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
+ "brace-expansion": ["brace-expansion@5.0.2", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw=="],
- "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
+ "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
- "chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="],
+ "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
"chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
@@ -295,10 +389,10 @@
"chardet": ["chardet@2.1.1", "", {}, "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ=="],
- "check-error": ["check-error@2.1.3", "", {}, "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA=="],
-
"ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="],
+ "citty": ["citty@0.2.1", "", {}, "sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg=="],
+
"color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
"color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
@@ -307,11 +401,11 @@
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
- "dataloader": ["dataloader@1.4.0", "", {}, "sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw=="],
+ "crypto-random-string": ["crypto-random-string@4.0.0", "", { "dependencies": { "type-fest": "^1.0.1" } }, "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA=="],
- "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
+ "dataloader": ["dataloader@1.4.0", "", {}, "sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw=="],
- "deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="],
+ "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
"detect-indent": ["detect-indent@6.1.0", "", {}, "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA=="],
@@ -355,12 +449,14 @@
"find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
- "fs-extra": ["fs-extra@7.0.1", "", { "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw=="],
+ "fs-extra": ["fs-extra@11.3.3", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"get-tsconfig": ["get-tsconfig@4.13.6", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw=="],
+ "glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
+
"glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"globby": ["globby@11.1.0", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.2.9", "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" } }, "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g=="],
@@ -383,6 +479,8 @@
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
+ "is-stream": ["is-stream@3.0.0", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="],
+
"is-subdir": ["is-subdir@1.2.0", "", { "dependencies": { "better-path-resolve": "1.0.0" } }, "sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw=="],
"is-windows": ["is-windows@1.0.2", "", {}, "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA=="],
@@ -391,19 +489,39 @@
"jju": ["jju@1.4.0", "", {}, "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA=="],
- "js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="],
-
"js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
- "jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="],
+ "jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="],
+
+ "jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="],
+
+ "lefthook": ["lefthook@2.1.1", "", { "optionalDependencies": { "lefthook-darwin-arm64": "2.1.1", "lefthook-darwin-x64": "2.1.1", "lefthook-freebsd-arm64": "2.1.1", "lefthook-freebsd-x64": "2.1.1", "lefthook-linux-arm64": "2.1.1", "lefthook-linux-x64": "2.1.1", "lefthook-openbsd-arm64": "2.1.1", "lefthook-openbsd-x64": "2.1.1", "lefthook-windows-arm64": "2.1.1", "lefthook-windows-x64": "2.1.1" }, "bin": { "lefthook": "bin/index.js" } }, "sha512-Tl9h9c+sG3ShzTHKuR3LAIblnnh+Mgxnm2Ul7yu9cu260Z27LEbO3V6Zw4YZFP59/2rlD42pt/llYsQCkkCFzw=="],
+
+ "lefthook-darwin-arm64": ["lefthook-darwin-arm64@2.1.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-O/RS1j03/Fnq5zCzEb2r7UOBsqPeBuf1C5pMkIJcO4TSE6hf3rhLUkcorKc2M5ni/n5zLGtzQUXHV08/fSAT3Q=="],
+
+ "lefthook-darwin-x64": ["lefthook-darwin-x64@2.1.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-mm/kdKl81ROPoYnj9XYk5JDqj+/6Al8w/SSPDfhItkLJyl4pqS+hWUOP6gDGrnuRk8S0DvJ2+hzhnDsQnZohWQ=="],
+
+ "lefthook-freebsd-arm64": ["lefthook-freebsd-arm64@2.1.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-F7JXlKmjxGqGbCWPLND0bVB4DMQezIe48pEwTlUQZbxh450c2gP5Q8FdttMZKOT163kBGGTqJAJSEC6zW+QSxA=="],
+
+ "lefthook-freebsd-x64": ["lefthook-freebsd-x64@2.1.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Po8/lJMqNzKSZPuEI46dLuWoBoXtAxCuRpeOh6DAV/M4RhBynaCu8rLMZ9BqF7cVbZEWoplOmYo6HdOuiYpCkQ=="],
+
+ "lefthook-linux-arm64": ["lefthook-linux-arm64@2.1.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-mI2ljFgPEqHxI8vrN9nKgnVu63Rz1KisDbPwlvs7BTYNwq3sncdK5ukpGR4zzWdh6saNJ5tCtHEtep5GQI11nw=="],
+
+ "lefthook-linux-x64": ["lefthook-linux-x64@2.1.1", "", { "os": "linux", "cpu": "x64" }, "sha512-m3G/FaxC+crxeg9XeaUuHfEoL+i9gbkg2Hp2KD2IcVVIxprqlyqf0Hb8zbLV2NMXuo5RSGokJu44oAoTO3Ou2g=="],
+
+ "lefthook-openbsd-arm64": ["lefthook-openbsd-arm64@2.1.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-gz/8FJPvhjOdOFt1GmFvuvDOe+W+BBRjoeAT1/mTgkN7HCXMXgqNjjvakQKQeGz1I1v08wXG1ZNf5y+T9XBCDQ=="],
+
+ "lefthook-openbsd-x64": ["lefthook-openbsd-x64@2.1.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-ch3lyMUtbmtWUufaQVn4IoEs/2hjK51XqaCdY1mh5ca//VctR1peknIwQ5feHu+vATCDviWQ7HsdNDewm3HMPg=="],
+
+ "lefthook-windows-arm64": ["lefthook-windows-arm64@2.1.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-mm3PZhKDs9FE/jQDimkfWxtoj9xQ2k8uw2MdhtC825bhvIh+MEi0WFj/MOW+ug0RBg0I55tGYzZ5aVuozAWpTQ=="],
+
+ "lefthook-windows-x64": ["lefthook-windows-x64@2.1.1", "", { "os": "win32", "cpu": "x64" }, "sha512-1L2oGIzmhfOTxfwbe5mpSQ+m3ilpvGNymwIhn4UHq6hwHsUL6HEhODqx02GfBn6OXpVIr56bvdBAusjL/SVYGQ=="],
"locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
"lodash.startcase": ["lodash.startcase@4.4.0", "", {}, "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg=="],
- "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="],
-
- "lru-cache": ["lru-cache@4.1.5", "", { "dependencies": { "pseudomap": "^1.0.2", "yallist": "^2.1.2" } }, "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g=="],
+ "lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="],
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
@@ -413,9 +531,11 @@
"mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="],
- "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
+ "minimatch": ["minimatch@10.2.2", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw=="],
- "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
+ "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
+
+ "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
"msgpackr": ["msgpackr@1.11.8", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.2" } }, "sha512-bC4UGzHhVvgDNS7kn9tV8fAucIYUBuGojcaLiz7v+P63Lmtm0Xeji8B/8tYKddALXxJLpwIeBmUN3u64C4YkRA=="],
@@ -431,8 +551,16 @@
"node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="],
+ "nypm": ["nypm@0.6.5", "", { "dependencies": { "citty": "^0.2.0", "pathe": "^2.0.3", "tinyexec": "^1.0.2" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ=="],
+
+ "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="],
+
"outdent": ["outdent@0.5.0", "", {}, "sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q=="],
+ "oxfmt": ["oxfmt@0.34.0", "", { "dependencies": { "tinypool": "2.1.0" }, "optionalDependencies": { "@oxfmt/binding-android-arm-eabi": "0.34.0", "@oxfmt/binding-android-arm64": "0.34.0", "@oxfmt/binding-darwin-arm64": "0.34.0", "@oxfmt/binding-darwin-x64": "0.34.0", "@oxfmt/binding-freebsd-x64": "0.34.0", "@oxfmt/binding-linux-arm-gnueabihf": "0.34.0", "@oxfmt/binding-linux-arm-musleabihf": "0.34.0", "@oxfmt/binding-linux-arm64-gnu": "0.34.0", "@oxfmt/binding-linux-arm64-musl": "0.34.0", "@oxfmt/binding-linux-ppc64-gnu": "0.34.0", "@oxfmt/binding-linux-riscv64-gnu": "0.34.0", "@oxfmt/binding-linux-riscv64-musl": "0.34.0", "@oxfmt/binding-linux-s390x-gnu": "0.34.0", "@oxfmt/binding-linux-x64-gnu": "0.34.0", "@oxfmt/binding-linux-x64-musl": "0.34.0", "@oxfmt/binding-openharmony-arm64": "0.34.0", "@oxfmt/binding-win32-arm64-msvc": "0.34.0", "@oxfmt/binding-win32-ia32-msvc": "0.34.0", "@oxfmt/binding-win32-x64-msvc": "0.34.0" }, "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-t+zTE4XGpzPTK+Zk9gSwcJcFi4pqjl6PwO/ZxPBJiJQ2XCKMucwjPlHxvPHyVKJtkMSyrDGfQ7Ntg/hUr4OgHQ=="],
+
+ "oxlint": ["oxlint@1.49.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.49.0", "@oxlint/binding-android-arm64": "1.49.0", "@oxlint/binding-darwin-arm64": "1.49.0", "@oxlint/binding-darwin-x64": "1.49.0", "@oxlint/binding-freebsd-x64": "1.49.0", "@oxlint/binding-linux-arm-gnueabihf": "1.49.0", "@oxlint/binding-linux-arm-musleabihf": "1.49.0", "@oxlint/binding-linux-arm64-gnu": "1.49.0", "@oxlint/binding-linux-arm64-musl": "1.49.0", "@oxlint/binding-linux-ppc64-gnu": "1.49.0", "@oxlint/binding-linux-riscv64-gnu": "1.49.0", "@oxlint/binding-linux-riscv64-musl": "1.49.0", "@oxlint/binding-linux-s390x-gnu": "1.49.0", "@oxlint/binding-linux-x64-gnu": "1.49.0", "@oxlint/binding-linux-x64-musl": "1.49.0", "@oxlint/binding-openharmony-arm64": "1.49.0", "@oxlint/binding-win32-arm64-msvc": "1.49.0", "@oxlint/binding-win32-ia32-msvc": "1.49.0", "@oxlint/binding-win32-x64-msvc": "1.49.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.14.1" }, "optionalPeers": ["oxlint-tsgolint"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-YZffp0gM+63CJoRhHjtjRnwKtAgUnXM6j63YQ++aigji2NVvLGsUlrXo9gJUXZOdcbfShLYtA6RuTu8GZ4lzOQ=="],
+
"p-filter": ["p-filter@2.1.0", "", { "dependencies": { "p-map": "^2.0.0" } }, "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw=="],
"p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
@@ -449,12 +577,12 @@
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
+ "path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="],
+
"path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="],
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
- "pathval": ["pathval@2.0.1", "", {}, "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ=="],
-
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
@@ -497,6 +625,8 @@
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
+ "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
+
"slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
@@ -513,23 +643,23 @@
"strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="],
- "strip-literal": ["strip-literal@3.1.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg=="],
-
"supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="],
+ "temp-dir": ["temp-dir@3.0.0", "", {}, "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw=="],
+
+ "tempy": ["tempy@3.2.0", "", { "dependencies": { "is-stream": "^3.0.0", "temp-dir": "^3.0.0", "type-fest": "^2.12.2", "unique-string": "^3.0.0" } }, "sha512-d79HhZya5Djd7am0q+W4RTsSU+D/aJzM+4Y4AGJGuGlgM2L6sx5ZvOYTmZjqPhrDrV6xJTtRSm1JCLj6V6LHLQ=="],
+
"term-size": ["term-size@2.2.1", "", {}, "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg=="],
"tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
- "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="],
+ "tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
- "tinypool": ["tinypool@1.1.1", "", {}, "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg=="],
-
- "tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="],
+ "tinypool": ["tinypool@2.1.0", "", {}, "sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw=="],
- "tinyspy": ["tinyspy@4.0.4", "", {}, "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q=="],
+ "tinyrainbow": ["tinyrainbow@3.0.3", "", {}, "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q=="],
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
@@ -539,21 +669,25 @@
"tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="],
+ "type-fest": ["type-fest@2.19.0", "", {}, "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA=="],
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
+ "ultracite": ["ultracite@7.2.3", "", { "dependencies": { "@clack/prompts": "^1.0.1", "commander": "^14.0.3", "deepmerge": "^4.3.1", "glob": "^13.0.3", "jsonc-parser": "^3.3.1", "nypm": "^0.6.5" }, "peerDependencies": { "oxlint": "^1.0.0" }, "optionalPeers": ["oxlint"], "bin": { "ultracite": "dist/index.js" } }, "sha512-WKNS2sKAZe4BHu+JGbZebXvy/A1QagDaBnndrK/zwOJAze/mQ8jeHfdG2bPlv3qcJ5fdS3w2Kd7c/eIcH78HvA=="],
+
"undici": ["undici@7.22.0", "", {}, "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg=="],
- "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
+ "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
- "universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="],
+ "unique-string": ["unique-string@3.0.0", "", { "dependencies": { "crypto-random-string": "^4.0.0" } }, "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ=="],
+
+ "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
"uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="],
"vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="],
- "vite-node": ["vite-node@3.2.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="],
-
- "vitest": ["vitest@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", "@vitest/mocker": "3.2.4", "@vitest/pretty-format": "^3.2.4", "@vitest/runner": "3.2.4", "@vitest/snapshot": "3.2.4", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "debug": "^4.4.1", "expect-type": "^1.2.1", "magic-string": "^0.30.17", "pathe": "^2.0.3", "picomatch": "^4.0.2", "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.14", "tinypool": "^1.1.1", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", "vite-node": "3.2.4", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.2.4", "@vitest/ui": "3.2.4", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A=="],
+ "vitest": ["vitest@4.0.18", "", { "dependencies": { "@vitest/expect": "4.0.18", "@vitest/mocker": "4.0.18", "@vitest/pretty-format": "4.0.18", "@vitest/runner": "4.0.18", "@vitest/snapshot": "4.0.18", "@vitest/spy": "4.0.18", "@vitest/utils": "4.0.18", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^3.10.0", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.0.18", "@vitest/browser-preview": "4.0.18", "@vitest/browser-webdriverio": "4.0.18", "@vitest/ui": "4.0.18", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ=="],
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
@@ -569,6 +703,18 @@
"yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="],
+ "@changesets/apply-release-plan/fs-extra": ["fs-extra@7.0.1", "", { "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw=="],
+
+ "@changesets/cli/fs-extra": ["fs-extra@7.0.1", "", { "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw=="],
+
+ "@changesets/config/fs-extra": ["fs-extra@7.0.1", "", { "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw=="],
+
+ "@changesets/pre/fs-extra": ["fs-extra@7.0.1", "", { "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw=="],
+
+ "@changesets/read/fs-extra": ["fs-extra@7.0.1", "", { "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw=="],
+
+ "@changesets/write/fs-extra": ["fs-extra@7.0.1", "", { "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw=="],
+
"@manypkg/find-root/@types/node": ["@types/node@12.20.55", "", {}, "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ=="],
"@manypkg/find-root/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="],
@@ -577,6 +723,10 @@
"@manypkg/get-packages/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="],
+ "@types/fs-extra/@types/node": ["@types/node@24.10.13", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg=="],
+
+ "@types/jsonfile/@types/node": ["@types/node@24.10.13", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg=="],
+
"changeset-conventional-commits/@changesets/read": ["@changesets/read@0.5.9", "", { "dependencies": { "@babel/runtime": "^7.20.1", "@changesets/git": "^2.0.0", "@changesets/logger": "^0.0.5", "@changesets/parse": "^0.3.16", "@changesets/types": "^5.2.1", "chalk": "^2.1.0", "fs-extra": "^7.0.1", "p-filter": "^2.1.0" } }, "sha512-T8BJ6JS6j1gfO1HFq50kU3qawYxa4NTbI/ASNVVCBTsKquy2HYwM9r7ZnzkiMe8IEObAJtUVGSrePCOxAK2haQ=="],
"changeset-conventional-commits/@changesets/types": ["@changesets/types@5.2.1", "", {}, "sha512-myLfHbVOqaq9UtUKqR/nZA/OY7xFjQMdfgfqeZIBK4d0hA6pgxArvdv8M+6NUzzBsjWLOtvApv8YHr4qM+Kpfg=="],
@@ -585,10 +735,48 @@
"changeset-conventional-commits/@manypkg/get-packages": ["@manypkg/get-packages@2.2.2", "", { "dependencies": { "@manypkg/find-root": "^2.2.2", "@manypkg/tools": "^1.1.1" } }, "sha512-3+Zd8kLZmsyJFmWTBtY0MAuCErI7yKB2cjMBlujvSVKZ2R/BMXi0kjCXu2dtRlSq/ML86t1FkumT0yreQ3n8OQ=="],
+ "crypto-random-string/type-fest": ["type-fest@1.4.0", "", {}, "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA=="],
+
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"read-yaml-file/js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="],
+ "@changesets/apply-release-plan/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="],
+
+ "@changesets/apply-release-plan/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="],
+
+ "@changesets/cli/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="],
+
+ "@changesets/cli/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="],
+
+ "@changesets/config/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="],
+
+ "@changesets/config/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="],
+
+ "@changesets/pre/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="],
+
+ "@changesets/pre/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="],
+
+ "@changesets/read/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="],
+
+ "@changesets/read/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="],
+
+ "@changesets/write/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="],
+
+ "@changesets/write/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="],
+
+ "@manypkg/find-root/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="],
+
+ "@manypkg/find-root/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="],
+
+ "@manypkg/get-packages/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="],
+
+ "@manypkg/get-packages/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="],
+
+ "@types/fs-extra/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
+
+ "@types/jsonfile/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
+
"changeset-conventional-commits/@changesets/read/@changesets/git": ["@changesets/git@2.0.0", "", { "dependencies": { "@babel/runtime": "^7.20.1", "@changesets/errors": "^0.1.4", "@changesets/types": "^5.2.1", "@manypkg/get-packages": "^1.1.3", "is-subdir": "^1.1.1", "micromatch": "^4.0.2", "spawndamnit": "^2.0.0" } }, "sha512-enUVEWbiqUTxqSnmesyJGWfzd51PY4H7mH9yUw0hPVpZBJ6tQZFMU3F3mT/t9OJ/GjyiM4770i+sehAn6ymx6A=="],
"changeset-conventional-commits/@changesets/read/@changesets/logger": ["@changesets/logger@0.0.5", "", { "dependencies": { "chalk": "^2.1.0" } }, "sha512-gJyZHomu8nASHpaANzc6bkQMO9gU/ib20lqew1rVx753FOxffnCrJlGIeQVxNWCqM+o6OOleCo/ivL8UAO5iFw=="],
@@ -597,6 +785,10 @@
"changeset-conventional-commits/@changesets/read/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="],
+ "changeset-conventional-commits/@changesets/read/fs-extra": ["fs-extra@7.0.1", "", { "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw=="],
+
+ "changeset-conventional-commits/@changesets/write/fs-extra": ["fs-extra@7.0.1", "", { "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw=="],
+
"changeset-conventional-commits/@changesets/write/human-id": ["human-id@1.0.2", "", {}, "sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw=="],
"changeset-conventional-commits/@manypkg/get-packages/@manypkg/find-root": ["@manypkg/find-root@2.2.3", "", { "dependencies": { "@manypkg/tools": "^1.1.2" } }, "sha512-jtEZKczWTueJYHjGpxU3KJQ08Gsrf4r6Q2GjmPp/RGk5leeYAA1eyDADSAF+KVCsQ6EwZd/FMcOFCoMhtqdCtQ=="],
@@ -611,6 +803,14 @@
"changeset-conventional-commits/@changesets/read/@changesets/parse/js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="],
+ "changeset-conventional-commits/@changesets/read/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="],
+
+ "changeset-conventional-commits/@changesets/read/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="],
+
+ "changeset-conventional-commits/@changesets/write/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="],
+
+ "changeset-conventional-commits/@changesets/write/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="],
+
"changeset-conventional-commits/@changesets/read/@changesets/git/@manypkg/get-packages/@changesets/types": ["@changesets/types@4.1.0", "", {}, "sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw=="],
"changeset-conventional-commits/@changesets/read/@changesets/git/@manypkg/get-packages/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="],
@@ -621,6 +821,12 @@
"changeset-conventional-commits/@changesets/read/@changesets/parse/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
+ "changeset-conventional-commits/@changesets/read/@changesets/git/@manypkg/get-packages/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="],
+
+ "changeset-conventional-commits/@changesets/read/@changesets/git/@manypkg/get-packages/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="],
+
+ "changeset-conventional-commits/@changesets/read/@changesets/git/spawndamnit/cross-spawn/lru-cache": ["lru-cache@4.1.5", "", { "dependencies": { "pseudomap": "^1.0.2", "yallist": "^2.1.2" } }, "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g=="],
+
"changeset-conventional-commits/@changesets/read/@changesets/git/spawndamnit/cross-spawn/shebang-command": ["shebang-command@1.2.0", "", { "dependencies": { "shebang-regex": "^1.0.0" } }, "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg=="],
"changeset-conventional-commits/@changesets/read/@changesets/git/spawndamnit/cross-spawn/which": ["which@1.3.1", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "which": "./bin/which" } }, "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ=="],
diff --git a/examples/basic/README.md b/examples/basic/README.md
index 60e3b0e..8dba017 100644
--- a/examples/basic/README.md
+++ b/examples/basic/README.md
@@ -28,9 +28,9 @@ npm install acme-toolkit
1. Import the toolkit and initialize your client.
```ts
-import { createClient } from 'acme-toolkit'
+import { createClient } from "acme-toolkit";
-const client = createClient()
+const client = createClient();
```
## Support
diff --git a/examples/basic/readie.json b/examples/basic/readie.json
index ea0cf3a..77b9984 100644
--- a/examples/basic/readie.json
+++ b/examples/basic/readie.json
@@ -1,29 +1,21 @@
{
- "$schema": "https://unpkg.com/readie/schemas/readie.schema.json",
- "version": "1",
- "title": "Acme Toolkit",
- "description": "A TypeScript toolkit for building internal tools.",
- "includeTableOfContents": true,
- "badges": [
- {
- "label": "npm",
- "image": "https://img.shields.io/npm/v/acme-toolkit.svg",
- "link": "https://www.npmjs.com/package/acme-toolkit"
- }
- ],
- "features": [
- "Typed APIs",
- "Fast local setup",
- "Works in Node.js and Bun"
- ],
- "installation": [
- "```bash\nnpm install acme-toolkit\n```"
- ],
- "usage": [
- "Import the toolkit and initialize your client.",
- "```ts\nimport { createClient } from 'acme-toolkit'\n\nconst client = createClient()\n```"
- ],
- "support": [
- "Open an issue in your repository tracker."
- ]
+ "$schema": "https://unpkg.com/readie/schemas/readie.schema.json",
+ "version": "1",
+ "title": "Acme Toolkit",
+ "description": "A TypeScript toolkit for building internal tools.",
+ "includeTableOfContents": true,
+ "badges": [
+ {
+ "label": "npm",
+ "image": "https://img.shields.io/npm/v/acme-toolkit.svg",
+ "link": "https://www.npmjs.com/package/acme-toolkit"
+ }
+ ],
+ "features": ["Typed APIs", "Fast local setup", "Works in Node.js and Bun"],
+ "installation": ["```bash\nnpm install acme-toolkit\n```"],
+ "usage": [
+ "Import the toolkit and initialize your client.",
+ "```ts\nimport { createClient } from \"acme-toolkit\";\n\nconst client = createClient();\n```"
+ ],
+ "support": ["Open an issue in your repository tracker."]
}
diff --git a/examples/c15t/README.md b/examples/c15t/README.md
index cd2ec38..816b3cb 100644
--- a/examples/c15t/README.md
+++ b/examples/c15t/README.md
@@ -74,15 +74,15 @@ To manually install, follow the guide in our [docs – manual setup](https://c15
```tsx
// App.tsx
-import { ConsentManagerProvider, CookieBanner } from '@c15t/react'
+import { ConsentManagerProvider, CookieBanner } from "@c15t/react";
function App() {
- return (
-
-
-
-
- )
+ return (
+
+
+
+
+ );
}
```
diff --git a/examples/c15t/old.md b/examples/c15t/old.md
index cd2ec38..816b3cb 100644
--- a/examples/c15t/old.md
+++ b/examples/c15t/old.md
@@ -74,15 +74,15 @@ To manually install, follow the guide in our [docs – manual setup](https://c15
```tsx
// App.tsx
-import { ConsentManagerProvider, CookieBanner } from '@c15t/react'
+import { ConsentManagerProvider, CookieBanner } from "@c15t/react";
function App() {
- return (
-
-
-
-
- )
+ return (
+
+
+
+
+ );
}
```
diff --git a/lefthook.yml b/lefthook.yml
new file mode 100644
index 0000000..0e19112
--- /dev/null
+++ b/lefthook.yml
@@ -0,0 +1,12 @@
+pre-commit:
+ jobs:
+ - run: bun x ultracite fix
+ glob:
+ - "**/*.js"
+ - "**/*.jsx"
+ - "**/*.ts"
+ - "**/*.tsx"
+ - "**/*.json"
+ - "**/*.jsonc"
+ - "**/*.css"
+ stage_fixed: true
diff --git a/package.json b/package.json
index bba6683..51c1fc1 100644
--- a/package.json
+++ b/package.json
@@ -1,64 +1,76 @@
{
- "name": "readie",
- "version": "0.0.1",
- "description": "Generate high-quality README files from a validated JSON config.",
- "type": "module",
- "main": "dist/index.js",
- "bin": {
- "readie": "dist/index.js"
- },
- "files": [
- "dist",
- "schemas",
- "README.md",
- "LICENSE",
- "CHANGELOG.md",
- "CONTRIBUTING.md",
- "CODE_OF_CONDUCT.md",
- "SECURITY.md"
- ],
- "scripts": {
- "build": "bun build src/index.ts --outdir dist --target node --minify",
- "clean": "bun --eval \"import { rmSync } from 'node:fs'; rmSync('dist', { recursive: true, force: true });\"",
- "dev": "bun run src/index.ts",
- "test": "bunx vitest run",
- "test:watch": "bunx vitest",
- "typecheck": "bunx tsc -p tsconfig.json --noEmit",
- "prepare": "bun run build",
- "changeset": "changeset"
- },
- "keywords": [
- "readme",
- "documentation",
- "cli",
- "generator",
- "markdown"
- ],
- "author": "Christopher Burns",
- "license": "MIT",
- "repository": {
- "type": "git",
- "url": "git+https://github.com/consentdotio/readie.git"
- },
- "homepage": "https://github.com/consentdotio/readie",
- "bugs": {
- "url": "https://github.com/consentdotio/readie/issues"
- },
- "dependencies": {
- "@effect/cli": "^0.73.2",
- "@effect/platform": "^0.94.5",
- "@effect/platform-node": "^0.104.1",
- "@effect/printer": "^0.47.0",
- "@effect/printer-ansi": "^0.47.0",
- "effect": "^3.19.16"
-
- },
- "devDependencies": {
- "@types/node": "^24.3.0",
- "typescript": "^5.9.2",
- "vitest": "^3.2.4",
- "@changesets/changelog-github": "^0.5.1",
- "@changesets/cli": "^2.29.7",
- "changeset-conventional-commits": "^0.2.5"
- }
+ "name": "readie",
+ "version": "0.0.1",
+ "description": "Generate high-quality README files from a validated JSON config.",
+ "keywords": [
+ "cli",
+ "documentation",
+ "generator",
+ "markdown",
+ "readme"
+ ],
+ "homepage": "https://github.com/consentdotio/readie",
+ "bugs": {
+ "url": "https://github.com/consentdotio/readie/issues"
+ },
+ "license": "MIT",
+ "author": "Christopher Burns ",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/consentdotio/readie.git"
+ },
+ "bin": {
+ "readie": "dist/index.js"
+ },
+ "files": [
+ "dist",
+ "schemas",
+ "README.md",
+ "LICENSE",
+ "CHANGELOG.md",
+ "CONTRIBUTING.md",
+ "CODE_OF_CONDUCT.md",
+ "SECURITY.md"
+ ],
+ "type": "module",
+ "main": "dist/index.js",
+ "imports": {
+ "#src/*": "./src/*"
+ },
+ "scripts": {
+ "build": "bun build src/index.ts --outdir dist --target node --minify",
+ "changeset": "changeset",
+ "check": "ultracite check",
+ "clean": "bun --eval \"import { rmSync } from 'node:fs'; rmSync('dist', { recursive: true, force: true });\"",
+ "dev": "bun run src/index.ts",
+ "fix": "ultracite fix",
+ "prepare": "lefthook install",
+ "test": "bunx vitest run",
+ "test:watch": "bunx vitest",
+ "typecheck": "bunx tsc -p tsconfig.json --noEmit"
+ },
+ "dependencies": {
+ "@effect/cli": "^0.73.2",
+ "@effect/platform": "^0.94.5",
+ "@effect/platform-node": "^0.104.1",
+ "@effect/printer": "^0.47.0",
+ "@effect/printer-ansi": "^0.47.0",
+ "effect": "^3.19.18"
+ },
+ "devDependencies": {
+ "@changesets/changelog-github": "^0.5.2",
+ "@changesets/cli": "^2.29.8",
+ "@types/fs-extra": "^11.0.4",
+ "@types/node": "^25.3.0",
+ "changeset-conventional-commits": "^0.2.5",
+ "fs-extra": "^11.3.3",
+ "lefthook": "^2.1.1",
+ "oxfmt": "^0.34.0",
+ "oxlint": "^1.49.0",
+ "pathe": "^2.0.3",
+ "tempy": "^3.2.0",
+ "typescript": "^5.9.3",
+ "ultracite": "^7.2.3",
+ "vitest": "^4.0.18"
+ }
}
diff --git a/readie.json b/readie.json
index c84c998..aebf0de 100644
--- a/readie.json
+++ b/readie.json
@@ -1,66 +1,66 @@
{
- "$schema": "https://unpkg.com/readie/schemas/readie.schema.json",
- "version": "1",
- "title": "Readie",
- "description": "Developer-first CLI for generating polished, consistent README files from a simple config.",
- "includeTableOfContents": true,
- "features": [
- "Generate a single project README or many READMEs across a workspace",
- "Schema-backed configuration with editor autocomplete and validation",
- "Composable sections for installation, quick start, commands, flags, and more",
- "Supports custom markdown sections, badges, and project-specific content",
- "Easy onboarding with starter config generation",
- "Dry-run and strict workspace options for safe large-scale updates"
- ],
- "prerequisites": [
- "Node.js 18 or later",
- "npm, pnpm, or yarn",
- "A project with a `readie.json` config file"
- ],
- "manualInstallation": [
- "",
- "```bash\nnpm install -g readie\n```",
- "",
- "You can also run it without global install via `npx readie`."
- ],
- "quickStart": "Create a starter config and generate your README in minutes:\n\n```bash\n# 1) Initialize a config\nnpx readie init\n\n# 2) Generate README.md from readie.json\nnpx readie\n```\n\nFor monorepos/workspaces:\n\n```bash\nnpx readie generate:workspace --root ./packages --config-name readie.json\n```\n\nThis workflow helps teams keep README files consistent while still allowing per-project customization.",
- "usage": [
- "Create a config file with `npx readie init`.",
- "Run `npx readie` (or `npx readie generate`) to generate one README.",
- "Use `npx readie generate:workspace --root ./packages` to generate for multiple packages.",
- "Use `--dry-run` to preview changes and `--strict` to fail CI on generation errors.",
- "Extend generated docs with rich markdown via `quickStart`, `customSections`, and `footer`.",
- "Use placeholders in top-level strings of `readie.global.json`: `{{title}}`, `{{packageName}}`, and `{{packageNameEncoded}}`.",
- "```json\n{\n \"banner\": \"{{title}}
\",\n \"footer\": \"https://example.com?ref={{packageNameEncoded}}\"\n}\n```",
- "```bash\n# Single project\nnpx readie --config ./readie.json\n\n# Workspace with package filtering\nnpx readie generate:workspace --root ./packages --package ui --package api --dry-run\n```"
- ],
- "commands": [
- {
- "name": "readie",
- "description": "Generate a README from the local readie.json (default command)."
- },
- {
- "name": "readie generate",
- "description": "Explicit single-project generation command."
- },
- {
- "name": "readie generate:workspace",
- "description": "Generate READMEs for multiple projects in a workspace."
- },
- {
- "name": "readie init",
- "description": "Create a starter readie.json in the current directory."
- }
- ],
- "globalFlags": [
- {
- "flag": "--help, -h",
- "description": "Show command help."
- },
- {
- "flag": "--config, -c",
- "description": "Set a custom config path for supported commands."
- }
- ],
- "docsLink": "https://github.com/readie-cli/readie"
+ "$schema": "https://unpkg.com/readie/schemas/readie.schema.json",
+ "version": "1",
+ "title": "Readie",
+ "description": "Developer-first CLI for generating polished, consistent README files from a simple config.",
+ "includeTableOfContents": true,
+ "features": [
+ "Generate a single project README or many READMEs across a workspace",
+ "Schema-backed configuration with editor autocomplete and validation",
+ "Composable sections for installation, quick start, commands, flags, and more",
+ "Supports custom markdown sections, badges, and project-specific content",
+ "Easy onboarding with starter config generation",
+ "Dry-run and strict workspace options for safe large-scale updates"
+ ],
+ "prerequisites": [
+ "Node.js 18 or later",
+ "npm, pnpm, or yarn",
+ "A project with a `readie.json` config file"
+ ],
+ "manualInstallation": [
+ "",
+ "```bash\nnpm install -g readie\n```",
+ "",
+ "You can also run it without global install via `npx readie`."
+ ],
+ "quickStart": "Create a starter config and generate your README in minutes:\n\n```bash\n# 1) Initialize a config\nnpx readie init\n\n# 2) Generate README.md from readie.json\nnpx readie\n```\n\nFor monorepos/workspaces:\n\n```bash\nnpx readie generate:workspace --root ./packages --config-name readie.json\n```\n\nThis workflow helps teams keep README files consistent while still allowing per-project customization.",
+ "usage": [
+ "Create a config file with `npx readie init`.",
+ "Run `npx readie` (or `npx readie generate`) to generate one README.",
+ "Use `npx readie generate:workspace --root ./packages` to generate for multiple packages.",
+ "Use `--dry-run` to preview changes and `--strict` to fail CI on generation errors.",
+ "Extend generated docs with rich markdown via `quickStart`, `customSections`, and `footer`.",
+ "Use placeholders in top-level strings of `readie.global.json`: `{{title}}`, `{{packageName}}`, and `{{packageNameEncoded}}`.",
+ "```json\n{\n \"banner\": \"{{title}}
\",\n \"footer\": \"https://example.com?ref={{packageNameEncoded}}\"\n}\n```",
+ "```bash\n# Single project\nnpx readie --config ./readie.json\n\n# Workspace with package filtering\nnpx readie generate:workspace --root ./packages --package ui --package api --dry-run\n```"
+ ],
+ "commands": [
+ {
+ "name": "readie",
+ "description": "Generate a README from the local readie.json (default command)."
+ },
+ {
+ "name": "readie generate",
+ "description": "Explicit single-project generation command."
+ },
+ {
+ "name": "readie generate:workspace",
+ "description": "Generate READMEs for multiple projects in a workspace."
+ },
+ {
+ "name": "readie init",
+ "description": "Create a starter readie.json in the current directory."
+ }
+ ],
+ "globalFlags": [
+ {
+ "flag": "--help, -h",
+ "description": "Show command help."
+ },
+ {
+ "flag": "--config, -c",
+ "description": "Set a custom config path for supported commands."
+ }
+ ],
+ "docsLink": "https://github.com/readie-cli/readie"
}
diff --git a/schemas/readie.global.schema.json b/schemas/readie.global.schema.json
index b1a7872..7edc188 100644
--- a/schemas/readie.global.schema.json
+++ b/schemas/readie.global.schema.json
@@ -1,141 +1,141 @@
{
- "$schema": "https://json-schema.org/draft/2020-12/schema",
- "$id": "https://unpkg.com/readie/schemas/readie.global.schema.json",
- "title": "Readie Global Config",
- "description": "Global defaults for README generation that can be merged into project readie.json files.",
- "type": "object",
- "additionalProperties": false,
- "properties": {
- "$schema": {
- "type": "string",
- "description": "Optional JSON Schema URL for editor integration."
- },
- "version": {
- "type": "string",
- "description": "Config version. Current value is 1.",
- "default": "1",
- "enum": ["1"]
- },
- "title": {
- "type": "string",
- "minLength": 1
- },
- "description": {
- "type": "string",
- "minLength": 1
- },
- "output": {
- "type": "string",
- "description": "Output README path. Relative paths are resolved from the project config file directory."
- },
- "includeTableOfContents": {
- "type": "boolean",
- "default": true
- },
- "features": {
- "type": "array",
- "items": { "type": "string" }
- },
- "prerequisites": {
- "type": "array",
- "items": { "type": "string" }
- },
- "installation": {
- "type": "array",
- "items": { "type": "string" }
- },
- "manualInstallation": {
- "type": "array",
- "items": { "type": "string" }
- },
- "usage": {
- "type": "array",
- "items": { "type": "string" }
- },
- "commands": {
- "type": "array",
- "items": {
- "type": "object",
- "additionalProperties": false,
- "required": ["name", "description"],
- "properties": {
- "name": { "type": "string", "minLength": 1 },
- "description": { "type": "string", "minLength": 1 }
- }
- }
- },
- "globalFlags": {
- "type": "array",
- "items": {
- "type": "object",
- "additionalProperties": false,
- "required": ["flag", "description"],
- "properties": {
- "flag": { "type": "string", "minLength": 1 },
- "description": { "type": "string", "minLength": 1 }
- }
- }
- },
- "badges": {
- "type": "array",
- "items": {
- "type": "object",
- "additionalProperties": false,
- "required": ["label", "image"],
- "properties": {
- "label": { "type": "string", "minLength": 1 },
- "image": { "type": "string", "minLength": 1 },
- "link": { "type": "string", "minLength": 1 }
- }
- }
- },
- "banner": {
- "type": "string",
- "description": "Raw markdown or HTML rendered before the title. Supports {{title}}, {{packageName}}, and {{packageNameEncoded}} interpolation."
- },
- "quickStart": {
- "type": "string",
- "description": "Raw markdown rendered in a Quick Start section."
- },
- "support": {
- "type": "array",
- "items": { "type": "string" }
- },
- "contributing": {
- "type": "array",
- "items": { "type": "string" }
- },
- "security": {
- "type": "string"
- },
- "license": {
- "description": "License section content as markdown text or a named URL object.",
- "oneOf": [
- { "type": "string", "minLength": 1 },
- {
- "type": "object",
- "additionalProperties": false,
- "required": ["name", "url"],
- "properties": {
- "name": { "type": "string", "minLength": 1 },
- "url": { "type": "string", "minLength": 1 }
- }
- }
- ]
- },
- "footer": {
- "type": "string",
- "description": "Raw markdown appended to the end of the README. Supports {{title}}, {{packageName}}, and {{packageNameEncoded}} interpolation."
- },
- "docsLink": {
- "type": "string"
- },
- "quickStartLink": {
- "type": "string"
- },
- "customSections": {
- "type": "object",
- "additionalProperties": { "type": "string" }
- }
- }
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://unpkg.com/readie/schemas/readie.global.schema.json",
+ "title": "Readie Global Config",
+ "description": "Global defaults for README generation that can be merged into project readie.json files.",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "$schema": {
+ "type": "string",
+ "description": "Optional JSON Schema URL for editor integration."
+ },
+ "version": {
+ "type": "string",
+ "description": "Config version. Current value is 1.",
+ "default": "1",
+ "enum": ["1"]
+ },
+ "title": {
+ "type": "string",
+ "minLength": 1
+ },
+ "description": {
+ "type": "string",
+ "minLength": 1
+ },
+ "output": {
+ "type": "string",
+ "description": "Output README path. Relative paths are resolved from the project config file directory."
+ },
+ "includeTableOfContents": {
+ "type": "boolean",
+ "default": true
+ },
+ "features": {
+ "type": "array",
+ "items": { "type": "string" }
+ },
+ "prerequisites": {
+ "type": "array",
+ "items": { "type": "string" }
+ },
+ "installation": {
+ "type": "array",
+ "items": { "type": "string" }
+ },
+ "manualInstallation": {
+ "type": "array",
+ "items": { "type": "string" }
+ },
+ "usage": {
+ "type": "array",
+ "items": { "type": "string" }
+ },
+ "commands": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["name", "description"],
+ "properties": {
+ "name": { "type": "string", "minLength": 1 },
+ "description": { "type": "string", "minLength": 1 }
+ }
+ }
+ },
+ "globalFlags": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["flag", "description"],
+ "properties": {
+ "flag": { "type": "string", "minLength": 1 },
+ "description": { "type": "string", "minLength": 1 }
+ }
+ }
+ },
+ "badges": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["label", "image"],
+ "properties": {
+ "label": { "type": "string", "minLength": 1 },
+ "image": { "type": "string", "minLength": 1 },
+ "link": { "type": "string", "minLength": 1 }
+ }
+ }
+ },
+ "banner": {
+ "type": "string",
+ "description": "Raw markdown or HTML rendered before the title. Supports {{title}}, {{packageName}}, and {{packageNameEncoded}} interpolation."
+ },
+ "quickStart": {
+ "type": "string",
+ "description": "Raw markdown rendered in a Quick Start section."
+ },
+ "support": {
+ "type": "array",
+ "items": { "type": "string" }
+ },
+ "contributing": {
+ "type": "array",
+ "items": { "type": "string" }
+ },
+ "security": {
+ "type": "string"
+ },
+ "license": {
+ "description": "License section content as markdown text or a named URL object.",
+ "oneOf": [
+ { "type": "string", "minLength": 1 },
+ {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["name", "url"],
+ "properties": {
+ "name": { "type": "string", "minLength": 1 },
+ "url": { "type": "string", "minLength": 1 }
+ }
+ }
+ ]
+ },
+ "footer": {
+ "type": "string",
+ "description": "Raw markdown appended to the end of the README. Supports {{title}}, {{packageName}}, and {{packageNameEncoded}} interpolation."
+ },
+ "docsLink": {
+ "type": "string"
+ },
+ "quickStartLink": {
+ "type": "string"
+ },
+ "customSections": {
+ "type": "object",
+ "additionalProperties": { "type": "string" }
+ }
+ }
}
diff --git a/schemas/readie.schema.json b/schemas/readie.schema.json
index bc9ba15..0207b89 100644
--- a/schemas/readie.schema.json
+++ b/schemas/readie.schema.json
@@ -1,155 +1,157 @@
{
- "$schema": "https://json-schema.org/draft/2020-12/schema",
- "$id": "https://unpkg.com/readie/schemas/readie.schema.json",
- "title": "Readie Config",
- "description": "Configuration for generating README files with Readie.",
- "type": "object",
- "additionalProperties": false,
- "required": ["title", "description"],
- "properties": {
- "$schema": {
- "type": "string",
- "description": "Optional JSON Schema URL for editor integration."
- },
- "version": {
- "type": "string",
- "description": "Config version. Current value is 1.",
- "default": "1",
- "enum": ["1"]
- },
- "title": {
- "type": "string",
- "minLength": 1
- },
- "description": {
- "type": "string",
- "minLength": 1
- },
- "output": {
- "type": "string",
- "description": "Output README path. Relative paths are resolved from the config file directory."
- },
- "includeTableOfContents": {
- "type": "boolean",
- "default": true
- },
- "features": {
- "type": "array",
- "items": { "type": "string" }
- },
- "prerequisites": {
- "type": "array",
- "items": { "type": "string" }
- },
- "installation": {
- "type": "array",
- "items": { "type": "string" }
- },
- "manualInstallation": {
- "type": "array",
- "items": { "type": "string" }
- },
- "usage": {
- "type": "array",
- "items": { "type": "string" }
- },
- "commands": {
- "type": "array",
- "items": {
- "type": "object",
- "additionalProperties": false,
- "required": ["name", "description"],
- "properties": {
- "name": { "type": "string", "minLength": 1 },
- "description": { "type": "string", "minLength": 1 }
- }
- }
- },
- "globalFlags": {
- "type": "array",
- "items": {
- "type": "object",
- "additionalProperties": false,
- "required": ["flag", "description"],
- "properties": {
- "flag": { "type": "string", "minLength": 1 },
- "description": { "type": "string", "minLength": 1 }
- }
- }
- },
- "badges": {
- "type": "array",
- "items": {
- "type": "object",
- "additionalProperties": false,
- "required": ["label", "image"],
- "properties": {
- "label": { "type": "string", "minLength": 1 },
- "image": { "type": "string", "minLength": 1 },
- "link": { "type": "string", "minLength": 1 }
- }
- }
- },
- "banner": {
- "type": "string",
- "description": "Raw markdown or HTML rendered before the title. In readie.global.json, top-level string values support `{{title}}`, `{{packageName}}`, and `{{packageNameEncoded}}` interpolation."
- },
- "quickStart": {
- "type": "string",
- "description": "Raw markdown rendered in a Quick Start section."
- },
- "support": {
- "type": "array",
- "items": { "type": "string" }
- },
- "contributing": {
- "type": "array",
- "items": { "type": "string" }
- },
- "security": {
- "type": "string"
- },
- "license": {
- "description": "License section content as markdown text or a named URL object.",
- "oneOf": [
- { "type": "string", "minLength": 1 },
- {
- "type": "object",
- "additionalProperties": false,
- "required": ["name", "url"],
- "properties": {
- "name": { "type": "string", "minLength": 1 },
- "url": { "type": "string", "minLength": 1 }
- }
- }
- ]
- },
- "footer": {
- "type": "string",
- "description": "Raw markdown appended to the end of the README."
- },
- "docsLink": {
- "type": "string"
- },
- "quickStartLink": {
- "type": "string"
- },
- "customSections": {
- "type": "object",
- "additionalProperties": { "type": "string" }
- }
- },
- "examples": [
- {
- "$schema": "https://unpkg.com/readie/schemas/readie.schema.json",
- "version": "1",
- "title": "my-library",
- "description": "A concise project description.",
- "includeTableOfContents": true,
- "features": ["Fast", "Typed", "Zero-config"],
- "installation": ["```bash\\nnpm i my-library\\n```"],
- "usage": ["Import and use the library in your project."],
- "commands": [{ "name": "my-cli init", "description": "Initialize project files." }]
- }
- ]
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://unpkg.com/readie/schemas/readie.schema.json",
+ "title": "Readie Config",
+ "description": "Configuration for generating README files with Readie.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["title", "description"],
+ "properties": {
+ "$schema": {
+ "type": "string",
+ "description": "Optional JSON Schema URL for editor integration."
+ },
+ "version": {
+ "type": "string",
+ "description": "Config version. Current value is 1.",
+ "default": "1",
+ "enum": ["1"]
+ },
+ "title": {
+ "type": "string",
+ "minLength": 1
+ },
+ "description": {
+ "type": "string",
+ "minLength": 1
+ },
+ "output": {
+ "type": "string",
+ "description": "Output README path. Relative paths are resolved from the config file directory."
+ },
+ "includeTableOfContents": {
+ "type": "boolean",
+ "default": true
+ },
+ "features": {
+ "type": "array",
+ "items": { "type": "string" }
+ },
+ "prerequisites": {
+ "type": "array",
+ "items": { "type": "string" }
+ },
+ "installation": {
+ "type": "array",
+ "items": { "type": "string" }
+ },
+ "manualInstallation": {
+ "type": "array",
+ "items": { "type": "string" }
+ },
+ "usage": {
+ "type": "array",
+ "items": { "type": "string" }
+ },
+ "commands": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["name", "description"],
+ "properties": {
+ "name": { "type": "string", "minLength": 1 },
+ "description": { "type": "string", "minLength": 1 }
+ }
+ }
+ },
+ "globalFlags": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["flag", "description"],
+ "properties": {
+ "flag": { "type": "string", "minLength": 1 },
+ "description": { "type": "string", "minLength": 1 }
+ }
+ }
+ },
+ "badges": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["label", "image"],
+ "properties": {
+ "label": { "type": "string", "minLength": 1 },
+ "image": { "type": "string", "minLength": 1 },
+ "link": { "type": "string", "minLength": 1 }
+ }
+ }
+ },
+ "banner": {
+ "type": "string",
+ "description": "Raw markdown or HTML rendered before the title. In readie.global.json, top-level string values support `{{title}}`, `{{packageName}}`, and `{{packageNameEncoded}}` interpolation."
+ },
+ "quickStart": {
+ "type": "string",
+ "description": "Raw markdown rendered in a Quick Start section."
+ },
+ "support": {
+ "type": "array",
+ "items": { "type": "string" }
+ },
+ "contributing": {
+ "type": "array",
+ "items": { "type": "string" }
+ },
+ "security": {
+ "type": "string"
+ },
+ "license": {
+ "description": "License section content as markdown text or a named URL object.",
+ "oneOf": [
+ { "type": "string", "minLength": 1 },
+ {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["name", "url"],
+ "properties": {
+ "name": { "type": "string", "minLength": 1 },
+ "url": { "type": "string", "minLength": 1 }
+ }
+ }
+ ]
+ },
+ "footer": {
+ "type": "string",
+ "description": "Raw markdown appended to the end of the README."
+ },
+ "docsLink": {
+ "type": "string"
+ },
+ "quickStartLink": {
+ "type": "string"
+ },
+ "customSections": {
+ "type": "object",
+ "additionalProperties": { "type": "string" }
+ }
+ },
+ "examples": [
+ {
+ "$schema": "https://unpkg.com/readie/schemas/readie.schema.json",
+ "version": "1",
+ "title": "my-library",
+ "description": "A concise project description.",
+ "includeTableOfContents": true,
+ "features": ["Fast", "Typed", "Zero-config"],
+ "installation": ["```bash\\nnpm i my-library\\n```"],
+ "usage": ["Import and use the library in your project."],
+ "commands": [
+ { "name": "my-cli init", "description": "Initialize project files." }
+ ]
+ }
+ ]
}
diff --git a/src/cli/commands/generate-workspace.ts b/src/cli/commands/generate-workspace.ts
index b0125ef..833bddc 100644
--- a/src/cli/commands/generate-workspace.ts
+++ b/src/cli/commands/generate-workspace.ts
@@ -1,69 +1,91 @@
-import path from 'node:path';
-import { Command, Options } from '@effect/cli';
-import { Effect } from 'effect';
-import { generateWorkspaceReadmes, parsePackageList } from '../../readme-generator/generator.js';
+import { Command, Options } from "@effect/cli";
+import { Effect } from "effect";
+import { resolve } from "pathe";
+
+import {
+ generateWorkspaceReadmes,
+ parsePackageList,
+} from "#src/readme-generator/generator";
interface GenerateWorkspaceCommandArgs {
- root: string;
- configName: string;
- packageValues: string[];
- dryRun: boolean;
- strict: boolean;
- noGlobal: boolean;
+ root: string;
+ configName: string;
+ packageValues: string[];
+ dryRun: boolean;
+ strict: boolean;
+ noGlobal: boolean;
}
export const generateWorkspaceCommand = Command.make(
- 'generate:workspace',
- {
- root: Options.directory('root').pipe(
- Options.withAlias('r'),
- Options.withDescription('Workspace root directory'),
- Options.withDefault(path.resolve('./packages')),
- ),
- configName: Options.text('config-name').pipe(
- Options.withDescription('Config filename to search for in each project'),
- Options.withDefault('readie.json'),
- ),
- packageValues: Options.text('package').pipe(
- Options.withAlias('p'),
- Options.withDescription('Project name filter (repeatable, comma-separated supported)'),
- Options.repeated,
- ),
- dryRun: Options.boolean('dry-run').pipe(Options.withDescription('Show changes without writing files')),
- strict: Options.boolean('strict').pipe(Options.withDescription('Exit with code 1 if any project fails')),
- noGlobal: Options.boolean('no-global').pipe(
- Options.withDescription('Disable readie.global.json discovery and merge'),
- ),
- },
- ({ root, configName, packageValues, dryRun, strict, noGlobal }: GenerateWorkspaceCommandArgs) =>
- Effect.gen(function* () {
- const result = yield* Effect.tryPromise({
- try: () =>
- generateWorkspaceReadmes({
- rootDir: root,
- configName,
- packageFilter: parsePackageList(packageValues),
- dryRun,
- useGlobalConfig: !noGlobal,
- }),
- catch: (error: unknown) =>
- error instanceof Error ? error : new Error(`Workspace generation failed: ${String(error)}`),
- });
+ "generate:workspace",
+ {
+ configName: Options.text("config-name").pipe(
+ Options.withDescription("Config filename to search for in each project"),
+ Options.withDefault("readie.json")
+ ),
+ dryRun: Options.boolean("dry-run").pipe(
+ Options.withDescription("Show changes without writing files")
+ ),
+ noGlobal: Options.boolean("no-global").pipe(
+ Options.withDescription("Disable readie.global.json discovery and merge")
+ ),
+ packageValues: Options.text("package").pipe(
+ Options.withAlias("p"),
+ Options.withDescription(
+ "Project name filter (repeatable, comma-separated supported)"
+ ),
+ Options.repeated
+ ),
+ root: Options.directory("root").pipe(
+ Options.withAlias("r"),
+ Options.withDescription("Workspace root directory"),
+ Options.withDefault("./packages")
+ ),
+ strict: Options.boolean("strict").pipe(
+ Options.withDescription("Exit with code 1 if any project fails")
+ ),
+ },
+ ({
+ root,
+ configName,
+ packageValues,
+ dryRun,
+ strict,
+ noGlobal,
+ }: GenerateWorkspaceCommandArgs) =>
+ Effect.gen(function* runGenerateWorkspaceCommand() {
+ const resolvedRoot = root ? resolve(root) : resolve("./packages");
+ const result = yield* Effect.tryPromise({
+ catch: (error: unknown) =>
+ error instanceof Error
+ ? error
+ : new Error(`Workspace generation failed: ${String(error)}`),
+ try: () =>
+ generateWorkspaceReadmes({
+ configName,
+ dryRun,
+ packageFilter: parsePackageList(packageValues),
+ rootDir: resolvedRoot,
+ useGlobalConfig: !noGlobal,
+ }),
+ });
- yield* Effect.sync(() => {
- console.log('');
- console.log('Summary');
- console.log(`- Updated: ${result.updated.length}`);
- console.log(`- Unchanged: ${result.unchanged.length}`);
- console.log(`- Failed: ${result.failed.length}`);
- if (result.skippedByFilter.length > 0) {
- console.log(`- Skipped by filter: ${result.skippedByFilter.length}`);
- }
- if (strict && result.failed.length > 0) {
- process.exitCode = 1;
- }
- });
- }),
+ yield* Effect.sync(() => {
+ console.log("");
+ console.log("Summary");
+ console.log(`- Updated: ${result.updated.length}`);
+ console.log(`- Unchanged: ${result.unchanged.length}`);
+ console.log(`- Failed: ${result.failed.length}`);
+ if (result.skippedByFilter.length > 0) {
+ console.log(`- Skipped by filter: ${result.skippedByFilter.length}`);
+ }
+ if (strict && result.failed.length > 0) {
+ process.exitCode = 1;
+ }
+ });
+ })
).pipe(
- Command.withDescription('Generate READMEs for projects inside a workspace root.'),
+ Command.withDescription(
+ "Generate READMEs for projects inside a workspace root."
+ )
);
diff --git a/src/cli/commands/generate.ts b/src/cli/commands/generate.ts
index ec79ecd..8a61db2 100644
--- a/src/cli/commands/generate.ts
+++ b/src/cli/commands/generate.ts
@@ -1,51 +1,65 @@
-import { Command, Options } from '@effect/cli';
-import { Effect } from 'effect';
-import { generateReadmeFromConfig } from '../../readme-generator/generator.js';
+import { Command, Options } from "@effect/cli";
+import { Effect } from "effect";
+
+import { generateReadmeFromConfig } from "#src/readme-generator/generator";
interface GenerateCommandArgs {
- config: string;
- output: string;
- dryRun: boolean;
- noGlobal: boolean;
+ config: string;
+ output: string;
+ dryRun: boolean;
+ noGlobal: boolean;
}
+const resultStatus = (updated: boolean, dryRun: boolean) => {
+ if (!updated) {
+ return "No changes";
+ }
+ return dryRun ? "Would update" : "Generated";
+};
+
export const generateCommand = Command.make(
- 'generate',
- {
- config: Options.text('config').pipe(
- Options.withAlias('c'),
- Options.withDescription('Path to readie config file'),
- Options.withDefault('./readie.json'),
- ),
- output: Options.text('output').pipe(
- Options.withAlias('o'),
- Options.withDescription('Optional output path for README'),
- Options.withDefault(''),
- ),
- dryRun: Options.boolean('dry-run').pipe(Options.withDescription('Show changes without writing files')),
- noGlobal: Options.boolean('no-global').pipe(
- Options.withDescription('Disable readie.global.json discovery and merge'),
- ),
- },
- ({ config, output, dryRun, noGlobal }: GenerateCommandArgs) =>
- Effect.gen(function* () {
- const result = yield* Effect.tryPromise({
- try: () =>
- generateReadmeFromConfig({
- configPath: config,
- outputPath: output.trim().length > 0 ? output : undefined,
- dryRun,
- useGlobalConfig: !noGlobal,
- }),
- catch: (error: unknown) =>
- error instanceof Error ? error : new Error(`Generation failed: ${String(error)}`),
- });
+ "generate",
+ {
+ config: Options.text("config").pipe(
+ Options.withAlias("c"),
+ Options.withDescription("Path to readie config file"),
+ Options.withDefault("./readie.json")
+ ),
+ dryRun: Options.boolean("dry-run").pipe(
+ Options.withDescription("Show changes without writing files")
+ ),
+ noGlobal: Options.boolean("no-global").pipe(
+ Options.withDescription("Disable readie.global.json discovery and merge")
+ ),
+ output: Options.text("output").pipe(
+ Options.withAlias("o"),
+ Options.withDescription("Optional output path for README"),
+ Options.withDefault("")
+ ),
+ },
+ ({ config, output, dryRun, noGlobal }: GenerateCommandArgs) =>
+ Effect.gen(function* runGenerateCommand() {
+ const result = yield* Effect.tryPromise({
+ catch: (error: unknown) =>
+ error instanceof Error
+ ? error
+ : new Error(`Generation failed: ${String(error)}`),
+ try: () =>
+ generateReadmeFromConfig({
+ configPath: config,
+ dryRun,
+ outputPath: output.trim().length > 0 ? output : undefined,
+ useGlobalConfig: !noGlobal,
+ }),
+ });
- yield* Effect.sync(() => {
- const status = result.updated ? (dryRun ? 'Would update' : 'Generated') : 'No changes';
- console.log(`${status}: ${result.outputPath}`);
- });
- }),
+ yield* Effect.sync(() => {
+ const status = resultStatus(result.updated, dryRun);
+ console.log(`${status}: ${result.outputPath}`);
+ });
+ })
).pipe(
- Command.withDescription('Generate a README from a single readie.json config file.'),
+ Command.withDescription(
+ "Generate a README from a single readie.json config file."
+ )
);
diff --git a/src/cli/commands/init.ts b/src/cli/commands/init.ts
index 3eef85d..a851be7 100644
--- a/src/cli/commands/init.ts
+++ b/src/cli/commands/init.ts
@@ -1,47 +1,55 @@
-import * as fssync from 'node:fs';
-import fs from 'node:fs/promises';
-import path from 'node:path';
-import { Command, Options } from '@effect/cli';
-import { Effect } from 'effect';
-import { starterConfigText } from '../../config/starter-config.js';
+import { Command, Options } from "@effect/cli";
+import { Effect } from "effect";
+import { existsSync, writeFile } from "fs-extra";
+import { resolve } from "pathe";
+
+import { starterConfigText } from "#src/config/starter-config";
interface InitCommandArgs {
- config: string;
- force: boolean;
+ config: string;
+ force: boolean;
}
export const initCommand = Command.make(
- 'init',
- {
- config: Options.text('config').pipe(
- Options.withAlias('c'),
- Options.withDescription('Path for generated starter config'),
- Options.withDefault('./readie.json'),
- ),
- force: Options.boolean('force').pipe(
- Options.withAlias('f'),
- Options.withDescription('Overwrite existing config file if it exists'),
- ),
- },
- ({ config, force }: InitCommandArgs) =>
- Effect.gen(function* () {
- const configPath = path.resolve(config);
- const exists = fssync.existsSync(configPath);
+ "init",
+ {
+ config: Options.text("config").pipe(
+ Options.withAlias("c"),
+ Options.withDescription("Path for generated starter config"),
+ Options.withDefault("./readie.json")
+ ),
+ force: Options.boolean("force").pipe(
+ Options.withAlias("f"),
+ Options.withDescription("Overwrite existing config file if it exists")
+ ),
+ },
+ ({ config, force }: InitCommandArgs) =>
+ Effect.gen(function* runInitCommand() {
+ const configPath = resolve(config);
+ const exists = existsSync(configPath);
- if (exists && !force) {
- yield* Effect.fail(new Error(`Config already exists at ${configPath}. Use --force to overwrite.`));
- }
+ if (exists && !force) {
+ yield* Effect.fail(
+ new Error(
+ `Config already exists at ${configPath}. Use --force to overwrite.`
+ )
+ );
+ }
- yield* Effect.tryPromise({
- try: () => fs.writeFile(configPath, starterConfigText, 'utf8'),
- catch: (error: unknown) =>
- error instanceof Error ? error : new Error(`Failed to write config: ${String(error)}`),
- });
+ yield* Effect.tryPromise({
+ catch: (error: unknown) =>
+ error instanceof Error
+ ? error
+ : new Error(`Failed to write config: ${String(error)}`),
+ try: () => writeFile(configPath, starterConfigText, "utf8"),
+ });
- yield* Effect.sync(() => {
- console.log(`Created starter config: ${configPath}`);
- });
- }),
+ yield* Effect.sync(() => {
+ console.log(`Created starter config: ${configPath}`);
+ });
+ })
).pipe(
- Command.withDescription('Create a starter readie.json file in the current directory.'),
+ Command.withDescription(
+ "Create a starter readie.json file in the current directory."
+ )
);
diff --git a/src/cli/help.ts b/src/cli/help.ts
index 76d55eb..6460134 100644
--- a/src/cli/help.ts
+++ b/src/cli/help.ts
@@ -1,5 +1,5 @@
export const printRootHelp = () => {
- console.log(`readie
+ console.log(`readie
Generate high-quality README files from readie.json.
diff --git a/src/cli/resolve-invocation.ts b/src/cli/resolve-invocation.ts
index 9b7aa92..19dfdcf 100644
--- a/src/cli/resolve-invocation.ts
+++ b/src/cli/resolve-invocation.ts
@@ -1,67 +1,43 @@
-export type InvocationMode = 'generate' | 'generate:workspace' | 'init' | 'help' | 'unknown';
+export type InvocationMode =
+ | "generate"
+ | "generate:workspace"
+ | "init"
+ | "help"
+ | "unknown";
export interface ResolvedInvocation {
- mode: InvocationMode;
- commandArgs: string[];
- originalArgs: string[];
+ mode: InvocationMode;
+ commandArgs: string[];
+ originalArgs: string[];
}
-const isHelpFlag = (value: string | undefined) => value === '--help' || value === '-h';
+const isHelpFlag = (value: string | undefined) =>
+ value === "--help" || value === "-h";
-export const resolveInvocation = (args: string[]): ResolvedInvocation => {
- const [first, ...rest] = args;
-
- if (!first) {
- return {
- mode: 'generate',
- commandArgs: [],
- originalArgs: args,
- };
- }
-
- if (isHelpFlag(first)) {
- return {
- mode: 'help',
- commandArgs: rest,
- originalArgs: args,
- };
- }
-
- if (first === 'generate') {
- return {
- mode: 'generate',
- commandArgs: rest,
- originalArgs: args,
- };
- }
+const modeFromToken = (token: string | undefined): InvocationMode => {
+ if (!token) {
+ return "generate";
+ }
+ if (isHelpFlag(token) || token === "help") {
+ return "help";
+ }
- if (first === 'generate:workspace') {
- return {
- mode: 'generate:workspace',
- commandArgs: rest,
- originalArgs: args,
- };
- }
+ const commandModes: Record = {
+ generate: "generate",
+ "generate:workspace": "generate:workspace",
+ init: "init",
+ };
- if (first === 'init') {
- return {
- mode: 'init',
- commandArgs: rest,
- originalArgs: args,
- };
- }
-
- if (first === 'help') {
- return {
- mode: 'help',
- commandArgs: rest,
- originalArgs: args,
- };
- }
+ return commandModes[token] ?? "unknown";
+};
- return {
- mode: 'unknown',
- commandArgs: args,
- originalArgs: args,
- };
+export const resolveInvocation = (args: string[]): ResolvedInvocation => {
+ const [first, ...rest] = args;
+ const mode = modeFromToken(first);
+
+ return {
+ commandArgs: mode === "unknown" ? args : rest,
+ mode,
+ originalArgs: args,
+ };
};
diff --git a/src/config/load-config.ts b/src/config/load-config.ts
index 73e0445..62a148a 100644
--- a/src/config/load-config.ts
+++ b/src/config/load-config.ts
@@ -1,268 +1,359 @@
-import fs from 'node:fs/promises';
-import path from 'node:path';
-import { Schema } from 'effect';
-import type { ReadieConfig, ReadieGlobalConfig } from './types.js';
+import { Schema } from "effect";
+import { pathExists, readFile } from "fs-extra";
+import { dirname, join, resolve } from "pathe";
+
+import type { ReadieConfig, ReadieGlobalConfig } from "./types";
const commandSchema = Schema.Struct({
- name: Schema.NonEmptyString,
- description: Schema.NonEmptyString,
+ description: Schema.NonEmptyString,
+ name: Schema.NonEmptyString,
});
const globalFlagSchema = Schema.Struct({
- flag: Schema.NonEmptyString,
- description: Schema.NonEmptyString,
+ description: Schema.NonEmptyString,
+ flag: Schema.NonEmptyString,
});
const badgeSchema = Schema.Struct({
- label: Schema.NonEmptyString,
- image: Schema.NonEmptyString,
- link: Schema.optional(Schema.NonEmptyString),
+ image: Schema.NonEmptyString,
+ label: Schema.NonEmptyString,
+ link: Schema.optional(Schema.NonEmptyString),
});
const licenseSchema = Schema.Union(
- Schema.NonEmptyString,
- Schema.Struct({
- name: Schema.NonEmptyString,
- url: Schema.NonEmptyString,
- }),
+ Schema.NonEmptyString,
+ Schema.Struct({
+ name: Schema.NonEmptyString,
+ url: Schema.NonEmptyString,
+ })
);
const readieConfigSchema = Schema.Struct({
- $schema: Schema.optional(Schema.String),
- version: Schema.optional(Schema.Literal('1')),
- title: Schema.NonEmptyString,
- description: Schema.NonEmptyString,
- output: Schema.optional(Schema.String),
- includeTableOfContents: Schema.optional(Schema.Boolean),
- features: Schema.optional(Schema.Array(Schema.String)),
- prerequisites: Schema.optional(Schema.Array(Schema.String)),
- installation: Schema.optional(Schema.Array(Schema.String)),
- manualInstallation: Schema.optional(Schema.Array(Schema.String)),
- usage: Schema.optional(Schema.Array(Schema.String)),
- commands: Schema.optional(Schema.Array(commandSchema)),
- globalFlags: Schema.optional(Schema.Array(globalFlagSchema)),
- badges: Schema.optional(Schema.Array(badgeSchema)),
- banner: Schema.optional(Schema.String),
- quickStart: Schema.optional(Schema.String),
- support: Schema.optional(Schema.Array(Schema.String)),
- contributing: Schema.optional(Schema.Array(Schema.String)),
- security: Schema.optional(Schema.String),
- license: Schema.optional(licenseSchema),
- footer: Schema.optional(Schema.String),
- docsLink: Schema.optional(Schema.String),
- quickStartLink: Schema.optional(Schema.String),
- customSections: Schema.optional(
- Schema.Record({
- key: Schema.String,
- value: Schema.String,
- }),
- ),
+ $schema: Schema.optional(Schema.String),
+ badges: Schema.optional(Schema.Array(badgeSchema)),
+ banner: Schema.optional(Schema.String),
+ commands: Schema.optional(Schema.Array(commandSchema)),
+ contributing: Schema.optional(Schema.Array(Schema.String)),
+ customSections: Schema.optional(
+ Schema.Record({
+ key: Schema.String,
+ value: Schema.String,
+ })
+ ),
+ description: Schema.NonEmptyString,
+ docsLink: Schema.optional(Schema.String),
+ features: Schema.optional(Schema.Array(Schema.String)),
+ footer: Schema.optional(Schema.String),
+ globalFlags: Schema.optional(Schema.Array(globalFlagSchema)),
+ includeTableOfContents: Schema.optional(Schema.Boolean),
+ installation: Schema.optional(Schema.Array(Schema.String)),
+ license: Schema.optional(licenseSchema),
+ manualInstallation: Schema.optional(Schema.Array(Schema.String)),
+ output: Schema.optional(Schema.String),
+ prerequisites: Schema.optional(Schema.Array(Schema.String)),
+ quickStart: Schema.optional(Schema.String),
+ quickStartLink: Schema.optional(Schema.String),
+ security: Schema.optional(Schema.String),
+ support: Schema.optional(Schema.Array(Schema.String)),
+ title: Schema.NonEmptyString,
+ usage: Schema.optional(Schema.Array(Schema.String)),
+ version: Schema.optional(Schema.Literal("1")),
});
const readieGlobalConfigSchema = Schema.Struct({
- $schema: Schema.optional(Schema.String),
- version: Schema.optional(Schema.Literal('1')),
- title: Schema.optional(Schema.NonEmptyString),
- description: Schema.optional(Schema.NonEmptyString),
- output: Schema.optional(Schema.String),
- includeTableOfContents: Schema.optional(Schema.Boolean),
- features: Schema.optional(Schema.Array(Schema.String)),
- prerequisites: Schema.optional(Schema.Array(Schema.String)),
- installation: Schema.optional(Schema.Array(Schema.String)),
- manualInstallation: Schema.optional(Schema.Array(Schema.String)),
- usage: Schema.optional(Schema.Array(Schema.String)),
- commands: Schema.optional(Schema.Array(commandSchema)),
- globalFlags: Schema.optional(Schema.Array(globalFlagSchema)),
- badges: Schema.optional(Schema.Array(badgeSchema)),
- banner: Schema.optional(Schema.String),
- quickStart: Schema.optional(Schema.String),
- support: Schema.optional(Schema.Array(Schema.String)),
- contributing: Schema.optional(Schema.Array(Schema.String)),
- security: Schema.optional(Schema.String),
- license: Schema.optional(licenseSchema),
- footer: Schema.optional(Schema.String),
- docsLink: Schema.optional(Schema.String),
- quickStartLink: Schema.optional(Schema.String),
- customSections: Schema.optional(
- Schema.Record({
- key: Schema.String,
- value: Schema.String,
- }),
- ),
+ $schema: Schema.optional(Schema.String),
+ badges: Schema.optional(Schema.Array(badgeSchema)),
+ banner: Schema.optional(Schema.String),
+ commands: Schema.optional(Schema.Array(commandSchema)),
+ contributing: Schema.optional(Schema.Array(Schema.String)),
+ customSections: Schema.optional(
+ Schema.Record({
+ key: Schema.String,
+ value: Schema.String,
+ })
+ ),
+ description: Schema.optional(Schema.NonEmptyString),
+ docsLink: Schema.optional(Schema.String),
+ features: Schema.optional(Schema.Array(Schema.String)),
+ footer: Schema.optional(Schema.String),
+ globalFlags: Schema.optional(Schema.Array(globalFlagSchema)),
+ includeTableOfContents: Schema.optional(Schema.Boolean),
+ installation: Schema.optional(Schema.Array(Schema.String)),
+ license: Schema.optional(licenseSchema),
+ manualInstallation: Schema.optional(Schema.Array(Schema.String)),
+ output: Schema.optional(Schema.String),
+ prerequisites: Schema.optional(Schema.Array(Schema.String)),
+ quickStart: Schema.optional(Schema.String),
+ quickStartLink: Schema.optional(Schema.String),
+ security: Schema.optional(Schema.String),
+ support: Schema.optional(Schema.Array(Schema.String)),
+ title: Schema.optional(Schema.NonEmptyString),
+ usage: Schema.optional(Schema.Array(Schema.String)),
+ version: Schema.optional(Schema.Literal("1")),
});
const decodeReadieConfig = Schema.decodeUnknownSync(readieConfigSchema);
-const decodeReadieGlobalConfig = Schema.decodeUnknownSync(readieGlobalConfigSchema);
+const decodeReadieGlobalConfig = Schema.decodeUnknownSync(
+ readieGlobalConfigSchema
+);
-const GLOBAL_CONFIG_NAME = 'readie.global.json';
+const GLOBAL_CONFIG_NAME = "readie.global.json";
const parseJsonFile = async (absolutePath: string): Promise => {
- const raw = await fs.readFile(absolutePath, 'utf8');
- try {
- return JSON.parse(raw);
- } catch (error) {
- const message = error instanceof Error ? error.message : String(error);
- throw new Error(`Failed to parse JSON in ${absolutePath}: ${message}`);
- }
+ const raw = await readFile(absolutePath, "utf8");
+ try {
+ return JSON.parse(raw);
+ } catch (error) {
+ const message = error instanceof Error ? error.message : String(error);
+ throw new Error(`Failed to parse JSON in ${absolutePath}: ${message}`, {
+ cause: error,
+ });
+ }
};
-export const loadReadieConfig = async (configPath: string): Promise => {
- const absolutePath = path.resolve(configPath);
- const parsed = await parseJsonFile(absolutePath);
-
- try {
- return decodeReadieConfig(parsed) as ReadieConfig;
- } catch (error) {
- const message = error instanceof Error ? error.message : String(error);
- throw new Error(`Configuration validation failed for ${absolutePath}\n${message}`);
- }
+export const loadReadieConfig = async (
+ configPath: string
+): Promise => {
+ const absolutePath = resolve(configPath);
+ const parsed = await parseJsonFile(absolutePath);
+
+ try {
+ return decodeReadieConfig(parsed) as ReadieConfig;
+ } catch (error) {
+ const message = error instanceof Error ? error.message : String(error);
+ throw new Error(
+ `Configuration validation failed for ${absolutePath}\n${message}`,
+ { cause: error }
+ );
+ }
};
-const loadGlobalReadieConfig = async (configPath: string): Promise => {
- const absolutePath = path.resolve(configPath);
- const parsed = await parseJsonFile(absolutePath);
-
- try {
- return decodeReadieGlobalConfig(parsed) as ReadieGlobalConfig;
- } catch (error) {
- const message = error instanceof Error ? error.message : String(error);
- throw new Error(`Global configuration validation failed for ${absolutePath}\n${message}`);
- }
+const loadGlobalReadieConfig = async (
+ configPath: string
+): Promise => {
+ const absolutePath = resolve(configPath);
+ const parsed = await parseJsonFile(absolutePath);
+
+ try {
+ return decodeReadieGlobalConfig(parsed) as ReadieGlobalConfig;
+ } catch (error) {
+ const message = error instanceof Error ? error.message : String(error);
+ throw new Error(
+ `Global configuration validation failed for ${absolutePath}\n${message}`,
+ { cause: error }
+ );
+ }
};
-export const loadGlobalConfig = async (startDir: string): Promise => {
- let current = path.resolve(startDir);
-
- while (true) {
- const candidate = path.join(current, GLOBAL_CONFIG_NAME);
- try {
- await fs.access(candidate);
- return await loadGlobalReadieConfig(candidate);
- } catch {
- // Keep walking up until filesystem root.
- }
-
- const parent = path.dirname(current);
- if (parent === current) {
- return null;
- }
- current = parent;
- }
+export const loadGlobalConfig = async (
+ startDir: string
+): Promise => {
+ let current = resolve(startDir);
+
+ while (true) {
+ const candidate = join(current, GLOBAL_CONFIG_NAME);
+ if (await pathExists(candidate)) {
+ return await loadGlobalReadieConfig(candidate);
+ }
+
+ const parent = dirname(current);
+ if (parent === current) {
+ return null;
+ }
+ current = parent;
+ }
};
-const hasOwn = (obj: object, key: string): boolean => Object.prototype.hasOwnProperty.call(obj, key);
+const hasOwn = (obj: object, key: string): boolean => Object.hasOwn(obj, key);
interface InterpolationContext {
- packageName?: string;
+ packageName?: string;
}
-const interpolatePlaceholders = (value: string, placeholders: Record): string =>
- value.replace(/\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/g, (match, key: string) => placeholders[key] ?? match);
+const interpolatePlaceholders = (
+ value: string,
+ placeholders: Record
+): string =>
+ value.replaceAll(
+ /\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/g,
+ (match, key: string) => placeholders[key] ?? match
+ );
+
+const createInterpolationPlaceholders = (
+ config: ReadieConfig,
+ interpolationContext: InterpolationContext
+): Record => {
+ const resolvedPackageName =
+ interpolationContext.packageName?.trim() || config.title;
+
+ return {
+ packageName: resolvedPackageName,
+ packageNameEncoded: encodeURIComponent(resolvedPackageName),
+ title: config.title,
+ };
+};
+
+const interpolateCustomSections = (
+ customSections: Record | undefined,
+ placeholders: Record
+): Record | undefined => {
+ if (!customSections) {
+ return;
+ }
+
+ const interpolatedSections: Record = {};
+ for (const [key, value] of Object.entries(customSections)) {
+ interpolatedSections[key] = interpolatePlaceholders(value, placeholders);
+ }
+ return interpolatedSections;
+};
const interpolateTopLevelStrings = (
- config: ReadieConfig,
- interpolationContext: InterpolationContext,
+ config: ReadieConfig,
+ interpolationContext: InterpolationContext
): ReadieConfig => {
- const resolvedPackageName = interpolationContext.packageName?.trim() || config.title;
- const placeholders: Record = {
- title: config.title,
- packageName: resolvedPackageName,
- packageNameEncoded: encodeURIComponent(resolvedPackageName),
- };
- const interpolated = { ...config } as Record;
-
- for (const [key, value] of Object.entries(interpolated)) {
- if (typeof value === 'string') {
- interpolated[key] = interpolatePlaceholders(value, placeholders);
- }
- }
-
- // Interpolate customSections values
- if (config.customSections) {
- const interpolatedSections: Record = {};
- for (const [key, value] of Object.entries(config.customSections)) {
- interpolatedSections[key] = interpolatePlaceholders(value, placeholders);
- }
- interpolated.customSections = interpolatedSections;
- }
-
-
- return interpolated as unknown as ReadieConfig;
+ const placeholders = createInterpolationPlaceholders(
+ config,
+ interpolationContext
+ );
+ const interpolated = { ...config } as Record;
+
+ for (const [key, value] of Object.entries(interpolated)) {
+ if (typeof value === "string") {
+ interpolated[key] = interpolatePlaceholders(value, placeholders);
+ }
+ }
+
+ interpolated.customSections = interpolateCustomSections(
+ config.customSections,
+ placeholders
+ );
+
+ return interpolated as unknown as ReadieConfig;
};
const resolveMergedValue = (
- key: keyof ReadieConfig,
- projectConfig: ReadieConfig,
- globalConfig: ReadieGlobalConfig | null,
+ key: keyof ReadieConfig,
+ projectConfig: ReadieConfig,
+ globalConfig: ReadieGlobalConfig | null
): T | undefined => {
- const project = projectConfig as unknown as Record;
- const global = (globalConfig ?? {}) as Record;
+ const project = projectConfig as unknown as Record;
+ const global = (globalConfig ?? {}) as Record;
- if (hasOwn(project, key)) {
- const projectValue = project[key];
- return projectValue === null ? undefined : (projectValue as T);
- }
+ if (hasOwn(project, key)) {
+ const projectValue = project[key];
+ return projectValue === null ? undefined : (projectValue as T);
+ }
+
+ const globalValue = global[key];
+ return globalValue === null ? undefined : (globalValue as T | undefined);
+};
+
+const readGlobalCustomSections = (global: Record) => {
+ if (typeof global.customSections !== "object" || !global.customSections) {
+ return;
+ }
+ return global.customSections as Record;
+};
+
+const readProjectCustomSections = (project: Record) => {
+ const projectCustomSections = project.customSections;
+ if (projectCustomSections === null) {
+ return null;
+ }
+ if (typeof projectCustomSections !== "object" || !projectCustomSections) {
+ return;
+ }
+ return projectCustomSections as Record;
+};
- const globalValue = global[key];
- return globalValue === null ? undefined : (globalValue as T | undefined);
+const resolveMergedCustomSections = (
+ globalConfig: ReadieGlobalConfig | null,
+ projectConfig: ReadieConfig
+) => {
+ const project = projectConfig as unknown as Record;
+ const global = (globalConfig ?? {}) as Record;
+ const globalCustomSections = readGlobalCustomSections(global);
+
+ if (!hasOwn(project, "customSections")) {
+ return globalCustomSections;
+ }
+
+ const projectCustomSections = readProjectCustomSections(project);
+ if (projectCustomSections === null || projectCustomSections === undefined) {
+ return;
+ }
+
+ return globalCustomSections
+ ? {
+ ...globalCustomSections,
+ ...projectCustomSections,
+ }
+ : {
+ ...projectCustomSections,
+ };
};
export const mergeConfigs = (
- globalConfig: ReadieGlobalConfig | null,
- projectConfig: ReadieConfig,
- interpolationContext: InterpolationContext = {},
+ globalConfig: ReadieGlobalConfig | null,
+ projectConfig: ReadieConfig,
+ interpolationContext: InterpolationContext = {}
): ReadieConfig => {
- const mergedCustomSections = (() => {
- const project = projectConfig as unknown as Record;
- const global = (globalConfig ?? {}) as Record;
-
- if (hasOwn(project, 'customSections')) {
- const projectCustomSections = project.customSections;
- if (projectCustomSections === null) {
- return undefined;
- }
- if (typeof projectCustomSections === 'object' && projectCustomSections !== null) {
- return {
- ...((global.customSections as Record | undefined) ?? {}),
- ...(projectCustomSections as Record),
- };
- }
- return undefined;
- }
-
- if (typeof global.customSections === 'object' && global.customSections !== null) {
- return global.customSections as Record;
- }
-
- return undefined;
- })();
-
- const merged: ReadieConfig = {
- title: projectConfig.title,
- description: projectConfig.description,
- $schema: resolveMergedValue('$schema', projectConfig, globalConfig),
- version: resolveMergedValue('version', projectConfig, globalConfig),
- output: resolveMergedValue('output', projectConfig, globalConfig),
- includeTableOfContents: resolveMergedValue('includeTableOfContents', projectConfig, globalConfig),
- features: resolveMergedValue('features', projectConfig, globalConfig),
- prerequisites: resolveMergedValue('prerequisites', projectConfig, globalConfig),
- installation: resolveMergedValue('installation', projectConfig, globalConfig),
- manualInstallation: resolveMergedValue('manualInstallation', projectConfig, globalConfig),
- usage: resolveMergedValue('usage', projectConfig, globalConfig),
- commands: resolveMergedValue('commands', projectConfig, globalConfig),
- globalFlags: resolveMergedValue('globalFlags', projectConfig, globalConfig),
- badges: resolveMergedValue('badges', projectConfig, globalConfig),
- banner: resolveMergedValue('banner', projectConfig, globalConfig),
- quickStart: resolveMergedValue('quickStart', projectConfig, globalConfig),
- support: resolveMergedValue('support', projectConfig, globalConfig),
- contributing: resolveMergedValue('contributing', projectConfig, globalConfig),
- security: resolveMergedValue('security', projectConfig, globalConfig),
- license: resolveMergedValue('license', projectConfig, globalConfig),
- footer: resolveMergedValue('footer', projectConfig, globalConfig),
- docsLink: resolveMergedValue('docsLink', projectConfig, globalConfig),
- quickStartLink: resolveMergedValue('quickStartLink', projectConfig, globalConfig),
- customSections: mergedCustomSections,
- };
-
- return interpolateTopLevelStrings(merged, interpolationContext);
+ const mergedCustomSections = resolveMergedCustomSections(
+ globalConfig,
+ projectConfig
+ );
+
+ const merged: ReadieConfig = {
+ $schema: resolveMergedValue("$schema", projectConfig, globalConfig),
+ badges: resolveMergedValue("badges", projectConfig, globalConfig),
+ banner: resolveMergedValue("banner", projectConfig, globalConfig),
+ commands: resolveMergedValue("commands", projectConfig, globalConfig),
+ contributing: resolveMergedValue(
+ "contributing",
+ projectConfig,
+ globalConfig
+ ),
+ customSections: mergedCustomSections,
+ description: projectConfig.description,
+ docsLink: resolveMergedValue("docsLink", projectConfig, globalConfig),
+ features: resolveMergedValue("features", projectConfig, globalConfig),
+ footer: resolveMergedValue("footer", projectConfig, globalConfig),
+ globalFlags: resolveMergedValue("globalFlags", projectConfig, globalConfig),
+ includeTableOfContents: resolveMergedValue(
+ "includeTableOfContents",
+ projectConfig,
+ globalConfig
+ ),
+ installation: resolveMergedValue(
+ "installation",
+ projectConfig,
+ globalConfig
+ ),
+ license: resolveMergedValue("license", projectConfig, globalConfig),
+ manualInstallation: resolveMergedValue(
+ "manualInstallation",
+ projectConfig,
+ globalConfig
+ ),
+ output: resolveMergedValue("output", projectConfig, globalConfig),
+ prerequisites: resolveMergedValue(
+ "prerequisites",
+ projectConfig,
+ globalConfig
+ ),
+ quickStart: resolveMergedValue("quickStart", projectConfig, globalConfig),
+ quickStartLink: resolveMergedValue(
+ "quickStartLink",
+ projectConfig,
+ globalConfig
+ ),
+ security: resolveMergedValue("security", projectConfig, globalConfig),
+ support: resolveMergedValue("support", projectConfig, globalConfig),
+ title: projectConfig.title,
+ usage: resolveMergedValue("usage", projectConfig, globalConfig),
+ version: resolveMergedValue("version", projectConfig, globalConfig),
+ };
+
+ return interpolateTopLevelStrings(merged, interpolationContext);
};
diff --git a/src/config/starter-config.ts b/src/config/starter-config.ts
index adde44b..01d2b91 100644
--- a/src/config/starter-config.ts
+++ b/src/config/starter-config.ts
@@ -1,17 +1,18 @@
-import type { ReadieConfig } from './types.js';
+import type { ReadieConfig } from "./types";
-export const DEFAULT_SCHEMA_URL = 'https://unpkg.com/readie/schemas/readie.schema.json';
+export const DEFAULT_SCHEMA_URL =
+ "https://unpkg.com/readie/schemas/readie.schema.json";
export const starterConfig: ReadieConfig = {
- $schema: DEFAULT_SCHEMA_URL,
- version: '1',
- title: 'My Project',
- description: 'A short description of what this project does.',
- includeTableOfContents: true,
- features: ['Fast setup', 'Clear docs', 'Simple CLI usage'],
- installation: ['```bash\nnpm install my-project\n```'],
- usage: ['Explain basic usage in a few steps.', '```bash\nnpm run start\n```'],
- docsLink: 'https://example.com/docs',
+ $schema: DEFAULT_SCHEMA_URL,
+ description: "A short description of what this project does.",
+ docsLink: "https://example.com/docs",
+ features: ["Fast setup", "Clear docs", "Simple CLI usage"],
+ includeTableOfContents: true,
+ installation: ["```bash\nnpm install my-project\n```"],
+ title: "My Project",
+ usage: ["Explain basic usage in a few steps.", "```bash\nnpm run start\n```"],
+ version: "1",
};
export const starterConfigText = `${JSON.stringify(starterConfig, null, 2)}\n`;
diff --git a/src/config/types.ts b/src/config/types.ts
index 6e6493a..59741cd 100644
--- a/src/config/types.ts
+++ b/src/config/types.ts
@@ -1,78 +1,78 @@
export interface ReadieCommand {
- name: string;
- description: string;
+ name: string;
+ description: string;
}
export interface ReadieFlag {
- flag: string;
- description: string;
+ flag: string;
+ description: string;
}
export interface ReadieBadge {
- label: string;
- image: string;
- link?: string;
+ label: string;
+ image: string;
+ link?: string;
}
export interface ReadieLicenseObject {
- name: string;
- url: string;
+ name: string;
+ url: string;
}
export type ReadieLicense = string | ReadieLicenseObject;
export interface ReadieConfig {
- $schema?: string;
- version?: '1';
- title: string;
- description: string;
- output?: string;
- includeTableOfContents?: boolean;
- features?: string[];
- prerequisites?: string[];
- installation?: string[];
- manualInstallation?: string[];
- usage?: string[];
- commands?: ReadieCommand[];
- globalFlags?: ReadieFlag[];
- badges?: ReadieBadge[];
- banner?: string;
- quickStart?: string;
- support?: string[];
- contributing?: string[];
- security?: string;
- license?: ReadieLicense;
- footer?: string;
- docsLink?: string;
- quickStartLink?: string;
- customSections?: Record;
+ $schema?: string;
+ version?: "1";
+ title: string;
+ description: string;
+ output?: string;
+ includeTableOfContents?: boolean;
+ features?: string[];
+ prerequisites?: string[];
+ installation?: string[];
+ manualInstallation?: string[];
+ usage?: string[];
+ commands?: ReadieCommand[];
+ globalFlags?: ReadieFlag[];
+ badges?: ReadieBadge[];
+ banner?: string;
+ quickStart?: string;
+ support?: string[];
+ contributing?: string[];
+ security?: string;
+ license?: ReadieLicense;
+ footer?: string;
+ docsLink?: string;
+ quickStartLink?: string;
+ customSections?: Record;
}
export type ReadieGlobalConfig = Partial;
export interface GenerateSingleOptions {
- configPath: string;
- outputPath?: string;
- dryRun: boolean;
- useGlobalConfig?: boolean;
+ configPath: string;
+ outputPath?: string;
+ dryRun: boolean;
+ useGlobalConfig?: boolean;
}
export interface GenerateSingleResult {
- outputPath: string;
- updated: boolean;
+ outputPath: string;
+ updated: boolean;
}
export interface GenerateWorkspaceOptions {
- rootDir: string;
- configName: string;
- packageFilter: Set;
- dryRun: boolean;
- useGlobalConfig?: boolean;
+ rootDir: string;
+ configName: string;
+ packageFilter: Set;
+ dryRun: boolean;
+ useGlobalConfig?: boolean;
}
export interface GenerateWorkspaceResult {
- updated: string[];
- unchanged: string[];
- failed: Array<{ projectDir: string; error: unknown }>;
- skippedByFilter: string[];
+ updated: string[];
+ unchanged: string[];
+ failed: { projectDir: string; error: unknown }[];
+ skippedByFilter: string[];
}
diff --git a/src/index.ts b/src/index.ts
index 50b0b68..c9b2e24 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,69 +1,78 @@
#!/usr/bin/env node
-import { Command, ValidationError } from '@effect/cli';
-import * as NodeContext from '@effect/platform-node/NodeContext';
-import * as NodeRuntime from '@effect/platform-node/NodeRuntime';
-import { Effect } from 'effect';
-import { generateCommand } from './cli/commands/generate.js';
-import { generateWorkspaceCommand } from './cli/commands/generate-workspace.js';
-import { initCommand } from './cli/commands/init.js';
-import { printRootHelp } from './cli/help.js';
-import { resolveInvocation } from './cli/resolve-invocation.js';
+import { Command, ValidationError } from "@effect/cli";
+import * as NodeContext from "@effect/platform-node/NodeContext";
+import { Effect } from "effect";
-const version = '0.1.0';
+import { generateCommand } from "./cli/commands/generate";
+import { generateWorkspaceCommand } from "./cli/commands/generate-workspace";
+import { initCommand } from "./cli/commands/init";
+import { printRootHelp } from "./cli/help";
+import { resolveInvocation } from "./cli/resolve-invocation";
+
+const version = "0.0.1";
const runGenerate = (args: string[]) =>
- Command.run(generateCommand, {
- name: 'readie',
- version,
- })(args).pipe(Effect.provide(NodeContext.layer));
+ Command.run(generateCommand, {
+ name: "readie",
+ version,
+ })(args).pipe(Effect.provide(NodeContext.layer));
const runGenerateWorkspace = (args: string[]) =>
- Command.run(generateWorkspaceCommand, {
- name: 'readie',
- version,
- })(args).pipe(Effect.provide(NodeContext.layer));
+ Command.run(generateWorkspaceCommand, {
+ name: "readie",
+ version,
+ })(args).pipe(Effect.provide(NodeContext.layer));
const runInit = (args: string[]) =>
- Command.run(initCommand, {
- name: 'readie',
- version,
- })(args).pipe(Effect.provide(NodeContext.layer));
+ Command.run(initCommand, {
+ name: "readie",
+ version,
+ })(args).pipe(Effect.provide(NodeContext.layer));
+
+const selectCommandEffect = (
+ resolved: ReturnType
+) => {
+ if (resolved.mode === "generate") {
+ return runGenerate(resolved.commandArgs);
+ }
+ if (resolved.mode === "generate:workspace") {
+ return runGenerateWorkspace(resolved.commandArgs);
+ }
+ if (resolved.mode === "init") {
+ return runInit(resolved.commandArgs);
+ }
+ throw new Error(`Unsupported invocation mode: ${resolved.mode}`);
+};
-const resolved = resolveInvocation(process.argv.slice(2));
+const handleError = (error: unknown) => {
+ if (ValidationError.isValidationError(error)) {
+ console.error(String(error));
+ process.exitCode = 1;
+ return;
+ }
-if (resolved.mode === 'help') {
- printRootHelp();
- process.exit(0);
-}
+ console.error(error instanceof Error ? error.message : String(error));
+ process.exitCode = 1;
+};
-if (resolved.mode === 'unknown') {
- printRootHelp();
- process.exit(1);
-}
+const main = async () => {
+ const resolved = resolveInvocation(process.argv.slice(2));
-const commandEffect =
- resolved.mode === 'generate'
- ? runGenerate(resolved.commandArgs)
- : resolved.mode === 'generate:workspace'
- ? runGenerateWorkspace(resolved.commandArgs)
- : runInit(resolved.commandArgs);
+ if (resolved.mode === "help") {
+ printRootHelp();
+ process.exit(0);
+ }
+ if (resolved.mode === "unknown") {
+ printRootHelp();
+ process.exit(1);
+ }
-const program = commandEffect.pipe(
- Effect.catchIf(
- (error): error is ValidationError.ValidationError => ValidationError.isValidationError(error),
- (error) =>
- Effect.sync(() => {
- console.error(String(error));
- process.exitCode = 1;
- }),
- ),
- Effect.catchAll((error) =>
- Effect.sync(() => {
- console.error(error instanceof Error ? error.message : String(error));
- process.exitCode = 1;
- }),
- ),
-);
+ try {
+ await Effect.runPromise(selectCommandEffect(resolved));
+ } catch (error) {
+ handleError(error);
+ }
+};
-NodeRuntime.runMain(program);
+await main();
diff --git a/src/readme-generator/generator.ts b/src/readme-generator/generator.ts
index 2417f11..1634d00 100644
--- a/src/readme-generator/generator.ts
+++ b/src/readme-generator/generator.ts
@@ -1,148 +1,228 @@
-import * as fssync from 'node:fs';
-import fs from 'node:fs/promises';
-import path from 'node:path';
-import { loadGlobalConfig, loadReadieConfig, mergeConfigs } from '../config/load-config.js';
+import { pathExists, readdir, readFile, readJson, writeFile } from "fs-extra";
+import { basename, dirname, join, resolve } from "pathe";
+
+import {
+ loadGlobalConfig,
+ loadReadieConfig,
+ mergeConfigs,
+} from "#src/config/load-config";
import type {
- GenerateSingleOptions,
- GenerateSingleResult,
- GenerateWorkspaceOptions,
- GenerateWorkspaceResult,
-} from '../config/types.js';
-import { baseReadmeTemplate } from './template.js';
+ GenerateSingleOptions,
+ GenerateSingleResult,
+ GenerateWorkspaceOptions,
+ GenerateWorkspaceResult,
+} from "#src/config/types";
+
+import { baseReadmeTemplate } from "./template";
export const parsePackageList = (values: string[]): Set => {
- const packages = new Set();
- for (const value of values) {
- for (const part of value.split(',')) {
- const name = part.trim();
- if (name.length > 0) {
- packages.add(name);
- }
- }
- }
- return packages;
+ const packages = new Set();
+ for (const value of values) {
+ for (const part of value.split(",")) {
+ const name = part.trim();
+ if (name.length > 0) {
+ packages.add(name);
+ }
+ }
+ }
+ return packages;
};
const resolveOutputPath = (
- configPath: string,
- configOutputPath: string | undefined,
- cliOutputPath: string | undefined,
+ configPath: string,
+ configOutputPath: string | undefined,
+ cliOutputPath: string | undefined
+) => {
+ if (cliOutputPath) {
+ return resolve(cliOutputPath);
+ }
+ if (configOutputPath) {
+ return resolve(dirname(configPath), configOutputPath);
+ }
+ return resolve(dirname(configPath), "README.md");
+};
+
+const resolvePackageName = async (
+ configPath: string
+): Promise => {
+ const packageJsonPath = join(dirname(configPath), "package.json");
+
+ try {
+ const parsed = (await readJson(packageJsonPath)) as { name?: unknown };
+ return typeof parsed.name === "string" && parsed.name.trim().length > 0
+ ? parsed.name
+ : undefined;
+ } catch {
+ return undefined;
+ }
+};
+
+const loadMergedConfig = async (
+ absoluteConfigPath: string,
+ useGlobalConfig: boolean
) => {
- if (cliOutputPath) {
- return path.resolve(cliOutputPath);
- }
- if (configOutputPath) {
- return path.resolve(path.dirname(configPath), configOutputPath);
- }
- return path.resolve(path.dirname(configPath), 'README.md');
+ const projectConfig = await loadReadieConfig(absoluteConfigPath);
+ const globalConfig = useGlobalConfig
+ ? await loadGlobalConfig(dirname(absoluteConfigPath))
+ : null;
+ const packageName = await resolvePackageName(absoluteConfigPath);
+ return mergeConfigs(globalConfig, projectConfig, { packageName });
};
-const resolvePackageName = async (configPath: string): Promise => {
- const packageJsonPath = path.join(path.dirname(configPath), 'package.json');
-
- try {
- const rawPackageJson = await fs.readFile(packageJsonPath, 'utf8');
- const parsed = JSON.parse(rawPackageJson) as { name?: unknown };
- return typeof parsed.name === 'string' && parsed.name.trim().length > 0 ? parsed.name : undefined;
- } catch (error) {
- console.warn(`Package.json not found at ${packageJsonPath}:`, error);
- return undefined;
- }
+const readExistingContent = async (filePath: string) => {
+ if (!(await pathExists(filePath))) {
+ return null;
+ }
+ return readFile(filePath, "utf8");
};
export const generateReadmeFromConfig = async ({
- configPath,
- outputPath,
- dryRun,
- useGlobalConfig = true,
+ configPath,
+ outputPath,
+ dryRun,
+ useGlobalConfig = true,
}: GenerateSingleOptions): Promise => {
- const absoluteConfigPath = path.resolve(configPath);
- const projectConfig = await loadReadieConfig(absoluteConfigPath);
- const globalConfig = useGlobalConfig ? await loadGlobalConfig(path.dirname(absoluteConfigPath)) : null;
- const packageName = await resolvePackageName(absoluteConfigPath);
- const config = mergeConfigs(globalConfig, projectConfig, { packageName });
- const resolvedOutputPath = resolveOutputPath(absoluteConfigPath, config.output, outputPath);
-
- const content = baseReadmeTemplate(config);
- const existingContent = fssync.existsSync(resolvedOutputPath)
- ? await fs.readFile(resolvedOutputPath, 'utf8')
- : null;
-
- if (existingContent === content) {
- return {
- outputPath: resolvedOutputPath,
- updated: false,
- };
- }
-
- if (!dryRun) {
- await fs.writeFile(resolvedOutputPath, content, 'utf8');
- }
-
- return {
- outputPath: resolvedOutputPath,
- updated: true,
- };
+ const absoluteConfigPath = resolve(configPath);
+ const config = await loadMergedConfig(absoluteConfigPath, useGlobalConfig);
+ const resolvedOutputPath = resolveOutputPath(
+ absoluteConfigPath,
+ config.output,
+ outputPath
+ );
+
+ const content = baseReadmeTemplate(config);
+ const existingContent = await readExistingContent(resolvedOutputPath);
+
+ if (existingContent === content) {
+ return {
+ outputPath: resolvedOutputPath,
+ updated: false,
+ };
+ }
+
+ if (!dryRun) {
+ await writeFile(resolvedOutputPath, content, "utf8");
+ }
+
+ return {
+ outputPath: resolvedOutputPath,
+ updated: true,
+ };
};
-export async function generateWorkspaceReadmes({
- rootDir,
- configName,
- packageFilter,
- dryRun,
- useGlobalConfig = true,
-}: GenerateWorkspaceOptions): Promise {
- const absoluteRootDir = path.resolve(rootDir);
- if (!fssync.existsSync(absoluteRootDir)) {
- throw new Error(`Workspace root not found at ${absoluteRootDir}`);
- }
-
- const entries = await fs.readdir(absoluteRootDir, { withFileTypes: true });
- const allProjectDirs = entries
- .filter((entry) => entry.isDirectory())
- .map((entry) => path.join(absoluteRootDir, entry.name))
- .filter((dir) => fssync.existsSync(path.join(dir, configName)));
-
- const selectedProjectDirs =
- packageFilter.size > 0
- ? allProjectDirs.filter((dir) => packageFilter.has(path.basename(dir)))
- : allProjectDirs;
-
- const skippedByFilter =
- packageFilter.size > 0
- ? allProjectDirs
- .map((dir) => path.basename(dir))
- .filter((dirName) => !packageFilter.has(dirName))
- : [];
-
- const result: GenerateWorkspaceResult = {
- updated: [],
- unchanged: [],
- failed: [],
- skippedByFilter,
- };
-
- for (const projectDir of selectedProjectDirs) {
- const projectName = path.basename(projectDir);
- const configPath = path.join(projectDir, configName);
- try {
- const singleResult = await generateReadmeFromConfig({
- configPath,
- dryRun,
- useGlobalConfig,
- });
- if (singleResult.updated) {
- result.updated.push(projectName);
- console.log(`${dryRun ? 'Would update' : 'Generated'} README for ${projectName}`);
- } else {
- result.unchanged.push(projectName);
- console.log(`No changes for ${projectName}`);
- }
- } catch (error) {
- result.failed.push({ projectDir: projectName, error });
- console.error(`Error generating README for ${projectName}:`, error);
- }
- }
-
- return result;
-}
+const listProjectDirsWithConfig = async (
+ rootDir: string,
+ configName: string
+) => {
+ const entries = await readdir(rootDir, { withFileTypes: true });
+ const allProjectDirs = entries
+ .filter((entry) => entry.isDirectory())
+ .map((entry) => join(rootDir, entry.name));
+
+ const projectChecks = await Promise.all(
+ allProjectDirs.map(async (dir) => ({
+ dir,
+ hasConfig: await pathExists(join(dir, configName)),
+ }))
+ );
+
+ return projectChecks.filter((item) => item.hasConfig).map((item) => item.dir);
+};
+
+const selectProjectDirs = (
+ allProjectDirs: string[],
+ packageFilter: Set
+) =>
+ packageFilter.size > 0
+ ? allProjectDirs.filter((dir) => packageFilter.has(basename(dir)))
+ : allProjectDirs;
+
+const collectSkippedByFilter = (
+ allProjectDirs: string[],
+ packageFilter: Set
+) => {
+ if (packageFilter.size === 0) {
+ return [];
+ }
+ return allProjectDirs
+ .map((dir) => basename(dir))
+ .filter((dirName) => !packageFilter.has(dirName));
+};
+
+const pushProjectResult = (
+ result: GenerateWorkspaceResult,
+ projectName: string,
+ updated: boolean,
+ dryRun: boolean
+) => {
+ if (updated) {
+ result.updated.push(projectName);
+ const action = dryRun ? "Would update" : "Generated";
+ console.log(`${action} README for ${projectName}`);
+ return;
+ }
+
+ result.unchanged.push(projectName);
+ console.log(`No changes for ${projectName}`);
+};
+
+const processWorkspaceProject = async (
+ projectDir: string,
+ configName: string,
+ dryRun: boolean,
+ useGlobalConfig: boolean,
+ result: GenerateWorkspaceResult
+) => {
+ const projectName = basename(projectDir);
+ const configPath = join(projectDir, configName);
+ try {
+ const singleResult = await generateReadmeFromConfig({
+ configPath,
+ dryRun,
+ useGlobalConfig,
+ });
+ pushProjectResult(result, projectName, singleResult.updated, dryRun);
+ } catch (error) {
+ result.failed.push({ error, projectDir });
+ console.error(`Error generating README for ${projectName}:`, error);
+ }
+};
+
+export const generateWorkspaceReadmes = async ({
+ rootDir,
+ configName,
+ packageFilter,
+ dryRun,
+ useGlobalConfig = true,
+}: GenerateWorkspaceOptions): Promise => {
+ const absoluteRootDir = resolve(rootDir);
+ if (!(await pathExists(absoluteRootDir))) {
+ throw new Error(`Workspace root not found at ${absoluteRootDir}`);
+ }
+
+ const allProjectDirs = await listProjectDirsWithConfig(
+ absoluteRootDir,
+ configName
+ );
+ const selectedProjectDirs = selectProjectDirs(allProjectDirs, packageFilter);
+ const skippedByFilter = collectSkippedByFilter(allProjectDirs, packageFilter);
+
+ const result: GenerateWorkspaceResult = {
+ failed: [],
+ skippedByFilter,
+ unchanged: [],
+ updated: [],
+ };
+
+ for (const projectDir of selectedProjectDirs) {
+ await processWorkspaceProject(
+ projectDir,
+ configName,
+ dryRun,
+ useGlobalConfig,
+ result
+ );
+ }
+
+ return result;
+};
diff --git a/src/readme-generator/template.ts b/src/readme-generator/template.ts
index 0924ae2..1e0ebaf 100644
--- a/src/readme-generator/template.ts
+++ b/src/readme-generator/template.ts
@@ -1,206 +1,288 @@
-import type { ReadieBadge, ReadieConfig, ReadieLicenseObject } from '../config/types.js';
+import type {
+ ReadieBadge,
+ ReadieConfig,
+ ReadieLicenseObject,
+} from "#src/config/types.js";
const isNonEmpty = (value?: string | null): value is string =>
- typeof value === 'string' && value.trim().length > 0;
+ typeof value === "string" && value.trim().length > 0;
+
+const normalizeSections = (sections: string[]) =>
+ sections
+ .join("\n")
+ .replaceAll(/\n{3,}/g, "\n\n")
+ .trim();
+
+const appendUsageItem = (lines: string[], item: string, index: number) => {
+ if (item.startsWith("```")) {
+ if (lines.length > 0 && lines.at(-1) !== "") {
+ lines.push("");
+ }
+ lines.push(item);
+ lines.push("");
+ return index;
+ }
+
+ const cleaned = item.startsWith("- ") ? item.slice(2) : item;
+ lines.push(`${index}. ${cleaned}`);
+ return index + 1;
+};
const renderNumberedWithCodeBlocks = (items: string[]) => {
- const lines: string[] = [];
- let i = 1;
-
- for (const rawItem of items) {
- const item = rawItem.trim();
- if (!item) continue;
-
- if (item.startsWith('```')) {
- if (lines.length > 0 && lines[lines.length - 1] !== '') {
- lines.push('');
- }
- lines.push(item);
- lines.push('');
- continue;
- }
-
- if (item.startsWith('- ')) {
- lines.push(`${i}. ${item.slice(2)}`);
- i += 1;
- continue;
- }
-
- lines.push(`${i}. ${item}`);
- i += 1;
- }
-
- return lines.join('\n').replace(/\n{3,}/g, '\n\n').trim();
+ const lines: string[] = [];
+ let i = 1;
+
+ for (const rawItem of items) {
+ const item = rawItem.trim();
+ if (!item) {
+ continue;
+ }
+ i = appendUsageItem(lines, item, i);
+ }
+
+ return normalizeSections(lines);
};
const addSection = (
- header: string,
- content: string[] | undefined,
- formatter: (item: string, index?: number) => string = (item) => `- ${item}`,
+ header: string,
+ content: string[] | undefined,
+ formatter: (item: string, index?: number) => string = (item) => `- ${item}`
) => {
- if (!content || content.length === 0) return '';
- const body = content.map(formatter).join('\n');
- return `${header}\n\n${body}`.trim();
+ if (!content || content.length === 0) {
+ return "";
+ }
+ const body = content.map(formatter).join("\n");
+ return `${header}\n\n${body}`.trim();
};
-export const baseReadmeTemplate = (rawConfig: ReadieConfig) => {
- const config: ReadieConfig = { ...rawConfig };
-
- const bannerBlock = isNonEmpty(config.banner) ? config.banner : '';
- const titleBlock =
- isNonEmpty(bannerBlock) && bannerBlock.toLowerCase().includes(' 0
- ? config.badges.map((badge: ReadieBadge) => {
- const image = ``;
- return isNonEmpty(badge.link) ? `[${image}](${badge.link})` : image;
- }).join('\n')
- : '';
-
- const featuresBlock =
- config.features && config.features.length > 0
- ? `## Key Features\n\n${config.features.map((feature) => `- ${feature}`).join('\n')}`
- : '';
-
- const prerequisitesBlock = addSection('## Prerequisites', config.prerequisites);
-
- const quickStartBlock = isNonEmpty(config.quickStart)
- ? config.quickStart.trimStart().startsWith('## ')
- ? config.quickStart
- : `## Quick Start\n\n${config.quickStart}`
- : '';
-
- const manualInstallationBlock =
- config.manualInstallation && config.manualInstallation.length > 0
- ? `## Manual Installation\n\n${config.manualInstallation.join('\n')}`
- : '';
-
- const installationBlock =
- config.installation && config.installation.length > 0
- ? `## Installation\n\n${config.installation.join('\n')}`
- : '';
-
- const usageBlock =
- config.usage && config.usage.length > 0
- ? `## Usage\n\n${renderNumberedWithCodeBlocks(config.usage)}`
- : '';
-
- const commandsBlock =
- config.commands && config.commands.length > 0
- ? `## Available Commands\n\n${config.commands.map((cmd) => `- \`${cmd.name}\`: ${cmd.description}`).join('\n')}`
- : '';
-
- const globalFlagsBlock =
- config.globalFlags && config.globalFlags.length > 0
- ? `## Global Flags\n\n${config.globalFlags.map((flag) => `- \`${flag.flag}\`: ${flag.description}`).join('\n')}`
- : '';
-
- const docsBlock = config.docsLink
- ? `## Documentation
+const renderHeadingBlock = (
+ heading: string,
+ content: string | undefined
+): string => {
+ if (!isNonEmpty(content)) {
+ return "";
+ }
+ if (content.trimStart().startsWith("## ")) {
+ return content;
+ }
+ return `${heading}\n\n${content}`;
+};
-For further information, guides, and examples visit the [reference documentation](${config.docsLink}).`
- : '';
+const renderBadges = (badges: ReadieBadge[] | undefined) => {
+ if (!badges || badges.length === 0) {
+ return "";
+ }
+ return badges
+ .map((badge) => {
+ const image = ``;
+ return isNonEmpty(badge.link) ? `[${image}](${badge.link})` : image;
+ })
+ .join("\n");
+};
+
+const renderSimpleListSection = (
+ heading: string,
+ items: string[] | undefined,
+ formatter: (value: string) => string = (value) => value
+) => {
+ if (!items || items.length === 0) {
+ return "";
+ }
+ return `${heading}\n\n${items.map(formatter).join("\n")}`;
+};
+
+const renderCommandsSection = (config: ReadieConfig) =>
+ renderSimpleListSection(
+ "## Available Commands",
+ config.commands?.map((cmd) => `- \`${cmd.name}\`: ${cmd.description}`)
+ );
+
+const renderGlobalFlagsSection = (config: ReadieConfig) =>
+ renderSimpleListSection(
+ "## Global Flags",
+ config.globalFlags?.map((flag) => `- \`${flag.flag}\`: ${flag.description}`)
+ );
+
+const renderLicenseBlock = (license: ReadieConfig["license"]) => {
+ if (!license) {
+ return "";
+ }
+ if (typeof license === "string") {
+ return renderHeadingBlock("## License", license);
+ }
+ const { name, url } = license as ReadieLicenseObject;
+ return `## License\n\n[${name}](${url})`;
+};
+
+interface ReadmeSections {
+ bannerBlock: string;
+ titleBlock: string;
+ badgesBlock: string;
+ featuresBlock: string;
+ prerequisitesBlock: string;
+ quickStartBlock: string;
+ installationBlock: string;
+ manualInstallationBlock: string;
+ usageBlock: string;
+ commandsBlock: string;
+ globalFlagsBlock: string;
+ docsBlock: string;
+ quickStartLinkBlock: string;
+ supportBlock: string;
+ contributingBlock: string;
+ securityBlock: string;
+ licenseBlock: string;
+ customSectionsBlock: string;
+ footerBlock: string;
+}
+
+const createReadmeSections = (config: ReadieConfig): ReadmeSections => {
+ const bannerBlock = isNonEmpty(config.banner) ? config.banner : "";
+ const titleBlock =
+ isNonEmpty(bannerBlock) && bannerBlock.toLowerCase().includes(" `## ${heading}\n\n${content}`)
+ .join("\n\n")
+ : "",
+ docsBlock: config.docsLink
+ ? `## Documentation
- const quickStartLinkBlock = config.quickStartLink
- ? `## Additional Quick Start
+For further information, guides, and examples visit the [reference documentation](${config.docsLink}).`
+ : "",
+ featuresBlock: renderSimpleListSection(
+ "## Key Features",
+ config.features,
+ (feature) => `- ${feature}`
+ ),
+ footerBlock: isNonEmpty(config.footer) ? config.footer : "",
+ globalFlagsBlock: renderGlobalFlagsSection(config),
+ installationBlock: renderSimpleListSection(
+ "## Installation",
+ config.installation
+ ),
+ licenseBlock: renderLicenseBlock(config.license),
+ manualInstallationBlock: renderSimpleListSection(
+ "## Manual Installation",
+ config.manualInstallation
+ ),
+ prerequisitesBlock: addSection("## Prerequisites", config.prerequisites),
+ quickStartBlock: renderHeadingBlock("## Quick Start", config.quickStart),
+ quickStartLinkBlock: config.quickStartLink
+ ? `## Additional Quick Start
See the full quick start guide [here](${config.quickStartLink}).`
- : '';
-
- const customSectionsBlock = config.customSections
- ? Object.entries(config.customSections)
- .map(([heading, content]) => `## ${heading}\n\n${content}`)
- .join('\n\n')
- : '';
-
- const supportBlock = addSection('## Support', config.support);
- const contributingBlock = addSection('## Contributing', config.contributing);
- const securityBlock = isNonEmpty(config.security)
- ? isNonEmpty(config.security) && config.security.trimStart().startsWith('## ')
- ? config.security
- : `## Security\n\n${config.security}`
- : '';
-
- const licenseBlock = (() => {
- if (!config.license) return '';
- if (typeof config.license === 'string') {
- return config.license.trimStart().startsWith('## ')
- ? config.license
- : `## License\n\n${config.license}`;
- }
- const { name, url } = config.license as ReadieLicenseObject;
- return `## License\n\n[${name}](${url})`;
- })();
-
- const footerBlock = isNonEmpty(config.footer) ? config.footer : '';
-
- const tocSectionTitles = [
- ['Key Features', featuresBlock],
- ['Prerequisites', prerequisitesBlock],
- ['Quick Start', quickStartBlock],
- ['Installation', installationBlock],
- ['Manual Installation', manualInstallationBlock],
- ['Usage', usageBlock],
- ['Available Commands', commandsBlock],
- ['Global Flags', globalFlagsBlock],
- ['Documentation', docsBlock],
- ['Additional Quick Start', quickStartLinkBlock],
- ['Support', supportBlock],
- ['Contributing', contributingBlock],
- ['Security', securityBlock],
- ['License', licenseBlock],
- ].filter(([, section]) => isNonEmpty(section));
-
- if (isNonEmpty(customSectionsBlock)) {
- for (const key of Object.keys(config.customSections ?? {})) {
- tocSectionTitles.push([key, `## ${key}`]);
- }
- }
-
- const tocBlock =
- config.includeTableOfContents !== false && tocSectionTitles.length > 0
- ? `## Table of Contents\n\n${tocSectionTitles
- .map(([title]) => {
- const slug = title
- .toLowerCase()
- .replace(/[^a-z0-9 -]/g, '')
- .trim()
- .replace(/\s+/g, '-');
- return `- [${title}](#${slug})`;
- })
- .join('\n')}`
- : '';
-
- const readmeContent = [
- bannerBlock,
- titleBlock,
- badgesBlock,
- config.description,
- tocBlock,
- featuresBlock,
- prerequisitesBlock,
- quickStartBlock,
- installationBlock,
- manualInstallationBlock,
- usageBlock,
- commandsBlock,
- globalFlagsBlock,
- docsBlock,
- quickStartLinkBlock,
- supportBlock,
- contributingBlock,
- securityBlock,
- licenseBlock,
- customSectionsBlock,
- footerBlock,
- ]
- .filter((section) => isNonEmpty(section))
- .join('\n\n')
- .replace(/\n{3,}/g, '\n\n')
- .replace(/\n{2,}$/, '\n');
-
- return `${readmeContent.trim()}\n`;
+ : "",
+ securityBlock: renderHeadingBlock("## Security", config.security),
+ supportBlock: addSection("## Support", config.support),
+ titleBlock,
+ usageBlock: config.usage
+ ? `## Usage\n\n${renderNumberedWithCodeBlocks(config.usage)}`
+ : "",
+ };
+};
+
+const slugifyHeading = (title: string) =>
+ title
+ .toLowerCase()
+ .replaceAll(/[^a-z0-9 -]/g, "")
+ .trim()
+ .replaceAll(/\s+/g, "-");
+
+const createUniqueSlug = (title: string, seenSlugs: Map) => {
+ const baseSlug = slugifyHeading(title);
+ const count = seenSlugs.get(baseSlug) ?? 0;
+ seenSlugs.set(baseSlug, count + 1);
+ return count === 0 ? baseSlug : `${baseSlug}-${count}`;
+};
+
+type TocTitleEntry = [title: string, section: string];
+
+const createTocTitles = (config: ReadieConfig, sections: ReadmeSections) => {
+ const tocSectionTitles: TocTitleEntry[] = [
+ ["Key Features", sections.featuresBlock],
+ ["Prerequisites", sections.prerequisitesBlock],
+ ["Quick Start", sections.quickStartBlock],
+ ["Installation", sections.installationBlock],
+ ["Manual Installation", sections.manualInstallationBlock],
+ ["Usage", sections.usageBlock],
+ ["Available Commands", sections.commandsBlock],
+ ["Global Flags", sections.globalFlagsBlock],
+ ["Documentation", sections.docsBlock],
+ ["Additional Quick Start", sections.quickStartLinkBlock],
+ ["Support", sections.supportBlock],
+ ["Contributing", sections.contributingBlock],
+ ["Security", sections.securityBlock],
+ ["License", sections.licenseBlock],
+ ];
+ const visibleTocSectionTitles = tocSectionTitles.filter(([, section]) =>
+ isNonEmpty(section)
+ );
+
+ if (!isNonEmpty(sections.customSectionsBlock)) {
+ return visibleTocSectionTitles;
+ }
+
+ for (const key of Object.keys(config.customSections ?? {})) {
+ visibleTocSectionTitles.push([key, `## ${key}`]);
+ }
+ return visibleTocSectionTitles;
+};
+
+const createTocBlock = (
+ includeTableOfContents: boolean | undefined,
+ titles: TocTitleEntry[]
+) => {
+ if (includeTableOfContents === false || titles.length === 0) {
+ return "";
+ }
+ const seenSlugs = new Map();
+ const links = titles
+ .map(([title]) => `- [${title}](#${createUniqueSlug(title, seenSlugs)})`)
+ .join("\n");
+ return `## Table of Contents\n\n${links}`;
+};
+
+export const baseReadmeTemplate = (rawConfig: ReadieConfig) => {
+ const sections = createReadmeSections(rawConfig);
+ const tocTitles = createTocTitles(rawConfig, sections);
+ const tocBlock = createTocBlock(rawConfig.includeTableOfContents, tocTitles);
+
+ const readmeContent = [
+ sections.bannerBlock,
+ sections.titleBlock,
+ sections.badgesBlock,
+ rawConfig.description,
+ tocBlock,
+ sections.featuresBlock,
+ sections.prerequisitesBlock,
+ sections.quickStartBlock,
+ sections.installationBlock,
+ sections.manualInstallationBlock,
+ sections.usageBlock,
+ sections.commandsBlock,
+ sections.globalFlagsBlock,
+ sections.docsBlock,
+ sections.quickStartLinkBlock,
+ sections.supportBlock,
+ sections.contributingBlock,
+ sections.securityBlock,
+ sections.licenseBlock,
+ sections.customSectionsBlock,
+ sections.footerBlock,
+ ]
+ .filter((section) => isNonEmpty(section))
+ .join("\n\n")
+ .replaceAll(/\n{3,}/g, "\n\n")
+ .replace(/\n{2,}$/, "\n");
+
+ return `${readmeContent.trim()}\n`;
};
diff --git a/test/generator-global-interpolation.test.ts b/test/generator-global-interpolation.test.ts
index 66c738e..3cd6f9b 100644
--- a/test/generator-global-interpolation.test.ts
+++ b/test/generator-global-interpolation.test.ts
@@ -1,47 +1,55 @@
-import fs from 'node:fs/promises';
-import os from 'node:os';
-import path from 'node:path';
-import { describe, expect, it } from 'vitest';
-import { generateReadmeFromConfig } from '../src/readme-generator/generator';
+import { ensureDir, readFile, remove, writeFile } from "fs-extra";
+import { join } from "pathe";
+import { temporaryDirectory } from "tempy";
+
+import { generateReadmeFromConfig } from "#src/readme-generator/generator.js";
const writeJson = async (filePath: string, value: unknown) => {
- await fs.writeFile(filePath, JSON.stringify(value, null, 2), 'utf8');
+ await writeFile(filePath, JSON.stringify(value, null, 2), "utf8");
+};
+
+const setupFixture = async () => {
+ const rootDir = temporaryDirectory();
+ const packageDir = join(rootDir, "packages", "react");
+ await ensureDir(packageDir);
+
+ await writeJson(join(rootDir, "readie.global.json"), {
+ banner: '{{title}}
',
+ footer: "Built for {{ title }} ({{packageNameEncoded}})",
+ });
+
+ await writeJson(join(packageDir, "package.json"), {
+ name: "@c15t/react",
+ version: "1.0.0",
+ });
+
+ const configPath = join(packageDir, "readie.json");
+ await writeJson(configPath, {
+ description: "CMP for React",
+ title: "@c15t/react: React Consent Components",
+ });
+
+ return { configPath, rootDir };
};
-describe('generateReadmeFromConfig with global interpolation', () => {
- it('injects title and package placeholders in global config', async () => {
- const rootDir = await fs.mkdtemp(path.join(os.tmpdir(), 'readie-global-'));
- try {
- const packageDir = path.join(rootDir, 'packages', 'react');
- await fs.mkdir(packageDir, { recursive: true });
-
- await writeJson(path.join(rootDir, 'readie.global.json'), {
- banner: '{{title}}
',
- footer: 'Built for {{ title }} ({{packageNameEncoded}})',
- });
-
- await writeJson(path.join(packageDir, 'package.json'), {
- name: '@c15t/react',
- version: '1.0.0',
- });
-
- const configPath = path.join(packageDir, 'readie.json');
- await writeJson(configPath, {
- title: '@c15t/react: React Consent Components',
- description: 'CMP for React',
- });
-
- const result = await generateReadmeFromConfig({
- configPath,
- dryRun: false,
- });
-
- const generated = await fs.readFile(result.outputPath, 'utf8');
-
- expect(generated).toContain('@c15t/react: React Consent Components
');
- expect(generated).toContain('Built for @c15t/react: React Consent Components (%40c15t%2Freact)');
- } finally {
- await fs.rm(rootDir, { recursive: true, force: true });
- }
- });
+describe("generateReadmeFromConfig with global interpolation", () => {
+ it("injects title and package placeholders in global config", async () => {
+ const fixture = await setupFixture();
+ try {
+ const result = await generateReadmeFromConfig({
+ configPath: fixture.configPath,
+ dryRun: false,
+ });
+ const generated = await readFile(result.outputPath, "utf8");
+
+ expect(generated).toContain(
+ '@c15t/react: React Consent Components
'
+ );
+ expect(generated).toContain(
+ "Built for @c15t/react: React Consent Components (%40c15t%2Freact)"
+ );
+ } finally {
+ await remove(fixture.rootDir);
+ }
+ });
});
diff --git a/test/merge-config.test.ts b/test/merge-config.test.ts
index 24d16a6..8587835 100644
--- a/test/merge-config.test.ts
+++ b/test/merge-config.test.ts
@@ -1,55 +1,60 @@
-import { describe, expect, it } from 'vitest';
-import { mergeConfigs } from '../src/config/load-config';
-import type { ReadieConfig, ReadieGlobalConfig } from '../src/config/types';
-
-const createProjectConfig = (overrides: Partial = {}): ReadieConfig => ({
- title: 'My Package',
- description: 'Project level description.',
- ...overrides,
+import { mergeConfigs } from "#src/config/load-config.js";
+import type { ReadieConfig, ReadieGlobalConfig } from "#src/config/types.js";
+
+const createProjectConfig = (
+ overrides: Partial = {}
+): ReadieConfig => ({
+ description: "Project level description.",
+ title: "My Package",
+ ...overrides,
});
-describe('mergeConfigs', () => {
- it('interpolates top-level global string placeholders', () => {
- const globalConfig: ReadieGlobalConfig = {
- banner: '{{title}}
',
- footer: 'Built by {{ title }} - {{packageName}} - {{packageNameEncoded}}',
- features: ['Feature A'],
- customSections: {
- Notes: 'Package: {{title}}',
- },
- };
-
- const merged = mergeConfigs(globalConfig, createProjectConfig(), { packageName: '@c15t/react' });
-
- expect(merged.banner).toBe('My Package
');
- expect(merged.footer).toBe('Built by My Package - @c15t/react - %40c15t%2Freact');
- expect(merged.features).toEqual(['Feature A']);
- expect(merged.customSections?.Notes).toBe('Package: {{title}}');
- });
-
- it('preserves project-over-global precedence before interpolation', () => {
- const globalConfig: ReadieGlobalConfig = {
- banner: 'Global banner {{title}}',
- quickStart: 'Global quick start',
- };
-
- const projectConfig = createProjectConfig({
- banner: 'Project banner',
- quickStart: 'Project quick start for {{title}}',
- });
-
- const merged = mergeConfigs(globalConfig, projectConfig);
-
- expect(merged.banner).toBe('Project banner');
- expect(merged.quickStart).toBe('Project quick start for My Package');
- });
-
- it('falls back to title when packageName is unavailable', () => {
- const globalConfig: ReadieGlobalConfig = {
- footer: 'Encoded: {{packageNameEncoded}}',
- };
-
- const merged = mergeConfigs(globalConfig, createProjectConfig());
- expect(merged.footer).toBe('Encoded: My%20Package');
- });
+describe("merge configs", () => {
+ it("interpolates top-level global string placeholders", () => {
+ const globalConfig: ReadieGlobalConfig = {
+ banner: '{{title}}
',
+ customSections: {
+ Notes: "Package: {{title}}",
+ },
+ features: ["Feature A"],
+ footer: "Built by {{ title }} - {{packageName}} - {{packageNameEncoded}}",
+ };
+
+ const merged = mergeConfigs(globalConfig, createProjectConfig(), {
+ packageName: "@c15t/react",
+ });
+
+ expect(merged.banner).toBe('My Package
');
+ expect(merged.footer).toBe(
+ "Built by My Package - @c15t/react - %40c15t%2Freact"
+ );
+ expect(merged.features).toStrictEqual(["Feature A"]);
+ expect(merged.customSections?.Notes).toBe("Package: My Package");
+ });
+
+ it("preserves project-over-global precedence before interpolation", () => {
+ const globalConfig: ReadieGlobalConfig = {
+ banner: "Global banner {{title}}",
+ quickStart: "Global quick start",
+ };
+
+ const projectConfig = createProjectConfig({
+ banner: "Project banner",
+ quickStart: "Project quick start for {{title}}",
+ });
+
+ const merged = mergeConfigs(globalConfig, projectConfig);
+
+ expect(merged.banner).toBe("Project banner");
+ expect(merged.quickStart).toBe("Project quick start for My Package");
+ });
+
+ it("falls back to title when packageName is unavailable", () => {
+ const globalConfig: ReadieGlobalConfig = {
+ footer: "Encoded: {{packageNameEncoded}}",
+ };
+
+ const merged = mergeConfigs(globalConfig, createProjectConfig());
+ expect(merged.footer).toBe("Encoded: My%20Package");
+ });
});
diff --git a/test/resolve-invocation.test.ts b/test/resolve-invocation.test.ts
index 6c4e648..6c8f805 100644
--- a/test/resolve-invocation.test.ts
+++ b/test/resolve-invocation.test.ts
@@ -1,22 +1,44 @@
-import { describe, expect, it } from 'vitest';
-import { resolveInvocation } from '../src/cli/resolve-invocation';
+import { resolveInvocation } from "#src/cli/resolve-invocation.js";
-describe('resolveInvocation', () => {
- it('defaults to generate when no args are passed', () => {
- const resolved = resolveInvocation([]);
- expect(resolved.mode).toBe('generate');
- expect(resolved.commandArgs).toEqual([]);
- });
+describe("resolve invocation routing", () => {
+ it("defaults to generate when no args are passed", () => {
+ const resolved = resolveInvocation([]);
+ expect(resolved.mode).toBe("generate");
+ expect(resolved.commandArgs).toStrictEqual([]);
+ });
- it('routes workspace subcommand', () => {
- const resolved = resolveInvocation(['generate:workspace', '--root', './packages']);
- expect(resolved.mode).toBe('generate:workspace');
- expect(resolved.commandArgs).toEqual(['--root', './packages']);
- });
+ it("routes workspace subcommand", () => {
+ const resolved = resolveInvocation([
+ "generate:workspace",
+ "--root",
+ "./packages",
+ ]);
+ expect(resolved.mode).toBe("generate:workspace");
+ expect(resolved.commandArgs).toStrictEqual(["--root", "./packages"]);
+ });
- it('routes init subcommand', () => {
- const resolved = resolveInvocation(['init', '--force']);
- expect(resolved.mode).toBe('init');
- expect(resolved.commandArgs).toEqual(['--force']);
- });
+ it("routes init subcommand", () => {
+ const resolved = resolveInvocation(["init", "--force"]);
+ expect(resolved.mode).toBe("init");
+ expect(resolved.commandArgs).toStrictEqual(["--force"]);
+ });
+
+ it("routes --help to help mode", () => {
+ const resolved = resolveInvocation(["--help"]);
+ expect(resolved.mode).toBe("help");
+ expect(resolved.commandArgs).toStrictEqual([]);
+ });
+
+ it("routes help command to help mode", () => {
+ const resolved = resolveInvocation(["help"]);
+ expect(resolved.mode).toBe("help");
+ expect(resolved.commandArgs).toStrictEqual([]);
+ });
+
+ it("routes unknown commands to unknown mode", () => {
+ const args = ["invalid-command", "--flag"];
+ const resolved = resolveInvocation(args);
+ expect(resolved.mode).toBe("unknown");
+ expect(resolved.commandArgs).toStrictEqual(args);
+ });
});
diff --git a/test/template.test.ts b/test/template.test.ts
index a11c3c0..5bf1218 100644
--- a/test/template.test.ts
+++ b/test/template.test.ts
@@ -1,23 +1,27 @@
-import { describe, expect, it } from 'vitest';
-import { baseReadmeTemplate } from '../src/readme-generator/template';
+import { baseReadmeTemplate } from "#src/readme-generator/template.js";
-describe('baseReadmeTemplate', () => {
- it('renders neutral markdown without c15t defaults', () => {
- const markdown = baseReadmeTemplate({
- title: 'Readie Demo',
- description: 'A neutral README.',
- includeTableOfContents: true,
- features: ['Fast', 'Simple'],
- installation: ['```bash\nnpm install readie-demo\n```'],
- usage: ['Run the command', '```bash\nnpx readie\n```'],
- security: 'Please report issues privately.',
- });
+describe("base readme template", () => {
+ it("renders neutral markdown without c15t defaults", () => {
+ const markdown = baseReadmeTemplate({
+ description: "A neutral README.",
+ features: ["Fast", "Simple"],
+ includeTableOfContents: true,
+ installation: ["```bash\nnpm install readie-demo\n```"],
+ security: "Please report issues privately.",
+ title: "Readie Demo",
+ usage: ["Run the command", "```bash\nnpx readie\n```"],
+ });
- expect(markdown).toContain('# Readie Demo');
- expect(markdown).toContain('## Table of Contents');
- expect(markdown).toContain('## Key Features');
- expect(markdown).toContain('## Security');
- expect(markdown).not.toContain('c15t');
- expect(markdown).not.toContain('consent.io');
- });
+ const requiredHeadings = [
+ "# Readie Demo",
+ "## Table of Contents",
+ "## Key Features",
+ "## Security",
+ ];
+ const missingHeadings = requiredHeadings.filter(
+ (heading) => !markdown.includes(heading)
+ );
+ expect(missingHeadings).toStrictEqual([]);
+ expect(markdown).not.toMatch(/c15t|consent\.io/);
+ });
});
diff --git a/test/validate-config.test.ts b/test/validate-config.test.ts
index 4ba5f24..cdb793a 100644
--- a/test/validate-config.test.ts
+++ b/test/validate-config.test.ts
@@ -1,36 +1,44 @@
-import fs from 'node:fs/promises';
-import os from 'node:os';
-import path from 'node:path';
-import { describe, expect, it } from 'vitest';
-import { loadReadieConfig } from '../src/config/load-config';
+import { remove, writeFile } from "fs-extra";
+import { join } from "pathe";
+import { temporaryDirectory } from "tempy";
+
+import { loadReadieConfig } from "#src/config/load-config.js";
const createTempFile = async (contents: string) => {
- const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'readie-test-'));
- const filePath = path.join(dir, 'readie.json');
- await fs.writeFile(filePath, contents, 'utf8');
- return filePath;
+ const dir = temporaryDirectory();
+ const filePath = join(dir, "readie.json");
+ await writeFile(filePath, contents, "utf8");
+ return { dir, filePath };
};
-describe('loadReadieConfig', () => {
- it('loads a valid config', async () => {
- const configPath = await createTempFile(
- JSON.stringify({
- title: 'Test Project',
- description: 'Config validation test.',
- }),
- );
-
- const config = await loadReadieConfig(configPath);
- expect(config.title).toBe('Test Project');
- });
-
- it('throws for invalid config', async () => {
- const configPath = await createTempFile(
- JSON.stringify({
- description: 'Missing title should fail.',
- }),
- );
+describe("load readie config", () => {
+ it("loads a valid config", async () => {
+ const { dir, filePath } = await createTempFile(
+ JSON.stringify({
+ description: "Config validation test.",
+ title: "Test Project",
+ })
+ );
+ try {
+ const config = await loadReadieConfig(filePath);
+ expect(config.title).toBe("Test Project");
+ } finally {
+ await remove(dir);
+ }
+ });
- await expect(loadReadieConfig(configPath)).rejects.toThrow('Configuration validation failed');
- });
+ it("throws for invalid config", async () => {
+ const { dir, filePath } = await createTempFile(
+ JSON.stringify({
+ description: "Missing title should fail.",
+ })
+ );
+ try {
+ await expect(loadReadieConfig(filePath)).rejects.toThrow(
+ "Configuration validation failed"
+ );
+ } finally {
+ await remove(dir);
+ }
+ });
});
diff --git a/tsconfig.json b/tsconfig.json
index 7c22f14..6ae6cef 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,16 +1,34 @@
{
- "compilerOptions": {
- "target": "ES2022",
- "module": "NodeNext",
- "moduleResolution": "NodeNext",
- "lib": ["ES2022"],
- "strict": true,
- "declaration": true,
- "outDir": "dist",
- "rootDir": "src",
- "esModuleInterop": true,
- "resolveJsonModule": true,
- "skipLibCheck": true
- },
- "include": ["src/**/*.ts"]
+ "compilerOptions": {
+ // Environment setup & latest features
+ "lib": ["ESNext"],
+ "target": "ESNext",
+ "module": "Preserve",
+ "moduleDetection": "force",
+ "jsx": "react-jsx",
+ "allowJs": true,
+
+ // Bundler mode
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "noEmit": true,
+
+ // Best practices
+ "strict": true,
+ "skipLibCheck": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedIndexedAccess": true,
+ "noImplicitOverride": true,
+
+ // Some stricter flags (disabled by default)
+ "noUnusedLocals": false,
+ "noUnusedParameters": false,
+ "noPropertyAccessFromIndexSignature": false,
+ "paths": {
+ "#src/*": ["./src/*"]
+ },
+ "types": ["vitest/globals"]
+ },
+ "include": ["src/**/*.ts", "test/**/*.ts"]
}
diff --git a/vitest.config.ts b/vitest.config.ts
new file mode 100644
index 0000000..076c92f
--- /dev/null
+++ b/vitest.config.ts
@@ -0,0 +1,7 @@
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ test: {
+ globals: true,
+ },
+});