vyriy 0.3.2 → 0.3.4

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.
Files changed (53) hide show
  1. package/AGENTS.md +59 -22
  2. package/README.md +38 -7
  3. package/cli/args/args.js +21 -10
  4. package/cli/args/types.d.ts +11 -1
  5. package/cli/cli.js +16 -2
  6. package/commands/doctor/doctor.js +5 -16
  7. package/commands/doctor/types.d.ts +2 -2
  8. package/commands/new/new.d.ts +2 -1
  9. package/commands/new/new.js +104 -18
  10. package/commands/new/types.d.ts +6 -0
  11. package/doctor/checkCorepack.d.ts +2 -0
  12. package/doctor/checkCorepack.js +24 -0
  13. package/doctor/checkGit.d.ts +2 -0
  14. package/doctor/checkGit.js +23 -0
  15. package/doctor/checkNodeVersion.d.ts +5 -0
  16. package/doctor/checkNodeVersion.js +24 -0
  17. package/doctor/checkYarn.d.ts +10 -0
  18. package/doctor/checkYarn.js +45 -0
  19. package/doctor/createDoctorReport.d.ts +2 -0
  20. package/doctor/createDoctorReport.js +17 -0
  21. package/doctor/index.d.ts +7 -0
  22. package/doctor/index.js +6 -0
  23. package/doctor/printDoctorReport.d.ts +2 -0
  24. package/doctor/printDoctorReport.js +42 -0
  25. package/doctor/types.d.ts +25 -0
  26. package/file-plan/createFilePlan.d.ts +4 -0
  27. package/file-plan/createFilePlan.js +29 -0
  28. package/file-plan/index.d.ts +4 -0
  29. package/file-plan/index.js +3 -0
  30. package/file-plan/printFilePlan.d.ts +2 -0
  31. package/file-plan/printFilePlan.js +44 -0
  32. package/file-plan/types.d.ts +12 -0
  33. package/file-plan/writeFilePlan.d.ts +2 -0
  34. package/file-plan/writeFilePlan.js +12 -0
  35. package/index.d.ts +4 -1
  36. package/index.js +4 -1
  37. package/package.json +181 -1
  38. package/presets/createProjectFiles.d.ts +2 -0
  39. package/presets/createProjectFiles.js +52 -0
  40. package/presets/index.d.ts +2 -0
  41. package/presets/index.js +1 -0
  42. package/presets/types.d.ts +3 -0
  43. package/shared/commandExists.d.ts +2 -0
  44. package/shared/commandExists.js +10 -0
  45. package/shared/execCommand.d.ts +2 -0
  46. package/shared/execCommand.js +7 -0
  47. package/shared/fileExists.d.ts +2 -0
  48. package/shared/fileExists.js +10 -0
  49. package/shared/index.d.ts +5 -0
  50. package/shared/index.js +4 -0
  51. package/shared/semver.d.ts +1 -0
  52. package/shared/semver.js +4 -0
  53. package/shared/types.d.ts +5 -0
package/AGENTS.md CHANGED
@@ -1,41 +1,62 @@
1
- # Vyriy Package Agent Guide
1
+ # Project Agent Guide
2
2
 
3
- This package belongs to the Vyriy toolkit. Keep changes calm, explicit, reusable, and easy to reason about.
3
+ This repository follows a calm engineering style: changes should be explicit, reusable, typed, documented, tested, and easy to reason about.
4
4
 
5
- ## Architecture
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
6
8
 
7
9
  - Prefer simple modules over clever frameworks or hidden conventions.
8
- - Keep package boundaries explicit and avoid project-specific coupling.
10
+ - Keep package and project boundaries explicit.
11
+ - Avoid project-specific coupling in reusable code.
9
12
  - Extract only proven reusable behavior.
10
13
  - Keep public APIs small, typed, documented, and stable.
11
- - Prefer SSR-friendly and SSG-friendly code paths.
12
- - Keep integrations replaceable and avoid hard coupling to a CMS or runtime host.
13
- - Prefer AWS serverless-compatible assumptions when infrastructure concerns appear.
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.
14
18
 
15
19
  ## File Shape
16
20
 
