@geekmidas/cli 0.26.0 → 0.28.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 (36) hide show
  1. package/dist/{config-CTftATBX.cjs → config-BhryDQEq.cjs} +2 -2
  2. package/dist/{config-CTftATBX.cjs.map → config-BhryDQEq.cjs.map} +1 -1
  3. package/dist/{config-BogU0_oQ.mjs → config-C9bdq0l-.mjs} +2 -2
  4. package/dist/{config-BogU0_oQ.mjs.map → config-C9bdq0l-.mjs.map} +1 -1
  5. package/dist/config.cjs +2 -2
  6. package/dist/config.mjs +2 -2
  7. package/dist/index-CWN-bgrO.d.mts.map +1 -1
  8. package/dist/index-DEWYvYvg.d.cts.map +1 -1
  9. package/dist/index.cjs +142 -40
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.mjs +142 -40
  12. package/dist/index.mjs.map +1 -1
  13. package/dist/{openapi-DNbXfhXE.mjs → openapi-BCEFhkLh.mjs} +2 -2
  14. package/dist/{openapi-DNbXfhXE.mjs.map → openapi-BCEFhkLh.mjs.map} +1 -1
  15. package/dist/{openapi-BrhkPKM7.cjs → openapi-D82bBqG7.cjs} +2 -2
  16. package/dist/{openapi-BrhkPKM7.cjs.map → openapi-D82bBqG7.cjs.map} +1 -1
  17. package/dist/openapi.cjs +3 -3
  18. package/dist/openapi.mjs +3 -3
  19. package/dist/workspace/index.cjs +1 -1
  20. package/dist/workspace/index.mjs +1 -1
  21. package/dist/{workspace-iWgBlX6h.cjs → workspace-CiZBOjf9.cjs} +1 -7
  22. package/dist/{workspace-iWgBlX6h.cjs.map → workspace-CiZBOjf9.cjs.map} +1 -1
  23. package/dist/{workspace-CPLEZDZf.mjs → workspace-DQjmv9lk.mjs} +1 -7
  24. package/dist/{workspace-CPLEZDZf.mjs.map → workspace-DQjmv9lk.mjs.map} +1 -1
  25. package/package.json +5 -5
  26. package/src/init/__tests__/generators.spec.ts +12 -6
  27. package/src/init/__tests__/init.spec.ts +18 -6
  28. package/src/init/generators/models.ts +51 -30
  29. package/src/init/index.ts +15 -0
  30. package/src/init/templates/api.ts +55 -3
  31. package/src/init/templates/minimal.ts +6 -0
  32. package/src/init/templates/serverless.ts +7 -0
  33. package/src/init/templates/worker.ts +6 -0
  34. package/src/workspace/__tests__/index.spec.ts +5 -6
  35. package/src/workspace/__tests__/schema.spec.ts +5 -5
  36. package/src/workspace/schema.ts +1 -13
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geekmidas/cli",
3
- "version": "0.26.0",
3
+ "version": "0.28.0",
4
4
  "description": "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs",
5
5
  "private": false,
6
6
  "type": "module",
@@ -48,11 +48,11 @@
48
48
  "lodash.kebabcase": "^4.1.1",
49
49
  "openapi-typescript": "^7.4.2",
50
50
  "prompts": "~2.4.2",
51
- "@geekmidas/constructs": "~0.6.0",
52
- "@geekmidas/envkit": "~0.4.0",
53
51
  "@geekmidas/logger": "~0.4.0",
54
- "@geekmidas/schema": "~0.1.0",
55
- "@geekmidas/errors": "~0.1.0"
52
+ "@geekmidas/errors": "~0.1.0",
53
+ "@geekmidas/envkit": "~0.4.0",
54
+ "@geekmidas/constructs": "~0.6.0",
55
+ "@geekmidas/schema": "~0.1.0"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@types/lodash.kebabcase": "^4.1.9",
@@ -297,7 +297,8 @@ describe('generateModelsPackage', () => {
297
297
  const paths = files.map((f) => f.path);
298
298
  expect(paths).toContain('packages/models/package.json');
299
299
  expect(paths).toContain('packages/models/tsconfig.json');
300
- expect(paths).toContain('packages/models/src/index.ts');
300
+ expect(paths).toContain('packages/models/src/common.ts');
301
+ expect(paths).toContain('packages/models/src/user.ts');
301
302
  });
