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 = `![${badge.label}](${badge.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 = `![${badge.label}](${badge.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, + }, +});