@geekmidas/cli 0.30.0 ā 0.31.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/package.json +4 -4
- package/src/dev/__tests__/entry.spec.ts +140 -0
- package/src/dev/index.ts +246 -1
- package/src/index.ts +30 -16
- package/src/init/versions.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geekmidas/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"description": "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -48,11 +48,11 @@
|
|
|
48
48
|
"lodash.kebabcase": "^4.1.1",
|
|
49
49
|
"openapi-typescript": "^7.4.2",
|
|
50
50
|
"prompts": "~2.4.2",
|
|
51
|
+
"@geekmidas/constructs": "~0.6.0",
|
|
51
52
|
"@geekmidas/errors": "~0.1.0",
|
|
52
53
|
"@geekmidas/logger": "~0.4.0",
|
|
53
|
-
"@geekmidas/
|
|
54
|
-
"@geekmidas/envkit": "~0.5.0"
|
|
55
|
-
"@geekmidas/schema": "~0.1.0"
|
|
54
|
+
"@geekmidas/schema": "~0.1.0",
|
|
55
|
+
"@geekmidas/envkit": "~0.5.0"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@types/lodash.kebabcase": "^4.1.9",
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { mkdir, readFile, rm } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
5
|
+
import { createEntryWrapper, findSecretsRoot } from '../index';
|
|
6
|
+
|
|
7
|
+
describe('findSecretsRoot', () => {
|
|
8
|
+
let testDir: string;
|
|
9
|
+
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
testDir = join(tmpdir(), `gkm-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
12
|
+
await mkdir(testDir, { recursive: true });
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
await rm(testDir, { recursive: true, force: true });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should return startDir when no .gkm/secrets exists', () => {
|
|
20
|
+
const result = findSecretsRoot(testDir);
|
|
21
|
+
expect(result).toBe(testDir);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should find secrets in current directory', async () => {
|
|
25
|
+
await mkdir(join(testDir, '.gkm', 'secrets'), { recursive: true });
|
|
26
|
+
|
|
27
|
+
const result = findSecretsRoot(testDir);
|
|
28
|
+
expect(result).toBe(testDir);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should find secrets in parent directory', async () => {
|
|
32
|
+
const childDir = join(testDir, 'apps', 'auth');
|
|
33
|
+
await mkdir(childDir, { recursive: true });
|
|
34
|
+
await mkdir(join(testDir, '.gkm', 'secrets'), { recursive: true });
|
|
35
|
+
|
|
36
|
+
const result = findSecretsRoot(childDir);
|
|
37
|
+
expect(result).toBe(testDir);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should find secrets in grandparent directory', async () => {
|
|
41
|
+
const grandchildDir = join(testDir, 'apps', 'auth', 'src');
|
|
42
|
+
await mkdir(grandchildDir, { recursive: true });
|
|
43
|
+
await mkdir(join(testDir, '.gkm', 'secrets'), { recursive: true });
|
|
44
|
+
|
|
45
|
+
const result = findSecretsRoot(grandchildDir);
|
|
46
|
+
expect(result).toBe(testDir);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should prefer closer secrets directory', async () => {
|
|
50
|
+
// Create secrets in both parent and child
|
|
51
|
+
const childDir = join(testDir, 'apps', 'auth');
|
|
52
|
+
await mkdir(join(testDir, '.gkm', 'secrets'), { recursive: true });
|
|
53
|
+
await mkdir(join(childDir, '.gkm', 'secrets'), { recursive: true });
|
|
54
|
+
|
|
55
|
+
const result = findSecretsRoot(childDir);
|
|
56
|
+
expect(result).toBe(childDir);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('createEntryWrapper', () => {
|
|
61
|
+
let testDir: string;
|
|
62
|
+
|
|
63
|
+
beforeEach(async () => {
|
|
64
|
+
testDir = join(tmpdir(), `gkm-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
65
|
+
await mkdir(testDir, { recursive: true });
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
afterEach(async () => {
|
|
69
|
+
await rm(testDir, { recursive: true, force: true });
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should create wrapper without secrets injection', async () => {
|
|
73
|
+
const wrapperPath = join(testDir, 'wrapper.ts');
|
|
74
|
+
const entryPath = '/path/to/entry.ts';
|
|
75
|
+
|
|
76
|
+
await createEntryWrapper(wrapperPath, entryPath, undefined);
|
|
77
|
+
|
|
78
|
+
const content = await readFile(wrapperPath, 'utf-8');
|
|
79
|
+
|
|
80
|
+
expect(content).toContain("import '/path/to/entry.ts'");
|
|
81
|
+
expect(content).not.toContain('Credentials');
|
|
82
|
+
expect(content).toContain("Entry wrapper generated by 'gkm dev --entry'");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should create wrapper with secrets injection', async () => {
|
|
86
|
+
const wrapperPath = join(testDir, 'wrapper.ts');
|
|
87
|
+
const entryPath = '/path/to/entry.ts';
|
|
88
|
+
const secretsPath = '/path/to/secrets.json';
|
|
89
|
+
|
|
90
|
+
await createEntryWrapper(wrapperPath, entryPath, secretsPath);
|
|
91
|
+
|
|
92
|
+
const content = await readFile(wrapperPath, 'utf-8');
|
|
93
|
+
|
|
94
|
+
expect(content).toContain(
|
|
95
|
+
"import { Credentials } from '@geekmidas/envkit/credentials'",
|
|
96
|
+
);
|
|
97
|
+
expect(content).toContain(secretsPath);
|
|
98
|
+
expect(content).toContain('Object.assign(Credentials');
|
|
99
|
+
expect(content).toContain("import '/path/to/entry.ts'");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should inject secrets before entry import', async () => {
|
|
103
|
+
const wrapperPath = join(testDir, 'wrapper.ts');
|
|
104
|
+
const entryPath = '/path/to/entry.ts';
|
|
105
|
+
const secretsPath = '/path/to/secrets.json';
|
|
106
|
+
|
|
107
|
+
await createEntryWrapper(wrapperPath, entryPath, secretsPath);
|
|
108
|
+
|
|
109
|
+
const content = await readFile(wrapperPath, 'utf-8');
|
|
110
|
+
|
|
111
|
+
const credentialsIndex = content.indexOf('Credentials');
|
|
112
|
+
const importIndex = content.indexOf("import '/path/to/entry.ts'");
|
|
113
|
+
|
|
114
|
+
expect(credentialsIndex).toBeGreaterThan(-1);
|
|
115
|
+
expect(importIndex).toBeGreaterThan(-1);
|
|
116
|
+
expect(credentialsIndex).toBeLessThan(importIndex);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should include shebang line', async () => {
|
|
120
|
+
const wrapperPath = join(testDir, 'wrapper.ts');
|
|
121
|
+
const entryPath = '/path/to/entry.ts';
|
|
122
|
+
|
|
123
|
+
await createEntryWrapper(wrapperPath, entryPath, undefined);
|
|
124
|
+
|
|
125
|
+
const content = await readFile(wrapperPath, 'utf-8');
|
|
126
|
+
|
|
127
|
+
expect(content.startsWith('#!/usr/bin/env node')).toBe(true);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should handle Windows-style paths', async () => {
|
|
131
|
+
const wrapperPath = join(testDir, 'wrapper.ts');
|
|
132
|
+
const entryPath = 'C:\\Users\\test\\project\\src\\index.ts';
|
|
133
|
+
|
|
134
|
+
await createEntryWrapper(wrapperPath, entryPath, undefined);
|
|
135
|
+
|
|
136
|
+
const content = await readFile(wrapperPath, 'utf-8');
|
|
137
|
+
|
|
138
|
+
expect(content).toContain(entryPath);
|
|
139
|
+
});
|
|
140
|
+
});
|
package/src/dev/index.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { type ChildProcess, execSync, spawn } from 'node:child_process';
|
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
3
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { createServer } from 'node:net';
|
|
5
|
-
import { join, resolve } from 'node:path';
|
|
5
|
+
import { dirname, join, resolve } from 'node:path';
|
|
6
6
|
import chokidar from 'chokidar';
|
|
7
7
|
import { config as dotenvConfig } from 'dotenv';
|
|
8
8
|
import fg from 'fast-glob';
|
|
@@ -304,9 +304,18 @@ export interface DevOptions {
|
|
|
304
304
|
app?: string;
|
|
305
305
|
/** Filter apps by pattern (passed to turbo --filter) */
|
|
306
306
|
filter?: string;
|
|
307
|
+
/** Entry file to run (bypasses gkm config) */
|
|
308
|
+
entry?: string;
|
|
309
|
+
/** Watch for file changes (default: true with --entry) */
|
|
310
|
+
watch?: boolean;
|
|
307
311
|
}
|
|
308
312
|
|
|
309
313
|
export async function devCommand(options: DevOptions): Promise<void> {
|
|
314
|
+
// Handle --entry mode: run any file with secret injection
|
|
315
|
+
if (options.entry) {
|
|
316
|
+
return entryDevCommand(options);
|
|
317
|
+
}
|
|
318
|
+
|
|
310
319
|
// Load default .env file BEFORE loading config
|
|
311
320
|
// This ensures env vars are available when config and its dependencies are loaded
|
|
312
321
|
const defaultEnv = loadEnvFiles('.env');
|
|
@@ -1242,6 +1251,242 @@ async function buildServer(
|
|
|
1242
1251
|
]);
|
|
1243
1252
|
}
|
|
1244
1253
|
|
|
1254
|
+
/**
|
|
1255
|
+
* Find the directory containing .gkm/secrets/.
|
|
1256
|
+
* Walks up from cwd until it finds one, or returns cwd.
|
|
1257
|
+
* @internal Exported for testing
|
|
1258
|
+
*/
|
|
1259
|
+
export function findSecretsRoot(startDir: string): string {
|
|
1260
|
+
let dir = startDir;
|
|
1261
|
+
while (dir !== '/') {
|
|
1262
|
+
if (existsSync(join(dir, '.gkm', 'secrets'))) {
|
|
1263
|
+
return dir;
|
|
1264
|
+
}
|
|
1265
|
+
const parent = dirname(dir);
|
|
1266
|
+
if (parent === dir) break;
|
|
1267
|
+
dir = parent;
|
|
1268
|
+
}
|
|
1269
|
+
return startDir;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
/**
|
|
1273
|
+
* Create a wrapper script that injects secrets before importing the entry file.
|
|
1274
|
+
* @internal Exported for testing
|
|
1275
|
+
*/
|
|
1276
|
+
export async function createEntryWrapper(
|
|
1277
|
+
wrapperPath: string,
|
|
1278
|
+
entryPath: string,
|
|
1279
|
+
secretsJsonPath?: string,
|
|
1280
|
+
): Promise<void> {
|
|
1281
|
+
const credentialsInjection = secretsJsonPath
|
|
1282
|
+
? `import { Credentials } from '@geekmidas/envkit/credentials';
|
|
1283
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
1284
|
+
|
|
1285
|
+
// Inject dev secrets into Credentials (before app import)
|
|
1286
|
+
const secretsPath = '${secretsJsonPath}';
|
|
1287
|
+
if (existsSync(secretsPath)) {
|
|
1288
|
+
Object.assign(Credentials, JSON.parse(readFileSync(secretsPath, 'utf-8')));
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
`
|
|
1292
|
+
: '';
|
|
1293
|
+
|
|
1294
|
+
const content = `#!/usr/bin/env node
|
|
1295
|
+
/**
|
|
1296
|
+
* Entry wrapper generated by 'gkm dev --entry'
|
|
1297
|
+
*/
|
|
1298
|
+
${credentialsInjection}// Import and run the user's entry file
|
|
1299
|
+
import '${entryPath}';
|
|
1300
|
+
`;
|
|
1301
|
+
|
|
1302
|
+
await writeFile(wrapperPath, content);
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
/**
|
|
1306
|
+
* Run any TypeScript file with secret injection.
|
|
1307
|
+
* Does not require gkm.config.ts.
|
|
1308
|
+
*/
|
|
1309
|
+
async function entryDevCommand(options: DevOptions): Promise<void> {
|
|
1310
|
+
const { entry, port = 3000, watch = true } = options;
|
|
1311
|
+
|
|
1312
|
+
if (!entry) {
|
|
1313
|
+
throw new Error('--entry requires a file path');
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
const entryPath = resolve(process.cwd(), entry);
|
|
1317
|
+
|
|
1318
|
+
if (!existsSync(entryPath)) {
|
|
1319
|
+
throw new Error(`Entry file not found: ${entryPath}`);
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
logger.log(`š Starting entry file: ${entry}`);
|
|
1323
|
+
|
|
1324
|
+
// Load .env files
|
|
1325
|
+
const defaultEnv = loadEnvFiles('.env');
|
|
1326
|
+
if (defaultEnv.loaded.length > 0) {
|
|
1327
|
+
logger.log(`š¦ Loaded env: ${defaultEnv.loaded.join(', ')}`);
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
// Determine secrets root (current dir or workspace root)
|
|
1331
|
+
const secretsRoot = findSecretsRoot(process.cwd());
|
|
1332
|
+
|
|
1333
|
+
// Determine app name for per-app secret mapping
|
|
1334
|
+
const appName = getAppNameFromCwd() ?? undefined;
|
|
1335
|
+
|
|
1336
|
+
// Load secrets
|
|
1337
|
+
const appSecrets = await loadSecretsForApp(secretsRoot, appName);
|
|
1338
|
+
if (Object.keys(appSecrets).length > 0) {
|
|
1339
|
+
logger.log(`š Loaded ${Object.keys(appSecrets).length} secret(s)`);
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
// Write secrets to temp JSON file
|
|
1343
|
+
let secretsJsonPath: string | undefined;
|
|
1344
|
+
if (Object.keys(appSecrets).length > 0) {
|
|
1345
|
+
const secretsDir = join(secretsRoot, '.gkm');
|
|
1346
|
+
await mkdir(secretsDir, { recursive: true });
|
|
1347
|
+
secretsJsonPath = join(secretsDir, 'dev-secrets.json');
|
|
1348
|
+
await writeFile(secretsJsonPath, JSON.stringify(appSecrets, null, 2));
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
// Create wrapper entry that injects secrets before importing user's file
|
|
1352
|
+
const wrapperDir = join(process.cwd(), '.gkm');
|
|
1353
|
+
await mkdir(wrapperDir, { recursive: true });
|
|
1354
|
+
const wrapperPath = join(wrapperDir, 'entry-wrapper.ts');
|
|
1355
|
+
await createEntryWrapper(wrapperPath, entryPath, secretsJsonPath);
|
|
1356
|
+
|
|
1357
|
+
// Start with tsx
|
|
1358
|
+
const runner = new EntryRunner(wrapperPath, entryPath, watch, port);
|
|
1359
|
+
await runner.start();
|
|
1360
|
+
|
|
1361
|
+
// Handle graceful shutdown
|
|
1362
|
+
let isShuttingDown = false;
|
|
1363
|
+
const shutdown = () => {
|
|
1364
|
+
if (isShuttingDown) return;
|
|
1365
|
+
isShuttingDown = true;
|
|
1366
|
+
|
|
1367
|
+
logger.log('\nš Shutting down...');
|
|
1368
|
+
runner.stop();
|
|
1369
|
+
process.exit(0);
|
|
1370
|
+
};
|
|
1371
|
+
|
|
1372
|
+
process.on('SIGINT', shutdown);
|
|
1373
|
+
process.on('SIGTERM', shutdown);
|
|
1374
|
+
|
|
1375
|
+
// Keep the process alive
|
|
1376
|
+
await new Promise(() => {});
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
/**
|
|
1380
|
+
* Runs and watches a TypeScript entry file using tsx.
|
|
1381
|
+
*/
|
|
1382
|
+
class EntryRunner {
|
|
1383
|
+
private childProcess: ChildProcess | null = null;
|
|
1384
|
+
private watcher: ReturnType<typeof chokidar.watch> | null = null;
|
|
1385
|
+
private isRunning = false;
|
|
1386
|
+
|
|
1387
|
+
constructor(
|
|
1388
|
+
private wrapperPath: string,
|
|
1389
|
+
private entryPath: string,
|
|
1390
|
+
private watch: boolean,
|
|
1391
|
+
private port: number,
|
|
1392
|
+
) {}
|
|
1393
|
+
|
|
1394
|
+
async start(): Promise<void> {
|
|
1395
|
+
await this.runProcess();
|
|
1396
|
+
|
|
1397
|
+
if (this.watch) {
|
|
1398
|
+
// Watch the entry file's directory for changes
|
|
1399
|
+
const watchDir = dirname(this.entryPath);
|
|
1400
|
+
|
|
1401
|
+
this.watcher = chokidar.watch(watchDir, {
|
|
1402
|
+
ignored: /(^|[/\\])\../,
|
|
1403
|
+
persistent: true,
|
|
1404
|
+
ignoreInitial: true,
|
|
1405
|
+
});
|
|
1406
|
+
|
|
1407
|
+
let restartTimeout: NodeJS.Timeout | null = null;
|
|
1408
|
+
|
|
1409
|
+
this.watcher.on('change', (path) => {
|
|
1410
|
+
logger.log(`š File changed: ${path}`);
|
|
1411
|
+
|
|
1412
|
+
// Debounce restarts
|
|
1413
|
+
if (restartTimeout) {
|
|
1414
|
+
clearTimeout(restartTimeout);
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
restartTimeout = setTimeout(async () => {
|
|
1418
|
+
logger.log('š Restarting...');
|
|
1419
|
+
await this.restart();
|
|
1420
|
+
}, 300);
|
|
1421
|
+
});
|
|
1422
|
+
|
|
1423
|
+
logger.log(`š Watching for changes in: ${watchDir}`);
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
private async runProcess(): Promise<void> {
|
|
1428
|
+
// Pass PORT as environment variable
|
|
1429
|
+
const env = { ...process.env, PORT: String(this.port) };
|
|
1430
|
+
|
|
1431
|
+
this.childProcess = spawn('npx', ['tsx', this.wrapperPath], {
|
|
1432
|
+
stdio: 'inherit',
|
|
1433
|
+
env,
|
|
1434
|
+
detached: true,
|
|
1435
|
+
});
|
|
1436
|
+
|
|
1437
|
+
this.isRunning = true;
|
|
1438
|
+
|
|
1439
|
+
this.childProcess.on('error', (error) => {
|
|
1440
|
+
logger.error('ā Process error:', error);
|
|
1441
|
+
});
|
|
1442
|
+
|
|
1443
|
+
this.childProcess.on('exit', (code) => {
|
|
1444
|
+
if (code !== null && code !== 0 && code !== 143) {
|
|
1445
|
+
// 143 = SIGTERM
|
|
1446
|
+
logger.error(`ā Process exited with code ${code}`);
|
|
1447
|
+
}
|
|
1448
|
+
this.isRunning = false;
|
|
1449
|
+
});
|
|
1450
|
+
|
|
1451
|
+
// Give the process a moment to start
|
|
1452
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1453
|
+
|
|
1454
|
+
if (this.isRunning) {
|
|
1455
|
+
logger.log(`\nš Running at http://localhost:${this.port}`);
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
async restart(): Promise<void> {
|
|
1460
|
+
this.stopProcess();
|
|
1461
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1462
|
+
await this.runProcess();
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
stop(): void {
|
|
1466
|
+
this.watcher?.close();
|
|
1467
|
+
this.stopProcess();
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
private stopProcess(): void {
|
|
1471
|
+
if (this.childProcess && this.isRunning) {
|
|
1472
|
+
const pid = this.childProcess.pid;
|
|
1473
|
+
if (pid) {
|
|
1474
|
+
try {
|
|
1475
|
+
process.kill(-pid, 'SIGTERM');
|
|
1476
|
+
} catch {
|
|
1477
|
+
try {
|
|
1478
|
+
process.kill(pid, 'SIGTERM');
|
|
1479
|
+
} catch {
|
|
1480
|
+
// Process already dead
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
this.childProcess = null;
|
|
1485
|
+
this.isRunning = false;
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1245
1490
|
class DevServer {
|
|
1246
1491
|
private serverProcess: ChildProcess | null = null;
|
|
1247
1492
|
private isRunning = false;
|
package/src/index.ts
CHANGED
|
@@ -136,28 +136,42 @@ program
|
|
|
136
136
|
.command('dev')
|
|
137
137
|
.description('Start development server with automatic reload')
|
|
138
138
|
.option('-p, --port <port>', 'Port to run the development server on')
|
|
139
|
+
.option('--entry <file>', 'Entry file to run (bypasses gkm config)')
|
|
140
|
+
.option('--watch', 'Watch for file changes (default: true with --entry)')
|
|
141
|
+
.option('--no-watch', 'Disable file watching')
|
|
139
142
|
.option(
|
|
140
143
|
'--enable-openapi',
|
|
141
144
|
'Enable OpenAPI documentation for development server',
|
|
142
145
|
true,
|
|
143
146
|
)
|
|
144
|
-
.action(
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
147
|
+
.action(
|
|
148
|
+
async (options: {
|
|
149
|
+
port?: string;
|
|
150
|
+
entry?: string;
|
|
151
|
+
watch?: boolean;
|
|
152
|
+
enableOpenapi?: boolean;
|
|
153
|
+
}) => {
|
|
154
|
+
try {
|
|
155
|
+
const globalOptions = program.opts();
|
|
156
|
+
if (globalOptions.cwd) {
|
|
157
|
+
process.chdir(globalOptions.cwd);
|
|
158
|
+
}
|
|
150
159
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
160
|
+
await devCommand({
|
|
161
|
+
port: options.port ? Number.parseInt(options.port, 10) : 3000,
|
|
162
|
+
portExplicit: !!options.port,
|
|
163
|
+
enableOpenApi: options.enableOpenapi ?? true,
|
|
164
|
+
entry: options.entry,
|
|
165
|
+
watch: options.watch,
|
|
166
|
+
});
|
|
167
|
+
} catch (error) {
|
|
168
|
+
console.error(
|
|
169
|
+
error instanceof Error ? error.message : 'Command failed',
|
|
170
|
+
);
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
);
|
|
161
175
|
|
|
162
176
|
program
|
|
163
177
|
.command('test')
|
package/src/init/versions.ts
CHANGED
|
@@ -35,7 +35,7 @@ export const GEEKMIDAS_VERSIONS = {
|
|
|
35
35
|
'@geekmidas/constructs': '~0.6.0',
|
|
36
36
|
'@geekmidas/db': '~0.3.0',
|
|
37
37
|
'@geekmidas/emailkit': '~0.2.0',
|
|
38
|
-
'@geekmidas/envkit': '~0.
|
|
38
|
+
'@geekmidas/envkit': '~0.5.0',
|
|
39
39
|
'@geekmidas/errors': '~0.1.0',
|
|
40
40
|
'@geekmidas/events': '~0.2.0',
|
|
41
41
|
'@geekmidas/logger': '~0.4.0',
|