By clicking
41 | Sign up you agree to our
42 | terms of service
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
83 |
--------------------------------------------------------------------------------
/cli/core/cleaner/index.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 | const rimraf = require('rimraf');
4 | const rm = require('./rm');
5 | const packageJson = require('./packageJson');
6 |
7 | const root = path.resolve();
8 |
9 | function removeFolder(folder) {
10 | rimraf.sync(folder);
11 | }
12 |
13 | function removeFile(file) {
14 | return new Promise(resolve => {
15 | fs.unlink(file, () => {
16 | resolve();
17 | });
18 | });
19 | }
20 |
21 | function writeFile(data, file) {
22 | return new Promise(resolve => {
23 | fs.writeFileSync(file, data, err => {
24 | console.error(err);
25 | resolve();
26 | });
27 | });
28 | }
29 |
30 | async function removeFiles(files) {
31 | const promises = [];
32 | /* eslint-disable-next-line */
33 | for (const file of files) {
34 | promises.push(removeFile(file));
35 | }
36 | await Promise.all(promises);
37 | }
38 |
39 | async function removeFolders(folders) {
40 | const promises = [];
41 | /* eslint-disable-next-line */
42 | for (const folder of folders) {
43 | promises.push(removeFolder(folder));
44 | }
45 | await Promise.all(promises);
46 | }
47 |
48 | async function run(options) {
49 | // Update package.json
50 | const newPackageJson = packageJson.update(options);
51 | writeFile(JSON.stringify(newPackageJson, null, 2), `${root}/package.json`);
52 |
53 | await removeFiles([`${root}/package-lock.json`]);
54 | await removeFolders([`${root}/node_modules`]);
55 |
56 | // Remove sentry
57 | if (options.sentry) {
58 | await rm.removeLines(`${root}/src/main.js`, [2, 3, 8, 9, [13, 21]]);
59 | }
60 |
61 | // Remove cypress
62 | if (options.cypress) {
63 | await removeFiles([
64 | `${root}/docker-compose.e2e.yml`,
65 | `${root}/cy-open.yml`,
66 | `${root}/cypress.json`,
67 | `${root}/docker/Dockerfile.e2e`,
68 | `${root}/tests/.eslintrc.js`
69 | ]);
70 | await removeFolders([`${root}/tests/e2e`, `${root}/tests/server`]);
71 | }
72 |
73 | // Remove jest
74 | if (options.jest) {
75 | await removeFiles([`${root}/jest.config.js`, `${root}/jest-coverage-badges.js`]);
76 | await removeFolders([`${root}/tests/unit`]);
77 | }
78 |
79 | // Remove linter
80 | if (options.linter) {
81 | await removeFiles([`${root}/.eslintignore`, `${root}/.eslintrc.js`]);
82 | }
83 |
84 | // Remove gitlab pages
85 | if (options.gitlabPage) {
86 | await removeFiles([`${root}/vue.config.js`]);
87 | }
88 |
89 | // Remove vue Doc
90 | if (options.vueDoc) {
91 | await removeFolders([`${root}/docs/components`]);
92 | }
93 |
94 | // Remove i18n
95 | if (options.multiLanguage) {
96 | await removeFolders([`${root}/src/locales`]);
97 | if (options.sentry) {
98 | await rm.removeLines(`${root}/src/main.js`, [5, 12]);
99 | } else {
100 | await rm.removeLines(`${root}/src/main.js`, [7, 25]);
101 | }
102 | }
103 |
104 | // Remove mdb
105 | if (options.mdb) {
106 | await rm.removeLines(`${root}/public/index.html`, [8]);
107 | await rm.removeLines(`${root}/src/assets/scss/index.scss`, [2]);
108 | }
109 |
110 | // Remove prettier
111 | if (options.prettier) {
112 | await removeFiles([`${root}/.prettierrc`]);
113 | }
114 |
115 | return true;
116 | }
117 |
118 | module.exports = { run };
119 |
--------------------------------------------------------------------------------
/src/api/config.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import LocalStorageService from '@/services/localStorageService';
3 |
4 | const { clearToken, getRefreshToken, getAccessToken, setToken } =
5 | LocalStorageService.installService();
6 |
7 | const DEFAULT_URL = 'http://localhost:8000/';
8 |
9 | export const REFRESH_URL = '/users/refresh_token';
10 | export const RESPONSE_ACCESS_PARAM = 'token';
11 | export const RESPONSE_REFRESH_PARAM = 'refresh_token';
12 | export const baseURL = process.env.REACT_APP_API_URL || DEFAULT_URL;
13 |
14 | export const apiInstance = axios.create({
15 | baseURL,
16 | headers: { Authorization: `Bearer ${getAccessToken()}` }
17 | });
18 |
19 | let isRefreshing = false;
20 | let failedQueue = [];
21 |
22 | export const createNewInstance = token =>
23 | axios.create({
24 | baseURL,
25 | headers: { Authorization: `Bearer ${token}` }
26 | });
27 |
28 | const processQueue = (error, token = null) => {
29 | failedQueue.forEach(prom => {
30 | if (error) {
31 | prom.reject(error);
32 | } else {
33 | prom.resolve(token);
34 | }
35 | });
36 |
37 | failedQueue = [];
38 | };
39 |
40 | export const errorHandler = error => {
41 | const originalRequest = error.config;
42 | const err = error.response;
43 | if (err.status === 403) {
44 | const message = err.data ? err.data.detail : err.statusText;
45 | console.error(message);
46 | }
47 |
48 | if (
49 | error.response.status === 401 &&
50 | !originalRequest._retry &&
51 | !originalRequest.url.includes(REFRESH_URL)
52 | ) {
53 | if (isRefreshing) {
54 | return new Promise((resolve, reject) => {
55 | failedQueue.push({
56 | resolve,
57 | reject
58 | });
59 | })
60 | .then(token => {
61 | originalRequest.headers.Authorization = `Bearer ${token}`;
62 | return apiInstance(originalRequest);
63 | })
64 | .catch(errResponse => Promise.reject(errResponse));
65 | }
66 |
67 | originalRequest._retry = true;
68 | isRefreshing = true;
69 |
70 | const refresh = getRefreshToken();
71 |
72 | return new Promise((resolve, reject) => {
73 | createNewInstance(refresh)
74 | .post(REFRESH_URL)
75 | .then(({ data }) => {
76 | setToken({
77 | access_token: data[RESPONSE_ACCESS_PARAM],
78 | refresh_token: data[RESPONSE_REFRESH_PARAM]
79 | });
80 | apiInstance.defaults.headers.common.Authorization = `Bearer ${data[RESPONSE_ACCESS_PARAM]}`;
81 | originalRequest.headers.Authorization = `Bearer ${data[RESPONSE_ACCESS_PARAM]}`;
82 | processQueue(null, data[RESPONSE_ACCESS_PARAM]);
83 | resolve(apiInstance(originalRequest));
84 | })
85 | .catch(errResponse => {
86 | processQueue(errResponse, null);
87 | clearToken();
88 | reject(errResponse);
89 | })
90 | .then(() => {
91 | isRefreshing = false;
92 | });
93 | });
94 | }
95 | return Promise.reject(error);
96 | };
97 |
98 | apiInstance.interceptors.response.use(
99 | response => response,
100 | error => errorHandler(error)
101 | );
102 |
103 | export default apiInstance;
104 |
105 | export const queryGet = (url, config = {}) => apiInstance.get(url, config);
106 |
107 | export const queryPost = (url, data = null, config = {}) => apiInstance.post(url, data, config);
108 |
--------------------------------------------------------------------------------
/cli/core/prompts.js:
--------------------------------------------------------------------------------
1 | const logSymbols = require('log-symbols');
2 |
3 | module.exports = {
4 | initPrompts: [
5 | {
6 | name: 'name',
7 | type: 'input',
8 | message: 'Project name:',
9 | filter(val) {
10 | return val
11 | .trim()
12 | .toLowerCase()
13 | .replace(/[^+\w]/g, ' ') // Change all symbols to space
14 | .trim() // Remove spaces from start & end string
15 | .replace(/\s+/g, '-') // Change spaces to "-";
16 | },
17 | validate: value =>
18 | value.length ? true : `${logSymbols.warning} This field should not be empty`
19 | },
20 | {
21 | name: 'author',
22 | type: 'input',
23 | message: 'Author:',
24 | validate: value =>
25 | value.length ? true : `${logSymbols.warning} This field should not be empty`
26 | },
27 | {
28 | name: 'cypress',
29 | type: 'list',
30 | message: 'Remove cypress?',
31 | validate: value =>
32 | value.length ? true : `${logSymbols.warning} This field should not be empty`,
33 | filter(val) {
34 | return val === 'Yes';
35 | },
36 | choices: ['No', 'Yes']
37 | },
38 | {
39 | name: 'jest',
40 | type: 'list',
41 | message: 'Remove unit tests?',
42 | validate: value =>
43 | value.length ? true : `${logSymbols.warning} This field should not be empty`,
44 | filter(val) {
45 | return val === 'Yes';
46 | },
47 | choices: ['No', 'Yes']
48 | },
49 | {
50 | name: 'linter',
51 | type: 'list',
52 | message: 'Remove linter?',
53 | validate: value =>
54 | value.length ? true : `${logSymbols.warning} This field should not be empty`,
55 | filter(val) {
56 | return val === 'Yes';
57 | },
58 | choices: ['No', 'Yes']
59 | },
60 | {
61 | name: 'prettier',
62 | type: 'list',
63 | message: 'Remove formatter?',
64 | validate: value =>
65 | value.length ? true : `${logSymbols.warning} This field should not be empty`,
66 | filter(val) {
67 | return val === 'Yes';
68 | },
69 | choices: ['No', 'Yes']
70 | },
71 | {
72 | name: 'gitlabPage',
73 | type: 'list',
74 | message: 'Remove gitlab page?',
75 | validate: value =>
76 | value.length ? true : `${logSymbols.warning} This field should not be empty`,
77 | filter(val) {
78 | return val === 'Yes';
79 | },
80 | choices: ['No', 'Yes']
81 | },
82 | {
83 | name: 'mdb',
84 | type: 'list',
85 | message: 'Remove Material Design Bootstrap?',
86 | validate: value =>
87 | value.length ? true : `${logSymbols.warning} This field should not be empty`,
88 | filter(val) {
89 | return val === 'Yes';
90 | },
91 | choices: ['No', 'Yes']
92 | },
93 | {
94 | name: 'vueDoc',
95 | type: 'list',
96 | message: 'Remove component documentation?',
97 | validate: value =>
98 | value.length ? true : `${logSymbols.warning} This field should not be empty`,
99 | filter(val) {
100 | return val === 'Yes';
101 | },
102 | choices: ['No', 'Yes']
103 | },
104 | {
105 | name: 'multiLanguage',
106 | type: 'list',
107 | message: 'Remove multi language settings?',
108 | validate: value =>
109 | value.length ? true : `${logSymbols.warning} This field should not be empty`,
110 | filter(val) {
111 | return val === 'Yes';
112 | },
113 | choices: ['No', 'Yes']
114 | },
115 | {
116 | name: 'sentry',
117 | type: 'list',
118 | message: 'Remove sentry?',
119 | validate: value =>
120 | value.length ? true : `${logSymbols.warning} This field should not be empty`,
121 | filter(val) {
122 | return val === 'Yes';
123 | },
124 | choices: ['No', 'Yes']
125 | }
126 | ]
127 | };
128 |
--------------------------------------------------------------------------------
/tests/unit/axios.spec.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import MockAdapter from 'axios-mock-adapter';
3 |
4 | import {
5 | queryGet,
6 | queryPost,
7 | apiInstance,
8 | baseURL,
9 | errorHandler,
10 | REFRESH_URL
11 | } from '../../src/api/config';
12 |
13 | const mock = new MockAdapter(apiInstance);
14 | const ACCESS_TOKEN = 'someaccesstoken';
15 | const REFRESH_TOKEN = 'somerefrestoken';
16 | const MOCKED_URL = 'http://someurl.com/';
17 |
18 | mock.onPost(REFRESH_URL).reply(200, {
19 | data: {
20 | access_token: ACCESS_TOKEN,
21 | refresh_token: REFRESH_TOKEN
22 | }
23 | });
24 |
25 | mock.onGet('/api/token/').reply(200, {
26 | data: {
27 | access_token: ACCESS_TOKEN,
28 | refresh_token: REFRESH_TOKEN
29 | }
30 | });
31 |
32 | mock.onPost('/api/token/').reply(200, {
33 | data: {
34 | access_token: ACCESS_TOKEN,
35 | refresh_token: REFRESH_TOKEN
36 | }
37 | });
38 |
39 | mock.onPost(REFRESH_URL).reply(201, {
40 | data: {
41 | access_token: 'new_access',
42 | refresh_token: 'new_refresh'
43 | }
44 | });
45 |
46 | mock.onGet('/error/').replyOnce(201, {
47 | data: {
48 | access_token: 'new_access',
49 | refresh_token: 'new_refresh'
50 | }
51 | });
52 |
53 | describe('request interceptor', () => {
54 | it('should include correct access token', async () => {
55 | const result = await queryGet('/api/token/');
56 | expect(result.data.data.access_token).toBe(ACCESS_TOKEN);
57 | });
58 |
59 | it('should include correct refresh token', async () => {
60 | const result = await queryGet('/api/token/');
61 | expect(result.data.data.refresh_token).toBe(REFRESH_TOKEN);
62 | });
63 |
64 | it('should must include response interceptor', async () => {
65 | expect(mock.axiosInstance.interceptors.response.handlers.length).toBe(1);
66 | });
67 |
68 | it('should must correct create new instance with params', async () => {
69 | const customInstance = await axios.create({
70 | baseURL: MOCKED_URL,
71 | headers: { Authorization: `Bearer ${ACCESS_TOKEN}` }
72 | });
73 | const mockedCustom = new MockAdapter(customInstance);
74 |
75 | expect(mockedCustom.axiosInstance.defaults.headers.Authorization).toBe(
76 | `Bearer ${ACCESS_TOKEN}`
77 | );
78 | expect(mockedCustom.axiosInstance.defaults.baseURL).toBe(MOCKED_URL);
79 | });
80 |
81 | it('should must correct return default url from config file', async () => {
82 | expect(mock.axiosInstance.defaults.baseURL).toBe(baseURL);
83 | });
84 |
85 | it('should must correct work post query method', async () => {
86 | const result = await queryPost('/api/token/');
87 | expect(result.data.data.refresh_token).toBe(REFRESH_TOKEN);
88 | });
89 |
90 | it('If error state is 401, last url param in error response should must be REFRESH_URL from config file', async () => {
91 | const ERROR_RESPONSE = {
92 | response: {
93 | status: 401
94 | },
95 | config: {
96 | _retry: false,
97 | url: '/error/some'
98 | }
99 | };
100 |
101 | try {
102 | await errorHandler(ERROR_RESPONSE);
103 | } catch (e) {
104 | expect(e.config.url).toBe(REFRESH_URL);
105 | }
106 | });
107 |
108 | it('If error state is 401, last url param in error response should must be REFRESH_URL from config file', async () => {
109 | const ERROR_RESPONSE = {
110 | response: {
111 | status: 401
112 | },
113 | config: {
114 | _retry: false,
115 | url: '/error/some'
116 | }
117 | };
118 |
119 | try {
120 | await errorHandler(ERROR_RESPONSE);
121 | } catch (e) {
122 | expect(e.config.url).toBe(REFRESH_URL);
123 | }
124 | });
125 |
126 | it('If error state is 401 and retry type is true, that response as last error', async () => {
127 | const ERROR_RESPONSE = {
128 | response: {
129 | status: 401
130 | },
131 | config: {
132 | _retry: false,
133 | url: '/error/some'
134 | }
135 | };
136 |
137 | try {
138 | await errorHandler(ERROR_RESPONSE);
139 | } catch (e) {
140 | console.log(e);
141 | }
142 | });
143 | });
144 |
--------------------------------------------------------------------------------
/cli/core/cleaner/packageJson.js:
--------------------------------------------------------------------------------
1 | function cypress(remove, key) {
2 | if (remove) return null;
3 | switch (key) {
4 | case 'scripts':
5 | return {
6 | 'docker:test:e2e':
7 | 'docker-compose -f docker-compose.e2e.yml -f cy-open.yml up --exit-code-from cypress',
8 | 'test:e2e': 'npm run test:server & vue-cli-service test:e2e --mode test',
9 | 'test:server': 'node ./tests/server/server.js'
10 | };
11 | case 'info':
12 | return {
13 | 'docker:test:e2e': 'Run e2e tests in docker',
14 | 'test:e2e': 'Run integration tests',
15 | 'test:server': 'Run mock server for tests'
16 | };
17 | case 'dep':
18 | return {
19 | cypress: '^6.4.0',
20 | 'json-server': '^0.16.3'
21 | };
22 | case 'devDep':
23 | return {
24 | '@cypress/vue': '^1.0.0-alpha.4',
25 | '@vue/cli-plugin-e2e-cypress': '^4.5.11',
26 | 'vue-cli-plugin-cypress-experimental': '~1.2.0'
27 | };
28 | default:
29 | return null;
30 | }
31 | }
32 |
33 | function linter(remove, key) {
34 | if (remove) return null;
35 | switch (key) {
36 | case 'scripts':
37 | return {
38 | lint: 'npm run lint-es-vue & npm run lint-es & npm run lint-vue-scss & npm run lint-scss',
39 | 'lint-es': 'eslint --ext .js,.vue .',
40 | 'lint-es-vue': 'vue-cli-service lint --no-fix',
41 | 'lint-scss': 'sass-lint src/assets/scss/*.scss --verbose',
42 | 'lint-vue-scss': 'sass-lint-vue src'
43 | };
44 | case 'info':
45 | return {
46 | lint: 'Run linters for vue, js, scss files',
47 | 'lint-es': 'Run es linter',
48 | 'lint-es-vue': 'Run es-vue linter',
49 | 'lint-scss': 'Run linter for check scss files',
50 | 'lint-vue-scss': 'Run linter for check scss styles in vue files'
51 | };
52 | case 'dep':
53 | return {
54 | 'eslint-config-prettier': '^6.15.0',
55 | 'eslint-plugin-prettier': '^3.1.4'
56 | };
57 | case 'devDep':
58 | return {
59 | '@vue/cli-plugin-eslint': '^4.5.11',
60 | '@vue/eslint-config-airbnb': '^4.0.0',
61 | 'babel-eslint': '^10.1.0',
62 | eslint: '^5.16.0',
63 | 'eslint-plugin-vue': '^5.0.0',
64 | 'sass-lint': '^1.13.1',
65 | 'sass-lint-vue': '^0.4.0'
66 | };
67 | default:
68 | return null;
69 | }
70 | }
71 |
72 | function jest(remove, key) {
73 | if (remove) return null;
74 | switch (key) {
75 | case 'scripts':
76 | return {
77 | 'test:unit': 'vue-cli-service test:unit --watch --coverage=false',
78 | 'test:unit:coverage':
79 | 'vue-cli-service test:unit --coverage && node ./jest-coverage-badges.js -output "./public"'
80 | };
81 | case 'info':
82 | return {
83 | 'test:unit': 'Run functional tests',
84 | 'test:unit:coverage': 'Generate code coverage for functional tests'
85 | };
86 | case 'devDep':
87 | return {
88 | '@vue/test-utils': '1.0.0-beta.29',
89 | '@vue/cli-plugin-unit-jest': '^4.5.11'
90 | };
91 | default:
92 | return null;
93 | }
94 | }
95 |
96 | function prettier(remove, key) {
97 | if (remove) return null;
98 | switch (key) {
99 | case 'scripts':
100 | return {
101 | format: 'prettier --write "{tests,src,.}/**/*.{js,vue}"'
102 | };
103 | case 'info':
104 | return {
105 | format: 'Run code formatting'
106 | };
107 | case 'devDep':
108 | return {
109 | prettier: '^2.2.1'
110 | };
111 | default:
112 | return null;
113 | }
114 | }
115 |
116 | function vueDoc(remove, key) {
117 | if (remove) return null;
118 | switch (key) {
119 | case 'dep':
120 | return {
121 | '@vuedoc/md': '^3.0.0',
122 | '@vuedoc/parser': '^3.3.0'
123 | };
124 | default:
125 | return null;
126 | }
127 | }
128 |
129 | function multiLanguage(remove, key) {
130 | if (remove) return null;
131 | switch (key) {
132 | case 'devDep':
133 | return {
134 | 'vue-i18n': '^9.1.6'
135 | };
136 | default:
137 | return null;
138 | }
139 | }
140 |
141 | function sentry(remove, key) {
142 | if (remove) return null;
143 | switch (key) {
144 | case 'dep':
145 | return {
146 | '@sentry/tracing': '^6.0.4',
147 | '@sentry/vue': '^6.0.4'
148 | };
149 | default:
150 | return null;
151 | }
152 | }
153 |
154 | function mdb(remove, key) {
155 | if (remove) return null;
156 | switch (key) {
157 | case 'dep':
158 | return {
159 | 'mdb-vue-ui-kit': '^1.0.0-beta6'
160 | };
161 | case 'devDep':
162 | return {
163 | 'vue-cli-plugin-mdb5': '~1.2.0'
164 | };
165 | default:
166 | return null;
167 | }
168 | }
169 |
170 | function tests(options, key) {
171 | if (!options.jest && !options.cypress) return null;
172 | switch (key) {
173 | case 'scripts':
174 | if (options.jest && options.cypress)
175 | return {
176 | tests:
177 | 'npm run test:server & vue-cli-service test:unit --coverage=false && vue-cli-service test:e2e --headless --mode test'
178 | };
179 | if (options.jest && !options.cypress)
180 | return { tests: 'vue-cli-service test:unit --coverage=false' };
181 | if (!options.jest && options.cypress)
182 | return { tests: 'npm run test:server & vue-cli-service test:e2e --headless --mode test' };
183 | return null;
184 | case 'info':
185 | if (options.jest && options.cypress) return { tests: 'Run integration and functional tests' };
186 | if (options.jest && !options.cypress) return { tests: 'Run functional tests' };
187 | if (!options.jest && options.cypress) return { tests: 'Run integration tests' };
188 | return null;
189 | default:
190 | return null;
191 | }
192 | }
193 |
194 | module.exports = {
195 | update: options => ({
196 | name: options.name,
197 | version: '1.0.0',
198 | private: true,
199 | author: options.author,
200 | engines: {
201 | node: 'v14.15.4',
202 | npm: '6.14.4'
203 | },
204 | scripts: {
205 | ...tests(options, 'scripts'),
206 | ...linter(options.linter, 'scripts'),
207 | ...jest(options.jest, 'scripts'),
208 | ...cypress(options.cypress, 'scripts'),
209 | ...prettier(options.prettier, 'scripts'),
210 | 'docker:dev': 'docker-compose -f docker-compose.dev.yml up',
211 | 'docker:prod': 'docker-compose -f docker-compose.prod.yml up',
212 | serve: 'vue-cli-service serve',
213 | build: 'vue-cli-service build'
214 | },
215 | 'scripts-info': {
216 | ...tests(options, 'info'),
217 | ...linter(options.linter, 'info'),
218 | ...jest(options.jest, 'info'),
219 | ...cypress(options.cypress, 'info'),
220 | 'docker:dev': 'Develop via Docker compose',
221 | 'docker:prod': 'Run project on server production',
222 | serve: 'Run develop server',
223 | build: 'Build project for production'
224 | },
225 | dependencies: {
226 | ...sentry(options.sentry, 'dep'),
227 | ...linter(options.linter, 'dep'),
228 | ...cypress(options.cypress, 'dep'),
229 | ...vueDoc(options.vueDoc, 'dep'),
230 | ...mdb(options.mdb, 'dep'),
231 | axios: '^0.21.1',
232 | 'axios-mock-adapter': '^1.19.0',
233 | 'core-js': '^3.8.0',
234 | "cytoscape": "^3.19.0",
235 | 'cytoscape-cola': '^2.4.0',
236 | 'document-register-element': '^1.14.10',
237 | save: '^2.4.0',
238 | vue: '^3.0.0-beta.1',
239 | 'vue-router': '^4.0.8',
240 | vuex: '^4.0.1',
241 | webpack: '^4.44.2'
242 | },
243 | devDependencies: {
244 | ...linter(options.linter, 'devDep'),
245 | ...jest(options.jest, 'devDep'),
246 | ...prettier(options.prettier, 'devDep'),
247 | ...cypress(options.cypress, 'devDep'),
248 | ...multiLanguage(options.multiLanguage, 'devDep'),
249 | ...mdb(options.mdb, 'devDep'),
250 | '@vue/compiler-sfc': '^3.0.0-beta.1',
251 | '@babel/plugin-proposal-nullish-coalescing-operator': '^7.12.13',
252 | '@babel/plugin-proposal-optional-chaining': '^7.12.13',
253 | '@babel/preset-env': '^7.12.13',
254 | '@vue/cli-plugin-babel': '^4.5.11',
255 | '@vue/cli-plugin-router': '^4.5.11',
256 | '@vue/cli-plugin-vuex': '^4.5.11',
257 | '@vue/cli-service': '^4.5.13',
258 | 'node-sass': '^4.14.1',
259 | 'sass-loader': '^8.0.2',
260 | 'vue-template-compiler': '^2.6.12'
261 | }
262 | })
263 | };
264 |
--------------------------------------------------------------------------------
/docs/How_to_run_and_cofugure_tests.md:
--------------------------------------------------------------------------------
1 | # How to run and configure tests
2 |
3 | To test the widget we have a type of test:
4 | 1. [Functional](#unit-tests)
5 | 2. [Component](#integration-and-component-tests)
6 | 3. [Integration](#integration-and-component-tests)
7 |
8 | ## Run all tests with mock json server on production
9 |
10 | Before running the tests, you need to run the `npm i` command to install all dependencies.
11 |
12 | ```bash
13 | npm run tests
14 | ```
15 |
16 | After running this command:
17 |
18 | 1. The Mock server will start
19 | 2. The unit test will start checking
20 | 3. Server for integration tests will start
21 | 4. Integration and component tests will start checking
22 | 5. Finish. A table of results will be displayed
23 |
24 | ```
25 | (Run Finished)
26 |
27 | Spec Tests Passing Failing Pending Skipped
28 | ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
29 | │ ✔ Home.spec.js 00:05 5 5 - - - │
30 | ├────────────────────────────────────────────────────────────────────────────────────────────────┤
31 | │ ✔ Search.spec.js 00:03 5 5 - - - │
32 | ├────────────────────────────────────────────────────────────────────────────────────────────────┤
33 | │ ✔ VehiclesList.spec.js 642ms 1 1 - - - │
34 | └────────────────────────────────────────────────────────────────────────────────────────────────┘
35 | ✔ All specs passed! 00:14 11 11 - - -
36 | ```
37 |
38 | **IMPORTANT**: Tests should be run before the project build to warn of bugs. The `npm run build` command should be run if the tests pass.
39 |
40 | Example:
41 |
42 | ```
43 | npm i
44 | npm run lint
45 | npm run tests
46 | npm run build
47 | ```
48 |
49 | ## Server for tests
50 |
51 | A special Mock server for tests has been added so that the tests do not depend on a real server.
52 |
53 | > We used this server > `https://github.com/typicode/json-server`
54 |
55 | ### Run server
56 |
57 | > Original command to run server `json-server --watch ./tests/server/server.js`
58 |
59 | ```bash
60 | npm run test:server
61 | ```
62 |
63 | The server will start on the localhost and will be available at [http://localhost:8888](http://localhost:8888)
64 |
65 | In the console you should see the following result:
66 |
67 | ```bash
68 | $ npm run test:server
69 |
70 | $ vms-showroom@0.0.1 test:server /home/denisoed/projects/XXX/vms-frontend
71 | $ json-server --port 8888 --watch ./tests/server/server.js --routes ./tests/server/router.json --middlewares ./tests/server/middleware.js
72 |
73 |
74 | \{^_^}/ hi!
75 |
76 | Loading ./tests/server/server.js
77 | Loading ./tests/server/router.json
78 | Loading ./tests/server/middleware.js
79 | Done
80 |
81 | Resources
82 | http://localhost:8888/cultures
83 | http://localhost:8888/instance
84 | http://localhost:8888/showroom
85 | http://localhost:8888/vehicles
86 | http://localhost:8888/vehicle
87 | http://localhost:8888/bestDeals
88 | http://localhost:8888/filtered
89 | http://localhost:8888/availableFilters
90 | http://localhost:8888/byIds
91 |
92 | Other routes
93 | /api/v1/CultureSections/json\?* -> /cultures
94 | /api/v2/Instances/:id -> /instance
95 | /api/v2/Showroom/public/:uid\?* -> /showroom
96 | /api/v2/Showroom/public/:uid/AvailableFilters\?* -> /availableFilters
97 | /api/v2/Showroom/public/:uid/vehicles/byIds\?* -> /byIds
98 | /api/v2/Showroom/public/:uid/vehicles\?* -> /vehicles
99 | /api/v2/Showroom/public/:uid/vehicles/best-deals\?* -> /bestDeals
100 | /api/v2/Showroom/public/:uid/vehicles/filtered\?* -> /filtered
101 | /api/v2/Showroom/public/:uid/vehicles/:uid\?* -> /vehicle
102 |
103 | Home
104 | http://localhost:8888
105 |
106 | ...
107 | ```
108 |
109 | ### Configure server
110 |
111 | In the root of the project there is a configuration [file](./json-server.json) for json-server, in it you can change the port, add new middlewares, etc.
112 |
113 | ```json
114 | {
115 | "port": "8888",
116 | "routes": "./tests/server/router.json",
117 | "middlewares": "./tests/server/middleware.js"
118 | }
119 | ```
120 |
121 | ### Structure folders
122 |
123 | The files for the server are located in the `/tests/server` folder.
124 |
125 | * File [server.js](./tests/server/server.js) - is the main file for starting the server.
126 | * File [router.json](./tests/server/router.json) - needed to create custom api urls.
127 | * File [middleware.js](./tests/server/middleware.js) - needed to change the behavior of the json-server. Example change all POST requests to GET requests
128 | * Folder **data** - stores files with test data, which will be returned by json-server. All data in the files can be changed.
129 |
130 | ## Unit tests
131 |
132 | Functional testing allows you to test individual functions in your code to identify logical errors
133 |
134 | > The jest library is used for unit tests > `https://vue-test-utils.vuejs.org/ru/guides`
135 |
136 | Run unit tests
137 |
138 | ```bash
139 | npm run test:unit
140 | ```
141 |
142 | Generate code coverage report
143 |
144 | ```bash
145 | npm run test:unit:coverage
146 | ```
147 |
148 | ## Integration and Component tests
149 |
150 | The [cypress](https://www.cypress.io/) framework is used for integration and component testing.
151 |
152 | For the component tests we use experimental lib `https://docs.cypress.io/guides/component-testing/introduction.html#What-is-Cypress-Component-Testing`
153 |
154 | Command for run:
155 |
156 | ```bash
157 | npm run test:e2e
158 | ```
159 |
160 | After running this command:
161 |
162 | 1. The Mock server will start
163 | 2. Server for integration tests will start
164 | 3. A window opens with a list of all tests that can be run and monitored.
165 | 
166 | 4. Now you can start writing tests
167 |
168 |
169 | ### Folders and files
170 |
171 | The integration tests are located in the `/tests/e2e` folder.
172 |
173 | ```bash
174 | tests
175 | e2e
176 | components
177 | - example.spec.js
178 | - ...
179 | fixtures
180 | - example.json
181 | - ...
182 | integration
183 | - example.spec.js
184 | - ...
185 | plugins
186 | - index.js
187 | support
188 | - commands.js
189 | - index.js
190 | - constants.js
191 | unit
192 | - ...
193 | server
194 | - ...
195 | ```
196 |
197 | * Add tests for components to the `/tests/e2e/components` folder
198 | * Add tests for pages to the `/tests/e2e/integrations` folder
199 | * *More information about folders and files can be found here* `https://docs.cypress.io/guides/core-concepts/writing-and-organizing-tests.html#Plugin-files`
200 |
201 | > Please, use fixtures from this folder `/tests/server/data/*.js`. Example [VehiclesList](../tests/e2e/components/VehiclesList.spec.js)
202 |
203 | ### Settings
204 |
205 | The settings file cypress.json for integration tests is located in the root of the project.
206 |
207 | ```bash
208 | {
209 | "baseUrl": "http://localhost:3000",
210 | "chromeWebSecurity": false,
211 | "pluginsFile": "tests/e2e/plugins/index.js",
212 | "supportFile": "tests/e2e/support/index.js",
213 | "fixturesFolder": "tests/e2e/fixtures",
214 | "integrationFolder": "tests/e2e/integrations",
215 | "testFiles": "**/*.spec.js*",
216 | "experimentalComponentTesting": true,
217 | "componentFolder": "tests/e2e/components",
218 | "nodeVersion":"system",
219 | "video": false,
220 | "viewportWidth": 1366,
221 | "viewportHeight": 768
222 | }
223 | ```
224 |
225 | You can read about all available settings here `https://docs.cypress.io/guides/references/configuration.html#Options`
226 |
227 | ### Run tests in docker container
228 |
229 | If you get this error
230 |
231 | ```bash
232 | cypress_1 | ----------
233 | cypress_1 |
234 | cypress_1 | No protocol specified
235 | cypress_1 | [101:0208/050449.746174:ERROR:browser_main_loop.cc(1434)] Unable to open X display.
236 | cypress_1 | The futex facility returned an unexpected error code.
237 | cypress_1 | No protocol specified
238 | cypress_1 | [115:0208/050450.882329:ERROR:browser_main_loop.cc(1434)] Unable to open X display.
239 | cypress_1 |
240 | cypress_1 | undefined:0
241 | cypress_1 |
242 | cypress_1 |
243 | cypress_1 | illegal access
244 | cypress_1 | (Use `Cypress --trace-uncaught ...` to show where the exception was thrown)
245 | cypress_1 |
246 | cypress_1 | ----------
247 | ```
248 |
249 | 1. Please, run this command
250 |
251 | ```bash
252 | xhost +si:localuser:root
253 | ```
254 |
255 | Result
256 |
257 | ```bash
258 | localuser:root being added to access control list
259 | ```
260 |
261 | 2. Run e2e tests
262 | ```bash
263 | docker-compose -f docker-compose.e2e.yml -f cy-open.yml up --exit-code-from cypress
264 | ```
265 |
--------------------------------------------------------------------------------
/src/assets/img/md.svg:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/src/components/BG.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
684 |
685 |
702 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Vue 3 Mad Boiler
2 |
3 | [](https://maddevs.io?utm_source=github&utm_medium=madboiler)
4 | [](https://opensource.org/licenses/MIT)
5 | 
6 |
7 | 
8 |
9 | ## Translations of documentation
10 |
11 | - English
12 | - [Russian](./README-RU.md)
13 |
14 | ## Why you should use this boilerplate
15 |
16 | **Vue Mad Boiler will save you time by giving you a ready-made, customized project.**
17 |
18 | When starting a project in vue, I constantly faced the fact that I had to set up the store again and again, add the basic structure of styles, look for a package of icons, configure the linter properly, etc. This was quite time-consuming.
19 | And what if a person just starts digging into all this?! Even a week won't be enough to put it all together.
20 |
21 | ## Benefits
22 |
23 | * JWT tokens
24 | * An example of a configured Vuex Store that uses a modular approach
25 | * A very angry linter. Scolds you for styles, layout and scripts.
26 | * Tests that generate a code coverage report
27 | * Ready-made folder structure
28 | * Very cool Material icon pack
29 | * Material Design Bootstrap is a framework of ready-made ui elements
30 | * Basic scss style structure
31 | * Example directive that adds wave animation for buttons
32 | * Multilanguage functionality.
33 | * Example of a service for working with localstorage
34 | * Router settings + middleware
35 |
36 | ## Contents
37 |
38 | **[1. Features](#features)**
39 |
40 | * [JWT functional](#jwt-functional)
41 | * [Sentry](#sentry)
42 | * [Init](#init)
43 | * [Params](#params)
44 | * [Gitlab pages](#gitlab-pages)
45 | * [Generate coverage badges for unit tests](#generate-coverage-badges-for-unit-tests)
46 |
47 | **[2. Run project](#run-project)**
48 |
49 | * [Initialization](#initialization)
50 | * [Run with docker](#run-with-docker)
51 | * [Run without docker](#run-without-docker)
52 |
53 | **[3. Component documentation](#component-documentation)**
54 |
55 | * [Install vuedoc](#install-vuedoc)
56 | * [Using](#using)
57 |
58 | **[4. Project testing](#project-testing)**
59 |
60 | * [Mock server](#mock-server)
61 | * [Folder structure](#folder-structure)
62 | * [Run server](#run-server)
63 | * [Unit tests](#unit-tests)
64 | * [Integration and component tests](#integration-and-component-tests)
65 | * [Folders and files](#folders-and-files)
66 | * [Settings](#settings)
67 | * [Run tests in docker container](#run-tests-in-docker-container)
68 |
69 | **[5. Checking, formatting code](#checking,-formatting-code)**
70 |
71 | * [Linter](#linter)
72 | * [Formatting code](#formatting-code)
73 |
74 | **[6. Run project in production](#run-project-in-production)**
75 |
76 | ## Features
77 |
78 | ### JWT functional
79 |
80 | To work with JWT Token, HTTP client axios (https://www.npmjs.com/package/axios) is used.
81 |
82 | The functionality is implemented in the file src/api/config.js
83 |
84 | REFRESH_URL - endpoint on which the request to update the access-token will be made
85 | RESPONSE_ACCESS_PARAM - name of the key by which the access-token will be saved in the local storage
86 | RESPONSE_REFRESH_PARAM - the name of the key by which the Refresh token will be saved in the local storage
87 | DEFAULT_URL - Default URL for the request in case process.env.REACT_APP_API_URL will be empty
88 |
89 | **Description:**
90 |
91 | An access-token is a token that gives its owner access to secure server resources.
92 | Refresh-token is a token that allows clients to request new access-tokens when their lifetime expires.
93 |
94 | 1. The client is authenticated in the application
95 | 2. If authentication is successful, server sends access and refresh tokens to client, which are stored in Local Storage by RESPONSE_ACCESS_PARAM and RESPONSE_REFRESH_PARAM keys
96 | 3. The client uses the access token to further access the server. Server checks token for validity and gives client access to resources
97 | 4. In case the access token becomes invalid, the client sends a refresh token (at the URL specified in REFRESH_URL), in response to which the server provides two updated tokens. (Which are updated in Local Storage).
98 | 5. If the refresh token expires, the tokens are removed from Local Storage and the client has to authenticate again
99 |
100 |
101 | **There are these methods:**
102 |
103 | 1. queryGet - Used for get requests to the server, where the first parameter is the URL, and the second is request parameters
104 |
105 | ```bash
106 | queryGet('/some-url', {query: 1})
107 | ```
108 |
109 | 2. queryPost - used for request post to the server, where the first parameter is the URL, the second one is data transferred to the server, and the third one is query parameters
110 |
111 | ```bash
112 | queryPost = ('/some-url', {data: 'some_data'}, {query: 1})
113 | ```
114 |
115 | It's possible to add your own queries or create a new instance axios.create.
116 |
117 | ### Sentry
118 |
119 | Sentry is a service for remote error monitoring in web applications.
120 |
121 | #### Init
122 |
123 | 1. Go to https://sentry.io and log in there
124 | 2. Create a new project https://docs.sentry.io/product/sentry-basics/guides/integrate-frontend/create-new-project/
125 | 3. After this step, you will have a dns url, which will be added to the settings in the file [main.js](./src/main.js)
126 | 4. Restart the project. Everything is set up and ready to catch errors.
127 |
128 | #### Params
129 |
130 | * **dns** - the url to which errors will be sent. You will get it when creating a new project in Sentry
131 | * **tracesSampleRate** - number of sent errors as a percentage from 0 to 1. If you need to send 40% of transactions - put 0.4
132 | ### Gitlab pages
133 |
134 | At the beginning of project development, it would be good to have a server to run your site on so that you can show it to the customer or someone else.
135 |
136 | Of course, there are a bunch of different options, but we settled on Giblab pages.
137 |
138 | https://madboiler.gitlab.io/frontend/vue-madboiler/
139 |
140 | In the file [vue.config.js](./vue.config.js), we added a function that determines the correct path to the files in gitlab. But you need to make sure to rewrite it for your project because paths can be different.
141 |
142 | Or use another option for hosting your project.
143 |
144 | To make it all work on gitlab, the file [.gitlab-ci.yml](./.gitlab-ci.yml) was added. Here you can find a block of code that is to deploy the page
145 |
146 | ```bash
147 | pages:
148 | image: node:14.15.4
149 | stage: deploy
150 | script:
151 | - npm run build
152 | - mv public public-vue # GitLab Pages hooks on the public folder
153 | - mv dist public # rename the dist folder (result of npm run build)
154 | # optionally, you can activate gzip support with the following line:
155 | - find public -type f -regex '.*\.\(htm\|html\|txt\|text\|js\|css\)$' -exec gzip -f -k {} \;
156 | artifacts:
157 | paths:
158 | - public # artifact path must be /public for GitLab Pages to pick it up
159 | only:
160 | - master
161 | ```
162 |
163 | The last line of code says that the page will be updated only if changes are sent to the master branch.
164 |
165 | ### Generate coverage badges for unit tests
166 |
167 | Understanding what percentage of the code is covered by tests is always good. This at least shows us how stable the project is.
168 |
169 | To visualize it, a script has been added to generate badges that display the percentage of code coverage.
170 |
171 | 
172 | 
173 | 
174 | 
175 |
176 | Command for generating badges
177 |
178 | ```bash
179 | node ./jest-coverage-badges.js -output './public'
180 | ```
181 |
182 | But it should be understood that in order to generate badges it is necessary to run a command that creates a folder `coverage` with the necessary files.
183 |
184 | To do this, we added a script in the [package.json](./package.json) file that runs both
185 |
186 | ```bash
187 | npm run test:unit:coverage
188 | ```
189 |
190 | After running the command, the badge will sit in the `public` folder.
191 |
192 | ## Run project
193 |
194 | ### Initialization
195 |
196 | The first time you start the project, you need to install the necessary packages
197 |
198 | ```bash
199 | npm install
200 | ```
201 |
202 | After that, execute the initialization command
203 |
204 | ```bash
205 | npm run init
206 | ```
207 |
208 | This command helps to configure the project and to remove unnecessary features.
209 |
210 | [](https://asciinema.org/a/oHRoVQMhlUZzvYuLuZ9kXyKUN)
211 |
212 | After successful setup, the only thing left to do is to run the project and start development.
213 |
214 | ### With docker
215 |
216 | The good thing about this option is that you don't need to install a bunch of npm dependencies on your working machine. The docker encapsulates all this and prevents your system from getting trashed.
217 |
218 | You only need to install [Docker](https://docs.docker.com/get-docker/) and [Docker compose](https://docs.docker.com/compose/install/).
219 |
220 | Run a project for development
221 |
222 | ```bash
223 | npm run docker:dev
224 | ```
225 | After docker builds the container, the site will be available at http://localhost:8080
226 |
227 | You can see the docker setup for development in [dockerfile.dev](./docker/Dockerfile.dev) and in [docker-compose.dev.yml](./docker-compose.dev.yml)
228 |
229 | ### Without docker
230 |
231 | If you don't want to install docker, you can run it without it.
232 |
233 | Run server
234 |
235 | ```bash
236 | npm run serve
237 | ```
238 |
239 | If everything is installed and started correctly, the console will show the result
240 |
241 | ```bash
242 | DONE Compiled successfully in 9320ms
243 |
244 | App running at:
245 | - Local: http://localhost:8080/
246 | - Network: http://192.168.20.242:8080/
247 |
248 | Note that the development build is not optimized.
249 | To create a production build, run npm run build.
250 | ```
251 |
252 | You can spot two references there
253 |
254 | 1. http://localhost:8080 - the link where our site will be available
255 | 2. http://192.168.20.242:8080 - the site will be available at this link, too, so it can be shared internally, for example, to test on your phone or a friend's laptop. The first link will only work on your PC
256 |
257 | ## Component documentation
258 |
259 | A project with well-documented code, in the future, will provide a lower entry threshold for new developers.
260 |
261 | [@vuedoc/md](https://www.npmjs.com/package/@vuedoc/md) the library we will use to document components.
262 |
263 | ### Install vuedoc
264 |
265 | To be able to call the `vuedoc.md` command, it must be installed globally.
266 |
267 | You may need to use the `sudo` command to give global permissions to install the package.
268 |
269 | ```bash
270 | sudo npm install --global @vuedoc/parser @vuedoc/md
271 | ```
272 |
273 | ### Using
274 |
275 | Now, we can document the components.
276 |
277 | Here are a few examples https://gitlab.com/vuedoc/md/-/tree/master/test/fixtures
278 |
279 | After you describe one of components, you can run the command and see the result. Just don't forget to adjust the command to your file and folder structure.
280 |
281 | ```bash
282 | vuedoc.md src/components/COMPONENT_NAME.vue --output docs/components
283 | ```
284 |
285 | ## Project testing
286 |
287 | There are three types of tests available in the project
288 |
289 | 1. **Unit** - they will test specific functions in the code to understand that they work as expected
290 | 2. **Component** - testing individual components. For example, dropdown. You may verify that when you click on it, the list will drop down; when you click on a list item, it will be highlighted, etc.
291 | 3. **Integration** - these tests already test the whole bundle, how it works together.
292 |
293 | ### Mock server
294 |
295 | In order to avoid dependence on the real server, which can fail at any second, we added a mock server, with request spoofing enabled.
296 | This way, we can test the project even without internet access.
297 |
298 | > For this we will use > `https://github.com/typicode/json-server`
299 |
300 | #### Folder structure
301 |
302 | The files for the server are in the `/tests/server` folder.
303 |
304 | * File [server.js](./tests/server/server.js) is the main file for starting the server.
305 | * File [config.js](./tests/server/config.js) - file with options for the server.
306 | * **data** folder - stores files with test data, which will be returned by the json server. All data in the files can be changed.
307 |
308 | #### Run server
309 |
310 | > Original command to run `json-server --watch ./tests/server/server.js`
311 |
312 | ```bash
313 | npm run test:server
314 | ```
315 |
316 | The server will start on the local host and will be available at [http://localhost:8888](http://localhost:8888).
317 |
318 | You should see the following result in the console:
319 |
320 | ```bash
321 | $ npm run test:server
322 |
323 | > vue-madboiler@1.0.0 test:server
324 | > node ./tests/server/server.js
325 |
326 | JSON Server is running on port: 8888
327 | ...
328 | ```
329 |
330 | ### Unit tests
331 |
332 | To run these tests, use [vue-cli-service](https://cli.vuejs.org/guide/cli-service.html), which is already fully configured and ready to go.
333 |
334 | We have two commands
335 |
336 | 1. Running tests.
337 |
338 | ```bash
339 | npm run test:unit
340 | ```
341 |
342 | The tests are located in the `/tests/unit` folder.
343 |
344 |
345 | 2. Generating a report on code coverage by tests
346 |
347 | ```bash
348 | npm run test:unit:coverage
349 | ```
350 |
351 | After running the command, a folder `/coverage` will be created in the root of the project, where the report will be located. This will generate badges, which you can read about [here](#generation-covreage-badges-for-unit-tests)
352 |
353 | To see the report, go to the folder `/coverage/lcov-report` and find the file [index.html](./coverage/lcov-report/index.html) there. This file must be run in the browser. This will open a page with detailed information about code coverage by tests.
354 |
355 | ### Integration and component tests
356 |
357 | For this kind of tests, we use the [cypress](https://www.cypress.io/) framework.
358 |
359 | To test specific components, we use the experimental library `https://docs.cypress.io/guides/component-testing/introduction.html#What-is-Cypress-Component-Testing`.
360 |
361 | The command to start:
362 |
363 | ```bash
364 | npm run test:e2e
365 | ```
366 |
367 | After executing this command:
368 |
369 | 1. The mock server will start
370 | 2. The server for integration and component tests starts
371 | 3. Opens a window with a list of all the tests that you can run and see the process
372 | 4. Then you can start writing tests
373 |
374 | #### Folders and files
375 |
376 | Integration and Component tests are located in the `/tests/e2e` folder.
377 |
378 | ```bash
379 | tests
380 | e2e
381 | components
382 | - UIButton.spec.js
383 | - ...
384 | fixtures
385 | - example.json
386 | - ...
387 | integrations
388 | - Home.spec.js
389 | - ...
390 | plugins
391 | - index.js
392 | support
393 | - commands.js
394 | - index.js
395 | unit
396 | - ...
397 | server
398 | - ...
399 | ```
400 |
401 | * `/tests/e2e/components` - this folder is for component tests.
402 | * `/tests/e2e/integrations` - this is for integration tests.
403 | * More information about folders and files can be found here `https://docs.cypress.io/guides/core-concepts/writing-and-organizing-tests.html#Plugin-files`.
404 |
405 | #### Settings
406 |
407 | The settings file cypress.json is in the root of the project.
408 |
409 | ```bash
410 | {
411 | "baseUrl": "http://localhost:3000",
412 | "chromeWebSecurity": false,
413 | "pluginsFile": "tests/e2e/plugins/index.js",
414 | "supportFile": "tests/e2e/support/index.js",
415 | "fixturesFolder": "tests/e2e/fixtures",
416 | "integrationFolder": "tests/e2e/integrations",
417 | "testFiles": "**/*.spec.js*",
418 | "experimentalComponentTesting": true,
419 | "componentFolder": "tests/e2e/components",
420 | "nodeVersion":"system",
421 | "video": false,
422 | "viewportWidth": 1366,
423 | "viewportHeight": 768
424 | }
425 | ```
426 |
427 | You can read about all the available settings here `https://docs.cypress.io/guides/references/configuration.html#Options`.
428 |
429 | #### Run tests in docker container
430 |
431 | ```bash
432 | npm run docker:test:e2e
433 | ```
434 |
435 | If we get an error like this, then
436 |
437 | ```bash
438 | cypress_1 | ----------
439 | cypress_1 |
440 | cypress_1 | No protocol specified
441 | cypress_1 | [101:0208/050449.746174:ERROR:browser_main_loop.cc(1434)] Unable to open X display.
442 | cypress_1 | The futex facility returned an unexpected error code.
443 | cypress_1 | No protocol specified
444 | cypress_1 | [115:0208/050450.882329:ERROR:browser_main_loop.cc(1434)] Unable to open X display.
445 | cypress_1 |
446 | cypress_1 | undefined:0
447 | cypress_1 |
448 | cypress_1 |
449 | cypress_1 | illegal access
450 | cypress_1 | (Use `Cypress --trace-uncaught ...` to show where the exception was thrown)
451 | cypress_1 |
452 | cypress_1 | ----------
453 | ```
454 |
455 | 1. In the console run this command
456 |
457 | ```bash
458 | xhost +si:localuser:root
459 | ```
460 |
461 | The result should be
462 |
463 | ```bash
464 | localuser:root being added to access control list
465 | ```
466 |
467 | 2. Everything should now run
468 |
469 | ```bash
470 | npm run docker:test:e2e
471 | ```
472 |
473 | ## Checking, formatting code
474 |
475 | ### Linter
476 |
477 | In order to ensure that the project is always written in "one handwriting" so to speak, it is necessary to use a linter in the project.
478 | This will make the code uniform and easy to understand for you and other developers.
479 |
480 | The linter is [eslint](https://eslint.org/) with preset [airbnb](https://github.com/airbnb/javascript).
481 |
482 | The command below will check the `.vue, .js, .scss` files. Also, in the `.vue` files, the block `` will be checked.
483 |
484 | ```bash
485 | npm run lint
486 | ```
487 |
488 | Settings for `.vue, .js` files can be found in [.eslintrc.js](./.eslintrc.js)
489 | Settings for `.scss` files are in [.sass-lint.yml](./.sass-lint.yml)
490 |
491 | ### Formatting code
492 |
493 | It's not always possible to write code neatly and the way the linter requires. To make life easier, [Prettier](https://prettier.io/) was added to the project
494 |
495 | It helps to automatically correct the code and to bring it as close as possible to the form required by the linter
496 |
497 | ```bash
498 | npm run format
499 | ```
500 |
501 | You can see the settings here [.prettierrc](./.prettierrc)
502 |
503 | ## Run project in production
504 |
505 | Once the project is ready for production, it must be properly assembled.
506 |
507 | For this purpose, a docker command was prepared.
508 |
509 | ```bash
510 | npm run docker:prod
511 | ```
512 |
513 | Upon its launch, docker will build the files and launch nginx, which will proxy our index.html
514 |
515 | The page will be available at http://localhost/
516 |
--------------------------------------------------------------------------------
/README-RU.md:
--------------------------------------------------------------------------------
1 | # Vue 3 Mad Boiler
2 |
3 | [](https://maddevs.io?utm_source=github&utm_medium=madboiler)
4 | [](https://opensource.org/licenses/MIT)
5 | 
6 |
7 | 
8 |
9 | ## Переводы документации
10 |
11 | - [English](./README.md)
12 | - Russian
13 |
14 | ## Почему вы должны использовать этот бойлер
15 |
16 | **Vue Mad Boiler - поможет сохранить ваше время, дав готовый, настроенный проект.**
17 |
18 | При старте проекта на vue, я постоянно сталкивался с тем, что приходилось снова и снова настраивать стор, добавлять базовую структуру стилей, искать пакет иконок, настраивать нормально линтер и тд. Это отнимало достаточно времени.
19 | Что уж говорить о человеке, который только начал копаться во всем в этом, там бывает и недели мало, чтобы собрать это всё.
20 |
21 | ## Преимущества
22 |
23 | * Пример настроенного стора, где применяется модульный подход
24 | * Очень злой линтер. Поругает за стили, верстку и скрипты
25 | * Само собой тесты, которые генерят отчет о покрытии кода
26 | * Готовая структура папок. Не нужно ничего придумывать
27 | * Очень крутой пакет Material иконок
28 | * Material Design Bootstrap - фреймворк готовых ui элементов
29 | * Базовая структура scss стилей
30 | * Пример директивы, которая добавляет анимацию волны для кнопок
31 | * Настройка мультиязычности.
32 | * Пример сервиса для работы с localstorage
33 | * Настройки роутера, + middleware
34 | * Функционал JWT Token
35 |
36 | ## Содержание
37 |
38 | **[1. Фичи](#фичи)**
39 |
40 | * [Функционал JWT Token](#функционал-jwt-token)
41 | * [Sentry](#sentry)
42 | * [Первым делом](#первым-делом)
43 | * [Параметры](#параметры)
44 | * [Gitlab pages](#gitlab-pages)
45 | * [Генерация ковредж баджей для юнит тестов](#генерация-ковредж-баджей-для-юнит-тестов)
46 |
47 | **[2. Запуск проекта](#запуск-проекта)**
48 |
49 | * [Инициализация](#инициализация)
50 | * [Запуск через докер](#запуск-через-докер)
51 | * [Запуск без докера](#запуск-без-докера)
52 |
53 | **[3. Документирование компонентов](#документирование-компонентов)**
54 |
55 | * [Установка vuedoc](#установка-vuedoc)
56 | * [Пробуем](#пробуем)
57 |
58 | **[4. Тестирование проекта](#тестирование-проекта)**
59 |
60 | * [Mock server](#mock-server)
61 | * [Структура папок](#структура-папок)
62 | * [Запуск сервера](#запуск-сервера)
63 | * [Unit тесты](#unit-тесты)
64 | * [Интеграционные и Компонентные тесты](#интеграционные-и-компонентные-тесты)
65 | * [Папки и файлы](#папки-и-файлы)
66 | * [Настройка](#настройка)
67 | * [Запуск тестов в докер контейнере](#запуск-тестов-в-докер-контейнере)
68 |
69 | **[5. Проверка, форматирование кода](#проверка,-форматирование-кода)**
70 |
71 | * [Линтер](#линтер)
72 | * [Форматирование кода](#форматирование-кода)
73 |
74 | **[6. Запуск проекта на продакшне](#запуск-проекта-на-продакшне)**
75 |
76 | ## Фичи
77 |
78 | ### Функционал JWT Token
79 |
80 | Для работы с JWT Token использован HTTP client axios (https://www.npmjs.com/package/axios).
81 |
82 | Функционал реализован в файле /api/config.js
83 |
84 | **REFRESH_URL** - эндпоинт по которому будет происходить запрос на обновление Access-token
85 | **RESPONSE_ACCESS_PARAM** - название ключа по которому Access token будет сохраняться в local storage
86 | **RESPONSE_REFRESH_PARAM** - название ключа по которому Refresh token будет сохраняться в local storage
87 | **DEFAULT_URL** - Дефолтный URL по которому будет производиться запрос в случае если process.env.REACT_APP_API_URL будет пустой
88 |
89 | #### Описание:
90 |
91 | **Access-token** — это токен, который предоставляет доступ его владельцу к защищенным ресурсам сервера
92 | **Refresh-token** — это токен, позволяющий клиентам запрашивать новые access-токены по истечении их времени жизни.
93 |
94 | 1. Клиент проходит аутентификацию в приложении
95 | 2. В случае успешной аутентификации сервер отправляет клиенту access- и refresh-токены, которые сохраняются в Local Storage по ключам RESPONSE_ACCESS_PARAM и RESPONSE_REFRESH_PARAM
96 | 3. При дальнейшем обращении к серверу клиент использует access-токен. Сервер проверяет токен на валидность и предоставляет клиенту доступ к ресурсам
97 | 4. В случае, если access-токен становится не валидным, клиент отправляет refresh-токен (по URL указанному в REFRESH_URL), в ответ на который сервер предоставляет два обновленных токена. (Которые обновляются в Local Storage)
98 | 5. В случае, если refresh-токен становится не валидным, то происходит удаление токенов из Local Storage и клиент опять должен пройти процесс аутентификации
99 |
100 |
101 | #### Имеются методы:
102 | 1. **queryGet** - Используется для get запросов к серверу, где первым параметром отправляется URL, вторым параметры запроса
103 |
104 | ```bash
105 | queryGet('/some-url', {query: 1})
106 | ```
107 |
108 | 2. **queryPost** - Используется для post запросов к серверу, где первым параметром отправляется URL, вторым данные передаваемые на сервер, третьим параметры запроса
109 |
110 | ```bash
111 | queryPost = ('/some-url', {data: 'some_data'}, {query: 1})
112 | ```
113 |
114 | Возможно добавление собственных запросов, либо создать новый instance axios.create.
115 |
116 | ### Sentry
117 |
118 | Sentry - сервис для удаленного мониторинга ошибок в веб приложениях.
119 |
120 | #### Первым делом
121 |
122 | 1. Идём на сайт https://sentry.io и авторизуемся
123 | 2. Создаём новый проект https://docs.sentry.io/product/sentry-basics/guides/integrate-frontend/create-new-project/
124 | 3. После этого шага, у вас в распоряжении окажется `dns url`, который добавим в настройках в файле [main.js](./src/main.js)
125 | 4. Перезапускаем проект. По идеи все настроено и готово к отлову ошибок.
126 |
127 | #### Параметры
128 |
129 | * **dns** - урл на который будут отправляться ошибки. Его получите при создании нового проекта в Sentry
130 | * **tracesSampleRate** - количество отправляемых ошибок в процентном соотношении от 0 до 1. Если нужно отправлять 40% транзакций - укажите 0.4
131 |
132 | ### Gitlab pages
133 |
134 | В начале разработки проекта, было бы хорошо иметь сервер, на котором бы крутился ваш сайт. чтобы иметь возможность демонстрировать его заказчику или еще кому-то.
135 |
136 | Конечно, есть куча разных вариантов, но мы остановились на Giblab pages.
137 |
138 | https://madboiler.gitlab.io/frontend/vue-madboiler/
139 |
140 | В файле [vue.config.js](./vue.config.js) добавили функцию, которая определяет правильный путь к файлам в gitlab. Но вам нужно обязательно переписать его под свой проект, так как пути могут быть разные.
141 |
142 | Ну или использовать другой вариант для хостинка вашего проекта.
143 |
144 | Чтобы это всё завелось на gitlab, был добавлен файл [.gitlab-ci.yml](./.gitlab-ci.yml). В нём можно найти блок кода, который отвечает за разветрывание страницы
145 |
146 | ```bash
147 | pages:
148 | image: node:14.15.4
149 | stage: deploy
150 | script:
151 | - npm run build
152 | - mv public public-vue # GitLab Pages hooks on the public folder
153 | - mv dist public # rename the dist folder (result of npm run build)
154 | # optionally, you can activate gzip support with the following line:
155 | - find public -type f -regex '.*\.\(htm\|html\|txt\|text\|js\|css\)$' -exec gzip -f -k {} \;
156 | artifacts:
157 | paths:
158 | - public # artifact path must be /public for GitLab Pages to pick it up
159 | only:
160 | - master
161 | ```
162 |
163 | Последняя строчка кода, говорит о том, что страница будет обновлена только в том случае, если изменения будет залиты в ветку master.
164 |
165 | ### Генерация ковредж баджей для юнит тестов
166 |
167 | Понимать какой процент кода покрыт тестами всегда хорошо. Это как минимум показывает на сколько проект стабилен.
168 |
169 | Чтобы выдеть это наглядно и без особо труда, был добален скрипт для генерации баджей, который отображают процент покрытия.
170 |
171 | 
172 | 
173 | 
174 | 
175 |
176 | Команда для генерации баджей
177 |
178 | ```bash
179 | node ./jest-coverage-badges.js -output './public'
180 | ```
181 |
182 | Но нужно понимать, что для генерации баджей нужно обязательно запустить команду, которая создаст папку `coverage` с нужными файлами.
183 |
184 | Для этого в файле [package.json](./package.json) добавили скрипт, который запускает и то и другое
185 |
186 | ```bash
187 | npm run test:unit:coverage
188 | ```
189 |
190 | После запуска команды, баджи будет лежать в папке `public`.
191 |
192 | ## Запуск проекта
193 |
194 | ### Инициализация
195 |
196 | При первом старте проекта, вам нужно установить необходимые пакеты
197 |
198 | ```bash
199 | npm install
200 | ```
201 |
202 | После чего запкстить команду инициализации
203 |
204 | ```bash
205 | npm run init
206 | ```
207 |
208 | Эта команда поможет настроить проект и удалить не нужные фичи.
209 |
210 | [](https://asciinema.org/a/oHRoVQMhlUZzvYuLuZ9kXyKUN)
211 |
212 | После успешной настройки, останется лишь запустить проект и начинать разработку.
213 |
214 | ### Запуск через докер
215 |
216 | Этот вариант хорош тем, что не нужно устанавливать на вашу рабочую машину кучу npm зависимостей. Докер инкапсулирует весь этот мусор и не позволит загадить вашу систему.
217 |
218 | Для этого потребуется установить только [Docker](https://docs.docker.com/get-docker/) и [Docker compose](https://docs.docker.com/compose/install/).
219 |
220 | Запускаем проект для разработки
221 |
222 | ```bash
223 | npm run docker:dev
224 | ```
225 | После того как докер соберет контейнер, сайт будет доступен по ссылке http://localhost:8080
226 |
227 | Настройки докера для разработки можно посмотреть в [Dockerfile.dev](./docker/Dockerfile.dev) и в [docker-compose.dev.yml](./docker-compose.dev.yml)
228 |
229 | ### Запуск без докера
230 |
231 | Если уж не хочется ставить докер, то запускаем без него.
232 |
233 | Запускаем сервер
234 |
235 | ```bash
236 | npm run serve
237 | ```
238 |
239 | Если всё установилось и нормально запустилось, то в консоле будет такая картина
240 |
241 | ```bash
242 | DONE Compiled successfully in 9320ms
243 |
244 | App running at:
245 | - Local: http://localhost:8080/
246 | - Network: http://192.168.20.242:8080/
247 |
248 | Note that the development build is not optimized.
249 | To create a production build, run npm run build.
250 | ```
251 |
252 | Там можно заметить две ссылки:
253 |
254 | 1. http://localhost:8080 - ссылка по которой будет доступен наш сайт
255 | 2. http://192.168.20.242:8080 - по этой ссылке тоже будет доступен сайт, но так им можно делиться внутри сети(под одним wifi), например, для того, чтобы тестировать на телефоне или у друга на ноуте. Первая же ссылка будет работать только на вашем ПК
256 |
257 | ## Документирование компонентов
258 |
259 | Проект с хорошо задокументированным кодом, в дальнейшем обеспечит более низкий порог входа для новых разработчиков.
260 |
261 | [@vuedoc/md](https://www.npmjs.com/package/@vuedoc/md) библиотека, которую будем использовать для документирования компонентов.
262 |
263 | ### Установка vuedoc
264 |
265 | Чтобы можно было вызвать команду `vuedoc.md`, нужно обязательно установить этот пакет глобально.
266 |
267 | Возможно понадобится использовать команду `sudo`, чтобы дать права на установку пакета глобально.
268 |
269 | ```bash
270 | sudo npm install --global @vuedoc/parser @vuedoc/md
271 | ```
272 |
273 | ### Пробуем
274 |
275 | Теперь, мы можем документировать компоненты.
276 |
277 | Вот несколько примеров https://gitlab.com/vuedoc/md/-/tree/master/test/fixtures
278 |
279 | После того, как описали один и компонентов, можно запускать команду и смореть результат. Только не забудьте поправить команду под вашу структуру файлов и папок.
280 |
281 | ```bash
282 | vuedoc.md src/components/COMPONENT_NAME.vue --output docs/components
283 | ```
284 |
285 | ## Тестирование проекта
286 |
287 | В проекте доступно три вида тестов
288 |
289 | 1. **Unit** - ими будут тестироваться конкретные функции в коде, чтобы понимать, что они работают так, как ожидается
290 | 2. **Component** - тестирование отдельных компонентов, например, дропдаун. Можно проверить, что при нажатии на него выпадает список или при нажатии на элемент списка он выделяется и тд
291 | 3. **Integration** - этими тестами уже проверяется вся связка, то как оно работает вместе.
292 |
293 | ### Mock server
294 |
295 | Чтобы тесты не зависели от реального сервера, который может отказать в любую секунду, был добавлен mock server, с возможностью подмены запросов.
296 | Таким образом мы сможет тестровать проект даже без доступа в интернет.
297 |
298 | > В этом нас поможет > `https://github.com/typicode/json-server`
299 |
300 | #### Структура папок
301 |
302 | Файлы для сервера находятся в папке `/tests/server`.
303 |
304 | * Файл [server.js](./tests/server/server.js) - это основной файл для запуска сервера.
305 | * Файл [config.js](./tests/server/config.js) - файл с опциями для сервера.
306 | * Папка **data** - хранит файлы с тестовыми данными, которые будут возвращены json-сервером. Все данные в файлах могут быть изменены.
307 |
308 | #### Запуск сервера
309 |
310 | > Оригинальная команда для запуска `json-server --watch ./tests/server/server.js`
311 |
312 | ```bash
313 | npm run test:server
314 | ```
315 |
316 | Сервер запустится на локальном хосте и будет доступен по адресу [http://localhost:8888](http://localhost:8888).
317 |
318 | В консоли должен быть виден следующий результат:
319 |
320 | ```bash
321 | $ npm run test:server
322 |
323 | > vue-madboiler@1.0.0 test:server
324 | > node ./tests/server/server.js
325 |
326 | JSON Server is running on port: 8888
327 | ...
328 | ```
329 |
330 | ### Unit тесты
331 |
332 | Для запуска unit тестов используется [vue-cli-service](https://cli.vuejs.org/guide/cli-service.html), который уже полностью настроен и готов к работе.
333 |
334 | Имеем две команды
335 |
336 | 1. Запуск тестов.
337 |
338 | ```bash
339 | npm run test:unit
340 | ```
341 |
342 | Тесты располагаются в папке `/tests/unit`.
343 |
344 |
345 | 2. Генерация отчета о покрытии кода тестами
346 |
347 | ```bash
348 | npm run test:unit:coverage
349 | ```
350 |
351 | После запуска команды, в корне проекта создастся папка `/coverage`, в которой будет лежать отчет. При этот будут сгенерированны баджи, о которых можно почитать [тут](#генерация-ковредж-баджей-для-юнит-тестов)
352 |
353 | Чтобы посмотреть отчёт, перейдем в папку `/coverage/lcov-report` и найдем там файл [index.html](./coverage/lcov-report/index.html). Этот файл нужно запустить в баузере. Откроется страница с подробной инфой о покрытии кода тестами.
354 |
355 | ### Интеграционные и Компонентные тесты
356 |
357 | Для данного вида тестов используем фреймворк [cypress](https://www.cypress.io/).
358 |
359 | Для тестирования конкретных компонентов используем экспериментальную библиотеку `https://docs.cypress.io/guides/component-testing/introduction.html#What-is-Cypress-Component-Testing`.
360 |
361 | Команда для запуска:
362 |
363 | ```bash
364 | npm run test:e2e
365 | ```
366 |
367 | После выполнения этой команды:
368 |
369 | 1. Запустится mock server
370 | 2. Запустится сервер для интеграционных и компонентных тестов
371 | 3. Открывается окно со списком всех тестов, которые можно запустить и видить процесс.
372 | 4. Далее можно приступать к написанию тестов.
373 |
374 | #### Папки и файлы
375 |
376 | Интеграционные и Компонентные тесты находятся в папке `/tests/e2e`.
377 |
378 | ```bash
379 | tests
380 | e2e
381 | components
382 | - UIButton.spec.js
383 | - ...
384 | fixtures
385 | - example.json
386 | - ...
387 | integrations
388 | - Home.spec.js
389 | - ...
390 | plugins
391 | - index.js
392 | support
393 | - commands.js
394 | - index.js
395 | unit
396 | - ...
397 | server
398 | - ...
399 | ```
400 |
401 | * `/tests/e2e/components` - эта папка предназначена для компонентных тестов.
402 | * `/tests/e2e/integrations` - эта для интеграционных.
403 | * Больше информации о папках и файлах можно найти здесь `https://docs.cypress.io/guides/core-concepts/writing-and-organizing-tests.html#Plugin-files`.
404 |
405 | #### Настройка
406 |
407 | Файл настроек cypress.json находится в корне проекта.
408 |
409 | ```bash
410 | {
411 | "baseUrl": "http://localhost:3000",
412 | "chromeWebSecurity": false,
413 | "pluginsFile": "tests/e2e/plugins/index.js",
414 | "supportFile": "tests/e2e/support/index.js",
415 | "fixturesFolder": "tests/e2e/fixtures",
416 | "integrationFolder": "tests/e2e/integrations",
417 | "testFiles": "**/*.spec.js*",
418 | "experimentalComponentTesting": true,
419 | "componentFolder": "tests/e2e/components",
420 | "nodeVersion":"system",
421 | "video": false,
422 | "viewportWidth": 1366,
423 | "viewportHeight": 768
424 | }
425 | ```
426 |
427 | Обо всех доступных настройках можно прочитать здесь `https://docs.cypress.io/guides/references/configuration.html#Options`.
428 |
429 | #### Запуск тестов в докер контейнере
430 |
431 | ```bash
432 | npm run docker:test:e2e
433 | ```
434 |
435 | Если получаем такую ошибку, то
436 |
437 | ```bash
438 | cypress_1 | ----------
439 | cypress_1 |
440 | cypress_1 | No protocol specified
441 | cypress_1 | [101:0208/050449.746174:ERROR:browser_main_loop.cc(1434)] Unable to open X display.
442 | cypress_1 | The futex facility returned an unexpected error code.
443 | cypress_1 | No protocol specified
444 | cypress_1 | [115:0208/050450.882329:ERROR:browser_main_loop.cc(1434)] Unable to open X display.
445 | cypress_1 |
446 | cypress_1 | undefined:0
447 | cypress_1 |
448 | cypress_1 |
449 | cypress_1 | illegal access
450 | cypress_1 | (Use `Cypress --trace-uncaught ...` to show where the exception was thrown)
451 | cypress_1 |
452 | cypress_1 | ----------
453 | ```
454 |
455 | 1. В консоле запустим эту команду
456 |
457 | ```bash
458 | xhost +si:localuser:root
459 | ```
460 |
461 | Результат должен быть таким
462 |
463 | ```bash
464 | localuser:root being added to access control list
465 | ```
466 |
467 | 2. По идеи дальше всё дожно запустить
468 |
469 | ```bash
470 | npm run docker:test:e2e
471 | ```
472 |
473 | ## Проверка, форматирование кода
474 |
475 | ### Линтер
476 |
477 | Для того, чтобы проект был всегда всегда написан так сказать "одним почерком", в проекте необходимо использовать линтер.
478 | Это позволит сделать код единообразным, удобным для восприятия вам и другим разработчиками.
479 |
480 | В качестве линтера используется [eslint](https://eslint.org/) в пресетом [airbnb](https://github.com/airbnb/javascript).
481 |
482 | Команда ниже, запустит проверку `.vue, .js, .scss` файлов. Так же во `.vue` файлах будет проверен блок ``.
483 |
484 | ```bash
485 | npm run lint
486 | ```
487 |
488 | Настройки для `.vue, .js` файлов можно посмотреть в [.eslintrc.js](./.eslintrc.js)
489 | Настройки для `.scss` файлов в [.sass-lint.yml](./.sass-lint.yml)
490 |
491 | ### Форматирование кода
492 |
493 | Не всегда получается писать код аккуратно и так как требует линтер. Чтобы упростить себе жизнь, в проект был добавлен [Prettier](https://prettier.io/)
494 |
495 | Он поможет автоматически поправить код, максимально привести его к той форме, которую требует линтер
496 |
497 | ```bash
498 | npm run format
499 | ```
500 |
501 | Настройки можно посмотреть тут [.prettierrc](./.prettierrc)
502 |
503 | ## Запуск проекта на продакшне
504 |
505 | После того, как проект готов к продакшену его нужно правильно собрать.
506 |
507 | Для этого подготовили докер команду
508 |
509 | ```bash
510 | npm run docker:prod
511 | ```
512 |
513 | После запуска, докер сбилдит файлы, запустит nginx, который будет проксировать наш index.html
514 |
515 | Страница будет доступна по ссылке http://localhost/
516 |
--------------------------------------------------------------------------------