@kuckit/cli 2.0.1 → 2.0.3

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/bin.js CHANGED
@@ -1,17 +1,18 @@
1
1
  #!/usr/bin/env node
2
- import { a as isLegacyConfig, c as loadTryLoadKuckitConfig, i as isGcpConfig, l as addModule, o as migrateLegacyConfig, s as discoverModules, u as generateModule } from "./provider-DbqTBb6C.js";
2
+ import { a as isLegacyConfig, c as loadTryLoadKuckitConfig, i as isGcpConfig, l as addModule, o as migrateLegacyConfig, s as discoverModules, u as generateModule } from "./provider-Cx1wuAN4.js";
3
3
  import { program } from "commander";
4
- import { dirname, join } from "node:path";
4
+ import { dirname, join, resolve } from "node:path";
5
+ import { spawn } from "node:child_process";
5
6
  import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
6
- import { spawn } from "child_process";
7
+ import { spawn as spawn$1 } from "child_process";
7
8
  import { access, constants as constants$1, mkdir, readFile, unlink, writeFile } from "fs/promises";
8
9
  import { dirname as dirname$1, join as join$1 } from "path";
9
10
  import { homedir } from "node:os";
10
- import { confirm, input, select } from "@inquirer/prompts";
11
11
  import { accessSync as accessSync$1, constants as constants$2 } from "fs";
12
+ import { confirm, input, select } from "@inquirer/prompts";
12
13
 
13
14
  //#region src/commands/doctor.ts
