@geekmidas/cli 0.13.0 → 0.14.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 (79) hide show
  1. package/dist/{bundler-B1qy9b-j.cjs → bundler-BjholBlA.cjs} +26 -7
  2. package/dist/bundler-BjholBlA.cjs.map +1 -0
  3. package/dist/{bundler-DskIqW2t.mjs → bundler-DWctKN1z.mjs} +27 -8
  4. package/dist/bundler-DWctKN1z.mjs.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 +1513 -1136
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.mjs +1513 -1136
  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 +3 -3
  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 +1 -1
  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 +53 -4
  54. package/src/build/index.ts +21 -0
  55. package/src/build/types.ts +6 -0
  56. package/src/deploy/__tests__/dokploy-api.spec.ts +698 -0
  57. package/src/deploy/__tests__/dokploy.spec.ts +196 -6
  58. package/src/deploy/__tests__/index.spec.ts +339 -0
  59. package/src/deploy/__tests__/init.spec.ts +147 -16
  60. package/src/deploy/docker.ts +32 -3
  61. package/src/deploy/dokploy-api.ts +581 -0
  62. package/src/deploy/dokploy.ts +66 -93
  63. package/src/deploy/index.ts +587 -32
  64. package/src/deploy/init.ts +192 -249
  65. package/src/deploy/types.ts +19 -1
  66. package/src/dev/__tests__/index.spec.ts +95 -0
  67. package/src/docker/__tests__/templates.spec.ts +144 -0
  68. package/src/docker/index.ts +96 -6
  69. package/src/docker/templates.ts +114 -27
  70. package/src/generators/EndpointGenerator.ts +2 -2
  71. package/src/index.ts +34 -13
  72. package/src/secrets/storage.ts +15 -0
  73. package/src/types.ts +2 -0
  74. package/dist/bundler-B1qy9b-j.cjs.map +0 -1
  75. package/dist/bundler-DskIqW2t.mjs.map +0 -1
  76. package/dist/storage-BOOpAF8N.cjs +0 -5
  77. package/dist/storage-Bj1E26lU.cjs.map +0 -1
  78. package/dist/storage-kSxTjkNb.mjs.map +0 -1
  79. package/dist/storage-tgZSUnKl.mjs +0 -3
