@geekmidas/cli 0.13.0 → 0.15.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.
Files changed (80) hide show
  1. package/dist/{bundler-DskIqW2t.mjs → bundler-D7cM_FWw.mjs} +34 -10
  2. package/dist/bundler-D7cM_FWw.mjs.map +1 -0
  3. package/dist/{bundler-B1qy9b-j.cjs → bundler-Nuew7Xcn.cjs} +33 -9
  4. package/dist/bundler-Nuew7Xcn.cjs.map +1 -0
  5. package/dist/config.d.cts +1 -1
  6. package/dist/config.d.mts +1 -1
  7. package/dist/dokploy-api-B7KxOQr3.cjs +3 -0
  8. package/dist/dokploy-api-C7F9VykY.cjs +317 -0
  9. package/dist/dokploy-api-C7F9VykY.cjs.map +1 -0
  10. package/dist/dokploy-api-CaETb2L6.mjs +305 -0
  11. package/dist/dokploy-api-CaETb2L6.mjs.map +1 -0
  12. package/dist/dokploy-api-DHvfmWbi.mjs +3 -0
  13. package/dist/{encryption-Dyf_r1h-.cjs → encryption-D7Efcdi9.cjs} +1 -1
  14. package/dist/{encryption-Dyf_r1h-.cjs.map → encryption-D7Efcdi9.cjs.map} +1 -1
  15. package/dist/{encryption-C8H-38Yy.mjs → encryption-h4Nb6W-M.mjs} +1 -1
  16. package/dist/{encryption-C8H-38Yy.mjs.map → encryption-h4Nb6W-M.mjs.map} +1 -1
  17. package/dist/index.cjs +1508 -1073
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.mjs +1508 -1073
  20. package/dist/index.mjs.map +1 -1
  21. package/dist/{openapi-Bt_1FDpT.cjs → openapi-C89hhkZC.cjs} +3 -3
  22. package/dist/{openapi-Bt_1FDpT.cjs.map → openapi-C89hhkZC.cjs.map} +1 -1
  23. package/dist/{openapi-BfFlOBCG.mjs → openapi-CZVcfxk-.mjs} +3 -3
  24. package/dist/{openapi-BfFlOBCG.mjs.map → openapi-CZVcfxk-.mjs.map} +1 -1
  25. package/dist/{openapi-react-query-B6XTeGqS.mjs → openapi-react-query-CM2_qlW9.mjs} +1 -1
  26. package/dist/{openapi-react-query-B6XTeGqS.mjs.map → openapi-react-query-CM2_qlW9.mjs.map} +1 -1
  27. package/dist/{openapi-react-query-B-sNWHFU.cjs → openapi-react-query-iKjfLzff.cjs} +1 -1
  28. package/dist/{openapi-react-query-B-sNWHFU.cjs.map → openapi-react-query-iKjfLzff.cjs.map} +1 -1
  29. package/dist/openapi-react-query.cjs +1 -1
  30. package/dist/openapi-react-query.mjs +1 -1
  31. package/dist/openapi.cjs +1 -1
  32. package/dist/openapi.d.cts +1 -1
  33. package/dist/openapi.d.mts +1 -1
  34. package/dist/openapi.mjs +1 -1
  35. package/dist/{storage-kSxTjkNb.mjs → storage-BaOP55oq.mjs} +16 -2
  36. package/dist/storage-BaOP55oq.mjs.map +1 -0
  37. package/dist/{storage-Bj1E26lU.cjs → storage-Bn3K9Ccu.cjs} +21 -1
  38. package/dist/storage-Bn3K9Ccu.cjs.map +1 -0
  39. package/dist/storage-UfyTn7Zm.cjs +7 -0
  40. package/dist/storage-nkGIjeXt.mjs +3 -0
  41. package/dist/{types-BhkZc-vm.d.cts → types-BgaMXsUa.d.cts} +3 -1
  42. package/dist/{types-BR0M2v_c.d.mts.map → types-BgaMXsUa.d.cts.map} +1 -1
  43. package/dist/{types-BR0M2v_c.d.mts → types-iFk5ms7y.d.mts} +3 -1
  44. package/dist/{types-BhkZc-vm.d.cts.map → types-iFk5ms7y.d.mts.map} +1 -1
  45. package/package.json +4 -4
  46. package/src/auth/__tests__/credentials.spec.ts +127 -0
  47. package/src/auth/__tests__/index.spec.ts +69 -0
  48. package/src/auth/credentials.ts +33 -0
  49. package/src/auth/index.ts +57 -50
  50. package/src/build/__tests__/bundler.spec.ts +5 -4
  51. package/src/build/__tests__/endpoint-analyzer.spec.ts +623 -0
  52. package/src/build/__tests__/handler-templates.spec.ts +272 -0
  53. package/src/build/bundler.ts +61 -8
  54. package/src/build/index.ts +21 -0
  55. package/src/build/types.ts +6 -0
  56. package/src/deploy/__tests__/docker.spec.ts +44 -6
  57. package/src/deploy/__tests__/dokploy-api.spec.ts +698 -0
  58. package/src/deploy/__tests__/dokploy.spec.ts +196 -6
  59. package/src/deploy/__tests__/index.spec.ts +401 -0
  60. package/src/deploy/__tests__/init.spec.ts +147 -16
  61. package/src/deploy/docker.ts +109 -5
  62. package/src/deploy/dokploy-api.ts +581 -0
  63. package/src/deploy/dokploy.ts +66 -93
  64. package/src/deploy/index.ts +630 -32
  65. package/src/deploy/init.ts +192 -249
  66. package/src/deploy/types.ts +24 -2
  67. package/src/dev/__tests__/index.spec.ts +95 -0
  68. package/src/docker/__tests__/templates.spec.ts +144 -0
  69. package/src/docker/index.ts +96 -6
  70. package/src/docker/templates.ts +114 -27
  71. package/src/generators/EndpointGenerator.ts +2 -2
  72. package/src/index.ts +34 -13
  73. package/src/secrets/storage.ts +15 -0
  74. package/src/types.ts +2 -0
  75. package/dist/bundler-B1qy9b-j.cjs.map +0 -1
  76. package/dist/bundler-DskIqW2t.mjs.map +0 -1
  77. package/dist/storage-BOOpAF8N.cjs +0 -5
  78. package/dist/storage-Bj1E26lU.cjs.map +0 -1
  79. package/dist/storage-kSxTjkNb.mjs.map +0 -1
  80. package/dist/storage-tgZSUnKl.mjs +0 -3
