@geekmidas/cli 0.18.0 → 0.20.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-C74EKlNa.cjs → bundler-CyHg1v_T.cjs} +3 -3
- package/dist/{bundler-C74EKlNa.cjs.map → bundler-CyHg1v_T.cjs.map} +1 -1
- package/dist/{bundler-B6z6HEeh.mjs → bundler-DQIuE3Kn.mjs} +3 -3
- package/dist/{bundler-B6z6HEeh.mjs.map → bundler-DQIuE3Kn.mjs.map} +1 -1
- package/dist/{config-DYULeEv8.mjs → config-BaYqrF3n.mjs} +48 -10
- package/dist/config-BaYqrF3n.mjs.map +1 -0
- package/dist/{config-AmInkU7k.cjs → config-CxrLu8ia.cjs} +53 -9
- package/dist/config-CxrLu8ia.cjs.map +1 -0
- package/dist/config.cjs +4 -1
- package/dist/config.d.cts +27 -2
- package/dist/config.d.cts.map +1 -1
- package/dist/config.d.mts +27 -2
- package/dist/config.d.mts.map +1 -1
- package/dist/config.mjs +3 -2
- package/dist/dokploy-api-B0w17y4_.mjs +3 -0
- package/dist/{dokploy-api-CaETb2L6.mjs → dokploy-api-B9qR2Yn1.mjs} +1 -1
- package/dist/{dokploy-api-CaETb2L6.mjs.map → dokploy-api-B9qR2Yn1.mjs.map} +1 -1
- package/dist/dokploy-api-BnGeUqN4.cjs +3 -0
- package/dist/{dokploy-api-C7F9VykY.cjs → dokploy-api-C5czOZoc.cjs} +1 -1
- package/dist/{dokploy-api-C7F9VykY.cjs.map → dokploy-api-C5czOZoc.cjs.map} +1 -1
- package/dist/{encryption-D7Efcdi9.cjs → encryption-BAz0xQ1Q.cjs} +1 -1
- package/dist/{encryption-D7Efcdi9.cjs.map → encryption-BAz0xQ1Q.cjs.map} +1 -1
- package/dist/{encryption-h4Nb6W-M.mjs → encryption-JtMsiGNp.mjs} +2 -2
- package/dist/{encryption-h4Nb6W-M.mjs.map → encryption-JtMsiGNp.mjs.map} +1 -1
- package/dist/index-CWN-bgrO.d.mts +495 -0
- package/dist/index-CWN-bgrO.d.mts.map +1 -0
- package/dist/index-DEWYvYvg.d.cts +495 -0
- package/dist/index-DEWYvYvg.d.cts.map +1 -0
- package/dist/index.cjs +2640 -564
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +2635 -564
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-CZVcfxk-.mjs → openapi-CgqR6Jkw.mjs} +3 -3
- package/dist/{openapi-CZVcfxk-.mjs.map → openapi-CgqR6Jkw.mjs.map} +1 -1
- package/dist/{openapi-C89hhkZC.cjs → openapi-DfpxS0xv.cjs} +8 -2
- package/dist/{openapi-C89hhkZC.cjs.map → openapi-DfpxS0xv.cjs.map} +1 -1
- package/dist/{openapi-react-query-CM2_qlW9.mjs → openapi-react-query-5rSortLH.mjs} +1 -1
- package/dist/{openapi-react-query-CM2_qlW9.mjs.map → openapi-react-query-5rSortLH.mjs.map} +1 -1
- package/dist/{openapi-react-query-iKjfLzff.cjs → openapi-react-query-DvNpdDpM.cjs} +1 -1
- package/dist/{openapi-react-query-iKjfLzff.cjs.map → openapi-react-query-DvNpdDpM.cjs.map} +1 -1
- package/dist/openapi-react-query.cjs +1 -1
- package/dist/openapi-react-query.mjs +1 -1
- package/dist/openapi.cjs +3 -2
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/openapi.mjs +3 -2
- package/dist/{storage-Bn3K9Ccu.cjs → storage-BPRgh3DU.cjs} +136 -5
- package/dist/storage-BPRgh3DU.cjs.map +1 -0
- package/dist/{storage-nkGIjeXt.mjs → storage-DNj_I11J.mjs} +1 -1
- package/dist/storage-Dhst7BhI.mjs +272 -0
- package/dist/storage-Dhst7BhI.mjs.map +1 -0
- package/dist/{storage-UfyTn7Zm.cjs → storage-fOR8dMu5.cjs} +1 -1
- package/dist/{types-iFk5ms7y.d.mts → types-K2uQJ-FO.d.mts} +2 -2
- package/dist/{types-BgaMXsUa.d.cts.map → types-K2uQJ-FO.d.mts.map} +1 -1
- package/dist/{types-BgaMXsUa.d.cts → types-l53qUmGt.d.cts} +2 -2
- package/dist/{types-iFk5ms7y.d.mts.map → types-l53qUmGt.d.cts.map} +1 -1
- package/dist/workspace/index.cjs +19 -0
- package/dist/workspace/index.d.cts +3 -0
- package/dist/workspace/index.d.mts +3 -0
- package/dist/workspace/index.mjs +3 -0
- package/dist/workspace-CPLEZDZf.mjs +3788 -0
- package/dist/workspace-CPLEZDZf.mjs.map +1 -0
- package/dist/workspace-iWgBlX6h.cjs +3885 -0
- package/dist/workspace-iWgBlX6h.cjs.map +1 -0
- package/package.json +9 -4
- package/src/build/__tests__/workspace-build.spec.ts +215 -0
- package/src/build/index.ts +189 -1
- package/src/config.ts +71 -14
- package/src/deploy/__tests__/docker.spec.ts +1 -1
- package/src/deploy/__tests__/index.spec.ts +305 -1
- package/src/deploy/index.ts +426 -4
- package/src/deploy/types.ts +32 -0
- package/src/dev/__tests__/index.spec.ts +572 -1
- package/src/dev/index.ts +582 -2
- package/src/docker/__tests__/compose.spec.ts +425 -0
- package/src/docker/__tests__/templates.spec.ts +145 -0
- package/src/docker/compose.ts +248 -0
- package/src/docker/index.ts +159 -3
- package/src/docker/templates.ts +219 -4
- package/src/index.ts +24 -0
- package/src/init/__tests__/generators.spec.ts +17 -24
- package/src/init/__tests__/init.spec.ts +157 -5
- package/src/init/generators/auth.ts +220 -0
- package/src/init/generators/config.ts +61 -4
- package/src/init/generators/docker.ts +115 -8
- package/src/init/generators/env.ts +7 -127
- package/src/init/generators/index.ts +1 -0
- package/src/init/generators/models.ts +3 -1
- package/src/init/generators/monorepo.ts +154 -10
- package/src/init/generators/package.ts +5 -3
- package/src/init/generators/web.ts +213 -0
- package/src/init/index.ts +290 -58
- package/src/init/templates/api.ts +38 -29
- package/src/init/templates/index.ts +132 -4
- package/src/init/templates/minimal.ts +33 -35
- package/src/init/templates/serverless.ts +16 -19
- package/src/init/templates/worker.ts +50 -25
- package/src/init/versions.ts +47 -0
- package/src/secrets/keystore.ts +144 -0
- package/src/secrets/storage.ts +109 -6
- package/src/test/index.ts +97 -0
- package/src/workspace/__tests__/client-generator.spec.ts +357 -0
- package/src/workspace/__tests__/index.spec.ts +543 -0
- package/src/workspace/__tests__/schema.spec.ts +519 -0
- package/src/workspace/__tests__/type-inference.spec.ts +251 -0
- package/src/workspace/client-generator.ts +307 -0
- package/src/workspace/index.ts +372 -0
- package/src/workspace/schema.ts +368 -0
- package/src/workspace/types.ts +336 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/tsdown.config.ts +1 -0
- package/dist/config-AmInkU7k.cjs.map +0 -1
- package/dist/config-DYULeEv8.mjs.map +0 -1
- package/dist/dokploy-api-B7KxOQr3.cjs +0 -3
- package/dist/dokploy-api-DHvfmWbi.mjs +0 -3
- package/dist/storage-BaOP55oq.mjs +0 -147
- package/dist/storage-BaOP55oq.mjs.map +0 -1
- package/dist/storage-Bn3K9Ccu.cjs.map +0 -1
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Routes can be a string glob or array of globs.
|
|
5
|
+
*/
|
|
6
|
+
const RoutesSchema = z.union([z.string(), z.array(z.string())]);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Telescope configuration schema.
|
|
10
|
+
*/
|
|
11
|
+
const TelescopeConfigSchema = z.object({
|
|
12
|
+
enabled: z.boolean().optional(),
|
|
13
|
+
port: z.number().optional(),
|
|
14
|
+
path: z.string().optional(),
|
|
15
|
+
ignore: z.array(z.string()).optional(),
|
|
16
|
+
recordBody: z.boolean().optional(),
|
|
17
|
+
maxEntries: z.number().optional(),
|
|
18
|
+
websocket: z.boolean().optional(),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Studio configuration schema.
|
|
23
|
+
*/
|
|
24
|
+
const StudioConfigSchema = z.object({
|
|
25
|
+
enabled: z.boolean().optional(),
|
|
26
|
+
path: z.string().optional(),
|
|
27
|
+
schema: z.string().optional(),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* OpenAPI configuration schema.
|
|
32
|
+
*/
|
|
33
|
+
const OpenApiConfigSchema = z.object({
|
|
34
|
+
enabled: z.boolean().optional(),
|
|
35
|
+
title: z.string().optional(),
|
|
36
|
+
version: z.string().optional(),
|
|
37
|
+
description: z.string().optional(),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Hooks configuration schema.
|
|
42
|
+
*/
|
|
43
|
+
const HooksConfigSchema = z.object({
|
|
44
|
+
server: z.string().optional(),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Client generation configuration schema.
|
|
49
|
+
*/
|
|
50
|
+
const ClientConfigSchema = z.object({
|
|
51
|
+
output: z.string().optional(),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Deploy target schema.
|
|
56
|
+
* Currently only 'dokploy' is supported.
|
|
57
|
+
* 'vercel' and 'cloudflare' are planned for Phase 2.
|
|
58
|
+
*/
|
|
59
|
+
const DeployTargetSchema = z.enum(['dokploy', 'vercel', 'cloudflare']);
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Supported deploy targets (Phase 1).
|
|
63
|
+
*/
|
|
64
|
+
const SUPPORTED_DEPLOY_TARGETS = ['dokploy'] as const;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Phase 2 deploy targets (not yet implemented).
|
|
68
|
+
*/
|
|
69
|
+
const PHASE_2_DEPLOY_TARGETS = ['vercel', 'cloudflare'] as const;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Check if a deploy target is supported.
|
|
73
|
+
*/
|
|
74
|
+
export function isDeployTargetSupported(target: string): boolean {
|
|
75
|
+
return SUPPORTED_DEPLOY_TARGETS.includes(
|
|
76
|
+
target as (typeof SUPPORTED_DEPLOY_TARGETS)[number],
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Check if a deploy target is planned for Phase 2.
|
|
82
|
+
*/
|
|
83
|
+
export function isPhase2DeployTarget(target: string): boolean {
|
|
84
|
+
return PHASE_2_DEPLOY_TARGETS.includes(
|
|
85
|
+
target as (typeof PHASE_2_DEPLOY_TARGETS)[number],
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get error message for unsupported deploy targets.
|
|
91
|
+
*/
|
|
92
|
+
export function getDeployTargetError(target: string, appName?: string): string {
|
|
93
|
+
if (isPhase2DeployTarget(target)) {
|
|
94
|
+
const context = appName ? ` for app "${appName}"` : '';
|
|
95
|
+
return `Deploy target "${target}"${context} is coming in Phase 2. Currently only "dokploy" is supported.`;
|
|
96
|
+
}
|
|
97
|
+
return `Unknown deploy target: ${target}. Supported: dokploy. Coming in Phase 2: vercel, cloudflare.`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Service image configuration schema.
|
|
102
|
+
*/
|
|
103
|
+
const ServiceImageConfigSchema = z.object({
|
|
104
|
+
version: z.string().optional(),
|
|
105
|
+
image: z.string().optional(),
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Mail service configuration schema.
|
|
110
|
+
*/
|
|
111
|
+
const MailServiceConfigSchema = ServiceImageConfigSchema.extend({
|
|
112
|
+
smtp: z
|
|
113
|
+
.object({
|
|
114
|
+
host: z.string(),
|
|
115
|
+
port: z.number(),
|
|
116
|
+
user: z.string().optional(),
|
|
117
|
+
pass: z.string().optional(),
|
|
118
|
+
})
|
|
119
|
+
.optional(),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Services configuration schema.
|
|
124
|
+
*/
|
|
125
|
+
const ServicesConfigSchema = z.object({
|
|
126
|
+
db: z.union([z.boolean(), ServiceImageConfigSchema]).optional(),
|
|
127
|
+
cache: z.union([z.boolean(), ServiceImageConfigSchema]).optional(),
|
|
128
|
+
mail: z.union([z.boolean(), MailServiceConfigSchema]).optional(),
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Dokploy workspace configuration schema.
|
|
133
|
+
*/
|
|
134
|
+
const DokployWorkspaceConfigSchema = z.object({
|
|
135
|
+
endpoint: z.url('Dokploy endpoint must be a valid URL'),
|
|
136
|
+
projectId: z.string().min(1, 'Project ID is required'),
|
|
137
|
+
registry: z.string().optional(),
|
|
138
|
+
registryId: z.string().optional(),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Deploy configuration schema.
|
|
143
|
+
*/
|
|
144
|
+
const DeployConfigSchema = z.object({
|
|
145
|
+
default: DeployTargetSchema.optional(),
|
|
146
|
+
dokploy: DokployWorkspaceConfigSchema.optional(),
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Models configuration schema.
|
|
151
|
+
*/
|
|
152
|
+
const ModelsConfigSchema = z.object({
|
|
153
|
+
path: z.string().optional(),
|
|
154
|
+
schema: z.enum(['zod']).optional(),
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Shared configuration schema.
|
|
159
|
+
*/
|
|
160
|
+
const SharedConfigSchema = z.object({
|
|
161
|
+
packages: z.array(z.string()).optional(),
|
|
162
|
+
models: ModelsConfigSchema.optional(),
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Secrets configuration schema.
|
|
167
|
+
*/
|
|
168
|
+
const SecretsConfigSchema = z.object({
|
|
169
|
+
enabled: z.boolean().optional(),
|
|
170
|
+
algorithm: z.string().optional(),
|
|
171
|
+
kdf: z.enum(['scrypt', 'pbkdf2']).optional(),
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* App configuration schema.
|
|
176
|
+
*/
|
|
177
|
+
const AppConfigSchema = z
|
|
178
|
+
.object({
|
|
179
|
+
// Core properties
|
|
180
|
+
type: z.enum(['backend', 'frontend']).optional().default('backend'),
|
|
181
|
+
path: z.string().min(1, 'App path is required'),
|
|
182
|
+
port: z.number().int().positive('Port must be a positive integer'),
|
|
183
|
+
dependencies: z.array(z.string()).optional(),
|
|
184
|
+
deploy: DeployTargetSchema.optional(),
|
|
185
|
+
|
|
186
|
+
// Backend-specific (from GkmConfig)
|
|
187
|
+
routes: RoutesSchema.optional(),
|
|
188
|
+
functions: RoutesSchema.optional(),
|
|
189
|
+
crons: RoutesSchema.optional(),
|
|
190
|
+
subscribers: RoutesSchema.optional(),
|
|
191
|
+
envParser: z.string().optional(),
|
|
192
|
+
logger: z.string().optional(),
|
|
193
|
+
hooks: HooksConfigSchema.optional(),
|
|
194
|
+
telescope: z
|
|
195
|
+
.union([z.string(), z.boolean(), TelescopeConfigSchema])
|
|
196
|
+
.optional(),
|
|
197
|
+
studio: z.union([z.string(), z.boolean(), StudioConfigSchema]).optional(),
|
|
198
|
+
openapi: z.union([z.boolean(), OpenApiConfigSchema]).optional(),
|
|
199
|
+
runtime: z.enum(['node', 'bun']).optional(),
|
|
200
|
+
env: z.union([z.string(), z.array(z.string())]).optional(),
|
|
201
|
+
|
|
202
|
+
// Frontend-specific
|
|
203
|
+
framework: z.enum(['nextjs']).optional(),
|
|
204
|
+
client: ClientConfigSchema.optional(),
|
|
205
|
+
})
|
|
206
|
+
.refine(
|
|
207
|
+
(data) => {
|
|
208
|
+
// Backend apps must have routes
|
|
209
|
+
if (data.type === 'backend' && !data.routes) {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
return true;
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
message: 'Backend apps must have routes defined',
|
|
216
|
+
path: ['routes'],
|
|
217
|
+
},
|
|
218
|
+
)
|
|
219
|
+
.refine(
|
|
220
|
+
(data) => {
|
|
221
|
+
// Frontend apps must have framework
|
|
222
|
+
if (data.type === 'frontend' && !data.framework) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
return true;
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
message: 'Frontend apps must have framework defined',
|
|
229
|
+
path: ['framework'],
|
|
230
|
+
},
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Workspace configuration schema.
|
|
235
|
+
*/
|
|
236
|
+
export const WorkspaceConfigSchema = z
|
|
237
|
+
.object({
|
|
238
|
+
name: z.string().optional(),
|
|
239
|
+
apps: z
|
|
240
|
+
.record(z.string(), AppConfigSchema)
|
|
241
|
+
.refine((apps) => Object.keys(apps).length > 0, {
|
|
242
|
+
message: 'At least one app must be defined',
|
|
243
|
+
}),
|
|
244
|
+
shared: SharedConfigSchema.optional(),
|
|
245
|
+
deploy: DeployConfigSchema.optional(),
|
|
246
|
+
services: ServicesConfigSchema.optional(),
|
|
247
|
+
secrets: SecretsConfigSchema.optional(),
|
|
248
|
+
})
|
|
249
|
+
.refine(
|
|
250
|
+
(data) => {
|
|
251
|
+
// Validate dependencies reference existing apps
|
|
252
|
+
const appNames = Object.keys(data.apps);
|
|
253
|
+
for (const [appName, app] of Object.entries(data.apps)) {
|
|
254
|
+
for (const dep of app.dependencies ?? []) {
|
|
255
|
+
if (!appNames.includes(dep)) {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
// Prevent self-dependency
|
|
259
|
+
if (dep === appName) {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return true;
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
message:
|
|
268
|
+
'App dependencies must reference existing apps and cannot be self-referential',
|
|
269
|
+
},
|
|
270
|
+
)
|
|
271
|
+
.refine(
|
|
272
|
+
(data) => {
|
|
273
|
+
// Check for circular dependencies
|
|
274
|
+
const appNames = Object.keys(data.apps);
|
|
275
|
+
const visited = new Set<string>();
|
|
276
|
+
const recStack = new Set<string>();
|
|
277
|
+
|
|
278
|
+
function hasCycle(app: string): boolean {
|
|
279
|
+
if (recStack.has(app)) return true;
|
|
280
|
+
if (visited.has(app)) return false;
|
|
281
|
+
|
|
282
|
+
visited.add(app);
|
|
283
|
+
recStack.add(app);
|
|
284
|
+
|
|
285
|
+
const deps = data.apps[app]?.dependencies ?? [];
|
|
286
|
+
for (const dep of deps) {
|
|
287
|
+
if (hasCycle(dep)) return true;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
recStack.delete(app);
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
for (const app of appNames) {
|
|
295
|
+
visited.clear();
|
|
296
|
+
recStack.clear();
|
|
297
|
+
if (hasCycle(app)) return false;
|
|
298
|
+
}
|
|
299
|
+
return true;
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
message: 'Circular dependencies detected between apps',
|
|
303
|
+
},
|
|
304
|
+
)
|
|
305
|
+
.superRefine((data, ctx) => {
|
|
306
|
+
// Validate deploy targets are supported
|
|
307
|
+
const defaultTarget = data.deploy?.default;
|
|
308
|
+
if (defaultTarget && !isDeployTargetSupported(defaultTarget)) {
|
|
309
|
+
ctx.addIssue({
|
|
310
|
+
code: z.ZodIssueCode.custom,
|
|
311
|
+
message: getDeployTargetError(defaultTarget),
|
|
312
|
+
path: ['deploy', 'default'],
|
|
313
|
+
});
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
for (const [appName, app] of Object.entries(data.apps)) {
|
|
318
|
+
if (app.deploy && !isDeployTargetSupported(app.deploy)) {
|
|
319
|
+
ctx.addIssue({
|
|
320
|
+
code: z.ZodIssueCode.custom,
|
|
321
|
+
message: getDeployTargetError(app.deploy, appName),
|
|
322
|
+
path: ['apps', appName, 'deploy'],
|
|
323
|
+
});
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Validate workspace configuration.
|
|
331
|
+
* Throws ZodError with detailed messages on validation failure.
|
|
332
|
+
*/
|
|
333
|
+
export function validateWorkspaceConfig(
|
|
334
|
+
config: unknown,
|
|
335
|
+
): z.infer<typeof WorkspaceConfigSchema> {
|
|
336
|
+
return WorkspaceConfigSchema.parse(config);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Safe validation that returns result instead of throwing.
|
|
341
|
+
*/
|
|
342
|
+
export function safeValidateWorkspaceConfig(config: unknown): {
|
|
343
|
+
success: boolean;
|
|
344
|
+
data?: z.infer<typeof WorkspaceConfigSchema>;
|
|
345
|
+
error?: z.ZodError;
|
|
346
|
+
} {
|
|
347
|
+
const result = WorkspaceConfigSchema.safeParse(config);
|
|
348
|
+
if (result.success) {
|
|
349
|
+
return { success: true, data: result.data };
|
|
350
|
+
}
|
|
351
|
+
return { success: false, error: result.error };
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Format Zod errors into user-friendly messages.
|
|
356
|
+
*/
|
|
357
|
+
export function formatValidationErrors(error: z.ZodError): string {
|
|
358
|
+
const messages = error.issues.map((issue: z.core.$ZodIssue) => {
|
|
359
|
+
const path = issue.path.join('.');
|
|
360
|
+
return path ? ` - ${path}: ${issue.message}` : ` - ${issue.message}`;
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
return `Workspace configuration validation failed:\n${messages.join('\n')}`;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export type ValidatedWorkspaceConfig = z.infer<typeof WorkspaceConfigSchema>;
|
|
367
|
+
|
|
368
|
+
export { SUPPORTED_DEPLOY_TARGETS, PHASE_2_DEPLOY_TARGETS };
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
GkmConfig,
|
|
3
|
+
HooksConfig,
|
|
4
|
+
OpenApiConfig,
|
|
5
|
+
ProvidersConfig,
|
|
6
|
+
Routes,
|
|
7
|
+
Runtime,
|
|
8
|
+
StudioConfig,
|
|
9
|
+
TelescopeConfig,
|
|
10
|
+
} from '../types.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Deploy target for an app.
|
|
14
|
+
* Currently only 'dokploy' is supported.
|
|
15
|
+
* Future: 'vercel' | 'cloudflare'
|
|
16
|
+
*/
|
|
17
|
+
export type DeployTarget = 'dokploy' | 'vercel' | 'cloudflare';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Service image configuration for custom Docker images.
|
|
21
|
+
*/
|
|
22
|
+
export interface ServiceImageConfig {
|
|
23
|
+
/** Docker image version/tag (e.g., '18-alpine') */
|
|
24
|
+
version?: string;
|
|
25
|
+
/** Full Docker image reference (overrides version) */
|
|
26
|
+
image?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Mail service configuration.
|
|
31
|
+
*/
|
|
32
|
+
export interface MailServiceConfig extends ServiceImageConfig {
|
|
33
|
+
/** SMTP configuration for production */
|
|
34
|
+
smtp?: {
|
|
35
|
+
host: string;
|
|
36
|
+
port: number;
|
|
37
|
+
user?: string;
|
|
38
|
+
pass?: string;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Development services configuration.
|
|
44
|
+
*/
|
|
45
|
+
export interface ServicesConfig {
|
|
46
|
+
/** PostgreSQL database (default: postgres:18-alpine) */
|
|
47
|
+
db?: boolean | ServiceImageConfig;
|
|
48
|
+
/** Redis cache (default: redis:8-alpine) */
|
|
49
|
+
cache?: boolean | ServiceImageConfig;
|
|
50
|
+
/** Mail service (mailpit for dev) */
|
|
51
|
+
mail?: boolean | MailServiceConfig;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Dokploy workspace deployment configuration.
|
|
56
|
+
*/
|
|
57
|
+
export interface DokployWorkspaceConfig {
|
|
58
|
+
/** Dokploy API endpoint */
|
|
59
|
+
endpoint: string;
|
|
60
|
+
/** Project ID (1 workspace = 1 project) */
|
|
61
|
+
projectId: string;
|
|
62
|
+
/** Container registry for images */
|
|
63
|
+
registry?: string;
|
|
64
|
+
/** Registry ID in Dokploy */
|
|
65
|
+
registryId?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Deployment configuration for the workspace.
|
|
70
|
+
*/
|
|
71
|
+
export interface DeployConfig {
|
|
72
|
+
/** Default deploy target for all apps */
|
|
73
|
+
default?: DeployTarget;
|
|
74
|
+
/** Dokploy-specific configuration */
|
|
75
|
+
dokploy?: DokployWorkspaceConfig;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Models package configuration for shared schemas.
|
|
80
|
+
*/
|
|
81
|
+
export interface ModelsConfig {
|
|
82
|
+
/** Path to models package (default: packages/models) */
|
|
83
|
+
path?: string;
|
|
84
|
+
/**
|
|
85
|
+
* Schema library to use.
|
|
86
|
+
* Currently only 'zod' is supported.
|
|
87
|
+
* Future: any StandardSchema-compatible library
|
|
88
|
+
*/
|
|
89
|
+
schema?: 'zod';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Shared packages configuration.
|
|
94
|
+
*/
|
|
95
|
+
export interface SharedConfig {
|
|
96
|
+
/** Glob patterns for shared packages (default: ['packages/*']) */
|
|
97
|
+
packages?: string[];
|
|
98
|
+
/** Models package configuration */
|
|
99
|
+
models?: ModelsConfig;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Secrets encryption configuration.
|
|
104
|
+
*/
|
|
105
|
+
export interface SecretsConfig {
|
|
106
|
+
/** Enable encrypted secrets */
|
|
107
|
+
enabled?: boolean;
|
|
108
|
+
/** Encryption algorithm (default: aes-256-gcm) */
|
|
109
|
+
algorithm?: string;
|
|
110
|
+
/** Key derivation function (default: scrypt) */
|
|
111
|
+
kdf?: 'scrypt' | 'pbkdf2';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Client generation configuration for frontend apps.
|
|
116
|
+
*/
|
|
117
|
+
export interface ClientConfig {
|
|
118
|
+
/** Output directory for generated client (relative to app path) */
|
|
119
|
+
output?: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Base app configuration properties (shared between input and normalized).
|
|
124
|
+
*/
|
|
125
|
+
interface AppConfigBase {
|
|
126
|
+
/** App type (default: 'backend') */
|
|
127
|
+
type?: 'backend' | 'frontend';
|
|
128
|
+
|
|
129
|
+
/** Path relative to workspace root */
|
|
130
|
+
path: string;
|
|
131
|
+
|
|
132
|
+
/** Dev server port */
|
|
133
|
+
port: number;
|
|
134
|
+
|
|
135
|
+
/** Per-app deploy target override */
|
|
136
|
+
deploy?: DeployTarget;
|
|
137
|
+
|
|
138
|
+
// Backend-specific (from GkmConfig)
|
|
139
|
+
/** Routes glob pattern */
|
|
140
|
+
routes?: Routes;
|
|
141
|
+
/** Functions glob pattern */
|
|
142
|
+
functions?: Routes;
|
|
143
|
+
/** Crons glob pattern */
|
|
144
|
+
crons?: Routes;
|
|
145
|
+
/** Subscribers glob pattern */
|
|
146
|
+
subscribers?: Routes;
|
|
147
|
+
/** Path to environment parser module */
|
|
148
|
+
envParser?: string;
|
|
149
|
+
/** Path to logger module */
|
|
150
|
+
logger?: string;
|
|
151
|
+
/** Provider configuration */
|
|
152
|
+
providers?: ProvidersConfig;
|
|
153
|
+
/** Server lifecycle hooks */
|
|
154
|
+
hooks?: HooksConfig;
|
|
155
|
+
/** Telescope configuration */
|
|
156
|
+
telescope?: string | boolean | TelescopeConfig;
|
|
157
|
+
/** Studio configuration */
|
|
158
|
+
studio?: string | boolean | StudioConfig;
|
|
159
|
+
/** OpenAPI configuration */
|
|
160
|
+
openapi?: boolean | OpenApiConfig;
|
|
161
|
+
/** Runtime (node or bun) */
|
|
162
|
+
runtime?: Runtime;
|
|
163
|
+
/** Environment file(s) to load */
|
|
164
|
+
env?: string | string[];
|
|
165
|
+
|
|
166
|
+
// Frontend-specific
|
|
167
|
+
/** Frontend framework (currently only 'nextjs') */
|
|
168
|
+
framework?: 'nextjs';
|
|
169
|
+
/** Client generation configuration */
|
|
170
|
+
client?: ClientConfig;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* App configuration input with type-safe dependencies.
|
|
175
|
+
* @template TAppNames - Union of valid app names in the workspace
|
|
176
|
+
*/
|
|
177
|
+
export interface AppConfigInput<TAppNames extends string = string>
|
|
178
|
+
extends AppConfigBase {
|
|
179
|
+
/** Dependencies on other apps in the workspace (type-safe) */
|
|
180
|
+
dependencies?: TAppNames[];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* App configuration (legacy, for backwards compatibility).
|
|
185
|
+
* @deprecated Use AppConfigInput for new code
|
|
186
|
+
*/
|
|
187
|
+
export interface AppConfig extends AppConfigBase {
|
|
188
|
+
/** Dependencies on other apps in the workspace */
|
|
189
|
+
dependencies?: string[];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Base app input type for type inference.
|
|
194
|
+
*/
|
|
195
|
+
export type AppInput = AppConfigBase & {
|
|
196
|
+
dependencies?: readonly string[];
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Apps record type for workspace configuration.
|
|
201
|
+
*/
|
|
202
|
+
export type AppsRecord = Record<string, AppInput>;
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Constrain apps so dependencies only reference valid app names.
|
|
206
|
+
* Dependencies must be an array of valid app names from the workspace.
|
|
207
|
+
*/
|
|
208
|
+
export type ConstrainedApps<TApps extends AppsRecord> = {
|
|
209
|
+
[K in keyof TApps]: Omit<TApps[K], 'dependencies'> & {
|
|
210
|
+
dependencies?: readonly (keyof TApps & string)[];
|
|
211
|
+
};
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Full workspace input type with constrained dependencies.
|
|
216
|
+
*/
|
|
217
|
+
export type WorkspaceInput<TApps extends AppsRecord> = {
|
|
218
|
+
name?: string;
|
|
219
|
+
apps: ConstrainedApps<TApps>;
|
|
220
|
+
shared?: SharedConfig;
|
|
221
|
+
deploy?: DeployConfig;
|
|
222
|
+
services?: ServicesConfig;
|
|
223
|
+
secrets?: SecretsConfig;
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Extract app names from apps record.
|
|
228
|
+
*/
|
|
229
|
+
export type InferAppNames<TApps extends AppsRecord> = keyof TApps & string;
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Inferred workspace config with proper app name types.
|
|
233
|
+
*/
|
|
234
|
+
export type InferredWorkspaceConfig<TApps extends AppsRecord> = {
|
|
235
|
+
name?: string;
|
|
236
|
+
apps: {
|
|
237
|
+
[K in keyof TApps]: Omit<TApps[K], 'dependencies'> & {
|
|
238
|
+
dependencies?: InferAppNames<TApps>[];
|
|
239
|
+
};
|
|
240
|
+
};
|
|
241
|
+
shared?: SharedConfig;
|
|
242
|
+
deploy?: DeployConfig;
|
|
243
|
+
services?: ServicesConfig;
|
|
244
|
+
secrets?: SecretsConfig;
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// Legacy types for backwards compatibility
|
|
248
|
+
/** @deprecated Use WorkspaceInput */
|
|
249
|
+
export type RawWorkspaceInput = {
|
|
250
|
+
name?: string;
|
|
251
|
+
apps: AppsRecord;
|
|
252
|
+
shared?: SharedConfig;
|
|
253
|
+
deploy?: DeployConfig;
|
|
254
|
+
services?: ServicesConfig;
|
|
255
|
+
secrets?: SecretsConfig;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
/** @deprecated Use WorkspaceInput */
|
|
259
|
+
export type WorkspaceConfigInput<
|
|
260
|
+
T extends RawWorkspaceInput = RawWorkspaceInput,
|
|
261
|
+
> = WorkspaceInput<T['apps']>;
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Workspace configuration for multi-app monorepos (legacy).
|
|
265
|
+
* @deprecated Use WorkspaceConfigInput with defineWorkspace for type inference
|
|
266
|
+
*/
|
|
267
|
+
export interface WorkspaceConfig {
|
|
268
|
+
/** Workspace name (defaults to root package.json name) */
|
|
269
|
+
name?: string;
|
|
270
|
+
|
|
271
|
+
/** App definitions */
|
|
272
|
+
apps: Record<string, AppConfig>;
|
|
273
|
+
|
|
274
|
+
/** Shared packages configuration */
|
|
275
|
+
shared?: SharedConfig;
|
|
276
|
+
|
|
277
|
+
/** Default deployment configuration */
|
|
278
|
+
deploy?: DeployConfig;
|
|
279
|
+
|
|
280
|
+
/** Development services (db, cache, mail) */
|
|
281
|
+
services?: ServicesConfig;
|
|
282
|
+
|
|
283
|
+
/** Encrypted secrets configuration */
|
|
284
|
+
secrets?: SecretsConfig;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Normalized app configuration with resolved defaults.
|
|
289
|
+
*/
|
|
290
|
+
export interface NormalizedAppConfig extends Omit<AppConfigBase, 'type'> {
|
|
291
|
+
type: 'backend' | 'frontend';
|
|
292
|
+
path: string;
|
|
293
|
+
port: number;
|
|
294
|
+
dependencies: string[];
|
|
295
|
+
/** Resolved deploy target (app.deploy > deploy.default > 'dokploy') */
|
|
296
|
+
resolvedDeployTarget: DeployTarget;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Normalized workspace configuration with resolved defaults.
|
|
301
|
+
*/
|
|
302
|
+
export interface NormalizedWorkspace {
|
|
303
|
+
name: string;
|
|
304
|
+
root: string;
|
|
305
|
+
apps: Record<string, NormalizedAppConfig>;
|
|
306
|
+
services: ServicesConfig;
|
|
307
|
+
deploy: DeployConfig;
|
|
308
|
+
shared: SharedConfig;
|
|
309
|
+
secrets: SecretsConfig;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Result of loading and processing a configuration.
|
|
314
|
+
*/
|
|
315
|
+
export interface LoadedConfig {
|
|
316
|
+
/** Whether this is a single-app or workspace config */
|
|
317
|
+
type: 'single' | 'workspace';
|
|
318
|
+
/** The raw configuration as loaded */
|
|
319
|
+
raw: GkmConfig | WorkspaceConfig;
|
|
320
|
+
/** Normalized workspace (always available) */
|
|
321
|
+
workspace: NormalizedWorkspace;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Type guard to check if a config is a WorkspaceConfig.
|
|
326
|
+
*/
|
|
327
|
+
export function isWorkspaceConfig(
|
|
328
|
+
config: GkmConfig | WorkspaceConfig,
|
|
329
|
+
): config is WorkspaceConfig {
|
|
330
|
+
return (
|
|
331
|
+
typeof config === 'object' &&
|
|
332
|
+
config !== null &&
|
|
333
|
+
'apps' in config &&
|
|
334
|
+
typeof config.apps === 'object'
|
|
335
|
+
);
|
|
336
|
+
}
|