@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.
Files changed (118) hide show
  1. package/dist/{bundler-C74EKlNa.cjs → bundler-CyHg1v_T.cjs} +3 -3
  2. package/dist/{bundler-C74EKlNa.cjs.map → bundler-CyHg1v_T.cjs.map} +1 -1
  3. package/dist/{bundler-B6z6HEeh.mjs → bundler-DQIuE3Kn.mjs} +3 -3
  4. package/dist/{bundler-B6z6HEeh.mjs.map → bundler-DQIuE3Kn.mjs.map} +1 -1
  5. package/dist/{config-DYULeEv8.mjs → config-BaYqrF3n.mjs} +48 -10
  6. package/dist/config-BaYqrF3n.mjs.map +1 -0
  7. package/dist/{config-AmInkU7k.cjs → config-CxrLu8ia.cjs} +53 -9
  8. package/dist/config-CxrLu8ia.cjs.map +1 -0
  9. package/dist/config.cjs +4 -1
  10. package/dist/config.d.cts +27 -2
  11. package/dist/config.d.cts.map +1 -1
  12. package/dist/config.d.mts +27 -2
  13. package/dist/config.d.mts.map +1 -1
  14. package/dist/config.mjs +3 -2
  15. package/dist/dokploy-api-B0w17y4_.mjs +3 -0
  16. package/dist/{dokploy-api-CaETb2L6.mjs → dokploy-api-B9qR2Yn1.mjs} +1 -1
  17. package/dist/{dokploy-api-CaETb2L6.mjs.map → dokploy-api-B9qR2Yn1.mjs.map} +1 -1
  18. package/dist/dokploy-api-BnGeUqN4.cjs +3 -0
  19. package/dist/{dokploy-api-C7F9VykY.cjs → dokploy-api-C5czOZoc.cjs} +1 -1
  20. package/dist/{dokploy-api-C7F9VykY.cjs.map → dokploy-api-C5czOZoc.cjs.map} +1 -1
  21. package/dist/{encryption-D7Efcdi9.cjs → encryption-BAz0xQ1Q.cjs} +1 -1
  22. package/dist/{encryption-D7Efcdi9.cjs.map → encryption-BAz0xQ1Q.cjs.map} +1 -1
  23. package/dist/{encryption-h4Nb6W-M.mjs → encryption-JtMsiGNp.mjs} +2 -2
  24. package/dist/{encryption-h4Nb6W-M.mjs.map → encryption-JtMsiGNp.mjs.map} +1 -1
  25. package/dist/index-CWN-bgrO.d.mts +495 -0
  26. package/dist/index-CWN-bgrO.d.mts.map +1 -0
  27. package/dist/index-DEWYvYvg.d.cts +495 -0
  28. package/dist/index-DEWYvYvg.d.cts.map +1 -0
  29. package/dist/index.cjs +2640 -564
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.mjs +2635 -564
  32. package/dist/index.mjs.map +1 -1
  33. package/dist/{openapi-CZVcfxk-.mjs → openapi-CgqR6Jkw.mjs} +3 -3
  34. package/dist/{openapi-CZVcfxk-.mjs.map → openapi-CgqR6Jkw.mjs.map} +1 -1
  35. package/dist/{openapi-C89hhkZC.cjs → openapi-DfpxS0xv.cjs} +8 -2
  36. package/dist/{openapi-C89hhkZC.cjs.map → openapi-DfpxS0xv.cjs.map} +1 -1
  37. package/dist/{openapi-react-query-CM2_qlW9.mjs → openapi-react-query-5rSortLH.mjs} +1 -1
  38. package/dist/{openapi-react-query-CM2_qlW9.mjs.map → openapi-react-query-5rSortLH.mjs.map} +1 -1
  39. package/dist/{openapi-react-query-iKjfLzff.cjs → openapi-react-query-DvNpdDpM.cjs} +1 -1
  40. package/dist/{openapi-react-query-iKjfLzff.cjs.map → openapi-react-query-DvNpdDpM.cjs.map} +1 -1
  41. package/dist/openapi-react-query.cjs +1 -1
  42. package/dist/openapi-react-query.mjs +1 -1
  43. package/dist/openapi.cjs +3 -2
  44. package/dist/openapi.d.cts +1 -1
  45. package/dist/openapi.d.mts +1 -1
  46. package/dist/openapi.mjs +3 -2
  47. package/dist/{storage-Bn3K9Ccu.cjs → storage-BPRgh3DU.cjs} +136 -5
  48. package/dist/storage-BPRgh3DU.cjs.map +1 -0
  49. package/dist/{storage-nkGIjeXt.mjs → storage-DNj_I11J.mjs} +1 -1
  50. package/dist/storage-Dhst7BhI.mjs +272 -0
  51. package/dist/storage-Dhst7BhI.mjs.map +1 -0
  52. package/dist/{storage-UfyTn7Zm.cjs → storage-fOR8dMu5.cjs} +1 -1
  53. package/dist/{types-iFk5ms7y.d.mts → types-K2uQJ-FO.d.mts} +2 -2
  54. package/dist/{types-BgaMXsUa.d.cts.map → types-K2uQJ-FO.d.mts.map} +1 -1
  55. package/dist/{types-BgaMXsUa.d.cts → types-l53qUmGt.d.cts} +2 -2
  56. package/dist/{types-iFk5ms7y.d.mts.map → types-l53qUmGt.d.cts.map} +1 -1
  57. package/dist/workspace/index.cjs +19 -0
  58. package/dist/workspace/index.d.cts +3 -0
  59. package/dist/workspace/index.d.mts +3 -0
  60. package/dist/workspace/index.mjs +3 -0
  61. package/dist/workspace-CPLEZDZf.mjs +3788 -0
  62. package/dist/workspace-CPLEZDZf.mjs.map +1 -0
  63. package/dist/workspace-iWgBlX6h.cjs +3885 -0
  64. package/dist/workspace-iWgBlX6h.cjs.map +1 -0
  65. package/package.json +9 -4
  66. package/src/build/__tests__/workspace-build.spec.ts +215 -0
  67. package/src/build/index.ts +189 -1
  68. package/src/config.ts +71 -14
  69. package/src/deploy/__tests__/docker.spec.ts +1 -1
  70. package/src/deploy/__tests__/index.spec.ts +305 -1
  71. package/src/deploy/index.ts +426 -4
  72. package/src/deploy/types.ts +32 -0
  73. package/src/dev/__tests__/index.spec.ts +572 -1
  74. package/src/dev/index.ts +582 -2
  75. package/src/docker/__tests__/compose.spec.ts +425 -0
  76. package/src/docker/__tests__/templates.spec.ts +145 -0
  77. package/src/docker/compose.ts +248 -0
  78. package/src/docker/index.ts +159 -3
  79. package/src/docker/templates.ts +219 -4
  80. package/src/index.ts +24 -0
  81. package/src/init/__tests__/generators.spec.ts +17 -24
  82. package/src/init/__tests__/init.spec.ts +157 -5
  83. package/src/init/generators/auth.ts +220 -0
  84. package/src/init/generators/config.ts +61 -4
  85. package/src/init/generators/docker.ts +115 -8
  86. package/src/init/generators/env.ts +7 -127
  87. package/src/init/generators/index.ts +1 -0
  88. package/src/init/generators/models.ts +3 -1
  89. package/src/init/generators/monorepo.ts +154 -10
  90. package/src/init/generators/package.ts +5 -3
  91. package/src/init/generators/web.ts +213 -0
  92. package/src/init/index.ts +290 -58
  93. package/src/init/templates/api.ts +38 -29
  94. package/src/init/templates/index.ts +132 -4
  95. package/src/init/templates/minimal.ts +33 -35
  96. package/src/init/templates/serverless.ts +16 -19
  97. package/src/init/templates/worker.ts +50 -25
  98. package/src/init/versions.ts +47 -0
  99. package/src/secrets/keystore.ts +144 -0
  100. package/src/secrets/storage.ts +109 -6
  101. package/src/test/index.ts +97 -0
  102. package/src/workspace/__tests__/client-generator.spec.ts +357 -0
  103. package/src/workspace/__tests__/index.spec.ts +543 -0
  104. package/src/workspace/__tests__/schema.spec.ts +519 -0
  105. package/src/workspace/__tests__/type-inference.spec.ts +251 -0
  106. package/src/workspace/client-generator.ts +307 -0
  107. package/src/workspace/index.ts +372 -0
  108. package/src/workspace/schema.ts +368 -0
  109. package/src/workspace/types.ts +336 -0
  110. package/tsconfig.tsbuildinfo +1 -1
  111. package/tsdown.config.ts +1 -0
  112. package/dist/config-AmInkU7k.cjs.map +0 -1
  113. package/dist/config-DYULeEv8.mjs.map +0 -1
  114. package/dist/dokploy-api-B7KxOQr3.cjs +0 -3
  115. package/dist/dokploy-api-DHvfmWbi.mjs +0 -3
  116. package/dist/storage-BaOP55oq.mjs +0 -147
  117. package/dist/storage-BaOP55oq.mjs.map +0 -1
  118. package/dist/storage-Bn3K9Ccu.cjs.map +0 -1