17
- - Prefer one exported runtime method, component, or helper per production file when it stays readable.
21
+ - Prefer one exported runtime method, component, helper, or class per production file when it stays readable.
18
22
  - Prefer one matching test file per production file, for example `feature.ts` and `feature.test.ts`.
19
23
  - Use focused folders when behavior naturally splits into several related files.
20
24
  - Keep `index.ts` as a public re-export surface only. Do not place implementation logic in it.
21
- - Use `.js` relative import and export specifiers in TypeScript source where package style requires 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.
22
27
  - Add `types.ts` when public shared types are part of the package contract.
23
28
  - Keep constants near the code that owns them unless they are shared or clarify repeated behavior.
24
29
 
25
30
  ## Public Surface
26
31
 
27
- - Every new public export should be re-exported from `index.ts`.
28
- - Add or update `index.test.ts` when the public export surface changes.
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.
29
34
  - Add JSDoc for public exports when behavior, parameters, return values, or usage expectations need explanation.
30
- - Do not hand-maintain source package `exports` maps unless the package has a real custom publishing need.
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.
31
37
 
32
38
  ## Tests
33
39
 
34
- - Tests use Jest and `@jest/globals`.
35
40
  - Cover public behavior and meaningful edge cases.
36
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.
37
44
  - When mocking modules, install mocks before loading the module under test.
38
- - Use focused validation when changing package behavior:
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:
39
60
 
40
61
  ```bash
41
62
  yarn jest packages/<package> --runInBand --coverage=false
@@ -44,22 +65,38 @@ yarn jest packages/<package> --runInBand --coverage=false
44
65
  ## Documentation
45
66
 
46
67
  - Keep `README.md` concise and usage-oriented.
47
- - Start package READMEs with `# @vyriy/<package>`.
68
+ - Start package READMEs with `# <package>`.
48
69
  - Document real public exports, supported options, and examples that actually work.
49
- - Keep `doc.mdx` as the Storybook/docs wrapper for the README when the package participates in docs.
50
- - For component packages, include Storybook coverage for supported states and common usage.
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.
51
73
 
52
74
  ## Components
53
75
 
54
- - Prefer lightweight React 19+ components with TypeScript.
76
+ - Prefer lightweight React components with TypeScript when working in React packages.
55
77
  - Keep components SSR-friendly and avoid browser globals during render.
56
- - Prefer composable props and Bootstrap-compatible ergonomics where practical.
78
+ - Prefer composable props and predictable ergonomics.
57
79
  - Put each public component in its own file with a matching test.
58
- - Add Storybook stories when a component has visual states, variants, or interaction states.
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.
59
82
 
60
83
  ## Change Discipline
61
84
 
62
85
  - Keep changes scoped to the requested behavior.
63
86
  - Avoid unrelated refactors and metadata churn.
64
- - Sync implementation, tests, README, `doc.mdx`, and public re-exports together.
65
- - Prefer the option that is simpler to explain, easier to evolve, and calmer to maintain.
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/README.md CHANGED
@@ -6,7 +6,7 @@ Interactive project master for calm cloud-ready applications.
6
6
 
7
7
  `vyriy` is the user-facing CLI entry point for the Vyriy ecosystem.
8
8
 
9
- The first implementation focuses on project planning rather than file generation. It checks the local environment, runs a small project wizard, creates a normalized `VyriyProjectPlan`, prints the summary, and exits without writing generated project files.
9
+ The CLI checks the local environment, runs a small project wizard, creates a normalized `VyriyProjectPlan`, builds generated project files in memory, prints a file plan, and writes only files that are safe to create or explicitly allowed to overwrite.
10
10
 
11
11
  ## Install
12
12
 
@@ -31,6 +31,10 @@ vyriy new my-app
31
31
  vyriy .
32
32
  vyriy init
33
33
  vyriy doctor
34
+ vyriy --dry-run
35
+ vyriy --yes
36
+ vyriy --overwrite
37
+ vyriy --skip-existing
34
38
  vyriy --help
35
39
  vyriy --version
