@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.
- package/dist/{bundler-DWctKN1z.mjs → bundler-B6z6HEeh.mjs} +39 -5
- package/dist/bundler-B6z6HEeh.mjs.map +1 -0
- package/dist/{bundler-BjholBlA.cjs → bundler-C74EKlNa.cjs} +38 -4
- package/dist/bundler-C74EKlNa.cjs.map +1 -0
- package/dist/index.cjs +73 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +74 -16
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/build/__tests__/bundler.spec.ts +5 -3
- package/src/build/bundler.ts +54 -5
- package/src/deploy/__tests__/docker.spec.ts +44 -6
- package/src/deploy/__tests__/index.spec.ts +62 -0
- package/src/deploy/docker.ts +77 -2
- package/src/deploy/index.ts +63 -20
- package/src/deploy/types.ts +5 -1
- package/dist/bundler-BjholBlA.cjs.map +0 -1
- package/dist/bundler-DWctKN1z.mjs.map +0 -1
|
@@ -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
|
-
|
|
61
|
-
if (!secrets)
|
|
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-
|
|
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
|
-
|
|
62
|
-
if (!secrets)
|
|
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-
|
|
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.
|
|
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-
|
|
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
|
|
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
|
|
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(` ✓
|
|
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(` ✓
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
2754
|
-
if (
|
|
2755
|
-
|
|
2756
|
-
|
|
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
|
}
|