302
303
 
303
304
  it('should use correct package name', () => {
@@ -335,12 +336,17 @@ describe('generateModelsPackage', () => {
335
336
  apiPath: 'apps/api',
336
337
  };
337
338
  const files = generateModelsPackage(options);
338
- const indexTs = files.find(
339
- (f) => f.path === 'packages/models/src/index.ts',
339
+ const userTs = files.find(
340
+ (f) => f.path === 'packages/models/src/user.ts',
340
341
  );
341
- expect(indexTs?.content).toContain('userSchema');
342
- expect(indexTs?.content).toContain('paginationSchema');
343
- expect(indexTs?.content).toContain("import { z } from 'zod'");
342
+ const commonTs = files.find(
343
+ (f) => f.path === 'packages/models/src/common.ts',
344
+ );
345
+ expect(userTs?.content).toContain('UserSchema');
346
+ expect(userTs?.content).toContain('UserResponseSchema');
347
+ expect(userTs?.content).toContain("import { z } from 'zod'");
348
+ expect(commonTs?.content).toContain('PaginationSchema');
349
+ expect(commonTs?.content).toContain('IdSchema');
344
350
  });
345
351
 
346
352
  it('should extend root tsconfig', () => {
@@ -176,7 +176,10 @@ describe('initCommand', () => {
176
176
  expect(
177
177
  existsSync(join(projectDir, 'packages/models/tsconfig.json')),
178
178
  ).toBe(true);
179
- expect(existsSync(join(projectDir, 'packages/models/src/index.ts'))).toBe(
179
+ expect(existsSync(join(projectDir, 'packages/models/src/common.ts'))).toBe(
180
+ true,
181
+ );
182
+ expect(existsSync(join(projectDir, 'packages/models/src/user.ts'))).toBe(
180
183
  true,
181
184
  );
182
185
  });
@@ -265,14 +268,23 @@ describe('initCommand', () => {
265
268
  expect(pkg.name).toBe('@my-monorepo/models');
266
269
  expect(pkg.dependencies.zod).toBeDefined();
267
270
 
268
- const indexPath = join(
271
+ const userPath = join(
272
+ tempDir,
273
+ 'my-monorepo',
274
+ 'packages/models/src/user.ts',
275
+ );
276
+ const userContent = await readFile(userPath, 'utf-8');
277
+ expect(userContent).toContain('UserSchema');
278
+ expect(userContent).toContain('UserResponseSchema');
279
+
280
+ const commonPath = join(
269
281
  tempDir,
270
282
  'my-monorepo',
271
- 'packages/models/src/index.ts',
283
+ 'packages/models/src/common.ts',
272
284
  );
273
- const indexContent = await readFile(indexPath, 'utf-8');
274
- expect(indexContent).toContain('userSchema');
275
- expect(indexContent).toContain('paginationSchema');
285
+ const commonContent = await readFile(commonPath, 'utf-8');
286
+ expect(commonContent).toContain('PaginationSchema');
287
+ expect(commonContent).toContain('IdSchema');
276
288
  });
277
289
 
278
290
  it('should support custom API path', async () => {
@@ -20,18 +20,9 @@ export function generateModelsPackage(
20
20
  private: true,
21
21
  type: 'module',
22
22
  exports: {
23
- '.': {
24
- types: './dist/index.d.ts',
25
- import: './dist/index.js',
26
- },
27
- './*': {
28
- types: './dist/*.d.ts',
29
- import: './dist/*.js',
30
- },
23
+ './*': './src/*.ts',
31
24
  },
32
25
  scripts: {
33
- build: 'tsc',
34
- 'build:watch': 'tsc --watch',
35
26
  typecheck: 'tsc --noEmit',
36
27
  },
37
28
  dependencies: {
@@ -42,7 +33,7 @@ export function generateModelsPackage(
42
33
  },
43
34
  };
44
35
 
45
- // tsconfig.json for models - library package that builds to dist
36
+ // tsconfig.json for models - extends root config
46
37
  const tsConfig = {
47
38
  extends: '../../tsconfig.json',
48
39
  compilerOptions: {
@@ -55,26 +46,26 @@ export function generateModelsPackage(
55
46
  exclude: ['node_modules', 'dist'],
56
47
  };
57
48
 
58
- // Main index.ts with example schemas
59
- const indexTs = `import { z } from 'zod';
49
+ // common.ts - shared utility schemas
50
+ const commonTs = `import { z } from 'zod';
60
51
 
61
52
  // ============================================
62
53
  // Common Schemas
63
54
  // ============================================
64
55
 
65
- export const idSchema = z.string().uuid();
56
+ export const IdSchema = z.string().uuid();
66
57
 
67
- export const timestampsSchema = z.object({
58
+ export const TimestampsSchema = z.object({
68
59
  createdAt: z.coerce.date(),
69
60
  updatedAt: z.coerce.date(),
70
61
  });
71
62
 
72
- export const paginationSchema = z.object({
63
+ export const PaginationSchema = z.object({
73
64
  page: z.coerce.number().int().positive().default(1),
74
65
  limit: z.coerce.number().int().positive().max(100).default(20),
75
66
  });
76
67
 
77
- export const paginatedResponseSchema = <T extends z.ZodTypeAny>(itemSchema: T) =>
68
+ export const PaginatedResponseSchema = <T extends z.ZodTypeAny>(itemSchema: T) =>
78
69
  z.object({
79
70
  items: z.array(itemSchema),
80
71
  total: z.number(),
@@ -83,35 +74,61 @@ export const paginatedResponseSchema = <T extends z.ZodTypeAny>(itemSchema: T) =
83
74
  totalPages: z.number(),
84
75
  });
85
76
 
77
+ // ============================================
78
+ // Type Exports
79
+ // ============================================
80
+
81
+ export type Id = z.infer<typeof IdSchema>;
82
+ export type Timestamps = z.infer<typeof TimestampsSchema>;
83
+ export type Pagination = z.infer<typeof PaginationSchema>;
84
+ `;
85
+
86
+ // user.ts - user-related schemas
87
+ const userTs = `import { z } from 'zod';
88
+ import { IdSchema, TimestampsSchema } from './common.js';
89
+
86
90
  // ============================================
87
91
  // User Schemas
88
92
  // ============================================
89
93
 
90
- export const userSchema = z.object({
91
- id: idSchema,
94
+ export const UserSchema = z.object({
95
+ id: IdSchema,
92
96
  email: z.string().email(),
93
97
  name: z.string().min(1).max(100),
94
- ...timestampsSchema.shape,
98
+ ...TimestampsSchema.shape,
95
99
  });
96
100
 
97
- export const createUserSchema = userSchema.omit({
101
+ export const CreateUserSchema = UserSchema.omit({
98
102
  id: true,
99
103
  createdAt: true,
100
104
  updatedAt: true,
101
105
  });
102
106
 
103
- export const updateUserSchema = createUserSchema.partial();
107
+ export const UpdateUserSchema = CreateUserSchema.partial();
108
+
109
+ // ============================================
110
+ // Response Schemas
111
+ // ============================================
112
+
113
+ export const UserResponseSchema = UserSchema.pick({
114
+ id: true,
115
+ name: true,
116
+ email: true,
117
+ });
118
+
119
+ export const ListUsersResponseSchema = z.object({
120
+ users: z.array(UserSchema.pick({ id: true, name: true })),
121
+ });
104
122
 
105
123
  // ============================================
106
124
  // Type Exports
107
125
  // ============================================
108
126
 
109
- export type Id = z.infer<typeof idSchema>;
110
- export type Timestamps = z.infer<typeof timestampsSchema>;
111
- export type Pagination = z.infer<typeof paginationSchema>;
112
- export type User = z.infer<typeof userSchema>;
113
- export type CreateUser = z.infer<typeof createUserSchema>;
114
- export type UpdateUser = z.infer<typeof updateUserSchema>;
127
+ export type User = z.infer<typeof UserSchema>;
128
+ export type CreateUser = z.infer<typeof CreateUserSchema>;
129
+ export type UpdateUser = z.infer<typeof UpdateUserSchema>;
130
+ export type UserResponse = z.infer<typeof UserResponseSchema>;
131
+ export type ListUsersResponse = z.infer<typeof ListUsersResponseSchema>;
115
132
  `;
116
133
 
117
134
  return [
@@ -124,8 +141,12 @@ export type UpdateUser = z.infer<typeof updateUserSchema>;
124
141
  content: `${JSON.stringify(tsConfig, null, 2)}\n`,
125
142
  },
126
143
  {
127
- path: 'packages/models/src/index.ts',
128
- content: indexTs,
144
+ path: 'packages/models/src/common.ts',
145
+ content: commonTs,
146
+ },
147
+ {
148
+ path: 'packages/models/src/user.ts',
149
+ content: userTs,
129
150
  },
130
151
  ];
131
152
  }
package/src/init/index.ts CHANGED
@@ -395,6 +395,21 @@ export async function initCommand(
395
395
  }
396
396
  }
397
397
 
398
+ // Initialize git repository
399
+ console.log('\n📦 Initializing git repository...\n');
400
+ try {
401
+ execSync('git init', { cwd: targetDir, stdio: 'pipe' });
402
+ execSync('git branch -M main', { cwd: targetDir, stdio: 'pipe' });
403
+ execSync('git add .', { cwd: targetDir, stdio: 'pipe' });
404
+ execSync('git commit -m "🎉 Project created with @geekmidas/toolbox"', {
405
+ cwd: targetDir,
406
+ stdio: 'pipe',
407
+ });
408
+ console.log(' Initialized git repository on branch main');
409
+ } catch {
410
+ console.log(' Could not initialize git repository (git may not be installed)');
411
+ }
412
+
398
413
  // Print success message with next steps
399
414
  printNextSteps(name, templateOptions, pkgManager);
400
415
  }
@@ -15,6 +15,7 @@ export const apiTemplate: TemplateConfig = {
15
15
  '@geekmidas/envkit': GEEKMIDAS_VERSIONS['@geekmidas/envkit'],
16
16
  '@geekmidas/events': GEEKMIDAS_VERSIONS['@geekmidas/events'],
17
17
  '@geekmidas/logger': GEEKMIDAS_VERSIONS['@geekmidas/logger'],
18
+ '@geekmidas/rate-limit': GEEKMIDAS_VERSIONS['@geekmidas/rate-limit'],
18
19
  '@geekmidas/schema': GEEKMIDAS_VERSIONS['@geekmidas/schema'],
19
20
  '@geekmidas/services': GEEKMIDAS_VERSIONS['@geekmidas/services'],
20
21
  '@geekmidas/errors': GEEKMIDAS_VERSIONS['@geekmidas/errors'],
@@ -46,13 +47,16 @@ export const apiTemplate: TemplateConfig = {
46
47
  },
47
48
 
48
49
  files: (options: TemplateOptions): GeneratedFile[] => {
49
- const { loggerType, routesStructure } = options;
50
+ const { loggerType, routesStructure, monorepo, name } = options;
50
51
 
51
52
  const loggerContent = `import { createLogger } from '@geekmidas/logger/${loggerType}';
52
53
 
53
54
  export const logger = createLogger();
54
55
  `;
55
56
 
57
+ // Models package import path for monorepo
58
+ const modelsImport = monorepo ? `@${name}/models` : null;
59
+
56
60
  // Get route path based on structure
57
61
  const getRoutePath = (file: string) => {
58
62
  switch (routesStructure) {
@@ -100,9 +104,14 @@ export const config = envParser
100
104
  {
101
105
  path: getRoutePath('health.ts'),
102
106
  content: `import { e } from '@geekmidas/constructs/endpoints';
107
+ import { z } from 'zod';
103
108
 
104
109
  export const healthEndpoint = e
105
110
  .get('/health')
111
+ .output(z.object({
112
+ status: z.string(),
113
+ timestamp: z.string(),
114
+ }))
106
115
  .handle(async () => ({
107
116
  status: 'ok',
108
117
  timestamp: new Date().toISOString(),
@@ -113,10 +122,33 @@ export const healthEndpoint = e
113
122
  // users endpoints
114
123
  {
115
124
  path: getRoutePath('users/list.ts'),
116
- content: `import { e } from '@geekmidas/constructs/endpoints';
125
+ content: modelsImport
126
+ ? `import { e } from '@geekmidas/constructs/endpoints';
127
+ import { ListUsersResponseSchema } from '${modelsImport}/user';
117
128
 
118
129
  export const listUsersEndpoint = e
119
130
  .get('/users')
131
+ .output(ListUsersResponseSchema)
132
+ .handle(async () => ({
133
+ users: [
134
+ { id: '1', name: 'Alice' },
135
+ { id: '2', name: 'Bob' },
136
+ ],
137
+ }));
138
+ `
139
+ : `import { e } from '@geekmidas/constructs/endpoints';
140
+ import { z } from 'zod';
141
+
142
+ const UserSchema = z.object({
143
+ id: z.string(),
144
+ name: z.string(),
145
+ });
146
+
147
+ export const listUsersEndpoint = e
148
+ .get('/users')
149
+ .output(z.object({
150
+ users: z.array(UserSchema),
151
+ }))
120
152
  .handle(async () => ({
121
153
  users: [
122
154
  { id: '1', name: 'Alice' },
@@ -127,12 +159,32 @@ export const listUsersEndpoint = e
127
159
  },
128
160
  {
129
161
  path: getRoutePath('users/get.ts'),
130
- content: `import { e } from '@geekmidas/constructs/endpoints';
162
+ content: modelsImport
163
+ ? `import { e } from '@geekmidas/constructs/endpoints';
164
+ import { z } from 'zod';
165
+ import { UserResponseSchema } from '${modelsImport}/user';
166
+
167
+ export const getUserEndpoint = e
168
+ .get('/users/:id')
169
+ .params(z.object({ id: z.string() }))
170
+ .output(UserResponseSchema)
171
+ .handle(async ({ params }) => ({
172
+ id: params.id,
173
+ name: 'Alice',
174
+ email: 'alice@example.com',
175
+ }));
176
+ `
177
+ : `import { e } from '@geekmidas/constructs/endpoints';
131
178
  import { z } from 'zod';
132
179
 
133
180
  export const getUserEndpoint = e
134
181
  .get('/users/:id')
135
182
  .params(z.object({ id: z.string() }))
183
+ .output(z.object({
184
+ id: z.string(),
185
+ name: z.string(),
186
+ email: z.string().email(),
187
+ }))
136
188
  .handle(async ({ params }) => ({
137
189
  id: params.id,
138
190
  name: 'Alice',
@@ -14,6 +14,7 @@ export const minimalTemplate: TemplateConfig = {
14
14
  '@geekmidas/constructs': GEEKMIDAS_VERSIONS['@geekmidas/constructs'],
15
15
  '@geekmidas/envkit': GEEKMIDAS_VERSIONS['@geekmidas/envkit'],
16
16
  '@geekmidas/logger': GEEKMIDAS_VERSIONS['@geekmidas/logger'],
17
+ '@geekmidas/rate-limit': GEEKMIDAS_VERSIONS['@geekmidas/rate-limit'],
17
18
  '@geekmidas/schema': GEEKMIDAS_VERSIONS['@geekmidas/schema'],
18
19
  '@hono/node-server': '~1.14.1',
19
20
  hono: '~4.8.2',
@@ -91,9 +92,14 @@ export const config = envParser
91
92
  {
92
93
  path: getRoutePath('health.ts'),
93
94
  content: `import { e } from '@geekmidas/constructs/endpoints';
95
+ import { z } from 'zod';
94
96
 
95
97
  export const healthEndpoint = e
96
98
  .get('/health')
99
+ .output(z.object({
100
+ status: z.string(),
101
+ timestamp: z.string(),
102
+ }))
97
103
  .handle(async () => ({
98
104
  status: 'ok',
99
105
  timestamp: new Date().toISOString(),
@@ -15,6 +15,7 @@ export const serverlessTemplate: TemplateConfig = {
15
15
  '@geekmidas/envkit': GEEKMIDAS_VERSIONS['@geekmidas/envkit'],
16
16
  '@geekmidas/logger': GEEKMIDAS_VERSIONS['@geekmidas/logger'],
17
17
  '@geekmidas/cloud': GEEKMIDAS_VERSIONS['@geekmidas/cloud'],
18
+ '@geekmidas/rate-limit': GEEKMIDAS_VERSIONS['@geekmidas/rate-limit'],
18
19
  '@geekmidas/schema': GEEKMIDAS_VERSIONS['@geekmidas/schema'],
19
20
  '@hono/node-server': '~1.14.1',
20
21
  hono: '~4.8.2',
@@ -93,9 +94,15 @@ export const config = envParser
93
94
  {
94
95
  path: getRoutePath('health.ts'),
95
96
  content: `import { e } from '@geekmidas/constructs/endpoints';
97
+ import { z } from 'zod';
96
98
 
97
99
  export const healthEndpoint = e
98
100
  .get('/health')
101
+ .output(z.object({
102
+ status: z.string(),
103
+ timestamp: z.string(),
104
+ region: z.string(),
105
+ }))
99
106
  .handle(async () => ({
100
107
  status: 'ok',
101
108
  timestamp: new Date().toISOString(),
@@ -15,6 +15,7 @@ export const workerTemplate: TemplateConfig = {
15
15
  '@geekmidas/envkit': GEEKMIDAS_VERSIONS['@geekmidas/envkit'],
16
16
  '@geekmidas/events': GEEKMIDAS_VERSIONS['@geekmidas/events'],
17
17
  '@geekmidas/logger': GEEKMIDAS_VERSIONS['@geekmidas/logger'],
18
+ '@geekmidas/rate-limit': GEEKMIDAS_VERSIONS['@geekmidas/rate-limit'],
18
19
  '@geekmidas/schema': GEEKMIDAS_VERSIONS['@geekmidas/schema'],
19
20
  '@hono/node-server': '~1.14.1',
20
21
  hono: '~4.8.2',
@@ -92,9 +93,14 @@ export const config = envParser
92
93
  {
93
94
  path: getRoutePath('health.ts'),
94
95
  content: `import { e } from '@geekmidas/constructs/endpoints';
96
+ import { z } from 'zod';
95
97
 
96
98
  export const healthEndpoint = e
97
99
  .get('/health')
100
+ .output(z.object({
101
+ status: z.string(),
102
+ timestamp: z.string(),
103
+ }))
98
104
  .handle(async () => ({
99
105
  status: 'ok',
100
106
  timestamp: new Date().toISOString(),
@@ -41,20 +41,19 @@ describe('defineWorkspace', () => {
41
41
  );
42
42
  });
43
43
 
44
- it('should throw with descriptive error for missing routes', () => {
44
+ it('should allow backend apps without routes (e.g., auth servers)', () => {
45
45
  const config = {
46
46
  apps: {
47
- api: {
47
+ auth: {
48
48
  type: 'backend',
49
- path: 'apps/api',
49
+ path: 'apps/auth',
50
50
  port: 3000,
51
51
  },
52
52
  },
53
53
  } as WorkspaceConfig;
54
54
 
55
- expect(() => defineWorkspace(config)).toThrow(
56
- 'Backend apps must have routes defined',
57
- );
55
+ // Should not throw - routes are optional for backend apps
56
+ expect(() => defineWorkspace(config)).not.toThrow();
58
57
  });
59
58
  });
60
59
 
@@ -142,21 +142,21 @@ describe('WorkspaceConfigSchema', () => {
142
142
  expect(result.error).toBeDefined();
143
143
  });
144
144
 
145
- it('should reject backend app without routes', () => {
145
+ it('should allow backend app without routes (e.g., auth servers)', () => {
146
146
  const config = {
147
147
  apps: {
148
- api: {
148
+ auth: {
149
149
  type: 'backend' as const,
150
- path: 'apps/api',
150
+ path: 'apps/auth',
151
151
  port: 3000,
152
- // Missing routes
152
+ // Routes are optional for backend apps
153
153
  },
154
154
  },
155
155
  };
156
156
 
157
157
  const result = safeValidateWorkspaceConfig(config);
158
158
 
159
- expect(result.success).toBe(false);
159
+ expect(result.success).toBe(true);
160
160
  });
161
161
 
162
162
  it('should reject frontend app without framework', () => {
@@ -203,19 +203,7 @@ const AppConfigSchema = z
203
203
  framework: z.enum(['nextjs']).optional(),
204
204
  client: ClientConfigSchema.optional(),
205
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
- )
206
+ // Note: routes is optional for backend apps - some backends like auth servers don't use routes
219
207
  .refine(
220
208
  (data) => {
221
209
  // Frontend apps must have framework