@kuckit/cli 1.0.1 → 2.0.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 +847 -411
- package/dist/bin.js.map +1 -0
- package/dist/index.d.ts +476 -1
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -2
- package/dist/provider-DbqTBb6C.js +1084 -0
- package/dist/provider-DbqTBb6C.js.map +1 -0
- package/package.json +3 -2
- package/dist/discover-module-B7rxN4vq.js +0 -595
package/dist/bin.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { a as isLegacyConfig, c as loadTryLoadKuckitConfig, i as isGcpConfig, l as addModule, o as migrateLegacyConfig, s as discoverModules, u as generateModule } from "./provider-DbqTBb6C.js";
|
|
3
3
|
import { program } from "commander";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
|
-
import { chmodSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
6
|
-
import {
|
|
5
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { spawn } from "child_process";
|
|
7
7
|
import { access, constants as constants$1, mkdir, readFile, unlink, writeFile } from "fs/promises";
|
|
8
8
|
import { dirname as dirname$1, join as join$1 } from "path";
|
|
9
9
|
import { homedir } from "node:os";
|
|
10
10
|
import { confirm, input, select } from "@inquirer/prompts";
|
|
11
|
+
import { accessSync as accessSync$1, constants as constants$2 } from "fs";
|
|
11
12
|
|
|
12
13
|
//#region src/commands/doctor.ts
|
|
13
14
|
const CONFIG_FILES = [
|
|
@@ -15,6 +16,15 @@ const CONFIG_FILES = [
|
|
|
15
16
|
"kuckit.config.js",
|
|
16
17
|
"kuckit.config.mjs"
|
|
17
18
|
];
|
|
19
|
+
const FRAMEWORK_PACKAGES = [
|
|
20
|
+
"@kuckit/sdk",
|
|
21
|
+
"@kuckit/sdk-react",
|
|
22
|
+
"@kuckit/api",
|
|
23
|
+
"@kuckit/db",
|
|
24
|
+
"@kuckit/auth",
|
|
25
|
+
"@kuckit/domain",
|
|
26
|
+
"@kuckit/contracts"
|
|
27
|
+
];
|
|
18
28
|
function findConfigFile(cwd) {
|
|
19
29
|
let dir = cwd;
|
|
20
30
|
while (dir !== dirname(dir)) {
|
|
@@ -58,6 +68,18 @@ function findPackagePath(packageName, cwd) {
|
|
|
58
68
|
}
|
|
59
69
|
return null;
|
|
60
70
|
}
|
|
71
|
+
function scanSourceFiles(dir) {
|
|
72
|
+
const files = [];
|
|
73
|
+
try {
|
|
74
|
+
const entries = readdirSync(dir);
|
|
75
|
+
for (const entry of entries) {
|
|
76
|
+
const fullPath = join(dir, entry);
|
|
77
|
+
if (statSync(fullPath).isDirectory()) files.push(...scanSourceFiles(fullPath));
|
|
78
|
+
else if (entry.endsWith(".ts") || entry.endsWith(".tsx")) files.push(fullPath);
|
|
79
|
+
}
|
|
80
|
+
} catch {}
|
|
81
|
+
return files;
|
|
82
|
+
}
|
|
61
83
|
function checkModuleExports(packageName, cwd) {
|
|
62
84
|
const result = {
|
|
63
85
|
server: false,
|
|
@@ -78,7 +100,7 @@ function checkModuleExports(packageName, cwd) {
|
|
|
78
100
|
].some((p) => existsSync(p));
|
|
79
101
|
return result;
|
|
80
102
|
}
|
|
81
|
-
async function loadConfig$
|
|
103
|
+
async function loadConfig$8(configPath) {
|
|
82
104
|
try {
|
|
83
105
|
if (configPath.endsWith(".ts")) {
|
|
84
106
|
const { createJiti } = await import("jiti");
|
|
@@ -118,7 +140,7 @@ async function doctor(options) {
|
|
|
118
140
|
});
|
|
119
141
|
let configModules = [];
|
|
120
142
|
if (configPath) {
|
|
121
|
-
const { success, config, error } = await loadConfig$
|
|
143
|
+
const { success, config, error } = await loadConfig$8(configPath);
|
|
122
144
|
if (success && config && typeof config === "object") {
|
|
123
145
|
const cfg = config;
|
|
124
146
|
if (Array.isArray(cfg.modules)) {
|
|
@@ -211,18 +233,75 @@ async function doctor(options) {
|
|
|
211
233
|
status: "pass",
|
|
212
234
|
message: "Module exports valid"
|
|
213
235
|
});
|
|
214
|
-
const
|
|
215
|
-
const
|
|
216
|
-
for (const
|
|
217
|
-
|
|
218
|
-
|
|
236
|
+
const missingFramework = [];
|
|
237
|
+
const installedFramework = [];
|
|
238
|
+
for (const pkg of FRAMEWORK_PACKAGES) {
|
|
239
|
+
const pkgJson = readPackageJson(pkg, cwd);
|
|
240
|
+
if (!pkgJson) missingFramework.push(pkg);
|
|
241
|
+
else installedFramework.push({
|
|
242
|
+
name: pkg,
|
|
243
|
+
version: pkgJson.version
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
if (missingFramework.length > 0) checks.push({
|
|
247
|
+
name: "framework-installed",
|
|
219
248
|
status: "warn",
|
|
220
|
-
message: `
|
|
249
|
+
message: `Framework package(s) not found: ${missingFramework.length}/${FRAMEWORK_PACKAGES.length}`,
|
|
250
|
+
details: missingFramework.map((p) => ` - ${p} (not installed)`)
|
|
221
251
|
});
|
|
222
252
|
else checks.push({
|
|
223
|
-
name: "
|
|
253
|
+
name: "framework-installed",
|
|
254
|
+
status: "pass",
|
|
255
|
+
message: `All ${FRAMEWORK_PACKAGES.length} framework packages installed`
|
|
256
|
+
});
|
|
257
|
+
const kuckitVersions = /* @__PURE__ */ new Map();
|
|
258
|
+
for (const { name, version } of installedFramework) if (version) kuckitVersions.set(name, version);
|
|
259
|
+
const uniqueVersions = new Set(kuckitVersions.values());
|
|
260
|
+
if (uniqueVersions.size > 1 && installedFramework.length >= 2) {
|
|
261
|
+
const versionParts = [...uniqueVersions].map((v) => {
|
|
262
|
+
const match = v.match(/^(\d+)\.(\d+)/);
|
|
263
|
+
return match ? `${match[1]}.${match[2]}` : v;
|
|
264
|
+
});
|
|
265
|
+
if (new Set(versionParts).size > 1) checks.push({
|
|
266
|
+
name: "version-compatibility",
|
|
267
|
+
status: "warn",
|
|
268
|
+
message: "Framework packages have mismatched versions",
|
|
269
|
+
details: [...kuckitVersions.entries()].map(([name, ver]) => ` - ${name}: ${ver}`)
|
|
270
|
+
});
|
|
271
|
+
else checks.push({
|
|
272
|
+
name: "version-compatibility",
|
|
273
|
+
status: "pass",
|
|
274
|
+
message: "Framework package versions are compatible"
|
|
275
|
+
});
|
|
276
|
+
} else if (installedFramework.length >= 2) checks.push({
|
|
277
|
+
name: "version-compatibility",
|
|
278
|
+
status: "pass",
|
|
279
|
+
message: "Framework package versions are compatible"
|
|
280
|
+
});
|
|
281
|
+
const localImportIssues = [];
|
|
282
|
+
for (const packageName of installedPackages) {
|
|
283
|
+
const packagePath = findPackagePath(packageName, cwd);
|
|
284
|
+
if (!packagePath) continue;
|
|
285
|
+
const srcPath = join(packagePath, "src");
|
|
286
|
+
if (!existsSync(srcPath)) continue;
|
|
287
|
+
try {
|
|
288
|
+
const files = scanSourceFiles(srcPath);
|
|
289
|
+
for (const file of files) if (readFileSync(file, "utf-8").match(/from\s+['"]@(?!kuckit\/)[^'"/]+\/(api|db|auth|domain|contracts)['"]/g)) {
|
|
290
|
+
const relPath = file.replace(cwd, ".");
|
|
291
|
+
localImportIssues.push(`${packageName}: ${relPath} imports from local package instead of @kuckit/*`);
|
|
292
|
+
}
|
|
293
|
+
} catch {}
|
|
294
|
+
}
|
|
295
|
+
if (localImportIssues.length > 0) checks.push({
|
|
296
|
+
name: "framework-imports",
|
|
297
|
+
status: "warn",
|
|
298
|
+
message: `${localImportIssues.length} module(s) import from local packages instead of @kuckit/*`,
|
|
299
|
+
details: localImportIssues.slice(0, 5).map((i) => ` - ${i}`)
|
|
300
|
+
});
|
|
301
|
+
else if (installedPackages.length > 0) checks.push({
|
|
302
|
+
name: "framework-imports",
|
|
224
303
|
status: "pass",
|
|
225
|
-
message: "
|
|
304
|
+
message: "Module imports use @kuckit/* framework packages"
|
|
226
305
|
});
|
|
227
306
|
const serverModulesPath = join(cwd, "apps", "server", "src", "config", "modules.ts");
|
|
228
307
|
const clientModulesPath = join(cwd, "apps", "web", "src", "modules.client.ts");
|
|
@@ -384,9 +463,9 @@ async function search(keyword, options) {
|
|
|
384
463
|
|
|
385
464
|
//#endregion
|
|
386
465
|
//#region src/commands/db.ts
|
|
387
|
-
const KUCKIT_DIR$
|
|
466
|
+
const KUCKIT_DIR$8 = ".kuckit";
|
|
388
467
|
const TEMP_CONFIG_NAME = "drizzle.config.ts";
|
|
389
|
-
async function fileExists$
|
|
468
|
+
async function fileExists$11(path) {
|
|
390
469
|
try {
|
|
391
470
|
await access(path, constants$1.F_OK);
|
|
392
471
|
return true;
|
|
@@ -394,10 +473,10 @@ async function fileExists$9(path) {
|
|
|
394
473
|
return false;
|
|
395
474
|
}
|
|
396
475
|
}
|
|
397
|
-
async function findProjectRoot$
|
|
476
|
+
async function findProjectRoot$11(cwd) {
|
|
398
477
|
let dir = cwd;
|
|
399
478
|
while (dir !== dirname$1(dir)) {
|
|
400
|
-
if (await fileExists$
|
|
479
|
+
if (await fileExists$11(join$1(dir, "package.json"))) return dir;
|
|
401
480
|
dir = dirname$1(dir);
|
|
402
481
|
}
|
|
403
482
|
return null;
|
|
@@ -410,7 +489,7 @@ async function getDatabaseUrl(cwd, options) {
|
|
|
410
489
|
join$1(cwd, "apps", "server", ".env"),
|
|
411
490
|
join$1(cwd, ".env.local")
|
|
412
491
|
];
|
|
413
|
-
for (const envPath of envPaths) if (await fileExists$
|
|
492
|
+
for (const envPath of envPaths) if (await fileExists$11(envPath)) try {
|
|
414
493
|
const match = (await readFile(envPath, "utf-8")).match(/^DATABASE_URL=(.+)$/m);
|
|
415
494
|
if (match) return match[1].replace(/^["']|["']$/g, "");
|
|
416
495
|
} catch {}
|
|
@@ -426,10 +505,10 @@ async function discoverModuleSchemas(cwd) {
|
|
|
426
505
|
let moduleId = null;
|
|
427
506
|
if (moduleSpec.package) {
|
|
428
507
|
const workspacePath = join$1(cwd, "packages", moduleSpec.package.replace(/^@[^/]+\//, ""));
|
|
429
|
-
if (await fileExists$
|
|
508
|
+
if (await fileExists$11(join$1(workspacePath, "package.json"))) packagePath = workspacePath;
|
|
430
509
|
else {
|
|
431
510
|
const nodeModulesPath = join$1(cwd, "node_modules", moduleSpec.package);
|
|
432
|
-
if (await fileExists$
|
|
511
|
+
if (await fileExists$11(join$1(nodeModulesPath, "package.json"))) packagePath = nodeModulesPath;
|
|
433
512
|
}
|
|
434
513
|
if (packagePath) try {
|
|
435
514
|
moduleId = JSON.parse(await readFile(join$1(packagePath, "package.json"), "utf-8")).kuckit?.id || moduleSpec.package;
|
|
@@ -444,7 +523,7 @@ async function discoverModuleSchemas(cwd) {
|
|
|
444
523
|
join$1(packagePath, "src", "schema", "index.ts"),
|
|
445
524
|
join$1(packagePath, "src", "schema.ts")
|
|
446
525
|
];
|
|
447
|
-
for (const schemaPath of schemaLocations) if (await fileExists$
|
|
526
|
+
for (const schemaPath of schemaLocations) if (await fileExists$11(schemaPath)) {
|
|
448
527
|
schemas.push({
|
|
449
528
|
moduleId: moduleId || "unknown",
|
|
450
529
|
schemaPath
|
|
@@ -453,14 +532,14 @@ async function discoverModuleSchemas(cwd) {
|
|
|
453
532
|
}
|
|
454
533
|
}
|
|
455
534
|
const centralSchemaPath = join$1(cwd, "packages", "db", "src", "schema");
|
|
456
|
-
if (await fileExists$
|
|
535
|
+
if (await fileExists$11(centralSchemaPath)) schemas.push({
|
|
457
536
|
moduleId: "core",
|
|
458
537
|
schemaPath: centralSchemaPath
|
|
459
538
|
});
|
|
460
539
|
return schemas;
|
|
461
540
|
}
|
|
462
541
|
async function generateTempDrizzleConfig(cwd, databaseUrl, schemas) {
|
|
463
|
-
const kuckitDir = join$1(cwd, KUCKIT_DIR$
|
|
542
|
+
const kuckitDir = join$1(cwd, KUCKIT_DIR$8);
|
|
464
543
|
await mkdir(kuckitDir, { recursive: true });
|
|
465
544
|
const configPath = join$1(kuckitDir, TEMP_CONFIG_NAME);
|
|
466
545
|
const schemaPaths = schemas.map((s) => s.schemaPath);
|
|
@@ -512,7 +591,7 @@ function runDrizzleKit(command, configPath, cwd) {
|
|
|
512
591
|
});
|
|
513
592
|
}
|
|
514
593
|
async function runDbCommand(command, options) {
|
|
515
|
-
const projectRoot = await findProjectRoot$
|
|
594
|
+
const projectRoot = await findProjectRoot$11(process.cwd());
|
|
516
595
|
if (!projectRoot) {
|
|
517
596
|
console.error("Error: Could not find project root (no package.json found)");
|
|
518
597
|
process.exit(1);
|
|
@@ -557,10 +636,10 @@ async function dbStudio(options) {
|
|
|
557
636
|
|
|
558
637
|
//#endregion
|
|
559
638
|
//#region src/lib/credentials.ts
|
|
560
|
-
const DEFAULT_SERVER_URL = "https://
|
|
639
|
+
const DEFAULT_SERVER_URL = "https://api.kuckit.dev";
|
|
561
640
|
const CONFIG_DIR = join(homedir(), ".kuckit");
|
|
562
641
|
const CONFIG_PATH = join(CONFIG_DIR, "config.json");
|
|
563
|
-
function loadConfig$
|
|
642
|
+
function loadConfig$7() {
|
|
564
643
|
try {
|
|
565
644
|
if (!existsSync(CONFIG_PATH)) return null;
|
|
566
645
|
const content = readFileSync(CONFIG_PATH, "utf-8");
|
|
@@ -569,7 +648,7 @@ function loadConfig$8() {
|
|
|
569
648
|
return null;
|
|
570
649
|
}
|
|
571
650
|
}
|
|
572
|
-
function saveConfig
|
|
651
|
+
function saveConfig(config) {
|
|
573
652
|
if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, {
|
|
574
653
|
recursive: true,
|
|
575
654
|
mode: 448
|
|
@@ -613,7 +692,7 @@ function formatExpiryDate(expiresAt) {
|
|
|
613
692
|
});
|
|
614
693
|
}
|
|
615
694
|
async function authLogin(options) {
|
|
616
|
-
const config = loadConfig$
|
|
695
|
+
const config = loadConfig$7();
|
|
617
696
|
const serverUrl = getServerUrl(config, options.server);
|
|
618
697
|
const scopes = options.scopes?.split(",").map((s) => s.trim()) ?? ["cli"];
|
|
619
698
|
console.log("\nAuthenticating with Kuckit...\n");
|
|
@@ -678,7 +757,7 @@ async function authLogin(options) {
|
|
|
678
757
|
process.exit(1);
|
|
679
758
|
}
|
|
680
759
|
const expiresAt = new Date(Date.now() + pollResult.expiresIn * 1e3).toISOString();
|
|
681
|
-
saveConfig
|
|
760
|
+
saveConfig({
|
|
682
761
|
...config,
|
|
683
762
|
cliToken: pollResult.token,
|
|
684
763
|
tokenExpiresAt: expiresAt,
|
|
@@ -711,7 +790,7 @@ async function authLogin(options) {
|
|
|
711
790
|
process.exit(1);
|
|
712
791
|
}
|
|
713
792
|
async function authWhoami(options) {
|
|
714
|
-
const config = loadConfig$
|
|
793
|
+
const config = loadConfig$7();
|
|
715
794
|
if (!config?.cliToken) {
|
|
716
795
|
if (options.json) console.log(JSON.stringify({ authenticated: false }));
|
|
717
796
|
else console.log("Not logged in. Run 'kuckit auth login' to authenticate.");
|
|
@@ -744,7 +823,7 @@ async function authWhoami(options) {
|
|
|
744
823
|
}
|
|
745
824
|
}
|
|
746
825
|
async function authLogout() {
|
|
747
|
-
if (!loadConfig$
|
|
826
|
+
if (!loadConfig$7()?.cliToken) {
|
|
748
827
|
console.log("Already logged out.");
|
|
749
828
|
return;
|
|
750
829
|
}
|
|
@@ -771,7 +850,7 @@ function registerAuthCommands(program$1) {
|
|
|
771
850
|
* Exits the process with an error message if not authenticated.
|
|
772
851
|
*/
|
|
773
852
|
function requireAuth() {
|
|
774
|
-
const config = loadConfig$
|
|
853
|
+
const config = loadConfig$7();
|
|
775
854
|
if (!config?.cliToken) {
|
|
776
855
|
console.error("\nError: Not logged in. Run 'kuckit auth login' first.\n");
|
|
777
856
|
process.exit(1);
|
|
@@ -782,6 +861,732 @@ function requireAuth() {
|
|
|
782
861
|
}
|
|
783
862
|
}
|
|
784
863
|
|
|
864
|
+
//#endregion
|
|
865
|
+
//#region src/lib/package-manager.ts
|
|
866
|
+
/**
|
|
867
|
+
* Detect the package manager used in a project
|
|
868
|
+
*
|
|
869
|
+
* @param cwd - Directory to check for lock files
|
|
870
|
+
* @returns The detected package manager, defaults to 'npm' if none found
|
|
871
|
+
*/
|
|
872
|
+
function detectPackageManager(cwd) {
|
|
873
|
+
for (const [file, pm] of Object.entries({
|
|
874
|
+
"bun.lock": "bun",
|
|
875
|
+
"bun.lockb": "bun",
|
|
876
|
+
"package-lock.json": "npm",
|
|
877
|
+
"yarn.lock": "yarn",
|
|
878
|
+
"pnpm-lock.yaml": "pnpm"
|
|
879
|
+
})) try {
|
|
880
|
+
accessSync$1(join$1(cwd, file), constants$2.F_OK);
|
|
881
|
+
return pm;
|
|
882
|
+
} catch {}
|
|
883
|
+
return "npm";
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Get the install command for a package manager
|
|
887
|
+
*
|
|
888
|
+
* @param pm - Package manager
|
|
889
|
+
* @param packageName - Package to install
|
|
890
|
+
* @param options - Install options
|
|
891
|
+
* @returns The full install command
|
|
892
|
+
*/
|
|
893
|
+
function getInstallCommand(pm, packageName, options = {}) {
|
|
894
|
+
const devFlag = options.dev ? " -D" : "";
|
|
895
|
+
switch (pm) {
|
|
896
|
+
case "bun": return `bun add${devFlag} ${packageName}`;
|
|
897
|
+
case "yarn": return `yarn add${options.dev ? " --dev" : ""} ${packageName}`;
|
|
898
|
+
case "pnpm": return `pnpm add${devFlag} ${packageName}`;
|
|
899
|
+
case "npm":
|
|
900
|
+
default: return `npm install${options.dev ? " --save-dev" : ""} ${packageName}`;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
//#endregion
|
|
905
|
+
//#region src/commands/infra/provider-loader.ts
|
|
906
|
+
const KUCKIT_DIR$7 = ".kuckit";
|
|
907
|
+
const CONFIG_FILE$7 = "infra.json";
|
|
908
|
+
/**
|
|
909
|
+
* Default provider packages by provider ID
|
|
910
|
+
*/
|
|
911
|
+
const DEFAULT_PROVIDER_PACKAGES = {
|
|
912
|
+
gcp: "@kuckit/infra-gcp",
|
|
913
|
+
aws: "@kuckit/infra-aws",
|
|
914
|
+
azure: "@kuckit/infra-azure"
|
|
915
|
+
};
|
|
916
|
+
/**
|
|
917
|
+
* Load the stored infrastructure config from .kuckit/infra.json
|
|
918
|
+
*/
|
|
919
|
+
async function loadStoredConfig(projectRoot) {
|
|
920
|
+
const configPath = join$1(projectRoot, KUCKIT_DIR$7, CONFIG_FILE$7);
|
|
921
|
+
try {
|
|
922
|
+
await access(configPath, constants$1.F_OK);
|
|
923
|
+
const content = await readFile(configPath, "utf-8");
|
|
924
|
+
const parsed = JSON.parse(content);
|
|
925
|
+
if (isLegacyConfig(parsed)) return migrateLegacyConfig(parsed);
|
|
926
|
+
return parsed;
|
|
927
|
+
} catch {
|
|
928
|
+
return null;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
/**
|
|
932
|
+
* Save infrastructure config to .kuckit/infra.json
|
|
933
|
+
*/
|
|
934
|
+
async function saveStoredConfig(projectRoot, config) {
|
|
935
|
+
const { mkdir: mkdir$1, writeFile: writeFile$1 } = await import("fs/promises");
|
|
936
|
+
const kuckitDir = join$1(projectRoot, KUCKIT_DIR$7);
|
|
937
|
+
await mkdir$1(kuckitDir, { recursive: true });
|
|
938
|
+
const configPath = join$1(kuckitDir, CONFIG_FILE$7);
|
|
939
|
+
config.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
940
|
+
await writeFile$1(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* Error thrown when provider package is not installed
|
|
944
|
+
*/
|
|
945
|
+
var ProviderNotInstalledError = class extends Error {
|
|
946
|
+
constructor(packageName, installCommand) {
|
|
947
|
+
super(`Provider package '${packageName}' is not installed.`);
|
|
948
|
+
this.packageName = packageName;
|
|
949
|
+
this.installCommand = installCommand;
|
|
950
|
+
this.name = "ProviderNotInstalledError";
|
|
951
|
+
}
|
|
952
|
+
};
|
|
953
|
+
/**
|
|
954
|
+
* Error thrown when provider package is invalid
|
|
955
|
+
*/
|
|
956
|
+
var InvalidProviderError = class extends Error {
|
|
957
|
+
constructor(packageName, reason) {
|
|
958
|
+
super(`Package '${packageName}' is not a valid Kuckit infrastructure provider: ${reason}`);
|
|
959
|
+
this.packageName = packageName;
|
|
960
|
+
this.name = "InvalidProviderError";
|
|
961
|
+
}
|
|
962
|
+
};
|
|
963
|
+
/**
|
|
964
|
+
* Load a provider from an npm package
|
|
965
|
+
*
|
|
966
|
+
* @param packageName - npm package name (e.g., '@kuckit/infra-gcp')
|
|
967
|
+
* @param projectRoot - Optional project root for package manager detection
|
|
968
|
+
* @returns The provider instance
|
|
969
|
+
* @throws ProviderNotInstalledError if package not found
|
|
970
|
+
* @throws InvalidProviderError if package doesn't export a valid provider
|
|
971
|
+
*/
|
|
972
|
+
async function loadProviderFromPackage(packageName, projectRoot) {
|
|
973
|
+
try {
|
|
974
|
+
const module = await import(packageName);
|
|
975
|
+
if (!module.provider) throw new InvalidProviderError(packageName, "does not export a 'provider' object");
|
|
976
|
+
const provider = module.provider;
|
|
977
|
+
if (!provider.id || !provider.label || !provider.getInfraDir || !provider.init || !provider.deploy) throw new InvalidProviderError(packageName, "missing required methods (id, label, getInfraDir, init, deploy)");
|
|
978
|
+
return provider;
|
|
979
|
+
} catch (error) {
|
|
980
|
+
if (error instanceof ProviderNotInstalledError || error instanceof InvalidProviderError) throw error;
|
|
981
|
+
if (error instanceof Error && (error.message.includes("Cannot find module") || error.message.includes("Cannot find package"))) throw new ProviderNotInstalledError(packageName, getInstallCommand(detectPackageManager(projectRoot ?? process.cwd()), packageName, { dev: true }));
|
|
982
|
+
throw error;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
/**
|
|
986
|
+
* Get the provider package name for a given provider ID
|
|
987
|
+
*/
|
|
988
|
+
function getProviderPackage(providerId) {
|
|
989
|
+
return DEFAULT_PROVIDER_PACKAGES[providerId] ?? `@kuckit/infra-${providerId}`;
|
|
990
|
+
}
|
|
991
|
+
/**
|
|
992
|
+
* Check if a provider package is available
|
|
993
|
+
*/
|
|
994
|
+
async function isProviderAvailable(providerId) {
|
|
995
|
+
const packageName = getProviderPackage(providerId);
|
|
996
|
+
try {
|
|
997
|
+
await import(packageName);
|
|
998
|
+
return true;
|
|
999
|
+
} catch {
|
|
1000
|
+
return false;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* List available provider packages
|
|
1005
|
+
*/
|
|
1006
|
+
async function listAvailableProviders() {
|
|
1007
|
+
return await Promise.all(Object.entries(DEFAULT_PROVIDER_PACKAGES).map(async ([id, pkg]) => ({
|
|
1008
|
+
id,
|
|
1009
|
+
package: pkg,
|
|
1010
|
+
available: await isProviderAvailable(id)
|
|
1011
|
+
})));
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
//#endregion
|
|
1015
|
+
//#region src/commands/infra/init.ts
|
|
1016
|
+
async function fileExists$10(path) {
|
|
1017
|
+
try {
|
|
1018
|
+
await access(path, constants$1.F_OK);
|
|
1019
|
+
return true;
|
|
1020
|
+
} catch {
|
|
1021
|
+
return false;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
async function findProjectRoot$10(cwd) {
|
|
1025
|
+
let dir = cwd;
|
|
1026
|
+
while (dir !== dirname$1(dir)) {
|
|
1027
|
+
if (await fileExists$10(join$1(dir, "package.json"))) return dir;
|
|
1028
|
+
dir = dirname$1(dir);
|
|
1029
|
+
}
|
|
1030
|
+
return null;
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Check if legacy packages/infra directory exists
|
|
1034
|
+
*/
|
|
1035
|
+
async function hasLegacyInfra(projectRoot) {
|
|
1036
|
+
return fileExists$10(join$1(projectRoot, "packages", "infra", "package.json"));
|
|
1037
|
+
}
|
|
1038
|
+
async function infraInit(options) {
|
|
1039
|
+
console.log("Initializing kuckit infrastructure...\n");
|
|
1040
|
+
const projectRoot = await findProjectRoot$10(process.cwd());
|
|
1041
|
+
if (!projectRoot) {
|
|
1042
|
+
console.error("Error: Could not find project root (no package.json found)");
|
|
1043
|
+
process.exit(1);
|
|
1044
|
+
}
|
|
1045
|
+
if (await hasLegacyInfra(projectRoot)) {
|
|
1046
|
+
console.log("⚠️ Legacy infrastructure detected: packages/infra");
|
|
1047
|
+
console.log("");
|
|
1048
|
+
console.log("Kuckit now uses provider packages (@kuckit/infra-gcp) instead of");
|
|
1049
|
+
console.log("local Pulumi code. Your existing Pulumi state will be preserved.");
|
|
1050
|
+
console.log("");
|
|
1051
|
+
console.log("After migration, you can remove packages/infra.");
|
|
1052
|
+
console.log("See docs/MIGRATION.md for details.");
|
|
1053
|
+
console.log("");
|
|
1054
|
+
}
|
|
1055
|
+
const existingConfig = await loadStoredConfig(projectRoot);
|
|
1056
|
+
let providerId = options.provider ?? existingConfig?.provider ?? "gcp";
|
|
1057
|
+
if (!options.provider && !existingConfig) {
|
|
1058
|
+
const installedProviders = (await listAvailableProviders()).filter((p) => p.available);
|
|
1059
|
+
if (installedProviders.length === 0) {
|
|
1060
|
+
const installCmd = getInstallCommand(detectPackageManager(projectRoot), "@kuckit/infra-gcp", { dev: true });
|
|
1061
|
+
console.error("Error: No infrastructure providers installed.");
|
|
1062
|
+
console.error("");
|
|
1063
|
+
console.error("Kuckit requires a cloud provider package to deploy infrastructure.");
|
|
1064
|
+
console.error("Install a provider to get started:");
|
|
1065
|
+
console.error("");
|
|
1066
|
+
console.error(` ${installCmd}`);
|
|
1067
|
+
console.error("");
|
|
1068
|
+
console.error("Available providers:");
|
|
1069
|
+
console.error(" @kuckit/infra-gcp - Google Cloud Platform (Cloud Run, Cloud SQL, Redis)");
|
|
1070
|
+
console.error(" @kuckit/infra-aws - Amazon Web Services (coming soon)");
|
|
1071
|
+
console.error(" @kuckit/infra-azure - Microsoft Azure (coming soon)");
|
|
1072
|
+
process.exit(1);
|
|
1073
|
+
}
|
|
1074
|
+
providerId = installedProviders[0].id;
|
|
1075
|
+
}
|
|
1076
|
+
const providerPackage = getProviderPackage(providerId);
|
|
1077
|
+
let provider;
|
|
1078
|
+
try {
|
|
1079
|
+
provider = await loadProviderFromPackage(providerPackage, projectRoot);
|
|
1080
|
+
} catch (error) {
|
|
1081
|
+
if (error instanceof ProviderNotInstalledError) {
|
|
1082
|
+
console.error(`Error: ${error.message}`);
|
|
1083
|
+
console.error("");
|
|
1084
|
+
console.error("Install the provider package:");
|
|
1085
|
+
console.error(` ${error.installCommand}`);
|
|
1086
|
+
process.exit(1);
|
|
1087
|
+
}
|
|
1088
|
+
if (error instanceof InvalidProviderError) {
|
|
1089
|
+
console.error(`Error: ${error.message}`);
|
|
1090
|
+
console.error("");
|
|
1091
|
+
console.error("This may be a corrupted or incompatible package version.");
|
|
1092
|
+
console.error("Try reinstalling the provider package.");
|
|
1093
|
+
process.exit(1);
|
|
1094
|
+
}
|
|
1095
|
+
console.error(`Error: ${error instanceof Error ? error.message : "Failed to load provider"}`);
|
|
1096
|
+
process.exit(1);
|
|
1097
|
+
}
|
|
1098
|
+
console.log("Checking prerequisites...");
|
|
1099
|
+
const prereqResult = await provider.checkPrerequisites();
|
|
1100
|
+
if (!prereqResult.ok) {
|
|
1101
|
+
console.error("\nError: Missing required tools:");
|
|
1102
|
+
for (const missing of prereqResult.missing) {
|
|
1103
|
+
console.error(` - ${missing.name}`);
|
|
1104
|
+
console.error(` Install from: ${missing.installUrl}`);
|
|
1105
|
+
}
|
|
1106
|
+
process.exit(1);
|
|
1107
|
+
}
|
|
1108
|
+
if (provider.validateProject) {
|
|
1109
|
+
const validation = await provider.validateProject(projectRoot);
|
|
1110
|
+
if (!validation.valid) {
|
|
1111
|
+
console.error("\nError: Project validation failed:");
|
|
1112
|
+
for (const issue of validation.issues.filter((i) => i.severity === "error")) console.error(` - ${issue.message}`);
|
|
1113
|
+
process.exit(1);
|
|
1114
|
+
}
|
|
1115
|
+
for (const issue of validation.issues.filter((i) => i.severity === "warning")) console.warn(`Warning: ${issue.message}`);
|
|
1116
|
+
}
|
|
1117
|
+
const prompts = await provider.getInitPrompts(existingConfig ?? void 0);
|
|
1118
|
+
const responses = {};
|
|
1119
|
+
if (options.project && providerId === "gcp") responses.gcpProject = options.project;
|
|
1120
|
+
for (const prompt of prompts) {
|
|
1121
|
+
if (responses[prompt.name] !== void 0) continue;
|
|
1122
|
+
if (prompt.type === "input") responses[prompt.name] = await input({
|
|
1123
|
+
message: prompt.message,
|
|
1124
|
+
default: prompt.default,
|
|
1125
|
+
validate: prompt.validate
|
|
1126
|
+
});
|
|
1127
|
+
else if (prompt.type === "select" && prompt.choices) responses[prompt.name] = await select({
|
|
1128
|
+
message: prompt.message,
|
|
1129
|
+
choices: prompt.choices.map((c) => ({
|
|
1130
|
+
value: c.value,
|
|
1131
|
+
name: c.label
|
|
1132
|
+
})),
|
|
1133
|
+
default: prompt.default
|
|
1134
|
+
});
|
|
1135
|
+
else if (prompt.type === "confirm") responses[prompt.name] = await confirm({
|
|
1136
|
+
message: prompt.message,
|
|
1137
|
+
default: prompt.default
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
let region = options.region ?? existingConfig?.region ?? "us-central1";
|
|
1141
|
+
if (!options.region && !existingConfig?.region) region = await input({
|
|
1142
|
+
message: "Deployment region:",
|
|
1143
|
+
default: "us-central1"
|
|
1144
|
+
});
|
|
1145
|
+
let env = options.env ?? existingConfig?.env ?? "dev";
|
|
1146
|
+
if (!options.env && !existingConfig?.env) env = await select({
|
|
1147
|
+
message: "Environment:",
|
|
1148
|
+
choices: [
|
|
1149
|
+
{
|
|
1150
|
+
value: "dev",
|
|
1151
|
+
name: "Development"
|
|
1152
|
+
},
|
|
1153
|
+
{
|
|
1154
|
+
value: "staging",
|
|
1155
|
+
name: "Staging"
|
|
1156
|
+
},
|
|
1157
|
+
{
|
|
1158
|
+
value: "prod",
|
|
1159
|
+
name: "Production"
|
|
1160
|
+
}
|
|
1161
|
+
],
|
|
1162
|
+
default: "dev"
|
|
1163
|
+
});
|
|
1164
|
+
const packageJsonPath = join$1(projectRoot, "package.json");
|
|
1165
|
+
let projectName = "kuckit-app";
|
|
1166
|
+
try {
|
|
1167
|
+
const { readFile: readFile$1 } = await import("fs/promises");
|
|
1168
|
+
projectName = JSON.parse(await readFile$1(packageJsonPath, "utf-8")).name?.replace(/^@[^/]+\//, "") ?? "kuckit-app";
|
|
1169
|
+
} catch {
|
|
1170
|
+
projectName = projectRoot.split("/").pop() ?? "kuckit-app";
|
|
1171
|
+
}
|
|
1172
|
+
const stackName = `${responses.gcpProject ?? ""}-${env}`;
|
|
1173
|
+
console.log("\nConfiguration:");
|
|
1174
|
+
console.log(` Provider: ${provider.label}`);
|
|
1175
|
+
for (const [key, value] of Object.entries(responses)) console.log(` ${key}: ${value}`);
|
|
1176
|
+
console.log(` Region: ${region}`);
|
|
1177
|
+
console.log(` Environment: ${env}`);
|
|
1178
|
+
console.log(` Stack: ${stackName}`);
|
|
1179
|
+
console.log("");
|
|
1180
|
+
if (!options.yes) {
|
|
1181
|
+
if (!await confirm({
|
|
1182
|
+
message: "Proceed with infrastructure initialization?",
|
|
1183
|
+
default: true
|
|
1184
|
+
})) {
|
|
1185
|
+
console.log("Aborted.");
|
|
1186
|
+
process.exit(0);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
const dockerfilePath = join$1(projectRoot, "Dockerfile");
|
|
1190
|
+
if (provider.generateDockerfile && !await fileExists$10(dockerfilePath)) {
|
|
1191
|
+
console.log("\nGenerating Dockerfile...");
|
|
1192
|
+
const dockerfileContent = await provider.generateDockerfile(projectRoot);
|
|
1193
|
+
if (dockerfileContent) {
|
|
1194
|
+
const { writeFile: writeFile$1 } = await import("fs/promises");
|
|
1195
|
+
await writeFile$1(dockerfilePath, dockerfileContent, "utf-8");
|
|
1196
|
+
console.log("Dockerfile created.");
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
console.log("\nInitializing infrastructure...");
|
|
1200
|
+
console.log("This may take several minutes.\n");
|
|
1201
|
+
const result = await provider.init({
|
|
1202
|
+
env,
|
|
1203
|
+
region,
|
|
1204
|
+
projectRoot,
|
|
1205
|
+
yes: options.yes,
|
|
1206
|
+
providerOptions: responses
|
|
1207
|
+
});
|
|
1208
|
+
if (!result.success) {
|
|
1209
|
+
console.error(`\nError: ${result.message}`);
|
|
1210
|
+
if (result.error) console.error(` ${result.error.code}: ${result.error.message}`);
|
|
1211
|
+
process.exit(1);
|
|
1212
|
+
}
|
|
1213
|
+
const baseConfig = {
|
|
1214
|
+
env,
|
|
1215
|
+
region,
|
|
1216
|
+
projectName,
|
|
1217
|
+
stackName
|
|
1218
|
+
};
|
|
1219
|
+
await saveStoredConfig(projectRoot, {
|
|
1220
|
+
...provider.buildConfig(responses, baseConfig),
|
|
1221
|
+
providerPackage,
|
|
1222
|
+
outputs: result.outputs,
|
|
1223
|
+
createdAt: existingConfig?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1224
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1225
|
+
});
|
|
1226
|
+
console.log("\nConfiguration saved to .kuckit/infra.json");
|
|
1227
|
+
console.log("\n" + "=".repeat(60));
|
|
1228
|
+
console.log("Infrastructure initialized successfully!");
|
|
1229
|
+
console.log("=".repeat(60));
|
|
1230
|
+
if (result.outputs) {
|
|
1231
|
+
console.log("\nOutputs:");
|
|
1232
|
+
const outputs = result.outputs;
|
|
1233
|
+
if (outputs.registryUrl) console.log(` Registry: ${outputs.registryUrl}`);
|
|
1234
|
+
if (outputs.databaseConnectionName) console.log(` Database: ${outputs.databaseConnectionName}`);
|
|
1235
|
+
if (outputs.redisHost) console.log(` Redis: ${outputs.redisHost}`);
|
|
1236
|
+
}
|
|
1237
|
+
console.log("\nNext steps:");
|
|
1238
|
+
console.log(" 1. Ensure you have a Dockerfile in your project root");
|
|
1239
|
+
console.log(` 2. Run: kuckit infra deploy --env ${env}`);
|
|
1240
|
+
console.log("");
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
//#endregion
|
|
1244
|
+
//#region src/commands/infra/deploy.ts
|
|
1245
|
+
async function fileExists$9(path) {
|
|
1246
|
+
try {
|
|
1247
|
+
await access(path, constants$1.F_OK);
|
|
1248
|
+
return true;
|
|
1249
|
+
} catch {
|
|
1250
|
+
return false;
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
async function findProjectRoot$9(cwd) {
|
|
1254
|
+
let dir = cwd;
|
|
1255
|
+
while (dir !== dirname$1(dir)) {
|
|
1256
|
+
if (await fileExists$9(join$1(dir, "package.json"))) return dir;
|
|
1257
|
+
dir = dirname$1(dir);
|
|
1258
|
+
}
|
|
1259
|
+
return null;
|
|
1260
|
+
}
|
|
1261
|
+
async function infraDeploy(options) {
|
|
1262
|
+
console.log("Deploying kuckit application...\n");
|
|
1263
|
+
const projectRoot = await findProjectRoot$9(process.cwd());
|
|
1264
|
+
if (!projectRoot) {
|
|
1265
|
+
console.error("Error: Could not find project root (no package.json found)");
|
|
1266
|
+
process.exit(1);
|
|
1267
|
+
}
|
|
1268
|
+
const config = await loadStoredConfig(projectRoot);
|
|
1269
|
+
if (!config) {
|
|
1270
|
+
console.error("Error: No infrastructure configuration found.");
|
|
1271
|
+
console.error("Run: kuckit infra init");
|
|
1272
|
+
process.exit(1);
|
|
1273
|
+
}
|
|
1274
|
+
const env = options.env ?? config.env;
|
|
1275
|
+
if (env !== "dev" && env !== "staging" && env !== "prod") {
|
|
1276
|
+
console.error(`Error: Invalid environment '${env}'. Must be 'dev', 'staging', or 'prod'.`);
|
|
1277
|
+
process.exit(1);
|
|
1278
|
+
}
|
|
1279
|
+
const providerPackage = config.providerPackage ?? getProviderPackage(config.provider);
|
|
1280
|
+
let provider;
|
|
1281
|
+
try {
|
|
1282
|
+
provider = await loadProviderFromPackage(providerPackage, projectRoot);
|
|
1283
|
+
} catch (error) {
|
|
1284
|
+
if (error instanceof ProviderNotInstalledError) {
|
|
1285
|
+
console.error(`Error: ${error.message}`);
|
|
1286
|
+
console.error("");
|
|
1287
|
+
console.error("Install the provider package:");
|
|
1288
|
+
console.error(` ${error.installCommand}`);
|
|
1289
|
+
process.exit(1);
|
|
1290
|
+
}
|
|
1291
|
+
if (error instanceof InvalidProviderError) {
|
|
1292
|
+
console.error(`Error: ${error.message}`);
|
|
1293
|
+
console.error("");
|
|
1294
|
+
console.error("This may be a corrupted or incompatible package version.");
|
|
1295
|
+
console.error("Try reinstalling the provider package.");
|
|
1296
|
+
process.exit(1);
|
|
1297
|
+
}
|
|
1298
|
+
console.error(`Error: ${error instanceof Error ? error.message : "Failed to load provider"}`);
|
|
1299
|
+
process.exit(1);
|
|
1300
|
+
}
|
|
1301
|
+
const prereqResult = await provider.checkPrerequisites();
|
|
1302
|
+
if (!prereqResult.ok) {
|
|
1303
|
+
console.error("\nError: Missing required tools:");
|
|
1304
|
+
for (const missing of prereqResult.missing) {
|
|
1305
|
+
console.error(` - ${missing.name}`);
|
|
1306
|
+
console.error(` Install from: ${missing.installUrl}`);
|
|
1307
|
+
}
|
|
1308
|
+
process.exit(1);
|
|
1309
|
+
}
|
|
1310
|
+
const dockerfilePath = join$1(projectRoot, "Dockerfile");
|
|
1311
|
+
if (!options.skipBuild && !options.image && !await fileExists$9(dockerfilePath)) if (provider.generateDockerfile) {
|
|
1312
|
+
console.log("Generating Dockerfile...");
|
|
1313
|
+
const dockerfileContent = await provider.generateDockerfile(projectRoot);
|
|
1314
|
+
if (dockerfileContent) {
|
|
1315
|
+
const { writeFile: writeFile$1 } = await import("fs/promises");
|
|
1316
|
+
await writeFile$1(dockerfilePath, dockerfileContent, "utf-8");
|
|
1317
|
+
console.log("Dockerfile created.\n");
|
|
1318
|
+
} else {
|
|
1319
|
+
console.error("Error: Dockerfile not found in project root.");
|
|
1320
|
+
console.error("Create a Dockerfile or use --skip-build with an existing image.");
|
|
1321
|
+
process.exit(1);
|
|
1322
|
+
}
|
|
1323
|
+
} else {
|
|
1324
|
+
console.error("Error: Dockerfile not found in project root.");
|
|
1325
|
+
console.error("Create a Dockerfile or use --skip-build with an existing image.");
|
|
1326
|
+
process.exit(1);
|
|
1327
|
+
}
|
|
1328
|
+
if (!config.outputs?.registryUrl) {
|
|
1329
|
+
console.error("Error: No registry URL found in configuration.");
|
|
1330
|
+
console.error("Run: kuckit infra init");
|
|
1331
|
+
process.exit(1);
|
|
1332
|
+
}
|
|
1333
|
+
let projectDisplay = config.projectName;
|
|
1334
|
+
if (isGcpConfig(config)) projectDisplay = config.providerConfig.gcpProject;
|
|
1335
|
+
console.log("Configuration:");
|
|
1336
|
+
console.log(` Provider: ${provider.label}`);
|
|
1337
|
+
console.log(` Project: ${projectDisplay}`);
|
|
1338
|
+
console.log(` Region: ${config.region}`);
|
|
1339
|
+
console.log(` Environment: ${env}`);
|
|
1340
|
+
console.log(` Registry: ${config.outputs.registryUrl}`);
|
|
1341
|
+
if (options.preview) console.log(" Mode: Preview (no changes will be applied)");
|
|
1342
|
+
console.log("");
|
|
1343
|
+
if (!options.yes && !options.preview) {
|
|
1344
|
+
if (!await confirm({
|
|
1345
|
+
message: "Proceed with deployment?",
|
|
1346
|
+
default: true
|
|
1347
|
+
})) {
|
|
1348
|
+
console.log("Aborted.");
|
|
1349
|
+
process.exit(0);
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
const result = await provider.deploy({
|
|
1353
|
+
env,
|
|
1354
|
+
projectRoot,
|
|
1355
|
+
preview: options.preview,
|
|
1356
|
+
skipBuild: options.skipBuild,
|
|
1357
|
+
image: options.image,
|
|
1358
|
+
yes: options.yes
|
|
1359
|
+
}, config);
|
|
1360
|
+
if (!result.success) {
|
|
1361
|
+
console.error(`\nError: ${result.message}`);
|
|
1362
|
+
if (result.error) console.error(` ${result.error.code}: ${result.error.message}`);
|
|
1363
|
+
process.exit(1);
|
|
1364
|
+
}
|
|
1365
|
+
if (!options.preview && result.outputs) await saveStoredConfig(projectRoot, {
|
|
1366
|
+
...config,
|
|
1367
|
+
outputs: result.outputs,
|
|
1368
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1369
|
+
});
|
|
1370
|
+
if (!options.preview) {
|
|
1371
|
+
console.log("\n" + "=".repeat(60));
|
|
1372
|
+
console.log("Deployment successful!");
|
|
1373
|
+
console.log("=".repeat(60));
|
|
1374
|
+
if (result.outputs) {
|
|
1375
|
+
const outputs = result.outputs;
|
|
1376
|
+
if (outputs.serviceUrl) console.log(`\nService URL: ${outputs.serviceUrl}`);
|
|
1377
|
+
if (outputs.migrationJobName) console.log(`Migration Job: ${outputs.migrationJobName}`);
|
|
1378
|
+
}
|
|
1379
|
+
console.log("\nUseful commands:");
|
|
1380
|
+
console.log(` View logs: kuckit infra logs --env ${env}`);
|
|
1381
|
+
console.log(` Check status: kuckit infra status --env ${env}`);
|
|
1382
|
+
console.log(` Run migrations: kuckit infra db:migrate --env ${env}`);
|
|
1383
|
+
console.log("");
|
|
1384
|
+
} else {
|
|
1385
|
+
console.log("\nPreview complete. No changes were applied.");
|
|
1386
|
+
console.log("Run without --preview to apply changes.");
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
//#endregion
|
|
1391
|
+
//#region src/commands/infra/up.ts
|
|
1392
|
+
async function fileExists$8(path) {
|
|
1393
|
+
try {
|
|
1394
|
+
await access(path, constants$1.F_OK);
|
|
1395
|
+
return true;
|
|
1396
|
+
} catch {
|
|
1397
|
+
return false;
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
async function findProjectRoot$8(cwd) {
|
|
1401
|
+
let dir = cwd;
|
|
1402
|
+
while (dir !== dirname$1(dir)) {
|
|
1403
|
+
if (await fileExists$8(join$1(dir, "package.json"))) return dir;
|
|
1404
|
+
dir = dirname$1(dir);
|
|
1405
|
+
}
|
|
1406
|
+
return null;
|
|
1407
|
+
}
|
|
1408
|
+
async function infraUp(options) {
|
|
1409
|
+
console.log("🚀 Kuckit Infrastructure Up\n");
|
|
1410
|
+
const projectRoot = await findProjectRoot$8(process.cwd());
|
|
1411
|
+
if (!projectRoot) {
|
|
1412
|
+
console.error("Error: Could not find project root (no package.json found)");
|
|
1413
|
+
process.exit(1);
|
|
1414
|
+
}
|
|
1415
|
+
const existingConfig = await loadStoredConfig(projectRoot);
|
|
1416
|
+
if (!existingConfig) {
|
|
1417
|
+
const providerPackage = getProviderPackage(options.provider ?? "gcp");
|
|
1418
|
+
let providerLabel = "cloud";
|
|
1419
|
+
try {
|
|
1420
|
+
providerLabel = (await loadProviderFromPackage(providerPackage, projectRoot)).label;
|
|
1421
|
+
} catch (error) {
|
|
1422
|
+
if (error instanceof ProviderNotInstalledError) {
|
|
1423
|
+
console.error(`Error: ${error.message}`);
|
|
1424
|
+
console.error("");
|
|
1425
|
+
console.error("Install the provider package first:");
|
|
1426
|
+
console.error(` ${error.installCommand}`);
|
|
1427
|
+
console.error("");
|
|
1428
|
+
console.error("Then run this command again.");
|
|
1429
|
+
process.exit(1);
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
console.log("No infrastructure configuration found. Starting initialization...\n");
|
|
1433
|
+
if (!options.yes) {
|
|
1434
|
+
if (!await confirm({
|
|
1435
|
+
message: `This will create ${providerLabel} infrastructure (VPC, Database, Redis, Registry). Continue?`,
|
|
1436
|
+
default: true
|
|
1437
|
+
})) {
|
|
1438
|
+
console.log("Aborted.");
|
|
1439
|
+
process.exit(0);
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
await infraInit({
|
|
1443
|
+
provider: options.provider,
|
|
1444
|
+
project: options.project,
|
|
1445
|
+
region: options.region,
|
|
1446
|
+
env: options.env,
|
|
1447
|
+
yes: options.yes
|
|
1448
|
+
});
|
|
1449
|
+
if (!await loadStoredConfig(projectRoot)) {
|
|
1450
|
+
console.error("Error: Initialization completed but no configuration found.");
|
|
1451
|
+
process.exit(1);
|
|
1452
|
+
}
|
|
1453
|
+
console.log("\n" + "─".repeat(60) + "\n");
|
|
1454
|
+
console.log("Infrastructure initialized! Proceeding to deployment...\n");
|
|
1455
|
+
} else {
|
|
1456
|
+
console.log(`Using existing configuration for ${existingConfig.env} environment.`);
|
|
1457
|
+
console.log(`Provider: ${existingConfig.provider}`);
|
|
1458
|
+
console.log(`Region: ${existingConfig.region}\n`);
|
|
1459
|
+
}
|
|
1460
|
+
await infraDeploy({
|
|
1461
|
+
env: options.env ?? existingConfig?.env ?? "dev",
|
|
1462
|
+
preview: options.preview,
|
|
1463
|
+
yes: options.yes
|
|
1464
|
+
});
|
|
1465
|
+
console.log("\n" + "═".repeat(60));
|
|
1466
|
+
console.log("✅ Infrastructure up and running!");
|
|
1467
|
+
console.log("═".repeat(60));
|
|
1468
|
+
console.log("\nUseful commands:");
|
|
1469
|
+
console.log(` kuckit infra status - Check current status`);
|
|
1470
|
+
console.log(` kuckit infra logs - View application logs`);
|
|
1471
|
+
console.log(` kuckit infra outputs - Show service URLs and secrets`);
|
|
1472
|
+
console.log("");
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
//#endregion
|
|
1476
|
+
//#region src/commands/infra/eject.ts
|
|
1477
|
+
async function fileExists$7(path) {
|
|
1478
|
+
try {
|
|
1479
|
+
await access(path, constants$1.F_OK);
|
|
1480
|
+
return true;
|
|
1481
|
+
} catch {
|
|
1482
|
+
return false;
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
async function findProjectRoot$7(cwd) {
|
|
1486
|
+
let dir = cwd;
|
|
1487
|
+
while (dir !== dirname$1(dir)) {
|
|
1488
|
+
if (await fileExists$7(join$1(dir, "package.json"))) return dir;
|
|
1489
|
+
dir = dirname$1(dir);
|
|
1490
|
+
}
|
|
1491
|
+
return null;
|
|
1492
|
+
}
|
|
1493
|
+
async function infraEject(options) {
|
|
1494
|
+
console.log("Ejecting infrastructure code...\n");
|
|
1495
|
+
const projectRoot = await findProjectRoot$7(process.cwd());
|
|
1496
|
+
if (!projectRoot) {
|
|
1497
|
+
console.error("Error: Could not find project root (no package.json found)");
|
|
1498
|
+
process.exit(1);
|
|
1499
|
+
}
|
|
1500
|
+
const config = await loadStoredConfig(projectRoot);
|
|
1501
|
+
if (!config) {
|
|
1502
|
+
console.error("Error: No infrastructure configuration found.");
|
|
1503
|
+
console.error("Run: kuckit infra init");
|
|
1504
|
+
process.exit(1);
|
|
1505
|
+
}
|
|
1506
|
+
if (config.localInfraDir) {
|
|
1507
|
+
console.error(`Error: Infrastructure already ejected to ${config.localInfraDir}`);
|
|
1508
|
+
console.error("Remove localInfraDir from .kuckit/infra.json to re-eject.");
|
|
1509
|
+
process.exit(1);
|
|
1510
|
+
}
|
|
1511
|
+
const providerPackage = config.providerPackage ?? getProviderPackage(config.provider);
|
|
1512
|
+
let provider;
|
|
1513
|
+
try {
|
|
1514
|
+
provider = await loadProviderFromPackage(providerPackage, projectRoot);
|
|
1515
|
+
} catch (error) {
|
|
1516
|
+
if (error instanceof ProviderNotInstalledError) {
|
|
1517
|
+
console.error(`Error: ${error.message}`);
|
|
1518
|
+
console.error("");
|
|
1519
|
+
console.error("Install the provider package:");
|
|
1520
|
+
console.error(` ${error.installCommand}`);
|
|
1521
|
+
process.exit(1);
|
|
1522
|
+
}
|
|
1523
|
+
if (error instanceof InvalidProviderError) {
|
|
1524
|
+
console.error(`Error: ${error.message}`);
|
|
1525
|
+
process.exit(1);
|
|
1526
|
+
}
|
|
1527
|
+
console.error(`Error: ${error instanceof Error ? error.message : "Failed to load provider"}`);
|
|
1528
|
+
process.exit(1);
|
|
1529
|
+
}
|
|
1530
|
+
if (!provider.eject) {
|
|
1531
|
+
console.error(`Error: Provider '${provider.id}' does not support ejecting.`);
|
|
1532
|
+
process.exit(1);
|
|
1533
|
+
}
|
|
1534
|
+
const targetDir = options.dir ?? "infra";
|
|
1535
|
+
const absoluteTargetDir = join$1(projectRoot, targetDir);
|
|
1536
|
+
if (await fileExists$7(absoluteTargetDir)) {
|
|
1537
|
+
if (!options.force) {
|
|
1538
|
+
console.error(`Error: Directory '${targetDir}' already exists.`);
|
|
1539
|
+
console.error("Use --force to overwrite, or specify a different directory with --dir.");
|
|
1540
|
+
process.exit(1);
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
console.log("Configuration:");
|
|
1544
|
+
console.log(` Provider: ${provider.label}`);
|
|
1545
|
+
console.log(` Target directory: ${targetDir}`);
|
|
1546
|
+
console.log("");
|
|
1547
|
+
if (!options.force) {
|
|
1548
|
+
console.log("This will:");
|
|
1549
|
+
console.log(` 1. Copy provider Pulumi code to ${targetDir}/`);
|
|
1550
|
+
console.log(" 2. Update .kuckit/infra.json to use local infrastructure");
|
|
1551
|
+
console.log(" 3. Future deployments will use your local copy");
|
|
1552
|
+
console.log("");
|
|
1553
|
+
if (!await confirm({
|
|
1554
|
+
message: "Proceed with eject?",
|
|
1555
|
+
default: true
|
|
1556
|
+
})) {
|
|
1557
|
+
console.log("Aborted.");
|
|
1558
|
+
process.exit(0);
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
const result = await provider.eject({
|
|
1562
|
+
targetDir: absoluteTargetDir,
|
|
1563
|
+
projectRoot,
|
|
1564
|
+
force: options.force
|
|
1565
|
+
});
|
|
1566
|
+
if (!result.success) {
|
|
1567
|
+
console.error(`\nError: ${result.message}`);
|
|
1568
|
+
if (result.error) console.error(` ${result.error.code}: ${result.error.message}`);
|
|
1569
|
+
process.exit(1);
|
|
1570
|
+
}
|
|
1571
|
+
await saveStoredConfig(projectRoot, {
|
|
1572
|
+
...config,
|
|
1573
|
+
localInfraDir: targetDir,
|
|
1574
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1575
|
+
});
|
|
1576
|
+
console.log("\n" + "=".repeat(60));
|
|
1577
|
+
console.log("Infrastructure ejected successfully!");
|
|
1578
|
+
console.log("=".repeat(60));
|
|
1579
|
+
console.log(`\nYour infrastructure code is now in: ${targetDir}/`);
|
|
1580
|
+
console.log("");
|
|
1581
|
+
console.log("Next steps:");
|
|
1582
|
+
console.log(` 1. Review and customize the Pulumi code in ${targetDir}/`);
|
|
1583
|
+
console.log(" 2. Install Pulumi dependencies: cd " + targetDir + " && bun install");
|
|
1584
|
+
console.log(" 3. Deploy with: kuckit infra deploy");
|
|
1585
|
+
console.log("");
|
|
1586
|
+
console.log("The CLI will now use your local infrastructure instead of the provider package.");
|
|
1587
|
+
console.log("");
|
|
1588
|
+
}
|
|
1589
|
+
|
|
785
1590
|
//#endregion
|
|
786
1591
|
//#region src/commands/infra/runner.ts
|
|
787
1592
|
/**
|
|
@@ -1003,31 +1808,6 @@ function runGcloud(args, options = {}) {
|
|
|
1003
1808
|
});
|
|
1004
1809
|
}
|
|
1005
1810
|
/**
|
|
1006
|
-
* Build and push Docker image using Cloud Build
|
|
1007
|
-
* No local Docker daemon required!
|
|
1008
|
-
*/
|
|
1009
|
-
async function buildAndPushImage(options) {
|
|
1010
|
-
const tag = options.tag ?? process.env.GIT_SHA ?? "latest";
|
|
1011
|
-
const imageUrl = `${options.registryUrl}/kuckit:${tag}`;
|
|
1012
|
-
console.log(`Building image: ${imageUrl}`);
|
|
1013
|
-
const args = [
|
|
1014
|
-
"builds",
|
|
1015
|
-
"submit",
|
|
1016
|
-
"--tag",
|
|
1017
|
-
imageUrl,
|
|
1018
|
-
"--project",
|
|
1019
|
-
options.project
|
|
1020
|
-
];
|
|
1021
|
-
if (options.dockerfile && options.dockerfile !== "Dockerfile") args.push("--config", options.dockerfile);
|
|
1022
|
-
return {
|
|
1023
|
-
code: (await runGcloud(args, {
|
|
1024
|
-
stream: true,
|
|
1025
|
-
cwd: options.cwd
|
|
1026
|
-
})).code,
|
|
1027
|
-
imageUrl
|
|
1028
|
-
};
|
|
1029
|
-
}
|
|
1030
|
-
/**
|
|
1031
1811
|
* Get the path to the packages/infra directory
|
|
1032
1812
|
*/
|
|
1033
1813
|
function getInfraDir(projectRoot) {
|
|
@@ -1275,359 +2055,6 @@ function formatError(error) {
|
|
|
1275
2055
|
return lines.join("\n");
|
|
1276
2056
|
}
|
|
1277
2057
|
|
|
1278
|
-
//#endregion
|
|
1279
|
-
//#region src/commands/infra/init.ts
|
|
1280
|
-
const KUCKIT_DIR$8 = ".kuckit";
|
|
1281
|
-
const CONFIG_FILE$8 = "infra.json";
|
|
1282
|
-
async function fileExists$8(path) {
|
|
1283
|
-
try {
|
|
1284
|
-
await access(path, constants$1.F_OK);
|
|
1285
|
-
return true;
|
|
1286
|
-
} catch {
|
|
1287
|
-
return false;
|
|
1288
|
-
}
|
|
1289
|
-
}
|
|
1290
|
-
async function findProjectRoot$8(cwd) {
|
|
1291
|
-
let dir = cwd;
|
|
1292
|
-
while (dir !== dirname$1(dir)) {
|
|
1293
|
-
if (await fileExists$8(join$1(dir, "package.json"))) return dir;
|
|
1294
|
-
dir = dirname$1(dir);
|
|
1295
|
-
}
|
|
1296
|
-
return null;
|
|
1297
|
-
}
|
|
1298
|
-
async function loadExistingConfig(projectRoot) {
|
|
1299
|
-
const configPath = join$1(projectRoot, KUCKIT_DIR$8, CONFIG_FILE$8);
|
|
1300
|
-
if (!await fileExists$8(configPath)) return null;
|
|
1301
|
-
try {
|
|
1302
|
-
const content = await readFile(configPath, "utf-8");
|
|
1303
|
-
return JSON.parse(content);
|
|
1304
|
-
} catch {
|
|
1305
|
-
return null;
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
async function saveConfig$1(projectRoot, config) {
|
|
1309
|
-
const kuckitDir = join$1(projectRoot, KUCKIT_DIR$8);
|
|
1310
|
-
await mkdir(kuckitDir, { recursive: true });
|
|
1311
|
-
await writeFile(join$1(kuckitDir, CONFIG_FILE$8), JSON.stringify(config, null, 2), "utf-8");
|
|
1312
|
-
}
|
|
1313
|
-
async function infraInit(options) {
|
|
1314
|
-
console.log("Initializing kuckit infrastructure...\n");
|
|
1315
|
-
if (!await checkPulumiInstalled()) {
|
|
1316
|
-
console.error("Error: Pulumi CLI is not installed.");
|
|
1317
|
-
console.error("Install it from: https://www.pulumi.com/docs/install/");
|
|
1318
|
-
process.exit(1);
|
|
1319
|
-
}
|
|
1320
|
-
if (!await checkGcloudInstalled()) {
|
|
1321
|
-
console.error("Error: gcloud CLI is not installed.");
|
|
1322
|
-
console.error("Install it from: https://cloud.google.com/sdk/docs/install");
|
|
1323
|
-
process.exit(1);
|
|
1324
|
-
}
|
|
1325
|
-
const projectRoot = await findProjectRoot$8(process.cwd());
|
|
1326
|
-
if (!projectRoot) {
|
|
1327
|
-
console.error("Error: Could not find project root (no package.json found)");
|
|
1328
|
-
process.exit(1);
|
|
1329
|
-
}
|
|
1330
|
-
const infraDir = getInfraDir(projectRoot);
|
|
1331
|
-
if (!await fileExists$8(infraDir)) {
|
|
1332
|
-
console.error("Error: packages/infra not found.");
|
|
1333
|
-
console.error("Make sure you have the @kuckit/infra package in your project.");
|
|
1334
|
-
process.exit(1);
|
|
1335
|
-
}
|
|
1336
|
-
const existingConfig = await loadExistingConfig(projectRoot);
|
|
1337
|
-
const provider = options.provider ?? "gcp";
|
|
1338
|
-
if (provider !== "gcp") {
|
|
1339
|
-
console.error(`Error: Provider '${provider}' is not supported. Only 'gcp' is currently supported.`);
|
|
1340
|
-
process.exit(1);
|
|
1341
|
-
}
|
|
1342
|
-
let gcpProject = options.project ?? existingConfig?.gcpProject;
|
|
1343
|
-
if (!gcpProject) gcpProject = await input({
|
|
1344
|
-
message: "GCP Project ID:",
|
|
1345
|
-
validate: (value) => value.length > 0 ? true : "Project ID is required"
|
|
1346
|
-
});
|
|
1347
|
-
let region = options.region ?? existingConfig?.region ?? "us-central1";
|
|
1348
|
-
if (!options.region && !existingConfig?.region) region = await input({
|
|
1349
|
-
message: "GCP Region:",
|
|
1350
|
-
default: "us-central1"
|
|
1351
|
-
});
|
|
1352
|
-
let env = options.env ?? existingConfig?.env ?? "dev";
|
|
1353
|
-
if (!options.env && !existingConfig?.env) env = await input({
|
|
1354
|
-
message: "Environment (dev/prod):",
|
|
1355
|
-
default: "dev",
|
|
1356
|
-
validate: (value) => value === "dev" || value === "prod" ? true : "Must be dev or prod"
|
|
1357
|
-
});
|
|
1358
|
-
const stackName = `${gcpProject}-${env}`;
|
|
1359
|
-
console.log("\nConfiguration:");
|
|
1360
|
-
console.log(` Provider: ${provider}`);
|
|
1361
|
-
console.log(` GCP Project: ${gcpProject}`);
|
|
1362
|
-
console.log(` Region: ${region}`);
|
|
1363
|
-
console.log(` Environment: ${env}`);
|
|
1364
|
-
console.log(` Stack: ${stackName}`);
|
|
1365
|
-
console.log("");
|
|
1366
|
-
if (!options.yes) {
|
|
1367
|
-
if (!await confirm({
|
|
1368
|
-
message: "Proceed with infrastructure initialization?",
|
|
1369
|
-
default: true
|
|
1370
|
-
})) {
|
|
1371
|
-
console.log("Aborted.");
|
|
1372
|
-
process.exit(0);
|
|
1373
|
-
}
|
|
1374
|
-
}
|
|
1375
|
-
console.log("\nInstalling infrastructure dependencies...");
|
|
1376
|
-
const { spawn: spawn$1 } = await import("child_process");
|
|
1377
|
-
await new Promise((resolve, reject) => {
|
|
1378
|
-
spawn$1("bun", ["install"], {
|
|
1379
|
-
cwd: infraDir,
|
|
1380
|
-
stdio: "inherit",
|
|
1381
|
-
shell: true
|
|
1382
|
-
}).on("close", (code) => {
|
|
1383
|
-
if (code === 0) resolve();
|
|
1384
|
-
else reject(/* @__PURE__ */ new Error(`bun install failed with code ${code}`));
|
|
1385
|
-
});
|
|
1386
|
-
});
|
|
1387
|
-
console.log("\nBuilding infrastructure package...");
|
|
1388
|
-
await new Promise((resolve, reject) => {
|
|
1389
|
-
spawn$1("bun", ["run", "build"], {
|
|
1390
|
-
cwd: infraDir,
|
|
1391
|
-
stdio: "inherit",
|
|
1392
|
-
shell: true
|
|
1393
|
-
}).on("close", (code) => {
|
|
1394
|
-
if (code === 0) resolve();
|
|
1395
|
-
else reject(/* @__PURE__ */ new Error(`Build failed with code ${code}`));
|
|
1396
|
-
});
|
|
1397
|
-
});
|
|
1398
|
-
console.log(`\nSelecting Pulumi stack: ${stackName}`);
|
|
1399
|
-
if (!await selectOrCreateStack(stackName, { cwd: infraDir })) {
|
|
1400
|
-
console.error("Error: Failed to select or create Pulumi stack");
|
|
1401
|
-
process.exit(1);
|
|
1402
|
-
}
|
|
1403
|
-
console.log("Configuring stack...");
|
|
1404
|
-
await setPulumiConfig("gcp:project", gcpProject, { cwd: infraDir });
|
|
1405
|
-
await setPulumiConfig("gcp:region", region, { cwd: infraDir });
|
|
1406
|
-
await setPulumiConfig("env", env, { cwd: infraDir });
|
|
1407
|
-
console.log("\nCreating infrastructure...");
|
|
1408
|
-
console.log("This may take several minutes.\n");
|
|
1409
|
-
const result = await pulumiUp({
|
|
1410
|
-
cwd: infraDir,
|
|
1411
|
-
stream: true
|
|
1412
|
-
});
|
|
1413
|
-
if (result.code !== 0) {
|
|
1414
|
-
const parsed = parseError(result.stderr);
|
|
1415
|
-
console.error("\n" + formatError(parsed));
|
|
1416
|
-
process.exit(1);
|
|
1417
|
-
}
|
|
1418
|
-
console.log("\nRetrieving outputs...");
|
|
1419
|
-
const outputs = await getPulumiOutputs({ cwd: infraDir });
|
|
1420
|
-
await saveConfig$1(projectRoot, {
|
|
1421
|
-
provider: "gcp",
|
|
1422
|
-
gcpProject,
|
|
1423
|
-
region,
|
|
1424
|
-
projectName: "kuckit-infra",
|
|
1425
|
-
stackName,
|
|
1426
|
-
env,
|
|
1427
|
-
outputs: outputs ? {
|
|
1428
|
-
registryUrl: outputs.registryUrl,
|
|
1429
|
-
databaseConnectionName: outputs.databaseConnectionName,
|
|
1430
|
-
redisHost: outputs.redisHost,
|
|
1431
|
-
secretIds: {
|
|
1432
|
-
dbPassword: outputs.dbPasswordSecretId,
|
|
1433
|
-
redisAuth: outputs.redisAuthSecretId ?? ""
|
|
1434
|
-
}
|
|
1435
|
-
} : void 0,
|
|
1436
|
-
createdAt: existingConfig?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1437
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1438
|
-
});
|
|
1439
|
-
console.log(`\nConfiguration saved to ${KUCKIT_DIR$8}/${CONFIG_FILE$8}`);
|
|
1440
|
-
console.log("\n" + "=".repeat(60));
|
|
1441
|
-
console.log("Infrastructure initialized successfully!");
|
|
1442
|
-
console.log("=".repeat(60));
|
|
1443
|
-
if (outputs) {
|
|
1444
|
-
console.log("\nOutputs:");
|
|
1445
|
-
console.log(` Registry: ${outputs.registryUrl}`);
|
|
1446
|
-
console.log(` Database: ${outputs.databaseConnectionName}`);
|
|
1447
|
-
console.log(` Redis: ${outputs.redisHost}`);
|
|
1448
|
-
}
|
|
1449
|
-
console.log("\nNext steps:");
|
|
1450
|
-
console.log(" 1. Ensure you have a Dockerfile in your project root");
|
|
1451
|
-
console.log(` 2. Run: kuckit infra deploy --env ${env}`);
|
|
1452
|
-
console.log("");
|
|
1453
|
-
}
|
|
1454
|
-
|
|
1455
|
-
//#endregion
|
|
1456
|
-
//#region src/commands/infra/deploy.ts
|
|
1457
|
-
const KUCKIT_DIR$7 = ".kuckit";
|
|
1458
|
-
const CONFIG_FILE$7 = "infra.json";
|
|
1459
|
-
async function saveConfig(projectRoot, config) {
|
|
1460
|
-
const kuckitDir = join$1(projectRoot, KUCKIT_DIR$7);
|
|
1461
|
-
await mkdir(kuckitDir, { recursive: true });
|
|
1462
|
-
await writeFile(join$1(kuckitDir, CONFIG_FILE$7), JSON.stringify(config, null, 2), "utf-8");
|
|
1463
|
-
}
|
|
1464
|
-
/**
|
|
1465
|
-
* Get the current git commit short hash
|
|
1466
|
-
* Returns null if not in a git repo or git command fails
|
|
1467
|
-
*/
|
|
1468
|
-
function getGitShortHash() {
|
|
1469
|
-
try {
|
|
1470
|
-
return execSync("git rev-parse --short HEAD", { encoding: "utf-8" }).trim();
|
|
1471
|
-
} catch {
|
|
1472
|
-
return null;
|
|
1473
|
-
}
|
|
1474
|
-
}
|
|
1475
|
-
async function fileExists$7(path) {
|
|
1476
|
-
try {
|
|
1477
|
-
await access(path, constants$1.F_OK);
|
|
1478
|
-
return true;
|
|
1479
|
-
} catch {
|
|
1480
|
-
return false;
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
|
-
async function findProjectRoot$7(cwd) {
|
|
1484
|
-
let dir = cwd;
|
|
1485
|
-
while (dir !== dirname$1(dir)) {
|
|
1486
|
-
if (await fileExists$7(join$1(dir, "package.json"))) return dir;
|
|
1487
|
-
dir = dirname$1(dir);
|
|
1488
|
-
}
|
|
1489
|
-
return null;
|
|
1490
|
-
}
|
|
1491
|
-
async function loadConfig$7(projectRoot) {
|
|
1492
|
-
const configPath = join$1(projectRoot, KUCKIT_DIR$7, CONFIG_FILE$7);
|
|
1493
|
-
if (!await fileExists$7(configPath)) return null;
|
|
1494
|
-
try {
|
|
1495
|
-
const content = await readFile(configPath, "utf-8");
|
|
1496
|
-
return JSON.parse(content);
|
|
1497
|
-
} catch {
|
|
1498
|
-
return null;
|
|
1499
|
-
}
|
|
1500
|
-
}
|
|
1501
|
-
async function infraDeploy(options) {
|
|
1502
|
-
console.log("Deploying kuckit application...\n");
|
|
1503
|
-
if (!await checkPulumiInstalled()) {
|
|
1504
|
-
console.error("Error: Pulumi CLI is not installed.");
|
|
1505
|
-
console.error("Install it from: https://www.pulumi.com/docs/install/");
|
|
1506
|
-
process.exit(1);
|
|
1507
|
-
}
|
|
1508
|
-
if (!await checkGcloudInstalled()) {
|
|
1509
|
-
console.error("Error: gcloud CLI is not installed.");
|
|
1510
|
-
console.error("Install it from: https://cloud.google.com/sdk/docs/install");
|
|
1511
|
-
process.exit(1);
|
|
1512
|
-
}
|
|
1513
|
-
const projectRoot = await findProjectRoot$7(process.cwd());
|
|
1514
|
-
if (!projectRoot) {
|
|
1515
|
-
console.error("Error: Could not find project root (no package.json found)");
|
|
1516
|
-
process.exit(1);
|
|
1517
|
-
}
|
|
1518
|
-
const config = await loadConfig$7(projectRoot);
|
|
1519
|
-
if (!config) {
|
|
1520
|
-
console.error("Error: No infrastructure configuration found.");
|
|
1521
|
-
console.error("Run: kuckit infra init");
|
|
1522
|
-
process.exit(1);
|
|
1523
|
-
}
|
|
1524
|
-
const env = options.env ?? config.env;
|
|
1525
|
-
if (env !== "dev" && env !== "prod") {
|
|
1526
|
-
console.error(`Error: Invalid environment '${env}'. Must be 'dev' or 'prod'.`);
|
|
1527
|
-
process.exit(1);
|
|
1528
|
-
}
|
|
1529
|
-
const infraDir = getInfraDir(projectRoot);
|
|
1530
|
-
if (!await fileExists$7(infraDir)) {
|
|
1531
|
-
console.error("Error: packages/infra not found.");
|
|
1532
|
-
process.exit(1);
|
|
1533
|
-
}
|
|
1534
|
-
const dockerfilePath = join$1(projectRoot, "Dockerfile");
|
|
1535
|
-
if (!options.skipBuild && !options.image && !await fileExists$7(dockerfilePath)) {
|
|
1536
|
-
console.error("Error: Dockerfile not found in project root.");
|
|
1537
|
-
console.error("Create a Dockerfile or use --skip-build with an existing image.");
|
|
1538
|
-
process.exit(1);
|
|
1539
|
-
}
|
|
1540
|
-
if (!config.outputs?.registryUrl) {
|
|
1541
|
-
console.error("Error: No registry URL found in configuration.");
|
|
1542
|
-
console.error("Run: kuckit infra init");
|
|
1543
|
-
process.exit(1);
|
|
1544
|
-
}
|
|
1545
|
-
console.log("Configuration:");
|
|
1546
|
-
console.log(` Project: ${config.gcpProject}`);
|
|
1547
|
-
console.log(` Region: ${config.region}`);
|
|
1548
|
-
console.log(` Environment: ${env}`);
|
|
1549
|
-
console.log(` Registry: ${config.outputs.registryUrl}`);
|
|
1550
|
-
if (options.preview) console.log(" Mode: Preview (no changes will be applied)");
|
|
1551
|
-
console.log("");
|
|
1552
|
-
let imageUrl;
|
|
1553
|
-
if (options.image) {
|
|
1554
|
-
imageUrl = options.image;
|
|
1555
|
-
console.log(`Using provided image: ${imageUrl}\n`);
|
|
1556
|
-
} else if (options.skipBuild) {
|
|
1557
|
-
imageUrl = `${config.outputs.registryUrl}/kuckit:latest`;
|
|
1558
|
-
console.log(`Using existing image: ${imageUrl}\n`);
|
|
1559
|
-
} else {
|
|
1560
|
-
console.log("Building and pushing Docker image...");
|
|
1561
|
-
console.log("(Using Cloud Build - no local Docker required)\n");
|
|
1562
|
-
const buildResult = await buildAndPushImage({
|
|
1563
|
-
project: config.gcpProject,
|
|
1564
|
-
registryUrl: config.outputs.registryUrl,
|
|
1565
|
-
tag: process.env.GIT_SHA ?? process.env.GITHUB_SHA ?? getGitShortHash() ?? "latest",
|
|
1566
|
-
cwd: projectRoot
|
|
1567
|
-
});
|
|
1568
|
-
if (buildResult.code !== 0) {
|
|
1569
|
-
console.error("\nError: Docker build failed.");
|
|
1570
|
-
process.exit(1);
|
|
1571
|
-
}
|
|
1572
|
-
imageUrl = buildResult.imageUrl;
|
|
1573
|
-
console.log(`\nImage built: ${imageUrl}\n`);
|
|
1574
|
-
}
|
|
1575
|
-
if (!options.yes && !options.preview) {
|
|
1576
|
-
if (!await confirm({
|
|
1577
|
-
message: "Proceed with deployment?",
|
|
1578
|
-
default: true
|
|
1579
|
-
})) {
|
|
1580
|
-
console.log("Aborted.");
|
|
1581
|
-
process.exit(0);
|
|
1582
|
-
}
|
|
1583
|
-
}
|
|
1584
|
-
const stackName = config.stackName;
|
|
1585
|
-
console.log(`Selecting Pulumi stack: ${stackName}`);
|
|
1586
|
-
if (!await selectOrCreateStack(stackName, { cwd: infraDir })) {
|
|
1587
|
-
console.error("Error: Failed to select Pulumi stack");
|
|
1588
|
-
process.exit(1);
|
|
1589
|
-
}
|
|
1590
|
-
console.log("Configuring deployment...");
|
|
1591
|
-
await setPulumiConfig("imageUrl", imageUrl, { cwd: infraDir });
|
|
1592
|
-
if (options.preview) console.log("\nPreviewing changes...\n");
|
|
1593
|
-
else console.log("\nDeploying to Cloud Run...\n");
|
|
1594
|
-
const result = await pulumiUp({
|
|
1595
|
-
cwd: infraDir,
|
|
1596
|
-
stream: true,
|
|
1597
|
-
preview: options.preview
|
|
1598
|
-
});
|
|
1599
|
-
if (result.code !== 0) {
|
|
1600
|
-
const parsed = parseError(result.stderr);
|
|
1601
|
-
console.error("\n" + formatError(parsed));
|
|
1602
|
-
process.exit(1);
|
|
1603
|
-
}
|
|
1604
|
-
if (!options.preview) {
|
|
1605
|
-
console.log("\nRetrieving deployment info...");
|
|
1606
|
-
const outputs = await getPulumiOutputs({ cwd: infraDir });
|
|
1607
|
-
if (outputs) {
|
|
1608
|
-
config.outputs = {
|
|
1609
|
-
...config.outputs,
|
|
1610
|
-
...outputs
|
|
1611
|
-
};
|
|
1612
|
-
config.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1613
|
-
await saveConfig(projectRoot, config);
|
|
1614
|
-
}
|
|
1615
|
-
console.log("\n" + "=".repeat(60));
|
|
1616
|
-
console.log("Deployment successful!");
|
|
1617
|
-
console.log("=".repeat(60));
|
|
1618
|
-
if (outputs?.serviceUrl) console.log(`\nService URL: ${outputs.serviceUrl}`);
|
|
1619
|
-
if (outputs?.migrationJobName) console.log(`Migration Job: ${outputs.migrationJobName}`);
|
|
1620
|
-
console.log("\nUseful commands:");
|
|
1621
|
-
console.log(` View logs: kuckit infra logs --env ${env}`);
|
|
1622
|
-
console.log(` Check status: kuckit infra status --env ${env}`);
|
|
1623
|
-
console.log(` Run migrations: kuckit infra db:migrate --env ${env}`);
|
|
1624
|
-
console.log("");
|
|
1625
|
-
} else {
|
|
1626
|
-
console.log("\nPreview complete. No changes were applied.");
|
|
1627
|
-
console.log("Run without --preview to apply changes.");
|
|
1628
|
-
}
|
|
1629
|
-
}
|
|
1630
|
-
|
|
1631
2058
|
//#endregion
|
|
1632
2059
|
//#region src/commands/infra/destroy.ts
|
|
1633
2060
|
const KUCKIT_DIR$6 = ".kuckit";
|
|
@@ -2594,6 +3021,10 @@ db.command("studio").description("Open Drizzle Studio with all module schemas").
|
|
|
2594
3021
|
});
|
|
2595
3022
|
registerAuthCommands(program);
|
|
2596
3023
|
const infra = program.command("infra").description("Infrastructure deployment and management");
|
|
3024
|
+
infra.command("up").description("Initialize (if needed) and deploy infrastructure in one command").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("--preview", "Preview changes without applying", false).option("-y, --yes", "Skip confirmation prompts", false).action(async (options) => {
|
|
3025
|
+
requireAuth();
|
|
3026
|
+
await infraUp(options);
|
|
3027
|
+
});
|
|
2597
3028
|
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) => {
|
|
2598
3029
|
requireAuth();
|
|
2599
3030
|
await infraInit(options);
|
|
@@ -2602,6 +3033,10 @@ infra.command("deploy").description("Build and deploy application to Cloud Run")
|
|
|
2602
3033
|
requireAuth();
|
|
2603
3034
|
await infraDeploy(options);
|
|
2604
3035
|
});
|
|
3036
|
+
infra.command("eject").description("Eject provider infrastructure code to local project for customization").option("-d, --dir <directory>", "Target directory for ejected code", "infra").option("--force", "Overwrite existing directory", false).action(async (options) => {
|
|
3037
|
+
requireAuth();
|
|
3038
|
+
await infraEject(options);
|
|
3039
|
+
});
|
|
2605
3040
|
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) => {
|
|
2606
3041
|
requireAuth();
|
|
2607
3042
|
await infraDestroy(options);
|
|
@@ -2637,4 +3072,5 @@ infra.command("outputs").description("Display infrastructure outputs (URLs, conn
|
|
|
2637
3072
|
program.parse();
|
|
2638
3073
|
|
|
2639
3074
|
//#endregion
|
|
2640
|
-
export { };
|
|
3075
|
+
export { };
|
|
3076
|
+
//# sourceMappingURL=bin.js.map
|