diff --git a/README.md b/README.md index c31f240..317283a 100644 --- a/README.md +++ b/README.md @@ -33,22 +33,23 @@ Generate changesets for dependency changes between commits: npx @izumisy/deps2changesets # Compare specific commits using Git range syntax -npx @izumisy/deps2changesets abc123..def456 +npx @izumisy/deps2changesets --range abc123..def456 # Compare branches -npx @izumisy/deps2changesets main..feature-branch +npx @izumisy/deps2changesets --range main..feature-branch # Compare from a specific ref to HEAD -npx @izumisy/deps2changesets main.. +npx @izumisy/deps2changesets --range main.. ``` ### Options -| Option | Description | Default | -|--------|-------------|---------| -| `[range]` | Git commit range (e.g., `main..HEAD`, `a1b2c3..d4e5f6`) | `main..HEAD` | -| `--releaseType` | Release type for changesets (`patch`, `minor`, `major`) | `patch` | -| `--cwd` | Working directory | Current directory | +| Option | Short | Description | Default | +|--------|-------|-------------|---------| +| `--range` | `-r` | Git commit range (e.g., `main..HEAD`, `a1b2c3..d4e5f6`) | `main..HEAD` | +| `--release-type` | `-t` | Release type for changesets (`patch`, `minor`, `major`) | `patch` | +| `--cwd` | `-c` | Working directory | Current directory | +| `--dry-run` | `-d` | Preview changes without creating changesets | `false` | ### Examples @@ -57,13 +58,16 @@ npx @izumisy/deps2changesets main.. npx @izumisy/deps2changesets # Generate changesets for a specific range -npx @izumisy/deps2changesets HEAD~3..HEAD +npx @izumisy/deps2changesets --range HEAD~3..HEAD # Generate minor changesets -npx @izumisy/deps2changesets main..HEAD --releaseType minor +npx @izumisy/deps2changesets --range main..HEAD --release-type minor # Run in a specific directory npx @izumisy/deps2changesets --cwd /path/to/repo + +# Preview changes without creating changesets +npx @izumisy/deps2changesets --dry-run ``` ## How it Works diff --git a/package.json b/package.json index 398a65d..9fc389a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@izumisy/deps2changesets", "private": false, - "version": "0.1.2", + "version": "0.1.3", "description": "CLI tool to automatically generate changesets from dependency changes in Git commits", "type": "module", "main": "dist/cli.mjs", @@ -27,9 +27,11 @@ "author": "", "license": "MIT", "dependencies": { + "@changesets/read": "^0.6.6", "@changesets/write": "^0.3.2", "@manypkg/get-packages": "^2.2.2", - "citty": "^0.1.6", + "consola": "^3.4.2", + "gunshi": "^0.27.5", "simple-git": "^3.30.0" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 16ee82a..c1c2232 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,15 +8,21 @@ importers: .: dependencies: + '@changesets/read': + specifier: ^0.6.6 + version: 0.6.6 '@changesets/write': specifier: ^0.3.2 version: 0.3.2 '@manypkg/get-packages': specifier: ^2.2.2 version: 2.2.2 - citty: - specifier: ^0.1.6 - version: 0.1.6 + consola: + specifier: ^3.4.2 + version: 3.4.2 + gunshi: + specifier: ^0.27.5 + version: 0.27.5 simple-git: specifier: ^3.30.0 version: 3.30.0 @@ -59,10 +65,32 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/runtime@7.28.4': + resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} + engines: {node: '>=6.9.0'} + '@babel/types@7.28.5': resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} + '@changesets/errors@0.2.0': + resolution: {integrity: sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==} + + '@changesets/git@3.0.4': + resolution: {integrity: sha512-BXANzRFkX+XcC1q/d27NKvlJ1yf7PSAgi8JG6dt8EfbHFHi4neau7mufcSca5zRhwOL8j9s6EqsxmT+s+/E6Sw==} + + '@changesets/logger@0.1.1': + resolution: {integrity: sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==} + + '@changesets/parse@0.4.2': + resolution: {integrity: sha512-Uo5MC5mfg4OM0jU3up66fmSn6/NE9INK+8/Vn/7sMVcdWg46zfbvvUSjD9EMonVqPi9fbrJH9SXHn48Tr1f2yA==} + + '@changesets/read@0.6.6': + resolution: {integrity: sha512-P5QaN9hJSQQKJShzzpBT13FzOSPyHbqdoIBUd2DJdgvnECCyO6LmAOWSV+O8se2TaZJVwSXjL+v9yhb+a9JeJg==} + + '@changesets/types@4.1.0': + resolution: {integrity: sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==} + '@changesets/types@6.1.0': resolution: {integrity: sha512-rKQcJ+o1nKNgeoYRHKOS07tAMNd3YSN0uHaJOZYjBAgxfV7TUE7JE+z4BzZdQwb5hKaYbayKN5KrYV7ODb2rAA==} @@ -271,10 +299,16 @@ packages: '@kwsites/promise-deferred@1.1.1': resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==} + '@manypkg/find-root@1.1.0': + resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} + '@manypkg/find-root@2.2.3': resolution: {integrity: sha512-jtEZKczWTueJYHjGpxU3KJQ08Gsrf4r6Q2GjmPp/RGk5leeYAA1eyDADSAF+KVCsQ6EwZd/FMcOFCoMhtqdCtQ==} engines: {node: '>=14.18.0'} + '@manypkg/get-packages@1.1.3': + resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} + '@manypkg/get-packages@2.2.2': resolution: {integrity: sha512-3+Zd8kLZmsyJFmWTBtY0MAuCErI7yKB2cjMBlujvSVKZ2R/BMXi0kjCXu2dtRlSq/ML86t1FkumT0yreQ3n8OQ==} engines: {node: '>=14.18.0'} @@ -500,6 +534,9 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/node@12.20.55': + resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} + '@types/node@20.19.27': resolution: {integrity: sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==} @@ -536,9 +573,20 @@ packages: resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} engines: {node: '>=14'} + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + args-tokens@0.23.0: + resolution: {integrity: sha512-VZETsQmpEGO7A9adjfIOTyZuxKzurUas9kh1Hm0WGhFq8dOyw50P6OjiTwKyURwnmDabw0DbFjI+BUucJfCtnQ==} + engines: {node: '>= 20'} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -547,6 +595,10 @@ packages: resolution: {integrity: sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==} engines: {node: '>=20.19.0'} + better-path-resolve@1.0.0: + resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} + engines: {node: '>=4'} + birpc@4.0.0: resolution: {integrity: sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw==} @@ -566,13 +618,14 @@ packages: resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} engines: {node: '>= 16'} - citty@0.1.6: - resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} - consola@3.4.2: resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -589,6 +642,10 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + dts-resolver@2.1.3: resolution: {integrity: sha512-bihc7jPC90VrosXNzK0LTE2cuLP6jr0Ro8jk+kMugHReJVLIpHz/xadeq3MhuwyO4TD4OA3L1Q8pBBFRc08Tsw==} engines: {node: '>=20.19.0'} @@ -610,6 +667,11 @@ packages: engines: {node: '>=12'} hasBin: true + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} @@ -617,6 +679,9 @@ packages: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} + extendable-error@0.1.7: + resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} + fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} @@ -637,10 +702,18 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + fs-extra@7.0.1: resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} engines: {node: '>=6 <7 || >=8'} + fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -659,9 +732,17 @@ packages: peerDependencies: tslib: '2' + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + gunshi@0.27.5: + resolution: {integrity: sha512-ExdnmI90hQ/eR1wnUc5rVom+K16F1hSpbYrplpt6rrJhXLprVGK9/E63bk7Rju27hAqc3WvpCa8PeY95R165fw==} + engines: {node: '>= 20'} + hookable@6.0.1: resolution: {integrity: sha512-uKGyY8BuzN/a5gvzvA+3FVWo0+wUjgtfSdnmjtrOVwQCZPHpHDH2WRO3VZSOeluYrHoDCiXFffZXs8Dj1ULWtw==} @@ -672,6 +753,10 @@ packages: resolution: {integrity: sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==} engines: {node: '>=10.18'} + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + import-without-cache@0.2.5: resolution: {integrity: sha512-B6Lc2s6yApwnD2/pMzFh/d5AVjdsDXjgkeJ766FmFuJELIGHNycKRj+l3A39yZPM4CchqNCB4RITEAYB1KUM6A==} engines: {node: '>=20.19.0'} @@ -688,9 +773,24 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-subdir@1.2.0: + resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} + engines: {node: '>=4'} + + is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + jju@1.4.0: resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} + js-yaml@3.14.2: + resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} + hasBin: true + js-yaml@4.1.1: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true @@ -703,6 +803,10 @@ packages: jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + loupe@3.2.1: resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} @@ -731,6 +835,38 @@ packages: obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + p-filter@2.1.0: + resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} + engines: {node: '>=8'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-map@2.1.0: + resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} + engines: {node: '>=6'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} @@ -752,6 +888,10 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + pify@4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} + postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} @@ -772,6 +912,10 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + read-yaml-file@1.1.0: + resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} + engines: {node: '>=6'} + resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} @@ -816,22 +960,48 @@ packages: engines: {node: '>=10'} hasBin: true + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + simple-git@3.30.0: resolution: {integrity: sha512-q6lxyDsCmEal/MEGhP1aVyQ3oxnagGlBDOVSIB4XUVLl1iZh0Pah6ebC9V4xBap/RfgP2WlI8EKs0WS0rMEJHg==} + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + spawndamnit@3.0.1: + resolution: {integrity: sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + thingies@2.5.0: resolution: {integrity: sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw==} engines: {node: '>=10.18'} @@ -992,6 +1162,11 @@ packages: jsdom: optional: true + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} @@ -1015,11 +1190,46 @@ snapshots: dependencies: '@babel/types': 7.28.5 + '@babel/runtime@7.28.4': {} + '@babel/types@7.28.5': dependencies: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@changesets/errors@0.2.0': + dependencies: + extendable-error: 0.1.7 + + '@changesets/git@3.0.4': + dependencies: + '@changesets/errors': 0.2.0 + '@manypkg/get-packages': 1.1.3 + is-subdir: 1.2.0 + micromatch: 4.0.8 + spawndamnit: 3.0.1 + + '@changesets/logger@0.1.1': + dependencies: + picocolors: 1.1.1 + + '@changesets/parse@0.4.2': + dependencies: + '@changesets/types': 6.1.0 + js-yaml: 4.1.1 + + '@changesets/read@0.6.6': + dependencies: + '@changesets/git': 3.0.4 + '@changesets/logger': 0.1.1 + '@changesets/parse': 0.4.2 + '@changesets/types': 6.1.0 + fs-extra: 7.0.1 + p-filter: 2.1.0 + picocolors: 1.1.1 + + '@changesets/types@4.1.0': {} + '@changesets/types@6.1.0': {} '@changesets/write@0.3.2': @@ -1172,10 +1382,26 @@ snapshots: '@kwsites/promise-deferred@1.1.1': {} + '@manypkg/find-root@1.1.0': + dependencies: + '@babel/runtime': 7.28.4 + '@types/node': 12.20.55 + find-up: 4.1.0 + fs-extra: 8.1.0 + '@manypkg/find-root@2.2.3': dependencies: '@manypkg/tools': 1.1.2 + '@manypkg/get-packages@1.1.3': + dependencies: + '@babel/runtime': 7.28.4 + '@changesets/types': 4.1.0 + '@manypkg/find-root': 1.1.0 + fs-extra: 8.1.0 + globby: 11.1.0 + read-yaml-file: 1.1.0 + '@manypkg/get-packages@2.2.2': dependencies: '@manypkg/find-root': 2.2.3 @@ -1328,6 +1554,8 @@ snapshots: '@types/estree@1.0.8': {} + '@types/node@12.20.55': {} + '@types/node@20.19.27': dependencies: undici-types: 6.21.0 @@ -1374,8 +1602,16 @@ snapshots: ansis@4.2.0: {} + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + argparse@2.0.1: {} + args-tokens@0.23.0: {} + + array-union@2.1.0: {} + assertion-error@2.0.1: {} ast-kit@2.2.0: @@ -1383,6 +1619,10 @@ snapshots: '@babel/parser': 7.28.5 pathe: 2.0.3 + better-path-resolve@1.0.0: + dependencies: + is-windows: 1.0.2 + birpc@4.0.0: {} braces@3.0.3: @@ -1401,12 +1641,14 @@ snapshots: check-error@2.1.1: {} - citty@0.1.6: - dependencies: - consola: 3.4.2 - consola@3.4.2: {} + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + debug@4.4.3: dependencies: ms: 2.1.3 @@ -1415,6 +1657,10 @@ snapshots: defu@6.1.4: {} + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + dts-resolver@2.1.3: {} empathic@2.0.0: {} @@ -1447,12 +1693,16 @@ snapshots: '@esbuild/win32-ia32': 0.21.5 '@esbuild/win32-x64': 0.21.5 + esprima@4.0.1: {} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.8 expect-type@1.3.0: {} + extendable-error@0.1.7: {} + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -1473,12 +1723,23 @@ snapshots: dependencies: to-regex-range: 5.0.1 + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + fs-extra@7.0.1: dependencies: graceful-fs: 4.2.11 jsonfile: 4.0.0 universalify: 0.1.2 + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + fsevents@2.3.3: optional: true @@ -1494,14 +1755,29 @@ snapshots: dependencies: tslib: 2.8.1 + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + graceful-fs@4.2.11: {} + gunshi@0.27.5: + dependencies: + args-tokens: 0.23.0 + hookable@6.0.1: {} human-id@1.0.2: {} hyperdyperid@1.2.0: {} + ignore@5.3.2: {} + import-without-cache@0.2.5: {} is-extglob@2.1.1: {} @@ -1512,8 +1788,21 @@ snapshots: is-number@7.0.0: {} + is-subdir@1.2.0: + dependencies: + better-path-resolve: 1.0.0 + + is-windows@1.0.2: {} + + isexe@2.0.0: {} + jju@1.4.0: {} + js-yaml@3.14.2: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + js-yaml@4.1.1: dependencies: argparse: 2.0.1 @@ -1524,6 +1813,10 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + loupe@3.2.1: {} magic-string@0.30.21: @@ -1552,6 +1845,28 @@ snapshots: obug@2.1.1: {} + p-filter@2.1.0: + dependencies: + p-map: 2.1.0 + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-map@2.1.0: {} + + p-try@2.2.0: {} + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-type@4.0.0: {} + pathe@1.1.2: {} pathe@2.0.3: {} @@ -1564,6 +1879,8 @@ snapshots: picomatch@4.0.3: {} + pify@4.0.1: {} + postcss@8.5.6: dependencies: nanoid: 3.3.11 @@ -1578,6 +1895,13 @@ snapshots: queue-microtask@1.2.3: {} + read-yaml-file@1.1.0: + dependencies: + graceful-fs: 4.2.11 + js-yaml: 3.14.2 + pify: 4.0.1 + strip-bom: 3.0.0 + resolve-pkg-maps@1.0.0: {} reusify@1.1.0: {} @@ -1651,8 +1975,16 @@ snapshots: semver@7.7.3: {} + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + siginfo@2.0.0: {} + signal-exit@4.1.0: {} + simple-git@3.30.0: dependencies: '@kwsites/file-exists': 1.1.1 @@ -1661,12 +1993,23 @@ snapshots: transitivePeerDependencies: - supports-color + slash@3.0.0: {} + source-map-js@1.2.1: {} + spawndamnit@3.0.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + sprintf-js@1.0.3: {} + stackback@0.0.2: {} std-env@3.10.0: {} + strip-bom@3.0.0: {} + thingies@2.5.0(tslib@2.8.1): dependencies: tslib: 2.8.1 @@ -1804,6 +2147,10 @@ snapshots: - supports-color - terser + which@2.0.2: + dependencies: + isexe: 2.0.0 + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 diff --git a/src/changeset.ts b/src/changeset.ts index 44cb2d1..6cdf26a 100644 --- a/src/changeset.ts +++ b/src/changeset.ts @@ -1,4 +1,7 @@ import writeChangeset from "@changesets/write"; +import readChangesets from "@changesets/read"; +import fs from "node:fs/promises"; +import path from "node:path"; import type { PublicChangedPackage, DependencyChange } from "./types"; /** @@ -23,41 +26,87 @@ function generateSingleLineSummary(change: DependencyChange): string { } } +/** + * Auto-generated banner comment for changesets + */ +export const AUTO_GENERATED_BANNER = ``; + +/** + * Check if a changeset summary contains the auto-generated banner + */ +function isAutoGeneratedChangeset(summary: string): boolean { + return summary.includes(AUTO_GENERATED_BANNER); +} + /** * Generate a human-readable summary from dependency changes + * @param changes - Non-empty array of dependency changes (empty array is not expected) */ function generateSummaryFromChanges(changes: DependencyChange[]): string { - if (changes.length === 0) { - return "Updated dependencies"; - } - - // Single change: return a single line without heading + let content: string; if (changes.length === 1) { - return generateSingleLineSummary(changes[0]); + // Single change: return a single line without heading + content = generateSingleLineSummary(changes[0]); + } else { + // Use markdown list format with heading for multiple changes + content = [ + "Dependencies updated\n", + ...changes.map((c) => `- ${generateSingleLineSummary(c)}`), + ].join("\n"); } - // Use markdown list format with heading for multiple changes - return [ - "Dependencies updated\n", - ...changes.map((c) => `- ${generateSingleLineSummary(c)}`), - ].join("\n"); + return `${AUTO_GENERATED_BANNER}\n\n${content}`; +} + +/** + * Result of creating a single changeset + */ +export interface ChangesetResult { + /** The changeset ID */ + id: string; + /** Whether this changeset replaced an existing auto-generated one */ + recreated: boolean; } /** * Create changesets for changed packages + * If auto-generated changesets already exist for the same packages, they will be removed first. + * @returns Array of created changesets with recreation status */ export async function createChangesets( changedPackages: PublicChangedPackage[], releaseType: "patch" | "minor" | "major", cwd: string -): Promise { +): Promise { if (changedPackages.length === 0) { return []; } - const changesetIds: string[] = []; + // First, find existing auto-generated changesets for each package + const changesets = await readChangesets(cwd); + const existingByPackage = new Map(); + + for (const changeset of changesets) { + if (!isAutoGeneratedChangeset(changeset.summary)) { + continue; + } + for (const release of changeset.releases) { + existingByPackage.set(release.name, changeset.id); + } + } + + const results: ChangesetResult[] = []; for (const changedPackage of changedPackages) { + const packageName = changedPackage.package.packageJson.name; + const existingId = existingByPackage.get(packageName); + + // Remove existing auto-generated changeset if it exists + if (existingId) { + const changesetPath = path.join(cwd, ".changeset", `${existingId}.md`); + await fs.rm(changesetPath); + } + const summary = generateSummaryFromChanges( changedPackage.dependencyChanges ); @@ -67,7 +116,7 @@ export async function createChangesets( summary, releases: [ { - name: changedPackage.package.packageJson.name, + name: packageName, type: releaseType, }, ], @@ -75,8 +124,11 @@ export async function createChangesets( cwd ); - changesetIds.push(changesetId); + results.push({ + id: changesetId, + recreated: existingId !== undefined, + }); } - return changesetIds; + return results; } diff --git a/src/cli.ts b/src/cli.ts index 947e7d9..4e5517d 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,5 +1,9 @@ #!/usr/bin/env node -import { defineCommand, runMain } from "citty"; +import * as pkg from "../package.json"; +import fs from "node:fs"; +import path from "node:path"; +import { cli, define } from "gunshi"; +import { consola } from "consola"; import { DependencyChangeAnalyzer } from "./dependency"; import { createChangesets } from "./changeset"; import { GitClientAdapter } from "./git"; @@ -21,41 +25,58 @@ function parseGitRange(range: string): { from: string; to: string } { return { from: range, to: "HEAD" }; } -const main = defineCommand({ - meta: { - name: "simple-dependabot-changeset", - version: "1.0.0", - description: "Generate changesets from dependency changes in Git commits", - }, +/** + * Check if .changeset directory exists + */ +function hasChangesetDirectory(cwd: string): boolean { + return fs.existsSync(path.join(cwd, ".changeset")); +} + +const command = define({ + toKebab: true, args: { range: { - type: "positional", + type: "string", + short: "r", description: "Git commit range (e.g., 'main..HEAD', 'a1b2c3..d4e5f6'). Defaults to 'main..HEAD' for dependabot branches.", - required: false, default: "main..HEAD", }, releaseType: { type: "string", - description: "Release type for changesets", + short: "t", + description: "Release type for changesets (patch|minor|major)", default: "patch", - valueHint: "patch|minor|major", }, cwd: { type: "string", + short: "c", description: "Working directory", default: process.cwd(), }, dryRun: { type: "boolean", + short: "d", description: "Preview changes without creating changesets", default: false, }, }, - async run({ args }) { - const { range, releaseType, cwd, dryRun } = args; + async run(ctx) { + const { + range = "main..HEAD", + releaseType = "patch", + cwd = process.cwd(), + dryRun = false, + } = ctx.values; const { from, to } = parseGitRange(range); + // Check if .changeset directory exists + if (!hasChangesetDirectory(cwd)) { + throw new Error( + "No .changeset directory found. Please initialize changesets first with `npx @changesets/cli init`." + ); + } + // Validate release type if (!["patch", "minor", "major"].includes(releaseType)) { throw new Error( @@ -99,4 +120,12 @@ const main = defineCommand({ }, }); -runMain(main); +await cli(process.argv.slice(2), command, { + name: "deps2changesets", + version: pkg.version, + description: "Generate changesets from dependency changes in Git commits", + onErrorCommand: (_ctx, error) => { + consola.error(error.message); + process.exit(1); + }, +}); diff --git a/src/dependency.test.ts b/src/dependency.test.ts index 40c461e..8be39cb 100644 --- a/src/dependency.test.ts +++ b/src/dependency.test.ts @@ -5,7 +5,13 @@ import type { IGitClient } from "./interfaces"; // Mock the dependencies vi.mock("@changesets/write"); +vi.mock("@changesets/read"); vi.mock("@manypkg/get-packages"); +vi.mock("node:fs/promises", () => ({ + default: { + rm: vi.fn().mockResolvedValue(undefined), + }, +})); describe("DependencyChangeAnalyzer", () => { let mockGitClient: IGitClient; @@ -177,6 +183,9 @@ describe("createChangesets", () => { const { default: writeChangeset } = await import("@changesets/write"); vi.mocked(writeChangeset).mockResolvedValue("changeset-id"); + + const { default: readChangesets } = await import("@changesets/read"); + vi.mocked(readChangesets).mockResolvedValue([]); }); it("should create changeset for changed packages", async () => { @@ -210,7 +219,8 @@ describe("createChangesets", () => { "/test" ); expect(result).toHaveLength(1); - expect(result[0]).toBe("changeset-id"); + expect(result[0].id).toBe("changeset-id"); + expect(result[0].recreated).toBe(false); }); it("should return empty array when no packages to create changesets for", async () => { @@ -262,4 +272,81 @@ describe("createChangesets", () => { const { default: writeChangeset } = await import("@changesets/write"); expect(writeChangeset).toHaveBeenCalledTimes(2); }); + + it("should remove existing auto-generated changesets before creating new ones", async () => { + const { AUTO_GENERATED_BANNER } = await import("./changeset"); + const { default: readChangesets } = await import("@changesets/read"); + + // Setup mock to return existing auto-generated changeset + vi.mocked(readChangesets).mockResolvedValue([ + { + id: "existing-changeset", + summary: `${AUTO_GENERATED_BANNER}\n\nUpdated lodash (^4.17.19 -> ^4.17.20)`, + releases: [{ name: "test-package", type: "patch" }], + }, + ]); + + const changedPackages = [ + { + private: false as const, + package: { + dir: "/test", + relativeDir: ".", + packageJson: { name: "test-package", version: "1.0.0" }, + }, + dependencyChanges: [ + { + name: "lodash", + type: "updated" as const, + oldVersion: "^4.17.20", + newVersion: "^4.17.21", + }, + ], + }, + ]; + + const result = await createChangesets(changedPackages, "patch", "/test"); + + // Should have created a new changeset with recreated flag + expect(result).toHaveLength(1); + expect(result[0].recreated).toBe(true); + }); + + it("should not remove changesets without auto-generated banner", async () => { + const { default: readChangesets } = await import("@changesets/read"); + + // Setup mock to return manually created changeset (no banner) + vi.mocked(readChangesets).mockResolvedValue([ + { + id: "manual-changeset", + summary: "Manually created changeset for test-package", + releases: [{ name: "test-package", type: "patch" }], + }, + ]); + + const changedPackages = [ + { + private: false as const, + package: { + dir: "/test", + relativeDir: ".", + packageJson: { name: "test-package", version: "1.0.0" }, + }, + dependencyChanges: [ + { + name: "lodash", + type: "updated" as const, + oldVersion: "^4.17.20", + newVersion: "^4.17.21", + }, + ], + }, + ]; + + const result = await createChangesets(changedPackages, "patch", "/test"); + + // Should have created a new changeset (not recreated since manual one wasn't removed) + expect(result).toHaveLength(1); + expect(result[0].recreated).toBe(false); + }); });