@geekmidas/cli 0.30.0 → 0.32.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
@@ -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.30.0";
29
+ var version = "0.31.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";
@@ -1011,6 +1011,7 @@ function getProductionConfigFromGkm(config$1) {
1011
1011
  return void 0;
1012
1012
  }
1013
1013
  async function devCommand(options) {
1014
+ if (options.entry) return entryDevCommand(options);
1014
1015
  const defaultEnv = loadEnvFiles(".env");
1015
1016
  if (defaultEnv.loaded.length > 0) logger$8.log(`šŸ“¦ Loaded env: ${defaultEnv.loaded.join(", ")}`);
1016
1017
  const appName = getAppNameFromCwd();
@@ -1522,6 +1523,165 @@ async function buildServer(config$1, context, provider, enableOpenApi, appRoot =
1522
1523
  subscriberGenerator.build(context, allSubscribers, outputDir, { provider })
1523
1524
  ]);
1524
1525
  }
1526
+ /**
1527
+ * Find the directory containing .gkm/secrets/.
1528
+ * Walks up from cwd until it finds one, or returns cwd.
1529
+ * @internal Exported for testing
1530
+ */
1531
+ function findSecretsRoot(startDir) {
1532
+ let dir = startDir;
1533
+ while (dir !== "/") {
1534
+ if (existsSync(join(dir, ".gkm", "secrets"))) return dir;
1535
+ const parent = dirname(dir);
1536
+ if (parent === dir) break;
1537
+ dir = parent;
1538
+ }
1539
+ return startDir;
1540
+ }
1541
+ /**
1542
+ * Create a wrapper script that injects secrets before importing the entry file.
1543
+ * @internal Exported for testing
1544
+ */
1545
+ async function createEntryWrapper(wrapperPath, entryPath, secretsJsonPath) {
1546
+ const credentialsInjection = secretsJsonPath ? `import { Credentials } from '@geekmidas/envkit/credentials';
1547
+ import { existsSync, readFileSync } from 'node:fs';
1548
+
1549
+ // Inject dev secrets into Credentials (before app import)
1550
+ const secretsPath = '${secretsJsonPath}';
1551
+ if (existsSync(secretsPath)) {
1552
+ Object.assign(Credentials, JSON.parse(readFileSync(secretsPath, 'utf-8')));
1553
+ }
1554
+
1555
+ ` : "";
1556
+ const content = `#!/usr/bin/env node
1557
+ /**
1558
+ * Entry wrapper generated by 'gkm dev --entry'
1559
+ */
1560
+ ${credentialsInjection}// Import and run the user's entry file
1561
+ import '${entryPath}';
1562
+ `;
1563
+ await writeFile(wrapperPath, content);
1564
+ }
1565
+ /**
1566
+ * Run any TypeScript file with secret injection.
1567
+ * Does not require gkm.config.ts.
1568
+ */
1569
+ async function entryDevCommand(options) {
1570
+ const { entry, port = 3e3, watch = true } = options;
1571
+ if (!entry) throw new Error("--entry requires a file path");
1572
+ const entryPath = resolve(process.cwd(), entry);
1573
+ if (!existsSync(entryPath)) throw new Error(`Entry file not found: ${entryPath}`);
1574
+ logger$8.log(`šŸš€ Starting entry file: ${entry}`);
1575
+ const defaultEnv = loadEnvFiles(".env");
1576
+ if (defaultEnv.loaded.length > 0) logger$8.log(`šŸ“¦ Loaded env: ${defaultEnv.loaded.join(", ")}`);
1577
+ const secretsRoot = findSecretsRoot(process.cwd());
1578
+ const appName = getAppNameFromCwd() ?? void 0;
1579
+ const appSecrets = await loadSecretsForApp(secretsRoot, appName);
1580
+ if (Object.keys(appSecrets).length > 0) logger$8.log(`šŸ” Loaded ${Object.keys(appSecrets).length} secret(s)`);
1581
+ let secretsJsonPath;
1582
+ if (Object.keys(appSecrets).length > 0) {
1583
+ const secretsDir = join(secretsRoot, ".gkm");
1584
+ await mkdir(secretsDir, { recursive: true });
1585
+ secretsJsonPath = join(secretsDir, "dev-secrets.json");
1586
+ await writeFile(secretsJsonPath, JSON.stringify(appSecrets, null, 2));
1587
+ }
1588
+ const wrapperDir = join(process.cwd(), ".gkm");
1589
+ await mkdir(wrapperDir, { recursive: true });
1590
+ const wrapperPath = join(wrapperDir, "entry-wrapper.ts");
1591
+ await createEntryWrapper(wrapperPath, entryPath, secretsJsonPath);
1592
+ const runner = new EntryRunner(wrapperPath, entryPath, watch, port);
1593
+ await runner.start();
1594
+ let isShuttingDown = false;
1595
+ const shutdown = () => {
1596
+ if (isShuttingDown) return;
1597
+ isShuttingDown = true;
1598
+ logger$8.log("\nšŸ›‘ Shutting down...");
1599
+ runner.stop();
1600
+ process.exit(0);
1601
+ };
1602
+ process.on("SIGINT", shutdown);
1603
+ process.on("SIGTERM", shutdown);
1604
+ await new Promise(() => {});
1605
+ }
1606
+ /**
1607
+ * Runs and watches a TypeScript entry file using tsx.
1608
+ */
1609
+ var EntryRunner = class {
1610
+ childProcess = null;
1611
+ watcher = null;
1612
+ isRunning = false;
1613
+ constructor(wrapperPath, entryPath, watch, port) {
1614
+ this.wrapperPath = wrapperPath;
1615
+ this.entryPath = entryPath;
1616
+ this.watch = watch;
1617
+ this.port = port;
1618
+ }
1619
+ async start() {
1620
+ await this.runProcess();
1621
+ if (this.watch) {
1622
+ const watchDir = dirname(this.entryPath);
1623
+ this.watcher = chokidar.watch(watchDir, {
1624
+ ignored: /(^|[/\\])\../,
1625
+ persistent: true,
1626
+ ignoreInitial: true
1627
+ });
1628
+ let restartTimeout = null;
1629
+ this.watcher.on("change", (path) => {
1630
+ logger$8.log(`šŸ“ File changed: ${path}`);
1631
+ if (restartTimeout) clearTimeout(restartTimeout);
1632
+ restartTimeout = setTimeout(async () => {
1633
+ logger$8.log("šŸ”„ Restarting...");
1634
+ await this.restart();
1635
+ }, 300);
1636
+ });
1637
+ logger$8.log(`šŸ‘€ Watching for changes in: ${watchDir}`);
1638
+ }
1639
+ }
1640
+ async runProcess() {
1641
+ const env = {
1642
+ ...process.env,
1643
+ PORT: String(this.port)
1644
+ };
1645
+ this.childProcess = spawn("npx", ["tsx", this.wrapperPath], {
1646
+ stdio: "inherit",
1647
+ env,
1648
+ detached: true
1649
+ });
1650
+ this.isRunning = true;
1651
+ this.childProcess.on("error", (error) => {
1652
+ logger$8.error("āŒ Process error:", error);
1653
+ });
1654
+ this.childProcess.on("exit", (code) => {
1655
+ if (code !== null && code !== 0 && code !== 143) logger$8.error(`āŒ Process exited with code ${code}`);
1656
+ this.isRunning = false;
1657
+ });
1658
+ await new Promise((resolve$1) => setTimeout(resolve$1, 500));
1659
+ if (this.isRunning) logger$8.log(`\nšŸŽ‰ Running at http://localhost:${this.port}`);
1660
+ }
1661
+ async restart() {
1662
+ this.stopProcess();
1663
+ await new Promise((resolve$1) => setTimeout(resolve$1, 500));
1664
+ await this.runProcess();
1665
+ }
1666
+ stop() {
1667
+ this.watcher?.close();
1668
+ this.stopProcess();
1669
+ }
1670
+ stopProcess() {
1671
+ if (this.childProcess && this.isRunning) {
1672
+ const pid = this.childProcess.pid;
1673
+ if (pid) try {
1674
+ process.kill(-pid, "SIGTERM");
1675
+ } catch {
1676
+ try {
1677
+ process.kill(pid, "SIGTERM");
1678
+ } catch {}
1679
+ }
1680
+ this.childProcess = null;
1681
+ this.isRunning = false;
1682
+ }
1683
+ }
1684
+ };
1525
1685
  var DevServer = class {
1526
1686
  serverProcess = null;
1527
1687
  isRunning = false;
@@ -4332,7 +4492,7 @@ const GEEKMIDAS_VERSIONS = {
4332
4492
  "@geekmidas/constructs": "~0.6.0",
4333
4493
  "@geekmidas/db": "~0.3.0",
4334
4494
  "@geekmidas/emailkit": "~0.2.0",
4335
- "@geekmidas/envkit": "~0.4.0",
4495
+ "@geekmidas/envkit": "~0.5.0",
4336
4496
  "@geekmidas/errors": "~0.1.0",
4337
4497
  "@geekmidas/events": "~0.2.0",
4338
4498
  "@geekmidas/logger": "~0.4.0",
@@ -7218,14 +7378,16 @@ program.command("build").description("Build handlers from endpoints, functions,
7218
7378
  process.exit(1);
7219
7379
  }
7220
7380
  });
7221
- program.command("dev").description("Start development server with automatic reload").option("-p, --port <port>", "Port to run the development server on").option("--enable-openapi", "Enable OpenAPI documentation for development server", true).action(async (options) => {
7381
+ program.command("dev").description("Start development server with automatic reload").option("-p, --port <port>", "Port to run the development server on").option("--entry <file>", "Entry file to run (bypasses gkm config)").option("--watch", "Watch for file changes (default: true with --entry)").option("--no-watch", "Disable file watching").option("--enable-openapi", "Enable OpenAPI documentation for development server", true).action(async (options) => {
7222
7382
  try {
7223
7383
  const globalOptions = program.opts();
7224
7384
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
7225
7385
  await devCommand({
7226
7386
  port: options.port ? Number.parseInt(options.port, 10) : 3e3,
7227
7387
  portExplicit: !!options.port,
7228
- enableOpenApi: options.enableOpenapi ?? true
7388
+ enableOpenApi: options.enableOpenapi ?? true,
7389
+ entry: options.entry,
7390
+ watch: options.watch
7229
7391
  });
7230
7392
  } catch (error) {
7231
7393
  console.error(error instanceof Error ? error.message : "Command failed");