@geekmidas/cli 1.10.17 → 1.10.19

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 (39) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/{bundler-B4AackW5.mjs → bundler-C5xkxnyr.mjs} +2 -2
  3. package/dist/{bundler-B4AackW5.mjs.map → bundler-C5xkxnyr.mjs.map} +1 -1
  4. package/dist/{bundler-BhhfkI9T.cjs → bundler-i-az1DZ2.cjs} +2 -2
  5. package/dist/{bundler-BhhfkI9T.cjs.map → bundler-i-az1DZ2.cjs.map} +1 -1
  6. package/dist/index.cjs +701 -707
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.mjs +689 -695
  9. package/dist/index.mjs.map +1 -1
  10. package/dist/{openapi-BYxAWwok.cjs → openapi-CsCNpSf8.cjs} +1 -1
  11. package/dist/{openapi-BYxAWwok.cjs.map → openapi-CsCNpSf8.cjs.map} +1 -1
  12. package/dist/{openapi-DenF-okj.mjs → openapi-kvwpKbNe.mjs} +1 -1
  13. package/dist/{openapi-DenF-okj.mjs.map → openapi-kvwpKbNe.mjs.map} +1 -1
  14. package/dist/openapi.cjs +1 -1
  15. package/dist/openapi.mjs +1 -1
  16. package/dist/{storage-DOEtT2Hr.cjs → storage-ChVQI_G7.cjs} +1 -1
  17. package/dist/{storage-dbb9RyBl.mjs → storage-CpMNB77O.mjs} +1 -1
  18. package/dist/{storage-dbb9RyBl.mjs.map → storage-CpMNB77O.mjs.map} +1 -1
  19. package/dist/{storage-B1wvztiJ.cjs → storage-DLEb8Dkd.cjs} +1 -1
  20. package/dist/{storage-B1wvztiJ.cjs.map → storage-DLEb8Dkd.cjs.map} +1 -1
  21. package/dist/{storage-Cs4WBsc4.mjs → storage-mwbL7PhP.mjs} +1 -1
  22. package/dist/{sync-DGXXSk2v.cjs → sync-BWD_I5Ai.cjs} +2 -2
  23. package/dist/{sync-DGXXSk2v.cjs.map → sync-BWD_I5Ai.cjs.map} +1 -1
  24. package/dist/sync-ByaRPBxh.cjs +4 -0
  25. package/dist/{sync-COnAugP-.mjs → sync-CYBVB64f.mjs} +1 -1
  26. package/dist/{sync-D_NowTkZ.mjs → sync-lExOTa9t.mjs} +2 -2
  27. package/dist/{sync-D_NowTkZ.mjs.map → sync-lExOTa9t.mjs.map} +1 -1
  28. package/package.json +2 -2
  29. package/src/credentials/__tests__/fullDockerPorts.spec.ts +144 -0
  30. package/src/credentials/__tests__/helpers.ts +112 -0
  31. package/src/credentials/__tests__/prepareEntryCredentials.spec.ts +125 -0
  32. package/src/credentials/__tests__/readonlyDockerPorts.spec.ts +190 -0
  33. package/src/credentials/__tests__/workspaceCredentials.spec.ts +209 -0
  34. package/src/credentials/index.ts +826 -0
  35. package/src/dev/index.ts +48 -830
  36. package/src/exec/index.ts +120 -0
  37. package/src/setup/index.ts +4 -1
  38. package/src/test/index.ts +32 -109
  39. package/dist/sync-D1Pa30oV.cjs +0 -4
