@geekmidas/cli 1.10.12 → 1.10.13
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 +6 -0
- package/dist/index.cjs +47 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +47 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/init/__tests__/generators.spec.ts +44 -2
- package/src/init/generators/docker.ts +24 -0
- 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/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.13",
|
|
4
4
|
"description": "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -57,8 +57,8 @@
|
|
|
57
57
|
"tsx": "~4.20.3",
|
|
58
58
|
"yaml": "~2.8.2",
|
|
59
59
|
"@geekmidas/constructs": "~3.0.2",
|
|
60
|
-
"@geekmidas/envkit": "~1.0.3",
|
|
61
60
|
"@geekmidas/logger": "~1.0.0",
|
|
61
|
+
"@geekmidas/envkit": "~1.0.3",
|
|
62
62
|
"@geekmidas/errors": "~1.0.0",
|
|
63
63
|
"@geekmidas/schema": "~1.0.0"
|
|
64
64
|
},
|
|
@@ -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,7 +245,7 @@ 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');
|
|
@@ -255,6 +255,48 @@ describe('generateDockerFiles', () => {
|
|
|
255
255
|
expect(files[0].content).toContain("'${MAILPIT_UI_HOST_PORT:-8025}:8025'");
|
|
256
256
|
expect(files[0].content).toContain('MP_SMTP_AUTH:');
|
|
257
257
|
});
|
|
258
|
+
|
|
259
|
+
it('should include minio with dynamic ports when storage is enabled', () => {
|
|
260
|
+
const options = {
|
|
261
|
+
...baseOptions,
|
|
262
|
+
services: { ...baseOptions.services, storage: true },
|
|
263
|
+
};
|
|
264
|
+
const files = generateDockerFiles(options, minimalTemplate);
|
|
265
|
+
expect(files[0].content).toContain('minio');
|
|
266
|
+
expect(files[0].content).toContain('minio/minio:latest');
|
|
267
|
+
expect(files[0].content).toContain("'${MINIO_API_HOST_PORT:-9000}:9000'");
|
|
268
|
+
expect(files[0].content).toContain(
|
|
269
|
+
"'${MINIO_CONSOLE_HOST_PORT:-9001}:9001'",
|
|
270
|
+
);
|
|
271
|
+
expect(files[0].content).toContain('MINIO_ROOT_USER:');
|
|
272
|
+
expect(files[0].content).toContain('MINIO_ROOT_PASSWORD:');
|
|
273
|
+
expect(files[0].content).toContain('minio_data:');
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('should include all services when all are enabled', () => {
|
|
277
|
+
const options = {
|
|
278
|
+
...baseOptions,
|
|
279
|
+
services: { db: true, cache: true, mail: true, storage: true },
|
|
280
|
+
};
|
|
281
|
+
const files = generateDockerFiles(options, minimalTemplate);
|
|
282
|
+
expect(files[0].content).toContain('postgres');
|
|
283
|
+
expect(files[0].content).toContain('redis');
|
|
284
|
+
expect(files[0].content).toContain('mailpit');
|
|
285
|
+
expect(files[0].content).toContain('minio');
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it('should not include disabled services', () => {
|
|
289
|
+
const options = {
|
|
290
|
+
...baseOptions,
|
|
291
|
+
database: false,
|
|
292
|
+
services: { db: false, cache: false, mail: false, storage: false },
|
|
293
|
+
};
|
|
294
|
+
const files = generateDockerFiles(options, minimalTemplate);
|
|
295
|
+
expect(files[0].content).not.toContain('postgres');
|
|
296
|
+
expect(files[0].content).toContain('redis'); // redis is always included
|
|
297
|
+
expect(files[0].content).not.toContain('mailpit');
|
|
298
|
+
expect(files[0].content).not.toContain('minio');
|
|
299
|
+
});
|
|
258
300
|
});
|
|
259
301
|
|
|
260
302
|
describe('generateMonorepoFiles', () => {
|
|
@@ -159,6 +159,30 @@ export function generateDockerFiles(
|
|
|
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
|
}
|