vyriy 0.3.4 → 0.3.8

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.
@@ -6,111 +6,107 @@ const baseFeatures = [
6
6
  'eslint',
7
7
  'prettier',
8
8
  'jest',
9
+ 'storybook',
9
10
  ];
10
11
  const presetFeatures = {
11
- library: ['react', 'storybook'],
12
+ empty: [],
13
+ library: ['react'],
12
14
  api: [],
13
- 'react-csr': ['react', 'webpack'],
14
- 'react-ssr': ['react', 'webpack'],
15
- 'react-ssg': ['react', 'webpack'],
16
- mfe: ['react', 'webpack'],
17
- openmfe: ['react', 'webpack', 'openmfe'],
18
- 'mfe-bff': ['react', 'webpack', 'bff'],
19
- 'openmfe-bff': [
20
- 'react',
21
- 'webpack',
22
- 'openmfe',
23
- 'bff',
24
- ],
15
+ csr: ['react', 'webpack'],
16
+ ssr: ['react', 'webpack'],
17
+ ssg: ['react', 'webpack'],
25
18
  fullstack: ['react', 'webpack'],
26
- 'aws-serverless': ['aws-cdk', 'lambda', 'dynamodb'],
27
- empty: [],
19
+ mfe: ['react', 'webpack', 'openmfe'],
28
20
  };
