vyriy 0.3.5 → 0.3.9
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 +48 -21
- package/cli/args/args.js +6 -0
- package/cli/args/types.d.ts +5 -1
- package/cli/cli.js +18 -2
- package/commands/new/new.js +77 -6
- package/commands/new/types.d.ts +2 -0
- package/commands/publish/index.d.ts +2 -0
- package/commands/publish/index.js +1 -0
- package/commands/publish/publish.d.ts +2 -0
- package/commands/publish/publish.js +274 -0
- package/commands/publish/types.d.ts +31 -0
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/package.json +159 -1
- package/presets/base/createBaseFiles.d.ts +3 -0
- package/presets/base/createBaseFiles.js +307 -0
- package/presets/config.d.ts +28 -0
- package/presets/config.js +7 -0
- package/presets/createProjectFiles.js +4 -175
- package/presets/library/createLibraryUiFiles.d.ts +3 -0
- package/presets/library/createLibraryUiFiles.js +127 -0
- package/presets/packages/createPackageFiles.d.ts +3 -0
- package/presets/packages/createPackageFiles.js +39 -0
- package/presets/packages/createPackageManifest.d.ts +7 -0
- package/presets/packages/createPackageManifest.js +13 -0
- package/presets/workspaces/createWorkspaceFiles.d.ts +3 -0
- package/presets/workspaces/createWorkspaceFiles.js +98 -0
- 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 +62 -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
|
@@ -34,8 +34,13 @@ vyriy new my-app
|
|
|
34
34
|
vyriy .
|
|
35
35
|
vyriy init
|
|
36
36
|
vyriy doctor
|
|
37
|
+
vyriy publish
|
|
37
38
|
vyriy --dry-run
|
|
38
39
|
vyriy --yes
|
|
40
|
+
vyriy --no-install
|
|
41
|
+
vyriy --no-verify
|
|
42
|
+
vyriy --install-only
|
|
43
|
+
vyriy --verify
|
|
39
44
|
vyriy --overwrite
|
|
40
45
|
vyriy --skip-existing
|
|
41
46
|
vyriy --help
|
|
@@ -48,7 +53,7 @@ Runs the same flow as `vyriy new`.
|
|
|
48
53
|
|
|
49
54
|
### `vyriy new [name]`
|
|
50
55
|
|
|
51
|
-
Starts the project planning wizard, prints the project summary and file plan,
|
|
56
|
+
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
57
|
|
|
53
58
|
If `name` is provided, it is used as the default project name and target directory.
|
|
54
59
|
|
|
@@ -75,15 +80,37 @@ Current checks:
|
|
|
75
80
|
|
|
76
81
|
Node.js is fatal when unsupported. Yarn and Git are warnings so generation can continue without silently installing tools or initializing Git.
|
|
77
82
|
|
|
83
|
+
### `vyriy publish`
|
|
84
|
+
|
|
85
|
+
Prepares compiled `dist` package metadata for publishing without running `npm publish`.
|
|
86
|
+
|
|
87
|
+
Use it after TypeScript emits package files into `dist`, for example from a `build:dist` script.
|
|
88
|
+
|
|
78
89
|
## Flags
|
|
79
90
|
|
|
80
91
|
### `--dry-run`
|
|
81
92
|
|
|
82
|
-
Prints the doctor report, project summary, and file plan without writing files or running
|
|
93
|
+
Prints the doctor report, project summary, and file plan without writing files, installing dependencies, or running checks.
|
|
94
|
+
|
|
95
|
+
### `--no-install`
|
|
96
|
+
|
|
97
|
+
Writes generated files but skips `yarn install` and `yarn check`.
|
|
98
|
+
|
|
99
|
+
### `--no-verify`
|
|
100
|
+
|
|
101
|
+
Writes generated files and runs `yarn install`, but skips `yarn check`.
|
|
102
|
+
|
|
103
|
+
### `--install-only`
|
|
104
|
+
|
|
105
|
+
Alias for `--no-verify`.
|
|
106
|
+
|
|
107
|
+
### `--verify`
|
|
108
|
+
|
|
109
|
+
Explicitly enables `yarn check`. This is already the default unless `--no-install`, `--no-verify`, or `--install-only` is passed.
|
|
83
110
|
|
|
84
111
|
### `--yes`
|
|
85
112
|
|
|
86
|
-
Uses default wizard answers and avoids prompts where possible. It does not overwrite existing files unless `--overwrite` is also passed.
|
|
113
|
+
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
114
|
|
|
88
115
|
### `--overwrite`
|
|
89
116
|
|
|
@@ -106,10 +133,10 @@ The wizard collects:
|
|
|
106
133
|
- project preset
|
|
107
134
|
- API style for API-capable presets
|
|
108
135
|
- CI/CD provider
|
|
109
|
-
-
|
|
136
|
+
- infrastructure provider
|
|
110
137
|
- confirmation
|
|
111
138
|
|
|
112
|
-
After confirmation, the CLI prints the project plan, creates generated files in memory, builds a conflict-aware file plan,
|
|
139
|
+
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
140
|
|
|
114
141
|
Presets do not write to disk directly.
|
|
115
142
|
|
|
@@ -119,28 +146,28 @@ Generated projects always include `AGENTS.md` based on the shared Vyriy package
|
|
|
119
146
|
|
|
120
147
|
Supported presets:
|
|
121
148
|
|
|
149
|
+
- `empty`
|
|
122
150
|
- `library`
|
|
123
151
|
- `api`
|
|
124
|
-
- `
|
|
125
|
-
- `
|
|
126
|
-
- `
|
|
127
|
-
- `mfe`
|
|
128
|
-
- `openmfe`
|
|
129
|
-
- `mfe-bff`
|
|
130
|
-
- `openmfe-bff`
|
|
152
|
+
- `ssr`
|
|
153
|
+
- `ssg`
|
|
154
|
+
- `csr`
|
|
131
155
|
- `fullstack`
|
|
132
|
-
- `
|
|
133
|
-
|
|
156
|
+
- `mfe`
|
|
157
|
+
|
|
158
|
+
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.
|
|
159
|
+
|
|
160
|
+
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
161
|
|
|
135
|
-
|
|
162
|
+
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
163
|
|
|
137
164
|
Examples:
|
|
138
165
|
|
|
139
|
-
- `
|
|
140
|
-
- `
|
|
141
|
-
- `
|
|
142
|
-
- `
|
|
143
|
-
- `
|
|
166
|
+
- `csr` -> `csr`
|
|
167
|
+
- `ssr` -> `ssr`
|
|
168
|
+
- `ssg` -> `ssg`
|
|
169
|
+
- `mfe` -> `mfe`
|
|
170
|
+
- `fullstack` -> `fullstack`
|
|
144
171
|
|
|
145
172
|
## Public API
|
|
146
173
|
|
|
@@ -178,7 +205,7 @@ It includes:
|
|
|
178
205
|
- architecture: `preset`, `projectKind`
|
|
179
206
|
- selected features
|
|
180
207
|
- CI/CD planning: enabled state, providers, and validation pipelines
|
|
181
|
-
- API planning for API-capable presets: REST
|
|
208
|
+
- API planning for API-capable presets: REST or GraphQL API style
|
|
182
209
|
- future package plans
|
|
183
210
|
- future workspace plans
|
|
184
211
|
|
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) {
|
|
@@ -28,6 +32,8 @@ export const parseArgs = (args) => {
|
|
|
28
32
|
return { type: 'init', ...options };
|
|
29
33
|
case 'doctor':
|
|
30
34
|
return { type: 'doctor' };
|
|
35
|
+
case 'publish':
|
|
36
|
+
return { type: 'publish' };
|
|
31
37
|
default:
|
|
32
38
|
return { type: 'unknown', command };
|
|
33
39
|
}
|
package/cli/args/types.d.ts
CHANGED
|
@@ -2,17 +2,21 @@ 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
|
-
readonly type: 'doctor' | 'help' | 'version';
|
|
19
|
+
readonly type: 'doctor' | 'help' | 'publish' | 'version';
|
|
16
20
|
} | {
|
|
17
21
|
readonly type: 'unknown';
|
|
18
22
|
readonly command: string;
|
package/cli/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { runDoctorCommand } from '../commands/doctor/index.js';
|
|
2
2
|
import { runInitCommand } from '../commands/init/index.js';
|
|
3
3
|
import { runNewCommand } from '../commands/new/index.js';
|
|
4
|
+
import { runPublishCommand } from '../commands/publish/index.js';
|
|
4
5
|
import packageJson from '../package.json' with { type: 'json' };
|
|
5
6
|
import { parseArgs } from './args/index.js';
|
|
6
7
|
const helpText = `Vyriy Project Master
|
|
@@ -10,9 +11,17 @@ Usage:
|
|
|
10
11
|
vyriy init Initialize the current directory
|
|
11
12
|
vyriy . Initialize the current directory
|
|
12
13
|
vyriy doctor Check local environment
|
|
14
|
+
vyriy publish Prepare dist package metadata without publishing to npm
|
|
15
|
+
vyriy --yes, -y Use defaults where possible (empty preset)
|
|
13
16
|
vyriy --dry-run Print checks and file plan without writing
|
|
14
|
-
vyriy --
|
|
15
|
-
vyriy --
|
|
17
|
+
vyriy --overwrite Overwrite existing generated paths
|
|
18
|
+
vyriy --skip-existing Leave existing generated paths untouched
|
|
19
|
+
vyriy --no-install Create files without installing dependencies
|
|
20
|
+
vyriy --no-verify Install dependencies without running checks
|
|
21
|
+
vyriy --install-only Alias for --no-verify
|
|
22
|
+
vyriy --verify Explicitly enable generated project checks
|
|
23
|
+
vyriy --help, -h Show help
|
|
24
|
+
vyriy --version, -v Show version
|
|
16
25
|
|
|
17
26
|
Examples:
|
|
18
27
|
vyriy new my-app
|
|
@@ -25,19 +34,23 @@ export const runVyriyCli = async (args = [], { output = console } = {}) => {
|
|
|
25
34
|
case 'new':
|
|
26
35
|
code = await runNewCommand({
|
|
27
36
|
dryRun: command.dryRun,
|
|
37
|
+
install: command.install,
|
|
28
38
|
output,
|
|
29
39
|
overwrite: command.overwrite,
|
|
30
40
|
projectName: command.projectName,
|
|
31
41
|
skipExisting: command.skipExisting,
|
|
42
|
+
verify: command.verify,
|
|
32
43
|
yes: command.yes,
|
|
33
44
|
});
|
|
34
45
|
break;
|
|
35
46
|
case 'init':
|
|
36
47
|
code = await runInitCommand({
|
|
37
48
|
dryRun: command.dryRun,
|
|
49
|
+
install: command.install,
|
|
38
50
|
output,
|
|
39
51
|
overwrite: command.overwrite,
|
|
40
52
|
skipExisting: command.skipExisting,
|
|
53
|
+
verify: command.verify,
|
|
41
54
|
yes: command.yes,
|
|
42
55
|
});
|
|
43
56
|
break;
|
|
@@ -46,6 +59,9 @@ export const runVyriyCli = async (args = [], { output = console } = {}) => {
|
|
|
46
59
|
code = result.code;
|
|
47
60
|
break;
|
|
48
61
|
}
|
|
62
|
+
case 'publish':
|
|
63
|
+
code = await runPublishCommand();
|
|
64
|
+
break;
|
|
49
65
|
case 'help':
|
|
50
66
|
output.log(helpText);
|
|
51
67
|
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>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './publish.js';
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { chmod, copyFile, readdir, readFile, stat, unlink, writeFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
const DIST_DIR = 'dist';
|
|
4
|
+
const AGENTS_FILE = 'AGENTS.md';
|
|
5
|
+
const LICENSE_FILE = 'LICENSE';
|
|
6
|
+
const PACKAGE_JSON_FILE = 'package.json';
|
|
7
|
+
const PACKAGES_DIR = 'packages';
|
|
8
|
+
const README_FILE = 'README.md';
|
|
9
|
+
const toPosixPath = (value) => value.split(path.sep).join('/');
|
|
10
|
+
const toPackagePath = (value) => {
|
|
11
|
+
const normalizedValue = toPosixPath(value);
|
|
12
|
+
return `./${normalizedValue.replace(/^\.\//, '')}`;
|
|
13
|
+
};
|
|
14
|
+
const hasFile = async (filePath) => {
|
|
15
|
+
try {
|
|
16
|
+
return (await stat(filePath)).isFile();
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
const readJson = async (filePath) => {
|
|
23
|
+
const content = await readFile(filePath, 'utf8');
|
|
24
|
+
return JSON.parse(content);
|
|
25
|
+
};
|
|
26
|
+
const writeJson = async (filePath, value) => {
|
|
27
|
+
await writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
28
|
+
};
|
|
29
|
+
const isEmptyJavaScriptContent = (content) => {
|
|
30
|
+
const normalizedContent = content.trim();
|
|
31
|
+
return normalizedContent.length === 0 || normalizedContent === 'export {};';
|
|
32
|
+
};
|
|
33
|
+
const exportTargetPattern = /^\s*export(?:\s+\*|\s+\{[^}]*\})\s+from\s+['"](\..+\.js)['"];?\s*$/;
|
|
34
|
+
const getMissingExportTarget = async (file, line) => {
|
|
35
|
+
const exportTarget = exportTargetPattern.exec(line)?.[1];
|
|
36
|
+
if (!exportTarget) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
const exportTargetPath = path.resolve(path.dirname(file), exportTarget);
|
|
40
|
+
return (await hasFile(exportTargetPath)) ? undefined : exportTargetPath;
|
|
41
|
+
};
|
|
42
|
+
const readFiles = async (directory) => {
|
|
43
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
44
|
+
const files = await Promise.all(entries.map(async (entry) => {
|
|
45
|
+
const entryPath = path.join(directory, entry.name);
|
|
46
|
+
if (entry.isDirectory()) {
|
|
47
|
+
return readFiles(entryPath);
|
|
48
|
+
}
|
|
49
|
+
return entry.isFile() ? [entryPath] : [];
|
|
50
|
+
}));
|
|
51
|
+
return files.flat();
|
|
52
|
+
};
|
|
53
|
+
const getPackageMain = async (packageDirectory, packageJson, javaScriptFiles) => {
|
|
54
|
+
if (packageJson.main?.endsWith('.js')) {
|
|
55
|
+
const mainPath = packageJson.main.replace(/^\.\//, '');
|
|
56
|
+
if (await hasFile(path.join(packageDirectory, mainPath))) {
|
|
57
|
+
return toPosixPath(mainPath);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (javaScriptFiles.includes('index.js')) {
|
|
61
|
+
return 'index.js';
|
|
62
|
+
}
|
|
63
|
+
return javaScriptFiles[0];
|
|
64
|
+
};
|
|
65
|
+
const createExportTarget = (javaScriptFile) => {
|
|
66
|
+
const packagePath = toPackagePath(javaScriptFile);
|
|
67
|
+
return {
|
|
68
|
+
types: packagePath.replace(/\.js$/, '.d.ts'),
|
|
69
|
+
import: packagePath,
|
|
70
|
+
default: packagePath,
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
const createExports = (mainFile, javaScriptFiles) => {
|
|
74
|
+
const exports = {
|
|
75
|
+
'.': createExportTarget(mainFile),
|
|
76
|
+
};
|
|
77
|
+
for (const javaScriptFile of javaScriptFiles) {
|
|
78
|
+
const packagePath = toPackagePath(javaScriptFile);
|
|
79
|
+
const extensionlessPackagePath = packagePath.replace(/\.js$/, '');
|
|
80
|
+
const target = createExportTarget(javaScriptFile);
|
|
81
|
+
exports[extensionlessPackagePath] = target;
|
|
82
|
+
exports[packagePath] = target;
|
|
83
|
+
}
|
|
84
|
+
return exports;
|
|
85
|
+
};
|
|
86
|
+
const createPackageRepository = (rootPackageJson, packageDirectory) => {
|
|
87
|
+
const rootRepository = rootPackageJson.repository;
|
|
88
|
+
if (!rootRepository) {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
...rootRepository,
|
|
93
|
+
directory: toPosixPath(path.join(PACKAGES_DIR, path.basename(packageDirectory))),
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
const getJavaScriptFiles = async (packageDirectory) => {
|
|
97
|
+
const files = await readFiles(packageDirectory);
|
|
98
|
+
const javaScriptFiles = [];
|
|
99
|
+
for (const file of files) {
|
|
100
|
+
const relativeFile = toPosixPath(path.relative(packageDirectory, file));
|
|
101
|
+
const declarationFile = path.join(packageDirectory, relativeFile.replace(/\.js$/, '.d.ts'));
|
|
102
|
+
if (relativeFile.endsWith('.js') && !relativeFile.endsWith('.test.js') && (await hasFile(declarationFile))) {
|
|
103
|
+
javaScriptFiles.push(relativeFile);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return javaScriptFiles.sort((left, right) => left.localeCompare(right));
|
|
107
|
+
};
|
|
108
|
+
const removeEmptyJavaScriptFiles = async (packageDirectory) => {
|
|
109
|
+
const files = await readFiles(packageDirectory);
|
|
110
|
+
for (const file of files) {
|
|
111
|
+
if (file.endsWith('.js') && isEmptyJavaScriptContent(await readFile(file, 'utf8'))) {
|
|
112
|
+
await unlink(file);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
const removeMissingJavaScriptExports = async (packageDirectory) => {
|
|
117
|
+
const files = await readFiles(packageDirectory);
|
|
118
|
+
for (const file of files) {
|
|
119
|
+
if (!file.endsWith('.js')) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const content = await readFile(file, 'utf8');
|
|
123
|
+
const lines = content.split('\n');
|
|
124
|
+
const retainedLines = [];
|
|
125
|
+
for (const line of lines) {
|
|
126
|
+
if (!(await getMissingExportTarget(file, line))) {
|
|
127
|
+
retainedLines.push(line);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (retainedLines.length !== lines.length) {
|
|
131
|
+
await writeFile(file, retainedLines.join('\n'));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
const copyReadme = async (packageDirectory) => {
|
|
136
|
+
const packageName = path.basename(packageDirectory);
|
|
137
|
+
const sourceReadmePath = path.join(PACKAGES_DIR, packageName, README_FILE);
|
|
138
|
+
if (await hasFile(sourceReadmePath)) {
|
|
139
|
+
await copyFile(sourceReadmePath, path.join(packageDirectory, README_FILE));
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
const resolveSourceAgentsPath = async (packageAgentsPath, sharedAgentsPath, rootAgentsPath) => {
|
|
143
|
+
if (await hasFile(packageAgentsPath)) {
|
|
144
|
+
return packageAgentsPath;
|
|
145
|
+
}
|
|
146
|
+
if (await hasFile(sharedAgentsPath)) {
|
|
147
|
+
return sharedAgentsPath;
|
|
148
|
+
}
|
|
149
|
+
return rootAgentsPath;
|
|
150
|
+
};
|
|
151
|
+
const copyAgents = async (packageDirectory, rootPackageJson) => {
|
|
152
|
+
const packageName = path.basename(packageDirectory);
|
|
153
|
+
const packageAgentsPath = path.join(PACKAGES_DIR, packageName, AGENTS_FILE);
|
|
154
|
+
const sharedAgentsPath = path.join(PACKAGES_DIR, AGENTS_FILE);
|
|
155
|
+
const rootAgentsPath = typeof rootPackageJson.agents === 'string' ? rootPackageJson.agents.replace(/^\.\//, '') : '';
|
|
156
|
+
const sourceAgentsPath = await resolveSourceAgentsPath(packageAgentsPath, sharedAgentsPath, rootAgentsPath);
|
|
157
|
+
if (await hasFile(sourceAgentsPath)) {
|
|
158
|
+
await copyFile(sourceAgentsPath, path.join(packageDirectory, AGENTS_FILE));
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
return false;
|
|
162
|
+
};
|
|
163
|
+
const copyLicense = async (packageDirectory) => {
|
|
164
|
+
if (await hasFile(LICENSE_FILE)) {
|
|
165
|
+
await copyFile(LICENSE_FILE, path.join(packageDirectory, LICENSE_FILE));
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
const getPackageBinFiles = (packageJson) => {
|
|
169
|
+
if (typeof packageJson.bin === 'string') {
|
|
170
|
+
return [packageJson.bin];
|
|
171
|
+
}
|
|
172
|
+
if (packageJson.bin && typeof packageJson.bin === 'object') {
|
|
173
|
+
return Object.values(packageJson.bin);
|
|
174
|
+
}
|
|
175
|
+
return [];
|
|
176
|
+
};
|
|
177
|
+
const makePackageBinsExecutable = async (packageDirectory, packageJson) => {
|
|
178
|
+
for (const binFile of getPackageBinFiles(packageJson)) {
|
|
179
|
+
const binFilePath = path.join(packageDirectory, binFile.replace(/^\.\//, ''));
|
|
180
|
+
if (await hasFile(binFilePath)) {
|
|
181
|
+
await chmod(binFilePath, 0o755);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
const copyRootFile = async (fileName) => {
|
|
186
|
+
if (await hasFile(fileName)) {
|
|
187
|
+
await copyFile(fileName, path.join(DIST_DIR, fileName));
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
const publishRootPackageJson = async () => {
|
|
191
|
+
const packageJson = await readJson(PACKAGE_JSON_FILE);
|
|
192
|
+
delete packageJson.agents;
|
|
193
|
+
delete packageJson.dependencies;
|
|
194
|
+
delete packageJson.packageManager;
|
|
195
|
+
delete packageJson.scripts;
|
|
196
|
+
delete packageJson.devDependencies;
|
|
197
|
+
await writeJson(path.join(DIST_DIR, PACKAGE_JSON_FILE), packageJson);
|
|
198
|
+
};
|
|
199
|
+
const publishRoot = async () => {
|
|
200
|
+
await copyRootFile(README_FILE);
|
|
201
|
+
await copyRootFile(LICENSE_FILE);
|
|
202
|
+
await publishRootPackageJson();
|
|
203
|
+
};
|
|
204
|
+
const syncPackageRuntimeMetadata = (packageJson, rootPackageJson) => {
|
|
205
|
+
if (packageJson.name !== 'vyriy') {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
packageJson.packageManager = rootPackageJson.packageManager;
|
|
209
|
+
packageJson.engines = rootPackageJson.engines;
|
|
210
|
+
};
|
|
211
|
+
const publishPackage = async (packageJsonPath, rootPackageJson) => {
|
|
212
|
+
const packageDirectory = path.dirname(packageJsonPath);
|
|
213
|
+
const packageJson = await readJson(packageJsonPath);
|
|
214
|
+
await removeEmptyJavaScriptFiles(packageDirectory);
|
|
215
|
+
await removeMissingJavaScriptExports(packageDirectory);
|
|
216
|
+
await removeEmptyJavaScriptFiles(packageDirectory);
|
|
217
|
+
const javaScriptFiles = await getJavaScriptFiles(packageDirectory);
|
|
218
|
+
await copyLicense(packageDirectory);
|
|
219
|
+
await copyReadme(packageDirectory);
|
|
220
|
+
const hasAgents = await copyAgents(packageDirectory, rootPackageJson);
|
|
221
|
+
delete packageJson.private;
|
|
222
|
+
if (hasAgents && rootPackageJson.agents) {
|
|
223
|
+
packageJson.agents = toPackagePath(AGENTS_FILE);
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
delete packageJson.agents;
|
|
227
|
+
}
|
|
228
|
+
if (rootPackageJson.license) {
|
|
229
|
+
packageJson.license = rootPackageJson.license;
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
delete packageJson.license;
|
|
233
|
+
}
|
|
234
|
+
const repository = createPackageRepository(rootPackageJson, packageDirectory);
|
|
235
|
+
if (repository) {
|
|
236
|
+
packageJson.repository = repository;
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
delete packageJson.repository;
|
|
240
|
+
}
|
|
241
|
+
syncPackageRuntimeMetadata(packageJson, rootPackageJson);
|
|
242
|
+
if (javaScriptFiles.length > 0) {
|
|
243
|
+
const mainFile = await getPackageMain(packageDirectory, packageJson, javaScriptFiles);
|
|
244
|
+
if (mainFile) {
|
|
245
|
+
packageJson.main = toPackagePath(mainFile);
|
|
246
|
+
packageJson.types = toPackagePath(mainFile).replace(/\.js$/, '.d.ts');
|
|
247
|
+
packageJson.exports = createExports(mainFile, javaScriptFiles);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
await makePackageBinsExecutable(packageDirectory, packageJson);
|
|
251
|
+
await writeJson(packageJsonPath, packageJson);
|
|
252
|
+
};
|
|
253
|
+
export const runPublishCommand = async ({ cwd = process.cwd() } = {}) => {
|
|
254
|
+
const previousCwd = process.cwd();
|
|
255
|
+
try {
|
|
256
|
+
process.chdir(cwd);
|
|
257
|
+
const rootPackageJson = await readJson(PACKAGE_JSON_FILE);
|
|
258
|
+
await publishRoot();
|
|
259
|
+
const entries = await readdir(DIST_DIR, { withFileTypes: true });
|
|
260
|
+
const packageJsonPaths = entries
|
|
261
|
+
.filter((entry) => entry.isDirectory())
|
|
262
|
+
.map((entry) => path.join(DIST_DIR, entry.name, 'package.json'))
|
|
263
|
+
.sort((left, right) => left.localeCompare(right));
|
|
264
|
+
for (const packageJsonPath of packageJsonPaths) {
|
|
265
|
+
if (await hasFile(packageJsonPath)) {
|
|
266
|
+
await publishPackage(packageJsonPath, rootPackageJson);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return 0;
|
|
270
|
+
}
|
|
271
|
+
finally {
|
|
272
|
+
process.chdir(previousCwd);
|
|
273
|
+
}
|
|
274
|
+
};
|