@geekmidas/cli 1.10.12 → 1.10.14

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geekmidas/cli",
3
- "version": "1.10.12",
3
+ "version": "1.10.14",
4
4
  "description": "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs",
5
5
  "private": false,
6
6
  "type": "module",
@@ -58,9 +58,9 @@
58
58
  "yaml": "~2.8.2",
59
59
  "@geekmidas/constructs": "~3.0.2",
60
60
  "@geekmidas/envkit": "~1.0.3",
61
- "@geekmidas/logger": "~1.0.0",
62
61
  "@geekmidas/errors": "~1.0.0",
63
- "@geekmidas/schema": "~1.0.0"
62
+ "@geekmidas/schema": "~1.0.0",
63
+ "@geekmidas/logger": "~1.0.0"
64
64
  },
65
65
  "devDependencies": {
66
66
  "@types/lodash.kebabcase": "^4.1.9",
@@ -1467,8 +1467,8 @@ services:
1467
1467
  mailpit:
1468
1468
  image: axllent/mailpit
1469
1469
  ports:
1470
- - '\${MAILPIT_SMTP_PORT:-1025}:1025'
1471
- - '\${MAILPIT_UI_PORT:-8025}:8025'
1470
+ - '\${SMTP_PORT:-1025}:1025'
1471
+ - '\${MAILPIT_PORT:-8025}:8025'
1472
1472
  `,
1473
1473
  );
1474
1474
 
@@ -1488,13 +1488,13 @@ services:
1488
1488
  },
1489
1489
  {
1490
1490
  service: 'mailpit',
1491
- envVar: 'MAILPIT_SMTP_PORT',
1491
+ envVar: 'SMTP_PORT',
1492
1492
  defaultPort: 1025,
1493
1493
  containerPort: 1025,
1494
1494
  },
1495
1495
  {
1496
1496
  service: 'mailpit',
1497
- envVar: 'MAILPIT_UI_PORT',
1497
+ envVar: 'MAILPIT_PORT',
1498
1498
  defaultPort: 8025,
1499
1499
  containerPort: 8025,
1500
1500
  },
@@ -946,8 +946,8 @@ describe('generateWorkspaceCompose', () => {
946
946
  expect(yaml).toContain('mailpit:');
947
947
  expect(yaml).toContain('image: axllent/mailpit:latest');
948
948
  expect(yaml).toContain('MP_SMTP_AUTH:');
949
- expect(yaml).toContain('${MAILPIT_UI_PORT:-8025}:8025'); // Web UI
950
- expect(yaml).toContain('${MAILPIT_SMTP_PORT:-1025}:1025'); // SMTP
949
+ expect(yaml).toContain('${MAILPIT_PORT:-8025}:8025'); // Web UI / API
950
+ expect(yaml).toContain('${SMTP_PORT:-1025}:1025'); // SMTP
951
951
  });
952
952
 
953
953
  it('should add SMTP env vars for backend apps when mail is enabled', () => {
@@ -269,8 +269,8 @@ services:
269
269
  environment:
270
270
  MP_SMTP_AUTH: \${SMTP_USER:-${imageName}}:\${SMTP_PASS:-${imageName}}
271
271
  ports:
272
- - "\${MAILPIT_UI_PORT:-8025}:8025" # Web UI
273
- - "\${MAILPIT_SMTP_PORT:-1025}:1025" # SMTP
272
+ - "\${MAILPIT_PORT:-8025}:8025" # Web UI / API
273
+ - "\${SMTP_PORT:-1025}:1025" # SMTP
274
274
  healthcheck:
275
275
  test: ["CMD", "wget", "-q", "--spider", "http://localhost:8025"]
276
276
  interval: 5s
@@ -457,8 +457,8 @@ services:
457
457
  environment:
458
458
  MP_SMTP_AUTH: \${SMTP_USER:-${workspace.name}}:\${SMTP_PASS:-${workspace.name}}
459
459
  ports:
460
- - "\${MAILPIT_UI_PORT:-8025}:8025" # Web UI
461
- - "\${MAILPIT_SMTP_PORT:-1025}:1025" # SMTP
460
+ - "\${MAILPIT_PORT:-8025}:8025" # Web UI / API
461
+ - "\${SMTP_PORT:-1025}:1025" # SMTP
462
462
  healthcheck:
463
463
  test: ["CMD", "wget", "-q", "--spider", "http://localhost:8025"]
464
464
  interval: 5s
@@ -25,7 +25,7 @@ const baseOptions: TemplateOptions = {
25
25
  apiPath: '',
26
26
  packageManager: 'pnpm',
27
27
  deployTarget: 'dokploy',
28
- services: { db: true, cache: true, mail: false },
28
+ services: { db: true, cache: true, mail: false, storage: false },
29
29
  };
30
30
 
31
31
  describe('generatePackageJson', () => {
@@ -245,15 +245,55 @@ describe('generateDockerFiles', () => {
245
245
  it('should include mailpit with dynamic ports when mail is enabled', () => {
246
246
  const options = {
247
247
  ...baseOptions,
248
- services: { db: true, cache: true, mail: true },
248
+ services: { ...baseOptions.services, mail: true },
249
249
  };
250
250
  const files = generateDockerFiles(options, minimalTemplate);
251
251
  expect(files[0].content).toContain('mailpit');
252
+ expect(files[0].content).toContain("'${SMTP_PORT:-1025}:1025'");
253
+ expect(files[0].content).toContain("'${MAILPIT_PORT:-8025}:8025'");
254
+ expect(files[0].content).toContain('MP_SMTP_AUTH:');
255
+ });
256
+
257
+ it('should include minio with dynamic ports when storage is enabled', () => {
258
+ const options = {
259
+ ...baseOptions,
260
+ services: { ...baseOptions.services, storage: true },
261
+ };
262
+ const files = generateDockerFiles(options, minimalTemplate);
263
+ expect(files[0].content).toContain('minio');
264
+ expect(files[0].content).toContain('minio/minio:latest');
265
+ expect(files[0].content).toContain("'${MINIO_API_HOST_PORT:-9000}:9000'");
252
266
  expect(files[0].content).toContain(
253
- "'${MAILPIT_SMTP_HOST_PORT:-1025}:1025'",
267
+ "'${MINIO_CONSOLE_HOST_PORT:-9001}:9001'",
254
268
  );
255
- expect(files[0].content).toContain("'${MAILPIT_UI_HOST_PORT:-8025}:8025'");
256
- expect(files[0].content).toContain('MP_SMTP_AUTH:');
269
+ expect(files[0].content).toContain('MINIO_ROOT_USER:');
270
+ expect(files[0].content).toContain('MINIO_ROOT_PASSWORD:');
271
+ expect(files[0].content).toContain('minio_data:');
272
+ });
273
+
274
+ it('should include all services when all are enabled', () => {
275
+ const options = {
276
+ ...baseOptions,
277
+ services: { db: true, cache: true, mail: true, storage: true },
278
+ };
279
+ const files = generateDockerFiles(options, minimalTemplate);
280
+ expect(files[0].content).toContain('postgres');
281
+ expect(files[0].content).toContain('redis');
282
+ expect(files[0].content).toContain('mailpit');
283
+ expect(files[0].content).toContain('minio');
284
+ });
285
+
286
+ it('should not include disabled services', () => {
287
+ const options = {
288
+ ...baseOptions,
289
+ database: false,
290
+ services: { db: false, cache: false, mail: false, storage: false },
291
+ };
292
+ const files = generateDockerFiles(options, minimalTemplate);
293
+ expect(files[0].content).not.toContain('postgres');
294
+ expect(files[0].content).toContain('redis'); // redis is always included
295
+ expect(files[0].content).not.toContain('mailpit');
296
+ expect(files[0].content).not.toContain('minio');
257
297
  });
258
298
  });
259
299
 
@@ -153,12 +153,36 @@ export function generateDockerFiles(
153
153
  container_name: ${options.name}-mailpit
154
154
  restart: unless-stopped
155
155
  ports:
156
- - '\${MAILPIT_SMTP_HOST_PORT:-1025}:1025'
157
- - '\${MAILPIT_UI_HOST_PORT:-8025}:8025'
156
+ - '\${SMTP_PORT:-1025}:1025'
157
+ - '\${MAILPIT_PORT:-8025}:8025'
158
158
  environment:
159
159
  MP_SMTP_AUTH: \${SMTP_USER:-${options.name}}:\${SMTP_PASS:-${options.name}}`);
