@geekmidas/cli 1.3.0 → 1.5.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/{Route53Provider-xrWuBXih.cjs → Route53Provider-Bs7Arms9.cjs} +3 -2
  3. package/dist/Route53Provider-Bs7Arms9.cjs.map +1 -0
  4. package/dist/{Route53Provider-DOWmFnwN.mjs → Route53Provider-C8mS0zY6.mjs} +3 -2
  5. package/dist/Route53Provider-C8mS0zY6.mjs.map +1 -0
  6. package/dist/{config-C1bidhvG.mjs → config-DfCJ29PQ.mjs} +2 -2
  7. package/dist/{config-C1bidhvG.mjs.map → config-DfCJ29PQ.mjs.map} +1 -1
  8. package/dist/{config-C1dM7aZb.cjs → config-ZQM1vBoz.cjs} +2 -2
  9. package/dist/{config-C1dM7aZb.cjs.map → config-ZQM1vBoz.cjs.map} +1 -1
  10. package/dist/config.cjs +2 -2
  11. package/dist/config.d.cts +1 -1
  12. package/dist/config.d.mts +1 -1
  13. package/dist/config.mjs +2 -2
  14. package/dist/{index-DzmZ6SUW.d.cts → index-B58qjyBd.d.cts} +27 -1
  15. package/dist/index-B58qjyBd.d.cts.map +1 -0
  16. package/dist/{index-DvpWzLD7.d.mts → index-C0SpUT9Y.d.mts} +27 -1
  17. package/dist/index-C0SpUT9Y.d.mts.map +1 -0
  18. package/dist/index.cjs +117 -49
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.mjs +117 -49
  21. package/dist/index.mjs.map +1 -1
  22. package/dist/{openapi-9k6a6VA4.mjs → openapi-BcSjLfWq.mjs} +2 -2
  23. package/dist/{openapi-9k6a6VA4.mjs.map → openapi-BcSjLfWq.mjs.map} +1 -1
  24. package/dist/{openapi-Dcja4e1C.cjs → openapi-D6Hcfov0.cjs} +2 -2
  25. package/dist/{openapi-Dcja4e1C.cjs.map → openapi-D6Hcfov0.cjs.map} +1 -1
  26. package/dist/openapi.cjs +3 -3
  27. package/dist/openapi.mjs +3 -3
  28. package/dist/workspace/index.cjs +1 -1
  29. package/dist/workspace/index.d.cts +1 -1
  30. package/dist/workspace/index.d.mts +1 -1
  31. package/dist/workspace/index.mjs +1 -1
  32. package/dist/{workspace-CeFgIDC-.cjs → workspace-2Do2YcGZ.cjs} +5 -1
  33. package/dist/{workspace-CeFgIDC-.cjs.map → workspace-2Do2YcGZ.cjs.map} +1 -1
  34. package/dist/{workspace-Cb_I7oCJ.mjs → workspace-BW2iU37P.mjs} +5 -1
  35. package/dist/{workspace-Cb_I7oCJ.mjs.map → workspace-BW2iU37P.mjs.map} +1 -1
  36. package/package.json +2 -2
  37. package/src/deploy/__tests__/Route53Provider.spec.ts +23 -0
  38. package/src/deploy/__tests__/env-resolver.spec.ts +384 -2
  39. package/src/deploy/__tests__/index.spec.ts +393 -5
  40. package/src/deploy/__tests__/sniffer.spec.ts +104 -93
  41. package/src/deploy/dns/Route53Provider.ts +4 -1
  42. package/src/deploy/env-resolver.ts +20 -0
  43. package/src/deploy/index.ts +83 -24
  44. package/src/deploy/sniffer.ts +39 -7
  45. package/src/init/generators/monorepo.ts +7 -1
  46. package/src/init/generators/web.ts +45 -2
  47. package/src/workspace/schema.ts +8 -0
  48. package/src/workspace/types.ts +23 -0
  49. package/dist/Route53Provider-DOWmFnwN.mjs.map +0 -1
  50. package/dist/Route53Provider-xrWuBXih.cjs.map +0 -1
  51. package/dist/index-DvpWzLD7.d.mts.map +0 -1
  52. package/dist/index-DzmZ6SUW.d.cts.map +0 -1
