@geekmidas/cli 0.9.0 → 0.12.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 (146) hide show
  1. package/README.md +525 -0
  2. package/dist/bundler-DRXCw_YR.mjs +70 -0
  3. package/dist/bundler-DRXCw_YR.mjs.map +1 -0
  4. package/dist/bundler-WsEvH_b2.cjs +71 -0
  5. package/dist/bundler-WsEvH_b2.cjs.map +1 -0
  6. package/dist/{config-CFls09Ey.cjs → config-AmInkU7k.cjs} +10 -8
  7. package/dist/config-AmInkU7k.cjs.map +1 -0
  8. package/dist/{config-Bq72aj8e.mjs → config-DYULeEv8.mjs} +6 -4
  9. package/dist/config-DYULeEv8.mjs.map +1 -0
  10. package/dist/config.cjs +1 -1
  11. package/dist/config.d.cts +2 -1
  12. package/dist/config.d.cts.map +1 -0
  13. package/dist/config.d.mts +2 -1
  14. package/dist/config.d.mts.map +1 -0
  15. package/dist/config.mjs +1 -1
  16. package/dist/encryption-C8H-38Yy.mjs +42 -0
  17. package/dist/encryption-C8H-38Yy.mjs.map +1 -0
  18. package/dist/encryption-Dyf_r1h-.cjs +44 -0
  19. package/dist/encryption-Dyf_r1h-.cjs.map +1 -0
  20. package/dist/index.cjs +2125 -184
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.mjs +2143 -197
  23. package/dist/index.mjs.map +1 -1
  24. package/dist/{openapi--vOy9mo4.mjs → openapi-BfFlOBCG.mjs} +812 -49
  25. package/dist/openapi-BfFlOBCG.mjs.map +1 -0
  26. package/dist/{openapi-CHhTPief.cjs → openapi-Bt_1FDpT.cjs} +805 -42
  27. package/dist/openapi-Bt_1FDpT.cjs.map +1 -0
  28. package/dist/{openapi-react-query-o5iMi8tz.cjs → openapi-react-query-B-sNWHFU.cjs} +5 -5
  29. package/dist/openapi-react-query-B-sNWHFU.cjs.map +1 -0
  30. package/dist/{openapi-react-query-CcciaVu5.mjs → openapi-react-query-B6XTeGqS.mjs} +5 -5
  31. package/dist/openapi-react-query-B6XTeGqS.mjs.map +1 -0
  32. package/dist/openapi-react-query.cjs +1 -1
  33. package/dist/openapi-react-query.d.cts.map +1 -0
  34. package/dist/openapi-react-query.d.mts.map +1 -0
  35. package/dist/openapi-react-query.mjs +1 -1
  36. package/dist/openapi.cjs +2 -2
  37. package/dist/openapi.d.cts +1 -1
  38. package/dist/openapi.d.cts.map +1 -0
  39. package/dist/openapi.d.mts +1 -1
  40. package/dist/openapi.d.mts.map +1 -0
  41. package/dist/openapi.mjs +2 -2
  42. package/dist/storage-BUYQJgz7.cjs +4 -0
  43. package/dist/storage-BXoJvmv2.cjs +149 -0
  44. package/dist/storage-BXoJvmv2.cjs.map +1 -0
  45. package/dist/storage-C9PU_30f.mjs +101 -0
  46. package/dist/storage-C9PU_30f.mjs.map +1 -0
  47. package/dist/storage-DLJAYxzJ.mjs +3 -0
  48. package/dist/{types-b-vwGpqc.d.cts → types-BR0M2v_c.d.mts} +100 -1
  49. package/dist/types-BR0M2v_c.d.mts.map +1 -0
  50. package/dist/{types-DXgiA1sF.d.mts → types-BhkZc-vm.d.cts} +100 -1
  51. package/dist/types-BhkZc-vm.d.cts.map +1 -0
  52. package/examples/cron-example.ts +27 -27
  53. package/examples/env.ts +27 -27
  54. package/examples/function-example.ts +31 -31
  55. package/examples/gkm.config.json +20 -20
  56. package/examples/gkm.config.ts +8 -8
  57. package/examples/gkm.minimal.config.json +5 -5
  58. package/examples/gkm.production.config.json +25 -25
  59. package/examples/logger.ts +2 -2
  60. package/package.json +6 -6
  61. package/src/__tests__/EndpointGenerator.hooks.spec.ts +191 -191
  62. package/src/__tests__/config.spec.ts +55 -55
  63. package/src/__tests__/loadEnvFiles.spec.ts +93 -93
  64. package/src/__tests__/normalizeHooksConfig.spec.ts +58 -58
  65. package/src/__tests__/openapi-react-query.spec.ts +497 -497
  66. package/src/__tests__/openapi.spec.ts +428 -428
  67. package/src/__tests__/test-helpers.ts +77 -76
  68. package/src/auth/__tests__/credentials.spec.ts +204 -0
  69. package/src/auth/__tests__/index.spec.ts +168 -0
  70. package/src/auth/credentials.ts +187 -0
  71. package/src/auth/index.ts +226 -0
  72. package/src/build/__tests__/index-new.spec.ts +474 -474
  73. package/src/build/__tests__/manifests.spec.ts +333 -333
  74. package/src/build/bundler.ts +141 -0
  75. package/src/build/endpoint-analyzer.ts +236 -0
  76. package/src/build/handler-templates.ts +1253 -0
  77. package/src/build/index.ts +250 -179
  78. package/src/build/manifests.ts +52 -52
  79. package/src/build/providerResolver.ts +145 -145
  80. package/src/build/types.ts +64 -43
  81. package/src/config.ts +39 -37
  82. package/src/deploy/__tests__/docker.spec.ts +111 -0
  83. package/src/deploy/__tests__/dokploy.spec.ts +245 -0
  84. package/src/deploy/__tests__/init.spec.ts +662 -0
  85. package/src/deploy/docker.ts +128 -0
  86. package/src/deploy/dokploy.ts +204 -0
  87. package/src/deploy/index.ts +136 -0
  88. package/src/deploy/init.ts +484 -0
  89. package/src/deploy/types.ts +48 -0
  90. package/src/dev/__tests__/index.spec.ts +266 -266
  91. package/src/dev/index.ts +647 -593
  92. package/src/docker/__tests__/compose.spec.ts +531 -0
  93. package/src/docker/__tests__/templates.spec.ts +280 -0
  94. package/src/docker/compose.ts +273 -0
  95. package/src/docker/index.ts +230 -0
  96. package/src/docker/templates.ts +446 -0
  97. package/src/generators/CronGenerator.ts +72 -72
  98. package/src/generators/EndpointGenerator.ts +699 -398
  99. package/src/generators/FunctionGenerator.ts +84 -84
  100. package/src/generators/Generator.ts +72 -72
  101. package/src/generators/OpenApiTsGenerator.ts +589 -589
  102. package/src/generators/SubscriberGenerator.ts +124 -124
  103. package/src/generators/__tests__/CronGenerator.spec.ts +433 -433
  104. package/src/generators/__tests__/EndpointGenerator.spec.ts +532 -382
  105. package/src/generators/__tests__/FunctionGenerator.spec.ts +244 -244
  106. package/src/generators/__tests__/SubscriberGenerator.spec.ts +397 -382
  107. package/src/generators/index.ts +4 -4
  108. package/src/index.ts +628 -206
  109. package/src/init/__tests__/generators.spec.ts +334 -334
  110. package/src/init/__tests__/init.spec.ts +332 -332
  111. package/src/init/__tests__/utils.spec.ts +89 -89
  112. package/src/init/generators/config.ts +175 -175
  113. package/src/init/generators/docker.ts +41 -41
  114. package/src/init/generators/env.ts +72 -72
  115. package/src/init/generators/index.ts +1 -1
  116. package/src/init/generators/models.ts +64 -64
  117. package/src/init/generators/monorepo.ts +161 -161
  118. package/src/init/generators/package.ts +71 -71
  119. package/src/init/generators/source.ts +6 -6
  120. package/src/init/index.ts +203 -208
  121. package/src/init/templates/api.ts +115 -115
  122. package/src/init/templates/index.ts +75 -75
  123. package/src/init/templates/minimal.ts +98 -98
  124. package/src/init/templates/serverless.ts +89 -89
  125. package/src/init/templates/worker.ts +98 -98
  126. package/src/init/utils.ts +54 -56
  127. package/src/openapi-react-query.ts +194 -194
  128. package/src/openapi.ts +63 -63
  129. package/src/secrets/__tests__/encryption.spec.ts +226 -0
  130. package/src/secrets/__tests__/generator.spec.ts +319 -0
  131. package/src/secrets/__tests__/index.spec.ts +91 -0
  132. package/src/secrets/__tests__/storage.spec.ts +403 -0
  133. package/src/secrets/encryption.ts +91 -0
  134. package/src/secrets/generator.ts +164 -0
  135. package/src/secrets/index.ts +383 -0
  136. package/src/secrets/storage.ts +134 -0
  137. package/src/secrets/types.ts +53 -0
  138. package/src/types.ts +295 -176
  139. package/tsconfig.json +9 -0
  140. package/tsdown.config.ts +11 -8
  141. package/dist/config-Bq72aj8e.mjs.map +0 -1
  142. package/dist/config-CFls09Ey.cjs.map +0 -1
  143. package/dist/openapi--vOy9mo4.mjs.map +0 -1
  144. package/dist/openapi-CHhTPief.cjs.map +0 -1
  145. package/dist/openapi-react-query-CcciaVu5.mjs.map +0 -1
  146. package/dist/openapi-react-query-o5iMi8tz.cjs.map +0 -1
