├── .github ├── FUNDING.yml └── workflows │ └── workflow.yml ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── bin └── sandhog.js ├── documentation └── asset │ ├── feature.svg │ ├── logo.png │ └── logo.svg ├── eslint.config.js ├── package.json ├── pnpm-lock.yaml ├── sandhog.config.js ├── src ├── badge │ ├── badge-color.ts │ ├── badge-kind.ts │ ├── ensure-badge-kind │ │ └── ensure-badge-kind.ts │ ├── generate-badge-url │ │ ├── generate-badge-options.ts │ │ └── generate-badge-url.ts │ └── get-badges │ │ ├── get-badges-options.ts │ │ ├── get-badges-result.ts │ │ └── get-badges.ts ├── cli │ ├── command │ │ ├── all │ │ │ └── all-command.ts │ │ ├── coc │ │ │ └── coc-command.ts │ │ ├── contributing │ │ │ └── contributing-command.ts │ │ ├── create-command │ │ │ ├── create-command-options.ts │ │ │ └── create-command.ts │ │ ├── create-program.ts │ │ ├── funding │ │ │ └── funding-command.ts │ │ ├── help │ │ │ └── help-command.ts │ │ ├── license │ │ │ └── license-command.ts │ │ ├── readme │ │ │ └── readme-command.ts │ │ └── shared │ │ │ └── shared-options.ts │ ├── index.ts │ └── task │ │ ├── coc │ │ ├── coc-task-options.ts │ │ └── coc-task.ts │ │ ├── contributing │ │ ├── contributing-task-options.ts │ │ └── contributing-task.ts │ │ ├── funding │ │ ├── funding-task-options.ts │ │ └── funding-task.ts │ │ ├── generate-task-options │ │ ├── generate-task-options.ts │ │ ├── sanitized-shared-options.ts │ │ └── select-log-level │ │ │ ├── select-log-level-options.ts │ │ │ └── select-log-level.ts │ │ ├── license │ │ ├── license-task-options.ts │ │ └── license-task.ts │ │ ├── readme │ │ ├── readme-task-options.ts │ │ └── readme-task.ts │ │ └── task-options.ts ├── coc │ └── generate-coc │ │ ├── generate-coc-options.ts │ │ └── generate-coc.ts ├── code-style │ ├── code-style-kind.ts │ ├── find-code-style │ │ ├── code-style.ts │ │ ├── find-code-styles-options.ts │ │ └── find-code-styles.ts │ └── get-code-style-for-code-style-kind │ │ └── get-code-style-for-code-style-kind.ts ├── config │ ├── default-sandhog-config.ts │ ├── find-config │ │ ├── find-config-options.ts │ │ └── find-config.ts │ ├── get-config │ │ └── get-config.ts │ └── sandhog-config.ts ├── constant │ └── constant.ts ├── contributing │ └── generate-contributing │ │ ├── generate-contributing-options.ts │ │ └── generate-contributing.ts ├── contributor │ ├── contributor.ts │ ├── ensure-contributor.ts │ ├── format-contributor.ts │ └── get-contributors-from-package.ts ├── file-system │ └── file-system.ts ├── funding │ └── generate-funding │ │ ├── generate-funding-options.ts │ │ └── generate-funding.ts ├── index.ts ├── license │ ├── detect-license │ │ └── detect-license.ts │ ├── find-license │ │ ├── find-license-options.ts │ │ └── find-license.ts │ ├── generate-license │ │ ├── generate-license-options.ts │ │ ├── generate-license.ts │ │ └── licenses │ │ │ ├── generate-agpl-3.0.ts │ │ │ ├── generate-apache-2.0.ts │ │ │ ├── generate-artistic-2.0.ts │ │ │ ├── generate-bsd-2-clause.ts │ │ │ ├── generate-bsd-3-clause.ts │ │ │ ├── generate-cc-by-4.0.ts │ │ │ ├── generate-cc-by-sa-4.0.ts │ │ │ ├── generate-epl-1.0.ts │ │ │ ├── generate-gpl-2.0.ts │ │ │ ├── generate-gpl-3.0.ts │ │ │ ├── generate-lgpl-3.0.ts │ │ │ ├── generate-mit.ts │ │ │ ├── generate-mpl-2.0.ts │ │ │ └── generate-zlib.ts │ ├── get-license-for-license-name │ │ └── get-license-for-license-name.ts │ ├── get-license │ │ ├── get-license-options.ts │ │ └── get-license.ts │ ├── is-known-license-name.ts │ ├── is-known-license.ts │ ├── license-name.ts │ └── license.ts ├── logger │ ├── i-logger.ts │ ├── log-level-kind.ts │ └── logger.ts ├── markdown │ ├── format-image │ │ ├── format-image-options.ts │ │ └── format-image.ts │ └── format-url │ │ ├── format-url-options.ts │ │ └── format-url.ts ├── package │ ├── find-package │ │ ├── find-package-options.ts │ │ ├── find-package-result.ts │ │ └── find-package.ts │ ├── package.ts │ └── take-github-repository-name │ │ └── take-github-repository-name.ts ├── readme │ ├── generate-readme │ │ ├── generate-readme-context.ts │ │ ├── generate-readme-options.ts │ │ └── generate-readme.ts │ └── get-shadow-section-mark │ │ └── get-shadow-section-mark.ts ├── section │ ├── ensure-section-kind │ │ └── ensure-section-kind.ts │ ├── get-relevant-sections │ │ ├── get-relevant-sections-options.ts │ │ └── get-relevant-sections.ts │ └── section-kind.ts ├── typings.d.ts └── util │ ├── list-format │ └── list-format.ts │ ├── path │ ├── is-lib.ts │ └── strip-leading-if-matched.ts │ └── prompt │ ├── confirm.ts │ └── radio.ts ├── test └── config.test.ts └── tsconfig.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: wessberg 2 | patreon: wessberg 3 | -------------------------------------------------------------------------------- /.github/workflows/workflow.yml: -------------------------------------------------------------------------------- 1 | name: Main Workflow 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | run: 7 | name: Run 8 | 9 | runs-on: ${{ matrix.os }} 10 | 11 | strategy: 12 | matrix: 13 | os: [windows-latest, macos-latest, ubuntu-latest] 14 | node: [21, 22] 15 | 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@master 19 | 20 | - name: Setup Node.js 21 | uses: actions/setup-node@master 22 | with: 23 | node-version: ${{ matrix.node }} 24 | 25 | - name: Setup pnpm 26 | run: npm install pnpm -g 27 | 28 | - name: Install 29 | run: pnpm install 30 | 31 | - name: Lint 32 | run: pnpm run lint 33 | 34 | - name: Test 35 | run: pnpm test 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .nyc_output 2 | .idea/ 3 | .vscode 4 | dist/ 5 | src/dist 6 | typings 7 | typings_backup/ 8 | typings_backup.* 9 | /node_modules/ 10 | *.js.map 11 | src/**/*.js 12 | test/**/*.js 13 | npm-debug.log 14 | coverage 15 | /compiled/ 16 | npm-debug* 17 | /.rpt2_cache 18 | /.idea/ 19 | /.cache/ 20 | /.vscode/ 21 | *.log 22 | /logs/ 23 | npm-debug.log* 24 | /lib-cov/ 25 | /coverage/ 26 | /.nyc_output/ 27 | /.grunt/ 28 | *.7z 29 | *.dmg 30 | *.gz 31 | *.iso 32 | *.jar 33 | *.rar 34 | *.tar 35 | *.zip 36 | .tgz 37 | .env 38 | .DS_Store 39 | .DS_Store? 40 | ._* 41 | .Spotlight-V100 42 | .Trashes 43 | ehthumbs.db 44 | Thumbs.db 45 | *.pem 46 | *.p12 47 | *.crt 48 | *.csr 49 | /dist/ 50 | package-lock.json 51 | lerna-debug.log -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | pretty-quick --staged 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [3.0.2](https://github.com/wessberg/sandhog/compare/v3.0.1...v3.0.2) (2024-10-17) 2 | 3 | 4 | 5 | ## [3.0.1](https://github.com/wessberg/sandhog/compare/v3.0.0...v3.0.1) (2024-10-11) 6 | 7 | 8 | 9 | # [3.0.0](https://github.com/wessberg/sandhog/compare/v2.0.2...v3.0.0) (2024-10-10) 10 | 11 | 12 | ### Features 13 | 14 | * migrate to Node 22 ([ddcb7fe](https://github.com/wessberg/sandhog/commit/ddcb7fe84def1dd721c30851ef2b86794291cd2b)) 15 | 16 | 17 | 18 | ## [2.0.2](https://github.com/wessberg/sandhog/compare/v2.0.1...v2.0.2) (2022-05-31) 19 | 20 | 21 | ### Bug Fixes 22 | 23 | * convert rollup config to mjs format to instruct Rollup to treat the config as a pure ES module ([336167a](https://github.com/wessberg/sandhog/commit/336167abe14fd7bc01f1c22503e6243fdb7f07bd)) 24 | * use ansi-colors instead of Chalk for better CJS fallback support ([85e15d5](https://github.com/wessberg/sandhog/commit/85e15d5a5163f8f17323d2ca0dcfa315cd863d1d)) 25 | 26 | 27 | 28 | ## [2.0.1](https://github.com/wessberg/sandhog/compare/v2.0.0...v2.0.1) (2022-05-29) 29 | 30 | 31 | ### Bug Fixes 32 | 33 | * handle the case where a module has a default export when constructing a config ([9bf7416](https://github.com/wessberg/sandhog/commit/9bf7416a7fa381a28ae8b283dd3311c2b641d95d)) 34 | 35 | 36 | 37 | # [2.0.0](https://github.com/wessberg/sandhog/compare/v1.0.44...v2.0.0) (2022-05-29) 38 | 39 | 40 | ### Bug Fixes 41 | 42 | * ensure node v14.19.0 or newer is used ([af3fb55](https://github.com/wessberg/sandhog/commit/af3fb5513db3b37a23ef487aeda2f6bedb133248)) 43 | * remove internal usage of import assertion in async import for compatibility reasons ([f0fbac8](https://github.com/wessberg/sandhog/commit/f0fbac8a39f5d75ec31ac5b65393319904b6ecc3)) 44 | 45 | 46 | 47 | ## [1.0.44](https://github.com/wessberg/sandhog/compare/v1.0.43...v1.0.44) (2022-05-28) 48 | 49 | 50 | ### Features 51 | 52 | * support .cjs and .mjs configs ([6a4208f](https://github.com/wessberg/sandhog/commit/6a4208f08a65b05005dac570281a06625a47adab)) 53 | 54 | 55 | 56 | ## [1.0.43](https://github.com/wessberg/sandhog/compare/v1.0.42...v1.0.43) (2021-11-17) 57 | 58 | 59 | 60 | ## [1.0.42](https://github.com/wessberg/sandhog/compare/v1.0.41...v1.0.42) (2021-11-16) 61 | 62 | 63 | ### Features 64 | 65 | * add support and handling for peerDependenciesMeta in generated README files ([343e3de](https://github.com/wessberg/sandhog/commit/343e3dea945d2872ce2d7a677b81f6add0a10918)) 66 | 67 | 68 | 69 | ## [1.0.41](https://github.com/wessberg/sandhog/compare/v1.0.40...v1.0.41) (2021-05-28) 70 | 71 | 72 | ### Features 73 | 74 | * export 'SandhogConfig' interface ([6616d2c](https://github.com/wessberg/sandhog/commit/6616d2c44a1ad92805a4a0ed4ace12335d57a663)) 75 | 76 | 77 | 78 | ## [1.0.40](https://github.com/wessberg/sandhog/compare/v1.0.38...v1.0.40) (2021-05-26) 79 | 80 | 81 | 82 | ## [1.0.38](https://github.com/wessberg/sandhog/compare/v1.0.37...v1.0.38) (2021-05-18) 83 | 84 | 85 | 86 | ## [1.0.37](https://github.com/wessberg/sandhog/compare/v1.0.36...v1.0.37) (2021-05-18) 87 | 88 | 89 | ### Bug Fixes 90 | 91 | * **options:** fix regression where CLI arguments were ignored. ([d1d6243](https://github.com/wessberg/sandhog/commit/d1d6243d11eb85560a9aac207f3b49aa0a9792e0)) 92 | 93 | 94 | 95 | ## [1.0.36](https://github.com/wessberg/sandhog/compare/v1.0.34...v1.0.36) (2021-03-16) 96 | 97 | 98 | ### Bug Fixes 99 | 100 | * **style:** don't match the Standard code style if the relevant ESLint rules are turned off ([7f58766](https://github.com/wessberg/sandhog/commit/7f5876633e20079c4711ba76fc3d5f7d58a8d485)) 101 | 102 | 103 | 104 | ## [1.0.34](https://github.com/wessberg/sandhog/compare/v1.0.33...v1.0.34) (2021-03-16) 105 | 106 | 107 | ### Features 108 | 109 | * **backers:** move backers section up in the README template ([1d1f609](https://github.com/wessberg/sandhog/commit/1d1f6094ee82c0b5421b021f10f506a450a49d59)) 110 | 111 | 112 | 113 | ## [1.0.33](https://github.com/wessberg/sandhog/compare/v1.0.32...v1.0.33) (2021-03-16) 114 | 115 | 116 | ### Bug Fixes 117 | 118 | * **patreon:** update patreon shield URL ([d6e0360](https://github.com/wessberg/sandhog/commit/d6e0360d26d8cc5196902d0df7b1499d68a80a83)) 119 | 120 | 121 | 122 | ## [1.0.32](https://github.com/wessberg/sandhog/compare/v1.0.31...v1.0.32) (2020-09-30) 123 | 124 | 125 | 126 | ## [1.0.31](https://github.com/wessberg/sandhog/compare/v1.0.30...v1.0.31) (2020-09-07) 127 | 128 | 129 | ### Bug Fixes 130 | 131 | * **badges:** ensure that all badges are rounded ([7fa4ac6](https://github.com/wessberg/sandhog/commit/7fa4ac673223bbd0a8b2af61139371858c6d5c08)) 132 | 133 | 134 | 135 | ## [1.0.30](https://github.com/wessberg/sandhog/compare/v1.0.29...v1.0.30) (2020-07-03) 136 | 137 | 138 | ### Bug Fixes 139 | 140 | * fix singular formatting of peer dependencies ([1e4e553](https://github.com/wessberg/sandhog/commit/1e4e55355dca408b5e5842be86dfdae479567694)) 141 | * **readme:** fix singular variant of peer dependencies install instructions ([9514f82](https://github.com/wessberg/sandhog/commit/9514f820b5cea55f138955049b001a8e6f8452d7)) 142 | 143 | 144 | 145 | ## [1.0.29](https://github.com/wessberg/sandhog/compare/v1.0.28...v1.0.29) (2020-03-13) 146 | 147 | 148 | ### Bug Fixes 149 | 150 | * **formatting:** fix list formatting for arrays with 2 elements ([8273ca9](https://github.com/wessberg/sandhog/commit/8273ca94228861d13ab69b7104aabf93f10a0dcd)) 151 | 152 | 153 | 154 | ## [1.0.28](https://github.com/wessberg/sandhog/compare/v1.0.27...v1.0.28) (2020-03-13) 155 | 156 | 157 | ### Bug Fixes 158 | 159 | * **formatting:** fix list formatting for arrays with 2 elements ([2c3d930](https://github.com/wessberg/sandhog/commit/2c3d930076306186eb1aa0f0058d3ffda2b4b13c)) 160 | 161 | 162 | 163 | ## [1.0.27](https://github.com/wessberg/sandhog/compare/v1.0.26...v1.0.27) (2020-03-13) 164 | 165 | 166 | 167 | ## [1.0.26](https://github.com/wessberg/sandhog/compare/v1.0.25...v1.0.26) (2020-03-13) 168 | 169 | 170 | ### Features 171 | 172 | * add support for 'isDevelopmentPackage' option that adds '--save-dev' and '--dev' options to generated install commands inside generated READMEs. ([fc14100](https://github.com/wessberg/sandhog/commit/fc14100188e44330b3a9b445d07ab0d531108fe2)) 173 | 174 | 175 | 176 | ## [1.0.25](https://github.com/wessberg/sandhog/compare/v1.0.24...v1.0.25) (2020-02-29) 177 | 178 | 179 | ### Features 180 | 181 | * Have maintainer and backer badges link to the related URL if any is given ([48fa456](https://github.com/wessberg/sandhog/commit/48fa4560f1e0ddf72f8836e53966c855f6843a7e)) 182 | 183 | 184 | 185 | ## [1.0.24](https://github.com/wessberg/sandhog/compare/v1.0.23...v1.0.24) (2020-02-28) 186 | 187 | 188 | ### Features 189 | 190 | * add support for manual sponsors ([8c53b40](https://github.com/wessberg/sandhog/commit/8c53b4019b49fa31fa900d8c3b4bd2b1701bd0a6)) 191 | 192 | 193 | 194 | ## [1.0.23](https://github.com/wessberg/sandhog/compare/v1.0.22...v1.0.23) (2019-11-16) 195 | 196 | 197 | 198 | ## [1.0.22](https://github.com/wessberg/sandhog/compare/v1.0.21...v1.0.22) (2019-11-11) 199 | 200 | 201 | 202 | ## [1.0.21](https://github.com/wessberg/sandhog/compare/v1.0.20...v1.0.21) (2019-11-10) 203 | 204 | 205 | 206 | ## [1.0.20](https://github.com/wessberg/sandhog/compare/v1.0.19...v1.0.20) (2019-10-31) 207 | 208 | 209 | ### Features 210 | 211 | * **patreon:** accepts patreon username as part of config ([d108ed2](https://github.com/wessberg/sandhog/commit/d108ed2afd7d68aed275177725e3d9a7f1b945c5)) 212 | 213 | 214 | 215 | ## [1.0.19](https://github.com/wessberg/sandhog/compare/v1.0.18...v1.0.19) (2019-06-21) 216 | 217 | 218 | 219 | ## [1.0.18](https://github.com/wessberg/sandhog/compare/v1.0.17...v1.0.18) (2019-05-29) 220 | 221 | 222 | ### Features 223 | 224 | * **funding:** adds two new commands: 'funding', which generates a config-driven .github/FUNDING.yml file for supporting Github Sponsors, as well as the 'all' command, which generates all of the files that scaffold supports in a single command ([dace835](https://github.com/wessberg/sandhog/commit/dace8359c37c316b977ac5f2d6c834b9f369c13d)) 225 | 226 | 227 | 228 | ## [1.0.17](https://github.com/wessberg/sandhog/compare/v1.0.16...v1.0.17) (2019-02-09) 229 | 230 | 231 | ### Bug Fixes 232 | 233 | * **readme:** fixes FAQ section heading size ([f2ae74c](https://github.com/wessberg/sandhog/commit/f2ae74c00d03887e869c98c9bd58b018dd75527e)) 234 | 235 | 236 | 237 | ## [1.0.16](https://github.com/wessberg/sandhog/compare/v1.0.15...v1.0.16) (2019-02-08) 238 | 239 | 240 | ### Bug Fixes 241 | 242 | * **badge:** fixes an issue where the NPM badge would not be generated ([5e970d9](https://github.com/wessberg/sandhog/commit/5e970d968f0db64c96700655ccde23875b3de765)) 243 | 244 | 245 | 246 | ## [1.0.15](https://github.com/wessberg/sandhog/compare/v1.0.14...v1.0.15) (2019-02-07) 247 | 248 | 249 | ### Bug Fixes 250 | 251 | * **badges:** fixes an issue where the dependencies badge would be wrong for non-scoped packages ([e6b05fe](https://github.com/wessberg/sandhog/commit/e6b05fece0c705e940056c1b1f359c43d7019a29)) 252 | 253 | 254 | 255 | ## [1.0.14](https://github.com/wessberg/sandhog/compare/v1.0.13...v1.0.14) (2019-02-07) 256 | 257 | 258 | 259 | ## [1.0.13](https://github.com/wessberg/sandhog/compare/v1.0.10...v1.0.13) (2019-02-06) 260 | 261 | 262 | ### Bug Fixes 263 | 264 | * **chore:** updates dependencies ([b181ad8](https://github.com/wessberg/sandhog/commit/b181ad8111aadec5805a4091d06b571a0a33f060)) 265 | * **chore:** updates dependencies ([81bea0a](https://github.com/wessberg/sandhog/commit/81bea0af29049a0bb0102f43414c0a12985a3d20)) 266 | * **readme:** removes emojis from headings which was hard to generate relative links to ([5a1f250](https://github.com/wessberg/sandhog/commit/5a1f250f2129779417c28a2a73c2a5ae0cf1f463)) 267 | 268 | 269 | 270 | ## [1.0.10](https://github.com/wessberg/sandhog/compare/v1.0.9...v1.0.10) (2019-02-05) 271 | 272 | 273 | ### Bug Fixes 274 | 275 | * **readme:** fixes an issue with the generation of the Patreon pledger badge ([22a2149](https://github.com/wessberg/sandhog/commit/22a2149b900ac4a77e2a67f0323dabed4c0b384c)) 276 | 277 | 278 | 279 | ## [1.0.9](https://github.com/wessberg/sandhog/compare/v1.0.8...v1.0.9) (2019-02-04) 280 | 281 | 282 | ### Bug Fixes 283 | 284 | * **lint:** fixes lint issues ([8742c98](https://github.com/wessberg/sandhog/commit/8742c98db7fb08bf91955626032fe1e0afc2946f)) 285 | 286 | 287 | ### Features 288 | 289 | * **core:** major rewrite ([f50d7a5](https://github.com/wessberg/sandhog/commit/f50d7a592e69902931cba65e50fa48522883c117)) 290 | * **core:** major rewrite ([f510154](https://github.com/wessberg/sandhog/commit/f510154f3ad328ca1ee140677694e86933e481e8)) 291 | 292 | 293 | 294 | ## [1.0.8](https://github.com/wessberg/sandhog/compare/v1.0.7...v1.0.8) (2019-01-21) 295 | 296 | 297 | 298 | ## [1.0.7](https://github.com/wessberg/sandhog/compare/v1.0.6...v1.0.7) (2019-01-21) 299 | 300 | 301 | 302 | ## [1.0.6](https://github.com/wessberg/sandhog/compare/v1.0.5...v1.0.6) (2019-01-21) 303 | 304 | 305 | 306 | ## [1.0.5](https://github.com/wessberg/sandhog/compare/v1.0.4...v1.0.5) (2018-12-13) 307 | 308 | 309 | 310 | ## [1.0.4](https://github.com/wessberg/sandhog/compare/v1.0.3...v1.0.4) (2018-09-11) 311 | 312 | 313 | 314 | ## [1.0.3](https://github.com/wessberg/sandhog/compare/v1.0.2...v1.0.3) (2018-09-11) 315 | 316 | 317 | 318 | ## [1.0.2](https://github.com/wessberg/sandhog/compare/v1.0.1...v1.0.2) (2018-09-11) 319 | 320 | 321 | 322 | ## [1.0.1](https://github.com/wessberg/sandhog/compare/v1.0.0...v1.0.1) (2018-08-30) 323 | 324 | 325 | 326 | # [1.0.0](https://github.com/wessberg/sandhog/compare/v0.1.0...v1.0.0) (2018-07-02) 327 | 328 | 329 | ### Bug Fixes 330 | 331 | * Fixed linting errors ([55a8746](https://github.com/wessberg/sandhog/commit/55a87460787f70c2e7245ce2c5009c01575e4ddb)) 332 | 333 | 334 | 335 | # [0.1.0](https://github.com/wessberg/sandhog/compare/v0.0.8...v0.1.0) (2018-07-02) 336 | 337 | 338 | 339 | ## [0.0.8](https://github.com/wessberg/sandhog/compare/v0.0.7...v0.0.8) (2018-07-02) 340 | 341 | 342 | 343 | ## [0.0.7](https://github.com/wessberg/sandhog/compare/v0.0.6...v0.0.7) (2018-07-02) 344 | 345 | 346 | 347 | ## [0.0.6](https://github.com/wessberg/sandhog/compare/v0.0.5...v0.0.6) (2018-06-30) 348 | 349 | 350 | 351 | ## [0.0.5](https://github.com/wessberg/sandhog/compare/v0.0.4...v0.0.5) (2018-06-30) 352 | 353 | 354 | 355 | ## [0.0.4](https://github.com/wessberg/sandhog/compare/v0.0.3...v0.0.4) (2018-06-30) 356 | 357 | 358 | 359 | ## [0.0.3](https://github.com/wessberg/sandhog/compare/v0.0.2...v0.0.3) (2018-06-30) 360 | 361 | 362 | 363 | ## [0.0.2](https://github.com/wessberg/sandhog/compare/v0.0.1...v0.0.2) (2018-06-30) 364 | 365 | 366 | 367 | ## 0.0.1 (2018-06-30) 368 | 369 | 370 | 371 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | Contributor Covenant Code of Conduct 2 | 3 | Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting any of the code of conduct enforcers: . 59 | All complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | Attribution 69 | 70 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4, 71 | available at http://contributor-covenant.org/version/1/4/ 72 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | You are more than welcome to contribute to `undefined` in any way you please, including: 2 | 3 | - Updating documentation. 4 | - Fixing spelling and grammar 5 | - Adding tests 6 | - Fixing issues and suggesting new features 7 | - Blogging, tweeting, and creating tutorials about `undefined` 8 | 9 | - Submit an issue or a Pull Request 10 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2024 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /bin/sandhog.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | "use strict"; 3 | import "../dist/cli/index.js"; 4 | process.title = "sandhog"; -------------------------------------------------------------------------------- /documentation/asset/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wessberg/sandhog/a651056337c3eac8edbb4ed86695ba4006608a13/documentation/asset/logo.png -------------------------------------------------------------------------------- /documentation/asset/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import shared from "@wessberg/ts-config/eslint.config.js"; 2 | 3 | export default [ 4 | ...shared, 5 | { 6 | rules: {} 7 | } 8 | ]; 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sandhog", 3 | "version": "3.0.2", 4 | "bin": { 5 | "sandhog": "bin/sandhog.js" 6 | }, 7 | "files": [ 8 | "dist/**/*.*", 9 | "bin/**/*" 10 | ], 11 | "description": "A virtual Open Source project maintainer", 12 | "license": "MIT", 13 | "scripts": { 14 | "generate:changelog": "standard-changelog --first-release", 15 | "generate:sandhog": "node dist/cli/index.js all --yes", 16 | "generate:all": "pnpm run generate:sandhog && pnpm run generate:changelog", 17 | "clean": "rimraf dist", 18 | "lint": "tsc --noEmit && eslint \"src/**/*.ts\" --color --fix", 19 | "prettier": "prettier --write \"{src,test,documentation}/**/*.{js,ts,json,html,xml,css,md}\"", 20 | "test": "node --import tsx --test \"./test/**/*.test.ts\"", 21 | "prebuild": "pnpm run clean", 22 | "build": "pnpm run prebuild && tsup --entry=\"src/index.ts\" --entry=\"src/cli/index.ts\" --sourcemap --dts --format esm", 23 | "preversion": "pnpm run lint && pnpm run build", 24 | "version": "pnpm run preversion && pnpm run generate:all && git add .", 25 | "release": "np --no-cleanup --no-yarn", 26 | "update:check": "pnpx npm-check-updates --dep dev,prod", 27 | "update:commit": "pnpx npm-check-updates -u --dep dev,prod && pnpm update && pnpm install" 28 | }, 29 | "keywords": [ 30 | "open source", 31 | "project", 32 | "sandhog", 33 | "code of conduct", 34 | "contributing", 35 | "readme", 36 | "codegen", 37 | "maintainers", 38 | "backers" 39 | ], 40 | "dependencies": { 41 | "@inquirer/prompts": "7.0.0", 42 | "ansi-colors": "^4.1.3", 43 | "commander": "^12.1.0", 44 | "eslint": "^9.12.0", 45 | "json5": "^2.2.3", 46 | "@effect/markdown-toc": "0.1.0", 47 | "prettier": "^3.3.3", 48 | "yaml": "^2.6.0", 49 | "helpertypes": "^0.0.19", 50 | "crosspath": "2.0.0" 51 | }, 52 | "devDependencies": { 53 | "@types/eslint": "^9.6.1", 54 | "@types/node": "^22.7.6", 55 | "@wessberg/ts-config": "^5.0.20", 56 | "@wessberg/prettier-config": "1.0.0", 57 | "@eslint/js": "9.12.0", 58 | "eslint": "^9.12.0", 59 | "eslint-config-prettier": "^9.1.0", 60 | "eslint-plugin-jsdoc": "^50.4.3", 61 | "eslint-plugin-prettier": "^5.2.1", 62 | "typescript-eslint": "^8.10.0", 63 | "rimraf": "^6.0.1", 64 | "husky": "^9.1.6", 65 | "lint-staged": "^15.2.10", 66 | "np": "10.0.7", 67 | "pnpm": "^9.12.2", 68 | "pretty-quick": "^4.0.0", 69 | "standard-changelog": "^6.0.0", 70 | "tsup": "^8.3.0", 71 | "tsx": "^4.19.1", 72 | "typescript": "5.6.3", 73 | "npm-check-updates": "17.1.4" 74 | }, 75 | "exports": { 76 | "import": "./dist/index.js" 77 | }, 78 | "type": "module", 79 | "types": "./dist/index.d.ts", 80 | "main": "./dist/index.js", 81 | "module": "./dist/index.js", 82 | "funding": { 83 | "type": "github", 84 | "url": "https://github.com/wessberg/sandhog?sponsor=1" 85 | }, 86 | "repository": { 87 | "type": "git", 88 | "url": "https://github.com/wessberg/sandhog.git" 89 | }, 90 | "bugs": { 91 | "url": "https://github.com/wessberg/sandhog/issues" 92 | }, 93 | "contributors": [ 94 | { 95 | "name": "Frederik Wessberg", 96 | "email": "frederikwessberg@hotmail.com", 97 | "url": "https://github.com/wessberg", 98 | "imageUrl": "https://avatars2.githubusercontent.com/u/20454213?s=460&v=4", 99 | "role": "Lead Developer", 100 | "twitter": "FredWessberg", 101 | "github": "wessberg" 102 | } 103 | ], 104 | "engines": { 105 | "node": ">=18.20.0" 106 | }, 107 | "lint-staged": { 108 | "*": "prettier --ignore-unknown --write" 109 | }, 110 | "prettier": "@wessberg/prettier-config" 111 | } 112 | -------------------------------------------------------------------------------- /sandhog.config.js: -------------------------------------------------------------------------------- 1 | import baseConfig from "@wessberg/ts-config/sandhog.config.js"; 2 | 3 | export default { 4 | ...baseConfig, 5 | isDevelopmentPackage: true, 6 | logo: { 7 | height: 200, 8 | url: "https://raw.githubusercontent.com/wessberg/sandhog/master/documentation/asset/logo.png" 9 | }, 10 | featureImage: { 11 | height: 300, 12 | url: "https://raw.githubusercontent.com/wessberg/sandhog/master/documentation/asset/feature.svg" 13 | } 14 | }; -------------------------------------------------------------------------------- /src/badge/badge-color.ts: -------------------------------------------------------------------------------- 1 | export type BadgeColor = "green" | "brightgreen" | "yellowgreen" | "yellow" | "orange" | "red" | "lightblue" | "blue"; 2 | -------------------------------------------------------------------------------- /src/badge/badge-kind.ts: -------------------------------------------------------------------------------- 1 | import type {ElementOf} from "helpertypes"; 2 | 3 | export const BADGE_KINDS = [ 4 | "downloads", 5 | "dependencies", 6 | "npm", 7 | "contributors", 8 | "license", 9 | "patreon", 10 | "open_collective_donate", 11 | "open_collective_backers", 12 | "open_collective_sponsors", 13 | "code_style" 14 | ] as const; 15 | export type BadgeKind = ElementOf; 16 | -------------------------------------------------------------------------------- /src/badge/ensure-badge-kind/ensure-badge-kind.ts: -------------------------------------------------------------------------------- 1 | import {listFormat} from "../../util/list-format/list-format.js"; 2 | import {type BadgeKind, BADGE_KINDS} from "../badge-kind.js"; 3 | 4 | /** 5 | * Ensures that the given input is a proper BadgeKind 6 | */ 7 | export function ensureBadgeKind(badgeKind: string): BadgeKind { 8 | if (typeof badgeKind !== "string") return badgeKind; 9 | if (BADGE_KINDS.some(key => key === badgeKind)) { 10 | return badgeKind as BadgeKind; 11 | } else { 12 | throw new TypeError(`Could not parse string: '${badgeKind}' as a BadgeKind. Possible values: ${listFormat(BADGE_KINDS, "and")}`); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/badge/generate-badge-url/generate-badge-options.ts: -------------------------------------------------------------------------------- 1 | import type {BadgeColor} from "../badge-color.js"; 2 | 3 | export interface GenerateBadgeOptions { 4 | subject: string; 5 | status: string; 6 | color: BadgeColor; 7 | } 8 | -------------------------------------------------------------------------------- /src/badge/generate-badge-url/generate-badge-url.ts: -------------------------------------------------------------------------------- 1 | import type {GenerateBadgeOptions} from "./generate-badge-options.js"; 2 | import {CONSTANT} from "../../constant/constant.js"; 3 | 4 | /** 5 | * Generates an URL for a badge based on the given options 6 | */ 7 | export function generateBadgeUrl({color, status, subject}: GenerateBadgeOptions): string { 8 | return `${CONSTANT.shieldRestEndpoint}/${subject}/${status}/${color}.svg`; 9 | } 10 | -------------------------------------------------------------------------------- /src/badge/get-badges/get-badges-options.ts: -------------------------------------------------------------------------------- 1 | import type {Package} from "../../package/package.js"; 2 | import type {SandhogConfig} from "../../config/sandhog-config.js"; 3 | import type {FindLicenseOptions} from "../../license/find-license/find-license-options.js"; 4 | 5 | export interface GetBadgesOptions extends FindLicenseOptions { 6 | pkg: Package; 7 | config: SandhogConfig; 8 | } 9 | -------------------------------------------------------------------------------- /src/badge/get-badges/get-badges-result.ts: -------------------------------------------------------------------------------- 1 | import type {BadgeKind} from "../badge-kind.js"; 2 | 3 | export type GetBadgesResult = {[Key in BadgeKind]?: string[]}; 4 | -------------------------------------------------------------------------------- /src/badge/get-badges/get-badges.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unnecessary-condition */ 2 | import type {GetBadgesOptions} from "./get-badges-options.js"; 3 | import type {GetBadgesResult} from "./get-badges-result.js"; 4 | import {findCodeStyles} from "../../code-style/find-code-style/find-code-styles.js"; 5 | import {CONSTANT} from "../../constant/constant.js"; 6 | import {formatUrl} from "../../markdown/format-url/format-url.js"; 7 | import {formatImage} from "../../markdown/format-image/format-image.js"; 8 | import {takeGithubRepositoryName} from "../../package/take-github-repository-name/take-github-repository-name.js"; 9 | import {findLicense} from "../../license/find-license/find-license.js"; 10 | import {getLicenseForLicenseName} from "../../license/get-license-for-license-name/get-license-for-license-name.js"; 11 | 12 | /** 13 | * Gets relevant badges based on the given options 14 | */ 15 | export async function getBadges(options: GetBadgesOptions): Promise { 16 | const result: GetBadgesResult = {}; 17 | const badgeOptions = options.config.readme.badges; 18 | const excluded = new Set(badgeOptions.exclude); 19 | 20 | const encodedName = options.pkg.name == null ? undefined : encodeURIComponent(options.pkg.name); 21 | const repoUrl = takeGithubRepositoryName(options.pkg); 22 | const encodedRepoUrl = repoUrl == null ? undefined : encodeURIComponent(repoUrl); 23 | 24 | // Unless explicitly excluded, and if possible, generate a badge for the amount of downloads for the project 25 | if (!excluded.has("downloads") && encodedName != null) { 26 | result.downloads = [ 27 | formatUrl({ 28 | url: `https://npmcharts.com/compare/${encodedName}?minimal=true`, 29 | inner: formatImage({ 30 | alt: "Downloads per month", 31 | url: `https://img.shields.io/npm/dm/${encodedName}.svg` 32 | }) 33 | }) 34 | ]; 35 | } 36 | 37 | // Unless explicitly excluded, and if possible, generate a badge for the NPM version 38 | if (!excluded.has("npm") && encodedName != null) { 39 | result.npm = [ 40 | formatUrl({ 41 | url: `https://www.npmjs.com/package/${encodedName}`, 42 | inner: formatImage({ 43 | url: `https://badge.fury.io/js/${encodedName}.svg`, 44 | alt: `NPM version` 45 | }) 46 | }) 47 | ]; 48 | } 49 | 50 | // Unless explicitly excluded, and if possible, generate a badge for the package dependencies 51 | if (!excluded.has("dependencies") && repoUrl != null && encodedRepoUrl != null) { 52 | result.dependencies = [ 53 | formatImage({ 54 | alt: `Dependencies`, 55 | url: `https://img.shields.io/librariesio/github/${encodedRepoUrl}.svg` 56 | }) 57 | ]; 58 | } 59 | 60 | // Unless explicitly excluded, and if possible, generate a badge for the package dependencies 61 | if (!excluded.has("contributors") && encodedName != null && repoUrl != null && encodedRepoUrl != null) { 62 | result.contributors = [ 63 | formatUrl({ 64 | url: `https://github.com/${repoUrl}/graphs/contributors`, 65 | inner: formatImage({ 66 | alt: `Contributors`, 67 | url: `https://img.shields.io/github/contributors/${encodedRepoUrl}.svg` 68 | }) 69 | }) 70 | ]; 71 | } 72 | 73 | // Unless explicitly excluded, and if possible, generate a badge for the project code style(s). 74 | if (!excluded.has("code_style")) { 75 | const codeStyles = await findCodeStyles(options); 76 | if (codeStyles.length > 0) { 77 | result.code_style = codeStyles.map(({kind, url, badgeUrl}) => 78 | formatUrl({ 79 | url, 80 | inner: formatImage({ 81 | alt: `code style: ${kind}`, 82 | url: badgeUrl 83 | }) 84 | }) 85 | ); 86 | } 87 | } 88 | 89 | // Unless explicitly excluded, and if possible, generate a badge for the project license 90 | if (!excluded.has("license")) { 91 | const licenseName = await findLicense(options); 92 | if (licenseName != null) { 93 | const {badgeUrl, url} = getLicenseForLicenseName(licenseName); 94 | result.license = [ 95 | formatUrl({ 96 | url, 97 | inner: formatImage({ 98 | alt: `License: ${licenseName}`, 99 | url: badgeUrl 100 | }) 101 | }) 102 | ]; 103 | } 104 | } 105 | 106 | // Unless explicitly excluded, and if possible, generate a badge for supporting on Patreon 107 | if (!excluded.has("patreon") && options.config.donate?.patreon?.userId != null) { 108 | result.patreon = [ 109 | formatUrl({ 110 | url: CONSTANT.patreonDonateUrl(options.config.donate.patreon.userId), 111 | inner: formatImage({ 112 | alt: `Support on Patreon`, 113 | url: `https://img.shields.io/badge/patreon-donate-green.svg` 114 | }) 115 | }) 116 | ]; 117 | } 118 | 119 | // Unless explicitly excluded, and if possible, generate a badge for supporting on Open Collective 120 | if (!excluded.has("open_collective_donate") && options.config.donate?.openCollective?.project != null) { 121 | result.open_collective_donate = [ 122 | formatUrl({ 123 | url: CONSTANT.openCollectiveDonateUrl(options.config.donate.openCollective.project), 124 | inner: formatImage({ 125 | alt: `Support on Open Collective`, 126 | url: `https://img.shields.io/badge/opencollective-donate-green.svg` 127 | }) 128 | }) 129 | ]; 130 | } 131 | 132 | // Unless explicitly excluded, and if possible, generate a badge for listing the amount of backers on Open Collective 133 | if (!excluded.has("open_collective_backers") && options.config.donate?.openCollective?.project != null) { 134 | result.open_collective_backers = [ 135 | formatUrl({ 136 | url: CONSTANT.openCollectiveContributorsUrl(options.config.donate.openCollective.project), 137 | inner: formatImage({ 138 | alt: `Backers on Open Collective`, 139 | url: `https://opencollective.com/${options.config.donate.openCollective.project}/backers/badge.svg` 140 | }) 141 | }) 142 | ]; 143 | } 144 | 145 | // Unless explicitly excluded, and if possible, generate a badge for listing the amount of sponsors on Open Collective 146 | if (!excluded.has("open_collective_sponsors") && options.config.donate?.openCollective?.project != null) { 147 | result.open_collective_sponsors = [ 148 | formatUrl({ 149 | url: CONSTANT.openCollectiveContributorsUrl(options.config.donate.openCollective.project), 150 | inner: formatImage({ 151 | alt: `Sponsors on Open Collective`, 152 | url: `https://opencollective.com/${options.config.donate.openCollective.project}/sponsors/badge.svg` 153 | }) 154 | }) 155 | ]; 156 | } 157 | 158 | return result; 159 | } 160 | -------------------------------------------------------------------------------- /src/cli/command/all/all-command.ts: -------------------------------------------------------------------------------- 1 | import type {Command} from "commander"; 2 | import {createCommand} from "../create-command/create-command.js"; 3 | import {SHARED_OPTIONS} from "../shared/shared-options.js"; 4 | import {generateTaskOptions} from "../../task/generate-task-options/generate-task-options.js"; 5 | import {getLicense} from "../../../license/get-license/get-license.js"; 6 | 7 | export function createAllCommand(program: Command) { 8 | return createCommand( 9 | program, 10 | { 11 | name: "all", 12 | description: `Generates all of the files sandhog supports in one command`, 13 | args: {}, 14 | options: { 15 | ...SHARED_OPTIONS 16 | } 17 | }, 18 | async args => { 19 | // Load the task 20 | const {cocTask} = await import("../../task/coc/coc-task.js"); 21 | const {contributingTask} = await import("../../task/contributing/contributing-task.js"); 22 | const {fundingTask} = await import("../../task/funding/funding-task.js"); 23 | const {licenseTask} = await import("../../task/license/license-task.js"); 24 | const {readmeTask} = await import("../../task/readme/readme-task.js"); 25 | 26 | // Prepare base options 27 | const taskOptions = await generateTaskOptions(args); 28 | 29 | // Execute it 30 | cocTask({ 31 | ...taskOptions 32 | }); 33 | 34 | contributingTask({ 35 | ...taskOptions 36 | }); 37 | 38 | fundingTask({ 39 | ...taskOptions 40 | }); 41 | 42 | licenseTask({ 43 | ...taskOptions, 44 | license: await getLicense(taskOptions) 45 | }); 46 | 47 | readmeTask({ 48 | ...taskOptions 49 | }); 50 | } 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /src/cli/command/coc/coc-command.ts: -------------------------------------------------------------------------------- 1 | import type {Command} from "commander"; 2 | import {createCommand} from "../create-command/create-command.js"; 3 | import {SHARED_OPTIONS} from "../shared/shared-options.js"; 4 | import {generateTaskOptions} from "../../task/generate-task-options/generate-task-options.js"; 5 | import {CONSTANT} from "../../../constant/constant.js"; 6 | 7 | export function createCocCommand(program: Command) { 8 | return createCommand( 9 | program, 10 | { 11 | name: "coc", 12 | description: `Generates a ${CONSTANT.codeOfConductFilename} file`, 13 | args: {}, 14 | options: { 15 | ...SHARED_OPTIONS 16 | } 17 | }, 18 | async args => { 19 | // Load the task 20 | const {cocTask} = await import("../../task/coc/coc-task.js"); 21 | // Execute it 22 | cocTask({ 23 | ...(await generateTaskOptions(args)) 24 | }); 25 | } 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/cli/command/contributing/contributing-command.ts: -------------------------------------------------------------------------------- 1 | import type {Command} from "commander"; 2 | import {createCommand} from "../create-command/create-command.js"; 3 | import {SHARED_OPTIONS} from "../shared/shared-options.js"; 4 | import {generateTaskOptions} from "../../task/generate-task-options/generate-task-options.js"; 5 | import {CONSTANT} from "../../../constant/constant.js"; 6 | 7 | export function createContributingCommand(program: Command) { 8 | return createCommand( 9 | program, 10 | { 11 | name: "contributing", 12 | description: `Generates a ${CONSTANT.contributingFilename} file`, 13 | args: {}, 14 | options: { 15 | ...SHARED_OPTIONS 16 | } 17 | }, 18 | async args => { 19 | // Load the task 20 | const {contributingTask} = await import("../../task/contributing/contributing-task.js"); 21 | // Execute it 22 | contributingTask({ 23 | ...(await generateTaskOptions(args)) 24 | }); 25 | } 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/cli/command/create-command/create-command-options.ts: -------------------------------------------------------------------------------- 1 | export type CommandOptionType = "string" | "number" | "boolean"; 2 | export type CommandArgType = "string" | "string[]"; 3 | 4 | export interface CommandOption { 5 | shortHand?: string; 6 | type: CommandOptionType; 7 | defaultValue?: unknown; 8 | description: string; 9 | } 10 | 11 | export type CommandOptions = Record; 12 | 13 | export interface CommandArg { 14 | type: CommandArgType; 15 | required: boolean; 16 | } 17 | 18 | export type CommandArgs = Record; 19 | 20 | export interface CreateCommandOptions { 21 | name: string; 22 | description: string; 23 | args: CommandArgs; 24 | options: CommandOptions; 25 | } 26 | 27 | export type CommandActionOptions = { 28 | [Key in keyof U]: U[Key]["type"] extends "number" ? number : U[Key]["type"] extends "boolean" ? boolean : string; 29 | } & {[Key in keyof J]: J[Key]["type"] extends "string[]" ? string[] : string}; 30 | 31 | export type CommandAction = (options: CommandActionOptions) => void; 32 | -------------------------------------------------------------------------------- /src/cli/command/create-command/create-command.ts: -------------------------------------------------------------------------------- 1 | import type {Command} from "commander"; 2 | import type {CommandAction, CommandActionOptions, CommandOptionType, CreateCommandOptions} from "./create-command-options.js"; 3 | 4 | /** 5 | * Coerces the given option value into an acceptable data type 6 | */ 7 | function coerceOptionValue(type: CommandOptionType, value: unknown): typeof type extends "boolean" ? boolean : typeof type extends "number" ? number : string { 8 | switch (type) { 9 | case "string": 10 | if (value === null) return "null"; 11 | else if (value === undefined) return "undefined"; 12 | return String(value); 13 | 14 | case "number": 15 | if (typeof value === "number") return value as unknown as string; 16 | else if (value === true) return 1 as unknown as string; 17 | else if (value === false) return 0 as unknown as string; 18 | return parseFloat(value as string) as unknown as string; 19 | case "boolean": 20 | if (value === "true" || value === "" || value === "1" || value === 1) return true as unknown as string; 21 | else if (value === "false" || value === "0" || value === 0) return false as unknown as string; 22 | return Boolean(value) as unknown as string; 23 | } 24 | } 25 | 26 | /** 27 | * Formats the given option flags 28 | */ 29 | function formatOptionFlags(shortHand: string | undefined, longHand: string): string { 30 | const formattedLongHand = `${longHand} [arg]`; 31 | return shortHand != null ? `-${shortHand}, --${formattedLongHand}` : `--${formattedLongHand}`; 32 | } 33 | 34 | /** 35 | * Formats the given command name, along with its arguments 36 | */ 37 | function formatCommandNameWithArgs(options: T): string { 38 | const formattedArgs = Object.entries(options.args) 39 | .map(([argName, {type, required}]) => { 40 | const left = required ? `<` : `[`; 41 | const right = required ? ">" : `]`; 42 | if (type === "string[]") { 43 | return `${left}${argName}...${right}`; 44 | } else { 45 | return `${left}${argName}${right}`; 46 | } 47 | }) 48 | .join(" "); 49 | return `${options.name} ${formattedArgs}`; 50 | } 51 | 52 | /** 53 | * Creates a new command 54 | */ 55 | export function createCommand(program: Command, options: T, action: CommandAction): void { 56 | // Add the command to the program 57 | const result = program.command(formatCommandNameWithArgs(options)).description(options.description); 58 | 59 | // Add options to the command 60 | Object.entries(options.options).forEach(([longhand, {shortHand, description, type, defaultValue}]) => { 61 | result.option(formatOptionFlags(shortHand, longhand), description, coerceOptionValue.bind(null, type), defaultValue); 62 | }); 63 | 64 | // Add the action to the command 65 | // Add the action to the command 66 | result.action((...args: unknown[]) => { 67 | const actionOptions = {} as CommandActionOptions; 68 | 69 | let offset = 0; 70 | 71 | for (const key of Object.keys(options.args)) { 72 | actionOptions[key as keyof CommandActionOptions] = args[offset++] as never; 73 | } 74 | 75 | Object.assign(actionOptions, args[offset]); 76 | 77 | // Invoke the action 78 | action(actionOptions); 79 | }); 80 | } 81 | -------------------------------------------------------------------------------- /src/cli/command/create-program.ts: -------------------------------------------------------------------------------- 1 | import {Command} from "commander"; 2 | 3 | export function createProgram(): Command { 4 | return new Command(); 5 | } 6 | -------------------------------------------------------------------------------- /src/cli/command/funding/funding-command.ts: -------------------------------------------------------------------------------- 1 | import type {Command} from "commander"; 2 | import {createCommand} from "../create-command/create-command.js"; 3 | import {SHARED_OPTIONS} from "../shared/shared-options.js"; 4 | import {generateTaskOptions} from "../../task/generate-task-options/generate-task-options.js"; 5 | import {CONSTANT} from "../../../constant/constant.js"; 6 | 7 | export function createFundingCommand(program: Command) { 8 | return createCommand( 9 | program, 10 | { 11 | name: "funding", 12 | description: `Generates a ${CONSTANT.fundingFilename} file`, 13 | args: {}, 14 | options: { 15 | ...SHARED_OPTIONS 16 | } 17 | }, 18 | async args => { 19 | // Load the task 20 | const {fundingTask} = await import("../../task/funding/funding-task.js"); 21 | // Execute it 22 | fundingTask({ 23 | ...(await generateTaskOptions(args)) 24 | }); 25 | } 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/cli/command/help/help-command.ts: -------------------------------------------------------------------------------- 1 | import type {Command} from "commander"; 2 | 3 | export function createHelpCommand(program: Command): void { 4 | program.addHelpText("before", `Welcome to Sandhog!\n`); 5 | program.addHelpText( 6 | "after", 7 | ` 8 | 9 | Example call: 10 | $ sandhog readme --yes` 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/cli/command/license/license-command.ts: -------------------------------------------------------------------------------- 1 | import type {Command} from "commander"; 2 | import {createCommand} from "../create-command/create-command.js"; 3 | import {SHARED_OPTIONS} from "../shared/shared-options.js"; 4 | import {generateTaskOptions} from "../../task/generate-task-options/generate-task-options.js"; 5 | import {CONSTANT} from "../../../constant/constant.js"; 6 | import {isKnownLicenseName} from "../../../license/is-known-license-name.js"; 7 | import {getLicense} from "../../../license/get-license/get-license.js"; 8 | 9 | export function createLicenseCommand(program: Command) { 10 | return createCommand( 11 | program, 12 | { 13 | name: "license", 14 | description: `Generates a ${CONSTANT.licenseFilename} file`, 15 | args: {}, 16 | options: { 17 | ...SHARED_OPTIONS, 18 | license: { 19 | description: "Override the license to use generate", 20 | type: "string", 21 | shortHand: "l" 22 | } 23 | } 24 | }, 25 | async args => { 26 | // Load the task 27 | const {licenseTask} = await import("../../task/license/license-task.js"); 28 | 29 | // Prepare base options 30 | const taskOptions = await generateTaskOptions(args); 31 | 32 | // Detect the license. It may be given/overridden from a CLI option 33 | const license = await (async () => { 34 | // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition 35 | if (args.license != null) { 36 | if (!isKnownLicenseName(args.license)) { 37 | throw new TypeError(`The license: '${args.license}' given via a CLI option is not supported`); 38 | } 39 | taskOptions.logger.debug(`License given as a CLI option: '${args.license}'`); 40 | return args.license; 41 | } 42 | 43 | // Otherwise, try to resolve it 44 | else { 45 | return await getLicense(taskOptions); 46 | } 47 | })(); 48 | 49 | // Execute the task 50 | licenseTask({ 51 | ...taskOptions, 52 | license 53 | }); 54 | } 55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /src/cli/command/readme/readme-command.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/naming-convention */ 2 | import type {Command} from "commander"; 3 | import {createCommand} from "../create-command/create-command.js"; 4 | import {SHARED_OPTIONS} from "../shared/shared-options.js"; 5 | import {generateTaskOptions} from "../../task/generate-task-options/generate-task-options.js"; 6 | import {CONSTANT} from "../../../constant/constant.js"; 7 | import {ensureSectionKind} from "../../../section/ensure-section-kind/ensure-section-kind.js"; 8 | import {ensureBadgeKind} from "../../../badge/ensure-badge-kind/ensure-badge-kind.js"; 9 | 10 | export function createReadmeCommand(program: Command) { 11 | return createCommand( 12 | program, 13 | { 14 | name: "readme", 15 | description: `Generates a ${CONSTANT.readmeFilename} file`, 16 | args: {}, 17 | options: { 18 | ...SHARED_OPTIONS, 19 | "section.exclude": { 20 | description: `The comma-separated sections to exclude from the generated ${CONSTANT.readmeFilename}`, 21 | type: "string" 22 | }, 23 | "badge.exclude": { 24 | description: `The comma-separated badges to exclude from the generated ${CONSTANT.readmeFilename}`, 25 | type: "string" 26 | } 27 | } 28 | }, 29 | async args => { 30 | // Load the task 31 | const {readmeTask} = await import("../../task/readme/readme-task.js"); 32 | 33 | // Prepare base options 34 | const taskOptions = await generateTaskOptions(args); 35 | 36 | // If given via the command line, update the excluded sections 37 | // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition 38 | if (args["section.exclude"] != null) { 39 | const splitted = args["section.exclude"].split(","); 40 | 41 | // Validate all of the given strings and update them on the options 42 | taskOptions.config.readme.sections.exclude = splitted.map(ensureSectionKind); 43 | } 44 | 45 | // If given via the command line, update the excluded badges 46 | // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition 47 | if (args["badge.exclude"] != null) { 48 | const splitted = args["badge.exclude"].split(","); 49 | 50 | // Validate all of the given strings and update them on the options 51 | taskOptions.config.readme.badges.exclude = splitted.map(ensureBadgeKind); 52 | } 53 | 54 | // Execute the task 55 | readmeTask({ 56 | ...taskOptions 57 | }); 58 | } 59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /src/cli/command/shared/shared-options.ts: -------------------------------------------------------------------------------- 1 | export const SHARED_OPTIONS = { 2 | config: { 3 | shortHand: "c", 4 | type: "string", 5 | description: "An (optional) path to the sandhog config to use" 6 | }, 7 | debug: { 8 | shortHand: "d", 9 | type: "boolean", 10 | description: "Whether to print debug information" 11 | }, 12 | verbose: { 13 | shortHand: "v", 14 | type: "boolean", 15 | description: "Whether to print verbose information" 16 | }, 17 | silent: { 18 | shortHand: "s", 19 | type: "boolean", 20 | description: "Whether to not print anything" 21 | }, 22 | yes: { 23 | shortHand: "y", 24 | type: "boolean", 25 | description: "Whether or not to auto-select 'yes' for all prompts" 26 | } 27 | } as const; 28 | -------------------------------------------------------------------------------- /src/cli/index.ts: -------------------------------------------------------------------------------- 1 | import {createProgram} from "./command/create-program.js"; 2 | import {createCocCommand} from "./command/coc/coc-command.js"; 3 | import {createFundingCommand} from "./command/funding/funding-command.js"; 4 | import {createContributingCommand} from "./command/contributing/contributing-command.js"; 5 | import {createLicenseCommand} from "./command/license/license-command.js"; 6 | import {createReadmeCommand} from "./command/readme/readme-command.js"; 7 | import {createAllCommand} from "./command/all/all-command.js"; 8 | import {createHelpCommand} from "./command/help/help-command.js"; 9 | 10 | const program = createProgram(); 11 | 12 | createCocCommand(program); 13 | createFundingCommand(program); 14 | createContributingCommand(program); 15 | createLicenseCommand(program); 16 | createReadmeCommand(program); 17 | createAllCommand(program); 18 | createHelpCommand(program); 19 | program.parse(); 20 | -------------------------------------------------------------------------------- /src/cli/task/coc/coc-task-options.ts: -------------------------------------------------------------------------------- 1 | import type {TaskOptions} from "../task-options.js"; 2 | 3 | export interface CocTaskOptions extends TaskOptions {} 4 | -------------------------------------------------------------------------------- /src/cli/task/coc/coc-task.ts: -------------------------------------------------------------------------------- 1 | import type {CocTaskOptions} from "./coc-task-options.js"; 2 | import {getContributorsFromPackage} from "../../../contributor/get-contributors-from-package.js"; 3 | import {generateCoc} from "../../../coc/generate-coc/generate-coc.js"; 4 | import path from "crosspath"; 5 | import {CONSTANT} from "../../../constant/constant.js"; 6 | import {confirm} from "../../../util/prompt/confirm.js"; 7 | 8 | /** 9 | * Executes the 'coc' task 10 | */ 11 | export async function cocTask({pkg, logger, prettier, config, root, fs, yes}: CocTaskOptions): Promise { 12 | const contributors = getContributorsFromPackage(pkg); 13 | const cocText = await generateCoc({contributors, config, prettier}); 14 | const pathFromRoot = path.join(root, CONSTANT.codeOfConductFilename); 15 | const nativePath = path.native.normalize(pathFromRoot); 16 | 17 | // If all prompts shouldn't be auto-accepted, request permission to overwrite it 18 | const writePermission = 19 | yes || 20 | !fs.existsSync(nativePath) || 21 | fs.readFileSync(nativePath, "utf8") === cocText || 22 | (await confirm(`A ${CONSTANT.codeOfConductFilename} file already exists at path: ${nativePath}. Do you wish to overwrite it?`, false)); 23 | 24 | if (writePermission) { 25 | // Write the CODE_OF_CONDUCT.md to disk 26 | logger.info(`Writing '${nativePath}'`); 27 | fs.writeFileSync(nativePath, cocText); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/cli/task/contributing/contributing-task-options.ts: -------------------------------------------------------------------------------- 1 | import type {TaskOptions} from "../task-options.js"; 2 | 3 | export interface ContributingTaskOptions extends TaskOptions {} 4 | -------------------------------------------------------------------------------- /src/cli/task/contributing/contributing-task.ts: -------------------------------------------------------------------------------- 1 | import type {ContributingTaskOptions} from "./contributing-task-options.js"; 2 | import {getContributorsFromPackage} from "../../../contributor/get-contributors-from-package.js"; 3 | import path from "crosspath"; 4 | import {CONSTANT} from "../../../constant/constant.js"; 5 | import {generateContributing} from "../../../contributing/generate-contributing/generate-contributing.js"; 6 | import {confirm} from "../../../util/prompt/confirm.js"; 7 | 8 | /** 9 | * Executes the 'coc' task 10 | */ 11 | export async function contributingTask({pkg, logger, prettier, config, root, fs, yes}: ContributingTaskOptions): Promise { 12 | const contributors = getContributorsFromPackage(pkg); 13 | const contributingText = await generateContributing({contributors, config, prettier, pkg}); 14 | const pathFromRoot = path.join(root, CONSTANT.contributingFilename); 15 | const nativePath = path.native.normalize(pathFromRoot); 16 | 17 | // If all prompts shouldn't be auto-accepted, request permission to overwrite it 18 | const writePermission = 19 | yes || 20 | !fs.existsSync(nativePath) || 21 | fs.readFileSync(nativePath, "utf8") === contributingText || 22 | (await confirm(`A ${CONSTANT.contributingFilename} file already exists at path: ${nativePath}. Do you wish to overwrite it?`, false)); 23 | 24 | if (writePermission) { 25 | // Write the CONTRIBUTING.md to disk 26 | logger.info(`Writing '${nativePath}'`); 27 | fs.writeFileSync(nativePath, contributingText); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/cli/task/funding/funding-task-options.ts: -------------------------------------------------------------------------------- 1 | import type {TaskOptions} from "../task-options.js"; 2 | 3 | export interface FundingTaskOptions extends TaskOptions {} 4 | -------------------------------------------------------------------------------- /src/cli/task/funding/funding-task.ts: -------------------------------------------------------------------------------- 1 | import type {FundingTaskOptions} from "./funding-task-options.js"; 2 | import {getContributorsFromPackage} from "../../../contributor/get-contributors-from-package.js"; 3 | import path from "crosspath"; 4 | import {CONSTANT} from "../../../constant/constant.js"; 5 | import {confirm} from "../../../util/prompt/confirm.js"; 6 | import {generateFunding} from "../../../funding/generate-funding/generate-funding.js"; 7 | 8 | /** 9 | * Executes the 'funding' task 10 | */ 11 | export async function fundingTask({pkg, logger, prettier, config, root, fs, yes}: FundingTaskOptions): Promise { 12 | const contributors = getContributorsFromPackage(pkg); 13 | const cocText = await generateFunding({contributors, config, prettier}); 14 | const dir = path.join(root, CONSTANT.githubDirName); 15 | const pathFromRoot = path.join(dir, CONSTANT.fundingFilename); 16 | const nativeDir = path.native.normalize(dir); 17 | const nativePath = path.native.normalize(pathFromRoot); 18 | 19 | // If all prompts shouldn't be auto-accepted, request permission to overwrite it 20 | const writePermission = 21 | yes || 22 | !fs.existsSync(nativePath) || 23 | fs.readFileSync(nativePath, "utf8") === cocText || 24 | (await confirm(`A ${CONSTANT.fundingFilename} file already exists at path: ${nativePath}. Do you wish to overwrite it?`, false)); 25 | 26 | if (writePermission) { 27 | // Ensure that the .github directory exists 28 | if (!fs.existsSync(nativeDir)) { 29 | fs.mkdirSync(nativeDir); 30 | } 31 | // Write the FUNDING.yml to disk 32 | logger.info(`Writing '${nativePath}'`); 33 | fs.writeFileSync(nativePath, cocText); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/cli/task/generate-task-options/generate-task-options.ts: -------------------------------------------------------------------------------- 1 | import type {TaskOptions} from "../task-options.js"; 2 | import {Logger} from "../../../logger/logger.js"; 3 | import type {SanitizedSharedOptions} from "./sanitized-shared-options.js"; 4 | import {selectLogLevel} from "./select-log-level/select-log-level.js"; 5 | import {findPackage} from "../../../package/find-package/find-package.js"; 6 | import prettier from "prettier"; 7 | import fs from "fs"; 8 | import {LogLevelKind} from "../../../logger/log-level-kind.js"; 9 | import {getConfig} from "../../../config/get-config/get-config.js"; 10 | 11 | /** 12 | * Generates the task options that are shared across all commands 13 | */ 14 | export async function generateTaskOptions(options: SanitizedSharedOptions): Promise { 15 | // Prepare a logger 16 | const logLevel = selectLogLevel(options); 17 | const logger = new Logger(logLevel); 18 | 19 | // Inform about the log level (if applicable) 20 | if (logLevel === LogLevelKind.VERBOSE) logger.verbose(`Logging mode: VERBOSE`); 21 | else if (logLevel === LogLevelKind.DEBUG) logger.debug(`Logging mode: DEBUG`); 22 | 23 | // Resolve the package.json file 24 | const {pkg, root} = await findPackage({ 25 | logger 26 | }); 27 | 28 | // Normalize the config 29 | const config = await getConfig({ 30 | root, 31 | pkg, 32 | filename: options.config, 33 | logger 34 | }); 35 | 36 | return { 37 | fs, 38 | pkg, 39 | config, 40 | logger, 41 | prettier, 42 | root, 43 | // eslint-disable-next-line @typescript-eslint/no-unnecessary-boolean-literal-compare 44 | yes: options.yes === true 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /src/cli/task/generate-task-options/sanitized-shared-options.ts: -------------------------------------------------------------------------------- 1 | import type {SHARED_OPTIONS} from "../../command/shared/shared-options.js"; 2 | 3 | export type SanitizedSharedOptions = { 4 | [Key in keyof typeof SHARED_OPTIONS]: (typeof SHARED_OPTIONS)[Key]["type"] extends "boolean" ? boolean : (typeof SHARED_OPTIONS)[Key]["type"] extends "number" ? number : string; 5 | }; 6 | -------------------------------------------------------------------------------- /src/cli/task/generate-task-options/select-log-level/select-log-level-options.ts: -------------------------------------------------------------------------------- 1 | import type {SanitizedSharedOptions} from "../sanitized-shared-options.js"; 2 | 3 | export type SelectLogLevelOptions = SanitizedSharedOptions; 4 | -------------------------------------------------------------------------------- /src/cli/task/generate-task-options/select-log-level/select-log-level.ts: -------------------------------------------------------------------------------- 1 | import type {SelectLogLevelOptions} from "./select-log-level-options.js"; 2 | import {LogLevelKind} from "../../../../logger/log-level-kind.js"; 3 | 4 | /** 5 | * Selects a LogLevel based on the given options 6 | * 7 | * @param options 8 | * @returns 9 | */ 10 | export function selectLogLevel(options: SelectLogLevelOptions): LogLevelKind { 11 | if (options.debug) { 12 | return LogLevelKind.DEBUG; 13 | } else if (options.verbose) { 14 | return LogLevelKind.VERBOSE; 15 | } else if (options.silent) { 16 | return LogLevelKind.NONE; 17 | } else { 18 | return LogLevelKind.INFO; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/cli/task/license/license-task-options.ts: -------------------------------------------------------------------------------- 1 | import type {TaskOptions} from "../task-options.js"; 2 | import type {LicenseName} from "../../../license/license-name.js"; 3 | 4 | export interface LicenseTaskOptions extends TaskOptions { 5 | license: LicenseName; 6 | } 7 | -------------------------------------------------------------------------------- /src/cli/task/license/license-task.ts: -------------------------------------------------------------------------------- 1 | import type {LicenseTaskOptions} from "./license-task-options.js"; 2 | import path from "crosspath"; 3 | import {CONSTANT} from "../../../constant/constant.js"; 4 | import {getContributorsFromPackage} from "../../../contributor/get-contributors-from-package.js"; 5 | import {generateLicense} from "../../../license/generate-license/generate-license.js"; 6 | import {confirm} from "../../../util/prompt/confirm.js"; 7 | 8 | /** 9 | * Executes the 'license' task 10 | */ 11 | export async function licenseTask({logger, root, license, prettier, config, pkg, fs, yes}: LicenseTaskOptions): Promise { 12 | const contributors = getContributorsFromPackage(pkg); 13 | const licenseText = await generateLicense(license, {contributors, config, prettier, pkg}); 14 | const pathFromRoot = path.join(root, CONSTANT.licenseFilename); 15 | const nativePath = path.native.normalize(pathFromRoot); 16 | 17 | // If all prompts shouldn't be auto-accepted, request permission to overwrite it 18 | const writePermission = 19 | yes || 20 | !fs.existsSync(nativePath) || 21 | fs.readFileSync(nativePath, "utf8") === licenseText || 22 | (await confirm(`A ${CONSTANT.licenseFilename} file already exists at path: ${nativePath}. Do you wish to overwrite it?`, false)); 23 | 24 | if (writePermission) { 25 | // Write the LICENSE.md to disk 26 | logger.info(`Writing '${nativePath}'`); 27 | fs.writeFileSync(nativePath, licenseText); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/cli/task/readme/readme-task-options.ts: -------------------------------------------------------------------------------- 1 | import type {TaskOptions} from "../task-options.js"; 2 | 3 | export interface ReadmeTaskOptions extends TaskOptions {} 4 | -------------------------------------------------------------------------------- /src/cli/task/readme/readme-task.ts: -------------------------------------------------------------------------------- 1 | import type {ReadmeTaskOptions} from "./readme-task-options.js"; 2 | import path from "crosspath"; 3 | import {CONSTANT} from "../../../constant/constant.js"; 4 | import {confirm} from "../../../util/prompt/confirm.js"; 5 | import {generateReadme} from "../../../readme/generate-readme/generate-readme.js"; 6 | 7 | /** 8 | * Executes the 'license' task 9 | */ 10 | export async function readmeTask(options: ReadmeTaskOptions): Promise { 11 | // Find all relevant sections for the README 12 | const {logger, root, fs, yes} = options; 13 | 14 | const pathFromRoot = path.join(root, CONSTANT.readmeFilename); 15 | const nativePath = path.native.normalize(pathFromRoot); 16 | const existingReadme = fs.existsSync(nativePath) ? fs.readFileSync(nativePath, "utf8") : undefined; 17 | 18 | const readmeText = await generateReadme({ 19 | ...options, 20 | existingReadme 21 | }); 22 | 23 | // If all prompts shouldn't be auto-accepted, request permission to overwrite it 24 | const writePermission = 25 | yes || 26 | !fs.existsSync(nativePath) || 27 | fs.readFileSync(nativePath, "utf8") === readmeText || 28 | (await confirm(`A ${CONSTANT.readmeFilename} file already exists at path: ${nativePath}. Do you wish to overwrite it?`, false)); 29 | 30 | if (writePermission) { 31 | // Write the README.md to disk 32 | logger.info(`Writing '${nativePath}'`); 33 | fs.writeFileSync(nativePath, readmeText); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/cli/task/task-options.ts: -------------------------------------------------------------------------------- 1 | import type {Package} from "../../package/package.js"; 2 | import type {SandhogConfig} from "../../config/sandhog-config.js"; 3 | import type {ILogger} from "../../logger/i-logger.js"; 4 | import type prettier from "prettier"; 5 | import type {FileSystem} from "../../file-system/file-system.js"; 6 | 7 | export interface TaskOptions { 8 | yes: boolean; 9 | root: string; 10 | pkg: Package; 11 | config: SandhogConfig; 12 | logger: ILogger; 13 | prettier: typeof prettier; 14 | fs: FileSystem; 15 | } 16 | -------------------------------------------------------------------------------- /src/coc/generate-coc/generate-coc-options.ts: -------------------------------------------------------------------------------- 1 | import type {Contributor} from "../../contributor/contributor.js"; 2 | import type prettier from "prettier"; 3 | import type {SandhogConfig} from "../../config/sandhog-config.js"; 4 | 5 | export interface GenerateCocOptions { 6 | prettier: typeof prettier; 7 | config: SandhogConfig; 8 | contributors: Contributor[]; 9 | } 10 | -------------------------------------------------------------------------------- /src/coc/generate-coc/generate-coc.ts: -------------------------------------------------------------------------------- 1 | import type {GenerateCocOptions} from "./generate-coc-options.js"; 2 | import {formatContributor} from "../../contributor/format-contributor.js"; 3 | import {listFormat} from "../../util/list-format/list-format.js"; 4 | 5 | /** 6 | * Generates a Code Of Conduct based on the given options 7 | */ 8 | export function generateCoc({contributors, prettier, config}: GenerateCocOptions): Promise { 9 | return prettier.format( 10 | `\ 11 | Contributor Covenant Code of Conduct 12 | 13 | Our Pledge 14 | 15 | In the interest of fostering an open and welcoming environment, we as 16 | contributors and maintainers pledge to making participation in our project and 17 | our community a harassment-free experience for everyone, regardless of age, body 18 | size, disability, ethnicity, gender identity and expression, level of experience, 19 | nationality, personal appearance, race, religion, or sexual identity and 20 | orientation. 21 | 22 | Our Standards 23 | 24 | Examples of behavior that contributes to creating a positive environment 25 | include: 26 | 27 | * Using welcoming and inclusive language 28 | * Being respectful of differing viewpoints and experiences 29 | * Gracefully accepting constructive criticism 30 | * Focusing on what is best for the community 31 | * Showing empathy towards other community members 32 | 33 | Examples of unacceptable behavior by participants include: 34 | 35 | * The use of sexualized language or imagery and unwelcome sexual attention or 36 | advances 37 | * Trolling, insulting/derogatory comments, and personal or political attacks 38 | * Public or private harassment 39 | * Publishing others' private information, such as a physical or electronic 40 | address, without explicit permission 41 | * Other conduct which could reasonably be considered inappropriate in a 42 | professional setting 43 | 44 | Our Responsibilities 45 | 46 | Project maintainers are responsible for clarifying the standards of acceptable 47 | behavior and are expected to take appropriate and fair corrective action in 48 | response to any instances of unacceptable behavior. 49 | 50 | Project maintainers have the right and responsibility to remove, edit, or 51 | reject comments, commits, code, wiki edits, issues, and other contributions 52 | that are not aligned to this Code of Conduct, or to ban temporarily or 53 | permanently any contributor for other behaviors that they deem inappropriate, 54 | threatening, offensive, or harmful. 55 | 56 | Scope 57 | 58 | This Code of Conduct applies both within project spaces and in public spaces 59 | when an individual is representing the project or its community. Examples of 60 | representing a project or community include using an official project e-mail 61 | address, posting via an official social media account, or acting as an appointed 62 | representative at an online or offline event. Representation of a project may be 63 | further defined and clarified by project maintainers. 64 | 65 | Enforcement 66 | 67 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 68 | reported by contacting any of the code of conduct enforcers: ${listFormat( 69 | contributors.map(contributor => formatContributor(contributor, "markdown")), 70 | "or" 71 | )}. 72 | All complaints will be reviewed and investigated and will result in a response that 73 | is deemed necessary and appropriate to the circumstances. The project team is 74 | obligated to maintain confidentiality with regard to the reporter of an incident. 75 | Further details of specific enforcement policies may be posted separately. 76 | 77 | Project maintainers who do not follow or enforce the Code of Conduct in good 78 | faith may face temporary or permanent repercussions as determined by other 79 | members of the project's leadership. 80 | 81 | Attribution 82 | 83 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4, 84 | available at http://contributor-covenant.org/version/1/4/`, 85 | { 86 | ...config.prettier, 87 | parser: "markdown" 88 | } 89 | ); 90 | } 91 | -------------------------------------------------------------------------------- /src/code-style/code-style-kind.ts: -------------------------------------------------------------------------------- 1 | export const enum CodeStyleKind { 2 | PRETTIER = "prettier", 3 | AIRBNB = "Airbnb", 4 | GOOGLE = "Google", 5 | IDIOMATIC = "Idiomatic.js", 6 | STANDARD = "Standard", 7 | XO = "XO" 8 | } 9 | -------------------------------------------------------------------------------- /src/code-style/find-code-style/code-style.ts: -------------------------------------------------------------------------------- 1 | import type {CodeStyleKind} from "../code-style-kind.js"; 2 | 3 | export interface CodeStyle { 4 | kind: CodeStyleKind; 5 | badgeUrl: string; 6 | url: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/code-style/find-code-style/find-code-styles-options.ts: -------------------------------------------------------------------------------- 1 | import type {FindPackageOptions} from "../../package/find-package/find-package-options.js"; 2 | import {type FileSystem} from "../../file-system/file-system.js"; 3 | import type {Package} from "../../package/package.js"; 4 | 5 | export interface FindCodeStylesOptions extends FindPackageOptions { 6 | pkg?: Package; 7 | fs?: Pick; 8 | } 9 | -------------------------------------------------------------------------------- /src/code-style/find-code-style/find-code-styles.ts: -------------------------------------------------------------------------------- 1 | import type {FindCodeStylesOptions} from "./find-code-styles-options.js"; 2 | import _fs from "fs"; 3 | import {CodeStyleKind} from "../code-style-kind.js"; 4 | import prettier from "prettier"; 5 | import path from "crosspath"; 6 | import eslintModule, {type Linter} from "eslint"; 7 | import {listFormat} from "../../util/list-format/list-format.js"; 8 | import {CONSTANT} from "../../constant/constant.js"; 9 | import {findPackage} from "../../package/find-package/find-package.js"; 10 | import type {Package} from "../../package/package.js"; 11 | import type {CodeStyle} from "./code-style.js"; 12 | import {getCodeStyleForCodeStyleKind} from "../get-code-style-for-code-style-kind/get-code-style-for-code-style-kind.js"; 13 | 14 | /** 15 | * Finds the code kind for the current project from the given root directory, if possible. 16 | * It will use various heuristics to attempt to do so. For example, if there is a prettier config 17 | * within the project, it is probably a Prettier project. 18 | */ 19 | export async function findCodeStyles({ 20 | logger, 21 | root = process.cwd(), 22 | fs = {existsSync: _fs.existsSync, readFileSync: _fs.readFileSync}, 23 | pkg 24 | }: FindCodeStylesOptions): Promise { 25 | if (pkg == null) { 26 | pkg = (await findPackage({root, logger, fs})).pkg; 27 | } 28 | 29 | const codeStyles: CodeStyleKind[] = []; 30 | 31 | logger.debug(`Trying to detect code style from root: '${root}'`); 32 | 33 | // If a Prettier config can be found from the root, we know that Prettier has some relation to the project 34 | if (await isPrettier(root)) { 35 | logger.debug(`Detected "Prettier" as a project code style.`); 36 | codeStyles.push(CodeStyleKind.PRETTIER); 37 | } 38 | 39 | if (isXo(pkg)) { 40 | logger.debug(`Detected "XO" as a project code style.`); 41 | codeStyles.push(CodeStyleKind.XO); 42 | } 43 | 44 | if (isStandard(pkg)) { 45 | logger.debug(`Detected "Standard" as a project code style.`); 46 | codeStyles.push(CodeStyleKind.STANDARD); 47 | } 48 | 49 | // Try to resolve an ESLint config from the root. It may contain some rules or 50 | // extended configs we can extract code styles from 51 | const eslintConfig = await findEslintConfig(root); 52 | if (eslintConfig != null) { 53 | logger.debug(`Found an ESLint config within the root. Parsing it for code styles`); 54 | const eslintCodeStyles = getCodeStylesFromEslintConfig(eslintConfig); 55 | 56 | // Log the results 57 | // eslint-disable-next-line @typescript-eslint/no-unused-expressions 58 | eslintCodeStyles.length > 0 59 | ? logger.debug( 60 | `Detected ${listFormat( 61 | eslintCodeStyles.map(style => `"${style}"`), 62 | "and" 63 | )} as project code style${eslintCodeStyles.length === 1 ? "" : "s"}` 64 | ) 65 | : logger.debug(`No code styles detected inside ESLint config`); 66 | 67 | codeStyles.push(...eslintCodeStyles); 68 | } 69 | 70 | // Dedupe the results 71 | return [...new Set(codeStyles)].map(getCodeStyleForCodeStyleKind); 72 | } 73 | 74 | /** 75 | * Parses the given Config for all CodeStyleKinds 76 | */ 77 | function getCodeStylesFromEslintConfig(config: Linter.Config): CodeStyleKind[] { 78 | const codeStyles: CodeStyleKind[] = []; 79 | 80 | const pluginNames = new Set(Object.keys(config.plugins ?? {})); 81 | 82 | // Check if it contains the Airbnb Style Guide 83 | const containsAirbnb = pluginNames.has(CONSTANT.eslintAirbnbCodeStyleName); 84 | 85 | if (containsAirbnb) { 86 | codeStyles.push(CodeStyleKind.AIRBNB); 87 | } 88 | 89 | // Check if it contains the Google Javascript Style Guide 90 | const containsGoogle = pluginNames.has(CONSTANT.eslintGoogleCodeStyleName); 91 | 92 | if (containsGoogle) { 93 | codeStyles.push(CodeStyleKind.GOOGLE); 94 | } 95 | 96 | // Check if it contains Prettier 97 | const containsPrettier = pluginNames.has(CONSTANT.eslintPrettierCodeStyleName); 98 | 99 | if (containsPrettier) { 100 | codeStyles.push(CodeStyleKind.PRETTIER); 101 | } 102 | 103 | // Check if it contains Idiomatic 104 | const containsIdiomatic = pluginNames.has(CONSTANT.eslintIdiomaticCodeStyleName); 105 | 106 | if (containsIdiomatic) { 107 | codeStyles.push(CodeStyleKind.IDIOMATIC); 108 | } 109 | 110 | // Check if it contains Standard 111 | const containsStandard = pluginNames.has(CONSTANT.eslintStandardCodeStyleName); 112 | 113 | if (containsStandard) { 114 | codeStyles.push(CodeStyleKind.STANDARD); 115 | } 116 | 117 | // Check if it contains XO 118 | const containsXo = pluginNames.has(CONSTANT.eslintXoCodeStyleName); 119 | 120 | if (containsXo) { 121 | codeStyles.push(CodeStyleKind.XO); 122 | } 123 | 124 | return codeStyles; 125 | } 126 | 127 | /** 128 | * Finds (and parses) the found ESLint config, if any, from the given root 129 | */ 130 | async function findEslintConfig(root: string): Promise { 131 | const eslint = new eslintModule.ESLint({}); 132 | // The config must be resolved for a specific file, so start from the package.json file (if it exists) 133 | try { 134 | // eslint-disable-next-line @typescript-eslint/no-unsafe-return 135 | return await eslint.calculateConfigForFile(path.native.join(root, "package.js")); 136 | } catch { 137 | return undefined; 138 | } 139 | } 140 | 141 | /** 142 | * Returns a Promise that resolves with a boolean value indicating if the project is a Prettier project 143 | */ 144 | async function isPrettier(root: string): Promise { 145 | // It may throw if the config is malformed, so wrap it in a try-catch; 146 | try { 147 | const config = await prettier.resolveConfig(root); 148 | return config != null; 149 | } catch { 150 | // The config was found, but it was malformed. This doesn't change the fact that it was found, though, 151 | // so technically this *is* a Prettier project 152 | return true; 153 | } 154 | } 155 | 156 | /** 157 | * Returns a Promise that resolves with a boolean value indicating if the project is an XO project 158 | */ 159 | function isXo(pkg: Package): boolean { 160 | return "xo" in pkg || (pkg.dependencies != null && "xo" in pkg.dependencies) || (pkg.devDependencies != null && "xo" in pkg.devDependencies); 161 | } 162 | 163 | /** 164 | * Returns a Promise that resolves with a boolean value indicating if the project is a Standard project 165 | */ 166 | function isStandard(pkg: Package): boolean { 167 | return "standard" in pkg || (pkg.dependencies != null && "standard" in pkg.dependencies) || (pkg.devDependencies != null && "standard" in pkg.devDependencies); 168 | } 169 | -------------------------------------------------------------------------------- /src/code-style/get-code-style-for-code-style-kind/get-code-style-for-code-style-kind.ts: -------------------------------------------------------------------------------- 1 | import {CodeStyleKind} from "../code-style-kind.js"; 2 | import type {CodeStyle} from "../find-code-style/code-style.js"; 3 | import {generateBadgeUrl} from "../../badge/generate-badge-url/generate-badge-url.js"; 4 | 5 | /** 6 | * Gets the Code Style for the given CodeStyleKind 7 | * 8 | * @param kind 9 | * @returns 10 | */ 11 | export function getCodeStyleForCodeStyleKind(kind: CodeStyleKind): CodeStyle { 12 | switch (kind) { 13 | case CodeStyleKind.PRETTIER: 14 | return { 15 | kind, 16 | badgeUrl: "https://img.shields.io/badge/code_style-prettier-ff69b4.svg", 17 | url: "https://github.com/prettier/prettier" 18 | }; 19 | 20 | case CodeStyleKind.STANDARD: 21 | return { 22 | kind, 23 | badgeUrl: "https://img.shields.io/badge/code%20style-standard-green.svg", 24 | url: "https://github.com/feross/standard" 25 | }; 26 | 27 | case CodeStyleKind.AIRBNB: 28 | return { 29 | kind, 30 | badgeUrl: "https://badgen.net/badge/code%20style/airbnb/ff5a5f", 31 | url: "https://github.com/airbnb/javascript" 32 | }; 33 | 34 | case CodeStyleKind.IDIOMATIC: 35 | return { 36 | kind, 37 | badgeUrl: generateBadgeUrl({subject: "code style", status: kind, color: "green"}), 38 | url: "https://github.com/rwaldron/idiomatic.js" 39 | }; 40 | case CodeStyleKind.GOOGLE: 41 | return { 42 | kind, 43 | badgeUrl: generateBadgeUrl({subject: "code style", status: kind, color: "green"}), 44 | url: "https://google.github.io/styleguide/jsguide.html" 45 | }; 46 | case CodeStyleKind.XO: 47 | return { 48 | kind, 49 | badgeUrl: "https://img.shields.io/badge/code_style-XO-5ed9c7.svg", 50 | url: "https://github.com/xojs/xo" 51 | }; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/config/default-sandhog-config.ts: -------------------------------------------------------------------------------- 1 | import type {SandhogConfig} from "./sandhog-config.js"; 2 | import prettier, {type Options} from "prettier"; 3 | 4 | /** 5 | * This object holds all of the default config options for a SandhogConfig 6 | * @type {Promise} 7 | */ 8 | export const DEFAULT_SANDHOG_CONFIG: Promise = (async () => ({ 9 | isDevelopmentPackage: false, 10 | logo: { 11 | height: 80 12 | }, 13 | featureImage: { 14 | height: 180 15 | }, 16 | donate: { 17 | other: { 18 | donors: [] 19 | }, 20 | patreon: {}, 21 | openCollective: {} 22 | }, 23 | readme: { 24 | badges: { 25 | exclude: [] 26 | }, 27 | sections: { 28 | exclude: [] 29 | } 30 | }, 31 | prettier: await (async () => { 32 | const defaultConfig = { 33 | // Tabs don't print well in Github Flavored Markdown Syntax highlighting 34 | useTabs: false, 35 | semi: true, 36 | tabWidth: 2, 37 | singleQuote: false, 38 | trailingComma: "none", 39 | bracketSpacing: false, 40 | printWidth: 90, 41 | arrowParens: "avoid" 42 | } as Options; 43 | 44 | // resolveConfig may throw, so wrap it in a try-catch 45 | try { 46 | const prettierConfig = await prettier.resolveConfig(process.cwd()); 47 | if (prettierConfig != null) { 48 | return prettierConfig; 49 | } 50 | return defaultConfig; 51 | } catch { 52 | return defaultConfig; 53 | } 54 | })() 55 | }))(); 56 | -------------------------------------------------------------------------------- /src/config/find-config/find-config-options.ts: -------------------------------------------------------------------------------- 1 | import type {FindPackageOptions} from "../../package/find-package/find-package-options.js"; 2 | import type {Package} from "../../package/package.js"; 3 | import type {FileSystem} from "../../file-system/file-system.js"; 4 | 5 | export interface FindConfigOptions extends FindPackageOptions { 6 | fs?: Pick; 7 | filename?: string; 8 | pkg?: Package; 9 | } 10 | -------------------------------------------------------------------------------- /src/config/find-config/find-config.ts: -------------------------------------------------------------------------------- 1 | import path from "crosspath"; 2 | import _fs from "fs"; 3 | import json5 from "json5"; 4 | import yaml from "yaml"; 5 | import type {SandhogConfig} from "../sandhog-config.js"; 6 | import {CONSTANT} from "../../constant/constant.js"; 7 | import type {FindConfigOptions} from "./find-config-options.js"; 8 | import {findPackage} from "../../package/find-package/find-package.js"; 9 | import type {ILogger} from "../../logger/i-logger.js"; 10 | import type {FileSystem} from "../../file-system/file-system.js"; 11 | import type {PartialDeep} from "helpertypes"; 12 | 13 | /** 14 | * Finds a sandhog config if possible 15 | */ 16 | export async function findConfig({ 17 | filename, 18 | logger, 19 | root = process.cwd(), 20 | fs = {existsSync: _fs.existsSync, readFileSync: _fs.readFileSync}, 21 | pkg 22 | }: FindConfigOptions): Promise | undefined> { 23 | if (pkg == null) { 24 | pkg = (await findPackage({root, logger, fs})).pkg; 25 | } 26 | logger.debug(`Checking package.json for config`); 27 | if (pkg.sandhog != null) { 28 | logger.debug(`Matched config in package.json`); 29 | return pkg.sandhog; 30 | } 31 | 32 | // If it wasn't matched in the package, attempt to resolve a config file 33 | return await findConfigRecursiveStep(root, logger, fs, filename); 34 | } 35 | 36 | /** 37 | * The recursive step of the findConfig algorithm 38 | */ 39 | async function findConfigRecursiveStep( 40 | root: string, 41 | logger: ILogger, 42 | fs: Pick, 43 | filename?: string 44 | ): Promise | undefined> { 45 | const absolutePaths = 46 | filename != null 47 | ? [path.isAbsolute(filename) ? path.normalize(filename) : path.join(root, filename)] 48 | : [ 49 | path.join(root, CONSTANT.configFilenameJs), 50 | path.join(root, CONSTANT.configFilenameCjs), 51 | path.join(root, CONSTANT.configFilenameMjs), 52 | path.join(root, CONSTANT.configFilenameJson), 53 | path.join(root, CONSTANT.configFilenameJson5), 54 | path.join(root, CONSTANT.configFilenameYaml), 55 | path.join(root, CONSTANT.configFilenameYml), 56 | path.join(root, CONSTANT.configFilenameRc) 57 | ]; 58 | 59 | for (const absolutePath of absolutePaths) { 60 | const nativeAbsolutePath = path.native.normalize(absolutePath); 61 | logger.debug(`Checking path for config: ${nativeAbsolutePath}`); 62 | if (fs.existsSync(nativeAbsolutePath)) { 63 | logger.debug(`Matched config at path: ${nativeAbsolutePath}`); 64 | const ext = path.extname(absolutePath); 65 | 66 | switch (ext) { 67 | case "": 68 | case ".json5": 69 | case ".json": 70 | // If it is extension-less, or if it has a .json[5] extension, parse it as JSON5 71 | return json5.parse(fs.readFileSync(nativeAbsolutePath, "utf8")); 72 | 73 | case ".yml": 74 | case ".yaml": 75 | // If it has a .y[a]ml extension, parse it as YAML 76 | // eslint-disable-next-line @typescript-eslint/no-unsafe-return 77 | return yaml.parse(fs.readFileSync(nativeAbsolutePath, "utf8")); 78 | default: { 79 | // Otherwise, use the module loader directly 80 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment 81 | const result = await import(`file://${absolutePath}`); 82 | return "default" in result ? (result as {default: PartialDeep}).default : (result as PartialDeep); 83 | } 84 | } 85 | } 86 | } 87 | 88 | // Rewrite the root to check the parent directory 89 | const newRoot = path.join(root, "../"); 90 | 91 | // If there is no more parent directories to look in, no config exists 92 | if (newRoot === root || newRoot === "/" || newRoot === "") return undefined; 93 | 94 | // Call recursively 95 | return await findConfigRecursiveStep(newRoot, logger, fs, filename); 96 | } 97 | -------------------------------------------------------------------------------- /src/config/get-config/get-config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unnecessary-condition */ 2 | import type {FindConfigOptions} from "../find-config/find-config-options.js"; 3 | import type {SandhogConfig} from "../sandhog-config.js"; 4 | import {findConfig} from "../find-config/find-config.js"; 5 | import {DEFAULT_SANDHOG_CONFIG} from "../default-sandhog-config.js"; 6 | import type {BadgeKind} from "../../badge/badge-kind.js"; 7 | import type {SectionKind} from "../../section/section-kind.js"; 8 | import type {Options} from "prettier"; 9 | import type {Contributor} from "../../contributor/contributor.js"; 10 | 11 | /** 12 | * Gets a SandhogConfig. Will attempt to resolve a config and fall back to defaults 13 | * if no such config could be found 14 | */ 15 | export async function getConfig(options: FindConfigOptions): Promise { 16 | // Resolve the config 17 | let config = await findConfig(options); 18 | // If no config could be found, initialize to an empty record 19 | if (config == null) { 20 | config = {}; 21 | } 22 | 23 | const defaultConfig = await DEFAULT_SANDHOG_CONFIG; 24 | 25 | return { 26 | isDevelopmentPackage: config.isDevelopmentPackage ?? defaultConfig.isDevelopmentPackage, 27 | logo: { 28 | height: config.logo?.height ?? defaultConfig.logo.height, 29 | url: config.logo?.url ?? defaultConfig.logo.url 30 | }, 31 | featureImage: { 32 | height: config.featureImage?.height ?? defaultConfig.featureImage.height, 33 | url: config.featureImage?.url ?? defaultConfig.featureImage.url 34 | }, 35 | prettier: (config.prettier as Options | undefined) ?? defaultConfig.prettier, 36 | donate: { 37 | other: { 38 | donors: (config.donate?.other?.donors as Contributor[] | undefined) ?? defaultConfig.donate.other.donors 39 | }, 40 | patreon: { 41 | username: config.donate?.patreon?.username ?? defaultConfig.donate.patreon.username, 42 | userId: config.donate?.patreon?.userId ?? defaultConfig.donate.patreon.userId 43 | }, 44 | openCollective: { 45 | project: config.donate?.openCollective?.project ?? defaultConfig.donate.openCollective.project 46 | } 47 | }, 48 | readme: { 49 | badges: { 50 | exclude: (config.readme?.badges?.exclude as Iterable) ?? defaultConfig.readme.badges.exclude 51 | }, 52 | sections: { 53 | exclude: (config.readme?.sections?.exclude as Iterable) ?? defaultConfig.readme.sections.exclude 54 | } 55 | } 56 | }; 57 | } 58 | -------------------------------------------------------------------------------- /src/config/sandhog-config.ts: -------------------------------------------------------------------------------- 1 | import type {SectionKind} from "../section/section-kind.js"; 2 | import type {BadgeKind} from "../badge/badge-kind.js"; 3 | import type {Options} from "prettier"; 4 | import type {Contributor} from "../contributor/contributor.js"; 5 | 6 | export interface PatreonConfig { 7 | userId?: string; 8 | username?: string; 9 | } 10 | 11 | export interface OpenCollectiveConfig { 12 | project?: string; 13 | } 14 | 15 | export interface OtherDonorsConfig { 16 | donors: Contributor[]; 17 | fundingUrl?: string; 18 | } 19 | 20 | export interface DonateConfig { 21 | patreon: PatreonConfig; 22 | openCollective: OpenCollectiveConfig; 23 | other: OtherDonorsConfig; 24 | } 25 | 26 | export interface ImageConfig { 27 | url?: string; 28 | height: number; 29 | } 30 | 31 | export interface BadgeConfig { 32 | exclude: Iterable; 33 | } 34 | 35 | export interface SectionConfig { 36 | exclude: Iterable; 37 | } 38 | 39 | export interface ReadmeConfig { 40 | sections: SectionConfig; 41 | badges: BadgeConfig; 42 | } 43 | 44 | export interface SandhogConfig { 45 | isDevelopmentPackage: boolean; 46 | donate: DonateConfig; 47 | logo: ImageConfig; 48 | featureImage: ImageConfig; 49 | readme: ReadmeConfig; 50 | prettier: Options; 51 | } 52 | -------------------------------------------------------------------------------- /src/constant/constant.ts: -------------------------------------------------------------------------------- 1 | export const CONSTANT = { 2 | configFilenameJs: "sandhog.config.js", 3 | configFilenameCjs: "sandhog.config.cjs", 4 | configFilenameMjs: "sandhog.config.mjs", 5 | configFilenameJson: "sandhog.config.json", 6 | configFilenameJson5: "sandhog.config.json5", 7 | configFilenameYaml: "sandhog.config.yaml", 8 | configFilenameYml: "sandhog.config.yml", 9 | configFilenameRc: ".sandhogrc", 10 | readmeFilename: "README.md", 11 | contributorImageUrlHeight: 70, 12 | githubDirName: ".github", 13 | codeOfConductFilename: "CODE_OF_CONDUCT.md", 14 | fundingFilename: "FUNDING.yml", 15 | contributingFilename: "CONTRIBUTING.md", 16 | licenseFilename: "LICENSE.md", 17 | eslintIdiomaticCodeStyleName: "idiomatic", 18 | eslintGoogleCodeStyleName: "google", 19 | eslintPrettierCodeStyleName: "prettier", 20 | eslintAirbnbCodeStyleName: "airbnb", 21 | eslintXoCodeStyleName: "xo", 22 | eslintStandardCodeStyleName: "standard", 23 | shieldRestEndpoint: "https://img.shields.io/badge", 24 | patreonDonateUrl: (userId: string) => `https://www.patreon.com/bePatron?u=${userId}`, 25 | patreonBadgeUrl: (username: string) => `https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fshieldsio-patreon.vercel.app%2Fapi%3Fusername%3D${username}%26type%3Dpatrons`, 26 | openCollectiveDonateUrl: (projectName: string) => `https://opencollective.com/${projectName}/contribute/`, 27 | openCollectiveContributorsUrl: (projectName: string) => `https://opencollective.com/${projectName}/#contributors`, 28 | openCollectiveSponsorsBadgeUrl: (project: string) => `https://opencollective.com/${project}/sponsors.svg?width=500`, 29 | openCollectiveBackersBadgeUrl: (project: string) => `https://opencollective.com/${project}/backers.svg?width=500` 30 | }; 31 | -------------------------------------------------------------------------------- /src/contributing/generate-contributing/generate-contributing-options.ts: -------------------------------------------------------------------------------- 1 | import type {Contributor} from "../../contributor/contributor.js"; 2 | import type prettier from "prettier"; 3 | import type {SandhogConfig} from "../../config/sandhog-config.js"; 4 | import type {Package} from "../../package/package.js"; 5 | 6 | export interface GenerateContributingOptions { 7 | prettier: typeof prettier; 8 | pkg: Package; 9 | config: SandhogConfig; 10 | contributors: Contributor[]; 11 | } 12 | -------------------------------------------------------------------------------- /src/contributing/generate-contributing/generate-contributing.ts: -------------------------------------------------------------------------------- 1 | import type {GenerateContributingOptions} from "./generate-contributing-options.js"; 2 | import {listFormat} from "../../util/list-format/list-format.js"; 3 | 4 | /** 5 | * Generates a CONTRIBUTING.md based on the given options 6 | */ 7 | export function generateContributing({contributors, prettier, config, pkg}: GenerateContributingOptions): Promise { 8 | return prettier.format( 9 | `\ 10 | You are more than welcome to contribute to \`${pkg.name}\` in any way you please, including: 11 | 12 | - Updating documentation. 13 | - Fixing spelling and grammar 14 | - Adding tests 15 | - Fixing issues and suggesting new features 16 | - Blogging, tweeting, and creating tutorials about \`${pkg.name}\` 17 | ${ 18 | contributors.every(contributor => contributor.twitter == null) 19 | ? "" 20 | : `- Reaching out to ${listFormat( 21 | contributors.filter(contributor => contributor.twitter != null).map(contributor => `[@${contributor.twitter}](https://twitter.com/${contributor.twitter})`), 22 | "or" 23 | )} on Twitter` 24 | } 25 | - Submit an issue or a Pull Request`, 26 | { 27 | ...config.prettier, 28 | parser: "markdown" 29 | } 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /src/contributor/contributor.ts: -------------------------------------------------------------------------------- 1 | export type Contributor = Partial<{ 2 | name: string; 3 | email: string; 4 | url: string; 5 | role: string; 6 | imageUrl: string; 7 | twitter: string; 8 | github: string; 9 | }>; 10 | -------------------------------------------------------------------------------- /src/contributor/ensure-contributor.ts: -------------------------------------------------------------------------------- 1 | import type {Contributor} from "./contributor.js"; 2 | 3 | const NAME_REGEXP = /([^<]*)/; 4 | const NAME_EMAIL_REGEXP = /([^<]*)<([^>]*)>/; 5 | const NAME_URL_REGEXP = /([^(]*)\s*\(([^)]*\))/; 6 | const NAME_EMAIL_URL_REGEXP = /([^<]*)<([^>]*)>\s*\(([^)]*\))/; 7 | 8 | /** 9 | * Ensures that the given input is a proper Contributor 10 | * 11 | * @param contributor 12 | */ 13 | export function ensureContributor(contributor: string | Contributor): Contributor { 14 | if (typeof contributor !== "string") return contributor; 15 | 16 | // Otherwise, try to parse the string which may look like this: 17 | // "Barney Rubble (http://barnyrubble.tumblr.com/)" 18 | if (NAME_EMAIL_URL_REGEXP.test(contributor)) { 19 | const [, name, email, url] = contributor.match(NAME_EMAIL_URL_REGEXP)!; 20 | return { 21 | name: name?.trim(), 22 | email, 23 | url 24 | }; 25 | } else if (NAME_URL_REGEXP.test(contributor)) { 26 | const [, name, url] = contributor.match(NAME_URL_REGEXP)!; 27 | return { 28 | name: name?.trim(), 29 | url 30 | }; 31 | } else if (NAME_EMAIL_REGEXP.test(contributor)) { 32 | const [, name, email] = contributor.match(NAME_EMAIL_REGEXP)!; 33 | return { 34 | name: name?.trim(), 35 | email 36 | }; 37 | } else if (NAME_REGEXP.test(contributor)) { 38 | const [, name] = contributor.match(NAME_REGEXP)!; 39 | return { 40 | name: name?.trim() 41 | }; 42 | } else { 43 | throw new TypeError(`Could not parse string: '${contributor}' as a contributor`); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/contributor/format-contributor.ts: -------------------------------------------------------------------------------- 1 | import type {Contributor} from "./contributor.js"; 2 | 3 | /** 4 | * Formats the relevant data of the given contributor 5 | */ 6 | export function formatContributor({name, email, twitter, url}: Contributor, format: "html" | "markdown" | "plain" = "plain"): string { 7 | switch (format) { 8 | case "plain": { 9 | const nameFormatted = name == null ? [] : [name]; 10 | const emailFormatted = email == null ? [] : [`<${email}>`]; 11 | const twitterFormatted = twitter == null ? [] : [`(@${twitter})`]; 12 | const urlFormatted = url == null ? [] : [`(${url})`]; 13 | return [...nameFormatted, ...emailFormatted, ...twitterFormatted, ...urlFormatted].join(" "); 14 | } 15 | 16 | case "html": { 17 | const nameFormatted: string[] = (() => { 18 | if (name == null) return []; 19 | else if (email != null) { 20 | return [`${name}`]; 21 | } else { 22 | return [name]; 23 | } 24 | })(); 25 | 26 | const twitterFormatted = twitter == null ? [] : [`(@${twitter})`]; 27 | const urlFormatted = url == null ? [] : [`(Website)`]; 28 | return [...nameFormatted, ...twitterFormatted, ...urlFormatted].join(" "); 29 | } 30 | 31 | case "markdown": { 32 | const nameFormatted: string[] = (() => { 33 | if (name == null) return []; 34 | else if (email != null) { 35 | return [`[${name}](mailto:${email})`]; 36 | } else { 37 | return [name]; 38 | } 39 | })(); 40 | 41 | const twitterFormatted = twitter == null ? [] : [`([@${twitter}](https://twitter.com/${twitter}))`]; 42 | const urlFormatted = url == null ? [] : [`([Website](${url}))`]; 43 | return [...nameFormatted, ...twitterFormatted, ...urlFormatted].join(" "); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/contributor/get-contributors-from-package.ts: -------------------------------------------------------------------------------- 1 | import type {Package} from "../package/package.js"; 2 | import type {Contributor} from "./contributor.js"; 3 | import {ensureContributor} from "./ensure-contributor.js"; 4 | 5 | /** 6 | * Gets all Contributors based on the given package.json 7 | * 8 | * @param pkg 9 | * @returns 10 | */ 11 | export function getContributorsFromPackage(pkg: Package): Contributor[] { 12 | const all: Contributor[] = []; 13 | 14 | const includesContributor = (contributor: Contributor) => all.some(({name}) => name === contributor.name); 15 | 16 | if (pkg.author != null) { 17 | const author = ensureContributor(pkg.author); 18 | if (!includesContributor(author)) { 19 | all.push(author); 20 | } 21 | } 22 | 23 | if (pkg.authors != null) { 24 | const authors: Contributor[] = pkg.authors.map(ensureContributor); 25 | authors.forEach(author => { 26 | if (!includesContributor(author)) { 27 | all.push(author); 28 | } 29 | }); 30 | } 31 | 32 | if (pkg.contributors != null) { 33 | const contributors: Contributor[] = pkg.contributors.map(ensureContributor); 34 | contributors.forEach(contributor => { 35 | if (!includesContributor(contributor)) { 36 | all.push(contributor); 37 | } 38 | }); 39 | } 40 | 41 | return all; 42 | } 43 | -------------------------------------------------------------------------------- /src/file-system/file-system.ts: -------------------------------------------------------------------------------- 1 | import type fs from "fs"; 2 | 3 | export interface FileSystem { 4 | writeFileSync: typeof fs.writeFileSync; 5 | readFileSync: typeof fs.readFileSync; 6 | existsSync: typeof fs.existsSync; 7 | mkdirSync: typeof fs.mkdirSync; 8 | } 9 | -------------------------------------------------------------------------------- /src/funding/generate-funding/generate-funding-options.ts: -------------------------------------------------------------------------------- 1 | import type {Contributor} from "../../contributor/contributor.js"; 2 | import type prettier from "prettier"; 3 | import type {SandhogConfig} from "../../config/sandhog-config.js"; 4 | 5 | export interface GenerateFundingOptions { 6 | prettier: typeof prettier; 7 | config: SandhogConfig; 8 | contributors: Contributor[]; 9 | } 10 | -------------------------------------------------------------------------------- /src/funding/generate-funding/generate-funding.ts: -------------------------------------------------------------------------------- 1 | import type {GenerateFundingOptions} from "./generate-funding-options.js"; 2 | import type {Contributor} from "../../contributor/contributor.js"; 3 | 4 | /** 5 | * Generates a FUNDING.yml based on the given options 6 | */ 7 | export function generateFunding({contributors, prettier, config}: GenerateFundingOptions): Promise { 8 | let yaml = ``; 9 | // Take all of the contributors that has a github username defined in the package.json file. 10 | // These will be the fundable ones. For those, take their Github user names and deduplicate them 11 | const githubUserNames = [...new Set(contributors.filter((contributor): contributor is Contributor & {github: string} => contributor.github != null).map(({github}) => github))]; 12 | 13 | if (githubUserNames.length === 1) { 14 | yaml += `github: ${githubUserNames[0]}\n`; 15 | } else if (githubUserNames.length > 1) { 16 | yaml += `github: [${githubUserNames.join(", ")}]\n`; 17 | } 18 | 19 | if (config.donate.patreon.username != null) { 20 | yaml += `patreon: ${config.donate.patreon.username}`; 21 | } 22 | 23 | if (config.donate.openCollective.project != null) { 24 | yaml += `open_collective: ${config.donate.openCollective.project}`; 25 | } 26 | 27 | return prettier.format(yaml, { 28 | ...config.prettier, 29 | parser: "yaml" 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export {generateReadme} from "./readme/generate-readme/generate-readme.js"; 2 | export {generateLicense} from "./license/generate-license/generate-license.js"; 3 | export {generateContributing} from "./contributing/generate-contributing/generate-contributing.js"; 4 | export {generateCoc} from "./coc/generate-coc/generate-coc.js"; 5 | export {generateFunding} from "./funding/generate-funding/generate-funding.js"; 6 | export {Logger} from "./logger/logger.js"; 7 | export {findConfig} from "./config/find-config/find-config.js"; 8 | export {getConfig} from "./config/get-config/get-config.js"; 9 | export type {SandhogConfig} from "./config/sandhog-config.js"; 10 | export type {Package} from "./package/package.js"; 11 | export type {FileSystem} from "./file-system/file-system.js"; 12 | export type {GenerateFundingOptions} from "./funding/generate-funding/generate-funding-options.js"; 13 | export type {BadgeKind} from "./badge/badge-kind.js"; 14 | export type {SectionKind} from "./section/section-kind.js"; 15 | export type {ILogger} from "./logger/i-logger.js"; 16 | export type {GenerateCocOptions} from "./coc/generate-coc/generate-coc-options.js"; 17 | export type {GenerateContributingOptions} from "./contributing/generate-contributing/generate-contributing-options.js"; 18 | export type {GenerateReadmeOptions} from "./readme/generate-readme/generate-readme-options.js"; 19 | export type {GenerateLicenseOptions} from "./license/generate-license/generate-license-options.js"; 20 | -------------------------------------------------------------------------------- /src/license/detect-license/detect-license.ts: -------------------------------------------------------------------------------- 1 | import type {LicenseName} from "../license-name.js"; 2 | 3 | /** 4 | * Detects the license of the given text, if possible 5 | * 6 | * @param text 7 | * @returns 8 | */ 9 | export function detectLicense(text: string): LicenseName | undefined { 10 | if (text.includes("GNU AGPL")) return "AGPL-3.0"; 11 | else if (text.includes("Apache License")) return "APACHE-2.0"; 12 | else if (text.includes("The Artistic License 2.0")) return "ARTISTIC-2.0"; 13 | else if (text.includes("BSD 2-Clause License")) return "BSD-2-CLAUSE"; 14 | else if (text.includes("BSD 3-Clause License")) return "BSD-3-CLAUSE"; 15 | else if (text.includes("Attribution 4.0 International")) return "CC-BY-4.0"; 16 | else if (text.includes("Attribution-ShareAlike 4.0 International")) return "CC-BY-SA-4.0"; 17 | else if (text.includes("Eclipse Public License")) return "EPL-1.0"; 18 | else if (text.includes("GNU GENERAL PUBLIC LICENSE") && text.includes("Version 2, June 1991")) return "GPL-2.0"; 19 | else if (text.includes("GNU GENERAL PUBLIC LICENSE") && text.includes("Version 3, 29 June 2007")) return "GPL-3.0"; 20 | else if (text.includes("GNU LESSER GENERAL PUBLIC LICENSE") && text.includes("Version 3, 29 June 2007")) return "LGPL-3.0"; 21 | else if (text.includes("The MIT License (MIT)")) return "MIT"; 22 | else if (text.includes("Mozilla Public License Version 2.0")) return "MPL-2.0"; 23 | else if (text.includes("zlib License")) return "ZLIB"; 24 | else return undefined; 25 | } 26 | -------------------------------------------------------------------------------- /src/license/find-license/find-license-options.ts: -------------------------------------------------------------------------------- 1 | import type {Package} from "../../package/package.js"; 2 | import type {FindPackageOptions} from "../../package/find-package/find-package-options.js"; 3 | import type {FileSystem} from "../../file-system/file-system.js"; 4 | 5 | export interface FindLicenseOptions extends FindPackageOptions { 6 | pkg?: Package; 7 | fs?: Pick; 8 | } 9 | -------------------------------------------------------------------------------- /src/license/find-license/find-license.ts: -------------------------------------------------------------------------------- 1 | import path from "crosspath"; 2 | import _fs from "fs"; 3 | import type {FindLicenseOptions} from "./find-license-options.js"; 4 | import type {LicenseName} from "../license-name.js"; 5 | import {isKnownLicenseName} from "../is-known-license-name.js"; 6 | import {CONSTANT} from "../../constant/constant.js"; 7 | import type {ILogger} from "../../logger/i-logger.js"; 8 | import {findPackage} from "../../package/find-package/find-package.js"; 9 | import type {FileSystem} from "../../file-system/file-system.js"; 10 | import {detectLicense} from "../detect-license/detect-license.js"; 11 | 12 | /** 13 | * Finds the project license from the given root directory. 14 | * It may be listed in the package.json file, or it may exist within the root as a LICENSE.md file already 15 | */ 16 | export async function findLicense({ 17 | logger, 18 | root = process.cwd(), 19 | fs = {existsSync: _fs.existsSync, readFileSync: _fs.readFileSync}, 20 | pkg 21 | }: FindLicenseOptions): Promise { 22 | if (pkg == null) { 23 | pkg = (await findPackage({root, logger, fs})).pkg; 24 | } 25 | // If the package.json file includes a license, just use that one. 26 | logger.debug(`Trying to locate license inside package.json`); 27 | if (pkg.license != null) { 28 | logger.debug(`Found license: '${pkg.license}' inside package.json`); 29 | 30 | // Ensure that it is valid and supported 31 | if (!isKnownLicenseName(pkg.license)) { 32 | throw new TypeError(`The license found within package.json: '${pkg.license}' is not valid or not supported`); 33 | } 34 | 35 | return pkg.license; 36 | } 37 | 38 | // Otherwise, we have to be smart about it. Recursively try to locate a LICENSE.md file 39 | const license = findLicenseRecursiveStep(root, logger, fs); 40 | 41 | // If no license could be found, return immediately 42 | if (license == null) { 43 | return undefined; 44 | } 45 | 46 | const [text, p] = license; 47 | const nativePath = path.native.normalize(p); 48 | 49 | // Otherwise, try to parse it to determine the license 50 | logger.verbose(`Detecting license for file: ${nativePath}`); 51 | 52 | const name = detectLicense(text); 53 | 54 | // Ensure that it is valid and supported 55 | if (name == null || !isKnownLicenseName(name)) { 56 | throw new TypeError(`The license found on path: '${nativePath}' is not valid or not supported`); 57 | } 58 | 59 | logger.verbose(`Detected license: '${name}' for file: ${nativePath}`); 60 | return name; 61 | } 62 | 63 | /** 64 | * The recursive step of the findConfig algorithm 65 | */ 66 | function findLicenseRecursiveStep(root: string, logger: ILogger, fs: Pick): [string, string] | undefined { 67 | const absolutePath = path.join(root, CONSTANT.licenseFilename); 68 | const nativeAbsolutePath = path.native.normalize(absolutePath); 69 | 70 | logger.debug(`Checking path for license: ${nativeAbsolutePath}`); 71 | if (fs.existsSync(nativeAbsolutePath)) { 72 | logger.debug(`Matched license at path: ${nativeAbsolutePath}`); 73 | return [fs.readFileSync(nativeAbsolutePath).toString(), absolutePath]; 74 | } 75 | 76 | // Rewrite the root to check the parent directory 77 | const newRoot = path.join(root, "../"); 78 | 79 | // If there is no more parent directories to look in, no config exists 80 | if (newRoot === root || newRoot === "/" || newRoot === "") return undefined; 81 | 82 | // Call recursively 83 | return findLicenseRecursiveStep(newRoot, logger, fs); 84 | } 85 | -------------------------------------------------------------------------------- /src/license/generate-license/generate-license-options.ts: -------------------------------------------------------------------------------- 1 | import type prettier from "prettier"; 2 | import type {SandhogConfig} from "../../config/sandhog-config.js"; 3 | import type {Contributor} from "../../contributor/contributor.js"; 4 | import type {Package} from "../../package/package.js"; 5 | 6 | export interface GenerateLicenseOptions { 7 | prettier: typeof prettier; 8 | config: SandhogConfig; 9 | pkg: Package; 10 | contributors: Contributor[]; 11 | } 12 | -------------------------------------------------------------------------------- /src/license/generate-license/generate-license.ts: -------------------------------------------------------------------------------- 1 | import type {LicenseName} from "../license-name.js"; 2 | import type {GenerateLicenseOptions} from "./generate-license-options.js"; 3 | import {isKnownLicenseName} from "../is-known-license-name.js"; 4 | import {generate as apache2} from "./licenses/generate-apache-2.0.js"; 5 | import {generate as agpl} from "./licenses/generate-agpl-3.0.js"; 6 | import {generate as mit} from "./licenses/generate-mit.js"; 7 | import {generate as artistic2} from "./licenses/generate-artistic-2.0.js"; 8 | import {generate as bsd2} from "./licenses/generate-bsd-2-clause.js"; 9 | import {generate as bsd3} from "./licenses/generate-bsd-3-clause.js"; 10 | import {generate as ccby4} from "./licenses/generate-cc-by-4.0.js"; 11 | import {generate as ccbysa4} from "./licenses/generate-cc-by-sa-4.0.js"; 12 | import {generate as epl1} from "./licenses/generate-epl-1.0.js"; 13 | import {generate as gpl2} from "./licenses/generate-gpl-2.0.js"; 14 | import {generate as gpl3} from "./licenses/generate-gpl-3.0.js"; 15 | import {generate as lgpl3} from "./licenses/generate-lgpl-3.0.js"; 16 | import {generate as mpl2} from "./licenses/generate-mpl-2.0.js"; 17 | import {generate as zlib} from "./licenses/generate-zlib.js"; 18 | 19 | /** 20 | * Generates license text for the given license name 21 | */ 22 | export async function generateLicense(license: LicenseName, options: GenerateLicenseOptions): Promise { 23 | if (!isKnownLicenseName(license)) { 24 | throw new TypeError(`The given license: '${String(license)}' is invalid or not supported`); 25 | } 26 | 27 | // Lazy-load the generate function to use 28 | const generate = (() => { 29 | switch (license) { 30 | case "APACHE-2.0": 31 | return apache2; 32 | 33 | case "AGPL-3.0": 34 | return agpl; 35 | 36 | case "MIT": 37 | return mit; 38 | 39 | case "ARTISTIC-2.0": 40 | return artistic2; 41 | 42 | case "BSD-2-CLAUSE": 43 | return bsd2; 44 | 45 | case "BSD-3-CLAUSE": 46 | return bsd3; 47 | 48 | case "CC-BY-4.0": 49 | return ccby4; 50 | 51 | case "CC-BY-SA-4.0": 52 | return ccbysa4; 53 | 54 | case "EPL-1.0": 55 | return epl1; 56 | 57 | case "GPL-2.0": 58 | return gpl2; 59 | 60 | case "GPL-3.0": 61 | return gpl3; 62 | 63 | case "LGPL-3.0": 64 | return lgpl3; 65 | 66 | case "MPL-2.0": 67 | return mpl2; 68 | 69 | case "ZLIB": 70 | return zlib; 71 | } 72 | })(); 73 | 74 | // Call the generate function with the options 75 | return generate(options); 76 | } 77 | -------------------------------------------------------------------------------- /src/license/generate-license/licenses/generate-apache-2.0.ts: -------------------------------------------------------------------------------- 1 | import type {GenerateLicenseOptions} from "../generate-license-options.js"; 2 | import {listFormat} from "../../../util/list-format/list-format.js"; 3 | import {formatContributor} from "../../../contributor/format-contributor.js"; 4 | 5 | /** 6 | * Generates an Apache-2.0 license 7 | */ 8 | export function generate({contributors, prettier, config}: GenerateLicenseOptions): Promise { 9 | return prettier.format( 10 | `\ 11 | Apache License 12 | Version 2.0, January 2004 13 | http://www.apache.org/licenses/ 14 | 15 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 16 | 17 | 1. Definitions. 18 | 19 | "License" shall mean the terms and conditions for use, reproduction, 20 | and distribution as defined by Sections 1 through 9 of this document. 21 | 22 | "Licensor" shall mean the copyright owner or entity authorized by 23 | the copyright owner that is granting the License. 24 | 25 | "Legal Entity" shall mean the union of the acting entity and all 26 | other entities that control, are controlled by, or are under common 27 | control with that entity. For the purposes of this definition, 28 | "control" means (i) the power, direct or indirect, to cause the 29 | direction or management of such entity, whether by contract or 30 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 31 | outstanding shares, or (iii) beneficial ownership of such entity. 32 | 33 | "You" (or "Your") shall mean an individual or Legal Entity 34 | exercising permissions granted by this License. 35 | 36 | "Source" form shall mean the preferred form for making modifications, 37 | including but not limited to software source code, documentation 38 | source, and configuration files. 39 | 40 | "Object" form shall mean any form resulting from mechanical 41 | transformation or translation of a Source form, including but 42 | not limited to compiled object code, generated documentation, 43 | and conversions to other media types. 44 | 45 | "Work" shall mean the work of authorship, whether in Source or 46 | Object form, made available under the License, as indicated by a 47 | copyright notice that is included in or attached to the work 48 | (an example is provided in the Appendix below). 49 | 50 | "Derivative Works" shall mean any work, whether in Source or Object 51 | form, that is based on (or derived from) the Work and for which the 52 | editorial revisions, annotations, elaborations, or other modifications 53 | represent, as a whole, an original work of authorship. For the purposes 54 | of this License, Derivative Works shall not include works that remain 55 | separable from, or merely link (or bind by name) to the interfaces of, 56 | the Work and Derivative Works thereof. 57 | 58 | "Contribution" shall mean any work of authorship, including 59 | the original version of the Work and any modifications or additions 60 | to that Work or Derivative Works thereof, that is intentionally 61 | submitted to Licensor for inclusion in the Work by the copyright owner 62 | or by an individual or Legal Entity authorized to submit on behalf of 63 | the copyright owner. For the purposes of this definition, "submitted" 64 | means any form of electronic, verbal, or written communication sent 65 | to the Licensor or its representatives, including but not limited to 66 | communication on electronic mailing lists, source code control systems, 67 | and issue tracking systems that are managed by, or on behalf of, the 68 | Licensor for the purpose of discussing and improving the Work, but 69 | excluding communication that is conspicuously marked or otherwise 70 | designated in writing by the copyright owner as "Not a Contribution." 71 | 72 | "Contributor" shall mean Licensor and any individual or Legal Entity 73 | on behalf of whom a Contribution has been received by Licensor and 74 | subsequently incorporated within the Work. 75 | 76 | 2. Grant of Copyright License. Subject to the terms and conditions of 77 | this License, each Contributor hereby grants to You a perpetual, 78 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 79 | copyright license to reproduce, prepare Derivative Works of, 80 | publicly display, publicly perform, sublicense, and distribute the 81 | Work and such Derivative Works in Source or Object form. 82 | 83 | 3. Grant of Patent License. Subject to the terms and conditions of 84 | this License, each Contributor hereby grants to You a perpetual, 85 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 86 | (except as stated in this section) patent license to make, have made, 87 | use, offer to sell, sell, import, and otherwise transfer the Work, 88 | where such license applies only to those patent claims licensable 89 | by such Contributor that are necessarily infringed by their 90 | Contribution(s) alone or by combination of their Contribution(s) 91 | with the Work to which such Contribution(s) was submitted. If You 92 | institute patent litigation against any entity (including a 93 | cross-claim or counterclaim in a lawsuit) alleging that the Work 94 | or a Contribution incorporated within the Work constitutes direct 95 | or contributory patent infringement, then any patent licenses 96 | granted to You under this License for that Work shall terminate 97 | as of the date such litigation is filed. 98 | 99 | 4. Redistribution. You may reproduce and distribute copies of the 100 | Work or Derivative Works thereof in any medium, with or without 101 | modifications, and in Source or Object form, provided that You 102 | meet the following conditions: 103 | 104 | (a) You must give any other recipients of the Work or 105 | Derivative Works a copy of this License; and 106 | 107 | (b) You must cause any modified files to carry prominent notices 108 | stating that You changed the files; and 109 | 110 | (c) You must retain, in the Source form of any Derivative Works 111 | that You distribute, all copyright, patent, trademark, and 112 | attribution notices from the Source form of the Work, 113 | excluding those notices that do not pertain to any part of 114 | the Derivative Works; and 115 | 116 | (d) If the Work includes a "NOTICE" text file as part of its 117 | distribution, then any Derivative Works that You distribute must 118 | include a readable copy of the attribution notices contained 119 | within such NOTICE file, excluding those notices that do not 120 | pertain to any part of the Derivative Works, in at least one 121 | of the following places: within a NOTICE text file distributed 122 | as part of the Derivative Works; within the Source form or 123 | documentation, if provided along with the Derivative Works; or, 124 | within a display generated by the Derivative Works, if and 125 | wherever such third-party notices normally appear. The contents 126 | of the NOTICE file are for informational purposes only and 127 | do not modify the License. You may add Your own attribution 128 | notices within Derivative Works that You distribute, alongside 129 | or as an addendum to the NOTICE text from the Work, provided 130 | that such additional attribution notices cannot be construed 131 | as modifying the License. 132 | 133 | You may add Your own copyright statement to Your modifications and 134 | may provide additional or different license terms and conditions 135 | for use, reproduction, or distribution of Your modifications, or 136 | for any such Derivative Works as a whole, provided Your use, 137 | reproduction, and distribution of the Work otherwise complies with 138 | the conditions stated in this License. 139 | 140 | 5. Submission of Contributions. Unless You explicitly state otherwise, 141 | any Contribution intentionally submitted for inclusion in the Work 142 | by You to the Licensor shall be under the terms and conditions of 143 | this License, without any additional terms or conditions. 144 | Notwithstanding the above, nothing herein shall supersede or modify 145 | the terms of any separate license agreement you may have executed 146 | with Licensor regarding such Contributions. 147 | 148 | 6. Trademarks. This License does not grant permission to use the trade 149 | names, trademarks, service marks, or product names of the Licensor, 150 | except as required for reasonable and customary use in describing the 151 | origin of the Work and reproducing the content of the NOTICE file. 152 | 153 | 7. Disclaimer of Warranty. Unless required by applicable law or 154 | agreed to in writing, Licensor provides the Work (and each 155 | Contributor provides its Contributions) on an "AS IS" BASIS, 156 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 157 | implied, including, without limitation, any warranties or conditions 158 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 159 | PARTICULAR PURPOSE. You are solely responsible for determining the 160 | appropriateness of using or redistributing the Work and assume any 161 | risks associated with Your exercise of permissions under this License. 162 | 163 | 8. Limitation of Liability. In no event and under no legal theory, 164 | whether in tort (including negligence), contract, or otherwise, 165 | unless required by applicable law (such as deliberate and grossly 166 | negligent acts) or agreed to in writing, shall any Contributor be 167 | liable to You for damages, including any direct, indirect, special, 168 | incidental, or consequential damages of any character arising as a 169 | result of this License or out of the use or inability to use the 170 | Work (including but not limited to damages for loss of goodwill, 171 | work stoppage, computer failure or malfunction, or any and all 172 | other commercial damages or losses), even if such Contributor 173 | has been advised of the possibility of such damages. 174 | 175 | 9. Accepting Warranty or Additional Liability. While redistributing 176 | the Work or Derivative Works thereof, You may choose to offer, 177 | and charge a fee for, acceptance of support, warranty, indemnity, 178 | or other liability obligations and/or rights consistent with this 179 | License. However, in accepting such obligations, You may act only 180 | on Your own behalf and on Your sole responsibility, not on behalf 181 | of any other Contributor, and only if You agree to indemnify, 182 | defend, and hold each Contributor harmless for any liability 183 | incurred by, or claims asserted against, such Contributor by reason 184 | of your accepting any such warranty or additional liability. 185 | 186 | END OF TERMS AND CONDITIONS 187 | 188 | APPENDIX: How to apply the Apache License to your work. 189 | 190 | To apply the Apache License to your work, attach the following 191 | boilerplate notice, with the fields enclosed by brackets "[]" 192 | replaced with your own identifying information. (Don't include 193 | the brackets!) The text should be enclosed in the appropriate 194 | comment syntax for the file format. We also recommend that a 195 | file or class name and description of purpose be included on the 196 | same "printed page" as the copyright notice for easier 197 | identification within third-party archives. 198 | 199 | Copyright ${new Date().getFullYear()} ${listFormat( 200 | contributors.map(contributor => formatContributor(contributor, "markdown")), 201 | "and" 202 | )} 203 | 204 | Licensed under the Apache License, Version 2.0 (the "License"); 205 | you may not use this file except in compliance with the License. 206 | You may obtain a copy of the License at 207 | 208 | http://www.apache.org/licenses/LICENSE-2.0 209 | 210 | Unless required by applicable law or agreed to in writing, software 211 | distributed under the License is distributed on an "AS IS" BASIS, 212 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 213 | See the License for the specific language governing permissions and 214 | limitations under the License. 215 | `, 216 | { 217 | ...config.prettier, 218 | parser: "markdown" 219 | } 220 | ); 221 | } 222 | -------------------------------------------------------------------------------- /src/license/generate-license/licenses/generate-artistic-2.0.ts: -------------------------------------------------------------------------------- 1 | import type {GenerateLicenseOptions} from "../generate-license-options.js"; 2 | 3 | /** 4 | * Generates a ARTISTIC-2.0 license 5 | */ 6 | export function generate({prettier, config}: GenerateLicenseOptions): Promise { 7 | return prettier.format( 8 | `\ 9 | The Artistic License 2.0 10 | 11 | Copyright (c) 2000-2006, The Perl Foundation. 12 | 13 | Everyone is permitted to copy and distribute verbatim copies 14 | of this license document, but changing it is not allowed. 15 | 16 | Preamble 17 | 18 | This license establishes the terms under which a given free software 19 | Package may be copied, modified, distributed, and/or redistributed. 20 | The intent is that the Copyright Holder maintains some artistic 21 | control over the development of that Package while still keeping the 22 | Package available as open source and free software. 23 | 24 | You are always permitted to make arrangements wholly outside of this 25 | license directly with the Copyright Holder of a given Package. If the 26 | terms of this license do not permit the full use that you propose to 27 | make of the Package, you should contact the Copyright Holder and seek 28 | a different licensing arrangement. 29 | 30 | Definitions 31 | 32 | "Copyright Holder" means the individual(s) or organization(s) 33 | named in the copyright notice for the entire Package. 34 | 35 | "Contributor" means any party that has contributed code or other 36 | material to the Package, in accordance with the Copyright Holder's 37 | procedures. 38 | 39 | "You" and "your" means any person who would like to copy, 40 | distribute, or modify the Package. 41 | 42 | "Package" means the collection of files distributed by the 43 | Copyright Holder, and derivatives of that collection and/or of 44 | those files. A given Package may consist of either the Standard 45 | Version, or a Modified Version. 46 | 47 | "Distribute" means providing a copy of the Package or making it 48 | accessible to anyone else, or in the case of a company or 49 | organization, to others outside of your company or organization. 50 | 51 | "Distributor Fee" means any fee that you charge for Distributing 52 | this Package or providing support for this Package to another 53 | party. It does not mean licensing fees. 54 | 55 | "Standard Version" refers to the Package if it has not been 56 | modified, or has been modified only in ways explicitly requested 57 | by the Copyright Holder. 58 | 59 | "Modified Version" means the Package, if it has been changed, and 60 | such changes were not explicitly requested by the Copyright 61 | Holder. 62 | 63 | "Original License" means this Artistic License as Distributed with 64 | the Standard Version of the Package, in its current version or as 65 | it may be modified by The Perl Foundation in the future. 66 | 67 | "Source" form means the source code, documentation source, and 68 | configuration files for the Package. 69 | 70 | "Compiled" form means the compiled bytecode, object code, binary, 71 | or any other form resulting from mechanical transformation or 72 | translation of the Source form. 73 | 74 | 75 | Permission for Use and Modification Without Distribution 76 | 77 | (1) You are permitted to use the Standard Version and create and use 78 | Modified Versions for any purpose without restriction, provided that 79 | you do not Distribute the Modified Version. 80 | 81 | 82 | Permissions for Redistribution of the Standard Version 83 | 84 | (2) You may Distribute verbatim copies of the Source form of the 85 | Standard Version of this Package in any medium without restriction, 86 | either gratis or for a Distributor Fee, provided that you duplicate 87 | all of the original copyright notices and associated disclaimers. At 88 | your discretion, such verbatim copies may or may not include a 89 | Compiled form of the Package. 90 | 91 | (3) You may apply any bug fixes, portability changes, and other 92 | modifications made available from the Copyright Holder. The resulting 93 | Package will still be considered the Standard Version, and as such 94 | will be subject to the Original License. 95 | 96 | 97 | Distribution of Modified Versions of the Package as Source 98 | 99 | (4) You may Distribute your Modified Version as Source (either gratis 100 | or for a Distributor Fee, and with or without a Compiled form of the 101 | Modified Version) provided that you clearly document how it differs 102 | from the Standard Version, including, but not limited to, documenting 103 | any non-standard features, executables, or modules, and provided that 104 | you do at least ONE of the following: 105 | 106 | (a) make the Modified Version available to the Copyright Holder 107 | of the Standard Version, under the Original License, so that the 108 | Copyright Holder may include your modifications in the Standard 109 | Version. 110 | 111 | (b) ensure that installation of your Modified Version does not 112 | prevent the user installing or running the Standard Version. In 113 | addition, the Modified Version must bear a name that is different 114 | from the name of the Standard Version. 115 | 116 | (c) allow anyone who receives a copy of the Modified Version to 117 | make the Source form of the Modified Version available to others 118 | under 119 | 120 | (i) the Original License or 121 | 122 | (ii) a license that permits the licensee to freely copy, 123 | modify and redistribute the Modified Version using the same 124 | licensing terms that apply to the copy that the licensee 125 | received, and requires that the Source form of the Modified 126 | Version, and of any works derived from it, be made freely 127 | available in that license fees are prohibited but Distributor 128 | Fees are allowed. 129 | 130 | 131 | Distribution of Compiled Forms of the Standard Version 132 | or Modified Versions without the Source 133 | 134 | (5) You may Distribute Compiled forms of the Standard Version without 135 | the Source, provided that you include complete instructions on how to 136 | get the Source of the Standard Version. Such instructions must be 137 | valid at the time of your distribution. If these instructions, at any 138 | time while you are carrying out such distribution, become invalid, you 139 | must provide new instructions on demand or cease further distribution. 140 | If you provide valid instructions or cease distribution within thirty 141 | days after you become aware that the instructions are invalid, then 142 | you do not forfeit any of your rights under this license. 143 | 144 | (6) You may Distribute a Modified Version in Compiled form without 145 | the Source, provided that you comply with Section 4 with respect to 146 | the Source of the Modified Version. 147 | 148 | 149 | Aggregating or Linking the Package 150 | 151 | (7) You may aggregate the Package (either the Standard Version or 152 | Modified Version) with other packages and Distribute the resulting 153 | aggregation provided that you do not charge a licensing fee for the 154 | Package. Distributor Fees are permitted, and licensing fees for other 155 | components in the aggregation are permitted. The terms of this license 156 | apply to the use and Distribution of the Standard or Modified Versions 157 | as included in the aggregation. 158 | 159 | (8) You are permitted to link Modified and Standard Versions with 160 | other works, to embed the Package in a larger work of your own, or to 161 | build stand-alone binary or bytecode versions of applications that 162 | include the Package, and Distribute the result without restriction, 163 | provided the result does not expose a direct interface to the Package. 164 | 165 | 166 | Items That are Not Considered Part of a Modified Version 167 | 168 | (9) Works (including, but not limited to, modules and scripts) that 169 | merely extend or make use of the Package, do not, by themselves, cause 170 | the Package to be a Modified Version. In addition, such works are not 171 | considered parts of the Package itself, and are not subject to the 172 | terms of this license. 173 | 174 | 175 | General Provisions 176 | 177 | (10) Any use, modification, and distribution of the Standard or 178 | Modified Versions is governed by this Artistic License. By using, 179 | modifying or distributing the Package, you accept this license. Do not 180 | use, modify, or distribute the Package, if you do not accept this 181 | license. 182 | 183 | (11) If your Modified Version has been derived from a Modified 184 | Version made by someone other than you, you are nevertheless required 185 | to ensure that your Modified Version complies with the requirements of 186 | this license. 187 | 188 | (12) This license does not grant you the right to use any trademark, 189 | service mark, tradename, or logo of the Copyright Holder. 190 | 191 | (13) This license includes the non-exclusive, worldwide, 192 | free-of-charge patent license to make, have made, use, offer to sell, 193 | sell, import and otherwise transfer the Package with respect to any 194 | patent claims licensable by the Copyright Holder that are necessarily 195 | infringed by the Package. If you institute patent litigation 196 | (including a cross-claim or counterclaim) against any party alleging 197 | that the Package constitutes direct or contributory patent 198 | infringement, then this Artistic License to you shall terminate on the 199 | date that such litigation is filed. 200 | 201 | (14) Disclaimer of Warranty: 202 | THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS 203 | IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED 204 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR 205 | NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL 206 | LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL 207 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 208 | DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF 209 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 210 | `, 211 | { 212 | ...config.prettier, 213 | parser: "markdown" 214 | } 215 | ); 216 | } 217 | -------------------------------------------------------------------------------- /src/license/generate-license/licenses/generate-bsd-2-clause.ts: -------------------------------------------------------------------------------- 1 | import type {GenerateLicenseOptions} from "../generate-license-options.js"; 2 | import {listFormat} from "../../../util/list-format/list-format.js"; 3 | import {formatContributor} from "../../../contributor/format-contributor.js"; 4 | 5 | /** 6 | * Generates a BSD-2-CLAUSE license 7 | */ 8 | export function generate({contributors, prettier, config}: GenerateLicenseOptions): Promise { 9 | return prettier.format( 10 | `\ 11 | BSD 2-Clause License 12 | 13 | Copyright © ${new Date().getFullYear()}, ${listFormat( 14 | contributors.map(contributor => formatContributor(contributor, "markdown")), 15 | "and" 16 | )} 17 | All rights reserved. 18 | 19 | Redistribution and use in source and binary forms, with or without 20 | modification, are permitted provided that the following conditions are met: 21 | 22 | * Redistributions of source code must retain the above copyright notice, this 23 | list of conditions and the following disclaimer. 24 | 25 | * Redistributions in binary form must reproduce the above copyright notice, 26 | this list of conditions and the following disclaimer in the documentation 27 | and/or other materials provided with the distribution. 28 | 29 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 30 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 31 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 32 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 33 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 34 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 35 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 36 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 37 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 38 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 39 | `, 40 | { 41 | ...config.prettier, 42 | parser: "markdown" 43 | } 44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /src/license/generate-license/licenses/generate-bsd-3-clause.ts: -------------------------------------------------------------------------------- 1 | import type {GenerateLicenseOptions} from "../generate-license-options.js"; 2 | import {listFormat} from "../../../util/list-format/list-format.js"; 3 | import {formatContributor} from "../../../contributor/format-contributor.js"; 4 | 5 | /** 6 | * Generates a BSD-3-CLAUSE license 7 | */ 8 | export function generate({contributors, prettier, config}: GenerateLicenseOptions): Promise { 9 | return prettier.format( 10 | `\ 11 | BSD 3-Clause License 12 | 13 | Copyright © ${new Date().getFullYear()}, ${listFormat( 14 | contributors.map(contributor => formatContributor(contributor, "markdown")), 15 | "and" 16 | )} 17 | All rights reserved. 18 | 19 | Redistribution and use in source and binary forms, with or without 20 | modification, are permitted provided that the following conditions are met: 21 | 22 | * Redistributions of source code must retain the above copyright notice, this 23 | list of conditions and the following disclaimer. 24 | 25 | * Redistributions in binary form must reproduce the above copyright notice, 26 | this list of conditions and the following disclaimer in the documentation 27 | and/or other materials provided with the distribution. 28 | 29 | * Neither the name of the copyright holder nor the names of its 30 | contributors may be used to endorse or promote products derived from 31 | this software without specific prior written permission. 32 | 33 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 34 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 35 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 36 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 37 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 38 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 39 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 40 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 41 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 42 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 43 | `, 44 | { 45 | ...config.prettier, 46 | parser: "markdown" 47 | } 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /src/license/generate-license/licenses/generate-epl-1.0.ts: -------------------------------------------------------------------------------- 1 | import type {GenerateLicenseOptions} from "../generate-license-options.js"; 2 | 3 | /** 4 | * Generates a EPL-1.0 license 5 | */ 6 | export function generate({prettier, config}: GenerateLicenseOptions): Promise { 7 | return prettier.format( 8 | `\ 9 | Eclipse Public License - v 1.0 10 | 11 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 12 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 13 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 14 | 15 | 1. DEFINITIONS 16 | 17 | "Contribution" means: 18 | a) in the case of the initial Contributor, the initial code and 19 | documentation distributed under this Agreement, and 20 | b) in the case of each subsequent Contributor: 21 | i) changes to the Program, and 22 | ii) additions to the Program; 23 | 24 | where such changes and/or additions to the Program originate from and are 25 | distributed by that particular Contributor. A Contribution 'originates' from a 26 | Contributor if it was added to the Program by such Contributor itself or 27 | anyone acting on such Contributor's behalf. Contributions do not include 28 | additions to the Program which: (i) are separate modules of software 29 | distributed in conjunction with the Program under their own license agreement, 30 | and (ii) are not derivative works of the Program. 31 | "Contributor" means any person or entity that distributes the Program. 32 | 33 | "Licensed Patents" mean patent claims licensable by a Contributor which are 34 | necessarily infringed by the use or sale of its Contribution alone or when 35 | combined with the Program. 36 | 37 | "Program" means the Contributions distributed in accordance with this 38 | Agreement. 39 | 40 | "Recipient" means anyone who receives the Program under this Agreement, 41 | including all Contributors. 42 | 43 | 2. GRANT OF RIGHTS 44 | 45 | a) Subject to the terms of this Agreement, each Contributor hereby grants 46 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 47 | reproduce, prepare derivative works of, publicly display, publicly 48 | perform, distribute and sublicense the Contribution of such Contributor, 49 | if any, and such derivative works, in source code and object code form. 50 | 51 | b) Subject to the terms of this Agreement, each Contributor hereby grants 52 | Recipient a non-exclusive, worldwide, royalty-free patent license under 53 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 54 | transfer the Contribution of such Contributor, if any, in source code and 55 | object code form. This patent license shall apply to the combination of 56 | the Contribution and the Program if, at the time the Contribution is 57 | added by the Contributor, such addition of the Contribution causes such 58 | combination to be covered by the Licensed Patents. The patent license 59 | shall not apply to any other combinations which include the Contribution. 60 | No hardware per se is licensed hereunder. 61 | 62 | c) Recipient understands that although each Contributor grants the 63 | licenses to its Contributions set forth herein, no assurances are 64 | provided by any Contributor that the Program does not infringe the patent 65 | or other intellectual property rights of any other entity. Each 66 | Contributor disclaims any liability to Recipient for claims brought by 67 | any other entity based on infringement of intellectual property rights or 68 | otherwise. As a condition to exercising the rights and licenses granted 69 | hereunder, each Recipient hereby assumes sole responsibility to secure 70 | any other intellectual property rights needed, if any. For example, if a 71 | third party patent license is required to allow Recipient to distribute 72 | the Program, it is Recipient's responsibility to acquire that license 73 | before distributing the Program. 74 | 75 | d) Each Contributor represents that to its knowledge it has sufficient 76 | copyright rights in its Contribution, if any, to grant the copyright 77 | license set forth in this Agreement. 78 | 79 | 3. REQUIREMENTS 80 | A Contributor may choose to distribute the Program in object code form under 81 | its own license agreement, provided that: 82 | 83 | a) it complies with the terms and conditions of this Agreement; and 84 | 85 | b) its license agreement: 86 | i) effectively disclaims on behalf of all Contributors all 87 | warranties and conditions, express and implied, including warranties 88 | or conditions of title and non-infringement, and implied warranties 89 | or conditions of merchantability and fitness for a particular 90 | purpose; 91 | ii) effectively excludes on behalf of all Contributors all liability 92 | for damages, including direct, indirect, special, incidental and 93 | consequential damages, such as lost profits; 94 | iii) states that any provisions which differ from this Agreement are 95 | offered by that Contributor alone and not by any other party; and 96 | iv) states that source code for the Program is available from such 97 | Contributor, and informs licensees how to obtain it in a reasonable 98 | manner on or through a medium customarily used for software 99 | exchange. 100 | 101 | When the Program is made available in source code form: 102 | 103 | a) it must be made available under this Agreement; and 104 | 105 | b) a copy of this Agreement must be included with each copy of the 106 | Program. 107 | Contributors may not remove or alter any copyright notices contained within 108 | the Program. 109 | 110 | Each Contributor must identify itself as the originator of its Contribution, 111 | if any, in a manner that reasonably allows subsequent Recipients to identify 112 | the originator of the Contribution. 113 | 114 | 4. COMMERCIAL DISTRIBUTION 115 | Commercial distributors of software may accept certain responsibilities with 116 | respect to end users, business partners and the like. While this license is 117 | intended to facilitate the commercial use of the Program, the Contributor who 118 | includes the Program in a commercial product offering should do so in a manner 119 | which does not create potential liability for other Contributors. Therefore, 120 | if a Contributor includes the Program in a commercial product offering, such 121 | Contributor ("Commercial Contributor") hereby agrees to defend and indemnify 122 | every other Contributor ("Indemnified Contributor") against any losses, 123 | damages and costs (collectively "Losses") arising from claims, lawsuits and 124 | other legal actions brought by a third party against the Indemnified 125 | Contributor to the extent caused by the acts or omissions of such Commercial 126 | Contributor in connection with its distribution of the Program in a commercial 127 | product offering. The obligations in this section do not apply to any claims 128 | or Losses relating to any actual or alleged intellectual property 129 | infringement. In order to qualify, an Indemnified Contributor must: a) 130 | promptly notify the Commercial Contributor in writing of such claim, and b) 131 | allow the Commercial Contributor to control, and cooperate with the Commercial 132 | Contributor in, the defense and any related settlement negotiations. The 133 | Indemnified Contributor may participate in any such claim at its own expense. 134 | 135 | For example, a Contributor might include the Program in a commercial product 136 | offering, Product X. That Contributor is then a Commercial Contributor. If 137 | that Commercial Contributor then makes performance claims, or offers 138 | warranties related to Product X, those performance claims and warranties are 139 | such Commercial Contributor's responsibility alone. Under this section, the 140 | Commercial Contributor would have to defend claims against the other 141 | Contributors related to those performance claims and warranties, and if a 142 | court requires any other Contributor to pay any damages as a result, the 143 | Commercial Contributor must pay those damages. 144 | 145 | 5. NO WARRANTY 146 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN 147 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 148 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, 149 | NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each 150 | Recipient is solely responsible for determining the appropriateness of using 151 | and distributing the Program and assumes all risks associated with its 152 | exercise of rights under this Agreement , including but not limited to the 153 | risks and costs of program errors, compliance with applicable laws, damage to 154 | or loss of data, programs or equipment, and unavailability or interruption of 155 | operations. 156 | 157 | 6. DISCLAIMER OF LIABILITY 158 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 159 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 160 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 161 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 162 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 163 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 164 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 165 | OF SUCH DAMAGES. 166 | 167 | 7. GENERAL 168 | 169 | If any provision of this Agreement is invalid or unenforceable under 170 | applicable law, it shall not affect the validity or enforceability of the 171 | remainder of the terms of this Agreement, and without further action by the 172 | parties hereto, such provision shall be reformed to the minimum extent 173 | necessary to make such provision valid and enforceable. 174 | 175 | If Recipient institutes patent litigation against any entity (including a 176 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 177 | (excluding combinations of the Program with other software or hardware) 178 | infringes such Recipient's patent(s), then such Recipient's rights granted 179 | under Section 2(b) shall terminate as of the date such litigation is filed. 180 | 181 | All Recipient's rights under this Agreement shall terminate if it fails to 182 | comply with any of the material terms or conditions of this Agreement and does 183 | not cure such failure in a reasonable period of time after becoming aware of 184 | such noncompliance. If all Recipient's rights under this Agreement terminate, 185 | Recipient agrees to cease use and distribution of the Program as soon as 186 | reasonably practicable. However, Recipient's obligations under this Agreement 187 | and any licenses granted by Recipient relating to the Program shall continue 188 | and survive. 189 | 190 | Everyone is permitted to copy and distribute copies of this Agreement, but in 191 | order to avoid inconsistency the Agreement is copyrighted and may only be 192 | modified in the following manner. The Agreement Steward reserves the right to 193 | publish new versions (including revisions) of this Agreement from time to 194 | time. No one other than the Agreement Steward has the right to modify this 195 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 196 | Eclipse Foundation may assign the responsibility to serve as the Agreement 197 | Steward to a suitable separate entity. Each new version of the Agreement will 198 | be given a distinguishing version number. The Program (including 199 | Contributions) may always be distributed subject to the version of the 200 | Agreement under which it was received. In addition, after a new version of the 201 | Agreement is published, Contributor may elect to distribute the Program 202 | (including its Contributions) under the new version. Except as expressly 203 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 204 | licenses to the intellectual property of any Contributor under this Agreement, 205 | whether expressly, by implication, estoppel or otherwise. All rights in the 206 | Program not expressly granted under this Agreement are reserved. 207 | 208 | This Agreement is governed by the laws of the State of New York and the 209 | intellectual property laws of the United States of America. No party to this 210 | Agreement will bring a legal action under this Agreement more than one year 211 | after the cause of action arose. Each party waives its rights to a jury trial 212 | in any resulting litigation. 213 | `, 214 | { 215 | ...config.prettier, 216 | parser: "markdown" 217 | } 218 | ); 219 | } 220 | -------------------------------------------------------------------------------- /src/license/generate-license/licenses/generate-lgpl-3.0.ts: -------------------------------------------------------------------------------- 1 | import type {GenerateLicenseOptions} from "../generate-license-options.js"; 2 | 3 | /** 4 | * Generates a LGPL-3.0 license 5 | */ 6 | export function generate({prettier, config}: GenerateLicenseOptions): Promise { 7 | return prettier.format( 8 | `\ 9 | GNU LESSER GENERAL PUBLIC LICENSE 10 | Version 3, 29 June 2007 11 | 12 | Copyright (C) 2007 Free Software Foundation, Inc. 13 | Everyone is permitted to copy and distribute verbatim copies 14 | of this license document, but changing it is not allowed. 15 | 16 | 17 | This version of the GNU Lesser General Public License incorporates 18 | the terms and conditions of version 3 of the GNU General Public 19 | License, supplemented by the additional permissions listed below. 20 | 21 | 0. Additional Definitions. 22 | 23 | As used herein, "this License" refers to version 3 of the GNU Lesser 24 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 25 | General Public License. 26 | 27 | "The Library" refers to a covered work governed by this License, 28 | other than an Application or a Combined Work as defined below. 29 | 30 | An "Application" is any work that makes use of an interface provided 31 | by the Library, but which is not otherwise based on the Library. 32 | Defining a subclass of a class defined by the Library is deemed a mode 33 | of using an interface provided by the Library. 34 | 35 | A "Combined Work" is a work produced by combining or linking an 36 | Application with the Library. The particular version of the Library 37 | with which the Combined Work was made is also called the "Linked 38 | Version". 39 | 40 | The "Minimal Corresponding Source" for a Combined Work means the 41 | Corresponding Source for the Combined Work, excluding any source code 42 | for portions of the Combined Work that, considered in isolation, are 43 | based on the Application, and not on the Linked Version. 44 | 45 | The "Corresponding Application Code" for a Combined Work means the 46 | object code and/or source code for the Application, including any data 47 | and utility programs needed for reproducing the Combined Work from the 48 | Application, but excluding the System Libraries of the Combined Work. 49 | 50 | 1. Exception to Section 3 of the GNU GPL. 51 | 52 | You may convey a covered work under sections 3 and 4 of this License 53 | without being bound by section 3 of the GNU GPL. 54 | 55 | 2. Conveying Modified Versions. 56 | 57 | If you modify a copy of the Library, and, in your modifications, a 58 | facility refers to a function or data to be supplied by an Application 59 | that uses the facility (other than as an argument passed when the 60 | facility is invoked), then you may convey a copy of the modified 61 | version: 62 | 63 | a) under this License, provided that you make a good faith effort to 64 | ensure that, in the event an Application does not supply the 65 | function or data, the facility still operates, and performs 66 | whatever part of its purpose remains meaningful, or 67 | 68 | b) under the GNU GPL, with none of the additional permissions of 69 | this License applicable to that copy. 70 | 71 | 3. Object Code Incorporating Material from Library Header Files. 72 | 73 | The object code form of an Application may incorporate material from 74 | a header file that is part of the Library. You may convey such object 75 | code under terms of your choice, provided that, if the incorporated 76 | material is not limited to numerical parameters, data structure 77 | layouts and accessors, or small macros, inline functions and templates 78 | (ten or fewer lines in length), you do both of the following: 79 | 80 | a) Give prominent notice with each copy of the object code that the 81 | Library is used in it and that the Library and its use are 82 | covered by this License. 83 | 84 | b) Accompany the object code with a copy of the GNU GPL and this license 85 | document. 86 | 87 | 4. Combined Works. 88 | 89 | You may convey a Combined Work under terms of your choice that, 90 | taken together, effectively do not restrict modification of the 91 | portions of the Library contained in the Combined Work and reverse 92 | engineering for debugging such modifications, if you also do each of 93 | the following: 94 | 95 | a) Give prominent notice with each copy of the Combined Work that 96 | the Library is used in it and that the Library and its use are 97 | covered by this License. 98 | 99 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 100 | document. 101 | 102 | c) For a Combined Work that displays copyright notices during 103 | execution, include the copyright notice for the Library among 104 | these notices, as well as a reference directing the user to the 105 | copies of the GNU GPL and this license document. 106 | 107 | d) Do one of the following: 108 | 109 | 0) Convey the Minimal Corresponding Source under the terms of this 110 | License, and the Corresponding Application Code in a form 111 | suitable for, and under terms that permit, the user to 112 | recombine or relink the Application with a modified version of 113 | the Linked Version to produce a modified Combined Work, in the 114 | manner specified by section 6 of the GNU GPL for conveying 115 | Corresponding Source. 116 | 117 | 1) Use a suitable shared library mechanism for linking with the 118 | Library. A suitable mechanism is one that (a) uses at run time 119 | a copy of the Library already present on the user's computer 120 | system, and (b) will operate properly with a modified version 121 | of the Library that is interface-compatible with the Linked 122 | Version. 123 | 124 | e) Provide Installation Information, but only if you would otherwise 125 | be required to provide such information under section 6 of the 126 | GNU GPL, and only to the extent that such information is 127 | necessary to install and execute a modified version of the 128 | Combined Work produced by recombining or relinking the 129 | Application with a modified version of the Linked Version. (If 130 | you use option 4d0, the Installation Information must accompany 131 | the Minimal Corresponding Source and Corresponding Application 132 | Code. If you use option 4d1, you must provide the Installation 133 | Information in the manner specified by section 6 of the GNU GPL 134 | for conveying Corresponding Source.) 135 | 136 | 5. Combined Libraries. 137 | 138 | You may place library facilities that are a work based on the 139 | Library side by side in a single library together with other library 140 | facilities that are not Applications and are not covered by this 141 | License, and convey such a combined library under terms of your 142 | choice, if you do both of the following: 143 | 144 | a) Accompany the combined library with a copy of the same work based 145 | on the Library, uncombined with any other library facilities, 146 | conveyed under the terms of this License. 147 | 148 | b) Give prominent notice with the combined library that part of it 149 | is a work based on the Library, and explaining where to find the 150 | accompanying uncombined form of the same work. 151 | 152 | 6. Revised Versions of the GNU Lesser General Public License. 153 | 154 | The Free Software Foundation may publish revised and/or new versions 155 | of the GNU Lesser General Public License from time to time. Such new 156 | versions will be similar in spirit to the present version, but may 157 | differ in detail to address new problems or concerns. 158 | 159 | Each version is given a distinguishing version number. If the 160 | Library as you received it specifies that a certain numbered version 161 | of the GNU Lesser General Public License "or any later version" 162 | applies to it, you have the option of following the terms and 163 | conditions either of that published version or of any later version 164 | published by the Free Software Foundation. If the Library as you 165 | received it does not specify a version number of the GNU Lesser 166 | General Public License, you may choose any version of the GNU Lesser 167 | General Public License ever published by the Free Software Foundation. 168 | 169 | If the Library as you received it specifies that a proxy can decide 170 | whether future versions of the GNU Lesser General Public License shall 171 | apply, that proxy's public statement of acceptance of any version is 172 | permanent authorization for you to choose that version for the 173 | Library. 174 | `, 175 | { 176 | ...config.prettier, 177 | parser: "markdown" 178 | } 179 | ); 180 | } 181 | -------------------------------------------------------------------------------- /src/license/generate-license/licenses/generate-mit.ts: -------------------------------------------------------------------------------- 1 | import type {GenerateLicenseOptions} from "../generate-license-options.js"; 2 | import {listFormat} from "../../../util/list-format/list-format.js"; 3 | import {formatContributor} from "../../../contributor/format-contributor.js"; 4 | 5 | /** 6 | * Generates a MIT license 7 | */ 8 | export function generate({contributors, prettier, config}: GenerateLicenseOptions): Promise { 9 | return prettier.format( 10 | `\ 11 | The MIT License (MIT) 12 | 13 | Copyright © ${new Date().getFullYear()} ${listFormat( 14 | contributors.map(contributor => formatContributor(contributor, "markdown")), 15 | "and" 16 | )} 17 | 18 | Permission is hereby granted, free of charge, to any person obtaining a copy 19 | of this software and associated documentation files (the "Software"), to deal 20 | in the Software without restriction, including without limitation the rights 21 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 22 | copies of the Software, and to permit persons to whom the Software is 23 | furnished to do so, subject to the following conditions: 24 | 25 | The above copyright notice and this permission notice shall be included in all 26 | copies or substantial portions of the Software. 27 | 28 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 29 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 30 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 31 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 32 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 33 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 34 | SOFTWARE`, 35 | { 36 | ...config.prettier, 37 | parser: "markdown" 38 | } 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /src/license/generate-license/licenses/generate-mpl-2.0.ts: -------------------------------------------------------------------------------- 1 | import type {GenerateLicenseOptions} from "../generate-license-options.js"; 2 | 3 | /** 4 | * Generates a MPL-2.0 license 5 | */ 6 | export function generate({prettier, config}: GenerateLicenseOptions): Promise { 7 | return prettier.format( 8 | `\ 9 | Mozilla Public License Version 2.0 10 | ================================== 11 | 12 | 1. Definitions 13 | -------------- 14 | 15 | 1.1. "Contributor" 16 | means each individual or legal entity that creates, contributes to 17 | the creation of, or owns Covered Software. 18 | 19 | 1.2. "Contributor Version" 20 | means the combination of the Contributions of others (if any) used 21 | by a Contributor and that particular Contributor's Contribution. 22 | 23 | 1.3. "Contribution" 24 | means Covered Software of a particular Contributor. 25 | 26 | 1.4. "Covered Software" 27 | means Source Code Form to which the initial Contributor has attached 28 | the notice in Exhibit A, the Executable Form of such Source Code 29 | Form, and Modifications of such Source Code Form, in each case 30 | including portions thereof. 31 | 32 | 1.5. "Incompatible With Secondary Licenses" 33 | means 34 | 35 | (a) that the initial Contributor has attached the notice described 36 | in Exhibit B to the Covered Software; or 37 | 38 | (b) that the Covered Software was made available under the terms of 39 | version 1.1 or earlier of the License, but not also under the 40 | terms of a Secondary License. 41 | 42 | 1.6. "Executable Form" 43 | means any form of the work other than Source Code Form. 44 | 45 | 1.7. "Larger Work" 46 | means a work that combines Covered Software with other material, in 47 | a separate file or files, that is not Covered Software. 48 | 49 | 1.8. "License" 50 | means this document. 51 | 52 | 1.9. "Licensable" 53 | means having the right to grant, to the maximum extent possible, 54 | whether at the time of the initial grant or subsequently, any and 55 | all of the rights conveyed by this License. 56 | 57 | 1.10. "Modifications" 58 | means any of the following: 59 | 60 | (a) any file in Source Code Form that results from an addition to, 61 | deletion from, or modification of the contents of Covered 62 | Software; or 63 | 64 | (b) any new file in Source Code Form that contains any Covered 65 | Software. 66 | 67 | 1.11. "Patent Claims" of a Contributor 68 | means any patent claim(s), including without limitation, method, 69 | process, and apparatus claims, in any patent Licensable by such 70 | Contributor that would be infringed, but for the grant of the 71 | License, by the making, using, selling, offering for sale, having 72 | made, import, or transfer of either its Contributions or its 73 | Contributor Version. 74 | 75 | 1.12. "Secondary License" 76 | means either the GNU General Public License, Version 2.0, the GNU 77 | Lesser General Public License, Version 2.1, the GNU Affero General 78 | Public License, Version 3.0, or any later versions of those 79 | licenses. 80 | 81 | 1.13. "Source Code Form" 82 | means the form of the work preferred for making modifications. 83 | 84 | 1.14. "You" (or "Your") 85 | means an individual or a legal entity exercising rights under this 86 | License. For legal entities, "You" includes any entity that 87 | controls, is controlled by, or is under common control with You. For 88 | purposes of this definition, "control" means (a) the power, direct 89 | or indirect, to cause the direction or management of such entity, 90 | whether by contract or otherwise, or (b) ownership of more than 91 | fifty percent (50%) of the outstanding shares or beneficial 92 | ownership of such entity. 93 | 94 | 2. License Grants and Conditions 95 | -------------------------------- 96 | 97 | 2.1. Grants 98 | 99 | Each Contributor hereby grants You a world-wide, royalty-free, 100 | non-exclusive license: 101 | 102 | (a) under intellectual property rights (other than patent or trademark) 103 | Licensable by such Contributor to use, reproduce, make available, 104 | modify, display, perform, distribute, and otherwise exploit its 105 | Contributions, either on an unmodified basis, with Modifications, or 106 | as part of a Larger Work; and 107 | 108 | (b) under Patent Claims of such Contributor to make, use, sell, offer 109 | for sale, have made, import, and otherwise transfer either its 110 | Contributions or its Contributor Version. 111 | 112 | 2.2. Effective Date 113 | 114 | The licenses granted in Section 2.1 with respect to any Contribution 115 | become effective for each Contribution on the date the Contributor first 116 | distributes such Contribution. 117 | 118 | 2.3. Limitations on Grant Scope 119 | 120 | The licenses granted in this Section 2 are the only rights granted under 121 | this License. No additional rights or licenses will be implied from the 122 | distribution or licensing of Covered Software under this License. 123 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 124 | Contributor: 125 | 126 | (a) for any code that a Contributor has removed from Covered Software; 127 | or 128 | 129 | (b) for infringements caused by: (i) Your and any other third party's 130 | modifications of Covered Software, or (ii) the combination of its 131 | Contributions with other software (except as part of its Contributor 132 | Version); or 133 | 134 | (c) under Patent Claims infringed by Covered Software in the absence of 135 | its Contributions. 136 | 137 | This License does not grant any rights in the trademarks, service marks, 138 | or logos of any Contributor (except as may be necessary to comply with 139 | the notice requirements in Section 3.4). 140 | 141 | 2.4. Subsequent Licenses 142 | 143 | No Contributor makes additional grants as a result of Your choice to 144 | distribute the Covered Software under a subsequent version of this 145 | License (see Section 10.2) or under the terms of a Secondary License (if 146 | permitted under the terms of Section 3.3). 147 | 148 | 2.5. Representation 149 | 150 | Each Contributor represents that the Contributor believes its 151 | Contributions are its original creation(s) or it has sufficient rights 152 | to grant the rights to its Contributions conveyed by this License. 153 | 154 | 2.6. Fair Use 155 | 156 | This License is not intended to limit any rights You have under 157 | applicable copyright doctrines of fair use, fair dealing, or other 158 | equivalents. 159 | 160 | 2.7. Conditions 161 | 162 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 163 | in Section 2.1. 164 | 165 | 3. Responsibilities 166 | ------------------- 167 | 168 | 3.1. Distribution of Source Form 169 | 170 | All distribution of Covered Software in Source Code Form, including any 171 | Modifications that You create or to which You contribute, must be under 172 | the terms of this License. You must inform recipients that the Source 173 | Code Form of the Covered Software is governed by the terms of this 174 | License, and how they can obtain a copy of this License. You may not 175 | attempt to alter or restrict the recipients' rights in the Source Code 176 | Form. 177 | 178 | 3.2. Distribution of Executable Form 179 | 180 | If You distribute Covered Software in Executable Form then: 181 | 182 | (a) such Covered Software must also be made available in Source Code 183 | Form, as described in Section 3.1, and You must inform recipients of 184 | the Executable Form how they can obtain a copy of such Source Code 185 | Form by reasonable means in a timely manner, at a charge no more 186 | than the cost of distribution to the recipient; and 187 | 188 | (b) You may distribute such Executable Form under the terms of this 189 | License, or sublicense it under different terms, provided that the 190 | license for the Executable Form does not attempt to limit or alter 191 | the recipients' rights in the Source Code Form under this License. 192 | 193 | 3.3. Distribution of a Larger Work 194 | 195 | You may create and distribute a Larger Work under terms of Your choice, 196 | provided that You also comply with the requirements of this License for 197 | the Covered Software. If the Larger Work is a combination of Covered 198 | Software with a work governed by one or more Secondary Licenses, and the 199 | Covered Software is not Incompatible With Secondary Licenses, this 200 | License permits You to additionally distribute such Covered Software 201 | under the terms of such Secondary License(s), so that the recipient of 202 | the Larger Work may, at their option, further distribute the Covered 203 | Software under the terms of either this License or such Secondary 204 | License(s). 205 | 206 | 3.4. Notices 207 | 208 | You may not remove or alter the substance of any license notices 209 | (including copyright notices, patent notices, disclaimers of warranty, 210 | or limitations of liability) contained within the Source Code Form of 211 | the Covered Software, except that You may alter any license notices to 212 | the extent required to remedy known factual inaccuracies. 213 | 214 | 3.5. Application of Additional Terms 215 | 216 | You may choose to offer, and to charge a fee for, warranty, support, 217 | indemnity or liability obligations to one or more recipients of Covered 218 | Software. However, You may do so only on Your own behalf, and not on 219 | behalf of any Contributor. You must make it absolutely clear that any 220 | such warranty, support, indemnity, or liability obligation is offered by 221 | You alone, and You hereby agree to indemnify every Contributor for any 222 | liability incurred by such Contributor as a result of warranty, support, 223 | indemnity or liability terms You offer. You may include additional 224 | disclaimers of warranty and limitations of liability specific to any 225 | jurisdiction. 226 | 227 | 4. Inability to Comply Due to Statute or Regulation 228 | --------------------------------------------------- 229 | 230 | If it is impossible for You to comply with any of the terms of this 231 | License with respect to some or all of the Covered Software due to 232 | statute, judicial order, or regulation then You must: (a) comply with 233 | the terms of this License to the maximum extent possible; and (b) 234 | describe the limitations and the code they affect. Such description must 235 | be placed in a text file included with all distributions of the Covered 236 | Software under this License. Except to the extent prohibited by statute 237 | or regulation, such description must be sufficiently detailed for a 238 | recipient of ordinary skill to be able to understand it. 239 | 240 | 5. Termination 241 | -------------- 242 | 243 | 5.1. The rights granted under this License will terminate automatically 244 | if You fail to comply with any of its terms. However, if You become 245 | compliant, then the rights granted under this License from a particular 246 | Contributor are reinstated (a) provisionally, unless and until such 247 | Contributor explicitly and finally terminates Your grants, and (b) on an 248 | ongoing basis, if such Contributor fails to notify You of the 249 | non-compliance by some reasonable means prior to 60 days after You have 250 | come back into compliance. Moreover, Your grants from a particular 251 | Contributor are reinstated on an ongoing basis if such Contributor 252 | notifies You of the non-compliance by some reasonable means, this is the 253 | first time You have received notice of non-compliance with this License 254 | from such Contributor, and You become compliant prior to 30 days after 255 | Your receipt of the notice. 256 | 257 | 5.2. If You initiate litigation against any entity by asserting a patent 258 | infringement claim (excluding declaratory judgment actions, 259 | counter-claims, and cross-claims) alleging that a Contributor Version 260 | directly or indirectly infringes any patent, then the rights granted to 261 | You by any and all Contributors for the Covered Software under Section 262 | 2.1 of this License shall terminate. 263 | 264 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 265 | end user license agreements (excluding distributors and resellers) which 266 | have been validly granted by You or Your distributors under this License 267 | prior to termination shall survive termination. 268 | 269 | ************************************************************************ 270 | * * 271 | * 6. Disclaimer of Warranty * 272 | * ------------------------- * 273 | * * 274 | * Covered Software is provided under this License on an "as is" * 275 | * basis, without warranty of any kind, either expressed, implied, or * 276 | * statutory, including, without limitation, warranties that the * 277 | * Covered Software is free of defects, merchantable, fit for a * 278 | * particular purpose or non-infringing. The entire risk as to the * 279 | * quality and performance of the Covered Software is with You. * 280 | * Should any Covered Software prove defective in any respect, You * 281 | * (not any Contributor) assume the cost of any necessary servicing, * 282 | * repair, or correction. This disclaimer of warranty constitutes an * 283 | * essential part of this License. No use of any Covered Software is * 284 | * authorized under this License except under this disclaimer. * 285 | * * 286 | ************************************************************************ 287 | 288 | ************************************************************************ 289 | * * 290 | * 7. Limitation of Liability * 291 | * -------------------------- * 292 | * * 293 | * Under no circumstances and under no legal theory, whether tort * 294 | * (including negligence), contract, or otherwise, shall any * 295 | * Contributor, or anyone who distributes Covered Software as * 296 | * permitted above, be liable to You for any direct, indirect, * 297 | * special, incidental, or consequential damages of any character * 298 | * including, without limitation, damages for lost profits, loss of * 299 | * goodwill, work stoppage, computer failure or malfunction, or any * 300 | * and all other commercial damages or losses, even if such party * 301 | * shall have been informed of the possibility of such damages. This * 302 | * limitation of liability shall not apply to liability for death or * 303 | * personal injury resulting from such party's negligence to the * 304 | * extent applicable law prohibits such limitation. Some * 305 | * jurisdictions do not allow the exclusion or limitation of * 306 | * incidental or consequential damages, so this exclusion and * 307 | * limitation may not apply to You. * 308 | * * 309 | ************************************************************************ 310 | 311 | 8. Litigation 312 | ------------- 313 | 314 | Any litigation relating to this License may be brought only in the 315 | courts of a jurisdiction where the defendant maintains its principal 316 | place of business and such litigation shall be governed by laws of that 317 | jurisdiction, without reference to its conflict-of-law provisions. 318 | Nothing in this Section shall prevent a party's ability to bring 319 | cross-claims or counter-claims. 320 | 321 | 9. Miscellaneous 322 | ---------------- 323 | 324 | This License represents the complete agreement concerning the subject 325 | matter hereof. If any provision of this License is held to be 326 | unenforceable, such provision shall be reformed only to the extent 327 | necessary to make it enforceable. Any law or regulation which provides 328 | that the language of a contract shall be construed against the drafter 329 | shall not be used to construe this License against a Contributor. 330 | 331 | 10. Versions of the License 332 | --------------------------- 333 | 334 | 10.1. New Versions 335 | 336 | Mozilla Foundation is the license steward. Except as provided in Section 337 | 10.3, no one other than the license steward has the right to modify or 338 | publish new versions of this License. Each version will be given a 339 | distinguishing version number. 340 | 341 | 10.2. Effect of New Versions 342 | 343 | You may distribute the Covered Software under the terms of the version 344 | of the License under which You originally received the Covered Software, 345 | or under the terms of any subsequent version published by the license 346 | steward. 347 | 348 | 10.3. Modified Versions 349 | 350 | If you create software not governed by this License, and you want to 351 | create a new license for such software, you may create and use a 352 | modified version of this License if you rename the license and remove 353 | any references to the name of the license steward (except to note that 354 | such modified license differs from this License). 355 | 356 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 357 | Licenses 358 | 359 | If You choose to distribute Source Code Form that is Incompatible With 360 | Secondary Licenses under the terms of this version of the License, the 361 | notice described in Exhibit B of this License must be attached. 362 | 363 | Exhibit A - Source Code Form License Notice 364 | ------------------------------------------- 365 | 366 | This Source Code Form is subject to the terms of the Mozilla Public 367 | License, v. 2.0. If a copy of the MPL was not distributed with this 368 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 369 | 370 | If it is not possible or desirable to put the notice in a particular 371 | file, then You may include the notice in a location (such as a LICENSE 372 | file in a relevant directory) where a recipient would be likely to look 373 | for such a notice. 374 | 375 | You may add additional accurate notices of copyright ownership. 376 | 377 | Exhibit B - "Incompatible With Secondary Licenses" Notice 378 | --------------------------------------------------------- 379 | 380 | This Source Code Form is "Incompatible With Secondary Licenses", as 381 | defined by the Mozilla Public License, v. 2.0. 382 | `, 383 | { 384 | ...config.prettier, 385 | parser: "markdown" 386 | } 387 | ); 388 | } 389 | -------------------------------------------------------------------------------- /src/license/generate-license/licenses/generate-zlib.ts: -------------------------------------------------------------------------------- 1 | import type {GenerateLicenseOptions} from "../generate-license-options.js"; 2 | import {formatContributor} from "../../../contributor/format-contributor.js"; 3 | import {listFormat} from "../../../util/list-format/list-format.js"; 4 | 5 | /** 6 | * Generates a ZLIB license 7 | */ 8 | export function generate({contributors, prettier, config}: GenerateLicenseOptions): Promise { 9 | return prettier.format( 10 | `\ 11 | zlib License 12 | 13 | © ${new Date().getFullYear()} ${listFormat( 14 | contributors.map(contributor => formatContributor(contributor, "markdown")), 15 | "and" 16 | )} 17 | 18 | This software is provided 'as-is', without any express or implied 19 | warranty. In no event will the authors be held liable for any damages 20 | arising from the use of this software. 21 | 22 | Permission is granted to anyone to use this software for any purpose, 23 | including commercial applications, and to alter it and redistribute it 24 | freely, subject to the following restrictions: 25 | 26 | 1. The origin of this software must not be misrepresented; you must not 27 | claim that you wrote the original software. If you use this software 28 | in a product, an acknowledgment in the product documentation would be 29 | appreciated but is not required. 30 | 2. Altered source versions must be plainly marked as such, and must not be 31 | misrepresented as being the original software. 32 | 3. This notice may not be removed or altered from any source distribution. 33 | `, 34 | { 35 | ...config.prettier, 36 | parser: "markdown" 37 | } 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /src/license/get-license-for-license-name/get-license-for-license-name.ts: -------------------------------------------------------------------------------- 1 | import type {LicenseName} from "../license-name.js"; 2 | import type {License} from "../license.js"; 3 | 4 | /** 5 | * Gets the License for the given LicenseName 6 | * 7 | * @param licenseName 8 | * @returns 9 | */ 10 | export function getLicenseForLicenseName(licenseName: LicenseName): License { 11 | switch (licenseName) { 12 | case "APACHE-2.0": 13 | return { 14 | licenseName, 15 | url: "https://opensource.org/licenses/Apache-2.0", 16 | badgeUrl: "https://img.shields.io/badge/License-Apache%202.0-blue.svg" 17 | }; 18 | 19 | case "BSD-2-CLAUSE": 20 | return { 21 | licenseName, 22 | url: "https://opensource.org/licenses/BSD-2-Clause", 23 | badgeUrl: "https://img.shields.io/badge/License-BSD%202--Clause-orange.svg" 24 | }; 25 | 26 | case "BSD-3-CLAUSE": 27 | return { 28 | licenseName, 29 | url: "https://opensource.org/licenses/BSD-3-Clause", 30 | badgeUrl: "https://img.shields.io/badge/License-BSD%203--Clause-blue.svg" 31 | }; 32 | 33 | case "CC-BY-4.0": 34 | return { 35 | licenseName, 36 | url: "https://creativecommons.org/licenses/by/4.0/", 37 | badgeUrl: "https://img.shields.io/badge/License-CC%20BY%204.0-lightgrey.svg" 38 | }; 39 | 40 | case "CC-BY-SA-4.0": 41 | return { 42 | licenseName, 43 | url: "https://creativecommons.org/licenses/by-sa/4.0/", 44 | badgeUrl: "https://img.shields.io/badge/License-CC%20BY--SA%204.0-lightgrey.svg" 45 | }; 46 | 47 | case "EPL-1.0": 48 | return { 49 | licenseName, 50 | url: "https://opensource.org/licenses/EPL-1.0", 51 | badgeUrl: "https://img.shields.io/badge/License-EPL%201.0-red.svg" 52 | }; 53 | 54 | case "GPL-3.0": 55 | return { 56 | licenseName, 57 | url: "https://img.shields.io/badge/License-GPL%20v3-blue.svg", 58 | badgeUrl: "https://www.gnu.org/licenses/gpl-3.0" 59 | }; 60 | 61 | case "GPL-2.0": 62 | return { 63 | licenseName, 64 | url: "https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html", 65 | badgeUrl: "https://img.shields.io/badge/License-GPL%20v2-blue.svg" 66 | }; 67 | 68 | case "AGPL-3.0": 69 | return { 70 | licenseName, 71 | url: "https://www.gnu.org/licenses/agpl-3.0", 72 | badgeUrl: "https://img.shields.io/badge/License-AGPL%20v3-blue.svg" 73 | }; 74 | 75 | case "LGPL-3.0": 76 | return { 77 | licenseName, 78 | url: "https://www.gnu.org/licenses/lgpl-3.0", 79 | badgeUrl: "https://img.shields.io/badge/License-LGPL%20v3-blue.svg" 80 | }; 81 | 82 | case "MIT": 83 | return { 84 | licenseName, 85 | url: "https://opensource.org/licenses/MIT", 86 | badgeUrl: "https://img.shields.io/badge/License-MIT-yellow.svg" 87 | }; 88 | 89 | case "MPL-2.0": 90 | return { 91 | licenseName, 92 | url: "https://opensource.org/licenses/MPL-2.0", 93 | badgeUrl: "https://img.shields.io/badge/License-MPL%202.0-brightgreen.svg" 94 | }; 95 | 96 | case "ARTISTIC-2.0": 97 | return { 98 | licenseName, 99 | url: "https://opensource.org/licenses/Artistic-2.0", 100 | badgeUrl: "https://img.shields.io/badge/License-Artistic%202.0-0298c3.svg" 101 | }; 102 | 103 | case "ZLIB": 104 | return { 105 | licenseName, 106 | url: "https://opensource.org/licenses/Zlib", 107 | badgeUrl: "https://img.shields.io/badge/License-Zlib-lightgrey.svg" 108 | }; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/license/get-license/get-license-options.ts: -------------------------------------------------------------------------------- 1 | import type {FindLicenseOptions} from "../find-license/find-license-options.js"; 2 | 3 | export interface GetLicenseOptions extends FindLicenseOptions {} 4 | -------------------------------------------------------------------------------- /src/license/get-license/get-license.ts: -------------------------------------------------------------------------------- 1 | import type {GetLicenseOptions} from "./get-license-options.js"; 2 | import type {LicenseName} from "../license-name.js"; 3 | import {LICENSE_NAMES} from "../license-name.js"; 4 | import {radio} from "../../util/prompt/radio.js"; 5 | import {findLicense} from "../find-license/find-license.js"; 6 | 7 | /** 8 | * Finds the project license from the given root directory. 9 | * It may be listed in the package.json file, or it may exist within the root as a LICENSE.md file already. 10 | * If no such file can be found, it will be request one from the user 11 | * 12 | * @param options 13 | * @returns 14 | */ 15 | export async function getLicense(options: GetLicenseOptions): Promise { 16 | const license = await findLicense(options); 17 | 18 | // If no license could be found, ask the user to select one 19 | if (license == null) { 20 | return await radio(`No license could be found. Which one would you like to use?`, LICENSE_NAMES); 21 | } else { 22 | return license; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/license/is-known-license-name.ts: -------------------------------------------------------------------------------- 1 | import type {LicenseName} from "./license-name.js"; 2 | import {LICENSE_NAMES} from "./license-name.js"; 3 | 4 | /** 5 | * Ensures that the given license is a proper known LicenseName 6 | * 7 | * @param license 8 | * @returns 9 | */ 10 | export function isKnownLicenseName(license: string): license is LicenseName { 11 | return LICENSE_NAMES.some(knownLicense => knownLicense === license); 12 | } 13 | -------------------------------------------------------------------------------- /src/license/is-known-license.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wessberg/sandhog/a651056337c3eac8edbb4ed86695ba4006608a13/src/license/is-known-license.ts -------------------------------------------------------------------------------- /src/license/license-name.ts: -------------------------------------------------------------------------------- 1 | import type {ElementOf} from "helpertypes"; 2 | 3 | export const LICENSE_NAMES = [ 4 | "APACHE-2.0", 5 | "BSD-2-CLAUSE", 6 | "BSD-3-CLAUSE", 7 | "CC-BY-4.0", 8 | "CC-BY-SA-4.0", 9 | "EPL-1.0", 10 | "GPL-3.0", 11 | "GPL-2.0", 12 | "AGPL-3.0", 13 | "LGPL-3.0", 14 | "MIT", 15 | "MPL-2.0", 16 | "ARTISTIC-2.0", 17 | "ZLIB" 18 | ] as const; 19 | 20 | export type LicenseName = ElementOf; 21 | -------------------------------------------------------------------------------- /src/license/license.ts: -------------------------------------------------------------------------------- 1 | import type {LicenseName} from "./license-name.js"; 2 | 3 | export interface License { 4 | licenseName: LicenseName; 5 | badgeUrl: string; 6 | url: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/logger/i-logger.ts: -------------------------------------------------------------------------------- 1 | export interface ILogger { 2 | /** 3 | * Logs info-related messages 4 | * @param messages 5 | */ 6 | info(...messages: string[]): void; 7 | 8 | /** 9 | * Logs verbose-related messages 10 | * @param messages 11 | */ 12 | verbose(...messages: string[]): void; 13 | 14 | /** 15 | * Logs debug-related messages 16 | * @param messages 17 | */ 18 | debug(...messages: string[]): void; 19 | 20 | /** 21 | * Logs warning-related messages 22 | * @param messages 23 | */ 24 | warn(...messages: string[]): void; 25 | } 26 | -------------------------------------------------------------------------------- /src/logger/log-level-kind.ts: -------------------------------------------------------------------------------- 1 | export const enum LogLevelKind { 2 | NONE = 0, 3 | INFO = 1, 4 | VERBOSE = 2, 5 | DEBUG = 3 6 | } 7 | -------------------------------------------------------------------------------- /src/logger/logger.ts: -------------------------------------------------------------------------------- 1 | import {LogLevelKind} from "./log-level-kind.js"; 2 | import color from "ansi-colors"; 3 | import type {ILogger} from "./i-logger.js"; 4 | 5 | /** 6 | * A logger that can print to the console 7 | */ 8 | export class Logger implements ILogger { 9 | private readonly VERBOSE_COLOR = "yellow"; 10 | private readonly WARNING_COLOR = "yellow"; 11 | private readonly DEBUG_COLOR = "cyan"; 12 | 13 | constructor(private readonly logLevel: LogLevelKind) {} 14 | 15 | /** 16 | * Logs info-related messages 17 | */ 18 | info(...messages: unknown[]): void { 19 | if (this.logLevel < LogLevelKind.INFO) return; 20 | console.log(...messages); 21 | } 22 | 23 | /** 24 | * Logs verbose-related messages 25 | */ 26 | verbose(...messages: unknown[]): void { 27 | if (this.logLevel < LogLevelKind.VERBOSE) return; 28 | console.log(color[this.VERBOSE_COLOR]("[VERBOSE]"), ...messages); 29 | } 30 | 31 | /** 32 | * Logs debug-related messages 33 | */ 34 | debug(...messages: unknown[]): void { 35 | if (this.logLevel < LogLevelKind.DEBUG) return; 36 | console.log(color[this.DEBUG_COLOR]("[DEBUG]"), ...messages); 37 | } 38 | 39 | /** 40 | * Logs warning-related messages 41 | */ 42 | warn(...messages: unknown[]): void { 43 | console.log(color[this.WARNING_COLOR](`(!)`), ...messages); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/markdown/format-image/format-image-options.ts: -------------------------------------------------------------------------------- 1 | export interface FormatImageOptions { 2 | url: string; 3 | alt?: string; 4 | height?: number; 5 | width?: number; 6 | kind?: "markdown" | "html"; 7 | } 8 | -------------------------------------------------------------------------------- /src/markdown/format-image/format-image.ts: -------------------------------------------------------------------------------- 1 | import type {FormatImageOptions} from "./format-image-options.js"; 2 | 3 | /** 4 | * Generates a Markdown-formatted image based on the given options 5 | */ 6 | export function formatImage({alt, url, height, width, kind = "html"}: FormatImageOptions): string { 7 | if (kind === "html") { 8 | return ``; 9 | } else { 10 | return `![${alt ?? ""}](${url})`; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/markdown/format-url/format-url-options.ts: -------------------------------------------------------------------------------- 1 | export interface FormatUrlOptions { 2 | url: string; 3 | inner: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/markdown/format-url/format-url.ts: -------------------------------------------------------------------------------- 1 | import type {FormatUrlOptions} from "./format-url-options.js"; 2 | 3 | /** 4 | * Generates an Markdown-formatted URL based on the given options 5 | */ 6 | export function formatUrl({inner, url}: FormatUrlOptions): string { 7 | return `${inner}`; 8 | } 9 | -------------------------------------------------------------------------------- /src/package/find-package/find-package-options.ts: -------------------------------------------------------------------------------- 1 | import type {ILogger} from "../../logger/i-logger.js"; 2 | import type {FileSystem} from "../../file-system/file-system.js"; 3 | 4 | export interface FindPackageOptions { 5 | root?: string; 6 | fs?: Pick; 7 | logger: ILogger; 8 | } 9 | -------------------------------------------------------------------------------- /src/package/find-package/find-package-result.ts: -------------------------------------------------------------------------------- 1 | import type {Package} from "../package.js"; 2 | 3 | export interface FindPackageResult { 4 | pkg: Package; 5 | root: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/package/find-package/find-package.ts: -------------------------------------------------------------------------------- 1 | import path from "crosspath"; 2 | import _fs from "fs"; 3 | import type {FindPackageOptions} from "./find-package-options.js"; 4 | import type {FindPackageResult} from "./find-package-result.js"; 5 | 6 | /** 7 | * Finds the nearest package.json from the given root directory 8 | */ 9 | export async function findPackage({ 10 | root = process.cwd(), 11 | logger, 12 | fs = {existsSync: _fs.existsSync, readFileSync: _fs.readFileSync} 13 | }: FindPackageOptions): Promise { 14 | const packageJsonPath = path.join(root, "package.json"); 15 | const nativePackageJsonPath = path.native.normalize(packageJsonPath); 16 | if (fs.existsSync(nativePackageJsonPath)) { 17 | logger.debug(`Found package.json: ${nativePackageJsonPath}`); 18 | 19 | return { 20 | root: path.dirname(packageJsonPath), 21 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment 22 | pkg: await import(packageJsonPath, {with: {type: "json"}}) 23 | }; 24 | } 25 | 26 | // Rewrite the root to check the parent directory 27 | const newRoot = path.join(root, "../"); 28 | 29 | // If there is no more parent directories to look in, no config exists 30 | if (newRoot === root || newRoot === "/" || newRoot === "") { 31 | throw new ReferenceError(`Could not find a package.json. Are you sure you are running sandhog inside an NPM project?`); 32 | } 33 | 34 | // Call recursively 35 | return await findPackage({ 36 | root: newRoot, 37 | logger 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /src/package/package.ts: -------------------------------------------------------------------------------- 1 | import type {SandhogConfig} from "../config/sandhog-config.js"; 2 | import type {PartialDeep} from "helpertypes"; 3 | 4 | interface Author { 5 | name: string; 6 | email: string; 7 | url: string; 8 | } 9 | 10 | export interface Package { 11 | name?: string; 12 | description?: string; 13 | bin?: Record; 14 | repository?: { 15 | url?: string; 16 | }; 17 | funding?: string | Partial<{type: string; url: string}>; 18 | license?: string; 19 | sandhog?: PartialDeep; 20 | author?: Partial | string; 21 | authors?: (Partial | string)[]; 22 | contributors?: (Partial | string)[]; 23 | xo?: Record; 24 | devDependencies?: Record; 25 | dependencies?: Record; 26 | peerDependencies?: Record; 27 | peerDependenciesMeta?: Record>; 28 | } 29 | -------------------------------------------------------------------------------- /src/package/take-github-repository-name/take-github-repository-name.ts: -------------------------------------------------------------------------------- 1 | import type {Package} from "../package.js"; 2 | 3 | const REGEX = /(http?s?:\/\/?)?(www\.)?github.com\//g; 4 | 5 | /** 6 | * Takes the Github repository name from the given Package 7 | * 8 | * @param pkg 9 | * @returns 10 | */ 11 | export function takeGithubRepositoryName(pkg: Package): string | undefined { 12 | if (pkg.repository?.url == null) return undefined; 13 | return pkg.repository.url.replace(REGEX, "").replace(".git", ""); 14 | } 15 | -------------------------------------------------------------------------------- /src/readme/generate-readme/generate-readme-context.ts: -------------------------------------------------------------------------------- 1 | import type {GenerateReadmeOptions} from "./generate-readme-options.js"; 2 | import type {SectionKind} from "../../section/section-kind.js"; 3 | 4 | export interface GenerateReadmeContext extends GenerateReadmeOptions { 5 | sections: Set; 6 | str: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/readme/generate-readme/generate-readme-options.ts: -------------------------------------------------------------------------------- 1 | import type prettier from "prettier"; 2 | import type {SandhogConfig} from "../../config/sandhog-config.js"; 3 | import type {Package} from "../../package/package.js"; 4 | import type {ILogger} from "../../logger/i-logger.js"; 5 | import type {FileSystem} from "../../file-system/file-system.js"; 6 | 7 | export interface GenerateReadmeOptions { 8 | prettier: typeof prettier; 9 | pkg: Package; 10 | logger: ILogger; 11 | root: string; 12 | fs: Pick; 13 | config: SandhogConfig; 14 | existingReadme?: string; 15 | } 16 | -------------------------------------------------------------------------------- /src/readme/generate-readme/generate-readme.ts: -------------------------------------------------------------------------------- 1 | import type {GenerateReadmeOptions} from "./generate-readme-options.js"; 2 | import type {SectionKind} from "../../section/section-kind.js"; 3 | import type {GenerateReadmeContext} from "./generate-readme-context.js"; 4 | import {getBadges} from "../../badge/get-badges/get-badges.js"; 5 | import {getRelevantSections} from "../../section/get-relevant-sections/get-relevant-sections.js"; 6 | import {formatImage} from "../../markdown/format-image/format-image.js"; 7 | import {getShadowSectionMark} from "../get-shadow-section-mark/get-shadow-section-mark.js"; 8 | import {getContributorsFromPackage} from "../../contributor/get-contributors-from-package.js"; 9 | import {CONSTANT} from "../../constant/constant.js"; 10 | import {findLicense} from "../../license/find-license/find-license.js"; 11 | import {formatContributor} from "../../contributor/format-contributor.js"; 12 | import {listFormat} from "../../util/list-format/list-format.js"; 13 | import {formatUrl} from "../../markdown/format-url/format-url.js"; 14 | import toc from "@effect/markdown-toc"; 15 | import path from "crosspath"; 16 | import type {Contributor} from "../../contributor/contributor.js"; 17 | 18 | /** 19 | * Generates the text for a README.md based on the given options 20 | */ 21 | export async function generateReadme(options: GenerateReadmeOptions): Promise { 22 | const sections = getRelevantSections(options); 23 | 24 | const context: GenerateReadmeContext = { 25 | ...options, 26 | sections, 27 | str: options.existingReadme ?? "" 28 | }; 29 | 30 | if (sections.has("logo")) { 31 | generateLogoSection(context); 32 | } 33 | 34 | if (sections.has("description_short")) { 35 | generateDescriptionShortSection(context); 36 | } 37 | 38 | if (sections.has("badges")) { 39 | generateBadgesSection(context); 40 | } 41 | 42 | if (sections.has("description_long")) { 43 | generateDescriptionLongSection(context); 44 | } 45 | 46 | if (sections.has("features")) { 47 | generateFeaturesSection(context); 48 | } 49 | 50 | if (sections.has("feature_image")) { 51 | generateFeatureImageSection(context); 52 | } 53 | 54 | if (sections.has("backers")) { 55 | generateBackersSection(context); 56 | } 57 | 58 | if (sections.has("toc")) { 59 | generateTableOfContentsSection(context, true); 60 | } 61 | 62 | if (sections.has("install")) { 63 | generateInstallSection(context); 64 | } 65 | 66 | if (sections.has("usage")) { 67 | generateUsageSection(context); 68 | } 69 | 70 | if (sections.has("contributing")) { 71 | generateContributingSection(context); 72 | } 73 | 74 | if (sections.has("maintainers")) { 75 | generateMaintainersSection(context); 76 | } 77 | 78 | if (sections.has("faq")) { 79 | generateFaqSection(context); 80 | } 81 | 82 | if (sections.has("license")) { 83 | await generateLicenseSection(context); 84 | } 85 | 86 | if (sections.has("toc")) { 87 | generateTableOfContentsSection(context, false); 88 | } 89 | 90 | // Stringify the StringBuilder 91 | return options.prettier.format(context.str, { 92 | ...options.config.prettier, 93 | parser: "markdown" 94 | }); 95 | } 96 | 97 | /** 98 | * Sets the given content with the given content within the context 99 | */ 100 | function setSection(context: GenerateReadmeContext, sectionKind: SectionKind, content: string, outro?: string): void { 101 | const startMark = getShadowSectionMark(sectionKind, "start"); 102 | const endMark = getShadowSectionMark(sectionKind, "end"); 103 | const startMarkIndex = context.str.indexOf(startMark); 104 | const endMarkIndex = context.str.indexOf(endMark); 105 | const markedContent = startMark + "\n\n" + content + "\n\n" + endMark + "\n\n" + (outro == null ? "" : outro + "\n\n"); 106 | 107 | if (startMarkIndex >= 0 && endMarkIndex >= 0) { 108 | const before = context.str.slice(0, startMarkIndex); 109 | const after = context.str.slice(endMarkIndex + endMark.length); 110 | context.str = before + markedContent + after; 111 | } else { 112 | context.str += markedContent; 113 | } 114 | } 115 | 116 | /** 117 | * Generates the logo section of the README 118 | */ 119 | function generateLogoSection(context: GenerateReadmeContext): void { 120 | setSection( 121 | context, 122 | "logo", 123 | // Don't proceed if there is no logo to generate an image for 124 | context.config.logo.url == null 125 | ? "" 126 | : `
${formatImage({ 127 | url: context.config.logo.url, 128 | alt: "Logo", 129 | height: context.config.logo.height 130 | })}
` 131 | ); 132 | } 133 | 134 | /** 135 | * Generates the feature image section of the README 136 | */ 137 | function generateFeatureImageSection(context: GenerateReadmeContext): void { 138 | setSection( 139 | context, 140 | "feature_image", 141 | // Don't proceed if there is no feature image to generate an image for 142 | context.config.featureImage.url == null 143 | ? "" 144 | : `
${formatImage({ 145 | url: context.config.featureImage.url, 146 | alt: "Feature image", 147 | height: context.config.featureImage.height 148 | })}

` 149 | ); 150 | } 151 | 152 | /** 153 | * Generates the Table Of Contents section of the README 154 | */ 155 | function generateTableOfContentsSection(context: GenerateReadmeContext, reserveOnly = false): void { 156 | setSection( 157 | context, 158 | "toc", 159 | `## Table of Contents\n\n` + 160 | (reserveOnly 161 | ? // Only reserve the spot within the README with an empty placeholder that can be replaced later on 162 | `` 163 | : toc(context.str).content) 164 | ); 165 | } 166 | 167 | /** 168 | * Generates the badges section of the README 169 | */ 170 | async function generateBadgesSection(context: GenerateReadmeContext): Promise { 171 | const badges = await getBadges(context); 172 | 173 | const content = Object.values(badges) 174 | .map(value => { 175 | // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition 176 | if (value == null) return ""; 177 | return value.join("\n"); 178 | }) 179 | .join("\n"); 180 | setSection(context, "badges", content); 181 | } 182 | 183 | /** 184 | * Generates the short description section of the README 185 | */ 186 | function generateDescriptionShortSection(context: GenerateReadmeContext): void { 187 | // Don't proceed if the package has no description 188 | if (context.pkg.description == null) return; 189 | 190 | setSection(context, "description_short", `> ${context.pkg.description}`); 191 | } 192 | 193 | /** 194 | * Generates the long description section of the README 195 | */ 196 | function generateDescriptionLongSection(context: GenerateReadmeContext): void { 197 | setSection(context, "description_long", `## Description`); 198 | } 199 | 200 | /** 201 | * Generates the features section of the README 202 | */ 203 | function generateFeaturesSection(context: GenerateReadmeContext): void { 204 | setSection(context, "features", `### Features\n\n`); 205 | } 206 | 207 | /** 208 | * Generates the FAQ section of the README 209 | */ 210 | function generateFaqSection(context: GenerateReadmeContext): void { 211 | setSection(context, "faq", `## FAQ\n\n`); 212 | } 213 | 214 | function generateNpxStep(binName: string, requiredPeerDependencies: string[], context: GenerateReadmeContext): string { 215 | const canUseShorthand = binName === context.pkg.name; 216 | const simpleCommand = "```\n" + `$ npx ${canUseShorthand ? `${context.pkg.name}` : `-p ${context.pkg.name} ${binName}`}\n` + "```"; 217 | 218 | if (requiredPeerDependencies.length < 1) { 219 | return simpleCommand; 220 | } else if (canUseShorthand) { 221 | return ( 222 | `First, add ${requiredPeerDependencies.length === 1 ? "the peer dependency" : "the peer dependencies"} ${listFormat( 223 | requiredPeerDependencies, 224 | "and", 225 | element => `\`${element}\`` 226 | )} as${requiredPeerDependencies.length === 1 ? " a" : ""}${context.config.isDevelopmentPackage ? " development " : ""}${ 227 | requiredPeerDependencies.length === 1 ? " dependency" : "dependencies" 228 | } to the package from which you're going to run \`${binName}\`. Alternatively, if you want to run it from _anywhere_, you can also install ${ 229 | requiredPeerDependencies.length === 1 ? "it" : "them" 230 | } globally: \`npm i -g ${requiredPeerDependencies.join(" ")}\`. Now, you can simply run:\n` + 231 | simpleCommand + 232 | "\n" + 233 | `You can also run \`${binName}\` along with its peer dependencies in one combined command:\n` + 234 | "```\n" + 235 | `$ npx${requiredPeerDependencies.map(requiredPeerDependency => ` -p ${requiredPeerDependency}`).join("")} -p ${context.pkg.name} ${binName}\n` + 236 | "```\n" 237 | ); 238 | } else { 239 | return "```\n" + `$ npx${requiredPeerDependencies.map(requiredPeerDependency => ` -p ${requiredPeerDependency}`).join("")} -p ${context.pkg.name} ${binName}\n` + "```\n"; 240 | } 241 | } 242 | 243 | /** 244 | * Generates the install section of the README 245 | */ 246 | function generateInstallSection(context: GenerateReadmeContext): void { 247 | // Don't proceed if the package has no name 248 | if (context.pkg.name == null) return; 249 | const peerDependencies = 250 | context.pkg.peerDependencies == null 251 | ? [] 252 | : Object.keys(context.pkg.peerDependencies).map(peerDependency => ({ 253 | peerDependency, 254 | optional: Boolean(context.pkg.peerDependenciesMeta?.[peerDependency]?.optional) 255 | })); 256 | 257 | const requiredPeerDependencies = peerDependencies.filter(({optional}) => !optional).map(({peerDependency}) => peerDependency); 258 | const optionalPeerDependencies = peerDependencies.filter(({optional}) => optional).map(({peerDependency}) => peerDependency); 259 | 260 | const firstBinName = context.pkg.bin == null ? undefined : Object.keys(context.pkg.bin)[0]; 261 | 262 | setSection( 263 | context, 264 | "install", 265 | `## Install\n\n` + 266 | `### npm\n\n` + 267 | "```\n" + 268 | `$ npm install ${context.pkg.name}${context.config.isDevelopmentPackage ? ` --save-dev` : ``}\n` + 269 | "```\n\n" + 270 | `### Yarn\n\n` + 271 | "```\n" + 272 | `$ yarn add ${context.pkg.name}${context.config.isDevelopmentPackage ? ` --dev` : ``}\n` + 273 | "```\n\n" + 274 | `### pnpm\n\n` + 275 | "```\n" + 276 | `$ pnpm add ${context.pkg.name}${context.config.isDevelopmentPackage ? ` --save-dev` : ``}\n` + 277 | "```" + 278 | (firstBinName == null ? "" : `\n\n` + `### Run once with npx\n\n` + generateNpxStep(firstBinName, requiredPeerDependencies, context)) + 279 | (peerDependencies.length < 1 280 | ? "" 281 | : "\n\n" + 282 | `### Peer Dependencies\n\n` + 283 | (requiredPeerDependencies.length < 1 284 | ? "" 285 | : `\`${context.pkg.name}\` depends on ${listFormat(requiredPeerDependencies, "and", element => `\`${element}\``)}, so you need to manually install ${ 286 | requiredPeerDependencies.length === 1 ? "this" : "these" 287 | }${context.config.isDevelopmentPackage ? ` as ${requiredPeerDependencies.length === 1 ? "a development dependency" : "development dependencies"}` : ``} as well.`) + 288 | (optionalPeerDependencies.length < 1 289 | ? "" 290 | : (requiredPeerDependencies.length < 1 ? `You may` : `\n\nYou may also`) + 291 | ` need to install ${ 292 | optionalPeerDependencies.length < 2 293 | ? `\`${optionalPeerDependencies[0]}\`` 294 | : `${requiredPeerDependencies.length < 1 ? "" : "additional "}peer dependencies such as ${listFormat( 295 | optionalPeerDependencies, 296 | "or", 297 | element => `\`${element}\`` 298 | )}` 299 | } depending on the features you are going to use. Refer to the documentation for the specific cases where ${ 300 | optionalPeerDependencies.length < 2 ? "it" : "any of these" 301 | } may be relevant.`)) 302 | ); 303 | } 304 | 305 | /** 306 | * Generates the usage section of the README 307 | */ 308 | function generateUsageSection(context: GenerateReadmeContext): void { 309 | setSection(context, "usage", `## Usage`); 310 | } 311 | 312 | /** 313 | * Generates the contributing section of the README 314 | */ 315 | function generateContributingSection(context: GenerateReadmeContext): void { 316 | // Only add the contributing section if a CONTRIBUTING.md file exists 317 | const contributingFilePath = path.join(context.root, CONSTANT.codeOfConductFilename); 318 | const nativeContributingFilePath = path.native.normalize(contributingFilePath); 319 | 320 | setSection( 321 | context, 322 | "contributing", 323 | !context.fs.existsSync(nativeContributingFilePath) 324 | ? "" 325 | : `## Contributing\n\n` + `Do you want to contribute? Awesome! Please follow [these recommendations](./${CONSTANT.contributingFilename}).` 326 | ); 327 | } 328 | 329 | function generateContributorTable(contributors: Contributor[]): string { 330 | let str = "\n|"; 331 | 332 | contributors.forEach(contributor => { 333 | const inner = 334 | contributor.imageUrl == null 335 | ? undefined 336 | : formatImage({ 337 | alt: contributor.name, 338 | url: contributor.imageUrl, 339 | height: CONSTANT.contributorImageUrlHeight 340 | }); 341 | 342 | const formattedImageWithUrl = 343 | inner == null 344 | ? "" 345 | : contributor.email != null 346 | ? formatUrl({url: `mailto:${contributor.email}`, inner}) 347 | : contributor.url != null 348 | ? formatUrl({url: contributor.url, inner}) 349 | : inner; 350 | 351 | str += formattedImageWithUrl; 352 | str += "|"; 353 | }); 354 | str += "\n|"; 355 | contributors.forEach(() => { 356 | str += "-----------|"; 357 | }); 358 | 359 | str += "\n|"; 360 | 361 | contributors.forEach(contributor => { 362 | if (contributor.name != null) { 363 | if (contributor.email != null) { 364 | str += `[${contributor.name}](mailto:${contributor.email})`; 365 | } else if (contributor.url != null) { 366 | str += `[${contributor.name}](${contributor.url})`; 367 | } else { 368 | str += contributor.name; 369 | } 370 | } 371 | 372 | if (contributor.twitter != null) { 373 | str += `
Twitter: [@${contributor.twitter}](https://twitter.com/${contributor.twitter})`; 374 | } 375 | 376 | if (contributor.github != null) { 377 | str += `
Github: [@${contributor.github}](https://github.com/${contributor.github})`; 378 | } 379 | 380 | // Also add the role of the contributor, if available 381 | if (contributor.role != null) { 382 | str += `
_${contributor.role}_`; 383 | } 384 | str += "|"; 385 | }); 386 | 387 | return str; 388 | } 389 | 390 | /** 391 | * Generates the maintainers section of the README 392 | */ 393 | function generateMaintainersSection(context: GenerateReadmeContext): void { 394 | const contributors = getContributorsFromPackage(context.pkg); 395 | 396 | setSection(context, "maintainers", contributors.length < 1 ? "" : `## Maintainers\n\n` + generateContributorTable(contributors)); 397 | } 398 | 399 | function guessPreferredFundingUrlForOtherDonations(context: GenerateReadmeContext): string | undefined { 400 | // If a funding url is given, that should take precedence. 401 | if (context.config.donate.other.fundingUrl != null) { 402 | return context.config.donate.other.fundingUrl; 403 | } 404 | 405 | // Otherwise, it might be provided from a "funding" property in the package.json file 406 | if (context.pkg.funding != null) { 407 | if (typeof context.pkg.funding === "string") { 408 | return context.pkg.funding; 409 | } else if (context.pkg.funding.url != null) { 410 | return context.pkg.funding.url; 411 | } 412 | } 413 | 414 | // Otherwise, there's no way to know. 415 | return undefined; 416 | } 417 | 418 | /** 419 | * Generates the backers section of the README 420 | */ 421 | function generateBackersSection(context: GenerateReadmeContext): void { 422 | let content = ""; 423 | 424 | if (context.config.donate.other.donors.length > 0) { 425 | const preferredFundingUrl = guessPreferredFundingUrlForOtherDonations(context); 426 | content += 427 | (preferredFundingUrl != null ? `[Become a sponsor/backer](${preferredFundingUrl}) and get your logo listed here.\n\n` : "") + 428 | generateContributorTable(context.config.donate.other.donors) + 429 | "\n\n"; 430 | } 431 | 432 | if (context.config.donate.openCollective.project != null) { 433 | content += 434 | `### Open Collective\n\n` + 435 | `[Become a sponsor/backer](${CONSTANT.openCollectiveDonateUrl(context.config.donate.openCollective.project)}) and get your logo listed here.\n\n` + 436 | `#### Sponsors\n\n` + 437 | formatUrl({ 438 | url: CONSTANT.openCollectiveContributorsUrl(context.config.donate.openCollective.project), 439 | inner: formatImage({ 440 | url: CONSTANT.openCollectiveSponsorsBadgeUrl(context.config.donate.openCollective.project), 441 | alt: "Sponsors on Open Collective", 442 | width: 500 443 | }) 444 | }) + 445 | "\n\n" + 446 | `#### Backers\n\n` + 447 | formatUrl({ 448 | url: CONSTANT.openCollectiveContributorsUrl(context.config.donate.openCollective.project), 449 | inner: formatImage({ 450 | url: CONSTANT.openCollectiveBackersBadgeUrl(context.config.donate.openCollective.project), 451 | alt: "Backers on Open Collective" 452 | }) 453 | }) + 454 | "\n\n"; 455 | } 456 | 457 | if (context.config.donate.patreon.userId != null && context.config.donate.patreon.username != null) { 458 | content += 459 | `### Patreon\n\n` + 460 | formatUrl({ 461 | url: CONSTANT.patreonDonateUrl(context.config.donate.patreon.userId), 462 | inner: formatImage({ 463 | url: CONSTANT.patreonBadgeUrl(context.config.donate.patreon.username), 464 | alt: "Patrons on Patreon", 465 | width: 200 466 | }) 467 | }) + 468 | "\n\n"; 469 | } 470 | 471 | setSection(context, "backers", context.config.donate.patreon.userId == null && context.config.donate.openCollective.project == null ? "" : `## Backers\n\n` + content); 472 | } 473 | 474 | async function generateLicenseSection(context: GenerateReadmeContext): Promise { 475 | const license = await findLicense(context); 476 | const contributors = getContributorsFromPackage(context.pkg); 477 | const licenseFilePath = path.join(context.root, CONSTANT.licenseFilename); 478 | const nativeLicenseFilePath = path.native.normalize(licenseFilePath); 479 | 480 | setSection( 481 | context, 482 | "license", 483 | license == null || !context.fs.existsSync(nativeLicenseFilePath) 484 | ? "" 485 | : `## License\n\n` + 486 | `${license} © ${listFormat( 487 | contributors.map(contributor => formatContributor(contributor, "markdown")), 488 | "and" 489 | )}` 490 | ); 491 | } 492 | -------------------------------------------------------------------------------- /src/readme/get-shadow-section-mark/get-shadow-section-mark.ts: -------------------------------------------------------------------------------- 1 | import type {SectionKind} from "../../section/section-kind.js"; 2 | 3 | /** 4 | * Gets a Section mark. 5 | * 6 | * @param kind 7 | * @param startOrEnd 8 | */ 9 | export function getShadowSectionMark(kind: SectionKind, startOrEnd: "start" | "end"): string { 10 | return ``.toUpperCase(); 11 | } 12 | -------------------------------------------------------------------------------- /src/section/ensure-section-kind/ensure-section-kind.ts: -------------------------------------------------------------------------------- 1 | import type {SectionKind} from "../section-kind.js"; 2 | import {SECTION_KINDS} from "../section-kind.js"; 3 | import {listFormat} from "../../util/list-format/list-format.js"; 4 | 5 | /** 6 | * Ensures that the given input is a proper SectionKind 7 | */ 8 | export function ensureSectionKind(sectionKind: string): SectionKind { 9 | if (typeof sectionKind !== "string") return sectionKind; 10 | if (SECTION_KINDS.some(key => key === sectionKind)) { 11 | return sectionKind as SectionKind; 12 | } else { 13 | throw new TypeError(`Could not parse string: '${sectionKind}' as a SectionKind. Possible values: ${listFormat(SECTION_KINDS, "and")}`); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/section/get-relevant-sections/get-relevant-sections-options.ts: -------------------------------------------------------------------------------- 1 | import type {SandhogConfig} from "../../config/sandhog-config.js"; 2 | 3 | export interface GetRelevantSectionsOptions { 4 | config: SandhogConfig; 5 | } 6 | -------------------------------------------------------------------------------- /src/section/get-relevant-sections/get-relevant-sections.ts: -------------------------------------------------------------------------------- 1 | import type {GetRelevantSectionsOptions} from "./get-relevant-sections-options.js"; 2 | import type {SectionKind} from "../section-kind.js"; 3 | import {SECTION_KINDS} from "../section-kind.js"; 4 | 5 | /** 6 | * Gets all those sections that are relevant in relation to the given options 7 | */ 8 | export function getRelevantSections({config}: GetRelevantSectionsOptions): Set { 9 | // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition 10 | const excluded = new Set(config.readme.sections.exclude ?? []); 11 | 12 | // Prepare the baseline of sections. This will be the sections that will be included always (except for the excluded ones). 13 | return new Set(SECTION_KINDS.filter(value => !excluded.has(value))); 14 | } 15 | -------------------------------------------------------------------------------- /src/section/section-kind.ts: -------------------------------------------------------------------------------- 1 | import type {ElementOf} from "helpertypes"; 2 | 3 | export const SECTION_KINDS = [ 4 | "toc", 5 | "logo", 6 | "badges", 7 | "description_short", 8 | "description_long", 9 | "features", 10 | "feature_image", 11 | "usage", 12 | "install", 13 | "contributing", 14 | "maintainers", 15 | "faq", 16 | "backers", 17 | "license" 18 | ] as const; 19 | 20 | export type SectionKind = ElementOf; 21 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module "@effect/markdown-toc" { 2 | interface Result { 3 | content: string; 4 | } 5 | 6 | export default function (content: string): Result; 7 | } 8 | -------------------------------------------------------------------------------- /src/util/list-format/list-format.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Formats the given iterable of strings in a list format (in the English locale) 3 | */ 4 | export function listFormat(elements: Iterable, andOrOr: "and" | "or", mapper: (element: string) => string = element => element): string { 5 | const arr = [...elements]; 6 | if (arr.length === 0) return ""; 7 | else if (arr.length === 1) return mapper(arr[0]!); 8 | else if (arr.length === 2) { 9 | const [first, last] = arr; 10 | return `${mapper(first!)} ${andOrOr} ${mapper(last!)}`; 11 | } else { 12 | const head = arr.slice(0, arr.length - 1).map(mapper); 13 | const last = mapper(arr.slice(-1)[0]!); 14 | return `${head.join(", ")}, ${andOrOr} ${last}`; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/util/path/is-lib.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns true if the given path represents a library 3 | * 4 | * @param path 5 | * @returns 6 | */ 7 | export function isLib(path: string): boolean { 8 | return !path.startsWith(".") && !path.startsWith("/"); 9 | } 10 | -------------------------------------------------------------------------------- /src/util/path/strip-leading-if-matched.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Strips the leading part of the given string if it is equal to the given match. 3 | * 4 | * @param str 5 | * @param match 6 | * @returns 7 | */ 8 | export function stripLeadingIfMatched(str: string, match: string): string { 9 | const encodedMatch = encodeURIComponent(match); 10 | if (str.startsWith(match)) return str.slice(match.length); 11 | else if (str.startsWith(encodedMatch)) return str.slice(encodedMatch.length); 12 | return str; 13 | } 14 | -------------------------------------------------------------------------------- /src/util/prompt/confirm.ts: -------------------------------------------------------------------------------- 1 | import {confirm as inquirerConfirm} from "@inquirer/prompts"; 2 | 3 | /** 4 | * Prints a 'confirm' prompt in the terminal 5 | */ 6 | export async function confirm(message: string, defaultValue?: boolean): Promise { 7 | const answer = await inquirerConfirm({ 8 | message, 9 | default: defaultValue 10 | }); 11 | 12 | return answer; 13 | } 14 | -------------------------------------------------------------------------------- /src/util/prompt/radio.ts: -------------------------------------------------------------------------------- 1 | import {select} from "@inquirer/prompts"; 2 | 3 | /** 4 | * Provides a "radio button group" of potential options the user may pick 5 | */ 6 | export async function radio(message: string, items: T[] | readonly T[]): Promise { 7 | const answer = await select({ 8 | message, 9 | choices: items.map(item => ({name: item, value: item})) 10 | }); 11 | 12 | return answer; 13 | } 14 | -------------------------------------------------------------------------------- /test/config.test.ts: -------------------------------------------------------------------------------- 1 | import test from "node:test"; 2 | import assert from "node:assert"; 3 | import {Logger} from "../src/logger/logger.js"; 4 | import {LogLevelKind} from "../src/logger/log-level-kind.js"; 5 | import {getConfig} from "../src/config/get-config/get-config.js"; 6 | 7 | test("Can sanitize a SandhogConfig", async () => { 8 | const config = await getConfig({ 9 | root: process.cwd(), 10 | logger: new Logger(LogLevelKind.NONE) 11 | }); 12 | 13 | // Verify that it has no optional keys 14 | assert( 15 | "donate" in config && 16 | "patreon" in config.donate && 17 | "userId" in config.donate.patreon && 18 | "openCollective" in config.donate && 19 | "project" in config.donate.openCollective && 20 | "prettier" in config && 21 | "logo" in config && 22 | "url" in config.logo && 23 | "height" in config.logo && 24 | "featureImage" in config && 25 | "url" in config.featureImage && 26 | "height" in config.featureImage && 27 | "readme" in config && 28 | "badges" in config.readme && 29 | "sections" in config.readme && 30 | "exclude" in config.readme.badges && 31 | "exclude" in config.readme.sections 32 | ); 33 | }); 34 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/@wessberg/ts-config/tsconfig.json", 3 | "include": ["src/**/*.*", "test/**/*.*", "loader.cjs", "sandhog.config.js"], 4 | "exclude": ["dist/*.*"], 5 | "compilerOptions": { 6 | "importHelpers": false 7 | } 8 | } 9 | --------------------------------------------------------------------------------