@@ -41,7 +41,7 @@ describe('Dokploy API interactions', () => {
41
41
  method: 'GET',
42
42
  headers: {
43
43
  'Content-Type': 'application/json',
44
- Authorization: 'Bearer test-token',
44
+ 'x-api-key': 'test-token',
45
45
  },
46
46
  },
47
47
  );
@@ -62,11 +62,18 @@ describe('Dokploy API interactions', () => {
62
62
  description?: string;
63
63
  };
64
64
  return HttpResponse.json({
65
- projectId: 'proj_new',
66
- name: body.name,
67
- description: body.description || null,
68
- createdAt: new Date().toISOString(),
69
- adminId: 'admin_1',
65
+ project: {
66
+ projectId: 'proj_new',
67
+ name: body.name,
68
+ description: body.description || null,
69
+ createdAt: new Date().toISOString(),
70
+ adminId: 'admin_1',
71
+ },
72
+ environment: {
73
+ environmentId: 'env_default',
74
+ name: 'production',
75
+ description: 'Production environment',
76
+ },
70
77
  });
71
78
  },
72
79
  ),
@@ -78,7 +85,7 @@ describe('Dokploy API interactions', () => {
78
85
  method: 'POST',
79
86
  headers: {
80
87
  'Content-Type': 'application/json',
81
- Authorization: 'Bearer test-token',
88
+ 'x-api-key': 'test-token',
82
89
  },
83
90
  body: JSON.stringify({
84
91
  name: 'New Project',
@@ -88,9 +95,10 @@ describe('Dokploy API interactions', () => {
88
95
  );
89
96
 
90
97
  expect(response.ok).toBe(true);
91
- const project = await response.json();
92
- expect(project.projectId).toBe('proj_new');
93
- expect(project.name).toBe('New Project');
98
+ const result = await response.json();
99
+ expect(result.project.projectId).toBe('proj_new');
100
+ expect(result.project.name).toBe('New Project');
101
+ expect(result.environment.environmentId).toBe('env_default');
94
102
  });
95
103
 
96
104
  it('should get a single project', async () => {
@@ -120,7 +128,7 @@ describe('Dokploy API interactions', () => {
120
128
  method: 'POST',
121
129
  headers: {
122
130
  'Content-Type': 'application/json',
123
- Authorization: 'Bearer test-token',
131
+ 'x-api-key': 'test-token',
124
132
  },
125
133
  body: JSON.stringify({ projectId: 'proj_1' }),
126
134
  },
@@ -159,7 +167,7 @@ describe('Dokploy API interactions', () => {
159
167
  method: 'POST',
160
168
  headers: {
161
169
  'Content-Type': 'application/json',
162
- Authorization: 'Bearer test-token',
170
+ 'x-api-key': 'test-token',
163
171
  },
164
172
  body: JSON.stringify({
165
173
  name: 'api',
@@ -194,7 +202,7 @@ describe('Dokploy API interactions', () => {
194
202
  method: 'POST',
195
203
  headers: {
196
204
  'Content-Type': 'application/json',
197
- Authorization: 'Bearer test-token',
205
+ 'x-api-key': 'test-token',
198
206
  },
199
207
  body: JSON.stringify({
200
208
  applicationId: 'app_1',
@@ -229,7 +237,7 @@ describe('Dokploy API interactions', () => {
229
237
  method: 'GET',
230
238
  headers: {
231
239
  'Content-Type': 'application/json',
232
- Authorization: 'Bearer test-token',
240
+ 'x-api-key': 'test-token',
233
241
  },
234
242
  },
235
243
  );
@@ -265,7 +273,7 @@ describe('Dokploy API interactions', () => {
265
273
  method: 'POST',
266
274
  headers: {
267
275
  'Content-Type': 'application/json',
268
- Authorization: 'Bearer test-token',
276
+ 'x-api-key': 'test-token',
269
277
  },
270
278
  body: JSON.stringify({
271
279
  projectId: 'proj_1',
@@ -298,7 +306,7 @@ describe('Dokploy API interactions', () => {
298
306
  method: 'GET',
299
307
  headers: {
300
308
  'Content-Type': 'application/json',
301
- Authorization: 'Bearer invalid-token',
309
+ 'x-api-key': 'invalid-token',
302
310
  },
303
311
  },
304
312
  );
@@ -659,4 +667,127 @@ export default defineConfig({
659
667
  ),
660
668
  ).resolves.toBeUndefined();
661
669
  });
670
+
671
+ it('should include registryId when provided', async () => {
672
+ const configPath = join(tempDir, 'gkm.config.ts');
673
+ await writeFile(
674
+ configPath,
675
+ `import { defineConfig } from '@geekmidas/cli';
676
+
677
+ export default defineConfig({
678
+ routes: 'src/endpoints/**/*.ts',
679
+ envParser: './src/env.ts',
680
+ });`,
681
+ );
682
+
683
+ await updateConfig(
684
+ {
685
+ endpoint: 'https://dokploy.example.com',
686
+ projectId: 'proj_123',
687
+ applicationId: 'app_456',
688
+ registryId: 'reg_789',
689
+ },
690
+ tempDir,
691
+ );
692
+
693
+ const content = await readFile(configPath, 'utf-8');
694
+ expect(content).toContain("registryId: 'reg_789'");
695
+ expect(content).toContain("endpoint: 'https://dokploy.example.com'");
696
+ expect(content).toContain("projectId: 'proj_123'");
697
+ expect(content).toContain("applicationId: 'app_456'");
698
+ });
699
+
700
+ it('should omit registryId when not provided', async () => {
701
+ const configPath = join(tempDir, 'gkm.config.ts');
702
+ await writeFile(
703
+ configPath,
704
+ `import { defineConfig } from '@geekmidas/cli';
705
+
706
+ export default defineConfig({
707
+ routes: 'src/endpoints/**/*.ts',
708
+ });`,
709
+ );
710
+
711
+ await updateConfig(
712
+ {
713
+ endpoint: 'https://dokploy.example.com',
714
+ projectId: 'proj_123',
715
+ applicationId: 'app_456',
716
+ },
717
+ tempDir,
718
+ );
719
+
720
+ const content = await readFile(configPath, 'utf-8');
721
+ expect(content).not.toContain('registryId:');
722
+ });
723
+
724
+ it('should update existing dokploy config with registryId', async () => {
725
+ const configPath = join(tempDir, 'gkm.config.ts');
726
+ await writeFile(
727
+ configPath,
728
+ `import { defineConfig } from '@geekmidas/cli';
729
+
730
+ export default defineConfig({
731
+ routes: 'src/endpoints/**/*.ts',
732
+ providers: {
733
+ dokploy: {
734
+ endpoint: 'https://old.dokploy.com',
735
+ projectId: 'old_proj',
736
+ applicationId: 'old_app',
737
+ },
738
+ },
739
+ });`,
740
+ );
741
+
742
+ await updateConfig(
743
+ {
744
+ endpoint: 'https://new.dokploy.com',
745
+ projectId: 'new_proj',
746
+ applicationId: 'new_app',
747
+ registryId: 'new_reg',
748
+ },
749
+ tempDir,
750
+ );
751
+
752
+ const content = await readFile(configPath, 'utf-8');
753
+ expect(content).toContain("registryId: 'new_reg'");
754
+ expect(content).toContain("endpoint: 'https://new.dokploy.com'");
755
+ expect(content).not.toContain('old.dokploy.com');
756
+ });
757
+
758
+ it('should preserve existing config with multi-line dokploy that has registryId', async () => {
759
+ const configPath = join(tempDir, 'gkm.config.ts');
760
+ await writeFile(
761
+ configPath,
762
+ `import { defineConfig } from '@geekmidas/cli';
763
+
764
+ export default defineConfig({
765
+ routes: 'src/endpoints/**/*.ts',
766
+ providers: {
767
+ dokploy: {
768
+ endpoint: 'https://old.dokploy.com',
769
+ projectId: 'old_proj',
770
+ applicationId: 'old_app',
771
+ registryId: 'old_reg',
772
+ },
773
+ },
774
+ });`,
775
+ );
776
+
777
+ await updateConfig(
778
+ {
779
+ endpoint: 'https://new.dokploy.com',
780
+ projectId: 'new_proj',
781
+ applicationId: 'new_app',
782
+ registryId: 'new_reg',
783
+ },
784
+ tempDir,
785
+ );
786
+
787
+ const content = await readFile(configPath, 'utf-8');
788
+ expect(content).toContain("registryId: 'new_reg'");
789
+ expect(content).toContain("endpoint: 'https://new.dokploy.com'");
790
+ expect(content).not.toContain('old.dokploy.com');
791
+ expect(content).not.toContain('old_reg');
792
+ });
662
793
  });
@@ -1,7 +1,68 @@
1
1
  import { execSync } from 'node:child_process';
2
- import type { GkmConfig } from '../types';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { dirname, join, relative } from 'node:path';
4
+ import type { GkmConfig } from '../config';
5
+ import { dockerCommand, findLockfilePath, isMonorepo } from '../docker';
3
6
  import type { DeployResult, DockerDeployConfig } from './types';
4
7
 
8
+ /**
9
+ * Get app name from package.json in the current working directory
10
+ * Used for Dokploy app/project naming
11
+ */
12
+ export function getAppNameFromCwd(): string | undefined {
13
+ const packageJsonPath = join(process.cwd(), 'package.json');
14
+
15
+ if (!existsSync(packageJsonPath)) {
16
+ return undefined;
17
+ }
18
+
19
+ try {
20
+ const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
21
+ if (pkg.name) {
22
+ // Strip org scope if present (e.g., @myorg/app -> app)
23
+ return pkg.name.replace(/^@[^/]+\//, '');
24
+ }
25
+ } catch {
26
+ // Ignore parse errors
27
+ }
28
+
29
+ return undefined;
30
+ }
31
+
32
+ /**
33
+ * Get app name from package.json adjacent to the lockfile (project root)
34
+ * Used for Docker image naming
35
+ */
36
+ export function getAppNameFromPackageJson(): string | undefined {
37
+ const cwd = process.cwd();
38
+
39
+ // Find the lockfile to determine the project root
40
+ const lockfilePath = findLockfilePath(cwd);
41
+ if (!lockfilePath) {
42
+ return undefined;
43
+ }
44
+
45
+ // Use the package.json adjacent to the lockfile
46
+ const projectRoot = dirname(lockfilePath);
47
+ const packageJsonPath = join(projectRoot, 'package.json');
48
+
49
+ if (!existsSync(packageJsonPath)) {
50
+ return undefined;
51
+ }
52
+
53
+ try {
54
+ const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
55
+ if (pkg.name) {
56
+ // Strip org scope if present (e.g., @myorg/app -> app)
57
+ return pkg.name.replace(/^@[^/]+\//, '');
58
+ }
59
+ } catch {
60
+ // Ignore parse errors
61
+ }
62
+
63
+ return undefined;
64
+ }
65
+
5
66
  const logger = console;
6
67
 
7
68
  export interface DockerDeployOptions {
@@ -37,11 +98,39 @@ export function getImageRef(
37
98
  async function buildImage(imageRef: string): Promise<void> {
38
99
  logger.log(`\n🔨 Building Docker image: ${imageRef}`);
39
100
 
101
+ const cwd = process.cwd();
102
+ const inMonorepo = isMonorepo(cwd);
103
+
104
+ // Generate appropriate Dockerfile
105
+ if (inMonorepo) {
106
+ logger.log(' Generating Dockerfile for monorepo (turbo prune)...');
107
+ } else {
108
+ logger.log(' Generating Dockerfile...');
109
+ }
110
+ await dockerCommand({});
111
+
112
+ // Determine build context and Dockerfile path
113
+ let buildCwd = cwd;
114
+ let dockerfilePath = '.gkm/docker/Dockerfile';
115
+
116
+ if (inMonorepo) {
117
+ // For monorepos, build from root so turbo prune can access all packages
118
+ const lockfilePath = findLockfilePath(cwd);
119
+ if (lockfilePath) {
120
+ const monorepoRoot = dirname(lockfilePath);
121
+ const appRelPath = relative(monorepoRoot, cwd);
122
+ dockerfilePath = join(appRelPath, '.gkm/docker/Dockerfile');
123
+ buildCwd = monorepoRoot;
124
+ logger.log(` Building from monorepo root: ${monorepoRoot}`);
125
+ }
126
+ }
127
+
40
128
  try {
129
+ // Build for linux/amd64 to ensure compatibility with most cloud servers
41
130
  execSync(
42
- `DOCKER_BUILDKIT=1 docker build -f .gkm/docker/Dockerfile -t ${imageRef} .`,
131
+ `DOCKER_BUILDKIT=1 docker build --platform linux/amd64 -f ${dockerfilePath} -t ${imageRef} .`,
43
132
  {
44
- cwd: process.cwd(),
133
+ cwd: buildCwd,
45
134
  stdio: 'inherit',
46
135
  env: { ...process.env, DOCKER_BUILDKIT: '1' },
47
136
  },
@@ -81,7 +170,8 @@ export async function deployDocker(
81
170
  ): Promise<DeployResult> {
82
171
  const { stage, tag, skipPush, masterKey, config } = options;
83
172
 
84
- const imageName = config.imageName ?? 'app';
173
+ // imageName should always be set by resolveDockerConfig
174
+ const imageName = config.imageName!;
85
175
  const imageRef = getImageRef(config.registry, imageName, tag);
86
176
 
87
177
  // Build image
@@ -119,10 +209,24 @@ export async function deployDocker(
119
209
 
120
210
  /**
121
211
  * Resolve Docker deploy config from gkm config
212
+ * - imageName: from config, or cwd package.json, or 'app' (for Docker image)
213
+ * - projectName: from root package.json, or 'app' (for Dokploy project)
214
+ * - appName: from cwd package.json, or projectName (for Dokploy app within project)
122
215
  */
123
216
  export function resolveDockerConfig(config: GkmConfig): DockerDeployConfig {
217
+ // projectName comes from root package.json (monorepo name)
218
+ const projectName = getAppNameFromPackageJson() ?? 'app';
219
+
220
+ // appName comes from cwd package.json (the app being deployed)
221
+ const appName = getAppNameFromCwd() ?? projectName;
222
+
223
+ // imageName defaults to appName (cwd package.json)
224
+ const imageName = config.docker?.imageName ?? appName;
225
+
124
226
  return {
125
227
  registry: config.docker?.registry,
126
- imageName: config.docker?.imageName,
228
+ imageName,
229
+ projectName,
230
+ appName,
127
231
  };
128
232
  }