package/src/index.ts CHANGED
@@ -1,215 +1,637 @@
1
1
  #!/usr/bin/env -S npx tsx
2
2
 
3
3
  import { Command } from 'commander';
4
- import pkg from '../package.json' assert { type: 'json' };
5
- import { buildCommand } from './build/index.ts';
6
- import { devCommand } from './dev/index.ts';
7
- import { type InitOptions, initCommand } from './init/index.ts';
8
- import { generateReactQueryCommand } from './openapi-react-query.ts';
9
- import { openapiCommand } from './openapi.ts';
10
- import type { LegacyProvider, MainProvider } from './types.ts';
4
+ import pkg from '../package.json';
5
+ import { loginCommand, logoutCommand, whoamiCommand } from './auth';
6
+ import { buildCommand } from './build/index';
7
+ import { type DeployProvider, deployCommand } from './deploy/index';
8
+ import { deployInitCommand, deployListCommand } from './deploy/init';
9
+ import { devCommand } from './dev/index';
10
+ import { type DockerOptions, dockerCommand } from './docker/index';
11
+ import { type InitOptions, initCommand } from './init/index';
12
+ import { openapiCommand } from './openapi';
13
+ import { generateReactQueryCommand } from './openapi-react-query';
14
+ import {
15
+ secretsImportCommand,
16
+ secretsInitCommand,
17
+ secretsRotateCommand,
18
+ secretsSetCommand,
19
+ secretsShowCommand,
20
+ } from './secrets';
21
+ import type { ComposeServiceName, LegacyProvider, MainProvider } from './types';
11
22
 