36
40
  ```
@@ -41,7 +45,7 @@ Runs the same flow as `vyriy new`.
41
45
 
42
46
  ### `vyriy new [name]`
43
47
 
44
- Starts the project planning wizard.
48
+ Starts the project planning wizard, prints the project summary and file plan, then writes generated files when no unresolved conflicts exist.
45
49
 
46
50
  If `name` is provided, it is used as the default project name and target directory.
47
51
 
@@ -62,7 +66,31 @@ Runs environment checks only.
62
66
  Current checks:
63
67
 
64
68
  - Node.js `>=24`
69
+ - Corepack availability
65
70
  - Yarn `>=4`
71
+ - Git availability
72
+
73
+ Node.js is fatal when unsupported. Yarn and Git are warnings so generation can continue without silently installing tools or initializing Git.
74
+
75
+ ## Flags
76
+
77
+ ### `--dry-run`
78
+
79
+ Prints the doctor report, project summary, and file plan without writing files or running fix commands.
80
+
81
+ ### `--yes`
82
+
83
+ Uses default wizard answers and avoids prompts where possible. It does not overwrite existing files unless `--overwrite` is also passed.
84
+
85
+ ### `--overwrite`
86
+
87
+ Marks existing generated paths as overwrite candidates and writes them.
88
+
89
+ ### `--skip-existing`
90
+
91
+ Marks existing generated paths as skipped and leaves them untouched.
92
+
93
+ `--overwrite` and `--skip-existing` cannot be used together.
66
94
 
67
95
  ## Wizard
68
96
 
@@ -78,9 +106,9 @@ The wizard collects:
78
106
  - optional extra features
79
107
  - confirmation
80
108
 
81
- After confirmation, the CLI prints the project plan and exits.
109
+ After confirmation, the CLI prints the project plan, creates generated files in memory, builds a conflict-aware file plan, and writes the accepted file plan.
82
110
 
83
- File generation is not implemented yet.
111
+ Presets do not write to disk directly.
84
112
 
85
113
  ## Project Presets
86
114
 
@@ -111,19 +139,23 @@ Examples:
111
139
 
112
140
  ## Public API
113
141
 
114
- The package exports the CLI runner, command helpers, environment checks, prompt helper, and project-plan utilities.
142
+ The package exports the CLI runner, command helpers, doctor checks, file-plan utilities, generated preset files, prompt helper, and project-plan utilities.
115
143
 
116
144
  ```ts
