@geekmidas/cli 0.12.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.
- package/dist/bundler-BjholBlA.cjs +131 -0
- package/dist/bundler-BjholBlA.cjs.map +1 -0
- package/dist/bundler-DWctKN1z.mjs +130 -0
- package/dist/bundler-DWctKN1z.mjs.map +1 -0
- package/dist/config.d.cts +1 -1
- package/dist/config.d.mts +1 -1
- package/dist/dokploy-api-B7KxOQr3.cjs +3 -0
- package/dist/dokploy-api-C7F9VykY.cjs +317 -0
- package/dist/dokploy-api-C7F9VykY.cjs.map +1 -0
- package/dist/dokploy-api-CaETb2L6.mjs +305 -0
- package/dist/dokploy-api-CaETb2L6.mjs.map +1 -0
- package/dist/dokploy-api-DHvfmWbi.mjs +3 -0
- package/dist/{encryption-Dyf_r1h-.cjs → encryption-D7Efcdi9.cjs} +1 -1
- package/dist/{encryption-Dyf_r1h-.cjs.map → encryption-D7Efcdi9.cjs.map} +1 -1
- package/dist/{encryption-C8H-38Yy.mjs → encryption-h4Nb6W-M.mjs} +1 -1
- package/dist/{encryption-C8H-38Yy.mjs.map → encryption-h4Nb6W-M.mjs.map} +1 -1
- package/dist/index.cjs +1520 -1136
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1520 -1136
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-Bt_1FDpT.cjs → openapi-C89hhkZC.cjs} +3 -3
- package/dist/{openapi-Bt_1FDpT.cjs.map → openapi-C89hhkZC.cjs.map} +1 -1
- package/dist/{openapi-BfFlOBCG.mjs → openapi-CZVcfxk-.mjs} +3 -3
- package/dist/{openapi-BfFlOBCG.mjs.map → openapi-CZVcfxk-.mjs.map} +1 -1
- package/dist/{openapi-react-query-B6XTeGqS.mjs → openapi-react-query-CM2_qlW9.mjs} +1 -1
- package/dist/{openapi-react-query-B6XTeGqS.mjs.map → openapi-react-query-CM2_qlW9.mjs.map} +1 -1
- package/dist/{openapi-react-query-B-sNWHFU.cjs → openapi-react-query-iKjfLzff.cjs} +1 -1
- package/dist/{openapi-react-query-B-sNWHFU.cjs.map → openapi-react-query-iKjfLzff.cjs.map} +1 -1
- package/dist/openapi-react-query.cjs +1 -1
- package/dist/openapi-react-query.mjs +1 -1
- package/dist/openapi.cjs +1 -1
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/openapi.mjs +1 -1
- package/dist/{storage-C9PU_30f.mjs → storage-BaOP55oq.mjs} +48 -2
- package/dist/storage-BaOP55oq.mjs.map +1 -0
- package/dist/{storage-BXoJvmv2.cjs → storage-Bn3K9Ccu.cjs} +59 -1
- package/dist/storage-Bn3K9Ccu.cjs.map +1 -0
- package/dist/storage-UfyTn7Zm.cjs +7 -0
- package/dist/storage-nkGIjeXt.mjs +3 -0
- package/dist/{types-BR0M2v_c.d.mts → types-BgaMXsUa.d.cts} +3 -1
- package/dist/{types-BR0M2v_c.d.mts.map → types-BgaMXsUa.d.cts.map} +1 -1
- package/dist/{types-BhkZc-vm.d.cts → types-iFk5ms7y.d.mts} +3 -1
- package/dist/{types-BhkZc-vm.d.cts.map → types-iFk5ms7y.d.mts.map} +1 -1
- package/package.json +4 -4
- package/src/auth/__tests__/credentials.spec.ts +127 -0
- package/src/auth/__tests__/index.spec.ts +69 -0
- package/src/auth/credentials.ts +33 -0
- package/src/auth/index.ts +57 -50
- package/src/build/__tests__/bundler.spec.ts +444 -0
- package/src/build/__tests__/endpoint-analyzer.spec.ts +623 -0
- package/src/build/__tests__/handler-templates.spec.ts +272 -0
- package/src/build/bundler.ts +126 -8
- package/src/build/index.ts +31 -0
- package/src/build/types.ts +6 -0
- package/src/deploy/__tests__/dokploy-api.spec.ts +698 -0
- package/src/deploy/__tests__/dokploy.spec.ts +196 -6
- package/src/deploy/__tests__/index.spec.ts +339 -0
- package/src/deploy/__tests__/init.spec.ts +147 -16
- package/src/deploy/docker.ts +32 -3
- package/src/deploy/dokploy-api.ts +581 -0
- package/src/deploy/dokploy.ts +66 -93
- package/src/deploy/index.ts +587 -32
- package/src/deploy/init.ts +192 -249
- package/src/deploy/types.ts +19 -1
- package/src/dev/__tests__/index.spec.ts +95 -0
- package/src/docker/__tests__/templates.spec.ts +144 -0
- package/src/docker/index.ts +96 -6
- package/src/docker/templates.ts +114 -27
- package/src/generators/EndpointGenerator.ts +2 -2
- package/src/index.ts +34 -13
- package/src/secrets/__tests__/storage.spec.ts +208 -0
- package/src/secrets/storage.ts +73 -0
- package/src/types.ts +2 -0
- package/dist/bundler-DRXCw_YR.mjs +0 -70
- package/dist/bundler-DRXCw_YR.mjs.map +0 -1
- package/dist/bundler-WsEvH_b2.cjs +0 -71
- package/dist/bundler-WsEvH_b2.cjs.map +0 -1
- package/dist/storage-BUYQJgz7.cjs +0 -4
- package/dist/storage-BXoJvmv2.cjs.map +0 -1
- package/dist/storage-C9PU_30f.mjs.map +0 -1
- package/dist/storage-DLJAYxzJ.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
|
+
});
|
package/src/build/bundler.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
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';
|
|
5
|
+
import type { Construct } from '@geekmidas/constructs';
|
|
5
6
|
|
|
6
7
|
export interface BundleOptions {
|
|
7
8
|
/** Entry point file (e.g., .gkm/server/server.ts) */
|
|
@@ -16,6 +17,14 @@ export interface BundleOptions {
|
|
|
16
17
|
external: string[];
|
|
17
18
|
/** Stage for secrets injection (optional) */
|
|
18
19
|
stage?: string;
|
|
20
|
+
/** Constructs to validate environment variables for */
|
|
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
|
+
};
|
|
19
28
|
}
|
|
20
29
|
|
|
21
30
|
export interface BundleResult {
|
|
@@ -25,16 +34,58 @@ export interface BundleResult {
|
|
|
25
34
|
masterKey?: string;
|
|
26
35
|
}
|
|
27
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Collect all required environment variables from constructs.
|
|
39
|
+
* Uses the SnifferEnvironmentParser to detect which env vars each service needs.
|
|
40
|
+
*
|
|
41
|
+
* @param constructs - Array of constructs to analyze
|
|
42
|
+
* @returns Deduplicated array of required environment variable names
|
|
43
|
+
*/
|
|
44
|
+
async function collectRequiredEnvVars(
|
|
45
|
+
constructs: Construct[],
|
|
46
|
+
): Promise<string[]> {
|
|
47
|
+
const allEnvVars = new Set<string>();
|
|
48
|
+
|
|
49
|
+
for (const construct of constructs) {
|
|
50
|
+
const envVars = await construct.getEnvironment();
|
|
51
|
+
envVars.forEach((v) => allEnvVars.add(v));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return Array.from(allEnvVars).sort();
|
|
55
|
+
}
|
|
56
|
+
|
|
28
57
|
/**
|
|
29
58
|
* Bundle the server application using tsdown
|
|
30
59
|
*
|
|
31
60
|
* @param options - Bundle configuration options
|
|
32
61
|
* @returns Bundle result with output path and optional master key
|
|
33
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
|
+
|
|
34
76
|
export async function bundleServer(
|
|
35
77
|
options: BundleOptions,
|
|
36
78
|
): Promise<BundleResult> {
|
|
37
|
-
const {
|
|
79
|
+
const {
|
|
80
|
+
entryPoint,
|
|
81
|
+
outputDir,
|
|
82
|
+
minify,
|
|
83
|
+
sourcemap,
|
|
84
|
+
external,
|
|
85
|
+
stage,
|
|
86
|
+
constructs,
|
|
87
|
+
dockerServices,
|
|
88
|
+
} = options;
|
|
38
89
|
|
|
39
90
|
// Ensure output directory exists
|
|
40
91
|
await mkdir(outputDir, { recursive: true });
|
|
@@ -76,9 +127,11 @@ export async function bundleServer(
|
|
|
76
127
|
let masterKey: string | undefined;
|
|
77
128
|
|
|
78
129
|
if (stage) {
|
|
79
|
-
const {
|
|
80
|
-
|
|
81
|
-
|
|
130
|
+
const {
|
|
131
|
+
readStageSecrets,
|
|
132
|
+
toEmbeddableSecrets,
|
|
133
|
+
validateEnvironmentVariables,
|
|
134
|
+
} = await import('../secrets/storage');
|
|
82
135
|
const { encryptSecrets, generateDefineOptions } = await import(
|
|
83
136
|
'../secrets/encryption'
|
|
84
137
|
);
|
|
@@ -91,15 +144,69 @@ export async function bundleServer(
|
|
|
91
144
|
);
|
|
92
145
|
}
|
|
93
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
|
+
|
|
165
|
+
// Validate environment variables if constructs are provided
|
|
166
|
+
if (constructs && constructs.length > 0) {
|
|
167
|
+
console.log(' Analyzing environment variable requirements...');
|
|
168
|
+
const requiredVars = await collectRequiredEnvVars(constructs);
|
|
169
|
+
|
|
170
|
+
if (requiredVars.length > 0) {
|
|
171
|
+
const validation = validateEnvironmentVariables(requiredVars, secrets);
|
|
172
|
+
|
|
173
|
+
if (!validation.valid) {
|
|
174
|
+
const errorMessage = [
|
|
175
|
+
`Missing environment variables for stage "${stage}":`,
|
|
176
|
+
'',
|
|
177
|
+
...validation.missing.map((v) => ` ❌ ${v}`),
|
|
178
|
+
'',
|
|
179
|
+
'To fix this, either:',
|
|
180
|
+
` 1. Add the missing variables to .gkm/secrets/${stage}.json using:`,
|
|
181
|
+
` gkm secrets:set <KEY> <VALUE> --stage ${stage}`,
|
|
182
|
+
'',
|
|
183
|
+
` 2. Or import from a JSON file:`,
|
|
184
|
+
` gkm secrets:import secrets.json --stage ${stage}`,
|
|
185
|
+
'',
|
|
186
|
+
'Required variables:',
|
|
187
|
+
...validation.required.map((v) =>
|
|
188
|
+
validation.missing.includes(v) ? ` ❌ ${v}` : ` ✓ ${v}`,
|
|
189
|
+
),
|
|
190
|
+
].join('\n');
|
|
191
|
+
|
|
192
|
+
throw new Error(errorMessage);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
console.log(
|
|
196
|
+
` ✓ All ${requiredVars.length} required environment variables found`,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
94
201
|
// Convert to embeddable format and encrypt
|
|
95
202
|
const embeddable = toEmbeddableSecrets(secrets);
|
|
96
203
|
const encrypted = encryptSecrets(embeddable);
|
|
97
204
|
masterKey = encrypted.masterKey;
|
|
98
205
|
|
|
99
|
-
// Add define options for build-time injection
|
|
206
|
+
// Add define options for build-time injection using tsdown's --env.* format
|
|
100
207
|
const defines = generateDefineOptions(encrypted);
|
|
101
208
|
for (const [key, value] of Object.entries(defines)) {
|
|
102
|
-
args.push(
|
|
209
|
+
args.push(`--env.${key}`, value);
|
|
103
210
|
}
|
|
104
211
|
|
|
105
212
|
console.log(` Secrets encrypted for stage "${stage}"`);
|
|
@@ -109,11 +216,22 @@ export async function bundleServer(
|
|
|
109
216
|
|
|
110
217
|
try {
|
|
111
218
|
// Run tsdown with command-line arguments
|
|
112
|
-
|
|
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, {
|
|
113
223
|
cwd: process.cwd(),
|
|
114
224
|
stdio: 'inherit',
|
|
225
|
+
shell: process.platform === 'win32', // Only use shell on Windows for npx resolution
|
|
115
226
|
});
|
|
116
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
|
+
|
|
117
235
|
// Rename output to .mjs for explicit ESM
|
|
118
236
|
// tsdown outputs as server.js for ESM format
|
|
119
237
|
const jsOutput = join(outputDir, 'server.js');
|
package/src/build/index.ts
CHANGED
|
@@ -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
|
|
@@ -236,6 +253,18 @@ async function buildForProvider(
|
|
|
236
253
|
if (context.production?.bundle && !skipBundle) {
|
|
237
254
|
logger.log(`\n📦 Bundling production server...`);
|
|
238
255
|
const { bundleServer } = await import('./bundler');
|
|
256
|
+
|
|
257
|
+
// Collect all constructs for environment variable validation
|
|
258
|
+
const allConstructs = [
|
|
259
|
+
...endpoints.map((e) => e.construct),
|
|
260
|
+
...functions.map((f) => f.construct),
|
|
261
|
+
...crons.map((c) => c.construct),
|
|
262
|
+
...subscribers.map((s) => s.construct),
|
|
263
|
+
];
|
|
264
|
+
|
|
265
|
+
// Get docker compose services for auto-populating env vars
|
|
266
|
+
const dockerServices = context.dockerServices;
|
|
267
|
+
|
|
239
268
|
const bundleResult = await bundleServer({
|
|
240
269
|
entryPoint: join(outputDir, 'server.ts'),
|
|
241
270
|
outputDir: join(outputDir, 'dist'),
|
|
@@ -243,6 +272,8 @@ async function buildForProvider(
|
|
|
243
272
|
sourcemap: false,
|
|
244
273
|
external: context.production.external,
|
|
245
274
|
stage,
|
|
275
|
+
constructs: allConstructs,
|
|
276
|
+
dockerServices,
|
|
246
277
|
});
|
|
247
278
|
masterKey = bundleResult.masterKey;
|
|
248
279
|
logger.log(`✅ Bundle complete: .gkm/server/dist/server.mjs`);
|
package/src/build/types.ts
CHANGED
|
@@ -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 {
|