generator-reackt 1.1.1 → 1.2.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 +2 -0
- package/generators/app/index.js +1 -1
- package/generators/component/index.js +1 -1
- package/generators/module/index.js +1 -1
- package/generators/page/index.js +1 -0
- package/generators/util/generator.js +1 -1
- package/package.json +8 -5
- package/templates/app/src/components/Layout.tsx +75 -4
- package/templates/app/src/index.tsx +3 -7
- package/templates/app/src/routes.tsx +14 -0
- package/templates/page/index.jest.test.tsx +17 -0
- package/templates/page/index.rtl.test.tsx +16 -0
- package/templates/page/index.stories.tsx +14 -0
- package/templates/page/index.tsx +17 -0
package/README.md
CHANGED
|
@@ -52,4 +52,6 @@ To create an application, run `yo reackt`.
|
|
|
52
52
|
|
|
53
53
|
To create a new React component, run `yo reackt:component`.
|
|
54
54
|
|
|
55
|
+
To create a new page, run `yo reackt:page`. You will be asked for a page name and a URL, and the page is registered in `src/routes.tsx` so that it is routable and shows up in the navigation bar.
|
|
56
|
+
|
|
55
57
|
If you are using Redux, you can create a new module (slice of reducer and sagas) with `yo reackt:module`.
|
package/generators/app/index.js
CHANGED
|
@@ -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{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()])
|
|
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("routes.tsx"),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()]),s=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}]);this.answers={...this.answers,...s}}async writing(){const{flags:t,styleFramework:a,testFramework:i,package:{license:o},author:{name:n,email:l}}=this.answers;this.config.set("testFramework",i),this.config.set("flags.addRedux",t.addRedux),this.config.set("flags.addStorybook",t.addStorybook);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{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()
|
|
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")}#e(){return this.config.get("flags.addRedux")}#s(){return this.config.get("flags.addStorybook")}async prompting(){const t=this.#t(),e=this.#e(),o=this.#s(),n=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:!0,when:()=>e},{type:"confirm",name:"flags.addStorybook",message:"Add a Storybook story?",default:!0,when:()=>o},{type:"confirm",name:"flags.addTest",message:"Add a test case?",default:!0,when:()=>t!==s.None}]);this.answers={...this.answers,...n}}async writing(){const{flags:e,component:{name:o}}=this.answers;if(!o)return void this.log("ERROR: No component name provided!");const 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{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")
|
|
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"),t=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}]);this.answers={...this.answers,...t}}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
|
+
import{src as e}from"../util/fs.js";import t from"../util/generator.js";import{TestFrameworks as s}from"../types/index.js";class i extends t{templateDirectory(){return"page"}#e(){return this.config.get("testFramework")}#t(){return this.config.get("flags.addRedux")}#s(){return this.config.get("flags.addStorybook")}#i(t,s){const i=this.destinationPath(e("routes.tsx"));if(!this.fs.exists(i))return void this.log("WARNING: src/routes.tsx is missing, skipping route");const a=this.fs.read(i);if(a.includes(`./pages/${t}`))return;const o="export const routes: AppRoute[] = [";a.includes(o)?this.fs.write(i,a.replace(o,[o," {",` path: '${s}',`,` handle: { title: '${t}' },`,` lazy: () => import(\`./pages/${t}\`)`," },"].join("\n"))):this.log("WARNING: could not find the route table, skipping route")}async prompting(){const e=this.#e(),t=this.#t(),i=this.#s(),a=await this.prompt([{type:"input",name:"page.name",message:"Page name"},{type:"input",name:"page.url",message:"Page URL"},{type:"confirm",name:"flags.addRedux",message:"Connect this page to the Redux store?",default:!0,when:()=>t},{type:"confirm",name:"flags.addStorybook",message:"Add a Storybook story?",default:!0,when:()=>i},{type:"confirm",name:"flags.addTest",message:"Add a test case?",default:!0,when:()=>e!==s.None}]);this.answers={...this.answers,...a}}async writing(){const{flags:t,page:{name:i,url:a}}=this.answers,o=(i??"").trim();if(!o)return void this.log("ERROR: No page name provided!");this.answers.page.name=o,this.log(`Creating ${o} page`),this.fileSystem.copyTemplate("index.tsx",e("pages",`${o}.tsx`)),t.addTest&&this.fileSystem.copyTemplate(this.#e()===s.ReactTestingLibrary?"index.rtl.test.tsx":"index.jest.test.tsx",e("pages",`${o}.test.tsx`)),t.addStorybook&&this.fileSystem.copyTemplate("index.stories.tsx",e("pages",`${o}.stories.tsx`));const r=(a??"").trim().replace(/^\/+|\/+$/g,"");r?this.#i(o,r):this.log("WARNING: No page URL provided, skipping route")}}export{i as default};
|
|
@@ -1 +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};
|
|
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:{},page:{},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.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Create an opinionated React-based web applcication",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"version": "auto-changelog -l false -p --hide-credit && git add CHANGELOG.md",
|
|
26
26
|
"prepare": "husky",
|
|
27
27
|
"prepack": "rollup -c",
|
|
28
|
-
"build": "rollup -c"
|
|
28
|
+
"build": "rollup -c",
|
|
29
|
+
"test": "rollup -c && node --experimental-test-coverage --test \"test/**/*.test.ts\""
|
|
29
30
|
},
|
|
30
31
|
"engines": {
|
|
31
32
|
"node": ">=22"
|
|
@@ -52,11 +53,11 @@
|
|
|
52
53
|
"@rollup/plugin-terser": "^1.0.0",
|
|
53
54
|
"@rollup/plugin-typescript": "^12.3.0",
|
|
54
55
|
"@types/gulp-if": "^3.0.5",
|
|
55
|
-
"@types/
|
|
56
|
-
"@types/jsonfile": "^6.1.4",
|
|
56
|
+
"@types/jsonfile": "^6",
|
|
57
57
|
"@types/node": "24.13.3",
|
|
58
58
|
"@types/rollup-plugin-auto-external": "^2",
|
|
59
59
|
"@types/spdx-license-ids": "^3.0.0",
|
|
60
|
+
"@yeoman/adapter": "^5.0.0",
|
|
60
61
|
"@yeoman/types": "^1.1.1",
|
|
61
62
|
"auto-changelog": "^2.6.0",
|
|
62
63
|
"eslint": "^9.39.5",
|
|
@@ -77,7 +78,9 @@
|
|
|
77
78
|
"stylelint-config-recommended": "^18.0.0",
|
|
78
79
|
"tslib": "^2.8.1",
|
|
79
80
|
"typescript": "^6.0.3",
|
|
80
|
-
"typescript-eslint": "^8.68.0"
|
|
81
|
+
"typescript-eslint": "^8.68.0",
|
|
82
|
+
"yeoman-environment": "^6.3.0",
|
|
83
|
+
"yeoman-test": "^11.7.0"
|
|
81
84
|
},
|
|
82
85
|
"packageManager": "yarn@4.18.0"
|
|
83
86
|
}
|
|
@@ -3,6 +3,11 @@ import { Fragment } from 'react';
|
|
|
3
3
|
import { Link, Outlet } from 'react-router-dom';
|
|
4
4
|
import { Container, Nav, Navbar } from 'react-bootstrap';
|
|
5
5
|
|
|
6
|
+
import { routes } from '../routes';
|
|
7
|
+
|
|
8
|
+
// only titled routes get a nav entry - the index route is reachable via the brand
|
|
9
|
+
const navRoutes = routes.filter((route) => route.path && route.handle?.title);
|
|
10
|
+
|
|
6
11
|
export default function Layout() {
|
|
7
12
|
return (
|
|
8
13
|
<Fragment>
|
|
@@ -15,7 +20,13 @@ export default function Layout() {
|
|
|
15
20
|
</Navbar.Brand>
|
|
16
21
|
<Navbar.Toggle />
|
|
17
22
|
<Navbar.Collapse>
|
|
18
|
-
<Nav
|
|
23
|
+
<Nav>
|
|
24
|
+
{navRoutes.map((route) => (
|
|
25
|
+
<Nav.Link as={Link} key={route.path} to={`/${route.path}`}>
|
|
26
|
+
{route.handle?.title}
|
|
27
|
+
</Nav.Link>
|
|
28
|
+
))}
|
|
29
|
+
</Nav>
|
|
19
30
|
</Navbar.Collapse>
|
|
20
31
|
</Container>
|
|
21
32
|
</Navbar>
|
|
@@ -28,6 +39,11 @@ export default function Layout() {
|
|
|
28
39
|
<% } else if (styleFramework === 'bulma') { %>
|
|
29
40
|
import { Link, Outlet } from 'react-router-dom';
|
|
30
41
|
|
|
42
|
+
import { routes } from '../routes';
|
|
43
|
+
|
|
44
|
+
// only titled routes get a nav entry - the index route is reachable via the brand
|
|
45
|
+
const navRoutes = routes.filter((route) => route.path && route.handle?.title);
|
|
46
|
+
|
|
31
47
|
export default function Layout() {
|
|
32
48
|
return (
|
|
33
49
|
<div className="container">
|
|
@@ -37,6 +53,15 @@ export default function Layout() {
|
|
|
37
53
|
<%= package.name %>
|
|
38
54
|
</Link>
|
|
39
55
|
</div>
|
|
56
|
+
<div className="navbar-menu">
|
|
57
|
+
<div className="navbar-start">
|
|
58
|
+
{navRoutes.map((route) => (
|
|
59
|
+
<Link className="navbar-item" key={route.path} to={`/${route.path}`}>
|
|
60
|
+
{route.handle?.title}
|
|
61
|
+
</Link>
|
|
62
|
+
))}
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
40
65
|
</nav>
|
|
41
66
|
<Outlet />
|
|
42
67
|
</div>
|
|
@@ -52,6 +77,11 @@ import {
|
|
|
52
77
|
TopBarLeft
|
|
53
78
|
} from 'react-foundation';
|
|
54
79
|
|
|
80
|
+
import { routes } from '../routes';
|
|
81
|
+
|
|
82
|
+
// only titled routes get a nav entry - the index route is reachable via the brand
|
|
83
|
+
const navRoutes = routes.filter((route) => route.path && route.handle?.title);
|
|
84
|
+
|
|
55
85
|
export default function Layout() {
|
|
56
86
|
return (
|
|
57
87
|
<GridContainer>
|
|
@@ -61,6 +91,11 @@ export default function Layout() {
|
|
|
61
91
|
<MenuItem>
|
|
62
92
|
<Link to="/"><%= package.name %></Link>
|
|
63
93
|
</MenuItem>
|
|
94
|
+
{navRoutes.map((route) => (
|
|
95
|
+
<MenuItem key={route.path}>
|
|
96
|
+
<Link to={`/${route.path}`}>{route.handle?.title}</Link>
|
|
97
|
+
</MenuItem>
|
|
98
|
+
))}
|
|
64
99
|
</Menu>
|
|
65
100
|
</TopBarLeft>
|
|
66
101
|
</TopBar>
|
|
@@ -70,7 +105,12 @@ export default function Layout() {
|
|
|
70
105
|
}
|
|
71
106
|
<% } else if (styleFramework === 'materialize') { %>
|
|
72
107
|
import { Link, Outlet } from 'react-router-dom';
|
|
73
|
-
import { Container, Icon, Navbar } from 'react-materialize';
|
|
108
|
+
import { Container, Icon, Navbar, NavItem } from 'react-materialize';
|
|
109
|
+
|
|
110
|
+
import { routes } from '../routes';
|
|
111
|
+
|
|
112
|
+
// only titled routes get a nav entry - the index route is reachable via the brand
|
|
113
|
+
const navRoutes = routes.filter((route) => route.path && route.handle?.title);
|
|
74
114
|
|
|
75
115
|
export default function Layout() {
|
|
76
116
|
return (
|
|
@@ -78,7 +118,13 @@ export default function Layout() {
|
|
|
78
118
|
<Navbar
|
|
79
119
|
brand={<Link to="/"><%= package.name %></Link>}
|
|
80
120
|
menuIcon={<Icon>menu</Icon>}
|
|
81
|
-
|
|
121
|
+
>
|
|
122
|
+
{navRoutes.map((route) => (
|
|
123
|
+
<NavItem key={route.path}>
|
|
124
|
+
<Link to={`/${route.path}`}>{route.handle?.title}</Link>
|
|
125
|
+
</NavItem>
|
|
126
|
+
))}
|
|
127
|
+
</Navbar>
|
|
82
128
|
<Outlet />
|
|
83
129
|
</Container>
|
|
84
130
|
);
|
|
@@ -87,6 +133,11 @@ export default function Layout() {
|
|
|
87
133
|
import { Link, Outlet } from 'react-router-dom';
|
|
88
134
|
import { Container, ListItem, Navbar, NavbarContainer } from 'uikit-react';
|
|
89
135
|
|
|
136
|
+
import { routes } from '../routes';
|
|
137
|
+
|
|
138
|
+
// only titled routes get a nav entry - the index route is reachable via the brand
|
|
139
|
+
const navRoutes = routes.filter((route) => route.path && route.handle?.title);
|
|
140
|
+
|
|
90
141
|
export default function Layout() {
|
|
91
142
|
return (
|
|
92
143
|
<Container>
|
|
@@ -95,6 +146,11 @@ export default function Layout() {
|
|
|
95
146
|
<ListItem>
|
|
96
147
|
<Link to="/"><%= package.name %></Link>
|
|
97
148
|
</ListItem>
|
|
149
|
+
{navRoutes.map((route) => (
|
|
150
|
+
<ListItem key={route.path}>
|
|
151
|
+
<Link to={`/${route.path}`}>{route.handle?.title}</Link>
|
|
152
|
+
</ListItem>
|
|
153
|
+
))}
|
|
98
154
|
</Navbar>
|
|
99
155
|
</NavbarContainer>
|
|
100
156
|
<Outlet />
|
|
@@ -112,6 +168,11 @@ import {
|
|
|
112
168
|
Typography
|
|
113
169
|
} from '@mui/material';
|
|
114
170
|
|
|
171
|
+
import { routes } from '../routes';
|
|
172
|
+
|
|
173
|
+
// only titled routes get a nav entry - the index route is reachable via the brand
|
|
174
|
+
const navRoutes = routes.filter((route) => route.path && route.handle?.title);
|
|
175
|
+
|
|
115
176
|
export default function Layout() {
|
|
116
177
|
return (
|
|
117
178
|
<Fragment>
|
|
@@ -121,6 +182,16 @@ export default function Layout() {
|
|
|
121
182
|
<Typography component={Link} to="/" variant="h6">
|
|
122
183
|
<%= package.name %>
|
|
123
184
|
</Typography>
|
|
185
|
+
{navRoutes.map((route) => (
|
|
186
|
+
<Typography
|
|
187
|
+
component={Link}
|
|
188
|
+
key={route.path}
|
|
189
|
+
to={`/${route.path}`}
|
|
190
|
+
variant="body1"
|
|
191
|
+
>
|
|
192
|
+
{route.handle?.title}
|
|
193
|
+
</Typography>
|
|
194
|
+
))}
|
|
124
195
|
</Toolbar>
|
|
125
196
|
</AppBar>
|
|
126
197
|
<Container>
|
|
@@ -139,4 +210,4 @@ export default function Layout() {
|
|
|
139
210
|
</div>
|
|
140
211
|
);
|
|
141
212
|
}
|
|
142
|
-
<% } %>
|
|
213
|
+
<% } %>
|
|
@@ -15,6 +15,7 @@ import 'materialize-css';
|
|
|
15
15
|
import Layout from './components/Layout';
|
|
16
16
|
import SuspenseFallback from './components/SuspenseFallback';
|
|
17
17
|
import ErrorBoundary from './components/ErrorBoundary';
|
|
18
|
+
import { routes } from './routes';
|
|
18
19
|
<% if (flags.addRedux) { %>
|
|
19
20
|
import rootSaga from './sagas';
|
|
20
21
|
import rootReducer from './reducers';
|
|
@@ -37,12 +38,7 @@ if (rootElem) {
|
|
|
37
38
|
path: '/',
|
|
38
39
|
element: <Layout />,
|
|
39
40
|
errorElement: <ErrorBoundary />,
|
|
40
|
-
children:
|
|
41
|
-
{
|
|
42
|
-
index: true,
|
|
43
|
-
lazy: () => import(`./pages/index`)
|
|
44
|
-
}
|
|
45
|
-
]
|
|
41
|
+
children: routes
|
|
46
42
|
}
|
|
47
43
|
]);
|
|
48
44
|
|
|
@@ -57,4 +53,4 @@ if (rootElem) {
|
|
|
57
53
|
</Provider>
|
|
58
54
|
<% } %>
|
|
59
55
|
)
|
|
60
|
-
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { RouteObject } from 'react-router-dom';
|
|
2
|
+
|
|
3
|
+
// handle.title is what Layout renders in the nav - routes without one
|
|
4
|
+
// (the index route) are reachable but not linked
|
|
5
|
+
export type AppRoute = RouteObject & {
|
|
6
|
+
handle?: { title: string };
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const routes: AppRoute[] = [
|
|
10
|
+
{
|
|
11
|
+
index: true,
|
|
12
|
+
lazy: () => import(`./pages/index`)
|
|
13
|
+
}
|
|
14
|
+
];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import renderer from 'react-test-renderer';
|
|
2
|
+
import { MemoryRouter } from 'react-router-dom';
|
|
3
|
+
|
|
4
|
+
import <%= page.name %> from './<%= page.name %>';
|
|
5
|
+
|
|
6
|
+
describe('<<%= page.name %> />', () => {
|
|
7
|
+
it('renders correctly', () => {
|
|
8
|
+
const component = renderer.create(
|
|
9
|
+
<MemoryRouter>
|
|
10
|
+
<<%= page.name %> />
|
|
11
|
+
</MemoryRouter>
|
|
12
|
+
);
|
|
13
|
+
const tree = component.toJSON();
|
|
14
|
+
|
|
15
|
+
expect(tree).toMatchSnapshot();
|
|
16
|
+
});
|
|
17
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { MemoryRouter } from 'react-router-dom';
|
|
3
|
+
|
|
4
|
+
import <%= page.name %> from './<%= page.name %>';
|
|
5
|
+
|
|
6
|
+
describe('<<%= page.name %> />', () => {
|
|
7
|
+
it('renders correctly', () => {
|
|
8
|
+
render(
|
|
9
|
+
<MemoryRouter>
|
|
10
|
+
<<%= page.name %> />
|
|
11
|
+
</MemoryRouter>
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
expect(screen.getByRole('heading')).toBeInTheDocument();
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
|
|
3
|
+
import <%= page.name %> from './<%= page.name %>';
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: 'Pages/<%= page.name %>',
|
|
7
|
+
component: <%= page.name %>
|
|
8
|
+
} satisfies Meta<typeof <%= page.name %>>;
|
|
9
|
+
|
|
10
|
+
export default meta;
|
|
11
|
+
|
|
12
|
+
type Story = StoryObj<typeof meta>;
|
|
13
|
+
|
|
14
|
+
export const Default: Story = {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<% if (flags.addRedux) { %>
|
|
2
|
+
import { connect } from 'react-redux';
|
|
3
|
+
<% } %>
|
|
4
|
+
|
|
5
|
+
function <%= page.name %>() {
|
|
6
|
+
return <h1><%= page.name %></h1>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
<% if (flags.addRedux) { %>
|
|
10
|
+
// react-router lazy() route modules load the named Component export
|
|
11
|
+
export const Component = connect(null, null)(<%= page.name %>);
|
|
12
|
+
<% } else { %>
|
|
13
|
+
// react-router lazy() route modules load the named Component export
|
|
14
|
+
export const Component = <%= page.name %>;
|
|
15
|
+
<% } %>
|
|
16
|
+
|
|
17
|
+
export default Component;
|