117
145
  import {
118
146
  askProjectPlan,
119
147
  checkNodeVersion,
120
148
  checkYarnVersion,
149
+ createDoctorReport,
150
+ createFilePlan,
151
+ createProjectFiles,
121
152
  createApiPlan,
122
153
  createCiPlan,
123
154
  createProjectPlanFromPreset,
124
155
  getProjectKindFromPreset,
125
156
  parseArgs,
126
157
  printProjectPlan,
158
+ writeFilePlan,
127
159
  runDoctorCommand,
128
160
  runInitCommand,
129
161
  runNewCommand,
@@ -145,13 +177,12 @@ It includes:
145
177
  - future package plans
146
178
  - future workspace plans
147
179
 
148
- This model is intentionally useful before generation exists. It gives future generator steps a stable contract to build from.
180
+ This model is intentionally useful before files are written. It gives generator steps a stable contract to build from.
149
181
 
150
182
  ## Current Non-Goals
151
183
 
152
184
  The CLI does not yet:
153
185
 
154
- - write project files
155
186
  - initialize Git
156
187
  - run `yarn install`
157
188
  - generate AWS CDK stacks
package/cli/args/args.js CHANGED
@@ -1,22 +1,33 @@
1
1
  export const parseArgs = (args) => {
2
- const [command, projectName] = args;
2
+ if (args.includes('--help') || args.includes('-h')) {
3
+ return { type: 'help' };
4
+ }
5
+ if (args.includes('--version') || args.includes('-v')) {
6
+ return { type: 'version' };
7
+ }
8
+ const dryRun = args.includes('--dry-run');
9
+ const yes = args.includes('--yes') || args.includes('-y');
10
+ const overwrite = args.includes('--overwrite');
11
+ const skipExisting = args.includes('--skip-existing');
12
+ const positionalArgs = args.filter((arg) => !arg.startsWith('-'));
13
+ const [command, projectName] = positionalArgs;
14
+ const options = {
15
+ dryRun,
16
+ yes,
17
+ overwrite,
18
+ skipExisting,
19
+ };
3
20
  if (!command) {
4
- return { type: 'new' };
21
+ return { type: 'new', ...options };
5
22
  }
6
23
  switch (command) {
7
24
  case 'new':
8
- return { type: 'new', projectName };
25
+ return { type: 'new', projectName, ...options };
9
26
  case '.':
10
27
  case 'init':
11
- return { type: 'init' };
28
+ return { type: 'init', ...options };
12
29
  case 'doctor':
13
30
  return { type: 'doctor' };
14
- case '--help':
15
- case '-h':
16
- return { type: 'help' };
17
- case '--version':
18
- case '-v':
19
- return { type: 'version' };
20
31
  default:
21
32
  return { type: 'unknown', command };
22
33
  }
@@ -1,8 +1,18 @@
1
1
  export type VyriyCliCommand = {
2
2
  readonly type: 'new';
3
3
  readonly projectName?: string;
4
+ readonly dryRun: boolean;
5
+ readonly yes: boolean;
6
+ readonly overwrite: boolean;
7
+ readonly skipExisting: boolean;
4
8
  } | {
5
- readonly type: 'init' | 'doctor' | 'help' | 'version';
9
+ readonly type: 'init';
10
+ readonly dryRun: boolean;
11
+ readonly yes: boolean;
12
+ readonly overwrite: boolean;
13
+ readonly skipExisting: boolean;
14
+ } | {
15
+ readonly type: 'doctor' | 'help' | 'version';
6
16
  } | {
7
17
  readonly type: 'unknown';
8
18
  readonly command: string;
package/cli/cli.js CHANGED
@@ -10,6 +10,7 @@ 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 --dry-run Print checks and file plan without writing
13
14
  vyriy --help Show help
14
15
  vyriy --version Show version
15
16
 
@@ -22,10 +23,23 @@ export const runVyriyCli = async (args = [], { output = console } = {}) => {
22
23
  let code = 0;
23
24
  switch (command.type) {
24
25
  case 'new':
25
- code = await runNewCommand({ output, projectName: command.projectName });
26
+ code = await runNewCommand({
27
+ dryRun: command.dryRun,
28
+ output,
29
+ overwrite: command.overwrite,
30
+ projectName: command.projectName,
31
+ skipExisting: command.skipExisting,
32
+ yes: command.yes,
33
+ });
26
34
  break;
27
35
  case 'init':
28
- code = await runInitCommand({ output });
36
+ code = await runInitCommand({
37
+ dryRun: command.dryRun,
38
+ output,
39
+ overwrite: command.overwrite,
40
+ skipExisting: command.skipExisting,
41
+ yes: command.yes,
42
+ });
29
43
  break;
30
44
  case 'doctor': {
31
45
  const result = await runDoctorCommand({ output });
@@ -1,20 +1,9 @@
1
- import { checkNodeVersion } from '../../checks/node/index.js';
2
- import { checkYarnVersion } from '../../checks/yarn/index.js';
1
+ import { createDoctorReport, printDoctorReport } from '../../doctor/index.js';
3
2
  export const runDoctorCommand = async ({ output = console } = {}) => {
4
- const checks = [checkNodeVersion(), await checkYarnVersion()];
5
- const failedCheck = checks.find((check) => !check.ok);
6
- output.log('Vyriy Project Master\n');
7
- for (const check of checks) {
8
- output.log(`${check.ok ? 'OK' : 'ERROR'} ${check.message}`);
9
- }
10
- if (failedCheck) {
11
- return {
12
- code: 1,
13
- checks,
14
- };
15
- }
3
+ const report = await createDoctorReport();
4
+ output.log(printDoctorReport(report));
16
5
  return {
17
- code: 0,
18
- checks,
6
+ code: report.hasErrors ? 1 : 0,
7
+ checks: report.checks,
19
8
  };
20
9
  };
@@ -1,8 +1,8 @@
1
- import { EnvironmentCheckResult } from '../../checks/node/index.js';
1
+ import { DoctorCheck } from '../../doctor/index.js';
2
2
  export type RunDoctorCommandOptions = {
3
3
  readonly output?: Pick<typeof console, 'log' | 'error'>;
4
4
  };
5
5
  export type RunDoctorCommand = (options?: RunDoctorCommandOptions) => Promise<{
6
6
  readonly code: number;
7
- readonly checks: readonly EnvironmentCheckResult[];
7
+ readonly checks: readonly DoctorCheck[];
8
8
  }>;
@@ -1,2 +1,3 @@
1
- import { RunNewCommand } from './types.js';
1
+ import { ConflictResolution, RunNewCommand } from './types.js';
2
+ export declare const askConflictResolutionDefault: () => Promise<ConflictResolution>;
2
3
  export declare const runNewCommand: RunNewCommand;
@@ -1,32 +1,118 @@
1
- import { checkNodeVersion } from '../../checks/node/index.js';
2
- import { checkYarnVersion } from '../../checks/yarn/index.js';
1
+ import { stdin, stdout } from 'node:process';
2
+ import { createInterface } from 'node:readline/promises';
3
+ import { createDoctorReport, printDoctorReport } from '../../doctor/index.js';
4
+ import { createFilePlan, printFilePlan, writeFilePlan } from '../../file-plan/index.js';
5
+ import { createProjectFiles } from '../../presets/index.js';
3
6
  import { askProjectPlan as askProjectPlanDefault } from '../../prompts/project-plan/index.js';
4
- import { printProjectPlan } from '../../project-plan/index.js';
5
- export const runNewCommand = async ({ askProjectPlan = askProjectPlanDefault, output = console, projectName = 'my-app', } = {}) => {
6
- const nodeCheck = checkNodeVersion();
7
- if (!nodeCheck.ok) {
8
- output.error(nodeCheck.message);
7
+ import { createProjectPlanFromPreset, printProjectPlan } from '../../project-plan/index.js';
8
+ const getConflicts = (filePlan) => filePlan.filter((item) => item.status === 'conflict');
9
+ const logConflicts = (output, conflicts, method) => {
10
+ output[method]('\nExisting files found:\n');
11
+ for (const conflict of conflicts) {
12
+ output[method](` ! ${conflict.path}`);
13
+ }
14
+ };
15
+ const printConflictPrompt = (output) => {
16
+ output.log('\nWhat should Vyriy do?');
17
+ output.log(' 1. overwrite existing files');
18
+ output.log(' 2. skip existing files');
19
+ output.log(' 3. abort');
20
+ };
21
+ const failOnNonInteractiveConflicts = (output, conflicts) => {
22
+ logConflicts(output, conflicts, 'error');
23
+ output.error('\nCannot continue in non-interactive mode without a conflict strategy.\n');
24
+ output.error('Use one of:\n\n vyriy --overwrite\n vyriy --skip-existing\n vyriy --dry-run');
25
+ return 1;
26
+ };
27
+ const createResolvedFilePlan = async (plan, projectFiles, resolution) => createFilePlan(plan.targetDirectory, projectFiles, {
28
+ overwrite: resolution === 'overwrite',
29
+ skipExisting: resolution === 'skip',
30
+ });
31
+ const resolveInteractiveConflicts = async (plan, projectFiles, output, conflicts, askConflictResolution) => {
32
+ logConflicts(output, conflicts, 'log');
33
+ printConflictPrompt(output);
34
+ const resolution = await askConflictResolution();
35
+ if (resolution === 'abort') {
36
+ output.log('Project generation aborted.');
37
+ return { result: 1, status: 'failed' };
38
+ }
39
+ const filePlan = await createResolvedFilePlan(plan, projectFiles, resolution);
40
+ if (getConflicts(filePlan).length > 0) {
41
+ output.error('Cannot continue with unresolved file conflicts.');
42
+ return { result: 1, status: 'failed' };
43
+ }
44
+ return { filePlan, status: 'resolved' };
45
+ };
46
+ export const askConflictResolutionDefault = async () => {
47
+ const readline = createInterface({ input: stdin, output: stdout });
48
+ try {
49
+ const answer = (await readline.question('What should Vyriy do? 1. overwrite existing files, 2. skip existing files, 3. abort (abort): '))
50
+ .trim()
51
+ .toLowerCase();
52
+ if (answer === '1' || answer === 'overwrite') {
53
+ return 'overwrite';
54
+ }
55
+ if (answer === '2' || answer === 'skip') {
56
+ return 'skip';
57
+ }
58
+ return 'abort';
59
+ }
60
+ finally {
61
+ readline.close();
62
+ }
63
+ };
64
+ export const runNewCommand = async ({ askConflictResolution = askConflictResolutionDefault, askProjectPlan = askProjectPlanDefault, dryRun = false, output = console, overwrite = false, projectName = 'my-app', skipExisting = false, yes = false, } = {}) => {
65
+ if (overwrite && skipExisting) {
66
+ output.error('Cannot use --overwrite and --skip-existing together.');
9
67
  return 1;
10
68
  }
11
- const yarnCheck = await checkYarnVersion();
12
- if (!yarnCheck.ok) {
13
- output.error(yarnCheck.message);
69
+ const report = await createDoctorReport();
70
+ output.log(printDoctorReport(report));
71
+ if (report.hasErrors) {
72
+ output.error('\nPlease install Node.js 24+ and run the command again.');
14
73
  return 1;
15
74
  }
16
- output.log(`OK ${nodeCheck.message}`);
17
- output.log(`OK ${yarnCheck.message}\n`);
18
- const plan = await askProjectPlan({
19
- defaults: {
75
+ const plan = dryRun || yes
76
+ ? createProjectPlanFromPreset({
77
+ apiStyle: 'rest',
78
+ ciProvider: 'none',
79
+ description: 'Calm cloud-ready application.',
80
+ packageScope: `@${projectName}`,
81
+ preset: 'react-ssr',
20
82
  projectName,
21
83
  targetDirectory: projectName,
22
- },
23
- });
84
+ })
85
+ : await askProjectPlan({
86
+ defaults: {
87
+ projectName,
88
+ targetDirectory: projectName,
89
+ },
90
+ });
24
91
  if (!plan) {
25
92
  output.log('Project planning cancelled.');
26
93
  return 1;
27
94
  }
28
95
  output.log(`\n${printProjectPlan(plan)}`);
29
- output.log('Project plan created.\n');
30
- output.log('File generation is not implemented yet.');
96
+ const projectFiles = createProjectFiles(plan);
97
+ let filePlan = await createFilePlan(plan.targetDirectory, projectFiles, { overwrite, skipExisting });
98
+ let conflicts = filePlan.filter((item) => item.status === 'conflict');
99
+ output.log(`\n${printFilePlan(filePlan)}`);
100
+ if (dryRun) {
101
+ output.log('\nNo files will be written in dry-run mode.');
102
+ return conflicts.length > 0 ? 1 : 0;
103
+ }
104
+ if (conflicts.length > 0 && yes) {
105
+ return failOnNonInteractiveConflicts(output, conflicts);
106
+ }
107
+ if (conflicts.length > 0) {
108
+ const resolved = await resolveInteractiveConflicts(plan, projectFiles, output, conflicts, askConflictResolution);
109
+ if (resolved.status === 'failed') {
110
+ return resolved.result;
111
+ }
112
+ filePlan = resolved.filePlan;
113
+ output.log(`\n${printFilePlan(resolved.filePlan)}`);
114
+ }
115
+ await writeFilePlan(plan.targetDirectory, filePlan);
116
+ output.log('\nProject files written.');
31
117
  return 0;
32
118
  };
@@ -1,7 +1,13 @@
1
1
  import { PromptProjectPlan } from '../../prompts/project-plan/index.js';
2
+ export type ConflictResolution = 'overwrite' | 'skip' | 'abort';
2
3
  export type RunNewCommandOptions = {
3
4
  readonly projectName?: string;
4
5
  readonly askProjectPlan?: PromptProjectPlan;
6
+ readonly askConflictResolution?: () => Promise<ConflictResolution>;
5
7
  readonly output?: Pick<typeof console, 'log' | 'error'>;
8
+ readonly dryRun?: boolean;
9
+ readonly yes?: boolean;
10
+ readonly overwrite?: boolean;
11
+ readonly skipExisting?: boolean;
6
12
  };
7
13
  export type RunNewCommand = (options?: RunNewCommandOptions) => Promise<number>;
@@ -0,0 +1,2 @@
1
+ import { DoctorCheck, DoctorCheckOptions } from './types.js';
2
+ export declare const checkCorepack: ({ execCommand, }?: DoctorCheckOptions) => Promise<DoctorCheck>;
@@ -0,0 +1,24 @@
1
+ import { execCommand as execCommandDefault } from '../shared/index.js';
2
+ export const checkCorepack = async ({ execCommand = execCommandDefault, } = {}) => {
3
+ try {
4
+ const version = await execCommand('corepack', ['--version']);
5
+ return {
6
+ name: 'corepack',
7
+ label: 'Corepack',
8
+ group: 'Package manager',
9
+ level: 'ok',
10
+ version,
11
+ message: 'Corepack available',
12
+ };
13
+ }
14
+ catch {
15
+ return {
16
+ name: 'corepack',
17
+ label: 'Corepack',
18
+ group: 'Package manager',
19
+ level: 'warning',
20
+ message: 'Corepack was not found',
21
+ detail: 'Yarn fixes cannot be run automatically without Corepack.',
22
+ };
23
+ }
24
+ };
@@ -0,0 +1,2 @@
1
+ import { DoctorCheck, DoctorCheckOptions } from './types.js';
2
+ export declare const checkGit: ({ execCommand }?: DoctorCheckOptions) => Promise<DoctorCheck>;
@@ -0,0 +1,23 @@
1
+ import { execCommand as execCommandDefault } from '../shared/index.js';
2
+ export const checkGit = async ({ execCommand = execCommandDefault } = {}) => {
3
+ try {
4
+ await execCommand('git', ['--version']);
5
+ return {
6
+ name: 'git',
7
+ label: 'Git',
8
+ group: 'Git',
9
+ level: 'ok',
10
+ message: 'Git available',
11
+ };
12
+ }
13
+ catch {
14
+ return {
15
+ name: 'git',
16
+ label: 'Git',
17
+ group: 'Git',
18
+ level: 'warning',
19
+ message: 'Git was not found',
20
+ detail: 'Git initialization will be skipped.',
21
+ };
22
+ }
23
+ };
@@ -0,0 +1,5 @@
1
+ import { DoctorCheck } from './types.js';
2
+ export declare const checkNodeVersion: ({ minimumMajor, version, }?: {
3
+ readonly minimumMajor?: number;
4
+ readonly version?: string;
5
+ }) => DoctorCheck;
@@ -0,0 +1,24 @@
1
+ import { getMajorVersion } from '../shared/index.js';
2
+ export const checkNodeVersion = ({ minimumMajor = 24, version = process.version, } = {}) => {
3
+ const normalizedVersion = version.replace(/^v/, '');
4
+ const majorVersion = getMajorVersion(normalizedVersion);
5
+ if (majorVersion !== undefined && majorVersion >= minimumMajor) {
6
+ return {
7
+ name: 'node',
8
+ label: 'Node.js',
9
+ group: 'Runtime',
10
+ level: 'ok',
11
+ version: normalizedVersion,
12
+ message: `Node.js ${normalizedVersion}`,
13
+ };
14
+ }
15
+ return {
16
+ name: 'node',
17
+ label: 'Node.js',
18
+ group: 'Runtime',
19
+ level: 'error',
20
+ version: normalizedVersion,
21
+ message: `Node.js ${normalizedVersion} detected`,
22
+ detail: `Vyriy requires Node.js ${minimumMajor} or newer.`,
23
+ };
24
+ };
@@ -0,0 +1,10 @@
1
+ import { DoctorCheck, DoctorCheckOptions } from './types.js';
2
+ export declare const yarnStableFix: {
3
+ readonly label: "Enable Yarn using Corepack";
4
+ readonly command: "corepack enable\ncorepack prepare yarn@stable --activate";
5
+ readonly safeToRun: true;
6
+ };
7
+ export declare const checkYarn: ({ execCommand, minimumMajor, version, }?: DoctorCheckOptions & {
8
+ readonly minimumMajor?: number;
9
+ readonly version?: string;
10
+ }) => Promise<DoctorCheck>;