@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
@@ -0,0 +1,213 @@
1
+ import type { GeneratedFile, TemplateOptions } from '../templates/index.js';
2
+
3
+ /**
4
+ * Generate Next.js web app files for fullstack template
5
+ */
6
+ export function generateWebAppFiles(options: TemplateOptions): GeneratedFile[] {
7
+ if (!options.monorepo || options.template !== 'fullstack') {
8
+ return [];
9
+ }
10
+
11
+ const packageName = `@${options.name}/web`;
12
+ const modelsPackage = `@${options.name}/models`;
13
+
14
+ // package.json for web app
15
+ const packageJson = {
16
+ name: packageName,
17
+ version: '0.0.1',
18
+ private: true,
19
+ type: 'module',
20
+ scripts: {
21
+ dev: 'next dev -p 3001',
22
+ build: 'next build',
23
+ start: 'next start',
24
+ typecheck: 'tsc --noEmit',
25
+ },
26
+ dependencies: {
27
+ [modelsPackage]: 'workspace:*',
28
+ next: '~16.1.0',
29
+ react: '~19.2.0',
30
+ 'react-dom': '~19.2.0',
31
+ },
32
+ devDependencies: {
33
+ '@types/node': '~22.0.0',
34
+ '@types/react': '~19.0.0',
35
+ '@types/react-dom': '~19.0.0',
36
+ typescript: '~5.8.2',
37
+ },
38
+ };
39
+
40
+ // next.config.ts
41
+ const nextConfig = `import type { NextConfig } from 'next';
42
+
43
+ const nextConfig: NextConfig = {
44
+ output: 'standalone',
45
+ reactStrictMode: true,
46
+ transpilePackages: ['${modelsPackage}'],
47
+ };
48
+
49
+ export default nextConfig;
50
+ `;
51
+
52
+ // tsconfig.json for Next.js
53
+ // Note: Next.js handles compilation, so noEmit: true
54
+ const tsConfig = {
55
+ extends: '../../tsconfig.json',
56
+ compilerOptions: {
57
+ lib: ['dom', 'dom.iterable', 'ES2022'],
58
+ allowJs: true,
59
+ skipLibCheck: true,
60
+ strict: true,
61
+ noEmit: true,
62
+ esModuleInterop: true,
63
+ module: 'ESNext',
64
+ moduleResolution: 'bundler',
65
+ resolveJsonModule: true,
66
+ isolatedModules: true,
67
+ jsx: 'preserve',
68
+ incremental: true,
69
+ plugins: [
70
+ {
71
+ name: 'next',
72
+ },
73
+ ],
74
+ paths: {
75
+ '@/*': ['./src/*'],
76
+ [`${modelsPackage}`]: ['../../packages/models/src'],
77
+ [`${modelsPackage}/*`]: ['../../packages/models/src/*'],
78
+ },
79
+ baseUrl: '.',
80
+ },
81
+ include: ['next-env.d.ts', '**/*.ts', '**/*.tsx', '.next/types/**/*.ts'],
82
+ exclude: ['node_modules'],
83
+ };
84
+
85
+ // App layout
86
+ const layoutTsx = `import type { Metadata } from 'next';
87
+
88
+ export const metadata: Metadata = {
89
+ title: '${options.name}',
90
+ description: 'Created with gkm init',
91
+ };
92
+
93
+ export default function RootLayout({
94
+ children,
95
+ }: {
96
+ children: React.ReactNode;
97
+ }) {
98
+ return (
99
+ <html lang="en">
100
+ <body>{children}</body>
101
+ </html>
102
+ );
103
+ }
104
+ `;
105
+
106
+ // Home page with API example
107
+ const pageTsx = `import type { User } from '${modelsPackage}';
108
+
109
+ export default async function Home() {
110
+ // Example: Fetch from API
111
+ const apiUrl = process.env.API_URL || 'http://localhost:3000';
112
+ let health = null;
113
+
114
+ try {
115
+ const response = await fetch(\`\${apiUrl}/health\`, {
116
+ cache: 'no-store',
117
+ });
118
+ health = await response.json();
119
+ } catch (error) {
120
+ console.error('Failed to fetch health:', error);
121
+ }
122
+
123
+ // Example: Type-safe model usage
124
+ const exampleUser: User = {
125
+ id: '123e4567-e89b-12d3-a456-426614174000',
126
+ email: 'user@example.com',
127
+ name: 'Example User',
128
+ createdAt: new Date(),
129
+ updatedAt: new Date(),
130
+ };
131
+
132
+ return (
133
+ <main style={{ padding: '2rem', fontFamily: 'system-ui' }}>
134
+ <h1>Welcome to ${options.name}</h1>
135
+
136
+ <section style={{ marginTop: '2rem' }}>
137
+ <h2>API Status</h2>
138
+ {health ? (
139
+ <pre style={{ background: '#f0f0f0', padding: '1rem', borderRadius: '8px' }}>
140
+ {JSON.stringify(health, null, 2)}
141
+ </pre>
142
+ ) : (
143
+ <p>Unable to connect to API at {apiUrl}</p>
144
+ )}
145
+ </section>
146
+
147
+ <section style={{ marginTop: '2rem' }}>
148
+ <h2>Shared Models</h2>
149
+ <p>This user object is typed from @${options.name}/models:</p>
150
+ <pre style={{ background: '#f0f0f0', padding: '1rem', borderRadius: '8px' }}>
151
+ {JSON.stringify(exampleUser, null, 2)}
152
+ </pre>
153
+ </section>
154
+
155
+ <section style={{ marginTop: '2rem' }}>
156
+ <h2>Next Steps</h2>
157
+ <ul>
158
+ <li>Edit <code>apps/web/src/app/page.tsx</code> to customize this page</li>
159
+ <li>Add API routes in <code>apps/api/src/endpoints/</code></li>
160
+ <li>Define shared schemas in <code>packages/models/src/</code></li>
161
+ </ul>
162
+ </section>
163
+ </main>
164
+ );
165
+ }
166
+ `;
167
+
168
+ // Environment file for web app
169
+ const envLocal = `# API URL (injected automatically in workspace mode)
170
+ API_URL=http://localhost:3000
171
+
172
+ # Other environment variables
173
+ # NEXT_PUBLIC_API_URL=http://localhost:3000
174
+ `;
175
+
176
+ // .gitignore for Next.js
177
+ const gitignore = `.next/
178
+ node_modules/
179
+ .env.local
180
+ *.log
181
+ `;
182
+
183
+ return [
184
+ {
185
+ path: 'apps/web/package.json',
186
+ content: `${JSON.stringify(packageJson, null, 2)}\n`,
187
+ },
188
+ {
189
+ path: 'apps/web/next.config.ts',
190
+ content: nextConfig,
191
+ },
192
+ {
193
+ path: 'apps/web/tsconfig.json',
194
+ content: `${JSON.stringify(tsConfig, null, 2)}\n`,
195
+ },
196
+ {
197
+ path: 'apps/web/src/app/layout.tsx',
198
+ content: layoutTsx,
199
+ },
200
+ {
201
+ path: 'apps/web/src/app/page.tsx',
202
+ content: pageTsx,
203
+ },
204
+ {
205
+ path: 'apps/web/.env.local',
206
+ content: envLocal,
207
+ },
208
+ {
209
+ path: 'apps/web/.gitignore',
210
+ content: gitignore,
211
+ },
212
+ ];
213
+ }
package/src/init/index.ts CHANGED
@@ -2,17 +2,33 @@ import { execSync } from 'node:child_process';
2
2
  import { mkdir, writeFile } from 'node:fs/promises';
