@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.
Files changed (2) hide show
  1. package/lib/index.js +287 -2
  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 path7 = "/" + urlSegments.join("/");
3884
- return { path: path7, method };
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.31",
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",