@geekmidas/cli 0.14.0 → 0.16.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.
@@ -1,9 +1,37 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { mkdir, rename, writeFile } from "node:fs/promises";
4
- import { spawnSync } from "node:child_process";
4
+ import { execSync, spawnSync } from "node:child_process";
5
5
 
6
6
  //#region src/build/bundler.ts
7
+ const MIN_TSDOWN_VERSION = "0.11.0";
8
+ /**
9
+ * Check if tsdown is installed and meets minimum version requirement
10
+ */
11
+ function checkTsdownVersion() {
12
+ try {
13
+ const result = execSync("npx tsdown --version", {
14
+ encoding: "utf-8",
15
+ stdio: [
16
+ "pipe",
17
+ "pipe",
18
+ "pipe"
19
+ ]
20
+ });
21
+ const match = result.match(/tsdown\/(\d+\.\d+\.\d+)/);
22
+ if (match) {
23
+ const version = match[1];
24
+ const [major, minor] = version.split(".").map(Number);
25
+ const [minMajor, minMinor] = MIN_TSDOWN_VERSION.split(".").map(Number);
26
+ if (major < minMajor || major === minMajor && minor < minMinor) throw new Error(`tsdown version ${version} is too old. Please upgrade to ${MIN_TSDOWN_VERSION} or later:\n npm install -D tsdown@latest
27
+ # or
28
+ pnpm add -D tsdown@latest`);
29
+ }
30
+ } catch (error) {
31
+ if (error instanceof Error && error.message.includes("too old")) throw error;
32
+ throw new Error("tsdown is required for bundling. Please install it:\n npm install -D tsdown@latest\n # or\n pnpm add -D tsdown@latest");
33
+ }
34
+ }
7
35
  /**
8
36
  * Collect all required environment variables from constructs.
9
37
  * Uses the SnifferEnvironmentParser to detect which env vars each service needs.
@@ -33,6 +61,7 @@ const DOCKER_SERVICE_ENV_VARS = {
33
61
  };
34
62
  async function bundleServer(options) {
35
63
  const { entryPoint, outputDir, minify, sourcemap, external, stage, constructs, dockerServices } = options;
64
+ checkTsdownVersion();
36
65
  await mkdir(outputDir, { recursive: true });
37
66
  const args = [
38
67
  "npx",
@@ -55,10 +84,15 @@ async function bundleServer(options) {
55
84
  args.push("--external", "node:*");
56
85
  let masterKey;
57
86
  if (stage) {
58
- const { readStageSecrets, toEmbeddableSecrets, validateEnvironmentVariables } = await import("./storage-nkGIjeXt.mjs");
87
+ const { readStageSecrets, toEmbeddableSecrets, validateEnvironmentVariables, initStageSecrets, writeStageSecrets } = await import("./storage-nkGIjeXt.mjs");
59
88
  const { encryptSecrets, generateDefineOptions } = await import("./encryption-h4Nb6W-M.mjs");
60
- const secrets = await readStageSecrets(stage);
61
- if (!secrets) throw new Error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
89
+ let secrets = await readStageSecrets(stage);
90
+ if (!secrets) {
91
+ console.log(` Initializing secrets for stage "${stage}"...`);
92
+ secrets = initStageSecrets(stage);
93
+ await writeStageSecrets(secrets);
94
+ console.log(` ✓ Created .gkm/secrets/${stage}.json`);
95
+ }
62
96
  if (dockerServices) {
63
97
  for (const [service, enabled] of Object.entries(dockerServices)) if (enabled && DOCKER_SERVICE_ENV_VARS[service]) for (const [envVar, defaultValue] of Object.entries(DOCKER_SERVICE_ENV_VARS[service])) {
64
98
  const urlKey = envVar;
@@ -127,4 +161,4 @@ async function bundleServer(options) {
127
161
 
128
162
  //#endregion
129
163
  export { bundleServer };
130
- //# sourceMappingURL=bundler-DWctKN1z.mjs.map
164
+ //# sourceMappingURL=bundler-B6z6HEeh.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundler-B6z6HEeh.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 { execSync, 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\nconst MIN_TSDOWN_VERSION = '0.11.0';\n\n/**\n * Check if tsdown is installed and meets minimum version requirement\n */\nfunction checkTsdownVersion(): void {\n\ttry {\n\t\tconst result = execSync('npx tsdown --version', {\n\t\t\tencoding: 'utf-8',\n\t\t\tstdio: ['pipe', 'pipe', 'pipe'],\n\t\t});\n\t\t// Output format: \"tsdown/0.12.8 darwin-arm64 node-v22.21.1\"\n\t\tconst match = result.match(/tsdown\\/(\\d+\\.\\d+\\.\\d+)/);\n\t\tif (match) {\n\t\t\tconst version = match[1]!;\n\t\t\tconst [major, minor] = version.split('.').map(Number) as [number, number];\n\t\t\tconst [minMajor, minMinor] = MIN_TSDOWN_VERSION.split('.').map(\n\t\t\t\tNumber,\n\t\t\t) as [number, number];\n\n\t\t\tif (major < minMajor || (major === minMajor && minor < minMinor)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`tsdown version ${version} is too old. Please upgrade to ${MIN_TSDOWN_VERSION} or later:\\n` +\n\t\t\t\t\t\t' npm install -D tsdown@latest\\n' +\n\t\t\t\t\t\t' # or\\n' +\n\t\t\t\t\t\t' pnpm add -D tsdown@latest',\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t} catch (error) {\n\t\tif (error instanceof Error && error.message.includes('too old')) {\n\t\t\tthrow error;\n\t\t}\n\t\tthrow new Error(\n\t\t\t'tsdown is required for bundling. Please install it:\\n' +\n\t\t\t\t' npm install -D tsdown@latest\\n' +\n\t\t\t\t' # or\\n' +\n\t\t\t\t' pnpm add -D tsdown@latest',\n\t\t);\n\t}\n}\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// Check tsdown version first\n\tcheckTsdownVersion();\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\tinitStageSecrets,\n\t\t\twriteStageSecrets,\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\tlet secrets = await readStageSecrets(stage);\n\n\t\tif (!secrets) {\n\t\t\t// Auto-initialize secrets for the stage\n\t\t\tconsole.log(` Initializing secrets for stage \"${stage}\"...`);\n\t\t\tsecrets = initStageSecrets(stage);\n\t\t\tawait writeStageSecrets(secrets);\n\t\t\tconsole.log(` ✓ Created .gkm/secrets/${stage}.json`);\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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"}
@@ -5,6 +5,34 @@ const node_fs_promises = require_chunk.__toESM(require("node:fs/promises"));
5
5
  const node_child_process = require_chunk.__toESM(require("node:child_process"));
6
6
 
7
7
  //#region src/build/bundler.ts
8
+ const MIN_TSDOWN_VERSION = "0.11.0";
9
+ /**
10
+ * Check if tsdown is installed and meets minimum version requirement
11
+ */
12
+ function checkTsdownVersion() {
13
+ try {
14
+ const result = (0, node_child_process.execSync)("npx tsdown --version", {
15
+ encoding: "utf-8",
16
+ stdio: [
17
+ "pipe",
18
+ "pipe",
19
+ "pipe"
20
+ ]
21
+ });
22
+ const match = result.match(/tsdown\/(\d+\.\d+\.\d+)/);
23
+ if (match) {
24
+ const version = match[1];
25
+ const [major, minor] = version.split(".").map(Number);
26
+ const [minMajor, minMinor] = MIN_TSDOWN_VERSION.split(".").map(Number);
27
+ if (major < minMajor || major === minMajor && minor < minMinor) throw new Error(`tsdown version ${version} is too old. Please upgrade to ${MIN_TSDOWN_VERSION} or later:\n npm install -D tsdown@latest
28
+ # or
29
+ pnpm add -D tsdown@latest`);
30
+ }
31
+ } catch (error) {
32
+ if (error instanceof Error && error.message.includes("too old")) throw error;
33
+ throw new Error("tsdown is required for bundling. Please install it:\n npm install -D tsdown@latest\n # or\n pnpm add -D tsdown@latest");
34
+ }
35
+ }
8
36
  /**
9
37
  * Collect all required environment variables from constructs.
10
38
  * Uses the SnifferEnvironmentParser to detect which env vars each service needs.
@@ -34,6 +62,7 @@ const DOCKER_SERVICE_ENV_VARS = {
34
62
  };
35
63
  async function bundleServer(options) {
36
64
  const { entryPoint, outputDir, minify, sourcemap, external, stage, constructs, dockerServices } = options;
65
+ checkTsdownVersion();
37
66
  await (0, node_fs_promises.mkdir)(outputDir, { recursive: true });
38
67
  const args = [
39
68
  "npx",
@@ -56,10 +85,15 @@ async function bundleServer(options) {
56
85
  args.push("--external", "node:*");
57
86
  let masterKey;
58
87
  if (stage) {
59
- const { readStageSecrets, toEmbeddableSecrets, validateEnvironmentVariables } = await Promise.resolve().then(() => require("./storage-UfyTn7Zm.cjs"));
88
+ const { readStageSecrets, toEmbeddableSecrets, validateEnvironmentVariables, initStageSecrets, writeStageSecrets } = await Promise.resolve().then(() => require("./storage-UfyTn7Zm.cjs"));
60
89
  const { encryptSecrets, generateDefineOptions } = await Promise.resolve().then(() => require("./encryption-D7Efcdi9.cjs"));
61
- const secrets = await readStageSecrets(stage);
62
- if (!secrets) throw new Error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
90
+ let secrets = await readStageSecrets(stage);
91
+ if (!secrets) {
92
+ console.log(` Initializing secrets for stage "${stage}"...`);
93
+ secrets = initStageSecrets(stage);
94
+ await writeStageSecrets(secrets);
95
+ console.log(` ✓ Created .gkm/secrets/${stage}.json`);
96
+ }
63
97
  if (dockerServices) {
64
98
  for (const [service, enabled] of Object.entries(dockerServices)) if (enabled && DOCKER_SERVICE_ENV_VARS[service]) for (const [envVar, defaultValue] of Object.entries(DOCKER_SERVICE_ENV_VARS[service])) {
65
99
  const urlKey = envVar;
@@ -128,4 +162,4 @@ async function bundleServer(options) {
128
162
 
129
163
  //#endregion
130
164
  exports.bundleServer = bundleServer;
131
- //# sourceMappingURL=bundler-BjholBlA.cjs.map
165
+ //# sourceMappingURL=bundler-C74EKlNa.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundler-C74EKlNa.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 { execSync, 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\nconst MIN_TSDOWN_VERSION = '0.11.0';\n\n/**\n * Check if tsdown is installed and meets minimum version requirement\n */\nfunction checkTsdownVersion(): void {\n\ttry {\n\t\tconst result = execSync('npx tsdown --version', {\n\t\t\tencoding: 'utf-8',\n\t\t\tstdio: ['pipe', 'pipe', 'pipe'],\n\t\t});\n\t\t// Output format: \"tsdown/0.12.8 darwin-arm64 node-v22.21.1\"\n\t\tconst match = result.match(/tsdown\\/(\\d+\\.\\d+\\.\\d+)/);\n\t\tif (match) {\n\t\t\tconst version = match[1]!;\n\t\t\tconst [major, minor] = version.split('.').map(Number) as [number, number];\n\t\t\tconst [minMajor, minMinor] = MIN_TSDOWN_VERSION.split('.').map(\n\t\t\t\tNumber,\n\t\t\t) as [number, number];\n\n\t\t\tif (major < minMajor || (major === minMajor && minor < minMinor)) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`tsdown version ${version} is too old. Please upgrade to ${MIN_TSDOWN_VERSION} or later:\\n` +\n\t\t\t\t\t\t' npm install -D tsdown@latest\\n' +\n\t\t\t\t\t\t' # or\\n' +\n\t\t\t\t\t\t' pnpm add -D tsdown@latest',\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t} catch (error) {\n\t\tif (error instanceof Error && error.message.includes('too old')) {\n\t\t\tthrow error;\n\t\t}\n\t\tthrow new Error(\n\t\t\t'tsdown is required for bundling. Please install it:\\n' +\n\t\t\t\t' npm install -D tsdown@latest\\n' +\n\t\t\t\t' # or\\n' +\n\t\t\t\t' pnpm add -D tsdown@latest',\n\t\t);\n\t}\n}\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// Check tsdown version first\n\tcheckTsdownVersion();\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\tinitStageSecrets,\n\t\t\twriteStageSecrets,\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\tlet secrets = await readStageSecrets(stage);\n\n\t\tif (!secrets) {\n\t\t\t// Auto-initialize secrets for the stage\n\t\t\tconsole.log(` Initializing secrets for stage \"${stage}\"...`);\n\t\t\tsecrets = initStageSecrets(stage);\n\t\t\tawait writeStageSecrets(secrets);\n\t\t\tconsole.log(` ✓ Created .gkm/secrets/${stage}.json`);\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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"}
package/dist/index.cjs CHANGED
@@ -25,7 +25,7 @@ const node_crypto = require_chunk.__toESM(require("node:crypto"));
25
25
 
26
26
  //#region package.json
27
27
  var name = "@geekmidas/cli";
28
- var version = "0.14.0";
28
+ var version = "0.16.0";
29
29
  var description = "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs";
30
30
  var private$1 = false;
31
31
  var type = "module";
@@ -1293,7 +1293,7 @@ async function buildForProvider(provider, context, rootOutputDir, endpointGenera
1293
1293
  let masterKey;
1294
1294
  if (context.production?.bundle && !skipBundle) {
1295
1295
  logger$6.log(`\n📦 Bundling production server...`);
1296
- const { bundleServer } = await Promise.resolve().then(() => require("./bundler-BjholBlA.cjs"));
1296
+ const { bundleServer } = await Promise.resolve().then(() => require("./bundler-C74EKlNa.cjs"));
1297
1297
  const allConstructs = [
1298
1298
  ...endpoints.map((e) => e.construct),
1299
1299
  ...functions.map((f) => f.construct),
@@ -2080,6 +2080,36 @@ async function pushDockerImage(imageName, options) {
2080
2080
 
2081
2081
  //#endregion
2082
2082
  //#region src/deploy/docker.ts
2083
+ /**
2084
+ * Get app name from package.json in the current working directory
2085
+ * Used for Dokploy app/project naming
2086
+ */
2087
+ function getAppNameFromCwd() {
2088
+ const packageJsonPath = (0, node_path.join)(process.cwd(), "package.json");
2089
+ if (!(0, node_fs.existsSync)(packageJsonPath)) return void 0;
2090
+ try {
2091
+ const pkg = JSON.parse((0, node_fs.readFileSync)(packageJsonPath, "utf-8"));
2092
+ if (pkg.name) return pkg.name.replace(/^@[^/]+\//, "");
2093
+ } catch {}
2094
+ return void 0;
2095
+ }
2096
+ /**
2097
+ * Get app name from package.json adjacent to the lockfile (project root)
2098
+ * Used for Docker image naming
2099
+ */
2100
+ function getAppNameFromPackageJson() {
2101
+ const cwd = process.cwd();
2102
+ const lockfilePath = findLockfilePath(cwd);
2103
+ if (!lockfilePath) return void 0;
2104
+ const projectRoot = (0, node_path.dirname)(lockfilePath);
2105
+ const packageJsonPath = (0, node_path.join)(projectRoot, "package.json");
2106
+ if (!(0, node_fs.existsSync)(packageJsonPath)) return void 0;
2107
+ try {
2108
+ const pkg = JSON.parse((0, node_fs.readFileSync)(packageJsonPath, "utf-8"));
2109
+ if (pkg.name) return pkg.name.replace(/^@[^/]+\//, "");
2110
+ } catch {}
2111
+ return void 0;
2112
+ }
2083
2113
  const logger$4 = console;
2084
2114
  /**
2085
2115
  * Get the full image reference
@@ -2144,7 +2174,7 @@ async function pushImage(imageRef) {
2144
2174
  */
2145
2175
  async function deployDocker(options) {
2146
2176
  const { stage, tag, skipPush, masterKey, config } = options;
2147
- const imageName = config.imageName ?? "app";
2177
+ const imageName = config.imageName;
2148
2178
  const imageRef = getImageRef(config.registry, imageName, tag);
2149
2179
  await buildImage(imageRef);
2150
2180
  if (!skipPush) if (!config.registry) logger$4.warn("\n⚠️ No registry configured. Use --skip-push or configure docker.registry in gkm.config.ts");
@@ -2166,11 +2196,19 @@ async function deployDocker(options) {
2166
2196
  }
2167
2197
  /**
2168
2198
  * Resolve Docker deploy config from gkm config
2199
+ * - imageName: from config, or cwd package.json, or 'app' (for Docker image)
2200
+ * - projectName: from root package.json, or 'app' (for Dokploy project)
2201
+ * - appName: from cwd package.json, or projectName (for Dokploy app within project)
2169
2202
  */
2170
2203
  function resolveDockerConfig(config) {
2204
+ const projectName = getAppNameFromPackageJson() ?? "app";
2205
+ const appName = getAppNameFromCwd() ?? projectName;
2206
+ const imageName = config.docker?.imageName ?? appName;
2171
2207
  return {
2172
2208
  registry: config.docker?.registry,
2173
- imageName: config.docker?.imageName
2209
+ imageName,
2210
+ projectName,
2211
+ appName
2174
2212
  };
2175
2213
  }
2176
2214
 
@@ -2498,8 +2536,13 @@ async function provisionServices(api, projectId, environmentId, appName, service
2498
2536
  logger$1.log(` ✓ Created PostgreSQL: ${postgres.postgresId}`);
2499
2537
  await api.deployPostgres(postgres.postgresId);
2500
2538
  logger$1.log(" ✓ PostgreSQL deployed");
2539
+ serviceUrls.DATABASE_HOST = postgres.appName;
2540
+ serviceUrls.DATABASE_PORT = "5432";
2541
+ serviceUrls.DATABASE_NAME = postgres.databaseName;
2542
+ serviceUrls.DATABASE_USER = postgres.databaseUser;
2543
+ serviceUrls.DATABASE_PASSWORD = postgres.databasePassword;
2501
2544
  serviceUrls.DATABASE_URL = `postgresql://${postgres.databaseUser}:${postgres.databasePassword}@${postgres.appName}:5432/${postgres.databaseName}`;
2502
- logger$1.log(` ✓ DATABASE_URL configured`);
2545
+ logger$1.log(` ✓ Database credentials configured`);
2503
2546
  } catch (error) {
2504
2547
  const message = error instanceof Error ? error.message : "Unknown error";
2505
2548
  if (message.includes("already exists") || message.includes("duplicate")) logger$1.log(` ℹ PostgreSQL already exists`);
@@ -2517,9 +2560,12 @@ async function provisionServices(api, projectId, environmentId, appName, service
2517
2560
  logger$1.log(` ✓ Created Redis: ${redis.redisId}`);
2518
2561
  await api.deployRedis(redis.redisId);
2519
2562
  logger$1.log(" ✓ Redis deployed");
2563
+ serviceUrls.REDIS_HOST = redis.appName;
2564
+ serviceUrls.REDIS_PORT = "6379";
2565
+ if (redis.databasePassword) serviceUrls.REDIS_PASSWORD = redis.databasePassword;
2520
2566
  const password = redis.databasePassword ? `:${redis.databasePassword}@` : "";
2521
2567
  serviceUrls.REDIS_URL = `redis://${password}${redis.appName}:6379`;
2522
- logger$1.log(` ✓ REDIS_URL configured`);
2568
+ logger$1.log(` ✓ Redis credentials configured`);
2523
2569
  } catch (error) {
2524
2570
  const message = error instanceof Error ? error.message : "Unknown error";
2525
2571
  if (message.includes("already exists") || message.includes("duplicate")) logger$1.log(` ℹ Redis already exists`);
@@ -2581,7 +2627,7 @@ async function ensureDokploySetup(config, dockerConfig, stage, services) {
2581
2627
  }
2582
2628
  const environmentId$1 = environment.environmentId;
2583
2629
  logger$1.log(` Services config: ${JSON.stringify(services)}, envId: ${environmentId$1}`);
2584
- const serviceUrls$1 = await provisionServices(api, existingConfig.projectId, environmentId$1, dockerConfig.imageName || "app", services, existingUrls);
2630
+ const serviceUrls$1 = await provisionServices(api, existingConfig.projectId, environmentId$1, dockerConfig.appName, services, existingUrls);
2585
2631
  return {
2586
2632
  config: {
2587
2633
  endpoint: existingConfig.endpoint,
@@ -2597,7 +2643,7 @@ async function ensureDokploySetup(config, dockerConfig, stage, services) {
2597
2643
  }
2598
2644
  }
2599
2645
  logger$1.log("\n📁 Looking for project...");
2600
- const projectName = dockerConfig.imageName || "app";
2646
+ const projectName = dockerConfig.projectName;
2601
2647
  const projects = await api.listProjects();
2602
2648
  let project = projects.find((p) => p.name.toLowerCase() === projectName.toLowerCase());
2603
2649
  let environmentId;
@@ -2628,7 +2674,7 @@ async function ensureDokploySetup(config, dockerConfig, stage, services) {
2628
2674
  logger$1.log(` ✓ Using environment: ${stage}`);
2629
2675
  }
2630
2676
  logger$1.log("\n📦 Looking for application...");
2631
- const appName = dockerConfig.imageName || projectName;
2677
+ const appName = dockerConfig.appName;
2632
2678
  let applicationId;
2633
2679
  if (existingConfig && typeof existingConfig !== "boolean" && existingConfig.applicationId) {
2634
2680
  applicationId = existingConfig.applicationId;
@@ -2697,7 +2743,7 @@ async function ensureDokploySetup(config, dockerConfig, stage, services) {
2697
2743
  logger$1.log(` Project: ${project.projectId}`);
2698
2744
  logger$1.log(` Application: ${applicationId}`);
2699
2745
  if (registryId) logger$1.log(` Registry: ${registryId}`);
2700
- const serviceUrls = await provisionServices(api, project.projectId, environmentId, dockerConfig.imageName || "app", services, existingUrls);
2746
+ const serviceUrls = await provisionServices(api, project.projectId, environmentId, dockerConfig.appName, services, existingUrls);
2701
2747
  return {
2702
2748
  config: dokployConfig,
2703
2749
  serviceUrls
@@ -2721,7 +2767,7 @@ async function deployCommand(options) {
2721
2767
  const imageTag = tag ?? generateTag(stage);
2722
2768
  logger$1.log(` Tag: ${imageTag}`);
2723
2769
  const dockerConfig = resolveDockerConfig(config);
2724
- const imageName = dockerConfig.imageName ?? "app";
2770
+ const imageName = dockerConfig.imageName;
2725
2771
  const registry = dockerConfig.registry;
2726
2772
  const imageRef = registry ? `${registry}/${imageName}:${imageTag}` : `${imageName}:${imageTag}`;
2727
2773
  let dokployConfig;
@@ -2749,11 +2795,23 @@ async function deployCommand(options) {
2749
2795
  secrets = initStageSecrets(stage);
2750
2796
  }
2751
2797
  let updated = false;
2798
+ const urlFields = [
2799
+ "DATABASE_URL",
2800
+ "REDIS_URL",
2801
+ "RABBITMQ_URL"
2802
+ ];
2752
2803
  for (const [key, value] of Object.entries(setupResult.serviceUrls)) {
2753
- const urlKey = key;
2754
- if (value && !secrets.urls[urlKey] && !secrets.custom[key]) {
2755
- secrets.urls[urlKey] = value;
2756
- logger$1.log(` Saved ${key} to secrets`);
2804
+ if (!value) continue;
2805
+ if (urlFields.includes(key)) {
2806
+ const urlKey = key;
2807
+ if (!secrets.urls[urlKey]) {
2808
+ secrets.urls[urlKey] = value;
2809
+ logger$1.log(` Saved ${key} to secrets.urls`);
2810
+ updated = true;
2811
+ }
2812
+ } else if (!secrets.custom[key]) {
2813
+ secrets.custom[key] = value;
2814
+ logger$1.log(` Saved ${key} to secrets.custom`);
2757
2815
  updated = true;
2758
2816
  }
2759
2817
  }