@veloxts/cli 0.6.106 → 0.7.0
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/CHANGELOG.md +27 -0
- package/README.md +1 -1
- package/dist/commands/dev.js +123 -16
- package/dist/dev/hmr-runner.d.ts +3 -1
- package/dist/dev/hmr-runner.js +6 -6
- package/dist/utils/paths.d.ts +38 -1
- package/dist/utils/paths.js +174 -4
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# @veloxts/cli
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- chore(deps): update Prisma from 7.3.0 to 7.4.0
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies
|
|
12
|
+
- @veloxts/auth@0.7.0
|
|
13
|
+
- @veloxts/core@0.7.0
|
|
14
|
+
- @veloxts/orm@0.7.0
|
|
15
|
+
- @veloxts/router@0.7.0
|
|
16
|
+
- @veloxts/validation@0.7.0
|
|
17
|
+
|
|
18
|
+
## 0.6.107
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- feat(cli): add workspace monorepo support for `velox dev`
|
|
23
|
+
- Updated dependencies
|
|
24
|
+
- @veloxts/auth@0.6.107
|
|
25
|
+
- @veloxts/core@0.6.107
|
|
26
|
+
- @veloxts/orm@0.6.107
|
|
27
|
+
- @veloxts/router@0.6.107
|
|
28
|
+
- @veloxts/validation@0.6.107
|
|
29
|
+
|
|
3
30
|
## 0.6.106
|
|
4
31
|
|
|
5
32
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @veloxts/cli
|
|
2
2
|
|
|
3
|
-
> **Early Access (v0.
|
|
3
|
+
> **Early Access (v0.7.x)**
|
|
4
4
|
|
|
5
5
|
Command-line interface for VeloxTS Framework - provides development server with HMR, migration commands, and code generators. Learn more at [@veloxts/velox](https://www.npmjs.com/package/@veloxts/velox).
|
|
6
6
|
|
package/dist/commands/dev.js
CHANGED
|
@@ -7,12 +7,13 @@
|
|
|
7
7
|
* Use --no-hmr for legacy tsx watch mode with full process restarts.
|
|
8
8
|
*/
|
|
9
9
|
import { spawn } from 'node:child_process';
|
|
10
|
+
import path from 'node:path';
|
|
10
11
|
import * as p from '@clack/prompts';
|
|
11
12
|
import { Command } from 'commander';
|
|
12
13
|
import pc from 'picocolors';
|
|
13
14
|
import { buildWatchArgs, runHMRServer } from '../dev/index.js';
|
|
14
15
|
import { error, formatCommand, formatPath, info, instruction, printBanner, success, } from '../utils/output.js';
|
|
15
|
-
import { detectProjectType, findEntryPoint, isVeloxProject, validateEntryPath, } from '../utils/paths.js';
|
|
16
|
+
import { detectProjectType, discoverEntryPoints, findApiPackageRoot, findEntryPoint, findWebPackageRoot, isVeloxProject, isWorkspaceRoot, validateEntryPath, } from '../utils/paths.js';
|
|
16
17
|
/**
|
|
17
18
|
* Create the dev command
|
|
18
19
|
*/
|
|
@@ -28,6 +29,7 @@ export function createDevCommand(version) {
|
|
|
28
29
|
.option('--no-hmr', 'Disable HMR and use legacy tsx watch mode')
|
|
29
30
|
.option('-v, --verbose', 'Show detailed timing and reload information', false)
|
|
30
31
|
.option('-d, --debug', 'Enable debug logging and request tracing', false)
|
|
32
|
+
.option('--all', 'Start both API and Web dev servers (workspace monorepos)', false)
|
|
31
33
|
.action(async (options) => {
|
|
32
34
|
await runDevServer(options, version);
|
|
33
35
|
});
|
|
@@ -89,15 +91,38 @@ async function runVinxiServer(options, version) {
|
|
|
89
91
|
}
|
|
90
92
|
});
|
|
91
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* Spawn the web dev server in a workspace monorepo.
|
|
96
|
+
* Runs `pnpm dev` inside apps/web/ with inherited stdio.
|
|
97
|
+
* Returns the child process for lifecycle management.
|
|
98
|
+
*/
|
|
99
|
+
function spawnWebDevServer(webRoot) {
|
|
100
|
+
info(`Starting web dev server in ${formatPath(webRoot)}`);
|
|
101
|
+
console.log(` ${pc.dim('Running pnpm dev in apps/web/')}`);
|
|
102
|
+
console.log('');
|
|
103
|
+
return spawn('pnpm', ['dev'], {
|
|
104
|
+
stdio: 'inherit',
|
|
105
|
+
cwd: webRoot,
|
|
106
|
+
env: { ...process.env, NODE_ENV: 'development' },
|
|
107
|
+
});
|
|
108
|
+
}
|
|
92
109
|
/**
|
|
93
110
|
* Run the development server
|
|
94
111
|
*/
|
|
95
112
|
async function runDevServer(options, version) {
|
|
113
|
+
const cwd = process.cwd();
|
|
96
114
|
const s = p.spinner();
|
|
97
115
|
try {
|
|
98
|
-
// Check if we're in a VeloxTS project
|
|
116
|
+
// Check if we're in a VeloxTS project (also check workspace subpackages)
|
|
99
117
|
s.start('Checking project...');
|
|
100
|
-
|
|
118
|
+
let isVelox = await isVeloxProject(cwd);
|
|
119
|
+
// In a workspace root, @veloxts deps live in subpackages, not root
|
|
120
|
+
if (!isVelox && isWorkspaceRoot(cwd)) {
|
|
121
|
+
const apiRoot = findApiPackageRoot(cwd);
|
|
122
|
+
if (apiRoot) {
|
|
123
|
+
isVelox = await isVeloxProject(apiRoot);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
101
126
|
if (!isVelox) {
|
|
102
127
|
s.stop('Project check failed');
|
|
103
128
|
error('This does not appear to be a VeloxTS project.');
|
|
@@ -105,7 +130,7 @@ async function runDevServer(options, version) {
|
|
|
105
130
|
process.exit(1);
|
|
106
131
|
}
|
|
107
132
|
// Detect project type (API-only vs Vinxi/RSC)
|
|
108
|
-
const projectType = await detectProjectType();
|
|
133
|
+
const projectType = await detectProjectType(cwd);
|
|
109
134
|
if (projectType.isVinxi) {
|
|
110
135
|
s.stop('Vinxi project detected');
|
|
111
136
|
success('Detected VeloxTS full-stack project (RSC + Vinxi)');
|
|
@@ -119,7 +144,7 @@ async function runDevServer(options, version) {
|
|
|
119
144
|
// User specified an entry point - validate it
|
|
120
145
|
s.start('Validating entry point...');
|
|
121
146
|
try {
|
|
122
|
-
entryPoint = validateEntryPath(options.entry);
|
|
147
|
+
entryPoint = validateEntryPath(options.entry, cwd);
|
|
123
148
|
s.stop(`Entry point: ${formatPath(entryPoint)}`);
|
|
124
149
|
}
|
|
125
150
|
catch (err) {
|
|
@@ -129,18 +154,54 @@ async function runDevServer(options, version) {
|
|
|
129
154
|
}
|
|
130
155
|
}
|
|
131
156
|
else {
|
|
132
|
-
// Auto-detect entry point
|
|
157
|
+
// Auto-detect entry point (workspace-aware via findEntryPoint)
|
|
133
158
|
s.start('Detecting entry point...');
|
|
134
|
-
const detected = findEntryPoint();
|
|
135
|
-
if (
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
instruction('Try specifying the entry point with --entry flag:');
|
|
139
|
-
console.log(` ${formatCommand('velox dev --entry src/index.ts')}`);
|
|
140
|
-
process.exit(1);
|
|
159
|
+
const detected = findEntryPoint(cwd);
|
|
160
|
+
if (detected) {
|
|
161
|
+
entryPoint = detected;
|
|
162
|
+
s.stop(`Entry point: ${formatPath(entryPoint)}`);
|
|
141
163
|
}
|
|
142
|
-
|
|
143
|
-
|
|
164
|
+
else {
|
|
165
|
+
// Auto-detection failed — discover possible entry points and let user pick
|
|
166
|
+
const candidates = discoverEntryPoints(cwd);
|
|
167
|
+
if (candidates.length > 0) {
|
|
168
|
+
s.stop('Multiple possible entry points found');
|
|
169
|
+
const selected = await p.select({
|
|
170
|
+
message: 'Select the entry point for your application',
|
|
171
|
+
options: candidates.map((c) => ({
|
|
172
|
+
value: c.absolute,
|
|
173
|
+
label: c.relative,
|
|
174
|
+
})),
|
|
175
|
+
});
|
|
176
|
+
if (p.isCancel(selected)) {
|
|
177
|
+
p.cancel('Operation cancelled.');
|
|
178
|
+
process.exit(0);
|
|
179
|
+
}
|
|
180
|
+
try {
|
|
181
|
+
entryPoint = validateEntryPath(String(selected), cwd);
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
error(err instanceof Error ? err.message : 'Invalid entry point');
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
success(`Using ${formatPath(entryPoint)}`);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
s.stop('Entry point not found');
|
|
191
|
+
error('Could not find any application entry point.');
|
|
192
|
+
instruction('Create src/index.ts or specify one with --entry:');
|
|
193
|
+
console.log(` ${formatCommand('velox dev --entry src/index.ts')}`);
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// Determine the working directory for the child process.
|
|
199
|
+
// In a workspace monorepo, the API server needs to run from apps/api/
|
|
200
|
+
// so that dotenv/config loads apps/api/.env and Prisma finds its config.
|
|
201
|
+
const apiRoot = findApiPackageRoot(cwd);
|
|
202
|
+
const apiProcessCwd = apiRoot ?? cwd;
|
|
203
|
+
if (apiRoot) {
|
|
204
|
+
info(`Workspace detected — API root: ${formatPath(apiRoot)}`);
|
|
144
205
|
}
|
|
145
206
|
// Validate port and host
|
|
146
207
|
const port = options.port || '3030';
|
|
@@ -173,6 +234,49 @@ async function runDevServer(options, version) {
|
|
|
173
234
|
};
|
|
174
235
|
const clearScreen = options.clear !== false;
|
|
175
236
|
const verbose = options.verbose ?? false;
|
|
237
|
+
// --all: also start the web dev server in parallel
|
|
238
|
+
let webProcess = null;
|
|
239
|
+
let isShuttingDown = false;
|
|
240
|
+
if (options.all) {
|
|
241
|
+
// If we're in a subpackage (e.g., apps/api/), search from the parent workspace root
|
|
242
|
+
let webSearchRoot = cwd;
|
|
243
|
+
if (!isWorkspaceRoot(cwd)) {
|
|
244
|
+
const parentDir = path.dirname(cwd);
|
|
245
|
+
if (isWorkspaceRoot(parentDir)) {
|
|
246
|
+
webSearchRoot = parentDir;
|
|
247
|
+
}
|
|
248
|
+
const grandparentDir = path.dirname(parentDir);
|
|
249
|
+
if (!isWorkspaceRoot(parentDir) && isWorkspaceRoot(grandparentDir)) {
|
|
250
|
+
webSearchRoot = grandparentDir;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
const webRoot = findWebPackageRoot(webSearchRoot);
|
|
254
|
+
if (webRoot) {
|
|
255
|
+
webProcess = spawnWebDevServer(webRoot);
|
|
256
|
+
webProcess.on('error', (err) => {
|
|
257
|
+
error(`Web dev server failed: ${err.message}`);
|
|
258
|
+
});
|
|
259
|
+
webProcess.on('exit', (code) => {
|
|
260
|
+
if (!isShuttingDown && code !== 0) {
|
|
261
|
+
console.log(pc.yellow(` Web dev server exited with code ${code}`));
|
|
262
|
+
console.log(pc.dim(' API server continues running.'));
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
info('No web package found — starting API only.');
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// Ensure the web process is cleaned up on exit (covers both HMR and legacy paths).
|
|
271
|
+
// The 'exit' handler runs synchronously when process.exit() is called,
|
|
272
|
+
// so SIGTERM is the best we can do (no async waiting).
|
|
273
|
+
if (webProcess) {
|
|
274
|
+
process.on('exit', () => {
|
|
275
|
+
if (webProcess && !webProcess.killed) {
|
|
276
|
+
webProcess.kill('SIGTERM');
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
}
|
|
176
280
|
// HMR is the default mode
|
|
177
281
|
// Use --no-hmr for legacy tsx watch mode
|
|
178
282
|
if (options.hmr !== false) {
|
|
@@ -185,6 +289,7 @@ async function runDevServer(options, version) {
|
|
|
185
289
|
verbose,
|
|
186
290
|
debug,
|
|
187
291
|
clearOnRestart: clearScreen,
|
|
292
|
+
cwd: apiProcessCwd,
|
|
188
293
|
});
|
|
189
294
|
return; // HMR runner handles its own lifecycle
|
|
190
295
|
}
|
|
@@ -202,19 +307,21 @@ async function runDevServer(options, version) {
|
|
|
202
307
|
const devProcess = spawn('npx', watchArgs, {
|
|
203
308
|
stdio: 'inherit',
|
|
204
309
|
env,
|
|
310
|
+
cwd: apiProcessCwd,
|
|
205
311
|
});
|
|
206
312
|
// Handle process termination
|
|
207
|
-
let isShuttingDown = false;
|
|
208
313
|
const shutdown = (signal) => {
|
|
209
314
|
if (isShuttingDown)
|
|
210
315
|
return;
|
|
211
316
|
isShuttingDown = true;
|
|
212
317
|
console.log(`\n\n${pc.yellow('⚠')} ${pc.dim(`Received ${signal}, shutting down gracefully...`)}`);
|
|
213
318
|
devProcess.kill('SIGTERM');
|
|
319
|
+
webProcess?.kill('SIGTERM');
|
|
214
320
|
// Force kill after 5 seconds if process doesn't exit
|
|
215
321
|
const forceKillTimeout = setTimeout(() => {
|
|
216
322
|
console.log(pc.red('✗ Force killing process...'));
|
|
217
323
|
devProcess.kill('SIGKILL');
|
|
324
|
+
webProcess?.kill('SIGKILL');
|
|
218
325
|
process.exit(1);
|
|
219
326
|
}, 5000);
|
|
220
327
|
devProcess.on('exit', () => {
|
package/dist/dev/hmr-runner.d.ts
CHANGED
|
@@ -24,6 +24,8 @@ export interface HMRRunnerOptions {
|
|
|
24
24
|
readonly debug?: boolean;
|
|
25
25
|
/** Clear console on full restart */
|
|
26
26
|
readonly clearOnRestart?: boolean;
|
|
27
|
+
/** Working directory for the child process (defaults to process.cwd()) */
|
|
28
|
+
readonly cwd?: string;
|
|
27
29
|
}
|
|
28
30
|
/**
|
|
29
31
|
* HMR Runner manages a Node.js process with hot-hook enabled
|
|
@@ -99,7 +101,7 @@ export declare class HMRRunner {
|
|
|
99
101
|
*/
|
|
100
102
|
private setupSignalHandlers;
|
|
101
103
|
/**
|
|
102
|
-
* Get basename from file path
|
|
104
|
+
* Get basename from file path (cross-platform)
|
|
103
105
|
*/
|
|
104
106
|
private getBasename;
|
|
105
107
|
}
|
package/dist/dev/hmr-runner.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { spawn } from 'node:child_process';
|
|
10
10
|
import { createRequire } from 'node:module';
|
|
11
|
+
import { basename } from 'node:path';
|
|
11
12
|
import pc from 'picocolors';
|
|
12
13
|
// Resolve hot-hook from CLI's node_modules, not from user's project
|
|
13
14
|
const require = createRequire(import.meta.url);
|
|
@@ -185,7 +186,7 @@ export class HMRRunner {
|
|
|
185
186
|
this.child = spawn('node', ['--import=tsx', `--import=${hotHookRegisterPath}`, this.options.entry], {
|
|
186
187
|
stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
|
|
187
188
|
env,
|
|
188
|
-
cwd: process.cwd(),
|
|
189
|
+
cwd: this.options.cwd ?? process.cwd(),
|
|
189
190
|
});
|
|
190
191
|
// Set up IPC message handler
|
|
191
192
|
this.child.on('message', (message) => this.handleIPCMessage(message));
|
|
@@ -379,18 +380,17 @@ export class HMRRunner {
|
|
|
379
380
|
console.log(pc.dim('Development server stopped.'));
|
|
380
381
|
process.exit(0);
|
|
381
382
|
};
|
|
382
|
-
process.
|
|
383
|
-
process.
|
|
383
|
+
process.once('SIGINT', () => shutdown('SIGINT'));
|
|
384
|
+
process.once('SIGTERM', () => shutdown('SIGTERM'));
|
|
384
385
|
}
|
|
385
386
|
// --------------------------------------------------------------------------
|
|
386
387
|
// Private: Utilities
|
|
387
388
|
// --------------------------------------------------------------------------
|
|
388
389
|
/**
|
|
389
|
-
* Get basename from file path
|
|
390
|
+
* Get basename from file path (cross-platform)
|
|
390
391
|
*/
|
|
391
392
|
getBasename(filePath) {
|
|
392
|
-
|
|
393
|
-
return parts[parts.length - 1] || filePath;
|
|
393
|
+
return basename(filePath);
|
|
394
394
|
}
|
|
395
395
|
}
|
|
396
396
|
// ============================================================================
|
package/dist/utils/paths.d.ts
CHANGED
|
@@ -3,9 +3,46 @@
|
|
|
3
3
|
*/
|
|
4
4
|
/**
|
|
5
5
|
* Find the project entry point
|
|
6
|
-
* Looks for common entry points in order of preference
|
|
6
|
+
* Looks for common entry points in order of preference.
|
|
7
|
+
* If nothing is found in cwd and cwd is a workspace root,
|
|
8
|
+
* also searches inside the API package (apps/api/).
|
|
7
9
|
*/
|
|
8
10
|
export declare function findEntryPoint(cwd?: string): string | null;
|
|
11
|
+
/**
|
|
12
|
+
* Check if a directory is a pnpm/npm/yarn workspace root.
|
|
13
|
+
* Looks for pnpm-workspace.yaml or "workspaces" in package.json.
|
|
14
|
+
*/
|
|
15
|
+
export declare function isWorkspaceRoot(cwd: string): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Find the API package root in a workspace monorepo.
|
|
18
|
+
*
|
|
19
|
+
* 1. Checks if cwd is a workspace root
|
|
20
|
+
* 2. Looks for apps/api/package.json with @veloxts/* deps
|
|
21
|
+
* 3. Falls back to scanning apps/* for any package with @veloxts/* deps
|
|
22
|
+
*
|
|
23
|
+
* Returns the absolute path to the API package root, or null.
|
|
24
|
+
*/
|
|
25
|
+
export declare function findApiPackageRoot(cwd?: string): string | null;
|
|
26
|
+
/**
|
|
27
|
+
* Find the Web package root in a workspace monorepo.
|
|
28
|
+
*
|
|
29
|
+
* Looks for apps/web/package.json with vite, @veloxts/web, or react in deps.
|
|
30
|
+
*
|
|
31
|
+
* Returns the absolute path to the Web package root, or null.
|
|
32
|
+
*/
|
|
33
|
+
export declare function findWebPackageRoot(cwd?: string): string | null;
|
|
34
|
+
/**
|
|
35
|
+
* Discover potential entry point files in the project.
|
|
36
|
+
*
|
|
37
|
+
* Scans cwd and, in workspaces, each apps/* subpackage for TypeScript files
|
|
38
|
+
* that look like entry points (index.ts, main.ts, server.ts, app.ts in src/ or root).
|
|
39
|
+
*
|
|
40
|
+
* Returns paths relative to cwd for display, paired with absolute paths.
|
|
41
|
+
*/
|
|
42
|
+
export declare function discoverEntryPoints(cwd?: string): Array<{
|
|
43
|
+
absolute: string;
|
|
44
|
+
relative: string;
|
|
45
|
+
}>;
|
|
9
46
|
/**
|
|
10
47
|
* Check if a file exists at the given path
|
|
11
48
|
*/
|
package/dist/utils/paths.js
CHANGED
|
@@ -1,23 +1,193 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Path utilities for finding project files
|
|
3
3
|
*/
|
|
4
|
-
import { existsSync, mkdirSync } from 'node:fs';
|
|
4
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync } from 'node:fs';
|
|
5
5
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
6
6
|
import path from 'node:path';
|
|
7
|
+
/** Common entry point file names, ordered by preference */
|
|
8
|
+
const ENTRY_POINT_PATTERNS = [
|
|
9
|
+
'src/index.ts',
|
|
10
|
+
'src/main.ts',
|
|
11
|
+
'src/app.ts',
|
|
12
|
+
'src/server.ts',
|
|
13
|
+
'index.ts',
|
|
14
|
+
'main.ts',
|
|
15
|
+
'app.ts',
|
|
16
|
+
'server.ts',
|
|
17
|
+
];
|
|
18
|
+
/** Default workspace subdirectory names */
|
|
19
|
+
const WORKSPACE_DIRS = {
|
|
20
|
+
APPS: 'apps',
|
|
21
|
+
API: 'api',
|
|
22
|
+
WEB: 'web',
|
|
23
|
+
};
|
|
24
|
+
/** Directories excluded from the API package fallback scan */
|
|
25
|
+
const NON_API_PACKAGES = ['web', 'docs', 'landing'];
|
|
7
26
|
/**
|
|
8
27
|
* Find the project entry point
|
|
9
|
-
* Looks for common entry points in order of preference
|
|
28
|
+
* Looks for common entry points in order of preference.
|
|
29
|
+
* If nothing is found in cwd and cwd is a workspace root,
|
|
30
|
+
* also searches inside the API package (apps/api/).
|
|
10
31
|
*/
|
|
11
32
|
export function findEntryPoint(cwd = process.cwd()) {
|
|
12
|
-
|
|
13
|
-
for (const candidate of
|
|
33
|
+
// 1. Try flat candidates in cwd (preserves existing behavior)
|
|
34
|
+
for (const candidate of ENTRY_POINT_PATTERNS) {
|
|
14
35
|
const fullPath = path.join(cwd, candidate);
|
|
15
36
|
if (existsSync(fullPath)) {
|
|
16
37
|
return fullPath;
|
|
17
38
|
}
|
|
18
39
|
}
|
|
40
|
+
// 2. If workspace detected, search inside the API package root
|
|
41
|
+
const apiRoot = findApiPackageRoot(cwd);
|
|
42
|
+
if (apiRoot) {
|
|
43
|
+
for (const candidate of ENTRY_POINT_PATTERNS) {
|
|
44
|
+
const fullPath = path.join(apiRoot, candidate);
|
|
45
|
+
if (existsSync(fullPath)) {
|
|
46
|
+
return fullPath;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
19
50
|
return null;
|
|
20
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Check if a directory is a pnpm/npm/yarn workspace root.
|
|
54
|
+
* Looks for pnpm-workspace.yaml or "workspaces" in package.json.
|
|
55
|
+
*/
|
|
56
|
+
export function isWorkspaceRoot(cwd) {
|
|
57
|
+
// Check pnpm-workspace.yaml
|
|
58
|
+
if (existsSync(path.join(cwd, 'pnpm-workspace.yaml'))) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
// Check package.json "workspaces" field (npm/yarn)
|
|
62
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
63
|
+
if (existsSync(pkgPath)) {
|
|
64
|
+
try {
|
|
65
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
66
|
+
if (pkg.workspaces) {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// Ignore parse errors
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Check if a package.json has @veloxts/* dependencies
|
|
78
|
+
*/
|
|
79
|
+
function hasVeloxDeps(pkgJsonPath) {
|
|
80
|
+
try {
|
|
81
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
|
|
82
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
83
|
+
return Object.keys(deps).some((dep) => dep.startsWith('@veloxts/'));
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Find the API package root in a workspace monorepo.
|
|
91
|
+
*
|
|
92
|
+
* 1. Checks if cwd is a workspace root
|
|
93
|
+
* 2. Looks for apps/api/package.json with @veloxts/* deps
|
|
94
|
+
* 3. Falls back to scanning apps/* for any package with @veloxts/* deps
|
|
95
|
+
*
|
|
96
|
+
* Returns the absolute path to the API package root, or null.
|
|
97
|
+
*/
|
|
98
|
+
export function findApiPackageRoot(cwd = process.cwd()) {
|
|
99
|
+
if (!isWorkspaceRoot(cwd)) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
// Primary: apps/api/
|
|
103
|
+
const primaryApiDir = path.join(cwd, WORKSPACE_DIRS.APPS, WORKSPACE_DIRS.API);
|
|
104
|
+
const primaryApiPkg = path.join(primaryApiDir, 'package.json');
|
|
105
|
+
if (existsSync(primaryApiPkg) && hasVeloxDeps(primaryApiPkg)) {
|
|
106
|
+
return primaryApiDir;
|
|
107
|
+
}
|
|
108
|
+
// Fallback: scan apps/*/package.json for first package with @veloxts/* deps
|
|
109
|
+
const appsDir = path.join(cwd, WORKSPACE_DIRS.APPS);
|
|
110
|
+
if (existsSync(appsDir)) {
|
|
111
|
+
try {
|
|
112
|
+
const entries = readdirSync(appsDir, { withFileTypes: true });
|
|
113
|
+
for (const entry of entries) {
|
|
114
|
+
if (entry.isDirectory() && !NON_API_PACKAGES.includes(entry.name)) {
|
|
115
|
+
const pkgPath = path.join(appsDir, entry.name, 'package.json');
|
|
116
|
+
if (existsSync(pkgPath) && hasVeloxDeps(pkgPath)) {
|
|
117
|
+
return path.join(appsDir, entry.name);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// Ignore scan errors
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Find the Web package root in a workspace monorepo.
|
|
130
|
+
*
|
|
131
|
+
* Looks for apps/web/package.json with vite, @veloxts/web, or react in deps.
|
|
132
|
+
*
|
|
133
|
+
* Returns the absolute path to the Web package root, or null.
|
|
134
|
+
*/
|
|
135
|
+
export function findWebPackageRoot(cwd = process.cwd()) {
|
|
136
|
+
if (!isWorkspaceRoot(cwd)) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
const webDir = path.join(cwd, WORKSPACE_DIRS.APPS, WORKSPACE_DIRS.WEB);
|
|
140
|
+
const webPkgPath = path.join(webDir, 'package.json');
|
|
141
|
+
if (!existsSync(webPkgPath)) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
const pkg = JSON.parse(readFileSync(webPkgPath, 'utf-8'));
|
|
146
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
147
|
+
const webMarkers = ['vite', '@veloxts/web', 'react', 'next', 'vinxi'];
|
|
148
|
+
if (webMarkers.some((marker) => marker in deps)) {
|
|
149
|
+
return webDir;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// Ignore parse errors
|
|
154
|
+
}
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Discover potential entry point files in the project.
|
|
159
|
+
*
|
|
160
|
+
* Scans cwd and, in workspaces, each apps/* subpackage for TypeScript files
|
|
161
|
+
* that look like entry points (index.ts, main.ts, server.ts, app.ts in src/ or root).
|
|
162
|
+
*
|
|
163
|
+
* Returns paths relative to cwd for display, paired with absolute paths.
|
|
164
|
+
*/
|
|
165
|
+
export function discoverEntryPoints(cwd = process.cwd()) {
|
|
166
|
+
const results = [];
|
|
167
|
+
const seen = new Set();
|
|
168
|
+
const scanDir = (dir) => {
|
|
169
|
+
for (const candidate of ENTRY_POINT_PATTERNS) {
|
|
170
|
+
const fullPath = path.normalize(path.join(dir, candidate));
|
|
171
|
+
if (existsSync(fullPath) && !seen.has(fullPath)) {
|
|
172
|
+
seen.add(fullPath);
|
|
173
|
+
results.push({
|
|
174
|
+
absolute: fullPath,
|
|
175
|
+
relative: path.relative(cwd, fullPath),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
// Scan cwd itself
|
|
181
|
+
scanDir(cwd);
|
|
182
|
+
// In workspaces, scan the detected API package root (not all apps/*)
|
|
183
|
+
if (isWorkspaceRoot(cwd)) {
|
|
184
|
+
const apiRoot = findApiPackageRoot(cwd);
|
|
185
|
+
if (apiRoot && apiRoot !== cwd) {
|
|
186
|
+
scanDir(apiRoot);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return results;
|
|
190
|
+
}
|
|
21
191
|
/**
|
|
22
192
|
* Check if a file exists at the given path
|
|
23
193
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veloxts/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Developer tooling and CLI commands for VeloxTS framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -41,17 +41,17 @@
|
|
|
41
41
|
"pluralize": "8.0.0",
|
|
42
42
|
"tsx": "4.21.0",
|
|
43
43
|
"yaml": "2.8.2",
|
|
44
|
-
"@veloxts/core": "0.
|
|
45
|
-
"@veloxts/
|
|
46
|
-
"@veloxts/
|
|
47
|
-
"@veloxts/
|
|
48
|
-
"@veloxts/
|
|
44
|
+
"@veloxts/core": "0.7.0",
|
|
45
|
+
"@veloxts/orm": "0.7.0",
|
|
46
|
+
"@veloxts/auth": "0.7.0",
|
|
47
|
+
"@veloxts/router": "0.7.0",
|
|
48
|
+
"@veloxts/validation": "0.7.0"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"@prisma/client": ">=7.0.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@prisma/client": "7.
|
|
54
|
+
"@prisma/client": "7.4.0",
|
|
55
55
|
"@types/node": "25.1.0",
|
|
56
56
|
"@types/pg": "8.16.0",
|
|
57
57
|
"@types/pluralize": "0.0.33",
|