├── .eslintrc.js ├── .github └── workflows │ ├── codeql-analysis.yml │ └── npmpublish.yml ├── .gitignore ├── .sonarcloud.properties ├── .travis.yml ├── README.md ├── assets ├── logo.png └── logo_512x512.png ├── js ├── bootstrap │ ├── create.js │ ├── edit.js │ ├── form.js │ ├── helpers │ │ ├── alert.js │ │ ├── app.js │ │ ├── home.js │ │ ├── mainFile.js │ │ └── menu.js │ ├── index.js │ └── view.js ├── crud.js ├── eslint │ └── eslintrc.js ├── init.js ├── libs │ └── capitalize.js ├── router │ ├── indexRouter.js │ └── router.js ├── service │ ├── httpService.js │ └── service.js ├── store │ ├── indexModule.js │ ├── indexStore.js │ └── module.js └── types.js ├── main.js ├── package-lock.json ├── package.json └── test ├── main_bootstrap.test.js └── types.test.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | // 'eslint:recommended', 4 | 'plugin:vue/strongly-recommended', 5 | 'plugin:prettier/recommended' 6 | ], 7 | rules: { 8 | // override/add rules settings here, such as: 9 | // 'vue/no-unused-vars': 'error' 10 | }, 11 | env:{ 12 | 'browser': true, 13 | 'node': true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '29 3 * * 1' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 37 | # Learn more: 38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v2 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v1 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v1 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v1 72 | -------------------------------------------------------------------------------- /.github/workflows/npmpublish.yml: -------------------------------------------------------------------------------- 1 | name: vue-crudgen 2 | 3 | on: 4 | release: 5 | types: [updated] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v1 12 | - uses: actions/setup-node@v1 13 | with: 14 | node-version: 12 15 | - run: npm ci 16 | - run: npm test 17 | 18 | publish-npm: 19 | needs: build 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v1 23 | - uses: actions/setup-node@v1 24 | with: 25 | node-version: 12 26 | registry-url: https://registry.npmjs.org/ 27 | - run: npm ci 28 | - run: npm publish 29 | env: 30 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 31 | 32 | publish-gpr: 33 | needs: build 34 | runs-on: ubuntu-latest 35 | steps: 36 | - uses: actions/checkout@v1 37 | - uses: actions/setup-node@v1 38 | with: 39 | node-version: 12 40 | registry-url: https://npm.pkg.github.com/ 41 | scope: '@dionmaicon' 42 | - run: npm ci 43 | - run: npm publish 44 | env: 45 | NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /nbproject/private/ 3 | coverage 4 | 5 | -------------------------------------------------------------------------------- /.sonarcloud.properties: -------------------------------------------------------------------------------- 1 | # Path to sources 2 | #sonar.sources=. 3 | #sonar.exclusions=./node_modules 4 | #sonar.inclusions= 5 | 6 | # Path to tests 7 | #sonar.tests=./test 8 | #sonar.test.exclusions= 9 | #sonar.test.inclusions= 10 | 11 | # Source encoding 12 | #sonar.sourceEncoding=UTF-8 13 | 14 | # Exclusions for copy-paste detection 15 | #sonar.cpd.exclusions= 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - stable 4 | install: 5 | - npm install 6 | script: 7 | - npm run test -- --coverage 8 | after_script: 9 | - COVERALLS_REPO_TOKEN=$coveralls_repo_token npm run coveralls 10 | deploy: 11 | provider: npm 12 | email: "dionmaicon@outlook.com" 13 | api_key: $npm_token 14 | # tag: beta 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-crudgen 2 | CRUD (Create, Read, Update and Delete) basic generator for Vue.js - Beta. 3 | 4 | [![Coverage Status](https://coveralls.io/repos/github/dionmaicon/vue-crudgen/badge.svg?branch=master)](https://coveralls.io/github/dionmaicon/vue-crudgen?branch=master) 5 | [![Build Status](https://travis-ci.org/dionmaicon/vue-crudgen.svg?branch=master)](https://travis-ci.org/dionmaicon/vue-crudgen) 6 | [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=dionmaicon_vue-crudgen&metric=reliability_rating)](https://sonarcloud.io/dashboard?id=dionmaicon_vue-crudgen) 7 | [![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=dionmaicon_vue-crudgen&metric=ncloc)](https://sonarcloud.io/dashboard?id=dionmaicon_vue-crudgen) 8 | [![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=dionmaicon_vue-crudgen&metric=duplicated_lines_density)](https://sonarcloud.io/dashboard?id=dionmaicon_vue-crudgen) 9 | [![Quality gate](https://sonarcloud.io/api/project_badges/quality_gate?project=dionmaicon_vue-crudgen)](https://sonarcloud.io/dashboard?id=dionmaicon_vue-crudgen) 10 | 11 | The focus this package is front-end with vue. 12 | 13 | Next Releases: 14 | - Vuetify 15 | - Typescript 16 | 17 | Best practices for better RESTful API: 18 | 19 | | Resource | GET (Read) | POST (Create) | PUT (Update) | DELETE (Delete) | 20 | | ------------- | ------------- |------------- | ------------- |------------- | 21 | | /users | Return a list of Users | Create a new User | Bulk Update of Users | Delete all Users | 22 | | /users/123 | Returns a Specific User | (405) | Update a Specific User | Delete a Specific User | 23 | 24 | Result for a model generate for this tool: 25 | 26 | ![vue-crudgen-laptop with hidpi screen](https://user-images.githubusercontent.com/19849921/51761375-05803080-20b4-11e9-9cab-055008397c32.png) 27 | 28 | 29 | Install Vue-CLI and Vue-Crudgen. 30 | ``` 31 | npm install -g @vue/cli 32 | npm install -g vue-crudgen 33 | 34 | ``` 35 | 36 | How to do? 37 | 38 | You need to create a Vue-project and setup your structure of files. My Recomended configuration is: Babel, PWA, Router, Vuex, Eslint and Unit-Jest. 39 | ``` 40 | vue create 41 | 42 | ``` 43 | 44 | IMPORTANT!! Vue crud Generator uses eslint to prettier/vue code. Check dependencies. You need to create or edit an .eslintrc.js file. 45 | 46 | ``` 47 | //.eslintrc.js 48 | ... 49 | 'extends': [ 50 | 'plugin:prettier/recommended', 51 | 'plugin:vue/essential', 52 | ], 53 | ... 54 | ``` 55 | 56 | Now init Vue-Crudgen structure project pattern. 57 | ``` 58 | cd 59 | vue-crudgen -i 60 | ``` 61 | After run the command just wait some seconds for scaffold and lint. 62 | Now you need to create models. 63 | ``` 64 | mkdir src/models 65 | cd src/models 66 | touch author.js 67 | ``` 68 | Files *.js models objects should be named in singular. 69 | 70 | ```javascript 71 | //author.js 72 | 73 | const resource = { 74 | endPoint: "authors" 75 | }; 76 | 77 | const model = { 78 | name: { 79 | type: "text", 80 | required: true 81 | }, 82 | birth: { 83 | type: "date", 84 | required: true 85 | }, 86 | active: { 87 | type: "radio", 88 | options: [{ id: "Active", value: true }, { id: "Inactive", value: false }] 89 | }, 90 | sponsor: { 91 | type: "select", 92 | options: ["Patreon", "Legacy"] 93 | } 94 | }; 95 | 96 | module.exports = { model, resource }; 97 | 98 | ``` 99 | After create a model, execute at command line: 100 | ``` 101 | vue-crudgen -m ./src/models/ 102 | ``` 103 | After run, you will see something like this in your project structure: 104 | ``` 105 | project/ 106 | ├── babel.config.js 107 | ├── jest.config.js 108 | ├── node_modules 109 | ├── package.json 110 | ├── package-lock.json 111 | ├── public 112 | │   ├── favicon.ico 113 | │   ├── img 114 | │   ├── index.html 115 | │   └── robots.txt 116 | ├── README.md 117 | │   ├── src 118 | │   | ├── App.vue 119 | │   | ├── assets 120 | │   | |  └── logo.png 121 | │   | ├── components 122 | │   | │   ├── author 123 | │   | │   └── HelloWorld.vue 124 | │   | ├── helpers 125 | │   | │   └── alert.vue 126 | │   | ├── main.js 127 | │   | ├── models 128 | │   | │   └── author.js 129 | │   | ├── registerServiceWorker.js 130 | │   | ├── router 131 | │   | │   └── index.js 132 | │   | ├── routes 133 | │   | │   ├── author.js 134 | │   | │   └── index.js 135 | │   | ├── services 136 | │   | │   ├── author.js 137 | │   | │   └── httpService.js 138 | │   | ├── store 139 | │   | │   ├── index.js 140 | │   | │   └── modules 141 | │   | └── views 142 | │   | ├── About.vue 143 | │   | └── Home.vue 144 | └── tests 145 | └── unit 146 | ``` 147 | You still have to create a .env file with your API base url or edit httpService.js in services directory. 148 | ``` 149 | VUE_APP_BASE_URL=http://localhost:8081 150 | ``` 151 | Now in your root project, start the app and browse to http://localhost:8080/author. 152 | 153 | ``` 154 | npm run serve 155 | 156 | ``` 157 | 158 | 159 | To test your requests, you can use this repository https://github.com/dionmaicon/books-backend. Follow the instructions in the page and run the backend. 160 | 161 | Others models: 162 | 163 | ``` javascript 164 | //book.js 165 | const resource = { 166 | endPoint: "books" 167 | }; 168 | 169 | const model = { 170 | title: { 171 | type: "text", 172 | required: true 173 | }, 174 | ISBN: { 175 | type: "number", 176 | required: true 177 | }, 178 | authors: { 179 | type: "oneToMany", 180 | attribute: "name", 181 | model: "author" 182 | }, 183 | publishing: { 184 | type: "oneToOne", 185 | attribute: "name", 186 | model: "publishing" 187 | }, 188 | year: { 189 | type: "number", 190 | required: true 191 | }, 192 | price: { 193 | type: "currency" 194 | }, 195 | fields: { 196 | type: "hiddenFields", 197 | options: ["price", "ISBN"] 198 | } 199 | }; 200 | module.exports = { model, resource }; 201 | ``` 202 | 203 | ``` javascript 204 | //publishing.js 205 | 206 | const resource = { 207 | endPoint: "publishings" 208 | }; 209 | 210 | const model = { 211 | name: { 212 | type: "text", 213 | required: true 214 | } 215 | }; 216 | 217 | module.exports = { model, resource }; 218 | ``` 219 | -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dionmaicon/vue-crudgen/3fd1be93a7582428266f3ab5a857edf03733cabe/assets/logo.png -------------------------------------------------------------------------------- /assets/logo_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dionmaicon/vue-crudgen/3fd1be93a7582428266f3ab5a857edf03733cabe/assets/logo_512x512.png -------------------------------------------------------------------------------- /js/bootstrap/create.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const capitalize = require('../libs/capitalize'); 3 | 4 | const Create = class { 5 | constructor(name, model, resource) { 6 | this.modelName = name; 7 | this.model = model; 8 | this.resource = resource; 9 | } 10 | 11 | getTemplate() { 12 | let capitalizedName = capitalize(this.modelName); 13 | 14 | let templateHTMLBegin = 15 | ` 28 | 41 | 42 | 61 | `; 62 | 63 | return templateHTMLBegin; 64 | } 65 | }; 66 | 67 | module.exports = Create; 68 | -------------------------------------------------------------------------------- /js/bootstrap/edit.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const capitalize = require('../libs/capitalize'); 3 | 4 | const Edit = class { 5 | constructor(name, model, resource) { 6 | this.modelName = name; 7 | this.model = model; 8 | this.resource = resource; 9 | } 10 | 11 | getTemplate() { 12 | let capitalizedName = capitalize(this.modelName); 13 | 14 | let templateHTMLBegin = 15 | ` 28 | 29 | 48 | 49 | 68 | `; 69 | 70 | return templateHTMLBegin; 71 | } 72 | }; 73 | 74 | module.exports = Edit; 75 | -------------------------------------------------------------------------------- /js/bootstrap/form.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const capitalize = require("../libs/capitalize"); 3 | const pluralize = require("pluralize"); 4 | const Types = require("../types"); 5 | 6 | const Form = class { 7 | constructor(name, model, resource) { 8 | this.modelName = name; 9 | this.model = model; 10 | this.resource = resource; 11 | } 12 | 13 | getTemplate() { 14 | let capitalizedName = capitalize(this.modelName); 15 | 16 | let template = ` 23 | `; 24 | 25 | let templateStruct = ""; 26 | 27 | let countFiles = 0; 28 | //Template Struct 29 | for (let property in this.model) { 30 | 31 | 32 | if (this.model.hasOwnProperty(property)) { 33 | 34 | if (this.model[property].type == Types.HIDDEN_FIELDS) continue; 35 | 36 | templateStruct += `\t
\n\t\t\n`; 37 | 38 | if (this.model[property].type == Types.SELECT) { 39 | templateStruct += ` 40 | \n`; 48 | } else if (this.model[property].type == Types.TEXTAREA) { 49 | templateStruct += `\t\n\n`; 50 | } else if (this.model[property].type == "radio") { 51 | templateStruct += "
"; 52 | for (let option of this.model[property].options) { 53 | templateStruct += `\t\n`; 54 | templateStruct += `\t
\n\n`; 55 | } 56 | } else if (this.model[property].type == Types.CHECKBOX) { 57 | for (let option of this.model[property].options) { 58 | templateStruct += `\t\n`; 59 | templateStruct += `\t
\n\n`; 60 | } 61 | } else if (this.model[property].type == Types.FILE) { 62 | templateStruct += `\t\n`; 63 | countFiles++; 64 | } else if ( 65 | this.model[property].type == Types.ONE_TO_ONE || 66 | this.model[property].type == Types.ONE_TO_MANY 67 | ) { 68 | templateStruct += `\t`; 69 | if (this.model[property].type == Types.ONE_TO_ONE) { 70 | templateStruct += ` 71 | 80 | \n`; 81 | } else { 82 | templateStruct += ` 83 | 94 | \n`; 95 | } 96 | } else if (this.model[property].type == Types.HTML) { 97 | templateStruct += `\t 98 | 103 | \n`; 104 | } else if (this.model[property].type == Types.CURRENCY) { 105 | templateStruct += `\t 106 | \n`; 111 | } else { 112 | templateStruct += `\t\n\n`; 119 | } 120 | } 121 | templateStruct += `
\n`; 122 | } 123 | 124 | let script = ` 125 | 219 | 232 | 233 | `; 234 | 235 | let dataImport = ``; 236 | let dataComponent = ``; 237 | 238 | for (let property in this.model) { 239 | if (this.model.hasOwnProperty(property)) { 240 | 241 | if (this.model[property].type == Types.HIDDEN_FIELDS) continue; 242 | 243 | if (this.model[property].type == Types.HTML) { 244 | if (dataImport == "") { 245 | dataImport += ` 246 | import "quill/dist/quill.core.css"; 247 | import "quill/dist/quill.snow.css"; 248 | import "quill/dist/quill.bubble.css"; 249 | 250 | import { quillEditor } from "vue-quill-editor";`; 251 | dataComponent += "quillEditor"; 252 | } 253 | } 254 | } 255 | } 256 | 257 | let dataScript = ``; 258 | let relationsScript = ``; 259 | 260 | for (let property in this.model) { 261 | if (this.model.hasOwnProperty(property)) { 262 | 263 | if (this.model[property].type == Types.HIDDEN_FIELDS) continue; 264 | 265 | if (this.model[property].type == Types.SELECT) { 266 | relationsScript += `${property}: ${JSON.stringify( 267 | this.model[property].options 268 | )},\n`; 269 | dataScript += `${property}: '',\n`; 270 | } else if (this.model[property].type == Types.CHECKBOX) { 271 | dataScript += `${property}: [],\n`; 272 | } else if (this.model[property].type == Types.ONE_TO_ONE) { 273 | relationsScript += `${property}: [],\n`; 274 | dataScript += `${property}: {},\n`; 275 | } else if (this.model[property].type == Types.ONE_TO_MANY) { 276 | relationsScript += `${property}: [],\n`; 277 | dataScript += `${property}: [],\n`; 278 | } else if ( 279 | this.model[property].type == Types.RADIO || 280 | this.model[property].type == Types.TEXTAREA || 281 | this.model[property].type == Types.FILE 282 | ) { 283 | dataScript += `${property}: '',\n`; 284 | } else { 285 | dataScript += `${property}: '',\n`; 286 | } 287 | } 288 | } 289 | 290 | let relationsFetchScript = ``; 291 | let relationsImport = ``; 292 | 293 | for (let property in this.model) { 294 | if (this.model.hasOwnProperty(property)) { 295 | 296 | if (this.model[property].type == Types.HIDDEN_FIELDS) continue; 297 | 298 | if ( 299 | this.model[property].type == Types.ONE_TO_ONE || 300 | this.model[property].type == Types.ONE_TO_MANY 301 | ) { 302 | let capitalizedRelationName = capitalize(this.model[property].model); 303 | let pluralizedAndCapitalizedRelationName = pluralize( 304 | capitalizedRelationName 305 | ); 306 | relationsImport += ` 307 | import { getAll${pluralizedAndCapitalizedRelationName} } from "@/services/${this.model[property].model}"; 308 | `; 309 | relationsFetchScript += ` 310 | getAll${pluralizedAndCapitalizedRelationName}() 311 | .then(response => { 312 | this.${this.modelName}.relations.${property} = response.data; 313 | }); 314 | `; 315 | } 316 | } 317 | } 318 | 319 | let methodsScript = ``; 320 | 321 | if (countFiles > 0) { 322 | for (let property in this.model) { 323 | if (this.model.hasOwnProperty(property)) { 324 | if (this.model[property].type == Types.FILE) { 325 | methodsScript += ` 326 | ${property}OnFileChange(e){ 327 | let files = e.target.files || e.dataTransfer.files; 328 | if(files.length){ 329 | this.${this.modelName}.${property} = files[0]; 330 | } 331 | }, 332 | `; 333 | } 334 | } 335 | } 336 | } 337 | 338 | //Template Struct Buttons 339 | templateStruct += ` 340 | 341 | 342 | 343 | `; 344 | 345 | template = template.replace(`templateStructString`, templateStruct); 346 | script = script.replace(`relationsScript`, relationsScript); 347 | script = script.replace(`dataScript`, dataScript); 348 | script = script.replace(`methodsScript`, methodsScript); 349 | script = script.replace(`dataImport`, dataImport); 350 | script = script.replace(`dataComponent`, dataComponent); 351 | script = script.replace(`relationsImport`, relationsImport); 352 | script = script.replace(`relationsFetchScript`, relationsFetchScript); 353 | 354 | return template + script; 355 | } 356 | }; 357 | 358 | module.exports = Form; 359 | -------------------------------------------------------------------------------- /js/bootstrap/helpers/alert.js: -------------------------------------------------------------------------------- 1 | const Alert = class { 2 | constructor() {} 3 | 4 | getTemplate() { 5 | return new Promise(resolve => { 6 | let template = ` 7 | 31 | 32 | 98 | 99 | 126 | `; 127 | resolve(template); 128 | }); 129 | } 130 | }; 131 | 132 | module.exports = Alert; 133 | -------------------------------------------------------------------------------- /js/bootstrap/helpers/app.js: -------------------------------------------------------------------------------- 1 | const App = class { 2 | constructor() {} 3 | getTemplate() { 4 | let template = ` 5 | 38 | 39 | 52 | 53 | 79 | `; 80 | return template; 81 | } 82 | }; 83 | 84 | module.exports = App; 85 | -------------------------------------------------------------------------------- /js/bootstrap/helpers/home.js: -------------------------------------------------------------------------------- 1 | const Home = class { 2 | constructor(models) { 3 | this.models = models; 4 | } 5 | 6 | getTemplate() { 7 | let template = ` 8 | 16 | 17 | 22 | 23 | 34 | `; 35 | return template; 36 | } 37 | }; 38 | 39 | module.exports = Home; 40 | -------------------------------------------------------------------------------- /js/bootstrap/helpers/mainFile.js: -------------------------------------------------------------------------------- 1 | const MainFile = class { 2 | constructor() {} 3 | 4 | getTemplate() { 5 | let template = ` 6 | import Vue from "vue"; 7 | import App from "./App.vue"; 8 | import router from "./router"; 9 | import store from "./store"; 10 | import alert from "./helpers/alert.vue"; 11 | import money from "v-money"; 12 | import VueTheMask from "vue-the-mask"; 13 | import Multiselect from "vue-multiselect"; 14 | import VueJsonPretty from "vue-json-pretty"; 15 | import "./registerServiceWorker"; 16 | 17 | import "bootstrap/dist/css/bootstrap.css"; 18 | import "@fortawesome/fontawesome-free/css/all.css"; 19 | import "vue-multiselect/dist/vue-multiselect.min.css"; 20 | 21 | Vue.component("multiselect", Multiselect); 22 | Vue.component("vue-json-pretty", VueJsonPretty); 23 | 24 | Vue.use(VueTheMask); 25 | Vue.use(money, { 26 | decimal: ",", 27 | thousands: ".", 28 | prefix: "R$ ", 29 | precision: 2 30 | }); 31 | 32 | Vue.prototype.$modal = { 33 | show(options) { 34 | return new Promise(resolve => { 35 | const alertComponent = Vue.extend(alert); 36 | const alertVue = new alertComponent(); 37 | 38 | alertVue.$once("confirm", value => { 39 | alertVue.$destroy(); 40 | alertVue.$el.remove(); 41 | resolve(value); 42 | }); 43 | 44 | alertVue.$props.options = options; 45 | alertVue.$mount(); 46 | document.body.appendChild(alertVue.$el); 47 | }); 48 | } 49 | }; 50 | 51 | new Vue({ 52 | router, 53 | store, 54 | render: h => h(App) 55 | }).$mount("#app"); 56 | 57 | `; 58 | return template; 59 | } 60 | }; 61 | 62 | module.exports = MainFile; 63 | -------------------------------------------------------------------------------- /js/bootstrap/helpers/menu.js: -------------------------------------------------------------------------------- 1 | const Menu = class { 2 | constructor(models) { 3 | this.models = models; 4 | } 5 | 6 | getTemplate() { 7 | let template = ` 8 | 19 | 20 | 33 | 34 | 62 | `; 63 | 64 | return template; 65 | } 66 | }; 67 | 68 | module.exports = Menu; 69 | -------------------------------------------------------------------------------- /js/bootstrap/index.js: -------------------------------------------------------------------------------- 1 | const capitalize = require("../libs/capitalize"); 2 | const pluralize = require("pluralize"); 3 | const Types = require("../types"); 4 | 5 | const Index = class { 6 | constructor(name, model, resource) { 7 | this.modelName = name; 8 | this.model = model; 9 | this.resource = resource; 10 | } 11 | 12 | getTemplate() { 13 | let templateStrucTableHead = ``; 14 | let templateStrucTableBody = ``; 15 | 16 | let capitalizedName = capitalize(this.modelName); 17 | let pluralizedAndCapitalizedName = pluralize(capitalizedName); 18 | 19 | let templateHTMLStart = ` 96 | 97 | 208 | 209 | 291 | 292 | `; 293 | 294 | let hiddenFields = []; 295 | for (let property in this.model) { 296 | if (!this.model.hasOwnProperty(property)) continue; 297 | 298 | if (this.model[property].type == Types.HIDDEN_FIELDS) { 299 | hiddenFields = this.model[property].options; 300 | break; 301 | } 302 | } 303 | 304 | for (let property in this.model) { 305 | if (!this.model.hasOwnProperty(property)) continue; 306 | 307 | if ( 308 | this.model[property].type === Types.ONE_TO_ONE || 309 | this.model[property].type === Types.ONE_TO_MANY || 310 | this.model[property].type === Types.HIDDEN_FIELDS || 311 | hiddenFields.includes(property) 312 | ) 313 | continue; 314 | 315 | templateStrucTableHead += ` ${property} \n`; 316 | templateStrucTableBody += `{{${this.modelName}.${property}}}\n`; 317 | } 318 | 319 | templateStrucTableBody += ` 320 | 321 |
322 | 325 | 328 | 331 |
332 | 333 | `; 334 | 335 | templateHTMLStart = templateHTMLStart.replace( 336 | "templateStrucTableHead", 337 | templateStrucTableHead 338 | ); 339 | templateHTMLStart = templateHTMLStart.replace( 340 | "templateStrucTableBody", 341 | templateStrucTableBody 342 | ); 343 | 344 | return templateHTMLStart; 345 | } 346 | }; 347 | 348 | module.exports = Index; 349 | -------------------------------------------------------------------------------- /js/bootstrap/view.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const capitalize = require("../libs/capitalize"); 3 | const Types = require("../types"); 4 | 5 | const View = class { 6 | constructor(name, model, resource) { 7 | this.modelName = name; 8 | this.model = model; 9 | this.resource = resource; 10 | } 11 | 12 | getTemplate() { 13 | let capitalizedName = capitalize(this.modelName); 14 | 15 | let template = ` 56 | 57 | 88 | 89 | 116 | `; 117 | let viewStruct = ""; 118 | for (var property in this.model) { 119 | if (this.model.hasOwnProperty(property)) { 120 | if (this.model[property].type === Types.HIDDEN_FIELDS) continue; 121 | 122 | if ( 123 | this.model[property].type === Types.ONE_TO_MANY || 124 | this.model[property].type === Types.ONE_TO_ONE 125 | ) { 126 | viewStruct += ` 127 |
  • 128 | 129 | ${property}: 130 |
    131 | 133 | 134 |
  • \n`; 135 | continue; 136 | } 137 | 138 | viewStruct += ` 139 |
  • 140 | 141 | ${property}: 142 | {{${this.modelName}.${property}}} 143 |
  • \n`; 144 | } 145 | } 146 | template = template.replace(`viewStruct`, viewStruct); 147 | return template; 148 | } 149 | }; 150 | 151 | module.exports = View; 152 | -------------------------------------------------------------------------------- /js/crud.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | const capitalize = require("./libs/capitalize"); 4 | 5 | const Crud = class { 6 | constructor(config) { 7 | this.config = config; 8 | this.capitalName = capitalize(this.config.name); 9 | } 10 | 11 | generate() { 12 | this.generateView(); 13 | this.generateForm(); 14 | this.generateIndex(); 15 | this.generateEdit(); 16 | this.generateCreate(); 17 | this.generateRoute(); 18 | this.generateService(); 19 | this.generateModule(); 20 | } 21 | 22 | generateView() { 23 | const View = require(`./${this.config.frontend}/view.js`); 24 | const view = new View( 25 | this.config.name, 26 | this.config.model, 27 | this.config.resource 28 | ); 29 | const viewTemplate = view.getTemplate(); 30 | 31 | fs.writeFile( 32 | `${this.config.pathComponents}/${this.config.name}/${this.capitalName}View.vue`, 33 | viewTemplate, 34 | err => { 35 | if (err) { 36 | console.error(err); 37 | } else { 38 | console.log( 39 | `${this.capitalName}View template was create with success!` 40 | ); 41 | } 42 | } 43 | ); 44 | } 45 | 46 | generateForm() { 47 | const Form = require(`./${this.config.frontend}/form.js`); 48 | const form = new Form( 49 | this.config.name, 50 | this.config.model, 51 | this.config.resource 52 | ); 53 | const formTemplate = form.getTemplate(); 54 | 55 | fs.writeFile( 56 | `${this.config.pathComponents}/${this.config.name}/${this.capitalName}Form.vue`, 57 | formTemplate, 58 | err => { 59 | if (err) { 60 | console.error(err); 61 | } else { 62 | console.log("Form template was create with success!"); 63 | } 64 | } 65 | ); 66 | } 67 | 68 | generateIndex() { 69 | const Index = require(`./${this.config.frontend}/index.js`); 70 | const index = new Index( 71 | this.config.name, 72 | this.config.model, 73 | this.config.resource 74 | ); 75 | const indexTemplate = index.getTemplate(); 76 | 77 | fs.writeFile( 78 | `${this.config.pathComponents}/${this.config.name}/${this.capitalName}Index.vue`, 79 | indexTemplate, 80 | err => { 81 | if (err) { 82 | console.error(err); 83 | } else { 84 | console.log( 85 | `${this.capitalName}Index template was create with success!` 86 | ); 87 | } 88 | } 89 | ); 90 | } 91 | 92 | generateCreate() { 93 | const Create = require(`./${this.config.frontend}/create.js`); 94 | const create = new Create( 95 | this.config.name, 96 | this.config.model, 97 | this.config.resource 98 | ); 99 | const createTemplate = create.getTemplate(); 100 | 101 | fs.writeFile( 102 | `${this.config.pathComponents}/${this.config.name}/${this.capitalName}Create.vue`, 103 | createTemplate, 104 | err => { 105 | if (err) { 106 | console.error(err); 107 | } else { 108 | console.log( 109 | `${this.capitalName}Create template was create with success!` 110 | ); 111 | } 112 | } 113 | ); 114 | } 115 | 116 | generateEdit() { 117 | const Edit = require(`./${this.config.frontend}/edit.js`); 118 | const edit = new Edit( 119 | this.config.name, 120 | this.config.model, 121 | this.config.resource 122 | ); 123 | const editTemplate = edit.getTemplate(); 124 | 125 | fs.writeFile( 126 | `${this.config.pathComponents}/${this.config.name}/${this.capitalName}Edit.vue`, 127 | editTemplate, 128 | err => { 129 | if (err) { 130 | console.error(err); 131 | } else { 132 | console.log( 133 | `${this.capitalName}Edit template was create with success!` 134 | ); 135 | } 136 | } 137 | ); 138 | } 139 | 140 | generateRoute() { 141 | const Router = require(`./router/router.js`); 142 | const router = new Router(this.config.name); 143 | const routerTemplate = router.getTemplate(); 144 | fs.writeFile( 145 | `${this.config.pathRoutes}/${this.config.name}.js`, 146 | routerTemplate, 147 | err => { 148 | if (err) { 149 | console.error(err); 150 | } else { 151 | console.log("Router file was create with success!"); 152 | } 153 | } 154 | ); 155 | } 156 | 157 | generateService() { 158 | const Service = require(`./service/service.js`); 159 | const service = new Service(this.config); 160 | const serviceTemplate = service.getTemplate(); 161 | fs.writeFile( 162 | `${this.config.pathServices}/${this.config.name}.js`, 163 | serviceTemplate, 164 | err => { 165 | if (err) { 166 | console.error(err); 167 | } else { 168 | console.log("Service file was create with success!"); 169 | } 170 | } 171 | ); 172 | } 173 | 174 | generateModule() { 175 | const Module = require(`./store/module.js`); 176 | const module = new Module(this.config); 177 | const moduleTemplate = module.getTemplate(); 178 | fs.writeFile( 179 | `${this.config.pathStoreModules}/${this.config.name}.js`, 180 | moduleTemplate, 181 | err => { 182 | if (err) { 183 | console.error(err); 184 | } else { 185 | console.log("Module file was create with success!"); 186 | } 187 | } 188 | ); 189 | } 190 | }; 191 | 192 | module.exports = Crud; 193 | -------------------------------------------------------------------------------- /js/eslint/eslintrc.js: -------------------------------------------------------------------------------- 1 | const Eslintrc = class { 2 | constructor() {} 3 | 4 | getTemplate() { 5 | let template = ` 6 | module.exports = { 7 | root: true, 8 | env: { 9 | node: true 10 | }, 11 | 'extends': [ 12 | 'plugin:prettier/recommended', 13 | 'plugin:vue/essential' 14 | ], 15 | rules: { 16 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 17 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 18 | }, 19 | parserOptions: { 20 | parser: 'babel-eslint' 21 | }, 22 | overrides: [ 23 | { 24 | files: [ 25 | '**/__tests__/*.{j,t}s?(x)', 26 | '**/tests/unit/**/*.spec.{j,t}s?(x)' 27 | ], 28 | env: { 29 | jest: true 30 | } 31 | } 32 | ] 33 | } 34 | `; 35 | 36 | return template; 37 | } 38 | }; 39 | 40 | module.exports = Eslintrc; 41 | -------------------------------------------------------------------------------- /js/init.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | const IndexRouter = require("./router/indexRouter.js"); 4 | const HttpService = require("./service/httpService.js"); 5 | const IndexStore = require("./store/indexStore.js"); 6 | const IndexModule = require("./store/indexModule.js"); 7 | const Eslintrc = require("./eslint/eslintrc.js"); 8 | 9 | const Init = class { 10 | constructor(config) { 11 | this.config = config; 12 | } 13 | 14 | generate() { 15 | this.createRouterStruct(); 16 | this.createIndexRoutesStruct(); 17 | this.createServicesStruct(); 18 | this.createStoreStruct(); 19 | this.createModulesStruct(); 20 | this.createTemplateAlert(); 21 | this.createTemplateMain(); 22 | this.createTemplateEslintrc(); 23 | } 24 | 25 | async createIndexRoutesStruct() { 26 | const indexRouter = new IndexRouter(); 27 | const indexTemplate = indexRouter.getIndexRouterTemplate(); 28 | fs.writeFile(this.config.pathRoutes + "/index.js", indexTemplate, err => { 29 | if (err) { 30 | console.error(err); 31 | } else { 32 | console.log("Routes Index file was create with success!"); 33 | } 34 | }); 35 | } 36 | 37 | async createRouterStruct() { 38 | const indexRouter = new IndexRouter(); 39 | const indexTemplate = indexRouter.getRouterTemplate(); 40 | fs.writeFile(this.config.pathRouter + "/index.js", indexTemplate, err => { 41 | if (err) { 42 | console.error(err); 43 | } else { 44 | console.log("Router Index file was create with success!"); 45 | } 46 | }); 47 | } 48 | 49 | async createServicesStruct() { 50 | const httpService = new HttpService(); 51 | const httpTemplate = httpService.getTemplate(); 52 | fs.writeFile( 53 | this.config.pathServices + "/httpService.js", 54 | httpTemplate, 55 | err => { 56 | if (err) { 57 | console.error(err); 58 | } else { 59 | console.log("Service Init file was create with success!"); 60 | } 61 | } 62 | ); 63 | } 64 | 65 | async createStoreStruct() { 66 | const indexStore = new IndexStore(); 67 | const indexTemplate = indexStore.getTemplate(); 68 | fs.writeFile(this.config.pathStore + "/index.js", indexTemplate, err => { 69 | if (err) { 70 | console.error(err); 71 | } else { 72 | console.log("Index Store file was create with success!"); 73 | } 74 | }); 75 | } 76 | 77 | async createModulesStruct() { 78 | const indexModule = new IndexModule(); 79 | const indexTemplate = indexModule.getTemplate(); 80 | fs.writeFile( 81 | this.config.pathStoreModules + "/index.js", 82 | indexTemplate, 83 | err => { 84 | if (err) { 85 | console.error(err); 86 | } else { 87 | console.log("Index Module file was create with success!"); 88 | } 89 | } 90 | ); 91 | } 92 | 93 | async createTemplateAlert() { 94 | const Alert = require(`./${this.config.frontend}/helpers/alert.js`); 95 | const alert = new Alert(); 96 | const alertTemplate = await alert.getTemplate(); 97 | 98 | fs.writeFile(this.config.pathHelpers + "/alert.vue", alertTemplate, err => { 99 | if (err) { 100 | console.error(err); 101 | } else { 102 | console.log("Alert file was create with success!"); 103 | } 104 | }); 105 | } 106 | 107 | createTemplateMain() { 108 | const MainFile = require(`./${this.config.frontend}/helpers/mainFile.js`); 109 | const mainF = new MainFile(); 110 | const mainTemplate = mainF.getTemplate(); 111 | fs.writeFile(process.cwd() + "/src/main.js", mainTemplate, err => { 112 | if (err) { 113 | console.error(err); 114 | } else { 115 | console.log("Main file was create with success!"); 116 | } 117 | }); 118 | } 119 | 120 | createTemplateApp() { 121 | const App = require(`./${this.config.frontend}/helpers/app.js`); 122 | const app = new App(); 123 | const appTemplate = app.getTemplate(); 124 | fs.writeFile(process.cwd() + "/src/App.vue", appTemplate, err => { 125 | if (err) { 126 | console.error(err); 127 | } else { 128 | console.log("App file was create with success!"); 129 | } 130 | }); 131 | } 132 | 133 | createTemplateHome(models) { 134 | const Home = require(`./${this.config.frontend}/helpers/home.js`); 135 | const home = new Home(models); 136 | const homeTemplate = home.getTemplate(); 137 | 138 | fs.writeFile(process.cwd() + "/src/views/Home.vue", homeTemplate, err => { 139 | if (err) { 140 | console.error(err); 141 | } else { 142 | console.log("Home template file was create with success!"); 143 | } 144 | }); 145 | } 146 | 147 | createTemplateMenu(models) { 148 | const Menu = require(`./${this.config.frontend}/helpers/menu.js`); 149 | const menu = new Menu(models); 150 | const menuTemplate = menu.getTemplate(); 151 | 152 | fs.writeFile(process.cwd() + "/src/menu.vue", menuTemplate, err => { 153 | if (err) { 154 | console.error(err); 155 | } else { 156 | console.log("Menu template file was create with success!"); 157 | } 158 | }); 159 | } 160 | 161 | async createTemplateEslintrc() { 162 | const eslintrc = new Eslintrc(); 163 | const eslintrcTemplate = eslintrc.getTemplate(); 164 | fs.writeFile("./.eslintrc.js", eslintrcTemplate, err => { 165 | if (err) { 166 | console.error(err); 167 | } else { 168 | console.log("eslintrc.js file was create with success!"); 169 | } 170 | }); 171 | } 172 | }; 173 | 174 | module.exports = Init; 175 | -------------------------------------------------------------------------------- /js/libs/capitalize.js: -------------------------------------------------------------------------------- 1 | const capitalize = string => { 2 | if (typeof string !== "string") return ""; 3 | return string.charAt(0).toUpperCase() + string.slice(1); 4 | }; 5 | 6 | module.exports = capitalize; 7 | -------------------------------------------------------------------------------- /js/router/indexRouter.js: -------------------------------------------------------------------------------- 1 | const IndexRouter = class { 2 | constructor() {} 3 | 4 | getIndexRouterTemplate() { 5 | let template = ` 6 | const requireroute = require.context(".", false, /\\.js$/); 7 | const routes = []; 8 | 9 | requireroute.keys().forEach(fileName => { 10 | if (fileName === "./index.js") return; 11 | 12 | routes.push(requireroute(fileName).default); 13 | }); 14 | 15 | export default routes; 16 | `; 17 | return template; 18 | } 19 | 20 | getRouterTemplate() { 21 | let template = ` 22 | import Vue from "vue"; 23 | import VueRouter from "vue-router"; 24 | import Home from "../views/Home.vue"; 25 | import routes from "../routes"; 26 | 27 | Vue.use(VueRouter); 28 | 29 | routes.push({ 30 | path: "/", 31 | name: "home", 32 | component: Home 33 | }); 34 | 35 | routes.push({ 36 | path: "/about", 37 | name: "about", 38 | // route level code-splitting 39 | // this generates a separate chunk (about.[hash].js) for this route 40 | // which is lazy-loaded when the route is visited. 41 | component: () => import(/* webpackChunkName: "about" */ "../views/About.vue") 42 | }); 43 | 44 | const router = new VueRouter({ 45 | mode: "history", 46 | base: process.env.BASE_URL, 47 | routes 48 | }); 49 | 50 | export default router; 51 | 52 | `; 53 | return template; 54 | } 55 | }; 56 | 57 | module.exports = IndexRouter; 58 | -------------------------------------------------------------------------------- /js/router/router.js: -------------------------------------------------------------------------------- 1 | const capitalize = require("../libs/capitalize"); 2 | 3 | const Router = class { 4 | constructor(model) { 5 | this.model = model; 6 | this.capitalName = capitalize(model); 7 | } 8 | 9 | getTemplate() { 10 | let template = ` 11 | import ${this.capitalName}View from "@/components/${this.model}/${this.capitalName}View.vue"; 12 | import ${this.capitalName}Index from "@/components/${this.model}/${this.capitalName}Index.vue"; 13 | import ${this.capitalName}Edit from "@/components/${this.model}/${this.capitalName}Edit.vue"; 14 | import ${this.capitalName}Create from "@/components/${this.model}/${this.capitalName}Create.vue"; 15 | 16 | const ${this.model} = { 17 | path: "/${this.model}", 18 | name: "${this.model}", 19 | component: ${this.capitalName}Index, 20 | children: [ 21 | { path: "view/:id", component: ${this.capitalName}View , name: "${this.model}View" }, 22 | { path: "edit/:id", component: ${this.capitalName}Edit , name: "${this.model}Edit" }, 23 | { path: "create", component: ${this.capitalName}Create , name: "${this.model}Create"} 24 | ] 25 | }; 26 | 27 | export default ${this.model}; 28 | `; 29 | return template; 30 | } 31 | }; 32 | 33 | module.exports = Router; 34 | -------------------------------------------------------------------------------- /js/service/httpService.js: -------------------------------------------------------------------------------- 1 | const HttpService = class { 2 | constructor() {} 3 | 4 | getTemplate() { 5 | let template = ` 6 | import axios from "axios"; 7 | 8 | const httpService = axios.create({ 9 | baseURL: process.env.VUE_APP_BASE_URL || "http://localhost", 10 | timeout: 5000, 11 | headers: { 12 | "Content-Type": "application/json" 13 | } 14 | }); 15 | export default httpService; 16 | `; 17 | 18 | return template; 19 | } 20 | }; 21 | 22 | module.exports = HttpService; 23 | -------------------------------------------------------------------------------- /js/service/service.js: -------------------------------------------------------------------------------- 1 | const capitalize = require("../libs/capitalize"); 2 | const pluralize = require("pluralize"); 3 | 4 | const Service = class { 5 | constructor(config) { 6 | this.config = config; 7 | } 8 | 9 | getTemplate() { 10 | let name = capitalize(this.config.name); 11 | let pluralizedName = pluralize(name); 12 | 13 | let template = ` 14 | import service from "@/services/httpService"; 15 | 16 | const END_POINT = "/${this.config.resource.endPoint}"; 17 | 18 | const get${name} = id => service.get(END_POINT + "/" + id); 19 | 20 | const getAll${pluralizedName} = params => service.get(END_POINT, { params }); 21 | 22 | const create${name} = ${this.config.name} => service.post(END_POINT, ${this.config.name} ); 23 | 24 | const update${name} = ${this.config.name} => service.put(END_POINT + "/" + ${this.config.name}.id, ${this.config.name} ); 25 | 26 | const delete${name} = id => service.delete(END_POINT + "/" + id); 27 | 28 | export { 29 | get${name}, 30 | getAll${pluralizedName}, 31 | create${name}, 32 | update${name}, 33 | delete${name} 34 | }; 35 | `; 36 | return template; 37 | } 38 | }; 39 | 40 | module.exports = Service; 41 | -------------------------------------------------------------------------------- /js/store/indexModule.js: -------------------------------------------------------------------------------- 1 | const IndexModule = class { 2 | constructor() {} 3 | 4 | getTemplate() { 5 | let template = ` 6 | import camelCase from "lodash/camelCase"; 7 | const requireModule = require.context(".", false, /\.js$/); 8 | const modules = {}; 9 | 10 | requireModule.keys().forEach(fileName => { 11 | if (fileName === "./index.js") return; 12 | 13 | const moduleName = camelCase(fileName.replace(/(\\.\\/|\\.js)/g, "")); 14 | 15 | modules[moduleName] = { 16 | namespaced: true, 17 | ...requireModule(fileName).default 18 | }; 19 | }); 20 | 21 | export default modules; 22 | `; 23 | 24 | return template; 25 | } 26 | }; 27 | 28 | module.exports = IndexModule; 29 | -------------------------------------------------------------------------------- /js/store/indexStore.js: -------------------------------------------------------------------------------- 1 | const IndexStore = class { 2 | constructor() {} 3 | 4 | getTemplate() { 5 | let template = ` 6 | import Vue from "vue"; 7 | import Vuex from "vuex"; 8 | import VuexPersistence from "vuex-persist"; 9 | 10 | import modules from "./modules"; 11 | 12 | const vuexLocal = new VuexPersistence({ 13 | storage: window.localStorage 14 | }); 15 | 16 | Vue.use(Vuex); 17 | 18 | export default new Vuex.Store({ 19 | modules, 20 | plugins: [vuexLocal.plugin] 21 | }); 22 | `; 23 | 24 | return template; 25 | } 26 | }; 27 | 28 | module.exports = IndexStore; 29 | -------------------------------------------------------------------------------- /js/store/module.js: -------------------------------------------------------------------------------- 1 | const pluralize = require("pluralize"); 2 | const capitalize = require("../libs/capitalize"); 3 | 4 | const Module = class { 5 | constructor(config) { 6 | this.config = config; 7 | } 8 | 9 | getTemplate() { 10 | let capitalizedName = capitalize(this.config.name); 11 | let upperCaseName = capitalizedName.toUpperCase(); 12 | let pluralizedName = pluralize(this.config.name); 13 | let pluralizedAndCapitalizedName = pluralize(capitalizedName); 14 | let pluralizedAndUpperCaseName = pluralize(upperCaseName); 15 | 16 | let template = ` 17 | import { 18 | getAll${pluralizedAndCapitalizedName}, 19 | create${capitalizedName}, 20 | update${capitalizedName}, 21 | delete${capitalizedName} 22 | } from "@/services/${this.config.name}"; 23 | 24 | const state = { 25 | ${pluralizedName}: [], 26 | processed: false 27 | }; 28 | 29 | const getters = { 30 | get${pluralizedAndCapitalizedName}(state) { 31 | return state.${pluralizedName}; 32 | }, 33 | ${this.config.name}Processed(state){ 34 | return state.processed; 35 | } 36 | }; 37 | 38 | const actions = { 39 | fetch${pluralizedAndCapitalizedName}({ commit }, params) { 40 | return new Promise((resolve, reject) => { 41 | getAll${pluralizedAndCapitalizedName}( 42 | params 43 | ) 44 | .then(response => { 45 | commit("SET_${pluralizedAndUpperCaseName}", response.data); 46 | resolve(response.data); 47 | }) 48 | .catch(error => { 49 | reject(error); 50 | }); 51 | }); 52 | }, 53 | create${capitalizedName}({ commit }, ${this.config.name}) { 54 | return new Promise((resolve, reject) => { 55 | create${capitalizedName}(${this.config.name}) 56 | .then(response => { 57 | commit("CREATED_${upperCaseName}"); 58 | resolve(response.data); 59 | }) 60 | .catch(error => { 61 | reject(error); 62 | }); 63 | }); 64 | }, 65 | update${capitalizedName}({ commit }, ${this.config.name}) { 66 | return new Promise((resolve, reject) => { 67 | update${capitalizedName}(${this.config.name}) 68 | .then(response => { 69 | commit("UPDATED_${upperCaseName}"); 70 | resolve(response.data); 71 | }) 72 | .catch(error => { 73 | reject(error); 74 | }); 75 | }); 76 | }, 77 | delete${capitalizedName}({ commit }, id) { 78 | return new Promise((resolve, reject) => { 79 | delete${capitalizedName}(id) 80 | .then(response => { 81 | commit("DELETED_${upperCaseName}"); 82 | resolve(response.data); 83 | }) 84 | .catch(error => { 85 | reject(error); 86 | }); 87 | }); 88 | } 89 | }; 90 | 91 | const mutations = { 92 | SET_${pluralizedAndUpperCaseName}(state, data) { 93 | state.${pluralizedName} = data; 94 | state.processed = false; 95 | }, 96 | CREATED_${upperCaseName}(state) { 97 | state.processed = true; 98 | }, 99 | UPDATED_${upperCaseName}(state) { 100 | state.processed = true; 101 | }, 102 | DELETED_${upperCaseName}(state) { 103 | state.processed = true; 104 | } 105 | }; 106 | 107 | export default { 108 | state, 109 | getters, 110 | actions, 111 | mutations 112 | }; 113 | `; 114 | 115 | return template; 116 | } 117 | }; 118 | 119 | module.exports = Module; 120 | -------------------------------------------------------------------------------- /js/types.js: -------------------------------------------------------------------------------- 1 | const ONE_TO_ONE = "oneToOne"; 2 | const ONE_TO_MANY = "oneToMany"; 3 | const TEXT = "text"; 4 | const SELECT = "select"; 5 | const TEXTAREA = "textarea"; 6 | const NUMBER = "number"; 7 | const CURRENCY = "currency"; 8 | const HTML = "html"; 9 | const RADIO = "radio"; 10 | const CHECKBOX = "checkbox"; 11 | const FILE = "file"; 12 | const HIDDEN_FIELDS = "hiddenFields"; 13 | 14 | const HTML5_TYPES = [ 15 | "color", 16 | "date", 17 | "datetime-local", 18 | "email", 19 | "hidden", 20 | "image", 21 | "month", 22 | "password", 23 | "range", 24 | "reset", 25 | "search", 26 | "submit", 27 | "tel", 28 | "time", 29 | "url", 30 | "week" 31 | ]; 32 | 33 | const Types = class { 34 | constructor() {} 35 | 36 | isValid() {} 37 | 38 | static get ONE_TO_ONE() { 39 | return ONE_TO_ONE; 40 | } 41 | 42 | static get ONE_TO_MANY() { 43 | return ONE_TO_MANY; 44 | } 45 | 46 | static get TEXT() { 47 | return TEXT; 48 | } 49 | 50 | static get SELECT() { 51 | return SELECT; 52 | } 53 | 54 | static get TEXTAREA() { 55 | return TEXTAREA; 56 | } 57 | 58 | static get NUMBER() { 59 | return NUMBER; 60 | } 61 | 62 | static get CURRENCY() { 63 | return CURRENCY; 64 | } 65 | 66 | static get HTML() { 67 | return HTML; 68 | } 69 | 70 | static get RADIO() { 71 | return RADIO; 72 | } 73 | 74 | static get CHECKBOX() { 75 | return CHECKBOX; 76 | } 77 | 78 | static get FILE() { 79 | return FILE; 80 | } 81 | 82 | static get HIDDEN_FIELDS() { 83 | return HIDDEN_FIELDS; 84 | } 85 | 86 | static get validTypes() { 87 | return HTML5_TYPES.concat([ 88 | ONE_TO_ONE, 89 | ONE_TO_MANY, 90 | TEXT, 91 | SELECT, 92 | TEXTAREA, 93 | NUMBER, 94 | CURRENCY, 95 | HTML, 96 | RADIO, 97 | CHECKBOX, 98 | FILE, 99 | HIDDEN_FIELDS 100 | ]); 101 | } 102 | 103 | static isValid(value) { 104 | let types = this.validTypes; 105 | return types.find(type => type == value); 106 | } 107 | 108 | static modelIsValid(model) { 109 | let response = { 110 | message: "", 111 | success: true 112 | }; 113 | 114 | for (let prop in model) { 115 | if (!model.hasOwnProperty(prop)) { 116 | response.message = "Model invalid."; 117 | response.success = false; 118 | continue; 119 | } 120 | 121 | let object = model[prop]; 122 | switch (model[prop].type) { 123 | case Types.ONE_TO_ONE: 124 | case Types.ONE_TO_MANY: 125 | if (!object.attribute) { 126 | response.message += `Model type: ${prop} needs attribute property.\n`; 127 | response.success = false; 128 | } 129 | if (!object.model) { 130 | response.message += `Model type: ${prop} needs model property.\n`; 131 | response.success = false; 132 | } 133 | break; 134 | case Types.RADIO: 135 | case Types.CHECKBOX: 136 | case Types.SELECT: 137 | case Types.HIDDEN_FIELDS: 138 | if (!object.options) { 139 | response.message += `Model type: ${prop} needs options property.\n`; 140 | response.success = false; 141 | } 142 | break; 143 | } 144 | if (!this.isValid(model[prop].type)) { 145 | response.message += `Model type: ${prop} is invalid.\n`; 146 | response.success = false; 147 | } 148 | } 149 | 150 | if (!response.success) { 151 | return response; 152 | } else { 153 | response.message = "Model is valid."; 154 | return response; 155 | } 156 | } 157 | }; 158 | 159 | module.exports = Types; 160 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const fs = require("fs"); 3 | const path = require("path"); 4 | const commander = require("commander"); 5 | const { exec } = require("child_process"); 6 | 7 | const Crud = require("./js/crud.js"); 8 | const Init = require("./js/init.js"); 9 | const Types = require("./js/types.js"); 10 | 11 | const config = { 12 | pathRoutes: path.join(process.cwd(), "src/routes"), 13 | pathRouter: path.join(process.cwd(), "src/router"), 14 | pathComponents: path.join(process.cwd(), "src/components"), 15 | pathModels: path.join(process.cwd(), "src/models"), 16 | pathServices: path.join(process.cwd(), "src/services"), 17 | pathStore: path.join(process.cwd(), "src/store"), 18 | pathViews: path.join(process.cwd(), "src/views"), 19 | pathStoreModules: path.join(process.cwd(), "src/store/modules"), 20 | pathHelpers: path.join(process.cwd(), "src/helpers"), 21 | uniqueFile: null, 22 | vuetify: null, 23 | bootstrap: null 24 | }; 25 | 26 | const createBaseFolders = () => { 27 | let paths = []; 28 | paths.push(config.pathRoutes); 29 | paths.push(config.pathRouter); 30 | paths.push(config.pathModels); 31 | paths.push(config.pathComponents); 32 | paths.push(config.pathStore); 33 | paths.push(config.pathViews); 34 | paths.push(config.pathHelpers); 35 | paths.push(config.pathStoreModules); 36 | paths.push(config.pathServices); 37 | 38 | paths.forEach(dir => { 39 | if (!fs.existsSync(dir)) { 40 | fs.mkdirSync(dir, { recursive: true }); 41 | } 42 | }); 43 | }; 44 | 45 | const createFolder = (name, basePath) => { 46 | let dir = `${basePath}/${name}`; 47 | if (!fs.existsSync(dir)) { 48 | fs.mkdirSync(dir, { recursive: true }); 49 | } 50 | }; 51 | 52 | const createFiles = (name, file) => { 53 | fs.stat(file, function(err, stats) { 54 | let basePath = config.pathComponents; 55 | createFolder(name, basePath); 56 | const { model, resource } = require(file); 57 | if (model == null || resource == null) { 58 | console.error(`Template model "${name}" has sintax error.`); 59 | process.exit(-1); 60 | } 61 | let valid = Types.modelIsValid(model); 62 | if (valid.success == true) { 63 | createTemplates(name, model, resource); 64 | } else { 65 | console.log(valid.message); 66 | } 67 | }); 68 | }; 69 | 70 | const initModels = async () => { 71 | try { 72 | let models = []; 73 | let files = fs.readdirSync(config.pathModels); 74 | 75 | for (let i = 0; i < files.length; i++) { 76 | let file = config.pathModels + "/" + files[i]; 77 | let name = path.basename(file, ".js"); 78 | models.push(name); 79 | await createFiles(name, file); 80 | } 81 | return models; 82 | } catch (e) { 83 | console.error(`Path to models looks wrong. Try again with new path`); 84 | process.exit(-1); 85 | } 86 | }; 87 | 88 | const initModel = async () => { 89 | try { 90 | let file = config.uniqueFile; 91 | let name = path.basename(file, ".js"); 92 | await createFiles(name, file); 93 | } catch (e) { 94 | console.error(`Path to model looks wrong. Try again with new path`); 95 | } 96 | }; 97 | 98 | const initApp = async () => { 99 | try { 100 | if (config.bootstrap) { 101 | config.frontend = "bootstrap"; 102 | } else { 103 | config.frontend = "vuetify"; 104 | } 105 | 106 | const init = new Init(config); 107 | init.generate(); 108 | } catch (e) { 109 | console.error(`Models cannot be generate, we have some problem.`); 110 | console.error(e); 111 | } 112 | }; 113 | 114 | const createTemplates = async (name, model, resource) => { 115 | let crud; 116 | 117 | config.name = name; 118 | config.model = model; 119 | config.resource = resource; 120 | 121 | if (config.bootstrap) { 122 | config.frontend = "bootstrap"; 123 | } else { 124 | config.frontend = "vuetify"; 125 | } 126 | 127 | crud = new Crud(config); 128 | crud.generate(); 129 | }; 130 | 131 | const InstallLocalDependecies = async () => { 132 | try { 133 | if (config.bootstrap) { 134 | exec( 135 | `npm install --save bootstrap axios v-money vue-the-mask vue-multiselect vuex-persist vue-json-pretty @fortawesome/fontawesome-free`, 136 | (error, stdout, stderr) => { 137 | if (error) { 138 | console.error(error); 139 | } 140 | console.log(stdout); 141 | } 142 | ); 143 | } else { 144 | config.frontend = "vuetify"; 145 | exec( 146 | "npm install --save vuetify axios v-money vue-the-mask vue-multiselect vuex-persist vue-json-pretty", 147 | (error, stdout, stderr) => { 148 | if (error) { 149 | console.error(error); 150 | } 151 | console.log(stdout); 152 | } 153 | ); 154 | } 155 | exec( 156 | `npm install --save-dev eslint-plugin-prettier eslint-config-prettier`, 157 | (error, stdout, stderr) => { 158 | if (error) { 159 | console.error(error); 160 | } 161 | console.log(stdout); 162 | } 163 | ); 164 | } catch (e) { 165 | console.error(`Models cannot to be generate, we have problems here.`); 166 | console.error(e); 167 | } 168 | }; 169 | 170 | async function main() { 171 | const program = new commander.Command(); 172 | 173 | program.version( 174 | "Vue.js CRUD-GEN Version: 1.0.6 developed by Dion Maicon - BETA" 175 | ); 176 | program 177 | // .option("-v, --vuetify", "Scaffold Vuetify Templates.") 178 | .option("-b, --bootstrap", "Scaffold Bootstrap Templates (Default).") 179 | .option("-m, --models ", "Generate views for all models in path.") 180 | .option("-u, --unique ", "Generate for an unique model.") 181 | .option("-i, --init ", "Init files to src scaffold."); 182 | 183 | program.parse(process.argv); 184 | 185 | if (program.vuetify) { 186 | config.vuetify = true; 187 | } 188 | 189 | if (program.bootstrap || config.vuetify === null) { 190 | config.bootstrap = true; 191 | } 192 | 193 | if (program.models) { 194 | config.pathModels = path.join( 195 | process.cwd(), 196 | path.normalize(program.models) 197 | ); 198 | } 199 | 200 | if (program.unique) { 201 | config.uniqueFile = path.join( 202 | process.cwd(), 203 | path.normalize(program.unique) 204 | ); 205 | } 206 | 207 | if (program.init) { 208 | createBaseFolders(); 209 | await initApp(); 210 | await InstallLocalDependecies(); 211 | } 212 | 213 | if (program.models) { 214 | await initModels(); 215 | } 216 | 217 | if (program.unique) { 218 | await initModel(); 219 | } 220 | 221 | exec("npx eslint --fix --ext=vue ./src/", (error, stdout, stderr) => { 222 | console.log(stdout); 223 | }); 224 | 225 | exec("npx eslint --fix --ext=js ./src/", (error, stdout, stderr) => { 226 | console.log(stdout); 227 | }); 228 | } 229 | 230 | main(); 231 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-crudgen", 3 | "version": "1.0.7", 4 | "description": "TryUs. Software CRUD (Create, Read, Update and Delete) basic generator for Vue.js", 5 | "author": "Dion Maicon E. Duarte ", 6 | "scripts": { 7 | "dev": "npm run serve", 8 | "test": "jest", 9 | "lint-staged": "lint-staged", 10 | "coveralls": "cat ./coverage/lcov.info | node node_modules/.bin/coveralls" 11 | }, 12 | "lint-staged": { 13 | "*.js": [ 14 | "eslint --fix", 15 | "git add", 16 | "jest --bail --findRelatedTests" 17 | ] 18 | }, 19 | "bin": { 20 | "vue-crudgen": "./main.js" 21 | }, 22 | "preferGlobal": true, 23 | "dependencies": { 24 | "commander": "^4.0.0", 25 | "pluralize": "^8.0.0" 26 | }, 27 | "devDependencies": { 28 | "coveralls": "^3.0.9", 29 | "eslint": "^6.6.0", 30 | "eslint-config-prettier": "^6.5.0", 31 | "eslint-plugin-prettier": "^3.1.1", 32 | "eslint-plugin-vue": "^6.0.1", 33 | "faker": "^4.1.0", 34 | "jest": "^24.9.0", 35 | "lint-staged": "^9.5.0", 36 | "prettier": "^1.19.1", 37 | "prettier-eslint": "^9.0.0" 38 | }, 39 | "bugs": { 40 | "url": "https://github.com/dionmaicon/vue-crudgen/issues" 41 | }, 42 | "directories": { 43 | "test": "test" 44 | }, 45 | "homepage": "https://github.com/dionmaicon/vue-crudgen#readme", 46 | "keywords": [ 47 | "CRUD", 48 | "Vue.js", 49 | "Generator", 50 | "Boostrap", 51 | "Vuetify" 52 | ], 53 | "license": "MIT", 54 | "main": "main.js", 55 | "repository": { 56 | "type": "git", 57 | "url": "git+https://github.com/dionmaicon/vue-crudgen.git" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /test/main_bootstrap.test.js: -------------------------------------------------------------------------------- 1 | const MainFile = require("../js/bootstrap/helpers/mainFile.js"); 2 | let mainFile = new MainFile(); 3 | let template = mainFile.getTemplate(); 4 | 5 | test("Check Dependencies Imports (Multiselect)", () => { 6 | expect(template).toMatch(/import Multiselect from "vue-multiselect";/); 7 | expect(template).toMatch( 8 | /import "vue-multiselect\/dist\/vue-multiselect\.min\.css";/ 9 | ); 10 | }); 11 | 12 | test("Check Dependencies Imports (VueTheMask)", () => { 13 | expect(template).toMatch(/import VueTheMask from "vue-the-mask";/); 14 | }); 15 | 16 | test("Check Dependencies Imports (VueJsonPretty)", () => { 17 | expect(template).toMatch(/import VueJsonPretty from "vue-json-pretty";/); 18 | }); 19 | 20 | test("Check Dependencies Imports VMoney)", () => { 21 | expect(template).toMatch(/import money from "v-money";/); 22 | }); 23 | 24 | test("Check Dependencies Imports (Boostrap)", () => { 25 | expect(template).toMatch(/import "bootstrap\/dist\/css\/bootstrap.css";/); 26 | expect(template).toMatch( 27 | /import "@fortawesome\/fontawesome-free\/css\/all.css";/ 28 | ); 29 | }); 30 | -------------------------------------------------------------------------------- /test/types.test.js: -------------------------------------------------------------------------------- 1 | const Types = require("../js/types"); 2 | 3 | const model = { 4 | title: { 5 | type: Types.TEXT, 6 | required: true 7 | }, 8 | ISBN: { 9 | type: Types.NUMBER, 10 | required: true 11 | }, 12 | authors: { 13 | type: Types.ONE_TO_MANY, 14 | attribute: "name", 15 | model: "author" 16 | }, 17 | publishing: { 18 | type: Types.ONE_TO_ONE, 19 | attribute: "name", 20 | model: "publishing" 21 | }, 22 | price: { 23 | type: Types.CURRENCY 24 | }, 25 | birth: { 26 | type: "date", 27 | required: true 28 | }, 29 | active: { 30 | type: Types.RADIO, 31 | options: [ 32 | { id: "Active", value: true }, 33 | { id: "Inactive", value: false } 34 | ] 35 | }, 36 | sponsor: { 37 | type: Types.SELECT, 38 | options: ["Patron", "Vue-Crudgen"] 39 | }, 40 | fields: { 41 | type: Types.HIDDEN_FIELDS, 42 | options: ["birth", "price"] 43 | } 44 | }; 45 | 46 | test("Check if types are valid", () => { 47 | let count = 0; 48 | 49 | for (let prop in model) { 50 | if (model.hasOwnProperty(prop)) { 51 | if (Types.isValid(model[prop].type)) { 52 | count++; 53 | } 54 | } 55 | } 56 | expect(count).toBe(9); 57 | }); 58 | 59 | test("Check if Types Models is invalid", () => { 60 | let person = { 61 | sponsor: { 62 | type: "selects", 63 | options: ["Patron", "Vue-Crudgen"] 64 | } 65 | }; 66 | 67 | let count; 68 | 69 | for (let prop in person) { 70 | if (person.hasOwnProperty(prop)) { 71 | if (Types.isValid(person[prop].type)) { 72 | count++; 73 | } 74 | } 75 | } 76 | expect(count).not.toBe(1); 77 | }); 78 | 79 | test("Check if model is valid", () => { 80 | let response = Types.modelIsValid(model); 81 | expect(response).toEqual({ 82 | message: "Model is valid.", 83 | success: true 84 | }); 85 | }); 86 | 87 | test("Check if model is invalid (CURRENCY)", () => { 88 | model.supply = { 89 | type: Types.CURRENCY 90 | }; 91 | 92 | let response = Types.modelIsValid(model); 93 | 94 | expect(response.message).toMatch("Model is valid."); 95 | expect(response.success).toBeTruthy(); 96 | }); 97 | 98 | test("Check if model is invalid (TEXTAREA)", () => { 99 | model.supply = { 100 | type: Types.TEXTAREA 101 | }; 102 | 103 | let response = Types.modelIsValid(model); 104 | 105 | expect(response.message).toMatch("Model is valid."); 106 | expect(response.success).toBeTruthy(); 107 | }); 108 | 109 | test("Check if model is invalid (HTML)", () => { 110 | model.supply = { 111 | type: Types.HTML 112 | }; 113 | 114 | let response = Types.modelIsValid(model); 115 | 116 | expect(response.message).toMatch("Model is valid."); 117 | expect(response.success).toBeTruthy(); 118 | }); 119 | 120 | test("Check if model is invalid (FILE)", () => { 121 | model.supply = { 122 | type: Types.FILE 123 | }; 124 | 125 | let response = Types.modelIsValid(model); 126 | 127 | expect(response.message).toMatch("Model is valid."); 128 | expect(response.success).toBeTruthy(); 129 | }); 130 | 131 | test("Check if model is invalid ()", () => { 132 | model.supply = { 133 | type: Types.CURRENCY 134 | }; 135 | 136 | let response = Types.modelIsValid(model); 137 | 138 | expect(response.message).toMatch("Model is valid."); 139 | expect(response.success).toBeTruthy(); 140 | }); 141 | 142 | test("Check if model is invalid (ONE_TO_ONE)", () => { 143 | model.supply = { 144 | type: Types.ONE_TO_ONE 145 | }; 146 | 147 | let response = Types.modelIsValid(model); 148 | 149 | expect(response.message).toMatch("Model type: supply needs model property."); 150 | 151 | expect(response.message).toMatch( 152 | "Model type: supply needs attribute property" 153 | ); 154 | 155 | expect(response.success).toBeFalsy(); 156 | }); 157 | 158 | test("Check if model is invalid (ONE_TO_MANY)", () => { 159 | model.supply = { 160 | type: Types.ONE_TO_MANY 161 | }; 162 | 163 | let response = Types.modelIsValid(model); 164 | 165 | expect(response.message).toMatch("Model type: supply needs model property."); 166 | 167 | expect(response.message).toMatch( 168 | "Model type: supply needs attribute property" 169 | ); 170 | 171 | expect(response.success).toBeFalsy(); 172 | }); 173 | 174 | test("Check if model is invalid (RADIO)", () => { 175 | model.supply = { 176 | type: Types.RADIO 177 | }; 178 | 179 | let response = Types.modelIsValid(model); 180 | 181 | expect(response.message).toMatch( 182 | "Model type: supply needs options property." 183 | ); 184 | 185 | expect(response.success).toBeFalsy(); 186 | }); 187 | 188 | test("Check if model is invalid (CHECKBOX)", () => { 189 | model.supply = { 190 | type: Types.CHECKBOX 191 | }; 192 | 193 | let response = Types.modelIsValid(model); 194 | 195 | expect(response.message).toMatch( 196 | "Model type: supply needs options property." 197 | ); 198 | 199 | expect(response.success).toBeFalsy(); 200 | }); 201 | 202 | test("Check if model is invalid (SELECT)", () => { 203 | model.supply = { 204 | type: Types.SELECT 205 | }; 206 | 207 | let response = Types.modelIsValid(model); 208 | 209 | expect(response.message).toMatch( 210 | "Model type: supply needs options property." 211 | ); 212 | 213 | expect(response.success).toBeFalsy(); 214 | }); 215 | 216 | test("Check if model is invalid (INVALID TYPE)", () => { 217 | model.supply = { 218 | type: "default" 219 | }; 220 | 221 | let response = Types.modelIsValid(model); 222 | 223 | expect(response.message).toMatch("Model type: supply is invalid."); 224 | 225 | expect(response.success).toBeFalsy(); 226 | }); 227 | 228 | test("Check if model is invalid (Model Inherited)", () => { 229 | const obj = Object.create({ name: "stuart" }); 230 | 231 | let response = Types.modelIsValid(obj); 232 | 233 | expect(response.message).toMatch("Model invalid."); 234 | 235 | expect(response.success).toBeFalsy(); 236 | }); 237 | --------------------------------------------------------------------------------