@geekmidas/cli 1.2.1 → 1.2.3
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 +12 -0
- package/dist/index.cjs +110 -182
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +111 -183
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-DrbBWq0s.mjs → openapi-NthphEWK.mjs} +3 -4
- package/dist/{openapi-DrbBWq0s.mjs.map → openapi-NthphEWK.mjs.map} +1 -1
- package/dist/{openapi-BZP8jkI4.cjs → openapi-ZhO7wwya.cjs} +2 -9
- package/dist/{openapi-BZP8jkI4.cjs.map → openapi-ZhO7wwya.cjs.map} +1 -1
- package/dist/openapi.cjs +1 -1
- package/dist/openapi.d.cts.map +1 -1
- package/dist/openapi.d.mts.map +1 -1
- package/dist/openapi.mjs +1 -1
- package/package.json +4 -4
- package/src/__tests__/openapi.spec.ts +385 -5
- package/src/dev/index.ts +71 -107
- package/src/generators/OpenApiTsGenerator.ts +0 -1
- package/src/init/generators/ui.ts +20 -20
- package/src/openapi.ts +2 -1
- package/src/workspace/__tests__/client-generator.spec.ts +472 -19
- package/src/workspace/client-generator.ts +139 -199
|
@@ -1,11 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
rmSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
} from 'node:fs';
|
|
2
8
|
import { tmpdir } from 'node:os';
|
|
3
9
|
import { join } from 'node:path';
|
|
4
10
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
5
11
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
12
|
+
copyAllClients,
|
|
13
|
+
copyClientToFrontends,
|
|
8
14
|
getBackendDependencies,
|
|
15
|
+
getBackendOpenApiPath,
|
|
9
16
|
getDependentFrontends,
|
|
10
17
|
getFirstRoute,
|
|
11
18
|
normalizeRoutes,
|
|
@@ -101,6 +108,7 @@ describe('Client Generator', () => {
|
|
|
101
108
|
port: 3000,
|
|
102
109
|
dependencies: [],
|
|
103
110
|
routes: './src/**/*.ts',
|
|
111
|
+
resolvedDeployTarget: 'dokploy',
|
|
104
112
|
},
|
|
105
113
|
auth: {
|
|
106
114
|
type: 'backend',
|
|
@@ -108,12 +116,14 @@ describe('Client Generator', () => {
|
|
|
108
116
|
port: 3001,
|
|
109
117
|
dependencies: [],
|
|
110
118
|
routes: './src/**/*.ts',
|
|
119
|
+
resolvedDeployTarget: 'dokploy',
|
|
111
120
|
},
|
|
112
121
|
web: {
|
|
113
122
|
type: 'frontend',
|
|
114
123
|
path: 'apps/web',
|
|
115
124
|
port: 3002,
|
|
116
125
|
dependencies: ['api', 'auth'],
|
|
126
|
+
resolvedDeployTarget: 'dokploy',
|
|
117
127
|
},
|
|
118
128
|
},
|
|
119
129
|
services: {},
|
|
@@ -137,12 +147,14 @@ describe('Client Generator', () => {
|
|
|
137
147
|
port: 3000,
|
|
138
148
|
dependencies: [],
|
|
139
149
|
routes: './src/**/*.ts',
|
|
150
|
+
resolvedDeployTarget: 'dokploy',
|
|
140
151
|
},
|
|
141
152
|
worker: {
|
|
142
153
|
type: 'backend',
|
|
143
154
|
path: 'apps/worker',
|
|
144
155
|
port: 3001,
|
|
145
156
|
dependencies: [],
|
|
157
|
+
resolvedDeployTarget: 'dokploy',
|
|
146
158
|
// No routes - not an HTTP backend
|
|
147
159
|
},
|
|
148
160
|
web: {
|
|
@@ -150,6 +162,7 @@ describe('Client Generator', () => {
|
|
|
150
162
|
path: 'apps/web',
|
|
151
163
|
port: 3002,
|
|
152
164
|
dependencies: ['api', 'worker'],
|
|
165
|
+
resolvedDeployTarget: 'dokploy',
|
|
153
166
|
},
|
|
154
167
|
},
|
|
155
168
|
services: {},
|
|
@@ -173,6 +186,7 @@ describe('Client Generator', () => {
|
|
|
173
186
|
port: 3000,
|
|
174
187
|
dependencies: [],
|
|
175
188
|
routes: './src/**/*.ts',
|
|
189
|
+
resolvedDeployTarget: 'dokploy',
|
|
176
190
|
},
|
|
177
191
|
},
|
|
178
192
|
services: {},
|
|
@@ -198,24 +212,28 @@ describe('Client Generator', () => {
|
|
|
198
212
|
port: 3000,
|
|
199
213
|
dependencies: [],
|
|
200
214
|
routes: './src/**/*.ts',
|
|
215
|
+
resolvedDeployTarget: 'dokploy',
|
|
201
216
|
},
|
|
202
217
|
web: {
|
|
203
218
|
type: 'frontend',
|
|
204
219
|
path: 'apps/web',
|
|
205
220
|
port: 3001,
|
|
206
221
|
dependencies: ['api'],
|
|
222
|
+
resolvedDeployTarget: 'dokploy',
|
|
207
223
|
},
|
|
208
224
|
admin: {
|
|
209
225
|
type: 'frontend',
|
|
210
226
|
path: 'apps/admin',
|
|
211
227
|
port: 3002,
|
|
212
228
|
dependencies: ['api'],
|
|
229
|
+
resolvedDeployTarget: 'dokploy',
|
|
213
230
|
},
|
|
214
231
|
docs: {
|
|
215
232
|
type: 'frontend',
|
|
216
233
|
path: 'apps/docs',
|
|
217
234
|
port: 3003,
|
|
218
235
|
dependencies: [], // No API dependency
|
|
236
|
+
resolvedDeployTarget: 'dokploy',
|
|
219
237
|
},
|
|
220
238
|
},
|
|
221
239
|
services: {},
|
|
@@ -239,6 +257,7 @@ describe('Client Generator', () => {
|
|
|
239
257
|
port: 3000,
|
|
240
258
|
dependencies: [],
|
|
241
259
|
routes: './src/**/*.ts',
|
|
260
|
+
resolvedDeployTarget: 'dokploy',
|
|
242
261
|
},
|
|
243
262
|
},
|
|
244
263
|
services: {},
|
|
@@ -252,7 +271,71 @@ describe('Client Generator', () => {
|
|
|
252
271
|
});
|
|
253
272
|
});
|
|
254
273
|
|
|
255
|
-
describe('
|
|
274
|
+
describe('getBackendOpenApiPath', () => {
|
|
275
|
+
it('should return correct path for backend app', () => {
|
|
276
|
+
const workspace: NormalizedWorkspace = {
|
|
277
|
+
name: 'test',
|
|
278
|
+
root: '/project',
|
|
279
|
+
apps: {
|
|
280
|
+
api: {
|
|
281
|
+
type: 'backend',
|
|
282
|
+
path: 'apps/api',
|
|
283
|
+
port: 3000,
|
|
284
|
+
dependencies: [],
|
|
285
|
+
routes: './src/**/*.ts',
|
|
286
|
+
resolvedDeployTarget: 'dokploy',
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
services: {},
|
|
290
|
+
deploy: { default: 'dokploy' },
|
|
291
|
+
shared: { packages: [] },
|
|
292
|
+
secrets: {},
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const path = getBackendOpenApiPath(workspace, 'api');
|
|
296
|
+
expect(path).toBe('/project/apps/api/.gkm/openapi.ts');
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('should return null for non-backend app', () => {
|
|
300
|
+
const workspace: NormalizedWorkspace = {
|
|
301
|
+
name: 'test',
|
|
302
|
+
root: '/project',
|
|
303
|
+
apps: {
|
|
304
|
+
web: {
|
|
305
|
+
type: 'frontend',
|
|
306
|
+
path: 'apps/web',
|
|
307
|
+
port: 3000,
|
|
308
|
+
dependencies: [],
|
|
309
|
+
resolvedDeployTarget: 'dokploy',
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
services: {},
|
|
313
|
+
deploy: { default: 'dokploy' },
|
|
314
|
+
shared: { packages: [] },
|
|
315
|
+
secrets: {},
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
const path = getBackendOpenApiPath(workspace, 'web');
|
|
319
|
+
expect(path).toBeNull();
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('should return null for non-existent app', () => {
|
|
323
|
+
const workspace: NormalizedWorkspace = {
|
|
324
|
+
name: 'test',
|
|
325
|
+
root: '/project',
|
|
326
|
+
apps: {},
|
|
327
|
+
services: {},
|
|
328
|
+
deploy: { default: 'dokploy' },
|
|
329
|
+
shared: { packages: [] },
|
|
330
|
+
secrets: {},
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
const path = getBackendOpenApiPath(workspace, 'nonexistent');
|
|
334
|
+
expect(path).toBeNull();
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
describe('copyClientToFrontends', () => {
|
|
256
339
|
let testDir: string;
|
|
257
340
|
|
|
258
341
|
beforeEach(() => {
|
|
@@ -261,7 +344,6 @@ describe('Client Generator', () => {
|
|
|
261
344
|
`gkm-client-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
262
345
|
);
|
|
263
346
|
mkdirSync(testDir, { recursive: true });
|
|
264
|
-
clearSpecHashCache();
|
|
265
347
|
});
|
|
266
348
|
|
|
267
349
|
afterEach(() => {
|
|
@@ -270,7 +352,120 @@ describe('Client Generator', () => {
|
|
|
270
352
|
}
|
|
271
353
|
});
|
|
272
354
|
|
|
273
|
-
|
|
355
|
+
function createOpenApiFile(
|
|
356
|
+
appDir: string,
|
|
357
|
+
endpoints: Array<{ method: string; path: string }>,
|
|
358
|
+
): void {
|
|
359
|
+
const endpointAuthEntries = endpoints
|
|
360
|
+
.map((ep) => ` '${ep.method.toUpperCase()} ${ep.path}': null,`)
|
|
361
|
+
.join('\n');
|
|
362
|
+
|
|
363
|
+
const content = `// Auto-generated by @geekmidas/cli - DO NOT EDIT
|
|
364
|
+
|
|
365
|
+
export const endpointAuth = {
|
|
366
|
+
${endpointAuthEntries}
|
|
367
|
+
} as const;
|
|
368
|
+
|
|
369
|
+
export const paths = {};
|
|
370
|
+
export function createApi() { return {}; }
|
|
371
|
+
`;
|
|
372
|
+
|
|
373
|
+
const gkmDir = join(appDir, '.gkm');
|
|
374
|
+
mkdirSync(gkmDir, { recursive: true });
|
|
375
|
+
writeFileSync(join(gkmDir, 'openapi.ts'), content);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
it('should copy client to dependent frontends', async () => {
|
|
379
|
+
const apiDir = join(testDir, 'apps/api');
|
|
380
|
+
const webDir = join(testDir, 'apps/web');
|
|
381
|
+
mkdirSync(webDir, { recursive: true });
|
|
382
|
+
|
|
383
|
+
createOpenApiFile(apiDir, [
|
|
384
|
+
{ method: 'GET', path: '/users' },
|
|
385
|
+
{ method: 'POST', path: '/users' },
|
|
386
|
+
]);
|
|
387
|
+
|
|
388
|
+
const workspace: NormalizedWorkspace = {
|
|
389
|
+
name: 'test',
|
|
390
|
+
root: testDir,
|
|
391
|
+
apps: {
|
|
392
|
+
api: {
|
|
393
|
+
type: 'backend',
|
|
394
|
+
path: 'apps/api',
|
|
395
|
+
port: 3000,
|
|
396
|
+
dependencies: [],
|
|
397
|
+
routes: './src/endpoints/**/*.ts',
|
|
398
|
+
resolvedDeployTarget: 'dokploy',
|
|
399
|
+
},
|
|
400
|
+
web: {
|
|
401
|
+
type: 'frontend',
|
|
402
|
+
path: 'apps/web',
|
|
403
|
+
port: 3001,
|
|
404
|
+
dependencies: ['api'],
|
|
405
|
+
client: { output: 'src/api' },
|
|
406
|
+
resolvedDeployTarget: 'dokploy',
|
|
407
|
+
},
|
|
408
|
+
},
|
|
409
|
+
services: {},
|
|
410
|
+
deploy: { default: 'dokploy' },
|
|
411
|
+
shared: { packages: [] },
|
|
412
|
+
secrets: {},
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
const results = await copyClientToFrontends(workspace, 'api', {
|
|
416
|
+
silent: true,
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
expect(results).toHaveLength(1);
|
|
420
|
+
expect(results[0]?.success).toBe(true);
|
|
421
|
+
expect(results[0]?.frontendApp).toBe('web');
|
|
422
|
+
expect(results[0]?.backendApp).toBe('api');
|
|
423
|
+
expect(results[0]?.endpointCount).toBe(2);
|
|
424
|
+
expect(existsSync(join(webDir, 'src/api/api.ts'))).toBe(true);
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it('should skip frontends without client.output configured', async () => {
|
|
428
|
+
const apiDir = join(testDir, 'apps/api');
|
|
429
|
+
const webDir = join(testDir, 'apps/web');
|
|
430
|
+
mkdirSync(webDir, { recursive: true });
|
|
431
|
+
|
|
432
|
+
createOpenApiFile(apiDir, [{ method: 'GET', path: '/users' }]);
|
|
433
|
+
|
|
434
|
+
const workspace: NormalizedWorkspace = {
|
|
435
|
+
name: 'test',
|
|
436
|
+
root: testDir,
|
|
437
|
+
apps: {
|
|
438
|
+
api: {
|
|
439
|
+
type: 'backend',
|
|
440
|
+
path: 'apps/api',
|
|
441
|
+
port: 3000,
|
|
442
|
+
dependencies: [],
|
|
443
|
+
routes: './src/endpoints/**/*.ts',
|
|
444
|
+
resolvedDeployTarget: 'dokploy',
|
|
445
|
+
},
|
|
446
|
+
web: {
|
|
447
|
+
type: 'frontend',
|
|
448
|
+
path: 'apps/web',
|
|
449
|
+
port: 3001,
|
|
450
|
+
dependencies: ['api'],
|
|
451
|
+
// No client.output configured
|
|
452
|
+
resolvedDeployTarget: 'dokploy',
|
|
453
|
+
},
|
|
454
|
+
},
|
|
455
|
+
services: {},
|
|
456
|
+
deploy: { default: 'dokploy' },
|
|
457
|
+
shared: { packages: [] },
|
|
458
|
+
secrets: {},
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
const results = await copyClientToFrontends(workspace, 'api', {
|
|
462
|
+
silent: true,
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
expect(results).toHaveLength(0);
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
it('should return empty results for non-backend app', async () => {
|
|
274
469
|
const workspace: NormalizedWorkspace = {
|
|
275
470
|
name: 'test',
|
|
276
471
|
root: testDir,
|
|
@@ -280,6 +475,7 @@ describe('Client Generator', () => {
|
|
|
280
475
|
path: 'apps/web',
|
|
281
476
|
port: 3001,
|
|
282
477
|
dependencies: [],
|
|
478
|
+
resolvedDeployTarget: 'dokploy',
|
|
283
479
|
},
|
|
284
480
|
},
|
|
285
481
|
services: {},
|
|
@@ -288,11 +484,20 @@ describe('Client Generator', () => {
|
|
|
288
484
|
secrets: {},
|
|
289
485
|
};
|
|
290
486
|
|
|
291
|
-
const results = await
|
|
487
|
+
const results = await copyClientToFrontends(workspace, 'web', {
|
|
488
|
+
silent: true,
|
|
489
|
+
});
|
|
490
|
+
|
|
292
491
|
expect(results).toEqual([]);
|
|
293
492
|
});
|
|
294
493
|
|
|
295
|
-
it('should return empty
|
|
494
|
+
it('should return empty results when openapi file does not exist', async () => {
|
|
495
|
+
const apiDir = join(testDir, 'apps/api');
|
|
496
|
+
const webDir = join(testDir, 'apps/web');
|
|
497
|
+
mkdirSync(apiDir, { recursive: true });
|
|
498
|
+
mkdirSync(webDir, { recursive: true });
|
|
499
|
+
// Don't create openapi file
|
|
500
|
+
|
|
296
501
|
const workspace: NormalizedWorkspace = {
|
|
297
502
|
name: 'test',
|
|
298
503
|
root: testDir,
|
|
@@ -302,7 +507,16 @@ describe('Client Generator', () => {
|
|
|
302
507
|
path: 'apps/api',
|
|
303
508
|
port: 3000,
|
|
304
509
|
dependencies: [],
|
|
305
|
-
routes: './src/**/*.ts',
|
|
510
|
+
routes: './src/endpoints/**/*.ts',
|
|
511
|
+
resolvedDeployTarget: 'dokploy',
|
|
512
|
+
},
|
|
513
|
+
web: {
|
|
514
|
+
type: 'frontend',
|
|
515
|
+
path: 'apps/web',
|
|
516
|
+
port: 3001,
|
|
517
|
+
dependencies: ['api'],
|
|
518
|
+
client: { output: 'src/api' },
|
|
519
|
+
resolvedDeployTarget: 'dokploy',
|
|
306
520
|
},
|
|
307
521
|
},
|
|
308
522
|
services: {},
|
|
@@ -311,16 +525,21 @@ describe('Client Generator', () => {
|
|
|
311
525
|
secrets: {},
|
|
312
526
|
};
|
|
313
527
|
|
|
314
|
-
const results = await
|
|
528
|
+
const results = await copyClientToFrontends(workspace, 'api', {
|
|
529
|
+
silent: true,
|
|
530
|
+
});
|
|
531
|
+
|
|
315
532
|
expect(results).toEqual([]);
|
|
316
533
|
});
|
|
317
534
|
|
|
318
|
-
it('should
|
|
319
|
-
// Create minimal workspace structure
|
|
535
|
+
it('should copy to multiple frontends', async () => {
|
|
320
536
|
const apiDir = join(testDir, 'apps/api');
|
|
321
537
|
const webDir = join(testDir, 'apps/web');
|
|
322
|
-
|
|
538
|
+
const adminDir = join(testDir, 'apps/admin');
|
|
323
539
|
mkdirSync(webDir, { recursive: true });
|
|
540
|
+
mkdirSync(adminDir, { recursive: true });
|
|
541
|
+
|
|
542
|
+
createOpenApiFile(apiDir, [{ method: 'GET', path: '/users' }]);
|
|
324
543
|
|
|
325
544
|
const workspace: NormalizedWorkspace = {
|
|
326
545
|
name: 'test',
|
|
@@ -332,13 +551,23 @@ describe('Client Generator', () => {
|
|
|
332
551
|
port: 3000,
|
|
333
552
|
dependencies: [],
|
|
334
553
|
routes: './src/endpoints/**/*.ts',
|
|
554
|
+
resolvedDeployTarget: 'dokploy',
|
|
335
555
|
},
|
|
336
556
|
web: {
|
|
337
557
|
type: 'frontend',
|
|
338
558
|
path: 'apps/web',
|
|
339
559
|
port: 3001,
|
|
340
560
|
dependencies: ['api'],
|
|
341
|
-
client: { output: '
|
|
561
|
+
client: { output: 'src/api' },
|
|
562
|
+
resolvedDeployTarget: 'dokploy',
|
|
563
|
+
},
|
|
564
|
+
admin: {
|
|
565
|
+
type: 'frontend',
|
|
566
|
+
path: 'apps/admin',
|
|
567
|
+
port: 3002,
|
|
568
|
+
dependencies: ['api'],
|
|
569
|
+
client: { output: 'lib/client' },
|
|
570
|
+
resolvedDeployTarget: 'dokploy',
|
|
342
571
|
},
|
|
343
572
|
},
|
|
344
573
|
services: {},
|
|
@@ -347,11 +576,235 @@ describe('Client Generator', () => {
|
|
|
347
576
|
secrets: {},
|
|
348
577
|
};
|
|
349
578
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
expect(results
|
|
579
|
+
const results = await copyClientToFrontends(workspace, 'api', {
|
|
580
|
+
silent: true,
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
expect(results).toHaveLength(2);
|
|
584
|
+
expect(results.every((r) => r.success)).toBe(true);
|
|
585
|
+
expect(existsSync(join(webDir, 'src/api/api.ts'))).toBe(true);
|
|
586
|
+
expect(existsSync(join(adminDir, 'lib/client/api.ts'))).toBe(true);
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
it('should add header comment to copied file', async () => {
|
|
590
|
+
const apiDir = join(testDir, 'apps/api');
|
|
591
|
+
const webDir = join(testDir, 'apps/web');
|
|
592
|
+
mkdirSync(webDir, { recursive: true });
|
|
593
|
+
|
|
594
|
+
createOpenApiFile(apiDir, [{ method: 'GET', path: '/users' }]);
|
|
595
|
+
|
|
596
|
+
const workspace: NormalizedWorkspace = {
|
|
597
|
+
name: 'test',
|
|
598
|
+
root: testDir,
|
|
599
|
+
apps: {
|
|
600
|
+
api: {
|
|
601
|
+
type: 'backend',
|
|
602
|
+
path: 'apps/api',
|
|
603
|
+
port: 3000,
|
|
604
|
+
dependencies: [],
|
|
605
|
+
routes: './src/endpoints/**/*.ts',
|
|
606
|
+
resolvedDeployTarget: 'dokploy',
|
|
607
|
+
},
|
|
608
|
+
web: {
|
|
609
|
+
type: 'frontend',
|
|
610
|
+
path: 'apps/web',
|
|
611
|
+
port: 3001,
|
|
612
|
+
dependencies: ['api'],
|
|
613
|
+
client: { output: 'src/api' },
|
|
614
|
+
resolvedDeployTarget: 'dokploy',
|
|
615
|
+
},
|
|
616
|
+
},
|
|
617
|
+
services: {},
|
|
618
|
+
deploy: { default: 'dokploy' },
|
|
619
|
+
shared: { packages: [] },
|
|
620
|
+
secrets: {},
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
await copyClientToFrontends(workspace, 'api', { silent: true });
|
|
624
|
+
|
|
625
|
+
const content = readFileSync(join(webDir, 'src/api/api.ts'), 'utf-8');
|
|
626
|
+
expect(content).toContain('Auto-generated API client for api');
|
|
627
|
+
expect(content).toContain('DO NOT EDIT');
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
it('should use backend name in filename when frontend has multiple backends', async () => {
|
|
631
|
+
const apiDir = join(testDir, 'apps/api');
|
|
632
|
+
const authDir = join(testDir, 'apps/auth');
|
|
633
|
+
const webDir = join(testDir, 'apps/web');
|
|
634
|
+
mkdirSync(webDir, { recursive: true });
|
|
635
|
+
|
|
636
|
+
createOpenApiFile(apiDir, [{ method: 'GET', path: '/users' }]);
|
|
637
|
+
createOpenApiFile(authDir, [{ method: 'POST', path: '/login' }]);
|
|
638
|
+
|
|
639
|
+
const workspace: NormalizedWorkspace = {
|
|
640
|
+
name: 'test',
|
|
641
|
+
root: testDir,
|
|
642
|
+
apps: {
|
|
643
|
+
api: {
|
|
644
|
+
type: 'backend',
|
|
645
|
+
path: 'apps/api',
|
|
646
|
+
port: 3000,
|
|
647
|
+
dependencies: [],
|
|
648
|
+
routes: './src/endpoints/**/*.ts',
|
|
649
|
+
resolvedDeployTarget: 'dokploy',
|
|
650
|
+
},
|
|
651
|
+
auth: {
|
|
652
|
+
type: 'backend',
|
|
653
|
+
path: 'apps/auth',
|
|
654
|
+
port: 3001,
|
|
655
|
+
dependencies: [],
|
|
656
|
+
routes: './src/endpoints/**/*.ts',
|
|
657
|
+
resolvedDeployTarget: 'dokploy',
|
|
658
|
+
},
|
|
659
|
+
web: {
|
|
660
|
+
type: 'frontend',
|
|
661
|
+
path: 'apps/web',
|
|
662
|
+
port: 3002,
|
|
663
|
+
dependencies: ['api', 'auth'], // Multiple backends
|
|
664
|
+
client: { output: 'src/api' },
|
|
665
|
+
resolvedDeployTarget: 'dokploy',
|
|
666
|
+
},
|
|
667
|
+
},
|
|
668
|
+
services: {},
|
|
669
|
+
deploy: { default: 'dokploy' },
|
|
670
|
+
shared: { packages: [] },
|
|
671
|
+
secrets: {},
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
await copyClientToFrontends(workspace, 'api', { silent: true });
|
|
675
|
+
await copyClientToFrontends(workspace, 'auth', { silent: true });
|
|
676
|
+
|
|
677
|
+
// Should use {backend}.ts naming
|
|
678
|
+
expect(existsSync(join(webDir, 'src/api/api.ts'))).toBe(true);
|
|
679
|
+
expect(existsSync(join(webDir, 'src/api/auth.ts'))).toBe(true);
|
|
680
|
+
});
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
describe('copyAllClients', () => {
|
|
684
|
+
let testDir: string;
|
|
685
|
+
|
|
686
|
+
beforeEach(() => {
|
|
687
|
+
testDir = join(
|
|
688
|
+
tmpdir(),
|
|
689
|
+
`gkm-client-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
690
|
+
);
|
|
691
|
+
mkdirSync(testDir, { recursive: true });
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
afterEach(() => {
|
|
695
|
+
if (existsSync(testDir)) {
|
|
696
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
697
|
+
}
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
function createOpenApiFile(
|
|
701
|
+
appDir: string,
|
|
702
|
+
endpoints: Array<{ method: string; path: string }>,
|
|
703
|
+
): void {
|
|
704
|
+
const endpointAuthEntries = endpoints
|
|
705
|
+
.map((ep) => ` '${ep.method.toUpperCase()} ${ep.path}': null,`)
|
|
706
|
+
.join('\n');
|
|
707
|
+
|
|
708
|
+
const content = `// Auto-generated by @geekmidas/cli - DO NOT EDIT
|
|
709
|
+
|
|
710
|
+
export const endpointAuth = {
|
|
711
|
+
${endpointAuthEntries}
|
|
712
|
+
} as const;
|
|
713
|
+
|
|
714
|
+
export const paths = {};
|
|
715
|
+
export function createApi() { return {}; }
|
|
716
|
+
`;
|
|
717
|
+
|
|
718
|
+
const gkmDir = join(appDir, '.gkm');
|
|
719
|
+
mkdirSync(gkmDir, { recursive: true });
|
|
720
|
+
writeFileSync(join(gkmDir, 'openapi.ts'), content);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
it('should copy from all backends to their dependent frontends', async () => {
|
|
724
|
+
const apiDir = join(testDir, 'apps/api');
|
|
725
|
+
const authDir = join(testDir, 'apps/auth');
|
|
726
|
+
const webDir = join(testDir, 'apps/web');
|
|
727
|
+
const adminDir = join(testDir, 'apps/admin');
|
|
728
|
+
mkdirSync(webDir, { recursive: true });
|
|
729
|
+
mkdirSync(adminDir, { recursive: true });
|
|
730
|
+
|
|
731
|
+
createOpenApiFile(apiDir, [{ method: 'GET', path: '/users' }]);
|
|
732
|
+
createOpenApiFile(authDir, [{ method: 'POST', path: '/login' }]);
|
|
733
|
+
|
|
734
|
+
const workspace: NormalizedWorkspace = {
|
|
735
|
+
name: 'test',
|
|
736
|
+
root: testDir,
|
|
737
|
+
apps: {
|
|
738
|
+
api: {
|
|
739
|
+
type: 'backend',
|
|
740
|
+
path: 'apps/api',
|
|
741
|
+
port: 3000,
|
|
742
|
+
dependencies: [],
|
|
743
|
+
routes: './src/endpoints/**/*.ts',
|
|
744
|
+
resolvedDeployTarget: 'dokploy',
|
|
745
|
+
},
|
|
746
|
+
auth: {
|
|
747
|
+
type: 'backend',
|
|
748
|
+
path: 'apps/auth',
|
|
749
|
+
port: 3001,
|
|
750
|
+
dependencies: [],
|
|
751
|
+
routes: './src/endpoints/**/*.ts',
|
|
752
|
+
resolvedDeployTarget: 'dokploy',
|
|
753
|
+
},
|
|
754
|
+
web: {
|
|
755
|
+
type: 'frontend',
|
|
756
|
+
path: 'apps/web',
|
|
757
|
+
port: 3002,
|
|
758
|
+
dependencies: ['api'],
|
|
759
|
+
client: { output: 'src/api' },
|
|
760
|
+
resolvedDeployTarget: 'dokploy',
|
|
761
|
+
},
|
|
762
|
+
admin: {
|
|
763
|
+
type: 'frontend',
|
|
764
|
+
path: 'apps/admin',
|
|
765
|
+
port: 3003,
|
|
766
|
+
dependencies: ['auth'],
|
|
767
|
+
client: { output: 'lib/client' },
|
|
768
|
+
resolvedDeployTarget: 'dokploy',
|
|
769
|
+
},
|
|
770
|
+
},
|
|
771
|
+
services: {},
|
|
772
|
+
deploy: { default: 'dokploy' },
|
|
773
|
+
shared: { packages: [] },
|
|
774
|
+
secrets: {},
|
|
775
|
+
};
|
|
776
|
+
|
|
777
|
+
const results = await copyAllClients(workspace, { silent: true });
|
|
778
|
+
|
|
779
|
+
expect(results).toHaveLength(2);
|
|
780
|
+
expect(results.every((r) => r.success)).toBe(true);
|
|
781
|
+
expect(existsSync(join(webDir, 'src/api/api.ts'))).toBe(true);
|
|
782
|
+
expect(existsSync(join(adminDir, 'lib/client/auth.ts'))).toBe(true);
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
it('should return empty array when no backends have routes', async () => {
|
|
786
|
+
const workspace: NormalizedWorkspace = {
|
|
787
|
+
name: 'test',
|
|
788
|
+
root: testDir,
|
|
789
|
+
apps: {
|
|
790
|
+
worker: {
|
|
791
|
+
type: 'backend',
|
|
792
|
+
path: 'apps/worker',
|
|
793
|
+
port: 3000,
|
|
794
|
+
dependencies: [],
|
|
795
|
+
// No routes
|
|
796
|
+
resolvedDeployTarget: 'dokploy',
|
|
797
|
+
},
|
|
798
|
+
},
|
|
799
|
+
services: {},
|
|
800
|
+
deploy: { default: 'dokploy' },
|
|
801
|
+
shared: { packages: [] },
|
|
802
|
+
secrets: {},
|
|
803
|
+
};
|
|
804
|
+
|
|
805
|
+
const results = await copyAllClients(workspace, { silent: true });
|
|
806
|
+
|
|
807
|
+
expect(results).toEqual([]);
|
|
355
808
|
});
|
|
356
809
|
});
|
|
357
810
|
});
|