@geekmidas/cli 0.36.0 → 0.38.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/dist/index.cjs +85 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +85 -29
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/dev/__tests__/entry-integration.spec.ts +346 -0
- package/src/dev/index.ts +80 -37
- package/src/init/generators/monorepo.ts +58 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geekmidas/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.38.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/schema": "~0.1.0",
|
|
51
52
|
"@geekmidas/constructs": "~0.6.0",
|
|
52
53
|
"@geekmidas/envkit": "~0.5.0",
|
|
53
54
|
"@geekmidas/errors": "~0.1.0",
|
|
54
|
-
"@geekmidas/logger": "~0.4.0"
|
|
55
|
-
"@geekmidas/schema": "~0.1.0"
|
|
55
|
+
"@geekmidas/logger": "~0.4.0"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@types/lodash.kebabcase": "^4.1.9",
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import { prepareEntryCredentials } from '../index';
|
|
6
|
+
|
|
7
|
+
describe('prepareEntryCredentials', () => {
|
|
8
|
+
let workspaceDir: string;
|
|
9
|
+
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
workspaceDir = join(tmpdir(), `gkm-entry-test-${Date.now()}`);
|
|
12
|
+
await mkdir(workspaceDir, { recursive: true });
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
await rm(workspaceDir, { recursive: true, force: true });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('workspace with port config', () => {
|
|
20
|
+
beforeEach(async () => {
|
|
21
|
+
// Create workspace structure:
|
|
22
|
+
// workspace/
|
|
23
|
+
// gkm.config.ts
|
|
24
|
+
// apps/
|
|
25
|
+
// api/
|
|
26
|
+
// package.json
|
|
27
|
+
// auth/
|
|
28
|
+
// package.json
|
|
29
|
+
|
|
30
|
+
// Create gkm.config.ts
|
|
31
|
+
const gkmConfig = `
|
|
32
|
+
import { defineWorkspace } from '@geekmidas/cli/config';
|
|
33
|
+
|
|
34
|
+
export default defineWorkspace({
|
|
35
|
+
name: 'test-workspace',
|
|
36
|
+
apps: {
|
|
37
|
+
api: {
|
|
38
|
+
type: 'backend',
|
|
39
|
+
path: 'apps/api',
|
|
40
|
+
port: 3000,
|
|
41
|
+
routes: './src/endpoints/**/*.ts',
|
|
42
|
+
envParser: './src/config/env#envParser',
|
|
43
|
+
logger: './src/config/logger#logger',
|
|
44
|
+
},
|
|
45
|
+
auth: {
|
|
46
|
+
type: 'backend',
|
|
47
|
+
path: 'apps/auth',
|
|
48
|
+
port: 3002,
|
|
49
|
+
envParser: './src/config/env#envParser',
|
|
50
|
+
logger: './src/config/logger#logger',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
services: {},
|
|
54
|
+
});
|
|
55
|
+
`;
|
|
56
|
+
await writeFile(join(workspaceDir, 'gkm.config.ts'), gkmConfig);
|
|
57
|
+
|
|
58
|
+
// Create apps directories
|
|
59
|
+
await mkdir(join(workspaceDir, 'apps', 'api', 'src'), {
|
|
60
|
+
recursive: true,
|
|
61
|
+
});
|
|
62
|
+
await mkdir(join(workspaceDir, 'apps', 'auth', 'src'), {
|
|
63
|
+
recursive: true,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Create package.json for api app
|
|
67
|
+
await writeFile(
|
|
68
|
+
join(workspaceDir, 'apps', 'api', 'package.json'),
|
|
69
|
+
JSON.stringify({ name: '@test/api', version: '0.0.1' }, null, 2),
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
// Create package.json for auth app
|
|
73
|
+
await writeFile(
|
|
74
|
+
join(workspaceDir, 'apps', 'auth', 'package.json'),
|
|
75
|
+
JSON.stringify({ name: '@test/auth', version: '0.0.1' }, null, 2),
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Create entry files
|
|
79
|
+
await writeFile(
|
|
80
|
+
join(workspaceDir, 'apps', 'api', 'src', 'index.ts'),
|
|
81
|
+
'console.log("api");',
|
|
82
|
+
);
|
|
83
|
+
await writeFile(
|
|
84
|
+
join(workspaceDir, 'apps', 'auth', 'src', 'index.ts'),
|
|
85
|
+
'console.log("auth");',
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should inject PORT from workspace config for api app (port 3000)', async () => {
|
|
90
|
+
const apiDir = join(workspaceDir, 'apps', 'api');
|
|
91
|
+
|
|
92
|
+
const result = await prepareEntryCredentials({ cwd: apiDir });
|
|
93
|
+
|
|
94
|
+
expect(result.resolvedPort).toBe(3000);
|
|
95
|
+
expect(result.credentials.PORT).toBe('3000');
|
|
96
|
+
expect(result.appName).toBe('api');
|
|
97
|
+
expect(result.secretsRoot).toBe(workspaceDir);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should inject PORT from workspace config for auth app (port 3002)', async () => {
|
|
101
|
+
const authDir = join(workspaceDir, 'apps', 'auth');
|
|
102
|
+
|
|
103
|
+
const result = await prepareEntryCredentials({ cwd: authDir });
|
|
104
|
+
|
|
105
|
+
expect(result.resolvedPort).toBe(3002);
|
|
106
|
+
expect(result.credentials.PORT).toBe('3002');
|
|
107
|
+
expect(result.appName).toBe('auth');
|
|
108
|
+
expect(result.secretsRoot).toBe(workspaceDir);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should use explicit --port over workspace config', async () => {
|
|
112
|
+
const authDir = join(workspaceDir, 'apps', 'auth');
|
|
113
|
+
|
|
114
|
+
const result = await prepareEntryCredentials({
|
|
115
|
+
cwd: authDir,
|
|
116
|
+
explicitPort: 4000,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
expect(result.resolvedPort).toBe(4000);
|
|
120
|
+
expect(result.credentials.PORT).toBe('4000');
|
|
121
|
+
expect(result.appName).toBe('auth');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should write credentials to dev-secrets.json at workspace root', async () => {
|
|
125
|
+
const apiDir = join(workspaceDir, 'apps', 'api');
|
|
126
|
+
|
|
127
|
+
const result = await prepareEntryCredentials({ cwd: apiDir });
|
|
128
|
+
|
|
129
|
+
// Verify the file was written at workspace root
|
|
130
|
+
expect(result.secretsJsonPath).toBe(
|
|
131
|
+
join(workspaceDir, '.gkm', 'dev-secrets.json'),
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
// Verify file contents
|
|
135
|
+
const content = await readFile(result.secretsJsonPath, 'utf-8');
|
|
136
|
+
const parsed = JSON.parse(content);
|
|
137
|
+
|
|
138
|
+
expect(parsed.PORT).toBe('3000');
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe('without workspace config', () => {
|
|
143
|
+
beforeEach(async () => {
|
|
144
|
+
// Create a simple directory without workspace config
|
|
145
|
+
await mkdir(join(workspaceDir, 'src'), { recursive: true });
|
|
146
|
+
await writeFile(
|
|
147
|
+
join(workspaceDir, 'package.json'),
|
|
148
|
+
JSON.stringify({ name: 'standalone-app', version: '0.0.1' }, null, 2),
|
|
149
|
+
);
|
|
150
|
+
await writeFile(
|
|
151
|
+
join(workspaceDir, 'src', 'index.ts'),
|
|
152
|
+
'console.log("standalone");',
|
|
153
|
+
);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('should fallback to port 3000 when no workspace config exists', async () => {
|
|
157
|
+
const result = await prepareEntryCredentials({ cwd: workspaceDir });
|
|
158
|
+
|
|
159
|
+
expect(result.resolvedPort).toBe(3000);
|
|
160
|
+
expect(result.credentials.PORT).toBe('3000');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should use explicit --port when no workspace config exists', async () => {
|
|
164
|
+
const result = await prepareEntryCredentials({
|
|
165
|
+
cwd: workspaceDir,
|
|
166
|
+
explicitPort: 5000,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
expect(result.resolvedPort).toBe(5000);
|
|
170
|
+
expect(result.credentials.PORT).toBe('5000');
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should write credentials to current directory when not in workspace', async () => {
|
|
174
|
+
const result = await prepareEntryCredentials({ cwd: workspaceDir });
|
|
175
|
+
|
|
176
|
+
expect(result.secretsJsonPath).toBe(
|
|
177
|
+
join(workspaceDir, '.gkm', 'dev-secrets.json'),
|
|
178
|
+
);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe('with GKM_CONFIG_PATH set (turbo environment)', () => {
|
|
183
|
+
const originalEnv = process.env.GKM_CONFIG_PATH;
|
|
184
|
+
|
|
185
|
+
beforeEach(async () => {
|
|
186
|
+
// Create workspace structure
|
|
187
|
+
const gkmConfig = `
|
|
188
|
+
import { defineWorkspace } from '@geekmidas/cli/config';
|
|
189
|
+
|
|
190
|
+
export default defineWorkspace({
|
|
191
|
+
name: 'test-workspace',
|
|
192
|
+
apps: {
|
|
193
|
+
api: {
|
|
194
|
+
type: 'backend',
|
|
195
|
+
path: 'apps/api',
|
|
196
|
+
port: 3000,
|
|
197
|
+
routes: './src/endpoints/**/*.ts',
|
|
198
|
+
envParser: './src/config/env#envParser',
|
|
199
|
+
logger: './src/config/logger#logger',
|
|
200
|
+
},
|
|
201
|
+
auth: {
|
|
202
|
+
type: 'backend',
|
|
203
|
+
path: 'apps/auth',
|
|
204
|
+
port: 3002,
|
|
205
|
+
envParser: './src/config/env#envParser',
|
|
206
|
+
logger: './src/config/logger#logger',
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
services: {},
|
|
210
|
+
});
|
|
211
|
+
`;
|
|
212
|
+
await writeFile(join(workspaceDir, 'gkm.config.ts'), gkmConfig);
|
|
213
|
+
|
|
214
|
+
// Create apps directories
|
|
215
|
+
await mkdir(join(workspaceDir, 'apps', 'api', 'src'), {
|
|
216
|
+
recursive: true,
|
|
217
|
+
});
|
|
218
|
+
await mkdir(join(workspaceDir, 'apps', 'auth', 'src'), {
|
|
219
|
+
recursive: true,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// Create package.json files
|
|
223
|
+
await writeFile(
|
|
224
|
+
join(workspaceDir, 'apps', 'api', 'package.json'),
|
|
225
|
+
JSON.stringify({ name: '@test/api', version: '0.0.1' }, null, 2),
|
|
226
|
+
);
|
|
227
|
+
await writeFile(
|
|
228
|
+
join(workspaceDir, 'apps', 'auth', 'package.json'),
|
|
229
|
+
JSON.stringify({ name: '@test/auth', version: '0.0.1' }, null, 2),
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
// Set GKM_CONFIG_PATH like turbo does
|
|
233
|
+
process.env.GKM_CONFIG_PATH = join(workspaceDir, 'gkm.config.ts');
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
afterEach(() => {
|
|
237
|
+
// Restore original env
|
|
238
|
+
if (originalEnv === undefined) {
|
|
239
|
+
delete process.env.GKM_CONFIG_PATH;
|
|
240
|
+
} else {
|
|
241
|
+
process.env.GKM_CONFIG_PATH = originalEnv;
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('should read port from workspace config when GKM_CONFIG_PATH is set', async () => {
|
|
246
|
+
const authDir = join(workspaceDir, 'apps', 'auth');
|
|
247
|
+
|
|
248
|
+
const result = await prepareEntryCredentials({ cwd: authDir });
|
|
249
|
+
|
|
250
|
+
expect(result.resolvedPort).toBe(3002);
|
|
251
|
+
expect(result.credentials.PORT).toBe('3002');
|
|
252
|
+
expect(result.appName).toBe('auth');
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('should read port 3000 for api when GKM_CONFIG_PATH is set', async () => {
|
|
256
|
+
const apiDir = join(workspaceDir, 'apps', 'api');
|
|
257
|
+
|
|
258
|
+
const result = await prepareEntryCredentials({ cwd: apiDir });
|
|
259
|
+
|
|
260
|
+
expect(result.resolvedPort).toBe(3000);
|
|
261
|
+
expect(result.credentials.PORT).toBe('3000');
|
|
262
|
+
expect(result.appName).toBe('api');
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
describe('with secrets', () => {
|
|
267
|
+
beforeEach(async () => {
|
|
268
|
+
// Create workspace with secrets
|
|
269
|
+
const gkmConfig = `
|
|
270
|
+
import { defineWorkspace } from '@geekmidas/cli/config';
|
|
271
|
+
|
|
272
|
+
export default defineWorkspace({
|
|
273
|
+
name: 'test-workspace',
|
|
274
|
+
apps: {
|
|
275
|
+
api: {
|
|
276
|
+
type: 'backend',
|
|
277
|
+
path: 'apps/api',
|
|
278
|
+
port: 3000,
|
|
279
|
+
routes: './src/endpoints/**/*.ts',
|
|
280
|
+
envParser: './src/config/env#envParser',
|
|
281
|
+
logger: './src/config/logger#logger',
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
services: {},
|
|
285
|
+
});
|
|
286
|
+
`;
|
|
287
|
+
await writeFile(join(workspaceDir, 'gkm.config.ts'), gkmConfig);
|
|
288
|
+
|
|
289
|
+
await mkdir(join(workspaceDir, 'apps', 'api', 'src'), {
|
|
290
|
+
recursive: true,
|
|
291
|
+
});
|
|
292
|
+
await writeFile(
|
|
293
|
+
join(workspaceDir, 'apps', 'api', 'package.json'),
|
|
294
|
+
JSON.stringify({ name: '@test/api', version: '0.0.1' }, null, 2),
|
|
295
|
+
);
|
|
296
|
+
await writeFile(
|
|
297
|
+
join(workspaceDir, 'apps', 'api', 'src', 'index.ts'),
|
|
298
|
+
'console.log("api");',
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
// Create unencrypted secrets (legacy format for testing)
|
|
302
|
+
await mkdir(join(workspaceDir, '.gkm', 'secrets'), { recursive: true });
|
|
303
|
+
const secrets = {
|
|
304
|
+
stage: 'development',
|
|
305
|
+
createdAt: new Date().toISOString(),
|
|
306
|
+
updatedAt: new Date().toISOString(),
|
|
307
|
+
services: {},
|
|
308
|
+
urls: {
|
|
309
|
+
DATABASE_URL: 'postgresql://localhost:5432/test',
|
|
310
|
+
},
|
|
311
|
+
custom: {
|
|
312
|
+
API_KEY: 'test-api-key',
|
|
313
|
+
},
|
|
314
|
+
};
|
|
315
|
+
await writeFile(
|
|
316
|
+
join(workspaceDir, '.gkm', 'secrets', 'development.json'),
|
|
317
|
+
JSON.stringify(secrets, null, 2),
|
|
318
|
+
);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('should load secrets and inject PORT', async () => {
|
|
322
|
+
const apiDir = join(workspaceDir, 'apps', 'api');
|
|
323
|
+
|
|
324
|
+
const result = await prepareEntryCredentials({ cwd: apiDir });
|
|
325
|
+
|
|
326
|
+
expect(result.credentials.PORT).toBe('3000');
|
|
327
|
+
expect(result.credentials.DATABASE_URL).toBe(
|
|
328
|
+
'postgresql://localhost:5432/test',
|
|
329
|
+
);
|
|
330
|
+
expect(result.credentials.API_KEY).toBe('test-api-key');
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('should write both secrets and PORT to dev-secrets.json', async () => {
|
|
334
|
+
const apiDir = join(workspaceDir, 'apps', 'api');
|
|
335
|
+
|
|
336
|
+
const result = await prepareEntryCredentials({ cwd: apiDir });
|
|
337
|
+
|
|
338
|
+
const content = await readFile(result.secretsJsonPath, 'utf-8');
|
|
339
|
+
const parsed = JSON.parse(content);
|
|
340
|
+
|
|
341
|
+
expect(parsed.PORT).toBe('3000');
|
|
342
|
+
expect(parsed.DATABASE_URL).toBe('postgresql://localhost:5432/test');
|
|
343
|
+
expect(parsed.API_KEY).toBe('test-api-key');
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
});
|
package/src/dev/index.ts
CHANGED
|
@@ -1307,6 +1307,74 @@ await import('${entryPath}');
|
|
|
1307
1307
|
await writeFile(wrapperPath, content);
|
|
1308
1308
|
}
|
|
1309
1309
|
|
|
1310
|
+
/**
|
|
1311
|
+
* Result of preparing entry credentials for dev mode.
|
|
1312
|
+
*/
|
|
1313
|
+
export interface EntryCredentialsResult {
|
|
1314
|
+
/** Credentials to inject (secrets + PORT) */
|
|
1315
|
+
credentials: Record<string, string>;
|
|
1316
|
+
/** Resolved port (from --port, workspace config, or default 3000) */
|
|
1317
|
+
resolvedPort: number;
|
|
1318
|
+
/** Path where credentials JSON was written */
|
|
1319
|
+
secretsJsonPath: string;
|
|
1320
|
+
/** Resolved app name (if in workspace) */
|
|
1321
|
+
appName: string | undefined;
|
|
1322
|
+
/** Secrets root directory */
|
|
1323
|
+
secretsRoot: string;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
/**
|
|
1327
|
+
* Prepare credentials for entry dev mode.
|
|
1328
|
+
* Loads workspace config, secrets, and injects PORT.
|
|
1329
|
+
* @internal Exported for testing
|
|
1330
|
+
*/
|
|
1331
|
+
export async function prepareEntryCredentials(options: {
|
|
1332
|
+
explicitPort?: number;
|
|
1333
|
+
cwd?: string;
|
|
1334
|
+
}): Promise<EntryCredentialsResult> {
|
|
1335
|
+
const cwd = options.cwd ?? process.cwd();
|
|
1336
|
+
|
|
1337
|
+
// Try to get workspace app config for port and secrets
|
|
1338
|
+
let workspaceAppPort: number | undefined;
|
|
1339
|
+
let secretsRoot: string = cwd;
|
|
1340
|
+
let appName: string | undefined;
|
|
1341
|
+
|
|
1342
|
+
try {
|
|
1343
|
+
const appConfig = await loadAppConfig(cwd);
|
|
1344
|
+
workspaceAppPort = appConfig.app.port;
|
|
1345
|
+
secretsRoot = appConfig.workspaceRoot;
|
|
1346
|
+
appName = appConfig.appName;
|
|
1347
|
+
} catch (error) {
|
|
1348
|
+
// Not in a workspace - use defaults
|
|
1349
|
+
logger.log(`⚠️ Could not load workspace config: ${(error as Error).message}`);
|
|
1350
|
+
secretsRoot = findSecretsRoot(cwd);
|
|
1351
|
+
appName = getAppNameFromCwd(cwd) ?? undefined;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
// Determine port: explicit --port > workspace config > default 3000
|
|
1355
|
+
const resolvedPort = options.explicitPort ?? workspaceAppPort ?? 3000;
|
|
1356
|
+
|
|
1357
|
+
// Load secrets and inject PORT
|
|
1358
|
+
const credentials = await loadSecretsForApp(secretsRoot, appName);
|
|
1359
|
+
|
|
1360
|
+
// Always inject PORT into credentials so apps can read it
|
|
1361
|
+
credentials.PORT = String(resolvedPort);
|
|
1362
|
+
|
|
1363
|
+
// Write secrets to temp JSON file (always write since we have PORT)
|
|
1364
|
+
const secretsDir = join(secretsRoot, '.gkm');
|
|
1365
|
+
await mkdir(secretsDir, { recursive: true });
|
|
1366
|
+
const secretsJsonPath = join(secretsDir, 'dev-secrets.json');
|
|
1367
|
+
await writeFile(secretsJsonPath, JSON.stringify(credentials, null, 2));
|
|
1368
|
+
|
|
1369
|
+
return {
|
|
1370
|
+
credentials,
|
|
1371
|
+
resolvedPort,
|
|
1372
|
+
secretsJsonPath,
|
|
1373
|
+
appName,
|
|
1374
|
+
secretsRoot,
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1310
1378
|
/**
|
|
1311
1379
|
* Run any TypeScript file with secret injection.
|
|
1312
1380
|
* Does not require gkm.config.ts.
|
|
@@ -1330,46 +1398,21 @@ async function entryDevCommand(options: DevOptions): Promise<void> {
|
|
|
1330
1398
|
logger.log(`📦 Loaded env: ${defaultEnv.loaded.join(', ')}`);
|
|
1331
1399
|
}
|
|
1332
1400
|
|
|
1333
|
-
//
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1401
|
+
// Prepare credentials (loads workspace config, secrets, injects PORT)
|
|
1402
|
+
// Only pass explicitPort if --port was actually specified by the user
|
|
1403
|
+
const { credentials, resolvedPort, secretsJsonPath, appName } =
|
|
1404
|
+
await prepareEntryCredentials({
|
|
1405
|
+
explicitPort: options.portExplicit ? options.port : undefined,
|
|
1406
|
+
});
|
|
1337
1407
|
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
workspaceAppPort = appConfig.app.port;
|
|
1341
|
-
secretsRoot = appConfig.workspaceRoot;
|
|
1342
|
-
appName = appConfig.appName;
|
|
1343
|
-
logger.log(`📦 App: ${appName} (port ${workspaceAppPort})`);
|
|
1344
|
-
} catch {
|
|
1345
|
-
// Not in a workspace - use defaults
|
|
1346
|
-
secretsRoot = findSecretsRoot(process.cwd());
|
|
1347
|
-
appName = getAppNameFromCwd() ?? undefined;
|
|
1348
|
-
if (appName) {
|
|
1349
|
-
logger.log(`📦 App name: ${appName}`);
|
|
1350
|
-
}
|
|
1408
|
+
if (appName) {
|
|
1409
|
+
logger.log(`📦 App: ${appName} (port ${resolvedPort})`);
|
|
1351
1410
|
}
|
|
1352
1411
|
|
|
1353
|
-
|
|
1354
|
-
const port = options.port ?? workspaceAppPort ?? 3000;
|
|
1355
|
-
|
|
1356
|
-
logger.log(`🚀 Starting entry file: ${entry} on port ${port}`);
|
|
1357
|
-
|
|
1358
|
-
// Load secrets
|
|
1359
|
-
const appSecrets = await loadSecretsForApp(secretsRoot, appName);
|
|
1360
|
-
if (Object.keys(appSecrets).length > 0) {
|
|
1361
|
-
logger.log(`🔐 Loaded ${Object.keys(appSecrets).length} secret(s)`);
|
|
1362
|
-
} else {
|
|
1363
|
-
logger.log(`⚠️ No secrets found in ${secretsRoot}/.gkm/secrets/`);
|
|
1364
|
-
}
|
|
1412
|
+
logger.log(`🚀 Starting entry file: ${entry} on port ${resolvedPort}`);
|
|
1365
1413
|
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
if (Object.keys(appSecrets).length > 0) {
|
|
1369
|
-
const secretsDir = join(secretsRoot, '.gkm');
|
|
1370
|
-
await mkdir(secretsDir, { recursive: true });
|
|
1371
|
-
secretsJsonPath = join(secretsDir, 'dev-secrets.json');
|
|
1372
|
-
await writeFile(secretsJsonPath, JSON.stringify(appSecrets, null, 2));
|
|
1414
|
+
if (Object.keys(credentials).length > 1) {
|
|
1415
|
+
logger.log(`🔐 Loaded ${Object.keys(credentials).length - 1} secret(s) + PORT`);
|
|
1373
1416
|
}
|
|
1374
1417
|
|
|
1375
1418
|
// Create wrapper entry that injects secrets before importing user's file
|
|
@@ -1379,7 +1422,7 @@ async function entryDevCommand(options: DevOptions): Promise<void> {
|
|
|
1379
1422
|
await createEntryWrapper(wrapperPath, entryPath, secretsJsonPath);
|
|
1380
1423
|
|
|
1381
1424
|
// Start with tsx
|
|
1382
|
-
const runner = new EntryRunner(wrapperPath, entryPath, watch,
|
|
1425
|
+
const runner = new EntryRunner(wrapperPath, entryPath, watch, resolvedPort);
|
|
1383
1426
|
await runner.start();
|
|
1384
1427
|
|
|
1385
1428
|
// Handle graceful shutdown
|
|
@@ -148,7 +148,6 @@ docker/.env
|
|
|
148
148
|
|
|
149
149
|
# IDE
|
|
150
150
|
.idea/
|
|
151
|
-
.vscode/
|
|
152
151
|
*.swp
|
|
153
152
|
*.swo
|
|
154
153
|
|
|
@@ -207,6 +206,56 @@ export default defineConfig({
|
|
|
207
206
|
});
|
|
208
207
|
`;
|
|
209
208
|
|
|
209
|
+
// VSCode settings for consistent development experience
|
|
210
|
+
const vscodeSettings = {
|
|
211
|
+
'search.exclude': {
|
|
212
|
+
'**/.sst': true,
|
|
213
|
+
'**/.gkm': true,
|
|
214
|
+
'**/.turbo': true,
|
|
215
|
+
},
|
|
216
|
+
'editor.formatOnSave': true,
|
|
217
|
+
'editor.defaultFormatter': 'biomejs.biome',
|
|
218
|
+
'editor.codeActionsOnSave': {
|
|
219
|
+
'source.fixAll.biome': 'always',
|
|
220
|
+
'source.organizeImports.biome': 'always',
|
|
221
|
+
'source.organizeImports': 'always',
|
|
222
|
+
},
|
|
223
|
+
'[typescriptreact]': {
|
|
224
|
+
'editor.defaultFormatter': 'biomejs.biome',
|
|
225
|
+
},
|
|
226
|
+
'[typescript]': {
|
|
227
|
+
'editor.defaultFormatter': 'biomejs.biome',
|
|
228
|
+
},
|
|
229
|
+
'[javascript]': {
|
|
230
|
+
'editor.defaultFormatter': 'biomejs.biome',
|
|
231
|
+
},
|
|
232
|
+
'[json]': {
|
|
233
|
+
'editor.defaultFormatter': 'biomejs.biome',
|
|
234
|
+
},
|
|
235
|
+
'cSpell.words': [
|
|
236
|
+
'betterauth',
|
|
237
|
+
'dokploy',
|
|
238
|
+
'envkit',
|
|
239
|
+
'geekmidas',
|
|
240
|
+
'healthcheck',
|
|
241
|
+
'kysely',
|
|
242
|
+
'testkit',
|
|
243
|
+
'timestamptz',
|
|
244
|
+
'turborepo',
|
|
245
|
+
options.name,
|
|
246
|
+
],
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// VSCode extensions recommendations
|
|
250
|
+
const vscodeExtensions = {
|
|
251
|
+
recommendations: [
|
|
252
|
+
'biomejs.biome',
|
|
253
|
+
'streetsidesoftware.code-spell-checker',
|
|
254
|
+
'dbaeumer.vscode-eslint',
|
|
255
|
+
'ms-azuretools.vscode-docker',
|
|
256
|
+
],
|
|
257
|
+
};
|
|
258
|
+
|
|
210
259
|
const files: GeneratedFile[] = [
|
|
211
260
|
{
|
|
212
261
|
path: 'package.json',
|
|
@@ -236,6 +285,14 @@ export default defineConfig({
|
|
|
236
285
|
path: '.gitignore',
|
|
237
286
|
content: gitignore,
|
|
238
287
|
},
|
|
288
|
+
{
|
|
289
|
+
path: '.vscode/settings.json',
|
|
290
|
+
content: `${JSON.stringify(vscodeSettings, null, '\t')}\n`,
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
path: '.vscode/extensions.json',
|
|
294
|
+
content: `${JSON.stringify(vscodeExtensions, null, '\t')}\n`,
|
|
295
|
+
},
|
|
239
296
|
];
|
|
240
297
|
|
|
241
298
|
// Add workspace config for fullstack template
|