@@ -0,0 +1,120 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { mkdir } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import {
5
+ createCredentialsPreload,
6
+ loadEnvFiles,
7
+ prepareEntryCredentials,
8
+ } from '../credentials';
9
+
10
+ const logger = console;
11
+
12
+ /**
13
+ * Options for the exec command.
14
+ */
15
+ export interface ExecOptions {
16
+ /** Working directory */
17
+ cwd?: string;
18
+ }
19
+
20
+ /**
21
+ * Run a command with secrets injected into Credentials.
22
+ * Uses Node's --import flag to preload a script that populates Credentials
23
+ * before the command loads any modules that depend on them.
24
+ *
25
+ * @example
26
+ * ```bash
27
+ * gkm exec -- npx @better-auth/cli migrate
28
+ * gkm exec -- npx prisma migrate dev
29
+ * ```
30
+ */
31
+ export async function execCommand(
32
+ commandArgs: string[],
33
+ options: ExecOptions = {},
34
+ ): Promise<void> {
35
+ const cwd = options.cwd ?? process.cwd();
36
+
37
+ if (commandArgs.length === 0) {
38
+ throw new Error('No command specified. Usage: gkm exec -- <command>');
39
+ }
40
+
41
+ // Load .env files
42
+ const defaultEnv = loadEnvFiles('.env');
43
+ if (defaultEnv.loaded.length > 0) {
44
+ logger.log(`📦 Loaded env: ${defaultEnv.loaded.join(', ')}`);
45
+ }
46
+
47
+ // Prepare credentials: loads secrets, resolves Docker ports, rewrites URLs,
48
+ // injects dependency URLs. Uses readonly port mode (no probing for new ports).
49
+ const { credentials, secretsJsonPath, appName } =
50
+ await prepareEntryCredentials({ cwd, resolveDockerPorts: 'readonly' });
51
+
52
+ if (appName) {
53
+ logger.log(`📦 App: ${appName}`);
54
+ }
55
+
56
+ const secretCount = Object.keys(credentials).filter(
57
+ (k) => k !== 'PORT',
58
+ ).length;
59
+ if (secretCount > 0) {
60
+ logger.log(`🔐 Loaded ${secretCount} secret(s)`);
61
+ }
62
+
63
+ // Create preload script that injects Credentials
64
+ // Create in cwd so package resolution works (finds node_modules in app directory)
65
+ const preloadDir = join(cwd, '.gkm');
66
+ await mkdir(preloadDir, { recursive: true });
67
+ const preloadPath = join(preloadDir, 'credentials-preload.ts');
68
+ await createCredentialsPreload(preloadPath, secretsJsonPath);
69
+
70
+ // Build command
71
+ const [cmd, ...rawArgs] = commandArgs;
72
+
73
+ if (!cmd) {
74
+ throw new Error('No command specified');
75
+ }
76
+
77
+ // Replace template variables in command args (e.g. $PORT -> resolved port)
78
+ const args = rawArgs.map((arg) =>
79
+ arg.replace(/\$PORT\b/g, credentials.PORT ?? '3000'),
80
+ );
81
+
82
+ logger.log(`🚀 Running: ${[cmd, ...args].join(' ')}`);
83
+
84
+ // Merge NODE_OPTIONS with existing value (if any)
85
+ // Add tsx loader first so our .ts preload can be loaded
86
+ const existingNodeOptions = process.env.NODE_OPTIONS ?? '';
87
+ const tsxImport = '--import=tsx';
88
+ const preloadImport = `--import=${preloadPath}`;
89
+
90
+ // Build NODE_OPTIONS: existing + tsx loader + our preload
91
+ const nodeOptions = [existingNodeOptions, tsxImport, preloadImport]
92
+ .filter(Boolean)
93
+ .join(' ');
94
+
95
+ // Spawn the command with secrets in both:
96
+ // 1. Environment variables (for tools that read process.env directly)
97
+ // 2. Preload script (for tools that use Credentials object)
98
+ const child = spawn(cmd, args, {
99
+ cwd,
100
+ stdio: 'inherit',
101
+ env: {
102
+ ...process.env,
103
+ ...credentials, // Inject secrets as env vars
104
+ NODE_OPTIONS: nodeOptions,
105
+ },
106
+ });
107
+
108
+ // Wait for the command to complete
109
+ const exitCode = await new Promise<number>((resolve) => {
110
+ child.on('close', (code: number | null) => resolve(code ?? 0));
111
+ child.on('error', (error: Error) => {
112
+ logger.error(`Failed to run command: ${error.message}`);
113
+ resolve(1);
114
+ });
115
+ });
116
+
117
+ if (exitCode !== 0) {
118
+ process.exit(exitCode);
119
+ }
120
+ }
@@ -2,7 +2,10 @@ import { existsSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import prompts from 'prompts';
4
4
  import { loadWorkspaceConfig } from '../config.js';
5
- import { resolveServicePorts, startWorkspaceServices } from '../dev/index.js';
5
+ import {
6
+ resolveServicePorts,
7
+ startWorkspaceServices,
8
+ } from '../credentials/index.js';
6
9
  import {
7
10
  createStageSecrets,
8
11
  generateConnectionUrls,
package/src/test/index.ts CHANGED
@@ -1,20 +1,12 @@
1
1
  import { spawn } from 'node:child_process';
2
- import { mkdir, writeFile } from 'node:fs/promises';
2
+ import { writeFile } from 'node:fs/promises';
3
3
  import { join } from 'node:path';
4
- import { loadWorkspaceAppInfo } from '../config';
5
- import { sniffAppEnvironment } from '../deploy/sniffer';
6
4
  import {
7
5
  createCredentialsPreload,
8
6
  loadEnvFiles,
9
- loadPortState,
10
- parseComposePortMappings,
11
- resolveServicePorts,
12
- rewriteUrlsWithPorts,
13
- startComposeServices,
14
- startWorkspaceServices,
15
- } from '../dev/index';
16
- import { readStageSecrets, toEmbeddableSecrets } from '../secrets/storage';
17
- import { getDependencyEnvVars } from '../workspace/index';
7
+ prepareEntryCredentials,
8
+ } from '../credentials';
9
+ import { sniffAppEnvironment } from '../deploy/sniffer';
18
10
 
19
11
  export interface TestOptions {
20
12
  /** Stage to load secrets from (default: development) */
@@ -47,124 +39,55 @@ export async function testCommand(options: TestOptions = {}): Promise<void> {
47
39
  console.log(` 📦 Loaded env: ${defaultEnv.loaded.join(', ')}`);
48
40
  }
49
41
 
50
- // 2. Load and decrypt secrets
51
- let secretsEnv: Record<string, string> = {};
52
- try {
53
- const secrets = await readStageSecrets(stage);
54
- if (secrets) {
55
- secretsEnv = toEmbeddableSecrets(secrets);
56
- console.log(
57
- ` 🔐 Loaded ${Object.keys(secretsEnv).length} secrets from ${stage}`,
58
- );
59
- } else {
60
- console.log(` No secrets found for ${stage}`);
61
- }
62
- } catch (error) {
63
- if (error instanceof Error && error.message.includes('key not found')) {
64
- console.log(` Decryption key not found for ${stage}`);
65
- } else {
66
- throw error;
67
- }
68
- }
69
-
70
- // 3. Load workspace config + start Docker services with secrets
71
- let dependencyEnv: Record<string, string> = {};
72
- try {
73
- const appInfo = await loadWorkspaceAppInfo(cwd);
74
-
75
- // Resolve ports and start Docker services with secrets so that
76
- // POSTGRES_USER, POSTGRES_PASSWORD, etc. are interpolated correctly
77
- const resolvedPorts = await resolveServicePorts(appInfo.workspaceRoot);
78
- await startWorkspaceServices(
79
- appInfo.workspace,
80
- resolvedPorts.dockerEnv,
81
- secretsEnv,
82
- );
83
-
84
- // Rewrite URLs with resolved Docker ports and hostnames
85
- if (resolvedPorts.mappings.length > 0) {
86
- secretsEnv = rewriteUrlsWithPorts(secretsEnv, resolvedPorts);
87
- console.log(
88
- ` 🔌 Applied ${Object.keys(resolvedPorts.ports).length} port mapping(s)`,
89
- );
90
- }
91
-
92
- dependencyEnv = getDependencyEnvVars(appInfo.workspace, appInfo.appName);
42
+ // 2. Prepare credentials: loads secrets, resolves Docker ports,
43
+ // starts services, rewrites URLs, injects dependency URLs
44
+ const result = await prepareEntryCredentials({
45
+ stages: [stage],
46
+ startDocker: true,
47
+ secretsFileName: 'test-secrets.json',
48
+ resolveDockerPorts: 'full',
49
+ });
93
50
 
94
- if (Object.keys(dependencyEnv).length > 0) {
95
- console.log(
96
- ` 🔗 Loaded ${Object.keys(dependencyEnv).length} dependency URL(s)`,
97
- );
98
- }
51
+ let finalCredentials = { ...result.credentials };
99
52
 
100
- // Sniff to detect which env vars the app needs
53
+ // 3. Sniff env vars to filter only what the app needs (workspace only)
54
+ if (result.appInfo) {
101
55
  const sniffed = await sniffAppEnvironment(
102
- appInfo.app,
103
- appInfo.appName,
104
- appInfo.workspaceRoot,
56
+ result.appInfo.app,
57
+ result.appInfo.appName,
58
+ result.appInfo.workspaceRoot,
105
59
  { logWarnings: false },
106
60
  );
107
61
 
108
- // Filter to only include what the app needs
109
62
  if (sniffed.requiredEnvVars.length > 0) {
110
63
  const needed = new Set(sniffed.requiredEnvVars);
111
- const allEnv = { ...secretsEnv, ...dependencyEnv };
112
- const filteredEnv: Record<string, string> = {};
113
- for (const [key, value] of Object.entries(allEnv)) {
64
+ const filtered: Record<string, string> = {};
65
+ for (const [key, value] of Object.entries(finalCredentials)) {
114
66
  if (needed.has(key)) {
115
- filteredEnv[key] = value;
67
+ filtered[key] = value;
116
68
  }
117
69
  }
118
- secretsEnv = {};
119
- dependencyEnv = filteredEnv;
70
+ finalCredentials = filtered;
120
71
  console.log(
121
72
  ` 🔍 Sniffed ${sniffed.requiredEnvVars.length} required env var(s)`,
122
73
  );
123
74
  }
124
- } catch {
125
- // Not in a workspace — start Docker services from local docker-compose.yml
126
- const composePath = join(cwd, 'docker-compose.yml');
127
- const mappings = parseComposePortMappings(composePath);
128
- if (mappings.length > 0) {
129
- const resolvedPorts = await resolveServicePorts(cwd);
130
- await startComposeServices(cwd, resolvedPorts.dockerEnv, secretsEnv);
131
-
132
- if (resolvedPorts.mappings.length > 0) {
133
- secretsEnv = rewriteUrlsWithPorts(secretsEnv, resolvedPorts);
134
- console.log(
135
- ` 🔌 Applied ${Object.keys(resolvedPorts.ports).length} port mapping(s)`,
136
- );
137
- } else {
138
- // Fallback to saved port state from a previous gkm dev run
139
- const ports = await loadPortState(cwd);
140
- if (Object.keys(ports).length > 0) {
141
- secretsEnv = rewriteUrlsWithPorts(secretsEnv, {
142
- dockerEnv: {},
143
- ports,
144
- mappings,
145
- });
146
- console.log(
147
- ` 🔌 Applied ${Object.keys(ports).length} port mapping(s)`,
148
- );
149
- }
150
- }
151
- }
152
75
  }
153
76
 
154
- // 4. Use a separate test database (append _test suffix)
155
- secretsEnv = rewriteDatabaseUrlForTests(secretsEnv);
77
+ // 4. Rewrite DATABASE_URL for test isolation (append _test suffix)
78
+ finalCredentials = rewriteDatabaseUrlForTests(finalCredentials);
156
79
 
157
80
  console.log('');
158
81
 
159
- // Write combined secrets to JSON and create credentials preload
160
- const allSecrets = { ...secretsEnv, ...dependencyEnv };
161
- const gkmDir = join(cwd, '.gkm');
162
- await mkdir(gkmDir, { recursive: true });
163
- const secretsJsonPath = join(gkmDir, 'test-secrets.json');
164
- await writeFile(secretsJsonPath, JSON.stringify(allSecrets, null, 2));
82
+ // 5. Write final credentials and create preload script
83
+ await writeFile(
84
+ result.secretsJsonPath,
85
+ JSON.stringify(finalCredentials, null, 2),
86
+ );
165
87
 
88
+ const gkmDir = join(cwd, '.gkm');
166
89
  const preloadPath = join(gkmDir, 'test-credentials-preload.ts');
167
- await createCredentialsPreload(preloadPath, secretsJsonPath);
90
+ await createCredentialsPreload(preloadPath, result.secretsJsonPath);
168
91
 
169
92
  // Merge NODE_OPTIONS with existing value (if any)
170
93
  const existingNodeOptions = process.env.NODE_OPTIONS ?? '';
@@ -201,7 +124,7 @@ export async function testCommand(options: TestOptions = {}): Promise<void> {
201
124
  stdio: 'inherit',
202
125
  env: {
203
126
  ...process.env,
204
- ...allSecrets,
127
+ ...finalCredentials,
205
128
  NODE_ENV: 'test',
206
129
  NODE_OPTIONS: nodeOptions,
207
130
  },
@@ -1,4 +0,0 @@
1
- const require_sync = require('./sync-DGXXSk2v.cjs');
2
-
3
- exports.pullSecrets = require_sync.pullSecrets;
4
- exports.pushSecrets = require_sync.pushSecrets;