@geekmidas/cli 1.2.0 → 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 +12 -0
- package/dist/index.cjs +29 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +29 -26
- 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 +4 -4
- package/src/__tests__/openapi.spec.ts +385 -5
- package/src/deploy/sniffer.ts +1 -1
- package/src/dev/index.ts +4 -3
- package/src/generators/OpenApiTsGenerator.ts +0 -1
- package/src/init/generators/ui.ts +20 -20
- package/src/init/generators/web.ts +2 -0
- package/src/openapi.ts +2 -1
- package/src/workspace/__tests__/client-generator.spec.ts +424 -0
package/dist/openapi.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env -S npx tsx
|
|
2
2
|
require('./workspace-BMJE18LV.cjs');
|
|
3
3
|
require('./config-Bayob8pB.cjs');
|
|
4
|
-
const require_openapi = require('./openapi-
|
|
4
|
+
const require_openapi = require('./openapi-CzfnHlhG.cjs');
|
|
5
5
|
|
|
6
6
|
exports.OPENAPI_OUTPUT_PATH = require_openapi.OPENAPI_OUTPUT_PATH;
|
|
7
7
|
exports.generateOpenApi = require_openapi.generateOpenApi;
|
package/dist/openapi.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openapi.d.cts","names":[],"sources":["../src/openapi.ts"],"sourcesContent":[],"mappings":";;;;UASU,cAAA;;AAFiD;AAS3D;AAKA;;AACS,cANI,mBAAA,GAMJ,mBAAA;;AACO;
|
|
1
|
+
{"version":3,"file":"openapi.d.cts","names":[],"sources":["../src/openapi.ts"],"sourcesContent":[],"mappings":";;;;UASU,cAAA;;AAFiD;AAS3D;AAKA;;AACS,cANI,mBAAA,GAMJ,mBAAA;;AACO;AA6BhB;AAAqC,iBA/BrB,oBAAA,CA+BqB,MAAA,EA9B5B,SA8B4B,CAAA,EA7BlC,aA6BkC,GAAA;EAAA,OAC5B,EAAA,OAAA;CAAS;AAER;AAkCV;;;AAEG,iBAvCmB,eAAA,CAuCnB,MAAA,EAtCM,SAsCN,EAAA,QAAA,EAAA;EAAO,MAAA,CAAA,EAAA,OAAA;IApCP;;;;iBAkCmB,cAAA,WACZ,iBACP"}
|
package/dist/openapi.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openapi.d.mts","names":[],"sources":["../src/openapi.ts"],"sourcesContent":[],"mappings":";;;;UASU,cAAA;;AAFiD;AAS3D;AAKA;;AACS,cANI,mBAAA,GAMJ,mBAAA;;AACO;
|
|
1
|
+
{"version":3,"file":"openapi.d.mts","names":[],"sources":["../src/openapi.ts"],"sourcesContent":[],"mappings":";;;;UASU,cAAA;;AAFiD;AAS3D;AAKA;;AACS,cANI,mBAAA,GAMJ,mBAAA;;AACO;AA6BhB;AAAqC,iBA/BrB,oBAAA,CA+BqB,MAAA,EA9B5B,SA8B4B,CAAA,EA7BlC,aA6BkC,GAAA;EAAA,OAC5B,EAAA,OAAA;CAAS;AAER;AAkCV;;;AAEG,iBAvCmB,eAAA,CAuCnB,MAAA,EAtCM,SAsCN,EAAA,QAAA,EAAA;EAAO,MAAA,CAAA,EAAA,OAAA;IApCP;;;;iBAkCmB,cAAA,WACZ,iBACP"}
|
package/dist/openapi.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env -S npx tsx
|
|
2
2
|
import "./workspace-CASoZOjs.mjs";
|
|
3
3
|
import "./config-BQ4a36Rq.mjs";
|
|
4
|
-
import { OPENAPI_OUTPUT_PATH, generateOpenApi, openapiCommand, resolveOpenApiConfig } from "./openapi-
|
|
4
|
+
import { OPENAPI_OUTPUT_PATH, generateOpenApi, openapiCommand, resolveOpenApiConfig } from "./openapi-BZ4Qik9w.mjs";
|
|
5
5
|
|
|
6
6
|
export { OPENAPI_OUTPUT_PATH, generateOpenApi, openapiCommand, resolveOpenApiConfig };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geekmidas/cli",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -54,10 +54,10 @@
|
|
|
54
54
|
"prompts": "~2.4.2",
|
|
55
55
|
"tsx": "~4.20.3",
|
|
56
56
|
"@geekmidas/constructs": "~1.0.0",
|
|
57
|
-
"@geekmidas/envkit": "~1.0.0",
|
|
58
57
|
"@geekmidas/errors": "~1.0.0",
|
|
59
|
-
"@geekmidas/
|
|
60
|
-
"@geekmidas/logger": "~1.0.0"
|
|
58
|
+
"@geekmidas/envkit": "~1.0.0",
|
|
59
|
+
"@geekmidas/logger": "~1.0.0",
|
|
60
|
+
"@geekmidas/schema": "~1.0.0"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@types/lodash.kebabcase": "^4.1.9",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, realpathSync } from 'node:fs';
|
|
2
|
-
import { readFile, rm } from 'node:fs/promises';
|
|
2
|
+
import { mkdir, readFile, rm } from 'node:fs/promises';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
5
|
import {
|
|
@@ -38,9 +38,9 @@ describe('resolveOpenApiConfig', () => {
|
|
|
38
38
|
});
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
-
it('should return
|
|
41
|
+
it('should return enabled by default when openapi is undefined', () => {
|
|
42
42
|
const result = resolveOpenApiConfig({ ...baseConfig });
|
|
43
|
-
expect(result.enabled).toBe(
|
|
43
|
+
expect(result.enabled).toBe(true);
|
|
44
44
|
});
|
|
45
45
|
|
|
46
46
|
it('should use custom config values when provided', () => {
|
|
@@ -111,9 +111,9 @@ describe('generateOpenApi', () => {
|
|
|
111
111
|
expect(result).toBeNull();
|
|
112
112
|
});
|
|
113
113
|
|
|
114
|
-
it('should return null when
|
|
114
|
+
it('should return null when no endpoints are found', async () => {
|
|
115
115
|
const config: GkmConfig = {
|
|
116
|
-
routes: './src/endpoints/**/*.ts',
|
|
116
|
+
routes: './src/endpoints/**/*.ts', // Path doesn't exist
|
|
117
117
|
envParser: './src/config/env#envParser',
|
|
118
118
|
logger: './src/config/logger#logger',
|
|
119
119
|
};
|
|
@@ -468,3 +468,383 @@ export const complexEndpoint = e
|
|
|
468
468
|
expect(content).toContain('export interface paths');
|
|
469
469
|
});
|
|
470
470
|
});
|
|
471
|
+
|
|
472
|
+
describe('openapiCommand - workspace mode', () => {
|
|
473
|
+
let tempDir: string;
|
|
474
|
+
const originalCwd = process.cwd();
|
|
475
|
+
|
|
476
|
+
beforeEach(async () => {
|
|
477
|
+
tempDir = realpathSync(await createTempDir('openapi-workspace-'));
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
afterEach(async () => {
|
|
481
|
+
process.chdir(originalCwd);
|
|
482
|
+
await cleanupDir(tempDir);
|
|
483
|
+
vi.restoreAllMocks();
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
it('should generate OpenAPI for backend app in workspace', async () => {
|
|
487
|
+
// Create workspace structure
|
|
488
|
+
const apiDir = join(tempDir, 'apps/api');
|
|
489
|
+
await mkdir(apiDir, { recursive: true });
|
|
490
|
+
|
|
491
|
+
// Create endpoint in backend app
|
|
492
|
+
await createMockEndpointFile(
|
|
493
|
+
apiDir,
|
|
494
|
+
'src/endpoints/users.ts',
|
|
495
|
+
'getUsers',
|
|
496
|
+
'/users',
|
|
497
|
+
'GET',
|
|
498
|
+
);
|
|
499
|
+
|
|
500
|
+
// Create workspace config (gkm.config.json)
|
|
501
|
+
await createTestFile(
|
|
502
|
+
tempDir,
|
|
503
|
+
'gkm.config.json',
|
|
504
|
+
JSON.stringify({
|
|
505
|
+
name: 'test-workspace',
|
|
506
|
+
apps: {
|
|
507
|
+
api: {
|
|
508
|
+
type: 'backend',
|
|
509
|
+
path: 'apps/api',
|
|
510
|
+
port: 3000,
|
|
511
|
+
routes: './src/endpoints/**/*.ts',
|
|
512
|
+
openapi: { enabled: true },
|
|
513
|
+
},
|
|
514
|
+
},
|
|
515
|
+
}),
|
|
516
|
+
);
|
|
517
|
+
|
|
518
|
+
process.chdir(tempDir);
|
|
519
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
520
|
+
|
|
521
|
+
await openapiCommand({ cwd: tempDir });
|
|
522
|
+
|
|
523
|
+
// Should generate OpenAPI in the backend app's .gkm folder
|
|
524
|
+
const outputPath = join(apiDir, OPENAPI_OUTPUT_PATH);
|
|
525
|
+
expect(existsSync(outputPath)).toBe(true);
|
|
526
|
+
|
|
527
|
+
const content = await readFile(outputPath, 'utf-8');
|
|
528
|
+
expect(content).toContain('export interface paths');
|
|
529
|
+
expect(content).toContain("'/users'");
|
|
530
|
+
|
|
531
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
532
|
+
expect.stringContaining('[api] Generated OpenAPI'),
|
|
533
|
+
);
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
it('should copy OpenAPI to frontend app with client.output', async () => {
|
|
537
|
+
// Create workspace structure
|
|
538
|
+
const apiDir = join(tempDir, 'apps/api');
|
|
539
|
+
const webDir = join(tempDir, 'apps/web');
|
|
540
|
+
await mkdir(apiDir, { recursive: true });
|
|
541
|
+
await mkdir(webDir, { recursive: true });
|
|
542
|
+
|
|
543
|
+
// Create endpoint in backend app
|
|
544
|
+
await createMockEndpointFile(
|
|
545
|
+
apiDir,
|
|
546
|
+
'src/endpoints/users.ts',
|
|
547
|
+
'getUsers',
|
|
548
|
+
'/users',
|
|
549
|
+
'GET',
|
|
550
|
+
);
|
|
551
|
+
|
|
552
|
+
// Create workspace config with frontend that depends on backend
|
|
553
|
+
await createTestFile(
|
|
554
|
+
tempDir,
|
|
555
|
+
'gkm.config.json',
|
|
556
|
+
JSON.stringify({
|
|
557
|
+
name: 'test-workspace',
|
|
558
|
+
apps: {
|
|
559
|
+
api: {
|
|
560
|
+
type: 'backend',
|
|
561
|
+
path: 'apps/api',
|
|
562
|
+
port: 3000,
|
|
563
|
+
routes: './src/endpoints/**/*.ts',
|
|
564
|
+
openapi: { enabled: true },
|
|
565
|
+
},
|
|
566
|
+
web: {
|
|
567
|
+
type: 'frontend',
|
|
568
|
+
framework: 'nextjs',
|
|
569
|
+
path: 'apps/web',
|
|
570
|
+
port: 3001,
|
|
571
|
+
dependencies: ['api'],
|
|
572
|
+
client: {
|
|
573
|
+
output: './src/api',
|
|
574
|
+
},
|
|
575
|
+
},
|
|
576
|
+
},
|
|
577
|
+
}),
|
|
578
|
+
);
|
|
579
|
+
|
|
580
|
+
process.chdir(tempDir);
|
|
581
|
+
const consoleSpy = vi.spyOn(console, 'log');
|
|
582
|
+
|
|
583
|
+
await openapiCommand({ cwd: tempDir });
|
|
584
|
+
|
|
585
|
+
// Should generate OpenAPI in backend app
|
|
586
|
+
const backendOutput = join(apiDir, OPENAPI_OUTPUT_PATH);
|
|
587
|
+
expect(existsSync(backendOutput)).toBe(true);
|
|
588
|
+
|
|
589
|
+
// Should copy to frontend app's client output path
|
|
590
|
+
const frontendOutput = join(webDir, 'src/api/openapi.ts');
|
|
591
|
+
expect(existsSync(frontendOutput)).toBe(true);
|
|
592
|
+
|
|
593
|
+
// Content should be the same
|
|
594
|
+
const backendContent = await readFile(backendOutput, 'utf-8');
|
|
595
|
+
const frontendContent = await readFile(frontendOutput, 'utf-8');
|
|
596
|
+
expect(frontendContent).toBe(backendContent);
|
|
597
|
+
|
|
598
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
599
|
+
expect.stringContaining('[web] ./src/api/openapi.ts'),
|
|
600
|
+
);
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
it('should only copy to frontend apps that depend on the backend', async () => {
|
|
604
|
+
// Create workspace structure with multiple apps
|
|
605
|
+
const apiDir = join(tempDir, 'apps/api');
|
|
606
|
+
const authDir = join(tempDir, 'apps/auth');
|
|
607
|
+
const webDir = join(tempDir, 'apps/web');
|
|
608
|
+
const adminDir = join(tempDir, 'apps/admin');
|
|
609
|
+
await mkdir(apiDir, { recursive: true });
|
|
610
|
+
await mkdir(authDir, { recursive: true });
|
|
611
|
+
await mkdir(webDir, { recursive: true });
|
|
612
|
+
await mkdir(adminDir, { recursive: true });
|
|
613
|
+
|
|
614
|
+
// Create endpoints
|
|
615
|
+
await createMockEndpointFile(
|
|
616
|
+
apiDir,
|
|
617
|
+
'src/endpoints/users.ts',
|
|
618
|
+
'getUsers',
|
|
619
|
+
'/users',
|
|
620
|
+
'GET',
|
|
621
|
+
);
|
|
622
|
+
await createMockEndpointFile(
|
|
623
|
+
authDir,
|
|
624
|
+
'src/endpoints/login.ts',
|
|
625
|
+
'login',
|
|
626
|
+
'/login',
|
|
627
|
+
'POST',
|
|
628
|
+
);
|
|
629
|
+
|
|
630
|
+
// Create workspace config
|
|
631
|
+
await createTestFile(
|
|
632
|
+
tempDir,
|
|
633
|
+
'gkm.config.json',
|
|
634
|
+
JSON.stringify({
|
|
635
|
+
name: 'test-workspace',
|
|
636
|
+
apps: {
|
|
637
|
+
api: {
|
|
638
|
+
type: 'backend',
|
|
639
|
+
path: 'apps/api',
|
|
640
|
+
port: 3000,
|
|
641
|
+
routes: './src/endpoints/**/*.ts',
|
|
642
|
+
openapi: { enabled: true },
|
|
643
|
+
},
|
|
644
|
+
auth: {
|
|
645
|
+
type: 'backend',
|
|
646
|
+
path: 'apps/auth',
|
|
647
|
+
port: 3001,
|
|
648
|
+
routes: './src/endpoints/**/*.ts',
|
|
649
|
+
openapi: { enabled: true },
|
|
650
|
+
},
|
|
651
|
+
web: {
|
|
652
|
+
type: 'frontend',
|
|
653
|
+
framework: 'nextjs',
|
|
654
|
+
path: 'apps/web',
|
|
655
|
+
port: 3002,
|
|
656
|
+
dependencies: ['api'], // Only depends on api
|
|
657
|
+
client: {
|
|
658
|
+
output: './src/api',
|
|
659
|
+
},
|
|
660
|
+
},
|
|
661
|
+
admin: {
|
|
662
|
+
type: 'frontend',
|
|
663
|
+
framework: 'nextjs',
|
|
664
|
+
path: 'apps/admin',
|
|
665
|
+
port: 3003,
|
|
666
|
+
dependencies: ['auth'], // Only depends on auth
|
|
667
|
+
client: {
|
|
668
|
+
output: './src/client',
|
|
669
|
+
},
|
|
670
|
+
},
|
|
671
|
+
},
|
|
672
|
+
}),
|
|
673
|
+
);
|
|
674
|
+
|
|
675
|
+
process.chdir(tempDir);
|
|
676
|
+
|
|
677
|
+
await openapiCommand({ cwd: tempDir });
|
|
678
|
+
|
|
679
|
+
// Web should have api's OpenAPI (not auth's)
|
|
680
|
+
const webApiOutput = join(webDir, 'src/api/openapi.ts');
|
|
681
|
+
expect(existsSync(webApiOutput)).toBe(true);
|
|
682
|
+
const webContent = await readFile(webApiOutput, 'utf-8');
|
|
683
|
+
expect(webContent).toContain("'/users'");
|
|
684
|
+
expect(webContent).not.toContain("'/login'");
|
|
685
|
+
|
|
686
|
+
// Admin should have auth's OpenAPI (not api's)
|
|
687
|
+
const adminClientOutput = join(adminDir, 'src/client/openapi.ts');
|
|
688
|
+
expect(existsSync(adminClientOutput)).toBe(true);
|
|
689
|
+
const adminContent = await readFile(adminClientOutput, 'utf-8');
|
|
690
|
+
expect(adminContent).toContain("'/login'");
|
|
691
|
+
expect(adminContent).not.toContain("'/users'");
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
it('should not copy to frontend with empty dependencies array', async () => {
|
|
695
|
+
// Create workspace structure
|
|
696
|
+
const apiDir = join(tempDir, 'apps/api');
|
|
697
|
+
const webDir = join(tempDir, 'apps/web');
|
|
698
|
+
await mkdir(apiDir, { recursive: true });
|
|
699
|
+
await mkdir(webDir, { recursive: true });
|
|
700
|
+
|
|
701
|
+
await createMockEndpointFile(
|
|
702
|
+
apiDir,
|
|
703
|
+
'src/endpoints/users.ts',
|
|
704
|
+
'getUsers',
|
|
705
|
+
'/users',
|
|
706
|
+
'GET',
|
|
707
|
+
);
|
|
708
|
+
|
|
709
|
+
// Frontend with empty dependencies array (depends on nothing)
|
|
710
|
+
await createTestFile(
|
|
711
|
+
tempDir,
|
|
712
|
+
'gkm.config.json',
|
|
713
|
+
JSON.stringify({
|
|
714
|
+
name: 'test-workspace',
|
|
715
|
+
apps: {
|
|
716
|
+
api: {
|
|
717
|
+
type: 'backend',
|
|
718
|
+
path: 'apps/api',
|
|
719
|
+
port: 3000,
|
|
720
|
+
routes: './src/endpoints/**/*.ts',
|
|
721
|
+
openapi: { enabled: true },
|
|
722
|
+
},
|
|
723
|
+
web: {
|
|
724
|
+
type: 'frontend',
|
|
725
|
+
framework: 'nextjs',
|
|
726
|
+
path: 'apps/web',
|
|
727
|
+
port: 3001,
|
|
728
|
+
dependencies: [], // Empty array means depends on nothing
|
|
729
|
+
client: {
|
|
730
|
+
output: './src/api',
|
|
731
|
+
},
|
|
732
|
+
},
|
|
733
|
+
},
|
|
734
|
+
}),
|
|
735
|
+
);
|
|
736
|
+
|
|
737
|
+
process.chdir(tempDir);
|
|
738
|
+
|
|
739
|
+
await openapiCommand({ cwd: tempDir });
|
|
740
|
+
|
|
741
|
+
// Should NOT copy to frontend since it has no dependencies
|
|
742
|
+
const frontendOutput = join(webDir, 'src/api/openapi.ts');
|
|
743
|
+
expect(existsSync(frontendOutput)).toBe(false);
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
it('should skip frontend apps without client.output', async () => {
|
|
747
|
+
const apiDir = join(tempDir, 'apps/api');
|
|
748
|
+
const webDir = join(tempDir, 'apps/web');
|
|
749
|
+
await mkdir(apiDir, { recursive: true });
|
|
750
|
+
await mkdir(webDir, { recursive: true });
|
|
751
|
+
|
|
752
|
+
await createMockEndpointFile(
|
|
753
|
+
apiDir,
|
|
754
|
+
'src/endpoints/users.ts',
|
|
755
|
+
'getUsers',
|
|
756
|
+
'/users',
|
|
757
|
+
'GET',
|
|
758
|
+
);
|
|
759
|
+
|
|
760
|
+
// Frontend without client.output
|
|
761
|
+
await createTestFile(
|
|
762
|
+
tempDir,
|
|
763
|
+
'gkm.config.json',
|
|
764
|
+
JSON.stringify({
|
|
765
|
+
name: 'test-workspace',
|
|
766
|
+
apps: {
|
|
767
|
+
api: {
|
|
768
|
+
type: 'backend',
|
|
769
|
+
path: 'apps/api',
|
|
770
|
+
port: 3000,
|
|
771
|
+
routes: './src/endpoints/**/*.ts',
|
|
772
|
+
openapi: { enabled: true },
|
|
773
|
+
},
|
|
774
|
+
web: {
|
|
775
|
+
type: 'frontend',
|
|
776
|
+
framework: 'nextjs',
|
|
777
|
+
path: 'apps/web',
|
|
778
|
+
port: 3001,
|
|
779
|
+
dependencies: ['api'],
|
|
780
|
+
// No client.output configured
|
|
781
|
+
},
|
|
782
|
+
},
|
|
783
|
+
}),
|
|
784
|
+
);
|
|
785
|
+
|
|
786
|
+
process.chdir(tempDir);
|
|
787
|
+
|
|
788
|
+
await openapiCommand({ cwd: tempDir });
|
|
789
|
+
|
|
790
|
+
// Backend should still have OpenAPI generated
|
|
791
|
+
expect(existsSync(join(apiDir, OPENAPI_OUTPUT_PATH))).toBe(true);
|
|
792
|
+
|
|
793
|
+
// But no files should be created in frontend
|
|
794
|
+
expect(existsSync(join(webDir, 'src/api/openapi.ts'))).toBe(false);
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
it('should handle nested client.output paths', async () => {
|
|
798
|
+
const apiDir = join(tempDir, 'apps/api');
|
|
799
|
+
const webDir = join(tempDir, 'apps/web');
|
|
800
|
+
await mkdir(apiDir, { recursive: true });
|
|
801
|
+
await mkdir(webDir, { recursive: true });
|
|
802
|
+
|
|
803
|
+
await createMockEndpointFile(
|
|
804
|
+
apiDir,
|
|
805
|
+
'src/endpoints/users.ts',
|
|
806
|
+
'getUsers',
|
|
807
|
+
'/users',
|
|
808
|
+
'GET',
|
|
809
|
+
);
|
|
810
|
+
|
|
811
|
+
// Deeply nested client output path
|
|
812
|
+
await createTestFile(
|
|
813
|
+
tempDir,
|
|
814
|
+
'gkm.config.json',
|
|
815
|
+
JSON.stringify({
|
|
816
|
+
name: 'test-workspace',
|
|
817
|
+
apps: {
|
|
818
|
+
api: {
|
|
819
|
+
type: 'backend',
|
|
820
|
+
path: 'apps/api',
|
|
821
|
+
port: 3000,
|
|
822
|
+
routes: './src/endpoints/**/*.ts',
|
|
823
|
+
openapi: { enabled: true },
|
|
824
|
+
},
|
|
825
|
+
web: {
|
|
826
|
+
type: 'frontend',
|
|
827
|
+
framework: 'nextjs',
|
|
828
|
+
path: 'apps/web',
|
|
829
|
+
port: 3001,
|
|
830
|
+
dependencies: ['api'],
|
|
831
|
+
client: {
|
|
832
|
+
output: './src/lib/api/generated',
|
|
833
|
+
},
|
|
834
|
+
},
|
|
835
|
+
},
|
|
836
|
+
}),
|
|
837
|
+
);
|
|
838
|
+
|
|
839
|
+
process.chdir(tempDir);
|
|
840
|
+
|
|
841
|
+
await openapiCommand({ cwd: tempDir });
|
|
842
|
+
|
|
843
|
+
// Should create nested directories and file
|
|
844
|
+
const frontendOutput = join(webDir, 'src/lib/api/generated/openapi.ts');
|
|
845
|
+
expect(existsSync(frontendOutput)).toBe(true);
|
|
846
|
+
|
|
847
|
+
const content = await readFile(frontendOutput, 'utf-8');
|
|
848
|
+
expect(content).toContain('export interface paths');
|
|
849
|
+
});
|
|
850
|
+
});
|
package/src/deploy/sniffer.ts
CHANGED
package/src/dev/index.ts
CHANGED
|
@@ -1294,11 +1294,12 @@ function generateCredentialsInjection(secretsJsonPath: string): string {
|
|
|
1294
1294
|
return `import { Credentials } from '@geekmidas/envkit/credentials';
|
|
1295
1295
|
import { existsSync, readFileSync } from 'node:fs';
|
|
1296
1296
|
|
|
1297
|
-
// Inject dev secrets into Credentials
|
|
1297
|
+
// Inject dev secrets into Credentials and process.env
|
|
1298
1298
|
const secretsPath = '${secretsJsonPath}';
|
|
1299
1299
|
if (existsSync(secretsPath)) {
|
|
1300
1300
|
const secrets = JSON.parse(readFileSync(secretsPath, 'utf-8'));
|
|
1301
1301
|
Object.assign(Credentials, secrets);
|
|
1302
|
+
Object.assign(process.env, secrets);
|
|
1302
1303
|
// Debug: uncomment to verify preload is running
|
|
1303
1304
|
// console.log('[gkm preload] Injected', Object.keys(secrets).length, 'credentials');
|
|
1304
1305
|
}
|
|
@@ -1911,8 +1912,8 @@ export async function execCommand(
|
|
|
1911
1912
|
// Merge NODE_OPTIONS with existing value (if any)
|
|
1912
1913
|
// Add tsx loader first so our .ts preload can be loaded
|
|
1913
1914
|
const existingNodeOptions = process.env.NODE_OPTIONS ?? '';
|
|
1914
|
-
const tsxImport = '--import
|
|
1915
|
-
const preloadImport = `--import
|
|
1915
|
+
const tsxImport = '--import=tsx';
|
|
1916
|
+
const preloadImport = `--import=${preloadPath}`;
|
|
1916
1917
|
|
|
1917
1918
|
// Build NODE_OPTIONS: existing + tsx loader + our preload
|
|
1918
1919
|
const nodeOptions = [existingNodeOptions, tsxImport, preloadImport]
|
|
@@ -722,7 +722,6 @@ export function createApi(options: CreateApiOptions) {
|
|
|
722
722
|
`;
|
|
723
723
|
|
|
724
724
|
return `// Auto-generated by @geekmidas/cli - DO NOT EDIT
|
|
725
|
-
// Generated: ${new Date().toISOString()}
|
|
726
725
|
|
|
727
726
|
// ============================================================
|
|
728
727
|
// Security Scheme Type
|
|
@@ -317,7 +317,7 @@ export { Button, buttonVariants };
|
|
|
317
317
|
|
|
318
318
|
// src/components/ui/button/button.stories.tsx
|
|
319
319
|
const buttonStories = `import type { Meta, StoryObj } from '@storybook/react';
|
|
320
|
-
import { Button } from '
|
|
320
|
+
import { Button } from '~/components/ui/button';
|
|
321
321
|
|
|
322
322
|
const meta: Meta<typeof Button> = {
|
|
323
323
|
title: 'Components/Button',
|
|
@@ -488,7 +488,7 @@ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
|
|
488
488
|
|
|
489
489
|
// src/components/ui/input/input.stories.tsx
|
|
490
490
|
const inputStories = `import type { Meta, StoryObj } from '@storybook/react';
|
|
491
|
-
import { Input } from '
|
|
491
|
+
import { Input } from '~/components/ui/input';
|
|
492
492
|
|
|
493
493
|
const meta: Meta<typeof Input> = {
|
|
494
494
|
title: 'Components/Input',
|
|
@@ -544,7 +544,7 @@ export const WithValue: Story = {
|
|
|
544
544
|
|
|
545
545
|
// src/components/ui/card/card.stories.tsx
|
|
546
546
|
const cardStories = `import type { Meta, StoryObj } from '@storybook/react';
|
|
547
|
-
import { Button } from '
|
|
547
|
+
import { Button } from '~/components/ui/button';
|
|
548
548
|
import {
|
|
549
549
|
Card,
|
|
550
550
|
CardContent,
|
|
@@ -552,8 +552,8 @@ import {
|
|
|
552
552
|
CardFooter,
|
|
553
553
|
CardHeader,
|
|
554
554
|
CardTitle,
|
|
555
|
-
} from '
|
|
556
|
-
import { Input } from '
|
|
555
|
+
} from '~/components/ui/card';
|
|
556
|
+
import { Input } from '~/components/ui/input';
|
|
557
557
|
|
|
558
558
|
const meta: Meta<typeof Card> = {
|
|
559
559
|
title: 'Components/Card',
|
|
@@ -635,8 +635,8 @@ export { Label };
|
|
|
635
635
|
|
|
636
636
|
// src/components/ui/label/label.stories.tsx
|
|
637
637
|
const labelStories = `import type { Meta, StoryObj } from '@storybook/react';
|
|
638
|
-
import { Input } from '
|
|
639
|
-
import { Label } from '
|
|
638
|
+
import { Input } from '~/components/ui/input';
|
|
639
|
+
import { Label } from '~/components/ui/label';
|
|
640
640
|
|
|
641
641
|
const meta: Meta<typeof Label> = {
|
|
642
642
|
title: 'Components/Label',
|
|
@@ -715,7 +715,7 @@ export { Badge, badgeVariants };
|
|
|
715
715
|
|
|
716
716
|
// src/components/ui/badge/badge.stories.tsx
|
|
717
717
|
const badgeStories = `import type { Meta, StoryObj } from '@storybook/react';
|
|
718
|
-
import { Badge } from '
|
|
718
|
+
import { Badge } from '~/components/ui/badge';
|
|
719
719
|
|
|
720
720
|
const meta: Meta<typeof Badge> = {
|
|
721
721
|
title: 'Components/Badge',
|
|
@@ -795,7 +795,7 @@ export { Separator };
|
|
|
795
795
|
|
|
796
796
|
// src/components/ui/separator/separator.stories.tsx
|
|
797
797
|
const separatorStories = `import type { Meta, StoryObj } from '@storybook/react';
|
|
798
|
-
import { Separator } from '
|
|
798
|
+
import { Separator } from '~/components/ui/separator';
|
|
799
799
|
|
|
800
800
|
const meta: Meta<typeof Separator> = {
|
|
801
801
|
title: 'Components/Separator',
|
|
@@ -904,11 +904,11 @@ export { Tabs, TabsList, TabsTrigger, TabsContent };
|
|
|
904
904
|
|
|
905
905
|
// src/components/ui/tabs/tabs.stories.tsx
|
|
906
906
|
const tabsStories = `import type { Meta, StoryObj } from '@storybook/react';
|
|
907
|
-
import { Tabs, TabsContent, TabsList, TabsTrigger } from '
|
|
908
|
-
import { Button } from '
|
|
909
|
-
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '
|
|
910
|
-
import { Input } from '
|
|
911
|
-
import { Label } from '
|
|
907
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from '~/components/ui/tabs';
|
|
908
|
+
import { Button } from '~/components/ui/button';
|
|
909
|
+
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '~/components/ui/card';
|
|
910
|
+
import { Input } from '~/components/ui/input';
|
|
911
|
+
import { Label } from '~/components/ui/label';
|
|
912
912
|
|
|
913
913
|
const meta: Meta<typeof Tabs> = {
|
|
914
914
|
title: 'Components/Tabs',
|
|
@@ -1012,8 +1012,8 @@ export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
|
|
1012
1012
|
|
|
1013
1013
|
// src/components/ui/tooltip/tooltip.stories.tsx
|
|
1014
1014
|
const tooltipStories = `import type { Meta, StoryObj } from '@storybook/react';
|
|
1015
|
-
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '
|
|
1016
|
-
import { Button } from '
|
|
1015
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '~/components/ui/tooltip';
|
|
1016
|
+
import { Button } from '~/components/ui/button';
|
|
1017
1017
|
|
|
1018
1018
|
const meta: Meta<typeof Tooltip> = {
|
|
1019
1019
|
title: 'Components/Tooltip',
|
|
@@ -1217,10 +1217,10 @@ import {
|
|
|
1217
1217
|
DialogHeader,
|
|
1218
1218
|
DialogTitle,
|
|
1219
1219
|
DialogTrigger,
|
|
1220
|
-
} from '
|
|
1221
|
-
import { Button } from '
|
|
1222
|
-
import { Input } from '
|
|
1223
|
-
import { Label } from '
|
|
1220
|
+
} from '~/components/ui/dialog';
|
|
1221
|
+
import { Button } from '~/components/ui/button';
|
|
1222
|
+
import { Input } from '~/components/ui/input';
|
|
1223
|
+
import { Label } from '~/components/ui/label';
|
|
1224
1224
|
|
|
1225
1225
|
const meta: Meta<typeof Dialog> = {
|
|
1226
1226
|
title: 'Components/Dialog',
|
|
@@ -29,6 +29,7 @@ export function generateWebAppFiles(options: TemplateOptions): GeneratedFile[] {
|
|
|
29
29
|
[modelsPackage]: 'workspace:*',
|
|
30
30
|
[uiPackage]: 'workspace:*',
|
|
31
31
|
'@geekmidas/client': GEEKMIDAS_VERSIONS['@geekmidas/client'],
|
|
32
|
+
'@geekmidas/envkit': GEEKMIDAS_VERSIONS['@geekmidas/envkit'],
|
|
32
33
|
'@tanstack/react-query': '~5.80.0',
|
|
33
34
|
'better-auth': '~1.2.0',
|
|
34
35
|
next: '~16.1.0',
|
|
@@ -42,6 +43,7 @@ export function generateWebAppFiles(options: TemplateOptions): GeneratedFile[] {
|
|
|
42
43
|
'@types/react': '~19.0.0',
|
|
43
44
|
'@types/react-dom': '~19.0.0',
|
|
44
45
|
tailwindcss: '^4.0.0',
|
|
46
|
+
tsx: '~4.20.0',
|
|
45
47
|
typescript: '~5.8.2',
|
|
46
48
|
},
|
|
47
49
|
};
|
package/src/openapi.ts
CHANGED
|
@@ -27,8 +27,9 @@ export function resolveOpenApiConfig(
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
if (config.openapi === true || config.openapi === undefined) {
|
|
30
|
+
// Enable by default when not explicitly set (undefined) or explicitly true
|
|
30
31
|
return {
|
|
31
|
-
enabled:
|
|
32
|
+
enabled: true,
|
|
32
33
|
title: 'API Documentation',
|
|
33
34
|
version: '1.0.0',
|
|
34
35
|
description: 'Auto-generated API documentation from endpoints',
|