@@ -0,0 +1,272 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import type { EndpointAnalysis, EndpointFeatures } from '../endpoint-analyzer';
3
+ import {
4
+ generateOptimizedImports,
5
+ generateValidatorFactories,
6
+ } from '../handler-templates';
7
+
8
+ // Helper to create endpoint features
9
+ function createFeatures(
10
+ overrides: Partial<EndpointFeatures> = {},
11
+ ): EndpointFeatures {
12
+ return {
13
+ hasAuth: false,
14
+ hasServices: false,
15
+ hasDatabase: false,
16
+ hasBodyValidation: false,
17
+ hasQueryValidation: false,
18
+ hasParamValidation: false,
19
+ hasAudits: false,
20
+ hasEvents: false,
21
+ hasRateLimit: false,
22
+ hasRls: false,
23
+ hasOutputValidation: false,
24
+ ...overrides,
25
+ };
26
+ }
27
+
28
+ // Helper to create analysis
29
+ function createAnalysis(
30
+ overrides: Partial<EndpointAnalysis> = {},
31
+ ): EndpointAnalysis {
32
+ return {
33
+ route: '/test',
34
+ method: 'GET',
35
+ exportName: 'testEndpoint',
36
+ tier: 'minimal',
37
+ serviceNames: [],
38
+ features: createFeatures(),
39
+ ...overrides,
40
+ };
41
+ }
42
+
43
+ describe('handler-templates', () => {
44
+ describe('generateOptimizedImports', () => {
45
+ it('should always include base imports', () => {
46
+ const result = generateOptimizedImports([]);
47
+
48
+ expect(result).toContain('import type { EnvironmentParser }');
49
+ expect(result).toContain('import type { Logger }');
50
+ expect(result).toContain('import type { Hono }');
51
+ expect(result).toContain('import { Endpoint }');
52
+ });
53
+
54
+ it('should include validator import when body validation needed', () => {
55
+ const analyses = [
56
+ createAnalysis({
57
+ features: createFeatures({ hasBodyValidation: true }),
58
+ }),
59
+ ];
60
+
61
+ const result = generateOptimizedImports(analyses);
62
+ expect(result).toContain('import { validator }');
63
+ });
64
+
65
+ it('should include validator import when query validation needed', () => {
66
+ const analyses = [
67
+ createAnalysis({
68
+ features: createFeatures({ hasQueryValidation: true }),
69
+ }),
70
+ ];
71
+
72
+ const result = generateOptimizedImports(analyses);
73
+ expect(result).toContain('import { validator }');
74
+ });
75
+
76
+ it('should include validator import when param validation needed', () => {
77
+ const analyses = [
78
+ createAnalysis({
79
+ features: createFeatures({ hasParamValidation: true }),
80
+ }),
81
+ ];
82
+
83
+ const result = generateOptimizedImports(analyses);
84
+ expect(result).toContain('import { validator }');
85
+ });
86
+
87
+ it('should include ResponseBuilder for standard tier', () => {
88
+ const analyses = [
89
+ createAnalysis({
90
+ tier: 'standard',
91
+ features: createFeatures({ hasAuth: true }),
92
+ }),
93
+ ];
94
+
95
+ const result = generateOptimizedImports(analyses);
96
+ expect(result).toContain('import { ResponseBuilder }');
97
+ });
98
+
99
+ it('should include ResponseBuilder for full tier', () => {
100
+ const analyses = [
101
+ createAnalysis({
102
+ tier: 'full',
103
+ features: createFeatures({ hasAudits: true }),
104
+ }),
105
+ ];
106
+
107
+ const result = generateOptimizedImports(analyses);
108
+ expect(result).toContain('import { ResponseBuilder }');
109
+ });
110
+
111
+ it('should include ServiceDiscovery when services are used', () => {
112
+ const analyses = [
113
+ createAnalysis({
114
+ features: createFeatures({ hasServices: true }),
115
+ }),
116
+ ];
117
+
118
+ const result = generateOptimizedImports(analyses);
119
+ expect(result).toContain('import { ServiceDiscovery }');
120
+ });
121
+
122
+ it('should include ServiceDiscovery when database is used', () => {
123
+ const analyses = [
124
+ createAnalysis({
125
+ features: createFeatures({ hasDatabase: true }),
126
+ }),
127
+ ];
128
+
129
+ const result = generateOptimizedImports(analyses);
130
+ expect(result).toContain('import { ServiceDiscovery }');
131
+ });
132
+
133
+ it('should include events import when events are used', () => {
134
+ const analyses = [
135
+ createAnalysis({
136
+ features: createFeatures({ hasEvents: true }),
137
+ }),
138
+ ];
139
+
140
+ const result = generateOptimizedImports(analyses);
141
+ expect(result).toContain('import { publishConstructEvents }');
142
+ });
143
+
144
+ it('should include audit imports when audits are used', () => {
145
+ const analyses = [
146
+ createAnalysis({
147
+ features: createFeatures({ hasAudits: true }),
148
+ }),
149
+ ];
150
+
151
+ const result = generateOptimizedImports(analyses);
152
+ expect(result).toContain('createAuditContext');
153
+ expect(result).toContain('withAuditableEndpointTransaction');
154
+ });
155
+
156
+ it('should include createError when rate limiting is used', () => {
157
+ const analyses = [
158
+ createAnalysis({
159
+ features: createFeatures({ hasRateLimit: true }),
160
+ }),
161
+ ];
162
+
163
+ const result = generateOptimizedImports(analyses);
164
+ expect(result).toContain('import { createError }');
165
+ });
166
+
167
+ it('should include RLS imports when RLS is used', () => {
168
+ const analyses = [
169
+ createAnalysis({
170
+ features: createFeatures({ hasRls: true }),
171
+ }),
172
+ ];
173
+
174
+ const result = generateOptimizedImports(analyses);
175
+ expect(result).toContain('withRlsContext');
176
+ expect(result).toContain('extractRlsContext');
177
+ });
178
+
179
+ it('should not include optional imports when not needed', () => {
180
+ const analyses = [createAnalysis()];
181
+
182
+ const result = generateOptimizedImports(analyses);
183
+ expect(result).not.toContain('import { validator }');
184
+ expect(result).not.toContain('ServiceDiscovery');
185
+ expect(result).not.toContain('publishConstructEvents');
186
+ expect(result).not.toContain('createAuditContext');
187
+ expect(result).not.toContain('createError');
188
+ expect(result).not.toContain('withRlsContext');
189
+ });
190
+ });
191
+
192
+ describe('generateValidatorFactories', () => {
193
+ it('should return empty string when no validation needed', () => {
194
+ const result = generateValidatorFactories([createAnalysis()]);
195
+ expect(result).toBe('');
196
+ });
197
+
198
+ it('should generate body validator when body validation needed', () => {
199
+ const analyses = [
200
+ createAnalysis({
201
+ features: createFeatures({ hasBodyValidation: true }),
202
+ }),
203
+ ];
204
+
205
+ const result = generateValidatorFactories(analyses);
206
+ expect(result).toContain('validateBody');
207
+ expect(result).toContain("validator('json'");
208
+ expect(result).toContain('endpoint.input?.body');
209
+ });
210
+
211
+ it('should generate query validator when query validation needed', () => {
212
+ const analyses = [
213
+ createAnalysis({
214
+ features: createFeatures({ hasQueryValidation: true }),
215
+ }),
216
+ ];
217
+
218
+ const result = generateValidatorFactories(analyses);
219
+ expect(result).toContain('validateQuery');
220
+ expect(result).toContain("validator('query'");
221
+ expect(result).toContain('endpoint.input?.query');
222
+ });
223
+
224
+ it('should generate params validator when param validation needed', () => {
225
+ const analyses = [
226
+ createAnalysis({
227
+ features: createFeatures({ hasParamValidation: true }),
228
+ }),
229
+ ];
230
+
231
+ const result = generateValidatorFactories(analyses);
232
+ expect(result).toContain('validateParams');
233
+ expect(result).toContain("validator('param'");
234
+ expect(result).toContain('endpoint.input?.params');
235
+ });
236
+
237
+ it('should generate all validators when all validation types needed', () => {
238
+ const analyses = [
239
+ createAnalysis({
240
+ features: createFeatures({
241
+ hasBodyValidation: true,
242
+ hasQueryValidation: true,
243
+ hasParamValidation: true,
244
+ }),
245
+ }),
246
+ ];
247
+
248
+ const result = generateValidatorFactories(analyses);
249
+ expect(result).toContain('validateBody');
250
+ expect(result).toContain('validateQuery');
251
+ expect(result).toContain('validateParams');
252
+ });
253
+
254
+ it('should handle multiple endpoints with different validation', () => {
255
+ const analyses = [
256
+ createAnalysis({
257
+ exportName: 'endpoint1',
258
+ features: createFeatures({ hasBodyValidation: true }),
259
+ }),
260
+ createAnalysis({
261
+ exportName: 'endpoint2',
262
+ features: createFeatures({ hasQueryValidation: true }),
263
+ }),
264
+ ];
265
+
266
+ const result = generateValidatorFactories(analyses);
267
+ expect(result).toContain('validateBody');
268
+ expect(result).toContain('validateQuery');
269
+ expect(result).not.toContain('validateParams');
270
+ });
271
+ });
272
+ });
@@ -1,4 +1,4 @@
1
- import { execSync } from 'node:child_process';
1
+ import { spawnSync } from 'node:child_process';
2
2
  import { existsSync } from 'node:fs';
