Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/nasty-squids-rush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'create-arui-scripts-app': minor
---

добавлена возможность подключать пакет arui-presets-lint (https://github.com/core-ds/arui-presets-lint/tree/master/packages/arui-presets-lint)
15 changes: 12 additions & 3 deletions packages/create-arui-scripts-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ npx create-arui-scripts-app my-app --yes
- `--css-modules` / `--no-css-modules` - CSS-модули
- `--client-port` / `--server-port` - порты клиента и сервера
- `--docker-registry` / `--presets` - docker registry и preset
- `--polyfills` / `--react-compiler` / `--install` - и соответствующие `--no-*`
- `--polyfills` / `--react-compiler` / `--lint` / `--install` - и соответствующие `--no-*`

## Что настраивает мастер

Expand All @@ -44,15 +44,24 @@ npx create-arui-scripts-app my-app --yes
- SSR-сервер на **Hapi** с рендерингом приложения (`renderToString`) и гидрацией на клиенте
- транспилятор (**swc** / babel / tsc) и тест-раннер (**Jest** / Vitest)
- CSS-модули, полифилы (`core-js`), `experimentalReactCompiler`, docker registry, preset
- опционально **arui-presets-lint** (eslint, prettier, stylelint, knip, secretlint, lefthook)
- опциональная установка зависимостей сразу после генерации

Пример с линтерами:

```bash
npx create-arui-scripts-app my-app --yes --lint --install
```

## Результат

Генерируются `package.json`, `arui-scripts.config.ts`, `tsconfig.json`, клиентская точка входа,
пример компонента со стилями и тестом, `global-definitions.d.ts`, `.gitignore`, `README.md`,
`.yarnrc.yml` (с `nodeLinker: node-modules`),
а также в зависимости от ответов - серверная точка входа на Hapi, store на RTK, полифилы и
`vitest.config.ts`.
а также в зависимости от ответов - серверная точка входа на Hapi, store на RTK, полифилы,
`vitest.config.ts` и конфиги `arui-presets-lint` (`eslint.config.mts`, `knip.ts`,
`.secretlintrc.json`, `lefthook.yml`).

Дальнейшая сборка и запуск через команды `arui-scripts` в созданном проекте.
См. [документацию arui-scripts](../arui-scripts/README.md).
При подключении lint: `yarn lint` / `yarn lint:fix`.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const base: InitAnswers = {
presets: '',
polyfills: false,
reactCompiler: false,
useLint: false,
install: false,
};

Expand Down Expand Up @@ -82,4 +83,13 @@ describe('buildContext', () => {
expect(vitestCtx.devDependencies).toHaveProperty('vitest');
expect(vitestCtx.devDependencies).not.toHaveProperty('ts-jest');
});

it('useLint добавляет arui-presets-lint', () => {
const withLint = buildContext({ ...base, useLint: true }, '1.0.0');
const withoutLint = buildContext({ ...base, useLint: false }, '1.0.0');

expect(withLint.devDependencies['arui-presets-lint']).toBe('^11.0.0');
expect(withLint.useLint).toBe(true);
expect(withoutLint.devDependencies).not.toHaveProperty('arui-presets-lint');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const base: InitAnswers = {
presets: '',
polyfills: false,
reactCompiler: false,
useLint: false,
install: false,
};

Expand All @@ -36,7 +37,7 @@ describe('buildFileMap', () => {
'README.md',
'src/client/index.tsx',
'src/client/components/app.tsx',
'src/client/components/__tests__/app.test.tsx',
'src/client/components/__tests__/app.test.ts',
]),
);
});
Expand Down Expand Up @@ -74,7 +75,7 @@ describe('buildFileMap', () => {

expect(ssr['src/client/index.tsx']).toBeDefined();
expect(ssr['src/index.tsx']).toBeUndefined();
expect(ssr['arui-scripts.config.ts']).toContain('clientEntry: "./src/client"');
expect(ssr['arui-scripts.config.ts']).toContain("clientEntry: './src/client'");

const spa = map({ clientOnly: true });

Expand Down Expand Up @@ -121,7 +122,7 @@ describe('buildFileMap', () => {

it('выбранный codeLoader попадает в конфиг', () => {
expect(map({ codeLoader: 'babel' })['arui-scripts.config.ts']).toContain(
'codeLoader: "babel"',
"codeLoader: 'babel'",
);
});

Expand All @@ -144,14 +145,15 @@ describe('buildFileMap', () => {
presets: 'my-preset\\name',
})['arui-scripts.config.ts'];

expect(config).toContain('dockerRegistry: "reg.io/org\'s"');
expect(config).toContain('presets: "my-preset\\\\name"');
expect(config).toContain("dockerRegistry: 'reg.io/org\\'s'");
expect(config).toContain("presets: 'my-preset\\\\name'");
});

it('экранирует имя проекта в JSX', () => {
const app = map({ name: "O'Reilly <App>" })['src/client/components/app.tsx'];

expect(app).toContain(`{${JSON.stringify("O'Reilly <App>")}}`);
expect(app).toContain("const appName = 'O\\'Reilly <App>';");
expect(app).toContain('{appName}');
});

it('App построен на core-components, конфиг подключает тему', () => {
Expand Down Expand Up @@ -187,7 +189,7 @@ describe('buildFileMap', () => {

expect(files['src/client/polyfills.ts']).toBeDefined();
expect(files['arui-scripts.config.ts']).toContain(
'clientPolyfillsEntry: "./src/client/polyfills"',
"clientPolyfillsEntry: './src/client/polyfills'",
);
});

Expand Down Expand Up @@ -217,4 +219,43 @@ describe('buildFileMap', () => {
expect(files['vitest.config.ts']).toBeUndefined();
expect(files['package.json']).toContain('"preset": "arui-scripts"');
});

it('useLint создает конфиги и scripts arui-presets-lint', () => {
const files = map({ useLint: true });
const pkg = JSON.parse(files['package.json']) as {
prettier: string;
stylelint: { extends: string };
commitlint: { extends: string };
scripts: Record<string, string>;
devDependencies: Record<string, string>;
};

expect(files['eslint.config.mts']).toContain('arui-presets-lint/eslint');
expect(files['knip.ts']).toContain("import baseConfig from 'arui-presets-lint/knip'");
expect(files['knip.ts']).toContain("'ts-jest'");
expect(files['.secretlintrc.json']).toContain(
'@secretlint/secretlint-rule-preset-recommend',
);
expect(files['lefthook.yml']).toContain(
'./node_modules/arui-presets-lint/lefthook/index.yml',
);
expect(pkg.prettier).toBe('arui-presets-lint/prettier');
expect(pkg.stylelint.extends).toBe('arui-presets-lint/stylelint');
expect(pkg.commitlint.extends).toBe('./node_modules/arui-presets-lint/commitlint');
expect(pkg.scripts.lint).toContain('yarn lint:scripts');
expect(pkg.scripts['lint:styles']).toBe('arui-presets-lint styles --max-warnings=0');
expect(pkg.scripts['lint:scripts']).toBe('arui-presets-lint scripts --max-warnings=0');
expect(pkg.devDependencies).toHaveProperty('arui-presets-lint');
expect(files['README.md']).toContain('yarn lint');
});

it('без useLint не создает lint-конфиги', () => {
const files = map({ useLint: false });

expect(files['eslint.config.mts']).toBeUndefined();
expect(files['knip.ts']).toBeUndefined();
expect(files['.secretlintrc.json']).toBeUndefined();
expect(files['lefthook.yml']).toBeUndefined();
expect(files['package.json']).not.toContain('arui-presets-lint');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ describe('createProgram', () => {
});

it('позитивные флаги дают true', async () => {
const flags = await parseFlags(['--rtk', '--css-modules', '--install']);
const flags = await parseFlags(['--rtk', '--css-modules', '--lint', '--install']);

expect(flags.useRtk).toBe(true);
expect(flags.cssModules).toBe(true);
expect(flags.useLint).toBe(true);
expect(flags.install).toBe(true);
});

Expand All @@ -35,13 +36,15 @@ describe('createProgram', () => {
'--no-css-modules',
'--no-polyfills',
'--no-react-compiler',
'--no-lint',
'--no-install',
]);

expect(flags.useRtk).toBe(false);
expect(flags.cssModules).toBe(false);
expect(flags.polyfills).toBe(false);
expect(flags.reactCompiler).toBe(false);
expect(flags.useLint).toBe(false);
expect(flags.install).toBe(false);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jest.mock('child_process', () => ({
}));

// eslint-disable-next-line import/first
import { installDependencies } from '../install-dependencies';
import { installDependencies, installLefthook } from '../install-dependencies';

function fakeChild(exitCode: number) {
const child = new EventEmitter() as EventEmitter & {
Expand Down Expand Up @@ -72,4 +72,17 @@ describe('installDependencies', () => {
/кодом 1[\s\S]*some output/,
);
});

it('installLefthook вызывает npx --no-install lefthook install', async () => {
setPlatform('linux');
spawnMock.mockImplementation(() => fakeChild(0));

await installLefthook('/target');

expect(spawnMock).toHaveBeenCalledWith(
'npx',
['--no-install', 'lefthook', 'install'],
expect.objectContaining({ cwd: '/target', shell: false }),
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ describe('getQuestions', () => {
'', // presets
false, // polyfills
false, // reactCompiler
false, // useLint
false, // install
]);

Expand All @@ -41,6 +42,7 @@ describe('getQuestions', () => {
'', // presets
false, // polyfills
false, // reactCompiler
false, // useLint
false, // install
]);

Expand All @@ -62,6 +64,7 @@ describe('getQuestions', () => {
'', // presets
false, // polyfills
false, // reactCompiler
false, // useLint
false, // install
]);

Expand Down Expand Up @@ -91,12 +94,14 @@ describe('getQuestions', () => {
'', // presets
false, // polyfills
false, // reactCompiler
true, // useLint
false, // install
]);

const answers = await prompts(getQuestions('app', { serverPort: 3005 }));

expect(answers.clientOnly).toBe(false);
expect(answers.serverPort).toBeUndefined();
expect(answers.useLint).toBe(true);
});
});
3 changes: 2 additions & 1 deletion packages/create-arui-scripts-app/src/__tests__/run.tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ describe('runInit', () => {
'', // presets
false, // polyfills
false, // reactCompiler
false, // useLint
false, // install
]);

Expand All @@ -137,7 +138,7 @@ describe('runInit', () => {
const config = await fs.readFile(path.join(target, 'arui-scripts.config.ts'), 'utf8');

expect(config).toContain('clientOnly: true');
expect(config).toContain('codeLoader: "babel"');
expect(config).toContain("codeLoader: 'babel'");
expect(config).toContain('clientServerPort: 8081');
});

Expand Down
6 changes: 6 additions & 0 deletions packages/create-arui-scripts-app/src/build-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const VERSIONS = {
tsJest: '^29.1.0',
typesJest: '^29.5.0',
vitest: '^4.1.5',
aruiPresetsLint: '^11.0.0',
} as const;

export function buildContext(answers: InitAnswers, aruiScriptsVersion: string): TemplateContext {
Expand Down Expand Up @@ -69,6 +70,10 @@ export function buildContext(answers: InitAnswers, aruiScriptsVersion: string):
devDependencies.vitest = VERSIONS.vitest;
}

if (answers.useLint) {
devDependencies['arui-presets-lint'] = VERSIONS.aruiPresetsLint;
}

return {
name: answers.name,
useRtk: answers.useRtk,
Expand All @@ -82,6 +87,7 @@ export function buildContext(answers: InitAnswers, aruiScriptsVersion: string):
presets: answers.presets.trim(),
polyfills: answers.polyfills,
reactCompiler: answers.reactCompiler,
useLint: answers.useLint,
aruiScriptsVersion,
dependencies,
devDependencies,
Expand Down
15 changes: 14 additions & 1 deletion packages/create-arui-scripts-app/src/build-file-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ import { appStylesFileName, appStylesTemplate } from './templates/app-styles.tem
import { appTestTemplate } from './templates/app-test.template';
import { aruiScriptsConfigTemplate } from './templates/arui-scripts-config.template';
import { clientEntryTemplate } from './templates/client-entry.template';
import {
eslintConfigTemplate,
knipConfigTemplate,
lefthookConfigTemplate,
secretlintConfigTemplate,
} from './templates/lint.template';
import {
gitignoreTemplate,
globalDefinitionsTemplate,
Expand Down Expand Up @@ -39,7 +45,7 @@ export function buildFileMap(ctx: TemplateContext): Record<string, string> {
[`${client}/index.tsx`]: clientEntryTemplate(ctx),
[`${client}/components/app.tsx`]: appComponentTemplate(ctx),
[`${client}/components/${appStylesFileName(ctx)}`]: appStylesTemplate(ctx),
[`${client}/components/__tests__/app.test.tsx`]: appTestTemplate(ctx),
[`${client}/components/__tests__/app.test.ts`]: appTestTemplate(ctx),
};

if (!ctx.clientOnly) {
Expand All @@ -60,5 +66,12 @@ export function buildFileMap(ctx: TemplateContext): Record<string, string> {
files[`${client}/store/counter-slice.ts`] = counterSliceTemplate();
}

if (ctx.useLint) {
files['eslint.config.mts'] = eslintConfigTemplate();
files['knip.ts'] = knipConfigTemplate();
files['.secretlintrc.json'] = secretlintConfigTemplate();
files['lefthook.yml'] = lefthookConfigTemplate();
}

return files;
}
6 changes: 6 additions & 0 deletions packages/create-arui-scripts-app/src/create-program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ export function createProgram(onInit: InitHandler = defaultInitHandler): Command
.option('--no-polyfills', 'Без полифилов')
.option('--react-compiler', 'Включить experimentalReactCompiler')
.option('--no-react-compiler', 'Выключить experimentalReactCompiler')
.option('--lint', 'Подключить arui-presets-lint')
.option('--no-lint', 'Без arui-presets-lint')
.option('--install', 'Установить зависимости после генерации')
.option('--no-install', 'Не устанавливать зависимости')
.showHelpAfterError('(используйте --help для справки)')
Expand Down Expand Up @@ -111,6 +113,10 @@ export function mapOptsToFlags(opts: Record<string, unknown>): CliFlags {
flags.reactCompiler = opts.reactCompiler;
}

if (typeof opts.lint === 'boolean') {
flags.useLint = opts.lint;
}

if (typeof opts.install === 'boolean') {
flags.install = opts.install;
}
Expand Down
4 changes: 4 additions & 0 deletions packages/create-arui-scripts-app/src/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type CliFlags = {
presets?: string;
polyfills?: boolean;
reactCompiler?: boolean;
useLint?: boolean;
install?: boolean;
};

Expand All @@ -32,6 +33,7 @@ export function defaultAnswers(name: string): InitAnswers {
presets: '',
polyfills: false,
reactCompiler: false,
useLint: false,
install: false,
};
}
Expand All @@ -49,6 +51,7 @@ const ANSWER_FLAG_KEYS: Array<keyof CliFlags> = [
'presets',
'polyfills',
'reactCompiler',
'useLint',
'install',
];

Expand All @@ -75,6 +78,7 @@ export function answersFromFlags(defaultName: string, flags: CliFlags): InitAnsw
...(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: (flags.name?.trim() || defaultName).trim(),
};
Expand Down
Loading
Loading