160
160
  }
161
161
 
162
+ // MinIO for S3-compatible object storage
163
+ if (options.services?.storage) {
164
+ services.push(` minio:
165
+ image: minio/minio:latest
166
+ container_name: ${options.name}-minio
167
+ restart: unless-stopped
168
+ entrypoint: sh
169
+ command: -c 'mkdir -p /data/\${STORAGE_BUCKET:-${options.name}} && /usr/bin/docker-entrypoint.sh server --console-address ":9001" /data'
170
+ environment:
171
+ MINIO_ROOT_USER: \${STORAGE_ACCESS_KEY_ID:-${options.name}}
172
+ MINIO_ROOT_PASSWORD: \${STORAGE_SECRET_ACCESS_KEY:-${options.name}}
173
+ ports:
174
+ - '\${MINIO_API_HOST_PORT:-9000}:9000'
175
+ - '\${MINIO_CONSOLE_HOST_PORT:-9001}:9001'
176
+ volumes:
177
+ - minio_data:/data
178
+ healthcheck:
179
+ test: ['CMD', 'mc', 'ready', 'local']
180
+ interval: 10s
181
+ timeout: 5s
182
+ retries: 5`);
183
+ volumes.push(' minio_data:');
184
+ }
185
+
162
186
  // Build docker-compose.yml
