@vyriy/tooling 0.8.2

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/AGENTS.md ADDED
@@ -0,0 +1,102 @@
1
+ # Project Agent Guide
2
+
3
+ This repository follows a calm engineering style: changes should be explicit, reusable, typed, documented, tested, and easy to reason about.
4
+
5
+ Use this guide as the default behavior for AI agents and contributors working in this repository. Prefer local package conventions when they are more specific than this document.
6
+
7
+ ## Core Principles
8
+
9
+ - Prefer simple modules over clever frameworks or hidden conventions.
10
+ - Keep package and project boundaries explicit.
11
+ - Avoid project-specific coupling in reusable code.
12
+ - Extract only proven reusable behavior.
13
+ - Keep public APIs small, typed, documented, and stable.
14
+ - Prefer SSR-friendly and SSG-friendly code paths when working with frontend or shared code.
15
+ - Keep integrations replaceable and avoid hard coupling to a CMS, framework, vendor, or runtime host.
16
+ - Prefer infrastructure assumptions that are easy to deploy, observe, and replace.
17
+ - Prefer the option that is simpler to explain, easier to evolve, and calmer to maintain.
18
+
19
+ ## File Shape
20
+
21
+ - Prefer one exported runtime method, component, helper, or class per production file when it stays readable.
22
+ - Prefer one matching test file per production file, for example `feature.ts` and `feature.test.ts`.
23
+ - Use focused folders when behavior naturally splits into several related files.
24
+ - Keep `index.ts` as a public re-export surface only. Do not place implementation logic in it.
25
+ - Use relative import and export specifiers that match the package module style.
26
+ - Use `.js` relative specifiers in TypeScript source for ESM/NodeNext packages.
27
+ - Add `types.ts` when public shared types are part of the package contract.
28
+ - Keep constants near the code that owns them unless they are shared or clarify repeated behavior.
29
+
30
+ ## Public Surface
31
+
32
+ - Every new public export must be re-exported from the package or module public entry point.
33
+ - Add or update public-surface tests when exports change.
34
+ - Add JSDoc for public exports when behavior, parameters, return values, or usage expectations need explanation.
35
+ - Avoid exporting internal helpers only to make tests easier.
36
+ - Do not hand-maintain package `exports` maps unless the project has a real custom publishing need.
37
+
38
+ ## Tests
39
+
40
+ - Cover public behavior and meaningful edge cases.
41
+ - Prefer behavior-focused tests over private implementation lock-in.
42
+ - Keep tests deterministic.
43
+ - Avoid real network, filesystem, timers, browser, or cloud dependencies unless the behavior specifically requires them.
44
+ - When mocking modules, install mocks before loading the module under test.
45
+ - Use focused validation when changing behavior.
46
+
47
+ Example validation commands:
48
+
49
+ ```bash
50
+ yarn test
51
+ ```
52
+
53
+ For workspaces, prefer the project convention, for example:
54
+
55
+ ```bash
56
+ yarn workspace <package-name> test
57
+ ```
58
+
59
+ For Jest-based packages, focused validation may look like:
60
+
61
+ ```bash
62
+ yarn jest packages/<package> --runInBand --coverage=false
63
+ ```
64
+
65
+ ## Documentation
66
+
67
+ - Keep `README.md` concise and usage-oriented.
68
+ - Start package READMEs with `# <package>`.
69
+ - Document real public exports, supported options, and examples that actually work.
70
+ - Update docs when public behavior changes.
71
+ - Keep generated docs wrappers, such as `doc.mdx`, aligned with the README when the project uses them.
72
+ - For component packages, include visual documentation or stories for supported states and common usage.
73
+
74
+ ## Components
75
+
76
+ - Prefer lightweight React components with TypeScript when working in React packages.
77
+ - Keep components SSR-friendly and avoid browser globals during render.
78
+ - Prefer composable props and predictable ergonomics.
79
+ - Put each public component in its own file with a matching test.
80
+ - Add stories or examples when a component has visual states, variants, or interaction states.
81
+ - Keep styling explicit and reusable. Avoid hidden theme assumptions unless they are part of the package contract.
82
+
83
+ ## Change Discipline
84
+
85
+ - Keep changes scoped to the requested behavior.
86
+ - Avoid unrelated refactors and metadata churn.
87
+ - Sync implementation, tests, docs, examples, and public re-exports together.
88
+ - Do not introduce new dependencies unless they clearly reduce complexity or are already part of the project direction.
89
+ - Prefer small, reviewable changes over broad rewrites.
90
+ - Preserve existing conventions unless there is a clear reason to change them.
91
+
92
+ ## Before Finishing
93
+
94
+ Check that the change is complete:
95
+
96
+ - Public exports are updated.
97
+ - Public-surface tests are updated when exports change.
98
+ - Matching unit tests exist for new behavior.
99
+ - README examples still match the real API.
100
+ - Visual docs, stories, or examples are updated for visible component behavior.
101
+ - TypeScript imports follow the package module style.
102
+ - No unrelated files, formatting churn, or generated artifacts were changed.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vyriy contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # @vyriy/tooling
2
+
3
+ Part of [Vyriy](https://vyriy.dev) - a calm architecture toolkit for TypeScript, React, SSR, SSG, APIs, and cloud-ready apps.
4
+
5
+ Full documentation: https://vyriy.dev/docs/tooling/
6
+
7
+ CLI for generating thin local tooling config files that delegate to shared Vyriy config packages.
8
+
9
+ ## CLI
10
+
11
+ Install globally:
12
+
13
+ ```bash
14
+ npm install --global @vyriy/tooling
15
+ ```
16
+
17
+ Generate default configs:
18
+
19
+ ```bash
20
+ vyriy-tooling
21
+ vyriy-tooling init
22
+ vt init
23
+ ```
24
+
25
+ Generate one config:
26
+
27
+ ```bash
28
+ vyriy-tooling typescript
29
+ vyriy-tooling eslint
30
+ vyriy-tooling prettier
31
+ vyriy-tooling jest
32
+ vyriy-tooling storybook
33
+ vyriy-tooling stylelint
34
+ ```
35
+
36
+ CLI flags:
37
+
38
+ - `--force` overwrites existing config files.
39
+ - `--dry-run` prints files that would be created without writing them.
40
+ - `--help` or `-h` prints command help.
41
+ - `--version` or `-v` prints the package version.
42
+
43
+ After writing files, the CLI prints missing Vyriy config packages that should be installed in the target project.
package/bin/index.js ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { runToolingCli } from '../index.js';
3
+ await runToolingCli(process.argv.slice(2));
@@ -0,0 +1,14 @@
1
+ import type { ConfigName } from './types.js';
2
+ export type ToolingConfigExtension = 'js' | 'ts';
3
+ export type ToolingConfigFilesOptions = {
4
+ readonly extension?: ToolingConfigExtension;
5
+ readonly names?: readonly ConfigName[];
6
+ readonly storybookPreviewSpecifier?: 'extensionless' | 'js';
7
+ readonly storybookPreviewStyleImport?: string;
8
+ readonly storybookStories?: 'path' | 'relative';
9
+ readonly storybookMainContent?: string;
10
+ readonly storybookPreviewContent?: string;
11
+ readonly storybookPreviewPath?: string;
12
+ };
13
+ export type ToolingConfigFileMap = Record<string, string>;
14
+ export declare const createToolingConfigFiles: (options?: ToolingConfigFilesOptions) => ToolingConfigFileMap;
@@ -0,0 +1,66 @@
1
+ import { configTargets } from './config-targets.js';
2
+ const extensionConfigPaths = new Set([
3
+ 'eslint.config.js',
4
+ 'jest.config.js',
5
+ 'prettier.config.js',
6
+ 'stylelint.config.js',
7
+ ]);
8
+ const defaultToolingConfigNames = [
9
+ 'typescript',
10
+ 'eslint',
11
+ 'prettier',
12
+ 'jest',
13
+ 'storybook',
14
+ ];
15
+ const storybookPathMainContent = `import config from '@vyriy/storybook-config';
16
+ import { path } from '@vyriy/path';
17
+
18
+ export default {
19
+ ...config,
20
+ stories: [
21
+ path('**/*.mdx'),
22
+ path('**/*.stories.@(js|jsx|mjs|ts|tsx)'),
23
+ ],
24
+ };
25
+ `;
26
+ const getStorybookPreviewContent = (specifier, styleImport) => {
27
+ const exportLine = specifier === 'extensionless'
28
+ ? "export { default } from '@vyriy/storybook-config/preview';\n"
29
+ : "export { default } from '@vyriy/storybook-config/preview.js';\n";
30
+ return styleImport ? `import '${styleImport}';\n\n${exportLine}` : exportLine;
31
+ };
32
+ const withExtension = (path, extension) => {
33
+ if (!extensionConfigPaths.has(path)) {
34
+ return path;
35
+ }
36
+ return path.replace(/\.js$/, `.${extension}`);
37
+ };
38
+ const withStorybookOverrides = (file, options) => {
39
+ if (file.path === '.storybook/main.ts') {
40
+ return {
41
+ ...file,
42
+ content: options.storybookMainContent ?? (options.storybookStories === 'path' ? storybookPathMainContent : file.content),
43
+ };
44
+ }
45
+ if (file.path === '.storybook/preview.ts') {
46
+ return {
47
+ ...file,
48
+ content: options.storybookPreviewContent ??
49
+ getStorybookPreviewContent(options.storybookPreviewSpecifier ?? 'js', options.storybookPreviewStyleImport),
50
+ path: options.storybookPreviewPath ?? file.path,
51
+ };
52
+ }
53
+ return file;
54
+ };
55
+ export const createToolingConfigFiles = (options = {}) => {
56
+ const extension = options.extension ?? 'js';
57
+ const names = options.names ?? defaultToolingConfigNames;
58
+ const files = {};
59
+ for (const name of names) {
60
+ for (const targetFile of configTargets[name].files) {
61
+ const file = withStorybookOverrides(targetFile, options);
62
+ files[withExtension(file.path, extension)] = file.content;
63
+ }
64
+ }
65
+ return files;
66
+ };
@@ -0,0 +1,4 @@
1
+ import type { ConfigName, ConfigTarget } from './types.js';
2
+ export declare const configTargets: Record<ConfigName, ConfigTarget>;
3
+ export declare const defaultConfigNames: readonly ConfigName[];
4
+ export declare const allConfigNames: ConfigName[];
@@ -0,0 +1,84 @@
1
+ const typescriptContent = `${JSON.stringify({
2
+ extends: '@vyriy/typescript-config/index.json',
3
+ include: [
4
+ '.bin/**/*.ts',
5
+ '.bin/**/*.tsx',
6
+ '.storybook/**/*.ts',
7
+ '.storybook/**/*.tsx',
8
+ 'packages/**/*.ts',
9
+ 'packages/**/*.tsx',
10
+ 'workspaces/**/*.ts',
11
+ 'workspaces/**/*.tsx',
12
+ '*.ts',
13
+ '*.tsx',
14
+ ],
15
+ }, null, 2)}
16
+ `;
17
+ const eslintContent = `import config from '@vyriy/eslint-config';
18
+
19
+ export default config;
20
+ `;
21
+ const prettierContent = `export { default } from '@vyriy/prettier-config';
22
+ `;
23
+ const jestContent = `export { default } from '@vyriy/jest-config';
24
+ `;
25
+ const stylelintContent = `export { default } from '@vyriy/stylelint-config';
26
+ `;
27
+ const storybookMainContent = `import config from '@vyriy/storybook-config';
28
+
29
+ import type { StorybookConfig } from '@vyriy/storybook-config';
30
+
31
+ const main: StorybookConfig = {
32
+ ...config,
33
+ stories: [
34
+ '../**/*.mdx',
35
+ '../**/*.stories.@(js|jsx|mjs|ts|tsx)',
36
+ ],
37
+ };
38
+
39
+ export default main;
40
+ `;
41
+ const storybookPreviewContent = `export { default } from '@vyriy/storybook-config/preview.js';
42
+ `;
43
+ export const configTargets = {
44
+ eslint: {
45
+ name: 'eslint',
46
+ packageName: '@vyriy/eslint-config',
47
+ files: [{ path: 'eslint.config.js', content: eslintContent }],
48
+ },
49
+ jest: {
50
+ name: 'jest',
51
+ packageName: '@vyriy/jest-config',
52
+ files: [{ path: 'jest.config.js', content: jestContent }],
53
+ },
54
+ prettier: {
55
+ name: 'prettier',
56
+ packageName: '@vyriy/prettier-config',
57
+ files: [{ path: 'prettier.config.js', content: prettierContent }],
58
+ },
59
+ storybook: {
60
+ name: 'storybook',
61
+ packageName: '@vyriy/storybook-config',
62
+ files: [
63
+ { path: '.storybook/main.ts', content: storybookMainContent },
64
+ { path: '.storybook/preview.ts', content: storybookPreviewContent },
65
+ ],
66
+ },
67
+ stylelint: {
68
+ name: 'stylelint',
69
+ packageName: '@vyriy/stylelint-config',
70
+ files: [{ path: 'stylelint.config.js', content: stylelintContent }],
71
+ },
72
+ typescript: {
73
+ name: 'typescript',
74
+ packageName: '@vyriy/typescript-config',
75
+ files: [{ path: 'tsconfig.json', content: typescriptContent }],
76
+ },
77
+ };
78
+ export const defaultConfigNames = [
79
+ 'typescript',
80
+ 'eslint',
81
+ 'prettier',
82
+ 'jest',
83
+ ];
84
+ export const allConfigNames = Object.keys(configTargets);
@@ -0,0 +1 @@
1
+ export declare const fileExists: (path: string) => Promise<boolean>;
package/file-exists.js ADDED
@@ -0,0 +1,13 @@
1
+ import { access } from 'node:fs/promises';
2
+ export const fileExists = async (path) => {
3
+ try {
4
+ await access(path);
5
+ return true;
6
+ }
7
+ catch (error) {
8
+ if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
9
+ return false;
10
+ }
11
+ throw error;
12
+ }
13
+ };
package/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from './config-files.js';
2
+ export * from './tooling.js';
3
+ export * from './config-targets.js';
4
+ export * from './parse-config-args.js';
5
+ export type * from './types.js';
package/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from './config-files.js';
2
+ export * from './tooling.js';
3
+ export * from './config-targets.js';
4
+ export * from './parse-config-args.js';
@@ -0,0 +1 @@
1
+ export declare const findMissingPackages: (cwd: string, packageNames: readonly string[]) => Promise<string[]>;
@@ -0,0 +1,28 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ const dependencyFields = [
4
+ 'dependencies',
5
+ 'devDependencies',
6
+ 'peerDependencies',
7
+ 'optionalDependencies',
8
+ ];
9
+ const readPackageJson = async (cwd) => {
10
+ try {
11
+ return JSON.parse(await readFile(join(cwd, 'package.json'), 'utf8'));
12
+ }
13
+ catch (error) {
14
+ if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
15
+ return undefined;
16
+ }
17
+ throw error;
18
+ }
19
+ };
20
+ export const findMissingPackages = async (cwd, packageNames) => {
21
+ const packageJson = await readPackageJson(cwd);
22
+ if (!packageJson) {
23
+ return [...packageNames];
24
+ }
25
+ return packageNames.filter((packageName) => {
26
+ return !dependencyFields.some((field) => Boolean(packageJson[field]?.[packageName]));
27
+ });
28
+ };
package/package.json ADDED
@@ -0,0 +1,121 @@
1
+ {
2
+ "name": "@vyriy/tooling",
3
+ "version": "0.8.2",
4
+ "description": "Vyriy tooling config generator CLI.",
5
+ "homepage": "https://vyriy.dev/docs/tooling/",
6
+ "type": "module",
7
+ "engines": {
8
+ "node": ">=24.0.0"
9
+ },
10
+ "packageManager": "yarn@4.16.0",
11
+ "bin": {
12
+ "vt": "./bin/index.js",
13
+ "vyriy-tooling": "./bin/index.js"
14
+ },
15
+ "agents": "./AGENTS.md",
16
+ "license": "MIT",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/evheniy/vyriy",
20
+ "directory": "packages/tooling"
21
+ },
22
+ "main": "./index.js",
23
+ "types": "./index.d.ts",
24
+ "exports": {
25
+ ".": {
26
+ "types": "./index.d.ts",
27
+ "import": "./index.js",
28
+ "default": "./index.js"
29
+ },
30
+ "./config-files": {
31
+ "types": "./config-files.d.ts",
32
+ "import": "./config-files.js",
33
+ "default": "./config-files.js"
34
+ },
35
+ "./config-files.js": {
36
+ "types": "./config-files.d.ts",
37
+ "import": "./config-files.js",
38
+ "default": "./config-files.js"
39
+ },
40
+ "./config-targets": {
41
+ "types": "./config-targets.d.ts",
42
+ "import": "./config-targets.js",
43
+ "default": "./config-targets.js"
44
+ },
45
+ "./config-targets.js": {
46
+ "types": "./config-targets.d.ts",
47
+ "import": "./config-targets.js",
48
+ "default": "./config-targets.js"
49
+ },
50
+ "./file-exists": {
51
+ "types": "./file-exists.d.ts",
52
+ "import": "./file-exists.js",
53
+ "default": "./file-exists.js"
54
+ },
55
+ "./file-exists.js": {
56
+ "types": "./file-exists.d.ts",
57
+ "import": "./file-exists.js",
58
+ "default": "./file-exists.js"
59
+ },
60
+ "./index": {
61
+ "types": "./index.d.ts",
62
+ "import": "./index.js",
63
+ "default": "./index.js"
64
+ },
65
+ "./index.js": {
66
+ "types": "./index.d.ts",
67
+ "import": "./index.js",
68
+ "default": "./index.js"
69
+ },
70
+ "./package-dependencies": {
71
+ "types": "./package-dependencies.d.ts",
72
+ "import": "./package-dependencies.js",
73
+ "default": "./package-dependencies.js"
74
+ },
75
+ "./package-dependencies.js": {
76
+ "types": "./package-dependencies.d.ts",
77
+ "import": "./package-dependencies.js",
78
+ "default": "./package-dependencies.js"
79
+ },
80
+ "./parse-config-args": {
81
+ "types": "./parse-config-args.d.ts",
82
+ "import": "./parse-config-args.js",
83
+ "default": "./parse-config-args.js"
84
+ },
85
+ "./parse-config-args.js": {
86
+ "types": "./parse-config-args.d.ts",
87
+ "import": "./parse-config-args.js",
88
+ "default": "./parse-config-args.js"
89
+ },
90
+ "./select-configs": {
91
+ "types": "./select-configs.d.ts",
92
+ "import": "./select-configs.js",
93
+ "default": "./select-configs.js"
94
+ },
95
+ "./select-configs.js": {
96
+ "types": "./select-configs.d.ts",
97
+ "import": "./select-configs.js",
98
+ "default": "./select-configs.js"
99
+ },
100
+ "./tooling": {
101
+ "types": "./tooling.d.ts",
102
+ "import": "./tooling.js",
103
+ "default": "./tooling.js"
104
+ },
105
+ "./tooling.js": {
106
+ "types": "./tooling.d.ts",
107
+ "import": "./tooling.js",
108
+ "default": "./tooling.js"
109
+ },
110
+ "./write-config-files": {
111
+ "types": "./write-config-files.d.ts",
112
+ "import": "./write-config-files.js",
113
+ "default": "./write-config-files.js"
114
+ },
115
+ "./write-config-files.js": {
116
+ "types": "./write-config-files.d.ts",
117
+ "import": "./write-config-files.js",
118
+ "default": "./write-config-files.js"
119
+ }
120
+ }
121
+ }
@@ -0,0 +1,2 @@
1
+ import type { ParseConfigArgs } from './types.js';
2
+ export declare const parseConfigArgs: ParseConfigArgs;
@@ -0,0 +1,49 @@
1
+ import { allConfigNames, defaultConfigNames } from './config-targets.js';
2
+ const isConfigName = (value) => {
3
+ return allConfigNames.includes(value);
4
+ };
5
+ export const parseConfigArgs = (args) => {
6
+ const dryRun = args.includes('--dry-run');
7
+ const force = args.includes('--force');
8
+ const help = args.includes('--help') || args.includes('-h');
9
+ const version = args.includes('--version') || args.includes('-v');
10
+ const type = args.find((arg) => !arg.startsWith('-')) ?? 'init';
11
+ if (help) {
12
+ return {
13
+ dryRun,
14
+ force,
15
+ names: [],
16
+ type: 'help',
17
+ };
18
+ }
19
+ if (version) {
20
+ return {
21
+ dryRun,
22
+ force,
23
+ names: [],
24
+ type: 'version',
25
+ };
26
+ }
27
+ if (type === 'init') {
28
+ return {
29
+ dryRun,
30
+ force,
31
+ names: defaultConfigNames,
32
+ type,
33
+ };
34
+ }
35
+ if (isConfigName(type)) {
36
+ return {
37
+ dryRun,
38
+ force,
39
+ names: [type],
40
+ type,
41
+ };
42
+ }
43
+ return {
44
+ dryRun,
45
+ force,
46
+ names: [],
47
+ type: 'unknown',
48
+ };
49
+ };
@@ -0,0 +1,2 @@
1
+ import type { ConfigName } from './types.js';
2
+ export declare const selectConfigs: () => Promise<readonly ConfigName[]>;
@@ -0,0 +1,50 @@
1
+ import { createInterface } from 'node:readline/promises';
2
+ import { allConfigNames, defaultConfigNames } from './config-targets.js';
3
+ const labels = {
4
+ eslint: 'ESLint',
5
+ jest: 'Jest',
6
+ prettier: 'Prettier',
7
+ storybook: 'Storybook',
8
+ stylelint: 'Stylelint',
9
+ typescript: 'TypeScript',
10
+ };
11
+ const parseAnswer = (answer) => {
12
+ const selected = new Set();
13
+ for (const token of answer
14
+ .split(',')
15
+ .map((part) => part.trim().toLowerCase())
16
+ .filter(Boolean)) {
17
+ const index = Number(token);
18
+ const configName = allConfigNames[index - 1];
19
+ if (configName) {
20
+ selected.add(configName);
21
+ continue;
22
+ }
23
+ if (allConfigNames.includes(token)) {
24
+ selected.add(token);
25
+ }
26
+ }
27
+ return [...selected];
28
+ };
29
+ export const selectConfigs = async () => {
30
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
31
+ return defaultConfigNames;
32
+ }
33
+ console.log('Select configs to create. Press enter for defaults.');
34
+ allConfigNames.forEach((name, index) => {
35
+ const defaultMarker = defaultConfigNames.includes(name) ? ' default' : '';
36
+ console.log(`${index + 1}. ${labels[name]}${defaultMarker}`);
37
+ });
38
+ const readline = createInterface({
39
+ input: process.stdin,
40
+ output: process.stdout,
41
+ });
42
+ try {
43
+ const answer = await readline.question('Configs: ');
44
+ const selected = parseAnswer(answer);
45
+ return selected.length > 0 ? selected : defaultConfigNames;
46
+ }
47
+ finally {
48
+ readline.close();
49
+ }
50
+ };
package/tooling.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import type { RunToolingCli, ToolingHelpText } from './types.js';
2
+ export declare const toolingVersion: string;
3
+ export declare const createToolingHelpText: ToolingHelpText;
4
+ export declare const runToolingCli: RunToolingCli;
package/tooling.js ADDED
@@ -0,0 +1,90 @@
1
+ import { configTargets } from './config-targets.js';
2
+ import { fileExists } from './file-exists.js';
3
+ import { findMissingPackages } from './package-dependencies.js';
4
+ import { parseConfigArgs } from './parse-config-args.js';
5
+ import { selectConfigs } from './select-configs.js';
6
+ import { writeConfigFiles } from './write-config-files.js';
7
+ import packageJson from './package.json' with { type: 'json' };
8
+ const unique = (values) => [...new Set(values)];
9
+ export const toolingVersion = packageJson.version;
10
+ export const createToolingHelpText = (command = 'vyriy-tooling', alias = 'vt') => {
11
+ const aliasText = alias ? ` ${alias} init Alias for ${command}\n` : '';
12
+ const aliasExampleText = alias ? `\n ${alias} init\n ${alias} typescript --dry-run` : '';
13
+ return `Vyriy Tooling
14
+
15
+ Usage:
16
+ ${command} init
17
+ ${command} typescript
18
+ ${command} eslint
19
+ ${command} prettier
20
+ ${command} jest
21
+ ${command} storybook
22
+ ${command} stylelint
23
+ ${aliasText}\
24
+ ${command} --help, -h Show tooling help
25
+ ${command} --version, -v Show version
26
+
27
+ Options:
28
+ --force Overwrite existing config files
29
+ --dry-run Print files that would be created without writing them
30
+
31
+ Examples:
32
+ ${command} init
33
+ ${command} typescript
34
+ ${command} storybook --force
35
+ ${command} prettier --dry-run${aliasExampleText}`;
36
+ };
37
+ const collectFiles = (names) => {
38
+ return names.flatMap((name) => [...configTargets[name].files]);
39
+ };
40
+ const printMissingPackages = (missingPackages) => {
41
+ if (missingPackages.length === 0) {
42
+ return;
43
+ }
44
+ console.log('');
45
+ console.log('Missing Vyriy config packages:');
46
+ console.log('');
47
+ for (const packageName of missingPackages) {
48
+ console.log(`- ${packageName}`);
49
+ }
50
+ console.log('');
51
+ console.log('Install them with:');
52
+ console.log('');
53
+ console.log(`yarn add -D ${missingPackages.join(' ')}`);
54
+ };
55
+ export const runToolingCli = async (args = [], commandName = 'vyriy-tooling', alias = 'vt', cwd = process.cwd()) => {
56
+ const command = parseConfigArgs(args);
57
+ if (command.type === 'help') {
58
+ console.log(createToolingHelpText(commandName, alias));
59
+ process.exitCode = 0;
60
+ return;
61
+ }
62
+ if (command.type === 'version') {
63
+ console.log(toolingVersion);
64
+ process.exitCode = 0;
65
+ return;
66
+ }
67
+ if (command.type === 'unknown') {
68
+ console.error('Unknown tooling command.');
69
+ console.log(createToolingHelpText(commandName, alias));
70
+ process.exitCode = 1;
71
+ return;
72
+ }
73
+ const names = command.type === 'init' ? await selectConfigs() : command.names;
74
+ const files = collectFiles(names);
75
+ const writtenFiles = await writeConfigFiles({
76
+ cwd,
77
+ dryRun: command.dryRun,
78
+ exists: fileExists,
79
+ files,
80
+ force: command.force,
81
+ });
82
+ if (command.dryRun || writtenFiles.length === 0) {
83
+ process.exitCode = 0;
84
+ return;
85
+ }
86
+ const packageNames = unique(names.map((name) => configTargets[name].packageName));
87
+ const missingPackages = await findMissingPackages(cwd, packageNames);
88
+ printMissingPackages(missingPackages);
89
+ process.exitCode = 0;
90
+ };
package/types.d.ts ADDED
@@ -0,0 +1,24 @@
1
+ export type ConfigName = 'eslint' | 'jest' | 'prettier' | 'storybook' | 'stylelint' | 'typescript';
2
+ export type ConfigCommand = {
3
+ readonly type: 'help' | 'version';
4
+ readonly dryRun: boolean;
5
+ readonly force: boolean;
6
+ readonly names: readonly ConfigName[];
7
+ } | {
8
+ readonly dryRun: boolean;
9
+ readonly force: boolean;
10
+ readonly names: readonly ConfigName[];
11
+ readonly type: 'init' | ConfigName | 'unknown';
12
+ };
13
+ export type ConfigFile = {
14
+ readonly path: string;
15
+ readonly content: string;
16
+ };
17
+ export type ConfigTarget = {
18
+ readonly files: readonly ConfigFile[];
19
+ readonly name: ConfigName;
20
+ readonly packageName: string;
21
+ };
22
+ export type ToolingHelpText = (command?: string, alias?: false | string) => string;
23
+ export type ParseConfigArgs = (args: readonly string[]) => ConfigCommand;
24
+ export type RunToolingCli = (args?: readonly string[], command?: string, alias?: false | string, cwd?: string) => Promise<void>;
@@ -0,0 +1,10 @@
1
+ import type { ConfigFile } from './types.js';
2
+ type WriteConfigFilesOptions = {
3
+ readonly cwd: string;
4
+ readonly dryRun: boolean;
5
+ readonly exists: (path: string) => Promise<boolean>;
6
+ readonly files: readonly ConfigFile[];
7
+ readonly force: boolean;
8
+ };
9
+ export declare const writeConfigFiles: ({ cwd, dryRun, exists, files, force, }: WriteConfigFilesOptions) => Promise<readonly ConfigFile[]>;
10
+ export {};
@@ -0,0 +1,29 @@
1
+ import { mkdir, writeFile } from 'node:fs/promises';
2
+ import { dirname, join } from 'node:path';
3
+ export const writeConfigFiles = async ({ cwd, dryRun, exists, files, force, }) => {
4
+ const writable = [];
5
+ for (const file of files) {
6
+ if (!force && (await exists(join(cwd, file.path)))) {
7
+ console.log(`Skipped ${file.path} because it already exists.`);
8
+ console.log('Use --force to overwrite it.');
9
+ continue;
10
+ }
11
+ writable.push(file);
12
+ }
13
+ if (dryRun) {
14
+ if (writable.length > 0) {
15
+ console.log('Would create:');
16
+ for (const file of writable) {
17
+ console.log(`- ${file.path}`);
18
+ }
19
+ }
20
+ return writable;
21
+ }
22
+ for (const file of writable) {
23
+ const path = join(cwd, file.path);
24
+ await mkdir(dirname(path), { recursive: true });
25
+ await writeFile(path, file.content, 'utf8');
26
+ console.log(`${force ? 'Wrote' : 'Created'} ${file.path}.`);
27
+ }
28
+ return writable;
29
+ };