@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.cjs CHANGED
@@ -240,7 +240,7 @@ async function prompt$1(message, hidden = false) {
240
240
  if (!process.stdin.isTTY) throw new Error("Interactive input required. Please provide --token option.");
241
241
  if (hidden) {
242
242
  process.stdout.write(message);
243
- return new Promise((resolve$1, reject) => {
243
+ return new Promise((resolve$2, reject) => {
244
244
  let value = "";
245
245
  const cleanup = () => {
246
246
  process.stdin.setRawMode(false);
@@ -257,7 +257,7 @@ async function prompt$1(message, hidden = false) {
257
257
  if (c === "\n" || c === "\r") {
258
258
  cleanup();
259
259
  process.stdout.write("\n");
260
- resolve$1(value);
260
+ resolve$2(value);
261
261
  } else if (c === "") {
262
262
  cleanup();
263
263
  process.stdout.write("\n");
@@ -892,15 +892,15 @@ function loadEnvFiles(envConfig, cwd = process.cwd()) {
892
892
  * @internal Exported for testing
893
893
  */
894
894
  async function isPortAvailable(port) {
895
- return new Promise((resolve$1) => {
895
+ return new Promise((resolve$2) => {
896
896
  const server = (0, node_net.createServer)();
897
897
  server.once("error", (err) => {
898
- if (err.code === "EADDRINUSE") resolve$1(false);
899
- else resolve$1(false);
898
+ if (err.code === "EADDRINUSE") resolve$2(false);
899
+ else resolve$2(false);
900
900
  });
901
901
  server.once("listening", () => {
902
902
  server.close();
903
- resolve$1(true);
903
+ resolve$2(true);
904
904
  });
905
905
  server.listen(port);
906
906
  });
@@ -1491,7 +1491,7 @@ async function workspaceDevCommand(workspace, options) {
1491
1491
  };
1492
1492
  process.on("SIGINT", shutdown);
1493
1493
  process.on("SIGTERM", shutdown);
1494
- return new Promise((resolve$1, reject) => {
1494
+ return new Promise((resolve$2, reject) => {
1495
1495
  turboProcess.on("error", (error) => {
1496
1496
  logger$8.error("❌ Turbo error:", error);
1497
1497
  reject(error);
@@ -1499,7 +1499,7 @@ async function workspaceDevCommand(workspace, options) {
1499
1499
  turboProcess.on("exit", (code) => {
1500
1500
  if (endpointWatcher) endpointWatcher.close().catch(() => {});
1501
1501
  if (code !== null && code !== 0) reject(new Error(`Turbo exited with code ${code}`));
1502
- else resolve$1();
1502
+ else resolve$2();
1503
1503
  });
1504
1504
  });
1505
1505
  }
@@ -1542,19 +1542,43 @@ function findSecretsRoot(startDir) {
1542
1542
  return startDir;
1543
1543
  }
1544
1544
  /**
1545
- * Create a wrapper script that injects secrets before importing the entry file.
1546
- * @internal Exported for testing
1545
+ * Generate the credentials injection code snippet.
1546
+ * This is the common logic used by both entry wrapper and exec preload.
1547
+ * @internal
1547
1548
  */
1548
- async function createEntryWrapper(wrapperPath, entryPath, secretsJsonPath) {
1549
- const credentialsInjection = secretsJsonPath ? `import { Credentials } from '@geekmidas/envkit/credentials';
1549
+ function generateCredentialsInjection(secretsJsonPath) {
1550
+ return `import { Credentials } from '@geekmidas/envkit/credentials';
1550
1551
  import { existsSync, readFileSync } from 'node:fs';
1551
1552
 
1552
- // Inject dev secrets into Credentials (before app import)
1553
+ // Inject dev secrets into Credentials
1553
1554
  const secretsPath = '${secretsJsonPath}';
1554
1555
  if (existsSync(secretsPath)) {
1555
- Object.assign(Credentials, JSON.parse(readFileSync(secretsPath, 'utf-8')));
1556
+ const secrets = JSON.parse(readFileSync(secretsPath, 'utf-8'));
1557
+ Object.assign(Credentials, secrets);
1558
+ // Debug: uncomment to verify preload is running
1559
+ // console.log('[gkm preload] Injected', Object.keys(secrets).length, 'credentials');
1556
1560
  }
1557
-
1561
+ `;
1562
+ }
1563
+ /**
1564
+ * Create a preload script that injects secrets into Credentials.
1565
+ * Used by `gkm exec` to inject secrets before running any command.
1566
+ * @internal Exported for testing
1567
+ */
1568
+ async function createCredentialsPreload(preloadPath, secretsJsonPath) {
1569
+ const content = `/**
1570
+ * Credentials preload generated by 'gkm exec'
1571
+ * This file is loaded via NODE_OPTIONS="--import <path>"
1572
+ */
1573
+ ${generateCredentialsInjection(secretsJsonPath)}`;
1574
+ await (0, node_fs_promises.writeFile)(preloadPath, content);
1575
+ }
1576
+ /**
1577
+ * Create a wrapper script that injects secrets before importing the entry file.
1578
+ * @internal Exported for testing
1579
+ */
1580
+ async function createEntryWrapper(wrapperPath, entryPath, secretsJsonPath) {
1581
+ const credentialsInjection = secretsJsonPath ? `${generateCredentialsInjection(secretsJsonPath)}
1558
1582
  ` : "";
1559
1583
  const content = `#!/usr/bin/env node
1560
1584
  /**
@@ -1685,12 +1709,12 @@ var EntryRunner = class {
1685
1709
  if (code !== null && code !== 0 && code !== 143) logger$8.error(`❌ Process exited with code ${code}`);
1686
1710
  this.isRunning = false;
1687
1711
  });
1688
- await new Promise((resolve$1) => setTimeout(resolve$1, 500));
1712
+ await new Promise((resolve$2) => setTimeout(resolve$2, 500));
1689
1713
  if (this.isRunning) logger$8.log(`\n🎉 Running at http://localhost:${this.port}`);
1690
1714
  }
1691
1715
  async restart() {
1692
1716
  this.stopProcess();
1693
- await new Promise((resolve$1) => setTimeout(resolve$1, 500));
1717
+ await new Promise((resolve$2) => setTimeout(resolve$2, 500));
1694
1718
  await this.runProcess();
1695
1719
  }
1696
1720
  stop() {
@@ -1762,7 +1786,7 @@ var DevServer = class {
1762
1786
  if (code !== null && code !== 0 && signal !== "SIGTERM") logger$8.error(`❌ Server exited with code ${code}`);
1763
1787
  this.isRunning = false;
1764
1788
  });
1765
- await new Promise((resolve$1) => setTimeout(resolve$1, 1e3));
1789
+ await new Promise((resolve$2) => setTimeout(resolve$2, 1e3));
1766
1790
  if (this.isRunning) {
1767
1791
  logger$8.log(`\n🎉 Server running at http://localhost:${this.actualPort}`);
1768
1792
  if (this.enableOpenApi) logger$8.log(`📚 API Docs available at http://localhost:${this.actualPort}/__docs`);
@@ -1797,7 +1821,7 @@ var DevServer = class {
1797
1821
  let attempts = 0;
1798
1822
  while (attempts < 30) {
1799
1823
  if (await isPortAvailable(portToReuse)) break;
1800
- await new Promise((resolve$1) => setTimeout(resolve$1, 100));
1824
+ await new Promise((resolve$2) => setTimeout(resolve$2, 100));
1801
1825
  attempts++;
1802
1826
  }
1803
1827
  this.requestedPort = portToReuse;
@@ -1805,9 +1829,9 @@ var DevServer = class {
1805
1829
  }
1806
1830
  async createServerEntry() {
1807
1831
  const { writeFile: fsWriteFile } = await import("node:fs/promises");
1808
- const { relative: relative$7, dirname: dirname$8 } = await import("node:path");
1832
+ const { relative: relative$6, dirname: dirname$8 } = await import("node:path");
1809
1833
  const serverPath = (0, node_path.join)(this.appRoot, ".gkm", this.provider, "server.ts");
1810
- const relativeAppPath = relative$7(dirname$8(serverPath), (0, node_path.join)(dirname$8(serverPath), "app.js"));
1834
+ const relativeAppPath = relative$6(dirname$8(serverPath), (0, node_path.join)(dirname$8(serverPath), "app.js"));
1811
1835
  const credentialsInjection = this.secretsJsonPath ? `import { Credentials } from '@geekmidas/envkit/credentials';
1812
1836
  import { existsSync, readFileSync } from 'node:fs';
1813
1837
 
@@ -1860,6 +1884,59 @@ start({
1860
1884
  await fsWriteFile(serverPath, content);
1861
1885
  }
1862
1886
  };
1887
+ /**
1888
+ * Run a command with secrets injected into Credentials.
1889
+ * Uses Node's --import flag to preload a script that populates Credentials
1890
+ * before the command loads any modules that depend on them.
1891
+ *
1892
+ * @example
1893
+ * ```bash
1894
+ * gkm exec -- npx @better-auth/cli migrate
1895
+ * gkm exec -- npx prisma migrate dev
1896
+ * ```
1897
+ */
1898
+ async function execCommand(commandArgs, options = {}) {
1899
+ const cwd = options.cwd ?? process.cwd();
1900
+ if (commandArgs.length === 0) throw new Error("No command specified. Usage: gkm exec -- <command>");
1901
+ const defaultEnv = loadEnvFiles(".env");
1902
+ if (defaultEnv.loaded.length > 0) logger$8.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
1903
+ const { credentials, secretsJsonPath, appName } = await prepareEntryCredentials({ cwd });
1904
+ if (appName) logger$8.log(`📦 App: ${appName}`);
1905
+ const secretCount = Object.keys(credentials).filter((k) => k !== "PORT").length;
1906
+ if (secretCount > 0) logger$8.log(`🔐 Loaded ${secretCount} secret(s)`);
1907
+ const preloadDir = (0, node_path.join)(cwd, ".gkm");
1908
+ await (0, node_fs_promises.mkdir)(preloadDir, { recursive: true });
1909
+ const preloadPath = (0, node_path.join)(preloadDir, "credentials-preload.ts");
1910
+ await createCredentialsPreload(preloadPath, secretsJsonPath);
1911
+ const [cmd, ...args] = commandArgs;
1912
+ if (!cmd) throw new Error("No command specified");
1913
+ logger$8.log(`🚀 Running: ${commandArgs.join(" ")}`);
1914
+ const existingNodeOptions = process.env.NODE_OPTIONS ?? "";
1915
+ const tsxImport = "--import tsx";
1916
+ const preloadImport = `--import ${preloadPath}`;
1917
+ const nodeOptions = [
1918
+ existingNodeOptions,
1919
+ tsxImport,
1920
+ preloadImport
1921
+ ].filter(Boolean).join(" ");
1922
+ const child = (0, node_child_process.spawn)(cmd, args, {
1923
+ cwd,
1924
+ stdio: "inherit",
1925
+ env: {
1926
+ ...process.env,
1927
+ ...credentials,
1928
+ NODE_OPTIONS: nodeOptions
1929
+ }
1930
+ });
1931
+ const exitCode = await new Promise((resolve$2) => {
1932
+ child.on("close", (code) => resolve$2(code ?? 0));
1933
+ child.on("error", (error) => {
1934
+ logger$8.error(`Failed to run command: ${error.message}`);
1935
+ resolve$2(1);
1936
+ });
1937
+ });
1938
+ if (exitCode !== 0) process.exit(exitCode);
1939
+ }
1863
1940
 
1864
1941
  //#endregion
1865
1942
  //#region src/build/manifests.ts
@@ -1930,10 +2007,15 @@ const logger$6 = console;
1930
2007
  async function buildCommand(options) {
1931
2008
  const loadedConfig = await require_config.loadWorkspaceConfig();
1932
2009
  if (loadedConfig.type === "workspace") {
1933
- logger$6.log("📦 Detected workspace configuration");
1934
- return workspaceBuildCommand(loadedConfig.workspace, options);
2010
+ const cwd = (0, node_path.resolve)(process.cwd());
2011
+ const workspaceRoot = (0, node_path.resolve)(loadedConfig.workspace.root);
2012
+ const isAtWorkspaceRoot = cwd === workspaceRoot;
2013
+ if (isAtWorkspaceRoot) {
2014
+ logger$6.log("📦 Detected workspace configuration");
2015
+ return workspaceBuildCommand(loadedConfig.workspace, options);
2016
+ }
1935
2017
  }
1936
- const config = await require_config.loadConfig();
2018
+ const config = loadedConfig.type === "workspace" ? (await require_config.loadAppConfig()).gkmConfig : await require_config.loadConfig();
1937
2019
  const resolved = resolveProviders(config, options);
1938
2020
  const productionConfigFromGkm = getProductionConfigFromGkm(config);
1939
2021
  const production = normalizeProductionConfig(options.production ?? false, productionConfigFromGkm);
@@ -2099,7 +2181,7 @@ async function workspaceBuildCommand(workspace, options) {
2099
2181
  try {
2100
2182
  const turboCommand = getTurboCommand(pm);
2101
2183
  logger$6.log(`Running: ${turboCommand}`);
2102
- await new Promise((resolve$1, reject) => {
2184
+ await new Promise((resolve$2, reject) => {
2103
2185
  const child = (0, node_child_process.spawn)(turboCommand, {
2104
2186
  shell: true,
2105
2187
  cwd: workspace.root,
@@ -2110,7 +2192,7 @@ async function workspaceBuildCommand(workspace, options) {
2110
2192
  }
2111
2193
  });
2112
2194
  child.on("close", (code) => {
2113
- if (code === 0) resolve$1();
2195
+ if (code === 0) resolve$2();
2114
2196
  else reject(new Error(`Turbo build failed with exit code ${code}`));
2115
2197
  });
2116
2198
  child.on("error", (err) => {
@@ -3377,26 +3459,22 @@ function getImageRef(registry, imageName, tag) {
3377
3459
  }
3378
3460
  /**
3379
3461
  * Build Docker image
3462
+ * @param imageRef - Full image reference (registry/name:tag)
3463
+ * @param appName - Name of the app (used for Dockerfile.{appName} in workspaces)
3380
3464
  */
3381
- async function buildImage(imageRef) {
3465
+ async function buildImage(imageRef, appName) {
3382
3466
  logger$4.log(`\n🔨 Building Docker image: ${imageRef}`);
3383
3467
  const cwd = process.cwd();
3384
- const inMonorepo = isMonorepo(cwd);
3385
- if (inMonorepo) logger$4.log(" Generating Dockerfile for monorepo (turbo prune)...");
3468
+ const lockfilePath = findLockfilePath(cwd);
3469
+ const lockfileDir = lockfilePath ? (0, node_path.dirname)(lockfilePath) : cwd;
3470
+ const inMonorepo = lockfileDir !== cwd;
3471
+ if (appName || inMonorepo) logger$4.log(" Generating Dockerfile for monorepo (turbo prune)...");
3386
3472
  else logger$4.log(" Generating Dockerfile...");
3387
3473
  await dockerCommand({});
3388
- let buildCwd = cwd;
3389
- let dockerfilePath = ".gkm/docker/Dockerfile";
3390
- if (inMonorepo) {
3391
- const lockfilePath = findLockfilePath(cwd);
3392
- if (lockfilePath) {
3393
- const monorepoRoot = (0, node_path.dirname)(lockfilePath);
3394
- const appRelPath = (0, node_path.relative)(monorepoRoot, cwd);
3395
- dockerfilePath = (0, node_path.join)(appRelPath, ".gkm/docker/Dockerfile");
3396
- buildCwd = monorepoRoot;
3397
- logger$4.log(` Building from monorepo root: ${monorepoRoot}`);
3398
- }
3399
- }
3474
+ const dockerfileSuffix = appName ? `.${appName}` : "";
3475
+ const dockerfilePath = `.gkm/docker/Dockerfile${dockerfileSuffix}`;
3476
+ const buildCwd = lockfilePath && (inMonorepo || appName) ? lockfileDir : cwd;
3477
+ if (buildCwd !== cwd) logger$4.log(` Building from workspace root: ${buildCwd}`);
3400
3478
  try {
3401
3479
  (0, node_child_process.execSync)(`DOCKER_BUILDKIT=1 docker build --platform linux/amd64 -f ${dockerfilePath} -t ${imageRef} .`, {
3402
3480
  cwd: buildCwd,
@@ -3433,7 +3511,7 @@ async function deployDocker(options) {
3433
3511
  const { stage, tag, skipPush, masterKey, config } = options;
3434
3512
  const imageName = config.imageName;
3435
3513
  const imageRef = getImageRef(config.registry, imageName, tag);
3436
- await buildImage(imageRef);
3514
+ await buildImage(imageRef, config.appName);
3437
3515
  if (!skipPush) if (!config.registry) logger$4.warn("\n⚠️ No registry configured. Use --skip-push or configure docker.registry in gkm.config.ts");
3438
3516
  else await pushImage(imageRef);
3439
3517
  logger$4.log("\n✅ Docker deployment ready!");
@@ -3737,7 +3815,7 @@ async function prompt(message, hidden = false) {
3737
3815
  if (!process.stdin.isTTY) throw new Error("Interactive input required. Please configure manually.");
3738
3816
  if (hidden) {
3739
3817
  process.stdout.write(message);
3740
- return new Promise((resolve$1) => {
3818
+ return new Promise((resolve$2) => {
3741
3819
  let value = "";
3742
3820
  const onData = (char) => {
3743
3821
  const c = char.toString();
@@ -3746,7 +3824,7 @@ async function prompt(message, hidden = false) {
3746
3824
  process.stdin.pause();
3747
3825
  process.stdin.removeListener("data", onData);
3748
3826
  process.stdout.write("\n");
3749
- resolve$1(value);
3827
+ resolve$2(value);
3750
3828
  } else if (c === "") {
3751
3829
  process.stdin.setRawMode(false);
3752
3830
  process.stdin.pause();
@@ -4145,11 +4223,25 @@ async function workspaceDeployCommand(workspace, options) {
4145
4223
  await provisionServices(api, project.projectId, environmentId, workspace.name, dockerServices);
4146
4224
  }
4147
4225
  const deployedAppUrls = {};
4226
+ if (!skipBuild) {
4227
+ logger$1.log("\n🏗️ Building workspace...");
4228
+ try {
4229
+ await buildCommand({
4230
+ provider: "server",
4231
+ production: true,
4232
+ stage
4233
+ });
4234
+ logger$1.log(" ✓ Workspace build complete");
4235
+ } catch (error) {
4236
+ const message = error instanceof Error ? error.message : "Unknown error";
4237
+ logger$1.log(` ✗ Workspace build failed: ${message}`);
4238
+ throw error;
4239
+ }
4240
+ }
4148
4241
  logger$1.log("\n📦 Deploying applications...");
4149
4242
  const results = [];
4150
4243
  for (const appName of appsToDeployNames) {
4151
4244
  const app = workspace.apps[appName];
4152
- const appPath = app.path;
4153
4245
  logger$1.log(`\n ${app.type === "backend" ? "⚙️" : "🌐"} Deploying ${appName}...`);
4154
4246
  try {
4155
4247
  const dokployAppName = `${workspace.name}-${appName}`;
@@ -4162,21 +4254,6 @@ async function workspaceDeployCommand(workspace, options) {
4162
4254
  if (message.includes("already exists") || message.includes("duplicate")) logger$1.log(` Application already exists`);
4163
4255
  else throw error;
4164
4256
  }
4165
- if (!skipBuild) {
4166
- logger$1.log(` Building ${appName}...`);
4167
- const originalCwd = process.cwd();
4168
- const fullAppPath = `${workspace.root}/${appPath}`;
4169
- try {
4170
- process.chdir(fullAppPath);
4171
- await buildCommand({
4172
- provider: "server",
4173
- production: true,
4174
- stage
4175
- });
4176
- } finally {
4177
- process.chdir(originalCwd);
4178
- }
4179
- }
4180
4257
  const imageName = `${workspace.name}-${appName}`;
4181
4258
  const imageRef = registry ? `${registry}/${imageName}:${imageTag}` : `${imageName}:${imageTag}`;
4182
4259
  logger$1.log(` Building Docker image: ${imageRef}`);
@@ -4186,7 +4263,8 @@ async function workspaceDeployCommand(workspace, options) {
4186
4263
  skipPush: false,
4187
4264
  config: {
4188
4265
  registry,
4189
- imageName
4266
+ imageName,
4267
+ appName
4190
4268
  }
4191
4269
  });
4192
4270
  const envVars = [`NODE_ENV=production`, `PORT=${app.port}`];
@@ -4554,7 +4632,9 @@ function generateAuthAppFiles(options) {
4554
4632
  dev: "gkm dev --entry ./src/index.ts",
4555
4633
  build: "tsc",
4556
4634
  start: "node dist/index.js",
4557
- typecheck: "tsc --noEmit"
4635
+ typecheck: "tsc --noEmit",
4636
+ "db:migrate": "gkm exec -- npx @better-auth/cli migrate",
4637
+ "db:generate": "gkm exec -- npx @better-auth/cli generate"
4558
4638
  },
4559
4639
  dependencies: {
4560
4640
  [modelsPackage]: "workspace:*",
@@ -7394,9 +7474,9 @@ async function testCommand(options = {}) {
7394
7474
  NODE_ENV: "test"
7395
7475
  }
7396
7476
  });
7397
- return new Promise((resolve$1, reject) => {
7477
+ return new Promise((resolve$2, reject) => {
7398
7478
  vitestProcess.on("close", (code) => {
7399
- if (code === 0) resolve$1();
7479
+ if (code === 0) resolve$2();
7400
7480
  else reject(new Error(`Tests failed with exit code ${code}`));
7401
7481
  });
7402
7482
  vitestProcess.on("error", (error) => {
@@ -7468,6 +7548,16 @@ program.command("dev").description("Start development server with automatic relo
7468
7548
  process.exit(1);
7469
7549
  }
7470
7550
  });
7551
+ program.command("exec").description("Run a command with secrets injected into Credentials").argument("<command...>", "Command to run (use -- before command)").action(async (commandArgs) => {
7552
+ try {
7553
+ const globalOptions = program.opts();
7554
+ if (globalOptions.cwd) process.chdir(globalOptions.cwd);
7555
+ await execCommand(commandArgs);
7556
+ } catch (error) {
7557
+ console.error(error instanceof Error ? error.message : "Command failed");
7558
+ process.exit(1);
7559
+ }
7560
+ });
7471
7561
  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) => {
7472
7562
  try {
7473
7563
  const globalOptions = program.opts();