@geekmidas/cli 0.35.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 +85 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +85 -19
- 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 -24
- 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,12 +1307,79 @@ 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.
|
|
1313
1380
|
*/
|
|
1314
1381
|
async function entryDevCommand(options: DevOptions): Promise<void> {
|
|
1315
|
-
const { entry,
|
|
1382
|
+
const { entry, watch = true } = options;
|
|
1316
1383
|
|
|
1317
1384
|
if (!entry) {
|
|
1318
1385
|
throw new Error('--entry requires a file path');
|
|
@@ -1324,39 +1391,24 @@ async function entryDevCommand(options: DevOptions): Promise<void> {
|
|
|
1324
1391
|
throw new Error(`Entry file not found: ${entryPath}`);
|
|
1325
1392
|
}
|
|
1326
1393
|
|
|
1327
|
-
logger.log(`🚀 Starting entry file: ${entry}`);
|
|
1328
|
-
|
|
1329
1394
|
// Load .env files
|
|
1330
1395
|
const defaultEnv = loadEnvFiles('.env');
|
|
1331
1396
|
if (defaultEnv.loaded.length > 0) {
|
|
1332
1397
|
logger.log(`📦 Loaded env: ${defaultEnv.loaded.join(', ')}`);
|
|
1333
1398
|
}
|
|
1334
1399
|
|
|
1335
|
-
//
|
|
1336
|
-
const
|
|
1337
|
-
|
|
1400
|
+
// Prepare credentials (loads workspace config, secrets, injects PORT)
|
|
1401
|
+
const { credentials, resolvedPort, secretsJsonPath, appName } =
|
|
1402
|
+
await prepareEntryCredentials({ explicitPort: options.port });
|
|
1338
1403
|
|
|
1339
|
-
// Determine app name for per-app secret mapping
|
|
1340
|
-
const appName = getAppNameFromCwd() ?? undefined;
|
|
1341
1404
|
if (appName) {
|
|
1342
|
-
logger.log(`📦 App
|
|
1405
|
+
logger.log(`📦 App: ${appName} (port ${resolvedPort})`);
|
|
1343
1406
|
}
|
|
1344
1407
|
|
|
1345
|
-
|
|
1346
|
-
const appSecrets = await loadSecretsForApp(secretsRoot, appName);
|
|
1347
|
-
if (Object.keys(appSecrets).length > 0) {
|
|
1348
|
-
logger.log(`🔐 Loaded ${Object.keys(appSecrets).length} secret(s)`);
|
|
1349
|
-
} else {
|
|
1350
|
-
logger.log(`⚠️ No secrets found in ${secretsRoot}/.gkm/secrets/`);
|
|
1351
|
-
}
|
|
1408
|
+
logger.log(`🚀 Starting entry file: ${entry} on port ${resolvedPort}`);
|
|
1352
1409
|
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
if (Object.keys(appSecrets).length > 0) {
|
|
1356
|
-
const secretsDir = join(secretsRoot, '.gkm');
|
|
1357
|
-
await mkdir(secretsDir, { recursive: true });
|
|
1358
|
-
secretsJsonPath = join(secretsDir, 'dev-secrets.json');
|
|
1359
|
-
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`);
|
|
1360
1412
|
}
|
|
1361
1413
|
|
|
1362
1414
|
// Create wrapper entry that injects secrets before importing user's file
|
|
@@ -1366,7 +1418,7 @@ async function entryDevCommand(options: DevOptions): Promise<void> {
|
|
|
1366
1418
|
await createEntryWrapper(wrapperPath, entryPath, secretsJsonPath);
|
|
1367
1419
|
|
|
1368
1420
|
// Start with tsx
|
|
1369
|
-
const runner = new EntryRunner(wrapperPath, entryPath, watch,
|
|
1421
|
+
const runner = new EntryRunner(wrapperPath, entryPath, watch, resolvedPort);
|
|
1370
1422
|
await runner.start();
|
|
1371
1423
|
|
|
1372
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
|