@kuckit/cli 2.0.0 → 2.0.2

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
@@ -636,7 +643,7 @@ async function dbStudio(options) {
636
643
 
637
644
  //#endregion
638
645
  //#region src/lib/credentials.ts
639
- const DEFAULT_SERVER_URL = "https://api.kuckit.dev";
646
+ const DEFAULT_SERVER_URL = "https://dev-app-nyh7i73bea-uc.a.run.app/";
640
647
  const CONFIG_DIR = join(homedir(), ".kuckit");
641
648
  const CONFIG_PATH = join(CONFIG_DIR, "config.json");
642
649
  function loadConfig$7() {
@@ -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");
@@ -1375,6 +1668,20 @@ async function infraDeploy(options) {
1375
1668
  const outputs = result.outputs;
1376
1669
  if (outputs.serviceUrl) console.log(`\nService URL: ${outputs.serviceUrl}`);
1377
1670
  if (outputs.migrationJobName) console.log(`Migration Job: ${outputs.migrationJobName}`);
1671
+ if (outputs.customDomain) {
1672
+ console.log(`\nCustom Domain: ${outputs.customDomain}`);
1673
+ console.log(`Status: ${outputs.customDomainStatus ?? "UNKNOWN"}`);
1674
+ const records = outputs.customDomainRecords;
1675
+ if (records && records.length > 0) {
1676
+ console.log("\nRequired DNS Records:");
1677
+ for (const record of records) console.log(` ${record.type} ${record.name} → ${record.rrdata}`);
1678
+ console.log("\nNote: If using Cloudflare:");
1679
+ console.log(" 1. Set DNS records to DNS-only (gray cloud) until status is READY");
1680
+ console.log(" 2. Certificate provisioning can take 15-60 minutes");
1681
+ console.log(" 3. Once READY, you can enable Cloudflare proxy (orange cloud)");
1682
+ console.log(" 4. Ensure Cloudflare SSL mode is set to \"Full (strict)\"");
1683
+ }
1684
+ }
1378
1685
  }
1379
1686
  console.log("\nUseful commands:");
1380
1687
  console.log(` View logs: kuckit infra logs --env ${env}`);
@@ -1412,7 +1719,7 @@ async function infraUp(options) {
1412
1719
  console.error("Error: Could not find project root (no package.json found)");
1413
1720
  process.exit(1);
1414
1721
  }
1415
- const existingConfig = await loadStoredConfig(projectRoot);
1722
+ const existingConfig = await loadStoredConfig(projectRoot, options.env);
1416
1723
  if (!existingConfig) {
1417
1724
  const providerPackage = getProviderPackage(options.provider ?? "gcp");
1418
1725
  let providerLabel = "cloud";
@@ -1446,7 +1753,7 @@ async function infraUp(options) {
1446
1753
  env: options.env,
1447
1754
  yes: options.yes
1448
1755
  });
1449
- if (!await loadStoredConfig(projectRoot)) {
1756
+ if (!await loadStoredConfig(projectRoot, options.env)) {
1450
1757
  console.error("Error: Initialization completed but no configuration found.");
1451
1758
  process.exit(1);
1452
1759
  }
@@ -1593,8 +1900,8 @@ async function infraEject(options) {
1593
1900
  * Run a Pulumi CLI command
1594
1901
  */
1595
1902
  function runPulumi(args, options) {
1596
- return new Promise((resolve) => {
1597
- const proc = spawn("pulumi", args, {
1903
+ return new Promise((resolve$1) => {
1904
+ const proc = spawn$1("pulumi", args, {
1598
1905
  cwd: options.cwd,
1599
1906
  stdio: options.stream ? [
1600
1907
  "inherit",
@@ -1625,7 +1932,7 @@ function runPulumi(args, options) {
1625
1932
  if (options.stream) process.stderr.write(data);
1626
1933
  });
1627
1934
  proc.on("close", (code) => {
1628
- resolve({
1935
+ resolve$1({
1629
1936
  code: code ?? 1,
1630
1937
  stdout,
1631
1938
  stderr
@@ -1772,8 +2079,8 @@ async function pulumiStackExport(filePath, options) {
1772
2079
  * Run a gcloud CLI command
1773
2080
  */
1774
2081
  function runGcloud(args, options = {}) {
1775
- return new Promise((resolve) => {
1776
- const proc = spawn("gcloud", args, {
2082
+ return new Promise((resolve$1) => {
2083
+ const proc = spawn$1("gcloud", args, {
1777
2084
  cwd: options.cwd ?? process.cwd(),
1778
2085
  stdio: options.stream ? [
1779
2086
  "inherit",
@@ -1799,7 +2106,7 @@ function runGcloud(args, options = {}) {
1799
2106
  if (options.stream) process.stderr.write(data);
1800
2107
  });
1801
2108
  proc.on("close", (code) => {
1802
- resolve({
2109
+ resolve$1({
1803
2110
  code: code ?? 1,
1804
2111
  stdout,
1805
2112
  stderr
@@ -1810,7 +2117,7 @@ function runGcloud(args, options = {}) {
1810
2117
  /**
1811
2118
  * Get the path to the packages/infra directory
1812
2119
  */
1813
- function getInfraDir(projectRoot) {
2120
+ function getInfraDir$1(projectRoot) {
1814
2121
  return join$1(projectRoot, "packages", "infra");
1815
2122
  }
1816
2123
  /**
@@ -1908,7 +2215,7 @@ function extractServiceNameFromUrl(url) {
1908
2215
  try {
1909
2216
  const hostname = new URL(url).hostname;
1910
2217
  const match = hostname.match(/^([^-]+(?:-[^-]+)*?)-[a-z0-9]+-[a-z]+\.a\.run\.app$/);
1911
- if (match) return match[1];
2218
+ if (match) return match[1] ?? null;
1912
2219
  const parts = hostname.split("-");
1913
2220
  if (parts.length >= 3) return parts.slice(0, -2).join("-");
1914
2221
  return null;
@@ -2110,16 +2417,18 @@ async function infraDestroy(options) {
2110
2417
  console.error(`Error: Invalid environment '${env}'. Must be 'dev' or 'prod'.`);
2111
2418
  process.exit(1);
2112
2419
  }
2113
- const infraDir = getInfraDir(projectRoot);
2420
+ const stackName = computeStackName(config, env);
2421
+ const infraDir = getInfraDir$1(projectRoot);
2114
2422
  if (!await fileExists$6(infraDir)) {
2115
2423
  console.error("Error: packages/infra not found.");
2116
2424
  process.exit(1);
2117
2425
  }
2426
+ const projectDisplay = isGcpConfig(config) ? config.providerConfig.gcpProject : config.projectName;
2118
2427
  console.log("Configuration:");
2119
- console.log(` Project: ${config.gcpProject}`);
2428
+ console.log(` Project: ${projectDisplay}`);
2120
2429
  console.log(` Region: ${config.region}`);
2121
2430
  console.log(` Environment: ${env}`);
2122
- console.log(` Stack: ${config.stackName}`);
2431
+ console.log(` Stack: ${stackName}`);
2123
2432
  console.log("");
2124
2433
  if (isAppOnly) {
2125
2434
  console.log("WARNING: This will destroy the Cloud Run service.");
@@ -2157,7 +2466,6 @@ async function infraDestroy(options) {
2157
2466
  }
2158
2467
  }
2159
2468
  }
2160
- const stackName = config.stackName;
2161
2469
  console.log(`\nSelecting Pulumi stack: ${stackName}`);
2162
2470
  if (!await selectOrCreateStack(stackName, { cwd: infraDir })) {
2163
2471
  console.error("Error: Failed to select Pulumi stack");
@@ -2263,14 +2571,15 @@ async function infraRepair(options) {
2263
2571
  console.error(`Error: Invalid environment '${env}'. Must be 'dev' or 'prod'.`);
2264
2572
  process.exit(1);
2265
2573
  }
2266
- const infraDir = getInfraDir(projectRoot);
2574
+ const stackName = computeStackName(config, env);
2575
+ const infraDir = getInfraDir$1(projectRoot);
2267
2576
  if (!await fileExists$5(infraDir)) {
2268
2577
  console.error("Error: packages/infra not found.");
2269
2578
  process.exit(1);
2270
2579
  }
2271
2580
  const runCancel = options.cancel || !options.cancel && !options.refresh;
2272
2581
  const runRefresh = options.refresh || !options.cancel && !options.refresh;
2273
- console.log(`Repairing stack: ${config.stackName}`);
2582
+ console.log(`Repairing stack: ${stackName}`);
2274
2583
  console.log(` Environment: ${env}`);
2275
2584
  console.log(` Operations: ${[runCancel && "cancel", runRefresh && "refresh"].filter(Boolean).join(", ")}`);
2276
2585
  console.log("");
@@ -2283,8 +2592,8 @@ async function infraRepair(options) {
2283
2592
  process.exit(0);
2284
2593
  }
2285
2594
  }
2286
- console.log(`Selecting Pulumi stack: ${config.stackName}`);
2287
- if (!await selectOrCreateStack(config.stackName, { cwd: infraDir })) {
2595
+ console.log(`Selecting Pulumi stack: ${stackName}`);
2596
+ if (!await selectOrCreateStack(stackName, { cwd: infraDir })) {
2288
2597
  console.error("Error: Failed to select Pulumi stack");
2289
2598
  process.exit(1);
2290
2599
  }
@@ -2370,6 +2679,12 @@ async function getJobContext(options) {
2370
2679
  console.error("Run: kuckit infra init");
2371
2680
  process.exit(1);
2372
2681
  }
2682
+ if (!isGcpConfig(config)) {
2683
+ console.error("Error: Database commands only support GCP provider.");
2684
+ console.error(`Current provider: ${config.provider}`);
2685
+ process.exit(1);
2686
+ }
2687
+ const gcpProject = config.providerConfig.gcpProject;
2373
2688
  const env = options.env ?? config.env;
2374
2689
  if (env !== "dev" && env !== "prod") {
2375
2690
  console.error(`Error: Invalid environment '${env}'. Must be 'dev' or 'prod'.`);
@@ -2384,6 +2699,7 @@ async function getJobContext(options) {
2384
2699
  }
2385
2700
  return {
2386
2701
  config,
2702
+ gcpProject,
2387
2703
  projectRoot,
2388
2704
  env,
2389
2705
  jobName
@@ -2460,18 +2776,18 @@ async function infraDbPush(options) {
2460
2776
  console.log("Running database schema push via Cloud Run Job...\n");
2461
2777
  const ctx = await getJobContext(options);
2462
2778
  console.log("Configuration:");
2463
- console.log(` Project: ${ctx.config.gcpProject}`);
2779
+ console.log(` Project: ${ctx.gcpProject}`);
2464
2780
  console.log(` Environment: ${ctx.env}`);
2465
2781
  console.log(` Job: ${ctx.jobName}`);
2466
2782
  console.log("");
2467
2783
  const commandOverride = [`export NODE_PATH=/app/node_modules && cd /app && node /app/node_modules/drizzle-kit/bin.cjs push --config=packages/db/drizzle.config.ts${options.force ? " --force" : ""}`];
2468
- if (!await executeCloudRunJob(ctx.config.gcpProject, ctx.config.region, ctx.jobName, commandOverride)) {
2784
+ if (!await executeCloudRunJob(ctx.gcpProject, ctx.config.region, ctx.jobName, commandOverride)) {
2469
2785
  console.error("\nError: Schema push failed.");
2470
2786
  console.error("Check the Cloud Run Job logs for details:");
2471
2787
  console.error(` gcloud run jobs executions list --job ${ctx.jobName} --region ${ctx.config.region}`);
2472
2788
  process.exit(1);
2473
2789
  }
2474
- const execution = await getLatestJobExecution(ctx.config.gcpProject, ctx.config.region, ctx.jobName);
2790
+ const execution = await getLatestJobExecution(ctx.gcpProject, ctx.config.region, ctx.jobName);
2475
2791
  if (execution?.logs) {
2476
2792
  console.log("\nJob output:");
2477
2793
  console.log(execution.logs);
@@ -2491,17 +2807,17 @@ async function infraDbMigrate(options) {
2491
2807
  }
2492
2808
  const ctx = await getJobContext(options);
2493
2809
  console.log("Configuration:");
2494
- console.log(` Project: ${ctx.config.gcpProject}`);
2810
+ console.log(` Project: ${ctx.gcpProject}`);
2495
2811
  console.log(` Environment: ${ctx.env}`);
2496
2812
  console.log(` Job: ${ctx.jobName}`);
2497
2813
  console.log("");
2498
- if (!await executeCloudRunJob(ctx.config.gcpProject, ctx.config.region, ctx.jobName)) {
2814
+ if (!await executeCloudRunJob(ctx.gcpProject, ctx.config.region, ctx.jobName)) {
2499
2815
  console.error("\nError: Migration failed.");
2500
2816
  console.error("Check the Cloud Run Job logs for details:");
2501
2817
  console.error(` gcloud run jobs executions list --job ${ctx.jobName} --region ${ctx.config.region}`);
2502
2818
  process.exit(1);
2503
2819
  }
2504
- const execution = await getLatestJobExecution(ctx.config.gcpProject, ctx.config.region, ctx.jobName);
2820
+ const execution = await getLatestJobExecution(ctx.gcpProject, ctx.config.region, ctx.jobName);
2505
2821
  if (execution?.logs) {
2506
2822
  console.log("\nJob output:");
2507
2823
  console.log(execution.logs);
@@ -2595,12 +2911,17 @@ async function infraRollback(options) {
2595
2911
  console.error("Error: Could not determine service name from URL:", serviceUrl);
2596
2912
  process.exit(1);
2597
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;
2598
2919
  console.log(`Service: ${serviceName}`);
2599
- console.log(`Project: ${config.gcpProject}`);
2920
+ console.log(`Project: ${gcpProject}`);
2600
2921
  console.log(`Region: ${config.region}`);
2601
2922
  console.log("");
2602
2923
  console.log("Fetching revisions...");
2603
- const revisions = await listCloudRunRevisions(serviceName, config.gcpProject, config.region);
2924
+ const revisions = await listCloudRunRevisions(serviceName, gcpProject, config.region);
2604
2925
  if (revisions.length === 0) {
2605
2926
  console.error("Error: No revisions found for service:", serviceName);
2606
2927
  process.exit(1);
@@ -2647,7 +2968,7 @@ async function infraRollback(options) {
2647
2968
  process.exit(0);
2648
2969
  }
2649
2970
  console.log(`Rolling back to ${targetRevision}...`);
2650
- const result = await updateCloudRunTraffic(serviceName, targetRevision, config.gcpProject, config.region);
2971
+ const result = await updateCloudRunTraffic(serviceName, targetRevision, gcpProject, config.region);
2651
2972
  if (result.code !== 0) {
2652
2973
  console.error("\nError: Failed to update traffic routing");
2653
2974
  console.error(result.stderr);
@@ -2687,12 +3008,12 @@ async function loadConfig$2(projectRoot) {
2687
3008
  }
2688
3009
  }
2689
3010
  function runGcloudStreaming(args) {
2690
- return new Promise((resolve) => {
2691
- spawn("gcloud", args, {
3011
+ return new Promise((resolve$1) => {
3012
+ spawn$1("gcloud", args, {
2692
3013
  stdio: "inherit",
2693
3014
  shell: true
2694
3015
  }).on("close", (code) => {
2695
- resolve(code ?? 1);
3016
+ resolve$1(code ?? 1);
2696
3017
  });
2697
3018
  });
2698
3019
  }
@@ -2731,8 +3052,13 @@ async function infraLogs(options) {
2731
3052
  process.exit(1);
2732
3053
  }
2733
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;
2734
3060
  console.log(`Fetching logs for service: ${serviceName}`);
2735
- console.log(` Project: ${config.gcpProject}`);
3061
+ console.log(` Project: ${gcpProject}`);
2736
3062
  console.log(` Region: ${config.region}`);
2737
3063
  if (options.follow) console.log(" Mode: Following (Ctrl+C to stop)");
2738
3064
  else console.log(` Since: ${since}`);
@@ -2746,7 +3072,7 @@ async function infraLogs(options) {
2746
3072
  if (options.follow) args.push("tail");
2747
3073
  else args.push("read");
2748
3074
  args.push(serviceName);
2749
- args.push("--project", config.gcpProject);
3075
+ args.push("--project", gcpProject);
2750
3076
  args.push("--region", config.region);
2751
3077
  if (!options.follow) {
2752
3078
  args.push("--limit", "100");
@@ -2823,9 +3149,8 @@ async function infraStatus(options) {
2823
3149
  console.error("Run: kuckit infra init");
2824
3150
  process.exit(1);
2825
3151
  }
2826
- const env = options.env ?? config.env ?? "dev";
2827
- const stackName = `${config.gcpProject}-${env}`;
2828
- const infraDir = getInfraDir(projectRoot);
3152
+ const stackName = computeStackName(config, options.env ?? config.env);
3153
+ const infraDir = getInfraDir$1(projectRoot);
2829
3154
  if (!await fileExists$1(infraDir)) {
2830
3155
  console.error("Error: packages/infra not found.");
2831
3156
  process.exit(1);
@@ -2935,8 +3260,8 @@ async function infraOutputs(options) {
2935
3260
  process.exit(1);
2936
3261
  }
2937
3262
  const env = options.env ?? config.env ?? "dev";
2938
- const stackName = `${config.gcpProject}-${env}`;
2939
- const infraDir = getInfraDir(projectRoot);
3263
+ const stackName = computeStackName(config, env);
3264
+ const infraDir = getInfraDir$1(projectRoot);
2940
3265
  if (!await fileExists(infraDir)) {
2941
3266
  console.error("Error: packages/infra not found.");
2942
3267
  process.exit(1);
@@ -2982,9 +3307,226 @@ async function infraOutputs(options) {
2982
3307
  console.log("");
2983
3308
  }
2984
3309
 
3310
+ //#endregion
3311
+ //#region src/commands/infra/config.ts
3312
+ /**
3313
+ * Known configuration keys with their descriptions
3314
+ */
3315
+ const KNOWN_CONFIG_KEYS = {
3316
+ appUrl: {
3317
+ description: "Application URL (used for CORS and redirects)",
3318
+ example: "https://app.example.com"
3319
+ },
3320
+ region: {
3321
+ description: "Deployment region",
3322
+ example: "us-central1"
3323
+ },
3324
+ env: {
3325
+ description: "Environment (dev, staging, prod)",
3326
+ example: "prod"
3327
+ }
3328
+ };
3329
+ function getProjectRoot() {
3330
+ return process.cwd();
3331
+ }
3332
+ async function ensureConfigExists(projectRoot, env) {
3333
+ const config = await loadStoredConfig(projectRoot, env);
3334
+ if (!config) throw new Error("No infrastructure configuration found. Run `kuckit infra init` first.");
3335
+ return config;
3336
+ }
3337
+ function getStackName(config, env) {
3338
+ return `${config.projectName}-${env}`;
3339
+ }
3340
+ async function getInfraDir(projectRoot, config) {
3341
+ if (config.localInfraDir) return config.localInfraDir;
3342
+ return (await loadProviderFromPackage(config.providerPackage ?? getProviderPackage(config.provider), projectRoot)).getInfraDir(projectRoot);
3343
+ }
3344
+ async function selectStack(infraDir, stackName) {
3345
+ return selectOrCreateStack(stackName, { cwd: infraDir });
3346
+ }
3347
+ async function syncToPulumi(projectRoot, config, env, key, value) {
3348
+ const infraDir = await getInfraDir(projectRoot, config);
3349
+ const stackName = getStackName(config, env);
3350
+ const pulumiOptions = { cwd: infraDir };
3351
+ if (!await selectStack(infraDir, stackName)) throw new Error(`Could not select stack '${stackName}'. Run 'kuckit infra init --env ${env}' first.`);
3352
+ await setPulumiConfig(key, value, pulumiOptions);
3353
+ }
3354
+ async function getPulumiConfigValue(projectRoot, config, env, key) {
3355
+ const infraDir = await getInfraDir(projectRoot, config);
3356
+ const stackName = getStackName(config, env);
3357
+ const pulumiOptions = { cwd: infraDir };
3358
+ if (!await selectStack(infraDir, stackName)) return null;
3359
+ try {
3360
+ const result = await runPulumi([
3361
+ "config",
3362
+ "get",
3363
+ key
3364
+ ], pulumiOptions);
3365
+ return result.code === 0 ? result.stdout.trim() : null;
3366
+ } catch {
3367
+ return null;
3368
+ }
3369
+ }
3370
+ async function getAllPulumiConfig(projectRoot, config, env) {
3371
+ const infraDir = await getInfraDir(projectRoot, config);
3372
+ const stackName = getStackName(config, env);
3373
+ const pulumiOptions = { cwd: infraDir };
3374
+ if (!await selectStack(infraDir, stackName)) return {};
3375
+ try {
3376
+ const result = await runPulumi(["config", "--json"], pulumiOptions);
3377
+ if (result.code === 0 && result.stdout) {
3378
+ const parsed = JSON.parse(result.stdout);
3379
+ const configMap = {};
3380
+ for (const [fullKey, data] of Object.entries(parsed)) {
3381
+ const key = fullKey.includes(":") ? fullKey.split(":")[1] : fullKey;
3382
+ if (key) configMap[key] = data.value;
3383
+ }
3384
+ return configMap;
3385
+ }
3386
+ return {};
3387
+ } catch {
3388
+ return {};
3389
+ }
3390
+ }
3391
+ async function unsetPulumiConfig(projectRoot, config, env, key) {
3392
+ const infraDir = await getInfraDir(projectRoot, config);
3393
+ const stackName = getStackName(config, env);
3394
+ const pulumiOptions = { cwd: infraDir };
3395
+ if (!await selectStack(infraDir, stackName)) throw new Error(`Could not select stack '${stackName}'. Run 'kuckit infra init --env ${env}' first.`);
3396
+ await runPulumi([
3397
+ "config",
3398
+ "rm",
3399
+ key
3400
+ ], pulumiOptions);
3401
+ }
3402
+ /**
3403
+ * Set a configuration value
3404
+ */
3405
+ async function infraConfigSet(key, value, options = {}) {
3406
+ const projectRoot = getProjectRoot();
3407
+ const env = options.env;
3408
+ const config = await ensureConfigExists(projectRoot, env);
3409
+ const resolvedEnv = env ?? config.env ?? "dev";
3410
+ if (["region", "projectName"].includes(key)) {
3411
+ if (key === "region") config.region = value;
3412
+ else if (key === "projectName") config.projectName = value;
3413
+ await saveStoredConfig(projectRoot, config);
3414
+ console.log(`✓ Set ${key}=${value} in .kuckit/infra.json`);
3415
+ return;
3416
+ }
3417
+ if (key.startsWith("provider.")) {
3418
+ const providerKey = key.replace("provider.", "");
3419
+ if (isGcpConfig(config) && providerKey === "gcpProject") {
3420
+ config.providerConfig.gcpProject = value;
3421
+ await saveStoredConfig(projectRoot, config);
3422
+ console.log(`✓ Set ${key}=${value} in .kuckit/infra.json`);
3423
+ return;
3424
+ }
3425
+ }
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.`);
3429
+ }
3430
+ /**
3431
+ * Get a configuration value
3432
+ */
3433
+ async function infraConfigGet(key, options = {}) {
3434
+ const projectRoot = getProjectRoot();
3435
+ const env = options.env;
3436
+ const config = await ensureConfigExists(projectRoot, env);
3437
+ const resolvedEnv = env ?? config.env ?? "dev";
3438
+ const localKeys = {
3439
+ region: (c) => c.region,
3440
+ projectName: (c) => c.projectName,
3441
+ provider: (c) => c.provider,
3442
+ "provider.gcpProject": (c) => isGcpConfig(c) ? c.providerConfig.gcpProject : void 0
3443
+ };
3444
+ if (key in localKeys) {
3445
+ const getter = localKeys[key];
3446
+ if (getter) {
3447
+ const value$1 = getter(config);
3448
+ if (value$1 !== void 0) {
3449
+ console.log(value$1);
3450
+ return;
3451
+ }
3452
+ }
3453
+ }
3454
+ const value = await getPulumiConfigValue(projectRoot, config, resolvedEnv, key);
3455
+ if (value !== null) console.log(value);
3456
+ else {
3457
+ console.error(`Config key '${key}' is not set for env '${resolvedEnv}'.`);
3458
+ process.exit(1);
3459
+ }
3460
+ }
3461
+ /**
3462
+ * List all configuration values
3463
+ */
3464
+ async function infraConfigList(options = {}) {
3465
+ const projectRoot = getProjectRoot();
3466
+ const env = options.env;
3467
+ const config = await ensureConfigExists(projectRoot, env);
3468
+ const resolvedEnv = env ?? config.env ?? "dev";
3469
+ const localConfig = {
3470
+ provider: config.provider,
3471
+ region: config.region,
3472
+ projectName: config.projectName
3473
+ };
3474
+ if (isGcpConfig(config)) localConfig["provider.gcpProject"] = config.providerConfig.gcpProject;
3475
+ const pulumiConfig = await getAllPulumiConfig(projectRoot, config, resolvedEnv);
3476
+ const allConfig = {
3477
+ ...localConfig,
3478
+ ...pulumiConfig
3479
+ };
3480
+ if (options.json) {
3481
+ console.log(JSON.stringify({
3482
+ env: resolvedEnv,
3483
+ config: allConfig
3484
+ }, null, 2));
3485
+ return;
3486
+ }
3487
+ console.log(`\nInfrastructure Configuration (env: ${resolvedEnv})\n`);
3488
+ console.log("Shared (.kuckit/infra.json):");
3489
+ for (const [key, value] of Object.entries(localConfig)) console.log(` ${key}: ${value}`);
3490
+ if (Object.keys(pulumiConfig).length > 0) {
3491
+ console.log(`\nEnvironment '${resolvedEnv}' Config:`);
3492
+ for (const [key, value] of Object.entries(pulumiConfig)) if (!(key in localConfig)) {
3493
+ const desc = KNOWN_CONFIG_KEYS[key]?.description;
3494
+ console.log(` ${key}: ${value}${desc ? ` (${desc})` : ""}`);
3495
+ }
3496
+ } else console.log(`\nEnvironment '${resolvedEnv}' Config: (none set)`);
3497
+ console.log("\nAvailable keys:");
3498
+ for (const [key, info] of Object.entries(KNOWN_CONFIG_KEYS)) if (!(key in allConfig)) console.log(` ${key}: ${info.description} (e.g., ${info.example})`);
3499
+ console.log(`\nTip: Use --env to configure other environments (dev, staging, prod)`);
3500
+ }
3501
+ /**
3502
+ * Unset a configuration value
3503
+ */
3504
+ async function infraConfigUnset(key, options = {}) {
3505
+ const projectRoot = getProjectRoot();
3506
+ const env = options.env;
3507
+ const config = await ensureConfigExists(projectRoot, env);
3508
+ const resolvedEnv = env ?? config.env ?? "dev";
3509
+ if ([
3510
+ "provider",
3511
+ "region",
3512
+ "projectName"
3513
+ ].includes(key)) throw new Error(`Cannot unset required key '${key}'.`);
3514
+ await unsetPulumiConfig(projectRoot, config, resolvedEnv, key);
3515
+ console.log(`✓ Unset ${key} for env '${resolvedEnv}'`);
3516
+ }
3517
+
2985
3518
  //#endregion
2986
3519
  //#region src/bin.ts
2987
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
+ });
2988
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) => {
2989
3531
  requireAuth();
2990
3532
  await generateModule(name, options);
@@ -3069,6 +3611,23 @@ infra.command("outputs").description("Display infrastructure outputs (URLs, conn
3069
3611
  requireAuth();
3070
3612
  await infraOutputs(options);
3071
3613
  });
3614
+ const configCmd = infra.command("config").description("Manage infrastructure configuration");
3615
+ configCmd.command("set <key> <value>").description("Set a configuration value").option("-e, --env <env>", "Environment (dev, prod)", "dev").action(async (key, value, options) => {
3616
+ requireAuth();
3617
+ await infraConfigSet(key, value, options);
3618
+ });
3619
+ configCmd.command("get <key>").description("Get a configuration value").option("-e, --env <env>", "Environment (dev, prod)", "dev").action(async (key, options) => {
3620
+ requireAuth();
3621
+ await infraConfigGet(key, options);
3622
+ });
3623
+ configCmd.command("list").description("List all configuration values").option("-e, --env <env>", "Environment (dev, prod)", "dev").option("--json", "Output as JSON", false).action(async (options) => {
3624
+ requireAuth();
3625
+ await infraConfigList(options);
3626
+ });
3627
+ configCmd.command("unset <key>").description("Remove a configuration value").option("-e, --env <env>", "Environment (dev, prod)", "dev").action(async (key, options) => {
3628
+ requireAuth();
3629
+ await infraConfigUnset(key, options);
3630
+ });
3072
3631
  program.parse();
3073
3632
 
3074
3633
  //#endregion