@@ -1,3 +1,4 @@
1
+ import { GEEKMIDAS_VERSIONS } from '../versions.js';
1
2
  import type {
2
3
  GeneratedFile,
3
4
  TemplateConfig,
@@ -9,19 +10,19 @@ export const apiTemplate: TemplateConfig = {
9
10
  description: 'Full API with auth, database, services',
10
11
 
11
12
  dependencies: {
12
- '@geekmidas/constructs': 'workspace:*',
13
- '@geekmidas/envkit': 'workspace:*',
14
- '@geekmidas/logger': 'workspace:*',
15
- '@geekmidas/services': 'workspace:*',
16
- '@geekmidas/errors': 'workspace:*',
17
- '@geekmidas/auth': 'workspace:*',
13
+ '@geekmidas/constructs': GEEKMIDAS_VERSIONS['@geekmidas/constructs'],
14
+ '@geekmidas/envkit': GEEKMIDAS_VERSIONS['@geekmidas/envkit'],
15
+ '@geekmidas/logger': GEEKMIDAS_VERSIONS['@geekmidas/logger'],
16
+ '@geekmidas/services': GEEKMIDAS_VERSIONS['@geekmidas/services'],
17
+ '@geekmidas/errors': GEEKMIDAS_VERSIONS['@geekmidas/errors'],
18
+ '@geekmidas/auth': GEEKMIDAS_VERSIONS['@geekmidas/auth'],
18
19
  hono: '~4.8.2',
19
20
  pino: '~9.6.0',
20
21
  },
21
22
 
22
23
  devDependencies: {
23
- '@biomejs/biome': '~1.9.4',
24
- '@geekmidas/cli': 'workspace:*',
24
+ '@biomejs/biome': '~2.3.0',
25
+ '@geekmidas/cli': GEEKMIDAS_VERSIONS['@geekmidas/cli'],
25
26
  '@types/node': '~22.0.0',
26
27
  tsx: '~4.20.0',
27
28
  turbo: '~2.3.0',
@@ -69,22 +70,17 @@ export const logger = createLogger();
69
70
  // src/config/env.ts
70
71
  {
71
72
  path: 'src/config/env.ts',
72
- content: `import { EnvironmentParser } from '@geekmidas/envkit';
73
+ content: `import { Credentials } from '@geekmidas/envkit/credentials';
74
+ import { EnvironmentParser } from '@geekmidas/envkit';
73
75
 
74
- export const envParser = new EnvironmentParser(process.env);
76
+ export const envParser = new EnvironmentParser({ ...process.env, ...Credentials });
75
77
 
78
+ // Global config - only minimal shared values
79
+ // Service-specific config should be parsed in each service
76
80
  export const config = envParser
77
81
  .create((get) => ({
78
- port: get('PORT').string().transform(Number).default(3000),
79
- nodeEnv: get('NODE_ENV').string().default('development'),
80
- jwtSecret: get('JWT_SECRET').string().default('change-me-in-production'),${
81
- options.database
82
- ? `
83
- database: {
84
- url: get('DATABASE_URL').string().default('postgresql://localhost:5432/mydb'),
85
- },`
86
- : ''
87
- }
82
+ nodeEnv: get('NODE_ENV').enum(['development', 'test', 'production']).default('development'),
83
+ stage: get('STAGE').enum(['development', 'staging', 'production']).default('development'),
88
84
  }))
89
85
  .parse();
90
86
  `,
@@ -101,7 +97,7 @@ export const config = envParser
101
97
  path: getRoutePath('health.ts'),
102
98
  content: `import { e } from '@geekmidas/constructs/endpoints';
103
99
 
104
- export default e
100
+ export const healthEndpoint = e
105
101
  .get('/health')
106
102
  .handle(async () => ({
107
103
  status: 'ok',
@@ -115,7 +111,7 @@ export default e
115
111
  path: getRoutePath('users/list.ts'),
116
112
  content: `import { e } from '@geekmidas/constructs/endpoints';
117
113
 
118
- export default e
114
+ export const listUsersEndpoint = e
119
115
  .get('/users')
120
116
  .handle(async () => ({
121
117
  users: [
@@ -130,7 +126,7 @@ export default e
130
126
  content: `import { e } from '@geekmidas/constructs/endpoints';
131
127
  import { z } from 'zod';
132
128
 
133
- export default e
129
+ export const getUserEndpoint = e
134
130
  .get('/users/:id')
135
131
  .params(z.object({ id: z.string() }))
136
132
  .handle(async ({ params }) => ({
@@ -146,7 +142,7 @@ export default e
146
142
  if (options.database) {
147
143
  files.push({
148
144
  path: 'src/services/database.ts',
149
- content: `import type { Service } from '@geekmidas/services';
145
+ content: `import type { Service, ServiceRegisterOptions } from '@geekmidas/services';
150
146
  import { Kysely, PostgresDialect } from 'kysely';
151
147
  import pg from 'pg';
152
148
 
@@ -162,18 +158,24 @@ export interface Database {
162
158
 
163
159
  export const databaseService = {
164
160
  serviceName: 'database' as const,
165
- async register(envParser) {
161
+ async register({ envParser, context }: ServiceRegisterOptions) {
162
+ const logger = context.getLogger();
163
+ logger.info('Connecting to database');
164
+
166
165
  const config = envParser
167
166
  .create((get) => ({
168
167
  url: get('DATABASE_URL').string(),
169
168
  }))
170
169
  .parse();
171
170
 
172
- return new Kysely<Database>({
171
+ const db = new Kysely<Database>({
173
172
  dialect: new PostgresDialect({
174
173
  pool: new pg.Pool({ connectionString: config.url }),
175
174
  }),
176
175
  });
176
+
177
+ logger.info('Database connection established');
178
+ return db;
177
179
  },
178
180
  } satisfies Service<'database', Kysely<Database>>;
179
181
  `,
@@ -202,13 +204,20 @@ export const telescope = new Telescope({
202
204
  content: `import { Direction, InMemoryMonitoringStorage, Studio } from '@geekmidas/studio';
203
205
  import { Kysely, PostgresDialect } from 'kysely';
204
206
  import pg from 'pg';
205
- import type { Database } from '../services/database';
206
- import { config } from './env';
207
+ import type { Database } from '../services/database.js';
208
+ import { envParser } from './env.js';
209
+
210
+ // Parse database config for Studio
211
+ const studioConfig = envParser
212
+ .create((get) => ({
213
+ databaseUrl: get('DATABASE_URL').string(),
214
+ }))
215
+ .parse();
207
216
 
208
217
  // Create a Kysely instance for Studio
209
218
  const db = new Kysely<Database>({
210
219
  dialect: new PostgresDialect({
211
- pool: new pg.Pool({ connectionString: config.database.url }),
220
+ pool: new pg.Pool({ connectionString: studioConfig.databaseUrl }),
212
221
  }),
213
222
  });
214
223
 
@@ -21,6 +21,25 @@ export type RoutesStructure =
21
21
  | 'centralized-routes'
22
22
  | 'domain-based';
23
23
 
24
+ /**
25
+ * Package manager type
26
+ */
27
+ export type PackageManager = 'pnpm' | 'npm' | 'yarn' | 'bun';
28
+
29
+ /**
30
+ * Deploy target type
31
+ */
32
+ export type DeployTarget = 'dokploy' | 'none';
33
+
34
+ /**
35
+ * Services selection
36
+ */
37
+ export interface ServicesSelection {
38
+ db: boolean;
39
+ cache: boolean;
40
+ mail: boolean;
41
+ }
42
+
24
43
  /**
25
44
  * Options collected from user prompts
26
45
  */
@@ -35,6 +54,12 @@ export interface TemplateOptions {
35
54
  monorepo: boolean;
36
55
  /** Path for the API app in monorepo (e.g., 'apps/api') */
37
56
  apiPath: string;
57
+ /** Selected package manager */
58
+ packageManager: PackageManager;
59
+ /** Deploy target */
60
+ deployTarget: DeployTarget;
61
+ /** Services selection */
62
+ services: ServicesSelection;
38
63
  }
39
64
 
40
65
  /**
@@ -57,12 +82,20 @@ export interface TemplateConfig {
57
82
  files: (options: TemplateOptions) => GeneratedFile[];
58
83
  }
59
84
 
60
- export type TemplateName = 'minimal' | 'api' | 'serverless' | 'worker';
85
+ export type TemplateName =
86
+ | 'minimal'
87
+ | 'api'
88
+ | 'serverless'
89
+ | 'worker'
90
+ | 'fullstack';
61
91
 
62
92
  /**
63
93
  * All available templates
64
94
  */
65
- export const templates: Record<TemplateName, TemplateConfig> = {
95
+ export const templates: Record<
96
+ Exclude<TemplateName, 'fullstack'>,
97
+ TemplateConfig
98
+ > = {
66
99
  minimal: minimalTemplate,
67
100
  api: apiTemplate,
68
101
  serverless: serverlessTemplate,
@@ -70,9 +103,25 @@ export const templates: Record<TemplateName, TemplateConfig> = {
70
103
  };
71
104
 
72
105
  /**
73
- * Template choices for prompts
106
+ * Template choices for prompts (Story 1.11 simplified to api + fullstack)
74
107
  */
75
108
  export const templateChoices = [
109
+ {
110
+ title: 'API',
111
+ value: 'api' as TemplateName,
112
+ description: 'Single backend API with endpoints',
113
+ },
114
+ {
115
+ title: 'Fullstack',
116
+ value: 'fullstack' as TemplateName,
117
+ description: 'Monorepo with API + Next.js + shared models',
118
+ },
119
+ ];
120
+
121
+ /**
122
+ * All template choices (includes advanced options)
123
+ */
124
+ export const allTemplateChoices = [
76
125
  {
77
126
  title: 'Minimal',
78
127
  value: 'minimal' as TemplateName,
@@ -83,6 +132,11 @@ export const templateChoices = [
83
132
  value: 'api' as TemplateName,
84
133
  description: 'Full API with auth, database, services',
85
134
  },
135
+ {
136
+ title: 'Fullstack',
137
+ value: 'fullstack' as TemplateName,
138
+ description: 'Monorepo with API + Next.js + shared models',
139
+ },
86
140
  {
87
141
  title: 'Serverless',
88
142
  value: 'serverless' as TemplateName,
@@ -132,13 +186,87 @@ export const routesStructureChoices = [
132
186
  },
133
187
  ];
134
188
 
189
+ /**
190
+ * Package manager choices for prompts
191
+ */
192
+ export const packageManagerChoices = [
193
+ {
194
+ title: 'pnpm',
195
+ value: 'pnpm' as PackageManager,
196
+ description: 'Fast, disk space efficient (recommended)',
197
+ },
198
+ {
199
+ title: 'npm',
200
+ value: 'npm' as PackageManager,
201
+ description: 'Node.js default package manager',
202
+ },
203
+ {
204
+ title: 'yarn',
205
+ value: 'yarn' as PackageManager,
206
+ description: 'Yarn package manager',
207
+ },
208
+ {
209
+ title: 'bun',
210
+ value: 'bun' as PackageManager,
211
+ description: 'Fast JavaScript runtime and package manager',
212
+ },
213
+ ];
214
+
215
+ /**
216
+ * Deploy target choices for prompts
217
+ */
218
+ export const deployTargetChoices = [
219
+ {
220
+ title: 'Dokploy',
221
+ value: 'dokploy' as DeployTarget,
222
+ description: 'Deploy to Dokploy (Docker-based hosting)',
223
+ },
224
+ {
225
+ title: 'Configure later',
226
+ value: 'none' as DeployTarget,
227
+ description: 'Skip deployment setup for now',
228
+ },
229
+ ];
230
+
231
+ /**
232
+ * Services choices for multi-select prompt
233
+ */
234
+ export const servicesChoices = [
235
+ {
236
+ title: 'PostgreSQL',
237
+ value: 'db',
238
+ description: 'PostgreSQL database',
239
+ },
240
+ {
241
+ title: 'Redis',
242
+ value: 'cache',
243
+ description: 'Redis cache',
244
+ },
245
+ {
246
+ title: 'Mailpit',
247
+ value: 'mail',
248
+ description: 'Email testing service (dev only)',
249
+ },
250
+ ];
251
+
135
252
  /**
136
253
  * Get a template by name
137
254
  */
138
- export function getTemplate(name: TemplateName): TemplateConfig {
255
+ export function getTemplate(name: TemplateName): TemplateConfig | null {
256
+ if (name === 'fullstack') {
257
+ // Fullstack template is handled specially, uses api template as base
258
+ return templates.api;
259
+ }
139
260
  const template = templates[name];
140
261
  if (!template) {
141
262
  throw new Error(`Unknown template: ${name}`);
142
263
  }
143
264
  return template;
144
265
  }
266
+
267
+ /**
268
+ * Check if a template is the fullstack monorepo template
269
+ */
270
+ export function isFullstackTemplate(name: TemplateName): boolean {
271
+ return name === 'fullstack';
272
+ }
@@ -1,3 +1,4 @@
1
+ import { GEEKMIDAS_VERSIONS } from '../versions.js';
1
2
  import type {
2
3
  GeneratedFile,
3
4
  TemplateConfig,
@@ -9,16 +10,16 @@ export const minimalTemplate: TemplateConfig = {
9
10
  description: 'Basic health endpoint',
10
11
 
11
12
  dependencies: {
12
- '@geekmidas/constructs': 'workspace:*',
13
- '@geekmidas/envkit': 'workspace:*',
14
- '@geekmidas/logger': 'workspace:*',
13
+ '@geekmidas/constructs': GEEKMIDAS_VERSIONS['@geekmidas/constructs'],
14
+ '@geekmidas/envkit': GEEKMIDAS_VERSIONS['@geekmidas/envkit'],
15
+ '@geekmidas/logger': GEEKMIDAS_VERSIONS['@geekmidas/logger'],
15
16
  hono: '~4.8.2',
16
17
  pino: '~9.6.0',
17
18
  },
18
19
 
19
20
  devDependencies: {
20
- '@biomejs/biome': '~1.9.4',
21
- '@geekmidas/cli': 'workspace:*',
21
+ '@biomejs/biome': '~2.3.0',
22
+ '@geekmidas/cli': GEEKMIDAS_VERSIONS['@geekmidas/cli'],
22
23
  '@types/node': '~22.0.0',
23
24
  tsx: '~4.20.0',
24
25
  turbo: '~2.3.0',
@@ -61,14 +62,17 @@ export const logger = createLogger();
61
62
  // src/config/env.ts
62
63
  {
63
64
  path: 'src/config/env.ts',
64
- content: `import { EnvironmentParser } from '@geekmidas/envkit';
65
+ content: `import { Credentials } from '@geekmidas/envkit/credentials';
66
+ import { EnvironmentParser } from '@geekmidas/envkit';
65
67
 
66
- export const envParser = new EnvironmentParser(process.env);
68
+ export const envParser = new EnvironmentParser({ ...process.env, ...Credentials });
67
69
 
70
+ // Global config - only minimal shared values
71
+ // Service-specific config should be parsed in each service
68
72
  export const config = envParser
69
73
  .create((get) => ({
70
- port: get('PORT').string().transform(Number).default(3000),
71
- nodeEnv: get('NODE_ENV').string().default('development'),
74
+ nodeEnv: get('NODE_ENV').enum(['development', 'test', 'production']).default('development'),
75
+ stage: get('STAGE').enum(['development', 'staging', 'production']).default('development'),
72
76
  }))
73
77
  .parse();
74
78
  `,
@@ -85,7 +89,7 @@ export const config = envParser
85
89
  path: getRoutePath('health.ts'),
86
90
  content: `import { e } from '@geekmidas/constructs/endpoints';
87
91
 
88
- export default e
92
+ export const healthEndpoint = e
89
93
  .get('/health')
90
94
  .handle(async () => ({
91
95
  status: 'ok',
@@ -97,28 +101,9 @@ export default e
97
101
 
98
102
  // Add database service if enabled
99
103
  if (options.database) {
100
- // Update env.ts to include database config
101
- files[0] = {
102
- path: 'src/config/env.ts',
103
- content: `import { EnvironmentParser } from '@geekmidas/envkit';
104
-
105
- export const envParser = new EnvironmentParser(process.env);
106
-
107
- export const config = envParser
108
- .create((get) => ({
109
- port: get('PORT').string().transform(Number).default(3000),
110
- nodeEnv: get('NODE_ENV').string().default('development'),
111
- database: {
112
- url: get('DATABASE_URL').string().default('postgresql://localhost:5432/mydb'),
113
- },
114
- }))
115
- .parse();
116
- `,
117
- };
118
-
119
104
  files.push({
120
105
  path: 'src/services/database.ts',
121
- content: `import type { Service } from '@geekmidas/services';
106
+ content: `import type { Service, ServiceRegisterOptions } from '@geekmidas/services';
122
107
  import { Kysely, PostgresDialect } from 'kysely';
123
108
  import pg from 'pg';
124
109
 
@@ -129,18 +114,24 @@ export interface Database {
129
114
 
130
115
  export const databaseService = {
131
116
  serviceName: 'database' as const,
132
- async register(envParser) {
117
+ async register({ envParser, context }: ServiceRegisterOptions) {
118
+ const logger = context.getLogger();
119
+ logger.info('Connecting to database');
120
+
133
121
  const config = envParser
134
122
  .create((get) => ({
135
123
  url: get('DATABASE_URL').string(),
136
124
  }))
137
125
  .parse();
138
126
 
139
- return new Kysely<Database>({
127
+ const db = new Kysely<Database>({
140
128
  dialect: new PostgresDialect({
141
129
  pool: new pg.Pool({ connectionString: config.url }),
142
130
  }),
143
131
  });
132
+
133
+ logger.info('Database connection established');
134
+ return db;
144
135
  },
145
136
  } satisfies Service<'database', Kysely<Database>>;
146
137
  `,
@@ -169,13 +160,20 @@ export const telescope = new Telescope({
169
160
  content: `import { Direction, InMemoryMonitoringStorage, Studio } from '@geekmidas/studio';
170
161
  import { Kysely, PostgresDialect } from 'kysely';
171
162
  import pg from 'pg';
172
- import type { Database } from '../services/database';
173
- import { config } from './env';
163
+ import type { Database } from '../services/database.js';
164
+ import { envParser } from './env.js';
165
+
166
+ // Parse database config for Studio
167
+ const studioConfig = envParser
168
+ .create((get) => ({
169
+ databaseUrl: get('DATABASE_URL').string(),
170
+ }))
171
+ .parse();
174
172
 
175
173
  // Create a Kysely instance for Studio
176
174
  const db = new Kysely<Database>({
177
175
  dialect: new PostgresDialect({
178
- pool: new pg.Pool({ connectionString: config.database.url }),
176
+ pool: new pg.Pool({ connectionString: studioConfig.databaseUrl }),
179
177
  }),
180
178
  });
181
179
 
@@ -1,3 +1,4 @@
1
+ import { GEEKMIDAS_VERSIONS } from '../versions.js';
1
2
  import type {
2
3
  GeneratedFile,
3
4
  TemplateConfig,
@@ -9,17 +10,17 @@ export const serverlessTemplate: TemplateConfig = {
9
10
  description: 'AWS Lambda handlers',
10
11
 
11
12
  dependencies: {
12
- '@geekmidas/constructs': 'workspace:*',
13
- '@geekmidas/envkit': 'workspace:*',
14
- '@geekmidas/logger': 'workspace:*',
15
- '@geekmidas/cloud': 'workspace:*',
13
+ '@geekmidas/constructs': GEEKMIDAS_VERSIONS['@geekmidas/constructs'],
14
+ '@geekmidas/envkit': GEEKMIDAS_VERSIONS['@geekmidas/envkit'],
15
+ '@geekmidas/logger': GEEKMIDAS_VERSIONS['@geekmidas/logger'],
16
+ '@geekmidas/cloud': GEEKMIDAS_VERSIONS['@geekmidas/cloud'],
16
17
  hono: '~4.8.2',
17
18
  pino: '~9.6.0',
18
19
  },
19
20
 
20
21
  devDependencies: {
21
- '@biomejs/biome': '~1.9.4',
22
- '@geekmidas/cli': 'workspace:*',
22
+ '@biomejs/biome': '~2.3.0',
23
+ '@geekmidas/cli': GEEKMIDAS_VERSIONS['@geekmidas/cli'],
23
24
  '@types/aws-lambda': '~8.10.92',
24
25
  '@types/node': '~22.0.0',
25
26
  tsx: '~4.20.0',
@@ -63,21 +64,17 @@ export const logger = createLogger();
63
64
  // src/config/env.ts
64
65
  {
65
66
  path: 'src/config/env.ts',
66
- content: `import { EnvironmentParser } from '@geekmidas/envkit';
67
+ content: `import { Credentials } from '@geekmidas/envkit/credentials';
68
+ import { EnvironmentParser } from '@geekmidas/envkit';
67
69
 
68
- export const envParser = new EnvironmentParser(process.env);
70
+ export const envParser = new EnvironmentParser({ ...process.env, ...Credentials });
69
71
 
72
+ // Global config - only minimal shared values
73
+ // Service-specific config should be parsed in each service
70
74
  export const config = envParser
71
75
  .create((get) => ({
72
- stage: get('STAGE').string().default('dev'),
73
- region: get('AWS_REGION').string().default('us-east-1'),${
74
- options.database
75
- ? `
76
- database: {
77
- url: get('DATABASE_URL').string(),
78
- },`
79
- : ''
80
- }
76
+ nodeEnv: get('NODE_ENV').enum(['development', 'test', 'production']).default('development'),
77
+ stage: get('STAGE').enum(['dev', 'staging', 'prod']).default('dev'),
81
78
  }))
82
79
  .parse();
83
80
  `,
@@ -94,7 +91,7 @@ export const config = envParser
94
91
  path: getRoutePath('health.ts'),
95
92
  content: `import { e } from '@geekmidas/constructs/endpoints';
96
93
 
97
- export default e
94
+ export const healthEndpoint = e
98
95
  .get('/health')
99
96
  .handle(async () => ({
100
97
  status: 'ok',
@@ -110,7 +107,7 @@ export default e
110
107
  content: `import { f } from '@geekmidas/constructs/functions';
111
108
  import { z } from 'zod';
112
109
 
113
- export default f
110
+ export const helloFunction = f
114
111
  .input(z.object({ name: z.string() }))
115
112
  .output(z.object({ message: z.string() }))
116
113
  .handle(async ({ input }) => ({
@@ -1,3 +1,4 @@
1
+ import { GEEKMIDAS_VERSIONS } from '../versions.js';
1
2
  import type {
2
3
  GeneratedFile,
3
4
  TemplateConfig,
@@ -9,17 +10,17 @@ export const workerTemplate: TemplateConfig = {
9
10
  description: 'Background job processing',
10
11
 
11
12
  dependencies: {
12
- '@geekmidas/constructs': 'workspace:*',
13
- '@geekmidas/envkit': 'workspace:*',
14
- '@geekmidas/logger': 'workspace:*',
15
- '@geekmidas/events': 'workspace:*',
13
+ '@geekmidas/constructs': GEEKMIDAS_VERSIONS['@geekmidas/constructs'],
14
+ '@geekmidas/envkit': GEEKMIDAS_VERSIONS['@geekmidas/envkit'],
15
+ '@geekmidas/logger': GEEKMIDAS_VERSIONS['@geekmidas/logger'],
16
+ '@geekmidas/events': GEEKMIDAS_VERSIONS['@geekmidas/events'],
16
17
  hono: '~4.8.2',
17
18
  pino: '~9.6.0',
18
19
  },
19
20
 
20
21
  devDependencies: {
21
- '@biomejs/biome': '~1.9.4',
22
- '@geekmidas/cli': 'workspace:*',
22
+ '@biomejs/biome': '~2.3.0',
23
+ '@geekmidas/cli': GEEKMIDAS_VERSIONS['@geekmidas/cli'],
23
24
  '@types/node': '~22.0.0',
24
25
  tsx: '~4.20.0',
25
26
  turbo: '~2.3.0',
@@ -62,24 +63,17 @@ export const logger = createLogger();
62
63
  // src/config/env.ts
63
64
  {
64
65
  path: 'src/config/env.ts',
65
- content: `import { EnvironmentParser } from '@geekmidas/envkit';
66
+ content: `import { Credentials } from '@geekmidas/envkit/credentials';
67
+ import { EnvironmentParser } from '@geekmidas/envkit';
66
68
 
67
- export const envParser = new EnvironmentParser(process.env);
69
+ export const envParser = new EnvironmentParser({ ...process.env, ...Credentials });
68
70
 
71
+ // Global config - only minimal shared values
72
+ // Service-specific config should be parsed in each service
69
73
  export const config = envParser
70
74
  .create((get) => ({
71
- port: get('PORT').string().transform(Number).default(3000),
72
- nodeEnv: get('NODE_ENV').string().default('development'),
73
- rabbitmq: {
74
- url: get('RABBITMQ_URL').string().default('amqp://localhost:5672'),
75
- },${
76
- options.database
77
- ? `
78
- database: {
79
- url: get('DATABASE_URL').string().default('postgresql://localhost:5432/mydb'),
80
- },`
81
- : ''
82
- }
75
+ nodeEnv: get('NODE_ENV').enum(['development', 'test', 'production']).default('development'),
76
+ stage: get('STAGE').enum(['development', 'staging', 'production']).default('development'),
83
77
  }))
84
78
  .parse();
85
79
  `,
@@ -96,7 +90,7 @@ export const config = envParser
96
90
  path: getRoutePath('health.ts'),
97
91
  content: `import { e } from '@geekmidas/constructs/endpoints';
98
92
 
99
- export default e
93
+ export const healthEndpoint = e
100
94
  .get('/health')
101
95
  .handle(async () => ({
102
96
  status: 'ok',
@@ -118,14 +112,45 @@ export type AppEvents =
118
112
  `,
119
113
  },
120
114
 
115
+ // src/events/publisher.ts
116
+ {
117
+ path: 'src/events/publisher.ts',
118
+ content: `import type { Service, ServiceRegisterOptions } from '@geekmidas/services';
119
+ import { Publisher, type EventPublisher } from '@geekmidas/events';
120
+ import type { AppEvents } from './types.js';
121
+
122
+ export const eventsPublisherService = {
123
+ serviceName: 'events' as const,
124
+ async register({ envParser, context }: ServiceRegisterOptions) {
125
+ const logger = context.getLogger();
126
+ logger.info('Connecting to message broker');
127
+
128
+ const config = envParser
129
+ .create((get) => ({
130
+ url: get('RABBITMQ_URL').string().default('amqp://localhost:5672'),
131
+ }))
132
+ .parse();
133
+
134
+ const publisher = await Publisher.fromConnectionString<AppEvents>(
135
+ \`rabbitmq://\${config.url.replace('amqp://', '')}?exchange=events\`
136
+ );
137
+
138
+ logger.info('Message broker connection established');
139
+ return publisher;
140
+ },
141
+ } satisfies Service<'events', EventPublisher<AppEvents>>;
142
+ `,
143
+ },
144
+
121
145
  // src/subscribers/user-events.ts
122
146
  {
123
147
  path: 'src/subscribers/user-events.ts',
124
148
  content: `import { s } from '@geekmidas/constructs/subscribers';
125
- import type { AppEvents } from '../events/types.js';
149
+ import { eventsPublisherService } from '../events/publisher.js';
126
150
 
127
- export default s<AppEvents>()
128
- .events(['user.created', 'user.updated'])
151
+ export const userEventsSubscriber = s
152
+ .publisher(eventsPublisherService)
153
+ .subscribe(['user.created', 'user.updated'])
129
154
  .handle(async ({ event, logger }) => {
130
155
  logger.info({ type: event.type, payload: event.payload }, 'Processing user event');
131
156
 
@@ -149,7 +174,7 @@ export default s<AppEvents>()
149
174
  content: `import { cron } from '@geekmidas/constructs/crons';
150
175
 
151
176
  // Run every day at midnight
152
- export default cron('0 0 * * *')
177
+ export const cleanupCron = cron('0 0 * * *')
153
178
  .handle(async ({ logger }) => {
154
179
  logger.info('Running cleanup job');
155
180