@geekmidas/cli 0.52.0 → 0.54.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
@@ -20,6 +20,7 @@ import chokidar from "chokidar";
20
20
  import { config } from "dotenv";
21
21
  import fg from "fast-glob";
22
22
  import { Cron } from "@geekmidas/constructs/crons";
23
+ import { Endpoint } from "@geekmidas/constructs/endpoints";
23
24
  import { Function } from "@geekmidas/constructs/functions";
24
25
  import { Subscriber } from "@geekmidas/constructs/subscribers";
25
26
  import { createHash, randomBytes } from "node:crypto";
@@ -30,7 +31,7 @@ import prompts from "prompts";
30
31
 
31
32
  //#region package.json
32
33
  var name = "@geekmidas/cli";
33
- var version = "0.52.0";
34
+ var version = "0.54.0";
34
35
  var description = "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs";
35
36
  var private$1 = false;
36
37
  var type = "module";
@@ -3385,7 +3386,7 @@ WORKDIR /app
3385
3386
  COPY . .
3386
3387
 
3387
3388
  # Build production server using gkm
3388
- RUN pnpm exec gkm build --provider server --production
3389
+ RUN ${pm.exec} gkm build --provider server --production
3389
3390
 
3390
3391
  # Stage 3: Production
3391
3392
  FROM ${baseImage} AS runner
@@ -3467,7 +3468,7 @@ WORKDIR /app
3467
3468
  COPY --from=pruner /app/out/full/ ./
3468
3469
 
3469
3470
  # Build production server using gkm
3470
- RUN pnpm exec gkm build --provider server --production
3471
+ RUN ${pm.exec} gkm build --provider server --production
3471
3472
 
3472
3473
  # Stage 4: Production
3473
3474
  FROM ${baseImage} AS runner
@@ -3788,7 +3789,7 @@ RUN if [ -n "$GKM_ENCRYPTED_CREDENTIALS" ]; then \
3788
3789
  fi
3789
3790
 
3790
3791
  # Build production server using gkm
3791
- RUN cd ${appPath} && pnpm exec gkm build --provider server --production
3792
+ RUN cd ${appPath} && ${pm.exec} gkm build --provider server --production
3792
3793
 
3793
3794
  # Stage 4: Production
3794
3795
  FROM ${baseImage} AS runner
