@geekmidas/cli 0.36.0 → 0.37.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 +84 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +84 -29
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/dev/__tests__/entry-integration.spec.ts +262 -0
- package/src/dev/index.ts +76 -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.37.0",
|
|
4
4
|
"description": "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -49,10 +49,10 @@
|
|
|
49
49
|
"openapi-typescript": "^7.4.2",
|
|
50
50
|
"prompts": "~2.4.2",
|
|
51
51
|
"@geekmidas/constructs": "~0.6.0",
|
|
52
|
-
"@geekmidas/envkit": "~0.5.0",
|
|
53
52
|
"@geekmidas/errors": "~0.1.0",
|
|
53
|
+
"@geekmidas/schema": "~0.1.0",
|
|
54
54
|
"@geekmidas/logger": "~0.4.0",
|
|
55
|
-
"@geekmidas/
|
|
55
|
+
"@geekmidas/envkit": "~0.5.0"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@types/lodash.kebabcase": "^4.1.9",
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } 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 secrets', () => {
|
|
183
|
+
beforeEach(async () => {
|
|
184
|
+
// Create workspace with secrets
|
|
185
|
+
const gkmConfig = `
|
|
186
|
+
import { defineWorkspace } from '@geekmidas/cli/config';
|
|
187
|
+
|
|
188
|
+
export default defineWorkspace({
|
|
189
|
+
name: 'test-workspace',
|
|
190
|
+
apps: {
|
|
191
|
+
api: {
|
|
192
|
+
type: 'backend',
|
|
193
|
+
path: 'apps/api',
|
|
194
|
+
port: 3000,
|
|
195
|
+
routes: './src/endpoints/**/*.ts',
|
|
196
|
+
envParser: './src/config/env#envParser',
|
|
197
|
+
logger: './src/config/logger#logger',
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
services: {},
|
|
201
|
+
});
|
|
202
|
+
`;
|
|
203
|
+
await writeFile(join(workspaceDir, 'gkm.config.ts'), gkmConfig);
|
|
204
|
+
|
|
205
|
+
await mkdir(join(workspaceDir, 'apps', 'api', 'src'), {
|
|
206
|
+
recursive: true,
|
|
207
|
+
});
|
|
208
|
+
await writeFile(
|
|
209
|
+
join(workspaceDir, 'apps', 'api', 'package.json'),
|
|
210
|
+
JSON.stringify({ name: '@test/api', version: '0.0.1' }, null, 2),
|
|
211
|
+
);
|
|
212
|
+
await writeFile(
|
|
213
|
+
join(workspaceDir, 'apps', 'api', 'src', 'index.ts'),
|
|
214
|
+
'console.log("api");',
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
// Create unencrypted secrets (legacy format for testing)
|
|
218
|
+
await mkdir(join(workspaceDir, '.gkm', 'secrets'), { recursive: true });
|
|
219
|
+
const secrets = {
|
|
220
|
+
stage: 'development',
|
|
221
|
+
createdAt: new Date().toISOString(),
|
|
222
|
+
updatedAt: new Date().toISOString(),
|
|
223
|
+
services: {},
|
|
224
|
+
urls: {
|
|
225
|
+
DATABASE_URL: 'postgresql://localhost:5432/test',
|
|
226
|
+
},
|
|
227
|
+
custom: {
|
|
228
|
+
API_KEY: 'test-api-key',
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
await writeFile(
|
|
232
|
+
join(workspaceDir, '.gkm', 'secrets', 'development.json'),
|
|
233
|
+
JSON.stringify(secrets, null, 2),
|
|
234
|
+
);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('should load secrets and inject PORT', async () => {
|
|
238
|
+
const apiDir = join(workspaceDir, 'apps', 'api');
|
|
239
|
+
|
|
240
|
+
const result = await prepareEntryCredentials({ cwd: apiDir });
|
|
241
|
+
|
|
242
|
+
expect(result.credentials.PORT).toBe('3000');
|
|
243
|
+
expect(result.credentials.DATABASE_URL).toBe(
|
|
244
|
+
'postgresql://localhost:5432/test',
|
|
245
|
+
);
|
|
246
|
+
expect(result.credentials.API_KEY).toBe('test-api-key');
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('should write both secrets and PORT to dev-secrets.json', async () => {
|
|
250
|
+
const apiDir = join(workspaceDir, 'apps', 'api');
|
|
251
|
+
|
|
252
|
+
const result = await prepareEntryCredentials({ cwd: apiDir });
|
|
253
|
+
|
|
254
|
+
const content = await readFile(result.secretsJsonPath, 'utf-8');
|
|
255
|
+
const parsed = JSON.parse(content);
|
|
256
|
+
|
|
257
|
+
expect(parsed.PORT).toBe('3000');
|
|
258
|
+
expect(parsed.DATABASE_URL).toBe('postgresql://localhost:5432/test');
|
|
259
|
+
expect(parsed.API_KEY).toBe('test-api-key');
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
});
|
package/src/dev/index.ts
CHANGED
|
@@ -1307,6 +1307,73 @@ 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 {
|
|
1348
|
+
// Not in a workspace - use defaults
|
|
1349
|
+
secretsRoot = findSecretsRoot(cwd);
|
|
1350
|
+
appName = getAppNameFromCwd(cwd) ?? undefined;
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
// Determine port: explicit --port > workspace config > default 3000
|
|
1354
|
+
const resolvedPort = options.explicitPort ?? workspaceAppPort ?? 3000;
|
|
1355
|
+
|
|
1356
|
+
// Load secrets and inject PORT
|
|
1357
|
+
const credentials = await loadSecretsForApp(secretsRoot, appName);
|
|
1358
|
+
|
|
1359
|
+
// Always inject PORT into credentials so apps can read it
|
|
1360
|
+
credentials.PORT = String(resolvedPort);
|
|
1361
|
+
|
|
1362
|
+
// Write secrets to temp JSON file (always write since we have PORT)
|
|
1363
|
+
const secretsDir = join(secretsRoot, '.gkm');
|
|
1364
|
+
await mkdir(secretsDir, { recursive: true });
|
|
1365
|
+
const secretsJsonPath = join(secretsDir, 'dev-secrets.json');
|
|
1366
|
+
await writeFile(secretsJsonPath, JSON.stringify(credentials, null, 2));
|
|
1367
|
+
|
|
1368
|
+
return {
|
|
1369
|
+
credentials,
|
|
1370
|
+
resolvedPort,
|
|
1371
|
+
secretsJsonPath,
|
|
1372
|
+
appName,
|
|
1373
|
+
secretsRoot,
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1310
1377
|
/**
|
|
1311
1378
|
* Run any TypeScript file with secret injection.
|
|
1312
1379
|
* Does not require gkm.config.ts.
|
|
@@ -1330,46 +1397,18 @@ async function entryDevCommand(options: DevOptions): Promise<void> {
|
|
|
1330
1397
|
logger.log(`📦 Loaded env: ${defaultEnv.loaded.join(', ')}`);
|
|
1331
1398
|
}
|
|
1332
1399
|
|
|
1333
|
-
//
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
let appName: string | undefined;
|
|
1400
|
+
// Prepare credentials (loads workspace config, secrets, injects PORT)
|
|
1401
|
+
const { credentials, resolvedPort, secretsJsonPath, appName } =
|
|
1402
|
+
await prepareEntryCredentials({ explicitPort: options.port });
|
|
1337
1403
|
|
|
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
|
-
}
|
|
1404
|
+
if (appName) {
|
|
1405
|
+
logger.log(`📦 App: ${appName} (port ${resolvedPort})`);
|
|
1351
1406
|
}
|
|
1352
1407
|
|
|
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
|
-
}
|
|
1408
|
+
logger.log(`🚀 Starting entry file: ${entry} on port ${resolvedPort}`);
|
|
1365
1409
|
|
|
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));
|
|
1410
|
+
if (Object.keys(credentials).length > 1) {
|
|
1411
|
+
logger.log(`🔐 Loaded ${Object.keys(credentials).length - 1} secret(s) + PORT`);
|
|
1373
1412
|
}
|
|
1374
1413
|
|
|
1375
1414
|
// Create wrapper entry that injects secrets before importing user's file
|
|
@@ -1379,7 +1418,7 @@ async function entryDevCommand(options: DevOptions): Promise<void> {
|
|
|
1379
1418
|
await createEntryWrapper(wrapperPath, entryPath, secretsJsonPath);
|
|
1380
1419
|
|
|
1381
1420
|
// Start with tsx
|
|
1382
|
-
const runner = new EntryRunner(wrapperPath, entryPath, watch,
|
|
1421
|
+
const runner = new EntryRunner(wrapperPath, entryPath, watch, resolvedPort);
|
|
1383
1422
|
await runner.start();
|
|
1384
1423
|
|
|
1385
1424
|
// 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
|