create-arui-scripts-app 1.0.0 → 1.2.0

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.
Files changed (34) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +15 -3
  3. package/assets/.yarn/releases/yarn-4.9.1.cjs +0 -0
  4. package/build/build-context.js +20 -0
  5. package/build/build-file-map.js +29 -2
  6. package/build/create-program.js +18 -0
  7. package/build/defaults.d.ts +4 -1
  8. package/build/defaults.js +8 -2
  9. package/build/install-dependencies.d.ts +2 -0
  10. package/build/install-dependencies.js +18 -5
  11. package/build/questions.js +31 -0
  12. package/build/run.js +40 -0
  13. package/build/templates/app-component.template.js +20 -7
  14. package/build/templates/app-styles.template.js +43 -12
  15. package/build/templates/arui-scripts-config.template.js +2 -2
  16. package/build/templates/client-entry.template.js +28 -18
  17. package/build/templates/e2e-cypress.template.d.ts +5 -0
  18. package/build/templates/e2e-cypress.template.js +52 -0
  19. package/build/templates/e2e-playwright.template.d.ts +4 -0
  20. package/build/templates/e2e-playwright.template.js +56 -0
  21. package/build/templates/lint.template.d.ts +4 -0
  22. package/build/templates/lint.template.js +52 -0
  23. package/build/templates/misc.template.d.ts +1 -1
  24. package/build/templates/misc.template.js +62 -5
  25. package/build/templates/package-json.template.js +26 -0
  26. package/build/templates/router.template.d.ts +5 -0
  27. package/build/templates/router.template.js +159 -0
  28. package/build/templates/server-entry.template.js +37 -41
  29. package/build/templates/store.template.js +1 -1
  30. package/build/templates/tsconfig.template.js +9 -9
  31. package/build/types.d.ts +7 -0
  32. package/build/versions.d.ts +1 -1
  33. package/build/versions.js +1 -1
  34. package/package.json +55 -55
