@geekmidas/cli 0.18.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{bundler-C74EKlNa.cjs → bundler-CyHg1v_T.cjs} +3 -3
- package/dist/{bundler-C74EKlNa.cjs.map → bundler-CyHg1v_T.cjs.map} +1 -1
- package/dist/{bundler-B6z6HEeh.mjs → bundler-DQIuE3Kn.mjs} +3 -3
- package/dist/{bundler-B6z6HEeh.mjs.map → bundler-DQIuE3Kn.mjs.map} +1 -1
- package/dist/{config-DYULeEv8.mjs → config-BaYqrF3n.mjs} +48 -10
- package/dist/config-BaYqrF3n.mjs.map +1 -0
- package/dist/{config-AmInkU7k.cjs → config-CxrLu8ia.cjs} +53 -9
- package/dist/config-CxrLu8ia.cjs.map +1 -0
- package/dist/config.cjs +4 -1
- package/dist/config.d.cts +27 -2
- package/dist/config.d.cts.map +1 -1
- package/dist/config.d.mts +27 -2
- package/dist/config.d.mts.map +1 -1
- package/dist/config.mjs +3 -2
- package/dist/dokploy-api-B0w17y4_.mjs +3 -0
- package/dist/{dokploy-api-CaETb2L6.mjs → dokploy-api-B9qR2Yn1.mjs} +1 -1
- package/dist/{dokploy-api-CaETb2L6.mjs.map → dokploy-api-B9qR2Yn1.mjs.map} +1 -1
- package/dist/dokploy-api-BnGeUqN4.cjs +3 -0
- package/dist/{dokploy-api-C7F9VykY.cjs → dokploy-api-C5czOZoc.cjs} +1 -1
- package/dist/{dokploy-api-C7F9VykY.cjs.map → dokploy-api-C5czOZoc.cjs.map} +1 -1
- package/dist/{encryption-D7Efcdi9.cjs → encryption-BAz0xQ1Q.cjs} +1 -1
- package/dist/{encryption-D7Efcdi9.cjs.map → encryption-BAz0xQ1Q.cjs.map} +1 -1
- package/dist/{encryption-h4Nb6W-M.mjs → encryption-JtMsiGNp.mjs} +2 -2
- package/dist/{encryption-h4Nb6W-M.mjs.map → encryption-JtMsiGNp.mjs.map} +1 -1
- package/dist/index-CWN-bgrO.d.mts +495 -0
- package/dist/index-CWN-bgrO.d.mts.map +1 -0
- package/dist/index-DEWYvYvg.d.cts +495 -0
- package/dist/index-DEWYvYvg.d.cts.map +1 -0
- package/dist/index.cjs +2639 -563
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +2634 -563
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-CZVcfxk-.mjs → openapi-CgqR6Jkw.mjs} +3 -3
- package/dist/{openapi-CZVcfxk-.mjs.map → openapi-CgqR6Jkw.mjs.map} +1 -1
- package/dist/{openapi-C89hhkZC.cjs → openapi-DfpxS0xv.cjs} +8 -2
- package/dist/{openapi-C89hhkZC.cjs.map → openapi-DfpxS0xv.cjs.map} +1 -1
- package/dist/{openapi-react-query-CM2_qlW9.mjs → openapi-react-query-5rSortLH.mjs} +1 -1
- package/dist/{openapi-react-query-CM2_qlW9.mjs.map → openapi-react-query-5rSortLH.mjs.map} +1 -1
- package/dist/{openapi-react-query-iKjfLzff.cjs → openapi-react-query-DvNpdDpM.cjs} +1 -1
- package/dist/{openapi-react-query-iKjfLzff.cjs.map → openapi-react-query-DvNpdDpM.cjs.map} +1 -1
- package/dist/openapi-react-query.cjs +1 -1
- package/dist/openapi-react-query.mjs +1 -1
- package/dist/openapi.cjs +3 -2
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/openapi.mjs +3 -2
- package/dist/{storage-Bn3K9Ccu.cjs → storage-BPRgh3DU.cjs} +136 -5
- package/dist/storage-BPRgh3DU.cjs.map +1 -0
- package/dist/{storage-nkGIjeXt.mjs → storage-DNj_I11J.mjs} +1 -1
- package/dist/storage-Dhst7BhI.mjs +272 -0
- package/dist/storage-Dhst7BhI.mjs.map +1 -0
- package/dist/{storage-UfyTn7Zm.cjs → storage-fOR8dMu5.cjs} +1 -1
- package/dist/{types-iFk5ms7y.d.mts → types-K2uQJ-FO.d.mts} +2 -2
- package/dist/{types-BgaMXsUa.d.cts.map → types-K2uQJ-FO.d.mts.map} +1 -1
- package/dist/{types-BgaMXsUa.d.cts → types-l53qUmGt.d.cts} +2 -2
- package/dist/{types-iFk5ms7y.d.mts.map → types-l53qUmGt.d.cts.map} +1 -1
- package/dist/workspace/index.cjs +19 -0
- package/dist/workspace/index.d.cts +3 -0
- package/dist/workspace/index.d.mts +3 -0
- package/dist/workspace/index.mjs +3 -0
- package/dist/workspace-CPLEZDZf.mjs +3788 -0
- package/dist/workspace-CPLEZDZf.mjs.map +1 -0
- package/dist/workspace-iWgBlX6h.cjs +3885 -0
- package/dist/workspace-iWgBlX6h.cjs.map +1 -0
- package/package.json +8 -3
- package/src/build/__tests__/workspace-build.spec.ts +215 -0
- package/src/build/index.ts +189 -1
- package/src/config.ts +71 -14
- package/src/deploy/__tests__/docker.spec.ts +1 -1
- package/src/deploy/__tests__/index.spec.ts +305 -1
- package/src/deploy/index.ts +426 -4
- package/src/deploy/types.ts +32 -0
- package/src/dev/__tests__/index.spec.ts +572 -1
- package/src/dev/index.ts +582 -2
- package/src/docker/__tests__/compose.spec.ts +425 -0
- package/src/docker/__tests__/templates.spec.ts +145 -0
- package/src/docker/compose.ts +248 -0
- package/src/docker/index.ts +159 -3
- package/src/docker/templates.ts +219 -4
- package/src/index.ts +24 -0
- package/src/init/__tests__/generators.spec.ts +17 -24
- package/src/init/__tests__/init.spec.ts +157 -5
- package/src/init/generators/auth.ts +220 -0
- package/src/init/generators/config.ts +61 -4
- package/src/init/generators/docker.ts +115 -8
- package/src/init/generators/env.ts +7 -127
- package/src/init/generators/index.ts +1 -0
- package/src/init/generators/models.ts +3 -1
- package/src/init/generators/monorepo.ts +154 -10
- package/src/init/generators/package.ts +5 -3
- package/src/init/generators/web.ts +213 -0
- package/src/init/index.ts +290 -58
- package/src/init/templates/api.ts +38 -29
- package/src/init/templates/index.ts +132 -4
- package/src/init/templates/minimal.ts +33 -35
- package/src/init/templates/serverless.ts +16 -19
- package/src/init/templates/worker.ts +50 -25
- package/src/init/versions.ts +47 -0
- package/src/secrets/keystore.ts +144 -0
- package/src/secrets/storage.ts +109 -6
- package/src/test/index.ts +97 -0
- package/src/workspace/__tests__/client-generator.spec.ts +357 -0
- package/src/workspace/__tests__/index.spec.ts +543 -0
- package/src/workspace/__tests__/schema.spec.ts +519 -0
- package/src/workspace/__tests__/type-inference.spec.ts +251 -0
- package/src/workspace/client-generator.ts +307 -0
- package/src/workspace/index.ts +372 -0
- package/src/workspace/schema.ts +368 -0
- package/src/workspace/types.ts +336 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/tsdown.config.ts +1 -0
- package/dist/config-AmInkU7k.cjs.map +0 -1
- package/dist/config-DYULeEv8.mjs.map +0 -1
- package/dist/dokploy-api-B7KxOQr3.cjs +0 -3
- package/dist/dokploy-api-DHvfmWbi.mjs +0 -3
- package/dist/storage-BaOP55oq.mjs +0 -147
- package/dist/storage-BaOP55oq.mjs.map +0 -1
- package/dist/storage-Bn3K9Ccu.cjs.map +0 -1
|
@@ -0,0 +1,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 {
|
|
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
|
|
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-
|
|
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 : '
|
|
77
|
-
name: '
|
|
78
|
-
message: '
|
|
79
|
-
|
|
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 : '
|
|
83
|
-
name: '
|
|
84
|
-
message: '
|
|
85
|
-
|
|
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:
|
|
89
|
-
name: '
|
|
90
|
-
message: '
|
|
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
|
|
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(
|
|
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
|
|
145
|
-
|
|
146
|
-
|
|
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
|
|
222
|
+
template,
|
|
150
223
|
telescope: options.yes ? true : (answers.telescope ?? true),
|
|
151
224
|
database,
|
|
152
|
-
studio: database
|
|
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 ??
|
|
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
|
|
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
|
-
//
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
188
|
-
|
|
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
|
|
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
|
-
|
|
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
|
}
|