@kuckit/cli 0.1.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/README.md +95 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +784 -0
- package/dist/discover-module-DpVX5MIS.js +579 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +3 -0
- package/package.json +55 -0
package/dist/bin.js
ADDED
|
@@ -0,0 +1,784 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { n as addModule, r as generateModule, t as discoverModules } from "./discover-module-DpVX5MIS.js";
|
|
3
|
+
import { program } from "commander";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { tryLoadKuckitConfig } from "@kuckit/sdk";
|
|
7
|
+
import { spawn } from "child_process";
|
|
8
|
+
import { access, constants as constants$1, mkdir, readFile, writeFile } from "fs/promises";
|
|
9
|
+
import { dirname as dirname$1, join as join$1 } from "path";
|
|
10
|
+
import { homedir } from "node:os";
|
|
11
|
+
|
|
12
|
+
//#region src/commands/doctor.ts
|
|
13
|
+
const CONFIG_FILES = [
|
|
14
|
+
"kuckit.config.ts",
|
|
15
|
+
"kuckit.config.js",
|
|
16
|
+
"kuckit.config.mjs"
|
|
17
|
+
];
|
|
18
|
+
function findConfigFile(cwd) {
|
|
19
|
+
let dir = cwd;
|
|
20
|
+
while (dir !== dirname(dir)) {
|
|
21
|
+
for (const file of CONFIG_FILES) {
|
|
22
|
+
const configPath = join(dir, file);
|
|
23
|
+
if (existsSync(configPath)) return configPath;
|
|
24
|
+
}
|
|
25
|
+
dir = dirname(dir);
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
function readPackageJson(packageName, cwd) {
|
|
30
|
+
const locations = [
|
|
31
|
+
join(cwd, "node_modules", packageName, "package.json"),
|
|
32
|
+
join(cwd, "apps", "server", "node_modules", packageName, "package.json"),
|
|
33
|
+
join(cwd, "apps", "web", "node_modules", packageName, "package.json")
|
|
34
|
+
];
|
|
35
|
+
const packageDir = packageName.startsWith("@") ? packageName.split("/")[1] : packageName;
|
|
36
|
+
locations.push(join(cwd, "packages", packageDir, "package.json"));
|
|
37
|
+
for (const location of locations) try {
|
|
38
|
+
const content = readFileSync(location, "utf-8");
|
|
39
|
+
const pkg = JSON.parse(content);
|
|
40
|
+
if (pkg.name === packageName) return pkg;
|
|
41
|
+
} catch {}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
function findPackagePath(packageName, cwd) {
|
|
45
|
+
const locations = [
|
|
46
|
+
join(cwd, "node_modules", packageName),
|
|
47
|
+
join(cwd, "apps", "server", "node_modules", packageName),
|
|
48
|
+
join(cwd, "apps", "web", "node_modules", packageName)
|
|
49
|
+
];
|
|
50
|
+
const packageDir = packageName.startsWith("@") ? packageName.split("/")[1] : packageName;
|
|
51
|
+
locations.push(join(cwd, "packages", packageDir));
|
|
52
|
+
for (const location of locations) if (existsSync(location)) {
|
|
53
|
+
const pkgJsonPath = join(location, "package.json");
|
|
54
|
+
if (existsSync(pkgJsonPath)) try {
|
|
55
|
+
const content = readFileSync(pkgJsonPath, "utf-8");
|
|
56
|
+
if (JSON.parse(content).name === packageName) return location;
|
|
57
|
+
} catch {}
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
function checkModuleExports(packageName, cwd) {
|
|
62
|
+
const result = {
|
|
63
|
+
server: false,
|
|
64
|
+
client: false
|
|
65
|
+
};
|
|
66
|
+
const packagePath = findPackagePath(packageName, cwd);
|
|
67
|
+
if (!packagePath) return result;
|
|
68
|
+
result.server = [
|
|
69
|
+
join(packagePath, "dist", "server", "module.js"),
|
|
70
|
+
join(packagePath, "dist", "index.js"),
|
|
71
|
+
join(packagePath, "src", "server", "module.ts"),
|
|
72
|
+
join(packagePath, "src", "module.ts")
|
|
73
|
+
].some((p) => existsSync(p));
|
|
74
|
+
result.client = [
|
|
75
|
+
join(packagePath, "dist", "client", "index.js"),
|
|
76
|
+
join(packagePath, "src", "client", "index.tsx"),
|
|
77
|
+
join(packagePath, "src", "client", "index.ts")
|
|
78
|
+
].some((p) => existsSync(p));
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
async function loadConfig$1(configPath) {
|
|
82
|
+
try {
|
|
83
|
+
if (configPath.endsWith(".ts")) {
|
|
84
|
+
const { createJiti } = await import("jiti");
|
|
85
|
+
const loaded = await createJiti(process.cwd(), { interopDefault: true }).import(configPath);
|
|
86
|
+
return {
|
|
87
|
+
success: true,
|
|
88
|
+
config: loaded.default ?? loaded
|
|
89
|
+
};
|
|
90
|
+
} else {
|
|
91
|
+
const loaded = await import(configPath);
|
|
92
|
+
return {
|
|
93
|
+
success: true,
|
|
94
|
+
config: loaded.default ?? loaded
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
} catch (err) {
|
|
98
|
+
return {
|
|
99
|
+
success: false,
|
|
100
|
+
error: err instanceof Error ? err.message : String(err)
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async function doctor(options) {
|
|
105
|
+
const cwd = process.cwd();
|
|
106
|
+
const checks = [];
|
|
107
|
+
if (!options.json) console.log("\nKuckit Doctor - Checking your setup...\n");
|
|
108
|
+
const configPath = findConfigFile(cwd);
|
|
109
|
+
if (configPath) checks.push({
|
|
110
|
+
name: "config-exists",
|
|
111
|
+
status: "pass",
|
|
112
|
+
message: `Config file found: ${configPath.replace(cwd, ".")}`
|
|
113
|
+
});
|
|
114
|
+
else checks.push({
|
|
115
|
+
name: "config-exists",
|
|
116
|
+
status: "warn",
|
|
117
|
+
message: "No kuckit.config.ts found (using legacy config)"
|
|
118
|
+
});
|
|
119
|
+
let configModules = [];
|
|
120
|
+
if (configPath) {
|
|
121
|
+
const { success, config, error } = await loadConfig$1(configPath);
|
|
122
|
+
if (success && config && typeof config === "object") {
|
|
123
|
+
const cfg = config;
|
|
124
|
+
if (Array.isArray(cfg.modules)) {
|
|
125
|
+
checks.push({
|
|
126
|
+
name: "config-valid",
|
|
127
|
+
status: "pass",
|
|
128
|
+
message: "Configuration is valid"
|
|
129
|
+
});
|
|
130
|
+
configModules = cfg.modules;
|
|
131
|
+
} else checks.push({
|
|
132
|
+
name: "config-valid",
|
|
133
|
+
status: "fail",
|
|
134
|
+
message: "Configuration invalid: 'modules' must be an array"
|
|
135
|
+
});
|
|
136
|
+
} else checks.push({
|
|
137
|
+
name: "config-valid",
|
|
138
|
+
status: "fail",
|
|
139
|
+
message: `Configuration failed to load: ${error}`
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
const enabledModules = configModules.filter((m) => m.package && !m.disabled);
|
|
143
|
+
if (enabledModules.length > 0) {
|
|
144
|
+
const moduleDetails = enabledModules.map((m) => ` - ${m.package} ${m.disabled ? "(disabled)" : "(enabled)"}`);
|
|
145
|
+
checks.push({
|
|
146
|
+
name: "modules-configured",
|
|
147
|
+
status: "pass",
|
|
148
|
+
message: `${enabledModules.length} module(s) configured`,
|
|
149
|
+
details: moduleDetails
|
|
150
|
+
});
|
|
151
|
+
} else if (configModules.length > 0) checks.push({
|
|
152
|
+
name: "modules-configured",
|
|
153
|
+
status: "warn",
|
|
154
|
+
message: "All configured modules are disabled"
|
|
155
|
+
});
|
|
156
|
+
else checks.push({
|
|
157
|
+
name: "modules-configured",
|
|
158
|
+
status: "warn",
|
|
159
|
+
message: "No modules configured"
|
|
160
|
+
});
|
|
161
|
+
const missingPackages = [];
|
|
162
|
+
const installedPackages = [];
|
|
163
|
+
for (const mod of enabledModules) {
|
|
164
|
+
if (!mod.package) continue;
|
|
165
|
+
if (readPackageJson(mod.package, cwd)) installedPackages.push(mod.package);
|
|
166
|
+
else missingPackages.push(mod.package);
|
|
167
|
+
}
|
|
168
|
+
if (missingPackages.length > 0) checks.push({
|
|
169
|
+
name: "packages-installed",
|
|
170
|
+
status: "fail",
|
|
171
|
+
message: `${missingPackages.length} package(s) not installed`,
|
|
172
|
+
details: missingPackages.map((p) => ` - ${p} (missing)`)
|
|
173
|
+
});
|
|
174
|
+
else if (installedPackages.length > 0) checks.push({
|
|
175
|
+
name: "packages-installed",
|
|
176
|
+
status: "pass",
|
|
177
|
+
message: "All module packages installed"
|
|
178
|
+
});
|
|
179
|
+
const invalidMetadata = [];
|
|
180
|
+
const validMetadata = [];
|
|
181
|
+
for (const packageName of installedPackages) if (!readPackageJson(packageName, cwd)?.kuckit?.id) invalidMetadata.push(`${packageName} (missing kuckit.id)`);
|
|
182
|
+
else validMetadata.push(packageName);
|
|
183
|
+
if (invalidMetadata.length > 0) checks.push({
|
|
184
|
+
name: "metadata-valid",
|
|
185
|
+
status: "warn",
|
|
186
|
+
message: `${invalidMetadata.length} package(s) with invalid/missing kuckit metadata`,
|
|
187
|
+
details: invalidMetadata.map((p) => ` - ${p}`)
|
|
188
|
+
});
|
|
189
|
+
else if (validMetadata.length > 0) checks.push({
|
|
190
|
+
name: "metadata-valid",
|
|
191
|
+
status: "pass",
|
|
192
|
+
message: "All modules have valid kuckit metadata"
|
|
193
|
+
});
|
|
194
|
+
const missingExports = [];
|
|
195
|
+
const validExports = [];
|
|
196
|
+
for (const packageName of validMetadata) {
|
|
197
|
+
const pkgJson = readPackageJson(packageName, cwd);
|
|
198
|
+
const exports = checkModuleExports(packageName, cwd);
|
|
199
|
+
if (!exports.server && pkgJson?.kuckit?.server) missingExports.push(`${packageName} (server export not built)`);
|
|
200
|
+
else if (!exports.client && pkgJson?.kuckit?.client) missingExports.push(`${packageName} (client export not built)`);
|
|
201
|
+
else validExports.push(packageName);
|
|
202
|
+
}
|
|
203
|
+
if (missingExports.length > 0) checks.push({
|
|
204
|
+
name: "exports-valid",
|
|
205
|
+
status: "warn",
|
|
206
|
+
message: `${missingExports.length} module(s) may need building`,
|
|
207
|
+
details: missingExports.map((p) => ` - ${p}`)
|
|
208
|
+
});
|
|
209
|
+
else if (validExports.length > 0) checks.push({
|
|
210
|
+
name: "exports-valid",
|
|
211
|
+
status: "pass",
|
|
212
|
+
message: "Module exports valid"
|
|
213
|
+
});
|
|
214
|
+
const sdkPackages = ["@kuckit/sdk", "@kuckit/sdk-react"];
|
|
215
|
+
const missingSdk = [];
|
|
216
|
+
for (const sdk of sdkPackages) if (!readPackageJson(sdk, cwd)) missingSdk.push(sdk);
|
|
217
|
+
if (missingSdk.length > 0) checks.push({
|
|
218
|
+
name: "sdk-installed",
|
|
219
|
+
status: "warn",
|
|
220
|
+
message: `SDK package(s) not found: ${missingSdk.join(", ")}`
|
|
221
|
+
});
|
|
222
|
+
else checks.push({
|
|
223
|
+
name: "sdk-installed",
|
|
224
|
+
status: "pass",
|
|
225
|
+
message: "SDK packages installed"
|
|
226
|
+
});
|
|
227
|
+
const report = {
|
|
228
|
+
success: checks.every((c) => c.status !== "fail"),
|
|
229
|
+
checks,
|
|
230
|
+
summary: {
|
|
231
|
+
total: checks.length,
|
|
232
|
+
passed: checks.filter((c) => c.status === "pass").length,
|
|
233
|
+
failed: checks.filter((c) => c.status === "fail").length,
|
|
234
|
+
warnings: checks.filter((c) => c.status === "warn").length
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
if (options.json) console.log(JSON.stringify(report, null, 2));
|
|
238
|
+
else {
|
|
239
|
+
for (const check of checks) {
|
|
240
|
+
const icon = check.status === "pass" ? "✓" : check.status === "fail" ? "✗" : "!";
|
|
241
|
+
const color = check.status === "pass" ? "\x1B[32m" : check.status === "fail" ? "\x1B[31m" : "\x1B[33m";
|
|
242
|
+
console.log(`${color}${icon}[0m ${check.message}`);
|
|
243
|
+
if (check.details) for (const detail of check.details) console.log(` ${detail}`);
|
|
244
|
+
}
|
|
245
|
+
console.log("");
|
|
246
|
+
if (report.success) if (report.summary.warnings > 0) console.log(`All checks passed with ${report.summary.warnings} warning(s). Run 'kuckit doctor --json' for details.`);
|
|
247
|
+
else console.log("All checks passed!");
|
|
248
|
+
else {
|
|
249
|
+
console.log(`${report.summary.failed} check(s) failed. Please fix the issues above and run 'kuckit doctor' again.`);
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
//#endregion
|
|
256
|
+
//#region src/commands/search.ts
|
|
257
|
+
const NPM_REGISTRY_SEARCH = "https://registry.npmjs.org/-/v1/search";
|
|
258
|
+
function scoreToStars(score) {
|
|
259
|
+
const normalizedScore = score > 1 ? score / 100 : score;
|
|
260
|
+
const safeScore = Math.max(0, Math.min(1, normalizedScore || 0));
|
|
261
|
+
const filled = Math.round(safeScore * 5);
|
|
262
|
+
return "★".repeat(filled) + "☆".repeat(5 - filled);
|
|
263
|
+
}
|
|
264
|
+
function truncate(str, maxLen) {
|
|
265
|
+
if (str.length <= maxLen) return str;
|
|
266
|
+
return str.slice(0, maxLen - 3) + "...";
|
|
267
|
+
}
|
|
268
|
+
async function searchNpm(query, limit) {
|
|
269
|
+
const url = new URL(NPM_REGISTRY_SEARCH);
|
|
270
|
+
url.searchParams.set("text", query);
|
|
271
|
+
url.searchParams.set("size", String(Math.min(limit, 250)));
|
|
272
|
+
const response = await fetch(url.toString(), { headers: { Accept: "application/json" } });
|
|
273
|
+
if (!response.ok) throw new Error(`npm registry returned ${response.status}: ${response.statusText}`);
|
|
274
|
+
return await response.json();
|
|
275
|
+
}
|
|
276
|
+
function isLikelyKuckitModule(pkg) {
|
|
277
|
+
return (pkg.keywords ?? []).some((k) => k.toLowerCase().includes("kuckit")) || pkg.name.toLowerCase().includes("kuckit");
|
|
278
|
+
}
|
|
279
|
+
function formatResult(result) {
|
|
280
|
+
const pkg = result.package;
|
|
281
|
+
return {
|
|
282
|
+
name: pkg.name,
|
|
283
|
+
version: pkg.version,
|
|
284
|
+
description: pkg.description ?? "",
|
|
285
|
+
score: result.score.final,
|
|
286
|
+
stars: scoreToStars(result.score.final),
|
|
287
|
+
isKuckitModule: isLikelyKuckitModule(pkg),
|
|
288
|
+
npm: pkg.links?.npm,
|
|
289
|
+
homepage: pkg.links?.homepage
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
async function search(keyword, options) {
|
|
293
|
+
if (!keyword.trim()) {
|
|
294
|
+
console.error("Error: Please provide a search keyword");
|
|
295
|
+
process.exit(1);
|
|
296
|
+
}
|
|
297
|
+
try {
|
|
298
|
+
let response = await searchNpm(`keywords:kuckit-module ${keyword}`, options.limit);
|
|
299
|
+
let fallback = false;
|
|
300
|
+
if (response.objects.length === 0) {
|
|
301
|
+
fallback = true;
|
|
302
|
+
response = await searchNpm(`kuckit ${keyword}`, options.limit);
|
|
303
|
+
}
|
|
304
|
+
const results = response.objects.map(formatResult);
|
|
305
|
+
const report = {
|
|
306
|
+
query: keyword,
|
|
307
|
+
total: results.length,
|
|
308
|
+
results
|
|
309
|
+
};
|
|
310
|
+
if (options.json) {
|
|
311
|
+
console.log(JSON.stringify(report, null, 2));
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (results.length === 0) {
|
|
315
|
+
console.log(`No Kuckit modules found for "${keyword}"`);
|
|
316
|
+
console.log("\nTips:");
|
|
317
|
+
console.log(" - Try different keywords");
|
|
318
|
+
console.log(" - Check npm directly: npm search kuckit");
|
|
319
|
+
console.log(" - Create your own: kuckit generate module <name>");
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
console.log(`\nResults for "${keyword}"${fallback ? " (broad search)" : ""}:\n`);
|
|
323
|
+
const maxNameLen = Math.min(40, Math.max(...results.map((r) => r.name.length)));
|
|
324
|
+
const maxVerLen = Math.max(...results.map((r) => r.version.length));
|
|
325
|
+
for (const result of results) {
|
|
326
|
+
const name = result.name.padEnd(maxNameLen);
|
|
327
|
+
const version = `v${result.version}`.padEnd(maxVerLen + 1);
|
|
328
|
+
const desc = truncate(result.description, 50);
|
|
329
|
+
const kuckitBadge = result.isKuckitModule ? "" : " [?]";
|
|
330
|
+
console.log(` ${name} ${version} ${result.stars} ${desc}${kuckitBadge}`);
|
|
331
|
+
}
|
|
332
|
+
console.log(`\n${results.length} package${results.length === 1 ? "" : "s"} found`);
|
|
333
|
+
if (results.some((r) => !r.isKuckitModule)) console.log("\n[?] = May not be a Kuckit module (verify before installing)");
|
|
334
|
+
if (results.length > 0) console.log(`\nInstall: kuckit add ${results[0].name}`);
|
|
335
|
+
} catch (error) {
|
|
336
|
+
if (options.json) console.log(JSON.stringify({
|
|
337
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
338
|
+
query: keyword,
|
|
339
|
+
total: 0,
|
|
340
|
+
results: []
|
|
341
|
+
}));
|
|
342
|
+
else console.error(`Error searching npm registry: ${error instanceof Error ? error.message : error}`);
|
|
343
|
+
process.exit(1);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
//#endregion
|
|
348
|
+
//#region src/commands/db.ts
|
|
349
|
+
const KUCKIT_DIR = ".kuckit";
|
|
350
|
+
const TEMP_CONFIG_NAME = "drizzle.config.ts";
|
|
351
|
+
async function fileExists(path) {
|
|
352
|
+
try {
|
|
353
|
+
await access(path, constants$1.F_OK);
|
|
354
|
+
return true;
|
|
355
|
+
} catch {
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
async function findProjectRoot(cwd) {
|
|
360
|
+
let dir = cwd;
|
|
361
|
+
while (dir !== dirname$1(dir)) {
|
|
362
|
+
if (await fileExists(join$1(dir, "package.json"))) return dir;
|
|
363
|
+
dir = dirname$1(dir);
|
|
364
|
+
}
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
async function getDatabaseUrl(cwd, options) {
|
|
368
|
+
if (options.url) return options.url;
|
|
369
|
+
if (process.env.DATABASE_URL) return process.env.DATABASE_URL;
|
|
370
|
+
const envPaths = [
|
|
371
|
+
join$1(cwd, ".env"),
|
|
372
|
+
join$1(cwd, "apps", "server", ".env"),
|
|
373
|
+
join$1(cwd, ".env.local")
|
|
374
|
+
];
|
|
375
|
+
for (const envPath of envPaths) if (await fileExists(envPath)) try {
|
|
376
|
+
const match = (await readFile(envPath, "utf-8")).match(/^DATABASE_URL=(.+)$/m);
|
|
377
|
+
if (match) return match[1].replace(/^["']|["']$/g, "");
|
|
378
|
+
} catch {}
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
async function discoverModuleSchemas(cwd) {
|
|
382
|
+
const schemas = [];
|
|
383
|
+
const config = await tryLoadKuckitConfig(cwd);
|
|
384
|
+
if (!config) return schemas;
|
|
385
|
+
for (const moduleSpec of config.modules) {
|
|
386
|
+
if (moduleSpec.disabled) continue;
|
|
387
|
+
let packagePath = null;
|
|
388
|
+
let moduleId = null;
|
|
389
|
+
if (moduleSpec.package) {
|
|
390
|
+
const workspacePath = join$1(cwd, "packages", moduleSpec.package.replace(/^@[^/]+\//, ""));
|
|
391
|
+
if (await fileExists(join$1(workspacePath, "package.json"))) packagePath = workspacePath;
|
|
392
|
+
else {
|
|
393
|
+
const nodeModulesPath = join$1(cwd, "node_modules", moduleSpec.package);
|
|
394
|
+
if (await fileExists(join$1(nodeModulesPath, "package.json"))) packagePath = nodeModulesPath;
|
|
395
|
+
}
|
|
396
|
+
if (packagePath) try {
|
|
397
|
+
moduleId = JSON.parse(await readFile(join$1(packagePath, "package.json"), "utf-8")).kuckit?.id || moduleSpec.package;
|
|
398
|
+
} catch {
|
|
399
|
+
moduleId = moduleSpec.package;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
if (!packagePath) continue;
|
|
403
|
+
const schemaLocations = [
|
|
404
|
+
join$1(packagePath, "src", "server", "schema", "index.ts"),
|
|
405
|
+
join$1(packagePath, "src", "server", "schema.ts"),
|
|
406
|
+
join$1(packagePath, "src", "schema", "index.ts"),
|
|
407
|
+
join$1(packagePath, "src", "schema.ts")
|
|
408
|
+
];
|
|
409
|
+
for (const schemaPath of schemaLocations) if (await fileExists(schemaPath)) {
|
|
410
|
+
schemas.push({
|
|
411
|
+
moduleId: moduleId || "unknown",
|
|
412
|
+
schemaPath
|
|
413
|
+
});
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
const centralSchemaPath = join$1(cwd, "packages", "db", "src", "schema");
|
|
418
|
+
if (await fileExists(centralSchemaPath)) schemas.push({
|
|
419
|
+
moduleId: "core",
|
|
420
|
+
schemaPath: centralSchemaPath
|
|
421
|
+
});
|
|
422
|
+
return schemas;
|
|
423
|
+
}
|
|
424
|
+
async function generateTempDrizzleConfig(cwd, databaseUrl, schemas) {
|
|
425
|
+
const kuckitDir = join$1(cwd, KUCKIT_DIR);
|
|
426
|
+
await mkdir(kuckitDir, { recursive: true });
|
|
427
|
+
const configPath = join$1(kuckitDir, TEMP_CONFIG_NAME);
|
|
428
|
+
const schemaPaths = schemas.map((s) => s.schemaPath);
|
|
429
|
+
await writeFile(configPath, `import { defineConfig } from 'drizzle-kit'
|
|
430
|
+
|
|
431
|
+
export default defineConfig({
|
|
432
|
+
schema: ${JSON.stringify(schemaPaths)},
|
|
433
|
+
out: './drizzle',
|
|
434
|
+
dialect: 'postgresql',
|
|
435
|
+
dbCredentials: {
|
|
436
|
+
url: ${JSON.stringify(databaseUrl)},
|
|
437
|
+
},
|
|
438
|
+
})
|
|
439
|
+
`, "utf-8");
|
|
440
|
+
return configPath;
|
|
441
|
+
}
|
|
442
|
+
function runDrizzleKit(command, configPath, cwd) {
|
|
443
|
+
return new Promise((resolve) => {
|
|
444
|
+
const proc = spawn("npx", ["drizzle-kit", ...[
|
|
445
|
+
command,
|
|
446
|
+
"--config",
|
|
447
|
+
configPath
|
|
448
|
+
]], {
|
|
449
|
+
cwd,
|
|
450
|
+
stdio: [
|
|
451
|
+
"inherit",
|
|
452
|
+
"pipe",
|
|
453
|
+
"pipe"
|
|
454
|
+
],
|
|
455
|
+
shell: true
|
|
456
|
+
});
|
|
457
|
+
let stdout = "";
|
|
458
|
+
let stderr = "";
|
|
459
|
+
proc.stdout?.on("data", (data) => {
|
|
460
|
+
stdout += data.toString();
|
|
461
|
+
process.stdout.write(data);
|
|
462
|
+
});
|
|
463
|
+
proc.stderr?.on("data", (data) => {
|
|
464
|
+
stderr += data.toString();
|
|
465
|
+
process.stderr.write(data);
|
|
466
|
+
});
|
|
467
|
+
proc.on("close", (code) => {
|
|
468
|
+
resolve({
|
|
469
|
+
code: code ?? 1,
|
|
470
|
+
stdout,
|
|
471
|
+
stderr
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
async function runDbCommand(command, options) {
|
|
477
|
+
const projectRoot = await findProjectRoot(process.cwd());
|
|
478
|
+
if (!projectRoot) {
|
|
479
|
+
console.error("Error: Could not find project root (no package.json found)");
|
|
480
|
+
process.exit(1);
|
|
481
|
+
}
|
|
482
|
+
const databaseUrl = await getDatabaseUrl(projectRoot, options);
|
|
483
|
+
if (!databaseUrl) {
|
|
484
|
+
console.error("Error: DATABASE_URL not found");
|
|
485
|
+
console.error("Set it via:");
|
|
486
|
+
console.error(" - --url flag: kuckit db push --url postgres://...");
|
|
487
|
+
console.error(" - Environment variable: DATABASE_URL=postgres://...");
|
|
488
|
+
console.error(" - .env file in project root or apps/server/");
|
|
489
|
+
process.exit(1);
|
|
490
|
+
}
|
|
491
|
+
console.log("Discovering module schemas...");
|
|
492
|
+
const schemas = await discoverModuleSchemas(projectRoot);
|
|
493
|
+
if (schemas.length === 0) {
|
|
494
|
+
console.error("Error: No schemas found");
|
|
495
|
+
console.error("Make sure your modules have schemas in one of these locations:");
|
|
496
|
+
console.error(" - src/server/schema/index.ts");
|
|
497
|
+
console.error(" - src/server/schema.ts");
|
|
498
|
+
console.error(" - src/schema/index.ts");
|
|
499
|
+
console.error(" - src/schema.ts");
|
|
500
|
+
process.exit(1);
|
|
501
|
+
}
|
|
502
|
+
console.log(`Found ${schemas.length} schema source(s):`);
|
|
503
|
+
for (const schema of schemas) console.log(` - ${schema.moduleId}: ${schema.schemaPath}`);
|
|
504
|
+
const configPath = await generateTempDrizzleConfig(projectRoot, databaseUrl, schemas);
|
|
505
|
+
console.log(`Generated drizzle config: ${configPath}`);
|
|
506
|
+
console.log("");
|
|
507
|
+
const result = await runDrizzleKit(command, configPath, projectRoot);
|
|
508
|
+
if (result.code !== 0) process.exit(result.code);
|
|
509
|
+
}
|
|
510
|
+
async function dbPush(options) {
|
|
511
|
+
await runDbCommand("push", options);
|
|
512
|
+
}
|
|
513
|
+
async function dbGenerate(options) {
|
|
514
|
+
await runDbCommand("generate", options);
|
|
515
|
+
}
|
|
516
|
+
async function dbStudio(options) {
|
|
517
|
+
await runDbCommand("studio", options);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
//#endregion
|
|
521
|
+
//#region src/lib/credentials.ts
|
|
522
|
+
const DEFAULT_SERVER_URL = "http://localhost:3000";
|
|
523
|
+
const CONFIG_DIR = join(homedir(), ".kuckit");
|
|
524
|
+
const CONFIG_PATH = join(CONFIG_DIR, "config.json");
|
|
525
|
+
function loadConfig() {
|
|
526
|
+
try {
|
|
527
|
+
if (!existsSync(CONFIG_PATH)) return null;
|
|
528
|
+
const content = readFileSync(CONFIG_PATH, "utf-8");
|
|
529
|
+
return JSON.parse(content);
|
|
530
|
+
} catch {
|
|
531
|
+
return null;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
function saveConfig(config) {
|
|
535
|
+
if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, {
|
|
536
|
+
recursive: true,
|
|
537
|
+
mode: 448
|
|
538
|
+
});
|
|
539
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { mode: 384 });
|
|
540
|
+
chmodSync(CONFIG_PATH, 384);
|
|
541
|
+
}
|
|
542
|
+
function clearConfig() {
|
|
543
|
+
try {
|
|
544
|
+
if (existsSync(CONFIG_PATH)) unlinkSync(CONFIG_PATH);
|
|
545
|
+
} catch {}
|
|
546
|
+
}
|
|
547
|
+
function getServerUrl(config, override) {
|
|
548
|
+
return override ?? config?.serverUrl ?? DEFAULT_SERVER_URL;
|
|
549
|
+
}
|
|
550
|
+
function isTokenExpired(config) {
|
|
551
|
+
if (!config.tokenExpiresAt) return false;
|
|
552
|
+
return new Date(config.tokenExpiresAt) < /* @__PURE__ */ new Date();
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
//#endregion
|
|
556
|
+
//#region src/commands/auth.ts
|
|
557
|
+
async function openBrowser(url) {
|
|
558
|
+
try {
|
|
559
|
+
await (await import("open")).default(url);
|
|
560
|
+
return true;
|
|
561
|
+
} catch {
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
function sleep(ms) {
|
|
566
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
567
|
+
}
|
|
568
|
+
function formatExpiryDate(expiresAt) {
|
|
569
|
+
return new Date(expiresAt).toLocaleDateString("en-US", {
|
|
570
|
+
year: "numeric",
|
|
571
|
+
month: "long",
|
|
572
|
+
day: "numeric",
|
|
573
|
+
hour: "2-digit",
|
|
574
|
+
minute: "2-digit"
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
async function authLogin(options) {
|
|
578
|
+
const config = loadConfig();
|
|
579
|
+
const serverUrl = getServerUrl(config, options.server);
|
|
580
|
+
const scopes = options.scopes?.split(",").map((s) => s.trim()) ?? ["cli"];
|
|
581
|
+
console.log("\nAuthenticating with Kuckit...\n");
|
|
582
|
+
let initResponse;
|
|
583
|
+
try {
|
|
584
|
+
const response = await fetch(`${serverUrl}/rpc/cliDevice/initiate`, {
|
|
585
|
+
method: "POST",
|
|
586
|
+
headers: { "Content-Type": "application/json" },
|
|
587
|
+
body: JSON.stringify({ json: { scopes } })
|
|
588
|
+
});
|
|
589
|
+
if (!response.ok) {
|
|
590
|
+
const error = await response.json().catch(() => ({}));
|
|
591
|
+
console.error("Failed to initiate authentication:", error.message ?? response.statusText);
|
|
592
|
+
process.exit(1);
|
|
593
|
+
}
|
|
594
|
+
initResponse = (await response.json()).json;
|
|
595
|
+
} catch (err) {
|
|
596
|
+
console.error("Failed to connect to server:", err instanceof Error ? err.message : String(err));
|
|
597
|
+
console.error(`\nMake sure the server is running at ${serverUrl}`);
|
|
598
|
+
process.exit(1);
|
|
599
|
+
}
|
|
600
|
+
const { deviceCode, userCode, verificationUrl, intervalSec } = initResponse;
|
|
601
|
+
console.log("┌─────────────────────────────────────┐");
|
|
602
|
+
console.log("│ │");
|
|
603
|
+
console.log("│ Enter this code in your browser │");
|
|
604
|
+
console.log("│ │");
|
|
605
|
+
console.log(`│ ${userCode} │`);
|
|
606
|
+
console.log("│ │");
|
|
607
|
+
console.log("└─────────────────────────────────────┘");
|
|
608
|
+
console.log("");
|
|
609
|
+
console.log(`Open: ${verificationUrl}`);
|
|
610
|
+
console.log("");
|
|
611
|
+
if (await openBrowser(verificationUrl)) console.log("Browser opened automatically.");
|
|
612
|
+
else console.log("Could not open browser. Please open the URL manually.");
|
|
613
|
+
console.log("\nWaiting for authorization...\n");
|
|
614
|
+
const pollIntervalMs = intervalSec * 1e3;
|
|
615
|
+
let attempts = 0;
|
|
616
|
+
const maxAttempts = 120;
|
|
617
|
+
while (attempts < maxAttempts) {
|
|
618
|
+
await sleep(pollIntervalMs);
|
|
619
|
+
attempts++;
|
|
620
|
+
try {
|
|
621
|
+
const response = await fetch(`${serverUrl}/rpc/cliDevice/poll`, {
|
|
622
|
+
method: "POST",
|
|
623
|
+
headers: { "Content-Type": "application/json" },
|
|
624
|
+
body: JSON.stringify({ json: { deviceCode } })
|
|
625
|
+
});
|
|
626
|
+
if (!response.ok) {
|
|
627
|
+
const error = await response.json().catch(() => ({}));
|
|
628
|
+
console.error("Poll failed:", error.message ?? response.statusText);
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
const pollResult = (await response.json()).json;
|
|
632
|
+
switch (pollResult.status) {
|
|
633
|
+
case "pending":
|
|
634
|
+
process.stdout.write(".");
|
|
635
|
+
continue;
|
|
636
|
+
case "approved": {
|
|
637
|
+
console.log("\n");
|
|
638
|
+
if (!pollResult.token || !pollResult.expiresIn) {
|
|
639
|
+
console.error("Authorization succeeded but no token received.");
|
|
640
|
+
process.exit(1);
|
|
641
|
+
}
|
|
642
|
+
const expiresAt = new Date(Date.now() + pollResult.expiresIn * 1e3).toISOString();
|
|
643
|
+
saveConfig({
|
|
644
|
+
...config,
|
|
645
|
+
cliToken: pollResult.token,
|
|
646
|
+
tokenExpiresAt: expiresAt,
|
|
647
|
+
serverUrl
|
|
648
|
+
});
|
|
649
|
+
console.log("✓ Successfully authenticated!");
|
|
650
|
+
console.log(` Token expires: ${formatExpiryDate(expiresAt)}`);
|
|
651
|
+
console.log("");
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
case "denied":
|
|
655
|
+
console.log("\n");
|
|
656
|
+
console.error("Authorization was denied.");
|
|
657
|
+
process.exit(1);
|
|
658
|
+
break;
|
|
659
|
+
case "expired":
|
|
660
|
+
console.log("\n");
|
|
661
|
+
console.error("Authorization request expired. Please try again.");
|
|
662
|
+
process.exit(1);
|
|
663
|
+
break;
|
|
664
|
+
}
|
|
665
|
+
} catch {
|
|
666
|
+
process.stdout.write("x");
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
console.log("\n");
|
|
670
|
+
console.error("Authorization timed out. Please try again.");
|
|
671
|
+
process.exit(1);
|
|
672
|
+
}
|
|
673
|
+
async function authWhoami(options) {
|
|
674
|
+
const config = loadConfig();
|
|
675
|
+
if (!config?.cliToken) {
|
|
676
|
+
if (options.json) console.log(JSON.stringify({ authenticated: false }));
|
|
677
|
+
else console.log("Not logged in. Run 'kuckit auth login' to authenticate.");
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
if (isTokenExpired(config)) {
|
|
681
|
+
if (options.json) console.log(JSON.stringify({
|
|
682
|
+
authenticated: false,
|
|
683
|
+
reason: "token_expired"
|
|
684
|
+
}));
|
|
685
|
+
else console.log("Your session has expired. Run 'kuckit auth login' to sign in again.");
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
if (options.json) console.log(JSON.stringify({
|
|
689
|
+
authenticated: true,
|
|
690
|
+
userId: config.userId,
|
|
691
|
+
userName: config.userName,
|
|
692
|
+
serverUrl: config.serverUrl,
|
|
693
|
+
tokenExpiresAt: config.tokenExpiresAt
|
|
694
|
+
}));
|
|
695
|
+
else {
|
|
696
|
+
console.log("\nAuthenticated");
|
|
697
|
+
if (config.userName) console.log(` User: ${config.userName}`);
|
|
698
|
+
if (config.userId) console.log(` ID: ${config.userId}`);
|
|
699
|
+
if (config.serverUrl) console.log(` Server: ${config.serverUrl}`);
|
|
700
|
+
if (config.tokenExpiresAt) console.log(` Token expires: ${formatExpiryDate(config.tokenExpiresAt)}`);
|
|
701
|
+
console.log("");
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
async function authLogout() {
|
|
705
|
+
if (!loadConfig()?.cliToken) {
|
|
706
|
+
console.log("Already logged out.");
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
clearConfig();
|
|
710
|
+
console.log("Logged out successfully.");
|
|
711
|
+
}
|
|
712
|
+
function registerAuthCommands(program$1) {
|
|
713
|
+
const auth = program$1.command("auth").description("Manage CLI authentication");
|
|
714
|
+
auth.command("login").description("Authenticate with Kuckit using device flow").option("-s, --server <url>", "Server URL (default: http://localhost:3000)").option("--scopes <scopes>", "Comma-separated list of scopes (default: cli)").action(async (options) => {
|
|
715
|
+
await authLogin(options);
|
|
716
|
+
});
|
|
717
|
+
auth.command("whoami").description("Show current authentication status").option("--json", "Output as JSON", false).action(async (options) => {
|
|
718
|
+
await authWhoami(options);
|
|
719
|
+
});
|
|
720
|
+
auth.command("logout").description("Clear stored credentials").action(async () => {
|
|
721
|
+
await authLogout();
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
//#endregion
|
|
726
|
+
//#region src/lib/require-auth.ts
|
|
727
|
+
/**
|
|
728
|
+
* Checks if the user is authenticated with a valid token.
|
|
729
|
+
* Exits the process with an error message if not authenticated.
|
|
730
|
+
*/
|
|
731
|
+
function requireAuth() {
|
|
732
|
+
const config = loadConfig();
|
|
733
|
+
if (!config?.cliToken) {
|
|
734
|
+
console.error("\nError: Not logged in. Run 'kuckit auth login' first.\n");
|
|
735
|
+
process.exit(1);
|
|
736
|
+
}
|
|
737
|
+
if (isTokenExpired(config)) {
|
|
738
|
+
console.error("\nError: Session expired. Run 'kuckit auth login' to refresh.\n");
|
|
739
|
+
process.exit(1);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
//#endregion
|
|
744
|
+
//#region src/bin.ts
|
|
745
|
+
program.name("kuckit").description("CLI tools for Kuckit SDK module development").version("0.1.0");
|
|
746
|
+
program.command("generate").description("Generate Kuckit resources").command("module <name>").description("Generate a new Kuckit module").option("-o, --org <org>", "Organization scope for package name", "").option("-d, --dir <directory>", "Target directory", "packages").action(async (name, options) => {
|
|
747
|
+
requireAuth();
|
|
748
|
+
await generateModule(name, options);
|
|
749
|
+
});
|
|
750
|
+
program.command("add <package>").description("Install and wire a Kuckit module").option("--skip-install", "Skip package installation", false).action(async (packageName, options) => {
|
|
751
|
+
requireAuth();
|
|
752
|
+
await addModule(packageName, options);
|
|
753
|
+
});
|
|
754
|
+
program.command("discover").description("Scan node_modules for installed Kuckit modules").option("--json", "Output as JSON (non-interactive)", false).option("--no-interactive", "Disable interactive prompts").action(async (options) => {
|
|
755
|
+
requireAuth();
|
|
756
|
+
await discoverModules(options);
|
|
757
|
+
});
|
|
758
|
+
program.command("doctor").description("Check Kuckit setup and validate module configuration").option("--json", "Output as JSON", false).action(async (options) => {
|
|
759
|
+
await doctor(options);
|
|
760
|
+
});
|
|
761
|
+
program.command("search <keyword>").description("Search npm registry for Kuckit modules").option("--json", "Output as JSON", false).option("-l, --limit <number>", "Maximum results to return", "10").action(async (keyword, options) => {
|
|
762
|
+
await search(keyword, {
|
|
763
|
+
json: options.json,
|
|
764
|
+
limit: parseInt(options.limit, 10)
|
|
765
|
+
});
|
|
766
|
+
});
|
|
767
|
+
const db = program.command("db").description("Database schema management for module-owned schemas");
|
|
768
|
+
db.command("push").description("Push all module schemas to database").option("--url <url>", "Database connection URL").action(async (options) => {
|
|
769
|
+
requireAuth();
|
|
770
|
+
await dbPush(options);
|
|
771
|
+
});
|
|
772
|
+
db.command("generate").description("Generate migration files for module schemas").option("--url <url>", "Database connection URL").action(async (options) => {
|
|
773
|
+
requireAuth();
|
|
774
|
+
await dbGenerate(options);
|
|
775
|
+
});
|
|
776
|
+
db.command("studio").description("Open Drizzle Studio with all module schemas").option("--url <url>", "Database connection URL").action(async (options) => {
|
|
777
|
+
requireAuth();
|
|
778
|
+
await dbStudio(options);
|
|
779
|
+
});
|
|
780
|
+
registerAuthCommands(program);
|
|
781
|
+
program.parse();
|
|
782
|
+
|
|
783
|
+
//#endregion
|
|
784
|
+
export { };
|