@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.
- package/CHANGELOG.md +12 -0
- package/dist/{bundler-B4AackW5.mjs → bundler-C5xkxnyr.mjs} +2 -2
- package/dist/{bundler-B4AackW5.mjs.map → bundler-C5xkxnyr.mjs.map} +1 -1
- package/dist/{bundler-BhhfkI9T.cjs → bundler-i-az1DZ2.cjs} +2 -2
- package/dist/{bundler-BhhfkI9T.cjs.map → bundler-i-az1DZ2.cjs.map} +1 -1
- package/dist/index.cjs +701 -707
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +689 -695
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-BYxAWwok.cjs → openapi-CsCNpSf8.cjs} +1 -1
- package/dist/{openapi-BYxAWwok.cjs.map → openapi-CsCNpSf8.cjs.map} +1 -1
- package/dist/{openapi-DenF-okj.mjs → openapi-kvwpKbNe.mjs} +1 -1
- package/dist/{openapi-DenF-okj.mjs.map → openapi-kvwpKbNe.mjs.map} +1 -1
- package/dist/openapi.cjs +1 -1
- package/dist/openapi.mjs +1 -1
- package/dist/{storage-DOEtT2Hr.cjs → storage-ChVQI_G7.cjs} +1 -1
- package/dist/{storage-dbb9RyBl.mjs → storage-CpMNB77O.mjs} +1 -1
- package/dist/{storage-dbb9RyBl.mjs.map → storage-CpMNB77O.mjs.map} +1 -1
- package/dist/{storage-B1wvztiJ.cjs → storage-DLEb8Dkd.cjs} +1 -1
- package/dist/{storage-B1wvztiJ.cjs.map → storage-DLEb8Dkd.cjs.map} +1 -1
- package/dist/{storage-Cs4WBsc4.mjs → storage-mwbL7PhP.mjs} +1 -1
- package/dist/{sync-DGXXSk2v.cjs → sync-BWD_I5Ai.cjs} +2 -2
- package/dist/{sync-DGXXSk2v.cjs.map → sync-BWD_I5Ai.cjs.map} +1 -1
- package/dist/sync-ByaRPBxh.cjs +4 -0
- package/dist/{sync-COnAugP-.mjs → sync-CYBVB64f.mjs} +1 -1
- package/dist/{sync-D_NowTkZ.mjs → sync-lExOTa9t.mjs} +2 -2
- package/dist/{sync-D_NowTkZ.mjs.map → sync-lExOTa9t.mjs.map} +1 -1
- package/package.json +2 -2
- package/src/credentials/__tests__/fullDockerPorts.spec.ts +144 -0
- package/src/credentials/__tests__/helpers.ts +112 -0
- package/src/credentials/__tests__/prepareEntryCredentials.spec.ts +125 -0
- package/src/credentials/__tests__/readonlyDockerPorts.spec.ts +190 -0
- package/src/credentials/__tests__/workspaceCredentials.spec.ts +209 -0
- package/src/credentials/index.ts +826 -0
- package/src/dev/index.ts +48 -830
- package/src/exec/index.ts +120 -0
- package/src/setup/index.ts +4 -1
- package/src/test/index.ts +32 -109
- 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
|
+
}
|
package/src/setup/index.ts
CHANGED
|
@@ -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 {
|
|
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 {
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
95
|
-
console.log(
|
|
96
|
-
` 🔗 Loaded ${Object.keys(dependencyEnv).length} dependency URL(s)`,
|
|
97
|
-
);
|
|
98
|
-
}
|
|
51
|
+
let finalCredentials = { ...result.credentials };
|
|
99
52
|
|
|
100
|
-
|
|
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
|
|
112
|
-
const
|
|
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
|
-
|
|
67
|
+
filtered[key] = value;
|
|
116
68
|
}
|
|
117
69
|
}
|
|
118
|
-
|
|
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.
|
|
155
|
-
|
|
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
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
...
|
|
127
|
+
...finalCredentials,
|
|
205
128
|
NODE_ENV: 'test',
|
|
206
129
|
NODE_OPTIONS: nodeOptions,
|
|
207
130
|
},
|
package/dist/sync-D1Pa30oV.cjs
DELETED