package/dist/index.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env -S npx tsx
2
2
  import { __require } from "./chunk-Duj1WY3L.mjs";
3
- import { getAppBuildOrder, getDependencyEnvVars, getDeployTargetError, isDeployTargetSupported } from "./workspace-Cb_I7oCJ.mjs";
4
- import { getAppNameFromCwd, loadAppConfig, loadConfig, loadWorkspaceConfig, parseModuleConfig } from "./config-C1bidhvG.mjs";
3
+ import { getAppBuildOrder, getDependencyEnvVars, getDeployTargetError, isDeployTargetSupported } from "./workspace-BW2iU37P.mjs";
4
+ import { getAppNameFromCwd, loadAppConfig, loadConfig, loadWorkspaceConfig, parseModuleConfig } from "./config-DfCJ29PQ.mjs";
5
5
  import { getCredentialsPath, getDokployCredentials, getDokployRegistryId, getDokployToken, removeDokployCredentials, storeDokployCredentials, storeDokployRegistryId } from "./credentials-s1kLcIzK.mjs";
6
- import { ConstructGenerator, EndpointGenerator, OPENAPI_OUTPUT_PATH, generateOpenApi, openapiCommand, resolveOpenApiConfig } from "./openapi-9k6a6VA4.mjs";
6
+ import { ConstructGenerator, EndpointGenerator, OPENAPI_OUTPUT_PATH, generateOpenApi, openapiCommand, resolveOpenApiConfig } from "./openapi-BcSjLfWq.mjs";
7
7
  import { getKeyPath, maskPassword, readStageSecrets, secretsExist, setCustomSecret, toEmbeddableSecrets, writeStageSecrets } from "./storage-DmCbr6DI.mjs";
8
8
  import { DokployApi } from "./dokploy-api-z0833e7r.mjs";
9
9
  import { encryptSecrets } from "./encryption-BOH5M-f-.mjs";
@@ -32,7 +32,7 @@ import prompts from "prompts";
32
32
 
33
33
  //#region package.json
34
34
  var name = "@geekmidas/cli";
35
- var version = "1.2.3";
35
+ var version = "1.4.0";
36
36
  var description = "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs";
37
37
  var private$1 = false;
38
38
  var type = "module";
@@ -2203,7 +2203,7 @@ async function createDnsProvider(options) {
2203
2203
  return new HostingerProvider();
2204
2204
  }