14
- const CONFIG_FILES = [
15
+ const CONFIG_FILES$1 = [
15
16
  "kuckit.config.ts",
16
17
  "kuckit.config.js",
17
18
  "kuckit.config.mjs"
@@ -25,10 +26,10 @@ const FRAMEWORK_PACKAGES = [
25
26
  "@kuckit/domain",
26
27
  "@kuckit/contracts"
27
28
  ];
28
- function findConfigFile(cwd) {
29
+ function findConfigFile$1(cwd) {
29
30
  let dir = cwd;
30
31
  while (dir !== dirname(dir)) {
31
- for (const file of CONFIG_FILES) {
32
+ for (const file of CONFIG_FILES$1) {
32
33
  const configPath = join(dir, file);
33
34
  if (existsSync(configPath)) return configPath;
34
35
  }
@@ -42,7 +43,7 @@ function readPackageJson(packageName, cwd) {
42
43
  join(cwd, "apps", "server", "node_modules", packageName, "package.json"),
43
44
  join(cwd, "apps", "web", "node_modules", packageName, "package.json")
44
45
  ];
45
- const packageDir = packageName.startsWith("@") ? packageName.split("/")[1] : packageName;
46
+ const packageDir = packageName.startsWith("@") ? packageName.split("/")[1] ?? packageName : packageName;
46
47
  locations.push(join(cwd, "packages", packageDir, "package.json"));
47
48
  for (const location of locations) try {
48
49
  const content = readFileSync(location, "utf-8");
@@ -57,7 +58,7 @@ function findPackagePath(packageName, cwd) {
57
58
  join(cwd, "apps", "server", "node_modules", packageName),
58
59
  join(cwd, "apps", "web", "node_modules", packageName)
59
60
  ];
60
- const packageDir = packageName.startsWith("@") ? packageName.split("/")[1] : packageName;
61
+ const packageDir = packageName.startsWith("@") ? packageName.split("/")[1] ?? packageName : packageName;
61
62
  locations.push(join(cwd, "packages", packageDir));
62
63
  for (const location of locations) if (existsSync(location)) {
63
64
  const pkgJsonPath = join(location, "package.json");
@@ -127,7 +128,7 @@ async function doctor(options) {
127
128
  const cwd = process.cwd();
128
129
  const checks = [];
129
130
  if (!options.json) console.log("\nKuckit Doctor - Checking your setup...\n");
130
- const configPath = findConfigFile(cwd);
131
+ const configPath = findConfigFile$1(cwd);
131
132
  if (configPath) checks.push({
132
133
  name: "config-exists",
133
134
  status: "pass",
@@ -315,11 +316,17 @@ async function doctor(options) {
315
316
  let match;
316
317
  while ((match = serverModulePattern.exec(serverContent)) !== null) {
317
318
  const lineStart = serverContent.lastIndexOf("\n", match.index) + 1;
318
- if (!serverContent.slice(lineStart, match.index).includes("//")) serverModules.add(match[1]);
319
+ if (!serverContent.slice(lineStart, match.index).includes("//")) {
320
+ const matchedModule = match[1];
321
+ if (matchedModule) serverModules.add(matchedModule);
322
+ }
319
323
  }
320
324
  while ((match = clientModulePattern.exec(clientContent)) !== null) {
321
325
  const lineStart = clientContent.lastIndexOf("\n", match.index) + 1;
322
- if (!clientContent.slice(lineStart, match.index).includes("//")) clientModules.add(match[1]);
326
+ if (!clientContent.slice(lineStart, match.index).includes("//")) {
327
+ const matchedModule = match[1];
328
+ if (matchedModule) clientModules.add(matchedModule);
329
+ }
323
330
  }
324
331
  const clientOnly = [...clientModules].filter((m) => !serverModules.has(m));
325
332
  const serverOnly = [...serverModules].filter((m) => !clientModules.has(m));
@@ -448,7 +455,7 @@ async function search(keyword, options) {
448
455
  }
449
456
  console.log(`\n${results.length} package${results.length === 1 ? "" : "s"} found`);
450
457
  if (results.some((r) => !r.isKuckitModule)) console.log("\n[?] = May not be a Kuckit module (verify before installing)");
451
- if (results.length > 0) console.log(`\nInstall: kuckit add ${results[0].name}`);
458
+ if (results.length > 0 && results[0]) console.log(`\nInstall: kuckit add ${results[0].name}`);
452
459
  } catch (error) {
453
460
  if (options.json) console.log(JSON.stringify({
454
461
  error: error instanceof Error ? error.message : "Unknown error",
@@ -491,7 +498,7 @@ async function getDatabaseUrl(cwd, options) {
491
498
  ];
492
499
  for (const envPath of envPaths) if (await fileExists$11(envPath)) try {
493
500
  const match = (await readFile(envPath, "utf-8")).match(/^DATABASE_URL=(.+)$/m);
494
- if (match) return match[1].replace(/^["']|["']$/g, "");
501
+ if (match?.[1]) return match[1].replace(/^["']|["']$/g, "");
495
502
  } catch {}
496
503
  return null;
497
504
  }
@@ -557,8 +564,8 @@ export default defineConfig({
557
564
  return configPath;
558
565
  }
559
566
  function runDrizzleKit(command, configPath, cwd) {
560
- return new Promise((resolve) => {
561
- const proc = spawn("npx", ["drizzle-kit", ...[
567
+ return new Promise((resolve$1) => {
568
+ const proc = spawn$1("npx", ["drizzle-kit", ...[
562
569
  command,
563
570
  "--config",
564
571
  configPath
@@ -582,7 +589,7 @@ function runDrizzleKit(command, configPath, cwd) {
582
589
  process.stderr.write(data);
583
590
  });
584
591
  proc.on("close", (code) => {
585
- resolve({
592
+ resolve$1({
586
593
  code: code ?? 1,
587
594
  stdout,
588
595
  stderr
@@ -680,7 +687,7 @@ async function openBrowser(url) {
680
687
  }
681
688
  }
682
689
  function sleep(ms) {
683
- return new Promise((resolve) => setTimeout(resolve, ms));
690
+ return new Promise((resolve$1) => setTimeout(resolve$1, ms));
684
691
  }
685
692
  function formatExpiryDate(expiresAt) {
686
693
  return new Date(expiresAt).toLocaleDateString("en-US", {
@@ -861,6 +868,32 @@ function requireAuth() {
861
868
  }
862
869
  }
863
870
 
871
+ //#endregion
872
+ //#region src/commands/server-utils.ts
873
+ const CONFIG_FILES = [
874
+ "kuckit.config.ts",
875
+ "kuckit.config.js",
876
+ "kuckit.config.mjs"
877
+ ];
878
+ /**
879
+ * Find the config file path by searching from cwd upward
880
+ */
881
+ function findConfigFile(cwd = process.cwd()) {
882
+ let dir = cwd;
883
+ while (dir !== dirname(dir)) {
884
+ for (const file of CONFIG_FILES) {
885
+ const configPath = resolve(dir, file);
886
+ if (existsSync(configPath)) return configPath;
887
+ }
888
+ dir = dirname(dir);
889
+ }
890
+ for (const file of CONFIG_FILES) {
891
+ const configPath = resolve(dir, file);
892
+ if (existsSync(configPath)) return configPath;
893
+ }
894
+ return null;
895
+ }
896
+
864
897
  //#endregion
865
898
  //#region src/lib/package-manager.ts
866
899
  /**
@@ -901,11 +934,238 @@ function getInstallCommand(pm, packageName, options = {}) {
901
934
  }
902
935
  }
903
936
 
937
+ //#endregion
938
+ //#region src/commands/dev.ts
939
+ /**
940
+ * Start the development server
941
+ */
942
+ async function dev(options) {
943
+ const cwd = process.cwd();
944
+ const configPath = options.config ? resolve(cwd, options.config) : findConfigFile(cwd);
945
+ if (!configPath) {
946
+ console.error("Error: No kuckit.config.ts found.");
947
+ console.error("Create one at your project root or specify with --config");
948
+ process.exit(1);
949
+ }
950
+ const projectRoot = dirname(configPath);
951
+ const pm = detectPackageManager(projectRoot);
952
+ console.log(`[kuckit] Using config: ${configPath}`);
953
+ console.log(`[kuckit] Starting development server...`);
954
+ const env = {
955
+ ...process.env,
956
+ KUCKIT_CONFIG_PATH: configPath,
957
+ NODE_ENV: "development"
958
+ };
959
+ if (options.port) env.PORT = options.port;
960
+ const serverEntry = await findServerEntry(projectRoot);
961
+ if (serverEntry) {
962
+ const args = getRunArgs(pm, serverEntry);
963
+ const command = args[0];
964
+ if (!command) {
965
+ console.error("Error: Could not determine run command.");
966
+ process.exit(1);
967
+ }
968
+ const child = spawn(command, args.slice(1), {
969
+ cwd: projectRoot,
970
+ env,
971
+ stdio: "inherit"
972
+ });
973
+ child.on("error", (error) => {
974
+ console.error("Failed to start dev server:", error);
975
+ process.exit(1);
976
+ });
977
+ child.on("exit", (code) => {
978
+ process.exit(code ?? 0);
979
+ });
980
+ } else {
981
+ console.error("Error: Could not find server entry point.");
982
+ console.error("Expected: apps/server/src/server.ts or src/server.ts");
983
+ process.exit(1);
984
+ }
985
+ }
986
+ /**
987
+ * Find the server entry point
988
+ */
989
+ async function findServerEntry(projectRoot) {
990
+ const { existsSync: existsSync$1 } = await import("node:fs");
991
+ for (const candidate of [
992
+ "apps/server/src/server.ts",
993
+ "server/src/server.ts",
994
+ "src/server.ts",
995
+ "server.ts"
996
+ ]) if (existsSync$1(resolve(projectRoot, candidate))) return candidate;
997
+ return null;
998
+ }
999
+ /**
1000
+ * Get run command arguments based on package manager
1001
+ */
1002
+ function getRunArgs(pm, entry) {
1003
+ switch (pm) {
1004
+ case "bun": return [
1005
+ "bun",
1006
+ "--watch",
1007
+ entry
1008
+ ];
1009
+ default: return [
1010
+ "npx",
1011
+ "tsx",
1012
+ "watch",
1013
+ entry
1014
+ ];
1015
+ }
1016
+ }
1017
+
1018
+ //#endregion
1019
+ //#region src/commands/build.ts
1020
+ /**
1021
+ * Build the production bundle
1022
+ */
1023
+ async function build(options) {
1024
+ const cwd = process.cwd();
1025
+ const configPath = options.config ? resolve(cwd, options.config) : findConfigFile(cwd);
1026
+ if (!configPath) {
1027
+ console.error("Error: No kuckit.config.ts found.");
1028
+ console.error("Create one at your project root or specify with --config");
1029
+ process.exit(1);
1030
+ }
1031
+ const projectRoot = dirname(configPath);
1032
+ const pm = detectPackageManager(projectRoot);
1033
+ const outDir = options.outDir || "dist";
1034
+ console.log(`[kuckit] Using config: ${configPath}`);
1035
+ console.log(`[kuckit] Building for production...`);
1036
+ const args = getBuildArgs(pm);
1037
+ const command = args[0];
1038
+ if (!command) {
1039
+ console.error("Error: Could not determine build command.");
1040
+ process.exit(1);
1041
+ }
1042
+ const child = spawn(command, args.slice(1), {
1043
+ cwd: projectRoot,
1044
+ env: {
1045
+ ...process.env,
1046
+ KUCKIT_CONFIG_PATH: configPath,
1047
+ NODE_ENV: "production",
1048
+ BUILD_OUT_DIR: outDir
1049
+ },
1050
+ stdio: "inherit"
1051
+ });
1052
+ child.on("error", (error) => {
1053
+ console.error("Failed to run build:", error);
1054
+ process.exit(1);
1055
+ });
1056
+ child.on("exit", (code) => {
1057
+ if (code === 0) console.log(`[kuckit] Build complete. Output: ${outDir}/`);
1058
+ process.exit(code ?? 0);
1059
+ });
1060
+ }
1061
+ /**
1062
+ * Get build command arguments based on package manager
1063
+ */
1064
+ function getBuildArgs(pm) {
1065
+ switch (pm) {
1066
+ case "bun": return [
1067
+ "bun",
1068
+ "run",
1069
+ "build"
1070
+ ];
1071
+ case "pnpm": return [
1072
+ "pnpm",
1073
+ "run",
1074
+ "build"
1075
+ ];
1076
+ case "yarn": return ["yarn", "build"];
1077
+ default: return [
1078
+ "npm",
1079
+ "run",
1080
+ "build"
1081
+ ];
1082
+ }
1083
+ }
1084
+
1085
+ //#endregion
1086
+ //#region src/commands/start.ts
1087
+ /**
1088
+ * Start the production server
1089
+ */
1090
+ async function start(options) {
1091
+ const cwd = process.cwd();
1092
+ const configPath = options.config ? resolve(cwd, options.config) : findConfigFile(cwd);
1093
+ if (!configPath) {
1094
+ console.error("Error: No kuckit.config.ts found.");
1095
+ console.error("Create one at your project root or specify with --config");
1096
+ process.exit(1);
1097
+ }
1098
+ const projectRoot = dirname(configPath);
1099
+ const pm = detectPackageManager(projectRoot);
1100
+ console.log(`[kuckit] Using config: ${configPath}`);
1101
+ console.log(`[kuckit] Starting production server...`);
1102
+ const env = {
1103
+ ...process.env,
1104
+ KUCKIT_CONFIG_PATH: configPath,
1105
+ NODE_ENV: "production"
1106
+ };
1107
+ if (options.port) env.PORT = options.port;
1108
+ const serverEntry = await findBuiltServerEntry(projectRoot);
1109
+ if (serverEntry) {
1110
+ const args = getStartArgs(pm, serverEntry);
1111
+ const command = args[0];
1112
+ if (!command) {
1113
+ console.error("Error: Could not determine start command.");
1114
+ process.exit(1);
1115
+ }
1116
+ const child = spawn(command, args.slice(1), {
1117
+ cwd: projectRoot,
1118
+ env,
1119
+ stdio: "inherit"
1120
+ });
1121
+ child.on("error", (error) => {
1122
+ console.error("Failed to start production server:", error);
1123
+ process.exit(1);
1124
+ });
1125
+ child.on("exit", (code) => {
1126
+ process.exit(code ?? 0);
1127
+ });
1128
+ } else {
1129
+ console.error("Error: Could not find built server entry point.");
1130
+ console.error("Run `kuckit build` first, then `kuckit start`");
1131
+ console.error("Expected: dist/server.js or apps/server/dist/server.js");
1132
+ process.exit(1);
1133
+ }
1134
+ }
1135
+ /**
1136
+ * Find the built server entry point
1137
+ */
1138
+ async function findBuiltServerEntry(projectRoot) {
1139
+ for (const candidate of [
1140
+ "dist/server.js",
1141
+ "apps/server/dist/server.js",
1142
+ "server/dist/server.js",
1143
+ "build/server.js"
1144
+ ]) if (existsSync(resolve(projectRoot, candidate))) return candidate;
1145
+ return null;
1146
+ }
1147
+ /**
1148
+ * Get start command arguments based on package manager
1149
+ */
1150
+ function getStartArgs(pm, entry) {
1151
+ switch (pm) {
1152
+ case "bun": return ["bun", entry];
1153
+ default: return ["node", entry];
1154
+ }
1155
+ }
1156
+
904
1157
  //#endregion
905
1158
  //#region src/commands/infra/provider-loader.ts
906
1159
  const KUCKIT_DIR$7 = ".kuckit";
907
1160
  const CONFIG_FILE$7 = "infra.json";
908
1161
  /**
1162
+ * Get the config file name for a specific environment
1163
+ * Returns 'infra.{env}.json' for per-env configs
1164
+ */
1165
+ function getEnvConfigFile(env) {
1166
+ return `infra.${env}.json`;
1167
+ }
1168
+ /**
909
1169
  * Default provider packages by provider ID
910
1170
  */
911
1171
  const DEFAULT_PROVIDER_PACKAGES = {
@@ -914,10 +1174,28 @@ const DEFAULT_PROVIDER_PACKAGES = {
914
1174
  azure: "@kuckit/infra-azure"
915
1175
  };
916
1176
  /**
917
- * Load the stored infrastructure config from .kuckit/infra.json
1177
+ * Load the stored infrastructure config
1178
+ *
1179
+ * Priority:
1180
+ * 1. If env is specified, try .kuckit/infra.{env}.json first
1181
+ * 2. Fall back to .kuckit/infra.json (legacy single-file format)
1182
+ *
1183
+ * @param projectRoot - Project root directory
1184
+ * @param env - Optional environment to load config for
918
1185
  */
919
- async function loadStoredConfig(projectRoot) {
920
- const configPath = join$1(projectRoot, KUCKIT_DIR$7, CONFIG_FILE$7);
1186
+ async function loadStoredConfig(projectRoot, env) {
1187
+ const kuckitDir = join$1(projectRoot, KUCKIT_DIR$7);
1188
+ if (env) {
1189
+ const envConfigPath = join$1(kuckitDir, getEnvConfigFile(env));
1190
+ try {
1191
+ await access(envConfigPath, constants$1.F_OK);
1192
+ const content = await readFile(envConfigPath, "utf-8");
1193
+ const parsed = JSON.parse(content);
1194
+ if (isLegacyConfig(parsed)) return migrateLegacyConfig(parsed);
1195
+ return parsed;
1196
+ } catch {}
1197
+ }
1198
+ const configPath = join$1(kuckitDir, CONFIG_FILE$7);
921
1199
  try {
922
1200
  await access(configPath, constants$1.F_OK);
923
1201
  const content = await readFile(configPath, "utf-8");
@@ -929,13 +1207,16 @@ async function loadStoredConfig(projectRoot) {
929
1207
  }
930
1208
  }
931
1209
  /**
932
- * Save infrastructure config to .kuckit/infra.json
1210
+ * Save infrastructure config to per-environment file
1211
+ *
1212
+ * Saves to .kuckit/infra.{env}.json based on the config's env field
1213
+ * Falls back to .kuckit/infra.json if no env is set
933
1214
  */
934
1215
  async function saveStoredConfig(projectRoot, config) {
935
1216
  const { mkdir: mkdir$1, writeFile: writeFile$1 } = await import("fs/promises");
936
1217
  const kuckitDir = join$1(projectRoot, KUCKIT_DIR$7);
937
1218
  await mkdir$1(kuckitDir, { recursive: true });
938
- const configPath = join$1(kuckitDir, CONFIG_FILE$7);
1219
+ const configPath = join$1(kuckitDir, config.env ? getEnvConfigFile(config.env) : CONFIG_FILE$7);
939
1220
  config.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
940
1221
  await writeFile$1(configPath, JSON.stringify(config, null, 2), "utf-8");
941
1222
  }
@@ -989,6 +1270,18 @@ function getProviderPackage(providerId) {
989
1270
  return DEFAULT_PROVIDER_PACKAGES[providerId] ?? `@kuckit/infra-${providerId}`;
990
1271
  }
991
1272
  /**
1273
+ * Compute the Pulumi stack name for a given config and environment.
1274
+ * Stack names follow the pattern: {gcpProject}-{env}
1275
+ *
1276
+ * @param config - The stored infrastructure config
1277
+ * @param env - The target environment (dev, staging, prod)
1278
+ * @returns The computed stack name
1279
+ */
1280
+ function computeStackName(config, env) {
1281
+ if (config.provider === "gcp" && "gcpProject" in config.providerConfig) return `${config.providerConfig.gcpProject}-${env}`;
1282
+ return config.stackName;
1283
+ }
1284
+ /**
992
1285
  * Check if a provider package is available
993
1286
  */
994
1287
  async function isProviderAvailable(providerId) {
@@ -1052,7 +1345,7 @@ async function infraInit(options) {
1052
1345
  console.log("See docs/MIGRATION.md for details.");
1053
1346
  console.log("");
1054
1347
  }
1055
- const existingConfig = await loadStoredConfig(projectRoot);
1348
+ const existingConfig = await loadStoredConfig(projectRoot, options.env);
1056
1349
  let providerId = options.provider ?? existingConfig?.provider ?? "gcp";
1057
1350
  if (!options.provider && !existingConfig) {
1058
1351
  const installedProviders = (await listAvailableProviders()).filter((p) => p.available);
@@ -1265,7 +1558,7 @@ async function infraDeploy(options) {
1265
1558
  console.error("Error: Could not find project root (no package.json found)");
1266
1559
  process.exit(1);
1267
1560
  }
1268
- const config = await loadStoredConfig(projectRoot);
1561
+ const config = await loadStoredConfig(projectRoot, options.env);
1269
1562
  if (!config) {
1270
1563
  console.error("Error: No infrastructure configuration found.");
1271
1564
  console.error("Run: kuckit infra init");
@@ -1426,7 +1719,7 @@ async function infraUp(options) {
1426
1719
  console.error("Error: Could not find project root (no package.json found)");
1427
1720
  process.exit(1);
1428
1721
  }
1429
- const existingConfig = await loadStoredConfig(projectRoot);
1722
+ const existingConfig = await loadStoredConfig(projectRoot, options.env);
1430
1723
  if (!existingConfig) {
1431
1724
  const providerPackage = getProviderPackage(options.provider ?? "gcp");
1432
1725
  let providerLabel = "cloud";
@@ -1460,7 +1753,7 @@ async function infraUp(options) {
1460
1753
  env: options.env,
1461
1754
  yes: options.yes
1462
1755
  });
1463
- if (!await loadStoredConfig(projectRoot)) {
1756
+ if (!await loadStoredConfig(projectRoot, options.env)) {
1464
1757
  console.error("Error: Initialization completed but no configuration found.");
1465
1758
  process.exit(1);
1466
1759
  }
@@ -1607,8 +1900,8 @@ async function infraEject(options) {
1607
1900
  * Run a Pulumi CLI command
1608
1901
  */
1609
1902
  function runPulumi(args, options) {
1610
- return new Promise((resolve) => {
1611
- const proc = spawn("pulumi", args, {
1903
+ return new Promise((resolve$1) => {
1904
+ const proc = spawn$1("pulumi", args, {
1612
1905
  cwd: options.cwd,
1613
1906
  stdio: options.stream ? [
1614
1907
  "inherit",
@@ -1639,7 +1932,7 @@ function runPulumi(args, options) {
1639
1932
  if (options.stream) process.stderr.write(data);
1640
1933
  });
1641
1934
  proc.on("close", (code) => {
1642
- resolve({
1935
+ resolve$1({
1643
1936
  code: code ?? 1,
1644
1937
  stdout,
1645
1938
  stderr
@@ -1786,8 +2079,8 @@ async function pulumiStackExport(filePath, options) {
1786
2079
  * Run a gcloud CLI command
1787
2080
  */
1788
2081
  function runGcloud(args, options = {}) {
1789
- return new Promise((resolve) => {
1790
- const proc = spawn("gcloud", args, {
2082
+ return new Promise((resolve$1) => {
2083
+ const proc = spawn$1("gcloud", args, {
1791
2084
  cwd: options.cwd ?? process.cwd(),
1792
2085
  stdio: options.stream ? [
1793
2086
  "inherit",
@@ -1813,7 +2106,7 @@ function runGcloud(args, options = {}) {
1813
2106
  if (options.stream) process.stderr.write(data);
1814
2107
  });
1815
2108
  proc.on("close", (code) => {
1816
- resolve({
2109
+ resolve$1({
1817
2110
  code: code ?? 1,
1818
2111
  stdout,
1819
2112
  stderr
@@ -1922,7 +2215,7 @@ function extractServiceNameFromUrl(url) {
1922
2215
  try {
1923
2216
  const hostname = new URL(url).hostname;
1924
2217
  const match = hostname.match(/^([^-]+(?:-[^-]+)*?)-[a-z0-9]+-[a-z]+\.a\.run\.app$/);
1925
- if (match) return match[1];
2218
+ if (match) return match[1] ?? null;
1926
2219
  const parts = hostname.split("-");
1927
2220
  if (parts.length >= 3) return parts.slice(0, -2).join("-");
1928
2221
  return null;
@@ -2124,16 +2417,18 @@ async function infraDestroy(options) {
2124
2417
  console.error(`Error: Invalid environment '${env}'. Must be 'dev' or 'prod'.`);
2125
2418
  process.exit(1);
2126
2419
  }
2420
+ const stackName = computeStackName(config, env);
2127
2421
  const infraDir = getInfraDir$1(projectRoot);
2128
2422
  if (!await fileExists$6(infraDir)) {
2129
2423
  console.error("Error: packages/infra not found.");
2130
2424
  process.exit(1);
2131
2425
  }
2426
+ const projectDisplay = isGcpConfig(config) ? config.providerConfig.gcpProject : config.projectName;
2132
2427
  console.log("Configuration:");
2133
- console.log(` Project: ${config.gcpProject}`);
2428
+ console.log(` Project: ${projectDisplay}`);
2134
2429
  console.log(` Region: ${config.region}`);
2135
2430
  console.log(` Environment: ${env}`);
2136
- console.log(` Stack: ${config.stackName}`);
2431
+ console.log(` Stack: ${stackName}`);
2137
2432
  console.log("");
2138
2433
  if (isAppOnly) {
2139
2434
  console.log("WARNING: This will destroy the Cloud Run service.");
@@ -2171,7 +2466,6 @@ async function infraDestroy(options) {
2171
2466
  }
2172
2467
  }
2173
2468
  }
2174
- const stackName = config.stackName;
2175
2469
  console.log(`\nSelecting Pulumi stack: ${stackName}`);
2176
2470
  if (!await selectOrCreateStack(stackName, { cwd: infraDir })) {
2177
2471
  console.error("Error: Failed to select Pulumi stack");
@@ -2277,6 +2571,7 @@ async function infraRepair(options) {
2277
2571
  console.error(`Error: Invalid environment '${env}'. Must be 'dev' or 'prod'.`);
2278
2572
  process.exit(1);
2279
2573
  }
2574
+ const stackName = computeStackName(config, env);
2280
2575
  const infraDir = getInfraDir$1(projectRoot);
2281
2576
  if (!await fileExists$5(infraDir)) {
2282
2577
  console.error("Error: packages/infra not found.");
@@ -2284,7 +2579,7 @@ async function infraRepair(options) {
2284
2579
  }
2285
2580
  const runCancel = options.cancel || !options.cancel && !options.refresh;
2286
2581
  const runRefresh = options.refresh || !options.cancel && !options.refresh;
2287
- console.log(`Repairing stack: ${config.stackName}`);
2582
+ console.log(`Repairing stack: ${stackName}`);
2288
2583
  console.log(` Environment: ${env}`);
2289
2584
  console.log(` Operations: ${[runCancel && "cancel", runRefresh && "refresh"].filter(Boolean).join(", ")}`);
2290
2585
  console.log("");
@@ -2297,8 +2592,8 @@ async function infraRepair(options) {
2297
2592
  process.exit(0);
2298
2593
  }
2299
2594
  }
2300
- console.log(`Selecting Pulumi stack: ${config.stackName}`);
2301
- if (!await selectOrCreateStack(config.stackName, { cwd: infraDir })) {
2595
+ console.log(`Selecting Pulumi stack: ${stackName}`);
2596
+ if (!await selectOrCreateStack(stackName, { cwd: infraDir })) {
2302
2597
  console.error("Error: Failed to select Pulumi stack");
2303
2598
  process.exit(1);
2304
2599
  }
@@ -2616,12 +2911,17 @@ async function infraRollback(options) {
2616
2911
  console.error("Error: Could not determine service name from URL:", serviceUrl);
2617
2912
  process.exit(1);
2618
2913
  }
2914
+ if (!isGcpConfig(config)) {
2915
+ console.error("Error: Rollback command is only supported for GCP deployments.");
2916
+ process.exit(1);
2917
+ }
2918
+ const gcpProject = config.providerConfig.gcpProject;
2619
2919
  console.log(`Service: ${serviceName}`);
2620
- console.log(`Project: ${config.gcpProject}`);
2920
+ console.log(`Project: ${gcpProject}`);
2621
2921
  console.log(`Region: ${config.region}`);
2622
2922
  console.log("");
2623
2923
  console.log("Fetching revisions...");
2624
- const revisions = await listCloudRunRevisions(serviceName, config.gcpProject, config.region);
2924
+ const revisions = await listCloudRunRevisions(serviceName, gcpProject, config.region);
2625
2925
  if (revisions.length === 0) {
2626
2926
  console.error("Error: No revisions found for service:", serviceName);
2627
2927
  process.exit(1);
@@ -2668,7 +2968,7 @@ async function infraRollback(options) {
2668
2968
  process.exit(0);
2669
2969
  }
2670
2970
  console.log(`Rolling back to ${targetRevision}...`);
2671
- const result = await updateCloudRunTraffic(serviceName, targetRevision, config.gcpProject, config.region);
2971
+ const result = await updateCloudRunTraffic(serviceName, targetRevision, gcpProject, config.region);
2672
2972
  if (result.code !== 0) {
2673
2973
  console.error("\nError: Failed to update traffic routing");
2674
2974
  console.error(result.stderr);
@@ -2708,12 +3008,12 @@ async function loadConfig$2(projectRoot) {
2708
3008
  }
2709
3009
  }
2710
3010
  function runGcloudStreaming(args) {
2711
- return new Promise((resolve) => {
2712
- spawn("gcloud", args, {
3011
+ return new Promise((resolve$1) => {
3012
+ spawn$1("gcloud", args, {
2713
3013
  stdio: "inherit",
2714
3014
  shell: true
2715
3015
  }).on("close", (code) => {
2716
- resolve(code ?? 1);
3016
+ resolve$1(code ?? 1);
2717
3017
  });
2718
3018
  });
2719
3019
  }
@@ -2752,8 +3052,13 @@ async function infraLogs(options) {
2752
3052
  process.exit(1);
2753
3053
  }
2754
3054
  const since = options.since ?? "1h";
3055
+ if (!isGcpConfig(config)) {
3056
+ console.error("Error: Logs command is only supported for GCP deployments.");
3057
+ process.exit(1);
3058
+ }
3059
+ const gcpProject = config.providerConfig.gcpProject;
2755
3060
  console.log(`Fetching logs for service: ${serviceName}`);
2756
- console.log(` Project: ${config.gcpProject}`);
3061
+ console.log(` Project: ${gcpProject}`);
2757
3062
  console.log(` Region: ${config.region}`);
2758
3063
  if (options.follow) console.log(" Mode: Following (Ctrl+C to stop)");
2759
3064
  else console.log(` Since: ${since}`);
@@ -2767,7 +3072,7 @@ async function infraLogs(options) {
2767
3072
  if (options.follow) args.push("tail");
2768
3073
  else args.push("read");
2769
3074
  args.push(serviceName);
2770
- args.push("--project", config.gcpProject);
3075
+ args.push("--project", gcpProject);
2771
3076
  args.push("--region", config.region);
2772
3077
  if (!options.follow) {
2773
3078
  args.push("--limit", "100");
@@ -2844,7 +3149,7 @@ async function infraStatus(options) {
2844
3149
  console.error("Run: kuckit infra init");
2845
3150
  process.exit(1);
2846
3151
  }
2847
- const stackName = config.stackName;
3152
+ const stackName = computeStackName(config, options.env ?? config.env);
2848
3153
  const infraDir = getInfraDir$1(projectRoot);
2849
3154
  if (!await fileExists$1(infraDir)) {
2850
3155
  console.error("Error: packages/infra not found.");
@@ -2955,7 +3260,7 @@ async function infraOutputs(options) {
2955
3260
  process.exit(1);
2956
3261
  }
2957
3262
  const env = options.env ?? config.env ?? "dev";
2958
- const stackName = config.stackName;
3263
+ const stackName = computeStackName(config, env);
2959
3264
  const infraDir = getInfraDir$1(projectRoot);
2960
3265
  if (!await fileExists(infraDir)) {
2961
3266
  console.error("Error: packages/infra not found.");
@@ -3024,8 +3329,8 @@ const KNOWN_CONFIG_KEYS = {
3024
3329
  function getProjectRoot() {
3025
3330
  return process.cwd();
3026
3331
  }
3027
- async function ensureConfigExists(projectRoot) {
3028
- const config = await loadStoredConfig(projectRoot);
3332
+ async function ensureConfigExists(projectRoot, env) {
3333
+ const config = await loadStoredConfig(projectRoot, env);
3029
3334
  if (!config) throw new Error("No infrastructure configuration found. Run `kuckit infra init` first.");
3030
3335
  return config;
3031
3336
  }
@@ -3099,8 +3404,9 @@ async function unsetPulumiConfig(projectRoot, config, env, key) {
3099
3404
  */
3100
3405
  async function infraConfigSet(key, value, options = {}) {
3101
3406
  const projectRoot = getProjectRoot();
3102
- const config = await ensureConfigExists(projectRoot);
3103
- const env = options.env ?? config.env ?? "dev";
3407
+ const env = options.env;
3408
+ const config = await ensureConfigExists(projectRoot, env);
3409
+ const resolvedEnv = env ?? config.env ?? "dev";
3104
3410
  if (["region", "projectName"].includes(key)) {
3105
3411
  if (key === "region") config.region = value;
3106
3412
  else if (key === "projectName") config.projectName = value;
@@ -3117,17 +3423,18 @@ async function infraConfigSet(key, value, options = {}) {
3117
3423
  return;
3118
3424
  }
3119
3425
  }
3120
- await syncToPulumi(projectRoot, config, env, key, value);
3121
- console.log(`✓ Set ${key}=${value} for env '${env}'`);
3122
- if (key === "appUrl") console.log(`\nNote: Run 'kuckit infra deploy --env ${env}' to apply this change.`);
3426
+ await syncToPulumi(projectRoot, config, resolvedEnv, key, value);
3427
+ console.log(`✓ Set ${key}=${value} for env '${resolvedEnv}'`);
3428
+ if (key === "appUrl") console.log(`\nNote: Run 'kuckit infra deploy --env ${resolvedEnv}' to apply this change.`);
3123
3429
  }
3124
3430
  /**
3125
3431
  * Get a configuration value
3126
3432
  */
3127
3433
  async function infraConfigGet(key, options = {}) {
3128
3434
  const projectRoot = getProjectRoot();
3129
- const config = await ensureConfigExists(projectRoot);
3130
- const env = options.env ?? config.env ?? "dev";
3435
+ const env = options.env;
3436
+ const config = await ensureConfigExists(projectRoot, env);
3437
+ const resolvedEnv = env ?? config.env ?? "dev";
3131
3438
  const localKeys = {
3132
3439
  region: (c) => c.region,
3133
3440
  projectName: (c) => c.projectName,
@@ -3144,10 +3451,10 @@ async function infraConfigGet(key, options = {}) {
3144
3451
  }
3145
3452
  }
3146
3453
  }
3147
- const value = await getPulumiConfigValue(projectRoot, config, env, key);
3454
+ const value = await getPulumiConfigValue(projectRoot, config, resolvedEnv, key);
3148
3455
  if (value !== null) console.log(value);
3149
3456
  else {
3150
- console.error(`Config key '${key}' is not set for env '${env}'.`);
3457
+ console.error(`Config key '${key}' is not set for env '${resolvedEnv}'.`);
3151
3458
  process.exit(1);
3152
3459
  }
3153
3460
  }
@@ -3156,36 +3463,37 @@ async function infraConfigGet(key, options = {}) {
3156
3463
  */
3157
3464
  async function infraConfigList(options = {}) {
3158
3465
  const projectRoot = getProjectRoot();
3159
- const config = await ensureConfigExists(projectRoot);
3160
- const env = options.env ?? config.env ?? "dev";
3466
+ const env = options.env;
3467
+ const config = await ensureConfigExists(projectRoot, env);
3468
+ const resolvedEnv = env ?? config.env ?? "dev";
3161
3469
  const localConfig = {
3162
3470
  provider: config.provider,
3163
3471
  region: config.region,
3164
3472
  projectName: config.projectName
3165
3473
  };
3166
3474
  if (isGcpConfig(config)) localConfig["provider.gcpProject"] = config.providerConfig.gcpProject;
3167
- const pulumiConfig = await getAllPulumiConfig(projectRoot, config, env);
3475
+ const pulumiConfig = await getAllPulumiConfig(projectRoot, config, resolvedEnv);
3168
3476
  const allConfig = {
3169
3477
  ...localConfig,
3170
3478
  ...pulumiConfig
3171
3479
  };
3172
3480
  if (options.json) {
3173
3481
  console.log(JSON.stringify({
3174
- env,
3482
+ env: resolvedEnv,
3175
3483
  config: allConfig
3176
3484
  }, null, 2));
3177
3485
  return;
3178
3486
  }
3179
- console.log(`\nInfrastructure Configuration (env: ${env})\n`);
3487
+ console.log(`\nInfrastructure Configuration (env: ${resolvedEnv})\n`);
3180
3488
  console.log("Shared (.kuckit/infra.json):");
3181
3489
  for (const [key, value] of Object.entries(localConfig)) console.log(` ${key}: ${value}`);
3182
3490
  if (Object.keys(pulumiConfig).length > 0) {
3183
- console.log(`\nEnvironment '${env}' Config:`);
3491
+ console.log(`\nEnvironment '${resolvedEnv}' Config:`);
3184
3492
  for (const [key, value] of Object.entries(pulumiConfig)) if (!(key in localConfig)) {
3185
3493
  const desc = KNOWN_CONFIG_KEYS[key]?.description;
3186
3494
  console.log(` ${key}: ${value}${desc ? ` (${desc})` : ""}`);
3187
3495
  }
3188
- } else console.log(`\nEnvironment '${env}' Config: (none set)`);
3496
+ } else console.log(`\nEnvironment '${resolvedEnv}' Config: (none set)`);
3189
3497
  console.log("\nAvailable keys:");
3190
3498
  for (const [key, info] of Object.entries(KNOWN_CONFIG_KEYS)) if (!(key in allConfig)) console.log(` ${key}: ${info.description} (e.g., ${info.example})`);
3191
3499
  console.log(`\nTip: Use --env to configure other environments (dev, staging, prod)`);
@@ -3195,20 +3503,30 @@ async function infraConfigList(options = {}) {
3195
3503
  */
3196
3504
  async function infraConfigUnset(key, options = {}) {
3197
3505
  const projectRoot = getProjectRoot();
3198
- const config = await ensureConfigExists(projectRoot);
3199
- const env = options.env ?? config.env ?? "dev";
3506
+ const env = options.env;
3507
+ const config = await ensureConfigExists(projectRoot, env);
3508
+ const resolvedEnv = env ?? config.env ?? "dev";
3200
3509
  if ([
3201
3510
  "provider",
3202
3511
  "region",
3203
3512
  "projectName"
3204
3513
  ].includes(key)) throw new Error(`Cannot unset required key '${key}'.`);
3205
- await unsetPulumiConfig(projectRoot, config, env, key);
3206
- console.log(`✓ Unset ${key} for env '${env}'`);
3514
+ await unsetPulumiConfig(projectRoot, config, resolvedEnv, key);
3515
+ console.log(`✓ Unset ${key} for env '${resolvedEnv}'`);
3207
3516
  }
3208
3517
 
3209
3518
  //#endregion
3210
3519
  //#region src/bin.ts
3211
3520
  program.name("kuckit").description("CLI tools for Kuckit SDK module development").version("0.1.0");
3521
+ program.command("dev").description("Start the development server with hot reload").option("-c, --config <path>", "Path to kuckit.config.ts").option("-p, --port <port>", "Port to run the server on").action(async (options) => {
3522
+ await dev(options);
3523
+ });
3524
+ program.command("build").description("Build the application for production").option("-c, --config <path>", "Path to kuckit.config.ts").option("-o, --out-dir <dir>", "Output directory", "dist").action(async (options) => {
3525
+ await build(options);
3526
+ });
3527
+ program.command("start").description("Start the production server").option("-c, --config <path>", "Path to kuckit.config.ts").option("-p, --port <port>", "Port to run the server on").action(async (options) => {
3528
+ await start(options);
3529
+ });
3212
3530
  program.command("generate").description("Generate Kuckit resources").command("module <name>").description("Generate a new Kuckit module").option("-o, --org <org>", "Organization scope for package name", "").option("-d, --dir <directory>", "Target directory", "packages").action(async (name, options) => {
3213
3531
  requireAuth();
3214
3532
  await generateModule(name, options);