@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.
- package/dist/{bundler-DskIqW2t.mjs → bundler-D7cM_FWw.mjs} +34 -10
- package/dist/bundler-D7cM_FWw.mjs.map +1 -0
- package/dist/{bundler-B1qy9b-j.cjs → bundler-Nuew7Xcn.cjs} +33 -9
- package/dist/bundler-Nuew7Xcn.cjs.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 +1508 -1073
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1508 -1073
- 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-kSxTjkNb.mjs → storage-BaOP55oq.mjs} +16 -2
- package/dist/storage-BaOP55oq.mjs.map +1 -0
- package/dist/{storage-Bj1E26lU.cjs → storage-Bn3K9Ccu.cjs} +21 -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-BhkZc-vm.d.cts → types-BgaMXsUa.d.cts} +3 -1
- package/dist/{types-BR0M2v_c.d.mts.map → types-BgaMXsUa.d.cts.map} +1 -1
- package/dist/{types-BR0M2v_c.d.mts → 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 +5 -4
- 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 +61 -8
- package/src/build/index.ts +21 -0
- package/src/build/types.ts +6 -0
- package/src/deploy/__tests__/docker.spec.ts +44 -6
- 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 +401 -0
- package/src/deploy/__tests__/init.spec.ts +147 -16
- package/src/deploy/docker.ts +109 -5
- package/src/deploy/dokploy-api.ts +581 -0
- package/src/deploy/dokploy.ts +66 -93
- package/src/deploy/index.ts +630 -32
- package/src/deploy/init.ts +192 -249
- package/src/deploy/types.ts +24 -2
- 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/storage.ts +15 -0
- package/src/types.ts +2 -0
- package/dist/bundler-B1qy9b-j.cjs.map +0 -1
- package/dist/bundler-DskIqW2t.mjs.map +0 -1
- package/dist/storage-BOOpAF8N.cjs +0 -5
- package/dist/storage-Bj1E26lU.cjs.map +0 -1
- package/dist/storage-kSxTjkNb.mjs.map +0 -1
- 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
|
+
});
|
package/src/build/bundler.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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';
|
|
@@ -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
|
|
@@ -111,17 +131,39 @@ export async function bundleServer(
|
|
|
111
131
|
readStageSecrets,
|
|
112
132
|
toEmbeddableSecrets,
|
|
113
133
|
validateEnvironmentVariables,
|
|
134
|
+
initStageSecrets,
|
|
135
|
+
writeStageSecrets,
|
|
114
136
|
} = await import('../secrets/storage');
|
|
115
137
|
const { encryptSecrets, generateDefineOptions } = await import(
|
|
116
138
|
'../secrets/encryption'
|
|
117
139
|
);
|
|
118
140
|
|
|
119
|
-
|
|
141
|
+
let secrets = await readStageSecrets(stage);
|
|
120
142
|
|
|
121
143
|
if (!secrets) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
);
|
|
144
|
+
// Auto-initialize secrets for the stage
|
|
145
|
+
console.log(` Initializing secrets for stage "${stage}"...`);
|
|
146
|
+
secrets = initStageSecrets(stage);
|
|
147
|
+
await writeStageSecrets(secrets);
|
|
148
|
+
console.log(` ✓ Created .gkm/secrets/${stage}.json`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Auto-populate env vars from docker compose services
|
|
152
|
+
if (dockerServices) {
|
|
153
|
+
for (const [service, enabled] of Object.entries(dockerServices)) {
|
|
154
|
+
if (enabled && DOCKER_SERVICE_ENV_VARS[service]) {
|
|
155
|
+
for (const [envVar, defaultValue] of Object.entries(
|
|
156
|
+
DOCKER_SERVICE_ENV_VARS[service],
|
|
157
|
+
)) {
|
|
158
|
+
// Check if not already in urls or custom
|
|
159
|
+
const urlKey = envVar as keyof typeof secrets.urls;
|
|
160
|
+
if (!secrets.urls[urlKey] && !secrets.custom[envVar]) {
|
|
161
|
+
secrets.urls[urlKey] = defaultValue;
|
|
162
|
+
console.log(` Auto-populated ${envVar} from docker compose`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
125
167
|
}
|
|
126
168
|
|
|
127
169
|
// Validate environment variables if constructs are provided
|
|
@@ -165,10 +207,10 @@ export async function bundleServer(
|
|
|
165
207
|
const encrypted = encryptSecrets(embeddable);
|
|
166
208
|
masterKey = encrypted.masterKey;
|
|
167
209
|
|
|
168
|
-
// Add define options for build-time injection
|
|
210
|
+
// Add define options for build-time injection using tsdown's --env.* format
|
|
169
211
|
const defines = generateDefineOptions(encrypted);
|
|
170
212
|
for (const [key, value] of Object.entries(defines)) {
|
|
171
|
-
args.push(
|
|
213
|
+
args.push(`--env.${key}`, value);
|
|
172
214
|
}
|
|
173
215
|
|
|
174
216
|
console.log(` Secrets encrypted for stage "${stage}"`);
|
|
@@ -178,11 +220,22 @@ export async function bundleServer(
|
|
|
178
220
|
|
|
179
221
|
try {
|
|
180
222
|
// Run tsdown with command-line arguments
|
|
181
|
-
|
|
223
|
+
// Use spawnSync with args array to avoid shell escaping issues with --define values
|
|
224
|
+
// args is always populated with ['npx', 'tsdown', ...] so cmd is never undefined
|
|
225
|
+
const [cmd, ...cmdArgs] = args as [string, ...string[]];
|
|
226
|
+
const result = spawnSync(cmd, cmdArgs, {
|
|
182
227
|
cwd: process.cwd(),
|
|
183
228
|
stdio: 'inherit',
|
|
229
|
+
shell: process.platform === 'win32', // Only use shell on Windows for npx resolution
|
|
184
230
|
});
|
|
185
231
|
|
|
232
|
+
if (result.error) {
|
|
233
|
+
throw result.error;
|
|
234
|
+
}
|
|
235
|
+
if (result.status !== 0) {
|
|
236
|
+
throw new Error(`tsdown exited with code ${result.status}`);
|
|
237
|
+
}
|
|
238
|
+
|
|
186
239
|
// Rename output to .mjs for explicit ESM
|
|
187
240
|
// tsdown outputs as server.js for ESM format
|
|
188
241
|
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
|
|
@@ -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`);
|
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 {
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import type { GkmConfig } from '../../types';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
getAppNameFromCwd,
|
|
5
|
+
getAppNameFromPackageJson,
|
|
6
|
+
getImageRef,
|
|
7
|
+
resolveDockerConfig,
|
|
8
|
+
} from '../docker';
|
|
4
9
|
|
|
5
10
|
describe('getImageRef', () => {
|
|
6
11
|
it('should return image with registry prefix', () => {
|
|
@@ -36,8 +41,26 @@ describe('getImageRef', () => {
|
|
|
36
41
|
});
|
|
37
42
|
});
|
|
38
43
|
|
|
44
|
+
describe('getAppNameFromCwd', () => {
|
|
45
|
+
it('should return app name from package.json in current directory', () => {
|
|
46
|
+
// Tests run from the monorepo root, so cwd is the root directory
|
|
47
|
+
const appName = getAppNameFromCwd();
|
|
48
|
+
// The root package.json has name "@geekmidas/toolbox", so it should strip the scope
|
|
49
|
+
expect(appName).toBe('toolbox');
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('getAppNameFromPackageJson', () => {
|
|
54
|
+
it('should return app name from package.json adjacent to lockfile', () => {
|
|
55
|
+
// This test runs in the toolbox monorepo, so it should find the root package.json
|
|
56
|
+
const appName = getAppNameFromPackageJson();
|
|
57
|
+
// The root package.json has name "@geekmidas/toolbox", so it should strip the scope
|
|
58
|
+
expect(appName).toBe('toolbox');
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
39
62
|
describe('resolveDockerConfig', () => {
|
|
40
|
-
it('should
|
|
63
|
+
it('should fallback to package.json name when docker not configured', () => {
|
|
41
64
|
const config: GkmConfig = {
|
|
42
65
|
routes: './src/endpoints',
|
|
43
66
|
envParser: './src/env',
|
|
@@ -47,7 +70,8 @@ describe('resolveDockerConfig', () => {
|
|
|
47
70
|
const dockerConfig = resolveDockerConfig(config);
|
|
48
71
|
|
|
49
72
|
expect(dockerConfig.registry).toBeUndefined();
|
|
50
|
-
|
|
73
|
+
// Should fallback to package.json name or 'app'
|
|
74
|
+
expect(dockerConfig.imageName).toBeDefined();
|
|
51
75
|
});
|
|
52
76
|
|
|
53
77
|
it('should return registry from docker config', () => {
|
|
@@ -97,7 +121,7 @@ describe('resolveDockerConfig', () => {
|
|
|
97
121
|
expect(dockerConfig.imageName).toBe('backend-api');
|
|
98
122
|
});
|
|
99
123
|
|
|
100
|
-
it('should
|
|
124
|
+
it('should fallback to package.json name when docker object is empty', () => {
|
|
101
125
|
const config: GkmConfig = {
|
|
102
126
|
routes: './src/endpoints',
|
|
103
127
|
docker: {},
|
|
@@ -106,6 +130,20 @@ describe('resolveDockerConfig', () => {
|
|
|
106
130
|
const dockerConfig = resolveDockerConfig(config);
|
|
107
131
|
|
|
108
132
|
expect(dockerConfig.registry).toBeUndefined();
|
|
109
|
-
|
|
133
|
+
// Should fallback to package.json name or 'app'
|
|
134
|
+
expect(dockerConfig.imageName).toBeDefined();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should prefer explicit imageName over package.json', () => {
|
|
138
|
+
const config: GkmConfig = {
|
|
139
|
+
routes: './src/endpoints',
|
|
140
|
+
docker: {
|
|
141
|
+
imageName: 'explicit-name',
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const dockerConfig = resolveDockerConfig(config);
|
|
146
|
+
|
|
147
|
+
expect(dockerConfig.imageName).toBe('explicit-name');
|
|
110
148
|
});
|
|
111
149
|
});
|