@geekmidas/cli 0.37.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 +158 -67
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +138 -47
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/build/index.ts +23 -6
- package/src/deploy/docker.ts +20 -20
- package/src/deploy/index.ts +20 -19
- package/src/dev/__tests__/entry-integration.spec.ts +86 -2
- package/src/dev/index.ts +150 -11
- package/src/index.ts +18 -1
- package/src/init/generators/auth.ts +2 -0
package/dist/index.mjs
CHANGED
|
@@ -26,7 +26,7 @@ import prompts from "prompts";
|
|
|
26
26
|
|
|
27
27
|
//#region package.json
|
|
28
28
|
var name = "@geekmidas/cli";
|
|
29
|
-
var version = "0.
|
|
29
|
+
var version = "0.39.0";
|
|
30
30
|
var description = "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs";
|
|
31
31
|
var private$1 = false;
|
|
32
32
|
var type = "module";
|
|
@@ -1541,19 +1541,43 @@ function findSecretsRoot(startDir) {
|
|
|
1541
1541
|
return startDir;
|
|
1542
1542
|
}
|
|
1543
1543
|
/**
|
|
1544
|
-
*
|
|
1545
|
-
*
|
|
1544
|
+
* Generate the credentials injection code snippet.
|
|
1545
|
+
* This is the common logic used by both entry wrapper and exec preload.
|
|
1546
|
+
* @internal
|
|
1546
1547
|
*/
|
|
1547
|
-
|
|
1548
|
-
|
|
1548
|
+
function generateCredentialsInjection(secretsJsonPath) {
|
|
1549
|
+
return `import { Credentials } from '@geekmidas/envkit/credentials';
|
|
1549
1550
|
import { existsSync, readFileSync } from 'node:fs';
|
|
1550
1551
|
|
|
1551
|
-
// Inject dev secrets into Credentials
|
|
1552
|
+
// Inject dev secrets into Credentials
|
|
1552
1553
|
const secretsPath = '${secretsJsonPath}';
|
|
1553
1554
|
if (existsSync(secretsPath)) {
|
|
1554
|
-
|
|
1555
|
+
const secrets = JSON.parse(readFileSync(secretsPath, 'utf-8'));
|
|
1556
|
+
Object.assign(Credentials, secrets);
|
|
1557
|
+
// Debug: uncomment to verify preload is running
|
|
1558
|
+
// console.log('[gkm preload] Injected', Object.keys(secrets).length, 'credentials');
|
|
1555
1559
|
}
|
|
1556
|
-
|
|
1560
|
+
`;
|
|
1561
|
+
}
|
|
1562
|
+
/**
|
|
1563
|
+
* Create a preload script that injects secrets into Credentials.
|
|
1564
|
+
* Used by `gkm exec` to inject secrets before running any command.
|
|
1565
|
+
* @internal Exported for testing
|
|
1566
|
+
*/
|
|
1567
|
+
async function createCredentialsPreload(preloadPath, secretsJsonPath) {
|
|
1568
|
+
const content = `/**
|
|
1569
|
+
* Credentials preload generated by 'gkm exec'
|
|
1570
|
+
* This file is loaded via NODE_OPTIONS="--import <path>"
|
|
1571
|
+
*/
|
|
1572
|
+
${generateCredentialsInjection(secretsJsonPath)}`;
|
|
1573
|
+
await writeFile(preloadPath, content);
|
|
1574
|
+
}
|
|
1575
|
+
/**
|
|
1576
|
+
* Create a wrapper script that injects secrets before importing the entry file.
|
|
1577
|
+
* @internal Exported for testing
|
|
1578
|
+
*/
|
|
1579
|
+
async function createEntryWrapper(wrapperPath, entryPath, secretsJsonPath) {
|
|
1580
|
+
const credentialsInjection = secretsJsonPath ? `${generateCredentialsInjection(secretsJsonPath)}
|
|
1557
1581
|
` : "";
|
|
1558
1582
|
const content = `#!/usr/bin/env node
|
|
1559
1583
|
/**
|
|
@@ -1579,7 +1603,8 @@ async function prepareEntryCredentials(options) {
|
|
|
1579
1603
|
workspaceAppPort = appConfig.app.port;
|
|
1580
1604
|
secretsRoot = appConfig.workspaceRoot;
|
|
1581
1605
|
appName = appConfig.appName;
|
|
1582
|
-
} catch {
|
|
1606
|
+
} catch (error) {
|
|
1607
|
+
logger$8.log(`⚠️ Could not load workspace config: ${error.message}`);
|
|
1583
1608
|
secretsRoot = findSecretsRoot(cwd);
|
|
1584
1609
|
appName = getAppNameFromCwd(cwd) ?? void 0;
|
|
1585
1610
|
}
|
|
@@ -1609,7 +1634,7 @@ async function entryDevCommand(options) {
|
|
|
1609
1634
|
if (!existsSync(entryPath)) throw new Error(`Entry file not found: ${entryPath}`);
|
|
1610
1635
|
const defaultEnv = loadEnvFiles(".env");
|
|
1611
1636
|
if (defaultEnv.loaded.length > 0) logger$8.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
1612
|
-
const { credentials, resolvedPort, secretsJsonPath, appName } = await prepareEntryCredentials({ explicitPort: options.port });
|
|
1637
|
+
const { credentials, resolvedPort, secretsJsonPath, appName } = await prepareEntryCredentials({ explicitPort: options.portExplicit ? options.port : void 0 });
|
|
1613
1638
|
if (appName) logger$8.log(`📦 App: ${appName} (port ${resolvedPort})`);
|
|
1614
1639
|
logger$8.log(`🚀 Starting entry file: ${entry} on port ${resolvedPort}`);
|
|
1615
1640
|
if (Object.keys(credentials).length > 1) logger$8.log(`🔐 Loaded ${Object.keys(credentials).length - 1} secret(s) + PORT`);
|
|
@@ -1858,6 +1883,59 @@ start({
|
|
|
1858
1883
|
await fsWriteFile(serverPath, content);
|
|
1859
1884
|
}
|
|
1860
1885
|
};
|
|
1886
|
+
/**
|
|
1887
|
+
* Run a command with secrets injected into Credentials.
|
|
1888
|
+
* Uses Node's --import flag to preload a script that populates Credentials
|
|
1889
|
+
* before the command loads any modules that depend on them.
|
|
1890
|
+
*
|
|
1891
|
+
* @example
|
|
1892
|
+
* ```bash
|
|
1893
|
+
* gkm exec -- npx @better-auth/cli migrate
|
|
1894
|
+
* gkm exec -- npx prisma migrate dev
|
|
1895
|
+
* ```
|
|
1896
|
+
*/
|
|
1897
|
+
async function execCommand(commandArgs, options = {}) {
|
|
1898
|
+
const cwd = options.cwd ?? process.cwd();
|
|
1899
|
+
if (commandArgs.length === 0) throw new Error("No command specified. Usage: gkm exec -- <command>");
|
|
1900
|
+
const defaultEnv = loadEnvFiles(".env");
|
|
1901
|
+
if (defaultEnv.loaded.length > 0) logger$8.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
1902
|
+
const { credentials, secretsJsonPath, appName } = await prepareEntryCredentials({ cwd });
|
|
1903
|
+
if (appName) logger$8.log(`📦 App: ${appName}`);
|
|
1904
|
+
const secretCount = Object.keys(credentials).filter((k) => k !== "PORT").length;
|
|
1905
|
+
if (secretCount > 0) logger$8.log(`🔐 Loaded ${secretCount} secret(s)`);
|
|
1906
|
+
const preloadDir = join(cwd, ".gkm");
|
|
1907
|
+
await mkdir(preloadDir, { recursive: true });
|
|
1908
|
+
const preloadPath = join(preloadDir, "credentials-preload.ts");
|
|
1909
|
+
await createCredentialsPreload(preloadPath, secretsJsonPath);
|
|
1910
|
+
const [cmd, ...args] = commandArgs;
|
|
1911
|
+
if (!cmd) throw new Error("No command specified");
|
|
1912
|
+
logger$8.log(`🚀 Running: ${commandArgs.join(" ")}`);
|
|
1913
|
+
const existingNodeOptions = process.env.NODE_OPTIONS ?? "";
|
|
1914
|
+
const tsxImport = "--import tsx";
|
|
1915
|
+
const preloadImport = `--import ${preloadPath}`;
|
|
1916
|
+
const nodeOptions = [
|
|
1917
|
+
existingNodeOptions,
|
|
1918
|
+
tsxImport,
|
|
1919
|
+
preloadImport
|
|
1920
|
+
].filter(Boolean).join(" ");
|
|
1921
|
+
const child = spawn(cmd, args, {
|
|
1922
|
+
cwd,
|
|
1923
|
+
stdio: "inherit",
|
|
1924
|
+
env: {
|
|
1925
|
+
...process.env,
|
|
1926
|
+
...credentials,
|
|
1927
|
+
NODE_OPTIONS: nodeOptions
|
|
1928
|
+
}
|
|
1929
|
+
});
|
|
1930
|
+
const exitCode = await new Promise((resolve$1) => {
|
|
1931
|
+
child.on("close", (code) => resolve$1(code ?? 0));
|
|
1932
|
+
child.on("error", (error) => {
|
|
1933
|
+
logger$8.error(`Failed to run command: ${error.message}`);
|
|
1934
|
+
resolve$1(1);
|
|
1935
|
+
});
|
|
1936
|
+
});
|
|
1937
|
+
if (exitCode !== 0) process.exit(exitCode);
|
|
1938
|
+
}
|
|
1861
1939
|
|
|
1862
1940
|
//#endregion
|
|
1863
1941
|
//#region src/build/manifests.ts
|
|
@@ -1928,10 +2006,15 @@ const logger$6 = console;
|
|
|
1928
2006
|
async function buildCommand(options) {
|
|
1929
2007
|
const loadedConfig = await loadWorkspaceConfig();
|
|
1930
2008
|
if (loadedConfig.type === "workspace") {
|
|
1931
|
-
|
|
1932
|
-
|
|
2009
|
+
const cwd = resolve(process.cwd());
|
|
2010
|
+
const workspaceRoot = resolve(loadedConfig.workspace.root);
|
|
2011
|
+
const isAtWorkspaceRoot = cwd === workspaceRoot;
|
|
2012
|
+
if (isAtWorkspaceRoot) {
|
|
2013
|
+
logger$6.log("📦 Detected workspace configuration");
|
|
2014
|
+
return workspaceBuildCommand(loadedConfig.workspace, options);
|
|
2015
|
+
}
|
|
1933
2016
|
}
|
|
1934
|
-
const config$1 = await loadConfig();
|
|
2017
|
+
const config$1 = loadedConfig.type === "workspace" ? (await loadAppConfig()).gkmConfig : await loadConfig();
|
|
1935
2018
|
const resolved = resolveProviders(config$1, options);
|
|
1936
2019
|
const productionConfigFromGkm = getProductionConfigFromGkm(config$1);
|
|
1937
2020
|
const production = normalizeProductionConfig(options.production ?? false, productionConfigFromGkm);
|
|
@@ -3375,26 +3458,22 @@ function getImageRef(registry, imageName, tag) {
|
|
|
3375
3458
|
}
|
|
3376
3459
|
/**
|
|
3377
3460
|
* Build Docker image
|
|
3461
|
+
* @param imageRef - Full image reference (registry/name:tag)
|
|
3462
|
+
* @param appName - Name of the app (used for Dockerfile.{appName} in workspaces)
|
|
3378
3463
|
*/
|
|
3379
|
-
async function buildImage(imageRef) {
|
|
3464
|
+
async function buildImage(imageRef, appName) {
|
|
3380
3465
|
logger$4.log(`\n🔨 Building Docker image: ${imageRef}`);
|
|
3381
3466
|
const cwd = process.cwd();
|
|
3382
|
-
const
|
|
3383
|
-
|
|
3467
|
+
const lockfilePath = findLockfilePath(cwd);
|
|
3468
|
+
const lockfileDir = lockfilePath ? dirname(lockfilePath) : cwd;
|
|
3469
|
+
const inMonorepo = lockfileDir !== cwd;
|
|
3470
|
+
if (appName || inMonorepo) logger$4.log(" Generating Dockerfile for monorepo (turbo prune)...");
|
|
3384
3471
|
else logger$4.log(" Generating Dockerfile...");
|
|
3385
3472
|
await dockerCommand({});
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
if (lockfilePath) {
|
|
3391
|
-
const monorepoRoot = dirname(lockfilePath);
|
|
3392
|
-
const appRelPath = relative(monorepoRoot, cwd);
|
|
3393
|
-
dockerfilePath = join(appRelPath, ".gkm/docker/Dockerfile");
|
|
3394
|
-
buildCwd = monorepoRoot;
|
|
3395
|
-
logger$4.log(` Building from monorepo root: ${monorepoRoot}`);
|
|
3396
|
-
}
|
|
3397
|
-
}
|
|
3473
|
+
const dockerfileSuffix = appName ? `.${appName}` : "";
|
|
3474
|
+
const dockerfilePath = `.gkm/docker/Dockerfile${dockerfileSuffix}`;
|
|
3475
|
+
const buildCwd = lockfilePath && (inMonorepo || appName) ? lockfileDir : cwd;
|
|
3476
|
+
if (buildCwd !== cwd) logger$4.log(` Building from workspace root: ${buildCwd}`);
|
|
3398
3477
|
try {
|
|
3399
3478
|
execSync(`DOCKER_BUILDKIT=1 docker build --platform linux/amd64 -f ${dockerfilePath} -t ${imageRef} .`, {
|
|
3400
3479
|
cwd: buildCwd,
|
|
@@ -3431,7 +3510,7 @@ async function deployDocker(options) {
|
|
|
3431
3510
|
const { stage, tag, skipPush, masterKey, config: config$1 } = options;
|
|
3432
3511
|
const imageName = config$1.imageName;
|
|
3433
3512
|
const imageRef = getImageRef(config$1.registry, imageName, tag);
|
|
3434
|
-
await buildImage(imageRef);
|
|
3513
|
+
await buildImage(imageRef, config$1.appName);
|
|
3435
3514
|
if (!skipPush) if (!config$1.registry) logger$4.warn("\n⚠️ No registry configured. Use --skip-push or configure docker.registry in gkm.config.ts");
|
|
3436
3515
|
else await pushImage(imageRef);
|
|
3437
3516
|
logger$4.log("\n✅ Docker deployment ready!");
|
|
@@ -4143,11 +4222,25 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
4143
4222
|
await provisionServices(api, project.projectId, environmentId, workspace.name, dockerServices);
|
|
4144
4223
|
}
|
|
4145
4224
|
const deployedAppUrls = {};
|
|
4225
|
+
if (!skipBuild) {
|
|
4226
|
+
logger$1.log("\n🏗️ Building workspace...");
|
|
4227
|
+
try {
|
|
4228
|
+
await buildCommand({
|
|
4229
|
+
provider: "server",
|
|
4230
|
+
production: true,
|
|
4231
|
+
stage
|
|
4232
|
+
});
|
|
4233
|
+
logger$1.log(" ✓ Workspace build complete");
|
|
4234
|
+
} catch (error) {
|
|
4235
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4236
|
+
logger$1.log(` ✗ Workspace build failed: ${message}`);
|
|
4237
|
+
throw error;
|
|
4238
|
+
}
|
|
4239
|
+
}
|
|
4146
4240
|
logger$1.log("\n📦 Deploying applications...");
|
|
4147
4241
|
const results = [];
|
|
4148
4242
|
for (const appName of appsToDeployNames) {
|
|
4149
4243
|
const app = workspace.apps[appName];
|
|
4150
|
-
const appPath = app.path;
|
|
4151
4244
|
logger$1.log(`\n ${app.type === "backend" ? "⚙️" : "🌐"} Deploying ${appName}...`);
|
|
4152
4245
|
try {
|
|
4153
4246
|
const dokployAppName = `${workspace.name}-${appName}`;
|
|
@@ -4160,21 +4253,6 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
4160
4253
|
if (message.includes("already exists") || message.includes("duplicate")) logger$1.log(` Application already exists`);
|
|
4161
4254
|
else throw error;
|
|
4162
4255
|
}
|
|
4163
|
-
if (!skipBuild) {
|
|
4164
|
-
logger$1.log(` Building ${appName}...`);
|
|
4165
|
-
const originalCwd = process.cwd();
|
|
4166
|
-
const fullAppPath = `${workspace.root}/${appPath}`;
|
|
4167
|
-
try {
|
|
4168
|
-
process.chdir(fullAppPath);
|
|
4169
|
-
await buildCommand({
|
|
4170
|
-
provider: "server",
|
|
4171
|
-
production: true,
|
|
4172
|
-
stage
|
|
4173
|
-
});
|
|
4174
|
-
} finally {
|
|
4175
|
-
process.chdir(originalCwd);
|
|
4176
|
-
}
|
|
4177
|
-
}
|
|
4178
4256
|
const imageName = `${workspace.name}-${appName}`;
|
|
4179
4257
|
const imageRef = registry ? `${registry}/${imageName}:${imageTag}` : `${imageName}:${imageTag}`;
|
|
4180
4258
|
logger$1.log(` Building Docker image: ${imageRef}`);
|
|
@@ -4184,7 +4262,8 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
4184
4262
|
skipPush: false,
|
|
4185
4263
|
config: {
|
|
4186
4264
|
registry,
|
|
4187
|
-
imageName
|
|
4265
|
+
imageName,
|
|
4266
|
+
appName
|
|
4188
4267
|
}
|
|
4189
4268
|
});
|
|
4190
4269
|
const envVars = [`NODE_ENV=production`, `PORT=${app.port}`];
|
|
@@ -4552,7 +4631,9 @@ function generateAuthAppFiles(options) {
|
|
|
4552
4631
|
dev: "gkm dev --entry ./src/index.ts",
|
|
4553
4632
|
build: "tsc",
|
|
4554
4633
|
start: "node dist/index.js",
|
|
4555
|
-
typecheck: "tsc --noEmit"
|
|
4634
|
+
typecheck: "tsc --noEmit",
|
|
4635
|
+
"db:migrate": "gkm exec -- npx @better-auth/cli migrate",
|
|
4636
|
+
"db:generate": "gkm exec -- npx @better-auth/cli generate"
|
|
4556
4637
|
},
|
|
4557
4638
|
dependencies: {
|
|
4558
4639
|
[modelsPackage]: "workspace:*",
|
|
@@ -7466,6 +7547,16 @@ program.command("dev").description("Start development server with automatic relo
|
|
|
7466
7547
|
process.exit(1);
|
|
7467
7548
|
}
|
|
7468
7549
|
});
|
|
7550
|
+
program.command("exec").description("Run a command with secrets injected into Credentials").argument("<command...>", "Command to run (use -- before command)").action(async (commandArgs) => {
|
|
7551
|
+
try {
|
|
7552
|
+
const globalOptions = program.opts();
|
|
7553
|
+
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
7554
|
+
await execCommand(commandArgs);
|
|
7555
|
+
} catch (error) {
|
|
7556
|
+
console.error(error instanceof Error ? error.message : "Command failed");
|
|
7557
|
+
process.exit(1);
|
|
7558
|
+
}
|
|
7559
|
+
});
|
|
7469
7560
|
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) => {
|
|
7470
7561
|
try {
|
|
7471
7562
|
const globalOptions = program.opts();
|