@geekmidas/cli 1.2.1 → 1.2.2
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 +6 -0
- package/dist/index.cjs +24 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +24 -23
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-DrbBWq0s.mjs → openapi-BZ4Qik9w.mjs} +2 -3
- package/dist/{openapi-DrbBWq0s.mjs.map → openapi-BZ4Qik9w.mjs.map} +1 -1
- package/dist/{openapi-BZP8jkI4.cjs → openapi-CzfnHlhG.cjs} +2 -3
- package/dist/{openapi-BZP8jkI4.cjs.map → openapi-CzfnHlhG.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 +3 -3
- package/src/__tests__/openapi.spec.ts +385 -5
- package/src/dev/index.ts +2 -1
- 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 +424 -0
|
@@ -101,6 +101,7 @@ describe('Client Generator', () => {
|
|
|
101
101
|
port: 3000,
|
|
102
102
|
dependencies: [],
|
|
103
103
|
routes: './src/**/*.ts',
|
|
104
|
+
resolvedDeployTarget: 'dokploy',
|
|
104
105
|
},
|
|
105
106
|
auth: {
|
|
106
107
|
type: 'backend',
|
|
@@ -108,12 +109,14 @@ describe('Client Generator', () => {
|
|
|
108
109
|
port: 3001,
|
|
109
110
|
dependencies: [],
|
|
110
111
|
routes: './src/**/*.ts',
|
|
112
|
+
resolvedDeployTarget: 'dokploy',
|
|
111
113
|
},
|
|
112
114
|
web: {
|
|
113
115
|
type: 'frontend',
|
|
114
116
|
path: 'apps/web',
|
|
115
117
|
port: 3002,
|
|
116
118
|
dependencies: ['api', 'auth'],
|
|
119
|
+
resolvedDeployTarget: 'dokploy',
|
|
117
120
|
},
|
|
118
121
|
},
|
|
119
122
|
services: {},
|
|
@@ -137,12 +140,14 @@ describe('Client Generator', () => {
|
|
|
137
140
|
port: 3000,
|
|
138
141
|
dependencies: [],
|
|
139
142
|
routes: './src/**/*.ts',
|
|
143
|
+
resolvedDeployTarget: 'dokploy',
|
|
140
144
|
},
|
|
141
145
|
worker: {
|
|
142
146
|
type: 'backend',
|
|
143
147
|
path: 'apps/worker',
|
|
144
148
|
port: 3001,
|
|
145
149
|
dependencies: [],
|
|
150
|
+
resolvedDeployTarget: 'dokploy',
|
|
146
151
|
// No routes - not an HTTP backend
|
|
147
152
|
},
|
|
148
153
|
web: {
|
|
@@ -150,6 +155,7 @@ describe('Client Generator', () => {
|
|
|
150
155
|
path: 'apps/web',
|
|
151
156
|
port: 3002,
|
|
152
157
|
dependencies: ['api', 'worker'],
|
|
158
|
+
resolvedDeployTarget: 'dokploy',
|
|
153
159
|
},
|
|
154
160
|
},
|
|
155
161
|
services: {},
|
|
@@ -173,6 +179,7 @@ describe('Client Generator', () => {
|
|
|
173
179
|
port: 3000,
|
|
174
180
|
dependencies: [],
|
|
175
181
|
routes: './src/**/*.ts',
|
|
182
|
+
resolvedDeployTarget: 'dokploy',
|
|
176
183
|
},
|
|
177
184
|
},
|
|
178
185
|
services: {},
|
|
@@ -198,24 +205,28 @@ describe('Client Generator', () => {
|
|
|
198
205
|
port: 3000,
|
|
199
206
|
dependencies: [],
|
|
200
207
|
routes: './src/**/*.ts',
|
|
208
|
+
resolvedDeployTarget: 'dokploy',
|
|
201
209
|
},
|
|
202
210
|
web: {
|
|
203
211
|
type: 'frontend',
|
|
204
212
|
path: 'apps/web',
|
|
205
213
|
port: 3001,
|
|
206
214
|
dependencies: ['api'],
|
|
215
|
+
resolvedDeployTarget: 'dokploy',
|
|
207
216
|
},
|
|
208
217
|
admin: {
|
|
209
218
|
type: 'frontend',
|
|
210
219
|
path: 'apps/admin',
|
|
211
220
|
port: 3002,
|
|
212
221
|
dependencies: ['api'],
|
|
222
|
+
resolvedDeployTarget: 'dokploy',
|
|
213
223
|
},
|
|
214
224
|
docs: {
|
|
215
225
|
type: 'frontend',
|
|
216
226
|
path: 'apps/docs',
|
|
217
227
|
port: 3003,
|
|
218
228
|
dependencies: [], // No API dependency
|
|
229
|
+
resolvedDeployTarget: 'dokploy',
|
|
219
230
|
},
|
|
220
231
|
},
|
|
221
232
|
services: {},
|
|
@@ -239,6 +250,7 @@ describe('Client Generator', () => {
|
|
|
239
250
|
port: 3000,
|
|
240
251
|
dependencies: [],
|
|
241
252
|
routes: './src/**/*.ts',
|
|
253
|
+
resolvedDeployTarget: 'dokploy',
|
|
242
254
|
},
|
|
243
255
|
},
|
|
244
256
|
services: {},
|
|
@@ -280,6 +292,7 @@ describe('Client Generator', () => {
|
|
|
280
292
|
path: 'apps/web',
|
|
281
293
|
port: 3001,
|
|
282
294
|
dependencies: [],
|
|
295
|
+
resolvedDeployTarget: 'dokploy',
|
|
283
296
|
},
|
|
284
297
|
},
|
|
285
298
|
services: {},
|
|
@@ -303,6 +316,7 @@ describe('Client Generator', () => {
|
|
|
303
316
|
port: 3000,
|
|
304
317
|
dependencies: [],
|
|
305
318
|
routes: './src/**/*.ts',
|
|
319
|
+
resolvedDeployTarget: 'dokploy',
|
|
306
320
|
},
|
|
307
321
|
},
|
|
308
322
|
services: {},
|
|
@@ -332,6 +346,7 @@ describe('Client Generator', () => {
|
|
|
332
346
|
port: 3000,
|
|
333
347
|
dependencies: [],
|
|
334
348
|
routes: './src/endpoints/**/*.ts',
|
|
349
|
+
resolvedDeployTarget: 'dokploy',
|
|
335
350
|
},
|
|
336
351
|
web: {
|
|
337
352
|
type: 'frontend',
|
|
@@ -339,6 +354,7 @@ describe('Client Generator', () => {
|
|
|
339
354
|
port: 3001,
|
|
340
355
|
dependencies: ['api'],
|
|
341
356
|
client: { output: 'lib/api' },
|
|
357
|
+
resolvedDeployTarget: 'dokploy',
|
|
342
358
|
},
|
|
343
359
|
},
|
|
344
360
|
services: {},
|
|
@@ -354,4 +370,412 @@ describe('Client Generator', () => {
|
|
|
354
370
|
expect(results[0]?.reason).toBe('No endpoints found in backend');
|
|
355
371
|
});
|
|
356
372
|
});
|
|
373
|
+
|
|
374
|
+
describe('hot reload - hash-based change detection', () => {
|
|
375
|
+
let testDir: string;
|
|
376
|
+
|
|
377
|
+
beforeEach(() => {
|
|
378
|
+
testDir = join(
|
|
379
|
+
tmpdir(),
|
|
380
|
+
`gkm-hotreload-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
381
|
+
);
|
|
382
|
+
mkdirSync(testDir, { recursive: true });
|
|
383
|
+
clearSpecHashCache();
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
afterEach(() => {
|
|
387
|
+
if (existsSync(testDir)) {
|
|
388
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
function createEndpointFile(
|
|
393
|
+
dir: string,
|
|
394
|
+
filename: string,
|
|
395
|
+
exportName: string,
|
|
396
|
+
path: string,
|
|
397
|
+
method: string,
|
|
398
|
+
): void {
|
|
399
|
+
const content = `
|
|
400
|
+
import { e } from '@geekmidas/constructs/endpoints';
|
|
401
|
+
import { z } from 'zod';
|
|
402
|
+
|
|
403
|
+
export const ${exportName} = e
|
|
404
|
+
.${method.toLowerCase()}('${path}')
|
|
405
|
+
.output(z.object({ message: z.string() }))
|
|
406
|
+
.handle(async () => ({ message: 'Hello' }));
|
|
407
|
+
`;
|
|
408
|
+
const { writeFileSync } = require('node:fs');
|
|
409
|
+
const { dirname } = require('node:path');
|
|
410
|
+
mkdirSync(dirname(join(dir, filename)), { recursive: true });
|
|
411
|
+
writeFileSync(join(dir, filename), content);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function createWorkspace(root: string): NormalizedWorkspace {
|
|
415
|
+
return {
|
|
416
|
+
name: 'test',
|
|
417
|
+
root,
|
|
418
|
+
apps: {
|
|
419
|
+
api: {
|
|
420
|
+
type: 'backend',
|
|
421
|
+
path: 'apps/api',
|
|
422
|
+
port: 3000,
|
|
423
|
+
dependencies: [],
|
|
424
|
+
routes: './src/endpoints/**/*.ts',
|
|
425
|
+
resolvedDeployTarget: 'dokploy',
|
|
426
|
+
},
|
|
427
|
+
web: {
|
|
428
|
+
type: 'frontend',
|
|
429
|
+
path: 'apps/web',
|
|
430
|
+
port: 3001,
|
|
431
|
+
dependencies: ['api'],
|
|
432
|
+
client: { output: 'src/api' },
|
|
433
|
+
resolvedDeployTarget: 'dokploy',
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
services: {},
|
|
437
|
+
deploy: { default: 'dokploy' },
|
|
438
|
+
shared: { packages: [] },
|
|
439
|
+
secrets: {},
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
it('should generate client on first call', async () => {
|
|
444
|
+
const apiDir = join(testDir, 'apps/api');
|
|
445
|
+
const webDir = join(testDir, 'apps/web');
|
|
446
|
+
mkdirSync(webDir, { recursive: true });
|
|
447
|
+
|
|
448
|
+
createEndpointFile(
|
|
449
|
+
apiDir,
|
|
450
|
+
'src/endpoints/users.ts',
|
|
451
|
+
'getUsers',
|
|
452
|
+
'/users',
|
|
453
|
+
'GET',
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
const workspace = createWorkspace(testDir);
|
|
457
|
+
const results = await generateClientForFrontend(workspace, 'web', {
|
|
458
|
+
force: true,
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
expect(results).toHaveLength(1);
|
|
462
|
+
expect(results[0]?.generated).toBe(true);
|
|
463
|
+
expect(results[0]?.endpointCount).toBe(1);
|
|
464
|
+
expect(existsSync(join(webDir, 'src/api/openapi.ts'))).toBe(true);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it('should skip regeneration when schema has not changed', async () => {
|
|
468
|
+
const apiDir = join(testDir, 'apps/api');
|
|
469
|
+
const webDir = join(testDir, 'apps/web');
|
|
470
|
+
mkdirSync(webDir, { recursive: true });
|
|
471
|
+
|
|
472
|
+
createEndpointFile(
|
|
473
|
+
apiDir,
|
|
474
|
+
'src/endpoints/users.ts',
|
|
475
|
+
'getUsers',
|
|
476
|
+
'/users',
|
|
477
|
+
'GET',
|
|
478
|
+
);
|
|
479
|
+
|
|
480
|
+
const workspace = createWorkspace(testDir);
|
|
481
|
+
|
|
482
|
+
// First call - should generate
|
|
483
|
+
const firstResults = await generateClientForFrontend(workspace, 'web', {
|
|
484
|
+
force: true,
|
|
485
|
+
});
|
|
486
|
+
expect(firstResults[0]?.generated).toBe(true);
|
|
487
|
+
|
|
488
|
+
// Second call - should skip (no changes)
|
|
489
|
+
const secondResults = await generateClientForFrontend(workspace, 'web');
|
|
490
|
+
expect(secondResults[0]?.generated).toBe(false);
|
|
491
|
+
expect(secondResults[0]?.reason).toBe('No schema changes detected');
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
it('should regenerate when endpoint schema changes', async () => {
|
|
495
|
+
const apiDir = join(testDir, 'apps/api');
|
|
496
|
+
const webDir = join(testDir, 'apps/web');
|
|
497
|
+
mkdirSync(webDir, { recursive: true });
|
|
498
|
+
|
|
499
|
+
createEndpointFile(
|
|
500
|
+
apiDir,
|
|
501
|
+
'src/endpoints/users.ts',
|
|
502
|
+
'getUsers',
|
|
503
|
+
'/users',
|
|
504
|
+
'GET',
|
|
505
|
+
);
|
|
506
|
+
|
|
507
|
+
const workspace = createWorkspace(testDir);
|
|
508
|
+
|
|
509
|
+
// First call - generate initial client
|
|
510
|
+
const firstResults = await generateClientForFrontend(workspace, 'web', {
|
|
511
|
+
force: true,
|
|
512
|
+
});
|
|
513
|
+
expect(firstResults[0]?.generated).toBe(true);
|
|
514
|
+
|
|
515
|
+
// Second call without changes - should skip
|
|
516
|
+
const secondResults = await generateClientForFrontend(workspace, 'web');
|
|
517
|
+
expect(secondResults[0]?.generated).toBe(false);
|
|
518
|
+
|
|
519
|
+
// Modify endpoint - add new route
|
|
520
|
+
createEndpointFile(
|
|
521
|
+
apiDir,
|
|
522
|
+
'src/endpoints/posts.ts',
|
|
523
|
+
'getPosts',
|
|
524
|
+
'/posts',
|
|
525
|
+
'GET',
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
// Third call - should regenerate due to schema change
|
|
529
|
+
const thirdResults = await generateClientForFrontend(workspace, 'web');
|
|
530
|
+
expect(thirdResults[0]?.generated).toBe(true);
|
|
531
|
+
expect(thirdResults[0]?.endpointCount).toBe(2);
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
it('should regenerate when endpoint path changes (via new file)', async () => {
|
|
535
|
+
const apiDir = join(testDir, 'apps/api');
|
|
536
|
+
const webDir = join(testDir, 'apps/web');
|
|
537
|
+
mkdirSync(webDir, { recursive: true });
|
|
538
|
+
|
|
539
|
+
createEndpointFile(
|
|
540
|
+
apiDir,
|
|
541
|
+
'src/endpoints/users.ts',
|
|
542
|
+
'getUsers',
|
|
543
|
+
'/users',
|
|
544
|
+
'GET',
|
|
545
|
+
);
|
|
546
|
+
|
|
547
|
+
const workspace = createWorkspace(testDir);
|
|
548
|
+
|
|
549
|
+
// First call
|
|
550
|
+
await generateClientForFrontend(workspace, 'web', { force: true });
|
|
551
|
+
|
|
552
|
+
// Second call - skip
|
|
553
|
+
const skipResult = await generateClientForFrontend(workspace, 'web');
|
|
554
|
+
expect(skipResult[0]?.generated).toBe(false);
|
|
555
|
+
|
|
556
|
+
// Add endpoint with different path (new file to avoid ESM cache)
|
|
557
|
+
// This simulates what happens when a developer changes a path -
|
|
558
|
+
// in real dev server, the watcher would reload the module
|
|
559
|
+
createEndpointFile(
|
|
560
|
+
apiDir,
|
|
561
|
+
'src/endpoints/api-users.ts',
|
|
562
|
+
'getApiUsers',
|
|
563
|
+
'/api/users',
|
|
564
|
+
'GET',
|
|
565
|
+
);
|
|
566
|
+
|
|
567
|
+
// Third call - should regenerate because schema changed
|
|
568
|
+
const regenerateResult = await generateClientForFrontend(
|
|
569
|
+
workspace,
|
|
570
|
+
'web',
|
|
571
|
+
);
|
|
572
|
+
expect(regenerateResult[0]?.generated).toBe(true);
|
|
573
|
+
expect(regenerateResult[0]?.endpointCount).toBe(2); // Now has 2 endpoints
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
it('should skip when only handler implementation changes', async () => {
|
|
577
|
+
const apiDir = join(testDir, 'apps/api');
|
|
578
|
+
const webDir = join(testDir, 'apps/web');
|
|
579
|
+
mkdirSync(webDir, { recursive: true });
|
|
580
|
+
|
|
581
|
+
createEndpointFile(
|
|
582
|
+
apiDir,
|
|
583
|
+
'src/endpoints/users.ts',
|
|
584
|
+
'getUsers',
|
|
585
|
+
'/users',
|
|
586
|
+
'GET',
|
|
587
|
+
);
|
|
588
|
+
|
|
589
|
+
const workspace = createWorkspace(testDir);
|
|
590
|
+
|
|
591
|
+
// First call
|
|
592
|
+
await generateClientForFrontend(workspace, 'web', { force: true });
|
|
593
|
+
|
|
594
|
+
// Change only the handler implementation (not the schema)
|
|
595
|
+
const { writeFileSync } = require('node:fs');
|
|
596
|
+
const modifiedContent = `
|
|
597
|
+
import { e } from '@geekmidas/constructs/endpoints';
|
|
598
|
+
import { z } from 'zod';
|
|
599
|
+
|
|
600
|
+
export const getUsers = e
|
|
601
|
+
.get('/users')
|
|
602
|
+
.output(z.object({ message: z.string() }))
|
|
603
|
+
.handle(async () => {
|
|
604
|
+
// Different implementation - added a comment and console.log
|
|
605
|
+
console.log('Getting users');
|
|
606
|
+
return { message: 'Hello World' };
|
|
607
|
+
});
|
|
608
|
+
`;
|
|
609
|
+
writeFileSync(join(apiDir, 'src/endpoints/users.ts'), modifiedContent);
|
|
610
|
+
|
|
611
|
+
// Second call - should skip (schema unchanged, only implementation changed)
|
|
612
|
+
const results = await generateClientForFrontend(workspace, 'web');
|
|
613
|
+
expect(results[0]?.generated).toBe(false);
|
|
614
|
+
expect(results[0]?.reason).toBe('No schema changes detected');
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
it('should regenerate for multiple frontends when backend changes', async () => {
|
|
618
|
+
const apiDir = join(testDir, 'apps/api');
|
|
619
|
+
const webDir = join(testDir, 'apps/web');
|
|
620
|
+
const adminDir = join(testDir, 'apps/admin');
|
|
621
|
+
mkdirSync(webDir, { recursive: true });
|
|
622
|
+
mkdirSync(adminDir, { recursive: true });
|
|
623
|
+
|
|
624
|
+
createEndpointFile(
|
|
625
|
+
apiDir,
|
|
626
|
+
'src/endpoints/users.ts',
|
|
627
|
+
'getUsers',
|
|
628
|
+
'/users',
|
|
629
|
+
'GET',
|
|
630
|
+
);
|
|
631
|
+
|
|
632
|
+
const workspace: NormalizedWorkspace = {
|
|
633
|
+
name: 'test',
|
|
634
|
+
root: testDir,
|
|
635
|
+
apps: {
|
|
636
|
+
api: {
|
|
637
|
+
type: 'backend',
|
|
638
|
+
path: 'apps/api',
|
|
639
|
+
port: 3000,
|
|
640
|
+
dependencies: [],
|
|
641
|
+
routes: './src/endpoints/**/*.ts',
|
|
642
|
+
resolvedDeployTarget: 'dokploy',
|
|
643
|
+
},
|
|
644
|
+
web: {
|
|
645
|
+
type: 'frontend',
|
|
646
|
+
path: 'apps/web',
|
|
647
|
+
port: 3001,
|
|
648
|
+
dependencies: ['api'],
|
|
649
|
+
client: { output: 'src/api' },
|
|
650
|
+
resolvedDeployTarget: 'dokploy',
|
|
651
|
+
},
|
|
652
|
+
admin: {
|
|
653
|
+
type: 'frontend',
|
|
654
|
+
path: 'apps/admin',
|
|
655
|
+
port: 3002,
|
|
656
|
+
dependencies: ['api'],
|
|
657
|
+
client: { output: 'src/client' },
|
|
658
|
+
resolvedDeployTarget: 'dokploy',
|
|
659
|
+
},
|
|
660
|
+
},
|
|
661
|
+
services: {},
|
|
662
|
+
deploy: { default: 'dokploy' },
|
|
663
|
+
shared: { packages: [] },
|
|
664
|
+
secrets: {},
|
|
665
|
+
};
|
|
666
|
+
|
|
667
|
+
// Generate for both frontends
|
|
668
|
+
await generateClientForFrontend(workspace, 'web', { force: true });
|
|
669
|
+
await generateClientForFrontend(workspace, 'admin', { force: true });
|
|
670
|
+
|
|
671
|
+
// Both should skip
|
|
672
|
+
const webSkip = await generateClientForFrontend(workspace, 'web');
|
|
673
|
+
const adminSkip = await generateClientForFrontend(workspace, 'admin');
|
|
674
|
+
expect(webSkip[0]?.generated).toBe(false);
|
|
675
|
+
expect(adminSkip[0]?.generated).toBe(false);
|
|
676
|
+
|
|
677
|
+
// Add new endpoint
|
|
678
|
+
createEndpointFile(
|
|
679
|
+
apiDir,
|
|
680
|
+
'src/endpoints/products.ts',
|
|
681
|
+
'getProducts',
|
|
682
|
+
'/products',
|
|
683
|
+
'GET',
|
|
684
|
+
);
|
|
685
|
+
|
|
686
|
+
// Both should regenerate
|
|
687
|
+
const webRegen = await generateClientForFrontend(workspace, 'web');
|
|
688
|
+
const adminRegen = await generateClientForFrontend(workspace, 'admin');
|
|
689
|
+
expect(webRegen[0]?.generated).toBe(true);
|
|
690
|
+
expect(adminRegen[0]?.generated).toBe(true);
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
it('should only regenerate affected frontend when specific backend changes', async () => {
|
|
694
|
+
const apiDir = join(testDir, 'apps/api');
|
|
695
|
+
const authDir = join(testDir, 'apps/auth');
|
|
696
|
+
const webDir = join(testDir, 'apps/web');
|
|
697
|
+
const adminDir = join(testDir, 'apps/admin');
|
|
698
|
+
mkdirSync(webDir, { recursive: true });
|
|
699
|
+
mkdirSync(adminDir, { recursive: true });
|
|
700
|
+
|
|
701
|
+
createEndpointFile(
|
|
702
|
+
apiDir,
|
|
703
|
+
'src/endpoints/users.ts',
|
|
704
|
+
'getUsers',
|
|
705
|
+
'/users',
|
|
706
|
+
'GET',
|
|
707
|
+
);
|
|
708
|
+
createEndpointFile(
|
|
709
|
+
authDir,
|
|
710
|
+
'src/endpoints/login.ts',
|
|
711
|
+
'login',
|
|
712
|
+
'/login',
|
|
713
|
+
'POST',
|
|
714
|
+
);
|
|
715
|
+
|
|
716
|
+
const workspace: NormalizedWorkspace = {
|
|
717
|
+
name: 'test',
|
|
718
|
+
root: testDir,
|
|
719
|
+
apps: {
|
|
720
|
+
api: {
|
|
721
|
+
type: 'backend',
|
|
722
|
+
path: 'apps/api',
|
|
723
|
+
port: 3000,
|
|
724
|
+
dependencies: [],
|
|
725
|
+
routes: './src/endpoints/**/*.ts',
|
|
726
|
+
resolvedDeployTarget: 'dokploy',
|
|
727
|
+
},
|
|
728
|
+
auth: {
|
|
729
|
+
type: 'backend',
|
|
730
|
+
path: 'apps/auth',
|
|
731
|
+
port: 3001,
|
|
732
|
+
dependencies: [],
|
|
733
|
+
routes: './src/endpoints/**/*.ts',
|
|
734
|
+
resolvedDeployTarget: 'dokploy',
|
|
735
|
+
},
|
|
736
|
+
web: {
|
|
737
|
+
type: 'frontend',
|
|
738
|
+
path: 'apps/web',
|
|
739
|
+
port: 3002,
|
|
740
|
+
dependencies: ['api'], // Only depends on api
|
|
741
|
+
client: { output: 'src/api' },
|
|
742
|
+
resolvedDeployTarget: 'dokploy',
|
|
743
|
+
},
|
|
744
|
+
admin: {
|
|
745
|
+
type: 'frontend',
|
|
746
|
+
path: 'apps/admin',
|
|
747
|
+
port: 3003,
|
|
748
|
+
dependencies: ['auth'], // Only depends on auth
|
|
749
|
+
client: { output: 'src/client' },
|
|
750
|
+
resolvedDeployTarget: 'dokploy',
|
|
751
|
+
},
|
|
752
|
+
},
|
|
753
|
+
services: {},
|
|
754
|
+
deploy: { default: 'dokploy' },
|
|
755
|
+
shared: { packages: [] },
|
|
756
|
+
secrets: {},
|
|
757
|
+
};
|
|
758
|
+
|
|
759
|
+
// Initial generation
|
|
760
|
+
await generateClientForFrontend(workspace, 'web', { force: true });
|
|
761
|
+
await generateClientForFrontend(workspace, 'admin', { force: true });
|
|
762
|
+
|
|
763
|
+
// Change only the api backend
|
|
764
|
+
createEndpointFile(
|
|
765
|
+
apiDir,
|
|
766
|
+
'src/endpoints/products.ts',
|
|
767
|
+
'getProducts',
|
|
768
|
+
'/products',
|
|
769
|
+
'GET',
|
|
770
|
+
);
|
|
771
|
+
|
|
772
|
+
// web should regenerate (depends on api)
|
|
773
|
+
const webResult = await generateClientForFrontend(workspace, 'web');
|
|
774
|
+
expect(webResult[0]?.generated).toBe(true);
|
|
775
|
+
|
|
776
|
+
// admin should skip (depends on auth, which didn't change)
|
|
777
|
+
const adminResult = await generateClientForFrontend(workspace, 'admin');
|
|
778
|
+
expect(adminResult[0]?.generated).toBe(false);
|
|
779
|
+
});
|
|
780
|
+
});
|
|
357
781
|
});
|