163
187
  let dockerCompose = `# Use "gkm dev" or "gkm test" to start services.
164
188
  # Running "docker compose up" directly will not inject secrets or resolve ports.
package/src/init/index.ts CHANGED
@@ -181,12 +181,13 @@ export async function initCommand(
181
181
 
182
182
  // Parse services selection
183
183
  const servicesArray: string[] = options.yes
184
- ? ['db', 'cache', 'mail']
184
+ ? ['db', 'cache', 'mail', 'storage']
185
185
  : answers.services || [];
186
186
  const services: ServicesSelection = {
187
187
  db: servicesArray.includes('db'),
188
188
  cache: servicesArray.includes('cache'),
189
189
  mail: servicesArray.includes('mail'),
190
+ storage: servicesArray.includes('storage'),
190
191
  };
191
192
 
192
193
  const pkgManager: PackageManager = options.pm
@@ -38,6 +38,7 @@ export interface ServicesSelection {
38
38
  db: boolean;
39
39
  cache: boolean;
40
40
  mail: boolean;
41
+ storage: boolean;
41
42
  }
42
43
 
43
44
  /**
@@ -247,6 +248,11 @@ export const servicesChoices = [
247
248
  value: 'mail',
248
249
  description: 'Email testing service (dev only)',
249
250
  },
251
+ {
252
+ title: 'MinIO',
253
+ value: 'storage',
254
+ description: 'S3-compatible object storage (dev only)',
255
+ },
250
256
  ];
251
257
 
252
258
  /**
@@ -158,6 +158,41 @@ describe('reconcileSecrets', () => {
158
158
  expect(result!.services.postgres).toEqual(secrets.services.postgres);
159
159
  });
160
160
 
161
+ it('should add missing mailpit credentials when mail service is enabled', () => {
162
+ const workspace = createWorkspace({
163
+ services: { db: true, mail: true },
164
+ });
165
+ // Secrets only have postgres, not mailpit
166
+ const secrets = createSecrets({
167
+ NODE_ENV: 'development',
168
+ PORT: '3000',
169
+ API_DATABASE_URL: 'postgresql://api:pass@localhost:5432/test_dev',
170
+ API_DB_PASSWORD: 'pass',
171
+ AUTH_DATABASE_URL: 'postgresql://auth:pass@localhost:5432/test_dev',
172
+ AUTH_DB_PASSWORD: 'pass',
173
+ WEB_URL: 'http://localhost:3002',
174
+ BETTER_AUTH_SECRET: 'existing',
175
+ BETTER_AUTH_URL: 'http://localhost:3001',
176
+ BETTER_AUTH_TRUSTED_ORIGINS:
177
+ 'http://localhost:3000,http://localhost:3001,http://localhost:3002',
178
+ AUTH_PORT: '3001',
179
+ AUTH_URL: 'http://localhost:3001',
180
+ });
181
+
182
+ const result = reconcileSecrets(secrets, workspace);
183
+
184
+ expect(result).not.toBeNull();
185
+ expect(result!.services.mailpit).toBeDefined();
186
+ expect(result!.services.mailpit!.host).toBe('localhost');
187
+ expect(result!.services.mailpit!.port).toBe(1025);
188
+ expect(result!.services.mailpit!.username).toBeDefined();
189
+ expect(result!.services.mailpit!.password).toHaveLength(32);
190
+ expect(result!.urls.SMTP_HOST).toBe('localhost');
191
+ expect(result!.urls.SMTP_PORT).toBe('1025');
192
+ // Existing postgres should be preserved
193
+ expect(result!.services.postgres).toEqual(secrets.services.postgres);
194
+ });
195
+
161
196
  it('should not regenerate credentials for existing services', () => {
162
197
  const workspace = createWorkspace({
163
198
  services: { db: true, storage: true },
@@ -168,6 +168,7 @@ export function reconcileSecrets(
168
168
  { key: 'db', name: 'postgres' },
169
169
  { key: 'cache', name: 'redis' },
170
170
  { key: 'storage', name: 'minio' },
171
+ { key: 'mail', name: 'mailpit' },
171
172
  ];
172
173
 
173
174
  for (const { key, name } of serviceMap) {
@@ -235,6 +236,7 @@ async function generateFreshSecrets(
235
236
  if (workspace.services.db) serviceNames.push('postgres');
236
237
  if (workspace.services.cache) serviceNames.push('redis');
237
238
  if (workspace.services.storage) serviceNames.push('minio');
239
+ if (workspace.services.mail) serviceNames.push('mailpit');
238
240
 
239
241
  // Create base secrets with service credentials
240
242
  const secrets = createStageSecrets(stage, serviceNames, {
@@ -99,6 +99,6 @@ services:
99
99
  mailpit:
100
100
  image: axllent/mailpit
101
101
  ports:
102
- - '\${MAILPIT_SMTP_PORT:-1025}:1025'
103
- - '\${MAILPIT_UI_PORT:-8025}:8025'
102
+ - '\${SMTP_PORT:-1025}:1025'
103
+ - '\${MAILPIT_PORT:-8025}:8025'
104
104
  `;
package/src/test/index.ts CHANGED
@@ -6,6 +6,7 @@ import { sniffAppEnvironment } from '../deploy/sniffer';
6
6
  import {
7
7
  createCredentialsPreload,
8
8
  loadEnvFiles,
9
+ loadPortState,
9
10
  parseComposePortMappings,
10
11
  resolveServicePorts,
11
12
  rewriteUrlsWithPorts,
@@ -133,6 +134,19 @@ export async function testCommand(options: TestOptions = {}): Promise<void> {
133
134
  console.log(
134
135
  ` 🔌 Applied ${Object.keys(resolvedPorts.ports).length} port mapping(s)`,
135
136
  );
137
+ } else {
138
+ // Fallback to saved port state from a previous gkm dev run
139
+ const ports = await loadPortState(cwd);
140
+ if (Object.keys(ports).length > 0) {
141
+ secretsEnv = rewriteUrlsWithPorts(secretsEnv, {
142
+ dockerEnv: {},
143
+ ports,
144
+ mappings,
145
+ });
146
+ console.log(
147
+ ` 🔌 Applied ${Object.keys(ports).length} port mapping(s)`,
148
+ );
149
+ }
136
150
  }
137
151
  }
138
152
  }