@kozojs/cli 0.1.31 → 0.1.32
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/lib/index.js +287 -2
- package/package.json +3 -2
package/lib/index.js
CHANGED
|
@@ -3880,8 +3880,8 @@ function fileToRoute(filePath) {
|
|
|
3880
3880
|
if (seg.startsWith("[") && seg.endsWith("]")) return ":" + seg.slice(1, -1);
|
|
3881
3881
|
return seg;
|
|
3882
3882
|
});
|
|
3883
|
-
const
|
|
3884
|
-
return { path:
|
|
3883
|
+
const path9 = "/" + urlSegments.join("/");
|
|
3884
|
+
return { path: path9, method };
|
|
3885
3885
|
}
|
|
3886
3886
|
function extractParams(urlPath) {
|
|
3887
3887
|
return urlPath.split("/").filter((seg) => seg.startsWith(":")).map((seg) => seg.slice(1));
|
|
@@ -4117,6 +4117,285 @@ async function buildCommand(options = {}) {
|
|
|
4117
4117
|
console.log();
|
|
4118
4118
|
}
|
|
4119
4119
|
|
|
4120
|
+
// src/commands/dev.ts
|
|
4121
|
+
var import_child_process = require("child_process");
|
|
4122
|
+
var import_chokidar = __toESM(require("chokidar"));
|
|
4123
|
+
var import_picocolors4 = __toESM(require("picocolors"));
|
|
4124
|
+
var import_fs_extra7 = __toESM(require("fs-extra"));
|
|
4125
|
+
var import_path = __toESM(require("path"));
|
|
4126
|
+
async function devCommand() {
|
|
4127
|
+
console.clear();
|
|
4128
|
+
printBox2("Kozo Development Server");
|
|
4129
|
+
await runStep(1, 4, "Checking project structure...", async () => {
|
|
4130
|
+
if (!import_fs_extra7.default.existsSync(import_path.default.join(process.cwd(), "package.json"))) {
|
|
4131
|
+
throw new Error("No package.json found. Run this command in a Kozo project.");
|
|
4132
|
+
}
|
|
4133
|
+
});
|
|
4134
|
+
await runStep(2, 4, "Checking dependencies...", async () => {
|
|
4135
|
+
if (!import_fs_extra7.default.existsSync(import_path.default.join(process.cwd(), "node_modules"))) {
|
|
4136
|
+
throw new Error("Dependencies not installed. Run: pnpm install");
|
|
4137
|
+
}
|
|
4138
|
+
await sleep(300);
|
|
4139
|
+
});
|
|
4140
|
+
const routesDir = resolveRoutesDir(process.cwd());
|
|
4141
|
+
await runStep(3, 4, "Scanning routes...", async () => {
|
|
4142
|
+
if (routesDir) {
|
|
4143
|
+
await generateManifest({ routesDir, useCache: false, verbose: false });
|
|
4144
|
+
}
|
|
4145
|
+
await sleep(300);
|
|
4146
|
+
});
|
|
4147
|
+
await runStep(4, 4, "Starting server on port 3000...", async () => {
|
|
4148
|
+
await sleep(200);
|
|
4149
|
+
});
|
|
4150
|
+
console.log(import_picocolors4.default.gray("\n\u2139 \u{1F440} Watching for file changes... (Ctrl+C to stop)\n"));
|
|
4151
|
+
console.log(import_picocolors4.default.dim("\u2500".repeat(50)) + "\n");
|
|
4152
|
+
const child = (0, import_child_process.spawn)("npx", ["tsx", "watch", "src/index.ts"], {
|
|
4153
|
+
stdio: "inherit",
|
|
4154
|
+
shell: true,
|
|
4155
|
+
cwd: process.cwd(),
|
|
4156
|
+
env: { ...process.env, FORCE_COLOR: "1" }
|
|
4157
|
+
});
|
|
4158
|
+
child.on("error", (err) => {
|
|
4159
|
+
console.error(import_picocolors4.default.red("\n\u274C Failed to start server"));
|
|
4160
|
+
console.error(err);
|
|
4161
|
+
process.exit(1);
|
|
4162
|
+
});
|
|
4163
|
+
child.on("close", (code) => {
|
|
4164
|
+
if (code === 0 || code === null) {
|
|
4165
|
+
console.log("\n" + import_picocolors4.default.dim("Server stopped"));
|
|
4166
|
+
}
|
|
4167
|
+
process.exit(code ?? 0);
|
|
4168
|
+
});
|
|
4169
|
+
if (routesDir) {
|
|
4170
|
+
startRouteWatcher(routesDir);
|
|
4171
|
+
}
|
|
4172
|
+
process.on("SIGINT", () => {
|
|
4173
|
+
console.log("\n" + import_picocolors4.default.yellow("\u23F9 Stopping Kozo dev server..."));
|
|
4174
|
+
child.kill("SIGTERM");
|
|
4175
|
+
process.exit(0);
|
|
4176
|
+
});
|
|
4177
|
+
}
|
|
4178
|
+
function startRouteWatcher(routesDir) {
|
|
4179
|
+
let debounceTimer = null;
|
|
4180
|
+
const watcher = import_chokidar.default.watch(routesDir, {
|
|
4181
|
+
ignored: /(^|[/\\])\..|(\.test\.[tj]s$)|(\.spec\.[tj]s$)/,
|
|
4182
|
+
persistent: true,
|
|
4183
|
+
ignoreInitial: true,
|
|
4184
|
+
// don't fire for files already present at startup
|
|
4185
|
+
awaitWriteFinish: {
|
|
4186
|
+
stabilityThreshold: 80,
|
|
4187
|
+
pollInterval: 50
|
|
4188
|
+
}
|
|
4189
|
+
});
|
|
4190
|
+
const handleChange = (eventType, filePath) => {
|
|
4191
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
4192
|
+
debounceTimer = setTimeout(async () => {
|
|
4193
|
+
try {
|
|
4194
|
+
const manifest = await generateManifest({
|
|
4195
|
+
routesDir,
|
|
4196
|
+
useCache: false,
|
|
4197
|
+
// always regenerate on file change
|
|
4198
|
+
verbose: false
|
|
4199
|
+
});
|
|
4200
|
+
const count = manifest.routes.length;
|
|
4201
|
+
console.log(
|
|
4202
|
+
import_picocolors4.default.cyan("[Kozo]") + " \u2728 Routes updated " + import_picocolors4.default.dim(`(${count} found)`) + import_picocolors4.default.dim(` \u2014 ${import_path.default.relative(process.cwd(), filePath)}`)
|
|
4203
|
+
);
|
|
4204
|
+
} catch (err) {
|
|
4205
|
+
console.error(
|
|
4206
|
+
import_picocolors4.default.red("[Kozo] \u274C Failed to regenerate routes manifest:"),
|
|
4207
|
+
err.message
|
|
4208
|
+
);
|
|
4209
|
+
}
|
|
4210
|
+
}, 120);
|
|
4211
|
+
};
|
|
4212
|
+
watcher.on("add", (p3) => handleChange("add", p3)).on("change", (p3) => handleChange("change", p3)).on("unlink", (p3) => handleChange("unlink", p3)).on("error", (err) => console.error(import_picocolors4.default.red("[Kozo] Watcher error:"), err));
|
|
4213
|
+
return watcher;
|
|
4214
|
+
}
|
|
4215
|
+
function resolveRoutesDir(cwd) {
|
|
4216
|
+
const candidates = [
|
|
4217
|
+
import_path.default.join(cwd, "src", "routes"),
|
|
4218
|
+
import_path.default.join(cwd, "routes"),
|
|
4219
|
+
import_path.default.join(cwd, "src", "app", "routes"),
|
|
4220
|
+
import_path.default.join(cwd, "app", "routes")
|
|
4221
|
+
];
|
|
4222
|
+
for (const candidate of candidates) {
|
|
4223
|
+
if (import_fs_extra7.default.existsSync(candidate) && import_fs_extra7.default.statSync(candidate).isDirectory()) {
|
|
4224
|
+
return candidate;
|
|
4225
|
+
}
|
|
4226
|
+
}
|
|
4227
|
+
return null;
|
|
4228
|
+
}
|
|
4229
|
+
function printBox2(title) {
|
|
4230
|
+
const width = 50;
|
|
4231
|
+
const pad = Math.floor((width - title.length) / 2);
|
|
4232
|
+
const line = "\u2500".repeat(width);
|
|
4233
|
+
console.log(import_picocolors4.default.cyan("\u250C" + line + "\u2510"));
|
|
4234
|
+
console.log(import_picocolors4.default.cyan("\u2502") + " ".repeat(pad) + import_picocolors4.default.bold(title) + " ".repeat(width - pad - title.length) + import_picocolors4.default.cyan("\u2502"));
|
|
4235
|
+
console.log(import_picocolors4.default.cyan("\u2514" + line + "\u2518"));
|
|
4236
|
+
console.log();
|
|
4237
|
+
}
|
|
4238
|
+
async function runStep(step2, total, label, fn) {
|
|
4239
|
+
const prefix = import_picocolors4.default.dim(`[${step2}/${total}]`);
|
|
4240
|
+
process.stdout.write(`${prefix} ${label}`);
|
|
4241
|
+
try {
|
|
4242
|
+
await fn();
|
|
4243
|
+
process.stdout.write(" " + import_picocolors4.default.green("\u2713") + "\n");
|
|
4244
|
+
} catch (err) {
|
|
4245
|
+
process.stdout.write(" " + import_picocolors4.default.red("\u2717") + "\n");
|
|
4246
|
+
console.error(import_picocolors4.default.red(`
|
|
4247
|
+
Error: ${err.message}`));
|
|
4248
|
+
process.exit(1);
|
|
4249
|
+
}
|
|
4250
|
+
}
|
|
4251
|
+
function sleep(ms) {
|
|
4252
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
4253
|
+
}
|
|
4254
|
+
|
|
4255
|
+
// src/commands/generate.ts
|
|
4256
|
+
var p2 = __toESM(require("@clack/prompts"));
|
|
4257
|
+
var import_picocolors5 = __toESM(require("picocolors"));
|
|
4258
|
+
var import_fs_extra8 = __toESM(require("fs-extra"));
|
|
4259
|
+
var import_node_path9 = __toESM(require("path"));
|
|
4260
|
+
var ROUTE_TEMPLATE = `import { z } from 'zod';
|
|
4261
|
+
import type { HandlerContext } from '@kozo/core';
|
|
4262
|
+
|
|
4263
|
+
// Validation schema (optional)
|
|
4264
|
+
export const schema = {
|
|
4265
|
+
body: z.object({
|
|
4266
|
+
// Define your schema here
|
|
4267
|
+
})
|
|
4268
|
+
};
|
|
4269
|
+
|
|
4270
|
+
type Body = z.infer<typeof schema.body>;
|
|
4271
|
+
|
|
4272
|
+
export default async ({ body, services }: HandlerContext<Body>) => {
|
|
4273
|
+
// TODO: Implement handler
|
|
4274
|
+
return { message: 'Not implemented' };
|
|
4275
|
+
};
|
|
4276
|
+
`;
|
|
4277
|
+
var GET_ROUTE_TEMPLATE = `import type { HandlerContext } from '@kozo/core';
|
|
4278
|
+
|
|
4279
|
+
export default async ({ params, services }: HandlerContext) => {
|
|
4280
|
+
// TODO: Implement handler
|
|
4281
|
+
return { message: 'Not implemented' };
|
|
4282
|
+
};
|
|
4283
|
+
`;
|
|
4284
|
+
var MIDDLEWARE_TEMPLATE = `import type { Context, Next } from 'hono';
|
|
4285
|
+
|
|
4286
|
+
export async function {{name}}(c: Context, next: Next) {
|
|
4287
|
+
// Before handler
|
|
4288
|
+
console.log('{{name}} middleware - before');
|
|
4289
|
+
|
|
4290
|
+
await next();
|
|
4291
|
+
|
|
4292
|
+
// After handler
|
|
4293
|
+
console.log('{{name}} middleware - after');
|
|
4294
|
+
}
|
|
4295
|
+
`;
|
|
4296
|
+
async function generateCommand(type, name) {
|
|
4297
|
+
if (!type) {
|
|
4298
|
+
p2.log.error("Please specify what to generate: route, middleware");
|
|
4299
|
+
process.exit(1);
|
|
4300
|
+
}
|
|
4301
|
+
switch (type.toLowerCase()) {
|
|
4302
|
+
case "route":
|
|
4303
|
+
case "r":
|
|
4304
|
+
await generateRoute(name);
|
|
4305
|
+
break;
|
|
4306
|
+
case "middleware":
|
|
4307
|
+
case "mw":
|
|
4308
|
+
await generateMiddleware(name);
|
|
4309
|
+
break;
|
|
4310
|
+
default:
|
|
4311
|
+
p2.log.error(`Unknown generator: ${type}`);
|
|
4312
|
+
p2.log.info("Available: route, middleware");
|
|
4313
|
+
process.exit(1);
|
|
4314
|
+
}
|
|
4315
|
+
}
|
|
4316
|
+
async function generateRoute(routePath) {
|
|
4317
|
+
let targetPath = routePath;
|
|
4318
|
+
if (!targetPath) {
|
|
4319
|
+
const result = await p2.text({
|
|
4320
|
+
message: "Route path (e.g., users/profile)",
|
|
4321
|
+
placeholder: "users/[id]",
|
|
4322
|
+
validate: (v) => !v ? "Path is required" : void 0
|
|
4323
|
+
});
|
|
4324
|
+
if (p2.isCancel(result)) {
|
|
4325
|
+
p2.cancel("Cancelled");
|
|
4326
|
+
process.exit(0);
|
|
4327
|
+
}
|
|
4328
|
+
targetPath = result;
|
|
4329
|
+
}
|
|
4330
|
+
const method = await p2.select({
|
|
4331
|
+
message: "HTTP method",
|
|
4332
|
+
options: [
|
|
4333
|
+
{ value: "get", label: "GET" },
|
|
4334
|
+
{ value: "post", label: "POST" },
|
|
4335
|
+
{ value: "put", label: "PUT" },
|
|
4336
|
+
{ value: "patch", label: "PATCH" },
|
|
4337
|
+
{ value: "delete", label: "DELETE" }
|
|
4338
|
+
]
|
|
4339
|
+
});
|
|
4340
|
+
if (p2.isCancel(method)) {
|
|
4341
|
+
p2.cancel("Cancelled");
|
|
4342
|
+
process.exit(0);
|
|
4343
|
+
}
|
|
4344
|
+
const routesDir = import_node_path9.default.join(process.cwd(), "src", "routes");
|
|
4345
|
+
const filePath = import_node_path9.default.join(routesDir, targetPath, `${method}.ts`);
|
|
4346
|
+
if (await import_fs_extra8.default.pathExists(filePath)) {
|
|
4347
|
+
const overwrite = await p2.confirm({
|
|
4348
|
+
message: `File ${filePath} already exists. Overwrite?`,
|
|
4349
|
+
initialValue: false
|
|
4350
|
+
});
|
|
4351
|
+
if (p2.isCancel(overwrite) || !overwrite) {
|
|
4352
|
+
p2.cancel("Cancelled");
|
|
4353
|
+
process.exit(0);
|
|
4354
|
+
}
|
|
4355
|
+
}
|
|
4356
|
+
await import_fs_extra8.default.ensureDir(import_node_path9.default.dirname(filePath));
|
|
4357
|
+
const template = method === "get" ? GET_ROUTE_TEMPLATE : ROUTE_TEMPLATE;
|
|
4358
|
+
await import_fs_extra8.default.writeFile(filePath, template);
|
|
4359
|
+
const relativePath = import_node_path9.default.relative(process.cwd(), filePath);
|
|
4360
|
+
p2.log.success(`Created ${import_picocolors5.default.cyan(relativePath)}`);
|
|
4361
|
+
const urlPath = "/" + targetPath.replace(/\[([^\]]+)\]/g, ":$1");
|
|
4362
|
+
console.log(`
|
|
4363
|
+
${import_picocolors5.default.bold(String(method).toUpperCase())} ${import_picocolors5.default.green(urlPath)}
|
|
4364
|
+
`);
|
|
4365
|
+
}
|
|
4366
|
+
async function generateMiddleware(middlewareName) {
|
|
4367
|
+
let name = middlewareName;
|
|
4368
|
+
if (!name) {
|
|
4369
|
+
const result = await p2.text({
|
|
4370
|
+
message: "Middleware name",
|
|
4371
|
+
placeholder: "auth",
|
|
4372
|
+
validate: (v) => !v ? "Name is required" : void 0
|
|
4373
|
+
});
|
|
4374
|
+
if (p2.isCancel(result)) {
|
|
4375
|
+
p2.cancel("Cancelled");
|
|
4376
|
+
process.exit(0);
|
|
4377
|
+
}
|
|
4378
|
+
name = result;
|
|
4379
|
+
}
|
|
4380
|
+
const middlewareDir = import_node_path9.default.join(process.cwd(), "src", "middleware");
|
|
4381
|
+
const filePath = import_node_path9.default.join(middlewareDir, `${name}.ts`);
|
|
4382
|
+
if (await import_fs_extra8.default.pathExists(filePath)) {
|
|
4383
|
+
const overwrite = await p2.confirm({
|
|
4384
|
+
message: `File ${filePath} already exists. Overwrite?`,
|
|
4385
|
+
initialValue: false
|
|
4386
|
+
});
|
|
4387
|
+
if (p2.isCancel(overwrite) || !overwrite) {
|
|
4388
|
+
p2.cancel("Cancelled");
|
|
4389
|
+
process.exit(0);
|
|
4390
|
+
}
|
|
4391
|
+
}
|
|
4392
|
+
await import_fs_extra8.default.ensureDir(middlewareDir);
|
|
4393
|
+
const content = MIDDLEWARE_TEMPLATE.replace(/\{\{name\}\}/g, name);
|
|
4394
|
+
await import_fs_extra8.default.writeFile(filePath, content);
|
|
4395
|
+
const relativePath = import_node_path9.default.relative(process.cwd(), filePath);
|
|
4396
|
+
p2.log.success(`Created ${import_picocolors5.default.cyan(relativePath)}`);
|
|
4397
|
+
}
|
|
4398
|
+
|
|
4120
4399
|
// src/index.ts
|
|
4121
4400
|
var program = new import_commander.Command();
|
|
4122
4401
|
program.name("kozo").description("CLI to scaffold new Kozo Framework projects").version("0.2.6");
|
|
@@ -4133,4 +4412,10 @@ program.command("build").description("Build the project (generates routes manife
|
|
|
4133
4412
|
tsupArgs
|
|
4134
4413
|
});
|
|
4135
4414
|
});
|
|
4415
|
+
program.command("dev").description("Start development server with hot reload and route watcher").action(async () => {
|
|
4416
|
+
await devCommand();
|
|
4417
|
+
});
|
|
4418
|
+
program.command("generate [type] [name]").alias("g").description("Generate scaffolding: route, middleware").action(async (type, name) => {
|
|
4419
|
+
await generateCommand(type ?? "", name);
|
|
4420
|
+
});
|
|
4136
4421
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kozojs/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.32",
|
|
4
4
|
"description": "CLI to scaffold new Kozo Framework projects - The next-gen TypeScript Backend Framework",
|
|
5
5
|
"bin": {
|
|
6
6
|
"create-kozo": "./lib/index.js",
|
|
@@ -40,7 +40,8 @@
|
|
|
40
40
|
"ora": "^8.1.0",
|
|
41
41
|
"execa": "^9.5.0",
|
|
42
42
|
"fs-extra": "^11.2.0",
|
|
43
|
-
"glob": "^11.0.0"
|
|
43
|
+
"glob": "^11.0.0",
|
|
44
|
+
"chokidar": "^3.6.0"
|
|
44
45
|
},
|
|
45
46
|
"devDependencies": {
|
|
46
47
|
"@types/fs-extra": "^11.0.4",
|