├── .gitignore ├── .npmignore ├── README.md ├── index.js ├── package-lock.json ├── package.json ├── tasks ├── checks.js ├── create-directory-content.js ├── create-project.js ├── installation.js ├── output.js └── question.js └── template ├── .DS_Store ├── .prettierignore ├── .prettierrc ├── .storybook ├── main.js └── preview.js ├── .swcrc ├── README.md ├── custom.d.ts ├── env ├── eslint.config.mjs ├── gitignore ├── jest.swc.config.js ├── package.json ├── src ├── .DS_Store ├── app.test.ts ├── app.tsx ├── assets │ ├── .DS_Store │ ├── images │ │ ├── .DS_Store │ │ └── favicon.png │ └── svg │ │ └── logo.svg ├── components │ ├── error-boundary │ │ ├── index.tsx │ │ └── layout.tsx │ └── title │ │ ├── index.tsx │ │ └── stories.tsx ├── index.html ├── index.tsx └── pages │ └── home │ ├── home.css │ └── index.tsx ├── tsconfig.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /build 3 | .DS_Store -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Screenshot 2023-04-19 at 12 52 14 2 | 3 | # ALRIGHT REACT APP 4 | 5 | Professional React app generator. Comes with an exposed, unopinionated, high performance configuration. 6 | Jest, SWC, Storybook 7, Typescript 5, Webpack 5. 7 | 8 | screenshot 9 | 10 | # GETTING STARTED 11 | 12 | In your terminal, type: 13 | 14 | ```js 15 | npx alright-react-app 16 | ``` 17 | 18 | The `npx` command is available if `npm` is installed globally on your computer. 19 | **Alright React App** will ask you to enter an application name. Then you just have to press `enter`. 20 | 21 | The following folder will be created at the root of your current working directory: 22 | 23 | ``` 24 | my-app 25 | ├── webpack.config.js 26 | ├── tsconfig.json 27 | ├── README.md 28 | ├── package.json 29 | ├── jest.swc.config.js 30 | ├── custom.d.ts 31 | ├── .swcrc 32 | ├── .gitignore 33 | ├── .eslintrc.js 34 | ├── .eslintignore 35 | ├── .env 36 | └── src 37 | ├── index.tsx 38 | ├── index.html 39 | ├── app.tsx 40 | ├── app.test.ts 41 | └── pages 42 | └── home 43 | ├── index.tsx 44 | ├── home.css 45 | └── components 46 | └── error-boundary 47 | ├── index.tsx 48 | ├── layout.tsx 49 | └── title 50 | ├── index.tsx 51 | ├── stories.tsx 52 | └── assets 53 | └── images 54 | ├── favicon.png 55 | └── svg 56 | ├── logo.svg 57 | └── .storybook 58 | ├── main.js 59 | ├── preview.js 60 | 61 | ``` 62 | 63 | Once the application is created, type `cd my-app'. 64 | 65 | # COMMANDS 66 | 67 | **DEV**: `npm run dev` 68 | 69 | **BUILD**: `npm run build` 70 | 71 | **STORYBOOK**: `npm run storybook` 72 | 73 | **TEST**: `npm run test` 74 | 75 | # STORYBOOK 76 | 77 | You must install Storybook globally: `npm i -g @storybook/cli` to use it. 78 | 79 | ## CREDITS 80 | 81 | DoneDeal0 82 | 83 | ## SUPPORT 84 | 85 | If you or your company is using the Alright React App, please show your support by buying me a coffee: 86 | https://www.buymeacoffee.com/donedeal0 87 | 88 |
89 | 90 | 91 | 92 |
93 | 94 | ## CONTRIBUTING 95 | 96 | Pull requests are welcome! 97 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | "use strict"; 3 | import { dirname } from "path"; 4 | import { fileURLToPath } from "url"; 5 | import { createProject } from "./tasks/create-project.js"; 6 | import { nodeErrorMessage, introMessage } from "./tasks/output.js"; 7 | import { isValidNodeVersion } from "./tasks/checks.js"; 8 | 9 | if (isValidNodeVersion()) { 10 | introMessage(); 11 | createProject(process.cwd(), dirname(fileURLToPath(import.meta.url))); 12 | } else { 13 | nodeErrorMessage(); 14 | } 15 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "alright-react-app", 3 | "version": "2.0.6", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "alright-react-app", 9 | "version": "2.0.6", 10 | "license": "ISC", 11 | "dependencies": { 12 | "chalk": "^5.4.1", 13 | "inquirer": "^12.4.2", 14 | "ora": "^8.2.0", 15 | "which": "^5.0.0" 16 | }, 17 | "bin": { 18 | "alright-react-app": "index.js" 19 | }, 20 | "engines": { 21 | "node": ">=18.0.0" 22 | } 23 | }, 24 | "node_modules/@inquirer/checkbox": { 25 | "version": "4.1.2", 26 | "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.1.2.tgz", 27 | "integrity": "sha512-PL9ixC5YsPXzXhAZFUPmkXGxfgjkdfZdPEPPmt4kFwQ4LBMDG9n/nHXYRGGZSKZJs+d1sGKWgS2GiPzVRKUdtQ==", 28 | "dependencies": { 29 | "@inquirer/core": "^10.1.7", 30 | "@inquirer/figures": "^1.0.10", 31 | "@inquirer/type": "^3.0.4", 32 | "ansi-escapes": "^4.3.2", 33 | "yoctocolors-cjs": "^2.1.2" 34 | }, 35 | "engines": { 36 | "node": ">=18" 37 | }, 38 | "peerDependencies": { 39 | "@types/node": ">=18" 40 | }, 41 | "peerDependenciesMeta": { 42 | "@types/node": { 43 | "optional": true 44 | } 45 | } 46 | }, 47 | "node_modules/@inquirer/confirm": { 48 | "version": "5.1.6", 49 | "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.6.tgz", 50 | "integrity": "sha512-6ZXYK3M1XmaVBZX6FCfChgtponnL0R6I7k8Nu+kaoNkT828FVZTcca1MqmWQipaW2oNREQl5AaPCUOOCVNdRMw==", 51 | "dependencies": { 52 | "@inquirer/core": "^10.1.7", 53 | "@inquirer/type": "^3.0.4" 54 | }, 55 | "engines": { 56 | "node": ">=18" 57 | }, 58 | "peerDependencies": { 59 | "@types/node": ">=18" 60 | }, 61 | "peerDependenciesMeta": { 62 | "@types/node": { 63 | "optional": true 64 | } 65 | } 66 | }, 67 | "node_modules/@inquirer/core": { 68 | "version": "10.1.7", 69 | "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.7.tgz", 70 | "integrity": "sha512-AA9CQhlrt6ZgiSy6qoAigiA1izOa751ugX6ioSjqgJ+/Gd+tEN/TORk5sUYNjXuHWfW0r1n/a6ak4u/NqHHrtA==", 71 | "dependencies": { 72 | "@inquirer/figures": "^1.0.10", 73 | "@inquirer/type": "^3.0.4", 74 | "ansi-escapes": "^4.3.2", 75 | "cli-width": "^4.1.0", 76 | "mute-stream": "^2.0.0", 77 | "signal-exit": "^4.1.0", 78 | "wrap-ansi": "^6.2.0", 79 | "yoctocolors-cjs": "^2.1.2" 80 | }, 81 | "engines": { 82 | "node": ">=18" 83 | }, 84 | "peerDependencies": { 85 | "@types/node": ">=18" 86 | }, 87 | "peerDependenciesMeta": { 88 | "@types/node": { 89 | "optional": true 90 | } 91 | } 92 | }, 93 | "node_modules/@inquirer/editor": { 94 | "version": "4.2.7", 95 | "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.7.tgz", 96 | "integrity": "sha512-gktCSQtnSZHaBytkJKMKEuswSk2cDBuXX5rxGFv306mwHfBPjg5UAldw9zWGoEyvA9KpRDkeM4jfrx0rXn0GyA==", 97 | "dependencies": { 98 | "@inquirer/core": "^10.1.7", 99 | "@inquirer/type": "^3.0.4", 100 | "external-editor": "^3.1.0" 101 | }, 102 | "engines": { 103 | "node": ">=18" 104 | }, 105 | "peerDependencies": { 106 | "@types/node": ">=18" 107 | }, 108 | "peerDependenciesMeta": { 109 | "@types/node": { 110 | "optional": true 111 | } 112 | } 113 | }, 114 | "node_modules/@inquirer/expand": { 115 | "version": "4.0.9", 116 | "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.9.tgz", 117 | "integrity": "sha512-Xxt6nhomWTAmuSX61kVgglLjMEFGa+7+F6UUtdEUeg7fg4r9vaFttUUKrtkViYYrQBA5Ia1tkOJj2koP9BuLig==", 118 | "dependencies": { 119 | "@inquirer/core": "^10.1.7", 120 | "@inquirer/type": "^3.0.4", 121 | "yoctocolors-cjs": "^2.1.2" 122 | }, 123 | "engines": { 124 | "node": ">=18" 125 | }, 126 | "peerDependencies": { 127 | "@types/node": ">=18" 128 | }, 129 | "peerDependenciesMeta": { 130 | "@types/node": { 131 | "optional": true 132 | } 133 | } 134 | }, 135 | "node_modules/@inquirer/figures": { 136 | "version": "1.0.10", 137 | "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.10.tgz", 138 | "integrity": "sha512-Ey6176gZmeqZuY/W/nZiUyvmb1/qInjcpiZjXWi6nON+nxJpD1bxtSoBxNliGISae32n6OwbY+TSXPZ1CfS4bw==", 139 | "engines": { 140 | "node": ">=18" 141 | } 142 | }, 143 | "node_modules/@inquirer/input": { 144 | "version": "4.1.6", 145 | "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.1.6.tgz", 146 | "integrity": "sha512-1f5AIsZuVjPT4ecA8AwaxDFNHny/tSershP/cTvTDxLdiIGTeILNcKozB0LaYt6mojJLUbOYhpIxicaYf7UKIQ==", 147 | "dependencies": { 148 | "@inquirer/core": "^10.1.7", 149 | "@inquirer/type": "^3.0.4" 150 | }, 151 | "engines": { 152 | "node": ">=18" 153 | }, 154 | "peerDependencies": { 155 | "@types/node": ">=18" 156 | }, 157 | "peerDependenciesMeta": { 158 | "@types/node": { 159 | "optional": true 160 | } 161 | } 162 | }, 163 | "node_modules/@inquirer/number": { 164 | "version": "3.0.9", 165 | "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.9.tgz", 166 | "integrity": "sha512-iN2xZvH3tyIYXLXBvlVh0npk1q/aVuKXZo5hj+K3W3D4ngAEq/DkLpofRzx6oebTUhBvOgryZ+rMV0yImKnG3w==", 167 | "dependencies": { 168 | "@inquirer/core": "^10.1.7", 169 | "@inquirer/type": "^3.0.4" 170 | }, 171 | "engines": { 172 | "node": ">=18" 173 | }, 174 | "peerDependencies": { 175 | "@types/node": ">=18" 176 | }, 177 | "peerDependenciesMeta": { 178 | "@types/node": { 179 | "optional": true 180 | } 181 | } 182 | }, 183 | "node_modules/@inquirer/password": { 184 | "version": "4.0.9", 185 | "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.9.tgz", 186 | "integrity": "sha512-xBEoOw1XKb0rIN208YU7wM7oJEHhIYkfG7LpTJAEW913GZeaoQerzf5U/LSHI45EVvjAdgNXmXgH51cUXKZcJQ==", 187 | "dependencies": { 188 | "@inquirer/core": "^10.1.7", 189 | "@inquirer/type": "^3.0.4", 190 | "ansi-escapes": "^4.3.2" 191 | }, 192 | "engines": { 193 | "node": ">=18" 194 | }, 195 | "peerDependencies": { 196 | "@types/node": ">=18" 197 | }, 198 | "peerDependenciesMeta": { 199 | "@types/node": { 200 | "optional": true 201 | } 202 | } 203 | }, 204 | "node_modules/@inquirer/prompts": { 205 | "version": "7.3.2", 206 | "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.3.2.tgz", 207 | "integrity": "sha512-G1ytyOoHh5BphmEBxSwALin3n1KGNYB6yImbICcRQdzXfOGbuJ9Jske/Of5Sebk339NSGGNfUshnzK8YWkTPsQ==", 208 | "dependencies": { 209 | "@inquirer/checkbox": "^4.1.2", 210 | "@inquirer/confirm": "^5.1.6", 211 | "@inquirer/editor": "^4.2.7", 212 | "@inquirer/expand": "^4.0.9", 213 | "@inquirer/input": "^4.1.6", 214 | "@inquirer/number": "^3.0.9", 215 | "@inquirer/password": "^4.0.9", 216 | "@inquirer/rawlist": "^4.0.9", 217 | "@inquirer/search": "^3.0.9", 218 | "@inquirer/select": "^4.0.9" 219 | }, 220 | "engines": { 221 | "node": ">=18" 222 | }, 223 | "peerDependencies": { 224 | "@types/node": ">=18" 225 | }, 226 | "peerDependenciesMeta": { 227 | "@types/node": { 228 | "optional": true 229 | } 230 | } 231 | }, 232 | "node_modules/@inquirer/rawlist": { 233 | "version": "4.0.9", 234 | "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.0.9.tgz", 235 | "integrity": "sha512-+5t6ebehKqgoxV8fXwE49HkSF2Rc9ijNiVGEQZwvbMI61/Q5RcD+jWD6Gs1tKdz5lkI8GRBL31iO0HjGK1bv+A==", 236 | "dependencies": { 237 | "@inquirer/core": "^10.1.7", 238 | "@inquirer/type": "^3.0.4", 239 | "yoctocolors-cjs": "^2.1.2" 240 | }, 241 | "engines": { 242 | "node": ">=18" 243 | }, 244 | "peerDependencies": { 245 | "@types/node": ">=18" 246 | }, 247 | "peerDependenciesMeta": { 248 | "@types/node": { 249 | "optional": true 250 | } 251 | } 252 | }, 253 | "node_modules/@inquirer/search": { 254 | "version": "3.0.9", 255 | "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.0.9.tgz", 256 | "integrity": "sha512-DWmKztkYo9CvldGBaRMr0ETUHgR86zE6sPDVOHsqz4ISe9o1LuiWfgJk+2r75acFclA93J/lqzhT0dTjCzHuoA==", 257 | "dependencies": { 258 | "@inquirer/core": "^10.1.7", 259 | "@inquirer/figures": "^1.0.10", 260 | "@inquirer/type": "^3.0.4", 261 | "yoctocolors-cjs": "^2.1.2" 262 | }, 263 | "engines": { 264 | "node": ">=18" 265 | }, 266 | "peerDependencies": { 267 | "@types/node": ">=18" 268 | }, 269 | "peerDependenciesMeta": { 270 | "@types/node": { 271 | "optional": true 272 | } 273 | } 274 | }, 275 | "node_modules/@inquirer/select": { 276 | "version": "4.0.9", 277 | "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.0.9.tgz", 278 | "integrity": "sha512-BpJyJe7Dkhv2kz7yG7bPSbJLQuu/rqyNlF1CfiiFeFwouegfH+zh13KDyt6+d9DwucKo7hqM3wKLLyJxZMO+Xg==", 279 | "dependencies": { 280 | "@inquirer/core": "^10.1.7", 281 | "@inquirer/figures": "^1.0.10", 282 | "@inquirer/type": "^3.0.4", 283 | "ansi-escapes": "^4.3.2", 284 | "yoctocolors-cjs": "^2.1.2" 285 | }, 286 | "engines": { 287 | "node": ">=18" 288 | }, 289 | "peerDependencies": { 290 | "@types/node": ">=18" 291 | }, 292 | "peerDependenciesMeta": { 293 | "@types/node": { 294 | "optional": true 295 | } 296 | } 297 | }, 298 | "node_modules/@inquirer/type": { 299 | "version": "3.0.4", 300 | "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.4.tgz", 301 | "integrity": "sha512-2MNFrDY8jkFYc9Il9DgLsHhMzuHnOYM1+CUYVWbzu9oT0hC7V7EcYvdCKeoll/Fcci04A+ERZ9wcc7cQ8lTkIA==", 302 | "engines": { 303 | "node": ">=18" 304 | }, 305 | "peerDependencies": { 306 | "@types/node": ">=18" 307 | }, 308 | "peerDependenciesMeta": { 309 | "@types/node": { 310 | "optional": true 311 | } 312 | } 313 | }, 314 | "node_modules/ansi-escapes": { 315 | "version": "4.3.2", 316 | "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", 317 | "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", 318 | "dependencies": { 319 | "type-fest": "^0.21.3" 320 | }, 321 | "engines": { 322 | "node": ">=8" 323 | }, 324 | "funding": { 325 | "url": "https://github.com/sponsors/sindresorhus" 326 | } 327 | }, 328 | "node_modules/ansi-regex": { 329 | "version": "6.0.1", 330 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", 331 | "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", 332 | "engines": { 333 | "node": ">=12" 334 | }, 335 | "funding": { 336 | "url": "https://github.com/chalk/ansi-regex?sponsor=1" 337 | } 338 | }, 339 | "node_modules/ansi-styles": { 340 | "version": "4.3.0", 341 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 342 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 343 | "dependencies": { 344 | "color-convert": "^2.0.1" 345 | }, 346 | "engines": { 347 | "node": ">=8" 348 | }, 349 | "funding": { 350 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 351 | } 352 | }, 353 | "node_modules/chalk": { 354 | "version": "5.4.1", 355 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", 356 | "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", 357 | "engines": { 358 | "node": "^12.17.0 || ^14.13 || >=16.0.0" 359 | }, 360 | "funding": { 361 | "url": "https://github.com/chalk/chalk?sponsor=1" 362 | } 363 | }, 364 | "node_modules/chardet": { 365 | "version": "0.7.0", 366 | "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", 367 | "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" 368 | }, 369 | "node_modules/cli-cursor": { 370 | "version": "5.0.0", 371 | "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", 372 | "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", 373 | "dependencies": { 374 | "restore-cursor": "^5.0.0" 375 | }, 376 | "engines": { 377 | "node": ">=18" 378 | }, 379 | "funding": { 380 | "url": "https://github.com/sponsors/sindresorhus" 381 | } 382 | }, 383 | "node_modules/cli-spinners": { 384 | "version": "2.9.2", 385 | "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", 386 | "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", 387 | "engines": { 388 | "node": ">=6" 389 | }, 390 | "funding": { 391 | "url": "https://github.com/sponsors/sindresorhus" 392 | } 393 | }, 394 | "node_modules/cli-width": { 395 | "version": "4.1.0", 396 | "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", 397 | "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", 398 | "engines": { 399 | "node": ">= 12" 400 | } 401 | }, 402 | "node_modules/color-convert": { 403 | "version": "2.0.1", 404 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 405 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 406 | "dependencies": { 407 | "color-name": "~1.1.4" 408 | }, 409 | "engines": { 410 | "node": ">=7.0.0" 411 | } 412 | }, 413 | "node_modules/color-name": { 414 | "version": "1.1.4", 415 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 416 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" 417 | }, 418 | "node_modules/emoji-regex": { 419 | "version": "10.4.0", 420 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", 421 | "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==" 422 | }, 423 | "node_modules/external-editor": { 424 | "version": "3.1.0", 425 | "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", 426 | "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", 427 | "dependencies": { 428 | "chardet": "^0.7.0", 429 | "iconv-lite": "^0.4.24", 430 | "tmp": "^0.0.33" 431 | }, 432 | "engines": { 433 | "node": ">=4" 434 | } 435 | }, 436 | "node_modules/get-east-asian-width": { 437 | "version": "1.3.0", 438 | "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", 439 | "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", 440 | "engines": { 441 | "node": ">=18" 442 | }, 443 | "funding": { 444 | "url": "https://github.com/sponsors/sindresorhus" 445 | } 446 | }, 447 | "node_modules/iconv-lite": { 448 | "version": "0.4.24", 449 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 450 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 451 | "dependencies": { 452 | "safer-buffer": ">= 2.1.2 < 3" 453 | }, 454 | "engines": { 455 | "node": ">=0.10.0" 456 | } 457 | }, 458 | "node_modules/inquirer": { 459 | "version": "12.4.2", 460 | "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-12.4.2.tgz", 461 | "integrity": "sha512-reyjHcwyK2LObXgTJH4T1Dpfhwu88LNPTZmg/KenmTsy3T8lN/kZT8Oo7UwwkB9q8+ss2qjjN7GV8oFAfyz9Xg==", 462 | "dependencies": { 463 | "@inquirer/core": "^10.1.7", 464 | "@inquirer/prompts": "^7.3.2", 465 | "@inquirer/type": "^3.0.4", 466 | "ansi-escapes": "^4.3.2", 467 | "mute-stream": "^2.0.0", 468 | "run-async": "^3.0.0", 469 | "rxjs": "^7.8.1" 470 | }, 471 | "engines": { 472 | "node": ">=18" 473 | }, 474 | "peerDependencies": { 475 | "@types/node": ">=18" 476 | }, 477 | "peerDependenciesMeta": { 478 | "@types/node": { 479 | "optional": true 480 | } 481 | } 482 | }, 483 | "node_modules/is-fullwidth-code-point": { 484 | "version": "3.0.0", 485 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 486 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 487 | "engines": { 488 | "node": ">=8" 489 | } 490 | }, 491 | "node_modules/is-interactive": { 492 | "version": "2.0.0", 493 | "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", 494 | "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", 495 | "engines": { 496 | "node": ">=12" 497 | }, 498 | "funding": { 499 | "url": "https://github.com/sponsors/sindresorhus" 500 | } 501 | }, 502 | "node_modules/is-unicode-supported": { 503 | "version": "2.1.0", 504 | "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", 505 | "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", 506 | "engines": { 507 | "node": ">=18" 508 | }, 509 | "funding": { 510 | "url": "https://github.com/sponsors/sindresorhus" 511 | } 512 | }, 513 | "node_modules/isexe": { 514 | "version": "3.1.1", 515 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", 516 | "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", 517 | "engines": { 518 | "node": ">=16" 519 | } 520 | }, 521 | "node_modules/log-symbols": { 522 | "version": "6.0.0", 523 | "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", 524 | "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", 525 | "dependencies": { 526 | "chalk": "^5.3.0", 527 | "is-unicode-supported": "^1.3.0" 528 | }, 529 | "engines": { 530 | "node": ">=18" 531 | }, 532 | "funding": { 533 | "url": "https://github.com/sponsors/sindresorhus" 534 | } 535 | }, 536 | "node_modules/log-symbols/node_modules/is-unicode-supported": { 537 | "version": "1.3.0", 538 | "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", 539 | "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", 540 | "engines": { 541 | "node": ">=12" 542 | }, 543 | "funding": { 544 | "url": "https://github.com/sponsors/sindresorhus" 545 | } 546 | }, 547 | "node_modules/mimic-function": { 548 | "version": "5.0.1", 549 | "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", 550 | "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", 551 | "engines": { 552 | "node": ">=18" 553 | }, 554 | "funding": { 555 | "url": "https://github.com/sponsors/sindresorhus" 556 | } 557 | }, 558 | "node_modules/mute-stream": { 559 | "version": "2.0.0", 560 | "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", 561 | "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", 562 | "engines": { 563 | "node": "^18.17.0 || >=20.5.0" 564 | } 565 | }, 566 | "node_modules/onetime": { 567 | "version": "7.0.0", 568 | "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", 569 | "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", 570 | "dependencies": { 571 | "mimic-function": "^5.0.0" 572 | }, 573 | "engines": { 574 | "node": ">=18" 575 | }, 576 | "funding": { 577 | "url": "https://github.com/sponsors/sindresorhus" 578 | } 579 | }, 580 | "node_modules/ora": { 581 | "version": "8.2.0", 582 | "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", 583 | "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", 584 | "dependencies": { 585 | "chalk": "^5.3.0", 586 | "cli-cursor": "^5.0.0", 587 | "cli-spinners": "^2.9.2", 588 | "is-interactive": "^2.0.0", 589 | "is-unicode-supported": "^2.0.0", 590 | "log-symbols": "^6.0.0", 591 | "stdin-discarder": "^0.2.2", 592 | "string-width": "^7.2.0", 593 | "strip-ansi": "^7.1.0" 594 | }, 595 | "engines": { 596 | "node": ">=18" 597 | }, 598 | "funding": { 599 | "url": "https://github.com/sponsors/sindresorhus" 600 | } 601 | }, 602 | "node_modules/os-tmpdir": { 603 | "version": "1.0.2", 604 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", 605 | "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", 606 | "engines": { 607 | "node": ">=0.10.0" 608 | } 609 | }, 610 | "node_modules/restore-cursor": { 611 | "version": "5.1.0", 612 | "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", 613 | "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", 614 | "dependencies": { 615 | "onetime": "^7.0.0", 616 | "signal-exit": "^4.1.0" 617 | }, 618 | "engines": { 619 | "node": ">=18" 620 | }, 621 | "funding": { 622 | "url": "https://github.com/sponsors/sindresorhus" 623 | } 624 | }, 625 | "node_modules/run-async": { 626 | "version": "3.0.0", 627 | "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", 628 | "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", 629 | "engines": { 630 | "node": ">=0.12.0" 631 | } 632 | }, 633 | "node_modules/rxjs": { 634 | "version": "7.8.1", 635 | "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", 636 | "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", 637 | "dependencies": { 638 | "tslib": "^2.1.0" 639 | } 640 | }, 641 | "node_modules/safer-buffer": { 642 | "version": "2.1.2", 643 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 644 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 645 | }, 646 | "node_modules/signal-exit": { 647 | "version": "4.1.0", 648 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", 649 | "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", 650 | "engines": { 651 | "node": ">=14" 652 | }, 653 | "funding": { 654 | "url": "https://github.com/sponsors/isaacs" 655 | } 656 | }, 657 | "node_modules/stdin-discarder": { 658 | "version": "0.2.2", 659 | "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", 660 | "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", 661 | "engines": { 662 | "node": ">=18" 663 | }, 664 | "funding": { 665 | "url": "https://github.com/sponsors/sindresorhus" 666 | } 667 | }, 668 | "node_modules/string-width": { 669 | "version": "7.2.0", 670 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", 671 | "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", 672 | "dependencies": { 673 | "emoji-regex": "^10.3.0", 674 | "get-east-asian-width": "^1.0.0", 675 | "strip-ansi": "^7.1.0" 676 | }, 677 | "engines": { 678 | "node": ">=18" 679 | }, 680 | "funding": { 681 | "url": "https://github.com/sponsors/sindresorhus" 682 | } 683 | }, 684 | "node_modules/strip-ansi": { 685 | "version": "7.1.0", 686 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", 687 | "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", 688 | "dependencies": { 689 | "ansi-regex": "^6.0.1" 690 | }, 691 | "engines": { 692 | "node": ">=12" 693 | }, 694 | "funding": { 695 | "url": "https://github.com/chalk/strip-ansi?sponsor=1" 696 | } 697 | }, 698 | "node_modules/tmp": { 699 | "version": "0.0.33", 700 | "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", 701 | "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", 702 | "dependencies": { 703 | "os-tmpdir": "~1.0.2" 704 | }, 705 | "engines": { 706 | "node": ">=0.6.0" 707 | } 708 | }, 709 | "node_modules/tslib": { 710 | "version": "2.5.3", 711 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", 712 | "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==" 713 | }, 714 | "node_modules/type-fest": { 715 | "version": "0.21.3", 716 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", 717 | "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", 718 | "engines": { 719 | "node": ">=10" 720 | }, 721 | "funding": { 722 | "url": "https://github.com/sponsors/sindresorhus" 723 | } 724 | }, 725 | "node_modules/which": { 726 | "version": "5.0.0", 727 | "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", 728 | "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", 729 | "dependencies": { 730 | "isexe": "^3.1.1" 731 | }, 732 | "bin": { 733 | "node-which": "bin/which.js" 734 | }, 735 | "engines": { 736 | "node": "^18.17.0 || >=20.5.0" 737 | } 738 | }, 739 | "node_modules/wrap-ansi": { 740 | "version": "6.2.0", 741 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", 742 | "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", 743 | "dependencies": { 744 | "ansi-styles": "^4.0.0", 745 | "string-width": "^4.1.0", 746 | "strip-ansi": "^6.0.0" 747 | }, 748 | "engines": { 749 | "node": ">=8" 750 | } 751 | }, 752 | "node_modules/wrap-ansi/node_modules/ansi-regex": { 753 | "version": "5.0.1", 754 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 755 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 756 | "engines": { 757 | "node": ">=8" 758 | } 759 | }, 760 | "node_modules/wrap-ansi/node_modules/emoji-regex": { 761 | "version": "8.0.0", 762 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 763 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" 764 | }, 765 | "node_modules/wrap-ansi/node_modules/string-width": { 766 | "version": "4.2.3", 767 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 768 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 769 | "dependencies": { 770 | "emoji-regex": "^8.0.0", 771 | "is-fullwidth-code-point": "^3.0.0", 772 | "strip-ansi": "^6.0.1" 773 | }, 774 | "engines": { 775 | "node": ">=8" 776 | } 777 | }, 778 | "node_modules/wrap-ansi/node_modules/strip-ansi": { 779 | "version": "6.0.1", 780 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 781 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 782 | "dependencies": { 783 | "ansi-regex": "^5.0.1" 784 | }, 785 | "engines": { 786 | "node": ">=8" 787 | } 788 | }, 789 | "node_modules/yoctocolors-cjs": { 790 | "version": "2.1.2", 791 | "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", 792 | "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", 793 | "engines": { 794 | "node": ">=18" 795 | }, 796 | "funding": { 797 | "url": "https://github.com/sponsors/sindresorhus" 798 | } 799 | } 800 | } 801 | } 802 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "alright-react-app", 3 | "version": "2.0.7", 4 | "description": "Professional React app generator. Shipped with an exposed, unopinionated, highly-performant config. Jest-SWC, Storybook, SWC, Typescript, Webpack 5.", 5 | "main": "index.js", 6 | "author": "DoneDeal0", 7 | "license": "ISC", 8 | "type": "module", 9 | "declaration": true, 10 | "keywords": [ 11 | "react", 12 | "reactjs", 13 | "create react app", 14 | "create-react-app", 15 | "alright pro react app", 16 | "alright-pro-react-app", 17 | "pro", 18 | "professional", 19 | "production", 20 | "generator", 21 | "swc" 22 | ], 23 | "bin": "./index.js", 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/DoneDeal0/alright-react-app" 27 | }, 28 | "dependencies": { 29 | "chalk": "^5.4.1", 30 | "inquirer": "^12.4.2", 31 | "ora": "^8.2.0", 32 | "which": "^5.0.0" 33 | }, 34 | "engines": { 35 | "node": ">=18.0.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tasks/checks.js: -------------------------------------------------------------------------------- 1 | import which from "which"; 2 | 3 | export async function getCommand() { 4 | try { 5 | await which("npm"); 6 | if (process.platform === "win32") return "npm.cmd"; 7 | return "npm"; 8 | } catch (err) { 9 | throw new Error(err); 10 | } 11 | } 12 | 13 | export function isValidNodeVersion() { 14 | const version = process.versions.node; 15 | if (!version) return false; 16 | const nodeNumber = Number(version.split(".").splice(0, 2).join(".")); 17 | return nodeNumber >= 18.0 18 | } -------------------------------------------------------------------------------- /tasks/create-directory-content.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | "use strict"; 3 | import fs from "fs"; 4 | import os from "os"; 5 | 6 | async function writePackageJSON(path, fileContent, projectName) { 7 | const userName = os.userInfo().username || ""; 8 | const parsedJSON = JSON.parse(fileContent); 9 | parsedJSON.name = projectName; 10 | parsedJSON.author = userName; 11 | const updatedJSON = JSON.stringify(parsedJSON, null, 4); 12 | return fs.writeFileSync(path, updatedJSON, "utf8"); 13 | } 14 | 15 | async function createFile(srcPath, destPath, fileName, projectName) { 16 | const fileContent = fs.readFileSync(srcPath, "utf8"); 17 | if (fileName === "package.json") { 18 | writePackageJSON(destPath, fileContent, projectName); 19 | } else if (fileName === "env" || fileName === "gitignore") { 20 | const dotPath = `${destPath.replace(fileName, `.${fileName}`)}`; 21 | fs.writeFileSync(dotPath, fileContent, "utf8"); 22 | } else { 23 | fs.writeFileSync(destPath, fileContent, "utf8"); 24 | } 25 | } 26 | 27 | export async function createDirectoryContent( 28 | templatePath, 29 | newPath, 30 | projectName, 31 | directory 32 | ) { 33 | try { 34 | const filesToCreate = fs.readdirSync(templatePath); 35 | 36 | for (const file of filesToCreate) { 37 | const currentPath = `${templatePath}/${file}`; 38 | const fileType = fs.statSync(currentPath); 39 | 40 | if (fileType.isFile()) { 41 | await createFile( 42 | currentPath, 43 | `${directory}/${newPath}/${file}`, 44 | file, 45 | projectName 46 | ); 47 | } 48 | 49 | if (fileType.isDirectory()) { 50 | fs.mkdirSync(`${directory}/${newPath}/${file}`); 51 | await createDirectoryContent( 52 | currentPath, 53 | `${newPath}/${file}`, 54 | projectName, 55 | directory 56 | ); 57 | } 58 | } 59 | } catch (err) { 60 | throw new Error(err); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tasks/create-project.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | "use strict"; 3 | import fs from "fs"; 4 | import inquirer from "inquirer"; 5 | import path from "path"; 6 | import { getCommand } from "./checks.js"; 7 | import { createDirectoryContent } from "./create-directory-content.js"; 8 | import { installDependencies, initializeGit } from "./installation.js"; 9 | import { errorMessage, preInstallMessage, postInstallMessage } from "./output.js"; 10 | import { question } from "./question.js"; 11 | 12 | export async function createProject(directory, __dirname) { 13 | try { 14 | const answer = await inquirer.prompt(question); 15 | const projectName = answer["project-name"]; 16 | const templatePath = `${__dirname}/template`; 17 | const projectPath = path.join(directory, projectName); 18 | fs.mkdirSync(projectPath); 19 | await createDirectoryContent( 20 | templatePath, 21 | projectName, 22 | projectName, 23 | directory 24 | ); 25 | process.chdir(projectPath); 26 | const command = await getCommand(); 27 | preInstallMessage(projectName, command); 28 | await initializeGit(projectPath); 29 | await installDependencies(command, projectPath); 30 | postInstallMessage(projectName, "npm"); 31 | } catch (err) { 32 | errorMessage(err) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tasks/installation.js: -------------------------------------------------------------------------------- 1 | import ora from "ora"; 2 | import { spawn } from "child_process"; 3 | import which from "which"; 4 | import { promisify } from "util"; 5 | import { gitMessage } from "./output.js"; 6 | 7 | export async function installDependencies(command, projectPath) { 8 | return new Promise((resolve, reject) => { 9 | const installationProcess = spawn(command, ["install --force"], { 10 | cwd: projectPath, 11 | stdio: "ignore", 12 | }); 13 | const spinner = ora("Installing dependencies...").start(); 14 | spinner.color = "yellow"; 15 | installationProcess.on("error", (error) => { 16 | spinner.fail("Failed to install dependencies"); 17 | reject(error); 18 | }); 19 | 20 | installationProcess.on("exit", () => { 21 | spinner.succeed("All dependencies have been installed"); 22 | resolve(); 23 | }); 24 | }); 25 | } 26 | 27 | export async function initializeGit(projectPath) { 28 | try { 29 | const hasGit = await which("git", { nothrow: true }); 30 | if (hasGit) { 31 | const gitConfig = spawn("git", [ 32 | "config", 33 | "--global", 34 | "init.defaultBranch", 35 | "main", 36 | ]); 37 | await promisify(gitConfig.on.bind(gitConfig))("close"); 38 | const gitInit = spawn("git", ["init"], { 39 | cwd: projectPath, 40 | stdio: "ignore", 41 | }); 42 | await promisify(gitInit.on.bind(gitInit))("close"); 43 | gitMessage(hasGit); 44 | } else { 45 | gitMessage(hasGit); 46 | } 47 | } catch (_) { 48 | gitMessage(false); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tasks/output.js: -------------------------------------------------------------------------------- 1 | import chalk from "chalk"; 2 | 3 | export function introMessage() { 4 | console.log("", "\n", chalk.yellow.bold("ALRIGHT REACT APP")); 5 | console.log( 6 | "", 7 | "\n", 8 | "Alright, alright, alright! Let's create a new React app!", 9 | "\n" 10 | ); 11 | } 12 | 13 | export function preInstallMessage(projectName, command) { 14 | console.log( 15 | "\n", 16 | `${chalk.yellow.bold("✓")}`, 17 | `${chalk.yellow.bold(projectName)}'s files and folders have been created`, 18 | "\n", 19 | `${chalk.yellow.bold("✓")}`, 20 | `${chalk.yellow.bold(command)} detected` 21 | ); 22 | } 23 | 24 | export function gitMessage(hasGit) { 25 | if (hasGit) { 26 | console.log( 27 | `${chalk.yellow.bold(" ✓")}`, 28 | `${chalk.yellow.bold("git")} initialized`, 29 | "\n" 30 | ); 31 | } else { 32 | console.log( 33 | `${chalk.red.bold(" ×")}`, 34 | `${chalk.red.bold("git")} couldn't be initialized`, 35 | "\n" 36 | ); 37 | } 38 | } 39 | 40 | const runLine = (name) => 41 | `now run ${chalk.bgYellow.black.bold(`cd ${name}`)} to get started!`; 42 | 43 | export function postInstallMessage(projectName, command) { 44 | console.log( 45 | "", 46 | "\n", 47 | "Project", 48 | chalk.yellow.bold(projectName), 49 | "was successfully created!", 50 | "\n", 51 | "\n", 52 | runLine(projectName), 53 | "\n", 54 | "\n", 55 | chalk.yellow.bold("DEV"), 56 | `: ${command} dev`, 57 | "\n", 58 | chalk.yellow.bold("BUILD"), 59 | `: ${command} build`, 60 | "\n", 61 | chalk.yellow.bold("STORYBOOK"), 62 | `: ${command} storybook`, 63 | "\n", 64 | chalk.yellow.bold("TEST"), 65 | `: ${command} test`, 66 | "\n" 67 | ); 68 | } 69 | 70 | 71 | export function errorMessage(error) { 72 | console.error(chalk.red.bold("The following error occurred:", error)); 73 | } 74 | 75 | export function nodeErrorMessage() { 76 | console.error( 77 | "\n", 78 | chalk.red.bold( 79 | `Alright React App requires Node 18.0.0 or higher. Your version is ${process.versions.node}.` 80 | ), 81 | "\n" 82 | ); 83 | } -------------------------------------------------------------------------------- /tasks/question.js: -------------------------------------------------------------------------------- 1 | export const question = [ 2 | { 3 | name: "project-name", 4 | type: "input", 5 | message: "Project name:", 6 | validate: function (input) { 7 | if (/^([A-Za-z\-\_\d])+$/.test(input)) { 8 | return true; 9 | } 10 | return "Sorry, your project name must only include letters, numbers, dashes or underscores."; 11 | }, 12 | }, 13 | ]; 14 | -------------------------------------------------------------------------------- /template/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DoneDeal0/alright-react-app/9859f48433931ee2e159f6c75507ed0c642871e3/template/.DS_Store -------------------------------------------------------------------------------- /template/.prettierignore: -------------------------------------------------------------------------------- 1 | assets/ 2 | build/ 3 | node_modules/ 4 | webpack.config.js -------------------------------------------------------------------------------- /template/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "semi": true, 4 | "singleQuote": false, 5 | "arrowParens": "always" 6 | } 7 | -------------------------------------------------------------------------------- /template/.storybook/main.js: -------------------------------------------------------------------------------- 1 | import webpackConfig from "../webpack.config"; 2 | const _webpackConfig = webpackConfig({ WEBPACK_SERVE: true }); 3 | 4 | export default { 5 | stories: ["../**/stories.tsx"], 6 | addons: [ 7 | "@storybook/addon-essentials", 8 | "@storybook/addon-actions", 9 | "@storybook/addon-interactions", 10 | ], 11 | framework: { 12 | name: "@storybook/react-webpack5", 13 | options: { 14 | fastRefresh: true, 15 | }, 16 | }, 17 | webpackFinal: async (config) => { 18 | return { 19 | ...config, 20 | resolve: { 21 | ...config.resolve, 22 | ..._webpackConfig.resolve, 23 | }, 24 | module: { 25 | ...config.module, 26 | rules: [..._webpackConfig.module.rules], 27 | }, 28 | }; 29 | }, 30 | docs: { 31 | autodocs: true, 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /template/.storybook/preview.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const preview = { 4 | decorators: [ 5 | (Story) => ( 6 |
7 | 8 |
9 | ), 10 | ], 11 | }; 12 | 13 | export default preview; 14 | -------------------------------------------------------------------------------- /template/.swcrc: -------------------------------------------------------------------------------- 1 | { 2 | "jsc": { 3 | "baseUrl": "./", 4 | "parser": { 5 | "syntax": "typescript", 6 | "tsx": true, 7 | "dynamicImport": true 8 | }, 9 | "paths": { 10 | "assets/*": ["src/assets/*"], 11 | "components/*": ["src/components/*"], 12 | "pages/*": ["src/pages/*"], 13 | "src/*": ["src/*"] 14 | }, 15 | "target": "es2016" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /template/README.md: -------------------------------------------------------------------------------- 1 | # Alright, alright, alright! 2 | 3 | This app was generated with ALRIGHT REACT APP, an app generator shipped with an exposed, unopinionated and highly-performant config. It includes Jest-SWC, Storybook, SWC, Typescript, and Webpack 5. 4 | 5 | # COMMANDS 6 | 7 | **START**: yarn dev 8 | 9 | **BUILD**: yarn build 10 | 11 | **STORYBOOK**: yarn storybook 12 | 13 | **TEST**: yarn test 14 | -------------------------------------------------------------------------------- /template/custom.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.svg" { 2 | const content: any; 3 | export default content; 4 | } 5 | 6 | declare module "*.jpeg" { 7 | const content: any; 8 | export default content; 9 | } 10 | 11 | declare module "*.jpg" { 12 | const content: any; 13 | export default content; 14 | } 15 | 16 | declare module "*.png" { 17 | const content: any; 18 | export default content; 19 | } 20 | -------------------------------------------------------------------------------- /template/env: -------------------------------------------------------------------------------- 1 | MY_SECRET_KEY = "12345" -------------------------------------------------------------------------------- /template/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import importHelpers from "eslint-plugin-import-helpers"; 2 | import unusedImports from "eslint-plugin-unused-imports"; 3 | import path from "node:path"; 4 | import { fileURLToPath } from "node:url"; 5 | import { fixupConfigRules } from "@eslint/compat"; 6 | import { FlatCompat } from "@eslint/eslintrc"; 7 | import js from "@eslint/js"; 8 | 9 | const __filename = fileURLToPath(import.meta.url); 10 | const __dirname = path.dirname(__filename); 11 | const compat = new FlatCompat({ 12 | baseDirectory: __dirname, 13 | recommendedConfig: js.configs.recommended, 14 | allConfig: js.configs.all, 15 | }); 16 | 17 | export default [ 18 | { 19 | ignores: [ 20 | "eslint-rules/*", 21 | "dist/**", 22 | ".storybook/**", 23 | "storybook-static/**", 24 | "custom.d.ts", 25 | "eslint.config.mjs", 26 | "webpack.config.js", 27 | "build", 28 | "jest.swc.config.js" 29 | ], 30 | }, 31 | 32 | ...fixupConfigRules( 33 | compat.extends( 34 | "plugin:react/recommended", 35 | "plugin:storybook/recommended", 36 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 37 | "plugin:testing-library/react", 38 | "plugin:react-hooks/recommended", 39 | "prettier", 40 | ), 41 | ), 42 | 43 | // Plugin and rule setup 44 | { 45 | plugins: { 46 | "import-helpers": importHelpers, 47 | "unused-imports": unusedImports, 48 | }, 49 | 50 | settings: { 51 | react: { 52 | version: "detect", 53 | }, 54 | }, 55 | 56 | languageOptions: { 57 | ecmaVersion: 2021, 58 | sourceType: "module", 59 | parserOptions: { 60 | project: "./tsconfig.json", 61 | tsConfigRootDir: __dirname, 62 | }, 63 | }, 64 | 65 | rules: { 66 | // Best practices and style 67 | complexity: "error", 68 | "no-console": ["error", { allow: ["warn", "error", "info"] }], 69 | 70 | // Import rules 71 | "import-helpers/order-imports": [ 72 | "error", 73 | { 74 | groups: ["absolute", "/^react$/", "module", "/^@/", "parent", "sibling", "index"], 75 | alphabetize: { order: "asc" }, 76 | }, 77 | ], 78 | "sort-imports": ["error", { ignoreDeclarationSort: true }], 79 | "unused-imports/no-unused-imports": "error", 80 | 81 | // React and hooks 82 | "react/react-in-jsx-scope": "error", 83 | "react-hooks/exhaustive-deps": "error", 84 | "react-hooks/rules-of-hooks": "error", 85 | 86 | // TypeScript-specific rules 87 | "@typescript-eslint/no-inferrable-types": "error", 88 | "@typescript-eslint/no-misused-promises": "error", 89 | "@typescript-eslint/no-non-null-assertion": "error", 90 | "@typescript-eslint/no-unnecessary-type-assertion": "error", 91 | "@typescript-eslint/no-unsafe-argument": "error", 92 | "@typescript-eslint/no-unsafe-assignment": "error", 93 | "@typescript-eslint/no-unsafe-call": "error", 94 | "@typescript-eslint/no-unsafe-member-access": "error", 95 | "@typescript-eslint/no-unsafe-return": "error", 96 | "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_", varsIgnorePattern: "^React$" }], 97 | "@typescript-eslint/unbound-method": "error", 98 | }, 99 | }, 100 | ]; -------------------------------------------------------------------------------- /template/gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /build 3 | .env 4 | tsconfig.tsbuildinfo 5 | .DS_Store -------------------------------------------------------------------------------- /template/jest.swc.config.js: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | const resolve = (_path) => path.resolve(__dirname, _path); 3 | 4 | module.exports = { 5 | testURL: "http://localhost", 6 | moduleNameMapper: { 7 | "assets/(.*)": resolve("./assets/$1"), 8 | "components/(.*)": resolve("./components/$1"), 9 | "pages/(.*)": resolve("./pages/$1"), 10 | "src/(.*)": resolve("./src/$1"), 11 | }, 12 | testEnvironment: "jsdom", 13 | snapshotSerializers: ["jest-serializer-html"], 14 | testPathIgnorePatterns: ["e2e/", ".*donottest.*"], 15 | transform: { "^.+\\.(t|j)sx?$": ["@swc/jest"] }, 16 | testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", 17 | moduleFileExtensions: ["js", "jsx", "json", "ts", "tsx"], 18 | }; 19 | -------------------------------------------------------------------------------- /template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "alright-create-app", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "ISC", 6 | "author": "DoneDeal0", 7 | "scripts": { 8 | "build-storybook": "storybook build", 9 | "build": "webpack", 10 | "dev": "webpack serve --progress", 11 | "lint": "eslint --cache", 12 | "start": "npm build/index.js", 13 | "storybook": "storybook dev -p 6006", 14 | "test": "jest --maxWorkers=50%", 15 | "tsc": "tsc --noEmit --skipLibCheck --incremental" 16 | }, 17 | "dependencies": { 18 | "react": "^19.0.0", 19 | "react-dom": "^19.0.0" 20 | }, 21 | "devDependencies": { 22 | "@eslint/compat": "^1.2.6", 23 | "@eslint/eslintrc": "^3.2.0", 24 | "@eslint/js": "^9.20.0", 25 | "@storybook/addon-actions": "^8.5.8", 26 | "@storybook/addon-essentials": "^8.5.8", 27 | "@storybook/addon-interactions": "^8.5.8", 28 | "@storybook/jest": "^0.2.3", 29 | "@storybook/react-webpack5": "^8.5.8", 30 | "@storybook/react": "^8.5.8", 31 | "@storybook/testing-library": "^0.2.2", 32 | "@svgr/webpack": "^8.1.0", 33 | "@swc/cli": "^0.6.0", 34 | "@swc/core": "^1.10.18", 35 | "@swc/jest": "^0.2.37", 36 | "@types/jest": "^29.5.14", 37 | "@types/react-dom": "^19.0.4", 38 | "@types/react": "^19.0.10", 39 | "@typescript-eslint/eslint-plugin": "^8.24.1", 40 | "@typescript-eslint/parser": "^8.24.1", 41 | "compression-webpack-plugin": "^11.1.0", 42 | "css-loader": "^7.1.2", 43 | "css-minimizer-webpack-plugin": "^7.0.0", 44 | "dotenv-webpack": "^8.1.0", 45 | "eslint-config-prettier": "^10.0.1", 46 | "eslint-config-react-app": "^7.0.1", 47 | "eslint-plugin-flowtype": "^8.0.3", 48 | "eslint-plugin-import-helpers": "^2.0.1", 49 | "eslint-plugin-import": "^2.31.0", 50 | "eslint-plugin-jest": "^28.11.0", 51 | "eslint-plugin-jsx-a11y": "^6.10.2", 52 | "eslint-plugin-react-hooks": "^5.1.0", 53 | "eslint-plugin-react": "^7.37.4", 54 | "eslint-plugin-storybook": "^0.11.3", 55 | "eslint-plugin-testing-library": "^7.1.1", 56 | "eslint-plugin-unused-imports": "^4.1.4", 57 | "eslint": "^9.20.0", 58 | "file-loader": "^6.2.0", 59 | "html-webpack-plugin": "^5.6.3", 60 | "jest": "^29.7.0", 61 | "lint-staged": "^15.4.3", 62 | "prettier": "^3.5.1", 63 | "storybook": "^8.5.8", 64 | "swc-loader": "^0.2.6", 65 | "terser-webpack-plugin": "^5.3.11", 66 | "typescript": "^5.7.3", 67 | "webpack-cli": "^6.0.1", 68 | "webpack-dev-server": "^5.2.0", 69 | "webpack": "^5.98.0" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /template/src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DoneDeal0/alright-react-app/9859f48433931ee2e159f6c75507ed0c642871e3/template/src/.DS_Store -------------------------------------------------------------------------------- /template/src/app.test.ts: -------------------------------------------------------------------------------- 1 | describe("addition", () => { 2 | it("returns 1 + 1 = 2", () => { 3 | expect(1 + 1).toEqual(2); 4 | }); 5 | }); 6 | -------------------------------------------------------------------------------- /template/src/app.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import ErrorBoundary, { ErrorLayout } from "components/error-boundary/index"; 3 | import Home from "pages/home"; 4 | 5 | export default function App() { 6 | return ( 7 | }> 8 | 9 | 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /template/src/assets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DoneDeal0/alright-react-app/9859f48433931ee2e159f6c75507ed0c642871e3/template/src/assets/.DS_Store -------------------------------------------------------------------------------- /template/src/assets/images/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DoneDeal0/alright-react-app/9859f48433931ee2e159f6c75507ed0c642871e3/template/src/assets/images/.DS_Store -------------------------------------------------------------------------------- /template/src/assets/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DoneDeal0/alright-react-app/9859f48433931ee2e159f6c75507ed0c642871e3/template/src/assets/images/favicon.png -------------------------------------------------------------------------------- /template/src/assets/svg/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /template/src/components/error-boundary/index.tsx: -------------------------------------------------------------------------------- 1 | import { Component, ReactNode } from "react"; 2 | export { ErrorLayout } from "./layout"; 3 | 4 | interface Props { 5 | fallback: ReactNode; 6 | children: ReactNode; 7 | } 8 | 9 | export default class ErrorBoundary extends Component { 10 | state = { error: null }; 11 | 12 | static defaultProps: Props = { 13 | fallback: [], 14 | children: null, 15 | }; 16 | 17 | static getDerivedStateFromError(error: Error) { 18 | return { error }; 19 | } 20 | 21 | render() { 22 | if (this.state.error) { 23 | return this.props.fallback; 24 | } 25 | return this.props.children; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /template/src/components/error-boundary/layout.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export function ErrorLayout() { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /template/src/components/title/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface ITitle { 4 | value: string; 5 | onClick: () => void; 6 | } 7 | 8 | export default function Title({ value, onClick }: ITitle) { 9 | return ( 10 |

11 | {value} 12 |

13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /template/src/components/title/stories.tsx: -------------------------------------------------------------------------------- 1 | import { action } from "@storybook/addon-actions"; 2 | import { expect } from "@storybook/jest"; 3 | import type { Meta, StoryObj } from "@storybook/react"; 4 | import { userEvent, within } from "@storybook/testing-library"; 5 | import Title from "."; 6 | 7 | const meta: Meta = { 8 | component: Title, 9 | title: "components/title", 10 | argTypes: { 11 | value: { 12 | control: { type: "text" }, 13 | }, 14 | onClick: { action: "click!" }, 15 | }, 16 | }; 17 | type Story = StoryObj; 18 | 19 | export const Default: Story = { 20 | args: { value: "Hello world", onClick: action("click!") }, 21 | }; 22 | 23 | // Automated test to verify that the component behaves as expected 24 | export const Test: Story = { 25 | args: { value: "Hello world", onClick: action("click!") }, 26 | play: async ({ canvasElement }) => { 27 | const canvas = within(canvasElement); 28 | const title = canvas.getByTestId("root"); 29 | await expect(title).toBeInTheDocument(); 30 | await userEvent.click(title); 31 | await expect(title).toHaveTextContent("Hello world"); 32 | }, 33 | }; 34 | 35 | export default meta; 36 | -------------------------------------------------------------------------------- /template/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Alright React App 11 | 12 | 13 | 14 |
15 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /template/src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { createRoot } from "react-dom/client"; 3 | import App from "./app"; 4 | 5 | const container = document.getElementById("root"); 6 | const root = createRoot(container); 7 | 8 | root.render(); 9 | -------------------------------------------------------------------------------- /template/src/pages/home/home.css: -------------------------------------------------------------------------------- 1 | .home { 2 | text-align: center; 3 | } 4 | 5 | .home-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .home-logo { 12 | animation: home-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .home-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .home-title { 28 | font-size: 32px; 29 | font-family: "monospace", sans-serif; 30 | color: yellow; 31 | } 32 | 33 | .home-text { 34 | font-size: 14px; 35 | font-family: "monospace", sans-serif; 36 | color: lightgrey; 37 | } 38 | 39 | @keyframes home-logo-spin { 40 | from { 41 | transform: rotate(0deg); 42 | } 43 | to { 44 | transform: rotate(360deg); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /template/src/pages/home/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Logo from "assets/svg/logo.svg"; 3 | import "./home.css"; 4 | 5 | export default function Home() { 6 | return ( 7 |
8 |
9 | 10 |

Alright React App

11 |

Edit to see changes

12 |
13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /template/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./build/", 4 | "baseUrl": "./", 5 | "paths": { 6 | "assets/*": ["src/assets/*"], 7 | "components/*": ["src/components/*"], 8 | "pages/*": ["src/pages/*"], 9 | "src/*": ["src/*"] 10 | }, 11 | "skipLibCheck": true, 12 | "noEmit": true, 13 | "noImplicitAny": false, 14 | "downlevelIteration": true, 15 | "lib": ["ESNext", "dom"], 16 | "rootDir": "src", 17 | "module": "esnext", 18 | "target": "ESNext", 19 | "allowSyntheticDefaultImports": true, 20 | "moduleResolution": "node", 21 | "resolveJsonModule": true, 22 | "jsx": "react", 23 | "allowJs": true 24 | }, 25 | "include": ["./src/**/*", "./custom.d.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /template/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | const CompressionPlugin = require("compression-webpack-plugin"); 3 | const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); 4 | const Dotenv = require("dotenv-webpack"); 5 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 6 | const path = require("path"); 7 | const TerserPlugin = require("terser-webpack-plugin"); 8 | const zlib = require("zlib"); 9 | 10 | const getPlugins = (isProduction) => { 11 | const plugins = [ 12 | new Dotenv({ 13 | systemvars: true, 14 | }), 15 | new HtmlWebpackPlugin({ 16 | template: "./src/index.html", 17 | favicon: path.join(__dirname, "./src/assets/images/favicon.png"), 18 | }), 19 | ]; 20 | if (isProduction) { 21 | plugins.push( 22 | new CompressionPlugin({ 23 | filename: "[path][base].gz", 24 | algorithm: "gzip", 25 | test: /\.(js|css|ts|tsx|html)$/, 26 | threshold: 10240, 27 | minRatio: 0.8, 28 | }), 29 | new CompressionPlugin({ 30 | filename: "[path][base].br", 31 | algorithm: "brotliCompress", 32 | test: /\.(js|css|ts|tsx|html|svg)$/, 33 | compressionOptions: { 34 | params: { 35 | [zlib.constants.BROTLI_PARAM_QUALITY]: 11, 36 | }, 37 | }, 38 | threshold: 10240, 39 | minRatio: 0.8, 40 | deleteOriginalAssets: false, 41 | }) 42 | ); 43 | } 44 | return plugins; 45 | }; 46 | 47 | module.exports = (env) => { 48 | const nodeEnv = env.WEBPACK_SERVE ? "development" : "production"; 49 | const isProduction = nodeEnv === "production"; 50 | const plugins = getPlugins(isProduction); 51 | return { 52 | mode: nodeEnv, 53 | entry: "./src/index.tsx", 54 | output: { 55 | path: path.join(__dirname, "./build"), 56 | filename: "[name].[contenthash].bundle.js", 57 | chunkFilename: "[name].chunk.bundle.js", 58 | publicPath: "/", 59 | clean: true, 60 | }, 61 | resolve: { 62 | extensions: [".ts", ".tsx", ".js", "jsx", ".json"], 63 | alias: { 64 | assets: path.resolve(__dirname, "src/assets/"), 65 | components: path.resolve(__dirname, "src/components/"), 66 | pages: path.resolve(__dirname, "src/pages/"), 67 | src: path.resolve(__dirname, "src/"), 68 | }, 69 | }, 70 | module: { 71 | rules: [ 72 | { 73 | test: /\.(ts|js)x?$/, 74 | exclude: /node_modules/, 75 | use: { loader: "swc-loader" }, 76 | }, 77 | { test: /\.css$/, use: ["style-loader", "css-loader"] }, 78 | { test: /\.(png|jpg|jpeg|woff2)$/, use: ["file-loader"] }, 79 | { 80 | test: /\.svg$/i, 81 | issuer: /\.[jt]sx?$/, 82 | use: ["@svgr/webpack"], 83 | }, 84 | ], 85 | }, 86 | devServer: { 87 | historyApiFallback: true, 88 | open: !isProduction, 89 | port: 3000, 90 | hot: true, 91 | }, 92 | plugins, 93 | optimization: { 94 | minimize: isProduction, 95 | minimizer: isProduction 96 | ? [new TerserPlugin(), new CssMinimizerPlugin()] 97 | : [], 98 | splitChunks: { 99 | chunks: "initial", 100 | cacheGroups: { 101 | vendor: { 102 | test: /[\\/]node_modules[\\/]/, 103 | chunks: "all", 104 | name: (module, chunks) => { 105 | const allChunksNames = chunks.map(({ name }) => name).join("."); 106 | const moduleName = (module.context.match( 107 | /[\\/]node_modules[\\/](.*?)([\\/]|$)/ 108 | ) || [])[1]; 109 | return `${moduleName}.${allChunksNames}`; 110 | }, 111 | }, 112 | }, 113 | }, 114 | }, 115 | }; 116 | }; 117 | 118 | process.on("SIGINT", () => process.exit(0)); 119 | --------------------------------------------------------------------------------