generator-reackt 1.0.1 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,16 +5,51 @@ This project scaffolds a modern web application. It is fairly opinionated, but i
5
5
  Here's the mile-long list of technologies the generated project can leverage:
6
6
 
7
7
  - [TypeScript](https://www.typescriptlang.org/) - The language of the modern web
8
- - [Webpack](https://webpack.js.org/) - Bundle code, styles, markup, and more into a web application
9
- - [husky](https://www.npmjs.com/package/husky) - Execute lint-staged before committing to Git
10
- - [lint-staged](https://www.npmjs.com/package/lint-staged) - Execute linters for specific file types
8
+ - [Webpack](https://webpack.js.org/) - Bundle code, styles, markup, and more
9
+ - [React](https://reactjs.org/) - Application framework library
10
+
11
+ Linting tools:
12
+
11
13
  - [ESLint](https://eslint.org/) - Apply hundreds of style/usage rules to JS/TS
12
14
  - [Prettier](https://prettier.io/) - Apply style/formatting rules to code
13
15
  - [stylelint](https://stylelint.io/) - Apply style/usage rules to Sass
14
- - [Bootstrap](https://getbootstrap.com/) - UI toolkit
15
- - [Material-UI](https://mui.com/material-ui/) - UI toolkit
16
- - [React](https://reactjs.org/) - UI library
16
+
17
+ Optional pre-commit linting:
18
+
19
+ - [husky](https://www.npmjs.com/package/husky) - Execute lint-staged before committing to Git
20
+ - [lint-staged](https://www.npmjs.com/package/lint-staged) - Execute linters for specific file types
21
+
22
+ Optional style frameworks:
23
+
24
+ - [Bulma](https://bulma.io/)
25
+ - [Bootstrap](https://getbootstrap.com/)
26
+ - [Foundation](https://get.foundation/sites.html)
27
+ - [Material-UI](https://mui.com/material-ui/)
28
+ - [Materialize CSS](https://materializecss.com/)
29
+ - [UIkit](https://getuikit.com/)
30
+
31
+ Optional test frameworks:
32
+
33
+ - [Jest](https://jestjs.io/) - Unit, integration, and snapshot testing
34
+ - [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/) - Opinionated unit testing for React
35
+
36
+ Optional state management:
37
+
17
38
  - [Redux](https://redux.js.org/) - Application state container
18
39
  - [Redux-Saga](https://redux-saga.js.org/) - Redux middleware for action/side-effect interaction
19
- - [Jest](https://jestjs.io/) - Unit, integration, and snapshot testing
20
- - [RTL](https://testing-library.com/docs/react-testing-library/intro/) - Opinionated unit testing for React
40
+
41
+ ## Installation
42
+
43
+ > npm install -g generator-reackt
44
+
45
+ OR
46
+
47
+ > yarn add -g generator-reackt
48
+
49
+ ## Usage
50
+
51
+ To create an application, run `yo reackt`.
52
+
53
+ To create a new React component, run `yo reackt:component`.
54
+
55
+ If you are using Redux, you can create a new module (slice of reducer and sagas) with `yo reackt:module`.
@@ -1 +1 @@
1
- import{join as e}from"path";import t from"gulp-if";import{format as s}from"date-fns";import a from"gulp-prettier";import{readFileSync as i}from"jsonfile";import o from"yeoman-stylelint";import n from"spdx-license-ids"with{type:"json"};import r from"yeoman-generator";import l,{src as c}from"../util/fs.js";n.push("SEE LICENSE IN LICENSE"),n.sort();const d=e=>Object.fromEntries(Object.entries(e).map(([e,t])=>[e,/^\d/.test(t)?`^${t}`:t])),p=[{value:null,name:"None"},{value:"bootstrap",name:"Bootstrap"},{value:"bulma",name:"Bulma"},{value:"foundation",name:"Foundation"},{value:"materialize",name:"Materialize"},{value:"uikit",name:"UIKit"},{value:"materialUi",name:"Material-UI"}],m={fontAwesome:["@fortawesome/fontawesome-svg-core","@fortawesome/free-solid-svg-icons","@fortawesome/react-fontawesome"],bootstrap:["bootstrap","react-bootstrap","@popperjs/core"],uikit:["uikit","uikit-react"],foundation:["foundation-sites","react-foundation"],materialize:["materialize-css","react-materialize"],bulma:["bulma"],materialUi:["@mui/material","@emotion/react","@emotion/styled"],lintStaged:["husky","lint-staged"],redux:["redux","react-redux","redux-saga"],reduxJest:["redux-mock-store"],rtl:["@testing-library/react","@testing-library/dom"],core:["normalize-scss","react","react-dom","react-router-dom@6","reselect","classnames"],storybook:["storybook@10","@storybook/react-vite@10","@storybook/addon-docs@10","@storybook/addon-a11y@10","@storybook/addon-links@10","@storybook/addon-themes@10","@vitejs/plugin-react","vite"],esdoc:["esdoc","esdoc-ecmascript-proposal-plugin","esdoc-jsx-plugin","esdoc-standard-plugin","opener"],jest:["@types/jest","eslint-plugin-jest","jest","react-test-renderer","opener"],dev:["@eslint/js@9","@types/react","@types/react-dom","autoprefixer","clean-webpack-plugin","cross-env","css-loader","css-minimizer-webpack-plugin","eslint-config-prettier","eslint-import-resolver-typescript","eslint-plugin-import-x","eslint-plugin-prettier","eslint-plugin-react-hooks","eslint-plugin-react","eslint-webpack-plugin","eslint@9","globals","html-loader","html-webpack-plugin","mini-css-extract-plugin","postcss","postcss-flexbugs-fixes","postcss-loader","postcss-scss","prettier","sass-loader","sass","style-loader","stylelint-config-recommended","stylelint-webpack-plugin","stylelint","terser-webpack-plugin","ts-loader","typescript@6","typescript-eslint","webpack-cli","webpack","webpack-dev-server"]},g={foundation:["@types/react-foundation"]},u={core:[".yarnrc.yml",".prettierrc",".stylelintrc",".editorconfig",".browserslistrc"],templated:["tsconfig.json",c("index.tsx"),c("index.html"),c("utils","index.ts")],esdoc:[".esdoc.json"],jest:["jest.config.js"],storybook:[e(".storybook","main.ts"),e(".storybook","preview.ts")],lintStaged:[".lintstagedrc"]},y={redux:[c("reducers"),c("sagas"),c("selectors")],templated:[c("components"),c("pages"),c("types")]},f={"build:documentation":"esdoc","view:documentation":"opener ./docs/index.html"},h={test:"jest","view:coverage":"opener ./coverage/index.html"},k={storybook:"storybook dev -p 6006","build:storybook":"storybook build"};class b extends r{answers;fileSystem;constructor(e,t,s){super(e,t,s),this.answers={component:{},deploy:{},module:{},package:{},author:{},flags:{}},this.fileSystem=l(this),this.sourceRoot(this.fileSystem.resolve("templates","app"))}initializing(){this.env.options.nodePackageManager="yarn",process.env.YARN_NPM_MINIMAL_AGE_GATE="0",this.queueTransformStream({},t(/\.(json|tsx?)$/,a(i(this.fileSystem.resolve(".prettierrc")))),t(/\.scss$/,o({configFile:this.fileSystem.resolve(".stylelintrc")})))}async prompting(){const[e,t]=await Promise.all([this.git.name(),this.git.email()]);this.answers=await this.prompt([{type:"input",name:"package.name",message:"Package name",default:this.appname.replace(/\s+/g,"-")},{type:"input",name:"package.version",message:"Package version",default:"0.1.0"},{type:"search",name:"package.license",message:"Package license",source:e=>{const t=new RegExp(`.*${e??""}.*`,"i");return n.filter(e=>t.test(e))}},{type:"input",name:"package.description",message:"Package description"},{type:"input",name:"author.name",message:"Author name",default:e},{type:"input",name:"author.email",message:"Author email address",default:t},{type:"select",name:"styleFramework",message:"What CSS framework would you like to use?",choices:p},{type:"confirm",name:"flags.addCnamePlugin",message:"Deploying to GitHub Pages with CNAME?",default:!1},{type:"input",name:"deploy.domain",message:"Hostname to use",when:e=>e.flags.addCnamePlugin},{type:"confirm",name:"flags.addLintStaged",message:"Force linting before commits?",default:!0},{type:"confirm",name:"flags.addFontAwesome",message:"Add Font Awesome?",default:!0},{type:"confirm",name:"flags.addRedux",message:"Add Redux?",default:!1},{type:"confirm",name:"flags.addJest",message:"Add Jest?",default:!1},{type:"confirm",name:"flags.addStorybook",message:"Add Storybook?",default:!1},{type:"confirm",name:"flags.addEsDoc",message:"Add ESDoc?",default:!1}])}async writing(){const{flags:t,styleFramework:a,package:{license:i},author:{name:o,email:n}}=this.answers;let r="Place your license here.\n";if("SEE LICENSE IN LICENSE"!==i){this.log(`Downloading ${i} license from spdx/license-list-data...`);const e=await fetch(`https://raw.githubusercontent.com/spdx/license-list-data/master/text/${i}.txt`);if(200!==e.status)this.log(`License download failed with HTTP ${e.status} - ${e.statusText}`);else{r=(await e.text()).replace("<year>",s(new Date,"yyyy")).replace("<copyright holders>",`${o} <${n}>`)}}this.fileSystem.createFile("LICENSE",r),u.core.forEach(this.fileSystem.copy),u.templated.forEach(this.fileSystem.copyTemplateInPlace),y.templated.forEach(this.fileSystem.copyDirectory),this.fileSystem.copyTemplate("_package.json","package.json"),this.fileSystem.copyTemplate("_eslint.config.mjs","eslint.config.mjs"),this.fileSystem.copyTemplate("webpack.config.nts","webpack.config.ts"),"materialUi"!==this.answers.styleFramework&&this.fileSystem.copyTemplate(c("index.nscss"),c("index.scss")),this.fileSystem.copyTo("gitignore",".gitignore"),t.addRedux&&y.redux.forEach(this.fileSystem.copyDirectory),t.addJest&&(u.jest.forEach(this.fileSystem.copy),this.fs.append(this.destinationPath(".gitignore"),"coverage/"),this.fs.extendJSON(this.destinationPath("package.json"),{scripts:{...h}})),t.addStorybook&&(u.storybook.forEach(this.fileSystem.copyTemplateInPlace),this.fileSystem.copyTemplate(e("_stories","SuspenseFallback.stories.tsx"),c("components","SuspenseFallback.stories.tsx")),this.fs.append(this.destinationPath(".gitignore"),"storybook-static/"),this.fs.extendJSON(this.destinationPath("package.json"),{scripts:{...k}})),t.addEsDoc&&(u.esdoc.forEach(this.fileSystem.copy),this.fs.append(this.destinationPath(".gitignore"),"docs/"),this.fs.extendJSON(this.destinationPath("package.json"),{scripts:{...f}})),t.addLintStaged&&(u.lintStaged.forEach(this.fileSystem.copy),this.fileSystem.createFile(".husky/pre-commit","npx lint-staged\n"));const l=[...m.core],p=[...m.dev];a&&(l.push(...m[a]),g[a]&&p.push(...g[a])),t.addLintStaged&&p.push(...m.lintStaged),t.addFontAwesome&&l.push(...m.fontAwesome),t.addRedux&&l.push(...m.redux),t.addJest&&p.push(...m.jest),t.addRedux&&t.addJest&&p.push(...m.reduxJest),t.addStorybook&&p.push(...m.storybook),t.addEsDoc&&l.push(...m.esdoc),this.log(`Resolving ${l.length} dependencies and ${p.length} dev dependencies.`);const[b,w]=await Promise.all([this.addDependencies(l),this.addDevDependencies(p)]);this.packageJson.merge({dependencies:d(b),devDependencies:d(w)})}install(){this.spawnSync("yarn",["set","version","stable"],{stdio:"inherit"})}async end(){const{flags:e}=this.answers;this.spawnSync("git",["init"],{stdio:"inherit"}),e.addLintStaged&&this.spawnSync("npx",["husky"],{stdio:"inherit"})}}export{b as default};
1
+ import{join as e}from"path";import t from"gulp-if";import{format as s}from"date-fns";import a from"gulp-prettier";import{readFileSync as i}from"jsonfile";import o from"yeoman-stylelint";import n from"spdx-license-ids"with{type:"json"};import{src as r}from"../util/fs.js";import l from"../util/generator.js";import{StyleFrameworks as c,TestFrameworks as d}from"../types/index.js";n.push("SEE LICENSE IN LICENSE"),n.sort();const p=e=>Object.fromEntries(Object.entries(e).map(([e,t])=>[e,/^\d/.test(t)?`^${t}`:t])),m=[{value:c.None,name:"None"},{value:c.Bootstrap,name:"Bootstrap"},{value:c.Bulma,name:"Bulma"},{value:c.Foundation,name:"Foundation"},{value:c.Materialize,name:"Materialize"},{value:c.UIkit,name:"UIkit"},{value:c.MaterialUI,name:"Material-UI"}],g=[{value:d.None,name:"None"},{value:d.Jest,name:"Jest"},{value:d.ReactTestingLibrary,name:"React Testing Library"}],y={fontAwesome:["@fortawesome/fontawesome-svg-core","@fortawesome/free-solid-svg-icons","@fortawesome/react-fontawesome"],[c.Bootstrap]:["bootstrap","react-bootstrap","@popperjs/core"],[c.Bulma]:["bulma"],[c.UIkit]:["uikit","uikit-react"],[c.Foundation]:["foundation-sites","react-foundation"],[c.Materialize]:["materialize-css","react-materialize"],[c.MaterialUI]:["@mui/material","@emotion/react","@emotion/styled"],lintStaged:["husky","lint-staged"],redux:["redux","react-redux","redux-saga"],reduxJest:["redux-mock-store"],[d.Jest]:["@types/jest","@types/react-test-renderer","eslint-plugin-jest","identity-obj-proxy","jest","jest-environment-jsdom","react-test-renderer","ts-jest","opener"],[d.ReactTestingLibrary]:["@testing-library/react","@testing-library/dom","@testing-library/jest-dom","@testing-library/user-event"],core:["normalize-scss","react","react-dom","react-router-dom@6","reselect","classnames"],storybook:["storybook@10","@storybook/react-vite","@storybook/addon-docs","@storybook/addon-a11y","@storybook/addon-links","@storybook/addon-themes","@vitejs/plugin-react","vite"],esdoc:["esdoc","esdoc-ecmascript-proposal-plugin","esdoc-jsx-plugin","esdoc-standard-plugin","opener"],dev:["@eslint/js@9","@types/react","@types/react-dom","autoprefixer","clean-webpack-plugin","cross-env","css-loader","css-minimizer-webpack-plugin","eslint-config-prettier","eslint-import-resolver-typescript","eslint-plugin-import-x","eslint-plugin-prettier","eslint-plugin-react-hooks","eslint-plugin-react","eslint-webpack-plugin","eslint@9","globals","html-loader","html-webpack-plugin","mini-css-extract-plugin","postcss","postcss-flexbugs-fixes","postcss-loader","postcss-scss","prettier","sass-loader","sass","style-loader","stylelint-config-recommended","stylelint-webpack-plugin","stylelint","terser-webpack-plugin","ts-loader","typescript@6","typescript-eslint","webpack-cli","webpack","webpack-dev-server"]},u={foundation:["@types/react-foundation"]},h={core:[".yarnrc.yml",".prettierrc",".stylelintrc",".editorconfig",".browserslistrc"],templated:["tsconfig.json",r("index.tsx"),r("index.html"),r("utils","index.ts")],esdoc:[".esdoc.json"],jest:["jest.config.mjs"],rtl:["jest.setup.ts"],storybook:[e(".storybook","main.ts"),e(".storybook","preview.ts")],lintStaged:[".lintstagedrc"]},f={redux:[r("reducers"),r("sagas"),r("selectors")],templated:[r("components"),r("pages"),r("types")]},k={esdoc:{"build:documentation":"esdoc","view:documentation":"opener ./docs/index.html"},jest:{test:"jest","view:coverage":"opener ./coverage/index.html"},storybook:{storybook:"storybook dev -p 6006","build:storybook":"storybook build"}};class b extends l{templateDirectory(){return"app"}initializing(){this.env.options.nodePackageManager="yarn",process.env.YARN_NPM_MINIMAL_AGE_GATE="0",this.queueTransformStream({},t(/\.(json|tsx?)$/,a(i(this.fileSystem.resolve(".prettierrc")))),t(/\.scss$/,o({configFile:this.fileSystem.resolve(".stylelintrc")})))}async prompting(){const[e,t]=await Promise.all([this.git.name(),this.git.email()]);this.answers=await this.prompt([{type:"input",name:"package.name",message:"Package name",default:this.appname.replace(/\s+/g,"-")},{type:"input",name:"package.version",message:"Package version",default:"0.1.0"},{type:"search",name:"package.license",message:"Package license",source:e=>{const t=new RegExp(`.*${e??""}.*`,"i");return n.filter(e=>t.test(e))}},{type:"input",name:"package.description",message:"Package description"},{type:"input",name:"author.name",message:"Author name",default:e},{type:"input",name:"author.email",message:"Author email address",default:t},{type:"select",name:"styleFramework",message:"What CSS framework would you like to use?",choices:m},{type:"select",name:"testFramework",message:"What testing framework would you like to use?",choices:g},{type:"confirm",name:"flags.addCnamePlugin",message:"Deploying to GitHub Pages with CNAME?",default:!1},{type:"input",name:"deploy.domain",message:"Hostname to use",when:e=>e.flags.addCnamePlugin},{type:"confirm",name:"flags.addLintStaged",message:"Force linting before commits?",default:!0},{type:"confirm",name:"flags.addFontAwesome",message:"Add Font Awesome?",default:!0},{type:"confirm",name:"flags.addRedux",message:"Add Redux?",default:!1},{type:"confirm",name:"flags.addStorybook",message:"Add Storybook?",default:!1},{type:"confirm",name:"flags.addEsDoc",message:"Add ESDoc?",default:!1}])}async writing(){const{flags:t,styleFramework:a,testFramework:i,package:{license:o},author:{name:n,email:l}}=this.answers;this.config.set("testFramework",i);let c="Place your license here.\n";if("SEE LICENSE IN LICENSE"!==o){this.log(`Downloading ${o} license from spdx/license-list-data...`);const e=await fetch(`https://raw.githubusercontent.com/spdx/license-list-data/master/text/${o}.txt`);if(200!==e.status)this.log(`License download failed with HTTP ${e.status} - ${e.statusText}`);else{c=(await e.text()).replace("<year>",s(new Date,"yyyy")).replace("<copyright holders>",`${n} <${l}>`)}}this.fileSystem.createFile("LICENSE",c),h.core.forEach(this.fileSystem.copy),h.templated.forEach(this.fileSystem.copyTemplateInPlace),f.templated.forEach(this.fileSystem.copyDirectory),this.fileSystem.copyTemplate("_package.json","package.json"),this.fileSystem.copyTemplate("_eslint.config.mjs","eslint.config.mjs"),this.fileSystem.copyTemplate("webpack.config.nts","webpack.config.ts"),"materialUi"!==this.answers.styleFramework&&this.fileSystem.copyTemplate(r("index.nscss"),r("index.scss")),this.fileSystem.copyTo("gitignore",".gitignore"),t.addRedux&&(f.redux.forEach(this.fileSystem.copyDirectory),i||this.fs.delete(this.destinationPath(r("selectors","application.test.ts")))),i&&(h[d.Jest].forEach(this.fileSystem.copyTemplateInPlace),this.fs.append(this.destinationPath(".gitignore"),"coverage/"),this.fs.extendJSON(this.destinationPath("package.json"),{scripts:{...k[d.Jest]}}),i===d.ReactTestingLibrary&&h[d.ReactTestingLibrary].forEach(this.fileSystem.copyTemplateInPlace)),t.addStorybook&&(h.storybook.forEach(this.fileSystem.copyTemplateInPlace),this.fileSystem.copyTemplate(e("_stories","SuspenseFallback.stories.tsx"),r("components","SuspenseFallback.stories.tsx")),this.fs.append(this.destinationPath(".gitignore"),"storybook-static/"),this.fs.extendJSON(this.destinationPath("package.json"),{scripts:{...k.storybook}})),t.addEsDoc&&(h.esdoc.forEach(this.fileSystem.copy),this.fs.append(this.destinationPath(".gitignore"),"docs/"),this.fs.extendJSON(this.destinationPath("package.json"),{scripts:{...k.esdoc}})),t.addLintStaged&&(h.lintStaged.forEach(this.fileSystem.copy),this.fileSystem.createFile(".husky/pre-commit","npx lint-staged\n"));const m=[...y.core],g=[...y.dev];a&&(m.push(...y[a]),u[a]&&g.push(...u[a])),i&&(g.push(...y[d.Jest]),i===d.ReactTestingLibrary&&g.push(...y[d.ReactTestingLibrary])),t.addLintStaged&&g.push(...y.lintStaged),t.addFontAwesome&&m.push(...y.fontAwesome),t.addRedux&&m.push(...y.redux),t.addRedux&&i&&g.push(...y.reduxJest),t.addStorybook&&g.push(...y.storybook),t.addEsDoc&&m.push(...y.esdoc),this.log(`Resolving ${m.length} dependencies and ${g.length} dev dependencies.`);const[b,w]=await Promise.all([this.addDependencies(m),this.addDevDependencies(g)]);this.packageJson.merge({dependencies:p(b),devDependencies:p(w)})}install(){this.spawnSync("yarn",["set","version","stable"],{stdio:"inherit"})}async end(){const{flags:e}=this.answers;this.spawnSync("git",["init"],{stdio:"inherit"}),e.addLintStaged&&this.spawnSync("npx",["husky"],{stdio:"inherit"})}}export{b as default};
@@ -1 +1 @@
1
- import e from"yeoman-generator";import t,{src as s}from"../util/fs.js";class o extends e{answers;fileSystem;constructor(e,s,o){super(e,s,o),this.answers={component:{},deploy:{},module:{},package:{},author:{},flags:{}},this.fileSystem=t(this),this.sourceRoot(this.fileSystem.resolve("templates","component"))}async prompting(){this.answers=await this.prompt([{type:"input",name:"component.name",message:"Component name"},{type:"confirm",name:"flags.addRedux",message:"Connect this component to the Redux store?",default:!1},{type:"confirm",name:"flags.addStorybook",message:"Add a Storybook story?",default:!1}])}async writing(){const{flags:e,component:{name:t}}=this.answers,o=s("components",`${t}.tsx`),n=s("components",`${t}.test.tsx`);this.log(`Creating ${t} component`),this.fileSystem.copyTemplate("index.tsx",o),this.fileSystem.copyTemplate("index.test.tsx",n),e.addStorybook&&this.fileSystem.copyTemplate("index.stories.tsx",s("components",`${t}.stories.tsx`))}}export{o as default};
1
+ import{src as t}from"../util/fs.js";import e from"../util/generator.js";import{TestFrameworks as s}from"../types/index.js";class o extends e{templateDirectory(){return"component"}#t(){return this.config.get("testFramework")}async prompting(){const t=this.#t();this.answers=await this.prompt([{type:"input",name:"component.name",message:"Component name"},{type:"confirm",name:"flags.addRedux",message:"Connect this component to the Redux store?",default:!1},{type:"confirm",name:"flags.addStorybook",message:"Add a Storybook story?",default:!1},{type:"confirm",name:"flags.addTest",message:"Add a test case?",default:!0,when:()=>t!==s.None}])}async writing(){const{flags:e,component:{name:o}}=this.answers,n=t("components",`${o}.tsx`),a=t("components",`${o}.test.tsx`);this.log(`Creating ${o} component`),this.fileSystem.copyTemplate("index.tsx",n),e.addTest&&this.fileSystem.copyTemplate(this.#t()===s.ReactTestingLibrary?"index.rtl.test.tsx":"index.jest.test.tsx",a),e.addStorybook&&this.fileSystem.copyTemplate("index.stories.tsx",t("components",`${o}.stories.tsx`))}}export{o as default};
@@ -1 +1 @@
1
- import{join as e}from"path";import t from"yeoman-generator";import a,{src as s}from"../util/fs.js";class r extends t{answers;fileSystem;constructor(e,t,s){super(e,t,s),this.answers={component:{},deploy:{},module:{},package:{},author:{},flags:{}},this.fileSystem=a(this),this.sourceRoot(this.fileSystem.resolve("templates","module"))}async prompting(){this.answers=await this.prompt([{type:"input",name:"module.name",message:"Module name"},{type:"select",name:"directoryMode",message:"Choose a way of structuring the module.",choices:[{name:"Create a directory with the module name and an index.js file inside that.",value:"dir",short:"Directory"},{name:"Create a file with the module name.",value:"file",short:"File"}]},{type:"confirm",name:"flags.createReducer",message:"Generate a reducer?",default:!0},{type:"confirm",name:"flags.createSaga",message:"Generate a saga?",default:!0},{type:"confirm",name:"flags.createSelector",message:"Generate a selector?",default:!0},{type:"confirm",name:"flags.addJest",message:"Generate test files?",default:!0}])}async writing(){const{directoryMode:t,module:{name:a},flags:r}=this.answers;if(!a)return void this.log("ERROR: No module name provided!");this.log(`Creating module ${a}`);const o=o=>{const i=`${a}${o}`,m=`index${o}`,l=[];switch(t){default:case"file":l.push(i);break;case"dir":l.push(a,m)}r.createReducer&&this.fileSystem.copyTemplate(e("reducer",m),s("reducers",...l)),r.createSaga&&this.fileSystem.copyTemplate(e("saga",m),s("sagas",...l)),r.createSelector&&this.fileSystem.copyTemplate(e("selector",m),s("selectors",...l))};o(".ts"),r.addJest&&o(".test.ts")}}export{r as default};
1
+ import{join as e}from"path";import{src as t}from"../util/fs.js";import s from"../util/generator.js";import{TestFrameworks as a}from"../types/index.js";class r extends s{templateDirectory(){return"module"}#e(e){const s=this.destinationPath(t("types","index.ts"));if(!this.fs.exists(s))return void this.log("WARNING: src/types/index.ts is missing, skipping AppState");const a=`${e.charAt(0).toUpperCase()}${e.slice(1)}State`,r=this.fs.read(s);if(r.includes(`export type ${a}`))return;const i="export type AppState = {";r.includes(i)?this.fs.write(s,r.replace(i,[`export type ${a} = Record<string, never>;`,"",i,` ${e}?: ${a};`].join("\n"))):this.log("WARNING: could not find AppState, skipping AppState update")}async prompting(){const e=this.config.get("testFramework");this.answers=await this.prompt([{type:"input",name:"module.name",message:"Module name"},{type:"select",name:"directoryMode",message:"Choose a way of structuring the module.",choices:[{name:"Create a directory with the module name and an index.js file inside that.",value:"dir",short:"Directory"},{name:"Create a file with the module name.",value:"file",short:"File"}]},{type:"confirm",name:"flags.createReducer",message:"Generate a reducer?",default:!0},{type:"confirm",name:"flags.createSaga",message:"Generate a saga?",default:!0},{type:"confirm",name:"flags.createSelector",message:"Generate a selector?",default:!0},{type:"confirm",name:"flags.addTest",message:"Add a test case?",default:!0,when:()=>e!==a.None}])}async writing(){const{directoryMode:s,module:{name:a},flags:r}=this.answers;if(!a)return void this.log("ERROR: No module name provided!");this.log(`Creating module ${a}`);const i=i=>{const o=`${a}${i}`,n=`index${i}`,p=[];switch(s){default:case"file":p.push(o);break;case"dir":p.push(a,n)}r.createReducer&&this.fileSystem.copyTemplate(e("reducer",n),t("reducers",...p)),r.createSaga&&this.fileSystem.copyTemplate(e("saga",n),t("sagas",...p)),r.createSelector&&this.fileSystem.copyTemplate(e("selector",n),t("selectors",...p))};this.#e(a),i(".ts"),r.addTest&&i(".test.ts")}}export{r as default};
@@ -0,0 +1 @@
1
+ var t,a;!function(t){t.None="",t.Bootstrap="bootstrap",t.Bulma="bulma",t.Foundation="foundation",t.Materialize="materialize",t.UIkit="uikit",t.MaterialUI="materialUi"}(t||(t={})),function(t){t.None="",t.Jest="jest",t.ReactTestingLibrary="rtl"}(a||(a={}));export{t as StyleFrameworks,a as TestFrameworks};
@@ -0,0 +1 @@
1
+ import e from"yeoman-generator";import s from"./fs.js";class t extends e{answers;fileSystem;constructor(e,t,o){super(e,t,o),this.answers={component:{},deploy:{},module:{},package:{},author:{},flags:{}},this.fileSystem=s(this),this.sourceRoot(this.fileSystem.resolve("templates",this.templateDirectory()))}}export{t as default};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "generator-reackt",
3
- "version": "1.0.1",
3
+ "version": "1.1.1",
4
4
  "description": "Create an opinionated React-based web applcication",
5
5
  "type": "module",
6
6
  "files": [
@@ -36,9 +36,9 @@ export default defineConfig(
36
36
  globals: globals.node
37
37
  }
38
38
  },
39
- <% if (flags.addJest) { %>
39
+ <% if (testFramework) { %>
40
40
  {
41
- files: ['./src/**/*.test.js'],
41
+ files: ['./src/**/*.test.{ts,tsx}'],
42
42
  languageOptions: {
43
43
  globals: {
44
44
  ...globals.node,
@@ -46,6 +46,6 @@ export default defineConfig(
46
46
  }
47
47
  }
48
48
  },
49
- <% } %>
49
+ <% } %>
50
50
  prettierPlugin
51
51
  );
@@ -5,12 +5,12 @@
5
5
  "author": "<%= author.name %> <<%= author.email %>>",
6
6
  "license": "<%= package.license %>",
7
7
  "scripts": {
8
- <% if (flags.addLintStaged) { %>
9
- "prepare": "husky",
10
- <% } %>
11
- <% if (flags.addJest) { %>
8
+ <% if (flags.addLintStaged) { %>
9
+ "prepare": "husky",
10
+ <% } %>
11
+ <% if (testFramework) { %>
12
12
  "test": "jest",
13
- <% } %>
13
+ <% } %>
14
14
  "build": "cross-env NODE_ENV=production webpack",
15
15
  "start": "cross-env NODE_ENV=development webpack serve"
16
16
  },
@@ -0,0 +1,26 @@
1
+ export default {
2
+ collectCoverage: true,
3
+ collectCoverageFrom: ['src/**/*.{ts,tsx}', '!src/*.tsx', '!src/types/**'],
4
+ coverageDirectory: './coverage',
5
+ coverageReporters: ['html', 'text-summary'],
6
+ modulePaths: ['<rootDir>/src/'],
7
+ moduleNameMapper: {
8
+ '\\.s?css$': 'identity-obj-proxy'
9
+ },
10
+ // an explicit transform keeps the suite on CommonJS, which avoids having to
11
+ // run jest under --experimental-vm-modules
12
+ transform: {
13
+ '^.+\\.tsx?$': [
14
+ 'ts-jest',
15
+ {
16
+ tsconfig: {
17
+ module: 'commonjs',
18
+ jsx: 'react-jsx',
19
+ esModuleInterop: true
20
+ }
21
+ }
22
+ ]
23
+ }<% if (testFramework === 'rtl') { %>,
24
+ testEnvironment: 'jsdom',
25
+ setupFilesAfterEnv: ['<rootDir>/jest.setup.ts']<% } %>
26
+ };
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom';
@@ -13,6 +13,6 @@ describe('application selectors', () => {
13
13
  });
14
14
 
15
15
  it('can getApplicationInited', () => {
16
- expect(getApplicationInited(state).toBe(state.application.inited));
16
+ expect(getApplicationInited(state)).toBe(state.application.inited);
17
17
  });
18
18
  });
@@ -1 +1,7 @@
1
1
  declare module '*.scss';
2
+ <% if (flags.addRedux) { %>
3
+ // installed by the Redux DevTools browser extension, so it may not be there
4
+ interface Window {
5
+ __REDUX_DEVTOOLS_EXTENSION_COMPOSE__?: typeof import('redux').compose;
6
+ }
7
+ <% } %>
@@ -1,5 +1,17 @@
1
- <% if (flags.addJest) { %>
2
- import { createElement, ComponentProps } from 'react';
1
+ <% if (testFramework === 'jest') { %>
2
+ import { createElement, type ReactNode } from 'react';
3
+
4
+ /**
5
+ * Creates a mock component which will expose its props for snapshot testing purposes.
6
+ *
7
+ * @param {string} name The component/element name (e.g. "MyComponent")
8
+ * @param {object} props An object containing props to set
9
+ * @return {object} Mock component with specified name and props
10
+ */
11
+ export const mockComponent =
12
+ (name: string, props: Record<string, unknown> = {}) =>
13
+ () =>
14
+ createElement(name, props, props.children as ReactNode);
3
15
  <% } %>
4
16
 
5
17
  <% if (flags.addRedux) { %>
@@ -12,8 +24,11 @@ import { createElement, ComponentProps } from 'react';
12
24
  * @param {string[]} actions A list of action names
13
25
  * @return {object} Object with action name keys and full action string values
14
26
  */
15
- export function buildActions(reducer, actions) {
16
- const result = {};
27
+ export function buildActions<T extends string>(
28
+ reducer: string,
29
+ actions: readonly T[]
30
+ ) {
31
+ const result = {} as Record<T, string>;
17
32
 
18
33
  for (const action of actions) {
19
34
  result[action] = `${reducer}/${action}`;
@@ -22,15 +37,3 @@ export function buildActions(reducer, actions) {
22
37
  return result;
23
38
  }
24
39
  <% } %>
25
-
26
- <% if (flags.addJest) { %>
27
- /**
28
- * Creates a mock component which will expose its props for snapshot testing purposes.
29
- *
30
- * @param {string} name The component/element name (e.g. "MyComponent")
31
- * @param {ComponentProps} props An object containing props to set
32
- * @return {object} Mock component with specified name and props
33
- */
34
- export const mockComponent = (name: string, props: ComponentProps = {}) => () =>
35
- createElement(name, props, props.children);
36
- <% } %>
@@ -5,7 +5,9 @@
5
5
  "module": "esnext",
6
6
  "moduleResolution": "bundler",
7
7
  "rootDir": ".",
8
- <% if (flags.addJest) { %>
8
+ <% if (testFramework === 'rtl') { %>
9
+ "types": ["jest", "@testing-library/jest-dom"],
10
+ <% } else if (testFramework) { %>
9
11
  "types": ["jest"],
10
12
  <% } %>
11
13
  <% if (styleFramework === 'uikit') { %>
@@ -14,5 +16,9 @@
14
16
  <% } %>
15
17
  "jsx": "react-jsx"
16
18
  },
19
+ <% if (testFramework === 'rtl') { %>
20
+ "include": ["src/", ".storybook/", "jest.setup.ts"]
21
+ <% } else { %>
17
22
  "include": ["src/", ".storybook/"]
23
+ <% } %>
18
24
  }
@@ -0,0 +1,11 @@
1
+ import { render, screen } from '@testing-library/react';
2
+
3
+ import <%= component.name %> from './<%= component.name %>';
4
+
5
+ describe('<<%= component.name %> />', () => {
6
+ it('renders correctly', () => {
7
+ render(<<%= component.name %> />);
8
+
9
+ expect(screen.getByRole('heading')).toBeInTheDocument();
10
+ });
11
+ });
@@ -2,15 +2,12 @@
2
2
  import { connect } from 'react-redux';
3
3
  <% } %>
4
4
 
5
- export default function <%= component.name %>() {
6
- return <h1>Hello World</h1>;
5
+ function <%= component.name %>() {
6
+ return <h1><%= component.name %></h1>;
7
7
  }
8
8
 
9
9
  <% if (flags.addRedux) { %>
10
- export default connect(
11
- null,
12
- null
13
- )(<%= component.name %>);
10
+ export default connect(null, null)(<%= component.name %>);
14
11
  <% } else { %>
15
12
  export default <%= component.name %>;
16
13
  <% } %>
@@ -0,0 +1,13 @@
1
+ <% const modulePath = directoryMode === 'dir' ? './index' : './' + module.name; -%>
2
+ import { actions, initialState, reducer, types } from '<%= modulePath %>';
3
+
4
+ describe('<%= module.name %> reducer', () => {
5
+ it('has a namespaced INIT_APP action', () => {
6
+ expect(types.INIT_APP).toBe('<%= module.name %>/INIT_APP');
7
+ expect(actions.initApp()).toEqual({ type: types.INIT_APP });
8
+ });
9
+
10
+ it('returns the initial state for an unknown action', () => {
11
+ expect(reducer(undefined, { type: 'UNKNOWN' })).toBe(initialState);
12
+ });
13
+ });
@@ -1,4 +1,7 @@
1
- import { buildActions } from 'utils';
1
+ <% const depth = directoryMode === 'dir' ? '../../' : '../'; -%>
2
+ <% const name = module.name.charAt(0).toUpperCase() + module.name.slice(1); -%>
3
+ import { ActionObject, <%= name %>State } from '<%= depth %>types';
4
+ import { buildActions } from '<%= depth %>utils';
2
5
 
3
6
  export const types = buildActions('<%= module.name %>', ['INIT_APP']);
4
7
 
@@ -10,9 +13,12 @@ export const actions = {
10
13
  initApp
11
14
  };
12
15
 
13
- export const initialState = {};
16
+ export const initialState: <%= name %>State = {};
14
17
 
15
- export const reducer = (state = initialState, action = {}) => {
18
+ export const reducer = (
19
+ state: <%= name %>State = initialState,
20
+ action: ActionObject = {}
21
+ ) => {
16
22
  switch (action.type) {
17
23
  default:
18
24
  return state;
@@ -0,0 +1,13 @@
1
+ <% const modulePath = directoryMode === 'dir' ? './index' : './' + module.name; -%>
2
+ import { all } from 'redux-saga/effects';
3
+
4
+ import saga, { watchers } from '<%= modulePath %>';
5
+
6
+ describe('<%= module.name %> saga', () => {
7
+ it('yields all of its watchers', () => {
8
+ const iterator = saga();
9
+
10
+ expect(iterator.next().value).toEqual(all(Object.values(watchers)));
11
+ expect(iterator.next().done).toBe(true);
12
+ });
13
+ });
@@ -0,0 +1,13 @@
1
+ <% const depth = directoryMode === 'dir' ? '../../' : '../'; -%>
2
+ <% const modulePath = directoryMode === 'dir' ? './index' : './' + module.name; -%>
3
+ <% const name = module.name.charAt(0).toUpperCase() + module.name.slice(1); -%>
4
+ import { AppState } from '<%= depth %>types';
5
+ import { get<%= name %> } from '<%= modulePath %>';
6
+
7
+ describe('<%= module.name %> selectors', () => {
8
+ const state = { <%= module.name %>: {} } as unknown as AppState;
9
+
10
+ it('can get<%= name %>', () => {
11
+ expect(get<%= name %>(state)).toBe(state.<%= module.name %>);
12
+ });
13
+ });
@@ -0,0 +1,5 @@
1
+ <% const depth = directoryMode === 'dir' ? '../../' : '../'; -%>
2
+ <% const name = module.name.charAt(0).toUpperCase() + module.name.slice(1); -%>
3
+ import { AppState } from '<%= depth %>types';
4
+
5
+ export const get<%= name %> = (state: AppState) => state.<%= module.name %>;
@@ -1,15 +0,0 @@
1
- export default {
2
- collectCoverage: true,
3
- collectCoverageFrom: ['src/**/*.js', '!src/*.js'],
4
- coverageDirectory: './coverage',
5
- coverageReporters: ['html', 'text-summary'],
6
- coverageThreshold: {
7
- global: {
8
- branches: 80,
9
- functions: 80,
10
- lines: 80,
11
- statements: 80
12
- }
13
- },
14
- modulePaths: ['<rootDir>/src/']
15
- };