vyriy 0.8.1 → 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/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # vyriy
2
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/vyriy/
6
+
3
7
  Interactive project master for Vyriy projects.
4
8
 
5
9
  ## Purpose
@@ -30,6 +34,7 @@ vyriy create . Initialise a new Vyriy project in the current directory
30
34
  vyriy dist Prepare dist package metadata without publishing to npm
31
35
  vyriy static [dir] Serve a static directory (defaults to .)
32
36
  vyriy check Check local environment (Node.js and Yarn versions)
37
+ vyriy config [tool] Generate thin local config files
33
38
  vyriy --help, -h Show help
34
39
  vyriy --version, -v Show version
35
40
  ```
@@ -77,6 +82,46 @@ vyriy check --help
77
82
  vyriy check --version
78
83
  ```
79
84
 
85
+ ### `config`
86
+
87
+ Vyriy can generate thin local config files that connect your project to Vyriy
88
+ standards.
89
+
90
+ ```bash
91
+ vyriy config init
92
+ ```
93
+
94
+ Or configure tools one by one:
95
+
96
+ ```bash
97
+ vyriy config typescript
98
+ vyriy config eslint
99
+ vyriy config prettier
100
+ vyriy config jest
101
+ vyriy config storybook
102
+ vyriy config stylelint
103
+ ```
104
+
105
+ Generated files stay intentionally small. For TypeScript, Vyriy writes a local
106
+ `tsconfig.json` that extends `@vyriy/typescript-config` and includes common
107
+ project paths such as `.storybook`, `packages`, `workspaces`, and root
108
+ TypeScript files.
109
+
110
+ Existing files are skipped by default:
111
+
112
+ ```bash
113
+ vyriy config typescript --force
114
+ vyriy config init --dry-run
115
+ ```
116
+
117
+ The command does not install dependencies. If required Vyriy config packages are
118
+ missing from `package.json`, it prints a suggested install command:
119
+
120
+ ```bash
121
+ yarn add -D @vyriy/typescript-config @vyriy/eslint-config
122
+ npm install --save-dev @vyriy/typescript-config @vyriy/eslint-config
123
+ ```
124
+
80
125
  ### `dist`
81
126
 
82
127
  Prepares every package inside the `dist/` directory for npm publishing:
package/args.js CHANGED
@@ -1,6 +1,10 @@
1
1
  export const parseArgs = (args) => {
2
2
  const [command = '', ...commandArgs] = args;
3
- if (command === 'check' || command === 'create' || command === 'dist' || command === 'static') {
3
+ if (command === 'check' ||
4
+ command === 'config' ||
5
+ command === 'create' ||
6
+ command === 'dist' ||
7
+ command === 'static') {
4
8
  return { type: command, args: commandArgs };
5
9
  }
6
10
  if (args.includes('--help') || args.includes('-h')) {
package/cli.js CHANGED
@@ -8,6 +8,7 @@ Usage:
8
8
  vyriy dist Prepare dist package metadata without publishing to npm
9
9
  vyriy static [dir] Serve a static directory (defaults to .)
10
10
  vyriy check Check local environment
11
+ vyriy config [tool] Generate thin local config files
11
12
  vyriy --help, -h Show help
12
13
  vyriy --version, -v Show version
13
14
 
@@ -26,6 +27,17 @@ Static options:
26
27
  vyriy static dist --spa Enable SPA fallback mode
27
28
  vyriy static dist --fallback index.html SPA fallback file
28
29
 
30
+ Config options:
31
+ vyriy config init Select configs to create
32
+ vyriy config typescript Create tsconfig.json
33
+ vyriy config eslint Create eslint.config.js
34
+ vyriy config prettier Create prettier.config.js
35
+ vyriy config jest Create jest.config.js
36
+ vyriy config storybook Create .storybook config files
37
+ vyriy config stylelint Create stylelint.config.js
38
+ vyriy config init --force Overwrite existing config files
39
+ vyriy config init --dry-run Print config files without writing them
40
+
29
41
  Examples:
30
42
  vyriy create app
31
43
  vyriy create app --dry-run
@@ -37,7 +49,9 @@ Examples:
37
49
  vyriy static dist --cache static
38
50
  vyriy static dist --spa --fallback index.html --cache static
39
51
  vyriy static dist
40
- vyriy check`;
52
+ vyriy check
53
+ vyriy config init
54
+ vyriy config typescript`;
41
55
  export const cli = async (args = []) => {
42
56
  const command = parseArgs(args);
43
57
  switch (command.type) {
@@ -69,6 +83,11 @@ export const cli = async (args = []) => {
69
83
  await runCreateCli(command.args, 'vyriy create', false);
70
84
  break;
71
85
  }
86
+ case 'config': {
87
+ const { runConfigCli } = await import('./config-cli.js');
88
+ await runConfigCli(command.args);
89
+ break;
90
+ }
72
91
  default:
73
92
  console.error(`Unknown command: ${command.command}\n`);
74
93
  console.log(text);
@@ -0,0 +1 @@
1
+ export declare const runConfigCli: (args?: readonly string[], cwd?: string) => Promise<void>;
package/config-cli.js ADDED
@@ -0,0 +1,69 @@
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
+ const helpText = `Usage:
8
+ vyriy config init
9
+ vyriy config typescript
10
+ vyriy config eslint
11
+ vyriy config prettier
12
+ vyriy config jest
13
+ vyriy config storybook
14
+ vyriy config stylelint
15
+
16
+ Options:
17
+ --force Overwrite existing config files
18
+ --dry-run Print files that would be created without writing them
19
+ --help Show config help`;
20
+ const unique = (values) => [...new Set(values)];
21
+ const collectFiles = (names) => {
22
+ return names.flatMap((name) => [...configTargets[name].files]);
23
+ };
24
+ const printMissingPackages = (missingPackages) => {
25
+ if (missingPackages.length === 0) {
26
+ return;
27
+ }
28
+ console.log('');
29
+ console.log('Missing Vyriy config packages:');
30
+ console.log('');
31
+ for (const packageName of missingPackages) {
32
+ console.log(`- ${packageName}`);
33
+ }
34
+ console.log('');
35
+ console.log('Install them with:');
36
+ console.log('');
37
+ console.log(`yarn add -D ${missingPackages.join(' ')}`);
38
+ };
39
+ export const runConfigCli = async (args = [], cwd = process.cwd()) => {
40
+ const command = parseConfigArgs(args);
41
+ if (command.help) {
42
+ console.log(helpText);
43
+ process.exitCode = 0;
44
+ return;
45
+ }
46
+ if (command.type === 'unknown') {
47
+ console.error('Unknown config command.');
48
+ console.log(helpText);
49
+ process.exitCode = 1;
50
+ return;
51
+ }
52
+ const names = command.type === 'init' ? await selectConfigs() : command.names;
53
+ const files = collectFiles(names);
54
+ const writtenFiles = await writeConfigFiles({
55
+ cwd,
56
+ dryRun: command.dryRun,
57
+ exists: fileExists,
58
+ files,
59
+ force: command.force,
60
+ });
61
+ if (command.dryRun || writtenFiles.length === 0) {
62
+ process.exitCode = 0;
63
+ return;
64
+ }
65
+ const packageNames = unique(names.map((name) => configTargets[name].packageName));
66
+ const missingPackages = await findMissingPackages(cwd, packageNames);
67
+ printMissingPackages(missingPackages);
68
+ process.exitCode = 0;
69
+ };
@@ -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,82 @@
1
+ const typescriptContent = `${JSON.stringify({
2
+ extends: '@vyriy/typescript-config/index.json',
3
+ include: [
4
+ '.storybook/**/*.ts',
5
+ '.storybook/**/*.tsx',
6
+ 'packages/**/*.ts',
7
+ 'packages/**/*.tsx',
8
+ 'workspaces/**/*.ts',
9
+ 'workspaces/**/*.tsx',
10
+ '*.ts',
11
+ '*.tsx',
12
+ ],
13
+ }, null, 2)}
14
+ `;
15
+ const eslintContent = `import config from '@vyriy/eslint-config';
16
+
17
+ export default config;
18
+ `;
19
+ const prettierContent = `export { default } from '@vyriy/prettier-config';
20
+ `;
21
+ const jestContent = `export { default } from '@vyriy/jest-config';
22
+ `;
23
+ const stylelintContent = `export { default } from '@vyriy/stylelint-config';
24
+ `;
25
+ const storybookMainContent = `import config from '@vyriy/storybook-config';
26
+
27
+ import type { StorybookConfig } from '@vyriy/storybook-config';
28
+
29
+ const main: StorybookConfig = {
30
+ ...config,
31
+ stories: [
32
+ '../**/*.mdx',
33
+ '../**/*.stories.@(js|jsx|mjs|ts|tsx)',
34
+ ],
35
+ };
36
+
37
+ export default main;
38
+ `;
39
+ const storybookPreviewContent = `export { default } from '@vyriy/storybook-config/preview.js';
40
+ `;
41
+ export const configTargets = {
42
+ eslint: {
43
+ name: 'eslint',
44
+ packageName: '@vyriy/eslint-config',
45
+ files: [{ path: 'eslint.config.js', content: eslintContent }],
46
+ },
47
+ jest: {
48
+ name: 'jest',
49
+ packageName: '@vyriy/jest-config',
50
+ files: [{ path: 'jest.config.js', content: jestContent }],
51
+ },
52
+ prettier: {
53
+ name: 'prettier',
54
+ packageName: '@vyriy/prettier-config',
55
+ files: [{ path: 'prettier.config.js', content: prettierContent }],
56
+ },
57
+ storybook: {
58
+ name: 'storybook',
59
+ packageName: '@vyriy/storybook-config',
60
+ files: [
61
+ { path: '.storybook/main.ts', content: storybookMainContent },
62
+ { path: '.storybook/preview.ts', content: storybookPreviewContent },
63
+ ],
64
+ },
65
+ stylelint: {
66
+ name: 'stylelint',
67
+ packageName: '@vyriy/stylelint-config',
68
+ files: [{ path: 'stylelint.config.js', content: stylelintContent }],
69
+ },
70
+ typescript: {
71
+ name: 'typescript',
72
+ packageName: '@vyriy/typescript-config',
73
+ files: [{ path: 'tsconfig.json', content: typescriptContent }],
74
+ },
75
+ };
76
+ export const defaultConfigNames = [
77
+ 'typescript',
78
+ 'eslint',
79
+ 'prettier',
80
+ 'jest',
81
+ ];
82
+ 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 CHANGED
@@ -1,3 +1,6 @@
1
1
  export * from './args.js';
2
2
  export * from './cli.js';
3
+ export * from './config-cli.js';
4
+ export * from './config-targets.js';
5
+ export * from './parse-config-args.js';
3
6
  export type * from './types.js';
package/index.js CHANGED
@@ -1,2 +1,5 @@
1
1
  export * from './args.js';
2
2
  export * from './cli.js';
3
+ export * from './config-cli.js';
4
+ export * from './config-targets.js';
5
+ 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vyriy",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "description": "Interactive project master for calm cloud-ready applications.",
5
5
  "homepage": "https://vyriy.dev/docs/vyriy/",
6
6
  "type": "module",
@@ -10,10 +10,10 @@
10
10
  },
11
11
  "packageManager": "yarn@4.16.0",
12
12
  "dependencies": {
13
- "@vyriy/check": "0.8.1",
14
- "@vyriy/create": "0.8.1",
15
- "@vyriy/dist": "0.8.1",
16
- "@vyriy/static": "0.8.1"
13
+ "@vyriy/check": "0.8.2",
14
+ "@vyriy/create": "0.8.2",
15
+ "@vyriy/dist": "0.8.2",
16
+ "@vyriy/static": "0.8.2"
17
17
  },
18
18
  "peerDependencies": {
19
19
  "@testing-library/dom": "^10.4.1",
@@ -146,6 +146,36 @@
146
146
  "import": "./cli.js",
147
147
  "default": "./cli.js"
148
148
  },
149
+ "./config-cli": {
150
+ "types": "./config-cli.d.ts",
151
+ "import": "./config-cli.js",
152
+ "default": "./config-cli.js"
153
+ },
154
+ "./config-cli.js": {
155
+ "types": "./config-cli.d.ts",
156
+ "import": "./config-cli.js",
157
+ "default": "./config-cli.js"
158
+ },
159
+ "./config-targets": {
160
+ "types": "./config-targets.d.ts",
161
+ "import": "./config-targets.js",
162
+ "default": "./config-targets.js"
163
+ },
164
+ "./config-targets.js": {
165
+ "types": "./config-targets.d.ts",
166
+ "import": "./config-targets.js",
167
+ "default": "./config-targets.js"
168
+ },
169
+ "./file-exists": {
170
+ "types": "./file-exists.d.ts",
171
+ "import": "./file-exists.js",
172
+ "default": "./file-exists.js"
173
+ },
174
+ "./file-exists.js": {
175
+ "types": "./file-exists.d.ts",
176
+ "import": "./file-exists.js",
177
+ "default": "./file-exists.js"
178
+ },
149
179
  "./index": {
150
180
  "types": "./index.d.ts",
151
181
  "import": "./index.js",
@@ -155,6 +185,46 @@
155
185
  "types": "./index.d.ts",
156
186
  "import": "./index.js",
157
187
  "default": "./index.js"
188
+ },
189
+ "./package-dependencies": {
190
+ "types": "./package-dependencies.d.ts",
191
+ "import": "./package-dependencies.js",
192
+ "default": "./package-dependencies.js"
193
+ },
194
+ "./package-dependencies.js": {
195
+ "types": "./package-dependencies.d.ts",
196
+ "import": "./package-dependencies.js",
197
+ "default": "./package-dependencies.js"
198
+ },
199
+ "./parse-config-args": {
200
+ "types": "./parse-config-args.d.ts",
201
+ "import": "./parse-config-args.js",
202
+ "default": "./parse-config-args.js"
203
+ },
204
+ "./parse-config-args.js": {
205
+ "types": "./parse-config-args.d.ts",
206
+ "import": "./parse-config-args.js",
207
+ "default": "./parse-config-args.js"
208
+ },
209
+ "./select-configs": {
210
+ "types": "./select-configs.d.ts",
211
+ "import": "./select-configs.js",
212
+ "default": "./select-configs.js"
213
+ },
214
+ "./select-configs.js": {
215
+ "types": "./select-configs.d.ts",
216
+ "import": "./select-configs.js",
217
+ "default": "./select-configs.js"
218
+ },
219
+ "./write-config-files": {
220
+ "types": "./write-config-files.d.ts",
221
+ "import": "./write-config-files.js",
222
+ "default": "./write-config-files.js"
223
+ },
224
+ "./write-config-files.js": {
225
+ "types": "./write-config-files.d.ts",
226
+ "import": "./write-config-files.js",
227
+ "default": "./write-config-files.js"
158
228
  }
159
229
  }
160
230
  }
@@ -0,0 +1,2 @@
1
+ import type { ConfigCommand } from './types.js';
2
+ export declare const parseConfigArgs: (args: readonly string[]) => ConfigCommand;
@@ -0,0 +1,44 @@
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 type = args.find((arg) => !arg.startsWith('-')) ?? 'init';
10
+ if (help) {
11
+ return {
12
+ dryRun,
13
+ force,
14
+ help,
15
+ names: [],
16
+ type: 'init',
17
+ };
18
+ }
19
+ if (type === 'init') {
20
+ return {
21
+ dryRun,
22
+ force,
23
+ help,
24
+ names: defaultConfigNames,
25
+ type,
26
+ };
27
+ }
28
+ if (isConfigName(type)) {
29
+ return {
30
+ dryRun,
31
+ force,
32
+ help,
33
+ names: [type],
34
+ type,
35
+ };
36
+ }
37
+ return {
38
+ dryRun,
39
+ force,
40
+ help,
41
+ names: [],
42
+ type: 'unknown',
43
+ };
44
+ };
@@ -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/types.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export type Command = {
2
2
  readonly type: 'help' | 'version';
3
3
  } | {
4
- readonly type: 'check' | 'create' | 'dist' | 'static';
4
+ readonly type: 'check' | 'config' | 'create' | 'dist' | 'static';
5
5
  readonly args: readonly string[];
6
6
  } | {
7
7
  readonly type: 'unknown';
@@ -9,3 +9,20 @@ export type Command = {
9
9
  };
10
10
  export type ParseArgs = (args: readonly string[]) => Command;
11
11
  export type Cli = (args?: readonly string[]) => Promise<void>;
12
+ export type ConfigName = 'eslint' | 'jest' | 'prettier' | 'storybook' | 'stylelint' | 'typescript';
13
+ export type ConfigCommand = {
14
+ readonly dryRun: boolean;
15
+ readonly force: boolean;
16
+ readonly help: boolean;
17
+ readonly names: readonly ConfigName[];
18
+ readonly type: 'init' | ConfigName | 'unknown';
19
+ };
20
+ export type ConfigFile = {
21
+ readonly path: string;
22
+ readonly content: string;
23
+ };
24
+ export type ConfigTarget = {
25
+ readonly files: readonly ConfigFile[];
26
+ readonly name: ConfigName;
27
+ readonly packageName: string;
28
+ };
@@ -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
+ };