@geekmidas/cli 1.10.5 → 1.10.6
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/config.d.cts +1 -1
- package/dist/config.d.mts +1 -1
- package/dist/{fullstack-secrets-DqxYGrgW.cjs → fullstack-secrets-BctGaE4E.cjs} +7 -2
- package/dist/fullstack-secrets-BctGaE4E.cjs.map +1 -0
- package/dist/{fullstack-secrets-odm79Uo1.mjs → fullstack-secrets-ca0Kyrvt.mjs} +7 -2
- package/dist/fullstack-secrets-ca0Kyrvt.mjs.map +1 -0
- package/dist/{index-3n-giNaw.d.mts → index-9tjTQjFt.d.mts} +3 -3
- package/dist/{index-3n-giNaw.d.mts.map → index-9tjTQjFt.d.mts.map} +1 -1
- package/dist/{index-CiEOtKEX.d.cts → index-VOKKO-lm.d.cts} +3 -3
- package/dist/{index-CiEOtKEX.d.cts.map → index-VOKKO-lm.d.cts.map} +1 -1
- package/dist/index.cjs +22 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +22 -19
- package/dist/index.mjs.map +1 -1
- package/dist/{reconcile-WzC1oAUV.mjs → reconcile-C5OyCA7V.mjs} +2 -2
- package/dist/{reconcile-WzC1oAUV.mjs.map → reconcile-C5OyCA7V.mjs.map} +1 -1
- package/dist/{reconcile-CCtrj-zt.cjs → reconcile-TEBsryVn.cjs} +2 -2
- package/dist/{reconcile-CCtrj-zt.cjs.map → reconcile-TEBsryVn.cjs.map} +1 -1
- package/dist/workspace/index.d.cts +1 -1
- package/dist/workspace/index.d.mts +1 -1
- package/dist/workspace-4SP3Gx4Y.cjs.map +1 -1
- package/dist/workspace-D4z4A4cq.mjs.map +1 -1
- package/package.json +4 -4
- package/src/docker/__tests__/compose.spec.ts +6 -6
- package/src/docker/compose.ts +6 -6
- package/src/init/__tests__/init.spec.ts +1 -1
- package/src/init/generators/docker.ts +3 -3
- package/src/init/index.ts +3 -1
- package/src/secrets/generator.ts +11 -0
- package/src/secrets/index.ts +12 -6
- package/src/setup/index.ts +3 -1
- package/src/workspace/types.ts +2 -2
- package/dist/fullstack-secrets-DqxYGrgW.cjs.map +0 -1
- package/dist/fullstack-secrets-odm79Uo1.mjs.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geekmidas/cli",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.6",
|
|
4
4
|
"description": "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -56,11 +56,11 @@
|
|
|
56
56
|
"prompts": "~2.4.2",
|
|
57
57
|
"tsx": "~4.20.3",
|
|
58
58
|
"yaml": "~2.8.2",
|
|
59
|
+
"@geekmidas/envkit": "~1.0.3",
|
|
59
60
|
"@geekmidas/constructs": "~3.0.2",
|
|
60
|
-
"@geekmidas/errors": "~1.0.0",
|
|
61
61
|
"@geekmidas/logger": "~1.0.0",
|
|
62
|
-
"@geekmidas/
|
|
63
|
-
"@geekmidas/
|
|
62
|
+
"@geekmidas/errors": "~1.0.0",
|
|
63
|
+
"@geekmidas/schema": "~1.0.0"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@types/lodash.kebabcase": "^4.1.9",
|
|
@@ -181,8 +181,8 @@ describe('generateDockerCompose', () => {
|
|
|
181
181
|
services: { postgres: true },
|
|
182
182
|
});
|
|
183
183
|
|
|
184
|
-
expect(yaml).toContain('-
|
|
185
|
-
expect(yaml).toContain('
|
|
184
|
+
expect(yaml).toContain('- dbdata:/var/lib/postgresql/18/data');
|
|
185
|
+
expect(yaml).toContain('dbdata:');
|
|
186
186
|
});
|
|
187
187
|
|
|
188
188
|
it('should include postgres healthcheck using POSTGRES_USER', () => {
|
|
@@ -373,7 +373,7 @@ describe('generateDockerCompose', () => {
|
|
|
373
373
|
services: { postgres: true, redis: true, rabbitmq: true },
|
|
374
374
|
});
|
|
375
375
|
|
|
376
|
-
expect(yaml).toContain('
|
|
376
|
+
expect(yaml).toContain('dbdata:');
|
|
377
377
|
expect(yaml).toContain('redis_data:');
|
|
378
378
|
expect(yaml).toContain('rabbitmq_data:');
|
|
379
379
|
});
|
|
@@ -792,7 +792,7 @@ describe('generateWorkspaceCompose', () => {
|
|
|
792
792
|
const yaml = generateWorkspaceCompose(workspace);
|
|
793
793
|
|
|
794
794
|
expect(yaml).toContain('postgres:');
|
|
795
|
-
expect(yaml).toContain('image: postgres:
|
|
795
|
+
expect(yaml).toContain('image: postgres:18-alpine');
|
|
796
796
|
expect(yaml).toContain('container_name: test-workspace-postgres');
|
|
797
797
|
});
|
|
798
798
|
|
|
@@ -845,8 +845,8 @@ describe('generateWorkspaceCompose', () => {
|
|
|
845
845
|
});
|
|
846
846
|
const yaml = generateWorkspaceCompose(workspace);
|
|
847
847
|
|
|
848
|
-
expect(yaml).toContain('
|
|
849
|
-
expect(yaml).toContain('
|
|
848
|
+
expect(yaml).toContain('dbdata:');
|
|
849
|
+
expect(yaml).toContain('dbdata:/var/lib/postgresql/18/data');
|
|
850
850
|
});
|
|
851
851
|
|
|
852
852
|
it('should add redis_data volume when redis is enabled', () => {
|
package/src/docker/compose.ts
CHANGED
|
@@ -17,7 +17,7 @@ export const DEFAULT_SERVICE_IMAGES: Record<ComposeServiceName, string> = {
|
|
|
17
17
|
|
|
18
18
|
/** Default Docker image versions for services */
|
|
19
19
|
export const DEFAULT_SERVICE_VERSIONS: Record<ComposeServiceName, string> = {
|
|
20
|
-
postgres: '
|
|
20
|
+
postgres: '18-alpine',
|
|
21
21
|
redis: '7-alpine',
|
|
22
22
|
rabbitmq: '3-management-alpine',
|
|
23
23
|
};
|
|
@@ -154,7 +154,7 @@ services:
|
|
|
154
154
|
POSTGRES_PASSWORD: \${POSTGRES_PASSWORD:-postgres}
|
|
155
155
|
POSTGRES_DB: \${POSTGRES_DB:-app}
|
|
156
156
|
volumes:
|
|
157
|
-
-
|
|
157
|
+
- dbdata:/var/lib/postgresql/18/data
|
|
158
158
|
healthcheck:
|
|
159
159
|
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER"]
|
|
160
160
|
interval: 5s
|
|
@@ -214,7 +214,7 @@ volumes:
|
|
|
214
214
|
`;
|
|
215
215
|
|
|
216
216
|
if (serviceMap.has('postgres')) {
|
|
217
|
-
yaml += `
|
|
217
|
+
yaml += ` dbdata:
|
|
218
218
|
`;
|
|
219
219
|
}
|
|
220
220
|
|
|
@@ -333,7 +333,7 @@ services:
|
|
|
333
333
|
POSTGRES_PASSWORD: \${POSTGRES_PASSWORD:-postgres}
|
|
334
334
|
POSTGRES_DB: \${POSTGRES_DB:-app}
|
|
335
335
|
volumes:
|
|
336
|
-
-
|
|
336
|
+
- dbdata:/var/lib/postgresql/18/data
|
|
337
337
|
healthcheck:
|
|
338
338
|
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER"]
|
|
339
339
|
interval: 5s
|
|
@@ -382,7 +382,7 @@ volumes:
|
|
|
382
382
|
`;
|
|
383
383
|
|
|
384
384
|
if (hasPostgres) {
|
|
385
|
-
yaml += `
|
|
385
|
+
yaml += ` dbdata:
|
|
386
386
|
`;
|
|
387
387
|
}
|
|
388
388
|
|
|
@@ -409,7 +409,7 @@ function getInfraServiceImage(
|
|
|
409
409
|
config: boolean | { version?: string; image?: string } | undefined,
|
|
410
410
|
): string {
|
|
411
411
|
const defaults: Record<'postgres' | 'redis', string> = {
|
|
412
|
-
postgres: 'postgres:
|
|
412
|
+
postgres: 'postgres:18-alpine',
|
|
413
413
|
redis: 'redis:7-alpine',
|
|
414
414
|
};
|
|
415
415
|
|
|
@@ -545,7 +545,7 @@ describe('initCommand', () => {
|
|
|
545
545
|
|
|
546
546
|
const dockerPath = join(tempDir, 'my-api', 'docker-compose.yml');
|
|
547
547
|
const content = await readFile(dockerPath, 'utf-8');
|
|
548
|
-
expect(content).toContain('postgres:
|
|
548
|
+
expect(content).toContain('postgres:18-alpine');
|
|
549
549
|
expect(content).toContain("'${POSTGRES_HOST_PORT:-5432}:5432'");
|
|
550
550
|
});
|
|
551
551
|
|
|
@@ -42,7 +42,7 @@ export function generateDockerFiles(
|
|
|
42
42
|
: '';
|
|
43
43
|
|
|
44
44
|
services.push(` postgres:
|
|
45
|
-
image: postgres:
|
|
45
|
+
image: postgres:18-alpine
|
|
46
46
|
container_name: ${options.name}-postgres
|
|
47
47
|
restart: unless-stopped${envFile}
|
|
48
48
|
environment:
|
|
@@ -52,13 +52,13 @@ export function generateDockerFiles(
|
|
|
52
52
|
ports:
|
|
53
53
|
- '\${POSTGRES_HOST_PORT:-5432}:5432'
|
|
54
54
|
volumes:
|
|
55
|
-
-
|
|
55
|
+
- dbdata:/var/lib/postgresql/18/data${initVolume}
|
|
56
56
|
healthcheck:
|
|
57
57
|
test: ['CMD-SHELL', 'pg_isready -U $$POSTGRES_USER']
|
|
58
58
|
interval: 5s
|
|
59
59
|
timeout: 5s
|
|
60
60
|
retries: 5`);
|
|
61
|
-
volumes.push('
|
|
61
|
+
volumes.push(' dbdata:');
|
|
62
62
|
|
|
63
63
|
// Generate PostgreSQL init script and .env for fullstack template
|
|
64
64
|
if (isFullstack && dbApps?.length) {
|
package/src/init/index.ts
CHANGED
|
@@ -331,7 +331,9 @@ export async function initCommand(
|
|
|
331
331
|
if (services.db) secretServices.push('postgres');
|
|
332
332
|
if (services.cache) secretServices.push('redis');
|
|
333
333
|
|
|
334
|
-
const devSecrets = createStageSecrets('development', secretServices
|
|
334
|
+
const devSecrets = createStageSecrets('development', secretServices, {
|
|
335
|
+
projectName: name,
|
|
336
|
+
});
|
|
335
337
|
|
|
336
338
|
// Add common custom secrets
|
|
337
339
|
const customSecrets: Record<string, string> = {
|
package/src/secrets/generator.ts
CHANGED
|
@@ -114,13 +114,24 @@ export function generateConnectionUrls(
|
|
|
114
114
|
|
|
115
115
|
/**
|
|
116
116
|
* Create a new StageSecrets object with generated credentials.
|
|
117
|
+
* @param stage - The deployment stage (e.g., 'development', 'production')
|
|
118
|
+
* @param services - List of services to generate credentials for
|
|
119
|
+
* @param options - Optional configuration
|
|
120
|
+
* @param options.projectName - Project name used to derive the database name (e.g., 'myapp' → 'myapp_dev')
|
|
117
121
|
*/
|
|
118
122
|
export function createStageSecrets(
|
|
119
123
|
stage: string,
|
|
120
124
|
services: ComposeServiceName[],
|
|
125
|
+
options?: { projectName?: string },
|
|
121
126
|
): StageSecrets {
|
|
122
127
|
const now = new Date().toISOString();
|
|
123
128
|
const serviceCredentials = generateServicesCredentials(services);
|
|
129
|
+
|
|
130
|
+
// Override postgres database name with project-derived name if provided
|
|
131
|
+
if (options?.projectName && serviceCredentials.postgres) {
|
|
132
|
+
serviceCredentials.postgres.database = `${options.projectName.replace(/-/g, '_')}_dev`;
|
|
133
|
+
}
|
|
134
|
+
|
|
124
135
|
const urls = generateConnectionUrls(serviceCredentials);
|
|
125
136
|
|
|
126
137
|
return {
|
package/src/secrets/index.ts
CHANGED
|
@@ -86,23 +86,29 @@ export async function secretsInitCommand(
|
|
|
86
86
|
);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
// Detect workspace mode and generate fullstack-aware custom secrets
|
|
89
|
+
// Detect workspace mode for project name and fullstack secrets
|
|
90
|
+
let projectName: string | undefined;
|
|
91
|
+
let workspaceSecrets: Record<string, string> | undefined;
|
|
93
92
|
try {
|
|
94
93
|
const loaded = await loadWorkspaceConfig();
|
|
94
|
+
projectName = loaded.workspace.name;
|
|
95
95
|
const isMultiApp = Object.keys(loaded.workspace.apps).length > 1;
|
|
96
96
|
|
|
97
97
|
if (isMultiApp) {
|
|
98
|
-
|
|
99
|
-
secrets.custom = customSecrets;
|
|
98
|
+
workspaceSecrets = generateFullstackCustomSecrets(loaded.workspace);
|
|
100
99
|
logger.log(' Detected workspace mode — generating per-app secrets');
|
|
101
100
|
}
|
|
102
101
|
} catch {
|
|
103
102
|
// Not a workspace — single-app mode, skip custom secrets
|
|
104
103
|
}
|
|
105
104
|
|
|
105
|
+
// Generate secrets (with project name so DATABASE_URL matches app-specific URLs)
|
|
106
|
+
const secrets = createStageSecrets(stage, services, { projectName });
|
|
107
|
+
|
|
108
|
+
if (workspaceSecrets) {
|
|
109
|
+
secrets.custom = workspaceSecrets;
|
|
110
|
+
}
|
|
111
|
+
|
|
106
112
|
// Write to file
|
|
107
113
|
await writeStageSecrets(secrets);
|
|
108
114
|
|
package/src/setup/index.ts
CHANGED
|
@@ -196,7 +196,9 @@ async function generateFreshSecrets(
|
|
|
196
196
|
if (workspace.services.cache) serviceNames.push('redis');
|
|
197
197
|
|
|
198
198
|
// Create base secrets with service credentials
|
|
199
|
-
const secrets = createStageSecrets(stage, serviceNames
|
|
199
|
+
const secrets = createStageSecrets(stage, serviceNames, {
|
|
200
|
+
projectName: workspace.name,
|
|
201
|
+
});
|
|
200
202
|
|
|
201
203
|
// Generate fullstack-aware custom secrets
|
|
202
204
|
const isMultiApp = Object.keys(workspace.apps).length > 1;
|
package/src/workspace/types.ts
CHANGED
|
@@ -85,7 +85,7 @@ export type FrontendFramework = 'nextjs' | 'remix' | 'vite';
|
|
|
85
85
|
* @example
|
|
86
86
|
* ```ts
|
|
87
87
|
* // Use specific version
|
|
88
|
-
* db: { version: '
|
|
88
|
+
* db: { version: '18-alpine' }
|
|
89
89
|
*
|
|
90
90
|
* // Use custom image
|
|
91
91
|
* db: { image: 'timescale/timescaledb:latest-pg16' }
|
|
@@ -144,7 +144,7 @@ export interface MailServiceConfig extends ServiceImageConfig {
|
|
|
144
144
|
*
|
|
145
145
|
* // Custom versions
|
|
146
146
|
* services: {
|
|
147
|
-
* db: { version: '
|
|
147
|
+
* db: { version: '18-alpine' },
|
|
148
148
|
* cache: { version: '7-alpine' },
|
|
149
149
|
* }
|
|
150
150
|
*
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"fullstack-secrets-DqxYGrgW.cjs","names":["SERVICE_DEFAULTS: Record<\n\tComposeServiceName,\n\tOmit<ServiceCredentials, 'password'>\n>","service: ComposeServiceName","services: ComposeServiceName[]","result: StageSecrets['services']","creds: ServiceCredentials","services: StageSecrets['services']","urls: StageSecrets['urls']","stage: string","secrets: StageSecrets","newCreds: ServiceCredentials","appName: string","password: string","projectName: string","workspace: NormalizedWorkspace","customs: Record<string, string>","frontendPorts: number[]","upperName","secrets: StageSecrets","workspaceRoot: string"],"sources":["../src/secrets/generator.ts","../src/setup/fullstack-secrets.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport type { ComposeServiceName } from '../types';\nimport type { ServiceCredentials, StageSecrets } from './types';\n\n/**\n * Generate a secure random password using URL-safe base64 characters.\n * @param length Password length (default: 32)\n */\nexport function generateSecurePassword(length = 32): string {\n\treturn randomBytes(Math.ceil((length * 3) / 4))\n\t\t.toString('base64url')\n\t\t.slice(0, length);\n}\n\n/** Default service configurations (localhost for local dev via Docker port mapping) */\nconst SERVICE_DEFAULTS: Record<\n\tComposeServiceName,\n\tOmit<ServiceCredentials, 'password'>\n> = {\n\tpostgres: {\n\t\thost: 'localhost',\n\t\tport: 5432,\n\t\tusername: 'app',\n\t\tdatabase: 'app',\n\t},\n\tredis: {\n\t\thost: 'localhost',\n\t\tport: 6379,\n\t\tusername: 'default',\n\t},\n\trabbitmq: {\n\t\thost: 'localhost',\n\t\tport: 5672,\n\t\tusername: 'app',\n\t\tvhost: '/',\n\t},\n};\n\n/**\n * Generate credentials for a specific service.\n */\nexport function generateServiceCredentials(\n\tservice: ComposeServiceName,\n): ServiceCredentials {\n\tconst defaults = SERVICE_DEFAULTS[service];\n\treturn {\n\t\t...defaults,\n\t\tpassword: generateSecurePassword(),\n\t};\n}\n\n/**\n * Generate credentials for multiple services.\n */\nexport function generateServicesCredentials(\n\tservices: ComposeServiceName[],\n): StageSecrets['services'] {\n\tconst result: StageSecrets['services'] = {};\n\n\tfor (const service of services) {\n\t\tresult[service] = generateServiceCredentials(service);\n\t}\n\n\treturn result;\n}\n\n/**\n * Generate connection URL for PostgreSQL.\n */\nexport function generatePostgresUrl(creds: ServiceCredentials): string {\n\tconst { username, password, host, port, database } = creds;\n\treturn `postgresql://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}`;\n}\n\n/**\n * Generate connection URL for Redis.\n */\nexport function generateRedisUrl(creds: ServiceCredentials): string {\n\tconst { password, host, port } = creds;\n\treturn `redis://:${encodeURIComponent(password)}@${host}:${port}`;\n}\n\n/**\n * Generate connection URL for RabbitMQ.\n */\nexport function generateRabbitmqUrl(creds: ServiceCredentials): string {\n\tconst { username, password, host, port, vhost } = creds;\n\tconst encodedVhost = encodeURIComponent(vhost ?? '/');\n\treturn `amqp://${username}:${encodeURIComponent(password)}@${host}:${port}/${encodedVhost}`;\n}\n\n/**\n * Generate connection URLs from service credentials.\n */\nexport function generateConnectionUrls(\n\tservices: StageSecrets['services'],\n): StageSecrets['urls'] {\n\tconst urls: StageSecrets['urls'] = {};\n\n\tif (services.postgres) {\n\t\turls.DATABASE_URL = generatePostgresUrl(services.postgres);\n\t}\n\n\tif (services.redis) {\n\t\turls.REDIS_URL = generateRedisUrl(services.redis);\n\t}\n\n\tif (services.rabbitmq) {\n\t\turls.RABBITMQ_URL = generateRabbitmqUrl(services.rabbitmq);\n\t}\n\n\treturn urls;\n}\n\n/**\n * Create a new StageSecrets object with generated credentials.\n */\nexport function createStageSecrets(\n\tstage: string,\n\tservices: ComposeServiceName[],\n): StageSecrets {\n\tconst now = new Date().toISOString();\n\tconst serviceCredentials = generateServicesCredentials(services);\n\tconst urls = generateConnectionUrls(serviceCredentials);\n\n\treturn {\n\t\tstage,\n\t\tcreatedAt: now,\n\t\tupdatedAt: now,\n\t\tservices: serviceCredentials,\n\t\turls,\n\t\tcustom: {},\n\t};\n}\n\n/**\n * Rotate password for a specific service.\n */\nexport function rotateServicePassword(\n\tsecrets: StageSecrets,\n\tservice: ComposeServiceName,\n): StageSecrets {\n\tconst currentCreds = secrets.services[service];\n\tif (!currentCreds) {\n\t\tthrow new Error(`Service \"${service}\" not configured in secrets`);\n\t}\n\n\tconst newCreds: ServiceCredentials = {\n\t\t...currentCreds,\n\t\tpassword: generateSecurePassword(),\n\t};\n\n\tconst newServices = {\n\t\t...secrets.services,\n\t\t[service]: newCreds,\n\t};\n\n\treturn {\n\t\t...secrets,\n\t\tupdatedAt: new Date().toISOString(),\n\t\tservices: newServices,\n\t\turls: generateConnectionUrls(newServices),\n\t};\n}\n","import { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { generateSecurePassword } from '../secrets/generator.js';\nimport type { StageSecrets } from '../secrets/types.js';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\n\n/**\n * Generate a secure random password for database users.\n * Uses a combination of timestamp and random bytes for uniqueness.\n */\nexport function generateDbPassword(): string {\n\treturn `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}${Math.random().toString(36).slice(2)}`;\n}\n\n/**\n * Generate database URL for an app.\n * All apps connect to the same database, but use different users/schemas.\n */\nexport function generateDbUrl(\n\tappName: string,\n\tpassword: string,\n\tprojectName: string,\n\thost = 'localhost',\n\tport = 5432,\n): string {\n\tconst userName = appName.replace(/-/g, '_');\n\tconst dbName = `${projectName.replace(/-/g, '_')}_dev`;\n\treturn `postgresql://${userName}:${password}@${host}:${port}/${dbName}`;\n}\n\n/**\n * Generate fullstack-aware custom secrets for a workspace.\n *\n * Generates:\n * - Common secrets: NODE_ENV, PORT, LOG_LEVEL, JWT_SECRET\n * - Per-app database passwords and URLs for backend apps with db service\n * - Better-auth secrets for apps using the better-auth framework\n */\nexport function generateFullstackCustomSecrets(\n\tworkspace: NormalizedWorkspace,\n): Record<string, string> {\n\tconst hasDb = !!workspace.services.db;\n\tconst customs: Record<string, string> = {\n\t\tNODE_ENV: 'development',\n\t\tPORT: '3000',\n\t\tLOG_LEVEL: 'debug',\n\t\tJWT_SECRET: `dev-${Date.now()}-${Math.random().toString(36).slice(2)}`,\n\t};\n\n\tif (!hasDb) {\n\t\treturn customs;\n\t}\n\n\t// Collect all frontend ports for trusted origins\n\tconst frontendPorts: number[] = [];\n\n\tfor (const [appName, appConfig] of Object.entries(workspace.apps)) {\n\t\tif (appConfig.type === 'frontend') {\n\t\t\tfrontendPorts.push(appConfig.port);\n\t\t\tconst upperName = appName.toUpperCase();\n\t\t\tcustoms[`${upperName}_URL`] = `http://localhost:${appConfig.port}`;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Backend apps with database: generate per-app DB passwords and URLs\n\t\tconst password = generateDbPassword();\n\t\tconst upperName = appName.toUpperCase();\n\n\t\tcustoms[`${upperName}_DATABASE_URL`] = generateDbUrl(\n\t\t\tappName,\n\t\t\tpassword,\n\t\t\tworkspace.name,\n\t\t);\n\t\tcustoms[`${upperName}_DB_PASSWORD`] = password;\n\n\t\t// Better-auth framework secrets\n\t\tif (appConfig.framework === 'better-auth') {\n\t\t\tcustoms.AUTH_PORT = String(appConfig.port);\n\t\t\tcustoms.AUTH_URL = `http://localhost:${appConfig.port}`;\n\t\t\tcustoms.BETTER_AUTH_SECRET = `better-auth-${Date.now()}-${generateSecurePassword(16)}`;\n\t\t\tcustoms.BETTER_AUTH_URL = `http://localhost:${appConfig.port}`;\n\t\t}\n\t}\n\n\t// Generate trusted origins for better-auth (all app ports)\n\tif (customs.BETTER_AUTH_SECRET) {\n\t\tconst allPorts = Object.values(workspace.apps).map((a) => a.port);\n\t\tcustoms.BETTER_AUTH_TRUSTED_ORIGINS = allPorts\n\t\t\t.map((p) => `http://localhost:${p}`)\n\t\t\t.join(',');\n\t}\n\n\treturn customs;\n}\n\n/**\n * Extract *_DB_PASSWORD keys from secrets and write docker/.env.\n *\n * The docker/.env file contains database passwords that the PostgreSQL\n * init script reads to create per-app database users.\n */\nexport async function writeDockerEnvFromSecrets(\n\tsecrets: StageSecrets,\n\tworkspaceRoot: string,\n): Promise<void> {\n\tconst dbPasswordEntries = Object.entries(secrets.custom).filter(([key]) =>\n\t\tkey.endsWith('_DB_PASSWORD'),\n\t);\n\n\tif (dbPasswordEntries.length === 0) {\n\t\treturn;\n\t}\n\n\tconst envContent = `# Auto-generated docker environment file\n# Contains database passwords for docker-compose postgres init\n# This file is gitignored - do not commit to version control\n${dbPasswordEntries.map(([key, value]) => `${key}=${value}`).join('\\n')}\n`;\n\n\tconst envPath = join(workspaceRoot, 'docker', '.env');\n\tawait mkdir(dirname(envPath), { recursive: true });\n\tawait writeFile(envPath, envContent);\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"fullstack-secrets-odm79Uo1.mjs","names":["SERVICE_DEFAULTS: Record<\n\tComposeServiceName,\n\tOmit<ServiceCredentials, 'password'>\n>","service: ComposeServiceName","services: ComposeServiceName[]","result: StageSecrets['services']","creds: ServiceCredentials","services: StageSecrets['services']","urls: StageSecrets['urls']","stage: string","secrets: StageSecrets","newCreds: ServiceCredentials","appName: string","password: string","projectName: string","workspace: NormalizedWorkspace","customs: Record<string, string>","frontendPorts: number[]","upperName","secrets: StageSecrets","workspaceRoot: string"],"sources":["../src/secrets/generator.ts","../src/setup/fullstack-secrets.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport type { ComposeServiceName } from '../types';\nimport type { ServiceCredentials, StageSecrets } from './types';\n\n/**\n * Generate a secure random password using URL-safe base64 characters.\n * @param length Password length (default: 32)\n */\nexport function generateSecurePassword(length = 32): string {\n\treturn randomBytes(Math.ceil((length * 3) / 4))\n\t\t.toString('base64url')\n\t\t.slice(0, length);\n}\n\n/** Default service configurations (localhost for local dev via Docker port mapping) */\nconst SERVICE_DEFAULTS: Record<\n\tComposeServiceName,\n\tOmit<ServiceCredentials, 'password'>\n> = {\n\tpostgres: {\n\t\thost: 'localhost',\n\t\tport: 5432,\n\t\tusername: 'app',\n\t\tdatabase: 'app',\n\t},\n\tredis: {\n\t\thost: 'localhost',\n\t\tport: 6379,\n\t\tusername: 'default',\n\t},\n\trabbitmq: {\n\t\thost: 'localhost',\n\t\tport: 5672,\n\t\tusername: 'app',\n\t\tvhost: '/',\n\t},\n};\n\n/**\n * Generate credentials for a specific service.\n */\nexport function generateServiceCredentials(\n\tservice: ComposeServiceName,\n): ServiceCredentials {\n\tconst defaults = SERVICE_DEFAULTS[service];\n\treturn {\n\t\t...defaults,\n\t\tpassword: generateSecurePassword(),\n\t};\n}\n\n/**\n * Generate credentials for multiple services.\n */\nexport function generateServicesCredentials(\n\tservices: ComposeServiceName[],\n): StageSecrets['services'] {\n\tconst result: StageSecrets['services'] = {};\n\n\tfor (const service of services) {\n\t\tresult[service] = generateServiceCredentials(service);\n\t}\n\n\treturn result;\n}\n\n/**\n * Generate connection URL for PostgreSQL.\n */\nexport function generatePostgresUrl(creds: ServiceCredentials): string {\n\tconst { username, password, host, port, database } = creds;\n\treturn `postgresql://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}`;\n}\n\n/**\n * Generate connection URL for Redis.\n */\nexport function generateRedisUrl(creds: ServiceCredentials): string {\n\tconst { password, host, port } = creds;\n\treturn `redis://:${encodeURIComponent(password)}@${host}:${port}`;\n}\n\n/**\n * Generate connection URL for RabbitMQ.\n */\nexport function generateRabbitmqUrl(creds: ServiceCredentials): string {\n\tconst { username, password, host, port, vhost } = creds;\n\tconst encodedVhost = encodeURIComponent(vhost ?? '/');\n\treturn `amqp://${username}:${encodeURIComponent(password)}@${host}:${port}/${encodedVhost}`;\n}\n\n/**\n * Generate connection URLs from service credentials.\n */\nexport function generateConnectionUrls(\n\tservices: StageSecrets['services'],\n): StageSecrets['urls'] {\n\tconst urls: StageSecrets['urls'] = {};\n\n\tif (services.postgres) {\n\t\turls.DATABASE_URL = generatePostgresUrl(services.postgres);\n\t}\n\n\tif (services.redis) {\n\t\turls.REDIS_URL = generateRedisUrl(services.redis);\n\t}\n\n\tif (services.rabbitmq) {\n\t\turls.RABBITMQ_URL = generateRabbitmqUrl(services.rabbitmq);\n\t}\n\n\treturn urls;\n}\n\n/**\n * Create a new StageSecrets object with generated credentials.\n */\nexport function createStageSecrets(\n\tstage: string,\n\tservices: ComposeServiceName[],\n): StageSecrets {\n\tconst now = new Date().toISOString();\n\tconst serviceCredentials = generateServicesCredentials(services);\n\tconst urls = generateConnectionUrls(serviceCredentials);\n\n\treturn {\n\t\tstage,\n\t\tcreatedAt: now,\n\t\tupdatedAt: now,\n\t\tservices: serviceCredentials,\n\t\turls,\n\t\tcustom: {},\n\t};\n}\n\n/**\n * Rotate password for a specific service.\n */\nexport function rotateServicePassword(\n\tsecrets: StageSecrets,\n\tservice: ComposeServiceName,\n): StageSecrets {\n\tconst currentCreds = secrets.services[service];\n\tif (!currentCreds) {\n\t\tthrow new Error(`Service \"${service}\" not configured in secrets`);\n\t}\n\n\tconst newCreds: ServiceCredentials = {\n\t\t...currentCreds,\n\t\tpassword: generateSecurePassword(),\n\t};\n\n\tconst newServices = {\n\t\t...secrets.services,\n\t\t[service]: newCreds,\n\t};\n\n\treturn {\n\t\t...secrets,\n\t\tupdatedAt: new Date().toISOString(),\n\t\tservices: newServices,\n\t\turls: generateConnectionUrls(newServices),\n\t};\n}\n","import { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { generateSecurePassword } from '../secrets/generator.js';\nimport type { StageSecrets } from '../secrets/types.js';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\n\n/**\n * Generate a secure random password for database users.\n * Uses a combination of timestamp and random bytes for uniqueness.\n */\nexport function generateDbPassword(): string {\n\treturn `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}${Math.random().toString(36).slice(2)}`;\n}\n\n/**\n * Generate database URL for an app.\n * All apps connect to the same database, but use different users/schemas.\n */\nexport function generateDbUrl(\n\tappName: string,\n\tpassword: string,\n\tprojectName: string,\n\thost = 'localhost',\n\tport = 5432,\n): string {\n\tconst userName = appName.replace(/-/g, '_');\n\tconst dbName = `${projectName.replace(/-/g, '_')}_dev`;\n\treturn `postgresql://${userName}:${password}@${host}:${port}/${dbName}`;\n}\n\n/**\n * Generate fullstack-aware custom secrets for a workspace.\n *\n * Generates:\n * - Common secrets: NODE_ENV, PORT, LOG_LEVEL, JWT_SECRET\n * - Per-app database passwords and URLs for backend apps with db service\n * - Better-auth secrets for apps using the better-auth framework\n */\nexport function generateFullstackCustomSecrets(\n\tworkspace: NormalizedWorkspace,\n): Record<string, string> {\n\tconst hasDb = !!workspace.services.db;\n\tconst customs: Record<string, string> = {\n\t\tNODE_ENV: 'development',\n\t\tPORT: '3000',\n\t\tLOG_LEVEL: 'debug',\n\t\tJWT_SECRET: `dev-${Date.now()}-${Math.random().toString(36).slice(2)}`,\n\t};\n\n\tif (!hasDb) {\n\t\treturn customs;\n\t}\n\n\t// Collect all frontend ports for trusted origins\n\tconst frontendPorts: number[] = [];\n\n\tfor (const [appName, appConfig] of Object.entries(workspace.apps)) {\n\t\tif (appConfig.type === 'frontend') {\n\t\t\tfrontendPorts.push(appConfig.port);\n\t\t\tconst upperName = appName.toUpperCase();\n\t\t\tcustoms[`${upperName}_URL`] = `http://localhost:${appConfig.port}`;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Backend apps with database: generate per-app DB passwords and URLs\n\t\tconst password = generateDbPassword();\n\t\tconst upperName = appName.toUpperCase();\n\n\t\tcustoms[`${upperName}_DATABASE_URL`] = generateDbUrl(\n\t\t\tappName,\n\t\t\tpassword,\n\t\t\tworkspace.name,\n\t\t);\n\t\tcustoms[`${upperName}_DB_PASSWORD`] = password;\n\n\t\t// Better-auth framework secrets\n\t\tif (appConfig.framework === 'better-auth') {\n\t\t\tcustoms.AUTH_PORT = String(appConfig.port);\n\t\t\tcustoms.AUTH_URL = `http://localhost:${appConfig.port}`;\n\t\t\tcustoms.BETTER_AUTH_SECRET = `better-auth-${Date.now()}-${generateSecurePassword(16)}`;\n\t\t\tcustoms.BETTER_AUTH_URL = `http://localhost:${appConfig.port}`;\n\t\t}\n\t}\n\n\t// Generate trusted origins for better-auth (all app ports)\n\tif (customs.BETTER_AUTH_SECRET) {\n\t\tconst allPorts = Object.values(workspace.apps).map((a) => a.port);\n\t\tcustoms.BETTER_AUTH_TRUSTED_ORIGINS = allPorts\n\t\t\t.map((p) => `http://localhost:${p}`)\n\t\t\t.join(',');\n\t}\n\n\treturn customs;\n}\n\n/**\n * Extract *_DB_PASSWORD keys from secrets and write docker/.env.\n *\n * The docker/.env file contains database passwords that the PostgreSQL\n * init script reads to create per-app database users.\n */\nexport async function writeDockerEnvFromSecrets(\n\tsecrets: StageSecrets,\n\tworkspaceRoot: string,\n): Promise<void> {\n\tconst dbPasswordEntries = Object.entries(secrets.custom).filter(([key]) =>\n\t\tkey.endsWith('_DB_PASSWORD'),\n\t);\n\n\tif (dbPasswordEntries.length === 0) {\n\t\treturn;\n\t}\n\n\tconst envContent = `# Auto-generated docker environment file\n# Contains database passwords for docker-compose postgres init\n# This file is gitignored - do not commit to version control\n${dbPasswordEntries.map(([key, value]) => `${key}=${value}`).join('\\n')}\n`;\n\n\tconst envPath = join(workspaceRoot, 'docker', '.env');\n\tawait mkdir(dirname(envPath), { recursive: true });\n\tawait writeFile(envPath, envContent);\n}\n"],"mappings":"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"}
|