3
3
  import { dirname, join } from 'node:path';
4
4
  import prompts from 'prompts';
5
+ import { createStageSecrets } from '../secrets/generator.js';
6
+ import { getKeyPath } from '../secrets/keystore.js';
7
+ import { writeStageSecrets } from '../secrets/storage.js';
8
+ import type { ComposeServiceName } from '../types.js';
9
+ import { generateAuthAppFiles } from './generators/auth.js';
5
10
  import { generateConfigFiles } from './generators/config.js';
6
- import { generateDockerFiles } from './generators/docker.js';
11
+ import {
12
+ type DatabaseAppConfig,
13
+ generateDockerFiles,
14
+ } from './generators/docker.js';
7
15
  import { generateEnvFiles } from './generators/env.js';
8
16
  import { generateModelsPackage } from './generators/models.js';
9
17
  import { generateMonorepoFiles } from './generators/monorepo.js';
10
18
  import { generatePackageJson } from './generators/package.js';
11
19
  import { generateSourceFiles } from './generators/source.js';
20
+ import { generateWebAppFiles } from './generators/web.js';
12
21
  import {
22
+ type DeployTarget,
23
+ deployTargetChoices,
13
24
  getTemplate,
25
+ isFullstackTemplate,
14
26
  loggerTypeChoices,
27
+ type PackageManager,
28
+ packageManagerChoices,
15
29
  routesStructureChoices,
30
+ type ServicesSelection,
31
+ servicesChoices,
16
32
  type TemplateName,
17
33
  type TemplateOptions,
18
34
  templateChoices,
@@ -26,11 +42,43 @@ import {
26
42
  } from './utils.js';
27
43
 
28
44
  export interface InitOptions {
45
+ /** Project name */
46
+ name?: string;
47
+ /** Template to use */
29
48
  template?: TemplateName;
49
+ /** Skip dependency installation */
30
50
  skipInstall?: boolean;
51
+ /** Use defaults for all prompts */
31
52
  yes?: boolean;
53
+ /** Force monorepo setup (deprecated, use fullstack template) */
32
54
  monorepo?: boolean;
55
+ /** API app path in monorepo */
33
56
  apiPath?: string;
57
+ /** Package manager to use */
58
+ pm?: PackageManager;
59
+ }
60
+
61
+ /**
62
+ * Generate a secure random password for database users
63
+ */
64
+ function generateDbPassword(): string {
65
+ return `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}${Math.random().toString(36).slice(2)}`;
66
+ }
67
+
68
+ /**
69
+ * Generate database URL for an app
70
+ * All apps connect to the same database, but use different users/schemas
71
+ */
72
+ function generateDbUrl(
73
+ appName: string,
74
+ password: string,
75
+ projectName: string,
76
+ host = 'localhost',
77
+ port = 5432,
78
+ ): string {
79
+ const userName = appName.replace(/-/g, '_');
80
+ const dbName = `${projectName.replace(/-/g, '_')}_dev`;
81
+ return `postgresql://${userName}:${password}@${host}:${port}/${dbName}`;
34
82
  }
35
83
 
36
84
  /**
@@ -41,7 +89,7 @@ export async function initCommand(
41
89
  options: InitOptions = {},
42
90
  ): Promise<void> {
43
91
  const cwd = process.cwd();
44
- const pkgManager = detectPackageManager(cwd);
92
+ const detectedPkgManager = detectPackageManager(cwd);
45
93
 
46
94
  // Handle Ctrl+C gracefully
47
95
  prompts.override({});
@@ -53,10 +101,10 @@ export async function initCommand(
53
101
  const answers = await prompts(
54
102
  [
55
103
  {
56
- type: projectName ? null : 'text',
104
+ type: projectName || options.name ? null : 'text',
57
105
  name: 'name',
58
106
  message: 'Project name:',
59
- initial: 'my-api',
107
+ initial: 'my-app',
60
108
  validate: (value: string) => {
61
109
  const nameValid = validateProjectName(value);
62
110
  if (nameValid !== true) return nameValid;
@@ -73,21 +121,32 @@ export async function initCommand(
73
121
  initial: 0,
74
122
  },
75
123
  {
76
- type: options.yes ? null : 'confirm',
77
- name: 'telescope',
78
- message: 'Include Telescope (debugging dashboard)?',
79
- initial: true,
124
+ type: options.yes ? null : 'multiselect',
125
+ name: 'services',
126
+ message: 'Services (space to select, enter to confirm):',
127
+ choices: servicesChoices.map((c) => ({ ...c, selected: true })),
128
+ hint: '- Space to select. Return to submit',
80
129
  },
81
130
  {
82
- type: options.yes ? null : 'confirm',
83
- name: 'database',
84
- message: 'Include database support (Kysely)?',
85
- initial: true,
131
+ type: options.yes ? null : 'select',
132
+ name: 'packageManager',
133
+ message: 'Package manager:',
134
+ choices: packageManagerChoices,
135
+ initial: packageManagerChoices.findIndex(
136
+ (c) => c.value === detectedPkgManager,
137
+ ),
86
138
  },
87
139
  {
88
- type: (prev) => (options.yes ? null : prev ? 'confirm' : null),
89
- name: 'studio',
90
- message: 'Include Studio (database browser)?',
140
+ type: options.yes ? null : 'select',
141
+ name: 'deployTarget',
142
+ message: 'Deployment target:',
143
+ choices: deployTargetChoices,
144
+ initial: 0,
145
+ },
146
+ {
147
+ type: options.yes ? null : 'confirm',
148
+ name: 'telescope',
149
+ message: 'Include Telescope (debugging dashboard)?',
91
150
  initial: true,
92
151
  },
93
152
  {
@@ -104,66 +163,85 @@ export async function initCommand(
104
163
  choices: routesStructureChoices,
105
164
  initial: 0,
106
165
  },
107
- {
108
- type: options.yes || options.monorepo !== undefined ? null : 'confirm',
109
- name: 'monorepo',
110
- message: 'Setup as monorepo?',
111
- initial: false,
112
- },
113
- {
114
- type: (prev) =>
115
- (prev === true || options.monorepo) && !options.apiPath
116
- ? 'text'
117
- : null,
118
- name: 'apiPath',
119
- message: 'API app path:',
120
- initial: 'apps/api',
121
- },
122
166
  ],
123
167
  { onCancel },
124
168
  );
125
169
 
126
170
  // Build final options
127
- const name = projectName || answers.name;
171
+ const name = projectName || options.name || answers.name;
128
172
  if (!name) {
173
+ console.error('Project name is required');
129
174
  process.exit(1);
130
175
  }
131
176
 
132
177
  // Validate name if provided via argument
133
- if (projectName) {
134
- const nameValid = validateProjectName(projectName);
178
+ if (projectName || options.name) {
179
+ const nameToValidate = projectName || options.name!;
180
+ const nameValid = validateProjectName(nameToValidate);
135
181
  if (nameValid !== true) {
182
+ console.error(nameValid);
136
183
  process.exit(1);
137
184
  }
138
- const dirValid = checkDirectoryExists(projectName, cwd);
185
+ const dirValid = checkDirectoryExists(nameToValidate, cwd);
139
186
  if (dirValid !== true) {
187
+ console.error(dirValid);
140
188
  process.exit(1);
141
189
  }
142
190
  }
143
191
 
144
- const monorepo =
145
- options.monorepo ?? (options.yes ? false : (answers.monorepo ?? false));
146
- const database = options.yes ? true : (answers.database ?? true);
192
+ const template: TemplateName = options.template || answers.template || 'api';
193
+ const isFullstack = isFullstackTemplate(template);
194
+
195
+ // For fullstack, force monorepo mode
196
+ // For api template, monorepo is optional (via --monorepo flag)
197
+ const monorepo = isFullstack || options.monorepo || false;
198
+
199
+ // Parse services selection
200
+ const servicesArray: string[] = options.yes
201
+ ? ['db', 'cache', 'mail']
202
+ : answers.services || [];
203
+ const services: ServicesSelection = {
204
+ db: servicesArray.includes('db'),
205
+ cache: servicesArray.includes('cache'),
206
+ mail: servicesArray.includes('mail'),
207
+ };
208
+
209
+ const pkgManager: PackageManager = options.pm
210
+ ? options.pm
211
+ : options.yes
212
+ ? 'pnpm'
213
+ : (answers.packageManager ?? detectedPkgManager);
214
+
215
+ const deployTarget: DeployTarget = options.yes
216
+ ? 'dokploy'
217
+ : (answers.deployTarget ?? 'dokploy');
218
+
219
+ const database = services.db;
147
220
  const templateOptions: TemplateOptions = {
148
221
  name,
149
- template: options.template || answers.template || 'minimal',
222
+ template,
150
223
  telescope: options.yes ? true : (answers.telescope ?? true),
151
224
  database,
152
- studio: database && (options.yes ? true : (answers.studio ?? true)),
225
+ studio: database,
153
226
  loggerType: options.yes ? 'pino' : (answers.loggerType ?? 'pino'),
154
227
  routesStructure: options.yes
155
228
  ? 'centralized-endpoints'
156
229
  : (answers.routesStructure ?? 'centralized-endpoints'),
157
230
  monorepo,
158
- apiPath: monorepo ? (options.apiPath ?? answers.apiPath ?? 'apps/api') : '',
231
+ apiPath: monorepo ? (options.apiPath ?? 'apps/api') : '',
232
+ packageManager: pkgManager,
233
+ deployTarget,
234
+ services,
159
235
  };
160
236
 
161
237
  const targetDir = join(cwd, name);
162
- const template = getTemplate(templateOptions.template);
238
+ const baseTemplate = getTemplate(templateOptions.template);
163
239
 
164
240
  const isMonorepo = templateOptions.monorepo;
165
241
  const apiPath = templateOptions.apiPath;
166
242
 
243
+ console.log('\n🚀 Creating your project...\n');
244
+
167
245
  // Create project directory
168
246
  await mkdir(targetDir, { recursive: true });
169
247
 
@@ -173,20 +251,48 @@ export async function initCommand(
173
251
  await mkdir(appDir, { recursive: true });
174
252
  }
175
253
 
176
- // Collect app files
177
- const appFiles = [
178
- ...generatePackageJson(templateOptions, template),
179
- ...generateConfigFiles(templateOptions, template),
180
- ...generateEnvFiles(templateOptions, template),
181
- ...generateSourceFiles(templateOptions, template),
182
- ...generateDockerFiles(templateOptions, template),
183
- ];
254
+ // Generate per-app database configs for fullstack template
255
+ const dbApps: DatabaseAppConfig[] = [];
256
+ if (isFullstack && services.db) {
257
+ dbApps.push(
258
+ { name: 'api', password: generateDbPassword() },
259
+ { name: 'auth', password: generateDbPassword() },
260
+ );
261
+ }
262
+
263
+ // Collect app files (backend/api)
264
+ // Note: Docker files go to root for monorepo, so exclude them here
265
+ const appFiles = baseTemplate
266
+ ? [
267
+ ...generatePackageJson(templateOptions, baseTemplate),
268
+ ...generateConfigFiles(templateOptions, baseTemplate),
269
+ ...generateEnvFiles(templateOptions, baseTemplate),
270
+ ...generateSourceFiles(templateOptions, baseTemplate),
271
+ ...(isMonorepo
272
+ ? []
273
+ : generateDockerFiles(templateOptions, baseTemplate, dbApps)),
274
+ ]
275
+ : [];
276
+
277
+ // For monorepo, docker files go at root level
278
+ const dockerFiles =
279
+ isMonorepo && baseTemplate
280
+ ? generateDockerFiles(templateOptions, baseTemplate, dbApps)
281
+ : [];
184
282
 
185
283
  // Collect root monorepo files (includes packages/models)
186
- const rootFiles = [
187
- ...generateMonorepoFiles(templateOptions, template),
188
- ...generateModelsPackage(templateOptions),
189
- ];
284
+ const rootFiles = baseTemplate
285
+ ? [
286
+ ...generateMonorepoFiles(templateOptions, baseTemplate),
287
+ ...generateModelsPackage(templateOptions),
288
+ ]
289
+ : [];
290
+
291
+ // Collect web app files for fullstack template
292
+ const webAppFiles = isFullstack ? generateWebAppFiles(templateOptions) : [];
293
+
294
+ // Collect auth app files for fullstack template
295
+ const authAppFiles = isFullstack ? generateAuthAppFiles(templateOptions) : [];
190
296
 
191
297
  // Write root files (for monorepo)
192
298
  for (const { path, content } of rootFiles) {
@@ -195,22 +301,88 @@ export async function initCommand(
195
301
  await writeFile(fullPath, content);
196
302
  }
197
303
 
198
- // Write app files
304
+ // Write docker files at root for monorepo
305
+ for (const { path, content } of dockerFiles) {
306
+ const fullPath = join(targetDir, path);
307
+ await mkdir(dirname(fullPath), { recursive: true });
308
+ await writeFile(fullPath, content);
309
+ }
310
+
311
+ // Write app files (backend)
199
312
  for (const { path, content } of appFiles) {
200
313
  const fullPath = join(appDir, path);
201
- const _displayPath = isMonorepo ? `${apiPath}/${path}` : path;
202
314
  await mkdir(dirname(fullPath), { recursive: true });
203
315
  await writeFile(fullPath, content);
204
316
  }
205
317
 
318
+ // Write web app files (frontend)
319
+ for (const { path, content } of webAppFiles) {
320
+ const fullPath = join(targetDir, path);
321
+ await mkdir(dirname(fullPath), { recursive: true });
322
+ await writeFile(fullPath, content);
323
+ }
324
+
325
+ // Write auth app files (authentication service)
326
+ for (const { path, content } of authAppFiles) {
327
+ const fullPath = join(targetDir, path);
328
+ await mkdir(dirname(fullPath), { recursive: true });
329
+ await writeFile(fullPath, content);
330
+ }
331
+
332
+ // Initialize encrypted secrets for development stage
333
+ console.log('🔐 Initializing encrypted secrets...\n');
334
+ const secretServices: ComposeServiceName[] = [];
335
+ if (services.db) secretServices.push('postgres');
336
+ if (services.cache) secretServices.push('redis');
337
+
338
+ const devSecrets = createStageSecrets('development', secretServices);
339
+
340
+ // Add common custom secrets
341
+ const customSecrets: Record<string, string> = {
342
+ NODE_ENV: 'development',
343
+ PORT: '3000',
344
+ LOG_LEVEL: 'debug',
345
+ JWT_SECRET: `dev-${Date.now()}-${Math.random().toString(36).slice(2)}`,
346
+ };
347
+
348
+ // Add per-app database URLs and passwords for fullstack template
349
+ if (isFullstack && dbApps.length > 0) {
350
+ for (const app of dbApps) {
351
+ // Database URL for the app to use (all apps use same database, different users/schemas)
352
+ const urlKey = `${app.name.toUpperCase()}_DATABASE_URL`;
353
+ customSecrets[urlKey] = generateDbUrl(app.name, app.password, name);
354
+
355
+ // Database password for docker-compose init script
356
+ const passwordKey = `${app.name.toUpperCase()}_DB_PASSWORD`;
357
+ customSecrets[passwordKey] = app.password;
358
+ }
359
+
360
+ // Auth service secrets (better-auth)
361
+ customSecrets.AUTH_PORT = '3002';
362
+ customSecrets.BETTER_AUTH_SECRET = `better-auth-${Date.now()}-${Math.random().toString(36).slice(2)}`;
363
+ customSecrets.BETTER_AUTH_URL = 'http://localhost:3002';
364
+ customSecrets.BETTER_AUTH_TRUSTED_ORIGINS =
365
+ 'http://localhost:3000,http://localhost:3001';
366
+ }
367
+
368
+ devSecrets.custom = customSecrets;
369
+
370
+ await writeStageSecrets(devSecrets, targetDir);
371
+ const keyPath = getKeyPath('development', name);
372
+ console.log(` Secrets: .gkm/secrets/development.json (encrypted)`);
373
+ console.log(` Key: ${keyPath}\n`);
374
+
206
375
  // Install dependencies
207
376
  if (!options.skipInstall) {
377
+ console.log('\n📦 Installing dependencies...\n');
208
378
  try {
209
379
  execSync(getInstallCommand(pkgManager), {
210
380
  cwd: targetDir,
211
381
  stdio: 'inherit',
212
382
  });
213
- } catch {}
383
+ } catch {
384
+ console.error('Failed to install dependencies');
385
+ }
214
386
 
215
387
  // Format generated files with biome
216
388
  try {
@@ -223,6 +395,66 @@ export async function initCommand(
223
395
  }
224
396
  }
225
397
 
226
- // Print next steps
227
- const _devCommand = getRunCommand(pkgManager, 'dev');
398
+ // Print success message with next steps
399
+ printNextSteps(name, templateOptions, pkgManager);
400
+ }
401
+
402
+ /**
403
+ * Print success message with next steps
404
+ */
405
+ function printNextSteps(
406
+ projectName: string,
407
+ options: TemplateOptions,
408
+ pkgManager: PackageManager,
409
+ ): void {
410
+ const devCommand = getRunCommand(pkgManager, 'dev');
411
+ const cdCommand = `cd ${projectName}`;
412
+
413
+ console.log(`\n${'─'.repeat(50)}`);
414
+ console.log('\n✅ Project created successfully!\n');
415
+
416
+ console.log('Next steps:\n');
417
+ console.log(` ${cdCommand}`);
418
+
419
+ if (options.services.db) {
420
+ console.log(` # Start PostgreSQL (if not running)`);
421
+ console.log(` docker compose up -d postgres`);
422
+ }
423
+
424
+ console.log(` ${devCommand}`);
425
+ console.log('');
426
+
427
+ if (options.monorepo) {
428
+ console.log('📁 Project structure:');
429
+ console.log(` ${projectName}/`);
430
+ console.log(` ├── apps/`);
431
+ console.log(` │ ├── api/ # Backend API`);
432
+ if (isFullstackTemplate(options.template)) {
433
+ console.log(` │ ├── auth/ # Auth service (better-auth)`);
434
+ console.log(` │ └── web/ # Next.js frontend`);
435
+ }
436
+ console.log(` ├── packages/`);
437
+ console.log(` │ └── models/ # Shared Zod schemas`);
438
+ console.log(` ├── .gkm/secrets/ # Encrypted secrets`);
439
+ console.log(` ├── gkm.config.ts # Workspace config`);
440
+ console.log(` └── turbo.json # Turbo config`);
441
+ console.log('');
442
+ }
443
+
444
+ console.log('🔐 Secrets management:');
445
+ console.log(` gkm secrets:show --stage development # View secrets`);
446
+ console.log(` gkm secrets:set KEY VALUE --stage development # Add secret`);
447
+ console.log(
448
+ ` gkm secrets:init --stage production # Create production secrets`,
449
+ );
450
+ console.log('');
451
+
452
+ if (options.deployTarget === 'dokploy') {
453
+ console.log('🚀 Deployment:');
454
+ console.log(` ${getRunCommand(pkgManager, 'deploy')}`);
455
+ console.log('');
456
+ }
457
+
458
+ console.log('📚 Documentation: https://docs.geekmidas.dev');
459
+ console.log('');
228
460
  }