@boon4681/giri 0.0.1

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/cli.js ADDED
@@ -0,0 +1,2070 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __esm = (fn, res) => function __init() {
10
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ };
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
16
+ var __copyProps = (to, from, except, desc) => {
17
+ if (from && typeof from === "object" || typeof from === "function") {
18
+ for (let key of __getOwnPropNames(from))
19
+ if (!__hasOwnProp.call(to, key) && key !== except)
20
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
21
+ }
22
+ return to;
23
+ };
24
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
25
+ // If the importer is in node compatibility mode or this is not an ESM
26
+ // file that has been converted to a CommonJS file using a Babel-
27
+ // compatible transform (i.e. "__esModule" has not been set), then set
28
+ // "default" to the CommonJS "module.exports" for node compatibility.
29
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
30
+ mod
31
+ ));
32
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
33
+
34
+ // src/loader/_es5.ts
35
+ var es5_exports = {};
36
+ __export(es5_exports, {
37
+ default: () => es5_default
38
+ });
39
+ var _, es5_default;
40
+ var init_es5 = __esm({
41
+ "src/loader/_es5.ts"() {
42
+ "use strict";
43
+ _ = "";
44
+ es5_default = _;
45
+ }
46
+ });
47
+
48
+ // src/cli.ts
49
+ var import_node_child_process = require("child_process");
50
+ var import_node_fs9 = require("fs");
51
+ var import_promises4 = require("fs/promises");
52
+ var import_node_path15 = require("path");
53
+ var prompts = __toESM(require("@clack/prompts"));
54
+
55
+ // src/app.ts
56
+ var import_node_module = __toESM(require("module"));
57
+ var import_node_path3 = require("path");
58
+
59
+ // src/loader/loader.ts
60
+ var import_prompts = require("@clack/prompts");
61
+ var import_node_fs = require("fs");
62
+ var import_node_path = require("path");
63
+ var import_node_process = require("process");
64
+
65
+ // src/config/schema.ts
66
+ var import_typebox = require("@sinclair/typebox");
67
+ var configSchema = import_typebox.Type.Object({
68
+ adapter: import_typebox.Type.Any(),
69
+ alias: import_typebox.Type.Optional(import_typebox.Type.Record(
70
+ import_typebox.Type.String(),
71
+ import_typebox.Type.Union([import_typebox.Type.String(), import_typebox.Type.Array(import_typebox.Type.String())])
72
+ )),
73
+ outDir: import_typebox.Type.Optional(import_typebox.Type.String()),
74
+ server: import_typebox.Type.Optional(import_typebox.Type.Object({
75
+ port: import_typebox.Type.Optional(import_typebox.Type.Number()),
76
+ hostname: import_typebox.Type.Optional(import_typebox.Type.String())
77
+ }, { additionalProperties: false })),
78
+ errorSchema: import_typebox.Type.Optional(import_typebox.Type.Any())
79
+ }, { additionalProperties: false });
80
+
81
+ // src/loader/loader.ts
82
+ var import_value = require("@sinclair/typebox/value");
83
+ var assertES5 = async (unregister) => {
84
+ try {
85
+ init_es5();
86
+ } catch (e) {
87
+ if ("errors" in e && Array.isArray(e.errors) && e.errors.length > 0) {
88
+ const es5Error = e.errors.filter((it) => it.text?.includes(`("es5") is not supported yet`)).length > 0;
89
+ if (es5Error) {
90
+ import_prompts.log.error(
91
+ `Please change compilerOptions.target from 'es5' to 'es6' or above in your tsconfig.json`
92
+ );
93
+ (0, import_node_process.exit)(1);
94
+ }
95
+ }
96
+ import_prompts.log.error(e);
97
+ (0, import_node_process.exit)(1);
98
+ }
99
+ };
100
+ var safeRegister = async () => {
101
+ const { register } = await import("esbuild-register/dist/node");
102
+ let res;
103
+ try {
104
+ res = register({
105
+ format: "cjs",
106
+ loader: "ts"
107
+ });
108
+ } catch {
109
+ res = {
110
+ unregister: () => {
111
+ }
112
+ };
113
+ }
114
+ await assertES5(res.unregister);
115
+ return res;
116
+ };
117
+ var load = async () => {
118
+ const defaultTsConfigExists = (0, import_node_fs.existsSync)((0, import_node_path.resolve)("giri.config.ts"));
119
+ const defaultJsConfigExists = (0, import_node_fs.existsSync)((0, import_node_path.resolve)("giri.config.js"));
120
+ const defaultConfigPath = defaultTsConfigExists ? "giri.config.ts" : defaultJsConfigExists ? "giri.config.js" : void 0;
121
+ if (!defaultConfigPath) {
122
+ import_prompts.log.error("Config file not found.");
123
+ (0, import_node_process.exit)(1);
124
+ }
125
+ const path = (0, import_node_path.resolve)(defaultConfigPath);
126
+ if (!(0, import_node_fs.existsSync)(path)) {
127
+ import_prompts.log.error(`${path} file does not exist`);
128
+ (0, import_node_process.exit)(1);
129
+ }
130
+ const { unregister } = await safeRegister();
131
+ const required = require(`${path}`);
132
+ const content = required.default ?? required;
133
+ unregister();
134
+ const res = import_value.Value.Check(configSchema, content);
135
+ if (!res) {
136
+ for (const error of [...import_value.Value.Errors(configSchema, content)]) {
137
+ import_prompts.log.error(error.message);
138
+ }
139
+ (0, import_node_process.exit)(1);
140
+ }
141
+ return content;
142
+ };
143
+
144
+ // src/routes.ts
145
+ var import_node_fs2 = require("fs");
146
+ var import_promises = require("fs/promises");
147
+ var import_node_path2 = require("path");
148
+ var import_tinyglobby = require("tinyglobby");
149
+ var METHOD_ORDER = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"];
150
+ var METHOD_FROM_FILE = new Map(
151
+ METHOD_ORDER.map((method) => [`+${method.toLowerCase()}`, method])
152
+ );
153
+ function normalizeSlashes(path) {
154
+ return path.split(import_node_path2.sep).join("/");
155
+ }
156
+ function isRouteSourceFile(fileName) {
157
+ return /\.(?:[cm]?[jt]s|[jt]sx)$/.test(fileName) && !fileName.endsWith(".d.ts");
158
+ }
159
+ function methodFromFile(fileName) {
160
+ if (!isRouteSourceFile(fileName)) {
161
+ return void 0;
162
+ }
163
+ const stem = fileName.replace(/\.(?:[cm]?[jt]s|[jt]sx)$/, "").toLowerCase();
164
+ return METHOD_FROM_FILE.get(stem);
165
+ }
166
+ function sharedFileIn(dir) {
167
+ for (const ext of ["ts", "tsx", "js", "jsx", "mjs", "cjs", "mts", "cts"]) {
168
+ const file = (0, import_node_path2.join)(dir, `+shared.${ext}`);
169
+ if ((0, import_node_fs2.existsSync)(file)) {
170
+ return file;
171
+ }
172
+ }
173
+ return void 0;
174
+ }
175
+ function physicalRouteSegments(routesDir, routeDir) {
176
+ const rel = (0, import_node_path2.relative)(routesDir, routeDir);
177
+ if (!rel) {
178
+ return [];
179
+ }
180
+ return normalizeSlashes(rel).split("/").filter(Boolean);
181
+ }
182
+ function urlSegment(segment) {
183
+ if (/^\(.+\)$/.test(segment)) {
184
+ return {};
185
+ }
186
+ const catchAll = /^\[\.\.\.(.+)\]$/.exec(segment);
187
+ if (catchAll) {
188
+ const name = catchAll[1];
189
+ return {
190
+ value: `:${name}{.*}`,
191
+ param: { name, catchAll: true }
192
+ };
193
+ }
194
+ const param = /^\[(.+)\]$/.exec(segment);
195
+ if (param) {
196
+ const name = param[1];
197
+ return {
198
+ value: `:${name}`,
199
+ param: { name, catchAll: false }
200
+ };
201
+ }
202
+ return { value: segment };
203
+ }
204
+ function pathFromSegments(segments) {
205
+ const pathSegments = [];
206
+ const params = [];
207
+ for (const segment of segments) {
208
+ const converted = urlSegment(segment);
209
+ if (converted.value) {
210
+ pathSegments.push(converted.value);
211
+ }
212
+ if (converted.param) {
213
+ params.push(converted.param);
214
+ }
215
+ }
216
+ return {
217
+ path: pathSegments.length > 0 ? `/${pathSegments.join("/")}` : "/",
218
+ params
219
+ };
220
+ }
221
+ async function scanRouteFolders(routesDir) {
222
+ if (!(0, import_node_fs2.existsSync)(routesDir)) {
223
+ return [];
224
+ }
225
+ const folders = [routesDir];
226
+ const walk = async (dir) => {
227
+ for (const entry of await (0, import_promises.readdir)(dir, { withFileTypes: true })) {
228
+ if (entry.isDirectory() && entry.name !== "node_modules") {
229
+ const full = (0, import_node_path2.join)(dir, entry.name);
230
+ folders.push(full);
231
+ await walk(full);
232
+ }
233
+ }
234
+ };
235
+ await walk(routesDir);
236
+ return folders;
237
+ }
238
+ function routeParamsForDir(routesDir, dir) {
239
+ return pathFromSegments(physicalRouteSegments(routesDir, dir)).params;
240
+ }
241
+ function sharedFilesForDir(routesDir, dir) {
242
+ const segments = physicalRouteSegments(routesDir, dir);
243
+ const dirs = [routesDir];
244
+ let current = routesDir;
245
+ for (const segment of segments) {
246
+ current = (0, import_node_path2.join)(current, segment);
247
+ dirs.push(current);
248
+ }
249
+ return dirs.map(sharedFileIn).filter((file) => Boolean(file));
250
+ }
251
+ async function scanRoutes(routesDir) {
252
+ if (!(0, import_node_fs2.existsSync)(routesDir)) {
253
+ return [];
254
+ }
255
+ const files = await (0, import_tinyglobby.glob)("**/+*.{ts,tsx,js,jsx,mjs,cjs,mts,cts}", {
256
+ cwd: routesDir,
257
+ absolute: true,
258
+ onlyFiles: true
259
+ });
260
+ const routes = [];
261
+ for (const file of files) {
262
+ const method = methodFromFile((0, import_node_path2.basename)(file));
263
+ if (!method) {
264
+ continue;
265
+ }
266
+ const routeDir = (0, import_node_path2.dirname)(file);
267
+ const routeSegments = physicalRouteSegments(routesDir, routeDir);
268
+ const { path, params } = pathFromSegments(routeSegments);
269
+ routes.push({
270
+ method,
271
+ path,
272
+ file,
273
+ routeDir,
274
+ routeSegments,
275
+ params,
276
+ sharedFiles: sharedFilesForDir(routesDir, routeDir)
277
+ });
278
+ }
279
+ return routes.sort((left, right) => {
280
+ const pathOrder = left.path.localeCompare(right.path);
281
+ if (pathOrder !== 0) {
282
+ return pathOrder;
283
+ }
284
+ return METHOD_ORDER.indexOf(left.method) - METHOD_ORDER.indexOf(right.method);
285
+ });
286
+ }
287
+
288
+ // src/types.ts
289
+ var inputSchemaBrand = /* @__PURE__ */ Symbol.for("giri.input-schema");
290
+ var bodySchemaBrand = /* @__PURE__ */ Symbol.for("giri.body-schema");
291
+
292
+ // src/validation.ts
293
+ function isGiriInputSchema(value) {
294
+ return Boolean(
295
+ value && typeof value === "object" && value[inputSchemaBrand] === true
296
+ );
297
+ }
298
+ function isGiriBodySchema(value) {
299
+ return Boolean(
300
+ value && typeof value === "object" && value[bodySchemaBrand] === true
301
+ );
302
+ }
303
+
304
+ // src/app.ts
305
+ function loadModule(file) {
306
+ const resolved = require.resolve(file);
307
+ delete require.cache[resolved];
308
+ return require(resolved);
309
+ }
310
+ function interopDefault(value) {
311
+ if (value && typeof value === "object" && "default" in value) {
312
+ return value.default;
313
+ }
314
+ return value;
315
+ }
316
+ function normalizeMiddleware(value, file) {
317
+ const exported = interopDefault(value);
318
+ if (exported === void 0) {
319
+ return [];
320
+ }
321
+ if (typeof exported === "function") {
322
+ return [exported];
323
+ }
324
+ if (Array.isArray(exported)) {
325
+ for (const middleware of exported) {
326
+ if (typeof middleware !== "function") {
327
+ throw new Error(`Middleware export in ${file} must contain only functions.`);
328
+ }
329
+ }
330
+ return exported;
331
+ }
332
+ throw new Error(`Middleware export in ${file} must be a function or an array of functions.`);
333
+ }
334
+ function assertBodySchema(value, file) {
335
+ if (!isGiriBodySchema(value)) {
336
+ throw new Error(
337
+ `${file}: "body" must be wrapped with a validator, e.g. \`export const body = zod.body({ json: ... })\` from @boon4681/giri/validators/zod.`
338
+ );
339
+ }
340
+ }
341
+ function assertQuerySchema(value, file) {
342
+ if (!isGiriInputSchema(value)) {
343
+ throw new Error(
344
+ `${file}: "query" must be wrapped with a validator, e.g. \`export const query = zod.query(...)\` from @boon4681/giri/validators/zod.`
345
+ );
346
+ }
347
+ }
348
+ function routeInput(routeModule, file) {
349
+ const input = {};
350
+ if (routeModule.body !== void 0) {
351
+ assertBodySchema(routeModule.body, file);
352
+ input.body = routeModule.body;
353
+ }
354
+ if (routeModule.query !== void 0) {
355
+ assertQuerySchema(routeModule.query, file);
356
+ input.query = routeModule.query;
357
+ }
358
+ return input.body || input.query ? input : void 0;
359
+ }
360
+ function aliasValues(value) {
361
+ return Array.isArray(value) ? value : [value];
362
+ }
363
+ function resolveAliasTarget(cwd, target, capture = "") {
364
+ const replaced = target.includes("*") ? target.replaceAll("*", capture) : target;
365
+ return (0, import_node_path3.isAbsolute)(replaced) ? replaced : (0, import_node_path3.resolve)(cwd, replaced);
366
+ }
367
+ function matchAlias(request, key) {
368
+ if (key.includes("*")) {
369
+ const [prefix2, suffix = ""] = key.split("*");
370
+ if (request.startsWith(prefix2) && request.endsWith(suffix)) {
371
+ return request.slice(prefix2.length, request.length - suffix.length);
372
+ }
373
+ return void 0;
374
+ }
375
+ if (request === key) {
376
+ return "";
377
+ }
378
+ const prefix = `${key}/`;
379
+ if (request.startsWith(prefix)) {
380
+ return request.slice(prefix.length);
381
+ }
382
+ return void 0;
383
+ }
384
+ function resolveAliasRequest(request, alias, cwd) {
385
+ for (const [key, value] of Object.entries(alias ?? {})) {
386
+ const capture = matchAlias(request, key);
387
+ if (capture === void 0) {
388
+ continue;
389
+ }
390
+ const [target] = aliasValues(value);
391
+ if (!target) {
392
+ continue;
393
+ }
394
+ return resolveAliasTarget(cwd, target, capture);
395
+ }
396
+ return void 0;
397
+ }
398
+ function registerAliasResolver(alias, cwd) {
399
+ if (!alias || Object.keys(alias).length === 0) {
400
+ return () => {
401
+ };
402
+ }
403
+ const moduleWithResolver = import_node_module.default;
404
+ const originalResolveFilename = moduleWithResolver._resolveFilename;
405
+ moduleWithResolver._resolveFilename = function resolveWithGiriAlias(request, parent, isMain, options) {
406
+ return originalResolveFilename.call(
407
+ this,
408
+ resolveAliasRequest(request, alias, cwd) ?? request,
409
+ parent,
410
+ isMain,
411
+ options
412
+ );
413
+ };
414
+ return () => {
415
+ moduleWithResolver._resolveFilename = originalResolveFilename;
416
+ };
417
+ }
418
+ var GIRI_ALIAS_PREFIX = "$giri/";
419
+ var giriOutDir;
420
+ var giriResolverInstalled = false;
421
+ function ensureGiriAliasResolver(outDir) {
422
+ giriOutDir = outDir;
423
+ if (giriResolverInstalled) {
424
+ return;
425
+ }
426
+ giriResolverInstalled = true;
427
+ const moduleWithResolver = import_node_module.default;
428
+ const originalResolveFilename = moduleWithResolver._resolveFilename;
429
+ moduleWithResolver._resolveFilename = function resolveWithGiriInternalAlias(request, parent, isMain, options) {
430
+ const mapped = typeof request === "string" && request.startsWith(GIRI_ALIAS_PREFIX) && giriOutDir ? (0, import_node_path3.join)(giriOutDir, request.slice(GIRI_ALIAS_PREFIX.length)) : request;
431
+ return originalResolveFilename.call(this, mapped, parent, isMain, options);
432
+ };
433
+ }
434
+ function resolveGiriPaths(config, cwd = process.cwd()) {
435
+ return {
436
+ cwd: (0, import_node_path3.resolve)(cwd),
437
+ routesDir: (0, import_node_path3.resolve)(cwd, "src/routes"),
438
+ outDir: (0, import_node_path3.resolve)(cwd, config.outDir ?? ".giri")
439
+ };
440
+ }
441
+ async function buildGiriApp(config, options = {}) {
442
+ const paths = resolveGiriPaths(config, options.cwd);
443
+ const routes = await scanRoutes(paths.routesDir);
444
+ const app = config.adapter.createApp();
445
+ ensureGiriAliasResolver(paths.outDir);
446
+ const { unregister } = await safeRegister();
447
+ const unregisterAliasResolver = registerAliasResolver(config.alias, paths.cwd);
448
+ try {
449
+ for (const route of routes) {
450
+ const routeModule = loadModule(route.file);
451
+ if (typeof routeModule.handle !== "function") {
452
+ throw new Error(`${route.file} must export a named handle function.`);
453
+ }
454
+ const folderMiddleware = routeModule.config?.skipInherited ? [] : route.sharedFiles.flatMap(
455
+ (file) => normalizeMiddleware(loadModule(file).middleware, file)
456
+ );
457
+ const verbMiddleware = normalizeMiddleware(routeModule.middleware, route.file);
458
+ config.adapter.register(app, {
459
+ method: route.method,
460
+ path: route.path,
461
+ handle: routeModule.handle,
462
+ middleware: [...folderMiddleware, ...verbMiddleware],
463
+ input: routeInput(routeModule, route.file),
464
+ services: options.services
465
+ });
466
+ }
467
+ } finally {
468
+ unregisterAliasResolver();
469
+ unregister();
470
+ }
471
+ return { app, routes, paths };
472
+ }
473
+
474
+ // src/generator/sync.ts
475
+ var import_node_fs6 = require("fs");
476
+ var import_promises3 = require("fs/promises");
477
+ var import_node_path11 = require("path");
478
+
479
+ // src/generator/app-types.ts
480
+ var import_node_fs4 = require("fs");
481
+ var import_node_path5 = require("path");
482
+
483
+ // src/generator/util.ts
484
+ var import_node_fs3 = require("fs");
485
+ var import_promises2 = require("fs/promises");
486
+ var import_node_path4 = require("path");
487
+ var GENERATED_HEADER = "// Generated by giri sync. Do not edit.";
488
+ function slash(path) {
489
+ return path.split(import_node_path4.sep).join("/");
490
+ }
491
+ function importPath(fromFile, toFile) {
492
+ let path = slash((0, import_node_path4.relative)((0, import_node_path4.dirname)(fromFile), toFile)).replace(/\.d\.ts$/, "");
493
+ if (!path.startsWith(".")) {
494
+ path = `./${path}`;
495
+ }
496
+ return path;
497
+ }
498
+ function relativeConfigPath(fromDir, toPath) {
499
+ let path = slash((0, import_node_path4.relative)(fromDir, toPath));
500
+ if (!path.startsWith(".")) {
501
+ path = `./${path}`;
502
+ }
503
+ return path;
504
+ }
505
+ function moduleSpecifier(fromDir, target) {
506
+ let path = slash((0, import_node_path4.relative)(fromDir, target)).replace(/\.(?:[cm]?[jt]sx?)$/, "");
507
+ if (!path.startsWith(".")) {
508
+ path = `./${path}`;
509
+ }
510
+ return path;
511
+ }
512
+ function typeFilePath(paths, routeDir) {
513
+ const sourceDir = (0, import_node_path4.relative)(paths.cwd, routeDir);
514
+ return (0, import_node_path4.join)(paths.outDir, "types", sourceDir, "$types.d.ts");
515
+ }
516
+ function assertSafeOutDir(paths) {
517
+ const rel = (0, import_node_path4.relative)(paths.cwd, paths.outDir);
518
+ if (!rel || rel.startsWith("..") || rel.includes(`..${import_node_path4.sep}`)) {
519
+ throw new Error(`Refusing to sync outside the project root: ${paths.outDir}`);
520
+ }
521
+ }
522
+ var writeCache = /* @__PURE__ */ new Map();
523
+ async function writeGenerated(path, content) {
524
+ if (writeCache.get(path) === content && (0, import_node_fs3.existsSync)(path)) {
525
+ return;
526
+ }
527
+ await (0, import_promises2.mkdir)((0, import_node_path4.dirname)(path), { recursive: true });
528
+ await (0, import_promises2.writeFile)(path, content);
529
+ writeCache.set(path, content);
530
+ }
531
+ async function writeJson(path, value) {
532
+ await writeGenerated(path, `${JSON.stringify(value, null, 2)}
533
+ `);
534
+ }
535
+ async function pruneDir(dir, keep) {
536
+ let entries;
537
+ try {
538
+ entries = await (0, import_promises2.readdir)(dir, { withFileTypes: true });
539
+ } catch {
540
+ return;
541
+ }
542
+ for (const entry of entries) {
543
+ const full = (0, import_node_path4.join)(dir, entry.name);
544
+ if (entry.isDirectory()) {
545
+ await pruneDir(full, keep);
546
+ await (0, import_promises2.rmdir)(full).catch(() => {
547
+ });
548
+ } else if (!keep.has(full)) {
549
+ await (0, import_promises2.rm)(full, { force: true });
550
+ writeCache.delete(full);
551
+ }
552
+ }
553
+ }
554
+
555
+ // src/generator/app-types.ts
556
+ var MAIN_EXTENSIONS = ["ts", "tsx", "mts", "cts", "js", "jsx", "mjs", "cjs"];
557
+ function findMainFile(cwd) {
558
+ for (const ext of MAIN_EXTENSIONS) {
559
+ const file = (0, import_node_path5.join)(cwd, "src", `main.${ext}`);
560
+ if ((0, import_node_fs4.existsSync)(file)) {
561
+ return file;
562
+ }
563
+ }
564
+ return void 0;
565
+ }
566
+ function moduleSpecifier2(fromDir, target) {
567
+ let path = slash((0, import_node_path5.relative)(fromDir, target)).replace(/\.(?:[cm]?[jt]sx?)$/, "");
568
+ if (!path.startsWith(".")) {
569
+ path = `./${path}`;
570
+ }
571
+ return path;
572
+ }
573
+ async function writeAppTypes(paths) {
574
+ const file = (0, import_node_path5.join)(paths.outDir, "types", "app.d.ts");
575
+ const mainFile = findMainFile(paths.cwd);
576
+ if (!mainFile) {
577
+ await writeGenerated(file, [GENERATED_HEADER, "export {};", ""].join("\n"));
578
+ return;
579
+ }
580
+ const spec = moduleSpecifier2((0, import_node_path5.join)(paths.outDir, "types"), mainFile);
581
+ await writeGenerated(
582
+ file,
583
+ [
584
+ GENERATED_HEADER,
585
+ "declare global {",
586
+ " namespace Giri {",
587
+ " interface Register {",
588
+ ` app: typeof import(${JSON.stringify(spec)}) extends {`,
589
+ " init: (...args: any[]) => infer R;",
590
+ " }",
591
+ " ? Awaited<R>",
592
+ " : Record<string, unknown>;",
593
+ " }",
594
+ " }",
595
+ "}",
596
+ "export {};",
597
+ ""
598
+ ].join("\n")
599
+ );
600
+ }
601
+
602
+ // src/generator/manifest.ts
603
+ var import_node_path6 = require("path");
604
+ async function writeManifest(paths, routes, data = {}) {
605
+ const manifest = {
606
+ version: 1,
607
+ routes: routes.map((route) => {
608
+ const responses = data.responsesByFile?.get(route.file);
609
+ const input = data.inputsByFile?.get(route.file);
610
+ const security = data.securityByFile?.get(route.file);
611
+ return {
612
+ method: route.method,
613
+ path: route.path,
614
+ file: slash((0, import_node_path6.relative)(paths.cwd, route.file)),
615
+ params: route.params,
616
+ shared: route.sharedFiles.map((file) => slash((0, import_node_path6.relative)(paths.cwd, file))),
617
+ types: slash((0, import_node_path6.relative)(paths.cwd, typeFilePath(paths, route.routeDir))),
618
+ ...data.hiddenFiles?.has(route.file) ? { hidden: true } : {},
619
+ ...input ? { input } : {},
620
+ ...security && security.security.length > 0 ? { security: security.security } : {},
621
+ responses: responses?.responses ?? [],
622
+ ...responses && Object.keys(responses.$defs).length > 0 ? { $defs: responses.$defs } : {}
623
+ };
624
+ })
625
+ };
626
+ await writeJson((0, import_node_path6.join)(paths.outDir, "manifest.json"), manifest);
627
+ }
628
+
629
+ // src/generator/openapi.ts
630
+ var import_node_fs5 = require("fs");
631
+ var import_node_path7 = require("path");
632
+ var REASON = {
633
+ 200: "OK",
634
+ 201: "Created",
635
+ 202: "Accepted",
636
+ 204: "No Content",
637
+ 400: "Bad Request",
638
+ 401: "Unauthorized",
639
+ 403: "Forbidden",
640
+ 404: "Not Found",
641
+ 409: "Conflict",
642
+ 422: "Unprocessable Entity",
643
+ 500: "Internal Server Error"
644
+ };
645
+ function toOpenApiPath(path) {
646
+ return path.replace(/:([A-Za-z0-9_]+)(?:\{[^}]*\})?/g, "{$1}");
647
+ }
648
+ function rewriteRefs(value) {
649
+ if (Array.isArray(value)) {
650
+ return value.map(rewriteRefs);
651
+ }
652
+ if (value && typeof value === "object") {
653
+ const out = {};
654
+ for (const [key, child] of Object.entries(value)) {
655
+ if (key === "$ref" && typeof child === "string" && child.startsWith("#/$defs/")) {
656
+ out.$ref = child.replace("#/$defs/", "#/components/schemas/");
657
+ } else {
658
+ out[key] = rewriteRefs(child);
659
+ }
660
+ }
661
+ return out;
662
+ }
663
+ return value;
664
+ }
665
+ function mediaTypeFor(format) {
666
+ return format === "text" ? "text/plain" : "application/json";
667
+ }
668
+ var BODY_MEDIA_TYPE = {
669
+ json: "application/json",
670
+ form: "multipart/form-data",
671
+ urlencoded: "application/x-www-form-urlencoded",
672
+ text: "text/plain"
673
+ };
674
+ function buildResponses(responses) {
675
+ if (responses.length === 0) {
676
+ return { default: { description: "Response" } };
677
+ }
678
+ const out = {};
679
+ for (const response of responses) {
680
+ const key = response.status === "default" ? "default" : String(response.status);
681
+ const description = typeof response.status === "number" && REASON[response.status] || "Response";
682
+ out[key] = {
683
+ description,
684
+ content: { [mediaTypeFor(response.format)]: { schema: rewriteRefs(response.schema) } }
685
+ };
686
+ }
687
+ return out;
688
+ }
689
+ function pathParameters(route) {
690
+ const seen = /* @__PURE__ */ new Set();
691
+ const params = [];
692
+ for (const param of route.params) {
693
+ if (seen.has(param.name)) {
694
+ continue;
695
+ }
696
+ seen.add(param.name);
697
+ params.push({ name: param.name, in: "path", required: true, schema: { type: "string" } });
698
+ }
699
+ return params;
700
+ }
701
+ function queryParameters(query) {
702
+ if (!query || query.type !== "object" || typeof query.properties !== "object") {
703
+ return [];
704
+ }
705
+ const properties = query.properties;
706
+ const required = Array.isArray(query.required) ? query.required : [];
707
+ return Object.entries(properties).map(([name, schema]) => ({
708
+ name,
709
+ in: "query",
710
+ required: required.includes(name),
711
+ schema: rewriteRefs(schema)
712
+ }));
713
+ }
714
+ function readProjectInfo(cwd) {
715
+ const file = (0, import_node_path7.join)(cwd, "package.json");
716
+ if ((0, import_node_fs5.existsSync)(file)) {
717
+ try {
718
+ const pkg = JSON.parse((0, import_node_fs5.readFileSync)(file, "utf8"));
719
+ return { title: pkg.name ?? "giri API", version: pkg.version ?? "0.0.0" };
720
+ } catch {
721
+ }
722
+ }
723
+ return { title: "giri API", version: "0.0.0" };
724
+ }
725
+ function buildOpenApiDocument(paths, routes, data = {}) {
726
+ const documentPaths = {};
727
+ const schemas = {};
728
+ const securitySchemes = {};
729
+ for (const route of routes) {
730
+ if (data.hiddenFiles?.has(route.file)) {
731
+ continue;
732
+ }
733
+ const responses = data.responsesByFile?.get(route.file);
734
+ const input = data.inputsByFile?.get(route.file);
735
+ const security = data.securityByFile?.get(route.file);
736
+ for (const [name, schema] of Object.entries(responses?.$defs ?? {})) {
737
+ schemas[name] = rewriteRefs(schema);
738
+ }
739
+ const operation = { responses: buildResponses(responses?.responses ?? []) };
740
+ const parameters = [...pathParameters(route), ...queryParameters(input?.query)];
741
+ if (parameters.length > 0) {
742
+ operation.parameters = parameters;
743
+ }
744
+ if (input?.body) {
745
+ const content = {};
746
+ for (const [contentType, schema] of Object.entries(input.body)) {
747
+ content[BODY_MEDIA_TYPE[contentType] ?? contentType] = {
748
+ schema: rewriteRefs(schema)
749
+ };
750
+ }
751
+ if (Object.keys(content).length > 0) {
752
+ operation.requestBody = { required: true, content };
753
+ }
754
+ }
755
+ if (security && security.security.length > 0) {
756
+ operation.security = security.security;
757
+ }
758
+ if (security) {
759
+ Object.assign(securitySchemes, security.securitySchemes);
760
+ }
761
+ const openApiPath = toOpenApiPath(route.path);
762
+ const pathItem = documentPaths[openApiPath] ?? {};
763
+ pathItem[route.method.toLowerCase()] = operation;
764
+ documentPaths[openApiPath] = pathItem;
765
+ }
766
+ const document = {
767
+ openapi: "3.1.0",
768
+ info: readProjectInfo(paths.cwd),
769
+ paths: documentPaths
770
+ };
771
+ const components = {};
772
+ if (Object.keys(schemas).length > 0) {
773
+ components.schemas = schemas;
774
+ }
775
+ if (Object.keys(securitySchemes).length > 0) {
776
+ components.securitySchemes = securitySchemes;
777
+ }
778
+ if (Object.keys(components).length > 0) {
779
+ document.components = components;
780
+ }
781
+ return document;
782
+ }
783
+ async function writeOpenApi(paths, routes, data = {}) {
784
+ await writeJson((0, import_node_path7.join)(paths.outDir, "openapi.json"), buildOpenApiDocument(paths, routes, data));
785
+ }
786
+
787
+ // src/generator/param-types.ts
788
+ var import_node_path8 = require("path");
789
+ function paramsType(params) {
790
+ if (params.length === 0) {
791
+ return "{}";
792
+ }
793
+ const unique = /* @__PURE__ */ new Map();
794
+ for (const param of params) {
795
+ unique.set(param.name, param);
796
+ }
797
+ const fields = [...unique.values()].map((param) => ` ${JSON.stringify(param.name)}: string;`).join("\n");
798
+ return `{
799
+ ${fields}
800
+ }`;
801
+ }
802
+ function varsType(typesDir, sharedFiles) {
803
+ if (sharedFiles.length === 0) {
804
+ return "{}";
805
+ }
806
+ return sharedFiles.map((file) => {
807
+ const spec = JSON.stringify(moduleSpecifier(typesDir, file));
808
+ return `(typeof import(${spec}) extends { middleware: infer M } ? import("@boon4681/giri").InferStackVars<M> : {})`;
809
+ }).join("\n & ");
810
+ }
811
+ function methodExports(typesDir, verbs) {
812
+ return verbs.map(({ method, file }) => {
813
+ const spec = JSON.stringify(moduleSpecifier(typesDir, file));
814
+ const input = `import("@boon4681/giri").RouteInputOf<typeof import(${spec})>`;
815
+ const vars = `Vars & import("@boon4681/giri").MiddlewareVarsOf<typeof import(${spec})>`;
816
+ return `export type ${method} = import("@boon4681/giri").Handle<Params, ${input}, ${vars}>;`;
817
+ });
818
+ }
819
+ async function writeParamTypes(paths, folders) {
820
+ for (const { dir, params, sharedFiles, verbs } of folders) {
821
+ const file = typeFilePath(paths, dir);
822
+ const typesDir = (0, import_node_path8.dirname)(file);
823
+ const lines = [
824
+ GENERATED_HEADER,
825
+ `export type Params = ${paramsType(params)};`,
826
+ "export type RouteParams = Params;",
827
+ `type Vars = ${varsType(typesDir, sharedFiles)};`,
828
+ "export type Middleware<Injects extends Record<string, unknown> = {}> =",
829
+ ' import("@boon4681/giri").Middleware<Params, import("@boon4681/giri").ValidatedInput, Injects>;',
830
+ 'export type Handle<Input extends import("@boon4681/giri").ValidatedInput = import("@boon4681/giri").ValidatedInput> =',
831
+ ' import("@boon4681/giri").Handle<Params, Input, Vars>;'
832
+ ];
833
+ if (verbs.length > 0) {
834
+ lines.push(...methodExports(typesDir, verbs));
835
+ }
836
+ lines.push("");
837
+ await writeGenerated(file, lines.join("\n"));
838
+ }
839
+ }
840
+
841
+ // src/generator/inputs.ts
842
+ function sanitize(schema) {
843
+ const { $schema, ...rest } = schema;
844
+ void $schema;
845
+ return rest;
846
+ }
847
+ function inputToJsonSchema(schema) {
848
+ if (!isGiriInputSchema(schema)) {
849
+ return void 0;
850
+ }
851
+ return sanitize(schema.toJsonSchema());
852
+ }
853
+ function bodyToJsonSchemas(value) {
854
+ if (!isGiriBodySchema(value)) {
855
+ return void 0;
856
+ }
857
+ const out = {};
858
+ for (const [contentType, schema] of Object.entries(value.contents)) {
859
+ const json = inputToJsonSchema(schema);
860
+ if (json) {
861
+ out[contentType] = json;
862
+ }
863
+ }
864
+ return Object.keys(out).length > 0 ? out : void 0;
865
+ }
866
+
867
+ // src/generator/route-meta.ts
868
+ function loadModule2(file) {
869
+ const resolved = require.resolve(file);
870
+ delete require.cache[resolved];
871
+ return require(resolved);
872
+ }
873
+ function interopDefault2(value) {
874
+ if (value && typeof value === "object" && "default" in value) {
875
+ return value.default;
876
+ }
877
+ return value;
878
+ }
879
+ function middlewareFunctions(value) {
880
+ const exported = interopDefault2(value);
881
+ if (typeof exported === "function") {
882
+ return [exported];
883
+ }
884
+ if (Array.isArray(exported)) {
885
+ return exported.filter((fn) => typeof fn === "function");
886
+ }
887
+ return [];
888
+ }
889
+ function readInput(routeModule) {
890
+ const input = {};
891
+ const body = bodyToJsonSchemas(routeModule.body);
892
+ const query = inputToJsonSchema(routeModule.query);
893
+ if (body) {
894
+ input.body = body;
895
+ }
896
+ if (query) {
897
+ input.query = query;
898
+ }
899
+ return input.body || input.query ? input : void 0;
900
+ }
901
+ function hiddenFrom(value) {
902
+ if (value === false) {
903
+ return true;
904
+ }
905
+ if (value === true) {
906
+ return false;
907
+ }
908
+ if (value && typeof value === "object" && "hidden" in value) {
909
+ return Boolean(value.hidden);
910
+ }
911
+ return void 0;
912
+ }
913
+ function collectHidden(route, routeModule, loadShared) {
914
+ let hidden = false;
915
+ for (const file of route.sharedFiles) {
916
+ const opinion = hiddenFrom(loadShared(file).openapi);
917
+ if (opinion !== void 0) {
918
+ hidden = opinion;
919
+ }
920
+ }
921
+ const verb = hiddenFrom(routeModule.openapi);
922
+ return verb ?? hidden;
923
+ }
924
+ function collectSecurity(route, routeModule, loadShared) {
925
+ const skipInherited = Boolean(
926
+ routeModule.config?.skipInherited
927
+ );
928
+ const middleware = [];
929
+ if (!skipInherited) {
930
+ for (const file of route.sharedFiles) {
931
+ middleware.push(...middlewareFunctions(loadShared(file).middleware));
932
+ }
933
+ }
934
+ middleware.push(...middlewareFunctions(routeModule.middleware));
935
+ const security = [];
936
+ const securitySchemes = {};
937
+ for (const fn of middleware) {
938
+ const openapi = fn.openapi;
939
+ if (openapi?.security) {
940
+ for (const requirement of openapi.security) {
941
+ if (!security.some((seen) => JSON.stringify(seen) === JSON.stringify(requirement))) {
942
+ security.push(requirement);
943
+ }
944
+ }
945
+ }
946
+ if (openapi?.securitySchemes) {
947
+ Object.assign(securitySchemes, openapi.securitySchemes);
948
+ }
949
+ }
950
+ return security.length > 0 || Object.keys(securitySchemes).length > 0 ? { security, securitySchemes } : void 0;
951
+ }
952
+ async function extractRouteMeta(config, paths, routes) {
953
+ const byFile = /* @__PURE__ */ new Map();
954
+ const { unregister } = await safeRegister();
955
+ const unregisterAlias = registerAliasResolver(config.alias, paths.cwd);
956
+ const sharedCache = /* @__PURE__ */ new Map();
957
+ const loadShared = (file) => {
958
+ if (!sharedCache.has(file)) {
959
+ try {
960
+ sharedCache.set(file, loadModule2(file));
961
+ } catch {
962
+ sharedCache.set(file, {});
963
+ }
964
+ }
965
+ return sharedCache.get(file);
966
+ };
967
+ try {
968
+ for (const route of routes) {
969
+ try {
970
+ const routeModule = loadModule2(route.file);
971
+ const meta = {};
972
+ const input = readInput(routeModule);
973
+ const security = collectSecurity(route, routeModule, loadShared);
974
+ const hidden = collectHidden(route, routeModule, loadShared);
975
+ if (input) {
976
+ meta.input = input;
977
+ }
978
+ if (security) {
979
+ meta.security = security;
980
+ }
981
+ if (hidden) {
982
+ meta.hidden = true;
983
+ }
984
+ if (meta.input || meta.security || meta.hidden) {
985
+ byFile.set(route.file, meta);
986
+ }
987
+ } catch {
988
+ }
989
+ }
990
+ } finally {
991
+ unregisterAlias();
992
+ unregister();
993
+ }
994
+ return byFile;
995
+ }
996
+
997
+ // src/generator/route-types.ts
998
+ var import_node_path9 = require("path");
999
+ async function writeRouteTypes(paths, routes) {
1000
+ const file = (0, import_node_path9.join)(paths.outDir, "routes.d.ts");
1001
+ const lines = [
1002
+ GENERATED_HEADER,
1003
+ "export interface RouteParams {"
1004
+ ];
1005
+ for (const route of routes) {
1006
+ const typeFile = typeFilePath(paths, route.routeDir);
1007
+ lines.push(
1008
+ ` ${JSON.stringify(`${route.method} ${route.path}`)}: import(${JSON.stringify(
1009
+ importPath(file, typeFile)
1010
+ )}).Params;`
1011
+ );
1012
+ }
1013
+ lines.push("}", "");
1014
+ await writeGenerated(file, lines.join("\n"));
1015
+ }
1016
+
1017
+ // src/generator/schema/program.ts
1018
+ var import_typescript = __toESM(require("typescript"));
1019
+ var DEFAULT_OPTIONS = {
1020
+ target: import_typescript.default.ScriptTarget.ES2022,
1021
+ module: import_typescript.default.ModuleKind.NodeNext,
1022
+ moduleResolution: import_typescript.default.ModuleResolutionKind.NodeNext,
1023
+ strict: true,
1024
+ skipLibCheck: true,
1025
+ noEmit: true
1026
+ };
1027
+ function createSchemaProgram(paths, routeFiles) {
1028
+ let options = { ...DEFAULT_OPTIONS };
1029
+ const configPath = import_typescript.default.findConfigFile(paths.cwd, import_typescript.default.sys.fileExists, "tsconfig.json");
1030
+ if (configPath) {
1031
+ const parsed = import_typescript.default.getParsedCommandLineOfConfigFile(configPath, {}, {
1032
+ ...import_typescript.default.sys,
1033
+ onUnRecoverableConfigFileDiagnostic: () => {
1034
+ }
1035
+ });
1036
+ if (parsed) {
1037
+ options = { ...parsed.options, noEmit: true };
1038
+ }
1039
+ }
1040
+ return import_typescript.default.createProgram(routeFiles, options);
1041
+ }
1042
+
1043
+ // src/generator/schema/responses.ts
1044
+ var import_typescript3 = __toESM(require("typescript"));
1045
+
1046
+ // src/generator/schema/json-schema.ts
1047
+ var import_typescript2 = __toESM(require("typescript"));
1048
+ function createWalkContext(checker, location) {
1049
+ return {
1050
+ checker,
1051
+ location,
1052
+ defs: {},
1053
+ inProgress: /* @__PURE__ */ new Map(),
1054
+ usedDefs: /* @__PURE__ */ new Set(),
1055
+ warnings: []
1056
+ };
1057
+ }
1058
+ function typeId(type) {
1059
+ return type.id;
1060
+ }
1061
+ function intrinsicName(type) {
1062
+ return type.intrinsicName;
1063
+ }
1064
+ function isDateType(type) {
1065
+ const symbol = type.getSymbol() ?? type.aliasSymbol;
1066
+ return symbol?.getName() === "Date";
1067
+ }
1068
+ function literalValuesOf(types) {
1069
+ const values = [];
1070
+ for (const member of types) {
1071
+ if (member.isStringLiteral() || member.isNumberLiteral()) {
1072
+ values.push(member.value);
1073
+ } else if (member.flags & import_typescript2.default.TypeFlags.BooleanLiteral) {
1074
+ values.push(intrinsicName(member) === "true");
1075
+ } else {
1076
+ return void 0;
1077
+ }
1078
+ }
1079
+ return values;
1080
+ }
1081
+ function walkUnion(type, ctx) {
1082
+ const flag = import_typescript2.default.TypeFlags.Undefined | import_typescript2.default.TypeFlags.Void | import_typescript2.default.TypeFlags.Never;
1083
+ const members = type.types.filter((member) => !(member.flags & flag));
1084
+ if (members.length === 1) {
1085
+ return walkType(members[0], ctx);
1086
+ }
1087
+ const enumValues = literalValuesOf(members);
1088
+ if (enumValues) {
1089
+ return { enum: enumValues };
1090
+ }
1091
+ return { anyOf: members.map((member) => walkType(member, ctx)) };
1092
+ }
1093
+ function buildObjectSchema(type, ctx) {
1094
+ const { checker } = ctx;
1095
+ const indexInfo = checker.getIndexInfoOfType(type, import_typescript2.default.IndexKind.String) ?? checker.getIndexInfoOfType(type, import_typescript2.default.IndexKind.Number);
1096
+ const properties = {};
1097
+ const required = [];
1098
+ for (const symbol of checker.getPropertiesOfType(type)) {
1099
+ const name = symbol.getName();
1100
+ const propType = checker.getTypeOfSymbolAtLocation(symbol, ctx.location);
1101
+ const optional = Boolean(symbol.getFlags() & import_typescript2.default.SymbolFlags.Optional) || Boolean(propType.flags & import_typescript2.default.TypeFlags.Union && propType.types.some((t) => t.flags & import_typescript2.default.TypeFlags.Undefined));
1102
+ properties[name] = walkType(propType, ctx);
1103
+ if (!optional) {
1104
+ required.push(name);
1105
+ }
1106
+ }
1107
+ const schema = { type: "object" };
1108
+ if (Object.keys(properties).length > 0) {
1109
+ schema.properties = properties;
1110
+ }
1111
+ if (required.length > 0) {
1112
+ schema.required = required;
1113
+ }
1114
+ if (indexInfo) {
1115
+ schema.additionalProperties = walkType(indexInfo.type, ctx);
1116
+ } else if (Object.keys(properties).length > 0) {
1117
+ schema.additionalProperties = false;
1118
+ }
1119
+ return schema;
1120
+ }
1121
+ function defName(type) {
1122
+ const symbol = type.getSymbol() ?? type.aliasSymbol;
1123
+ const name = symbol?.getName();
1124
+ if (name && name !== "__type" && name !== "__object") {
1125
+ return name;
1126
+ }
1127
+ return `Anonymous${typeId(type)}`;
1128
+ }
1129
+ function walkObject(type, ctx) {
1130
+ const { checker } = ctx;
1131
+ if (isDateType(type)) {
1132
+ return { type: "string", format: "date-time" };
1133
+ }
1134
+ if (checker.isArrayType(type)) {
1135
+ const [element] = checker.getTypeArguments(type);
1136
+ return { type: "array", items: element ? walkType(element, ctx) : {} };
1137
+ }
1138
+ if (checker.isTupleType(type)) {
1139
+ const elements = checker.getTypeArguments(type);
1140
+ return { type: "array", items: elements.map((element) => walkType(element, ctx)) };
1141
+ }
1142
+ const id = typeId(type);
1143
+ const existing = ctx.inProgress.get(id);
1144
+ if (existing) {
1145
+ ctx.usedDefs.add(existing);
1146
+ return { $ref: `#/$defs/${existing}` };
1147
+ }
1148
+ const name = defName(type);
1149
+ ctx.inProgress.set(id, name);
1150
+ const schema = buildObjectSchema(type, ctx);
1151
+ ctx.inProgress.delete(id);
1152
+ if (ctx.usedDefs.has(name)) {
1153
+ ctx.defs[name] = schema;
1154
+ return { $ref: `#/$defs/${name}` };
1155
+ }
1156
+ return schema;
1157
+ }
1158
+ function walkType(type, ctx) {
1159
+ const flags = type.flags;
1160
+ if (flags & (import_typescript2.default.TypeFlags.Any | import_typescript2.default.TypeFlags.Unknown)) {
1161
+ return {};
1162
+ }
1163
+ if (flags & import_typescript2.default.TypeFlags.Null) {
1164
+ return { type: "null" };
1165
+ }
1166
+ if (flags & (import_typescript2.default.TypeFlags.Undefined | import_typescript2.default.TypeFlags.Void)) {
1167
+ return {};
1168
+ }
1169
+ if (flags & (import_typescript2.default.TypeFlags.BigInt | import_typescript2.default.TypeFlags.BigIntLiteral)) {
1170
+ ctx.warnings.push("bigint is not JSON-serializable (JSON.stringify throws); documented as string.");
1171
+ return { type: "string" };
1172
+ }
1173
+ if (type.isStringLiteral()) {
1174
+ return { type: "string", const: type.value };
1175
+ }
1176
+ if (type.isNumberLiteral()) {
1177
+ return { type: "number", const: type.value };
1178
+ }
1179
+ if (flags & import_typescript2.default.TypeFlags.BooleanLiteral) {
1180
+ return { type: "boolean", const: intrinsicName(type) === "true" };
1181
+ }
1182
+ if (flags & import_typescript2.default.TypeFlags.String) {
1183
+ return { type: "string" };
1184
+ }
1185
+ if (flags & import_typescript2.default.TypeFlags.Number) {
1186
+ return { type: "number" };
1187
+ }
1188
+ if (flags & import_typescript2.default.TypeFlags.Boolean) {
1189
+ return { type: "boolean" };
1190
+ }
1191
+ if (type.isUnion()) {
1192
+ return walkUnion(type, ctx);
1193
+ }
1194
+ if (flags & import_typescript2.default.TypeFlags.Object || type.isIntersection()) {
1195
+ return walkObject(type, ctx);
1196
+ }
1197
+ return {};
1198
+ }
1199
+
1200
+ // src/generator/schema/responses.ts
1201
+ function findHandleFunction(source) {
1202
+ let found;
1203
+ const isExported = (node) => import_typescript3.default.canHaveModifiers(node) && (import_typescript3.default.getModifiers(node)?.some((m) => m.kind === import_typescript3.default.SyntaxKind.ExportKeyword) ?? false);
1204
+ for (const statement of source.statements) {
1205
+ if (import_typescript3.default.isFunctionDeclaration(statement) && statement.name?.text === "handle" && isExported(statement)) {
1206
+ found = statement;
1207
+ }
1208
+ if (import_typescript3.default.isVariableStatement(statement) && isExported(statement)) {
1209
+ for (const declaration of statement.declarationList.declarations) {
1210
+ if (import_typescript3.default.isIdentifier(declaration.name) && declaration.name.text === "handle" && declaration.initializer && (import_typescript3.default.isArrowFunction(declaration.initializer) || import_typescript3.default.isFunctionExpression(declaration.initializer))) {
1211
+ found = declaration.initializer;
1212
+ }
1213
+ }
1214
+ }
1215
+ }
1216
+ return found;
1217
+ }
1218
+ function collectReturnExpressions(fn) {
1219
+ if (import_typescript3.default.isArrowFunction(fn) && !import_typescript3.default.isBlock(fn.body)) {
1220
+ return [fn.body];
1221
+ }
1222
+ if (!fn.body) {
1223
+ return [];
1224
+ }
1225
+ const expressions = [];
1226
+ const visit = (node) => {
1227
+ if (import_typescript3.default.isFunctionDeclaration(node) || import_typescript3.default.isFunctionExpression(node) || import_typescript3.default.isArrowFunction(node)) {
1228
+ return;
1229
+ }
1230
+ if (import_typescript3.default.isReturnStatement(node) && node.expression) {
1231
+ expressions.push(node.expression);
1232
+ }
1233
+ import_typescript3.default.forEachChild(node, visit);
1234
+ };
1235
+ import_typescript3.default.forEachChild(fn.body, visit);
1236
+ return expressions;
1237
+ }
1238
+ function propertyType(checker, type, name, location) {
1239
+ const symbol = checker.getPropertyOfType(type, name);
1240
+ return symbol ? checker.getTypeOfSymbolAtLocation(symbol, location) : void 0;
1241
+ }
1242
+ function isTypedResponse(checker, type) {
1243
+ return Boolean(
1244
+ checker.getPropertyOfType(type, "data") && checker.getPropertyOfType(type, "status") && checker.getPropertyOfType(type, "format")
1245
+ );
1246
+ }
1247
+ function readFromCall(checker, expression) {
1248
+ if (!import_typescript3.default.isCallExpression(expression) || !import_typescript3.default.isPropertyAccessExpression(expression.expression)) {
1249
+ return void 0;
1250
+ }
1251
+ const method = expression.expression.name.text;
1252
+ if (method !== "json" && method !== "text") {
1253
+ return void 0;
1254
+ }
1255
+ if (!isTypedResponse(checker, checker.getTypeAtLocation(expression))) {
1256
+ return void 0;
1257
+ }
1258
+ const [dataArg, statusArg] = expression.arguments;
1259
+ if (!dataArg) {
1260
+ return void 0;
1261
+ }
1262
+ let status = 200;
1263
+ if (statusArg) {
1264
+ const statusType = checker.getTypeAtLocation(statusArg);
1265
+ status = statusType.isNumberLiteral() ? statusType.value : "default";
1266
+ }
1267
+ return { status, format: method === "text" ? "text" : "json", data: checker.getTypeAtLocation(dataArg) };
1268
+ }
1269
+ function readFromType(checker, type, location) {
1270
+ const dataType = propertyType(checker, type, "data", location);
1271
+ const statusType = propertyType(checker, type, "status", location);
1272
+ const formatType = propertyType(checker, type, "format", location);
1273
+ if (!dataType || !statusType || !formatType) {
1274
+ return void 0;
1275
+ }
1276
+ const status = statusType.isNumberLiteral() ? statusType.value : "default";
1277
+ const format = formatType.isStringLiteral() && formatType.value === "text" ? "text" : "json";
1278
+ return { status, format, data: dataType };
1279
+ }
1280
+ function constituents(type) {
1281
+ return type.isUnion() ? type.types : [type];
1282
+ }
1283
+ function extractRouteResponses(program, file) {
1284
+ const result = { responses: [], opaque: false, warnings: [], $defs: {} };
1285
+ const source = program.getSourceFile(file);
1286
+ if (!source) {
1287
+ return result;
1288
+ }
1289
+ const checker = program.getTypeChecker();
1290
+ const fn = findHandleFunction(source);
1291
+ if (!fn) {
1292
+ return result;
1293
+ }
1294
+ const ctx = createWalkContext(checker, fn);
1295
+ const byStatus = /* @__PURE__ */ new Map();
1296
+ const record = (hit) => {
1297
+ const schema = walkType(hit.data, ctx);
1298
+ const bucket = byStatus.get(hit.status) ?? { format: hit.format, schemas: [] };
1299
+ bucket.schemas.push(schema);
1300
+ byStatus.set(hit.status, bucket);
1301
+ };
1302
+ for (const expression of collectReturnExpressions(fn)) {
1303
+ const fromCall = readFromCall(checker, expression);
1304
+ if (fromCall) {
1305
+ record(fromCall);
1306
+ continue;
1307
+ }
1308
+ let matched = false;
1309
+ for (const member of constituents(checker.getTypeAtLocation(expression))) {
1310
+ const hit = readFromType(checker, member, expression);
1311
+ if (hit) {
1312
+ record(hit);
1313
+ matched = true;
1314
+ }
1315
+ }
1316
+ if (!matched) {
1317
+ result.opaque = true;
1318
+ }
1319
+ }
1320
+ for (const [status, { format, schemas }] of byStatus) {
1321
+ const schema = schemas.length === 1 ? schemas[0] : { anyOf: schemas };
1322
+ result.responses.push({ status, format, schema });
1323
+ }
1324
+ result.responses.sort((a, b) => Number(a.status) - Number(b.status));
1325
+ result.warnings = ctx.warnings;
1326
+ result.$defs = ctx.defs;
1327
+ return result;
1328
+ }
1329
+
1330
+ // src/generator/tsconfig.ts
1331
+ var import_node_path10 = require("path");
1332
+ function normalizeAlias(alias, paths) {
1333
+ const result = {};
1334
+ for (const [key, value] of Object.entries(alias ?? {})) {
1335
+ const targets = Array.isArray(value) ? value : [value];
1336
+ result[key] = targets.map(
1337
+ (target) => relativeConfigPath(paths.outDir, (0, import_node_path10.resolve)(paths.cwd, target))
1338
+ );
1339
+ }
1340
+ return result;
1341
+ }
1342
+ async function writeTsConfig(paths, config) {
1343
+ const file = (0, import_node_path10.join)(paths.outDir, "tsconfig.json");
1344
+ await writeJson(file, {
1345
+ compilerOptions: {
1346
+ rootDirs: [
1347
+ "..",
1348
+ "./types"
1349
+ ],
1350
+ paths: {
1351
+ // The tsconfig lives in outDir, so `$giri/*` maps to its own folder.
1352
+ "$giri/*": ["./*"],
1353
+ ...normalizeAlias(config.alias, paths)
1354
+ },
1355
+ plugins: [
1356
+ {
1357
+ name: "@boon4681/giri/tsc"
1358
+ }
1359
+ ]
1360
+ },
1361
+ include: [
1362
+ relativeConfigPath(paths.outDir, (0, import_node_path10.join)(paths.cwd, "src")),
1363
+ relativeConfigPath(paths.outDir, (0, import_node_path10.join)(paths.cwd, "giri.config.ts")),
1364
+ "./types/**/*.d.ts"
1365
+ ]
1366
+ });
1367
+ }
1368
+
1369
+ // src/generator/sync.ts
1370
+ async function typeFolders(paths, routes) {
1371
+ const verbsByDir = /* @__PURE__ */ new Map();
1372
+ for (const route of routes) {
1373
+ const key = slash(route.routeDir);
1374
+ const list = verbsByDir.get(key) ?? [];
1375
+ list.push({ method: route.method, file: route.file });
1376
+ verbsByDir.set(key, list);
1377
+ }
1378
+ const dirs = await scanRouteFolders(paths.routesDir);
1379
+ return dirs.map((dir) => ({
1380
+ dir,
1381
+ params: routeParamsForDir(paths.routesDir, dir),
1382
+ sharedFiles: sharedFilesForDir(paths.routesDir, dir),
1383
+ verbs: verbsByDir.get(slash(dir)) ?? []
1384
+ }));
1385
+ }
1386
+ function extractResponses(paths, routes) {
1387
+ const byFile = /* @__PURE__ */ new Map();
1388
+ if (routes.length === 0) {
1389
+ return byFile;
1390
+ }
1391
+ try {
1392
+ const files = [...new Set(routes.map((route) => route.file))];
1393
+ const appTypes = (0, import_node_path11.join)(paths.outDir, "types", "app.d.ts");
1394
+ const program = createSchemaProgram(
1395
+ paths,
1396
+ (0, import_node_fs6.existsSync)(appTypes) ? [...files, appTypes] : files
1397
+ );
1398
+ for (const file of files) {
1399
+ byFile.set(file, extractRouteResponses(program, file));
1400
+ }
1401
+ } catch (error) {
1402
+ console.warn(`giri: skipped response schema generation (${error.message}).`);
1403
+ }
1404
+ return byFile;
1405
+ }
1406
+ async function extractMeta(config, paths, routes) {
1407
+ const inputsByFile = /* @__PURE__ */ new Map();
1408
+ const securityByFile = /* @__PURE__ */ new Map();
1409
+ const hiddenFiles = /* @__PURE__ */ new Set();
1410
+ if (routes.length === 0) {
1411
+ return { inputsByFile, securityByFile, hiddenFiles };
1412
+ }
1413
+ try {
1414
+ const meta = await extractRouteMeta(config, paths, routes);
1415
+ for (const [file, entry] of meta) {
1416
+ if (entry.input) {
1417
+ inputsByFile.set(file, entry.input);
1418
+ }
1419
+ if (entry.security) {
1420
+ securityByFile.set(file, entry.security);
1421
+ }
1422
+ if (entry.hidden) {
1423
+ hiddenFiles.add(file);
1424
+ }
1425
+ }
1426
+ } catch (error) {
1427
+ console.warn(`giri: skipped input/security generation (${error.message}).`);
1428
+ }
1429
+ return { inputsByFile, securityByFile, hiddenFiles };
1430
+ }
1431
+ async function syncProject(config, options = {}) {
1432
+ const paths = resolveGiriPaths(config, options.cwd);
1433
+ assertSafeOutDir(paths);
1434
+ const routes = await scanRoutes(paths.routesDir);
1435
+ const folders = await typeFolders(paths, routes);
1436
+ await (0, import_promises3.mkdir)(paths.outDir, { recursive: true });
1437
+ await writeParamTypes(paths, folders);
1438
+ await writeRouteTypes(paths, routes);
1439
+ await writeAppTypes(paths);
1440
+ await writeTsConfig(paths, config);
1441
+ const responsesByFile = extractResponses(paths, routes);
1442
+ const { inputsByFile, securityByFile, hiddenFiles } = await extractMeta(config, paths, routes);
1443
+ const data = { responsesByFile, inputsByFile, securityByFile, hiddenFiles };
1444
+ await writeManifest(paths, routes, data);
1445
+ await writeOpenApi(paths, routes, data);
1446
+ await pruneDir(
1447
+ paths.outDir,
1448
+ /* @__PURE__ */ new Set([
1449
+ (0, import_node_path11.join)(paths.outDir, "tsconfig.json"),
1450
+ (0, import_node_path11.join)(paths.outDir, "manifest.json"),
1451
+ (0, import_node_path11.join)(paths.outDir, "openapi.json"),
1452
+ (0, import_node_path11.join)(paths.outDir, "routes.d.ts"),
1453
+ (0, import_node_path11.join)(paths.outDir, "types", "app.d.ts"),
1454
+ ...folders.map((folder) => typeFilePath(paths, folder.dir))
1455
+ ])
1456
+ );
1457
+ return { paths, routes, folders, data };
1458
+ }
1459
+
1460
+ // src/generator/watch.ts
1461
+ var import_node_fs7 = require("fs");
1462
+ var import_node_path13 = require("path");
1463
+
1464
+ // src/loader/module-loader.ts
1465
+ var import_node_path12 = require("path");
1466
+ var toSlash = (path) => path.split(import_node_path12.sep).join("/");
1467
+ var isProjectModule = (id, root) => {
1468
+ return id.startsWith(root) && !id.includes(`${import_node_path12.sep}node_modules${import_node_path12.sep}`) && !id.includes(`${import_node_path12.sep}.giri${import_node_path12.sep}`);
1469
+ };
1470
+ var buildModuleGraph = (cwd) => {
1471
+ const root = (0, import_node_path12.resolve)(cwd) + import_node_path12.sep;
1472
+ const importers = /* @__PURE__ */ new Map();
1473
+ const nodes = /* @__PURE__ */ new Set();
1474
+ for (const id of Object.keys(require.cache)) {
1475
+ if (!isProjectModule(id, root)) {
1476
+ continue;
1477
+ }
1478
+ const mod = require.cache[id];
1479
+ if (!mod) {
1480
+ continue;
1481
+ }
1482
+ nodes.add(toSlash(id));
1483
+ for (const child of mod.children) {
1484
+ if (!isProjectModule(child.id, root)) {
1485
+ continue;
1486
+ }
1487
+ nodes.add(toSlash(child.id));
1488
+ const dep = toSlash(child.id);
1489
+ let set = importers.get(dep);
1490
+ if (!set) {
1491
+ set = /* @__PURE__ */ new Set();
1492
+ importers.set(dep, set);
1493
+ }
1494
+ set.add(toSlash(id));
1495
+ }
1496
+ }
1497
+ return { importers, nodes };
1498
+ };
1499
+ var collectDependents = (graph, start) => {
1500
+ const out = /* @__PURE__ */ new Set([start]);
1501
+ const stack = [start];
1502
+ while (stack.length > 0) {
1503
+ const current = stack.pop();
1504
+ for (const importer of graph.importers.get(current) ?? []) {
1505
+ if (!out.has(importer)) {
1506
+ out.add(importer);
1507
+ stack.push(importer);
1508
+ }
1509
+ }
1510
+ }
1511
+ return out;
1512
+ };
1513
+ var purgeModules = (files) => {
1514
+ for (const id of Object.keys(require.cache)) {
1515
+ if (files.has(toSlash(id))) {
1516
+ delete require.cache[id];
1517
+ }
1518
+ }
1519
+ };
1520
+ var purgeProjectModules = (cwd) => {
1521
+ const root = (0, import_node_path12.resolve)(cwd) + import_node_path12.sep;
1522
+ for (const id of Object.keys(require.cache)) {
1523
+ if (isProjectModule(id, root)) {
1524
+ delete require.cache[id];
1525
+ }
1526
+ }
1527
+ };
1528
+
1529
+ // src/generator/watch.ts
1530
+ function createWatchUpdater(config, initial) {
1531
+ const paths = initial.paths;
1532
+ let routes = initial.routes;
1533
+ const data = initial.data;
1534
+ const fullResync = async () => {
1535
+ purgeProjectModules(paths.cwd);
1536
+ const result = await syncProject(config, { cwd: paths.cwd });
1537
+ routes = result.routes;
1538
+ data.responsesByFile = result.data.responsesByFile;
1539
+ data.inputsByFile = result.data.inputsByFile;
1540
+ data.securityByFile = result.data.securityByFile;
1541
+ data.hiddenFiles = result.data.hiddenFiles;
1542
+ return "full";
1543
+ };
1544
+ const reextractRoute = async (route) => {
1545
+ const key = route.file;
1546
+ try {
1547
+ const appTypes = (0, import_node_path13.join)(paths.outDir, "types", "app.d.ts");
1548
+ const program = createSchemaProgram(paths, (0, import_node_fs7.existsSync)(appTypes) ? [key, appTypes] : [key]);
1549
+ data.responsesByFile.set(key, extractRouteResponses(program, key));
1550
+ } catch {
1551
+ }
1552
+ try {
1553
+ const meta = await extractRouteMeta(config, paths, [route]);
1554
+ const entry = meta.get(key);
1555
+ data.inputsByFile.delete(key);
1556
+ data.securityByFile.delete(key);
1557
+ data.hiddenFiles.delete(key);
1558
+ if (entry?.input) {
1559
+ data.inputsByFile.set(key, entry.input);
1560
+ }
1561
+ if (entry?.security) {
1562
+ data.securityByFile.set(key, entry.security);
1563
+ }
1564
+ if (entry?.hidden) {
1565
+ data.hiddenFiles.add(key);
1566
+ }
1567
+ } catch {
1568
+ }
1569
+ };
1570
+ return {
1571
+ async apply(filename) {
1572
+ if (!filename) {
1573
+ return fullResync();
1574
+ }
1575
+ const abs = (0, import_node_path13.resolve)((0, import_node_path13.dirname)(paths.routesDir), filename);
1576
+ const file = slash(abs);
1577
+ if (!(0, import_node_fs7.existsSync)(abs)) {
1578
+ return fullResync();
1579
+ }
1580
+ const graph = buildModuleGraph(paths.cwd);
1581
+ const isRoute = routes.some((candidate) => slash(candidate.file) === file);
1582
+ if (!graph.nodes.has(file) && !isRoute) {
1583
+ return fullResync();
1584
+ }
1585
+ const dependents = collectDependents(graph, file);
1586
+ const affected = routes.filter(
1587
+ (route) => dependents.has(slash(route.file)) || route.sharedFiles.some((shared) => dependents.has(slash(shared)))
1588
+ );
1589
+ purgeModules(dependents);
1590
+ for (const route of affected) {
1591
+ await reextractRoute(route);
1592
+ }
1593
+ await writeManifest(paths, routes, data);
1594
+ await writeOpenApi(paths, routes, data);
1595
+ return "incremental";
1596
+ }
1597
+ };
1598
+ }
1599
+
1600
+ // src/lifecycle.ts
1601
+ var import_node_fs8 = require("fs");
1602
+ var import_node_path14 = require("path");
1603
+ var MAIN_EXTENSIONS2 = ["ts", "tsx", "mts", "cts", "js", "jsx", "mjs", "cjs"];
1604
+ function resolveMainFile(cwd) {
1605
+ for (const ext of MAIN_EXTENSIONS2) {
1606
+ const file = (0, import_node_path14.join)(cwd, "src", `main.${ext}`);
1607
+ if ((0, import_node_fs8.existsSync)(file)) {
1608
+ return file;
1609
+ }
1610
+ }
1611
+ return void 0;
1612
+ }
1613
+ async function loadLifecycle(cwd = process.cwd()) {
1614
+ const file = resolveMainFile((0, import_node_path14.resolve)(cwd));
1615
+ if (!file) {
1616
+ return {};
1617
+ }
1618
+ const { unregister } = await safeRegister();
1619
+ try {
1620
+ const resolved = require.resolve(file);
1621
+ delete require.cache[resolved];
1622
+ const loaded = require(resolved);
1623
+ const lifecycle = { file };
1624
+ if (loaded.init !== void 0) {
1625
+ if (typeof loaded.init !== "function") {
1626
+ throw new Error(`${file}: "init" must be a function.`);
1627
+ }
1628
+ lifecycle.init = loaded.init;
1629
+ }
1630
+ if (loaded.teardown !== void 0) {
1631
+ if (typeof loaded.teardown !== "function") {
1632
+ throw new Error(`${file}: "teardown" must be a function.`);
1633
+ }
1634
+ lifecycle.teardown = loaded.teardown;
1635
+ }
1636
+ return lifecycle;
1637
+ } finally {
1638
+ unregister();
1639
+ }
1640
+ }
1641
+ async function runInit(lifecycle) {
1642
+ if (!lifecycle.init) {
1643
+ return {};
1644
+ }
1645
+ const services = await lifecycle.init();
1646
+ return services ?? {};
1647
+ }
1648
+
1649
+ // src/logger.ts
1650
+ var noColor = !process.stdout.isTTY || process.env.NO_COLOR !== void 0 || process.env.TERM === "dumb" || process.env.FORCE_COLOR === "0";
1651
+ function paint(open, close) {
1652
+ return (text) => noColor ? text : `\x1B[${open}m${text}\x1B[${close}m`;
1653
+ }
1654
+ var color = {
1655
+ dim: paint(2, 22),
1656
+ bold: paint(1, 22),
1657
+ red: paint(31, 39),
1658
+ green: paint(32, 39),
1659
+ yellow: paint(33, 39),
1660
+ blue: paint(34, 39),
1661
+ magenta: paint(35, 39),
1662
+ cyan: paint(36, 39),
1663
+ gray: paint(90, 39)
1664
+ };
1665
+ var highlight = (text) => color.green(text);
1666
+ var muted = (text) => color.dim(text);
1667
+ function timestamp() {
1668
+ const now = /* @__PURE__ */ new Date();
1669
+ const pad = (n) => String(n).padStart(2, "0");
1670
+ return `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
1671
+ }
1672
+ var TAG = "giri";
1673
+ function line(tag2, message, scope) {
1674
+ const parts = [color.gray(timestamp()), tag2];
1675
+ if (scope) {
1676
+ parts.push(color.dim(`(${scope})`));
1677
+ }
1678
+ parts.push(message);
1679
+ return parts.join(" ");
1680
+ }
1681
+ var tag = {
1682
+ info: color.bold(color.cyan(`[${TAG}]`)),
1683
+ warn: color.bold(color.yellow(`[${TAG}]`)),
1684
+ error: color.bold(color.red(`[${TAG}]`))
1685
+ };
1686
+ var log2 = {
1687
+ info(message, scope) {
1688
+ console.log(line(tag.info, message, scope));
1689
+ },
1690
+ success(message, scope) {
1691
+ console.log(line(tag.info, color.green(message), scope));
1692
+ },
1693
+ warn(message, scope) {
1694
+ console.warn(line(tag.warn, color.yellow(message), scope));
1695
+ },
1696
+ error(message, scope) {
1697
+ console.error(line(tag.error, color.red(message), scope));
1698
+ },
1699
+ ready(url) {
1700
+ console.log(line(tag.info, `${color.green("ready")} on ${color.cyan(url)}`));
1701
+ },
1702
+ change(verb, path, count) {
1703
+ const suffix = count && count > 1 ? ` ${color.dim(`(x${count})`)}` : "";
1704
+ console.log(line(tag.info, `${color.green(verb)} ${highlight(path)}${suffix}`, "watch"));
1705
+ }
1706
+ };
1707
+
1708
+ // src/cli.ts
1709
+ var ADAPTERS = [
1710
+ {
1711
+ value: "hono",
1712
+ label: "Hono",
1713
+ hint: "recommended, ships today",
1714
+ available: true,
1715
+ importLine: 'import { hono } from "@boon4681/giri/adapters/hono";',
1716
+ expr: "hono()",
1717
+ deps: ["hono", "@hono/node-server"]
1718
+ }
1719
+ ];
1720
+ function help() {
1721
+ console.log(`giri
1722
+
1723
+ Usage:
1724
+ giri init [--adapter hono] [--pm npm|yarn|pnpm|bun] [--no-install] [-y]
1725
+ giri sync
1726
+ giri serve [--port 3000] [--host 127.0.0.1] [--no-watch]
1727
+ giri build
1728
+ `);
1729
+ }
1730
+ function parseInitFlags(args) {
1731
+ const flags = { yes: false };
1732
+ const managers = ["npm", "yarn", "pnpm", "bun"];
1733
+ for (let index = 0; index < args.length; index += 1) {
1734
+ const arg = args[index];
1735
+ if (arg === "--adapter" || arg === "-a") {
1736
+ flags.adapter = args[++index];
1737
+ } else if (arg === "--pm" || arg === "--package-manager") {
1738
+ const value = args[++index];
1739
+ if (!managers.includes(value)) {
1740
+ throw new Error(`Unknown package manager: ${value} (expected ${managers.join(", ")})`);
1741
+ }
1742
+ flags.packageManager = value;
1743
+ } else if (arg === "--install") {
1744
+ flags.install = true;
1745
+ } else if (arg === "--no-install") {
1746
+ flags.install = false;
1747
+ } else if (arg === "-y" || arg === "--yes") {
1748
+ flags.yes = true;
1749
+ } else {
1750
+ throw new Error(`Unknown option: ${arg}`);
1751
+ }
1752
+ }
1753
+ return flags;
1754
+ }
1755
+ function parseFlags(args) {
1756
+ const flags = { watch: true };
1757
+ for (let index = 0; index < args.length; index += 1) {
1758
+ const arg = args[index];
1759
+ if (arg === "--port" || arg === "-p") {
1760
+ flags.port = Number(args[++index]);
1761
+ } else if (arg === "--host" || arg === "--hostname") {
1762
+ flags.hostname = args[++index];
1763
+ } else if (arg === "--no-watch") {
1764
+ flags.watch = false;
1765
+ } else {
1766
+ throw new Error(`Unknown option: ${arg}`);
1767
+ }
1768
+ }
1769
+ return flags;
1770
+ }
1771
+ async function ensureGitignore(cwd) {
1772
+ const file = (0, import_node_path15.join)(cwd, ".gitignore");
1773
+ const entry = ".giri";
1774
+ if (!(0, import_node_fs9.existsSync)(file)) {
1775
+ await (0, import_promises4.writeFile)(file, `${entry}
1776
+ `);
1777
+ return;
1778
+ }
1779
+ const content = await (0, import_promises4.readFile)(file, "utf8");
1780
+ if (!content.split(/\r?\n/).includes(entry)) {
1781
+ await (0, import_promises4.appendFile)(file, `${content.endsWith("\n") ? "" : "\n"}${entry}
1782
+ `);
1783
+ }
1784
+ }
1785
+ async function ensureTsConfig(cwd) {
1786
+ const file = (0, import_node_path15.join)(cwd, "tsconfig.json");
1787
+ if ((0, import_node_fs9.existsSync)(file)) {
1788
+ return;
1789
+ }
1790
+ await (0, import_promises4.writeFile)(
1791
+ file,
1792
+ `${JSON.stringify(
1793
+ {
1794
+ extends: "./.giri/tsconfig.json",
1795
+ compilerOptions: {
1796
+ target: "ES2022",
1797
+ lib: ["ES2022", "DOM"],
1798
+ module: "NodeNext",
1799
+ moduleResolution: "NodeNext",
1800
+ strict: true,
1801
+ esModuleInterop: true,
1802
+ forceConsistentCasingInFileNames: true,
1803
+ skipLibCheck: true,
1804
+ types: ["node"]
1805
+ }
1806
+ },
1807
+ null,
1808
+ 2
1809
+ )}
1810
+ `
1811
+ );
1812
+ }
1813
+ function detectPackageManager() {
1814
+ const ua = process.env.npm_config_user_agent ?? "";
1815
+ if (ua.startsWith("yarn")) return "yarn";
1816
+ if (ua.startsWith("pnpm")) return "pnpm";
1817
+ if (ua.startsWith("bun")) return "bun";
1818
+ return "npm";
1819
+ }
1820
+ function installArgs(pm, deps, dev) {
1821
+ if (pm === "npm") return ["install", ...dev ? ["--save-dev"] : [], ...deps];
1822
+ if (pm === "bun") return ["add", ...dev ? ["--dev"] : [], ...deps];
1823
+ return ["add", ...dev ? ["--dev"] : [], ...deps];
1824
+ }
1825
+ function runCommand(cmd, args, cwd) {
1826
+ return new Promise((resolvePromise, reject) => {
1827
+ const child = (0, import_node_child_process.spawn)(cmd, args, { cwd, stdio: "inherit", shell: process.platform === "win32" });
1828
+ child.on("error", reject);
1829
+ child.on("close", (code) => {
1830
+ if (code === 0) {
1831
+ resolvePromise();
1832
+ } else {
1833
+ reject(new Error(`${cmd} ${args.join(" ")} exited with code ${code}`));
1834
+ }
1835
+ });
1836
+ });
1837
+ }
1838
+ function configSource(adapter) {
1839
+ return [
1840
+ 'import { defineConfig } from "@boon4681/giri";',
1841
+ adapter.importLine,
1842
+ "",
1843
+ "export default defineConfig({",
1844
+ ` adapter: ${adapter.expr},`,
1845
+ "});",
1846
+ ""
1847
+ ].join("\n");
1848
+ }
1849
+ async function selectAdapter(interactive) {
1850
+ if (!interactive || ADAPTERS.length === 1) {
1851
+ return ADAPTERS[0];
1852
+ }
1853
+ const picked = await prompts.select({
1854
+ message: "Which backend adapter?",
1855
+ initialValue: "hono",
1856
+ options: ADAPTERS.map((adapter) => ({
1857
+ value: adapter.value,
1858
+ label: adapter.label,
1859
+ hint: adapter.hint
1860
+ }))
1861
+ });
1862
+ if (prompts.isCancel(picked)) {
1863
+ return null;
1864
+ }
1865
+ return ADAPTERS.find((adapter) => adapter.value === picked) ?? null;
1866
+ }
1867
+ async function initProject(cwd, flags) {
1868
+ const interactive = Boolean(process.stdout.isTTY) && !flags.yes;
1869
+ prompts.intro("giri init");
1870
+ let adapter;
1871
+ if (flags.adapter) {
1872
+ adapter = ADAPTERS.find((choice) => choice.value === flags.adapter) ?? null;
1873
+ if (!adapter) {
1874
+ prompts.cancel(`Unknown adapter "${flags.adapter}". Available: ${ADAPTERS.map((a) => a.value).join(", ")}.`);
1875
+ return;
1876
+ }
1877
+ } else {
1878
+ adapter = await selectAdapter(interactive);
1879
+ if (!adapter) {
1880
+ prompts.cancel("Cancelled.");
1881
+ return;
1882
+ }
1883
+ }
1884
+ if (!adapter.available) {
1885
+ prompts.cancel(`The ${adapter.label} adapter isn't available yet \u2014 only Hono ships today.`);
1886
+ return;
1887
+ }
1888
+ const configPath = (0, import_node_path15.join)(cwd, "giri.config.ts");
1889
+ if (!(0, import_node_fs9.existsSync)(configPath)) {
1890
+ await (0, import_promises4.writeFile)(configPath, configSource(adapter));
1891
+ }
1892
+ const routePath = (0, import_node_path15.join)(cwd, "src", "routes", "+get.ts");
1893
+ if (!(0, import_node_fs9.existsSync)(routePath)) {
1894
+ await (0, import_promises4.mkdir)((0, import_node_path15.join)(cwd, "src", "routes"), { recursive: true });
1895
+ await (0, import_promises4.writeFile)(
1896
+ routePath,
1897
+ [
1898
+ 'import type { Handle } from "@boon4681/giri";',
1899
+ "",
1900
+ "export const handle: Handle = (c) => c.json({ ok: true });",
1901
+ ""
1902
+ ].join("\n")
1903
+ );
1904
+ }
1905
+ await ensureGitignore(cwd);
1906
+ await ensureTsConfig(cwd);
1907
+ prompts.log.success(`scaffolded a ${adapter.label} project`);
1908
+ const pm = flags.packageManager ?? detectPackageManager();
1909
+ let install = flags.install;
1910
+ if (install === void 0) {
1911
+ if (!interactive) {
1912
+ install = flags.yes;
1913
+ } else {
1914
+ const answer = await prompts.confirm({ message: `Install dependencies with ${pm}?` });
1915
+ if (prompts.isCancel(answer)) {
1916
+ prompts.cancel("Cancelled \u2014 files written, skipped install.");
1917
+ return;
1918
+ }
1919
+ install = answer;
1920
+ }
1921
+ }
1922
+ const deps = ["@boon4681/giri", ...adapter.deps, "zod"];
1923
+ const devDeps = ["typescript", "@types/node"];
1924
+ if (install) {
1925
+ try {
1926
+ prompts.log.step(`Installing ${deps.join(", ")}`);
1927
+ await runCommand(pm, installArgs(pm, deps, false), cwd);
1928
+ prompts.log.step(`Installing dev deps ${devDeps.join(", ")}`);
1929
+ await runCommand(pm, installArgs(pm, devDeps, true), cwd);
1930
+ } catch (error) {
1931
+ prompts.log.error(error instanceof Error ? error.message : String(error));
1932
+ prompts.outro(`Install failed \u2014 run \`${pm} ${installArgs(pm, deps, false).join(" ")}\` yourself, then \`giri serve\`.`);
1933
+ return;
1934
+ }
1935
+ prompts.outro("Ready. Run `giri serve` to start the dev server.");
1936
+ return;
1937
+ }
1938
+ prompts.outro(
1939
+ `Next:
1940
+ ${pm} ${installArgs(pm, deps, false).join(" ")}
1941
+ ${pm} ${installArgs(pm, devDeps, true).join(" ")}
1942
+ giri serve`
1943
+ );
1944
+ }
1945
+ function displayHost(address) {
1946
+ if (!address || address === "::" || address === "0.0.0.0") {
1947
+ return "localhost";
1948
+ }
1949
+ return address.includes(":") ? `[${address}]` : address;
1950
+ }
1951
+ async function serveProject(config, flags) {
1952
+ const initial = await syncProject(config);
1953
+ log2.success(
1954
+ `synced ${initial.routes.length} route${initial.routes.length === 1 ? "" : "s"} ${muted(`at ${initial.paths.outDir}`)}`,
1955
+ "sync"
1956
+ );
1957
+ const lifecycle = await loadLifecycle();
1958
+ const services = await runInit(lifecycle);
1959
+ let current = await buildGiriApp(config, { services });
1960
+ const port = flags.port ?? config.server?.port ?? 3e3;
1961
+ const hostname = flags.hostname ?? config.server?.hostname;
1962
+ if (flags.watch) {
1963
+ const srcDir = (0, import_node_path15.resolve)(current.paths.routesDir, "..");
1964
+ if ((0, import_node_fs9.existsSync)(srcDir)) {
1965
+ let timer;
1966
+ let syncing = false;
1967
+ const changed = /* @__PURE__ */ new Set();
1968
+ const { watch } = await import("fs");
1969
+ const updater = createWatchUpdater(config, initial);
1970
+ const hmrCount = /* @__PURE__ */ new Map();
1971
+ const bump = (key) => {
1972
+ const next = (hmrCount.get(key) ?? 0) + 1;
1973
+ hmrCount.set(key, next);
1974
+ return next;
1975
+ };
1976
+ const flush = async () => {
1977
+ if (syncing) {
1978
+ return;
1979
+ }
1980
+ syncing = true;
1981
+ try {
1982
+ while (changed.size > 0) {
1983
+ const batch = [...changed];
1984
+ changed.clear();
1985
+ for (const name of batch) {
1986
+ const outcome = await updater.apply(name || null);
1987
+ const rel = name ? `src/${name.replace(/\\/g, "/")}` : "src";
1988
+ log2.change(outcome === "full" ? "sync" : "update", rel, bump(rel));
1989
+ }
1990
+ current = await buildGiriApp(config, { services });
1991
+ }
1992
+ } catch (error) {
1993
+ log2.error(error instanceof Error ? error.message : String(error), "watch");
1994
+ } finally {
1995
+ syncing = false;
1996
+ }
1997
+ if (changed.size > 0) {
1998
+ void flush();
1999
+ }
2000
+ };
2001
+ watch(srcDir, { recursive: true }, (_event, filename) => {
2002
+ changed.add(filename ? filename.toString() : "");
2003
+ clearTimeout(timer);
2004
+ timer = setTimeout(() => void flush(), 150);
2005
+ });
2006
+ }
2007
+ }
2008
+ const handler = (request) => config.adapter.fetch(current.app, request);
2009
+ const server = config.adapter.serve(handler, { port, hostname }, (info) => {
2010
+ log2.ready(`http://${displayHost(info.address)}:${info.port}`);
2011
+ });
2012
+ registerShutdown(server, lifecycle, services);
2013
+ }
2014
+ function registerShutdown(server, lifecycle, services) {
2015
+ let shuttingDown = false;
2016
+ const shutdown = async () => {
2017
+ if (shuttingDown) {
2018
+ return;
2019
+ }
2020
+ shuttingDown = true;
2021
+ try {
2022
+ await server.close();
2023
+ if (lifecycle.teardown) {
2024
+ await lifecycle.teardown(services);
2025
+ }
2026
+ } catch (error) {
2027
+ log2.error(error instanceof Error ? error.message : String(error));
2028
+ process.exitCode = 1;
2029
+ } finally {
2030
+ process.exit(process.exitCode ?? 0);
2031
+ }
2032
+ };
2033
+ process.once("SIGINT", () => void shutdown());
2034
+ process.once("SIGTERM", () => void shutdown());
2035
+ }
2036
+ async function main() {
2037
+ const [command = "help", ...args] = process.argv.slice(2);
2038
+ const cwd = (0, import_node_path15.resolve)(process.cwd());
2039
+ if (command === "help" || command === "--help" || command === "-h") {
2040
+ help();
2041
+ return;
2042
+ }
2043
+ if (command === "init") {
2044
+ await initProject(cwd, parseInitFlags(args));
2045
+ return;
2046
+ }
2047
+ if (command === "build") {
2048
+ log2.warn("build is planned, but is currently a no-op", "build");
2049
+ return;
2050
+ }
2051
+ const config = await load();
2052
+ if (command === "sync") {
2053
+ const result = await syncProject(config);
2054
+ log2.success(
2055
+ `synced ${result.routes.length} route${result.routes.length === 1 ? "" : "s"} ${muted(`at ${result.paths.outDir}`)}`,
2056
+ "sync"
2057
+ );
2058
+ return;
2059
+ }
2060
+ if (command === "serve") {
2061
+ await serveProject(config, parseFlags(args));
2062
+ return;
2063
+ }
2064
+ throw new Error(`Unknown command: ${command}`);
2065
+ }
2066
+ main().catch((error) => {
2067
+ log2.error(error instanceof Error ? error.message : String(error));
2068
+ process.exitCode = 1;
2069
+ });
2070
+ //# sourceMappingURL=cli.js.map