package/CHANGELOG.md ADDED
@@ -0,0 +1,19 @@
1
+ # create-arui-scripts-app
2
+
3
+ ## 1.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#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
8
+
9
+ ## 1.1.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [#566](https://github.com/core-ds/arui-scripts/pull/566) [`d5de571`](https://github.com/core-ds/arui-scripts/commit/d5de571bf16582ad78258d3b35acbc08d7da42a4) Thanks [@dmitrbrvsk](https://github.com/dmitrbrvsk)! - добавлена возможность подключать пакет arui-presets-lint (https://github.com/core-ds/arui-presets-lint/tree/master/packages/arui-presets-lint)
14
+
15
+ ## 1.0.0
16
+
17
+ ### Major Changes
18
+
19
+ - [#552](https://github.com/core-ds/arui-scripts/pull/552) [`3658e49`](https://github.com/core-ds/arui-scripts/commit/3658e49b0b752c592e738726aa8056569e28e19b) Thanks [@dmitrbrvsk](https://github.com/dmitrbrvsk)! - Добавлен пакет `create-arui-scripts-app` для генерации нового приложения через CLI. В `arui-scripts` CLI был переведен на commander.
package/README.md CHANGED
@@ -32,10 +32,11 @@ 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
38
- - `--polyfills` / `--react-compiler` / `--install` - и соответствующие `--no-*`
39
+ - `--polyfills` / `--react-compiler` / `--lint` / `--install` - и соответствующие `--no-*`
39
40
 
40
41
  ## Что настраивает мастер
41
42
 
@@ -43,16 +44,27 @@ 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
50
+ - опционально **arui-presets-lint** (eslint, prettier, stylelint, knip, secretlint, lefthook)
47
51
  - опциональная установка зависимостей сразу после генерации
48
52
 
53
+ Пример с линтерами:
54
+
55
+ ```bash
56
+ npx create-arui-scripts-app my-app --yes --lint --install
57
+ ```
58
+
49
59
  ## Результат
50
60
 
51
61
  Генерируются `package.json`, `arui-scripts.config.ts`, `tsconfig.json`, клиентская точка входа,
52
62
  пример компонента со стилями и тестом, `global-definitions.d.ts`, `.gitignore`, `README.md`,
53
63
  `.yarnrc.yml` (с `nodeLinker: node-modules`),
54
- а также в зависимости от ответов - серверная точка входа на Hapi, store на RTK, полифилы и
55
- `vitest.config.ts`.
64
+ а также в зависимости от ответов - серверная точка входа на Hapi, store на RTK, полифилы,
65
+ `vitest.config.ts`, e2e-каркас (Playwright или Cypress), роуты React Router и конфиги
66
+ `arui-presets-lint` (`eslint.config.mts`, `knip.ts`, `.secretlintrc.json`, `lefthook.yml`).
56
67
 
57
68
  Дальнейшая сборка и запуск через команды `arui-scripts` в созданном проекте.
58
69
  См. [документацию arui-scripts](../arui-scripts/README.md).
70
+ При подключении lint: `yarn lint` / `yarn lint:fix`.
File without changes
@@ -23,6 +23,10 @@ const VERSIONS = {
23
23
  tsJest: '^29.1.0',
24
24
  typesJest: '^29.5.0',
25
25
  vitest: '^4.1.5',
26
+ playwrightTest: '^1.57.0',
27
+ cypress: '^15.19.0',
28
+ reactRouterDom: '^7.6.0',
29
+ aruiPresetsLint: '^11.0.0',
26
30
  };
27
31
  function buildContext(answers, aruiScriptsVersion) {
28
32
  const dependencies = {
@@ -49,6 +53,10 @@ function buildContext(answers, aruiScriptsVersion) {
49
53
  dependencies['@reduxjs/toolkit'] = VERSIONS.reduxToolkit;
50
54
  dependencies['react-redux'] = VERSIONS.reactRedux;
51
55
  }
56
+ if (answers.useRouter) {
57
+ dependencies['react-router'] = VERSIONS.reactRouterDom;
58
+ dependencies['react-router-dom'] = VERSIONS.reactRouterDom;
59
+ }
52
60
  if (answers.polyfills) {
53
61
  dependencies['core-js'] = VERSIONS.coreJs;
54
62
  }
@@ -63,12 +71,23 @@ function buildContext(answers, aruiScriptsVersion) {
63
71
  else {
64
72
  devDependencies.vitest = VERSIONS.vitest;
65
73
  }
74
+ if (answers.e2eFramework === 'playwright') {
75
+ devDependencies['@playwright/test'] = VERSIONS.playwrightTest;
76
+ }
77
+ else if (answers.e2eFramework === 'cypress') {
78
+ devDependencies.cypress = VERSIONS.cypress;
79
+ }
80
+ if (answers.useLint) {
81
+ devDependencies['arui-presets-lint'] = VERSIONS.aruiPresetsLint;
82
+ }
66
83
  return {
67
84
  name: answers.name,
68
85
  useRtk: answers.useRtk,
69
86
  clientOnly: answers.clientOnly,
70
87
  codeLoader: answers.codeLoader,
71
88
  testRunner: answers.testRunner,
89
+ e2eFramework: answers.e2eFramework,
90
+ useRouter: answers.useRouter,
72
91
  cssModules: answers.cssModules,
73
92
  clientServerPort: answers.clientServerPort,
74
93
  serverPort: answers.serverPort,
@@ -76,6 +95,7 @@ function buildContext(answers, aruiScriptsVersion) {
76
95
  presets: answers.presets.trim(),
77
96
  polyfills: answers.polyfills,
78
97
  reactCompiler: answers.reactCompiler,
98
+ useLint: answers.useLint,
79
99
  aruiScriptsVersion,
80
100
  dependencies,
81
101
  devDependencies,
@@ -7,8 +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");
12
+ const lint_template_1 = require("./templates/lint.template");
10
13
  const misc_template_1 = require("./templates/misc.template");
11
14
  const package_json_template_1 = require("./templates/package-json.template");
15
+ const router_template_1 = require("./templates/router.template");
12
16
  const server_entry_template_1 = require("./templates/server-entry.template");
13
17
  const store_template_1 = require("./templates/store.template");
14
18
  const tsconfig_template_1 = require("./templates/tsconfig.template");
@@ -22,14 +26,14 @@ function buildFileMap(ctx) {
22
26
  'package.json': (0, package_json_template_1.packageJsonTemplate)(ctx),
23
27
  'arui-scripts.config.ts': (0, arui_scripts_config_template_1.aruiScriptsConfigTemplate)(ctx),
24
28
  'tsconfig.json': (0, tsconfig_template_1.tsconfigTemplate)(ctx),
25
- '.gitignore': (0, misc_template_1.gitignoreTemplate)(),
29
+ '.gitignore': (0, misc_template_1.gitignoreTemplate)(ctx),
26
30
  '.yarnrc.yml': (0, misc_template_1.yarnrcTemplate)(),
27
31
  'global-definitions.d.ts': (0, misc_template_1.globalDefinitionsTemplate)(),
28
32
  'README.md': (0, misc_template_1.readmeTemplate)(ctx),
29
33
  [`${client}/index.tsx`]: (0, client_entry_template_1.clientEntryTemplate)(ctx),
30
34
  [`${client}/components/app.tsx`]: (0, app_component_template_1.appComponentTemplate)(ctx),
31
35
  [`${client}/components/${(0, app_styles_template_1.appStylesFileName)(ctx)}`]: (0, app_styles_template_1.appStylesTemplate)(ctx),
32
- [`${client}/components/__tests__/app.test.tsx`]: (0, app_test_template_1.appTestTemplate)(ctx),
36
+ [`${client}/components/__tests__/app.test.ts`]: (0, app_test_template_1.appTestTemplate)(ctx),
33
37
  };
34
38
  if (!ctx.clientOnly) {
35
39
  files['src/server/index.tsx'] = (0, server_entry_template_1.serverEntryTemplate)(ctx);
@@ -45,5 +49,28 @@ function buildFileMap(ctx) {
45
49
  files[`${client}/store/hooks.ts`] = (0, store_template_1.storeHooksTemplate)();
46
50
  files[`${client}/store/counter-slice.ts`] = (0, store_template_1.counterSliceTemplate)();
47
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
+ }
58
+ if (ctx.useLint) {
59
+ files['eslint.config.mts'] = (0, lint_template_1.eslintConfigTemplate)();
60
+ files['knip.ts'] = (0, lint_template_1.knipConfigTemplate)();
61
+ files['.secretlintrc.json'] = (0, lint_template_1.secretlintConfigTemplate)();
62
+ files['lefthook.yml'] = (0, lint_template_1.lefthookConfigTemplate)();
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
+ }
48
75
  return files;
49
76
  }
@@ -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))
@@ -41,6 +48,8 @@ function createProgram(onInit = defaultInitHandler) {
41
48
  .option('--no-polyfills', 'Без полифилов')
42
49
  .option('--react-compiler', 'Включить experimentalReactCompiler')
43
50
  .option('--no-react-compiler', 'Выключить experimentalReactCompiler')
51
+ .option('--lint', 'Подключить arui-presets-lint')
52
+ .option('--no-lint', 'Без arui-presets-lint')
44
53
  .option('--install', 'Установить зависимости после генерации')
45
54
  .option('--no-install', 'Не устанавливать зависимости')
46
55
  .showHelpAfterError('(используйте --help для справки)')
@@ -75,6 +84,9 @@ function mapOptsToFlags(opts) {
75
84
  if (typeof opts.testRunner === 'string') {
76
85
  flags.testRunner = opts.testRunner;
77
86
  }
87
+ if (typeof opts.e2eFramework === 'string') {
88
+ flags.e2eFramework = opts.e2eFramework;
89
+ }
78
90
  if (typeof opts.cssModules === 'boolean') {
79
91
  flags.cssModules = opts.cssModules;
80
92
  }
@@ -96,6 +108,12 @@ function mapOptsToFlags(opts) {
96
108
  if (typeof opts.reactCompiler === 'boolean') {
97
109
  flags.reactCompiler = opts.reactCompiler;
98
110
  }
111
+ if (typeof opts.router === 'boolean') {
112
+ flags.useRouter = opts.router;
113
+ }
114
+ if (typeof opts.lint === 'boolean') {
115
+ flags.useLint = opts.lint;
116
+ }
99
117
  if (typeof opts.install === 'boolean') {
100
118
  flags.install = opts.install;
101
119
  }
@@ -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;
@@ -14,6 +16,7 @@ export type CliFlags = {
14
16
  presets?: string;
15
17
  polyfills?: boolean;
16
18
  reactCompiler?: boolean;
19
+ useLint?: boolean;
17
20
  install?: boolean;
18
21
  };
19
22
  export declare function defaultAnswers(name: string): InitAnswers;
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,
@@ -17,6 +19,7 @@ function defaultAnswers(name) {
17
19
  presets: '',
18
20
  polyfills: false,
19
21
  reactCompiler: false,
22
+ useLint: false,
20
23
  install: false,
21
24
  };
22
25
  }
@@ -26,6 +29,8 @@ const ANSWER_FLAG_KEYS = [
26
29
  'clientOnly',
27
30
  'codeLoader',
28
31
  'testRunner',
32
+ 'e2eFramework',
33
+ 'useRouter',
29
34
  'cssModules',
30
35
  'clientServerPort',
31
36
  'serverPort',
@@ -33,6 +38,7 @@ const ANSWER_FLAG_KEYS = [
33
38
  'presets',
34
39
  'polyfills',
35
40
  'reactCompiler',
41
+ 'useLint',
36
42
  'install',
37
43
  ];
38
44
  function hasAnswerFlags(flags) {
@@ -42,7 +48,7 @@ function hasAnswerFlags(flags) {
42
48
  function answersFromFlags(defaultName, flags) {
43
49
  var _a, _b;
44
50
  const base = defaultAnswers(((_a = flags.name) === null || _a === void 0 ? void 0 : _a.trim()) || defaultName);
45
- 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({}, 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
46
52
  ? {}
47
- : { 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.install === undefined ? {} : { install: flags.install })), { name: (((_b = flags.name) === null || _b === void 0 ? void 0 : _b.trim()) || defaultName).trim() });
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() });
48
54
  }
@@ -1,3 +1,5 @@
1
1
  export type PackageManager = 'yarn' | 'npm';
2
2
  export declare function detectPackageManager(): PackageManager;
3
3
  export declare function installDependencies(targetDir: string, packageManager?: PackageManager): Promise<void>;
4
+ export declare function hasGitRepository(targetDir: string): boolean;
5
+ export declare function installLefthook(targetDir: string): Promise<void>;
@@ -5,17 +5,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.detectPackageManager = detectPackageManager;
7
7
  exports.installDependencies = installDependencies;
8
+ exports.hasGitRepository = hasGitRepository;
9
+ exports.installLefthook = installLefthook;
8
10
  const child_process_1 = require("child_process");
11
+ const fs_1 = __importDefault(require("fs"));
12
+ const path_1 = __importDefault(require("path"));
9
13
  const shelljs_1 = __importDefault(require("shelljs"));
10
14
  function detectPackageManager() {
11
15
  return shelljs_1.default.which('yarn') ? 'yarn' : 'npm';
12
16
  }
13
- function installDependencies(targetDir, packageManager = detectPackageManager()) {
14
- const args = packageManager === 'yarn' ? [] : ['install'];
17
+ function runCommand(command, args, cwd) {
15
18
  return new Promise((resolve, reject) => {
16
19
  var _a, _b;
17
- const child = (0, child_process_1.spawn)(packageManager, args, {
18
- cwd: targetDir,
20
+ const child = (0, child_process_1.spawn)(command, args, {
21
+ cwd,
19
22
  stdio: ['ignore', 'pipe', 'pipe'],
20
23
  // для windows
21
24
  shell: process.platform === 'win32',
@@ -32,8 +35,18 @@ function installDependencies(targetDir, packageManager = detectPackageManager())
32
35
  resolve();
33
36
  }
34
37
  else {
35
- reject(new Error(`${packageManager} ${args.join(' ')} завершился с кодом ${code}\n${output.trim()}`));
38
+ reject(new Error(`${command} ${args.join(' ')} завершился с кодом ${code}\n${output.trim()}`));
36
39
  }
37
40
  });
38
41
  });
39
42
  }
43
+ function installDependencies(targetDir, packageManager = detectPackageManager()) {
44
+ const args = packageManager === 'yarn' ? [] : ['install'];
45
+ return runCommand(packageManager, args, targetDir);
46
+ }
47
+ function hasGitRepository(targetDir) {
48
+ return fs_1.default.existsSync(path_1.default.join(targetDir, '.git'));
49
+ }
50
+ function installLefthook(targetDir) {
51
+ return runCommand('npx', ['--no-install', 'lefthook', 'install'], targetDir);
52
+ }
@@ -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',
@@ -114,6 +137,14 @@ function getQuestions(defaultName, prefill = {}) {
114
137
  active: 'да',
115
138
  inactive: 'нет',
116
139
  },
140
+ {
141
+ type: unlessAnswered('useLint', 'toggle'),
142
+ name: 'useLint',
143
+ message: 'Подключить arui-presets-lint',
144
+ initial: false,
145
+ active: 'да',
146
+ inactive: 'нет',
147
+ },
117
148
  {
118
149
  type: unlessAnswered('install', 'toggle'),
119
150
  name: 'install',
package/build/run.js CHANGED
@@ -67,10 +67,32 @@ function runInit() {
67
67
  spinner.fail(chalk_1.default.red('Не удалось установить зависимости'));
68
68
  throw error;
69
69
  }
70
+ if (initAnswers.useLint) {
71
+ yield tryInstallLefthook(targetDir);
72
+ }
70
73
  }
71
74
  printNextSteps(targetDir, baseCwd, initAnswers, packageManager);
72
75
  });
73
76
  }
77
+ function tryInstallLefthook(targetDir) {
78
+ return __awaiter(this, void 0, void 0, function* () {
79
+ if (!(0, install_dependencies_1.hasGitRepository)(targetDir)) {
80
+ console.log(` ${chalk_1.default.dim('lefthook: пропущено (нет .git). После git init выполните: npx --no-install lefthook install')}`);
81
+ return;
82
+ }
83
+ const spinner = (0, ora_1.default)({ text: 'Устанавливаю git-хуки lefthook…', color: 'cyan' }).start();
84
+ try {
85
+ yield (0, install_dependencies_1.installLefthook)(targetDir);
86
+ spinner.succeed(chalk_1.default.green('Git-хуки lefthook установлены'));
87
+ }
88
+ catch (error) {
89
+ spinner.fail(chalk_1.default.yellow('Не удалось установить lefthook'));
90
+ if (error instanceof Error && error.message) {
91
+ console.log(` ${chalk_1.default.dim(error.message)}`);
92
+ }
93
+ }
94
+ });
95
+ }
74
96
  function resolveAnswers(defaultName, flags) {
75
97
  return __awaiter(this, void 0, void 0, function* () {
76
98
  const base = (0, defaults_1.answersFromFlags)(defaultName, flags);
@@ -115,6 +137,9 @@ function mergePromptAnswers(base, answers) {
115
137
  if (answers.testRunner !== undefined) {
116
138
  merged.testRunner = answers.testRunner;
117
139
  }
140
+ if (answers.e2eFramework !== undefined) {
141
+ merged.e2eFramework = answers.e2eFramework;
142
+ }
118
143
  if (answers.cssModules !== undefined) {
119
144
  merged.cssModules = Boolean(answers.cssModules);
120
145
  }
@@ -136,6 +161,12 @@ function mergePromptAnswers(base, answers) {
136
161
  if (answers.reactCompiler !== undefined) {
137
162
  merged.reactCompiler = Boolean(answers.reactCompiler);
138
163
  }
164
+ if (answers.useRouter !== undefined) {
165
+ merged.useRouter = Boolean(answers.useRouter);
166
+ }
167
+ if (answers.useLint !== undefined) {
168
+ merged.useLint = Boolean(answers.useLint);
169
+ }
139
170
  if (answers.install !== undefined) {
140
171
  merged.install = Boolean(answers.install);
141
172
  }
@@ -153,6 +184,9 @@ function printSuccess(context, targetDir, fileCount) {
153
184
  chalk_1.default.dim(context.clientOnly ? 'clientOnly' : 'SSR'),
154
185
  chalk_1.default.dim(context.codeLoader),
155
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')] : []),
189
+ ...(context.useLint ? [chalk_1.default.dim('lint')] : []),
156
190
  ].join(chalk_1.default.dim(' · '));
157
191
  console.log();
158
192
  console.log(` ${chalk_1.default.green('✔')} ${chalk_1.default.bold('Готово!')} ${chalk_1.default.dim(`проект «${context.name}» создан`)}`);
@@ -181,6 +215,12 @@ function printNextSteps(targetDir, baseCwd, answers, packageManager) {
181
215
  if (!answers.install) {
182
216
  steps.push(installCommand);
183
217
  }
218
+ if (answers.useLint && (!answers.install || !(0, install_dependencies_1.hasGitRepository)(targetDir))) {
219
+ steps.push('npx --no-install lefthook install');
220
+ }
221
+ if (answers.useLint) {
222
+ steps.push(packageManager === 'yarn' ? 'yarn lint' : 'npm run lint');
223
+ }
184
224
  steps.push(startCommand);
185
225
  console.log();
186
226
  console.log(` ${chalk_1.default.bold('Дальше')}`);
@@ -1,42 +1,54 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.appComponentTemplate = appComponentTemplate;
4
+ function tsString(value) {
5
+ return `'${value.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
6
+ }
4
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
+ }
5
18
  const styleImport = ctx.cssModules
6
19
  ? "import styles from './app.module.css';"
7
20
  : "import './app.css';";
8
21
  const rootClass = ctx.cssModules ? '{styles.root}' : "'app'";
9
22
  const titleClass = ctx.cssModules ? '{styles.title}' : "'app__title'";
23
+ const appNameLiteral = tsString(ctx.name);
10
24
  const coreImports = `import { Button } from '@alfalab/core-components/button';
11
25
  import { Gap } from '@alfalab/core-components/gap';
12
26
  import { Typography } from '@alfalab/core-components/typography';`;
13
27
  const view = (count) => ` <div className=${rootClass}>
14
28
  <Typography.Title view='medium' tag='h1' className=${titleClass}>
15
- {${JSON.stringify(ctx.name)}}
29
+ {appName}
16
30
  </Typography.Title>
17
-
18
31
  <Gap size={16} />
19
-
20
32
  <Typography.Text view='primary-medium'>Счетчик: {${count}}</Typography.Text>
21
-
22
33
  <Gap size={16} />`;
23
34
  if (ctx.useRtk) {
24
35
  return `import React from 'react';
25
36
 
26
37
  ${coreImports}
27
38
 
28
- import { useAppDispatch, useAppSelector } from '../store/hooks';
29
39
  import { decrement, increment } from '../store/counter-slice';
40
+ import { useAppDispatch, useAppSelector } from '../store/hooks';
30
41
 
31
42
  ${styleImport}
32
43
 
44
+ const appName = ${appNameLiteral};
45
+
33
46
  export function App() {
34
47
  const count = useAppSelector((state) => state.counter.value);
35
48
  const dispatch = useAppDispatch();
36
49
 
37
50
  return (
38
51
  ${view('count')}
39
-
40
52
  <Button view='accent' size={48} onClick={() => dispatch(increment())}>
41
53
  +1
42
54
  </Button>{' '}
@@ -54,12 +66,13 @@ ${coreImports}
54
66
 
55
67
  ${styleImport}
56
68
 
69
+ const appName = ${appNameLiteral};
70
+
57
71
  export function App() {
58
72
  const [count, setCount] = useState(0);
59
73
 
60
74
  return (
61
75
  ${view('count')}
62
-
63
76
  <Button view='accent' size={48} onClick={() => setCount((prev) => prev + 1)}>
64
77
  +1
65
78
  </Button>{' '}
@@ -2,30 +2,61 @@
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
- return `.root {
11
- padding: 16px;
12
- font-family: sans-serif;
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 {
28
+ padding: var(--gap-16);
13
29
  }
14
30
 
15
31
  .title {
16
- margin: 0 0 12px;
17
- font-size: 24px;
32
+ margin: var(--gap-0) var(--gap-0) var(--gap-12);
33
+ @mixin headline_small;
18
34
  }
19
- `;
35
+ ${nav}`;
20
36
  }
21
- return `.app {
22
- padding: 16px;
23
- font-family: sans-serif;
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 {
54
+ padding: var(--gap-16);
24
55
  }
25
56
 
26
57
  .app__title {
27
- margin: 0 0 12px;
28
- font-size: 24px;
58
+ margin: var(--gap-0) var(--gap-0) var(--gap-12);
59
+ @mixin headline_small;
29
60
  }
30
- `;
61
+ ${nav}`;
31
62
  }
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.aruiScriptsConfigTemplate = aruiScriptsConfigTemplate;
4
- // Безопасная вставка строки в конфиг
4
+ // Безопасная вставка строки в конфиг (одинарные кавычки как у prettier)
5
5
  function tsString(value) {
6
- return JSON.stringify(value);
6
+ return `'${value.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
7
7
  }
8
8
  function aruiScriptsConfigTemplate(ctx) {
9
9
  const lines = [];