@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/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-BZP8jkI4.cjs');
4
+ const require_openapi = require('./openapi-ZhO7wwya.cjs');
5
5
 
6
6
  exports.OPENAPI_OUTPUT_PATH = require_openapi.OPENAPI_OUTPUT_PATH;
7
7
  exports.generateOpenApi = require_openapi.generateOpenApi;
@@ -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;AA4BhB;AAAqC,iBA9BrB,oBAAA,CA8BqB,MAAA,EA7B5B,SA6B4B,CAAA,EA5BlC,aA4BkC,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"}
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"}
@@ -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;AA4BhB;AAAqC,iBA9BrB,oBAAA,CA8BqB,MAAA,EA7B5B,SA6B4B,CAAA,EA5BlC,aA4BkC,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"}
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-DrbBWq0s.mjs";
4
+ import { OPENAPI_OUTPUT_PATH, generateOpenApi, openapiCommand, resolveOpenApiConfig } from "./openapi-NthphEWK.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.1",
3
+ "version": "1.2.3",
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/errors": "~1.0.0",
58
57
  "@geekmidas/envkit": "~1.0.0",
59
- "@geekmidas/schema": "~1.0.0",
60
- "@geekmidas/logger": "~1.0.0"
58
+ "@geekmidas/errors": "~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 disabled when openapi is undefined', () => {
41
+ it('should return enabled by default when openapi is undefined', () => {
42
42
  const result = resolveOpenApiConfig({ ...baseConfig });
43
- expect(result.enabled).toBe(false);
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 openapi is undefined', async () => {
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
+ });