@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-
|
|
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("//"))
|
|
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("//"))
|
|
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://
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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: ${
|
|
2428
|
+
console.log(` Project: ${projectDisplay}`);
|
|
2120
2429
|
console.log(` Region: ${config.region}`);
|
|
2121
2430
|
console.log(` Environment: ${env}`);
|
|
2122
|
-
console.log(` Stack: ${
|
|
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
|
|
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: ${
|
|
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: ${
|
|
2287
|
-
if (!await selectOrCreateStack(
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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: ${
|
|
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,
|
|
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,
|
|
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: ${
|
|
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",
|
|
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
|
|
2827
|
-
const
|
|
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 =
|
|
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
|