3
3
  import { mkdir, rename, writeFile } from 'node:fs/promises';
4
4
  import { join } from 'node:path';
@@ -19,6 +19,12 @@ export interface BundleOptions {
19
19
  stage?: string;
20
20
  /** Constructs to validate environment variables for */
21
21
  constructs?: Construct[];
22
+ /** Docker compose services configured (for auto-populating env vars) */
23
+ dockerServices?: {
24
+ postgres?: boolean;
25
+ redis?: boolean;
26
+ rabbitmq?: boolean;
27
+ };
22
28
  }
23
29
 
24
30
  export interface BundleResult {
@@ -54,6 +60,19 @@ async function collectRequiredEnvVars(
54
60
  * @param options - Bundle configuration options
55
61
  * @returns Bundle result with output path and optional master key
56
62
  */
63
+ /** Default env var values for docker compose services */
64
+ const DOCKER_SERVICE_ENV_VARS: Record<string, Record<string, string>> = {
65
+ postgres: {
66
+ DATABASE_URL: 'postgresql://postgres:postgres@postgres:5432/app',
67
+ },
68
+ redis: {
69
+ REDIS_URL: 'redis://redis:6379',
70
+ },
71
+ rabbitmq: {
72
+ RABBITMQ_URL: 'amqp://rabbitmq:5672',
73
+ },
74
+ };
75
+
57
76
  export async function bundleServer(
58
77
  options: BundleOptions,
59
78
  ): Promise<BundleResult> {
@@ -65,6 +84,7 @@ export async function bundleServer(
65
84
  external,
66
85
  stage,
67
86
  constructs,
87
+ dockerServices,
68
88
  } = options;
69
89
 
70
90
  // Ensure output directory exists
@@ -124,6 +144,24 @@ export async function bundleServer(
124
144
  );
125
145
  }
126
146
 
147
+ // Auto-populate env vars from docker compose services
148
+ if (dockerServices) {
149
+ for (const [service, enabled] of Object.entries(dockerServices)) {
150
+ if (enabled && DOCKER_SERVICE_ENV_VARS[service]) {
151
+ for (const [envVar, defaultValue] of Object.entries(
152
+ DOCKER_SERVICE_ENV_VARS[service],
153
+ )) {
154
+ // Check if not already in urls or custom
155
+ const urlKey = envVar as keyof typeof secrets.urls;
156
+ if (!secrets.urls[urlKey] && !secrets.custom[envVar]) {
157
+ secrets.urls[urlKey] = defaultValue;
158
+ console.log(` Auto-populated ${envVar} from docker compose`);
159
+ }
160
+ }
161
+ }
162
+ }
163
+ }
164
+
127
165
  // Validate environment variables if constructs are provided
128
166
  if (constructs && constructs.length > 0) {
129
167
  console.log(' Analyzing environment variable requirements...');
@@ -165,10 +203,10 @@ export async function bundleServer(
165
203
  const encrypted = encryptSecrets(embeddable);
166
204
  masterKey = encrypted.masterKey;
167
205
 
168
- // Add define options for build-time injection
206
+ // Add define options for build-time injection using tsdown's --env.* format
169
207
  const defines = generateDefineOptions(encrypted);
170
208
  for (const [key, value] of Object.entries(defines)) {
171
- args.push('--define', `${key}=${value}`);
209
+ args.push(`--env.${key}`, value);
172
210
  }
173
211
 
174
212
  console.log(` Secrets encrypted for stage "${stage}"`);
@@ -178,11 +216,22 @@ export async function bundleServer(
178
216
 
179
217
  try {
180
218
  // Run tsdown with command-line arguments
181
- execSync(args.join(' '), {
219
+ // Use spawnSync with args array to avoid shell escaping issues with --define values
220
+ // args is always populated with ['npx', 'tsdown', ...] so cmd is never undefined
221
+ const [cmd, ...cmdArgs] = args as [string, ...string[]];
222
+ const result = spawnSync(cmd, cmdArgs, {
182
223
  cwd: process.cwd(),
183
224
  stdio: 'inherit',
225
+ shell: process.platform === 'win32', // Only use shell on Windows for npx resolution
184
226
  });
185
227
 
228
+ if (result.error) {
229
+ throw result.error;
230
+ }
231
+ if (result.status !== 0) {
232
+ throw new Error(`tsdown exited with code ${result.status}`);
233
+ }
234
+
186
235
  // Rename output to .mjs for explicit ESM
187
236
  // tsdown outputs as server.js for ESM format
188
237
  const jsOutput = join(outputDir, 'server.js');
@@ -93,6 +93,22 @@ export async function buildCommand(
93
93
  logger.log(`🪝 Server hooks enabled`);
94
94
  }
95
95
 
96
+ // Extract docker compose services for env var auto-population
97
+ const services = config.docker?.compose?.services;
98
+ const dockerServices = services
99
+ ? Array.isArray(services)
100
+ ? {
101
+ postgres: services.includes('postgres'),
102
+ redis: services.includes('redis'),
103
+ rabbitmq: services.includes('rabbitmq'),
104
+ }
105
+ : {
106
+ postgres: Boolean(services.postgres),
107
+ redis: Boolean(services.redis),
108
+ rabbitmq: Boolean(services.rabbitmq),
109
+ }
110
+ : undefined;
111
+
96
112
  const buildContext: BuildContext = {
97
113
  envParserPath,
98
114
  envParserImportPattern,
@@ -102,6 +118,7 @@ export async function buildCommand(
102
118
  studio,
103
119
  hooks,
104
120
  production,
121
+ dockerServices,
105
122
  };
106
123
 
107
124
  // Initialize generators
@@ -245,6 +262,9 @@ async function buildForProvider(
245
262
  ...subscribers.map((s) => s.construct),
246
263
  ];
247
264
 
265
+ // Get docker compose services for auto-populating env vars
266
+ const dockerServices = context.dockerServices;
267
+
248
268
  const bundleResult = await bundleServer({
249
269
  entryPoint: join(outputDir, 'server.ts'),
250
270
  outputDir: join(outputDir, 'dist'),
@@ -253,6 +273,7 @@ async function buildForProvider(
253
273
  external: context.production.external,
254
274
  stage,
255
275
  constructs: allConstructs,
276
+ dockerServices,
256
277
  });
257
278
  masterKey = bundleResult.masterKey;
258
279
  logger.log(`✅ Bundle complete: .gkm/server/dist/server.mjs`);
@@ -84,6 +84,12 @@ export interface BuildContext {
84
84
  hooks?: NormalizedHooksConfig;
85
85
  /** Production build configuration */
86
86
  production?: NormalizedProductionConfig;
87
+ /** Docker compose services for auto-populating env vars */
88
+ dockerServices?: {
89
+ postgres?: boolean;
90
+ redis?: boolean;
91
+ rabbitmq?: boolean;
92
+ };
87
93
  }
88
94
 
89
95
  export interface ProviderBuildResult {