@@ -4522,7 +4523,8 @@ function buildRedisUrl(redis) {
4522
4523
  function resolveEnvVar(varName, context) {
4523
4524
  switch (varName) {
4524
4525
  case "PORT": return String(context.app.port);
4525
- case "NODE_ENV": return context.stage === "production" ? "production" : "development";
4526
+ case "NODE_ENV": return "production";
4527
+ case "STAGE": return context.stage;
4526
4528
  case "DATABASE_URL":
4527
4529
  if (context.appCredentials && context.postgres) return buildDatabaseUrl(context.appCredentials, context.postgres);
4528
4530
  break;
@@ -4856,6 +4858,12 @@ function generateSecretsReport(encryptedApps, sniffedApps) {
4856
4858
  const __filename = fileURLToPath(import.meta.url);
4857
4859
  const __dirname = dirname(__filename);
4858
4860
  /**
4861
+ * Check if a value is a gkm construct (Endpoint, Function, Cron, or Subscriber).
4862
+ */
4863
+ function isConstruct(value) {
4864
+ return Endpoint.isEndpoint(value) || Function.isFunction(value) || Cron.isCron(value) || Subscriber.isSubscriber(value);
4865
+ }
4866
+ /**
4859
4867
  * Resolve the path to a sniffer helper file.
4860
4868
  * Handles both dev (.ts with tsx) and production (.mjs from dist).
4861
4869
  *
@@ -4880,8 +4888,9 @@ function resolveSnifferFile(baseName) {
4880
4888
  * 1. Frontend apps: Returns empty (no server secrets)
4881
4889
  * 2. Apps with `requiredEnv`: Uses explicit list from config
4882
4890
  * 3. Entry apps: Imports entry file in subprocess to capture config.parse() calls
4883
- * 4. Apps with `envParser`: Runs SnifferEnvironmentParser to detect usage
4884
- * 5. Apps with neither: Returns empty
4891
+ * 4. Route-based apps: Loads route files and calls getEnvironment() on each construct
4892
+ * 5. Apps with `envParser` (no routes): Runs SnifferEnvironmentParser to detect usage
4893
+ * 6. Apps with neither: Returns empty
4885
4894
  *
4886
4895
  * This function handles "fire and forget" async operations gracefully,
4887
4896
  * capturing errors and unhandled rejections without failing the build.
@@ -4910,6 +4919,14 @@ async function sniffAppEnvironment(app, appName, workspacePath, options = {}) {
4910
4919
  requiredEnvVars: result.envVars
4911
4920
  };
4912
4921
  }
4922
+ if (app.routes) {
4923
+ const result = await sniffRouteFiles(app.routes, app.path, workspacePath);
4924
+ if (logWarnings && result.error) console.warn(`[sniffer] ${appName}: Route sniffing threw error (env vars still captured): ${result.error.message}`);
4925
+ return {
4926
+ appName,
4927
+ requiredEnvVars: result.envVars
4928
+ };
4929
+ }
4913
4930
  if (app.envParser) {
4914
4931
  const result = await sniffEnvParser(app.envParser, app.path, workspacePath);
4915
4932
  if (logWarnings) {
@@ -5001,6 +5018,47 @@ async function sniffEntryFile(entryPath, appPath, workspacePath) {
5001
5018
  });
5002
5019
  }
5003
5020
  /**
5021
+ * Sniff route files by loading constructs and calling getEnvironment().
5022
+ *
5023
+ * Route-based apps have endpoints, functions, crons, and subscribers that
5024
+ * use services. Each service's register() method accesses environment variables.
5025
+ * This function mimics what the bundler does during build to capture those vars.
5026
+ *
5027
+ * @param routes - Glob pattern(s) for route files
5028
+ * @param appPath - The app's path relative to workspace (e.g., 'apps/api')
5029
+ * @param workspacePath - Absolute path to workspace root
5030
+ * @returns EntrySniffResult with env vars and optional error
5031
+ */
5032
+ async function sniffRouteFiles(routes, appPath, workspacePath) {
5033
+ const fullAppPath = resolve(workspacePath, appPath);
5034
+ const patterns = Array.isArray(routes) ? routes : [routes];
5035
+ const envVars = /* @__PURE__ */ new Set();
5036
+ let error;
5037
+ try {
5038
+ const files = await fg(patterns, {
5039
+ cwd: fullAppPath,
5040
+ absolute: true
5041
+ });
5042
+ for (const file of files) try {
5043
+ const module = await import(file);
5044
+ for (const [, exportValue] of Object.entries(module)) if (isConstruct(exportValue)) try {
5045
+ const constructEnvVars = await exportValue.getEnvironment();
5046
+ constructEnvVars.forEach((v) => envVars.add(v));
5047
+ } catch (e) {
5048
+ console.warn(`[sniffer] Failed to get environment for construct in ${file}: ${e instanceof Error ? e.message : String(e)}`);
5049
+ }
5050
+ } catch (e) {
5051
+ console.warn(`[sniffer] Failed to import ${file}: ${e instanceof Error ? e.message : String(e)}`);
5052
+ }
5053
+ } catch (e) {
5054
+ error = e instanceof Error ? e : new Error(String(e));
5055
+ }
5056
+ return {
5057
+ envVars: Array.from(envVars).sort(),
5058
+ error
5059
+ };
5060
+ }
5061
+ /**
5004
5062
  * Run the SnifferEnvironmentParser on an envParser module to detect
5005
5063
  * which environment variables it accesses.
5006
5064
  *
@@ -5788,7 +5846,13 @@ async function workspaceDeployCommand(workspace, options) {
5788
5846
  masterKey: appSecrets?.masterKey
5789
5847
  };
5790
5848
  const appRequirements = sniffedApps.get(appName);
5791
- const requiredVars = appRequirements?.requiredEnvVars ?? [];
5849
+ const sniffedVars = appRequirements?.requiredEnvVars ?? [];
5850
+ const requiredVars = [...new Set([
5851
+ "PORT",
5852
+ "NODE_ENV",
5853
+ "STAGE",
5854
+ ...sniffedVars
5855
+ ])];
5792
5856
  const { valid, missing, resolved } = validateEnvVars(requiredVars, envContext);
5793
5857
  if (!valid) throw new Error(formatMissingVarsError(appName, missing, stage));
5794
5858
  const envVars = Object.entries(resolved).map(([key, value]) => `${key}=${value}`);
@@ -5882,7 +5946,11 @@ async function workspaceDeployCommand(workspace, options) {
5882
5946
  buildArgs,
5883
5947
  publicUrlArgs: getPublicUrlArgNames(app)
5884
5948
  });
5885
- const envVars = [`NODE_ENV=production`, `PORT=${app.port}`];
5949
+ const envVars = [
5950
+ `NODE_ENV=production`,
5951
+ `PORT=${app.port}`,
5952
+ `STAGE=${stage}`
5953
+ ];
5886
5954
  await api.saveDockerProvider(application.applicationId, imageRef, { registryId });
5887
5955
  await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
5888
5956
  logger$1.log(` Deploying to Dokploy...`);