create-arui-scripts-app 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +5 -2
- package/build/build-context.js +18 -1
- package/build/build-file-map.js +21 -1
- package/build/create-program.js +13 -0
- package/build/defaults.d.ts +3 -1
- package/build/defaults.js +5 -1
- package/build/questions.js +23 -0
- package/build/run.js +8 -0
- package/build/templates/app-component.template.js +10 -0
- package/build/templates/app-styles.template.js +37 -4
- package/build/templates/client-entry.template.js +26 -15
- package/build/templates/e2e-cypress.template.d.ts +5 -0
- package/build/templates/e2e-cypress.template.js +52 -0
- package/build/templates/e2e-playwright.template.d.ts +4 -0
- package/build/templates/e2e-playwright.template.js +56 -0
- package/build/templates/misc.template.d.ts +1 -1
- package/build/templates/misc.template.js +54 -3
- package/build/templates/package-json.template.js +8 -0
- package/build/templates/router.template.d.ts +5 -0
- package/build/templates/router.template.js +159 -0
- package/build/templates/server-entry.template.js +25 -7
- package/build/templates/tsconfig.template.js +1 -3
- package/build/types.d.ts +5 -0
- package/build/versions.d.ts +1 -1
- package/build/versions.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# create-arui-scripts-app
|
|
2
2
|
|
|
3
|
+
## 1.2.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#609](https://github.com/core-ds/arui-scripts/pull/609) [`fdc0e343`](https://github.com/core-ds/arui-scripts/commit/fdc0e3433d79884165072aa0f68c94345b574f71) Thanks [@dmitrbrvsk](https://github.com/dmitrbrvsk)! - В шаблоне приложения с Vitest обновлена версия `vitest` до `^5.0.2` и добавлен `vite@^8.3.1`: vitest 5 требует его как peer зависимость
|
|
8
|
+
|
|
9
|
+
## 1.2.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- [#577](https://github.com/core-ds/arui-scripts/pull/577) [`231321b`](https://github.com/core-ds/arui-scripts/commit/231321bf2707abbbd909931cbd358880e488a2b5) Thanks [@dmitrbrvsk](https://github.com/dmitrbrvsk)! - В CLI генерации приложения добавлена возможность выбор e2e фреймворка (Cypress, Playwright) и подключение простого роутинга через React Router
|
|
14
|
+
|
|
3
15
|
## 1.1.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -32,6 +32,7 @@ npx create-arui-scripts-app my-app --yes
|
|
|
32
32
|
- `--ssr` / `--client-only` - SSR или только клиент
|
|
33
33
|
- `--code-loader <swc|babel|tsc>` - транспилятор
|
|
34
34
|
- `--test-runner <jest|vitest>` - тест-раннер
|
|
35
|
+
- `--e2e-framework <cypress|playwright|none>` - e2e фреймворк
|
|
35
36
|
- `--css-modules` / `--no-css-modules` - CSS-модули
|
|
36
37
|
- `--client-port` / `--server-port` - порты клиента и сервера
|
|
37
38
|
- `--docker-registry` / `--presets` - docker registry и preset
|
|
@@ -43,6 +44,8 @@ npx create-arui-scripts-app my-app --yes
|
|
|
43
44
|
- режим **SSR** (клиент + сервер, клиент в `src/client/`) или **clientOnly** (статика)
|
|
44
45
|
- SSR-сервер на **Hapi** с рендерингом приложения (`renderToString`) и гидрацией на клиенте
|
|
45
46
|
- транспилятор (**swc** / babel / tsc) и тест-раннер (**Jest** / Vitest)
|
|
47
|
+
- опционально **e2e** (**Cypress** / Playwright) с конфигом и хелперами
|
|
48
|
+
- опционально **React Router**
|
|
46
49
|
- CSS-модули, полифилы (`core-js`), `experimentalReactCompiler`, docker registry, preset
|
|
47
50
|
- опционально **arui-presets-lint** (eslint, prettier, stylelint, knip, secretlint, lefthook)
|
|
48
51
|
- опциональная установка зависимостей сразу после генерации
|
|
@@ -59,8 +62,8 @@ npx create-arui-scripts-app my-app --yes --lint --install
|
|
|
59
62
|
пример компонента со стилями и тестом, `global-definitions.d.ts`, `.gitignore`, `README.md`,
|
|
60
63
|
`.yarnrc.yml` (с `nodeLinker: node-modules`),
|
|
61
64
|
а также в зависимости от ответов - серверная точка входа на Hapi, store на RTK, полифилы,
|
|
62
|
-
`vitest.config.ts
|
|
63
|
-
`.secretlintrc.json`, `lefthook.yml`).
|
|
65
|
+
`vitest.config.ts`, e2e-каркас (Playwright или Cypress), роуты React Router и конфиги
|
|
66
|
+
`arui-presets-lint` (`eslint.config.mts`, `knip.ts`, `.secretlintrc.json`, `lefthook.yml`).
|
|
64
67
|
|
|
65
68
|
Дальнейшая сборка и запуск через команды `arui-scripts` в созданном проекте.
|
|
66
69
|
См. [документацию arui-scripts](../arui-scripts/README.md).
|
package/build/build-context.js
CHANGED
|
@@ -22,7 +22,11 @@ const VERSIONS = {
|
|
|
22
22
|
jest: '^29.7.0',
|
|
23
23
|
tsJest: '^29.1.0',
|
|
24
24
|
typesJest: '^29.5.0',
|
|
25
|
-
vitest: '^
|
|
25
|
+
vitest: '^5.0.2',
|
|
26
|
+
vite: '^8.3.1',
|
|
27
|
+
playwrightTest: '^1.57.0',
|
|
28
|
+
cypress: '^15.19.0',
|
|
29
|
+
reactRouterDom: '^7.6.0',
|
|
26
30
|
aruiPresetsLint: '^11.0.0',
|
|
27
31
|
};
|
|
28
32
|
function buildContext(answers, aruiScriptsVersion) {
|
|
@@ -50,6 +54,10 @@ function buildContext(answers, aruiScriptsVersion) {
|
|
|
50
54
|
dependencies['@reduxjs/toolkit'] = VERSIONS.reduxToolkit;
|
|
51
55
|
dependencies['react-redux'] = VERSIONS.reactRedux;
|
|
52
56
|
}
|
|
57
|
+
if (answers.useRouter) {
|
|
58
|
+
dependencies['react-router'] = VERSIONS.reactRouterDom;
|
|
59
|
+
dependencies['react-router-dom'] = VERSIONS.reactRouterDom;
|
|
60
|
+
}
|
|
53
61
|
if (answers.polyfills) {
|
|
54
62
|
dependencies['core-js'] = VERSIONS.coreJs;
|
|
55
63
|
}
|
|
@@ -63,6 +71,13 @@ function buildContext(answers, aruiScriptsVersion) {
|
|
|
63
71
|
}
|
|
64
72
|
else {
|
|
65
73
|
devDependencies.vitest = VERSIONS.vitest;
|
|
74
|
+
devDependencies.vite = VERSIONS.vite;
|
|
75
|
+
}
|
|
76
|
+
if (answers.e2eFramework === 'playwright') {
|
|
77
|
+
devDependencies['@playwright/test'] = VERSIONS.playwrightTest;
|
|
78
|
+
}
|
|
79
|
+
else if (answers.e2eFramework === 'cypress') {
|
|
80
|
+
devDependencies.cypress = VERSIONS.cypress;
|
|
66
81
|
}
|
|
67
82
|
if (answers.useLint) {
|
|
68
83
|
devDependencies['arui-presets-lint'] = VERSIONS.aruiPresetsLint;
|
|
@@ -73,6 +88,8 @@ function buildContext(answers, aruiScriptsVersion) {
|
|
|
73
88
|
clientOnly: answers.clientOnly,
|
|
74
89
|
codeLoader: answers.codeLoader,
|
|
75
90
|
testRunner: answers.testRunner,
|
|
91
|
+
e2eFramework: answers.e2eFramework,
|
|
92
|
+
useRouter: answers.useRouter,
|
|
76
93
|
cssModules: answers.cssModules,
|
|
77
94
|
clientServerPort: answers.clientServerPort,
|
|
78
95
|
serverPort: answers.serverPort,
|
package/build/build-file-map.js
CHANGED
|
@@ -7,9 +7,12 @@ const app_styles_template_1 = require("./templates/app-styles.template");
|
|
|
7
7
|
const app_test_template_1 = require("./templates/app-test.template");
|
|
8
8
|
const arui_scripts_config_template_1 = require("./templates/arui-scripts-config.template");
|
|
9
9
|
const client_entry_template_1 = require("./templates/client-entry.template");
|
|
10
|
+
const e2e_cypress_template_1 = require("./templates/e2e-cypress.template");
|
|
11
|
+
const e2e_playwright_template_1 = require("./templates/e2e-playwright.template");
|
|
10
12
|
const lint_template_1 = require("./templates/lint.template");
|
|
11
13
|
const misc_template_1 = require("./templates/misc.template");
|
|
12
14
|
const package_json_template_1 = require("./templates/package-json.template");
|
|
15
|
+
const router_template_1 = require("./templates/router.template");
|
|
13
16
|
const server_entry_template_1 = require("./templates/server-entry.template");
|
|
14
17
|
const store_template_1 = require("./templates/store.template");
|
|
15
18
|
const tsconfig_template_1 = require("./templates/tsconfig.template");
|
|
@@ -23,7 +26,7 @@ function buildFileMap(ctx) {
|
|
|
23
26
|
'package.json': (0, package_json_template_1.packageJsonTemplate)(ctx),
|
|
24
27
|
'arui-scripts.config.ts': (0, arui_scripts_config_template_1.aruiScriptsConfigTemplate)(ctx),
|
|
25
28
|
'tsconfig.json': (0, tsconfig_template_1.tsconfigTemplate)(ctx),
|
|
26
|
-
'.gitignore': (0, misc_template_1.gitignoreTemplate)(),
|
|
29
|
+
'.gitignore': (0, misc_template_1.gitignoreTemplate)(ctx),
|
|
27
30
|
'.yarnrc.yml': (0, misc_template_1.yarnrcTemplate)(),
|
|
28
31
|
'global-definitions.d.ts': (0, misc_template_1.globalDefinitionsTemplate)(),
|
|
29
32
|
'README.md': (0, misc_template_1.readmeTemplate)(ctx),
|
|
@@ -46,11 +49,28 @@ function buildFileMap(ctx) {
|
|
|
46
49
|
files[`${client}/store/hooks.ts`] = (0, store_template_1.storeHooksTemplate)();
|
|
47
50
|
files[`${client}/store/counter-slice.ts`] = (0, store_template_1.counterSliceTemplate)();
|
|
48
51
|
}
|
|
52
|
+
if (ctx.useRouter) {
|
|
53
|
+
files[`${client}/routes.tsx`] = (0, router_template_1.routesTemplate)();
|
|
54
|
+
files[`${client}/components/layout.tsx`] = (0, router_template_1.layoutTemplate)(ctx);
|
|
55
|
+
files[`${client}/pages/home.tsx`] = (0, router_template_1.homePageTemplate)(ctx);
|
|
56
|
+
files[`${client}/pages/about.tsx`] = (0, router_template_1.aboutPageTemplate)(ctx);
|
|
57
|
+
}
|
|
49
58
|
if (ctx.useLint) {
|
|
50
59
|
files['eslint.config.mts'] = (0, lint_template_1.eslintConfigTemplate)();
|
|
51
60
|
files['knip.ts'] = (0, lint_template_1.knipConfigTemplate)();
|
|
52
61
|
files['.secretlintrc.json'] = (0, lint_template_1.secretlintConfigTemplate)();
|
|
53
62
|
files['lefthook.yml'] = (0, lint_template_1.lefthookConfigTemplate)();
|
|
54
63
|
}
|
|
64
|
+
if (ctx.e2eFramework === 'playwright') {
|
|
65
|
+
files['playwright.config.ts'] = (0, e2e_playwright_template_1.playwrightConfigTemplate)(ctx);
|
|
66
|
+
files['e2e/helpers/index.ts'] = (0, e2e_playwright_template_1.playwrightHelpersTemplate)();
|
|
67
|
+
files['e2e/example.spec.ts'] = (0, e2e_playwright_template_1.playwrightExampleSpecTemplate)();
|
|
68
|
+
}
|
|
69
|
+
else if (ctx.e2eFramework === 'cypress') {
|
|
70
|
+
files['cypress.config.ts'] = (0, e2e_cypress_template_1.cypressConfigTemplate)(ctx);
|
|
71
|
+
files['cypress/support/e2e.ts'] = (0, e2e_cypress_template_1.cypressSupportE2eTemplate)();
|
|
72
|
+
files['cypress/support/commands.ts'] = (0, e2e_cypress_template_1.cypressSupportCommandsTemplate)();
|
|
73
|
+
files['cypress/e2e/example.cy.ts'] = (0, e2e_cypress_template_1.cypressExampleSpecTemplate)();
|
|
74
|
+
}
|
|
55
75
|
return files;
|
|
56
76
|
}
|
package/build/create-program.js
CHANGED
|
@@ -31,6 +31,13 @@ function createProgram(onInit = defaultInitHandler) {
|
|
|
31
31
|
.addOption(new commander_1.Option('--ssr', 'Клиент + сервер').conflicts('clientOnly'))
|
|
32
32
|
.addOption(new commander_1.Option('--code-loader <loader>', 'Транспилятор').choices(['swc', 'babel', 'tsc']))
|
|
33
33
|
.addOption(new commander_1.Option('--test-runner <runner>', 'Тест-раннер').choices(['jest', 'vitest']))
|
|
34
|
+
.addOption(new commander_1.Option('--e2e-framework <framework>', 'e2e фреймворк').choices([
|
|
35
|
+
'cypress',
|
|
36
|
+
'playwright',
|
|
37
|
+
'none',
|
|
38
|
+
]))
|
|
39
|
+
.option('--router', 'Подключить React Router')
|
|
40
|
+
.option('--no-router', 'Без React Router')
|
|
34
41
|
.option('--css-modules', 'CSS-модули')
|
|
35
42
|
.option('--no-css-modules', 'Обычный css')
|
|
36
43
|
.option('--client-port <port>', 'Порт dev-сервера', (value) => Number(value))
|
|
@@ -77,6 +84,9 @@ function mapOptsToFlags(opts) {
|
|
|
77
84
|
if (typeof opts.testRunner === 'string') {
|
|
78
85
|
flags.testRunner = opts.testRunner;
|
|
79
86
|
}
|
|
87
|
+
if (typeof opts.e2eFramework === 'string') {
|
|
88
|
+
flags.e2eFramework = opts.e2eFramework;
|
|
89
|
+
}
|
|
80
90
|
if (typeof opts.cssModules === 'boolean') {
|
|
81
91
|
flags.cssModules = opts.cssModules;
|
|
82
92
|
}
|
|
@@ -98,6 +108,9 @@ function mapOptsToFlags(opts) {
|
|
|
98
108
|
if (typeof opts.reactCompiler === 'boolean') {
|
|
99
109
|
flags.reactCompiler = opts.reactCompiler;
|
|
100
110
|
}
|
|
111
|
+
if (typeof opts.router === 'boolean') {
|
|
112
|
+
flags.useRouter = opts.router;
|
|
113
|
+
}
|
|
101
114
|
if (typeof opts.lint === 'boolean') {
|
|
102
115
|
flags.useLint = opts.lint;
|
|
103
116
|
}
|
package/build/defaults.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type CodeLoader, type InitAnswers, type TestRunner } from './types';
|
|
1
|
+
import { type CodeLoader, type E2eFramework, type InitAnswers, type TestRunner } from './types';
|
|
2
2
|
export type CliFlags = {
|
|
3
3
|
yes?: boolean;
|
|
4
4
|
force?: boolean;
|
|
@@ -7,6 +7,8 @@ export type CliFlags = {
|
|
|
7
7
|
clientOnly?: boolean;
|
|
8
8
|
codeLoader?: CodeLoader;
|
|
9
9
|
testRunner?: TestRunner;
|
|
10
|
+
e2eFramework?: E2eFramework;
|
|
11
|
+
useRouter?: boolean;
|
|
10
12
|
cssModules?: boolean;
|
|
11
13
|
clientServerPort?: number;
|
|
12
14
|
serverPort?: number;
|
package/build/defaults.js
CHANGED
|
@@ -10,6 +10,8 @@ function defaultAnswers(name) {
|
|
|
10
10
|
clientOnly: false,
|
|
11
11
|
codeLoader: 'swc',
|
|
12
12
|
testRunner: 'jest',
|
|
13
|
+
e2eFramework: 'none',
|
|
14
|
+
useRouter: false,
|
|
13
15
|
cssModules: true,
|
|
14
16
|
clientServerPort: 8080,
|
|
15
17
|
serverPort: 3000,
|
|
@@ -27,6 +29,8 @@ const ANSWER_FLAG_KEYS = [
|
|
|
27
29
|
'clientOnly',
|
|
28
30
|
'codeLoader',
|
|
29
31
|
'testRunner',
|
|
32
|
+
'e2eFramework',
|
|
33
|
+
'useRouter',
|
|
30
34
|
'cssModules',
|
|
31
35
|
'clientServerPort',
|
|
32
36
|
'serverPort',
|
|
@@ -44,7 +48,7 @@ function hasAnswerFlags(flags) {
|
|
|
44
48
|
function answersFromFlags(defaultName, flags) {
|
|
45
49
|
var _a, _b;
|
|
46
50
|
const base = defaultAnswers(((_a = flags.name) === null || _a === void 0 ? void 0 : _a.trim()) || defaultName);
|
|
47
|
-
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, base), (flags.useRtk === undefined ? {} : { useRtk: flags.useRtk })), (flags.clientOnly === undefined ? {} : { clientOnly: flags.clientOnly })), (flags.codeLoader === undefined ? {} : { codeLoader: flags.codeLoader })), (flags.testRunner === undefined ? {} : { testRunner: flags.testRunner })), (flags.cssModules === undefined ? {} : { cssModules: flags.cssModules })), (flags.clientServerPort === undefined
|
|
51
|
+
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, base), (flags.useRtk === undefined ? {} : { useRtk: flags.useRtk })), (flags.clientOnly === undefined ? {} : { clientOnly: flags.clientOnly })), (flags.codeLoader === undefined ? {} : { codeLoader: flags.codeLoader })), (flags.testRunner === undefined ? {} : { testRunner: flags.testRunner })), (flags.e2eFramework === undefined ? {} : { e2eFramework: flags.e2eFramework })), (flags.useRouter === undefined ? {} : { useRouter: flags.useRouter })), (flags.cssModules === undefined ? {} : { cssModules: flags.cssModules })), (flags.clientServerPort === undefined
|
|
48
52
|
? {}
|
|
49
53
|
: { clientServerPort: flags.clientServerPort })), (flags.serverPort === undefined ? {} : { serverPort: flags.serverPort })), (flags.dockerRegistry === undefined ? {} : { dockerRegistry: flags.dockerRegistry })), (flags.presets === undefined ? {} : { presets: flags.presets })), (flags.polyfills === undefined ? {} : { polyfills: flags.polyfills })), (flags.reactCompiler === undefined ? {} : { reactCompiler: flags.reactCompiler })), (flags.useLint === undefined ? {} : { useLint: flags.useLint })), (flags.install === undefined ? {} : { install: flags.install })), { name: (((_b = flags.name) === null || _b === void 0 ? void 0 : _b.trim()) || defaultName).trim() });
|
|
50
54
|
}
|
package/build/questions.js
CHANGED
|
@@ -57,6 +57,29 @@ function getQuestions(defaultName, prefill = {}) {
|
|
|
57
57
|
{ title: 'Vitest', value: 'vitest', description: 'Быстрее, на базе Vite' },
|
|
58
58
|
],
|
|
59
59
|
},
|
|
60
|
+
{
|
|
61
|
+
type: unlessAnswered('e2eFramework', 'select'),
|
|
62
|
+
name: 'e2eFramework',
|
|
63
|
+
message: 'e2e фреймворк',
|
|
64
|
+
initial: 2,
|
|
65
|
+
choices: [
|
|
66
|
+
{ title: 'Cypress', value: 'cypress', description: 'классический e2e фреймворк' },
|
|
67
|
+
{
|
|
68
|
+
title: 'Playwright',
|
|
69
|
+
value: 'playwright',
|
|
70
|
+
description: 'Быстрый и современный e2e фреймворк',
|
|
71
|
+
},
|
|
72
|
+
{ title: 'Без e2e', value: 'none', description: 'Не подключать e2e' },
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
type: unlessAnswered('useRouter', 'toggle'),
|
|
77
|
+
name: 'useRouter',
|
|
78
|
+
message: 'Подключить React Router',
|
|
79
|
+
initial: false,
|
|
80
|
+
active: 'да',
|
|
81
|
+
inactive: 'нет',
|
|
82
|
+
},
|
|
60
83
|
{
|
|
61
84
|
type: unlessAnswered('cssModules', 'toggle'),
|
|
62
85
|
name: 'cssModules',
|
package/build/run.js
CHANGED
|
@@ -137,6 +137,9 @@ function mergePromptAnswers(base, answers) {
|
|
|
137
137
|
if (answers.testRunner !== undefined) {
|
|
138
138
|
merged.testRunner = answers.testRunner;
|
|
139
139
|
}
|
|
140
|
+
if (answers.e2eFramework !== undefined) {
|
|
141
|
+
merged.e2eFramework = answers.e2eFramework;
|
|
142
|
+
}
|
|
140
143
|
if (answers.cssModules !== undefined) {
|
|
141
144
|
merged.cssModules = Boolean(answers.cssModules);
|
|
142
145
|
}
|
|
@@ -158,6 +161,9 @@ function mergePromptAnswers(base, answers) {
|
|
|
158
161
|
if (answers.reactCompiler !== undefined) {
|
|
159
162
|
merged.reactCompiler = Boolean(answers.reactCompiler);
|
|
160
163
|
}
|
|
164
|
+
if (answers.useRouter !== undefined) {
|
|
165
|
+
merged.useRouter = Boolean(answers.useRouter);
|
|
166
|
+
}
|
|
161
167
|
if (answers.useLint !== undefined) {
|
|
162
168
|
merged.useLint = Boolean(answers.useLint);
|
|
163
169
|
}
|
|
@@ -178,6 +184,8 @@ function printSuccess(context, targetDir, fileCount) {
|
|
|
178
184
|
chalk_1.default.dim(context.clientOnly ? 'clientOnly' : 'SSR'),
|
|
179
185
|
chalk_1.default.dim(context.codeLoader),
|
|
180
186
|
chalk_1.default.dim(context.testRunner),
|
|
187
|
+
...(context.e2eFramework !== 'none' ? [chalk_1.default.dim(context.e2eFramework)] : []),
|
|
188
|
+
...(context.useRouter ? [chalk_1.default.dim('router')] : []),
|
|
181
189
|
...(context.useLint ? [chalk_1.default.dim('lint')] : []),
|
|
182
190
|
].join(chalk_1.default.dim(' · '));
|
|
183
191
|
console.log();
|
|
@@ -5,6 +5,16 @@ function tsString(value) {
|
|
|
5
5
|
return `'${value.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
|
|
6
6
|
}
|
|
7
7
|
function appComponentTemplate(ctx) {
|
|
8
|
+
if (ctx.useRouter) {
|
|
9
|
+
return `import React from 'react';
|
|
10
|
+
|
|
11
|
+
import { AppRoutes } from '../routes';
|
|
12
|
+
|
|
13
|
+
export function App() {
|
|
14
|
+
return <AppRoutes />;
|
|
15
|
+
}
|
|
16
|
+
`;
|
|
17
|
+
}
|
|
8
18
|
const styleImport = ctx.cssModules
|
|
9
19
|
? "import styles from './app.module.css';"
|
|
10
20
|
: "import './app.css';";
|
|
@@ -2,12 +2,29 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.appStylesFileName = appStylesFileName;
|
|
4
4
|
exports.appStylesTemplate = appStylesTemplate;
|
|
5
|
+
const CORE_COMPONENTS_VARS_IMPORT = "@import '@alfalab/core-components/vars';";
|
|
5
6
|
function appStylesFileName(ctx) {
|
|
6
7
|
return ctx.cssModules ? 'app.module.css' : 'app.css';
|
|
7
8
|
}
|
|
8
9
|
function appStylesTemplate(ctx) {
|
|
9
10
|
if (ctx.cssModules) {
|
|
10
|
-
|
|
11
|
+
const nav = ctx.useRouter
|
|
12
|
+
? `
|
|
13
|
+
.nav {
|
|
14
|
+
display: flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
padding: var(--gap-16);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.navLink {
|
|
20
|
+
color: var(--color-light-text-primary);
|
|
21
|
+
text-decoration: none;
|
|
22
|
+
}
|
|
23
|
+
`
|
|
24
|
+
: '';
|
|
25
|
+
return `${CORE_COMPONENTS_VARS_IMPORT}
|
|
26
|
+
|
|
27
|
+
.root {
|
|
11
28
|
padding: var(--gap-16);
|
|
12
29
|
}
|
|
13
30
|
|
|
@@ -15,9 +32,25 @@ function appStylesTemplate(ctx) {
|
|
|
15
32
|
margin: var(--gap-0) var(--gap-0) var(--gap-12);
|
|
16
33
|
@mixin headline_small;
|
|
17
34
|
}
|
|
18
|
-
`;
|
|
35
|
+
${nav}`;
|
|
19
36
|
}
|
|
20
|
-
|
|
37
|
+
const nav = ctx.useRouter
|
|
38
|
+
? `
|
|
39
|
+
.app__nav {
|
|
40
|
+
display: flex;
|
|
41
|
+
align-items: center;
|
|
42
|
+
padding: var(--gap-16);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.app__nav-link {
|
|
46
|
+
color: var(--color-light-text-primary);
|
|
47
|
+
text-decoration: none;
|
|
48
|
+
}
|
|
49
|
+
`
|
|
50
|
+
: '';
|
|
51
|
+
return `${CORE_COMPONENTS_VARS_IMPORT}
|
|
52
|
+
|
|
53
|
+
.app {
|
|
21
54
|
padding: var(--gap-16);
|
|
22
55
|
}
|
|
23
56
|
|
|
@@ -25,5 +58,5 @@ function appStylesTemplate(ctx) {
|
|
|
25
58
|
margin: var(--gap-0) var(--gap-0) var(--gap-12);
|
|
26
59
|
@mixin headline_small;
|
|
27
60
|
}
|
|
28
|
-
`;
|
|
61
|
+
${nav}`;
|
|
29
62
|
}
|
|
@@ -9,14 +9,27 @@ const HMR_BLOCK = `if (process.env.NODE_ENV !== 'production' && module.hot) {
|
|
|
9
9
|
});
|
|
10
10
|
}
|
|
11
11
|
`;
|
|
12
|
+
function wrapWithRouter(jsx, ctx) {
|
|
13
|
+
if (!ctx.useRouter) {
|
|
14
|
+
return jsx;
|
|
15
|
+
}
|
|
16
|
+
return `<BrowserRouter>${jsx}</BrowserRouter>`;
|
|
17
|
+
}
|
|
12
18
|
function clientEntryTemplate(ctx) {
|
|
13
19
|
return ctx.clientOnly ? clientOnlyEntryTemplate(ctx) : ssrEntryTemplate(ctx);
|
|
14
20
|
}
|
|
21
|
+
function routerImport(ctx) {
|
|
22
|
+
return ctx.useRouter ? "\nimport { BrowserRouter } from 'react-router-dom';" : '';
|
|
23
|
+
}
|
|
15
24
|
function ssrEntryTemplate(ctx) {
|
|
16
25
|
if (ctx.useRtk) {
|
|
26
|
+
const appTree = wrapWithRouter(`
|
|
27
|
+
<Provider store={store}>
|
|
28
|
+
<App />
|
|
29
|
+
</Provider>`, ctx);
|
|
17
30
|
return `import React from 'react';
|
|
18
31
|
import { hydrateRoot } from 'react-dom/client';
|
|
19
|
-
import { Provider } from 'react-redux'
|
|
32
|
+
import { Provider } from 'react-redux';${routerImport(ctx)}
|
|
20
33
|
|
|
21
34
|
import { App } from './components/app';
|
|
22
35
|
import { makeStore, type RootState } from './store';
|
|
@@ -31,32 +44,30 @@ const store = makeStore(window.__PRELOADED_STATE__);
|
|
|
31
44
|
const targetElement = document.getElementById('react-app');
|
|
32
45
|
|
|
33
46
|
const root = hydrateRoot(
|
|
34
|
-
targetElement
|
|
35
|
-
<Provider store={store}>
|
|
36
|
-
<App />
|
|
37
|
-
</Provider>,
|
|
47
|
+
targetElement!,${appTree},
|
|
38
48
|
);
|
|
39
49
|
|
|
40
50
|
function render(AppComponent: typeof App) {
|
|
41
|
-
root.render(
|
|
51
|
+
root.render(${wrapWithRouter(`
|
|
42
52
|
<Provider store={store}>
|
|
43
53
|
<AppComponent />
|
|
44
|
-
</Provider
|
|
54
|
+
</Provider>`, ctx)},
|
|
45
55
|
);
|
|
46
56
|
}
|
|
47
57
|
|
|
48
58
|
${HMR_BLOCK}`;
|
|
49
59
|
}
|
|
60
|
+
const appTree = wrapWithRouter('<App />', ctx);
|
|
50
61
|
return `import React from 'react';
|
|
51
|
-
import { hydrateRoot } from 'react-dom/client'
|
|
62
|
+
import { hydrateRoot } from 'react-dom/client';${routerImport(ctx)}
|
|
52
63
|
|
|
53
64
|
import { App } from './components/app';
|
|
54
65
|
|
|
55
66
|
const targetElement = document.getElementById('react-app');
|
|
56
|
-
const root = hydrateRoot(targetElement!,
|
|
67
|
+
const root = hydrateRoot(targetElement!, ${appTree});
|
|
57
68
|
|
|
58
69
|
function render(AppComponent: typeof App) {
|
|
59
|
-
root.render(<AppComponent />);
|
|
70
|
+
root.render(${wrapWithRouter('<AppComponent />', ctx)});
|
|
60
71
|
}
|
|
61
72
|
|
|
62
73
|
${HMR_BLOCK}`;
|
|
@@ -65,7 +76,7 @@ function clientOnlyEntryTemplate(ctx) {
|
|
|
65
76
|
if (ctx.useRtk) {
|
|
66
77
|
return `import React from 'react';
|
|
67
78
|
import { createRoot } from 'react-dom/client';
|
|
68
|
-
import { Provider } from 'react-redux'
|
|
79
|
+
import { Provider } from 'react-redux';${routerImport(ctx)}
|
|
69
80
|
|
|
70
81
|
import { App } from './components/app';
|
|
71
82
|
import { makeStore } from './store';
|
|
@@ -75,10 +86,10 @@ const targetElement = document.getElementById('react-app');
|
|
|
75
86
|
const root = createRoot(targetElement!);
|
|
76
87
|
|
|
77
88
|
function render(AppComponent: typeof App) {
|
|
78
|
-
root.render(
|
|
89
|
+
root.render(${wrapWithRouter(`
|
|
79
90
|
<Provider store={store}>
|
|
80
91
|
<AppComponent />
|
|
81
|
-
</Provider
|
|
92
|
+
</Provider>`, ctx)},
|
|
82
93
|
);
|
|
83
94
|
}
|
|
84
95
|
|
|
@@ -87,7 +98,7 @@ render(App);
|
|
|
87
98
|
${HMR_BLOCK}`;
|
|
88
99
|
}
|
|
89
100
|
return `import React from 'react';
|
|
90
|
-
import { createRoot } from 'react-dom/client'
|
|
101
|
+
import { createRoot } from 'react-dom/client';${routerImport(ctx)}
|
|
91
102
|
|
|
92
103
|
import { App } from './components/app';
|
|
93
104
|
|
|
@@ -95,7 +106,7 @@ const targetElement = document.getElementById('react-app');
|
|
|
95
106
|
const root = createRoot(targetElement!);
|
|
96
107
|
|
|
97
108
|
function render(AppComponent: typeof App) {
|
|
98
|
-
root.render(<AppComponent />);
|
|
109
|
+
root.render(${wrapWithRouter('<AppComponent />', ctx)});
|
|
99
110
|
}
|
|
100
111
|
|
|
101
112
|
render(App);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type TemplateContext } from '../types';
|
|
2
|
+
export declare function cypressConfigTemplate(ctx: TemplateContext): string;
|
|
3
|
+
export declare function cypressSupportE2eTemplate(): string;
|
|
4
|
+
export declare function cypressSupportCommandsTemplate(): string;
|
|
5
|
+
export declare function cypressExampleSpecTemplate(): string;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.cypressConfigTemplate = cypressConfigTemplate;
|
|
4
|
+
exports.cypressSupportE2eTemplate = cypressSupportE2eTemplate;
|
|
5
|
+
exports.cypressSupportCommandsTemplate = cypressSupportCommandsTemplate;
|
|
6
|
+
exports.cypressExampleSpecTemplate = cypressExampleSpecTemplate;
|
|
7
|
+
function baseUrl(ctx) {
|
|
8
|
+
return `http://localhost:${ctx.clientServerPort}`;
|
|
9
|
+
}
|
|
10
|
+
function cypressConfigTemplate(ctx) {
|
|
11
|
+
return `import { defineConfig } from 'cypress';
|
|
12
|
+
|
|
13
|
+
export default defineConfig({
|
|
14
|
+
video: false,
|
|
15
|
+
e2e: {
|
|
16
|
+
baseUrl: '${baseUrl(ctx)}',
|
|
17
|
+
specPattern: 'cypress/e2e/**/*.cy.{ts,tsx}',
|
|
18
|
+
supportFile: 'cypress/support/e2e.ts',
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
`;
|
|
22
|
+
}
|
|
23
|
+
function cypressSupportE2eTemplate() {
|
|
24
|
+
return `import './commands';
|
|
25
|
+
`;
|
|
26
|
+
}
|
|
27
|
+
function cypressSupportCommandsTemplate() {
|
|
28
|
+
return `// Кастомные команды Cypress.
|
|
29
|
+
// Пример:
|
|
30
|
+
// Cypress.Commands.add('login', (email, password) => { ... })
|
|
31
|
+
|
|
32
|
+
export {};
|
|
33
|
+
|
|
34
|
+
declare global {
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
36
|
+
namespace Cypress {
|
|
37
|
+
interface Chainable {
|
|
38
|
+
// login(email: string, password: string): Chainable<void>;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
`;
|
|
43
|
+
}
|
|
44
|
+
function cypressExampleSpecTemplate() {
|
|
45
|
+
return `describe('home page', () => {
|
|
46
|
+
it('loads', () => {
|
|
47
|
+
cy.visit('/');
|
|
48
|
+
cy.get('body').should('be.visible');
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
`;
|
|
52
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.playwrightConfigTemplate = playwrightConfigTemplate;
|
|
4
|
+
exports.playwrightHelpersTemplate = playwrightHelpersTemplate;
|
|
5
|
+
exports.playwrightExampleSpecTemplate = playwrightExampleSpecTemplate;
|
|
6
|
+
function baseUrl(ctx) {
|
|
7
|
+
return `http://localhost:${ctx.clientServerPort}`;
|
|
8
|
+
}
|
|
9
|
+
function playwrightConfigTemplate(ctx) {
|
|
10
|
+
const url = baseUrl(ctx);
|
|
11
|
+
return `import { defineConfig, devices } from '@playwright/test';
|
|
12
|
+
|
|
13
|
+
export default defineConfig({
|
|
14
|
+
testDir: './e2e',
|
|
15
|
+
fullyParallel: true,
|
|
16
|
+
forbidOnly: !!process.env.CI,
|
|
17
|
+
retries: process.env.CI ? 2 : 0,
|
|
18
|
+
workers: process.env.CI ? 1 : undefined,
|
|
19
|
+
reporter: 'html',
|
|
20
|
+
use: {
|
|
21
|
+
baseURL: '${url}',
|
|
22
|
+
trace: 'on-first-retry',
|
|
23
|
+
},
|
|
24
|
+
projects: [
|
|
25
|
+
{
|
|
26
|
+
name: 'chromium',
|
|
27
|
+
use: { ...devices['Desktop Chrome'] },
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
webServer: {
|
|
31
|
+
command: 'yarn start',
|
|
32
|
+
url: '${url}',
|
|
33
|
+
reuseExistingServer: !process.env.CI,
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
`;
|
|
37
|
+
}
|
|
38
|
+
function playwrightHelpersTemplate() {
|
|
39
|
+
return `import { type Page } from '@playwright/test';
|
|
40
|
+
|
|
41
|
+
export async function gotoHome(page: Page): Promise<void> {
|
|
42
|
+
await page.goto('/');
|
|
43
|
+
}
|
|
44
|
+
`;
|
|
45
|
+
}
|
|
46
|
+
function playwrightExampleSpecTemplate() {
|
|
47
|
+
return `import { expect, test } from '@playwright/test';
|
|
48
|
+
|
|
49
|
+
import { gotoHome } from './helpers';
|
|
50
|
+
|
|
51
|
+
test('home page loads', async ({ page }) => {
|
|
52
|
+
await gotoHome(page);
|
|
53
|
+
await expect(page.locator('body')).toBeVisible();
|
|
54
|
+
});
|
|
55
|
+
`;
|
|
56
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type TemplateContext } from '../types';
|
|
2
|
-
export declare function gitignoreTemplate(): string;
|
|
2
|
+
export declare function gitignoreTemplate(ctx?: TemplateContext): string;
|
|
3
3
|
export declare function globalDefinitionsTemplate(): string;
|
|
4
4
|
export declare function polyfillsTemplate(): string;
|
|
5
5
|
export declare function yarnrcTemplate(): string;
|
|
@@ -5,7 +5,23 @@ exports.globalDefinitionsTemplate = globalDefinitionsTemplate;
|
|
|
5
5
|
exports.polyfillsTemplate = polyfillsTemplate;
|
|
6
6
|
exports.yarnrcTemplate = yarnrcTemplate;
|
|
7
7
|
exports.readmeTemplate = readmeTemplate;
|
|
8
|
-
function gitignoreTemplate() {
|
|
8
|
+
function gitignoreTemplate(ctx) {
|
|
9
|
+
let e2eIgnores = '';
|
|
10
|
+
if ((ctx === null || ctx === void 0 ? void 0 : ctx.e2eFramework) === 'playwright') {
|
|
11
|
+
e2eIgnores = `
|
|
12
|
+
test-results/
|
|
13
|
+
playwright-report/
|
|
14
|
+
blob-report/
|
|
15
|
+
playwright/.cache/
|
|
16
|
+
`;
|
|
17
|
+
}
|
|
18
|
+
else if ((ctx === null || ctx === void 0 ? void 0 : ctx.e2eFramework) === 'cypress') {
|
|
19
|
+
e2eIgnores = `
|
|
20
|
+
cypress/videos/
|
|
21
|
+
cypress/screenshots/
|
|
22
|
+
cypress/downloads/
|
|
23
|
+
`;
|
|
24
|
+
}
|
|
9
25
|
return `node_modules
|
|
10
26
|
.build
|
|
11
27
|
build.tar
|
|
@@ -18,7 +34,7 @@ build.tar
|
|
|
18
34
|
!.yarn/releases
|
|
19
35
|
!.yarn/sdks
|
|
20
36
|
!.yarn/versions
|
|
21
|
-
`;
|
|
37
|
+
${e2eIgnores}`;
|
|
22
38
|
}
|
|
23
39
|
function globalDefinitionsTemplate() {
|
|
24
40
|
return `declare module '*.png' {
|
|
@@ -62,6 +78,41 @@ function readmeTemplate(ctx) {
|
|
|
62
78
|
yarn lint - eslint, stylelint, prettier, knip, secretlint
|
|
63
79
|
yarn lint:fix - автофикс eslint/stylelint + format
|
|
64
80
|
yarn format - prettier
|
|
81
|
+
`
|
|
82
|
+
: '';
|
|
83
|
+
let e2eSection = '';
|
|
84
|
+
if (ctx.e2eFramework === 'playwright') {
|
|
85
|
+
e2eSection = `
|
|
86
|
+
## E2E (Playwright)
|
|
87
|
+
|
|
88
|
+
Перед первым запуском установите браузеры:
|
|
89
|
+
|
|
90
|
+
yarn playwright install
|
|
91
|
+
|
|
92
|
+
Команды:
|
|
93
|
+
|
|
94
|
+
yarn e2e - headless-прогон
|
|
95
|
+
yarn e2e:ui - UI-режим Playwright
|
|
96
|
+
|
|
97
|
+
Dev-сервер поднимается автоматически через webServer в playwright.config.ts.
|
|
98
|
+
`;
|
|
99
|
+
}
|
|
100
|
+
else if (ctx.e2eFramework === 'cypress') {
|
|
101
|
+
e2eSection = `
|
|
102
|
+
## E2E (Cypress)
|
|
103
|
+
|
|
104
|
+
Сначала запустите приложение (\`yarn start\`), затем в другом терминале:
|
|
105
|
+
|
|
106
|
+
yarn e2e - headless-прогон
|
|
107
|
+
yarn e2e:open - интерактивный Cypress
|
|
108
|
+
`;
|
|
109
|
+
}
|
|
110
|
+
const routerSection = ctx.useRouter
|
|
111
|
+
? `
|
|
112
|
+
## Маршруты
|
|
113
|
+
|
|
114
|
+
/ - главная (Home)
|
|
115
|
+
/about - о проекте (About)
|
|
65
116
|
`
|
|
66
117
|
: '';
|
|
67
118
|
return `# ${ctx.name}
|
|
@@ -76,5 +127,5 @@ function readmeTemplate(ctx) {
|
|
|
76
127
|
## Установка
|
|
77
128
|
|
|
78
129
|
yarn install
|
|
79
|
-
`;
|
|
130
|
+
${routerSection}${e2eSection}`;
|
|
80
131
|
}
|
|
@@ -31,6 +31,14 @@ function packageJsonTemplate(ctx) {
|
|
|
31
31
|
scripts['lint:fix'] =
|
|
32
32
|
'yarn lint:styles --fix && yarn lint:scripts --fix && yarn format && yarn lint:secrets';
|
|
33
33
|
}
|
|
34
|
+
if (ctx.e2eFramework === 'playwright') {
|
|
35
|
+
scripts.e2e = 'playwright test';
|
|
36
|
+
scripts['e2e:ui'] = 'playwright test --ui';
|
|
37
|
+
}
|
|
38
|
+
else if (ctx.e2eFramework === 'cypress') {
|
|
39
|
+
scripts.e2e = 'cypress run';
|
|
40
|
+
scripts['e2e:open'] = 'cypress open';
|
|
41
|
+
}
|
|
34
42
|
const pkg = {
|
|
35
43
|
name: ctx.name,
|
|
36
44
|
version: '0.1.0',
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type TemplateContext } from '../types';
|
|
2
|
+
export declare function layoutTemplate(ctx: TemplateContext): string;
|
|
3
|
+
export declare function homePageTemplate(ctx: TemplateContext): string;
|
|
4
|
+
export declare function aboutPageTemplate(ctx: TemplateContext): string;
|
|
5
|
+
export declare function routesTemplate(): string;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.layoutTemplate = layoutTemplate;
|
|
4
|
+
exports.homePageTemplate = homePageTemplate;
|
|
5
|
+
exports.aboutPageTemplate = aboutPageTemplate;
|
|
6
|
+
exports.routesTemplate = routesTemplate;
|
|
7
|
+
function tsString(value) {
|
|
8
|
+
return `'${value.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
|
|
9
|
+
}
|
|
10
|
+
function layoutTemplate(ctx) {
|
|
11
|
+
const styleImport = ctx.cssModules
|
|
12
|
+
? "import styles from './app.module.css';"
|
|
13
|
+
: "import './app.css';";
|
|
14
|
+
const navClass = ctx.cssModules ? '{styles.nav}' : "'app__nav'";
|
|
15
|
+
const linkClass = ctx.cssModules ? '{styles.navLink}' : "'app__nav-link'";
|
|
16
|
+
return `import React from 'react';
|
|
17
|
+
import { Link, Outlet } from 'react-router-dom';
|
|
18
|
+
|
|
19
|
+
${styleImport}
|
|
20
|
+
|
|
21
|
+
export function Layout() {
|
|
22
|
+
return (
|
|
23
|
+
<div>
|
|
24
|
+
<nav className=${navClass}>
|
|
25
|
+
<Link to='/' className=${linkClass}>
|
|
26
|
+
Главная
|
|
27
|
+
</Link>
|
|
28
|
+
<span aria-hidden={true}> · </span>
|
|
29
|
+
<Link to='/about' className=${linkClass}>
|
|
30
|
+
О проекте
|
|
31
|
+
</Link>
|
|
32
|
+
</nav>
|
|
33
|
+
<Outlet />
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
`;
|
|
38
|
+
}
|
|
39
|
+
function homePageTemplate(ctx) {
|
|
40
|
+
const styleImport = ctx.cssModules
|
|
41
|
+
? "import styles from '../components/app.module.css';"
|
|
42
|
+
: "import '../components/app.css';";
|
|
43
|
+
const rootClass = ctx.cssModules ? '{styles.root}' : "'app'";
|
|
44
|
+
const titleClass = ctx.cssModules ? '{styles.title}' : "'app__title'";
|
|
45
|
+
const appNameLiteral = tsString(ctx.name);
|
|
46
|
+
const coreImports = `import { Button } from '@alfalab/core-components/button';
|
|
47
|
+
import { Gap } from '@alfalab/core-components/gap';
|
|
48
|
+
import { Typography } from '@alfalab/core-components/typography';`;
|
|
49
|
+
if (ctx.useRtk) {
|
|
50
|
+
return `import React from 'react';
|
|
51
|
+
|
|
52
|
+
${coreImports}
|
|
53
|
+
|
|
54
|
+
import { decrement, increment } from '../store/counter-slice';
|
|
55
|
+
import { useAppDispatch, useAppSelector } from '../store/hooks';
|
|
56
|
+
|
|
57
|
+
${styleImport}
|
|
58
|
+
|
|
59
|
+
const appName = ${appNameLiteral};
|
|
60
|
+
|
|
61
|
+
export function HomePage() {
|
|
62
|
+
const count = useAppSelector((state) => state.counter.value);
|
|
63
|
+
const dispatch = useAppDispatch();
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<div className=${rootClass}>
|
|
67
|
+
<Typography.Title view='medium' tag='h1' className=${titleClass}>
|
|
68
|
+
{appName}
|
|
69
|
+
</Typography.Title>
|
|
70
|
+
<Gap size={16} />
|
|
71
|
+
<Typography.Text view='primary-medium'>Счетчик: {count}</Typography.Text>
|
|
72
|
+
<Gap size={16} />
|
|
73
|
+
<Button view='accent' size={48} onClick={() => dispatch(increment())}>
|
|
74
|
+
+1
|
|
75
|
+
</Button>{' '}
|
|
76
|
+
<Button view='secondary' size={48} onClick={() => dispatch(decrement())}>
|
|
77
|
+
-1
|
|
78
|
+
</Button>
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
`;
|
|
83
|
+
}
|
|
84
|
+
return `import React, { useState } from 'react';
|
|
85
|
+
|
|
86
|
+
${coreImports}
|
|
87
|
+
|
|
88
|
+
${styleImport}
|
|
89
|
+
|
|
90
|
+
const appName = ${appNameLiteral};
|
|
91
|
+
|
|
92
|
+
export function HomePage() {
|
|
93
|
+
const [count, setCount] = useState(0);
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<div className=${rootClass}>
|
|
97
|
+
<Typography.Title view='medium' tag='h1' className=${titleClass}>
|
|
98
|
+
{appName}
|
|
99
|
+
</Typography.Title>
|
|
100
|
+
<Gap size={16} />
|
|
101
|
+
<Typography.Text view='primary-medium'>Счетчик: {count}</Typography.Text>
|
|
102
|
+
<Gap size={16} />
|
|
103
|
+
<Button view='accent' size={48} onClick={() => setCount((prev) => prev + 1)}>
|
|
104
|
+
+1
|
|
105
|
+
</Button>{' '}
|
|
106
|
+
<Button view='secondary' size={48} onClick={() => setCount((prev) => prev - 1)}>
|
|
107
|
+
-1
|
|
108
|
+
</Button>
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
`;
|
|
113
|
+
}
|
|
114
|
+
function aboutPageTemplate(ctx) {
|
|
115
|
+
const styleImport = ctx.cssModules
|
|
116
|
+
? "import styles from '../components/app.module.css';"
|
|
117
|
+
: "import '../components/app.css';";
|
|
118
|
+
const rootClass = ctx.cssModules ? '{styles.root}' : "'app'";
|
|
119
|
+
const titleClass = ctx.cssModules ? '{styles.title}' : "'app__title'";
|
|
120
|
+
return `import React from 'react';
|
|
121
|
+
|
|
122
|
+
import { Typography } from '@alfalab/core-components/typography';
|
|
123
|
+
|
|
124
|
+
${styleImport}
|
|
125
|
+
|
|
126
|
+
export function AboutPage() {
|
|
127
|
+
return (
|
|
128
|
+
<div className=${rootClass}>
|
|
129
|
+
<Typography.Title view='medium' tag='h1' className=${titleClass}>
|
|
130
|
+
О проекте
|
|
131
|
+
</Typography.Title>
|
|
132
|
+
<Typography.Text view='primary-medium'>
|
|
133
|
+
Страница для React Router
|
|
134
|
+
</Typography.Text>
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
`;
|
|
139
|
+
}
|
|
140
|
+
function routesTemplate() {
|
|
141
|
+
return `import React from 'react';
|
|
142
|
+
import { Route, Routes } from 'react-router-dom';
|
|
143
|
+
|
|
144
|
+
import { Layout } from './components/layout';
|
|
145
|
+
import { AboutPage } from './pages/about';
|
|
146
|
+
import { HomePage } from './pages/home';
|
|
147
|
+
|
|
148
|
+
export function AppRoutes() {
|
|
149
|
+
return (
|
|
150
|
+
<Routes>
|
|
151
|
+
<Route element={<Layout />}>
|
|
152
|
+
<Route index={true} element={<HomePage />} />
|
|
153
|
+
<Route path='about' element={<AboutPage />} />
|
|
154
|
+
</Route>
|
|
155
|
+
</Routes>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
`;
|
|
159
|
+
}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.serverEntryTemplate = serverEntryTemplate;
|
|
4
4
|
function serverEntryTemplate(ctx) {
|
|
5
5
|
const reduxImport = ctx.useRtk ? "\nimport { Provider } from 'react-redux';" : '';
|
|
6
|
+
const routerImport = ctx.useRouter ? "\nimport { StaticRouter } from 'react-router';" : '';
|
|
6
7
|
const storeImport = ctx.useRtk ? "\nimport { makeStore } from '../client/store';" : '';
|
|
7
8
|
const renderPageFn = ctx.useRtk
|
|
8
9
|
? `function renderPage(appHtml: string, assets: Assets, preloadedState: string): string {
|
|
@@ -18,24 +19,41 @@ function serverEntryTemplate(ctx) {
|
|
|
18
19
|
|
|
19
20
|
return \`<!DOCTYPE html><html lang="ru"><head><meta charset="utf-8" /><base href="/" />\${css}</head><body><div id="react-app">\${appHtml}</div>\${js}</body></html>\`;
|
|
20
21
|
}`;
|
|
22
|
+
const appJsx = (() => {
|
|
23
|
+
let inner = '<App />';
|
|
24
|
+
if (ctx.useRtk) {
|
|
25
|
+
inner = `<Provider store={store}>
|
|
26
|
+
${inner}
|
|
27
|
+
</Provider>`;
|
|
28
|
+
}
|
|
29
|
+
if (ctx.useRouter) {
|
|
30
|
+
inner = `<StaticRouter location={location}>
|
|
31
|
+
${inner}
|
|
32
|
+
</StaticRouter>`;
|
|
33
|
+
}
|
|
34
|
+
return inner;
|
|
35
|
+
})();
|
|
21
36
|
const handlerBody = ctx.useRtk
|
|
22
37
|
? ` const assets = await readAssetsManifest();
|
|
23
38
|
const store = makeStore();
|
|
24
39
|
const appHtml = renderToString(
|
|
25
|
-
|
|
26
|
-
<App />
|
|
27
|
-
</Provider>,
|
|
40
|
+
${appJsx},
|
|
28
41
|
);
|
|
29
42
|
// Экранируем символ "<", чтобы через состояние нельзя было внедрить теги в HTML
|
|
30
43
|
const preloadedState = JSON.stringify(store.getState()).replace(/</g, '\\\\u003c');
|
|
31
44
|
|
|
32
45
|
return renderPage(appHtml, assets, preloadedState);`
|
|
33
46
|
: ` const assets = await readAssetsManifest();
|
|
34
|
-
const appHtml = renderToString(
|
|
47
|
+
const appHtml = renderToString(${appJsx});
|
|
35
48
|
|
|
36
49
|
return renderPage(appHtml, assets);`;
|
|
50
|
+
const routePath = ctx.useRouter ? '/{path*}' : '/';
|
|
51
|
+
const locationLine = ctx.useRouter
|
|
52
|
+
? `
|
|
53
|
+
const location = request.url.pathname;`
|
|
54
|
+
: '';
|
|
37
55
|
return `import React from 'react';
|
|
38
|
-
import { renderToString } from 'react-dom/server';${reduxImport}
|
|
56
|
+
import { renderToString } from 'react-dom/server';${reduxImport}${routerImport}
|
|
39
57
|
import Hapi from '@hapi/hapi';
|
|
40
58
|
import Inert from '@hapi/inert';
|
|
41
59
|
import path from 'node:path';
|
|
@@ -77,8 +95,8 @@ async function start() {
|
|
|
77
95
|
|
|
78
96
|
server.route({
|
|
79
97
|
method: 'GET',
|
|
80
|
-
path: '
|
|
81
|
-
handler: async () => {
|
|
98
|
+
path: '${routePath}',
|
|
99
|
+
handler: async (${ctx.useRouter ? 'request' : ''}) => {${locationLine}
|
|
82
100
|
${handlerBody}
|
|
83
101
|
},
|
|
84
102
|
});
|
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.tsconfigTemplate = tsconfigTemplate;
|
|
4
4
|
function tsconfigTemplate(ctx) {
|
|
5
|
-
const types = ctx.testRunner === 'jest'
|
|
6
|
-
? '["jest", "node", "webpack-env"]'
|
|
7
|
-
: '["node", "webpack-env"]';
|
|
5
|
+
const types = ctx.testRunner === 'jest' ? '["jest", "node", "webpack-env"]' : '["node", "webpack-env"]';
|
|
8
6
|
return `{
|
|
9
7
|
"extends": "arui-scripts/tsconfig.json",
|
|
10
8
|
"include": ["global-definitions.d.ts", "src/**/*.ts", "src/**/*.tsx"],
|
package/build/types.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
export type CodeLoader = 'swc' | 'babel' | 'tsc';
|
|
2
2
|
export type TestRunner = 'jest' | 'vitest';
|
|
3
|
+
export type E2eFramework = 'cypress' | 'playwright' | 'none';
|
|
3
4
|
export type InitAnswers = {
|
|
4
5
|
name: string;
|
|
5
6
|
useRtk: boolean;
|
|
6
7
|
clientOnly: boolean;
|
|
7
8
|
codeLoader: CodeLoader;
|
|
8
9
|
testRunner: TestRunner;
|
|
10
|
+
e2eFramework: E2eFramework;
|
|
11
|
+
useRouter: boolean;
|
|
9
12
|
cssModules: boolean;
|
|
10
13
|
clientServerPort: number;
|
|
11
14
|
serverPort: number;
|
|
@@ -22,6 +25,8 @@ export type TemplateContext = {
|
|
|
22
25
|
clientOnly: boolean;
|
|
23
26
|
codeLoader: CodeLoader;
|
|
24
27
|
testRunner: TestRunner;
|
|
28
|
+
e2eFramework: E2eFramework;
|
|
29
|
+
useRouter: boolean;
|
|
25
30
|
cssModules: boolean;
|
|
26
31
|
clientServerPort: number;
|
|
27
32
|
serverPort: number;
|
package/build/versions.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/** Версия arui-scripts, которую scaffold кладёт в package.json нового проекта.
|
|
2
2
|
* Генерируется скриптом scripts/sync-arui-scripts-version.js при сборке.
|
|
3
3
|
*/
|
|
4
|
-
export declare const DEFAULT_ARUI_SCRIPTS_VERSION = "
|
|
4
|
+
export declare const DEFAULT_ARUI_SCRIPTS_VERSION = "24.0.0";
|
package/build/versions.js
CHANGED
|
@@ -4,4 +4,4 @@ exports.DEFAULT_ARUI_SCRIPTS_VERSION = void 0;
|
|
|
4
4
|
/** Версия arui-scripts, которую scaffold кладёт в package.json нового проекта.
|
|
5
5
|
* Генерируется скриптом scripts/sync-arui-scripts-version.js при сборке.
|
|
6
6
|
*/
|
|
7
|
-
exports.DEFAULT_ARUI_SCRIPTS_VERSION = '
|
|
7
|
+
exports.DEFAULT_ARUI_SCRIPTS_VERSION = '24.0.0';
|