@kuckit/cli 0.1.0 → 0.2.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/bin.js CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
- import { n as addModule, r as generateModule, t as discoverModules } from "./discover-module-DpVX5MIS.js";
2
+ import { i as generateModule, n as loadTryLoadKuckitConfig, r as addModule, t as discoverModules } from "./discover-module-B7rxN4vq.js";
3
3
  import { program } from "commander";
4
4
  import { dirname, join } from "node:path";
5
5
  import { chmodSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
6
- import { tryLoadKuckitConfig } from "@kuckit/sdk";
7
- import { spawn } from "child_process";
8
- import { access, constants as constants$1, mkdir, readFile, writeFile } from "fs/promises";
6
+ import { execSync, spawn } from "child_process";
7
+ import { access, constants as constants$1, mkdir, readFile, unlink, writeFile } from "fs/promises";
9
8
  import { dirname as dirname$1, join as join$1 } from "path";
10
9
  import { homedir } from "node:os";
10
+ import { confirm, input, select } from "@inquirer/prompts";
11
11
 
12
12
  //#region src/commands/doctor.ts
13
13
  const CONFIG_FILES = [
@@ -78,7 +78,7 @@ function checkModuleExports(packageName, cwd) {
78
78
  ].some((p) => existsSync(p));
79
79
  return result;
80
80
  }
81
- async function loadConfig$1(configPath) {
81
+ async function loadConfig$9(configPath) {
82
82
  try {
83
83
  if (configPath.endsWith(".ts")) {
84
84
  const { createJiti } = await import("jiti");
@@ -118,7 +118,7 @@ async function doctor(options) {
118
118
  });
119
119
  let configModules = [];
120
120
  if (configPath) {
121
- const { success, config, error } = await loadConfig$1(configPath);
121
+ const { success, config, error } = await loadConfig$9(configPath);
122
122
  if (success && config && typeof config === "object") {
123
123
  const cfg = config;
124
124
  if (Array.isArray(cfg.modules)) {
@@ -346,9 +346,9 @@ async function search(keyword, options) {
346
346
 
347
347
  //#endregion
348
348
  //#region src/commands/db.ts
349
- const KUCKIT_DIR = ".kuckit";
349
+ const KUCKIT_DIR$9 = ".kuckit";
350
350
  const TEMP_CONFIG_NAME = "drizzle.config.ts";
351
- async function fileExists(path) {
351
+ async function fileExists$9(path) {
352
352
  try {
353
353
  await access(path, constants$1.F_OK);
354
354
  return true;
@@ -356,10 +356,10 @@ async function fileExists(path) {
356
356
  return false;
357
357
  }
358
358
  }
359
- async function findProjectRoot(cwd) {
359
+ async function findProjectRoot$9(cwd) {
360
360
  let dir = cwd;
361
361
  while (dir !== dirname$1(dir)) {
362
- if (await fileExists(join$1(dir, "package.json"))) return dir;
362
+ if (await fileExists$9(join$1(dir, "package.json"))) return dir;
363
363
  dir = dirname$1(dir);
364
364
  }
365
365
  return null;
@@ -372,7 +372,7 @@ async function getDatabaseUrl(cwd, options) {
372
372
  join$1(cwd, "apps", "server", ".env"),
373
373
  join$1(cwd, ".env.local")
374
374
  ];
375
- for (const envPath of envPaths) if (await fileExists(envPath)) try {
375
+ for (const envPath of envPaths) if (await fileExists$9(envPath)) try {
376
376
  const match = (await readFile(envPath, "utf-8")).match(/^DATABASE_URL=(.+)$/m);
377
377
  if (match) return match[1].replace(/^["']|["']$/g, "");
378
378
  } catch {}
@@ -380,7 +380,7 @@ async function getDatabaseUrl(cwd, options) {
380
380
  }
381
381
  async function discoverModuleSchemas(cwd) {
382
382
  const schemas = [];
383
- const config = await tryLoadKuckitConfig(cwd);
383
+ const config = await (await loadTryLoadKuckitConfig())(cwd);
384
384
  if (!config) return schemas;
385
385
  for (const moduleSpec of config.modules) {
386
386
  if (moduleSpec.disabled) continue;
@@ -388,10 +388,10 @@ async function discoverModuleSchemas(cwd) {
388
388
  let moduleId = null;
389
389
  if (moduleSpec.package) {
390
390
  const workspacePath = join$1(cwd, "packages", moduleSpec.package.replace(/^@[^/]+\//, ""));
391
- if (await fileExists(join$1(workspacePath, "package.json"))) packagePath = workspacePath;
391
+ if (await fileExists$9(join$1(workspacePath, "package.json"))) packagePath = workspacePath;
392
392
  else {
393
393
  const nodeModulesPath = join$1(cwd, "node_modules", moduleSpec.package);
394
- if (await fileExists(join$1(nodeModulesPath, "package.json"))) packagePath = nodeModulesPath;
394
+ if (await fileExists$9(join$1(nodeModulesPath, "package.json"))) packagePath = nodeModulesPath;
395
395
  }
396
396
  if (packagePath) try {
397
397
  moduleId = JSON.parse(await readFile(join$1(packagePath, "package.json"), "utf-8")).kuckit?.id || moduleSpec.package;
@@ -406,7 +406,7 @@ async function discoverModuleSchemas(cwd) {
406
406
  join$1(packagePath, "src", "schema", "index.ts"),
407
407
  join$1(packagePath, "src", "schema.ts")
408
408
  ];
409
- for (const schemaPath of schemaLocations) if (await fileExists(schemaPath)) {
409
+ for (const schemaPath of schemaLocations) if (await fileExists$9(schemaPath)) {
410
410
  schemas.push({
411
411
  moduleId: moduleId || "unknown",
412
412
  schemaPath
@@ -415,14 +415,14 @@ async function discoverModuleSchemas(cwd) {
415
415
  }
416
416
  }
417
417
  const centralSchemaPath = join$1(cwd, "packages", "db", "src", "schema");
418
- if (await fileExists(centralSchemaPath)) schemas.push({
418
+ if (await fileExists$9(centralSchemaPath)) schemas.push({
419
419
  moduleId: "core",
420
420
  schemaPath: centralSchemaPath
421
421
  });
422
422
  return schemas;
423
423
  }
424
424
  async function generateTempDrizzleConfig(cwd, databaseUrl, schemas) {
425
- const kuckitDir = join$1(cwd, KUCKIT_DIR);
425
+ const kuckitDir = join$1(cwd, KUCKIT_DIR$9);
426
426
  await mkdir(kuckitDir, { recursive: true });
427
427
  const configPath = join$1(kuckitDir, TEMP_CONFIG_NAME);
428
428
  const schemaPaths = schemas.map((s) => s.schemaPath);
@@ -474,7 +474,7 @@ function runDrizzleKit(command, configPath, cwd) {
474
474
  });
475
475
  }
476
476
  async function runDbCommand(command, options) {
477
- const projectRoot = await findProjectRoot(process.cwd());
477
+ const projectRoot = await findProjectRoot$9(process.cwd());
478
478
  if (!projectRoot) {
479
479
  console.error("Error: Could not find project root (no package.json found)");
480
480
  process.exit(1);
@@ -522,7 +522,7 @@ async function dbStudio(options) {
522
522
  const DEFAULT_SERVER_URL = "http://localhost:3000";
523
523
  const CONFIG_DIR = join(homedir(), ".kuckit");
524
524
  const CONFIG_PATH = join(CONFIG_DIR, "config.json");
525
- function loadConfig() {
525
+ function loadConfig$8() {
526
526
  try {
527
527
  if (!existsSync(CONFIG_PATH)) return null;
528
528
  const content = readFileSync(CONFIG_PATH, "utf-8");
@@ -531,7 +531,7 @@ function loadConfig() {
531
531
  return null;
532
532
  }
533
533
  }
534
- function saveConfig(config) {
534
+ function saveConfig$2(config) {
535
535
  if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, {
536
536
  recursive: true,
537
537
  mode: 448
@@ -575,7 +575,7 @@ function formatExpiryDate(expiresAt) {
575
575
  });
576
576
  }
577
577
  async function authLogin(options) {
578
- const config = loadConfig();
578
+ const config = loadConfig$8();
579
579
  const serverUrl = getServerUrl(config, options.server);
580
580
  const scopes = options.scopes?.split(",").map((s) => s.trim()) ?? ["cli"];
581
581
  console.log("\nAuthenticating with Kuckit...\n");
@@ -640,7 +640,7 @@ async function authLogin(options) {
640
640
  process.exit(1);
641
641
  }
642
642
  const expiresAt = new Date(Date.now() + pollResult.expiresIn * 1e3).toISOString();
643
- saveConfig({
643
+ saveConfig$2({
644
644
  ...config,
645
645
  cliToken: pollResult.token,
646
646
  tokenExpiresAt: expiresAt,
@@ -671,7 +671,7 @@ async function authLogin(options) {
671
671
  process.exit(1);
672
672
  }
673
673
  async function authWhoami(options) {
674
- const config = loadConfig();
674
+ const config = loadConfig$8();
675
675
  if (!config?.cliToken) {
676
676
  if (options.json) console.log(JSON.stringify({ authenticated: false }));
677
677
  else console.log("Not logged in. Run 'kuckit auth login' to authenticate.");
@@ -702,7 +702,7 @@ async function authWhoami(options) {
702
702
  }
703
703
  }
704
704
  async function authLogout() {
705
- if (!loadConfig()?.cliToken) {
705
+ if (!loadConfig$8()?.cliToken) {
706
706
  console.log("Already logged out.");
707
707
  return;
708
708
  }
@@ -729,7 +729,7 @@ function registerAuthCommands(program$1) {
729
729
  * Exits the process with an error message if not authenticated.
730
730
  */
731
731
  function requireAuth() {
732
- const config = loadConfig();
732
+ const config = loadConfig$8();
733
733
  if (!config?.cliToken) {
734
734
  console.error("\nError: Not logged in. Run 'kuckit auth login' first.\n");
735
735
  process.exit(1);
@@ -741,43 +741,1857 @@ function requireAuth() {
741
741
  }
742
742
 
743
743
  //#endregion
744
- //#region src/bin.ts
745
- program.name("kuckit").description("CLI tools for Kuckit SDK module development").version("0.1.0");
746
- 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) => {
747
- requireAuth();
748
- await generateModule(name, options);
749
- });
750
- program.command("add <package>").description("Install and wire a Kuckit module").option("--skip-install", "Skip package installation", false).action(async (packageName, options) => {
751
- requireAuth();
752
- await addModule(packageName, options);
753
- });
754
- program.command("discover").description("Scan node_modules for installed Kuckit modules").option("--json", "Output as JSON (non-interactive)", false).option("--no-interactive", "Disable interactive prompts").action(async (options) => {
755
- requireAuth();
756
- await discoverModules(options);
757
- });
758
- program.command("doctor").description("Check Kuckit setup and validate module configuration").option("--json", "Output as JSON", false).action(async (options) => {
759
- await doctor(options);
760
- });
761
- program.command("search <keyword>").description("Search npm registry for Kuckit modules").option("--json", "Output as JSON", false).option("-l, --limit <number>", "Maximum results to return", "10").action(async (keyword, options) => {
762
- await search(keyword, {
763
- json: options.json,
764
- limit: parseInt(options.limit, 10)
744
+ //#region src/commands/infra/runner.ts
745
+ /**
746
+ * Run a Pulumi CLI command
747
+ */
748
+ function runPulumi(args, options) {
749
+ return new Promise((resolve) => {
750
+ const proc = spawn("pulumi", args, {
751
+ cwd: options.cwd,
752
+ stdio: options.stream ? [
753
+ "inherit",
754
+ "pipe",
755
+ "pipe"
756
+ ] : [
757
+ "pipe",
758
+ "pipe",
759
+ "pipe"
760
+ ],
761
+ shell: true,
762
+ env: {
763
+ ...process.env,
764
+ ...options.env,
765
+ PULUMI_SKIP_UPDATE_CHECK: "true"
766
+ }
767
+ });
768
+ let stdout = "";
769
+ let stderr = "";
770
+ proc.stdout?.on("data", (data) => {
771
+ const str = data.toString();
772
+ stdout += str;
773
+ if (options.stream) process.stdout.write(data);
774
+ });
775
+ proc.stderr?.on("data", (data) => {
776
+ const str = data.toString();
777
+ stderr += str;
778
+ if (options.stream) process.stderr.write(data);
779
+ });
780
+ proc.on("close", (code) => {
781
+ resolve({
782
+ code: code ?? 1,
783
+ stdout,
784
+ stderr
785
+ });
786
+ });
765
787
  });
766
- });
767
- const db = program.command("db").description("Database schema management for module-owned schemas");
768
- db.command("push").description("Push all module schemas to database").option("--url <url>", "Database connection URL").action(async (options) => {
769
- requireAuth();
770
- await dbPush(options);
771
- });
772
- db.command("generate").description("Generate migration files for module schemas").option("--url <url>", "Database connection URL").action(async (options) => {
773
- requireAuth();
774
- await dbGenerate(options);
775
- });
776
- db.command("studio").description("Open Drizzle Studio with all module schemas").option("--url <url>", "Database connection URL").action(async (options) => {
777
- requireAuth();
778
- await dbStudio(options);
779
- });
780
- registerAuthCommands(program);
788
+ }
789
+ /**
790
+ * Run pulumi stack output --json and parse the result
791
+ */
792
+ async function getPulumiOutputs(options) {
793
+ const result = await runPulumi([
794
+ "stack",
795
+ "output",
796
+ "--json"
797
+ ], options);
798
+ if (result.code !== 0) return null;
799
+ try {
800
+ return JSON.parse(result.stdout);
801
+ } catch {
802
+ return null;
803
+ }
804
+ }
805
+ /**
806
+ * Run pulumi stack --json and parse the result
807
+ */
808
+ async function getPulumiStackInfo(options) {
809
+ const result = await runPulumi(["stack", "--json"], options);
810
+ if (result.code !== 0) return null;
811
+ try {
812
+ const data = JSON.parse(result.stdout);
813
+ return {
814
+ name: data.name ?? "",
815
+ current: data.current ?? false,
816
+ updateInProgress: data.updateInProgress ?? false,
817
+ resourceCount: data.resourceCount ?? 0,
818
+ lastUpdate: data.lastUpdate,
819
+ url: data.url
820
+ };
821
+ } catch {
822
+ return null;
823
+ }
824
+ }
825
+ /**
826
+ * Run pulumi stack history --json and parse the result
827
+ */
828
+ async function getPulumiStackHistory(options, limit = 5) {
829
+ const result = await runPulumi([
830
+ "stack",
831
+ "history",
832
+ "--json",
833
+ "--show-secrets=false"
834
+ ], options);
835
+ if (result.code !== 0) return [];
836
+ try {
837
+ const data = JSON.parse(result.stdout);
838
+ if (!Array.isArray(data)) return [];
839
+ return data.slice(0, limit).map((entry) => ({
840
+ version: entry.version ?? 0,
841
+ startTime: entry.startTime ?? "",
842
+ endTime: entry.endTime ?? "",
843
+ result: entry.result ?? "failed",
844
+ resourceChanges: entry.resourceChanges
845
+ }));
846
+ } catch {
847
+ return [];
848
+ }
849
+ }
850
+ /**
851
+ * Select or create a Pulumi stack
852
+ */
853
+ async function selectOrCreateStack(stackName, options) {
854
+ if ((await runPulumi([
855
+ "stack",
856
+ "select",
857
+ stackName
858
+ ], options)).code === 0) return true;
859
+ return (await runPulumi([
860
+ "stack",
861
+ "init",
862
+ stackName
863
+ ], options)).code === 0;
864
+ }
865
+ /**
866
+ * Set a Pulumi config value
867
+ */
868
+ async function setPulumiConfig(key, value, options) {
869
+ return (await runPulumi([
870
+ "config",
871
+ "set",
872
+ key,
873
+ value
874
+ ], options)).code === 0;
875
+ }
876
+ /**
877
+ * Run pulumi up with optional preview mode
878
+ */
879
+ async function pulumiUp(options) {
880
+ return runPulumi(options.preview ? ["preview"] : ["up", "--yes"], {
881
+ ...options,
882
+ stream: true
883
+ });
884
+ }
885
+ /**
886
+ * Run pulumi destroy
887
+ */
888
+ async function pulumiDestroy(options) {
889
+ return runPulumi(options.force ? [
890
+ "destroy",
891
+ "--yes",
892
+ "-f"
893
+ ] : ["destroy", "--yes"], {
894
+ ...options,
895
+ stream: true
896
+ });
897
+ }
898
+ /**
899
+ * Run pulumi cancel to cancel stuck operations
900
+ */
901
+ async function pulumiCancel(options) {
902
+ return runPulumi(["cancel", "--yes"], options);
903
+ }
904
+ /**
905
+ * Run pulumi refresh to sync state with cloud provider
906
+ */
907
+ async function pulumiRefresh(options) {
908
+ return runPulumi(["refresh", "--yes"], {
909
+ ...options,
910
+ stream: true
911
+ });
912
+ }
913
+ /**
914
+ * Export Pulumi stack state to a file
915
+ */
916
+ async function pulumiStackExport(filePath, options) {
917
+ return runPulumi([
918
+ "stack",
919
+ "export",
920
+ "--file",
921
+ filePath
922
+ ], options);
923
+ }
924
+ /**
925
+ * Run a gcloud CLI command
926
+ */
927
+ function runGcloud(args, options = {}) {
928
+ return new Promise((resolve) => {
929
+ const proc = spawn("gcloud", args, {
930
+ cwd: options.cwd ?? process.cwd(),
931
+ stdio: options.stream ? [
932
+ "inherit",
933
+ "pipe",
934
+ "pipe"
935
+ ] : [
936
+ "pipe",
937
+ "pipe",
938
+ "pipe"
939
+ ],
940
+ shell: true
941
+ });
942
+ let stdout = "";
943
+ let stderr = "";
944
+ proc.stdout?.on("data", (data) => {
945
+ const str = data.toString();
946
+ stdout += str;
947
+ if (options.stream) process.stdout.write(data);
948
+ });
949
+ proc.stderr?.on("data", (data) => {
950
+ const str = data.toString();
951
+ stderr += str;
952
+ if (options.stream) process.stderr.write(data);
953
+ });
954
+ proc.on("close", (code) => {
955
+ resolve({
956
+ code: code ?? 1,
957
+ stdout,
958
+ stderr
959
+ });
960
+ });
961
+ });
962
+ }
963
+ /**
964
+ * Build and push Docker image using Cloud Build
965
+ * No local Docker daemon required!
966
+ */
967
+ async function buildAndPushImage(options) {
968
+ const tag = options.tag ?? process.env.GIT_SHA ?? "latest";
969
+ const imageUrl = `${options.registryUrl}/kuckit:${tag}`;
970
+ console.log(`Building image: ${imageUrl}`);
971
+ const args = [
972
+ "builds",
973
+ "submit",
974
+ "--tag",
975
+ imageUrl,
976
+ "--project",
977
+ options.project
978
+ ];
979
+ if (options.dockerfile && options.dockerfile !== "Dockerfile") args.push("--config", options.dockerfile);
980
+ return {
981
+ code: (await runGcloud(args, {
982
+ stream: true,
983
+ cwd: options.cwd
984
+ })).code,
985
+ imageUrl
986
+ };
987
+ }
988
+ /**
989
+ * Get the path to the packages/infra directory
990
+ */
991
+ function getInfraDir(projectRoot) {
992
+ return join$1(projectRoot, "packages", "infra");
993
+ }
994
+ /**
995
+ * Check if Pulumi CLI is installed
996
+ */
997
+ async function checkPulumiInstalled() {
998
+ try {
999
+ return (await runPulumi(["version"], { cwd: process.cwd() })).code === 0;
1000
+ } catch {
1001
+ return false;
1002
+ }
1003
+ }
1004
+ /**
1005
+ * Check if gcloud CLI is installed
1006
+ */
1007
+ async function checkGcloudInstalled() {
1008
+ try {
1009
+ return (await runGcloud(["version"])).code === 0;
1010
+ } catch {
1011
+ return false;
1012
+ }
1013
+ }
1014
+ /**
1015
+ * List Cloud Run service revisions
1016
+ */
1017
+ async function listCloudRunRevisions(serviceName, project, region) {
1018
+ const revisionsResult = await runGcloud([
1019
+ "run",
1020
+ "revisions",
1021
+ "list",
1022
+ "--service",
1023
+ serviceName,
1024
+ "--project",
1025
+ project,
1026
+ "--region",
1027
+ region,
1028
+ "--format",
1029
+ "json"
1030
+ ]);
1031
+ if (revisionsResult.code !== 0) return [];
1032
+ const trafficResult = await runGcloud([
1033
+ "run",
1034
+ "services",
1035
+ "describe",
1036
+ serviceName,
1037
+ "--project",
1038
+ project,
1039
+ "--region",
1040
+ region,
1041
+ "--format",
1042
+ "json"
1043
+ ]);
1044
+ const trafficMap = {};
1045
+ if (trafficResult.code === 0) try {
1046
+ const traffic = JSON.parse(trafficResult.stdout).status?.traffic ?? [];
1047
+ for (const t of traffic) if (t.revisionName && t.percent) trafficMap[t.revisionName] = t.percent;
1048
+ } catch {}
1049
+ try {
1050
+ return JSON.parse(revisionsResult.stdout).map((rev) => {
1051
+ const name = rev.metadata?.name ?? "";
1052
+ const trafficPercent = trafficMap[name] ?? 0;
1053
+ return {
1054
+ name,
1055
+ createTime: rev.metadata?.creationTimestamp ?? "",
1056
+ active: trafficPercent > 0,
1057
+ trafficPercent
1058
+ };
1059
+ });
1060
+ } catch {
1061
+ return [];
1062
+ }
1063
+ }
1064
+ /**
1065
+ * Update Cloud Run traffic routing to a specific revision
1066
+ */
1067
+ async function updateCloudRunTraffic(serviceName, revisionName, project, region) {
1068
+ return runGcloud([
1069
+ "run",
1070
+ "services",
1071
+ "update-traffic",
1072
+ serviceName,
1073
+ "--to-revisions",
1074
+ `${revisionName}=100`,
1075
+ "--project",
1076
+ project,
1077
+ "--region",
1078
+ region
1079
+ ], { stream: true });
1080
+ }
1081
+ /**
1082
+ * Extract service name from Cloud Run URL
1083
+ * e.g., https://myapp-abc123-uc.a.run.app -> myapp
1084
+ */
1085
+ function extractServiceNameFromUrl(url) {
1086
+ try {
1087
+ const hostname = new URL(url).hostname;
1088
+ const match = hostname.match(/^([^-]+(?:-[^-]+)*?)-[a-z0-9]+-[a-z]+\.a\.run\.app$/);
1089
+ if (match) return match[1];
1090
+ const parts = hostname.split("-");
1091
+ if (parts.length >= 3) return parts.slice(0, -2).join("-");
1092
+ return null;
1093
+ } catch {
1094
+ return null;
1095
+ }
1096
+ }
1097
+
1098
+ //#endregion
1099
+ //#region src/commands/infra/errors.ts
1100
+ /**
1101
+ * Error parsing for Pulumi and infrastructure operations
1102
+ */
1103
+ /**
1104
+ * Types of errors that can occur during infrastructure operations
1105
+ */
1106
+ let PulumiErrorType = /* @__PURE__ */ function(PulumiErrorType$1) {
1107
+ /** Another update is already in progress */
1108
+ PulumiErrorType$1["ConcurrentUpdate"] = "CONCURRENT_UPDATE";
1109
+ /** Resource already exists */
1110
+ PulumiErrorType$1["ResourceConflict"] = "RESOURCE_CONFLICT";
1111
+ /** GCP quota exceeded */
1112
+ PulumiErrorType$1["QuotaExceeded"] = "QUOTA_EXCEEDED";
1113
+ /** Permission denied (IAM issues) */
1114
+ PulumiErrorType$1["PermissionDenied"] = "PERMISSION_DENIED";
1115
+ /** State corruption or pending operations */
1116
+ PulumiErrorType$1["StateCorruption"] = "STATE_CORRUPTION";
1117
+ /** Network or transient failure */
1118
+ PulumiErrorType$1["NetworkError"] = "NETWORK_ERROR";
1119
+ /** Resource not found */
1120
+ PulumiErrorType$1["ResourceNotFound"] = "RESOURCE_NOT_FOUND";
1121
+ /** Invalid configuration */
1122
+ PulumiErrorType$1["InvalidConfig"] = "INVALID_CONFIG";
1123
+ /** Unknown error */
1124
+ PulumiErrorType$1["Unknown"] = "UNKNOWN";
1125
+ return PulumiErrorType$1;
1126
+ }({});
1127
+ /**
1128
+ * Error patterns and their mappings
1129
+ */
1130
+ const ERROR_PATTERNS = [
1131
+ {
1132
+ pattern: /conflict.*concurrent.*update|another update is in progress|lock/i,
1133
+ type: PulumiErrorType.ConcurrentUpdate,
1134
+ suggestion: "Run: kuckit infra repair --cancel",
1135
+ retryable: false
1136
+ },
1137
+ {
1138
+ pattern: /already exists|resource.*conflict|duplicate/i,
1139
+ type: PulumiErrorType.ResourceConflict,
1140
+ suggestion: "Run: kuckit infra repair --refresh",
1141
+ retryable: false
1142
+ },
1143
+ {
1144
+ pattern: /quota.*exceeded|limit.*reached|resource exhausted/i,
1145
+ type: PulumiErrorType.QuotaExceeded,
1146
+ suggestion: "Request quota increase in GCP Console: https://console.cloud.google.com/iam-admin/quotas",
1147
+ retryable: false
1148
+ },
1149
+ {
1150
+ pattern: /permission.*denied|access.*denied|forbidden|unauthorized|403/i,
1151
+ type: PulumiErrorType.PermissionDenied,
1152
+ suggestion: "Check IAM permissions in GCP Console. Ensure your account has the required roles.",
1153
+ retryable: false
1154
+ },
1155
+ {
1156
+ pattern: /pending.*operation|state.*corrupt|inconsistent.*state/i,
1157
+ type: PulumiErrorType.StateCorruption,
1158
+ suggestion: "Run: kuckit infra repair --refresh",
1159
+ retryable: false
1160
+ },
1161
+ {
1162
+ pattern: /network.*error|connection.*refused|timeout|ETIMEDOUT|ECONNRESET/i,
1163
+ type: PulumiErrorType.NetworkError,
1164
+ suggestion: "Check your network connection and try again.",
1165
+ retryable: true
1166
+ },
1167
+ {
1168
+ pattern: /not found|does not exist|404/i,
1169
+ type: PulumiErrorType.ResourceNotFound,
1170
+ suggestion: "The resource may have been deleted externally. Run: kuckit infra repair --refresh",
1171
+ retryable: false
1172
+ },
1173
+ {
1174
+ pattern: /invalid.*config|configuration.*error|missing.*required/i,
1175
+ type: PulumiErrorType.InvalidConfig,
1176
+ suggestion: "Check your configuration in .kuckit/infra.json and Pulumi stack config.",
1177
+ retryable: false
1178
+ }
1179
+ ];
1180
+ /**
1181
+ * Extract resource name from error output
1182
+ */
1183
+ function extractResourceName(error) {
1184
+ const urnMatch = error.match(/urn:pulumi:[^:]+:[^:]+::([^:]+:[^:\s]+)/i);
1185
+ if (urnMatch) return urnMatch[1];
1186
+ const gcpMatch = error.match(/projects\/[^/]+\/(?:locations|regions)\/[^/]+\/([^/\s]+\/[^/\s]+)/i);
1187
+ if (gcpMatch) return gcpMatch[1];
1188
+ }
1189
+ /**
1190
+ * Parse an error output and return a structured ParsedError
1191
+ */
1192
+ function parseError(error) {
1193
+ for (const { pattern, type, suggestion, retryable } of ERROR_PATTERNS) if (pattern.test(error)) return {
1194
+ type,
1195
+ message: getErrorMessage(type, error),
1196
+ resource: extractResourceName(error),
1197
+ suggestion,
1198
+ retryable,
1199
+ originalError: error
1200
+ };
1201
+ return {
1202
+ type: PulumiErrorType.Unknown,
1203
+ message: "An unknown error occurred during infrastructure operation.",
1204
+ resource: extractResourceName(error),
1205
+ suggestion: "Check the error output above. If the issue persists, run: kuckit infra repair --refresh",
1206
+ retryable: false,
1207
+ originalError: error
1208
+ };
1209
+ }
1210
+ /**
1211
+ * Get a human-readable error message for an error type
1212
+ */
1213
+ function getErrorMessage(type, originalError) {
1214
+ switch (type) {
1215
+ case PulumiErrorType.ConcurrentUpdate: return "Another infrastructure update is already in progress.";
1216
+ case PulumiErrorType.ResourceConflict: return "A resource with this name already exists.";
1217
+ case PulumiErrorType.QuotaExceeded: return "GCP quota limit has been reached.";
1218
+ case PulumiErrorType.PermissionDenied: return "Permission denied. Check your IAM permissions.";
1219
+ case PulumiErrorType.StateCorruption: return "Infrastructure state is inconsistent.";
1220
+ case PulumiErrorType.NetworkError: return "Network error occurred. This may be a transient issue.";
1221
+ case PulumiErrorType.ResourceNotFound: return "A required resource was not found.";
1222
+ case PulumiErrorType.InvalidConfig: return "Invalid configuration detected.";
1223
+ default: return originalError.split("\n").find((line) => line.trim().length > 0)?.slice(0, 200) ?? "An error occurred.";
1224
+ }
1225
+ }
1226
+ /**
1227
+ * Format a ParsedError for console output
1228
+ */
1229
+ function formatError(error) {
1230
+ const lines = [`Error: ${error.message}`, ""];
1231
+ if (error.resource) lines.push(`Resource: ${error.resource}`);
1232
+ lines.push(`Suggestion: ${error.suggestion}`);
1233
+ return lines.join("\n");
1234
+ }
1235
+
1236
+ //#endregion
1237
+ //#region src/commands/infra/init.ts
1238
+ const KUCKIT_DIR$8 = ".kuckit";
1239
+ const CONFIG_FILE$8 = "infra.json";
1240
+ async function fileExists$8(path) {
1241
+ try {
1242
+ await access(path, constants$1.F_OK);
1243
+ return true;
1244
+ } catch {
1245
+ return false;
1246
+ }
1247
+ }
1248
+ async function findProjectRoot$8(cwd) {
1249
+ let dir = cwd;
1250
+ while (dir !== dirname$1(dir)) {
1251
+ if (await fileExists$8(join$1(dir, "package.json"))) return dir;
1252
+ dir = dirname$1(dir);
1253
+ }
1254
+ return null;
1255
+ }
1256
+ async function loadExistingConfig(projectRoot) {
1257
+ const configPath = join$1(projectRoot, KUCKIT_DIR$8, CONFIG_FILE$8);
1258
+ if (!await fileExists$8(configPath)) return null;
1259
+ try {
1260
+ const content = await readFile(configPath, "utf-8");
1261
+ return JSON.parse(content);
1262
+ } catch {
1263
+ return null;
1264
+ }
1265
+ }
1266
+ async function saveConfig$1(projectRoot, config) {
1267
+ const kuckitDir = join$1(projectRoot, KUCKIT_DIR$8);
1268
+ await mkdir(kuckitDir, { recursive: true });
1269
+ await writeFile(join$1(kuckitDir, CONFIG_FILE$8), JSON.stringify(config, null, 2), "utf-8");
1270
+ }
1271
+ async function infraInit(options) {
1272
+ console.log("Initializing kuckit infrastructure...\n");
1273
+ if (!await checkPulumiInstalled()) {
1274
+ console.error("Error: Pulumi CLI is not installed.");
1275
+ console.error("Install it from: https://www.pulumi.com/docs/install/");
1276
+ process.exit(1);
1277
+ }
1278
+ if (!await checkGcloudInstalled()) {
1279
+ console.error("Error: gcloud CLI is not installed.");
1280
+ console.error("Install it from: https://cloud.google.com/sdk/docs/install");
1281
+ process.exit(1);
1282
+ }
1283
+ const projectRoot = await findProjectRoot$8(process.cwd());
1284
+ if (!projectRoot) {
1285
+ console.error("Error: Could not find project root (no package.json found)");
1286
+ process.exit(1);
1287
+ }
1288
+ const infraDir = getInfraDir(projectRoot);
1289
+ if (!await fileExists$8(infraDir)) {
1290
+ console.error("Error: packages/infra not found.");
1291
+ console.error("Make sure you have the @kuckit/infra package in your project.");
1292
+ process.exit(1);
1293
+ }
1294
+ const existingConfig = await loadExistingConfig(projectRoot);
1295
+ const provider = options.provider ?? "gcp";
1296
+ if (provider !== "gcp") {
1297
+ console.error(`Error: Provider '${provider}' is not supported. Only 'gcp' is currently supported.`);
1298
+ process.exit(1);
1299
+ }
1300
+ let gcpProject = options.project ?? existingConfig?.gcpProject;
1301
+ if (!gcpProject) gcpProject = await input({
1302
+ message: "GCP Project ID:",
1303
+ validate: (value) => value.length > 0 ? true : "Project ID is required"
1304
+ });
1305
+ let region = options.region ?? existingConfig?.region ?? "us-central1";
1306
+ if (!options.region && !existingConfig?.region) region = await input({
1307
+ message: "GCP Region:",
1308
+ default: "us-central1"
1309
+ });
1310
+ let env = options.env ?? existingConfig?.env ?? "dev";
1311
+ if (!options.env && !existingConfig?.env) env = await input({
1312
+ message: "Environment (dev/prod):",
1313
+ default: "dev",
1314
+ validate: (value) => value === "dev" || value === "prod" ? true : "Must be dev or prod"
1315
+ });
1316
+ const stackName = `${gcpProject}-${env}`;
1317
+ console.log("\nConfiguration:");
1318
+ console.log(` Provider: ${provider}`);
1319
+ console.log(` GCP Project: ${gcpProject}`);
1320
+ console.log(` Region: ${region}`);
1321
+ console.log(` Environment: ${env}`);
1322
+ console.log(` Stack: ${stackName}`);
1323
+ console.log("");
1324
+ if (!options.yes) {
1325
+ if (!await confirm({
1326
+ message: "Proceed with infrastructure initialization?",
1327
+ default: true
1328
+ })) {
1329
+ console.log("Aborted.");
1330
+ process.exit(0);
1331
+ }
1332
+ }
1333
+ console.log("\nInstalling infrastructure dependencies...");
1334
+ const { spawn: spawn$1 } = await import("child_process");
1335
+ await new Promise((resolve, reject) => {
1336
+ spawn$1("bun", ["install"], {
1337
+ cwd: infraDir,
1338
+ stdio: "inherit",
1339
+ shell: true
1340
+ }).on("close", (code) => {
1341
+ if (code === 0) resolve();
1342
+ else reject(/* @__PURE__ */ new Error(`bun install failed with code ${code}`));
1343
+ });
1344
+ });
1345
+ console.log("\nBuilding infrastructure package...");
1346
+ await new Promise((resolve, reject) => {
1347
+ spawn$1("bun", ["run", "build"], {
1348
+ cwd: infraDir,
1349
+ stdio: "inherit",
1350
+ shell: true
1351
+ }).on("close", (code) => {
1352
+ if (code === 0) resolve();
1353
+ else reject(/* @__PURE__ */ new Error(`Build failed with code ${code}`));
1354
+ });
1355
+ });
1356
+ console.log(`\nSelecting Pulumi stack: ${stackName}`);
1357
+ if (!await selectOrCreateStack(stackName, { cwd: infraDir })) {
1358
+ console.error("Error: Failed to select or create Pulumi stack");
1359
+ process.exit(1);
1360
+ }
1361
+ console.log("Configuring stack...");
1362
+ await setPulumiConfig("gcp:project", gcpProject, { cwd: infraDir });
1363
+ await setPulumiConfig("gcp:region", region, { cwd: infraDir });
1364
+ await setPulumiConfig("env", env, { cwd: infraDir });
1365
+ console.log("\nCreating infrastructure...");
1366
+ console.log("This may take several minutes.\n");
1367
+ const result = await pulumiUp({
1368
+ cwd: infraDir,
1369
+ stream: true
1370
+ });
1371
+ if (result.code !== 0) {
1372
+ const parsed = parseError(result.stderr);
1373
+ console.error("\n" + formatError(parsed));
1374
+ process.exit(1);
1375
+ }
1376
+ console.log("\nRetrieving outputs...");
1377
+ const outputs = await getPulumiOutputs({ cwd: infraDir });
1378
+ await saveConfig$1(projectRoot, {
1379
+ provider: "gcp",
1380
+ gcpProject,
1381
+ region,
1382
+ projectName: "kuckit-infra",
1383
+ stackName,
1384
+ env,
1385
+ outputs: outputs ? {
1386
+ registryUrl: outputs.registryUrl,
1387
+ databaseConnectionName: outputs.databaseConnectionName,
1388
+ redisHost: outputs.redisHost,
1389
+ secretIds: {
1390
+ dbPassword: outputs.dbPasswordSecretId,
1391
+ redisAuth: outputs.redisAuthSecretId ?? ""
1392
+ }
1393
+ } : void 0,
1394
+ createdAt: existingConfig?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
1395
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1396
+ });
1397
+ console.log(`\nConfiguration saved to ${KUCKIT_DIR$8}/${CONFIG_FILE$8}`);
1398
+ console.log("\n" + "=".repeat(60));
1399
+ console.log("Infrastructure initialized successfully!");
1400
+ console.log("=".repeat(60));
1401
+ if (outputs) {
1402
+ console.log("\nOutputs:");
1403
+ console.log(` Registry: ${outputs.registryUrl}`);
1404
+ console.log(` Database: ${outputs.databaseConnectionName}`);
1405
+ console.log(` Redis: ${outputs.redisHost}`);
1406
+ }
1407
+ console.log("\nNext steps:");
1408
+ console.log(" 1. Ensure you have a Dockerfile in your project root");
1409
+ console.log(` 2. Run: kuckit infra deploy --env ${env}`);
1410
+ console.log("");
1411
+ }
1412
+
1413
+ //#endregion
1414
+ //#region src/commands/infra/deploy.ts
1415
+ const KUCKIT_DIR$7 = ".kuckit";
1416
+ const CONFIG_FILE$7 = "infra.json";
1417
+ async function saveConfig(projectRoot, config) {
1418
+ const kuckitDir = join$1(projectRoot, KUCKIT_DIR$7);
1419
+ await mkdir(kuckitDir, { recursive: true });
1420
+ await writeFile(join$1(kuckitDir, CONFIG_FILE$7), JSON.stringify(config, null, 2), "utf-8");
1421
+ }
1422
+ /**
1423
+ * Get the current git commit short hash
1424
+ * Returns null if not in a git repo or git command fails
1425
+ */
1426
+ function getGitShortHash() {
1427
+ try {
1428
+ return execSync("git rev-parse --short HEAD", { encoding: "utf-8" }).trim();
1429
+ } catch {
1430
+ return null;
1431
+ }
1432
+ }
1433
+ async function fileExists$7(path) {
1434
+ try {
1435
+ await access(path, constants$1.F_OK);
1436
+ return true;
1437
+ } catch {
1438
+ return false;
1439
+ }
1440
+ }
1441
+ async function findProjectRoot$7(cwd) {
1442
+ let dir = cwd;
1443
+ while (dir !== dirname$1(dir)) {
1444
+ if (await fileExists$7(join$1(dir, "package.json"))) return dir;
1445
+ dir = dirname$1(dir);
1446
+ }
1447
+ return null;
1448
+ }
1449
+ async function loadConfig$7(projectRoot) {
1450
+ const configPath = join$1(projectRoot, KUCKIT_DIR$7, CONFIG_FILE$7);
1451
+ if (!await fileExists$7(configPath)) return null;
1452
+ try {
1453
+ const content = await readFile(configPath, "utf-8");
1454
+ return JSON.parse(content);
1455
+ } catch {
1456
+ return null;
1457
+ }
1458
+ }
1459
+ async function infraDeploy(options) {
1460
+ console.log("Deploying kuckit application...\n");
1461
+ if (!await checkPulumiInstalled()) {
1462
+ console.error("Error: Pulumi CLI is not installed.");
1463
+ console.error("Install it from: https://www.pulumi.com/docs/install/");
1464
+ process.exit(1);
1465
+ }
1466
+ if (!await checkGcloudInstalled()) {
1467
+ console.error("Error: gcloud CLI is not installed.");
1468
+ console.error("Install it from: https://cloud.google.com/sdk/docs/install");
1469
+ process.exit(1);
1470
+ }
1471
+ const projectRoot = await findProjectRoot$7(process.cwd());
1472
+ if (!projectRoot) {
1473
+ console.error("Error: Could not find project root (no package.json found)");
1474
+ process.exit(1);
1475
+ }
1476
+ const config = await loadConfig$7(projectRoot);
1477
+ if (!config) {
1478
+ console.error("Error: No infrastructure configuration found.");
1479
+ console.error("Run: kuckit infra init");
1480
+ process.exit(1);
1481
+ }
1482
+ const env = options.env ?? config.env;
1483
+ if (env !== "dev" && env !== "prod") {
1484
+ console.error(`Error: Invalid environment '${env}'. Must be 'dev' or 'prod'.`);
1485
+ process.exit(1);
1486
+ }
1487
+ const infraDir = getInfraDir(projectRoot);
1488
+ if (!await fileExists$7(infraDir)) {
1489
+ console.error("Error: packages/infra not found.");
1490
+ process.exit(1);
1491
+ }
1492
+ const dockerfilePath = join$1(projectRoot, "Dockerfile");
1493
+ if (!options.skipBuild && !options.image && !await fileExists$7(dockerfilePath)) {
1494
+ console.error("Error: Dockerfile not found in project root.");
1495
+ console.error("Create a Dockerfile or use --skip-build with an existing image.");
1496
+ process.exit(1);
1497
+ }
1498
+ if (!config.outputs?.registryUrl) {
1499
+ console.error("Error: No registry URL found in configuration.");
1500
+ console.error("Run: kuckit infra init");
1501
+ process.exit(1);
1502
+ }
1503
+ console.log("Configuration:");
1504
+ console.log(` Project: ${config.gcpProject}`);
1505
+ console.log(` Region: ${config.region}`);
1506
+ console.log(` Environment: ${env}`);
1507
+ console.log(` Registry: ${config.outputs.registryUrl}`);
1508
+ if (options.preview) console.log(" Mode: Preview (no changes will be applied)");
1509
+ console.log("");
1510
+ let imageUrl;
1511
+ if (options.image) {
1512
+ imageUrl = options.image;
1513
+ console.log(`Using provided image: ${imageUrl}\n`);
1514
+ } else if (options.skipBuild) {
1515
+ imageUrl = `${config.outputs.registryUrl}/kuckit:latest`;
1516
+ console.log(`Using existing image: ${imageUrl}\n`);
1517
+ } else {
1518
+ console.log("Building and pushing Docker image...");
1519
+ console.log("(Using Cloud Build - no local Docker required)\n");
1520
+ const buildResult = await buildAndPushImage({
1521
+ project: config.gcpProject,
1522
+ registryUrl: config.outputs.registryUrl,
1523
+ tag: process.env.GIT_SHA ?? process.env.GITHUB_SHA ?? getGitShortHash() ?? "latest",
1524
+ cwd: projectRoot
1525
+ });
1526
+ if (buildResult.code !== 0) {
1527
+ console.error("\nError: Docker build failed.");
1528
+ process.exit(1);
1529
+ }
1530
+ imageUrl = buildResult.imageUrl;
1531
+ console.log(`\nImage built: ${imageUrl}\n`);
1532
+ }
1533
+ if (!options.yes && !options.preview) {
1534
+ if (!await confirm({
1535
+ message: "Proceed with deployment?",
1536
+ default: true
1537
+ })) {
1538
+ console.log("Aborted.");
1539
+ process.exit(0);
1540
+ }
1541
+ }
1542
+ const stackName = config.stackName;
1543
+ console.log(`Selecting Pulumi stack: ${stackName}`);
1544
+ if (!await selectOrCreateStack(stackName, { cwd: infraDir })) {
1545
+ console.error("Error: Failed to select Pulumi stack");
1546
+ process.exit(1);
1547
+ }
1548
+ console.log("Configuring deployment...");
1549
+ await setPulumiConfig("imageUrl", imageUrl, { cwd: infraDir });
1550
+ if (options.preview) console.log("\nPreviewing changes...\n");
1551
+ else console.log("\nDeploying to Cloud Run...\n");
1552
+ const result = await pulumiUp({
1553
+ cwd: infraDir,
1554
+ stream: true,
1555
+ preview: options.preview
1556
+ });
1557
+ if (result.code !== 0) {
1558
+ const parsed = parseError(result.stderr);
1559
+ console.error("\n" + formatError(parsed));
1560
+ process.exit(1);
1561
+ }
1562
+ if (!options.preview) {
1563
+ console.log("\nRetrieving deployment info...");
1564
+ const outputs = await getPulumiOutputs({ cwd: infraDir });
1565
+ if (outputs) {
1566
+ config.outputs = {
1567
+ ...config.outputs,
1568
+ ...outputs
1569
+ };
1570
+ config.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1571
+ await saveConfig(projectRoot, config);
1572
+ }
1573
+ console.log("\n" + "=".repeat(60));
1574
+ console.log("Deployment successful!");
1575
+ console.log("=".repeat(60));
1576
+ if (outputs?.serviceUrl) console.log(`\nService URL: ${outputs.serviceUrl}`);
1577
+ if (outputs?.migrationJobName) console.log(`Migration Job: ${outputs.migrationJobName}`);
1578
+ console.log("\nUseful commands:");
1579
+ console.log(` View logs: kuckit infra logs --env ${env}`);
1580
+ console.log(` Check status: kuckit infra status --env ${env}`);
1581
+ console.log(` Run migrations: kuckit infra db:migrate --env ${env}`);
1582
+ console.log("");
1583
+ } else {
1584
+ console.log("\nPreview complete. No changes were applied.");
1585
+ console.log("Run without --preview to apply changes.");
1586
+ }
1587
+ }
1588
+
1589
+ //#endregion
1590
+ //#region src/commands/infra/destroy.ts
1591
+ const KUCKIT_DIR$6 = ".kuckit";
1592
+ const CONFIG_FILE$6 = "infra.json";
1593
+ async function fileExists$6(path) {
1594
+ try {
1595
+ await access(path, constants$1.F_OK);
1596
+ return true;
1597
+ } catch {
1598
+ return false;
1599
+ }
1600
+ }
1601
+ async function findProjectRoot$6(cwd) {
1602
+ let dir = cwd;
1603
+ while (dir !== dirname$1(dir)) {
1604
+ if (await fileExists$6(join$1(dir, "package.json"))) return dir;
1605
+ dir = dirname$1(dir);
1606
+ }
1607
+ return null;
1608
+ }
1609
+ async function loadConfig$6(projectRoot) {
1610
+ const configPath = join$1(projectRoot, KUCKIT_DIR$6, CONFIG_FILE$6);
1611
+ if (!await fileExists$6(configPath)) return null;
1612
+ try {
1613
+ const content = await readFile(configPath, "utf-8");
1614
+ return JSON.parse(content);
1615
+ } catch {
1616
+ return null;
1617
+ }
1618
+ }
1619
+ async function infraDestroy(options) {
1620
+ const isAppOnly = options.appOnly ?? false;
1621
+ if (isAppOnly) console.log("Destroying Cloud Run application (keeping database and Redis)...\n");
1622
+ else console.log("Destroying kuckit infrastructure...\n");
1623
+ if (!await checkPulumiInstalled()) {
1624
+ console.error("Error: Pulumi CLI is not installed.");
1625
+ console.error("Install it from: https://www.pulumi.com/docs/install/");
1626
+ process.exit(1);
1627
+ }
1628
+ const projectRoot = await findProjectRoot$6(process.cwd());
1629
+ if (!projectRoot) {
1630
+ console.error("Error: Could not find project root (no package.json found)");
1631
+ process.exit(1);
1632
+ }
1633
+ const config = await loadConfig$6(projectRoot);
1634
+ if (!config) {
1635
+ console.error("Error: No infrastructure configuration found.");
1636
+ console.error("Nothing to destroy.");
1637
+ process.exit(1);
1638
+ }
1639
+ const env = options.env ?? config.env;
1640
+ if (env !== "dev" && env !== "prod") {
1641
+ console.error(`Error: Invalid environment '${env}'. Must be 'dev' or 'prod'.`);
1642
+ process.exit(1);
1643
+ }
1644
+ const infraDir = getInfraDir(projectRoot);
1645
+ if (!await fileExists$6(infraDir)) {
1646
+ console.error("Error: packages/infra not found.");
1647
+ process.exit(1);
1648
+ }
1649
+ console.log("Configuration:");
1650
+ console.log(` Project: ${config.gcpProject}`);
1651
+ console.log(` Region: ${config.region}`);
1652
+ console.log(` Environment: ${env}`);
1653
+ console.log(` Stack: ${config.stackName}`);
1654
+ console.log("");
1655
+ if (isAppOnly) {
1656
+ console.log("WARNING: This will destroy the Cloud Run service.");
1657
+ console.log(" Database and Redis will be preserved.\n");
1658
+ } else {
1659
+ console.log("=".repeat(60));
1660
+ console.log("WARNING: DESTRUCTIVE OPERATION");
1661
+ console.log("=".repeat(60));
1662
+ console.log("");
1663
+ console.log("This will PERMANENTLY destroy:");
1664
+ console.log(" - Cloud Run service");
1665
+ console.log(" - Cloud SQL database (ALL DATA WILL BE LOST)");
1666
+ console.log(" - Memorystore Redis instance");
1667
+ console.log(" - Artifact Registry and all container images");
1668
+ console.log(" - All associated secrets");
1669
+ console.log("");
1670
+ console.log("This action CANNOT be undone.");
1671
+ console.log("");
1672
+ }
1673
+ if (!options.force) {
1674
+ if (!await confirm({
1675
+ message: isAppOnly ? "Are you sure you want to destroy the Cloud Run service?" : "Are you ABSOLUTELY sure you want to destroy ALL infrastructure?",
1676
+ default: false
1677
+ })) {
1678
+ console.log("Aborted.");
1679
+ process.exit(0);
1680
+ }
1681
+ if (!isAppOnly) {
1682
+ if (!await confirm({
1683
+ message: "This will delete ALL data. Type \"yes\" to confirm:",
1684
+ default: false
1685
+ })) {
1686
+ console.log("Aborted.");
1687
+ process.exit(0);
1688
+ }
1689
+ }
1690
+ }
1691
+ const stackName = config.stackName;
1692
+ console.log(`\nSelecting Pulumi stack: ${stackName}`);
1693
+ if (!await selectOrCreateStack(stackName, { cwd: infraDir })) {
1694
+ console.error("Error: Failed to select Pulumi stack");
1695
+ process.exit(1);
1696
+ }
1697
+ if (isAppOnly) {
1698
+ console.log("\nRemoving Cloud Run service...");
1699
+ console.log("(Database and Redis will be preserved)\n");
1700
+ await setPulumiConfig("imageUrl", "", { cwd: infraDir });
1701
+ const result = await pulumiUp({
1702
+ cwd: infraDir,
1703
+ stream: true
1704
+ });
1705
+ if (result.code !== 0) {
1706
+ const parsed = parseError(result.stderr);
1707
+ console.error("\n" + formatError(parsed));
1708
+ process.exit(1);
1709
+ }
1710
+ console.log("\n" + "=".repeat(60));
1711
+ console.log("Cloud Run service destroyed successfully!");
1712
+ console.log("=".repeat(60));
1713
+ console.log("\nBase infrastructure preserved:");
1714
+ console.log(" - Cloud SQL database");
1715
+ console.log(" - Memorystore Redis");
1716
+ console.log(" - Artifact Registry");
1717
+ console.log("\nTo redeploy: kuckit infra deploy --env " + env);
1718
+ } else {
1719
+ console.log("\nDestroying all infrastructure...");
1720
+ console.log("This may take several minutes.\n");
1721
+ const result = await pulumiDestroy({
1722
+ cwd: infraDir,
1723
+ force: true
1724
+ });
1725
+ if (result.code !== 0) {
1726
+ const parsed = parseError(result.stderr);
1727
+ console.error("\n" + formatError(parsed));
1728
+ process.exit(1);
1729
+ }
1730
+ const configPath = join$1(projectRoot, KUCKIT_DIR$6, CONFIG_FILE$6);
1731
+ try {
1732
+ await unlink(configPath);
1733
+ console.log(`\nRemoved ${KUCKIT_DIR$6}/${CONFIG_FILE$6}`);
1734
+ } catch {}
1735
+ console.log("\n" + "=".repeat(60));
1736
+ console.log("Infrastructure destroyed successfully!");
1737
+ console.log("=".repeat(60));
1738
+ console.log("\nAll resources have been removed.");
1739
+ console.log("To recreate: kuckit infra init");
1740
+ }
1741
+ console.log("");
1742
+ }
1743
+
1744
+ //#endregion
1745
+ //#region src/commands/infra/repair.ts
1746
+ const KUCKIT_DIR$5 = ".kuckit";
1747
+ const CONFIG_FILE$5 = "infra.json";
1748
+ async function fileExists$5(path) {
1749
+ try {
1750
+ await access(path, constants$1.F_OK);
1751
+ return true;
1752
+ } catch {
1753
+ return false;
1754
+ }
1755
+ }
1756
+ async function findProjectRoot$5(cwd) {
1757
+ let dir = cwd;
1758
+ while (dir !== dirname$1(dir)) {
1759
+ if (await fileExists$5(join$1(dir, "package.json"))) return dir;
1760
+ dir = dirname$1(dir);
1761
+ }
1762
+ return null;
1763
+ }
1764
+ async function loadConfig$5(projectRoot) {
1765
+ const configPath = join$1(projectRoot, KUCKIT_DIR$5, CONFIG_FILE$5);
1766
+ if (!await fileExists$5(configPath)) return null;
1767
+ try {
1768
+ const content = await readFile(configPath, "utf-8");
1769
+ return JSON.parse(content);
1770
+ } catch {
1771
+ return null;
1772
+ }
1773
+ }
1774
+ async function infraRepair(options) {
1775
+ console.log("Repairing kuckit infrastructure state...\n");
1776
+ if (!await checkPulumiInstalled()) {
1777
+ console.error("Error: Pulumi CLI is not installed.");
1778
+ console.error("Install it from: https://www.pulumi.com/docs/install/");
1779
+ process.exit(1);
1780
+ }
1781
+ const projectRoot = await findProjectRoot$5(process.cwd());
1782
+ if (!projectRoot) {
1783
+ console.error("Error: Could not find project root (no package.json found)");
1784
+ process.exit(1);
1785
+ }
1786
+ const config = await loadConfig$5(projectRoot);
1787
+ if (!config) {
1788
+ console.error("Error: No infrastructure configuration found.");
1789
+ console.error("Run: kuckit infra init");
1790
+ process.exit(1);
1791
+ }
1792
+ const env = options.env ?? config.env;
1793
+ if (env !== "dev" && env !== "prod") {
1794
+ console.error(`Error: Invalid environment '${env}'. Must be 'dev' or 'prod'.`);
1795
+ process.exit(1);
1796
+ }
1797
+ const infraDir = getInfraDir(projectRoot);
1798
+ if (!await fileExists$5(infraDir)) {
1799
+ console.error("Error: packages/infra not found.");
1800
+ process.exit(1);
1801
+ }
1802
+ const runCancel = options.cancel || !options.cancel && !options.refresh;
1803
+ const runRefresh = options.refresh || !options.cancel && !options.refresh;
1804
+ console.log(`Repairing stack: ${config.stackName}`);
1805
+ console.log(` Environment: ${env}`);
1806
+ console.log(` Operations: ${[runCancel && "cancel", runRefresh && "refresh"].filter(Boolean).join(", ")}`);
1807
+ console.log("");
1808
+ if (!options.force) {
1809
+ if (!await confirm({
1810
+ message: "Proceed with repair?",
1811
+ default: true
1812
+ })) {
1813
+ console.log("Aborted.");
1814
+ process.exit(0);
1815
+ }
1816
+ }
1817
+ console.log(`Selecting Pulumi stack: ${config.stackName}`);
1818
+ if (!await selectOrCreateStack(config.stackName, { cwd: infraDir })) {
1819
+ console.error("Error: Failed to select Pulumi stack");
1820
+ process.exit(1);
1821
+ }
1822
+ console.log("");
1823
+ if (!options.force) {
1824
+ const kuckitDir = join$1(projectRoot, KUCKIT_DIR$5);
1825
+ await mkdir(kuckitDir, { recursive: true });
1826
+ const backupFileName = `state-backup-${env}-${Math.floor(Date.now() / 1e3)}.json`;
1827
+ const backupPath = join$1(kuckitDir, backupFileName);
1828
+ console.log("Backing up state...");
1829
+ if ((await pulumiStackExport(backupPath, { cwd: infraDir })).code === 0) console.log(`State backed up to ${KUCKIT_DIR$5}/${backupFileName}\n`);
1830
+ else console.warn("Warning: Failed to backup state. Continuing anyway...\n");
1831
+ }
1832
+ if (runCancel) {
1833
+ console.log("Cancelling any stuck operations...");
1834
+ const cancelResult = await pulumiCancel({ cwd: infraDir });
1835
+ if (cancelResult.code === 0) if (cancelResult.stdout.includes("no update") || cancelResult.stderr.includes("no update")) console.log("No stuck operation to cancel\n");
1836
+ else console.log("✓ Cancelled stuck operation\n");
1837
+ else if (cancelResult.stderr.includes("no update") || cancelResult.stdout.includes("no update")) console.log("No stuck operation to cancel\n");
1838
+ else {
1839
+ console.error("Warning: Cancel operation failed");
1840
+ console.error(cancelResult.stderr || cancelResult.stdout);
1841
+ console.log("");
1842
+ }
1843
+ }
1844
+ if (runRefresh) {
1845
+ console.log("Refreshing state from cloud provider...");
1846
+ const refreshResult = await pulumiRefresh({ cwd: infraDir });
1847
+ if (refreshResult.code === 0) console.log("✓ State refreshed successfully\n");
1848
+ else {
1849
+ console.error("\nError: Refresh operation failed");
1850
+ console.error(refreshResult.stderr);
1851
+ process.exit(1);
1852
+ }
1853
+ }
1854
+ console.log("✓ Infrastructure state repair complete");
1855
+ }
1856
+
1857
+ //#endregion
1858
+ //#region src/commands/infra/db.ts
1859
+ const KUCKIT_DIR$4 = ".kuckit";
1860
+ const CONFIG_FILE$4 = "infra.json";
1861
+ async function fileExists$4(path) {
1862
+ try {
1863
+ await access(path, constants$1.F_OK);
1864
+ return true;
1865
+ } catch {
1866
+ return false;
1867
+ }
1868
+ }
1869
+ async function findProjectRoot$4(cwd) {
1870
+ let dir = cwd;
1871
+ while (dir !== dirname$1(dir)) {
1872
+ if (await fileExists$4(join$1(dir, "package.json"))) return dir;
1873
+ dir = dirname$1(dir);
1874
+ }
1875
+ return null;
1876
+ }
1877
+ async function loadConfig$4(projectRoot) {
1878
+ const configPath = join$1(projectRoot, KUCKIT_DIR$4, CONFIG_FILE$4);
1879
+ if (!await fileExists$4(configPath)) return null;
1880
+ try {
1881
+ const content = await readFile(configPath, "utf-8");
1882
+ return JSON.parse(content);
1883
+ } catch {
1884
+ return null;
1885
+ }
1886
+ }
1887
+ async function getJobContext(options) {
1888
+ if (!await checkGcloudInstalled()) {
1889
+ console.error("Error: gcloud CLI is not installed.");
1890
+ console.error("Install it from: https://cloud.google.com/sdk/docs/install");
1891
+ process.exit(1);
1892
+ }
1893
+ const projectRoot = await findProjectRoot$4(process.cwd());
1894
+ if (!projectRoot) {
1895
+ console.error("Error: Could not find project root (no package.json found)");
1896
+ process.exit(1);
1897
+ }
1898
+ const config = await loadConfig$4(projectRoot);
1899
+ if (!config) {
1900
+ console.error("Error: No infrastructure configuration found.");
1901
+ console.error("Run: kuckit infra init");
1902
+ process.exit(1);
1903
+ }
1904
+ const env = options.env ?? config.env;
1905
+ if (env !== "dev" && env !== "prod") {
1906
+ console.error(`Error: Invalid environment '${env}'. Must be 'dev' or 'prod'.`);
1907
+ process.exit(1);
1908
+ }
1909
+ const jobName = config.outputs?.migrationJobName;
1910
+ if (!jobName) {
1911
+ console.error("Error: No migration job found in configuration.");
1912
+ console.error("Make sure you have deployed the infrastructure with an image:");
1913
+ console.error(" kuckit infra deploy");
1914
+ process.exit(1);
1915
+ }
1916
+ return {
1917
+ config,
1918
+ projectRoot,
1919
+ env,
1920
+ jobName
1921
+ };
1922
+ }
1923
+ async function executeCloudRunJob(project, region, jobName, commandOverride) {
1924
+ const args = [
1925
+ "run",
1926
+ "jobs",
1927
+ "execute",
1928
+ jobName,
1929
+ "--region",
1930
+ region,
1931
+ "--project",
1932
+ project,
1933
+ "--wait"
1934
+ ];
1935
+ if (commandOverride && commandOverride.length > 0) {
1936
+ const escapedArgs = commandOverride.map((arg) => arg.replace(/'/g, "'\\''")).join(",");
1937
+ args.push(`--args='${escapedArgs}'`);
1938
+ }
1939
+ console.log(`Executing Cloud Run Job: ${jobName}`);
1940
+ console.log(` Project: ${project}`);
1941
+ console.log(` Region: ${region}`);
1942
+ console.log("");
1943
+ return (await runGcloud(args, { stream: true })).code === 0;
1944
+ }
1945
+ async function getLatestJobExecution(project, region, jobName) {
1946
+ const listResult = await runGcloud([
1947
+ "run",
1948
+ "jobs",
1949
+ "executions",
1950
+ "list",
1951
+ "--job",
1952
+ jobName,
1953
+ "--region",
1954
+ region,
1955
+ "--project",
1956
+ project,
1957
+ "--limit",
1958
+ "1",
1959
+ "--format",
1960
+ "json"
1961
+ ], { stream: false });
1962
+ if (listResult.code !== 0 || !listResult.stdout.trim()) return null;
1963
+ try {
1964
+ const executions = JSON.parse(listResult.stdout);
1965
+ if (executions.length === 0) return null;
1966
+ const execution = executions[0];
1967
+ const executionName = execution.metadata?.name;
1968
+ if (executionName) {
1969
+ const logsResult = await runGcloud([
1970
+ "logging",
1971
+ "read",
1972
+ `resource.type="cloud_run_job" AND resource.labels.job_name="${jobName}" AND labels."run.googleapis.com/execution_name"="${executionName}"`,
1973
+ "--project",
1974
+ project,
1975
+ "--limit",
1976
+ "50",
1977
+ "--format",
1978
+ "value(textPayload)"
1979
+ ], { stream: false });
1980
+ return {
1981
+ status: execution.status?.conditions?.[0]?.type ?? "Unknown",
1982
+ logs: logsResult.stdout
1983
+ };
1984
+ }
1985
+ return { status: execution.status?.conditions?.[0]?.type ?? "Unknown" };
1986
+ } catch {
1987
+ return null;
1988
+ }
1989
+ }
1990
+ async function infraDbPush(options) {
1991
+ console.log("Running database schema push via Cloud Run Job...\n");
1992
+ const ctx = await getJobContext(options);
1993
+ console.log("Configuration:");
1994
+ console.log(` Project: ${ctx.config.gcpProject}`);
1995
+ console.log(` Environment: ${ctx.env}`);
1996
+ console.log(` Job: ${ctx.jobName}`);
1997
+ console.log("");
1998
+ 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" : ""}`];
1999
+ if (!await executeCloudRunJob(ctx.config.gcpProject, ctx.config.region, ctx.jobName, commandOverride)) {
2000
+ console.error("\nError: Schema push failed.");
2001
+ console.error("Check the Cloud Run Job logs for details:");
2002
+ console.error(` gcloud run jobs executions list --job ${ctx.jobName} --region ${ctx.config.region}`);
2003
+ process.exit(1);
2004
+ }
2005
+ const execution = await getLatestJobExecution(ctx.config.gcpProject, ctx.config.region, ctx.jobName);
2006
+ if (execution?.logs) {
2007
+ console.log("\nJob output:");
2008
+ console.log(execution.logs);
2009
+ }
2010
+ console.log("\n✓ Schema push complete");
2011
+ }
2012
+ async function infraDbMigrate(options) {
2013
+ console.log("Running database migrations via Cloud Run Job...\n");
2014
+ if (options.dryRun) {
2015
+ console.log("Note: Dry run mode is not supported for Cloud Run Jobs.");
2016
+ console.log("The job will execute actual migrations.\n");
2017
+ }
2018
+ if (options.rollback) {
2019
+ console.error("Error: Rollback is not supported via Cloud Run Jobs.");
2020
+ console.error("Use local drizzle-kit for rollback operations.");
2021
+ process.exit(1);
2022
+ }
2023
+ const ctx = await getJobContext(options);
2024
+ console.log("Configuration:");
2025
+ console.log(` Project: ${ctx.config.gcpProject}`);
2026
+ console.log(` Environment: ${ctx.env}`);
2027
+ console.log(` Job: ${ctx.jobName}`);
2028
+ console.log("");
2029
+ if (!await executeCloudRunJob(ctx.config.gcpProject, ctx.config.region, ctx.jobName)) {
2030
+ console.error("\nError: Migration failed.");
2031
+ console.error("Check the Cloud Run Job logs for details:");
2032
+ console.error(` gcloud run jobs executions list --job ${ctx.jobName} --region ${ctx.config.region}`);
2033
+ process.exit(1);
2034
+ }
2035
+ const execution = await getLatestJobExecution(ctx.config.gcpProject, ctx.config.region, ctx.jobName);
2036
+ if (execution?.logs) {
2037
+ console.log("\nJob output:");
2038
+ console.log(execution.logs);
2039
+ }
2040
+ console.log("\n✓ Migrations complete");
2041
+ }
2042
+
2043
+ //#endregion
2044
+ //#region src/commands/infra/rollback.ts
2045
+ const KUCKIT_DIR$3 = ".kuckit";
2046
+ const CONFIG_FILE$3 = "infra.json";
2047
+ async function fileExists$3(path) {
2048
+ try {
2049
+ await access(path, constants$1.F_OK);
2050
+ return true;
2051
+ } catch {
2052
+ return false;
2053
+ }
2054
+ }
2055
+ async function findProjectRoot$3(cwd) {
2056
+ let dir = cwd;
2057
+ while (dir !== dirname$1(dir)) {
2058
+ if (await fileExists$3(join$1(dir, "package.json"))) return dir;
2059
+ dir = dirname$1(dir);
2060
+ }
2061
+ return null;
2062
+ }
2063
+ async function loadConfig$3(projectRoot) {
2064
+ const configPath = join$1(projectRoot, KUCKIT_DIR$3, CONFIG_FILE$3);
2065
+ if (!await fileExists$3(configPath)) return null;
2066
+ try {
2067
+ const content = await readFile(configPath, "utf-8");
2068
+ return JSON.parse(content);
2069
+ } catch {
2070
+ return null;
2071
+ }
2072
+ }
2073
+ function formatRevisionDate(isoDate) {
2074
+ try {
2075
+ return new Date(isoDate).toLocaleString("en-US", {
2076
+ year: "numeric",
2077
+ month: "2-digit",
2078
+ day: "2-digit",
2079
+ hour: "2-digit",
2080
+ minute: "2-digit"
2081
+ });
2082
+ } catch {
2083
+ return isoDate;
2084
+ }
2085
+ }
2086
+ function displayRevisions(revisions) {
2087
+ console.log("Available revisions:");
2088
+ for (const rev of revisions) {
2089
+ const marker = rev.active ? " (current)" : "";
2090
+ const traffic = rev.trafficPercent > 0 ? ` [${rev.trafficPercent}%]` : "";
2091
+ console.log(` ${rev.name}${marker}${traffic} - ${formatRevisionDate(rev.createTime)}`);
2092
+ }
2093
+ console.log("");
2094
+ }
2095
+ async function infraRollback(options) {
2096
+ console.log("Rolling back Cloud Run service...\n");
2097
+ if (!await checkGcloudInstalled()) {
2098
+ console.error("Error: gcloud CLI is not installed.");
2099
+ console.error("Install it from: https://cloud.google.com/sdk/docs/install");
2100
+ process.exit(1);
2101
+ }
2102
+ const projectRoot = await findProjectRoot$3(process.cwd());
2103
+ if (!projectRoot) {
2104
+ console.error("Error: Could not find project root (no package.json found)");
2105
+ process.exit(1);
2106
+ }
2107
+ const config = await loadConfig$3(projectRoot);
2108
+ if (!config) {
2109
+ console.error("Error: No infrastructure configuration found.");
2110
+ console.error("Run: kuckit infra init");
2111
+ process.exit(1);
2112
+ }
2113
+ const env = options.env ?? config.env;
2114
+ if (env !== "dev" && env !== "prod") {
2115
+ console.error(`Error: Invalid environment '${env}'. Must be 'dev' or 'prod'.`);
2116
+ process.exit(1);
2117
+ }
2118
+ const serviceUrl = config.outputs?.serviceUrl;
2119
+ if (!serviceUrl) {
2120
+ console.error("Error: No Cloud Run service deployed yet.");
2121
+ console.error("Run: kuckit infra deploy");
2122
+ process.exit(1);
2123
+ }
2124
+ const serviceName = extractServiceNameFromUrl(serviceUrl);
2125
+ if (!serviceName) {
2126
+ console.error("Error: Could not determine service name from URL:", serviceUrl);
2127
+ process.exit(1);
2128
+ }
2129
+ console.log(`Service: ${serviceName}`);
2130
+ console.log(`Project: ${config.gcpProject}`);
2131
+ console.log(`Region: ${config.region}`);
2132
+ console.log("");
2133
+ console.log("Fetching revisions...");
2134
+ const revisions = await listCloudRunRevisions(serviceName, config.gcpProject, config.region);
2135
+ if (revisions.length === 0) {
2136
+ console.error("Error: No revisions found for service:", serviceName);
2137
+ process.exit(1);
2138
+ }
2139
+ revisions.sort((a, b) => new Date(b.createTime).getTime() - new Date(a.createTime).getTime());
2140
+ displayRevisions(revisions);
2141
+ let targetRevision;
2142
+ if (options.revision) {
2143
+ if (!revisions.find((r) => r.name === options.revision)) {
2144
+ console.error(`Error: Revision '${options.revision}' not found.`);
2145
+ console.error("Available revisions:", revisions.map((r) => r.name).join(", "));
2146
+ process.exit(1);
2147
+ }
2148
+ targetRevision = options.revision;
2149
+ } else if (options.previous) {
2150
+ if (revisions.length < 2) {
2151
+ console.error("Error: No previous revision available.");
2152
+ console.error("Only one revision exists:", revisions[0]?.name);
2153
+ process.exit(1);
2154
+ }
2155
+ const previousRev = revisions.find((r) => !r.active);
2156
+ if (!previousRev) targetRevision = revisions[1]?.name;
2157
+ else targetRevision = previousRev.name;
2158
+ } else {
2159
+ const choices = revisions.filter((r) => !r.active || r.trafficPercent < 100).map((r) => ({
2160
+ name: `${r.name} - ${formatRevisionDate(r.createTime)}${r.active ? " (partial traffic)" : ""}`,
2161
+ value: r.name
2162
+ }));
2163
+ if (choices.length === 0) {
2164
+ console.log("No other revisions available to rollback to.");
2165
+ process.exit(0);
2166
+ }
2167
+ targetRevision = await select({
2168
+ message: "Select revision to rollback to:",
2169
+ choices
2170
+ });
2171
+ }
2172
+ if (!targetRevision) {
2173
+ console.error("Error: Could not determine target revision.");
2174
+ process.exit(1);
2175
+ }
2176
+ if (revisions.find((r) => r.name === targetRevision)?.trafficPercent === 100) {
2177
+ console.log(`Already routing 100% traffic to ${targetRevision}`);
2178
+ process.exit(0);
2179
+ }
2180
+ console.log(`Rolling back to ${targetRevision}...`);
2181
+ const result = await updateCloudRunTraffic(serviceName, targetRevision, config.gcpProject, config.region);
2182
+ if (result.code !== 0) {
2183
+ console.error("\nError: Failed to update traffic routing");
2184
+ console.error(result.stderr);
2185
+ process.exit(1);
2186
+ }
2187
+ console.log(`\n✓ Traffic routed to ${targetRevision}`);
2188
+ }
2189
+
2190
+ //#endregion
2191
+ //#region src/commands/infra/logs.ts
2192
+ const KUCKIT_DIR$2 = ".kuckit";
2193
+ const CONFIG_FILE$2 = "infra.json";
2194
+ async function fileExists$2(path) {
2195
+ try {
2196
+ await access(path, constants$1.F_OK);
2197
+ return true;
2198
+ } catch {
2199
+ return false;
2200
+ }
2201
+ }
2202
+ async function findProjectRoot$2(cwd) {
2203
+ let dir = cwd;
2204
+ while (dir !== dirname$1(dir)) {
2205
+ if (await fileExists$2(join$1(dir, "package.json"))) return dir;
2206
+ dir = dirname$1(dir);
2207
+ }
2208
+ return null;
2209
+ }
2210
+ async function loadConfig$2(projectRoot) {
2211
+ const configPath = join$1(projectRoot, KUCKIT_DIR$2, CONFIG_FILE$2);
2212
+ if (!await fileExists$2(configPath)) return null;
2213
+ try {
2214
+ const content = await readFile(configPath, "utf-8");
2215
+ return JSON.parse(content);
2216
+ } catch {
2217
+ return null;
2218
+ }
2219
+ }
2220
+ function runGcloudStreaming(args) {
2221
+ return new Promise((resolve) => {
2222
+ spawn("gcloud", args, {
2223
+ stdio: "inherit",
2224
+ shell: true
2225
+ }).on("close", (code) => {
2226
+ resolve(code ?? 1);
2227
+ });
2228
+ });
2229
+ }
2230
+ async function infraLogs(options) {
2231
+ if (!await checkGcloudInstalled()) {
2232
+ console.error("Error: gcloud CLI is not installed.");
2233
+ console.error("Install it from: https://cloud.google.com/sdk/docs/install");
2234
+ process.exit(1);
2235
+ }
2236
+ const projectRoot = await findProjectRoot$2(process.cwd());
2237
+ if (!projectRoot) {
2238
+ console.error("Error: Could not find project root (no package.json found)");
2239
+ process.exit(1);
2240
+ }
2241
+ const config = await loadConfig$2(projectRoot);
2242
+ if (!config) {
2243
+ console.error("Error: No infrastructure configuration found.");
2244
+ console.error("Run: kuckit infra init");
2245
+ process.exit(1);
2246
+ }
2247
+ const env = options.env ?? config.env;
2248
+ if (env !== "dev" && env !== "prod") {
2249
+ console.error(`Error: Invalid environment '${env}'. Must be 'dev' or 'prod'.`);
2250
+ process.exit(1);
2251
+ }
2252
+ const serviceUrl = config.outputs?.serviceUrl;
2253
+ if (!serviceUrl) {
2254
+ console.error("Error: No Cloud Run service URL found.");
2255
+ console.error("The service may not be deployed yet. Run: kuckit infra deploy");
2256
+ process.exit(1);
2257
+ }
2258
+ const serviceName = extractServiceNameFromUrl(serviceUrl);
2259
+ if (!serviceName) {
2260
+ console.error("Error: Could not extract service name from URL.");
2261
+ console.error(`Service URL: ${serviceUrl}`);
2262
+ process.exit(1);
2263
+ }
2264
+ const since = options.since ?? "1h";
2265
+ console.log(`Fetching logs for service: ${serviceName}`);
2266
+ console.log(` Project: ${config.gcpProject}`);
2267
+ console.log(` Region: ${config.region}`);
2268
+ if (options.follow) console.log(" Mode: Following (Ctrl+C to stop)");
2269
+ else console.log(` Since: ${since}`);
2270
+ if (options.severity) console.log(` Severity: >= ${options.severity}`);
2271
+ console.log("");
2272
+ const args = [
2273
+ "run",
2274
+ "services",
2275
+ "logs"
2276
+ ];
2277
+ if (options.follow) args.push("tail");
2278
+ else args.push("read");
2279
+ args.push(serviceName);
2280
+ args.push("--project", config.gcpProject);
2281
+ args.push("--region", config.region);
2282
+ if (!options.follow) {
2283
+ args.push("--limit", "100");
2284
+ args.push("--freshness", since);
2285
+ }
2286
+ if (options.severity) {
2287
+ const severity = options.severity.toUpperCase();
2288
+ const validSeverities = [
2289
+ "DEBUG",
2290
+ "INFO",
2291
+ "WARNING",
2292
+ "ERROR",
2293
+ "CRITICAL"
2294
+ ];
2295
+ if (!validSeverities.includes(severity)) {
2296
+ console.error(`Error: Invalid severity '${severity}'.`);
2297
+ console.error(`Valid values: ${validSeverities.join(", ")}`);
2298
+ process.exit(1);
2299
+ }
2300
+ args.push("--log-filter", `severity>=${severity}`);
2301
+ }
2302
+ if (await runGcloudStreaming(args) !== 0) {
2303
+ console.error("\nError: Failed to fetch logs.");
2304
+ process.exit(1);
2305
+ }
2306
+ }
2307
+
2308
+ //#endregion
2309
+ //#region src/commands/infra/status.ts
2310
+ const KUCKIT_DIR$1 = ".kuckit";
2311
+ const CONFIG_FILE$1 = "infra.json";
2312
+ async function fileExists$1(path) {
2313
+ try {
2314
+ await access(path, constants$1.F_OK);
2315
+ return true;
2316
+ } catch {
2317
+ return false;
2318
+ }
2319
+ }
2320
+ async function findProjectRoot$1(cwd) {
2321
+ let dir = cwd;
2322
+ while (dir !== dirname$1(dir)) {
2323
+ if (await fileExists$1(join$1(dir, "package.json"))) return dir;
2324
+ dir = dirname$1(dir);
2325
+ }
2326
+ return null;
2327
+ }
2328
+ async function loadConfig$1(projectRoot) {
2329
+ const configPath = join$1(projectRoot, KUCKIT_DIR$1, CONFIG_FILE$1);
2330
+ if (!await fileExists$1(configPath)) return null;
2331
+ try {
2332
+ const content = await readFile(configPath, "utf-8");
2333
+ return JSON.parse(content);
2334
+ } catch {
2335
+ return null;
2336
+ }
2337
+ }
2338
+ function formatDate(dateStr) {
2339
+ try {
2340
+ return new Date(dateStr).toLocaleString();
2341
+ } catch {
2342
+ return dateStr;
2343
+ }
2344
+ }
2345
+ async function infraStatus(options) {
2346
+ const projectRoot = await findProjectRoot$1(process.cwd());
2347
+ if (!projectRoot) {
2348
+ console.error("Error: Could not find project root (no package.json found)");
2349
+ process.exit(1);
2350
+ }
2351
+ const config = await loadConfig$1(projectRoot);
2352
+ if (!config) {
2353
+ console.error("Error: No infrastructure configuration found.");
2354
+ console.error("Run: kuckit infra init");
2355
+ process.exit(1);
2356
+ }
2357
+ const env = options.env ?? config.env ?? "dev";
2358
+ const stackName = `${config.gcpProject}-${env}`;
2359
+ const infraDir = getInfraDir(projectRoot);
2360
+ if (!await fileExists$1(infraDir)) {
2361
+ console.error("Error: packages/infra not found.");
2362
+ process.exit(1);
2363
+ }
2364
+ if (!await selectOrCreateStack(stackName, { cwd: infraDir })) {
2365
+ console.error(`Error: Could not select stack '${stackName}'`);
2366
+ process.exit(1);
2367
+ }
2368
+ const stackInfo = await getPulumiStackInfo({ cwd: infraDir });
2369
+ const history = await getPulumiStackHistory({ cwd: infraDir }, 1);
2370
+ const outputs = await getPulumiOutputs({ cwd: infraDir });
2371
+ let status = "unknown";
2372
+ if (stackInfo?.updateInProgress) status = "updating";
2373
+ else if (stackInfo) status = "active";
2374
+ const resources = [];
2375
+ if (outputs) {
2376
+ if (outputs.registryUrl) resources.push({
2377
+ name: "Artifact Registry",
2378
+ healthy: true
2379
+ });
2380
+ if (outputs.databaseConnectionName) resources.push({
2381
+ name: "Cloud SQL",
2382
+ healthy: true
2383
+ });
2384
+ if (outputs.redisHost) resources.push({
2385
+ name: "Memorystore Redis",
2386
+ healthy: true
2387
+ });
2388
+ if (outputs.serviceUrl) resources.push({
2389
+ name: "Cloud Run Service",
2390
+ healthy: true
2391
+ });
2392
+ }
2393
+ const lastUpdate = history[0]?.endTime ?? stackInfo?.lastUpdate ?? null;
2394
+ const result = {
2395
+ stack: stackName,
2396
+ status,
2397
+ lastUpdate,
2398
+ resourceCount: stackInfo?.resourceCount ?? 0,
2399
+ resources,
2400
+ outputs
2401
+ };
2402
+ if (options.json) {
2403
+ console.log(JSON.stringify(result, null, 2));
2404
+ return;
2405
+ }
2406
+ console.log("");
2407
+ console.log(`Stack: ${result.stack}`);
2408
+ console.log(`Status: ${result.status}`);
2409
+ if (result.lastUpdate) console.log(`Last updated: ${formatDate(result.lastUpdate)}`);
2410
+ console.log(`Resources: ${result.resourceCount}`);
2411
+ console.log("");
2412
+ if (resources.length > 0) {
2413
+ console.log("Infrastructure:");
2414
+ for (const resource of resources) {
2415
+ const icon = resource.healthy ? "✓" : "✗";
2416
+ console.log(` ${icon} ${resource.name}`);
2417
+ }
2418
+ console.log("");
2419
+ } else {
2420
+ console.log("No resources deployed yet.");
2421
+ console.log("Run: kuckit infra deploy");
2422
+ console.log("");
2423
+ }
2424
+ }
2425
+
2426
+ //#endregion
2427
+ //#region src/commands/infra/outputs.ts
2428
+ const KUCKIT_DIR = ".kuckit";
2429
+ const CONFIG_FILE = "infra.json";
2430
+ async function fileExists(path) {
2431
+ try {
2432
+ await access(path, constants$1.F_OK);
2433
+ return true;
2434
+ } catch {
2435
+ return false;
2436
+ }
2437
+ }
2438
+ async function findProjectRoot(cwd) {
2439
+ let dir = cwd;
2440
+ while (dir !== dirname$1(dir)) {
2441
+ if (await fileExists(join$1(dir, "package.json"))) return dir;
2442
+ dir = dirname$1(dir);
2443
+ }
2444
+ return null;
2445
+ }
2446
+ async function loadConfig(projectRoot) {
2447
+ const configPath = join$1(projectRoot, KUCKIT_DIR, CONFIG_FILE);
2448
+ if (!await fileExists(configPath)) return null;
2449
+ try {
2450
+ const content = await readFile(configPath, "utf-8");
2451
+ return JSON.parse(content);
2452
+ } catch {
2453
+ return null;
2454
+ }
2455
+ }
2456
+ async function infraOutputs(options) {
2457
+ const projectRoot = await findProjectRoot(process.cwd());
2458
+ if (!projectRoot) {
2459
+ console.error("Error: Could not find project root (no package.json found)");
2460
+ process.exit(1);
2461
+ }
2462
+ const config = await loadConfig(projectRoot);
2463
+ if (!config) {
2464
+ console.error("Error: No infrastructure configuration found.");
2465
+ console.error("Run: kuckit infra init");
2466
+ process.exit(1);
2467
+ }
2468
+ const env = options.env ?? config.env ?? "dev";
2469
+ const stackName = `${config.gcpProject}-${env}`;
2470
+ const infraDir = getInfraDir(projectRoot);
2471
+ if (!await fileExists(infraDir)) {
2472
+ console.error("Error: packages/infra not found.");
2473
+ process.exit(1);
2474
+ }
2475
+ if (!await selectOrCreateStack(stackName, { cwd: infraDir })) {
2476
+ console.error(`Error: Could not select stack '${stackName}'`);
2477
+ process.exit(1);
2478
+ }
2479
+ const outputs = await getPulumiOutputs({ cwd: infraDir });
2480
+ if (!outputs) {
2481
+ console.error("Error: Could not retrieve stack outputs.");
2482
+ console.error("The stack may not have been deployed yet.");
2483
+ console.error("Run: kuckit infra deploy");
2484
+ process.exit(1);
2485
+ }
2486
+ if (options.json) {
2487
+ console.log(JSON.stringify(outputs, null, 2));
2488
+ return;
2489
+ }
2490
+ console.log("");
2491
+ console.log(`Environment: ${env}`);
2492
+ console.log("");
2493
+ if (outputs.serviceUrl) console.log(`Service URL: ${outputs.serviceUrl}`);
2494
+ if (outputs.registryUrl) console.log(`Registry URL: ${outputs.registryUrl}`);
2495
+ if (outputs.databaseConnectionName) console.log(`Database: ${outputs.databaseConnectionName}`);
2496
+ if (outputs.redisHost) console.log(`Redis Host: ${outputs.redisHost}`);
2497
+ const knownKeys = [
2498
+ "serviceUrl",
2499
+ "registryUrl",
2500
+ "databaseConnectionName",
2501
+ "redisHost"
2502
+ ];
2503
+ const additionalKeys = Object.keys(outputs).filter((k) => !knownKeys.includes(k));
2504
+ if (additionalKeys.length > 0) {
2505
+ console.log("");
2506
+ console.log("Additional outputs:");
2507
+ for (const key of additionalKeys) {
2508
+ const value = outputs[key];
2509
+ if (typeof value === "object") console.log(` ${key}: ${JSON.stringify(value)}`);
2510
+ else console.log(` ${key}: ${value}`);
2511
+ }
2512
+ }
2513
+ console.log("");
2514
+ }
2515
+
2516
+ //#endregion
2517
+ //#region src/bin.ts
2518
+ program.name("kuckit").description("CLI tools for Kuckit SDK module development").version("0.1.0");
2519
+ 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) => {
2520
+ requireAuth();
2521
+ await generateModule(name, options);
2522
+ });
2523
+ program.command("add <package>").description("Install and wire a Kuckit module").option("--skip-install", "Skip package installation", false).action(async (packageName, options) => {
2524
+ requireAuth();
2525
+ await addModule(packageName, options);
2526
+ });
2527
+ program.command("discover").description("Scan node_modules for installed Kuckit modules").option("--json", "Output as JSON (non-interactive)", false).option("--no-interactive", "Disable interactive prompts").action(async (options) => {
2528
+ requireAuth();
2529
+ await discoverModules(options);
2530
+ });
2531
+ program.command("doctor").description("Check Kuckit setup and validate module configuration").option("--json", "Output as JSON", false).action(async (options) => {
2532
+ await doctor(options);
2533
+ });
2534
+ program.command("search <keyword>").description("Search npm registry for Kuckit modules").option("--json", "Output as JSON", false).option("-l, --limit <number>", "Maximum results to return", "10").action(async (keyword, options) => {
2535
+ await search(keyword, {
2536
+ json: options.json,
2537
+ limit: parseInt(options.limit, 10)
2538
+ });
2539
+ });
2540
+ const db = program.command("db").description("Database schema management for module-owned schemas");
2541
+ db.command("push").description("Push all module schemas to database").option("--url <url>", "Database connection URL").action(async (options) => {
2542
+ requireAuth();
2543
+ await dbPush(options);
2544
+ });
2545
+ db.command("generate").description("Generate migration files for module schemas").option("--url <url>", "Database connection URL").action(async (options) => {
2546
+ requireAuth();
2547
+ await dbGenerate(options);
2548
+ });
2549
+ db.command("studio").description("Open Drizzle Studio with all module schemas").option("--url <url>", "Database connection URL").action(async (options) => {
2550
+ requireAuth();
2551
+ await dbStudio(options);
2552
+ });
2553
+ registerAuthCommands(program);
2554
+ const infra = program.command("infra").description("Infrastructure deployment and management");
2555
+ infra.command("init").description("Initialize base infrastructure (no Docker required)").option("-p, --provider <provider>", "Cloud provider (gcp)", "gcp").option("--project <id>", "GCP project ID").option("--region <region>", "Deployment region", "us-central1").option("-e, --env <env>", "Environment (dev, prod)", "dev").option("-y, --yes", "Skip confirmation prompts", false).action(async (options) => {
2556
+ requireAuth();
2557
+ await infraInit(options);
2558
+ });
2559
+ infra.command("deploy").description("Build and deploy application to Cloud Run").option("-e, --env <env>", "Environment (dev, prod)", "dev").option("--preview", "Preview changes without applying", false).option("--skip-build", "Use existing image (skip Docker build)", false).option("--image <url>", "Use specific image URL").option("-y, --yes", "Skip confirmation prompts", false).action(async (options) => {
2560
+ requireAuth();
2561
+ await infraDeploy(options);
2562
+ });
2563
+ infra.command("destroy").description("Destroy infrastructure resources").option("-e, --env <env>", "Environment (dev, prod)", "dev").option("--app-only", "Only destroy Cloud Run, keep DB/Redis", false).option("--force", "Skip confirmation prompt", false).action(async (options) => {
2564
+ requireAuth();
2565
+ await infraDestroy(options);
2566
+ });
2567
+ infra.command("repair").description("Repair infrastructure state issues").option("-e, --env <env>", "Environment (dev, prod)", "dev").option("--refresh", "Refresh state from cloud provider", false).option("--cancel", "Cancel any stuck operations", false).option("--force", "Force repair without confirmation", false).action(async (options) => {
2568
+ requireAuth();
2569
+ await infraRepair(options);
2570
+ });
2571
+ infra.command("db:push").description("Push schema directly to Cloud SQL (dev/fresh databases)").option("-e, --env <env>", "Environment (dev, prod)", "dev").option("--force", "Skip confirmation for destructive changes", false).option("--public-ip", "Use public IP (default: private IP, requires VPC access)", false).action(async (options) => {
2572
+ requireAuth();
2573
+ await infraDbPush(options);
2574
+ });
2575
+ infra.command("db:migrate").description("Run database migrations via Cloud SQL Auth Proxy").option("-e, --env <env>", "Environment (dev, prod)", "dev").option("--dry-run", "Show migrations without applying", false).option("--rollback", "Rollback the last migration", false).option("--public-ip", "Use public IP (default: private IP, requires VPC access)", false).action(async (options) => {
2576
+ requireAuth();
2577
+ await infraDbMigrate(options);
2578
+ });
2579
+ infra.command("rollback").description("Rollback Cloud Run service to a previous revision").option("-e, --env <env>", "Environment (dev, prod)", "dev").option("--revision <name>", "Specific revision name to rollback to").option("--previous", "Rollback to the previous revision", false).action(async (options) => {
2580
+ requireAuth();
2581
+ await infraRollback(options);
2582
+ });
2583
+ infra.command("logs").description("View Cloud Run service logs").option("-e, --env <env>", "Environment (dev, prod)", "dev").option("-f, --follow", "Stream logs in real-time", false).option("--since <duration>", "Show logs since duration (e.g., 1h, 30m)", "1h").option("--severity <level>", "Filter by severity (DEBUG, INFO, WARNING, ERROR)").action(async (options) => {
2584
+ requireAuth();
2585
+ await infraLogs(options);
2586
+ });
2587
+ infra.command("status").description("Show current infrastructure state").option("-e, --env <env>", "Environment (dev, prod)", "dev").option("--json", "Output as JSON", false).action(async (options) => {
2588
+ requireAuth();
2589
+ await infraStatus(options);
2590
+ });
2591
+ infra.command("outputs").description("Display infrastructure outputs (URLs, connection strings)").option("-e, --env <env>", "Environment (dev, prod)", "dev").option("--json", "Output as JSON", false).action(async (options) => {
2592
+ requireAuth();
2593
+ await infraOutputs(options);
2594
+ });
781
2595
  program.parse();
782
2596
 
783
2597
  //#endregion