vyriy 0.3.5 → 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.
- package/README.md +41 -21
- package/cli/args/args.js +4 -0
- package/cli/args/types.d.ts +4 -0
- package/cli/cli.js +13 -2
- package/commands/new/new.js +77 -6
- package/commands/new/types.d.ts +2 -0
- package/package.json +11 -1
- package/presets/createProjectFiles.js +271 -42
- package/project-plan/api/api.js +5 -17
- package/project-plan/api/types.d.ts +1 -0
- package/project-plan/create/create.js +68 -77
- package/project-plan/kind/kind.js +1 -16
- package/project-plan/types.d.ts +7 -7
- package/prompts/project-plan/project-plan.js +58 -24
- package/shared/index.d.ts +1 -0
- package/shared/index.js +1 -0
- package/shared/runCommand.d.ts +9 -0
- package/shared/runCommand.js +34 -0
- package/shared/types.d.ts +7 -0
package/README.md
CHANGED
|
@@ -36,6 +36,10 @@ vyriy init
|
|
|
36
36
|
vyriy doctor
|
|
37
37
|
vyriy --dry-run
|
|
38
38
|
vyriy --yes
|
|
39
|
+
vyriy --no-install
|
|
40
|
+
vyriy --no-verify
|
|
41
|
+
vyriy --install-only
|
|
42
|
+
vyriy --verify
|
|
39
43
|
vyriy --overwrite
|
|
40
44
|
vyriy --skip-existing
|
|
41
45
|
vyriy --help
|
|
@@ -48,7 +52,7 @@ Runs the same flow as `vyriy new`.
|
|
|
48
52
|
|
|
49
53
|
### `vyriy new [name]`
|
|
50
54
|
|
|
51
|
-
Starts the project planning wizard, prints the project summary and file plan,
|
|
55
|
+
Starts the project planning wizard, prints the project summary and file plan, writes generated files when no unresolved conflicts exist, installs dependencies, and runs generated project checks.
|
|
52
56
|
|
|
53
57
|
If `name` is provided, it is used as the default project name and target directory.
|
|
54
58
|
|
|
@@ -79,11 +83,27 @@ Node.js is fatal when unsupported. Yarn and Git are warnings so generation can c
|
|
|
79
83
|
|
|
80
84
|
### `--dry-run`
|
|
81
85
|
|
|
82
|
-
Prints the doctor report, project summary, and file plan without writing files or running
|
|
86
|
+
Prints the doctor report, project summary, and file plan without writing files, installing dependencies, or running checks.
|
|
87
|
+
|
|
88
|
+
### `--no-install`
|
|
89
|
+
|
|
90
|
+
Writes generated files but skips `yarn install` and `yarn check`.
|
|
91
|
+
|
|
92
|
+
### `--no-verify`
|
|
93
|
+
|
|
94
|
+
Writes generated files and runs `yarn install`, but skips `yarn check`.
|
|
95
|
+
|
|
96
|
+
### `--install-only`
|
|
97
|
+
|
|
98
|
+
Alias for `--no-verify`.
|
|
99
|
+
|
|
100
|
+
### `--verify`
|
|
101
|
+
|
|
102
|
+
Explicitly enables `yarn check`. This is already the default unless `--no-install`, `--no-verify`, or `--install-only` is passed.
|
|
83
103
|
|
|
84
104
|
### `--yes`
|
|
85
105
|
|
|
86
|
-
Uses default wizard answers and avoids prompts where possible. It does not overwrite existing files unless `--overwrite` is also passed.
|
|
106
|
+
Uses default wizard answers and avoids prompts where possible. In non-interactive mode, the default preset is `empty` with no CI/CD provider. It does not overwrite existing files unless `--overwrite` is also passed.
|
|
87
107
|
|
|
88
108
|
### `--overwrite`
|
|
89
109
|
|
|
@@ -106,10 +126,10 @@ The wizard collects:
|
|
|
106
126
|
- project preset
|
|
107
127
|
- API style for API-capable presets
|
|
108
128
|
- CI/CD provider
|
|
109
|
-
-
|
|
129
|
+
- infrastructure provider
|
|
110
130
|
- confirmation
|
|
111
131
|
|
|
112
|
-
After confirmation, the CLI prints the project plan, creates generated files in memory, builds a conflict-aware file plan,
|
|
132
|
+
After confirmation, the CLI prints the project plan, creates generated files in memory, builds a conflict-aware file plan, writes the accepted file plan, runs `yarn install`, and runs `yarn check`.
|
|
113
133
|
|
|
114
134
|
Presets do not write to disk directly.
|
|
115
135
|
|
|
@@ -119,28 +139,28 @@ Generated projects always include `AGENTS.md` based on the shared Vyriy package
|
|
|
119
139
|
|
|
120
140
|
Supported presets:
|
|
121
141
|
|
|
142
|
+
- `empty`
|
|
122
143
|
- `library`
|
|
123
144
|
- `api`
|
|
124
|
-
- `
|
|
125
|
-
- `
|
|
126
|
-
- `
|
|
127
|
-
- `mfe`
|
|
128
|
-
- `openmfe`
|
|
129
|
-
- `mfe-bff`
|
|
130
|
-
- `openmfe-bff`
|
|
145
|
+
- `ssr`
|
|
146
|
+
- `ssg`
|
|
147
|
+
- `csr`
|
|
131
148
|
- `fullstack`
|
|
132
|
-
- `
|
|
133
|
-
|
|
149
|
+
- `mfe`
|
|
150
|
+
|
|
151
|
+
The preset is the concrete future generated setup. The project kind is the broader architecture category. The infrastructure choice is selected separately: Docker is the default local/container shape, while AWS selects CDK plus Lambda/API Gateway for API-capable presets.
|
|
152
|
+
|
|
153
|
+
The `mfe` preset uses OpenMFE as the default MFE contract shape. There is no separate `openmfe` preset unless a future use case proves that split is useful.
|
|
134
154
|
|
|
135
|
-
|
|
155
|
+
Workspace kinds describe deployment intent: `ui` is universal UI output, `api` is Docker-oriented, `lambda` is the AWS API runtime, `fargate` is an AWS container runtime, and `stack` contains AWS infrastructure.
|
|
136
156
|
|
|
137
157
|
Examples:
|
|
138
158
|
|
|
139
|
-
- `
|
|
140
|
-
- `
|
|
141
|
-
- `
|
|
142
|
-
- `
|
|
143
|
-
- `
|
|
159
|
+
- `csr` -> `csr`
|
|
160
|
+
- `ssr` -> `ssr`
|
|
161
|
+
- `ssg` -> `ssg`
|
|
162
|
+
- `mfe` -> `mfe`
|
|
163
|
+
- `fullstack` -> `fullstack`
|
|
144
164
|
|
|
145
165
|
## Public API
|
|
146
166
|
|
|
@@ -178,7 +198,7 @@ It includes:
|
|
|
178
198
|
- architecture: `preset`, `projectKind`
|
|
179
199
|
- selected features
|
|
180
200
|
- CI/CD planning: enabled state, providers, and validation pipelines
|
|
181
|
-
- API planning for API-capable presets: REST
|
|
201
|
+
- API planning for API-capable presets: REST or GraphQL API style
|
|
182
202
|
- future package plans
|
|
183
203
|
- future workspace plans
|
|
184
204
|
|
package/cli/args/args.js
CHANGED
|
@@ -6,6 +6,8 @@ export const parseArgs = (args) => {
|
|
|
6
6
|
return { type: 'version' };
|
|
7
7
|
}
|
|
8
8
|
const dryRun = args.includes('--dry-run');
|
|
9
|
+
const install = !args.includes('--no-install');
|
|
10
|
+
const verify = install && (args.includes('--verify') || (!args.includes('--no-verify') && !args.includes('--install-only')));
|
|
9
11
|
const yes = args.includes('--yes') || args.includes('-y');
|
|
10
12
|
const overwrite = args.includes('--overwrite');
|
|
11
13
|
const skipExisting = args.includes('--skip-existing');
|
|
@@ -13,8 +15,10 @@ export const parseArgs = (args) => {
|
|
|
13
15
|
const [command, projectName] = positionalArgs;
|
|
14
16
|
const options = {
|
|
15
17
|
dryRun,
|
|
18
|
+
install,
|
|
16
19
|
yes,
|
|
17
20
|
overwrite,
|
|
21
|
+
verify,
|
|
18
22
|
skipExisting,
|
|
19
23
|
};
|
|
20
24
|
if (!command) {
|
package/cli/args/types.d.ts
CHANGED
|
@@ -2,14 +2,18 @@ export type VyriyCliCommand = {
|
|
|
2
2
|
readonly type: 'new';
|
|
3
3
|
readonly projectName?: string;
|
|
4
4
|
readonly dryRun: boolean;
|
|
5
|
+
readonly install: boolean;
|
|
5
6
|
readonly yes: boolean;
|
|
6
7
|
readonly overwrite: boolean;
|
|
8
|
+
readonly verify: boolean;
|
|
7
9
|
readonly skipExisting: boolean;
|
|
8
10
|
} | {
|
|
9
11
|
readonly type: 'init';
|
|
10
12
|
readonly dryRun: boolean;
|
|
13
|
+
readonly install: boolean;
|
|
11
14
|
readonly yes: boolean;
|
|
12
15
|
readonly overwrite: boolean;
|
|
16
|
+
readonly verify: boolean;
|
|
13
17
|
readonly skipExisting: boolean;
|
|
14
18
|
} | {
|
|
15
19
|
readonly type: 'doctor' | 'help' | 'version';
|
package/cli/cli.js
CHANGED
|
@@ -10,9 +10,16 @@ Usage:
|
|
|
10
10
|
vyriy init Initialize the current directory
|
|
11
11
|
vyriy . Initialize the current directory
|
|
12
12
|
vyriy doctor Check local environment
|
|
13
|
+
vyriy --yes, -y Use defaults where possible (empty preset)
|
|
13
14
|
vyriy --dry-run Print checks and file plan without writing
|
|
14
|
-
vyriy --
|
|
15
|
-
vyriy --
|
|
15
|
+
vyriy --overwrite Overwrite existing generated paths
|
|
16
|
+
vyriy --skip-existing Leave existing generated paths untouched
|
|
17
|
+
vyriy --no-install Create files without installing dependencies
|
|
18
|
+
vyriy --no-verify Install dependencies without running checks
|
|
19
|
+
vyriy --install-only Alias for --no-verify
|
|
20
|
+
vyriy --verify Explicitly enable generated project checks
|
|
21
|
+
vyriy --help, -h Show help
|
|
22
|
+
vyriy --version, -v Show version
|
|
16
23
|
|
|
17
24
|
Examples:
|
|
18
25
|
vyriy new my-app
|
|
@@ -25,19 +32,23 @@ export const runVyriyCli = async (args = [], { output = console } = {}) => {
|
|
|
25
32
|
case 'new':
|
|
26
33
|
code = await runNewCommand({
|
|
27
34
|
dryRun: command.dryRun,
|
|
35
|
+
install: command.install,
|
|
28
36
|
output,
|
|
29
37
|
overwrite: command.overwrite,
|
|
30
38
|
projectName: command.projectName,
|
|
31
39
|
skipExisting: command.skipExisting,
|
|
40
|
+
verify: command.verify,
|
|
32
41
|
yes: command.yes,
|
|
33
42
|
});
|
|
34
43
|
break;
|
|
35
44
|
case 'init':
|
|
36
45
|
code = await runInitCommand({
|
|
37
46
|
dryRun: command.dryRun,
|
|
47
|
+
install: command.install,
|
|
38
48
|
output,
|
|
39
49
|
overwrite: command.overwrite,
|
|
40
50
|
skipExisting: command.skipExisting,
|
|
51
|
+
verify: command.verify,
|
|
41
52
|
yes: command.yes,
|
|
42
53
|
});
|
|
43
54
|
break;
|
package/commands/new/new.js
CHANGED
|
@@ -5,6 +5,8 @@ import { createFilePlan, printFilePlan, writeFilePlan } from '../../file-plan/in
|
|
|
5
5
|
import { createProjectFiles } from '../../presets/index.js';
|
|
6
6
|
import { askProjectPlan as askProjectPlanDefault } from '../../prompts/project-plan/index.js';
|
|
7
7
|
import { createProjectPlanFromPreset, printProjectPlan } from '../../project-plan/index.js';
|
|
8
|
+
import { runCommand } from '../../shared/index.js';
|
|
9
|
+
const defaultYesPreset = 'empty';
|
|
8
10
|
const getConflicts = (filePlan) => filePlan.filter((item) => item.status === 'conflict');
|
|
9
11
|
const logConflicts = (output, conflicts, method) => {
|
|
10
12
|
output[method]('\nExisting files found:\n');
|
|
@@ -28,6 +30,70 @@ const createResolvedFilePlan = async (plan, projectFiles, resolution) => createF
|
|
|
28
30
|
overwrite: resolution === 'overwrite',
|
|
29
31
|
skipExisting: resolution === 'skip',
|
|
30
32
|
});
|
|
33
|
+
const formatCommand = (command, args) => [command, ...args].join(' ');
|
|
34
|
+
const printFailedPostGenerationCommand = ({ args, command, intro, output, projectDirectory, }) => {
|
|
35
|
+
const commandText = formatCommand(command, args);
|
|
36
|
+
output.error(`\n${intro}\n`);
|
|
37
|
+
output.error(`Failed command:\n ${commandText}\n`);
|
|
38
|
+
output.error(`Project directory:\n ${projectDirectory}\n`);
|
|
39
|
+
output.error(`You can inspect it and run manually:\n cd ${projectDirectory}\n ${commandText}`);
|
|
40
|
+
};
|
|
41
|
+
const runPostGenerationCommands = async ({ install, output, projectDirectory, verify, }) => {
|
|
42
|
+
if (!install) {
|
|
43
|
+
output.log('Installing dependencies... SKIPPED');
|
|
44
|
+
output.log('Running checks... SKIPPED');
|
|
45
|
+
output.log('\nProject files were created.');
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
await runCommand({
|
|
50
|
+
args: ['install'],
|
|
51
|
+
command: 'yarn',
|
|
52
|
+
cwd: projectDirectory,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
printFailedPostGenerationCommand({
|
|
57
|
+
args: ['install'],
|
|
58
|
+
command: 'yarn',
|
|
59
|
+
intro: 'Project files were created, but dependency installation failed.',
|
|
60
|
+
output,
|
|
61
|
+
projectDirectory,
|
|
62
|
+
});
|
|
63
|
+
return 1;
|
|
64
|
+
}
|
|
65
|
+
output.log('Installing dependencies... OK');
|
|
66
|
+
if (!verify) {
|
|
67
|
+
output.log('Running checks... SKIPPED');
|
|
68
|
+
output.log('\nProject files were created and dependencies were installed.');
|
|
69
|
+
return 0;
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
await runCommand({
|
|
73
|
+
args: ['fix'],
|
|
74
|
+
command: 'yarn',
|
|
75
|
+
cwd: projectDirectory,
|
|
76
|
+
});
|
|
77
|
+
await runCommand({
|
|
78
|
+
args: ['check'],
|
|
79
|
+
command: 'yarn',
|
|
80
|
+
cwd: projectDirectory,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
printFailedPostGenerationCommand({
|
|
85
|
+
args: ['check'],
|
|
86
|
+
command: 'yarn',
|
|
87
|
+
intro: 'Project files were created and dependencies were installed, but verification failed.',
|
|
88
|
+
output,
|
|
89
|
+
projectDirectory,
|
|
90
|
+
});
|
|
91
|
+
return 1;
|
|
92
|
+
}
|
|
93
|
+
output.log('Running checks... OK');
|
|
94
|
+
output.log('\nProject is ready.');
|
|
95
|
+
return 0;
|
|
96
|
+
};
|
|
31
97
|
const resolveInteractiveConflicts = async (plan, projectFiles, output, conflicts, askConflictResolution) => {
|
|
32
98
|
logConflicts(output, conflicts, 'log');
|
|
33
99
|
printConflictPrompt(output);
|
|
@@ -46,7 +112,7 @@ const resolveInteractiveConflicts = async (plan, projectFiles, output, conflicts
|
|
|
46
112
|
export const askConflictResolutionDefault = async () => {
|
|
47
113
|
const readline = createInterface({ input: stdin, output: stdout });
|
|
48
114
|
try {
|
|
49
|
-
const answer = (await readline.question('What should Vyriy do
|
|
115
|
+
const answer = (await readline.question('What should Vyriy do?\n\n 1. overwrite existing files,\n\n 2. skip existing files,\n\n 3. abort (abort): '))
|
|
50
116
|
.trim()
|
|
51
117
|
.toLowerCase();
|
|
52
118
|
if (answer === '1' || answer === 'overwrite') {
|
|
@@ -61,7 +127,7 @@ export const askConflictResolutionDefault = async () => {
|
|
|
61
127
|
readline.close();
|
|
62
128
|
}
|
|
63
129
|
};
|
|
64
|
-
export const runNewCommand = async ({ askConflictResolution = askConflictResolutionDefault, askProjectPlan = askProjectPlanDefault, dryRun = false, output = console, overwrite = false, projectName = 'my-app', skipExisting = false, yes = false, } = {}) => {
|
|
130
|
+
export const runNewCommand = async ({ askConflictResolution = askConflictResolutionDefault, askProjectPlan = askProjectPlanDefault, dryRun = false, install = true, output = console, overwrite = false, projectName = 'my-app', skipExisting = false, verify = true, yes = false, } = {}) => {
|
|
65
131
|
if (overwrite && skipExisting) {
|
|
66
132
|
output.error('Cannot use --overwrite and --skip-existing together.');
|
|
67
133
|
return 1;
|
|
@@ -72,13 +138,13 @@ export const runNewCommand = async ({ askConflictResolution = askConflictResolut
|
|
|
72
138
|
output.error('\nPlease install Node.js 24+ and run the command again.');
|
|
73
139
|
return 1;
|
|
74
140
|
}
|
|
75
|
-
const plan =
|
|
141
|
+
const plan = yes
|
|
76
142
|
? createProjectPlanFromPreset({
|
|
77
143
|
apiStyle: 'rest',
|
|
78
144
|
ciProvider: 'none',
|
|
79
145
|
description: 'Calm cloud-ready application.',
|
|
80
146
|
packageScope: `@${projectName}`,
|
|
81
|
-
preset:
|
|
147
|
+
preset: defaultYesPreset,
|
|
82
148
|
projectName,
|
|
83
149
|
targetDirectory: projectName,
|
|
84
150
|
})
|
|
@@ -113,6 +179,11 @@ export const runNewCommand = async ({ askConflictResolution = askConflictResolut
|
|
|
113
179
|
output.log(`\n${printFilePlan(resolved.filePlan)}`);
|
|
114
180
|
}
|
|
115
181
|
await writeFilePlan(plan.targetDirectory, filePlan);
|
|
116
|
-
output.log('\
|
|
117
|
-
return
|
|
182
|
+
output.log('\nCreating project files... OK');
|
|
183
|
+
return runPostGenerationCommands({
|
|
184
|
+
install,
|
|
185
|
+
output,
|
|
186
|
+
projectDirectory: plan.targetDirectory,
|
|
187
|
+
verify: install && verify,
|
|
188
|
+
});
|
|
118
189
|
};
|
package/commands/new/types.d.ts
CHANGED
|
@@ -6,8 +6,10 @@ export type RunNewCommandOptions = {
|
|
|
6
6
|
readonly askConflictResolution?: () => Promise<ConflictResolution>;
|
|
7
7
|
readonly output?: Pick<typeof console, 'log' | 'error'>;
|
|
8
8
|
readonly dryRun?: boolean;
|
|
9
|
+
readonly install?: boolean;
|
|
9
10
|
readonly yes?: boolean;
|
|
10
11
|
readonly overwrite?: boolean;
|
|
12
|
+
readonly verify?: boolean;
|
|
11
13
|
readonly skipExisting?: boolean;
|
|
12
14
|
};
|
|
13
15
|
export type RunNewCommand = (options?: RunNewCommandOptions) => Promise<number>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vyriy",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.8",
|
|
4
4
|
"description": "Interactive project master for calm cloud-ready applications.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.js",
|
|
@@ -489,6 +489,16 @@
|
|
|
489
489
|
"import": "./shared/index.js",
|
|
490
490
|
"default": "./shared/index.js"
|
|
491
491
|
},
|
|
492
|
+
"./shared/runCommand": {
|
|
493
|
+
"types": "./shared/runCommand.d.ts",
|
|
494
|
+
"import": "./shared/runCommand.js",
|
|
495
|
+
"default": "./shared/runCommand.js"
|
|
496
|
+
},
|
|
497
|
+
"./shared/runCommand.js": {
|
|
498
|
+
"types": "./shared/runCommand.d.ts",
|
|
499
|
+
"import": "./shared/runCommand.js",
|
|
500
|
+
"default": "./shared/runCommand.js"
|
|
501
|
+
},
|
|
492
502
|
"./shared/semver": {
|
|
493
503
|
"types": "./shared/semver.d.ts",
|
|
494
504
|
"import": "./shared/semver.js",
|
|
@@ -1,42 +1,94 @@
|
|
|
1
1
|
import { agentsTemplate } from './agentsTemplate.js';
|
|
2
|
+
import rootPackageJson from '../../../package.json' with { type: 'json' };
|
|
2
3
|
const json = (value) => `${JSON.stringify(value, null, 2)}\n`;
|
|
3
|
-
const
|
|
4
|
+
const packageVersion = (version) => `^${version}`;
|
|
5
|
+
const baseRootDevDependencies = {
|
|
6
|
+
'@vyriy/typescript-config': packageVersion(rootPackageJson.version),
|
|
7
|
+
typescript: rootPackageJson.dependencies.typescript,
|
|
8
|
+
'@vyriy/prettier-config': packageVersion(rootPackageJson.version),
|
|
9
|
+
prettier: rootPackageJson.dependencies.prettier,
|
|
10
|
+
'@vyriy/eslint-config': packageVersion(rootPackageJson.version),
|
|
11
|
+
eslint: rootPackageJson.dependencies.eslint,
|
|
12
|
+
'@vyriy/jest-config': packageVersion(rootPackageJson.version),
|
|
13
|
+
jest: rootPackageJson.dependencies.jest,
|
|
14
|
+
'@vyriy/storybook-config': packageVersion(rootPackageJson.version),
|
|
15
|
+
storybook: rootPackageJson.dependencies.storybook,
|
|
16
|
+
'@vyriy/path': packageVersion(rootPackageJson.version),
|
|
17
|
+
husky: rootPackageJson.dependencies.husky,
|
|
18
|
+
'npm-run-all2': rootPackageJson.dependencies['npm-run-all2'],
|
|
19
|
+
'cross-env': rootPackageJson.dependencies['cross-env'],
|
|
20
|
+
};
|
|
21
|
+
const stylelintDevDependencies = {
|
|
22
|
+
'@vyriy/stylelint-config': packageVersion(rootPackageJson.version),
|
|
23
|
+
stylelint: rootPackageJson.dependencies.stylelint,
|
|
24
|
+
};
|
|
25
|
+
const createRootPackageJson = ({ description, packageScope, projectName, stylelint, }) => ({
|
|
4
26
|
path: 'package.json',
|
|
5
27
|
content: json({
|
|
6
28
|
name: `${packageScope}/${projectName}`,
|
|
7
|
-
version: '0.
|
|
29
|
+
version: '0.0.0',
|
|
8
30
|
description,
|
|
9
31
|
private: true,
|
|
10
32
|
type: 'module',
|
|
11
|
-
packageManager:
|
|
33
|
+
packageManager: rootPackageJson.packageManager,
|
|
12
34
|
engines: {
|
|
13
|
-
node:
|
|
35
|
+
node: rootPackageJson.engines.node,
|
|
14
36
|
},
|
|
15
|
-
scripts: {
|
|
16
|
-
lint: 'eslint .',
|
|
17
|
-
test: 'jest --coverage=false',
|
|
18
|
-
build: 'tsc --pretty false',
|
|
19
|
-
deploy: 'echo "Deploy is not configured yet."',
|
|
20
|
-
smoke: 'echo "Smoke checks are not configured yet."',
|
|
21
|
-
e2e: 'echo "E2E checks are not configured yet."',
|
|
22
|
-
},
|
|
23
|
-
devDependencies: {},
|
|
24
37
|
workspaces: [
|
|
25
38
|
'packages/*',
|
|
26
39
|
'workspaces/*',
|
|
27
40
|
],
|
|
41
|
+
scripts: {
|
|
42
|
+
storybook: 'cross-env STORYBOOK_DISABLE_TELEMETRY=1 storybook dev -p 6006 --disable-telemetry',
|
|
43
|
+
check: 'run-s lint build test',
|
|
44
|
+
fix: "run-s 'fix:*'",
|
|
45
|
+
lint: "run-s 'lint:*'",
|
|
46
|
+
build: "run-s 'build:*'",
|
|
47
|
+
test: "run-s 'test:*'",
|
|
48
|
+
'fix:prettier': 'prettier . --write',
|
|
49
|
+
'fix:eslint': 'eslint . --fix',
|
|
50
|
+
'lint:ts': 'tsc --pretty false',
|
|
51
|
+
'lint:prettier': 'prettier . --check',
|
|
52
|
+
'lint:eslint': 'eslint .',
|
|
53
|
+
...(stylelint ? { 'lint:stylelint': 'stylelint "packages/**/*.{scss,css}"' } : {}),
|
|
54
|
+
'build:dist': 'echo "Build dist is not configured yet."',
|
|
55
|
+
'build:storybook': 'cross-env STORYBOOK_DISABLE_TELEMETRY=1 storybook build --quiet --disable-telemetry',
|
|
56
|
+
'test:jest': 'jest --passWithNoTests',
|
|
57
|
+
postinstall: 'husky',
|
|
58
|
+
},
|
|
59
|
+
devDependencies: {
|
|
60
|
+
...baseRootDevDependencies,
|
|
61
|
+
...(stylelint ? stylelintDevDependencies : {}),
|
|
62
|
+
},
|
|
28
63
|
}),
|
|
29
64
|
});
|
|
30
65
|
const createPackageManifest = ({ packageScope, workspaceName, }) => ({
|
|
31
66
|
path: `packages/${workspaceName}/package.json`,
|
|
32
67
|
content: json({
|
|
33
68
|
name: `${packageScope}/${workspaceName}`,
|
|
34
|
-
version: '0.
|
|
69
|
+
version: '0.0.0',
|
|
35
70
|
private: true,
|
|
36
71
|
type: 'module',
|
|
37
72
|
main: 'index.js',
|
|
38
73
|
}),
|
|
39
74
|
});
|
|
75
|
+
const getWorkspacePath = (workspacePlan) => workspacePlan.kind === 'lambda' && workspacePlan.name === 'api'
|
|
76
|
+
? `workspaces/lambda/${workspacePlan.name}`
|
|
77
|
+
: `workspaces/${workspacePlan.name}`;
|
|
78
|
+
const getWorkspacePackageName = ({ packageScope, workspacePlan, }) => workspacePlan.kind === 'lambda' && workspacePlan.name === 'api'
|
|
79
|
+
? `${packageScope}/lambda-api-workspace`
|
|
80
|
+
: `${packageScope}/${workspacePlan.name}-workspace`;
|
|
81
|
+
const isApiWorkspace = (workspacePlan) => [
|
|
82
|
+
'api',
|
|
83
|
+
'lambda',
|
|
84
|
+
'fargate',
|
|
85
|
+
].includes(workspacePlan.kind);
|
|
86
|
+
const createApiWorkspaceDependencies = (workspacePlan) => isApiWorkspace(workspacePlan)
|
|
87
|
+
? {
|
|
88
|
+
'@vyriy/handler': packageVersion(rootPackageJson.version),
|
|
89
|
+
'@vyriy/server': packageVersion(rootPackageJson.version),
|
|
90
|
+
}
|
|
91
|
+
: {};
|
|
40
92
|
const createPackageFiles = (plan, packagePlan) => [
|
|
41
93
|
createPackageManifest({
|
|
42
94
|
packageScope: plan.packageScope,
|
|
@@ -72,39 +124,109 @@ const createPackageFiles = (plan, packagePlan) => [
|
|
|
72
124
|
},
|
|
73
125
|
]),
|
|
74
126
|
];
|
|
75
|
-
const
|
|
76
|
-
{
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
127
|
+
const createApiHandlerFile = (workspacePath) => ({
|
|
128
|
+
path: `${workspacePath}/handler.ts`,
|
|
129
|
+
content: `import { api } from '@vyriy/handler';
|
|
130
|
+
|
|
131
|
+
export const handler = api(async (event) => ({
|
|
132
|
+
statusCode: 200,
|
|
133
|
+
body: JSON.stringify({
|
|
134
|
+
path: event.path,
|
|
135
|
+
}),
|
|
136
|
+
}));
|
|
137
|
+
`,
|
|
138
|
+
});
|
|
139
|
+
const createApiServerFile = ({ entrypoint, workspacePath, }) => ({
|
|
140
|
+
path: `${workspacePath}/${entrypoint}`,
|
|
141
|
+
content: `import { server } from '@vyriy/server';
|
|
142
|
+
|
|
143
|
+
import { handler } from './handler.js';
|
|
144
|
+
|
|
145
|
+
server(handler);
|
|
146
|
+
`,
|
|
147
|
+
});
|
|
148
|
+
const createDockerfile = (workspacePath) => ({
|
|
149
|
+
path: `${workspacePath}/Dockerfile`,
|
|
150
|
+
content: `FROM node:24-alpine
|
|
151
|
+
|
|
152
|
+
WORKDIR /app
|
|
153
|
+
|
|
154
|
+
COPY package.json yarn.lock .yarnrc.yml ./
|
|
155
|
+
COPY .yarn ./.yarn
|
|
156
|
+
COPY packages ./packages
|
|
157
|
+
COPY workspaces ./workspaces
|
|
158
|
+
|
|
159
|
+
RUN corepack enable && yarn install --immutable
|
|
160
|
+
RUN yarn build
|
|
161
|
+
|
|
162
|
+
CMD ["node", "workspaces/api/index.js"]
|
|
163
|
+
`,
|
|
164
|
+
});
|
|
165
|
+
const createApiWorkspaceFiles = (plan, workspacePlan) => {
|
|
166
|
+
const workspacePath = getWorkspacePath(workspacePlan);
|
|
167
|
+
const isLambda = workspacePlan.kind === 'lambda';
|
|
168
|
+
return [
|
|
169
|
+
createApiHandlerFile(workspacePath),
|
|
170
|
+
createApiServerFile({ entrypoint: isLambda ? 'server.ts' : 'index.ts', workspacePath }),
|
|
171
|
+
{
|
|
172
|
+
path: `${workspacePath}/${workspacePlan.name}.test.ts`,
|
|
173
|
+
content: "import { describe, expect, it } from '@jest/globals';\n\nimport { handler } from './handler.js';\n\ndescribe('api workspace', () => {\n it('exports a handler', () => {\n expect(handler).toEqual(expect.any(Function));\n });\n});\n",
|
|
174
|
+
},
|
|
175
|
+
...(plan.features.includes('docker') && !isLambda ? [createDockerfile(workspacePath)] : []),
|
|
176
|
+
];
|
|
177
|
+
};
|
|
178
|
+
const createWorkspaceFiles = (plan, workspacePlan) => {
|
|
179
|
+
const workspacePath = getWorkspacePath(workspacePlan);
|
|
180
|
+
const dependencies = createApiWorkspaceDependencies(workspacePlan);
|
|
181
|
+
return [
|
|
182
|
+
{
|
|
183
|
+
path: `${workspacePath}/package.json`,
|
|
184
|
+
content: json({
|
|
185
|
+
name: getWorkspacePackageName({ packageScope: plan.packageScope, workspacePlan }),
|
|
186
|
+
version: '0.0.0',
|
|
187
|
+
private: true,
|
|
188
|
+
type: 'module',
|
|
189
|
+
main: workspacePlan.kind === 'lambda' ? 'server.js' : 'index.js',
|
|
190
|
+
...(Object.keys(dependencies).length > 0 ? { dependencies } : {}),
|
|
191
|
+
}),
|
|
192
|
+
},
|
|
193
|
+
...(isApiWorkspace(workspacePlan)
|
|
194
|
+
? createApiWorkspaceFiles(plan, workspacePlan)
|
|
195
|
+
: [
|
|
196
|
+
{
|
|
197
|
+
path: `${workspacePath}/index.ts`,
|
|
198
|
+
content: 'export type WorkspaceName = string;\n',
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
path: `${workspacePath}/${workspacePlan.name}.test.ts`,
|
|
202
|
+
content: "import { describe, expect, it } from '@jest/globals';\n\ndescribe('workspace', () => {\n it('has a test harness', () => {\n expect(true).toBe(true);\n });\n});\n",
|
|
203
|
+
},
|
|
204
|
+
]),
|
|
205
|
+
];
|
|
206
|
+
};
|
|
95
207
|
const shouldCreateStylelintConfig = (plan) => plan.features.some((feature) => [
|
|
96
208
|
'react',
|
|
97
209
|
'webpack',
|
|
98
210
|
].includes(feature));
|
|
211
|
+
const getStylePackageName = (plan) => plan.packages.find((packagePlan) => packagePlan.kind === 'ui')?.name ?? 'ui';
|
|
99
212
|
export const createProjectFiles = (plan) => [
|
|
100
|
-
createRootPackageJson(
|
|
213
|
+
createRootPackageJson({
|
|
214
|
+
...plan,
|
|
215
|
+
stylelint: shouldCreateStylelintConfig(plan),
|
|
216
|
+
}),
|
|
101
217
|
{
|
|
102
218
|
path: 'README.md',
|
|
103
219
|
content: `# ${plan.projectName}\n\n${plan.description}\n`,
|
|
104
220
|
},
|
|
105
221
|
{
|
|
106
222
|
path: 'doc.mdx',
|
|
107
|
-
content:
|
|
223
|
+
content: `import { Meta, Markdown } from '@storybook/addon-docs/blocks';
|
|
224
|
+
import ReadMe from './README.md?raw';
|
|
225
|
+
|
|
226
|
+
<Meta title="${plan.projectName}" />
|
|
227
|
+
|
|
228
|
+
<Markdown>{ReadMe}</Markdown>
|
|
229
|
+
`,
|
|
108
230
|
},
|
|
109
231
|
{
|
|
110
232
|
path: 'AGENTS.md',
|
|
@@ -112,11 +234,64 @@ export const createProjectFiles = (plan) => [
|
|
|
112
234
|
},
|
|
113
235
|
{
|
|
114
236
|
path: '.editorconfig',
|
|
115
|
-
content:
|
|
237
|
+
content: `# https://editorconfig.org
|
|
238
|
+
root = true
|
|
239
|
+
|
|
240
|
+
[*]
|
|
241
|
+
charset = utf-8
|
|
242
|
+
end_of_line = lf
|
|
243
|
+
insert_final_newline = true
|
|
244
|
+
trim_trailing_whitespace = true
|
|
245
|
+
|
|
246
|
+
indent_style = space
|
|
247
|
+
indent_size = 2
|
|
248
|
+
|
|
249
|
+
max_line_length = 100
|
|
250
|
+
|
|
251
|
+
# Markdown
|
|
252
|
+
[*.md]
|
|
253
|
+
trim_trailing_whitespace = false
|
|
254
|
+
max_line_length = off
|
|
255
|
+
|
|
256
|
+
# YAML / YML
|
|
257
|
+
[*.{yml,yaml}]
|
|
258
|
+
indent_size = 2
|
|
259
|
+
|
|
260
|
+
# JSON
|
|
261
|
+
[*.json]
|
|
262
|
+
indent_size = 2
|
|
263
|
+
|
|
264
|
+
# TypeScript / JavaScript
|
|
265
|
+
[*.{ts,tsx,js,jsx}]
|
|
266
|
+
indent_size = 2
|
|
267
|
+
|
|
268
|
+
# Shell / Bash
|
|
269
|
+
[*.sh]
|
|
270
|
+
indent_size = 2`,
|
|
116
271
|
},
|
|
117
272
|
{
|
|
118
273
|
path: '.gitignore',
|
|
119
|
-
content:
|
|
274
|
+
content: `.yarn/*
|
|
275
|
+
!.yarn/cache
|
|
276
|
+
!.yarn/patches
|
|
277
|
+
!.yarn/plugins
|
|
278
|
+
!.yarn/releases
|
|
279
|
+
!.yarn/sdks
|
|
280
|
+
!.yarn/versions
|
|
281
|
+
|
|
282
|
+
.DS_Store
|
|
283
|
+
.idea
|
|
284
|
+
node_modules
|
|
285
|
+
coverage
|
|
286
|
+
dist
|
|
287
|
+
storybook-static
|
|
288
|
+
*storybook.log
|
|
289
|
+
consumer
|
|
290
|
+
|
|
291
|
+
cdk.out
|
|
292
|
+
cdk.context.json
|
|
293
|
+
|
|
294
|
+
!/**/.gitkeep`,
|
|
120
295
|
},
|
|
121
296
|
{
|
|
122
297
|
path: '.npmrc',
|
|
@@ -124,12 +299,50 @@ export const createProjectFiles = (plan) => [
|
|
|
124
299
|
},
|
|
125
300
|
{
|
|
126
301
|
path: '.nvmrc',
|
|
127
|
-
content: '
|
|
302
|
+
content: 'lts/krypton',
|
|
128
303
|
},
|
|
129
304
|
{
|
|
130
305
|
path: '.yarnrc.yml',
|
|
131
306
|
content: 'nodeLinker: node-modules\n',
|
|
132
307
|
},
|
|
308
|
+
{
|
|
309
|
+
path: '.husky/commit-msg',
|
|
310
|
+
content: '#!/bin/sh\n',
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
path: '.husky/post-checkout',
|
|
314
|
+
content: '#!/bin/sh\n\nyarn\n',
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
path: '.husky/post-merge',
|
|
318
|
+
content: '#!/bin/sh\n\nyarn\n',
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
path: '.husky/pre-commit',
|
|
322
|
+
content: '#!/bin/sh\n\nyarn check\n',
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
path: '.husky/pre-push',
|
|
326
|
+
content: '#!/bin/sh\n\nyarn check\n',
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
path: '.storybook/main.ts',
|
|
330
|
+
content: `import config from '@vyriy/storybook-config';
|
|
331
|
+
import { path } from '@vyriy/path';
|
|
332
|
+
|
|
333
|
+
export default {
|
|
334
|
+
...config,
|
|
335
|
+
stories: [
|
|
336
|
+
path('**/*.mdx'),
|
|
337
|
+
path('**/*.stories.@(js|jsx|mjs|ts|tsx)'),
|
|
338
|
+
],
|
|
339
|
+
};
|
|
340
|
+
`,
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
path: '.storybook/preview.tsx',
|
|
344
|
+
content: "export { default } from '@vyriy/storybook-config/preview';\n",
|
|
345
|
+
},
|
|
133
346
|
{
|
|
134
347
|
path: 'yarn.lock',
|
|
135
348
|
content: '',
|
|
@@ -138,10 +351,9 @@ export const createProjectFiles = (plan) => [
|
|
|
138
351
|
path: 'tsconfig.json',
|
|
139
352
|
content: json({
|
|
140
353
|
extends: '@vyriy/typescript-config/index.json',
|
|
141
|
-
compilerOptions: {
|
|
142
|
-
noEmit: false,
|
|
143
|
-
},
|
|
144
354
|
include: [
|
|
355
|
+
'.storybook/**/*.ts',
|
|
356
|
+
'.storybook/**/*.tsx',
|
|
145
357
|
'packages/**/*.ts',
|
|
146
358
|
'packages/**/*.tsx',
|
|
147
359
|
'workspaces/**/*.ts',
|
|
@@ -172,6 +384,23 @@ export const createProjectFiles = (plan) => [
|
|
|
172
384
|
path: 'stylelint.config.ts',
|
|
173
385
|
content: "export { default } from '@vyriy/stylelint-config';\n",
|
|
174
386
|
},
|
|
387
|
+
{
|
|
388
|
+
path: `packages/${getStylePackageName(plan)}/reset.scss`,
|
|
389
|
+
content: `html {
|
|
390
|
+
box-sizing: border-box;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
*,
|
|
394
|
+
*::before,
|
|
395
|
+
*::after {
|
|
396
|
+
box-sizing: inherit;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
body {
|
|
400
|
+
margin: 0;
|
|
401
|
+
}
|
|
402
|
+
`,
|
|
403
|
+
},
|
|
175
404
|
]
|
|
176
405
|
: []),
|
|
177
406
|
...plan.packages.flatMap((packagePlan) => createPackageFiles(plan, packagePlan)),
|
package/project-plan/api/api.js
CHANGED
|
@@ -1,30 +1,25 @@
|
|
|
1
1
|
const apiPresets = [
|
|
2
2
|
'api',
|
|
3
3
|
'fullstack',
|
|
4
|
-
'mfe
|
|
5
|
-
'openmfe-bff',
|
|
6
|
-
'aws-serverless',
|
|
4
|
+
'mfe',
|
|
7
5
|
];
|
|
8
6
|
export const isApiPreset = (preset) => apiPresets.includes(preset);
|
|
9
|
-
export const getApiRuntimeFromPreset = (
|
|
10
|
-
export const getDefaultApiStyleFromPreset = (
|
|
7
|
+
export const getApiRuntimeFromPreset = () => 'docker';
|
|
8
|
+
export const getDefaultApiStyleFromPreset = () => 'rest';
|
|
11
9
|
export const getFeaturesFromApiPlan = (api) => {
|
|
12
10
|
if (!api) {
|
|
13
11
|
return [];
|
|
14
12
|
}
|
|
15
|
-
if (api.style === 'mixed') {
|
|
16
|
-
return ['rest-api', 'graphql-api'];
|
|
17
|
-
}
|
|
18
13
|
return api.style === 'rest' ? ['rest-api'] : ['graphql-api'];
|
|
19
14
|
};
|
|
20
|
-
export const createApiPlan = ({ preset, style = getDefaultApiStyleFromPreset(preset) }) => {
|
|
15
|
+
export const createApiPlan = ({ preset, runtime = getApiRuntimeFromPreset(preset), style = getDefaultApiStyleFromPreset(preset), }) => {
|
|
21
16
|
if (!isApiPreset(preset)) {
|
|
22
17
|
return undefined;
|
|
23
18
|
}
|
|
24
19
|
const basePlan = {
|
|
25
20
|
enabled: true,
|
|
26
21
|
style,
|
|
27
|
-
runtime
|
|
22
|
+
runtime,
|
|
28
23
|
};
|
|
29
24
|
const restPlan = {
|
|
30
25
|
rest: {
|
|
@@ -37,13 +32,6 @@ export const createApiPlan = ({ preset, style = getDefaultApiStyleFromPreset(pre
|
|
|
37
32
|
packageName: 'graphql',
|
|
38
33
|
},
|
|
39
34
|
};
|
|
40
|
-
if (style === 'mixed') {
|
|
41
|
-
return {
|
|
42
|
-
...basePlan,
|
|
43
|
-
...restPlan,
|
|
44
|
-
...graphqlPlan,
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
35
|
return style === 'rest'
|
|
48
36
|
? {
|
|
49
37
|
...basePlan,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { VyriyApiPlan, VyriyApiRuntime, VyriyApiStyle, VyriyFeature, VyriyPreset } from '../types.js';
|
|
2
2
|
export type CreateApiPlanOptions = {
|
|
3
3
|
readonly preset: VyriyPreset;
|
|
4
|
+
readonly runtime?: VyriyApiRuntime;
|
|
4
5
|
readonly style?: VyriyApiStyle;
|
|
5
6
|
};
|
|
6
7
|
export type CreateApiPlan = (options: CreateApiPlanOptions) => VyriyApiPlan | undefined;
|
|
@@ -9,113 +9,104 @@ const baseFeatures = [
|
|
|
9
9
|
'storybook',
|
|
10
10
|
];
|
|
11
11
|
const presetFeatures = {
|
|
12
|
+
empty: [],
|
|
12
13
|
library: ['react'],
|
|
13
14
|
api: [],
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
mfe: ['react', 'webpack'],
|
|
18
|
-
openmfe: ['react', 'webpack', 'openmfe'],
|
|
19
|
-
'mfe-bff': ['react', 'webpack', 'bff'],
|
|
20
|
-
'openmfe-bff': [
|
|
21
|
-
'react',
|
|
22
|
-
'webpack',
|
|
23
|
-
'openmfe',
|
|
24
|
-
'bff',
|
|
25
|
-
],
|
|
15
|
+
csr: ['react', 'webpack'],
|
|
16
|
+
ssr: ['react', 'webpack'],
|
|
17
|
+
ssg: ['react', 'webpack'],
|
|
26
18
|
fullstack: ['react', 'webpack'],
|
|
27
|
-
'
|
|
28
|
-
'aws-cdk',
|
|
29
|
-
'lambda',
|
|
30
|
-
'apigateway',
|
|
31
|
-
],
|
|
32
|
-
empty: [],
|
|
19
|
+
mfe: ['react', 'webpack', 'openmfe'],
|
|
33
20
|
};
|
|
34
21
|
const packagePlans = {
|
|
22
|
+
empty: [],
|
|
35
23
|
library: [{ name: 'ui', kind: 'ui', publishable: true }],
|
|
36
24
|
api: [{ name: 'api', kind: 'api', publishable: false }],
|
|
37
|
-
|
|
38
|
-
{ name: 'app', kind: '
|
|
39
|
-
{ name: 'ui', kind: 'ui', publishable: true },
|
|
40
|
-
],
|
|
41
|
-
'react-ssr': [
|
|
42
|
-
{ name: 'app', kind: 'core', publishable: false },
|
|
43
|
-
{ name: 'ui', kind: 'ui', publishable: true },
|
|
44
|
-
{ name: 'ssr', kind: 'ssr', publishable: false },
|
|
45
|
-
],
|
|
46
|
-
'react-ssg': [
|
|
47
|
-
{ name: 'app', kind: 'core', publishable: false },
|
|
48
|
-
{ name: 'ui', kind: 'ui', publishable: true },
|
|
49
|
-
{ name: 'ssg', kind: 'ssg', publishable: false },
|
|
50
|
-
{ name: 'content', kind: 'core', publishable: false },
|
|
51
|
-
],
|
|
52
|
-
mfe: [
|
|
53
|
-
{ name: 'mfe', kind: 'mfe', publishable: false },
|
|
54
|
-
{ name: 'ui', kind: 'ui', publishable: true },
|
|
55
|
-
],
|
|
56
|
-
openmfe: [
|
|
57
|
-
{ name: 'mfe', kind: 'mfe', publishable: false },
|
|
25
|
+
csr: [
|
|
26
|
+
{ name: 'app', kind: 'app', publishable: false },
|
|
58
27
|
{ name: 'ui', kind: 'ui', publishable: true },
|
|
59
|
-
{ name: 'openmfe-contract', kind: 'contract', publishable: true },
|
|
60
28
|
],
|
|
61
|
-
|
|
62
|
-
{ name: '
|
|
29
|
+
ssr: [
|
|
30
|
+
{ name: 'app', kind: 'app', publishable: false },
|
|
63
31
|
{ name: 'ui', kind: 'ui', publishable: true },
|
|
64
|
-
{ name: '
|
|
32
|
+
{ name: 'ssr', kind: 'app', publishable: false },
|
|
65
33
|
],
|
|
66
|
-
|
|
67
|
-
{ name: '
|
|
34
|
+
ssg: [
|
|
35
|
+
{ name: 'app', kind: 'app', publishable: false },
|
|
68
36
|
{ name: 'ui', kind: 'ui', publishable: true },
|
|
69
|
-
{ name: '
|
|
70
|
-
{ name: '
|
|
37
|
+
{ name: 'ssg', kind: 'app', publishable: false },
|
|
38
|
+
{ name: 'content', kind: 'utils', publishable: false },
|
|
71
39
|
],
|
|
72
40
|
fullstack: [
|
|
73
|
-
{ name: 'app', kind: '
|
|
41
|
+
{ name: 'app', kind: 'app', publishable: false },
|
|
74
42
|
{ name: 'ui', kind: 'ui', publishable: true },
|
|
75
43
|
{ name: 'api', kind: 'api', publishable: false },
|
|
76
44
|
],
|
|
77
|
-
|
|
45
|
+
mfe: [
|
|
46
|
+
{ name: 'mfe', kind: 'app', publishable: false },
|
|
47
|
+
{ name: 'ui', kind: 'ui', publishable: true },
|
|
78
48
|
{ name: 'api', kind: 'api', publishable: false },
|
|
79
|
-
{ name: '
|
|
49
|
+
{ name: 'openmfe-contract', kind: 'config', publishable: true },
|
|
80
50
|
],
|
|
81
|
-
empty: [],
|
|
82
51
|
};
|
|
83
52
|
const workspacePlans = {
|
|
53
|
+
empty: [],
|
|
84
54
|
library: [],
|
|
85
55
|
api: [{ name: 'api', kind: 'api' }],
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
{ name: 'ssr', kind: '
|
|
89
|
-
{ name: 'web', kind: '
|
|
90
|
-
],
|
|
91
|
-
'react-ssg': [
|
|
92
|
-
{ name: 'ssg', kind: 'ssg' },
|
|
93
|
-
{ name: 'web', kind: 'web' },
|
|
94
|
-
],
|
|
95
|
-
mfe: [{ name: 'mfe', kind: 'mfe' }],
|
|
96
|
-
openmfe: [
|
|
97
|
-
{ name: 'mfe', kind: 'mfe' },
|
|
98
|
-
{ name: 'openmfe', kind: 'openmfe' },
|
|
99
|
-
],
|
|
100
|
-
'mfe-bff': [
|
|
101
|
-
{ name: 'mfe', kind: 'mfe' },
|
|
102
|
-
{ name: 'bff', kind: 'bff' },
|
|
56
|
+
csr: [{ name: 'web', kind: 'ui' }],
|
|
57
|
+
ssr: [
|
|
58
|
+
{ name: 'ssr', kind: 'ui' },
|
|
59
|
+
{ name: 'web', kind: 'ui' },
|
|
103
60
|
],
|
|
104
|
-
|
|
105
|
-
{ name: '
|
|
106
|
-
{ name: '
|
|
107
|
-
{ name: 'openmfe', kind: 'openmfe' },
|
|
61
|
+
ssg: [
|
|
62
|
+
{ name: 'ssg', kind: 'ui' },
|
|
63
|
+
{ name: 'web', kind: 'ui' },
|
|
108
64
|
],
|
|
109
65
|
fullstack: [
|
|
110
|
-
{ name: 'web', kind: '
|
|
66
|
+
{ name: 'web', kind: 'ui' },
|
|
67
|
+
{ name: 'api', kind: 'api' },
|
|
68
|
+
],
|
|
69
|
+
mfe: [
|
|
70
|
+
{ name: 'mfe', kind: 'ui' },
|
|
111
71
|
{ name: 'api', kind: 'api' },
|
|
112
72
|
],
|
|
113
|
-
'aws-serverless': [{ name: 'stack', kind: 'stack' }],
|
|
114
|
-
empty: [],
|
|
115
73
|
};
|
|
116
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
|
+
};
|
|
117
108
|
export const createProjectPlanFromPreset = ({ description, apiStyle, ciProvider, features = [], packageScope, preset, projectName, targetDirectory, }) => {
|
|
118
|
-
const api = createApiPlan({ preset, style: apiStyle });
|
|
109
|
+
const api = createApiPlan({ preset, runtime: getApiRuntimeFromFeatures(features), style: apiStyle });
|
|
119
110
|
const apiFeatures = getFeaturesFromApiPlan(api);
|
|
120
111
|
return {
|
|
121
112
|
projectName,
|
|
@@ -131,7 +122,7 @@ export const createProjectPlanFromPreset = ({ description, apiStyle, ciProvider,
|
|
|
131
122
|
...features,
|
|
132
123
|
]),
|
|
133
124
|
packages: [...packagePlans[preset]],
|
|
134
|
-
workspaces:
|
|
125
|
+
workspaces: createWorkspacePlans({ features, preset }),
|
|
135
126
|
ci: createCiPlan({ provider: ciProvider }),
|
|
136
127
|
...(api ? { api } : {}),
|
|
137
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;
|
package/project-plan/types.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
export type VyriyProjectKind = 'library' | 'api' | 'csr' | 'ssr' | 'ssg' | 'mfe' | 'fullstack' | '
|
|
2
|
-
export type VyriyPreset = '
|
|
3
|
-
export type VyriyFeature = 'typescript' | 'eslint' | 'prettier' | 'jest' | 'rest-api' | 'graphql-api' | 'react' | 'storybook' | 'webpack' | 'docker' | 'aws-cdk' | 'apigateway' | 'lambda' | 'fargate' | 's3' | 'cloudfront' | 'openmfe'
|
|
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' | '
|
|
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: '
|
|
11
|
+
readonly kind: 'api' | 'ui' | 'stack' | 'lambda' | 'fargate';
|
|
12
12
|
};
|
|
13
13
|
export type VyriyCiProvider = 'gitlab' | 'github';
|
|
14
14
|
export type VyriyCiPipeline = 'install' | 'lint' | 'test' | 'build' | 'deploy' | 'smoke' | 'e2e';
|
|
@@ -17,8 +17,8 @@ export type VyriyCiPlan = {
|
|
|
17
17
|
readonly providers: VyriyCiProvider[];
|
|
18
18
|
readonly pipelines: VyriyCiPipeline[];
|
|
19
19
|
};
|
|
20
|
-
export type VyriyApiStyle = 'rest' | 'graphql'
|
|
21
|
-
export type VyriyApiRuntime = '
|
|
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,25 +2,34 @@ 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
|
-
'
|
|
8
|
-
'
|
|
9
|
-
'
|
|
10
|
-
'mfe',
|
|
11
|
-
'openmfe',
|
|
12
|
-
'mfe-bff',
|
|
13
|
-
'openmfe-bff',
|
|
8
|
+
'ssr',
|
|
9
|
+
'ssg',
|
|
10
|
+
'csr',
|
|
14
11
|
'fullstack',
|
|
15
|
-
'
|
|
16
|
-
'empty',
|
|
17
|
-
];
|
|
18
|
-
const extraFeatures = [
|
|
19
|
-
'docker',
|
|
20
|
-
'aws-api',
|
|
21
|
-
'aws-fargate',
|
|
22
|
-
'aws-static',
|
|
12
|
+
'mfe',
|
|
23
13
|
];
|
|
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
|
+
};
|
|
24
33
|
const extraFeatureMap = {
|
|
25
34
|
docker: ['docker'],
|
|
26
35
|
'aws-api': [
|
|
@@ -48,7 +57,7 @@ const directFeatureInputs = [
|
|
|
48
57
|
's3',
|
|
49
58
|
'cloudfront',
|
|
50
59
|
];
|
|
51
|
-
const apiStyles = ['rest', 'graphql'
|
|
60
|
+
const apiStyles = ['rest', 'graphql'];
|
|
52
61
|
const ciProviders = ['none', 'gitlab', 'github'];
|
|
53
62
|
const createQuestion = (readline, output) => {
|
|
54
63
|
const queuedLines = [];
|
|
@@ -96,6 +105,28 @@ const parseFeatures = (value) => [
|
|
|
96
105
|
? [feature]
|
|
97
106
|
: []))),
|
|
98
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';
|
|
99
130
|
const parseApiStyle = (value, defaultValue) => {
|
|
100
131
|
const normalizedValue = value.trim().toLowerCase();
|
|
101
132
|
const numericValue = Number.parseInt(normalizedValue, 10);
|
|
@@ -124,12 +155,13 @@ export const askProjectPlan = async ({ defaults = {}, input = stdin, output = st
|
|
|
124
155
|
const packageScope = await promptWithDefault(question, 'Package scope', defaults.packageScope ?? `@${projectName}`);
|
|
125
156
|
const description = await promptWithDefault(question, 'Description', defaults.description ?? 'Calm cloud-ready application.');
|
|
126
157
|
output.write('\nProject preset:\n');
|
|
127
|
-
presets.forEach((preset, index) => output.write(` ${index + 1}. ${preset}\n`));
|
|
128
|
-
const
|
|
129
|
-
const
|
|
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);
|
|
130
162
|
const defaultApiStyle = defaults.apiStyle ?? getDefaultApiStyleFromPreset(preset);
|
|
131
163
|
const apiStyle = isApiPreset(preset)
|
|
132
|
-
? parseApiStyle(await promptWithDefault(question, 'API style
|
|
164
|
+
? parseApiStyle(await promptWithDefault(question, 'API style:\n 1. rest (@vyriy/router),\n 2. graphql', defaultApiStyle), defaultApiStyle)
|
|
133
165
|
: undefined;
|
|
134
166
|
output.write('\nCI/CD provider:\n');
|
|
135
167
|
output.write(' 1. none\n');
|
|
@@ -137,10 +169,12 @@ export const askProjectPlan = async ({ defaults = {}, input = stdin, output = st
|
|
|
137
169
|
output.write(' 3. github\n');
|
|
138
170
|
const defaultCiProvider = defaults.ciProvider ?? 'none';
|
|
139
171
|
const ciProvider = parseCiProvider(await promptWithDefault(question, 'CI/CD provider number or name', defaultCiProvider), defaultCiProvider);
|
|
140
|
-
output.write('\
|
|
141
|
-
output.write(
|
|
142
|
-
|
|
143
|
-
const
|
|
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);
|
|
144
178
|
const plan = createProjectPlanFromPreset({
|
|
145
179
|
projectName,
|
|
146
180
|
targetDirectory,
|
package/shared/index.d.ts
CHANGED
package/shared/index.js
CHANGED
|
@@ -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>;
|