@geekmidas/cli 0.54.0 → 1.0.1

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 (154) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +26 -5
  3. package/dist/CachedStateProvider-D73dCqfH.cjs +60 -0
  4. package/dist/CachedStateProvider-D73dCqfH.cjs.map +1 -0
  5. package/dist/CachedStateProvider-DVyKfaMm.mjs +54 -0
  6. package/dist/CachedStateProvider-DVyKfaMm.mjs.map +1 -0
  7. package/dist/CachedStateProvider-D_uISMmJ.cjs +3 -0
  8. package/dist/CachedStateProvider-OiFUGr7p.mjs +3 -0
  9. package/dist/HostingerProvider-DUV9-Tzg.cjs +210 -0
  10. package/dist/HostingerProvider-DUV9-Tzg.cjs.map +1 -0
  11. package/dist/HostingerProvider-DqUq6e9i.mjs +210 -0
  12. package/dist/HostingerProvider-DqUq6e9i.mjs.map +1 -0
  13. package/dist/LocalStateProvider-CdspeSVL.cjs +43 -0
  14. package/dist/LocalStateProvider-CdspeSVL.cjs.map +1 -0
  15. package/dist/LocalStateProvider-DxoSaWUV.mjs +42 -0
  16. package/dist/LocalStateProvider-DxoSaWUV.mjs.map +1 -0
  17. package/dist/Route53Provider-CpRIqu69.cjs +157 -0
  18. package/dist/Route53Provider-CpRIqu69.cjs.map +1 -0
  19. package/dist/Route53Provider-KUAX3vz9.mjs +156 -0
  20. package/dist/Route53Provider-KUAX3vz9.mjs.map +1 -0
  21. package/dist/SSMStateProvider-BxAPU99a.cjs +53 -0
  22. package/dist/SSMStateProvider-BxAPU99a.cjs.map +1 -0
  23. package/dist/SSMStateProvider-C4wp4AZe.mjs +52 -0
  24. package/dist/SSMStateProvider-C4wp4AZe.mjs.map +1 -0
  25. package/dist/{bundler-DGry2vaR.mjs → bundler-BqTN5Dj5.mjs} +3 -3
  26. package/dist/{bundler-DGry2vaR.mjs.map → bundler-BqTN5Dj5.mjs.map} +1 -1
  27. package/dist/{bundler-BB-kETMd.cjs → bundler-tHLLwYuU.cjs} +3 -3
  28. package/dist/{bundler-BB-kETMd.cjs.map → bundler-tHLLwYuU.cjs.map} +1 -1
  29. package/dist/{config-HYiM3iQJ.cjs → config-BGeJsW1r.cjs} +2 -2
  30. package/dist/{config-HYiM3iQJ.cjs.map → config-BGeJsW1r.cjs.map} +1 -1
  31. package/dist/{config-C3LSBNSl.mjs → config-C6awcFBx.mjs} +2 -2
  32. package/dist/{config-C3LSBNSl.mjs.map → config-C6awcFBx.mjs.map} +1 -1
  33. package/dist/config.cjs +2 -2
  34. package/dist/config.d.cts +1 -1
  35. package/dist/config.d.mts +2 -2
  36. package/dist/config.mjs +2 -2
  37. package/dist/credentials-C8DWtnMY.cjs +174 -0
  38. package/dist/credentials-C8DWtnMY.cjs.map +1 -0
  39. package/dist/credentials-DT1dSxIx.mjs +126 -0
  40. package/dist/credentials-DT1dSxIx.mjs.map +1 -0
  41. package/dist/deploy/sniffer-envkit-patch.cjs.map +1 -1
  42. package/dist/deploy/sniffer-envkit-patch.mjs.map +1 -1
  43. package/dist/deploy/sniffer-loader.cjs +1 -1
  44. package/dist/{dokploy-api-94KzmTVf.mjs → dokploy-api-7k3t7_zd.mjs} +1 -1
  45. package/dist/{dokploy-api-94KzmTVf.mjs.map → dokploy-api-7k3t7_zd.mjs.map} +1 -1
  46. package/dist/dokploy-api-CHa8G51l.mjs +3 -0
  47. package/dist/{dokploy-api-YD8WCQfW.cjs → dokploy-api-CQvhV6Hd.cjs} +1 -1
  48. package/dist/{dokploy-api-YD8WCQfW.cjs.map → dokploy-api-CQvhV6Hd.cjs.map} +1 -1
  49. package/dist/dokploy-api-CWc02yyg.cjs +3 -0
  50. package/dist/{encryption-DaCB_NmS.cjs → encryption-BE0UOb8j.cjs} +1 -1
  51. package/dist/{encryption-DaCB_NmS.cjs.map → encryption-BE0UOb8j.cjs.map} +1 -1
  52. package/dist/{encryption-Biq0EZ4m.cjs → encryption-Cv3zips0.cjs} +1 -1
  53. package/dist/{encryption-BC4MAODn.mjs → encryption-JtMsiGNp.mjs} +1 -1
  54. package/dist/{encryption-BC4MAODn.mjs.map → encryption-JtMsiGNp.mjs.map} +1 -1
  55. package/dist/encryption-UUmaWAmz.mjs +3 -0
  56. package/dist/{index-pOA56MWT.d.cts → index-B5rGIc4g.d.cts} +553 -196
  57. package/dist/index-B5rGIc4g.d.cts.map +1 -0
  58. package/dist/{index-A70abJ1m.d.mts → index-KFEbMIRa.d.mts} +554 -197
  59. package/dist/index-KFEbMIRa.d.mts.map +1 -0
  60. package/dist/index.cjs +2265 -658
  61. package/dist/index.cjs.map +1 -1
  62. package/dist/index.mjs +2242 -635
  63. package/dist/index.mjs.map +1 -1
  64. package/dist/{openapi-C3C-BzIZ.mjs → openapi-BMFmLnX6.mjs} +51 -7
  65. package/dist/openapi-BMFmLnX6.mjs.map +1 -0
  66. package/dist/{openapi-D7WwlpPF.cjs → openapi-D1KXv2Ml.cjs} +51 -7
  67. package/dist/openapi-D1KXv2Ml.cjs.map +1 -0
  68. package/dist/{openapi-react-query-C_MxpBgF.cjs → openapi-react-query-BeXvk-wa.cjs} +1 -1
  69. package/dist/{openapi-react-query-C_MxpBgF.cjs.map → openapi-react-query-BeXvk-wa.cjs.map} +1 -1
  70. package/dist/{openapi-react-query-ZoP9DPbY.mjs → openapi-react-query-DGEkD39r.mjs} +1 -1
  71. package/dist/{openapi-react-query-ZoP9DPbY.mjs.map → openapi-react-query-DGEkD39r.mjs.map} +1 -1
  72. package/dist/openapi-react-query.cjs +1 -1
  73. package/dist/openapi-react-query.mjs +1 -1
  74. package/dist/openapi.cjs +3 -3
  75. package/dist/openapi.d.cts +1 -1
  76. package/dist/openapi.d.mts +2 -2
  77. package/dist/openapi.mjs +3 -3
  78. package/dist/{storage-Dhst7BhI.mjs → storage-BMW6yLu3.mjs} +1 -1
  79. package/dist/{storage-Dhst7BhI.mjs.map → storage-BMW6yLu3.mjs.map} +1 -1
  80. package/dist/{storage-fOR8dMu5.cjs → storage-C7pmBq1u.cjs} +1 -1
  81. package/dist/{storage-BPRgh3DU.cjs → storage-CoCNe0Pt.cjs} +1 -1
  82. package/dist/{storage-BPRgh3DU.cjs.map → storage-CoCNe0Pt.cjs.map} +1 -1
  83. package/dist/{storage-DNj_I11J.mjs → storage-D8XzjVaO.mjs} +1 -1
  84. package/dist/{types-BtGL-8QS.d.mts → types-BldpmqQX.d.mts} +1 -1
  85. package/dist/{types-BtGL-8QS.d.mts.map → types-BldpmqQX.d.mts.map} +1 -1
  86. package/dist/workspace/index.cjs +1 -1
  87. package/dist/workspace/index.d.cts +1 -1
  88. package/dist/workspace/index.d.mts +2 -2
  89. package/dist/workspace/index.mjs +1 -1
  90. package/dist/{workspace-CaVW6j2q.cjs → workspace-BFRUOOrh.cjs} +309 -25
  91. package/dist/workspace-BFRUOOrh.cjs.map +1 -0
  92. package/dist/{workspace-DLFRaDc-.mjs → workspace-DAxG3_H2.mjs} +309 -25
  93. package/dist/workspace-DAxG3_H2.mjs.map +1 -0
  94. package/package.json +14 -8
  95. package/scripts/sync-versions.ts +86 -0
  96. package/src/build/__tests__/handler-templates.spec.ts +115 -47
  97. package/src/deploy/CachedStateProvider.ts +86 -0
  98. package/src/deploy/LocalStateProvider.ts +57 -0
  99. package/src/deploy/SSMStateProvider.ts +93 -0
  100. package/src/deploy/StateProvider.ts +171 -0
  101. package/src/deploy/__tests__/CachedStateProvider.spec.ts +228 -0
  102. package/src/deploy/__tests__/HostingerProvider.spec.ts +347 -0
  103. package/src/deploy/__tests__/LocalStateProvider.spec.ts +126 -0
  104. package/src/deploy/__tests__/Route53Provider.spec.ts +402 -0
  105. package/src/deploy/__tests__/SSMStateProvider.spec.ts +177 -0
  106. package/src/deploy/__tests__/__fixtures__/env-parsers/throwing-env-parser.ts +1 -3
  107. package/src/deploy/__tests__/__fixtures__/route-apps/services.ts +28 -19
  108. package/src/deploy/__tests__/createDnsProvider.spec.ts +172 -0
  109. package/src/deploy/__tests__/createStateProvider.spec.ts +116 -0
  110. package/src/deploy/__tests__/dns-orchestration.spec.ts +192 -0
  111. package/src/deploy/__tests__/dns-verification.spec.ts +2 -2
  112. package/src/deploy/__tests__/env-resolver.spec.ts +37 -15
  113. package/src/deploy/__tests__/sniffer.spec.ts +4 -20
  114. package/src/deploy/__tests__/state.spec.ts +13 -5
  115. package/src/deploy/dns/DnsProvider.ts +163 -0
  116. package/src/deploy/dns/HostingerProvider.ts +100 -0
  117. package/src/deploy/dns/Route53Provider.ts +256 -0
  118. package/src/deploy/dns/index.ts +257 -165
  119. package/src/deploy/env-resolver.ts +12 -5
  120. package/src/deploy/index.ts +16 -13
  121. package/src/deploy/sniffer-envkit-patch.ts +3 -1
  122. package/src/deploy/sniffer-routes-worker.ts +104 -0
  123. package/src/deploy/sniffer.ts +77 -55
  124. package/src/deploy/state-commands.ts +274 -0
  125. package/src/dev/__tests__/entry.spec.ts +8 -2
  126. package/src/dev/__tests__/index.spec.ts +1 -3
  127. package/src/dev/index.ts +9 -3
  128. package/src/docker/__tests__/templates.spec.ts +3 -1
  129. package/src/index.ts +88 -0
  130. package/src/init/__tests__/generators.spec.ts +273 -0
  131. package/src/init/__tests__/init.spec.ts +3 -3
  132. package/src/init/generators/auth.ts +1 -0
  133. package/src/init/generators/config.ts +2 -0
  134. package/src/init/generators/models.ts +6 -1
  135. package/src/init/generators/monorepo.ts +3 -0
  136. package/src/init/generators/ui.ts +1472 -0
  137. package/src/init/generators/web.ts +134 -87
  138. package/src/init/index.ts +22 -3
  139. package/src/init/templates/api.ts +109 -3
  140. package/src/init/versions.ts +25 -53
  141. package/src/openapi.ts +99 -13
  142. package/src/workspace/__tests__/schema.spec.ts +107 -0
  143. package/src/workspace/schema.ts +314 -4
  144. package/src/workspace/types.ts +22 -36
  145. package/dist/dokploy-api-CItuaWTq.mjs +0 -3
  146. package/dist/dokploy-api-DBNE8MDt.cjs +0 -3
  147. package/dist/encryption-CQXBZGkt.mjs +0 -3
  148. package/dist/index-A70abJ1m.d.mts.map +0 -1
  149. package/dist/index-pOA56MWT.d.cts.map +0 -1
  150. package/dist/openapi-C3C-BzIZ.mjs.map +0 -1
  151. package/dist/openapi-D7WwlpPF.cjs.map +0 -1
  152. package/dist/workspace-CaVW6j2q.cjs.map +0 -1
  153. package/dist/workspace-DLFRaDc-.mjs.map +0 -1
  154. package/tsconfig.tsbuildinfo +0 -1