29
21
  const packagePlans = {
22
+ empty: [],
30
23
  library: [{ name: 'ui', kind: 'ui', publishable: true }],
31
24
  api: [{ name: 'api', kind: 'api', publishable: false }],
32
- 'react-csr': [
33
- { name: 'app', kind: 'core', publishable: false },
34
- { name: 'ui', kind: 'ui', publishable: true },
35
- ],
36
- 'react-ssr': [
37
- { name: 'app', kind: 'core', publishable: false },
38
- { name: 'ui', kind: 'ui', publishable: true },
39
- { name: 'ssr', kind: 'ssr', publishable: false },
40
- ],
41
- 'react-ssg': [
42
- { name: 'app', kind: 'core', publishable: false },
43
- { name: 'ui', kind: 'ui', publishable: true },
44
- { name: 'ssg', kind: 'ssg', publishable: false },
45
- { name: 'content', kind: 'core', publishable: false },
46
- ],
47
- mfe: [
48
- { name: 'mfe', kind: 'mfe', publishable: false },
25
+ csr: [
26
+ { name: 'app', kind: 'app', publishable: false },
49
27
  { name: 'ui', kind: 'ui', publishable: true },
50
28
  ],
51
- openmfe: [
52
- { name: 'mfe', kind: 'mfe', publishable: false },
29
+ ssr: [
30
+ { name: 'app', kind: 'app', publishable: false },
53
31
  { name: 'ui', kind: 'ui', publishable: true },
54
- { name: 'openmfe-contract', kind: 'contract', publishable: true },
32
+ { name: 'ssr', kind: 'app', publishable: false },
55
33
  ],
56
- 'mfe-bff': [
57
- { name: 'mfe', kind: 'mfe', publishable: false },
34
+ ssg: [
35
+ { name: 'app', kind: 'app', publishable: false },
58
36
  { name: 'ui', kind: 'ui', publishable: true },
59
- { name: 'bff', kind: 'bff', publishable: false },
60
- ],
61
- 'openmfe-bff': [
62
- { name: 'mfe', kind: 'mfe', publishable: false },
63
- { name: 'ui', kind: 'ui', publishable: true },
64
- { name: 'bff', kind: 'bff', publishable: false },
65
- { name: 'openmfe-contract', kind: 'contract', publishable: true },
37
+ { name: 'ssg', kind: 'app', publishable: false },
38
+ { name: 'content', kind: 'utils', publishable: false },
66
39
  ],
67
40
  fullstack: [
68
- { name: 'app', kind: 'core', publishable: false },
41
+ { name: 'app', kind: 'app', publishable: false },
69
42
  { name: 'ui', kind: 'ui', publishable: true },
70
43
  { name: 'api', kind: 'api', publishable: false },
71
44
  ],
72
- 'aws-serverless': [
45
+ mfe: [
46
+ { name: 'mfe', kind: 'app', publishable: false },
47
+ { name: 'ui', kind: 'ui', publishable: true },
73
48
  { name: 'api', kind: 'api', publishable: false },
74
- { name: 'infrastructure', kind: 'core', publishable: false },
49
+ { name: 'openmfe-contract', kind: 'config', publishable: true },
75
50
  ],
76
- empty: [],
77
51
  };
78
52
  const workspacePlans = {
53
+ empty: [],
79
54
  library: [],
80
55
  api: [{ name: 'api', kind: 'api' }],
81
- 'react-csr': [{ name: 'web', kind: 'web' }],
82
- 'react-ssr': [
83
- { name: 'ssr', kind: 'ssr' },
84
- { name: 'web', kind: 'web' },
85
- ],
86
- 'react-ssg': [
87
- { name: 'ssg', kind: 'ssg' },
88
- { name: 'web', kind: 'web' },
56
+ csr: [{ name: 'web', kind: 'ui' }],
57
+ ssr: [
58
+ { name: 'ssr', kind: 'ui' },
59
+ { name: 'web', kind: 'ui' },
89
60
  ],
90
- mfe: [{ name: 'mfe', kind: 'mfe' }],
91
- openmfe: [
92
- { name: 'mfe', kind: 'mfe' },
93
- { name: 'openmfe', kind: 'openmfe' },
94
- ],
95
- 'mfe-bff': [
96
- { name: 'mfe', kind: 'mfe' },
97
- { name: 'bff', kind: 'bff' },
98
- ],
99
- 'openmfe-bff': [
100
- { name: 'mfe', kind: 'mfe' },
101
- { name: 'bff', kind: 'bff' },
102
- { name: 'openmfe', kind: 'openmfe' },
61
+ ssg: [
62
+ { name: 'ssg', kind: 'ui' },
63
+ { name: 'web', kind: 'ui' },
103
64
  ],
104
65
  fullstack: [
105
- { name: 'web', kind: 'web' },
66
+ { name: 'web', kind: 'ui' },
67
+ { name: 'api', kind: 'api' },
68
+ ],
69
+ mfe: [
70
+ { name: 'mfe', kind: 'ui' },
106
71
  { name: 'api', kind: 'api' },
107
72
  ],
108
- 'aws-serverless': [{ name: 'cdk', kind: 'cdk' }],
109
- empty: [],
110
73
  };
111
74
  const uniqueFeatures = (features) => [...new Set(features)];
75
+ const getApiRuntimeFromFeatures = (features) => features.includes('lambda') ? 'lambda' : 'docker';
76
+ const awsInfrastructureFeatures = [
77
+ 'aws-cdk',
78
+ 'lambda',
79
+ 'fargate',
80
+ 's3',
81
+ 'cloudfront',
82
+ ];
83
+ const getApiWorkspaceKindFromFeatures = (features) => {
84
+ if (features.includes('lambda')) {
85
+ return 'lambda';
86
+ }
87
+ if (features.includes('fargate')) {
88
+ return 'fargate';
89
+ }
90
+ return 'api';
91
+ };
92
+ const createWorkspacePlans = ({ features, preset, }) => {
93
+ const apiWorkspaceKind = getApiWorkspaceKindFromFeatures(features);
94
+ const workspaces = workspacePlans[preset].map((workspacePlan) => workspacePlan.name === 'api'
95
+ ? {
96
+ ...workspacePlan,
97
+ kind: apiWorkspaceKind,
98
+ }
99
+ : workspacePlan);
100
+ if (features.some((feature) => awsInfrastructureFeatures.includes(feature))) {
101
+ return [
102
+ ...workspaces,
103
+ { name: 'stack', kind: 'stack' },
104
+ ];
105
+ }
106
+ return workspaces;
107
+ };
112
108
  export const createProjectPlanFromPreset = ({ description, apiStyle, ciProvider, features = [], packageScope, preset, projectName, targetDirectory, }) => {
113
- const api = createApiPlan({ preset, style: apiStyle });
109
+ const api = createApiPlan({ preset, runtime: getApiRuntimeFromFeatures(features), style: apiStyle });
114
110
  const apiFeatures = getFeaturesFromApiPlan(api);
115
111
  return {
116
112
  projectName,
@@ -126,7 +122,7 @@ export const createProjectPlanFromPreset = ({ description, apiStyle, ciProvider,
126
122
  ...features,
127
123
  ]),
128
124
  packages: [...packagePlans[preset]],
129
- workspaces: [...workspacePlans[preset]],
125
+ workspaces: createWorkspacePlans({ features, preset }),
130
126
  ci: createCiPlan({ provider: ciProvider }),
131
127
  ...(api ? { api } : {}),
132
128
  };
@@ -1,16 +1 @@
1
- export const getProjectKindFromPreset = (preset) => {
2
- switch (preset) {
3
- case 'react-csr':
4
- return 'csr';
5
- case 'react-ssr':
6
- return 'ssr';
7
- case 'react-ssg':
8
- return 'ssg';
9
- case 'openmfe':
10
- case 'mfe-bff':
11
- case 'openmfe-bff':
12
- return 'mfe';
13
- default:
14
- return preset;
15
- }
16
- };
1
+ export const getProjectKindFromPreset = (preset) => preset;
@@ -1,24 +1,24 @@
1
- export type VyriyProjectKind = 'library' | 'api' | 'csr' | 'ssr' | 'ssg' | 'mfe' | 'fullstack' | 'aws-serverless' | 'empty';
2
- export type VyriyPreset = 'library' | 'api' | 'react-csr' | 'react-ssr' | 'react-ssg' | 'mfe' | 'openmfe' | 'mfe-bff' | 'openmfe-bff' | 'fullstack' | 'aws-serverless' | 'empty';
3
- export type VyriyFeature = 'typescript' | 'eslint' | 'prettier' | 'jest' | 'rest-api' | 'graphql-api' | 'react' | 'storybook' | 'webpack' | 'docker' | 'aws-cdk' | 'dynamodb' | 'lambda' | 'fargate' | 's3' | 'cloudfront' | 'openmfe' | 'bff';
1
+ export type VyriyProjectKind = 'library' | 'api' | 'csr' | 'ssr' | 'ssg' | 'mfe' | 'fullstack' | 'empty';
2
+ export type VyriyPreset = 'empty' | 'library' | 'api' | 'ssr' | 'ssg' | 'csr' | 'fullstack' | 'mfe';
3
+ export type VyriyFeature = 'typescript' | 'eslint' | 'prettier' | 'jest' | 'rest-api' | 'graphql-api' | 'react' | 'storybook' | 'webpack' | 'docker' | 'aws-cdk' | 'apigateway' | 'lambda' | 'fargate' | 's3' | 'cloudfront' | 'openmfe';
4
4
  export type VyriyPackagePlan = {
5
5
  readonly name: string;
6
- readonly kind: 'core' | 'ui' | 'api' | 'bff' | 'ssr' | 'ssg' | 'mfe' | 'contract';
6
+ readonly kind: 'core' | 'ui' | 'api' | 'services' | 'stack' | 'config' | 'utils' | 'components' | 'app';
7
7
  readonly publishable: boolean;
8
8
  };
9
9
  export type VyriyWorkspacePlan = {
10
10
  readonly name: string;
11
- readonly kind: 'web' | 'api' | 'ssr' | 'ssg' | 'storybook' | 'bff' | 'mfe' | 'openmfe' | 'cdk';
11
+ readonly kind: 'api' | 'ui' | 'stack' | 'lambda' | 'fargate';
12
12
  };
13
13
  export type VyriyCiProvider = 'gitlab' | 'github';
14
- export type VyriyCiPipeline = 'install' | 'typecheck' | 'lint' | 'prettier' | 'test' | 'build' | 'storybook' | 'docker' | 'npm-publish' | 'aws-deploy';
14
+ export type VyriyCiPipeline = 'install' | 'lint' | 'test' | 'build' | 'deploy' | 'smoke' | 'e2e';
15
15
  export type VyriyCiPlan = {
16
16
  readonly enabled: boolean;
17
17
  readonly providers: VyriyCiProvider[];
18
18
  readonly pipelines: VyriyCiPipeline[];
19
19
  };
20
- export type VyriyApiStyle = 'rest' | 'graphql' | 'mixed';
21
- export type VyriyApiRuntime = 'node' | 'lambda' | 'fargate';
20
+ export type VyriyApiStyle = 'rest' | 'graphql';
21
+ export type VyriyApiRuntime = 'docker' | 'lambda';
22
22
  export type VyriyApiPlan = {
23
23
  readonly enabled: boolean;
24
24
  readonly style: VyriyApiStyle;
@@ -2,30 +2,62 @@ import { stdin, stdout } from 'node:process';
2
2
  import { createInterface } from 'node:readline';
3
3
  import { createProjectPlanFromPreset, getDefaultApiStyleFromPreset, isApiPreset, } from '../../project-plan/index.js';
4
4
  const presets = [
5
+ 'empty',
5
6
  'library',
6
7
  'api',
7
- 'react-csr',
8
- 'react-ssr',
9
- 'react-ssg',
10
- 'mfe',
11
- 'openmfe',
12
- 'mfe-bff',
13
- 'openmfe-bff',
8
+ 'ssr',
9
+ 'ssg',
10
+ 'csr',
14
11
  'fullstack',
15
- 'aws-serverless',
16
- 'empty',
12
+ 'mfe',
17
13
  ];
18
- const extraFeatures = [
19
- 'storybook',
14
+ const presetDescriptions = {
15
+ empty: 'shared tooling without application code',
16
+ library: 'publishable React package for reusable UI',
17
+ api: 'REST or GraphQL backend API',
18
+ ssr: 'server-rendered React application',
19
+ ssg: 'build-time generated static React site',
20
+ csr: 'browser-rendered React application',
21
+ fullstack: 'React frontend with backend API',
22
+ mfe: 'OpenMFE widget with UI, API, SSR, and manifest',
23
+ };
24
+ const infrastructureOptions = ['docker', 'aws'];
25
+ const infrastructureFeatureMap = {
26
+ docker: ['docker'],
27
+ aws: [
28
+ 'aws-cdk',
29
+ 'lambda',
30
+ 'apigateway',
31
+ ],
32
+ };
33
+ const extraFeatureMap = {
34
+ docker: ['docker'],
35
+ 'aws-api': [
36
+ 'aws-cdk',
37
+ 'lambda',
38
+ 'apigateway',
39
+ ],
40
+ 'aws-fargate': [
41
+ 'aws-cdk',
42
+ 'fargate',
43
+ 'docker',
44
+ ],
45
+ 'aws-static': [
46
+ 'aws-cdk',
47
+ 's3',
48
+ 'cloudfront',
49
+ ],
50
+ };
51
+ const directFeatureInputs = [
20
52
  'docker',
21
53
  'aws-cdk',
22
- 'dynamodb',
54
+ 'apigateway',
23
55
  'lambda',
24
56
  'fargate',
25
57
  's3',
26
58
  'cloudfront',
27
59
  ];
28
- const apiStyles = ['rest', 'graphql', 'mixed'];
60
+ const apiStyles = ['rest', 'graphql'];
29
61
  const ciProviders = ['none', 'gitlab', 'github'];
30
62
  const createQuestion = (readline, output) => {
31
63
  const queuedLines = [];
@@ -64,10 +96,37 @@ const parsePreset = (value, defaultValue) => {
64
96
  }
65
97
  return presets.includes(normalizedValue) ? normalizedValue : defaultValue;
66
98
  };
67
- const parseFeatures = (value) => value
68
- .split(',')
69
- .map((feature) => feature.trim())
70
- .filter((feature) => extraFeatures.includes(feature));
99
+ const parseFeatures = (value) => [
100
+ ...new Set(value
101
+ .split(',')
102
+ .map((feature) => feature.trim())
103
+ .flatMap((feature) => extraFeatureMap[feature] ??
104
+ (directFeatureInputs.includes(feature)
105
+ ? [feature]
106
+ : []))),
107
+ ];
108
+ const parseInfrastructure = (value, defaultValue) => {
109
+ const normalizedValue = value.trim().toLowerCase();
110
+ const numericValue = Number.parseInt(normalizedValue, 10);
111
+ if (Number.isInteger(numericValue) && infrastructureOptions[numericValue - 1]) {
112
+ return [...infrastructureFeatureMap[infrastructureOptions[numericValue - 1]]];
113
+ }
114
+ if (infrastructureOptions.includes(normalizedValue)) {
115
+ return [...infrastructureFeatureMap[normalizedValue]];
116
+ }
117
+ const legacyFeatures = parseFeatures(value);
118
+ return legacyFeatures.length > 0 ? legacyFeatures : [...infrastructureFeatureMap[defaultValue]];
119
+ };
120
+ const getDefaultInfrastructureInput = (features) => features?.some((feature) => [
121
+ 'aws-cdk',
122
+ 'lambda',
123
+ 'apigateway',
124
+ 'fargate',
125
+ 's3',
126
+ 'cloudfront',
127
+ ].includes(feature))
128
+ ? 'aws'
129
+ : 'docker';
71
130
  const parseApiStyle = (value, defaultValue) => {
72
131
  const normalizedValue = value.trim().toLowerCase();
73
132
  const numericValue = Number.parseInt(normalizedValue, 10);
@@ -96,12 +155,13 @@ export const askProjectPlan = async ({ defaults = {}, input = stdin, output = st
96
155
  const packageScope = await promptWithDefault(question, 'Package scope', defaults.packageScope ?? `@${projectName}`);
97
156
  const description = await promptWithDefault(question, 'Description', defaults.description ?? 'Calm cloud-ready application.');
98
157
  output.write('\nProject preset:\n');
99
- presets.forEach((preset, index) => output.write(` ${index + 1}. ${preset}\n`));
100
- const presetAnswer = await promptWithDefault(question, 'Preset number or name', defaults.preset ?? 'react-ssr');
101
- const preset = parsePreset(presetAnswer, defaults.preset ?? 'react-ssr');
158
+ presets.forEach((preset, index) => output.write(` ${index + 1}. ${preset} - ${presetDescriptions[preset]}\n`));
159
+ const defaultPreset = defaults.preset ?? 'empty';
160
+ const presetAnswer = await promptWithDefault(question, 'Preset number or name', defaultPreset);
161
+ const preset = parsePreset(presetAnswer, defaultPreset);
102
162
  const defaultApiStyle = defaults.apiStyle ?? getDefaultApiStyleFromPreset(preset);
103
163
  const apiStyle = isApiPreset(preset)
104
- ? parseApiStyle(await promptWithDefault(question, 'API style: 1. rest (@vyriy/router), 2. graphql, 3. mixed', defaultApiStyle), defaultApiStyle)
164
+ ? parseApiStyle(await promptWithDefault(question, 'API style:\n 1. rest (@vyriy/router),\n 2. graphql', defaultApiStyle), defaultApiStyle)
105
165
  : undefined;
106
166
  output.write('\nCI/CD provider:\n');
107
167
  output.write(' 1. none\n');
@@ -109,10 +169,12 @@ export const askProjectPlan = async ({ defaults = {}, input = stdin, output = st
109
169
  output.write(' 3. github\n');
110
170
  const defaultCiProvider = defaults.ciProvider ?? 'none';
111
171
  const ciProvider = parseCiProvider(await promptWithDefault(question, 'CI/CD provider number or name', defaultCiProvider), defaultCiProvider);
112
- output.write('\nAdditional features, comma-separated:\n');
113
- output.write(` ${extraFeatures.join(', ')}\n`);
114
- const featuresAnswer = await promptWithDefault(question, 'Features', defaults.features?.join(', ') ?? '');
115
- const features = parseFeatures(featuresAnswer);
172
+ output.write('\nInfrastructure:\n');
173
+ output.write(' 1. Docker\n');
174
+ output.write(' 2. AWS\n');
175
+ const defaultInfrastructure = getDefaultInfrastructureInput(defaults.features);
176
+ const featuresAnswer = await promptWithDefault(question, 'Infrastructure number or name', defaultInfrastructure);
177
+ const features = parseInfrastructure(featuresAnswer, defaultInfrastructure);
116
178
  const plan = createProjectPlanFromPreset({
117
179
  projectName,
118
180
  targetDirectory,
package/shared/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './commandExists.js';
2
2
  export * from './execCommand.js';
3
3
  export * from './fileExists.js';
4
+ export * from './runCommand.js';
4
5
  export * from './semver.js';
5
6
  export type * from './types.js';
package/shared/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './commandExists.js';
2
2
  export * from './execCommand.js';
3
3
  export * from './fileExists.js';
4
+ export * from './runCommand.js';
4
5
  export * from './semver.js';
@@ -0,0 +1,9 @@
1
+ import { RunCommand, RunCommandOptions } from './types.js';
2
+ export declare class RunCommandError extends Error {
3
+ readonly args: readonly string[];
4
+ readonly command: string;
5
+ readonly cwd: string;
6
+ readonly exitCode: number | null;
7
+ constructor({ args, command, cwd }: RunCommandOptions, exitCode: number | null);
8
+ }
9
+ export declare const runCommand: RunCommand;
@@ -0,0 +1,34 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { env as processEnv } from 'node:process';
3
+ export class RunCommandError extends Error {
4
+ args;
5
+ command;
6
+ cwd;
7
+ exitCode;
8
+ constructor({ args = [], command, cwd }, exitCode) {
9
+ super(`Command failed with exit code ${exitCode ?? 'unknown'}: ${command} ${args.join(' ')}`.trim());
10
+ this.name = 'RunCommandError';
11
+ this.args = args;
12
+ this.command = command;
13
+ this.cwd = cwd;
14
+ this.exitCode = exitCode;
15
+ }
16
+ }
17
+ export const runCommand = async (options) => {
18
+ const child = spawn(options.command, [...(options.args ?? [])], {
19
+ cwd: options.cwd,
20
+ env: options.env ?? processEnv,
21
+ shell: false,
22
+ stdio: 'inherit',
23
+ });
24
+ await new Promise((resolve, reject) => {
25
+ child.on('error', reject);
26
+ child.on('close', (code) => {
27
+ if (code === 0) {
28
+ resolve();
29
+ return;
30
+ }
31
+ reject(new RunCommandError(options, code));
32
+ });
33
+ });
34
+ };
package/shared/types.d.ts CHANGED
@@ -1,4 +1,11 @@
1
1
  export type ExecCommand = (command: string, args?: readonly string[]) => Promise<string>;
2
+ export type RunCommandOptions = {
3
+ readonly command: string;
4
+ readonly args?: readonly string[];
5
+ readonly cwd: string;
6
+ readonly env?: Record<string, string | undefined>;
7
+ };
8
+ export type RunCommand = (options: RunCommandOptions) => Promise<void>;
2
9
  export type CommandExists = (command: string, options?: {
3
10
  readonly execCommand?: ExecCommand;
4
11
  }) => Promise<boolean>;