@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/CHANGELOG.md +12 -0
- package/dist/index.cjs +53 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +53 -9
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/dev/__tests__/index.spec.ts +4 -4
- package/src/docker/__tests__/compose.spec.ts +2 -2
- package/src/docker/compose.ts +4 -4
- package/src/init/__tests__/generators.spec.ts +45 -5
- package/src/init/generators/docker.ts +26 -2
- package/src/init/index.ts +2 -1
- package/src/init/templates/index.ts +6 -0
- package/src/setup/__tests__/reconcile-secrets.spec.ts +35 -0
- package/src/setup/index.ts +2 -0
- package/src/test/__tests__/__fixtures__/workspace.ts +2 -2
- package/src/test/index.ts +14 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geekmidas/cli",
|
|
3
|
-
"version": "1.10.
|
|
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
|
-
- '\${
|
|
1471
|
-
- '\${
|
|
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: '
|
|
1491
|
+
envVar: 'SMTP_PORT',
|
|
1492
1492
|
defaultPort: 1025,
|
|
1493
1493
|
containerPort: 1025,
|
|
1494
1494
|
},
|
|
1495
1495
|
{
|
|
1496
1496
|
service: 'mailpit',
|
|
1497
|
-
envVar: '
|
|
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('${
|
|
950
|
-
expect(yaml).toContain('${
|
|
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', () => {
|
package/src/docker/compose.ts
CHANGED
|
@@ -269,8 +269,8 @@ services:
|
|
|
269
269
|
environment:
|
|
270
270
|
MP_SMTP_AUTH: \${SMTP_USER:-${imageName}}:\${SMTP_PASS:-${imageName}}
|
|
271
271
|
ports:
|
|
272
|
-
- "\${
|
|
273
|
-
- "\${
|
|
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
|
-
- "\${
|
|
461
|
-
- "\${
|
|
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: {
|
|
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
|
-
"'${
|
|
267
|
+
"'${MINIO_CONSOLE_HOST_PORT:-9001}:9001'",
|
|
254
268
|
);
|
|
255
|
-
expect(files[0].content).toContain(
|
|
256
|
-
expect(files[0].content).toContain('
|
|
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
|
-
- '\${
|
|
157
|
-
- '\${
|
|
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 },
|
package/src/setup/index.ts
CHANGED
|
@@ -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, {
|
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
|
}
|