@@ -11,6 +11,7 @@ export function generateWebAppFiles(options: TemplateOptions): GeneratedFile[] {
11
11
 
12
12
  const packageName = `@${options.name}/web`;
13
13
  const modelsPackage = `@${options.name}/models`;
14
+ const uiPackage = `@${options.name}/ui`;
14
15
 
15
16
  // package.json for web app
16
17
  const packageJson = {
@@ -19,23 +20,28 @@ export function generateWebAppFiles(options: TemplateOptions): GeneratedFile[] {
19
20
  private: true,
20
21
  type: 'module',
21
22
  scripts: {
22
- dev: 'next dev -p 3001',
23
- build: 'next build',
23
+ dev: 'gkm exec -- next dev --turbopack',
24
+ build: 'gkm exec -- next build',
24
25
  start: 'next start',
25
26
  typecheck: 'tsc --noEmit',
26
27
  },
27
28
  dependencies: {
28
29
  [modelsPackage]: 'workspace:*',
30
+ [uiPackage]: 'workspace:*',
29
31
  '@geekmidas/client': GEEKMIDAS_VERSIONS['@geekmidas/client'],
30
32
  '@tanstack/react-query': '~5.80.0',
33
+ 'better-auth': '~1.2.0',
31
34
  next: '~16.1.0',
32
35
  react: '~19.2.0',
33
36
  'react-dom': '~19.2.0',
34
37
  },
35
38
  devDependencies: {
39
+ '@geekmidas/cli': GEEKMIDAS_VERSIONS['@geekmidas/cli'],
40
+ '@tailwindcss/postcss': '^4.0.0',
36
41
  '@types/node': '~22.0.0',
37
42
  '@types/react': '~19.0.0',
38
43
  '@types/react-dom': '~19.0.0',
44
+ tailwindcss: '^4.0.0',
39
45
  typescript: '~5.8.2',
40
46
  },
41
47
  };
@@ -46,10 +52,18 @@ export function generateWebAppFiles(options: TemplateOptions): GeneratedFile[] {
46
52
  const nextConfig: NextConfig = {
47
53
  output: 'standalone',
48
54
  reactStrictMode: true,
49
- transpilePackages: ['${modelsPackage}'],
55
+ transpilePackages: ['${modelsPackage}', '${uiPackage}'],
50
56
  };
51
57
 
52
58
  export default nextConfig;
59
+ `;
60
+
61
+ // postcss.config.mjs for Tailwind v4
62
+ const postcssConfig = `export default {
63
+ plugins: {
64
+ '@tailwindcss/postcss': {},
65
+ },
66
+ };
53
67
  `;
54
68
 
55
69
  // tsconfig.json for Next.js
@@ -75,9 +89,11 @@ export default nextConfig;
75
89
  },
76
90
  ],
77
91
  paths: {
78
- '@/*': ['./src/*'],
92
+ '~/*': ['./src/*'],
79
93
  [`${modelsPackage}`]: ['../../packages/models/src'],
80
94
  [`${modelsPackage}/*`]: ['../../packages/models/src/*'],
95
+ [`${uiPackage}`]: ['../../packages/ui/src'],
96
+ [`${uiPackage}/*`]: ['../../packages/ui/src/*'],
81
97
  },
82
98
  baseUrl: '.',
83
99
  },
@@ -85,23 +101,52 @@ export default nextConfig;
85
101
  exclude: ['node_modules'],
86
102
  };
87
103
 
88
- // Providers with QueryClient
104
+ // Query client singleton for browser, fresh instance for server
105
+ const queryClientTs = `import { QueryClient } from '@tanstack/react-query';
106
+
107
+ function makeQueryClient() {
108
+ return new QueryClient({
109
+ defaultOptions: {
110
+ queries: {
111
+ staleTime: 60 * 1000,
112
+ },
113
+ },
114
+ });
115
+ }
116
+
117
+ let browserQueryClient: QueryClient | undefined = undefined;
118
+
119
+ export function getQueryClient() {
120
+ if (typeof window === 'undefined') {
121
+ // Server: always make a new query client
122
+ return makeQueryClient();
123
+ }
124
+ // Browser: reuse existing query client
125
+ if (!browserQueryClient) browserQueryClient = makeQueryClient();
126
+ return browserQueryClient;
127
+ }
128
+ `;
129
+
130
+ // Auth client for better-auth
131
+ const authClientTs = `import { createAuthClient } from 'better-auth/react';
132
+ import { magicLinkClient } from 'better-auth/client/plugins';
133
+
134
+ export const authClient = createAuthClient({
135
+ baseURL: process.env.NEXT_PUBLIC_AUTH_URL || 'http://localhost:3002',
136
+ plugins: [magicLinkClient()],
137
+ });
138
+
139
+ export const { signIn, signUp, signOut, useSession, magicLink } = authClient;
140
+ `;
141
+
142
+ // Providers using shared QueryClient
89
143
  const providersTsx = `'use client';
90
144
 
91
- import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
92
- import { useState } from 'react';
145
+ import { QueryClientProvider } from '@tanstack/react-query';
146
+ import { getQueryClient } from '~/lib/query-client';
93
147
 
94
148
  export function Providers({ children }: { children: React.ReactNode }) {
95
- const [queryClient] = useState(
96
- () =>
97
- new QueryClient({
98
- defaultOptions: {
99
- queries: {
100
- staleTime: 60 * 1000,
101
- },
102
- },
103
- }),
104
- );
149
+ const queryClient = getQueryClient();
105
150
 
106
151
  return (
107
152
  <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
@@ -109,49 +154,24 @@ export function Providers({ children }: { children: React.ReactNode }) {
109
154
  }
110
155
  `;
111
156
 
112
- // API client setup
113
- const apiIndexTs = `import { TypedFetcher } from '@geekmidas/client/fetcher';
114
- import { createEndpointHooks } from '@geekmidas/client/endpoint-hooks';
115
-
116
- // TODO: Run 'gkm openapi' to generate typed paths from your API
117
- // This is a placeholder that will be replaced by the generated openapi.ts
118
- interface paths {
119
- '/health': {
120
- get: {
121
- responses: {
122
- 200: {
123
- content: {
124
- 'application/json': { status: string; timestamp: string };
125
- };
126
- };
127
- };
128
- };
129
- };
130
- '/users': {
131
- get: {
132
- responses: {
133
- 200: {
134
- content: {
135
- 'application/json': { users: Array<{ id: string; name: string }> };
136
- };
137
- };
138
- };
139
- };
140
- };
141
- }
142
-
143
- const baseURL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000';
157
+ // API client setup - uses createApi with shared QueryClient
158
+ const apiIndexTs = `import { createApi } from './openapi';
159
+ import { getQueryClient } from '~/lib/query-client';
144
160
 
145
- const fetcher = new TypedFetcher<paths>({ baseURL });
146
-
147
- const hooks = createEndpointHooks<paths>(fetcher.request.bind(fetcher));
161
+ export const api = createApi({
162
+ baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000',
163
+ queryClient: getQueryClient(),
164
+ });
165
+ `;
148
166
 
149
- export const api = Object.assign(fetcher.request.bind(fetcher), hooks);
167
+ // globals.css that imports UI package styles
168
+ const globalsCss = `@import '${uiPackage}/styles';
150
169
  `;
151
170
 
152
171
  // App layout
153
172
  const layoutTsx = `import type { Metadata } from 'next';
154
173
  import { Providers } from './providers';
174
+ import './globals.css';
155
175
 
156
176
  export const metadata: Metadata = {
157
177
  title: '${options.name}',
@@ -173,45 +193,60 @@ export default function RootLayout({
173
193
  }
174
194
  `;
175
195
 
176
- // Home page with API example
177
- const pageTsx = `import { api } from '@/api';
196
+ // Home page with API example using UI components
197
+ const pageTsx = `import { api } from '~/api';
198
+ import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle } from '${uiPackage}/components';
178
199
 
179
200
  export default async function Home() {
180
201
  // Type-safe API call using the generated client
181
202
  const health = await api('GET /health').catch(() => null);
182
203
 
183
204
  return (
184
- <main style={{ padding: '2rem', fontFamily: 'system-ui' }}>
185
- <h1>Welcome to ${options.name}</h1>
186
-
187
- <section style={{ marginTop: '2rem' }}>
188
- <h2>API Status</h2>
189
- {health ? (
190
- <pre style={{ background: '#f0f0f0', padding: '1rem', borderRadius: '8px' }}>
191
- {JSON.stringify(health, null, 2)}
192
- </pre>
193
- ) : (
194
- <p>Unable to connect to API</p>
195
- )}
196
- </section>
197
-
198
- <section style={{ marginTop: '2rem' }}>
199
- <h2>Next Steps</h2>
200
- <ul>
201
- <li>Run <code>gkm openapi</code> to generate typed API client</li>
202
- <li>Edit <code>apps/web/src/app/page.tsx</code> to customize this page</li>
203
- <li>Add API routes in <code>apps/api/src/endpoints/</code></li>
204
- <li>Define shared schemas in <code>packages/models/src/</code></li>
205
- </ul>
206
- </section>
205
+ <main className="min-h-screen bg-background p-8">
206
+ <div className="mx-auto max-w-4xl space-y-8">
207
+ <div className="space-y-2">
208
+ <h1 className="text-4xl font-bold tracking-tight">Welcome to ${options.name}</h1>
209
+ <p className="text-muted-foreground">Your fullstack application is ready.</p>
210
+ </div>
211
+
212
+ <Card>
213
+ <CardHeader>
214
+ <CardTitle>API Status</CardTitle>
215
+ <CardDescription>Connection to your backend API</CardDescription>
216
+ </CardHeader>
217
+ <CardContent>
218
+ {health ? (
219
+ <pre className="rounded-lg bg-muted p-4 text-sm">
220
+ {JSON.stringify(health, null, 2)}
221
+ </pre>
222
+ ) : (
223
+ <p className="text-destructive">Unable to connect to API</p>
224
+ )}
225
+ </CardContent>
226
+ </Card>
227
+
228
+ <Card>
229
+ <CardHeader>
230
+ <CardTitle>Next Steps</CardTitle>
231
+ <CardDescription>Get started with your project</CardDescription>
232
+ </CardHeader>
233
+ <CardContent className="space-y-4">
234
+ <ul className="list-inside list-disc space-y-2 text-muted-foreground">
235
+ <li>Run <code className="rounded bg-muted px-1">gkm openapi</code> to generate typed API client</li>
236
+ <li>Edit <code className="rounded bg-muted px-1">apps/web/src/app/page.tsx</code> to customize this page</li>
237
+ <li>Add API routes in <code className="rounded bg-muted px-1">apps/api/src/endpoints/</code></li>
238
+ <li>Add UI components with <code className="rounded bg-muted px-1">npx shadcn@latest add</code> in packages/ui</li>
239
+ </ul>
240
+ <div className="flex gap-4">
241
+ <Button>Get Started</Button>
242
+ <Button variant="outline">Documentation</Button>
243
+ </div>
244
+ </CardContent>
245
+ </Card>
246
+ </div>
207
247
  </main>
208
248
  );
209
249
  }
210
- `;
211
-
212
- // Environment file for web app
213
- const envLocal = `# API URL for client-side requests
214
- NEXT_PUBLIC_API_URL=http://localhost:3000
215
250
  `;
216
251
 
217
252
  // .gitignore for Next.js
@@ -230,10 +265,18 @@ node_modules/
230
265
  path: 'apps/web/next.config.ts',
231
266
  content: nextConfig,
232
267
  },
268
+ {
269
+ path: 'apps/web/postcss.config.mjs',
270
+ content: postcssConfig,
271
+ },
233
272
  {
234
273
  path: 'apps/web/tsconfig.json',
235
274
  content: `${JSON.stringify(tsConfig, null, 2)}\n`,
236
275
  },
276
+ {
277
+ path: 'apps/web/src/app/globals.css',
278
+ content: globalsCss,
279
+ },
237
280
  {
238
281
  path: 'apps/web/src/app/layout.tsx',
239
282
  content: layoutTsx,
@@ -247,12 +290,16 @@ node_modules/
247
290
  content: pageTsx,
248
291
  },
249
292
  {
250
- path: 'apps/web/src/api/index.ts',
251
- content: apiIndexTs,
293
+ path: 'apps/web/src/lib/query-client.ts',
294
+ content: queryClientTs,
252
295
  },
253
296
  {
254
- path: 'apps/web/.env.local',
255
- content: envLocal,
297
+ path: 'apps/web/src/lib/auth-client.ts',
298
+ content: authClientTs,
299
+ },
300
+ {
301
+ path: 'apps/web/src/api/index.ts',
302
+ content: apiIndexTs,
256
303
  },
257
304
  {
258
305
  path: 'apps/web/.gitignore',
package/src/init/index.ts CHANGED
@@ -17,6 +17,7 @@ import { generateModelsPackage } from './generators/models.js';
17
17
  import { generateMonorepoFiles } from './generators/monorepo.js';
18
18
  import { generatePackageJson } from './generators/package.js';
19
19
  import { generateSourceFiles } from './generators/source.js';
20
+ import { generateUiPackageFiles } from './generators/ui.js';
20
21
  import { generateWebAppFiles } from './generators/web.js';
21
22
  import {
22
23
  type DeployTarget,
@@ -294,6 +295,11 @@ export async function initCommand(
294
295
  // Collect auth app files for fullstack template
295
296
  const authAppFiles = isFullstack ? generateAuthAppFiles(templateOptions) : [];
296
297
 
298
+ // Collect UI package files for fullstack template
299
+ const uiPackageFiles = isFullstack
300
+ ? generateUiPackageFiles(templateOptions)
301
+ : [];
302
+
297
303
  // Write root files (for monorepo)
298
304
  for (const { path, content } of rootFiles) {
299
305
  const fullPath = join(targetDir, path);
@@ -329,6 +335,13 @@ export async function initCommand(
329
335
  await writeFile(fullPath, content);
330
336
  }
331
337
 
338
+ // Write UI package files (shared components)
339
+ for (const { path, content } of uiPackageFiles) {
340
+ const fullPath = join(targetDir, path);
341
+ await mkdir(dirname(fullPath), { recursive: true });
342
+ await writeFile(fullPath, content);
343
+ }
344
+
332
345
  // Initialize encrypted secrets for development stage
333
346
  console.log('🔐 Initializing encrypted secrets...\n');
334
347
  const secretServices: ComposeServiceName[] = [];
@@ -359,6 +372,7 @@ export async function initCommand(
359
372
 
360
373
  // Auth service secrets (better-auth)
361
374
  customSecrets.AUTH_PORT = '3002';
375
+ customSecrets.AUTH_URL = 'http://localhost:3002'; // For API app to call auth service
362
376
  customSecrets.BETTER_AUTH_SECRET = `better-auth-${Date.now()}-${Math.random().toString(36).slice(2)}`;
363
377
  customSecrets.BETTER_AUTH_URL = 'http://localhost:3002';
364
378
  customSecrets.BETTER_AUTH_TRUSTED_ORIGINS =
@@ -407,7 +421,9 @@ export async function initCommand(
407
421
  });
408
422
  console.log(' Initialized git repository on branch main');
409
423
  } catch {
410
- console.log(' Could not initialize git repository (git may not be installed)');
424
+ console.log(
425
+ ' Could not initialize git repository (git may not be installed)',
426
+ );
411
427
  }
412
428
 
413
429
  // Print success message with next steps
@@ -449,7 +465,10 @@ function printNextSteps(
449
465
  console.log(` │ └── web/ # Next.js frontend`);
450
466
  }
451
467
  console.log(` ├── packages/`);
452
- console.log(` │ └── models/ # Shared Zod schemas`);
468
+ console.log(` │ ├── models/ # Shared Zod schemas`);
469
+ if (isFullstackTemplate(options.template)) {
470
+ console.log(` │ └── ui/ # Shared UI components`);
471
+ }
453
472
  console.log(` ├── .gkm/secrets/ # Encrypted secrets`);
454
473
  console.log(` ├── gkm.config.ts # Workspace config`);
455
474
  console.log(` └── turbo.json # Turbo config`);
@@ -470,6 +489,6 @@ function printNextSteps(
470
489
  console.log('');
471
490
  }
472
491
 
473
- console.log('📚 Documentation: https://docs.geekmidas.dev');
492
+ console.log('📚 Documentation: https://geekmidas.github.io/toolbox/');
474
493
  console.log('');
475
494
  }
@@ -105,7 +105,22 @@ export const config = envParser
105
105
  // health endpoint
106
106
  {
107
107
  path: getRoutePath('health.ts'),
108
- content: `import { e } from '@geekmidas/constructs/endpoints';
108
+ content: monorepo
109
+ ? `import { z } from 'zod';
110
+ import { publicRouter } from '~/router';
111
+
112
+ export const healthEndpoint = publicRouter
113
+ .get('/health')
114
+ .output(z.object({
115
+ status: z.string(),
116
+ timestamp: z.string(),
117
+ }))
118
+ .handle(async () => ({
119
+ status: 'ok',
120
+ timestamp: new Date().toISOString(),
121
+ }));
122
+ `
123
+ : `import { e } from '@geekmidas/constructs/endpoints';
109
124
  import { z } from 'zod';
110
125
 
111
126
  export const healthEndpoint = e
@@ -163,12 +178,12 @@ export const listUsersEndpoint = e
163
178
  path: getRoutePath('users/get.ts'),
164
179
  content: modelsImport
165
180
  ? `import { e } from '@geekmidas/constructs/endpoints';
166
- import { IdSchema } from '${modelsImport}/common';
181
+ import { IdParamsSchema } from '${modelsImport}/common';
167
182
  import { UserResponseSchema } from '${modelsImport}/user';
168
183
 
169
184
  export const getUserEndpoint = e
170
185
  .get('/users/:id')
171
- .params({ id: IdSchema })
186
+ .params(IdParamsSchema)
172
187
  .output(UserResponseSchema)
173
188
  .handle(async ({ params }) => ({
174
189
  id: params.id,
@@ -196,6 +211,97 @@ export const getUserEndpoint = e
196
211
  },
197
212
  ];
198
213
 
214
+ // Add auth service for monorepo (calls auth app for session)
215
+ if (options.monorepo) {
216
+ files.push({
217
+ path: 'src/services/auth.ts',
218
+ content: `import type { Service, ServiceRegisterOptions } from '@geekmidas/services';
219
+
220
+ export interface Session {
221
+ user: {
222
+ id: string;
223
+ email: string;
224
+ name: string;
225
+ };
226
+ }
227
+
228
+ export interface AuthClient {
229
+ getSession: (cookie: string) => Promise<Session | null>;
230
+ }
231
+
232
+ export const authService = {
233
+ serviceName: 'auth' as const,
234
+ async register({ envParser, context }: ServiceRegisterOptions) {
235
+ const logger = context.getLogger();
236
+
237
+ const config = envParser
238
+ .create((get) => ({
239
+ url: get('AUTH_URL').string(),
240
+ }))
241
+ .parse();
242
+
243
+ logger.info({ authUrl: config.url }, 'Auth service configured');
244
+
245
+ return {
246
+ getSession: async (cookie: string): Promise<Session | null> => {
247
+ const res = await fetch(\`\${config.url}/api/auth/get-session\`, {
248
+ headers: { cookie },
249
+ });
250
+ if (!res.ok) return null;
251
+ return res.json();
252
+ },
253
+ };
254
+ },
255
+ } satisfies Service<'auth', AuthClient>;
256
+ `,
257
+ });
258
+
259
+ // Add router with session
260
+ files.push({
261
+ path: 'src/router.ts',
262
+ content: `import { e } from '@geekmidas/constructs/endpoints';
263
+ import { UnauthorizedError } from '@geekmidas/errors';
264
+ import { authService, type Session } from './services/auth.js';
265
+ import { logger } from './config/logger.js';
266
+
267
+ // Public router - no auth required
268
+ export const publicRouter = e.logger(logger);
269
+
270
+ // Router with auth service available (but session not enforced)
271
+ export const r = publicRouter.services([authService]);
272
+
273
+ // Session router - requires active session, throws if not authenticated
274
+ export const sessionRouter = r.session<Session>(async ({ services, header }) => {
275
+ const cookie = header('cookie') || '';
276
+ const session = await services.auth.getSession(cookie);
277
+
278
+ if (!session?.user) {
279
+ throw new UnauthorizedError('No active session');
280
+ }
281
+
282
+ return session;
283
+ });
284
+ `,
285
+ });
286
+
287
+ // Add protected endpoint example
288
+ files.push({
289
+ path: getRoutePath('profile.ts'),
290
+ content: `import { z } from 'zod';
291
+ import { sessionRouter } from '~/router';
292
+
293
+ export const profileEndpoint = sessionRouter
294
+ .get('/profile')
295
+ .output(z.object({
296
+ id: z.string(),
297
+ email: z.string(),
298
+ name: z.string(),
299
+ }))
300
+ .handle(async ({ session }) => session.user);
301
+ `,
302
+ });
303
+ }
304
+
199
305
  // Add database service if enabled
200
306
  if (options.database) {
201
307
  files.push({
@@ -1,58 +1,30 @@
1
- import { createRequire } from 'node:module';
2
-
3
- const require = createRequire(import.meta.url);
4
-
5
- // Load package.json - handles both bundled (flat dist/) and source (nested src/init/)
6
- function loadPackageJson(): { version: string } {
7
- try {
8
- // Try flat dist path first (../package.json from dist/)
9
- return require('../package.json');
10
- } catch {
11
- // Fall back to nested source path (../../package.json from src/init/)
12
- return require('../../package.json');
13
- }
14
- }
15
-
16
- const pkg = loadPackageJson();
17
-
18
- /**
19
- * CLI version from package.json (used for scaffolded projects)
20
- */
21
- export const CLI_VERSION = `~${pkg.version}`;
22
-
23
1
  /**
24
- * Current released versions of @geekmidas packages
25
- * Update these when publishing new versions
26
- * Note: CLI version is read from package.json via CLI_VERSION
2
+ * Package versions for @geekmidas packages
3
+ *
4
+ * AUTO-GENERATED - Do not edit manually
5
+ * Run: pnpm --filter @geekmidas/cli sync-versions
27
6
  */
28
7
  export const GEEKMIDAS_VERSIONS = {
29
- '@geekmidas/audit': '~0.2.0',
30
- '@geekmidas/auth': '~0.2.0',
31
- '@geekmidas/cache': '~0.2.0',
32
- '@geekmidas/cli': CLI_VERSION,
33
- '@geekmidas/client': '~0.5.0',
34
- '@geekmidas/cloud': '~0.2.0',
35
- '@geekmidas/constructs': '~0.8.0',
36
- '@geekmidas/db': '~0.3.0',
37
- '@geekmidas/emailkit': '~0.2.0',
38
- '@geekmidas/envkit': '~0.7.0',
39
- '@geekmidas/errors': '~0.1.0',
40
- '@geekmidas/events': '~0.2.0',
41
- '@geekmidas/logger': '~0.4.0',
42
- '@geekmidas/rate-limit': '~0.3.0',
43
- '@geekmidas/schema': '~0.1.0',
44
- '@geekmidas/services': '~0.2.0',
45
- '@geekmidas/storage': '~0.1.0',
46
- '@geekmidas/studio': '~0.4.0',
47
- '@geekmidas/telescope': '~0.6.0',
48
- '@geekmidas/testkit': '~0.6.0',
49
- };
8
+ '@geekmidas/audit': '~1.0.0',
9
+ '@geekmidas/auth': '~1.0.0',
10
+ '@geekmidas/cache': '~1.0.0',
11
+ '@geekmidas/cli': '~1.0.0',
12
+ '@geekmidas/client': '~1.0.0',
13
+ '@geekmidas/cloud': '~1.0.0',
14
+ '@geekmidas/constructs': '~1.0.0',
15
+ '@geekmidas/db': '~1.0.0',
16
+ '@geekmidas/emailkit': '~1.0.0',
17
+ '@geekmidas/envkit': '~1.0.0',
18
+ '@geekmidas/errors': '~1.0.0',
19
+ '@geekmidas/events': '~1.0.0',
20
+ '@geekmidas/logger': '~1.0.0',
21
+ '@geekmidas/rate-limit': '~1.0.0',
22
+ '@geekmidas/schema': '~1.0.0',
23
+ '@geekmidas/services': '~1.0.0',
24
+ '@geekmidas/storage': '~1.0.0',
25
+ '@geekmidas/studio': '~1.0.0',
26
+ '@geekmidas/telescope': '~1.0.0',
27
+ '@geekmidas/testkit': '~1.0.0',
28
+ } as const;
50
29
 
51
30
  export type GeekmidasPackage = keyof typeof GEEKMIDAS_VERSIONS;
52
-
53
- /**
54
- * Get the version for a @geekmidas package
55
- */
56
- export function getPackageVersion(pkg: GeekmidasPackage): string {
57
- return GEEKMIDAS_VERSIONS[pkg]!;
58
- }