2205
2205
  if (provider === "route53") {
2206
- const { Route53Provider } = await import("./Route53Provider-DOWmFnwN.mjs");
2206
+ const { Route53Provider } = await import("./Route53Provider-C8mS0zY6.mjs");
2207
2207
  const route53Config = config$1;
2208
2208
  return new Route53Provider({
2209
2209
  region: route53Config.region,
@@ -4137,33 +4137,6 @@ function isMainFrontendApp(appName, app, allApps) {
4137
4137
  for (const [name$1, a] of Object.entries(allApps)) if (a.type === "frontend") return name$1 === appName;
4138
4138
  return false;
4139
4139
  }
4140
- /**
4141
- * Generate public URL build args for a frontend app based on its dependencies.
4142
- *
4143
- * @param app - The frontend app configuration
4144
- * @param deployedUrls - Map of app name to deployed public URL
4145
- * @returns Array of build args like 'NEXT_PUBLIC_API_URL=https://api.example.com'
4146
- */
4147
- function generatePublicUrlBuildArgs(app, deployedUrls) {
4148
- const buildArgs = [];
4149
- for (const dep of app.dependencies) {
4150
- const publicUrl = deployedUrls[dep];
4151
- if (publicUrl) {
4152
- const envVarName = `NEXT_PUBLIC_${dep.toUpperCase()}_URL`;
4153
- buildArgs.push(`${envVarName}=${publicUrl}`);
4154
- }
4155
- }
4156
- return buildArgs;
4157
- }
4158
- /**
4159
- * Get public URL arg names from app dependencies.
4160
- *
4161
- * @param app - The frontend app configuration
4162
- * @returns Array of arg names like 'NEXT_PUBLIC_API_URL'
4163
- */
4164
- function getPublicUrlArgNames(app) {
4165
- return app.dependencies.map((dep) => `NEXT_PUBLIC_${dep.toUpperCase()}_URL`);
4166
- }
4167
4140
 
4168
4141
  //#endregion
4169
4142
  //#region src/deploy/env-resolver.ts
@@ -4224,6 +4197,12 @@ function resolveEnvVar(varName, context) {
4224
4197
  if (context.masterKey) return context.masterKey;
4225
4198
  break;
4226
4199
  }
4200
+ if (context.dependencyUrls && varName.endsWith("_URL")) {
4201
+ let depName;
4202
+ if (varName.startsWith("NEXT_PUBLIC_")) depName = varName.slice(12, -4).toLowerCase();
4203
+ else depName = varName.slice(0, -4).toLowerCase();
4204
+ if (context.dependencyUrls[depName]) return context.dependencyUrls[depName];
4205
+ }
4227
4206
  if (context.userSecrets) {
4228
4207
  if (context.userSecrets.custom[varName]) return context.userSecrets.custom[varName];
4229
4208
  if (varName in context.userSecrets.urls) return context.userSecrets.urls[varName];
@@ -4633,14 +4612,29 @@ function resolveSnifferFile(baseName) {
4633
4612
  */
4634
4613
  async function sniffAppEnvironment(app, appName, workspacePath, options = {}) {
4635
4614
  const { logWarnings = true } = options;
4636
- if (app.type === "frontend") return {
4637
- appName,
4638
- requiredEnvVars: []
4639
- };
4640
- if (app.requiredEnv && app.requiredEnv.length > 0) return {
4641
- appName,
4642
- requiredEnvVars: [...app.requiredEnv]
4643
- };
4615
+ if (app.type === "frontend") {
4616
+ const depVars = (app.dependencies ?? []).map((dep) => `NEXT_PUBLIC_${dep.toUpperCase()}_URL`);
4617
+ if (app.config) {
4618
+ const sniffedVars = [];
4619
+ const configPaths = [];
4620
+ if (app.config.client) configPaths.push(app.config.client);
4621
+ if (app.config.server) configPaths.push(app.config.server);
4622
+ for (const configPath of configPaths) {
4623
+ const result = await sniffEntryFile(configPath, app.path, workspacePath);
4624
+ if (logWarnings && result.error) console.warn(`[sniffer] ${appName}: Config file "${configPath}" threw error during sniffing (env vars still captured): ${result.error.message}`);
4625
+ sniffedVars.push(...result.envVars);
4626
+ }
4627
+ const allVars = [...new Set([...depVars, ...sniffedVars])];
4628
+ return {
4629
+ appName,
4630
+ requiredEnvVars: allVars
4631
+ };
4632
+ }
4633
+ return {
4634
+ appName,
4635
+ requiredEnvVars: depVars
4636
+ };
4637
+ }
4644
4638
  if (app.entry) {
4645
4639
  const result = await sniffEntryFile(app.entry, app.path, workspacePath);
4646
4640
  if (logWarnings && result.error) console.warn(`[sniffer] ${appName}: Entry file threw error during sniffing (env vars still captured): ${result.error.message}`);
@@ -5596,6 +5590,10 @@ async function workspaceDeployCommand(workspace, options) {
5596
5590
  buildArgs
5597
5591
  });
5598
5592
  const backendHost = resolveHost(appName, app, stage, dokployConfig, false);
5593
+ const dependencyUrls = {};
5594
+ if (app.dependencies) {
5595
+ for (const dep of app.dependencies) if (publicUrls[dep]) dependencyUrls[dep] = publicUrls[dep];
5596
+ }
5599
5597
  const envContext = {
5600
5598
  app,
5601
5599
  appName,
@@ -5615,7 +5613,8 @@ async function workspaceDeployCommand(workspace, options) {
5615
5613
  appHostname: backendHost,
5616
5614
  frontendUrls,
5617
5615
  userSecrets: stageSecrets ?? void 0,
5618
- masterKey: appSecrets?.masterKey
5616
+ masterKey: appSecrets?.masterKey,
5617
+ dependencyUrls
5619
5618
  };
5620
5619
  const appRequirements = sniffedApps.get(appName);
5621
5620
  const sniffedVars = appRequirements?.requiredEnvVars ?? [];
@@ -5701,8 +5700,33 @@ async function workspaceDeployCommand(workspace, options) {
5701
5700
  else logger$1.log(` Found existing application: ${application.applicationId}`);
5702
5701
  }
5703
5702
  setApplicationId(state, appName, application.applicationId);
5704
- const buildArgs = generatePublicUrlBuildArgs(app, publicUrls);
5705
- if (buildArgs.length > 0) logger$1.log(` Public URLs: ${buildArgs.join(", ")}`);
5703
+ const dependencyUrls = {};
5704
+ if (app.dependencies) {
5705
+ for (const dep of app.dependencies) if (publicUrls[dep]) dependencyUrls[dep] = publicUrls[dep];
5706
+ }
5707
+ const isMainFrontend = isMainFrontendApp(appName, app, workspace.apps);
5708
+ const frontendHost = resolveHost(appName, app, stage, dokployConfig, isMainFrontend);
5709
+ const envContext = {
5710
+ app,
5711
+ appName,
5712
+ stage,
5713
+ state,
5714
+ appHostname: frontendHost,
5715
+ frontendUrls: [],
5716
+ userSecrets: stageSecrets ?? void 0,
5717
+ dependencyUrls
5718
+ };
5719
+ const sniffedVars = sniffedApps.get(appName)?.requiredEnvVars ?? [];
5720
+ const { valid, missing, resolved } = validateEnvVars(sniffedVars, envContext);
5721
+ if (!valid) throw new Error(formatMissingVarsError(appName, missing, stage));
5722
+ if (Object.keys(resolved).length > 0) logger$1.log(` Resolved ${Object.keys(resolved).length} env vars: ${Object.keys(resolved).join(", ")}`);
5723
+ const buildArgs = [];
5724
+ const publicUrlArgNames = [];
5725
+ for (const [key, value] of Object.entries(resolved)) if (key.startsWith("NEXT_PUBLIC_")) {
5726
+ buildArgs.push(`${key}=${value}`);
5727
+ publicUrlArgNames.push(key);
5728
+ }
5729
+ if (buildArgs.length > 0) logger$1.log(` Build args: ${publicUrlArgNames.join(", ")}`);
5706
5730
  const imageName = `${workspace.name}-${appName}`;
5707
5731
  const imageRef = registry ? `${registry}/${imageName}:${imageTag}` : `${imageName}:${imageTag}`;
5708
5732
  logger$1.log(` Building Docker image: ${imageRef}`);
@@ -5716,19 +5740,18 @@ async function workspaceDeployCommand(workspace, options) {
5716
5740
  appName
5717
5741
  },
5718
5742
  buildArgs,
5719
- publicUrlArgs: getPublicUrlArgNames(app)
5743
+ publicUrlArgs: publicUrlArgNames
5720
5744
  });
5721
5745
  const envVars = [
5722
5746
  `NODE_ENV=production`,
5723
5747
  `PORT=${app.port}`,
5724
5748
  `STAGE=${stage}`
5725
5749
  ];
5750
+ for (const [key, value] of Object.entries(resolved)) envVars.push(`${key}=${value}`);
5726
5751
  await api.saveDockerProvider(application.applicationId, imageRef, { registryId });
5727
5752
  await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
5728
5753
  logger$1.log(` Deploying to Dokploy...`);
5729
5754
  await api.deployApplication(application.applicationId);
5730
- const isMainFrontend = isMainFrontendApp(appName, app, workspace.apps);
5731
- const frontendHost = resolveHost(appName, app, stage, dokployConfig, isMainFrontend);
5732
5755
  const existingFrontendDomains = await api.getDomainsByApplicationId(application.applicationId);
5733
5756
  const existingFrontendDomain = existingFrontendDomains.find((d) => d.host === frontendHost);
5734
5757
  if (existingFrontendDomain) {
@@ -7083,6 +7106,7 @@ function generateMonorepoFiles(options, _template) {
7083
7106
  "@biomejs/biome": "~2.3.0",
7084
7107
  "@geekmidas/cli": GEEKMIDAS_VERSIONS["@geekmidas/cli"],
7085
7108
  esbuild: "~0.27.0",
7109
+ tsx: "~4.20.0",
7086
7110
  turbo: "~2.3.0",
7087
7111
  typescript: "~5.8.2",
7088
7112
  vitest: "~4.0.0"
@@ -7329,7 +7353,8 @@ export default defineWorkspace({
7329
7353
  port: 3000,
7330
7354
  routes: '${getRoutesGlob()}',
7331
7355
  envParser: './src/config/env#envParser',
7332
- logger: './src/config/logger#logger',`;
7356
+ logger: './src/config/logger#logger',
7357
+ dependencies: ['auth'],`;
7333
7358
  if (telescope) config$1 += `
7334
7359
  telescope: {
7335
7360
  enabled: true,
@@ -7354,6 +7379,10 @@ export default defineWorkspace({
7354
7379
  path: 'apps/web',
7355
7380
  port: 3001,
7356
7381
  dependencies: ['api', 'auth'],
7382
+ config: {
7383
+ client: './src/config/client.ts',
7384
+ server: './src/config/server.ts',
7385
+ },
7357
7386
  client: {
7358
7387
  output: './src/api',
7359
7388
  },
@@ -9948,12 +9977,42 @@ export function getQueryClient() {
9948
9977
  if (!browserQueryClient) browserQueryClient = makeQueryClient();
9949
9978
  return browserQueryClient;
9950
9979
  }
9980
+ `;
9981
+ const clientConfigTs = `import { EnvironmentParser } from '@geekmidas/envkit';
9982
+
9983
+ // Client config - only NEXT_PUBLIC_* vars (available in browser)
9984
+ // These values are inlined at build time by Next.js
9985
+ const envParser = new EnvironmentParser({
9986
+ NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
9987
+ NEXT_PUBLIC_AUTH_URL: process.env.NEXT_PUBLIC_AUTH_URL,
9988
+ });
9989
+
9990
+ export const clientConfig = envParser
9991
+ .create((get) => ({
9992
+ apiUrl: get('NEXT_PUBLIC_API_URL').string(),
9993
+ authUrl: get('NEXT_PUBLIC_AUTH_URL').string(),
9994
+ }))
9995
+ .parse();
9996
+ `;
9997
+ const serverConfigTs = `import { EnvironmentParser } from '@geekmidas/envkit';
9998
+
9999
+ // Server config - all env vars (server-side only, not exposed to browser)
10000
+ // Access these only in Server Components, Route Handlers, or Server Actions
10001
+ const envParser = new EnvironmentParser({ ...process.env });
10002
+
10003
+ export const serverConfig = envParser
10004
+ .create((get) => ({
10005
+ // Add server-only secrets here
10006
+ // Example: stripeSecretKey: get('STRIPE_SECRET_KEY').string(),
10007
+ }))
10008
+ .parse();
9951
10009
  `;
9952
10010
  const authClientTs = `import { createAuthClient } from 'better-auth/react';
9953
10011
  import { magicLinkClient } from 'better-auth/client/plugins';
10012
+ import { clientConfig } from '~/config/client';
9954
10013
 
9955
10014
  export const authClient = createAuthClient({
9956
- baseURL: process.env.NEXT_PUBLIC_AUTH_URL || 'http://localhost:3002',
10015
+ baseURL: clientConfig.authUrl,
9957
10016
  plugins: [magicLinkClient()],
9958
10017
  });
9959
10018
 
@@ -9974,9 +10033,10 @@ export function Providers({ children }: { children: React.ReactNode }) {
9974
10033
  `;
9975
10034
  const apiIndexTs = `import { createApi } from './openapi';
9976
10035
  import { getQueryClient } from '~/lib/query-client';
10036
+ import { clientConfig } from '~/config/client';
9977
10037
 
9978
10038
  export const api = createApi({
9979
- baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000',
10039
+ baseURL: clientConfig.apiUrl,
9980
10040
  queryClient: getQueryClient(),
9981
10041
  });
9982
10042
  `;
@@ -10097,6 +10157,14 @@ node_modules/
10097
10157
  path: "apps/web/src/app/page.tsx",
10098
10158
  content: pageTsx
10099
10159
  },
10160
+ {
10161
+ path: "apps/web/src/config/client.ts",
10162
+ content: clientConfigTs
10163
+ },
10164
+ {
10165
+ path: "apps/web/src/config/server.ts",
10166
+ content: serverConfigTs
10167
+ },
10100
10168
  {
10101
10169
  path: "apps/web/src/lib/query-client.ts",
10102
10170
  content: queryClientTs