@geekmidas/cli 0.14.0 → 0.15.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geekmidas/cli",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs",
5
5
  "private": false,
6
6
  "type": "module",
@@ -54,9 +54,9 @@
54
54
  },
55
55
  "peerDependencies": {
56
56
  "@geekmidas/constructs": "~0.5.0",
57
- "@geekmidas/envkit": "~0.3.0",
58
57
  "@geekmidas/schema": "~0.1.0",
59
58
  "@geekmidas/telescope": "~0.4.0",
59
+ "@geekmidas/envkit": "~0.3.0",
60
60
  "@geekmidas/logger": "~0.4.0"
61
61
  },
62
62
  "peerDependenciesMeta": {
@@ -371,10 +371,10 @@ describe('bundleServer environment validation', () => {
371
371
  );
372
372
 
373
373
  itWithDir(
374
- 'should throw when secrets file does not exist',
374
+ 'should auto-initialize secrets and throw for missing env vars',
375
375
  async ({ dir }) => {
376
376
  const entryPoint = await createEntryPoint(dir);
377
- // Don't create secrets file
377
+ // Don't create secrets file - it should be auto-initialized
378
378
 
379
379
  const constructs = [createMockConstruct(['DATABASE_URL'])];
380
380
 
@@ -382,6 +382,7 @@ describe('bundleServer environment validation', () => {
382
382
  process.chdir(dir);
383
383
 
384
384
  try {
385
+ // Should auto-initialize secrets but then fail because DATABASE_URL is required
385
386
  await expect(
386
387
  bundleServer({
387
388
  entryPoint,
@@ -392,7 +393,7 @@ describe('bundleServer environment validation', () => {
392
393
  stage: 'production',
393
394
  constructs,
394
395
  }),
395
- ).rejects.toThrow('No secrets found for stage "production"');
396
+ ).rejects.toThrow('Missing environment variables');
396
397
  } finally {
397
398
  process.chdir(originalCwd);
398
399
  }
@@ -131,17 +131,21 @@ export async function bundleServer(
131
131
  readStageSecrets,
132
132
  toEmbeddableSecrets,
133
133
  validateEnvironmentVariables,
134
+ initStageSecrets,
135
+ writeStageSecrets,
134
136
  } = await import('../secrets/storage');
135
137
  const { encryptSecrets, generateDefineOptions } = await import(
136
138
  '../secrets/encryption'
137
139
  );
138
140
 
139
- const secrets = await readStageSecrets(stage);
141
+ let secrets = await readStageSecrets(stage);
140
142
 
141
143
  if (!secrets) {
142
- throw new Error(
143
- `No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`,
144
- );
144
+ // Auto-initialize secrets for the stage
145
+ console.log(` Initializing secrets for stage "${stage}"...`);
146
+ secrets = initStageSecrets(stage);
147
+ await writeStageSecrets(secrets);
148
+ console.log(` ✓ Created .gkm/secrets/${stage}.json`);
145
149
  }
146
150
 
147
151
  // Auto-populate env vars from docker compose services
@@ -1,6 +1,11 @@
1
- import { describe, expect, it } from 'vitest';
1
+ import { describe, expect, it, vi } from 'vitest';
2
2
  import type { GkmConfig } from '../../types';
3
- import { getImageRef, resolveDockerConfig } from '../docker';
3
+ import {
4
+ getAppNameFromCwd,
5
+ getAppNameFromPackageJson,
6
+ getImageRef,
7
+ resolveDockerConfig,
8
+ } from '../docker';
4
9
 
5
10
  describe('getImageRef', () => {
6
11
  it('should return image with registry prefix', () => {
@@ -36,8 +41,26 @@ describe('getImageRef', () => {
36
41
  });
37
42
  });
38
43
 
44
+ describe('getAppNameFromCwd', () => {
45
+ it('should return app name from package.json in current directory', () => {
46
+ // Tests run from the monorepo root, so cwd is the root directory
47
+ const appName = getAppNameFromCwd();
48
+ // The root package.json has name "@geekmidas/toolbox", so it should strip the scope
49
+ expect(appName).toBe('toolbox');
50
+ });
51
+ });
52
+
53
+ describe('getAppNameFromPackageJson', () => {
54
+ it('should return app name from package.json adjacent to lockfile', () => {
55
+ // This test runs in the toolbox monorepo, so it should find the root package.json
56
+ const appName = getAppNameFromPackageJson();
57
+ // The root package.json has name "@geekmidas/toolbox", so it should strip the scope
58
+ expect(appName).toBe('toolbox');
59
+ });
60
+ });
61
+
39
62
  describe('resolveDockerConfig', () => {
40
- it('should return empty config when docker not configured', () => {
63
+ it('should fallback to package.json name when docker not configured', () => {
41
64
  const config: GkmConfig = {
42
65
  routes: './src/endpoints',
43
66
  envParser: './src/env',
@@ -47,7 +70,8 @@ describe('resolveDockerConfig', () => {
47
70
  const dockerConfig = resolveDockerConfig(config);
48
71
 
49
72
  expect(dockerConfig.registry).toBeUndefined();
50
- expect(dockerConfig.imageName).toBeUndefined();
73
+ // Should fallback to package.json name or 'app'
74
+ expect(dockerConfig.imageName).toBeDefined();
51
75
  });
52
76
 
53
77
  it('should return registry from docker config', () => {
@@ -97,7 +121,7 @@ describe('resolveDockerConfig', () => {
97
121
  expect(dockerConfig.imageName).toBe('backend-api');
98
122
  });
99
123
 
100
- it('should handle empty docker object', () => {
124
+ it('should fallback to package.json name when docker object is empty', () => {
101
125
  const config: GkmConfig = {
102
126
  routes: './src/endpoints',
103
127
  docker: {},
@@ -106,6 +130,20 @@ describe('resolveDockerConfig', () => {
106
130
  const dockerConfig = resolveDockerConfig(config);
107
131
 
108
132
  expect(dockerConfig.registry).toBeUndefined();
109
- expect(dockerConfig.imageName).toBeUndefined();
133
+ // Should fallback to package.json name or 'app'
134
+ expect(dockerConfig.imageName).toBeDefined();
135
+ });
136
+
137
+ it('should prefer explicit imageName over package.json', () => {
138
+ const config: GkmConfig = {
139
+ routes: './src/endpoints',
140
+ docker: {
141
+ imageName: 'explicit-name',
142
+ },
143
+ };
144
+
145
+ const dockerConfig = resolveDockerConfig(config);
146
+
147
+ expect(dockerConfig.imageName).toBe('explicit-name');
110
148
  });
111
149
  });
@@ -133,6 +133,39 @@ describe('provisionServices', () => {
133
133
  );
134
134
  });
135
135
 
136
+ it('should provision postgres and return individual connection parameters', async () => {
137
+ server.use(
138
+ http.post(`${BASE_URL}/api/postgres.create`, async ({ request }) => {
139
+ const body = (await request.json()) as { databasePassword?: string };
140
+ return HttpResponse.json({
141
+ postgresId: 'pg_123',
142
+ name: 'myapp-db',
143
+ appName: 'myapp-db',
144
+ databaseName: 'mydb',
145
+ databaseUser: 'dbuser',
146
+ databasePassword: body.databasePassword,
147
+ applicationStatus: 'idle',
148
+ });
149
+ }),
150
+ http.post(`${BASE_URL}/api/postgres.deploy`, () => {
151
+ return HttpResponse.json({ success: true });
152
+ }),
153
+ );
154
+
155
+ const api = new DokployApi({ baseUrl: BASE_URL, token: 'test-token' });
156
+
157
+ const result = await provisionServices(api, 'proj_1', 'env_1', 'myapp', {
158
+ postgres: true,
159
+ });
160
+
161
+ expect(result).toBeDefined();
162
+ expect(result?.DATABASE_HOST).toBe('myapp-db');
163
+ expect(result?.DATABASE_PORT).toBe('5432');
164
+ expect(result?.DATABASE_NAME).toBe('mydb');
165
+ expect(result?.DATABASE_USER).toBe('dbuser');
166
+ expect(result?.DATABASE_PASSWORD).toMatch(/^[a-f0-9]{32}$/);
167
+ });
168
+
136
169
  it('should provision redis and return REDIS_URL', async () => {
137
170
  server.use(
138
171
  http.post(`${BASE_URL}/api/redis.create`, async ({ request }) => {
@@ -162,6 +195,35 @@ describe('provisionServices', () => {
162
195
  );
163
196
  });
164
197
 
198
+ it('should provision redis and return individual connection parameters', async () => {
199
+ server.use(
200
+ http.post(`${BASE_URL}/api/redis.create`, async ({ request }) => {
201
+ const body = (await request.json()) as { databasePassword?: string };
202
+ return HttpResponse.json({
203
+ redisId: 'redis_123',
204
+ name: 'myapp-cache',
205
+ appName: 'myapp-cache',
206
+ databasePassword: body.databasePassword,
207
+ applicationStatus: 'idle',
208
+ });
209
+ }),
210
+ http.post(`${BASE_URL}/api/redis.deploy`, () => {
211
+ return HttpResponse.json({ success: true });
212
+ }),
213
+ );
214
+
215
+ const api = new DokployApi({ baseUrl: BASE_URL, token: 'test-token' });
216
+
217
+ const result = await provisionServices(api, 'proj_1', 'env_1', 'myapp', {
218
+ redis: true,
219
+ });
220
+
221
+ expect(result).toBeDefined();
222
+ expect(result?.REDIS_HOST).toBe('myapp-cache');
223
+ expect(result?.REDIS_PORT).toBe('6379');
224
+ expect(result?.REDIS_PASSWORD).toMatch(/^[a-f0-9]{32}$/);
225
+ });
226
+
165
227
  it('should provision both postgres and redis', async () => {
166
228
  server.use(
167
229
  http.post(`${BASE_URL}/api/postgres.create`, async ({ request }) => {
@@ -1,8 +1,68 @@
1
1
  import { execSync } from 'node:child_process';
2
+ import { existsSync, readFileSync } from 'node:fs';
2
3
  import { dirname, join, relative } from 'node:path';
4
+ import type { GkmConfig } from '../config';
3
5
  import { dockerCommand, findLockfilePath, isMonorepo } from '../docker';
4
6
  import type { DeployResult, DockerDeployConfig } from './types';
5
7
 
8
+ /**
9
+ * Get app name from package.json in the current working directory
10
+ * Used for Dokploy app/project naming
11
+ */
12
+ export function getAppNameFromCwd(): string | undefined {
13
+ const packageJsonPath = join(process.cwd(), 'package.json');
14
+
15
+ if (!existsSync(packageJsonPath)) {
16
+ return undefined;
17
+ }
18
+
19
+ try {
20
+ const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
21
+ if (pkg.name) {
22
+ // Strip org scope if present (e.g., @myorg/app -> app)
23
+ return pkg.name.replace(/^@[^/]+\//, '');
24
+ }
25
+ } catch {
26
+ // Ignore parse errors
27
+ }
28
+
29
+ return undefined;
30
+ }
31
+
32
+ /**
33
+ * Get app name from package.json adjacent to the lockfile (project root)
34
+ * Used for Docker image naming
35
+ */
36
+ export function getAppNameFromPackageJson(): string | undefined {
37
+ const cwd = process.cwd();
38
+
39
+ // Find the lockfile to determine the project root
40
+ const lockfilePath = findLockfilePath(cwd);
41
+ if (!lockfilePath) {
42
+ return undefined;
43
+ }
44
+
45
+ // Use the package.json adjacent to the lockfile
46
+ const projectRoot = dirname(lockfilePath);
47
+ const packageJsonPath = join(projectRoot, 'package.json');
48
+
49
+ if (!existsSync(packageJsonPath)) {
50
+ return undefined;
51
+ }
52
+
53
+ try {
54
+ const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
55
+ if (pkg.name) {
56
+ // Strip org scope if present (e.g., @myorg/app -> app)
57
+ return pkg.name.replace(/^@[^/]+\//, '');
58
+ }
59
+ } catch {
60
+ // Ignore parse errors
61
+ }
62
+
63
+ return undefined;
64
+ }
65
+
6
66
  const logger = console;
7
67
 
8
68
  export interface DockerDeployOptions {
@@ -110,7 +170,8 @@ export async function deployDocker(
110
170
  ): Promise<DeployResult> {
111
171
  const { stage, tag, skipPush, masterKey, config } = options;
112
172
 
113
- const imageName = config.imageName ?? 'app';
173
+ // imageName should always be set by resolveDockerConfig
174
+ const imageName = config.imageName!;
114
175
  const imageRef = getImageRef(config.registry, imageName, tag);
115
176
 
116
177
  // Build image
@@ -148,10 +209,24 @@ export async function deployDocker(
148
209
 
149
210
  /**
150
211
  * Resolve Docker deploy config from gkm config
212
+ * - imageName: from config, or cwd package.json, or 'app' (for Docker image)
213
+ * - projectName: from root package.json, or 'app' (for Dokploy project)
214
+ * - appName: from cwd package.json, or projectName (for Dokploy app within project)
151
215
  */
152
216
  export function resolveDockerConfig(config: GkmConfig): DockerDeployConfig {
217
+ // projectName comes from root package.json (monorepo name)
218
+ const projectName = getAppNameFromPackageJson() ?? 'app';
219
+
220
+ // appName comes from cwd package.json (the app being deployed)
221
+ const appName = getAppNameFromCwd() ?? projectName;
222
+
223
+ // imageName defaults to appName (cwd package.json)
224
+ const imageName = config.docker?.imageName ?? appName;
225
+
153
226
  return {
154
227
  registry: config.docker?.registry,
155
- imageName: config.docker?.imageName,
228
+ imageName,
229
+ projectName,
230
+ appName,
156
231
  };
157
232
  }
@@ -17,6 +17,7 @@ import type {
17
17
  DeployOptions,
18
18
  DeployProvider,
19
19
  DeployResult,
20
+ DockerDeployConfig,
20
21
  DokployDeployConfig,
21
22
  } from './types';
22
23
 
@@ -76,15 +77,28 @@ interface DockerComposeServices {
76
77
  rabbitmq?: boolean;
77
78
  }
78
79
 
80
+ /**
81
+ * Service URLs including both connection URLs and individual parameters
82
+ */
83
+ interface ServiceUrls {
84
+ DATABASE_URL?: string;
85
+ DATABASE_HOST?: string;
86
+ DATABASE_PORT?: string;
87
+ DATABASE_NAME?: string;
88
+ DATABASE_USER?: string;
89
+ DATABASE_PASSWORD?: string;
90
+ REDIS_URL?: string;
91
+ REDIS_HOST?: string;
92
+ REDIS_PORT?: string;
93
+ REDIS_PASSWORD?: string;
94
+ }
95
+
79
96
  /**
80
97
  * Result of Dokploy setup including provisioned service URLs
81
98
  */
82
99
  interface DokploySetupResult {
83
100
  config: DokployDeployConfig;
84
- serviceUrls?: {
85
- DATABASE_URL?: string;
86
- REDIS_URL?: string;
87
- };
101
+ serviceUrls?: ServiceUrls;
88
102
  }
89
103
 
90
104
  /**
@@ -97,8 +111,8 @@ export async function provisionServices(
97
111
  environmentId: string | undefined,
98
112
  appName: string,
99
113
  services?: DockerComposeServices,
100
- existingUrls?: { DATABASE_URL?: string; REDIS_URL?: string },
101
- ): Promise<{ DATABASE_URL?: string; REDIS_URL?: string } | undefined> {
114
+ existingUrls?: Pick<ServiceUrls, 'DATABASE_URL' | 'REDIS_URL'>,
115
+ ): Promise<ServiceUrls | undefined> {
102
116
  logger.log(
103
117
  `\n🔍 provisionServices called: services=${JSON.stringify(services)}, envId=${environmentId}`,
104
118
  );
@@ -107,7 +121,7 @@ export async function provisionServices(
107
121
  return undefined;
108
122
  }
109
123
 
110
- const serviceUrls: { DATABASE_URL?: string; REDIS_URL?: string } = {};
124
+ const serviceUrls: ServiceUrls = {};
111
125
 
112
126
  if (services.postgres) {
113
127
  // Skip if DATABASE_URL already exists in secrets
@@ -134,9 +148,16 @@ export async function provisionServices(
134
148
  await api.deployPostgres(postgres.postgresId);
135
149
  logger.log(' ✓ PostgreSQL deployed');
136
150
 
151
+ // Store individual connection parameters
152
+ serviceUrls.DATABASE_HOST = postgres.appName;
153
+ serviceUrls.DATABASE_PORT = '5432';
154
+ serviceUrls.DATABASE_NAME = postgres.databaseName;
155
+ serviceUrls.DATABASE_USER = postgres.databaseUser;
156
+ serviceUrls.DATABASE_PASSWORD = postgres.databasePassword;
157
+
137
158
  // Construct connection URL using internal docker network hostname
138
159
  serviceUrls.DATABASE_URL = `postgresql://${postgres.databaseUser}:${postgres.databasePassword}@${postgres.appName}:5432/${postgres.databaseName}`;
139
- logger.log(` ✓ DATABASE_URL configured`);
160
+ logger.log(` ✓ Database credentials configured`);
140
161
  } catch (error) {
141
162
  const message =
142
163
  error instanceof Error ? error.message : 'Unknown error';
@@ -179,12 +200,19 @@ export async function provisionServices(
179
200
  await api.deployRedis(redis.redisId);
180
201
  logger.log(' ✓ Redis deployed');
181
202
 
203
+ // Store individual connection parameters
204
+ serviceUrls.REDIS_HOST = redis.appName;
205
+ serviceUrls.REDIS_PORT = '6379';
206
+ if (redis.databasePassword) {
207
+ serviceUrls.REDIS_PASSWORD = redis.databasePassword;
208
+ }
209
+
182
210
  // Construct connection URL
183
211
  const password = redis.databasePassword
184
212
  ? `:${redis.databasePassword}@`
185
213
  : '';
186
214
  serviceUrls.REDIS_URL = `redis://${password}${redis.appName}:6379`;
187
- logger.log(` ✓ REDIS_URL configured`);
215
+ logger.log(` ✓ Redis credentials configured`);
188
216
  } catch (error) {
189
217
  const message =
190
218
  error instanceof Error ? error.message : 'Unknown error';
@@ -208,7 +236,7 @@ export async function provisionServices(
208
236
  */
209
237
  async function ensureDokploySetup(
210
238
  config: GkmConfig,
211
- dockerConfig: { registry?: string; imageName?: string },
239
+ dockerConfig: DockerDeployConfig,
212
240
  stage: string,
213
241
  services?: DockerComposeServices,
214
242
  ): Promise<DokploySetupResult> {
@@ -301,7 +329,7 @@ async function ensureDokploySetup(
301
329
  api,
302
330
  existingConfig.projectId,
303
331
  environmentId,
304
- dockerConfig.imageName || 'app',
332
+ dockerConfig.appName!,
305
333
  services,
306
334
  existingUrls,
307
335
  );
@@ -323,7 +351,7 @@ async function ensureDokploySetup(
323
351
 
324
352
  // Step 3: Find or create project
325
353
  logger.log('\n📁 Looking for project...');
326
- const projectName = dockerConfig.imageName || 'app';
354
+ const projectName = dockerConfig.projectName!;
327
355
  const projects = await api.listProjects();
328
356
  let project = projects.find(
329
357
  (p) => p.name.toLowerCase() === projectName.toLowerCase(),
@@ -369,7 +397,7 @@ async function ensureDokploySetup(
369
397
 
370
398
  // Step 5: Find or create application
371
399
  logger.log('\n📦 Looking for application...');
372
- const appName = dockerConfig.imageName || projectName;
400
+ const appName = dockerConfig.appName!;
373
401
 
374
402
  let applicationId: string;
375
403
 
@@ -502,7 +530,7 @@ async function ensureDokploySetup(
502
530
  api,
503
531
  project.projectId,
504
532
  environmentId,
505
- dockerConfig.imageName || 'app',
533
+ dockerConfig.appName!,
506
534
  services,
507
535
  existingUrls,
508
536
  );
@@ -541,7 +569,7 @@ export async function deployCommand(
541
569
 
542
570
  // Resolve docker config for image reference
543
571
  const dockerConfig = resolveDockerConfig(config);
544
- const imageName = dockerConfig.imageName ?? 'app';
572
+ const imageName = dockerConfig.imageName!;
545
573
  const registry = dockerConfig.registry;
546
574
  const imageRef = registry
547
575
  ? `${registry}/${imageName}:${imageTag}`
@@ -594,12 +622,27 @@ export async function deployCommand(
594
622
  }
595
623
 
596
624
  let updated = false;
625
+ // URL fields go to secrets.urls, individual params go to secrets.custom
626
+ const urlFields = ['DATABASE_URL', 'REDIS_URL', 'RABBITMQ_URL'] as const;
627
+
597
628
  for (const [key, value] of Object.entries(setupResult.serviceUrls)) {
598
- const urlKey = key as keyof typeof secrets.urls;
599
- if (value && !secrets.urls[urlKey] && !secrets.custom[key]) {
600
- secrets.urls[urlKey] = value;
601
- logger.log(` Saved ${key} to secrets`);
602
- updated = true;
629
+ if (!value) continue;
630
+
631
+ if (urlFields.includes(key as (typeof urlFields)[number])) {
632
+ // URL fields
633
+ const urlKey = key as keyof typeof secrets.urls;
634
+ if (!secrets.urls[urlKey]) {
635
+ secrets.urls[urlKey] = value;
636
+ logger.log(` Saved ${key} to secrets.urls`);
637
+ updated = true;
638
+ }
639
+ } else {
640
+ // Individual parameters (HOST, PORT, NAME, USER, PASSWORD)
641
+ if (!secrets.custom[key]) {
642
+ secrets.custom[key] = value;
643
+ logger.log(` Saved ${key} to secrets.custom`);
644
+ updated = true;
645
+ }
603
646
  }
604
647
  }
605
648
  if (updated) {
@@ -31,8 +31,12 @@ export interface DeployResult {
31
31
  export interface DockerDeployConfig {
32
32
  /** Container registry URL */
33
33
  registry?: string;
34
- /** Image name (default: from package.json) */
34
+ /** Image name for Docker (default: from root package.json) */
35
35
  imageName?: string;
36
+ /** Project name for Dokploy (default: from root package.json) */
37
+ projectName?: string;
38
+ /** App name within Dokploy project (default: from cwd package.json) */
39
+ appName?: string;
36
40
  }
37
41
 
38
42
  /** Dokploy provider configuration */
@@ -1 +0,0 @@
1
- {"version":3,"file":"bundler-BjholBlA.cjs","names":["constructs: Construct[]","DOCKER_SERVICE_ENV_VARS: Record<string, Record<string, string>>","options: BundleOptions","masterKey: string | undefined"],"sources":["../src/build/bundler.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { mkdir, rename, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { Construct } from '@geekmidas/constructs';\n\nexport interface BundleOptions {\n\t/** Entry point file (e.g., .gkm/server/server.ts) */\n\tentryPoint: string;\n\t/** Output directory for bundled files */\n\toutputDir: string;\n\t/** Minify the output (default: true) */\n\tminify: boolean;\n\t/** Generate sourcemaps (default: false) */\n\tsourcemap: boolean;\n\t/** Packages to exclude from bundling */\n\texternal: string[];\n\t/** Stage for secrets injection (optional) */\n\tstage?: string;\n\t/** Constructs to validate environment variables for */\n\tconstructs?: Construct[];\n\t/** Docker compose services configured (for auto-populating env vars) */\n\tdockerServices?: {\n\t\tpostgres?: boolean;\n\t\tredis?: boolean;\n\t\trabbitmq?: boolean;\n\t};\n}\n\nexport interface BundleResult {\n\t/** Path to the bundled output */\n\toutputPath: string;\n\t/** Ephemeral master key for deployment (only if stage was provided) */\n\tmasterKey?: string;\n}\n\n/**\n * Collect all required environment variables from constructs.\n * Uses the SnifferEnvironmentParser to detect which env vars each service needs.\n *\n * @param constructs - Array of constructs to analyze\n * @returns Deduplicated array of required environment variable names\n */\nasync function collectRequiredEnvVars(\n\tconstructs: Construct[],\n): Promise<string[]> {\n\tconst allEnvVars = new Set<string>();\n\n\tfor (const construct of constructs) {\n\t\tconst envVars = await construct.getEnvironment();\n\t\tenvVars.forEach((v) => allEnvVars.add(v));\n\t}\n\n\treturn Array.from(allEnvVars).sort();\n}\n\n/**\n * Bundle the server application using tsdown\n *\n * @param options - Bundle configuration options\n * @returns Bundle result with output path and optional master key\n */\n/** Default env var values for docker compose services */\nconst DOCKER_SERVICE_ENV_VARS: Record<string, Record<string, string>> = {\n\tpostgres: {\n\t\tDATABASE_URL: 'postgresql://postgres:postgres@postgres:5432/app',\n\t},\n\tredis: {\n\t\tREDIS_URL: 'redis://redis:6379',\n\t},\n\trabbitmq: {\n\t\tRABBITMQ_URL: 'amqp://rabbitmq:5672',\n\t},\n};\n\nexport async function bundleServer(\n\toptions: BundleOptions,\n): Promise<BundleResult> {\n\tconst {\n\t\tentryPoint,\n\t\toutputDir,\n\t\tminify,\n\t\tsourcemap,\n\t\texternal,\n\t\tstage,\n\t\tconstructs,\n\t\tdockerServices,\n\t} = options;\n\n\t// Ensure output directory exists\n\tawait mkdir(outputDir, { recursive: true });\n\n\t// Build command-line arguments for tsdown\n\tconst args = [\n\t\t'npx',\n\t\t'tsdown',\n\t\tentryPoint,\n\t\t'--no-config', // Don't use any config file from workspace\n\t\t'--out-dir',\n\t\toutputDir,\n\t\t'--format',\n\t\t'esm',\n\t\t'--platform',\n\t\t'node',\n\t\t'--target',\n\t\t'node22',\n\t\t'--clean',\n\t];\n\n\tif (minify) {\n\t\targs.push('--minify');\n\t}\n\n\tif (sourcemap) {\n\t\targs.push('--sourcemap');\n\t}\n\n\t// Add external packages\n\tfor (const ext of external) {\n\t\targs.push('--external', ext);\n\t}\n\n\t// Always exclude node: builtins\n\targs.push('--external', 'node:*');\n\n\t// Handle secrets injection if stage is provided\n\tlet masterKey: string | undefined;\n\n\tif (stage) {\n\t\tconst {\n\t\t\treadStageSecrets,\n\t\t\ttoEmbeddableSecrets,\n\t\t\tvalidateEnvironmentVariables,\n\t\t} = await import('../secrets/storage');\n\t\tconst { encryptSecrets, generateDefineOptions } = await import(\n\t\t\t'../secrets/encryption'\n\t\t);\n\n\t\tconst secrets = await readStageSecrets(stage);\n\n\t\tif (!secrets) {\n\t\t\tthrow new Error(\n\t\t\t\t`No secrets found for stage \"${stage}\". Run \"gkm secrets:init --stage ${stage}\" first.`,\n\t\t\t);\n\t\t}\n\n\t\t// Auto-populate env vars from docker compose services\n\t\tif (dockerServices) {\n\t\t\tfor (const [service, enabled] of Object.entries(dockerServices)) {\n\t\t\t\tif (enabled && DOCKER_SERVICE_ENV_VARS[service]) {\n\t\t\t\t\tfor (const [envVar, defaultValue] of Object.entries(\n\t\t\t\t\t\tDOCKER_SERVICE_ENV_VARS[service],\n\t\t\t\t\t)) {\n\t\t\t\t\t\t// Check if not already in urls or custom\n\t\t\t\t\t\tconst urlKey = envVar as keyof typeof secrets.urls;\n\t\t\t\t\t\tif (!secrets.urls[urlKey] && !secrets.custom[envVar]) {\n\t\t\t\t\t\t\tsecrets.urls[urlKey] = defaultValue;\n\t\t\t\t\t\t\tconsole.log(` Auto-populated ${envVar} from docker compose`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Validate environment variables if constructs are provided\n\t\tif (constructs && constructs.length > 0) {\n\t\t\tconsole.log(' Analyzing environment variable requirements...');\n\t\t\tconst requiredVars = await collectRequiredEnvVars(constructs);\n\n\t\t\tif (requiredVars.length > 0) {\n\t\t\t\tconst validation = validateEnvironmentVariables(requiredVars, secrets);\n\n\t\t\t\tif (!validation.valid) {\n\t\t\t\t\tconst errorMessage = [\n\t\t\t\t\t\t`Missing environment variables for stage \"${stage}\":`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t...validation.missing.map((v) => ` ❌ ${v}`),\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t'To fix this, either:',\n\t\t\t\t\t\t` 1. Add the missing variables to .gkm/secrets/${stage}.json using:`,\n\t\t\t\t\t\t` gkm secrets:set <KEY> <VALUE> --stage ${stage}`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t` 2. Or import from a JSON file:`,\n\t\t\t\t\t\t` gkm secrets:import secrets.json --stage ${stage}`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t'Required variables:',\n\t\t\t\t\t\t...validation.required.map((v) =>\n\t\t\t\t\t\t\tvalidation.missing.includes(v) ? ` ❌ ${v}` : ` ✓ ${v}`,\n\t\t\t\t\t\t),\n\t\t\t\t\t].join('\\n');\n\n\t\t\t\t\tthrow new Error(errorMessage);\n\t\t\t\t}\n\n\t\t\t\tconsole.log(\n\t\t\t\t\t` ✓ All ${requiredVars.length} required environment variables found`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Convert to embeddable format and encrypt\n\t\tconst embeddable = toEmbeddableSecrets(secrets);\n\t\tconst encrypted = encryptSecrets(embeddable);\n\t\tmasterKey = encrypted.masterKey;\n\n\t\t// Add define options for build-time injection using tsdown's --env.* format\n\t\tconst defines = generateDefineOptions(encrypted);\n\t\tfor (const [key, value] of Object.entries(defines)) {\n\t\t\targs.push(`--env.${key}`, value);\n\t\t}\n\n\t\tconsole.log(` Secrets encrypted for stage \"${stage}\"`);\n\t}\n\n\tconst mjsOutput = join(outputDir, 'server.mjs');\n\n\ttry {\n\t\t// Run tsdown with command-line arguments\n\t\t// Use spawnSync with args array to avoid shell escaping issues with --define values\n\t\t// args is always populated with ['npx', 'tsdown', ...] so cmd is never undefined\n\t\tconst [cmd, ...cmdArgs] = args as [string, ...string[]];\n\t\tconst result = spawnSync(cmd, cmdArgs, {\n\t\t\tcwd: process.cwd(),\n\t\t\tstdio: 'inherit',\n\t\t\tshell: process.platform === 'win32', // Only use shell on Windows for npx resolution\n\t\t});\n\n\t\tif (result.error) {\n\t\t\tthrow result.error;\n\t\t}\n\t\tif (result.status !== 0) {\n\t\t\tthrow new Error(`tsdown exited with code ${result.status}`);\n\t\t}\n\n\t\t// Rename output to .mjs for explicit ESM\n\t\t// tsdown outputs as server.js for ESM format\n\t\tconst jsOutput = join(outputDir, 'server.js');\n\n\t\tif (existsSync(jsOutput)) {\n\t\t\tawait rename(jsOutput, mjsOutput);\n\t\t}\n\n\t\t// Add shebang to the bundled file\n\t\tconst { readFile } = await import('node:fs/promises');\n\t\tconst content = await readFile(mjsOutput, 'utf-8');\n\t\tif (!content.startsWith('#!')) {\n\t\t\tawait writeFile(mjsOutput, `#!/usr/bin/env node\\n${content}`);\n\t\t}\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Failed to bundle server: ${error instanceof Error ? error.message : 'Unknown error'}`,\n\t\t);\n\t}\n\n\treturn {\n\t\toutputPath: mjsOutput,\n\t\tmasterKey,\n\t};\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"bundler-DWctKN1z.mjs","names":["constructs: Construct[]","DOCKER_SERVICE_ENV_VARS: Record<string, Record<string, string>>","options: BundleOptions","masterKey: string | undefined"],"sources":["../src/build/bundler.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { mkdir, rename, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { Construct } from '@geekmidas/constructs';\n\nexport interface BundleOptions {\n\t/** Entry point file (e.g., .gkm/server/server.ts) */\n\tentryPoint: string;\n\t/** Output directory for bundled files */\n\toutputDir: string;\n\t/** Minify the output (default: true) */\n\tminify: boolean;\n\t/** Generate sourcemaps (default: false) */\n\tsourcemap: boolean;\n\t/** Packages to exclude from bundling */\n\texternal: string[];\n\t/** Stage for secrets injection (optional) */\n\tstage?: string;\n\t/** Constructs to validate environment variables for */\n\tconstructs?: Construct[];\n\t/** Docker compose services configured (for auto-populating env vars) */\n\tdockerServices?: {\n\t\tpostgres?: boolean;\n\t\tredis?: boolean;\n\t\trabbitmq?: boolean;\n\t};\n}\n\nexport interface BundleResult {\n\t/** Path to the bundled output */\n\toutputPath: string;\n\t/** Ephemeral master key for deployment (only if stage was provided) */\n\tmasterKey?: string;\n}\n\n/**\n * Collect all required environment variables from constructs.\n * Uses the SnifferEnvironmentParser to detect which env vars each service needs.\n *\n * @param constructs - Array of constructs to analyze\n * @returns Deduplicated array of required environment variable names\n */\nasync function collectRequiredEnvVars(\n\tconstructs: Construct[],\n): Promise<string[]> {\n\tconst allEnvVars = new Set<string>();\n\n\tfor (const construct of constructs) {\n\t\tconst envVars = await construct.getEnvironment();\n\t\tenvVars.forEach((v) => allEnvVars.add(v));\n\t}\n\n\treturn Array.from(allEnvVars).sort();\n}\n\n/**\n * Bundle the server application using tsdown\n *\n * @param options - Bundle configuration options\n * @returns Bundle result with output path and optional master key\n */\n/** Default env var values for docker compose services */\nconst DOCKER_SERVICE_ENV_VARS: Record<string, Record<string, string>> = {\n\tpostgres: {\n\t\tDATABASE_URL: 'postgresql://postgres:postgres@postgres:5432/app',\n\t},\n\tredis: {\n\t\tREDIS_URL: 'redis://redis:6379',\n\t},\n\trabbitmq: {\n\t\tRABBITMQ_URL: 'amqp://rabbitmq:5672',\n\t},\n};\n\nexport async function bundleServer(\n\toptions: BundleOptions,\n): Promise<BundleResult> {\n\tconst {\n\t\tentryPoint,\n\t\toutputDir,\n\t\tminify,\n\t\tsourcemap,\n\t\texternal,\n\t\tstage,\n\t\tconstructs,\n\t\tdockerServices,\n\t} = options;\n\n\t// Ensure output directory exists\n\tawait mkdir(outputDir, { recursive: true });\n\n\t// Build command-line arguments for tsdown\n\tconst args = [\n\t\t'npx',\n\t\t'tsdown',\n\t\tentryPoint,\n\t\t'--no-config', // Don't use any config file from workspace\n\t\t'--out-dir',\n\t\toutputDir,\n\t\t'--format',\n\t\t'esm',\n\t\t'--platform',\n\t\t'node',\n\t\t'--target',\n\t\t'node22',\n\t\t'--clean',\n\t];\n\n\tif (minify) {\n\t\targs.push('--minify');\n\t}\n\n\tif (sourcemap) {\n\t\targs.push('--sourcemap');\n\t}\n\n\t// Add external packages\n\tfor (const ext of external) {\n\t\targs.push('--external', ext);\n\t}\n\n\t// Always exclude node: builtins\n\targs.push('--external', 'node:*');\n\n\t// Handle secrets injection if stage is provided\n\tlet masterKey: string | undefined;\n\n\tif (stage) {\n\t\tconst {\n\t\t\treadStageSecrets,\n\t\t\ttoEmbeddableSecrets,\n\t\t\tvalidateEnvironmentVariables,\n\t\t} = await import('../secrets/storage');\n\t\tconst { encryptSecrets, generateDefineOptions } = await import(\n\t\t\t'../secrets/encryption'\n\t\t);\n\n\t\tconst secrets = await readStageSecrets(stage);\n\n\t\tif (!secrets) {\n\t\t\tthrow new Error(\n\t\t\t\t`No secrets found for stage \"${stage}\". Run \"gkm secrets:init --stage ${stage}\" first.`,\n\t\t\t);\n\t\t}\n\n\t\t// Auto-populate env vars from docker compose services\n\t\tif (dockerServices) {\n\t\t\tfor (const [service, enabled] of Object.entries(dockerServices)) {\n\t\t\t\tif (enabled && DOCKER_SERVICE_ENV_VARS[service]) {\n\t\t\t\t\tfor (const [envVar, defaultValue] of Object.entries(\n\t\t\t\t\t\tDOCKER_SERVICE_ENV_VARS[service],\n\t\t\t\t\t)) {\n\t\t\t\t\t\t// Check if not already in urls or custom\n\t\t\t\t\t\tconst urlKey = envVar as keyof typeof secrets.urls;\n\t\t\t\t\t\tif (!secrets.urls[urlKey] && !secrets.custom[envVar]) {\n\t\t\t\t\t\t\tsecrets.urls[urlKey] = defaultValue;\n\t\t\t\t\t\t\tconsole.log(` Auto-populated ${envVar} from docker compose`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Validate environment variables if constructs are provided\n\t\tif (constructs && constructs.length > 0) {\n\t\t\tconsole.log(' Analyzing environment variable requirements...');\n\t\t\tconst requiredVars = await collectRequiredEnvVars(constructs);\n\n\t\t\tif (requiredVars.length > 0) {\n\t\t\t\tconst validation = validateEnvironmentVariables(requiredVars, secrets);\n\n\t\t\t\tif (!validation.valid) {\n\t\t\t\t\tconst errorMessage = [\n\t\t\t\t\t\t`Missing environment variables for stage \"${stage}\":`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t...validation.missing.map((v) => ` ❌ ${v}`),\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t'To fix this, either:',\n\t\t\t\t\t\t` 1. Add the missing variables to .gkm/secrets/${stage}.json using:`,\n\t\t\t\t\t\t` gkm secrets:set <KEY> <VALUE> --stage ${stage}`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t` 2. Or import from a JSON file:`,\n\t\t\t\t\t\t` gkm secrets:import secrets.json --stage ${stage}`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t'Required variables:',\n\t\t\t\t\t\t...validation.required.map((v) =>\n\t\t\t\t\t\t\tvalidation.missing.includes(v) ? ` ❌ ${v}` : ` ✓ ${v}`,\n\t\t\t\t\t\t),\n\t\t\t\t\t].join('\\n');\n\n\t\t\t\t\tthrow new Error(errorMessage);\n\t\t\t\t}\n\n\t\t\t\tconsole.log(\n\t\t\t\t\t` ✓ All ${requiredVars.length} required environment variables found`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Convert to embeddable format and encrypt\n\t\tconst embeddable = toEmbeddableSecrets(secrets);\n\t\tconst encrypted = encryptSecrets(embeddable);\n\t\tmasterKey = encrypted.masterKey;\n\n\t\t// Add define options for build-time injection using tsdown's --env.* format\n\t\tconst defines = generateDefineOptions(encrypted);\n\t\tfor (const [key, value] of Object.entries(defines)) {\n\t\t\targs.push(`--env.${key}`, value);\n\t\t}\n\n\t\tconsole.log(` Secrets encrypted for stage \"${stage}\"`);\n\t}\n\n\tconst mjsOutput = join(outputDir, 'server.mjs');\n\n\ttry {\n\t\t// Run tsdown with command-line arguments\n\t\t// Use spawnSync with args array to avoid shell escaping issues with --define values\n\t\t// args is always populated with ['npx', 'tsdown', ...] so cmd is never undefined\n\t\tconst [cmd, ...cmdArgs] = args as [string, ...string[]];\n\t\tconst result = spawnSync(cmd, cmdArgs, {\n\t\t\tcwd: process.cwd(),\n\t\t\tstdio: 'inherit',\n\t\t\tshell: process.platform === 'win32', // Only use shell on Windows for npx resolution\n\t\t});\n\n\t\tif (result.error) {\n\t\t\tthrow result.error;\n\t\t}\n\t\tif (result.status !== 0) {\n\t\t\tthrow new Error(`tsdown exited with code ${result.status}`);\n\t\t}\n\n\t\t// Rename output to .mjs for explicit ESM\n\t\t// tsdown outputs as server.js for ESM format\n\t\tconst jsOutput = join(outputDir, 'server.js');\n\n\t\tif (existsSync(jsOutput)) {\n\t\t\tawait rename(jsOutput, mjsOutput);\n\t\t}\n\n\t\t// Add shebang to the bundled file\n\t\tconst { readFile } = await import('node:fs/promises');\n\t\tconst content = await readFile(mjsOutput, 'utf-8');\n\t\tif (!content.startsWith('#!')) {\n\t\t\tawait writeFile(mjsOutput, `#!/usr/bin/env node\\n${content}`);\n\t\t}\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Failed to bundle server: ${error instanceof Error ? error.message : 'Unknown error'}`,\n\t\t);\n\t}\n\n\treturn {\n\t\toutputPath: mjsOutput,\n\t\tmasterKey,\n\t};\n}\n"],"mappings":"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"}