@geekmidas/cli 0.38.0 → 0.39.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/index.mjs CHANGED
@@ -26,7 +26,7 @@ import prompts from "prompts";
26
26
 
27
27
  //#region package.json
28
28
  var name = "@geekmidas/cli";
29
- var version = "0.38.0";
29
+ var version = "0.39.0";
30
30
  var description = "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs";
31
31
  var private$1 = false;
32
32
  var type = "module";
@@ -1541,19 +1541,43 @@ function findSecretsRoot(startDir) {
1541
1541
  return startDir;
1542
1542
  }
1543
1543
  /**
1544
- * Create a wrapper script that injects secrets before importing the entry file.
1545
- * @internal Exported for testing
1544
+ * Generate the credentials injection code snippet.
1545
+ * This is the common logic used by both entry wrapper and exec preload.
1546
+ * @internal
1546
1547
  */
1547
- async function createEntryWrapper(wrapperPath, entryPath, secretsJsonPath) {
1548
- const credentialsInjection = secretsJsonPath ? `import { Credentials } from '@geekmidas/envkit/credentials';
1548
+ function generateCredentialsInjection(secretsJsonPath) {
1549
+ return `import { Credentials } from '@geekmidas/envkit/credentials';
1549
1550
  import { existsSync, readFileSync } from 'node:fs';
1550
1551
 
1551
- // Inject dev secrets into Credentials (before app import)
1552
+ // Inject dev secrets into Credentials
1552
1553
  const secretsPath = '${secretsJsonPath}';
1553
1554
  if (existsSync(secretsPath)) {
1554
- Object.assign(Credentials, JSON.parse(readFileSync(secretsPath, 'utf-8')));
1555
+ const secrets = JSON.parse(readFileSync(secretsPath, 'utf-8'));
1556
+ Object.assign(Credentials, secrets);
1557
+ // Debug: uncomment to verify preload is running
1558
+ // console.log('[gkm preload] Injected', Object.keys(secrets).length, 'credentials');
1555
1559
  }
1556
-
1560
+ `;
1561
+ }
1562
+ /**
1563
+ * Create a preload script that injects secrets into Credentials.
1564
+ * Used by `gkm exec` to inject secrets before running any command.
1565
+ * @internal Exported for testing
1566
+ */
1567
+ async function createCredentialsPreload(preloadPath, secretsJsonPath) {
1568
+ const content = `/**
1569
+ * Credentials preload generated by 'gkm exec'
1570
+ * This file is loaded via NODE_OPTIONS="--import <path>"
1571
+ */
1572
+ ${generateCredentialsInjection(secretsJsonPath)}`;
1573
+ await writeFile(preloadPath, content);
1574
+ }
1575
+ /**
1576
+ * Create a wrapper script that injects secrets before importing the entry file.
1577
+ * @internal Exported for testing
1578
+ */
1579
+ async function createEntryWrapper(wrapperPath, entryPath, secretsJsonPath) {
1580
+ const credentialsInjection = secretsJsonPath ? `${generateCredentialsInjection(secretsJsonPath)}
1557
1581
  ` : "";
1558
1582
  const content = `#!/usr/bin/env node
1559
1583
  /**
@@ -1859,6 +1883,59 @@ start({
1859
1883
  await fsWriteFile(serverPath, content);
1860
1884
  }
1861
1885
  };
1886
+ /**
1887
+ * Run a command with secrets injected into Credentials.
1888
+ * Uses Node's --import flag to preload a script that populates Credentials
1889
+ * before the command loads any modules that depend on them.
1890
+ *
1891
+ * @example
1892
+ * ```bash
1893
+ * gkm exec -- npx @better-auth/cli migrate
1894
+ * gkm exec -- npx prisma migrate dev
1895
+ * ```
1896
+ */
1897
+ async function execCommand(commandArgs, options = {}) {
1898
+ const cwd = options.cwd ?? process.cwd();
1899
+ if (commandArgs.length === 0) throw new Error("No command specified. Usage: gkm exec -- <command>");
1900
+ const defaultEnv = loadEnvFiles(".env");
1901
+ if (defaultEnv.loaded.length > 0) logger$8.log(`šŸ“¦ Loaded env: ${defaultEnv.loaded.join(", ")}`);
1902
+ const { credentials, secretsJsonPath, appName } = await prepareEntryCredentials({ cwd });
1903
+ if (appName) logger$8.log(`šŸ“¦ App: ${appName}`);
1904
+ const secretCount = Object.keys(credentials).filter((k) => k !== "PORT").length;
1905
+ if (secretCount > 0) logger$8.log(`šŸ” Loaded ${secretCount} secret(s)`);
1906
+ const preloadDir = join(cwd, ".gkm");
1907
+ await mkdir(preloadDir, { recursive: true });
1908
+ const preloadPath = join(preloadDir, "credentials-preload.ts");
1909
+ await createCredentialsPreload(preloadPath, secretsJsonPath);
1910
+ const [cmd, ...args] = commandArgs;
1911
+ if (!cmd) throw new Error("No command specified");
1912
+ logger$8.log(`šŸš€ Running: ${commandArgs.join(" ")}`);
1913
+ const existingNodeOptions = process.env.NODE_OPTIONS ?? "";
1914
+ const tsxImport = "--import tsx";
1915
+ const preloadImport = `--import ${preloadPath}`;
1916
+ const nodeOptions = [
1917
+ existingNodeOptions,
1918
+ tsxImport,
1919
+ preloadImport
1920
+ ].filter(Boolean).join(" ");
1921
+ const child = spawn(cmd, args, {
1922
+ cwd,
1923
+ stdio: "inherit",
1924
+ env: {
1925
+ ...process.env,
1926
+ ...credentials,
1927
+ NODE_OPTIONS: nodeOptions
1928
+ }
1929
+ });
1930
+ const exitCode = await new Promise((resolve$1) => {
1931
+ child.on("close", (code) => resolve$1(code ?? 0));
1932
+ child.on("error", (error) => {
1933
+ logger$8.error(`Failed to run command: ${error.message}`);
1934
+ resolve$1(1);
1935
+ });
1936
+ });
1937
+ if (exitCode !== 0) process.exit(exitCode);
1938
+ }
1862
1939
 
1863
1940
  //#endregion
1864
1941
  //#region src/build/manifests.ts
@@ -1929,10 +2006,15 @@ const logger$6 = console;
1929
2006
  async function buildCommand(options) {
1930
2007
  const loadedConfig = await loadWorkspaceConfig();
1931
2008
  if (loadedConfig.type === "workspace") {
1932
- logger$6.log("šŸ“¦ Detected workspace configuration");
1933
- return workspaceBuildCommand(loadedConfig.workspace, options);
2009
+ const cwd = resolve(process.cwd());
2010
+ const workspaceRoot = resolve(loadedConfig.workspace.root);
2011
+ const isAtWorkspaceRoot = cwd === workspaceRoot;
2012
+ if (isAtWorkspaceRoot) {
2013
+ logger$6.log("šŸ“¦ Detected workspace configuration");
2014
+ return workspaceBuildCommand(loadedConfig.workspace, options);
2015
+ }
1934
2016
  }
1935
- const config$1 = await loadConfig();
2017
+ const config$1 = loadedConfig.type === "workspace" ? (await loadAppConfig()).gkmConfig : await loadConfig();
1936
2018
  const resolved = resolveProviders(config$1, options);
1937
2019
  const productionConfigFromGkm = getProductionConfigFromGkm(config$1);
1938
2020
  const production = normalizeProductionConfig(options.production ?? false, productionConfigFromGkm);
@@ -3376,26 +3458,22 @@ function getImageRef(registry, imageName, tag) {
3376
3458
  }
3377
3459
  /**
3378
3460
  * Build Docker image
3461
+ * @param imageRef - Full image reference (registry/name:tag)
3462
+ * @param appName - Name of the app (used for Dockerfile.{appName} in workspaces)
3379
3463
  */
3380
- async function buildImage(imageRef) {
3464
+ async function buildImage(imageRef, appName) {
3381
3465
  logger$4.log(`\nšŸ”Ø Building Docker image: ${imageRef}`);
3382
3466
  const cwd = process.cwd();
3383
- const inMonorepo = isMonorepo(cwd);
3384
- if (inMonorepo) logger$4.log(" Generating Dockerfile for monorepo (turbo prune)...");
3467
+ const lockfilePath = findLockfilePath(cwd);
3468
+ const lockfileDir = lockfilePath ? dirname(lockfilePath) : cwd;
3469
+ const inMonorepo = lockfileDir !== cwd;
3470
+ if (appName || inMonorepo) logger$4.log(" Generating Dockerfile for monorepo (turbo prune)...");
3385
3471
  else logger$4.log(" Generating Dockerfile...");
3386
3472
  await dockerCommand({});
3387
- let buildCwd = cwd;
3388
- let dockerfilePath = ".gkm/docker/Dockerfile";
3389
- if (inMonorepo) {
3390
- const lockfilePath = findLockfilePath(cwd);
3391
- if (lockfilePath) {
3392
- const monorepoRoot = dirname(lockfilePath);
3393
- const appRelPath = relative(monorepoRoot, cwd);
3394
- dockerfilePath = join(appRelPath, ".gkm/docker/Dockerfile");
3395
- buildCwd = monorepoRoot;
3396
- logger$4.log(` Building from monorepo root: ${monorepoRoot}`);
3397
- }
3398
- }
3473
+ const dockerfileSuffix = appName ? `.${appName}` : "";
3474
+ const dockerfilePath = `.gkm/docker/Dockerfile${dockerfileSuffix}`;
3475
+ const buildCwd = lockfilePath && (inMonorepo || appName) ? lockfileDir : cwd;
3476
+ if (buildCwd !== cwd) logger$4.log(` Building from workspace root: ${buildCwd}`);
3399
3477
  try {
3400
3478
  execSync(`DOCKER_BUILDKIT=1 docker build --platform linux/amd64 -f ${dockerfilePath} -t ${imageRef} .`, {
3401
3479
  cwd: buildCwd,
@@ -3432,7 +3510,7 @@ async function deployDocker(options) {
3432
3510
  const { stage, tag, skipPush, masterKey, config: config$1 } = options;
3433
3511
  const imageName = config$1.imageName;
3434
3512
  const imageRef = getImageRef(config$1.registry, imageName, tag);
3435
- await buildImage(imageRef);
3513
+ await buildImage(imageRef, config$1.appName);
3436
3514
  if (!skipPush) if (!config$1.registry) logger$4.warn("\nāš ļø No registry configured. Use --skip-push or configure docker.registry in gkm.config.ts");
3437
3515
  else await pushImage(imageRef);
3438
3516
  logger$4.log("\nāœ… Docker deployment ready!");
@@ -4144,11 +4222,25 @@ async function workspaceDeployCommand(workspace, options) {
4144
4222
  await provisionServices(api, project.projectId, environmentId, workspace.name, dockerServices);
4145
4223
  }
4146
4224
  const deployedAppUrls = {};
4225
+ if (!skipBuild) {
4226
+ logger$1.log("\nšŸ—ļø Building workspace...");
4227
+ try {
4228
+ await buildCommand({
4229
+ provider: "server",
4230
+ production: true,
4231
+ stage
4232
+ });
4233
+ logger$1.log(" āœ“ Workspace build complete");
4234
+ } catch (error) {
4235
+ const message = error instanceof Error ? error.message : "Unknown error";
4236
+ logger$1.log(` āœ— Workspace build failed: ${message}`);
4237
+ throw error;
4238
+ }
4239
+ }
4147
4240
  logger$1.log("\nšŸ“¦ Deploying applications...");
4148
4241
  const results = [];
4149
4242
  for (const appName of appsToDeployNames) {
4150
4243
  const app = workspace.apps[appName];
4151
- const appPath = app.path;
4152
4244
  logger$1.log(`\n ${app.type === "backend" ? "āš™ļø" : "🌐"} Deploying ${appName}...`);
4153
4245
  try {
4154
4246
  const dokployAppName = `${workspace.name}-${appName}`;
@@ -4161,21 +4253,6 @@ async function workspaceDeployCommand(workspace, options) {
4161
4253
  if (message.includes("already exists") || message.includes("duplicate")) logger$1.log(` Application already exists`);
4162
4254
  else throw error;
4163
4255
  }
4164
- if (!skipBuild) {
4165
- logger$1.log(` Building ${appName}...`);
4166
- const originalCwd = process.cwd();
4167
- const fullAppPath = `${workspace.root}/${appPath}`;
4168
- try {
4169
- process.chdir(fullAppPath);
4170
- await buildCommand({
4171
- provider: "server",
4172
- production: true,
4173
- stage
4174
- });
4175
- } finally {
4176
- process.chdir(originalCwd);
4177
- }
4178
- }
4179
4256
  const imageName = `${workspace.name}-${appName}`;
4180
4257
  const imageRef = registry ? `${registry}/${imageName}:${imageTag}` : `${imageName}:${imageTag}`;
4181
4258
  logger$1.log(` Building Docker image: ${imageRef}`);
@@ -4185,7 +4262,8 @@ async function workspaceDeployCommand(workspace, options) {
4185
4262
  skipPush: false,
4186
4263
  config: {
4187
4264
  registry,
4188
- imageName
4265
+ imageName,
4266
+ appName
4189
4267
  }
4190
4268
  });
4191
4269
  const envVars = [`NODE_ENV=production`, `PORT=${app.port}`];
@@ -4553,7 +4631,9 @@ function generateAuthAppFiles(options) {
4553
4631
  dev: "gkm dev --entry ./src/index.ts",
4554
4632
  build: "tsc",
4555
4633
  start: "node dist/index.js",
4556
- typecheck: "tsc --noEmit"
4634
+ typecheck: "tsc --noEmit",
4635
+ "db:migrate": "gkm exec -- npx @better-auth/cli migrate",
4636
+ "db:generate": "gkm exec -- npx @better-auth/cli generate"
4557
4637
  },
4558
4638
  dependencies: {
4559
4639
  [modelsPackage]: "workspace:*",
@@ -7467,6 +7547,16 @@ program.command("dev").description("Start development server with automatic relo
7467
7547
  process.exit(1);
7468
7548
  }
7469
7549
  });
7550
+ program.command("exec").description("Run a command with secrets injected into Credentials").argument("<command...>", "Command to run (use -- before command)").action(async (commandArgs) => {
7551
+ try {
7552
+ const globalOptions = program.opts();
7553
+ if (globalOptions.cwd) process.chdir(globalOptions.cwd);
7554
+ await execCommand(commandArgs);
7555
+ } catch (error) {
7556
+ console.error(error instanceof Error ? error.message : "Command failed");
7557
+ process.exit(1);
7558
+ }
7559
+ });
7470
7560
  program.command("test").description("Run tests with secrets loaded from environment").option("--stage <stage>", "Stage to load secrets from", "development").option("--run", "Run tests once without watch mode").option("--watch", "Enable watch mode").option("--coverage", "Generate coverage report").option("--ui", "Open Vitest UI").argument("[pattern]", "Pattern to filter tests").action(async (pattern, options) => {
7471
7561
  try {
7472
7562
  const globalOptions = program.opts();