12
23
  const program = new Command();
13
24
 
14
25
  program
15
- .name('gkm')
16
- .description('GeekMidas backend framework CLI')
17
- .version(pkg.version)
18
- .option('--cwd <path>', 'Change working directory');
19
-
20
- program
21
- .command('init')
22
- .description('Scaffold a new project')
23
- .argument('[name]', 'Project name')
24
- .option(
25
- '--template <template>',
26
- 'Project template (minimal, api, serverless, worker)',
27
- )
28
- .option('--skip-install', 'Skip dependency installation', false)
29
- .option('-y, --yes', 'Skip prompts, use defaults', false)
30
- .option('--monorepo', 'Setup as monorepo with packages/models', false)
31
- .option('--api-path <path>', 'API app path in monorepo (default: apps/api)')
32
- .action(async (name: string | undefined, options: InitOptions) => {
33
- try {
34
- const globalOptions = program.opts();
35
- if (globalOptions.cwd) {
36
- process.chdir(globalOptions.cwd);
37
- }
38
- await initCommand(name, options);
39
- } catch (error) {
40
- console.error('Init failed:', (error as Error).message);
41
- process.exit(1);
42
- }
43
- });
44
-
45
- program
46
- .command('build')
47
- .description('Build handlers from endpoints, functions, and crons')
48
- .option(
49
- '--provider <provider>',
50
- 'Target provider for generated handlers (aws, server)',
51
- )
52
- .option(
53
- '--providers <providers>',
54
- '[DEPRECATED] Use --provider instead. Target providers for generated handlers (comma-separated)',
55
- )
56
- .option(
57
- '--enable-openapi',
58
- 'Enable OpenAPI documentation generation for server builds',
59
- )
60
- .action(
61
- async (options: {
62
- provider?: string;
63
- providers?: string;
64
- enableOpenapi?: boolean;
65
- }) => {
66
- try {
67
- const globalOptions = program.opts();
68
- if (globalOptions.cwd) {
69
- process.chdir(globalOptions.cwd);
70
- }
71
-
72
- // Handle new single provider option
73
- if (options.provider) {
74
- if (!['aws', 'server'].includes(options.provider)) {
75
- console.error(
76
- `Invalid provider: ${options.provider}. Must be 'aws' or 'server'.`,
77
- );
78
- process.exit(1);
79
- }
80
- await buildCommand({
81
- provider: options.provider as MainProvider,
82
- enableOpenApi: options.enableOpenapi || false,
83
- });
84
- }
85
- // Handle legacy providers option
86
- else if (options.providers) {
87
- console.warn(
88
- '⚠️ --providers flag is deprecated. Use --provider instead.',
89
- );
90
- const providerList = [
91
- ...new Set(options.providers.split(',').map((p) => p.trim())),
92
- ] as LegacyProvider[];
93
- await buildCommand({
94
- providers: providerList,
95
- enableOpenApi: options.enableOpenapi || false,
96
- });
97
- }
98
- // Default to config-driven build
99
- else {
100
- await buildCommand({
101
- enableOpenApi: options.enableOpenapi || false,
102
- });
103
- }
104
- } catch (error) {
105
- console.error('Build failed:', (error as Error).message);
106
- process.exit(1);
107
- }
108
- },
109
- );
110
-
111
- program
112
- .command('dev')
113
- .description('Start development server with automatic reload')
114
- .option('-p, --port <port>', 'Port to run the development server on')
115
- .option(
116
- '--enable-openapi',
117
- 'Enable OpenAPI documentation for development server',
118
- true,
119
- )
120
- .action(async (options: { port?: string; enableOpenapi?: boolean }) => {
121
- try {
122
- const globalOptions = program.opts();
123
- if (globalOptions.cwd) {
124
- process.chdir(globalOptions.cwd);
125
- }
126
-
127
- await devCommand({
128
- port: options.port ? Number.parseInt(options.port) : 3000,
129
- portExplicit: !!options.port,
130
- enableOpenApi: options.enableOpenapi ?? true,
131
- });
132
- } catch (error) {
133
- console.error('Dev server failed:', (error as Error).message);
134
- process.exit(1);
135
- }
136
- });
137
-
138
- program
139
- .command('cron')
140
- .description('Manage cron jobs')
141
- .action(() => {
142
- const globalOptions = program.opts();
143
- if (globalOptions.cwd) {
144
- process.chdir(globalOptions.cwd);
145
- }
146
- process.stdout.write('Cron management - coming soon\n');
147
- });
148
-
149
- program
150
- .command('function')
151
- .description('Manage serverless functions')
152
- .action(() => {
153
- const globalOptions = program.opts();
154
- if (globalOptions.cwd) {
155
- process.chdir(globalOptions.cwd);
156
- }
157
- process.stdout.write('Serverless function management - coming soon\n');
158
- });
159
-
160
- program
161
- .command('api')
162
- .description('Manage REST API endpoints')
163
- .action(() => {
164
- const globalOptions = program.opts();
165
- if (globalOptions.cwd) {
166
- process.chdir(globalOptions.cwd);
167
- }
168
- process.stdout.write('REST API management - coming soon\n');
169
- });
170
-
171
- program
172
- .command('openapi')
173
- .description('Generate OpenAPI specification from endpoints')
174
- .action(async () => {
175
- try {
176
- const globalOptions = program.opts();
177
- if (globalOptions.cwd) {
178
- process.chdir(globalOptions.cwd);
179
- }
180
- await openapiCommand({});
181
- } catch (error) {
182
- console.error('OpenAPI generation failed:', (error as Error).message);
183
- process.exit(1);
184
- }
185
- });
186
-
187
- program
188
- .command('generate:react-query')
189
- .description('Generate React Query hooks from OpenAPI specification')
190
- .option('--input <path>', 'Input OpenAPI spec file path', 'openapi.json')
191
- .option(
192
- '--output <path>',
193
- 'Output file path for generated hooks',
194
- 'src/api/hooks.ts',
195
- )
196
- .option('--name <name>', 'API name prefix for generated code', 'API')
197
- .action(
198
- async (options: { input?: string; output?: string; name?: string }) => {
199
- try {
200
- const globalOptions = program.opts();
201
- if (globalOptions.cwd) {
202
- process.chdir(globalOptions.cwd);
203
- }
204
- await generateReactQueryCommand(options);
205
- } catch (error) {
206
- console.error(
207
- 'React Query generation failed:',
208
- (error as Error).message,
209
- );
210
- process.exit(1);
211
- }
212
- },
213
- );
26
+ .name('gkm')
27
+ .description('GeekMidas backend framework CLI')
28
+ .version(pkg.version)
29
+ .option('--cwd <path>', 'Change working directory');
30
+
31
+ program
32
+ .command('init')
33
+ .description('Scaffold a new project')
34
+ .argument('[name]', 'Project name')
35
+ .option(
36
+ '--template <template>',
37
+ 'Project template (minimal, api, serverless, worker)',
38
+ )
39
+ .option('--skip-install', 'Skip dependency installation', false)
40
+ .option('-y, --yes', 'Skip prompts, use defaults', false)
41
+ .option('--monorepo', 'Setup as monorepo with packages/models', false)
42
+ .option('--api-path <path>', 'API app path in monorepo (default: apps/api)')
43
+ .action(async (name: string | undefined, options: InitOptions) => {
44
+ try {
45
+ const globalOptions = program.opts();
46
+ if (globalOptions.cwd) {
47
+ process.chdir(globalOptions.cwd);
48
+ }
49
+ await initCommand(name, options);
50
+ } catch (_error) {
51
+ process.exit(1);
52
+ }
53
+ });
54
+
55
+ program
56
+ .command('build')
57
+ .description('Build handlers from endpoints, functions, and crons')
58
+ .option(
59
+ '--provider <provider>',
60
+ 'Target provider for generated handlers (aws, server)',
61
+ )
62
+ .option(
63
+ '--providers <providers>',
64
+ '[DEPRECATED] Use --provider instead. Target providers for generated handlers (comma-separated)',
65
+ )
66
+ .option(
67
+ '--enable-openapi',
68
+ 'Enable OpenAPI documentation generation for server builds',
69
+ )
70
+ .option('--production', 'Build for production (no dev tools, bundled output)')
71
+ .option('--skip-bundle', 'Skip bundling step in production build')
72
+ .option('--stage <stage>', 'Inject encrypted secrets for deployment stage')
73
+ .action(
74
+ async (options: {
75
+ provider?: string;
76
+ providers?: string;
77
+ enableOpenapi?: boolean;
78
+ production?: boolean;
79
+ skipBundle?: boolean;
80
+ stage?: string;
81
+ }) => {
82
+ try {
83
+ const globalOptions = program.opts();
84
+ if (globalOptions.cwd) {
85
+ process.chdir(globalOptions.cwd);
86
+ }
87
+
88
+ // Handle new single provider option
89
+ if (options.provider) {
90
+ if (!['aws', 'server'].includes(options.provider)) {
91
+ process.exit(1);
92
+ }
93
+ await buildCommand({
94
+ provider: options.provider as MainProvider,
95
+ enableOpenApi: options.enableOpenapi || false,
96
+ production: options.production || false,
97
+ skipBundle: options.skipBundle || false,
98
+ stage: options.stage,
99
+ });
100
+ }
101
+ // Handle legacy providers option
102
+ else if (options.providers) {
103
+ const providerList = [
104
+ ...new Set(options.providers.split(',').map((p) => p.trim())),
105
+ ] as LegacyProvider[];
106
+ await buildCommand({
107
+ providers: providerList,
108
+ enableOpenApi: options.enableOpenapi || false,
109
+ production: options.production || false,
110
+ skipBundle: options.skipBundle || false,
111
+ stage: options.stage,
112
+ });
113
+ }
114
+ // Default to config-driven build
115
+ else {
116
+ await buildCommand({
117
+ enableOpenApi: options.enableOpenapi || false,
118
+ production: options.production || false,
119
+ skipBundle: options.skipBundle || false,
120
+ stage: options.stage,
121
+ });
122
+ }
123
+ } catch (_error) {
124
+ process.exit(1);
125
+ }
126
+ },
127
+ );
128
+
129
+ program
130
+ .command('dev')
131
+ .description('Start development server with automatic reload')
132
+ .option('-p, --port <port>', 'Port to run the development server on')
133
+ .option(
134
+ '--enable-openapi',
135
+ 'Enable OpenAPI documentation for development server',
136
+ true,
137
+ )
138
+ .action(async (options: { port?: string; enableOpenapi?: boolean }) => {
139
+ try {
140
+ const globalOptions = program.opts();
141
+ if (globalOptions.cwd) {
142
+ process.chdir(globalOptions.cwd);
143
+ }
144
+
145
+ await devCommand({
146
+ port: options.port ? Number.parseInt(options.port, 10) : 3000,
147
+ portExplicit: !!options.port,
148
+ enableOpenApi: options.enableOpenapi ?? true,
149
+ });
150
+ } catch (_error) {
151
+ process.exit(1);
152
+ }
153
+ });
154
+
155
+ program
156
+ .command('cron')
157
+ .description('Manage cron jobs')
158
+ .action(() => {
159
+ const globalOptions = program.opts();
160
+ if (globalOptions.cwd) {
161
+ process.chdir(globalOptions.cwd);
162
+ }
163
+ process.stdout.write('Cron management - coming soon\n');
164
+ });
165
+
166
+ program
167
+ .command('function')
168
+ .description('Manage serverless functions')
169
+ .action(() => {
170
+ const globalOptions = program.opts();
171
+ if (globalOptions.cwd) {
172
+ process.chdir(globalOptions.cwd);
173
+ }
174
+ process.stdout.write('Serverless function management - coming soon\n');
175
+ });
176
+
177
+ program
178
+ .command('api')
179
+ .description('Manage REST API endpoints')
180
+ .action(() => {
181
+ const globalOptions = program.opts();
182
+ if (globalOptions.cwd) {
183
+ process.chdir(globalOptions.cwd);
184
+ }
185
+ process.stdout.write('REST API management - coming soon\n');
186
+ });
187
+
188
+ program
189
+ .command('openapi')
190
+ .description('Generate OpenAPI specification from endpoints')
191
+ .action(async () => {
192
+ try {
193
+ const globalOptions = program.opts();
194
+ if (globalOptions.cwd) {
195
+ process.chdir(globalOptions.cwd);
196
+ }
197
+ await openapiCommand({});
198
+ } catch (_error) {
199
+ process.exit(1);
200
+ }
201
+ });
202
+
203
+ program
204
+ .command('generate:react-query')
205
+ .description('Generate React Query hooks from OpenAPI specification')
206
+ .option('--input <path>', 'Input OpenAPI spec file path', 'openapi.json')
207
+ .option(
208
+ '--output <path>',
209
+ 'Output file path for generated hooks',
210
+ 'src/api/hooks.ts',
211
+ )
212
+ .option('--name <name>', 'API name prefix for generated code', 'API')
213
+ .action(
214
+ async (options: { input?: string; output?: string; name?: string }) => {
215
+ try {
216
+ const globalOptions = program.opts();
217
+ if (globalOptions.cwd) {
218
+ process.chdir(globalOptions.cwd);
219
+ }
220
+ await generateReactQueryCommand(options);
221
+ } catch (_error) {
222
+ process.exit(1);
223
+ }
224
+ },
225
+ );
226
+
227
+ program
228
+ .command('docker')
229
+ .description('Generate Docker deployment files')
230
+ .option('--build', 'Build Docker image after generating files')
231
+ .option('--push', 'Push image to registry after building')
232
+ .option('--tag <tag>', 'Image tag', 'latest')
233
+ .option('--registry <registry>', 'Container registry URL')
234
+ .option('--slim', 'Use slim Dockerfile (assumes pre-built bundle exists)')
235
+ .option('--turbo', 'Use turbo prune for monorepo optimization')
236
+ .option('--turbo-package <name>', 'Package name for turbo prune')
237
+ .action(async (options: DockerOptions) => {
238
+ try {
239
+ const globalOptions = program.opts();
240
+ if (globalOptions.cwd) {
241
+ process.chdir(globalOptions.cwd);
242
+ }
243
+ await dockerCommand(options);
244
+ } catch (_error) {
245
+ process.exit(1);
246
+ }
247
+ });
248
+
249
+ program
250
+ .command('prepack')
251
+ .description('Generate Docker files for production deployment')
252
+ .option('--build', 'Build Docker image after generating files')
253
+ .option('--push', 'Push image to registry after building')
254
+ .option('--tag <tag>', 'Image tag', 'latest')
255
+ .option('--registry <registry>', 'Container registry URL')
256
+ .option('--slim', 'Build locally first, then use slim Dockerfile')
257
+ .option('--skip-bundle', 'Skip bundling step (only with --slim)')
258
+ .option('--turbo', 'Use turbo prune for monorepo optimization')
259
+ .option('--turbo-package <name>', 'Package name for turbo prune')
260
+ .action(
261
+ async (options: {
262
+ build?: boolean;
263
+ push?: boolean;
264
+ tag?: string;
265
+ registry?: string;
266
+ slim?: boolean;
267
+ skipBundle?: boolean;
268
+ turbo?: boolean;
269
+ turboPackage?: string;
270
+ }) => {
271
+ try {
272
+ const globalOptions = program.opts();
273
+ if (globalOptions.cwd) {
274
+ process.chdir(globalOptions.cwd);
275
+ }
276
+
277
+ if (options.slim) {
278
+ await buildCommand({
279
+ provider: 'server',
280
+ production: true,
281
+ skipBundle: options.skipBundle,
282
+ });
283
+ }
284
+ await dockerCommand({
285
+ build: options.build,
286
+ push: options.push,
287
+ tag: options.tag,
288
+ registry: options.registry,
289
+ slim: options.slim,
290
+ turbo: options.turbo,
291
+ turboPackage: options.turboPackage,
292
+ });
293
+ if (options.slim) {
294
+ } else {
295
+ }
296
+
297
+ if (options.build) {
298
+ const tag = options.tag ?? 'latest';
299
+ const registry = options.registry;
300
+ const _imageRef = registry ? `${registry}/api:${tag}` : `api:${tag}`;
301
+ }
302
+ } catch (_error) {
303
+ process.exit(1);
304
+ }
305
+ },
306
+ );
307
+
308
+ // Secrets management commands
309
+ program
310
+ .command('secrets:init')
311
+ .description('Initialize secrets for a deployment stage')
312
+ .requiredOption('--stage <stage>', 'Stage name (e.g., production, staging)')
313
+ .option('--force', 'Overwrite existing secrets')
314
+ .action(async (options: { stage: string; force?: boolean }) => {
315
+ try {
316
+ const globalOptions = program.opts();
317
+ if (globalOptions.cwd) {
318
+ process.chdir(globalOptions.cwd);
319
+ }
320
+ await secretsInitCommand(options);
321
+ } catch (_error) {
322
+ process.exit(1);
323
+ }
324
+ });
325
+
326
+ program
327
+ .command('secrets:set')
328
+ .description('Set a custom secret for a stage')
329
+ .argument('<key>', 'Secret key (e.g., API_KEY)')
330
+ .argument('[value]', 'Secret value (reads from stdin if omitted)')
331
+ .requiredOption('--stage <stage>', 'Stage name')
332
+ .action(
333
+ async (
334
+ key: string,
335
+ value: string | undefined,
336
+ options: { stage: string },
337
+ ) => {
338
+ try {
339
+ const globalOptions = program.opts();
340
+ if (globalOptions.cwd) {
341
+ process.chdir(globalOptions.cwd);
342
+ }
343
+ await secretsSetCommand(key, value, options);
344
+ } catch (_error) {
345
+ process.exit(1);
346
+ }
347
+ },
348
+ );
349
+
350
+ program
351
+ .command('secrets:show')
352
+ .description('Show secrets for a stage')
353
+ .requiredOption('--stage <stage>', 'Stage name')
354
+ .option('--reveal', 'Show actual secret values (not masked)')
355
+ .action(async (options: { stage: string; reveal?: boolean }) => {
356
+ try {
357
+ const globalOptions = program.opts();
358
+ if (globalOptions.cwd) {
359
+ process.chdir(globalOptions.cwd);
360
+ }
361
+ await secretsShowCommand(options);
362
+ } catch (_error) {
363
+ process.exit(1);
364
+ }
365
+ });
366
+
367
+ program
368
+ .command('secrets:rotate')
369
+ .description('Rotate service passwords')
370
+ .requiredOption('--stage <stage>', 'Stage name')
371
+ .option(
372
+ '--service <service>',
373
+ 'Specific service to rotate (postgres, redis, rabbitmq)',
374
+ )
375
+ .action(async (options: { stage: string; service?: ComposeServiceName }) => {
376
+ try {
377
+ const globalOptions = program.opts();
378
+ if (globalOptions.cwd) {
379
+ process.chdir(globalOptions.cwd);
380
+ }
381
+ await secretsRotateCommand(options);
382
+ } catch (_error) {
383
+ process.exit(1);
384
+ }
385
+ });
386
+
387
+ program
388
+ .command('secrets:import')
389
+ .description('Import secrets from a JSON file')
390
+ .argument('<file>', 'JSON file path (e.g., secrets.json)')
391
+ .requiredOption('--stage <stage>', 'Stage name')
392
+ .option('--no-merge', 'Replace all custom secrets instead of merging')
393
+ .action(async (file: string, options: { stage: string; merge?: boolean }) => {
394
+ try {
395
+ const globalOptions = program.opts();
396
+ if (globalOptions.cwd) {
397
+ process.chdir(globalOptions.cwd);
398
+ }
399
+ await secretsImportCommand(file, options);
400
+ } catch (_error) {
401
+ process.exit(1);
402
+ }
403
+ });
404
+
405
+ // Deploy command
406
+ program
407
+ .command('deploy')
408
+ .description('Deploy application to a provider')
409
+ .requiredOption(
410
+ '--provider <provider>',
411
+ 'Deploy provider (docker, dokploy, aws-lambda)',
412
+ )
413
+ .requiredOption(
414
+ '--stage <stage>',
415
+ 'Deployment stage (e.g., production, staging)',
416
+ )
417
+ .option('--tag <tag>', 'Image tag (default: stage-timestamp)')
418
+ .option('--skip-push', 'Skip pushing image to registry')
419
+ .option('--skip-build', 'Skip build step (use existing build)')
420
+ .action(
421
+ async (options: {
422
+ provider: string;
423
+ stage: string;
424
+ tag?: string;
425
+ skipPush?: boolean;
426
+ skipBuild?: boolean;
427
+ }) => {
428
+ try {
429
+ const globalOptions = program.opts();
430
+ if (globalOptions.cwd) {
431
+ process.chdir(globalOptions.cwd);
432
+ }
433
+
434
+ const validProviders = ['docker', 'dokploy', 'aws-lambda'];
435
+ if (!validProviders.includes(options.provider)) {
436
+ console.error(
437
+ `Invalid provider: ${options.provider}\n` +
438
+ `Valid providers: ${validProviders.join(', ')}`,
439
+ );
440
+ process.exit(1);
441
+ }
442
+
443
+ await deployCommand({
444
+ provider: options.provider as DeployProvider,
445
+ stage: options.stage,
446
+ tag: options.tag,
447
+ skipPush: options.skipPush,
448
+ skipBuild: options.skipBuild,
449
+ });
450
+ } catch (_error) {
451
+ process.exit(1);
452
+ }
453
+ },
454
+ );
455
+
456
+ // Deploy init command - Initialize Dokploy project and application
457
+ program
458
+ .command('deploy:init')
459
+ .description('Initialize Dokploy deployment (create project and application)')
460
+ .option(
461
+ '--endpoint <url>',
462
+ 'Dokploy server URL (uses stored credentials if logged in)',
463
+ )
464
+ .requiredOption('--project <name>', 'Project name (creates if not exists)')
465
+ .requiredOption('--app <name>', 'Application name')
466
+ .option('--project-id <id>', 'Use existing project ID instead of creating')
467
+ .option('--registry-id <id>', 'Configure registry for the application')
468
+ .action(
469
+ async (options: {
470
+ endpoint?: string;
471
+ project: string;
472
+ app: string;
473
+ projectId?: string;
474
+ registryId?: string;
475
+ }) => {
476
+ try {
477
+ const globalOptions = program.opts();
478
+ if (globalOptions.cwd) {
479
+ process.chdir(globalOptions.cwd);
480
+ }
481
+
482
+ await deployInitCommand({
483
+ endpoint: options.endpoint,
484
+ projectName: options.project,
485
+ appName: options.app,
486
+ projectId: options.projectId,
487
+ registryId: options.registryId,
488
+ });
489
+ } catch (error) {
490
+ console.error(
491
+ error instanceof Error
492
+ ? error.message
493
+ : 'Failed to initialize deployment',
494
+ );
495
+ process.exit(1);
496
+ }
497
+ },
498
+ );
499
+
500
+ // Deploy list command - List Dokploy resources
501
+ program
502
+ .command('deploy:list')
503
+ .description('List Dokploy resources (projects, registries)')
504
+ .option(
505
+ '--endpoint <url>',
506
+ 'Dokploy server URL (uses stored credentials if logged in)',
507
+ )
508
+ .option('--projects', 'List projects')
509
+ .option('--registries', 'List registries')
510
+ .action(
511
+ async (options: {
512
+ endpoint?: string;
513
+ projects?: boolean;
514
+ registries?: boolean;
515
+ }) => {
516
+ try {
517
+ const globalOptions = program.opts();
518
+ if (globalOptions.cwd) {
519
+ process.chdir(globalOptions.cwd);
520
+ }
521
+
522
+ if (options.projects) {
523
+ await deployListCommand({
524
+ endpoint: options.endpoint,
525
+ resource: 'projects',
526
+ });
527
+ }
528
+ if (options.registries) {
529
+ await deployListCommand({
530
+ endpoint: options.endpoint,
531
+ resource: 'registries',
532
+ });
533
+ }
534
+ if (!options.projects && !options.registries) {
535
+ // Default to listing both
536
+ await deployListCommand({
537
+ endpoint: options.endpoint,
538
+ resource: 'projects',
539
+ });
540
+ await deployListCommand({
541
+ endpoint: options.endpoint,
542
+ resource: 'registries',
543
+ });
544
+ }
545
+ } catch (error) {
546
+ console.error(
547
+ error instanceof Error ? error.message : 'Failed to list resources',
548
+ );
549
+ process.exit(1);
550
+ }
551
+ },
552
+ );
553
+
554
+ // Login command
555
+ program
556
+ .command('login')
557
+ .description('Authenticate with a deployment service')
558
+ .option('--service <service>', 'Service to login to (dokploy)', 'dokploy')
559
+ .option('--token <token>', 'API token (will prompt if not provided)')
560
+ .option('--endpoint <url>', 'Service endpoint URL')
561
+ .action(
562
+ async (options: { service: string; token?: string; endpoint?: string }) => {
563
+ try {
564
+ const globalOptions = program.opts();
565
+ if (globalOptions.cwd) {
566
+ process.chdir(globalOptions.cwd);
567
+ }
568
+
569
+ if (options.service !== 'dokploy') {
570
+ console.error(
571
+ `Unknown service: ${options.service}. Supported: dokploy`,
572
+ );
573
+ process.exit(1);
574
+ }
575
+
576
+ await loginCommand({
577
+ service: options.service as 'dokploy',
578
+ token: options.token,
579
+ endpoint: options.endpoint,
580
+ });
581
+ } catch (error) {
582
+ console.error(
583
+ error instanceof Error ? error.message : 'Failed to login',
584
+ );
585
+ process.exit(1);
586
+ }
587
+ },
588
+ );
589
+
590
+ // Logout command
591
+ program
592
+ .command('logout')
593
+ .description('Remove stored credentials')
594
+ .option(
595
+ '--service <service>',
596
+ 'Service to logout from (dokploy, all)',
597
+ 'dokploy',
598
+ )
599
+ .action(async (options: { service: string }) => {
600
+ try {
601
+ const globalOptions = program.opts();
602
+ if (globalOptions.cwd) {
603
+ process.chdir(globalOptions.cwd);
604
+ }
605
+
606
+ await logoutCommand({
607
+ service: options.service as 'dokploy' | 'all',
608
+ });
609
+ } catch (error) {
610
+ console.error(
611
+ error instanceof Error ? error.message : 'Failed to logout',
612
+ );
613
+ process.exit(1);
614
+ }
615
+ });
616
+
617
+ // Whoami command
618
+ program
619
+ .command('whoami')
620
+ .description('Show current authentication status')
621
+ .action(async () => {
622
+ try {
623
+ const globalOptions = program.opts();
624
+ if (globalOptions.cwd) {
625
+ process.chdir(globalOptions.cwd);
626
+ }
627
+
628
+ await whoamiCommand();
629
+ } catch (error) {
630
+ console.error(
631
+ error instanceof Error ? error.message : 'Failed to get status',
632
+ );
633
+ process.exit(1);
634
+ }
635
+ });
214
636
 
215
637
  program.parse();