@boon4681/giri 0.0.2-alpha-6 → 0.0.2-alpha-8
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/adapters/hono.d.ts +25 -4
- package/dist/adapters/hono.js.map +1 -1
- package/dist/cli.js +585 -118
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +13 -2
- package/dist/index.js +547 -99
- package/dist/index.js.map +1 -1
- package/dist/{types-NVvedshc.d.ts → types-BvRph0mx.d.ts} +15 -1
- package/dist/validators/valibot.d.ts +1 -1
- package/dist/validators/valibot.js.map +1 -1
- package/dist/validators/zod.d.ts +1 -1
- package/dist/validators/zod.js.map +1 -1
- package/package.json +4 -1
package/dist/cli.js
CHANGED
|
@@ -46,12 +46,18 @@ var init_es5 = __esm({
|
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
// src/generator/schema/program.ts
|
|
49
|
-
function
|
|
49
|
+
function leanOptions(options) {
|
|
50
|
+
return {
|
|
51
|
+
...options,
|
|
52
|
+
types: []
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function createSchemaProgram(paths, routeFiles, programOptions = {}) {
|
|
50
56
|
let options = { ...DEFAULT_OPTIONS };
|
|
51
|
-
const configPath =
|
|
57
|
+
const configPath = import_typescript2.default.findConfigFile(paths.cwd, import_typescript2.default.sys.fileExists, "tsconfig.json");
|
|
52
58
|
if (configPath) {
|
|
53
|
-
const parsed =
|
|
54
|
-
...
|
|
59
|
+
const parsed = import_typescript2.default.getParsedCommandLineOfConfigFile(configPath, {}, {
|
|
60
|
+
...import_typescript2.default.sys,
|
|
55
61
|
onUnRecoverableConfigFileDiagnostic: () => {
|
|
56
62
|
}
|
|
57
63
|
});
|
|
@@ -59,17 +65,20 @@ function createSchemaProgram(paths, routeFiles) {
|
|
|
59
65
|
options = { ...parsed.options, noEmit: true };
|
|
60
66
|
}
|
|
61
67
|
}
|
|
62
|
-
|
|
68
|
+
if (programOptions.lean) {
|
|
69
|
+
options = leanOptions(options);
|
|
70
|
+
}
|
|
71
|
+
return import_typescript2.default.createProgram(routeFiles, options);
|
|
63
72
|
}
|
|
64
|
-
var
|
|
73
|
+
var import_typescript2, DEFAULT_OPTIONS;
|
|
65
74
|
var init_program = __esm({
|
|
66
75
|
"src/generator/schema/program.ts"() {
|
|
67
76
|
"use strict";
|
|
68
|
-
|
|
77
|
+
import_typescript2 = __toESM(require("typescript"));
|
|
69
78
|
DEFAULT_OPTIONS = {
|
|
70
|
-
target:
|
|
71
|
-
module:
|
|
72
|
-
moduleResolution:
|
|
79
|
+
target: import_typescript2.default.ScriptTarget.ES2022,
|
|
80
|
+
module: import_typescript2.default.ModuleKind.NodeNext,
|
|
81
|
+
moduleResolution: import_typescript2.default.ModuleResolutionKind.NodeNext,
|
|
73
82
|
strict: true,
|
|
74
83
|
skipLibCheck: true,
|
|
75
84
|
noEmit: true
|
|
@@ -103,7 +112,7 @@ function literalValuesOf(types) {
|
|
|
103
112
|
for (const member of types) {
|
|
104
113
|
if (member.isStringLiteral() || member.isNumberLiteral()) {
|
|
105
114
|
values.push(member.value);
|
|
106
|
-
} else if (member.flags &
|
|
115
|
+
} else if (member.flags & import_typescript3.default.TypeFlags.BooleanLiteral) {
|
|
107
116
|
values.push(intrinsicName(member) === "true");
|
|
108
117
|
} else {
|
|
109
118
|
return void 0;
|
|
@@ -112,7 +121,7 @@ function literalValuesOf(types) {
|
|
|
112
121
|
return values;
|
|
113
122
|
}
|
|
114
123
|
function walkUnion(type, ctx) {
|
|
115
|
-
const flag =
|
|
124
|
+
const flag = import_typescript3.default.TypeFlags.Undefined | import_typescript3.default.TypeFlags.Void | import_typescript3.default.TypeFlags.Never;
|
|
116
125
|
const members = type.types.filter((member) => !(member.flags & flag));
|
|
117
126
|
if (members.length === 1) {
|
|
118
127
|
return walkType(members[0], ctx);
|
|
@@ -125,13 +134,13 @@ function walkUnion(type, ctx) {
|
|
|
125
134
|
}
|
|
126
135
|
function buildObjectSchema(type, ctx) {
|
|
127
136
|
const { checker } = ctx;
|
|
128
|
-
const indexInfo = checker.getIndexInfoOfType(type,
|
|
137
|
+
const indexInfo = checker.getIndexInfoOfType(type, import_typescript3.default.IndexKind.String) ?? checker.getIndexInfoOfType(type, import_typescript3.default.IndexKind.Number);
|
|
129
138
|
const properties = {};
|
|
130
139
|
const required = [];
|
|
131
140
|
for (const symbol of checker.getPropertiesOfType(type)) {
|
|
132
141
|
const name = symbol.getName();
|
|
133
142
|
const propType = checker.getTypeOfSymbolAtLocation(symbol, ctx.location);
|
|
134
|
-
const optional = Boolean(symbol.getFlags() &
|
|
143
|
+
const optional = Boolean(symbol.getFlags() & import_typescript3.default.SymbolFlags.Optional) || Boolean(propType.flags & import_typescript3.default.TypeFlags.Union && propType.types.some((t) => t.flags & import_typescript3.default.TypeFlags.Undefined));
|
|
135
144
|
properties[name] = walkType(propType, ctx);
|
|
136
145
|
if (!optional) {
|
|
137
146
|
required.push(name);
|
|
@@ -190,16 +199,16 @@ function walkObject(type, ctx) {
|
|
|
190
199
|
}
|
|
191
200
|
function walkType(type, ctx) {
|
|
192
201
|
const flags = type.flags;
|
|
193
|
-
if (flags & (
|
|
202
|
+
if (flags & (import_typescript3.default.TypeFlags.Any | import_typescript3.default.TypeFlags.Unknown)) {
|
|
194
203
|
return {};
|
|
195
204
|
}
|
|
196
|
-
if (flags &
|
|
205
|
+
if (flags & import_typescript3.default.TypeFlags.Null) {
|
|
197
206
|
return { type: "null" };
|
|
198
207
|
}
|
|
199
|
-
if (flags & (
|
|
208
|
+
if (flags & (import_typescript3.default.TypeFlags.Undefined | import_typescript3.default.TypeFlags.Void)) {
|
|
200
209
|
return {};
|
|
201
210
|
}
|
|
202
|
-
if (flags & (
|
|
211
|
+
if (flags & (import_typescript3.default.TypeFlags.BigInt | import_typescript3.default.TypeFlags.BigIntLiteral)) {
|
|
203
212
|
ctx.warnings.push("bigint is not JSON-serializable (JSON.stringify throws); documented as string.");
|
|
204
213
|
return { type: "string" };
|
|
205
214
|
}
|
|
@@ -209,45 +218,45 @@ function walkType(type, ctx) {
|
|
|
209
218
|
if (type.isNumberLiteral()) {
|
|
210
219
|
return { type: "number", const: type.value };
|
|
211
220
|
}
|
|
212
|
-
if (flags &
|
|
221
|
+
if (flags & import_typescript3.default.TypeFlags.BooleanLiteral) {
|
|
213
222
|
return { type: "boolean", const: intrinsicName(type) === "true" };
|
|
214
223
|
}
|
|
215
|
-
if (flags &
|
|
224
|
+
if (flags & import_typescript3.default.TypeFlags.String) {
|
|
216
225
|
return { type: "string" };
|
|
217
226
|
}
|
|
218
|
-
if (flags &
|
|
227
|
+
if (flags & import_typescript3.default.TypeFlags.Number) {
|
|
219
228
|
return { type: "number" };
|
|
220
229
|
}
|
|
221
|
-
if (flags &
|
|
230
|
+
if (flags & import_typescript3.default.TypeFlags.Boolean) {
|
|
222
231
|
return { type: "boolean" };
|
|
223
232
|
}
|
|
224
233
|
if (type.isUnion()) {
|
|
225
234
|
return walkUnion(type, ctx);
|
|
226
235
|
}
|
|
227
|
-
if (flags &
|
|
236
|
+
if (flags & import_typescript3.default.TypeFlags.Object || type.isIntersection()) {
|
|
228
237
|
return walkObject(type, ctx);
|
|
229
238
|
}
|
|
230
239
|
return {};
|
|
231
240
|
}
|
|
232
|
-
var
|
|
241
|
+
var import_typescript3;
|
|
233
242
|
var init_json_schema = __esm({
|
|
234
243
|
"src/generator/schema/json-schema.ts"() {
|
|
235
244
|
"use strict";
|
|
236
|
-
|
|
245
|
+
import_typescript3 = __toESM(require("typescript"));
|
|
237
246
|
}
|
|
238
247
|
});
|
|
239
248
|
|
|
240
249
|
// src/generator/schema/responses.ts
|
|
241
250
|
function findHandleFunction(source) {
|
|
242
251
|
let found;
|
|
243
|
-
const isExported = (node) =>
|
|
252
|
+
const isExported = (node) => import_typescript4.default.canHaveModifiers(node) && (import_typescript4.default.getModifiers(node)?.some((m) => m.kind === import_typescript4.default.SyntaxKind.ExportKeyword) ?? false);
|
|
244
253
|
for (const statement of source.statements) {
|
|
245
|
-
if (
|
|
254
|
+
if (import_typescript4.default.isFunctionDeclaration(statement) && statement.name?.text === "handle" && isExported(statement)) {
|
|
246
255
|
found = statement;
|
|
247
256
|
}
|
|
248
|
-
if (
|
|
257
|
+
if (import_typescript4.default.isVariableStatement(statement) && isExported(statement)) {
|
|
249
258
|
for (const declaration of statement.declarationList.declarations) {
|
|
250
|
-
if (
|
|
259
|
+
if (import_typescript4.default.isIdentifier(declaration.name) && declaration.name.text === "handle" && declaration.initializer && (import_typescript4.default.isArrowFunction(declaration.initializer) || import_typescript4.default.isFunctionExpression(declaration.initializer))) {
|
|
251
260
|
found = declaration.initializer;
|
|
252
261
|
}
|
|
253
262
|
}
|
|
@@ -256,7 +265,7 @@ function findHandleFunction(source) {
|
|
|
256
265
|
return found;
|
|
257
266
|
}
|
|
258
267
|
function collectReturnExpressions(fn) {
|
|
259
|
-
if (
|
|
268
|
+
if (import_typescript4.default.isArrowFunction(fn) && !import_typescript4.default.isBlock(fn.body)) {
|
|
260
269
|
return [fn.body];
|
|
261
270
|
}
|
|
262
271
|
if (!fn.body) {
|
|
@@ -264,15 +273,15 @@ function collectReturnExpressions(fn) {
|
|
|
264
273
|
}
|
|
265
274
|
const expressions = [];
|
|
266
275
|
const visit = (node) => {
|
|
267
|
-
if (
|
|
276
|
+
if (import_typescript4.default.isFunctionDeclaration(node) || import_typescript4.default.isFunctionExpression(node) || import_typescript4.default.isArrowFunction(node)) {
|
|
268
277
|
return;
|
|
269
278
|
}
|
|
270
|
-
if (
|
|
279
|
+
if (import_typescript4.default.isReturnStatement(node) && node.expression) {
|
|
271
280
|
expressions.push(node.expression);
|
|
272
281
|
}
|
|
273
|
-
|
|
282
|
+
import_typescript4.default.forEachChild(node, visit);
|
|
274
283
|
};
|
|
275
|
-
|
|
284
|
+
import_typescript4.default.forEachChild(fn.body, visit);
|
|
276
285
|
return expressions;
|
|
277
286
|
}
|
|
278
287
|
function propertyType(checker, type, name, location) {
|
|
@@ -284,15 +293,21 @@ function isTypedResponse(checker, type) {
|
|
|
284
293
|
checker.getPropertyOfType(type, "data") && checker.getPropertyOfType(type, "status") && checker.getPropertyOfType(type, "format")
|
|
285
294
|
);
|
|
286
295
|
}
|
|
287
|
-
function
|
|
288
|
-
|
|
296
|
+
function firstParameterName(fn) {
|
|
297
|
+
const [first] = fn.parameters;
|
|
298
|
+
return first && import_typescript4.default.isIdentifier(first.name) ? first.name.text : void 0;
|
|
299
|
+
}
|
|
300
|
+
function readFromCall(checker, expression, contextName) {
|
|
301
|
+
if (!import_typescript4.default.isCallExpression(expression) || !import_typescript4.default.isPropertyAccessExpression(expression.expression)) {
|
|
289
302
|
return void 0;
|
|
290
303
|
}
|
|
291
304
|
const method = expression.expression.name.text;
|
|
292
305
|
if (method !== "json" && method !== "text") {
|
|
293
306
|
return void 0;
|
|
294
307
|
}
|
|
295
|
-
|
|
308
|
+
const target = expression.expression.expression;
|
|
309
|
+
const directContextCall = contextName && import_typescript4.default.isIdentifier(target) && target.text === contextName;
|
|
310
|
+
if (!directContextCall && !isTypedResponse(checker, checker.getTypeAtLocation(expression))) {
|
|
296
311
|
return void 0;
|
|
297
312
|
}
|
|
298
313
|
const [dataArg, statusArg] = expression.arguments;
|
|
@@ -332,6 +347,7 @@ function extractRouteResponses(program, file) {
|
|
|
332
347
|
return result;
|
|
333
348
|
}
|
|
334
349
|
const ctx = createWalkContext(checker, fn);
|
|
350
|
+
const contextName = firstParameterName(fn);
|
|
335
351
|
const byStatus = /* @__PURE__ */ new Map();
|
|
336
352
|
const record = (hit) => {
|
|
337
353
|
const schema = walkType(hit.data, ctx);
|
|
@@ -340,7 +356,7 @@ function extractRouteResponses(program, file) {
|
|
|
340
356
|
byStatus.set(hit.status, bucket);
|
|
341
357
|
};
|
|
342
358
|
for (const expression of collectReturnExpressions(fn)) {
|
|
343
|
-
const fromCall = readFromCall(checker, expression);
|
|
359
|
+
const fromCall = readFromCall(checker, expression, contextName);
|
|
344
360
|
if (fromCall) {
|
|
345
361
|
record(fromCall);
|
|
346
362
|
continue;
|
|
@@ -366,11 +382,11 @@ function extractRouteResponses(program, file) {
|
|
|
366
382
|
result.$defs = ctx.defs;
|
|
367
383
|
return result;
|
|
368
384
|
}
|
|
369
|
-
var
|
|
385
|
+
var import_typescript4;
|
|
370
386
|
var init_responses = __esm({
|
|
371
387
|
"src/generator/schema/responses.ts"() {
|
|
372
388
|
"use strict";
|
|
373
|
-
|
|
389
|
+
import_typescript4 = __toESM(require("typescript"));
|
|
374
390
|
init_json_schema();
|
|
375
391
|
}
|
|
376
392
|
});
|
|
@@ -394,7 +410,7 @@ var init_schema = __esm({
|
|
|
394
410
|
|
|
395
411
|
// src/cli.ts
|
|
396
412
|
var import_node_child_process = require("child_process");
|
|
397
|
-
var
|
|
413
|
+
var import_node_fs10 = require("fs");
|
|
398
414
|
var import_promises4 = require("fs/promises");
|
|
399
415
|
var import_node_path15 = require("path");
|
|
400
416
|
var prompts = __toESM(require("@clack/prompts"));
|
|
@@ -526,13 +542,18 @@ function methodFromFile(fileName) {
|
|
|
526
542
|
const stem = fileName.replace(/\.(?:[cm]?[jt]s|[jt]sx)$/, "").toLowerCase();
|
|
527
543
|
return METHOD_FROM_FILE.get(stem);
|
|
528
544
|
}
|
|
529
|
-
function sharedFileIn(dir) {
|
|
545
|
+
function sharedFileIn(dir, cache) {
|
|
546
|
+
if (cache?.has(dir)) {
|
|
547
|
+
return cache.get(dir);
|
|
548
|
+
}
|
|
530
549
|
for (const ext of ["ts", "tsx", "js", "jsx", "mjs", "cjs", "mts", "cts"]) {
|
|
531
550
|
const file = (0, import_node_path2.join)(dir, `+shared.${ext}`);
|
|
532
551
|
if ((0, import_node_fs2.existsSync)(file)) {
|
|
552
|
+
cache?.set(dir, file);
|
|
533
553
|
return file;
|
|
534
554
|
}
|
|
535
555
|
}
|
|
556
|
+
cache?.set(dir, void 0);
|
|
536
557
|
return void 0;
|
|
537
558
|
}
|
|
538
559
|
function physicalRouteSegments(routesDir, routeDir) {
|
|
@@ -601,7 +622,7 @@ async function scanRouteFolders(routesDir) {
|
|
|
601
622
|
function routeParamsForDir(routesDir, dir) {
|
|
602
623
|
return pathFromSegments(physicalRouteSegments(routesDir, dir)).params;
|
|
603
624
|
}
|
|
604
|
-
function sharedFilesForDir(routesDir, dir) {
|
|
625
|
+
function sharedFilesForDir(routesDir, dir, cache) {
|
|
605
626
|
const segments = physicalRouteSegments(routesDir, dir);
|
|
606
627
|
const dirs = [routesDir];
|
|
607
628
|
let current = routesDir;
|
|
@@ -609,7 +630,7 @@ function sharedFilesForDir(routesDir, dir) {
|
|
|
609
630
|
current = (0, import_node_path2.join)(current, segment);
|
|
610
631
|
dirs.push(current);
|
|
611
632
|
}
|
|
612
|
-
return dirs.map(sharedFileIn).filter((file) => Boolean(file));
|
|
633
|
+
return dirs.map((currentDir) => sharedFileIn(currentDir, cache)).filter((file) => Boolean(file));
|
|
613
634
|
}
|
|
614
635
|
async function scanRoutes(routesDir) {
|
|
615
636
|
if (!(0, import_node_fs2.existsSync)(routesDir)) {
|
|
@@ -621,6 +642,7 @@ async function scanRoutes(routesDir) {
|
|
|
621
642
|
onlyFiles: true
|
|
622
643
|
});
|
|
623
644
|
const routes = [];
|
|
645
|
+
const sharedCache = /* @__PURE__ */ new Map();
|
|
624
646
|
for (const file of files) {
|
|
625
647
|
const method = methodFromFile((0, import_node_path2.basename)(file));
|
|
626
648
|
if (!method) {
|
|
@@ -636,7 +658,7 @@ async function scanRoutes(routesDir) {
|
|
|
636
658
|
routeDir,
|
|
637
659
|
routeSegments,
|
|
638
660
|
params,
|
|
639
|
-
sharedFiles: sharedFilesForDir(routesDir, routeDir)
|
|
661
|
+
sharedFiles: sharedFilesForDir(routesDir, routeDir, sharedCache)
|
|
640
662
|
});
|
|
641
663
|
}
|
|
642
664
|
return routes.sort((left, right) => {
|
|
@@ -665,9 +687,11 @@ function isGiriBodySchema(value) {
|
|
|
665
687
|
}
|
|
666
688
|
|
|
667
689
|
// src/app.ts
|
|
668
|
-
function loadModule(file) {
|
|
690
|
+
function loadModule(file, force = true) {
|
|
669
691
|
const resolved = require.resolve(file);
|
|
670
|
-
|
|
692
|
+
if (force) {
|
|
693
|
+
delete require.cache[resolved];
|
|
694
|
+
}
|
|
671
695
|
return require(resolved);
|
|
672
696
|
}
|
|
673
697
|
function interopDefault(value) {
|
|
@@ -802,13 +826,23 @@ async function buildGiriApp(config, options = {}) {
|
|
|
802
826
|
const { unregister } = await safeRegister();
|
|
803
827
|
const unregisterAliasResolver = registerAliasResolver(config.alias, paths.cwd);
|
|
804
828
|
try {
|
|
829
|
+
const dirty = options.dirty;
|
|
830
|
+
const forceReload = dirty === void 0;
|
|
831
|
+
const isDirty = (file) => forceReload || dirty.has(file);
|
|
832
|
+
const sharedCache = /* @__PURE__ */ new Map();
|
|
833
|
+
const loadShared = (file) => {
|
|
834
|
+
if (!sharedCache.has(file)) {
|
|
835
|
+
sharedCache.set(file, loadModule(file, isDirty(file)));
|
|
836
|
+
}
|
|
837
|
+
return sharedCache.get(file);
|
|
838
|
+
};
|
|
805
839
|
for (const route of routes) {
|
|
806
|
-
const routeModule = loadModule(route.file);
|
|
840
|
+
const routeModule = loadModule(route.file, isDirty(route.file));
|
|
807
841
|
if (typeof routeModule.handle !== "function") {
|
|
808
842
|
throw new Error(`${route.file} must export a named handle function.`);
|
|
809
843
|
}
|
|
810
844
|
const folderMiddleware = routeModule.config?.skipInherited ? [] : route.sharedFiles.flatMap(
|
|
811
|
-
(file) => normalizeMiddleware(
|
|
845
|
+
(file) => normalizeMiddleware(loadShared(file).middleware, file)
|
|
812
846
|
);
|
|
813
847
|
const verbMiddleware = normalizeMiddleware(routeModule.middleware, route.file);
|
|
814
848
|
config.adapter.register(app, {
|
|
@@ -829,7 +863,7 @@ async function buildGiriApp(config, options = {}) {
|
|
|
829
863
|
}
|
|
830
864
|
|
|
831
865
|
// src/generator/sync.ts
|
|
832
|
-
var
|
|
866
|
+
var import_node_fs7 = require("fs");
|
|
833
867
|
var import_promises3 = require("fs/promises");
|
|
834
868
|
var import_node_path11 = require("path");
|
|
835
869
|
|
|
@@ -1083,6 +1117,7 @@ function buildOpenApiDocument(paths, routes, data = {}) {
|
|
|
1083
1117
|
const documentPaths = {};
|
|
1084
1118
|
const schemas = {};
|
|
1085
1119
|
const securitySchemes = {};
|
|
1120
|
+
const tagOrder = [];
|
|
1086
1121
|
for (const route of routes) {
|
|
1087
1122
|
if (data.hiddenFiles?.has(route.file)) {
|
|
1088
1123
|
continue;
|
|
@@ -1090,10 +1125,32 @@ function buildOpenApiDocument(paths, routes, data = {}) {
|
|
|
1090
1125
|
const responses = data.responsesByFile?.get(route.file);
|
|
1091
1126
|
const input = data.inputsByFile?.get(route.file);
|
|
1092
1127
|
const security = data.securityByFile?.get(route.file);
|
|
1128
|
+
const meta = data.openapiByFile?.get(route.file);
|
|
1093
1129
|
for (const [name, schema] of Object.entries(responses?.$defs ?? {})) {
|
|
1094
1130
|
schemas[name] = rewriteRefs(schema);
|
|
1095
1131
|
}
|
|
1096
|
-
const operation = {
|
|
1132
|
+
const operation = {};
|
|
1133
|
+
if (meta?.tags && meta.tags.length > 0) {
|
|
1134
|
+
operation.tags = meta.tags;
|
|
1135
|
+
for (const tag2 of meta.tags) {
|
|
1136
|
+
if (!tagOrder.includes(tag2)) {
|
|
1137
|
+
tagOrder.push(tag2);
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
if (meta?.summary) {
|
|
1142
|
+
operation.summary = meta.summary;
|
|
1143
|
+
}
|
|
1144
|
+
if (meta?.description) {
|
|
1145
|
+
operation.description = meta.description;
|
|
1146
|
+
}
|
|
1147
|
+
if (meta?.operationId) {
|
|
1148
|
+
operation.operationId = meta.operationId;
|
|
1149
|
+
}
|
|
1150
|
+
if (meta?.deprecated) {
|
|
1151
|
+
operation.deprecated = true;
|
|
1152
|
+
}
|
|
1153
|
+
operation.responses = buildResponses(responses?.responses ?? []);
|
|
1097
1154
|
const parameters = [...pathParameters(route), ...queryParameters(input?.query)];
|
|
1098
1155
|
if (parameters.length > 0) {
|
|
1099
1156
|
operation.parameters = parameters;
|
|
@@ -1125,6 +1182,9 @@ function buildOpenApiDocument(paths, routes, data = {}) {
|
|
|
1125
1182
|
info: readProjectInfo(paths.cwd),
|
|
1126
1183
|
paths: documentPaths
|
|
1127
1184
|
};
|
|
1185
|
+
if (tagOrder.length > 0) {
|
|
1186
|
+
document.tags = tagOrder.map((name) => ({ name }));
|
|
1187
|
+
}
|
|
1128
1188
|
const components = {};
|
|
1129
1189
|
if (Object.keys(schemas).length > 0) {
|
|
1130
1190
|
components.schemas = schemas;
|
|
@@ -1156,14 +1216,29 @@ function paramsType(params) {
|
|
|
1156
1216
|
${fields}
|
|
1157
1217
|
}`;
|
|
1158
1218
|
}
|
|
1159
|
-
function
|
|
1160
|
-
|
|
1161
|
-
|
|
1219
|
+
function middlewareVarsType(typesDir, sharedFile) {
|
|
1220
|
+
const spec = JSON.stringify(moduleSpecifier(typesDir, sharedFile));
|
|
1221
|
+
return `(typeof import(${spec}) extends { middleware: infer M } ? import("@boon4681/giri").InferStackVars<M> : {})`;
|
|
1222
|
+
}
|
|
1223
|
+
function ownSharedFile(dir, sharedFiles) {
|
|
1224
|
+
for (let index = sharedFiles.length - 1; index >= 0; index -= 1) {
|
|
1225
|
+
if ((0, import_node_path8.dirname)(sharedFiles[index]) === dir) {
|
|
1226
|
+
return sharedFiles[index];
|
|
1227
|
+
}
|
|
1162
1228
|
}
|
|
1163
|
-
return
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1229
|
+
return void 0;
|
|
1230
|
+
}
|
|
1231
|
+
function varsType(paths, file, dir, sharedFiles) {
|
|
1232
|
+
const typesDir = (0, import_node_path8.dirname)(file);
|
|
1233
|
+
const parts = [];
|
|
1234
|
+
if (dir !== paths.routesDir) {
|
|
1235
|
+
parts.push(`import(${JSON.stringify(importPath(file, typeFilePath(paths, (0, import_node_path8.dirname)(dir))))}).Vars`);
|
|
1236
|
+
}
|
|
1237
|
+
const ownShared = ownSharedFile(dir, sharedFiles);
|
|
1238
|
+
if (ownShared) {
|
|
1239
|
+
parts.push(middlewareVarsType(typesDir, ownShared));
|
|
1240
|
+
}
|
|
1241
|
+
return parts.length > 0 ? parts.join("\n & ") : "{}";
|
|
1167
1242
|
}
|
|
1168
1243
|
function methodExports(typesDir, verbs) {
|
|
1169
1244
|
return verbs.map(({ method, file }) => {
|
|
@@ -1174,14 +1249,14 @@ function methodExports(typesDir, verbs) {
|
|
|
1174
1249
|
});
|
|
1175
1250
|
}
|
|
1176
1251
|
async function writeParamTypes(paths, folders) {
|
|
1177
|
-
|
|
1252
|
+
await Promise.all(folders.map(({ dir, params, sharedFiles, verbs }) => {
|
|
1178
1253
|
const file = typeFilePath(paths, dir);
|
|
1179
1254
|
const typesDir = (0, import_node_path8.dirname)(file);
|
|
1180
1255
|
const lines = [
|
|
1181
1256
|
GENERATED_HEADER,
|
|
1182
1257
|
`export type Params = ${paramsType(params)};`,
|
|
1183
1258
|
"export type RouteParams = Params;",
|
|
1184
|
-
`type Vars = ${varsType(
|
|
1259
|
+
`export type Vars = ${varsType(paths, file, dir, sharedFiles)};`,
|
|
1185
1260
|
"export type Middleware<Injects extends Record<string, unknown> = {}> =",
|
|
1186
1261
|
' import("@boon4681/giri").Middleware<Params, import("@boon4681/giri").ValidatedInput, Injects>;',
|
|
1187
1262
|
'export type Handle<Input extends import("@boon4681/giri").ValidatedInput = import("@boon4681/giri").ValidatedInput> =',
|
|
@@ -1191,10 +1266,14 @@ async function writeParamTypes(paths, folders) {
|
|
|
1191
1266
|
lines.push(...methodExports(typesDir, verbs));
|
|
1192
1267
|
}
|
|
1193
1268
|
lines.push("");
|
|
1194
|
-
|
|
1195
|
-
}
|
|
1269
|
+
return writeGenerated(file, lines.join("\n"));
|
|
1270
|
+
}));
|
|
1196
1271
|
}
|
|
1197
1272
|
|
|
1273
|
+
// src/generator/route-meta.ts
|
|
1274
|
+
var import_node_fs6 = require("fs");
|
|
1275
|
+
var import_typescript = __toESM(require("typescript"));
|
|
1276
|
+
|
|
1198
1277
|
// src/generator/inputs.ts
|
|
1199
1278
|
function sanitize(schema) {
|
|
1200
1279
|
const { $schema, ...rest } = schema;
|
|
@@ -1255,28 +1334,315 @@ function readInput(routeModule) {
|
|
|
1255
1334
|
}
|
|
1256
1335
|
return input.body || input.query ? input : void 0;
|
|
1257
1336
|
}
|
|
1258
|
-
function
|
|
1259
|
-
|
|
1337
|
+
function hasExportModifier(node) {
|
|
1338
|
+
return import_typescript.default.canHaveModifiers(node) && (import_typescript.default.getModifiers(node)?.some((modifier) => modifier.kind === import_typescript.default.SyntaxKind.ExportKeyword) ?? false);
|
|
1339
|
+
}
|
|
1340
|
+
function unwrapExpression(expression) {
|
|
1341
|
+
let current = expression;
|
|
1342
|
+
while (import_typescript.default.isParenthesizedExpression(current) || import_typescript.default.isAsExpression(current) || import_typescript.default.isSatisfiesExpression(current)) {
|
|
1343
|
+
current = current.expression;
|
|
1344
|
+
}
|
|
1345
|
+
return current;
|
|
1346
|
+
}
|
|
1347
|
+
function staticBoolean(expression) {
|
|
1348
|
+
const value = unwrapExpression(expression);
|
|
1349
|
+
if (value.kind === import_typescript.default.SyntaxKind.TrueKeyword) {
|
|
1260
1350
|
return true;
|
|
1261
1351
|
}
|
|
1262
|
-
if (value ===
|
|
1352
|
+
if (value.kind === import_typescript.default.SyntaxKind.FalseKeyword) {
|
|
1263
1353
|
return false;
|
|
1264
1354
|
}
|
|
1265
|
-
|
|
1266
|
-
|
|
1355
|
+
return void 0;
|
|
1356
|
+
}
|
|
1357
|
+
function staticString(expression) {
|
|
1358
|
+
const value = unwrapExpression(expression);
|
|
1359
|
+
return import_typescript.default.isStringLiteralLike(value) ? value.text : void 0;
|
|
1360
|
+
}
|
|
1361
|
+
function staticStringArray(expression) {
|
|
1362
|
+
const value = unwrapExpression(expression);
|
|
1363
|
+
if (!import_typescript.default.isArrayLiteralExpression(value)) {
|
|
1364
|
+
return void 0;
|
|
1365
|
+
}
|
|
1366
|
+
const strings = [];
|
|
1367
|
+
for (const element of value.elements) {
|
|
1368
|
+
const string = staticString(element);
|
|
1369
|
+
if (string === void 0) {
|
|
1370
|
+
return void 0;
|
|
1371
|
+
}
|
|
1372
|
+
strings.push(string);
|
|
1373
|
+
}
|
|
1374
|
+
return strings;
|
|
1375
|
+
}
|
|
1376
|
+
function propertyName(name) {
|
|
1377
|
+
if (import_typescript.default.isIdentifier(name) || import_typescript.default.isStringLiteral(name) || import_typescript.default.isNumericLiteral(name)) {
|
|
1378
|
+
return name.text;
|
|
1267
1379
|
}
|
|
1268
1380
|
return void 0;
|
|
1269
1381
|
}
|
|
1270
|
-
function
|
|
1382
|
+
function collectImportedNames(source) {
|
|
1383
|
+
const names = /* @__PURE__ */ new Set();
|
|
1384
|
+
for (const statement of source.statements) {
|
|
1385
|
+
if (!import_typescript.default.isImportDeclaration(statement)) {
|
|
1386
|
+
continue;
|
|
1387
|
+
}
|
|
1388
|
+
const clause = statement.importClause;
|
|
1389
|
+
if (!clause) {
|
|
1390
|
+
continue;
|
|
1391
|
+
}
|
|
1392
|
+
if (clause.name) {
|
|
1393
|
+
names.add(clause.name.text);
|
|
1394
|
+
}
|
|
1395
|
+
const bindings = clause.namedBindings;
|
|
1396
|
+
if (bindings && import_typescript.default.isNamespaceImport(bindings)) {
|
|
1397
|
+
names.add(bindings.name.text);
|
|
1398
|
+
}
|
|
1399
|
+
if (bindings && import_typescript.default.isNamedImports(bindings)) {
|
|
1400
|
+
for (const element of bindings.elements) {
|
|
1401
|
+
names.add(element.name.text);
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
return names;
|
|
1406
|
+
}
|
|
1407
|
+
function expressionReferencesImportedMiddleware(expression, importedNames) {
|
|
1408
|
+
let found = false;
|
|
1409
|
+
const allowedImportedHelpers = /* @__PURE__ */ new Set(["stack", "fromHono"]);
|
|
1410
|
+
const visit = (node) => {
|
|
1411
|
+
if (found) {
|
|
1412
|
+
return;
|
|
1413
|
+
}
|
|
1414
|
+
if (import_typescript.default.isIdentifier(node) && importedNames.has(node.text) && !allowedImportedHelpers.has(node.text)) {
|
|
1415
|
+
found = true;
|
|
1416
|
+
return;
|
|
1417
|
+
}
|
|
1418
|
+
import_typescript.default.forEachChild(node, visit);
|
|
1419
|
+
};
|
|
1420
|
+
visit(expression);
|
|
1421
|
+
return found;
|
|
1422
|
+
}
|
|
1423
|
+
function parseStaticOpenApi(expression) {
|
|
1424
|
+
const value = unwrapExpression(expression);
|
|
1425
|
+
const boolean = staticBoolean(value);
|
|
1426
|
+
if (boolean !== void 0) {
|
|
1427
|
+
return boolean;
|
|
1428
|
+
}
|
|
1429
|
+
if (!import_typescript.default.isObjectLiteralExpression(value)) {
|
|
1430
|
+
return void 0;
|
|
1431
|
+
}
|
|
1432
|
+
const openapi = {};
|
|
1433
|
+
for (const property of value.properties) {
|
|
1434
|
+
if (!import_typescript.default.isPropertyAssignment(property)) {
|
|
1435
|
+
return void 0;
|
|
1436
|
+
}
|
|
1437
|
+
const name = propertyName(property.name);
|
|
1438
|
+
if (!name) {
|
|
1439
|
+
return void 0;
|
|
1440
|
+
}
|
|
1441
|
+
if (name === "hidden") {
|
|
1442
|
+
const hidden = staticBoolean(property.initializer);
|
|
1443
|
+
if (hidden === void 0) {
|
|
1444
|
+
return void 0;
|
|
1445
|
+
}
|
|
1446
|
+
openapi.hidden = hidden;
|
|
1447
|
+
} else if (name === "tags") {
|
|
1448
|
+
const tags = staticStringArray(property.initializer);
|
|
1449
|
+
if (!tags) {
|
|
1450
|
+
return void 0;
|
|
1451
|
+
}
|
|
1452
|
+
openapi.tags = tags;
|
|
1453
|
+
} else if (name === "summary" || name === "description" || name === "operationId") {
|
|
1454
|
+
const string = staticString(property.initializer);
|
|
1455
|
+
if (string === void 0) {
|
|
1456
|
+
return void 0;
|
|
1457
|
+
}
|
|
1458
|
+
openapi[name] = string;
|
|
1459
|
+
} else if (name === "deprecated") {
|
|
1460
|
+
const deprecated = staticBoolean(property.initializer);
|
|
1461
|
+
if (deprecated === void 0) {
|
|
1462
|
+
return void 0;
|
|
1463
|
+
}
|
|
1464
|
+
openapi.deprecated = deprecated;
|
|
1465
|
+
} else {
|
|
1466
|
+
return void 0;
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
return openapi;
|
|
1470
|
+
}
|
|
1471
|
+
function readStaticModuleMeta(file) {
|
|
1472
|
+
let source;
|
|
1473
|
+
try {
|
|
1474
|
+
source = import_typescript.default.createSourceFile(file, (0, import_node_fs6.readFileSync)(file, "utf8"), import_typescript.default.ScriptTarget.Latest, true);
|
|
1475
|
+
} catch {
|
|
1476
|
+
return void 0;
|
|
1477
|
+
}
|
|
1478
|
+
const importedNames = collectImportedNames(source);
|
|
1479
|
+
const sourceText = source.getFullText();
|
|
1480
|
+
const canSkipMiddlewareRuntime = !sourceText.includes("defineMiddleware") && !sourceText.includes(".openapi");
|
|
1481
|
+
const meta = { middlewareSecurity: false };
|
|
1482
|
+
for (const statement of source.statements) {
|
|
1483
|
+
if (import_typescript.default.isImportDeclaration(statement) || import_typescript.default.isInterfaceDeclaration(statement) || import_typescript.default.isTypeAliasDeclaration(statement) || import_typescript.default.isEmptyStatement(statement) || !hasExportModifier(statement)) {
|
|
1484
|
+
continue;
|
|
1485
|
+
}
|
|
1486
|
+
if (import_typescript.default.isFunctionDeclaration(statement) && statement.name?.text === "handle") {
|
|
1487
|
+
continue;
|
|
1488
|
+
}
|
|
1489
|
+
if (import_typescript.default.isVariableStatement(statement)) {
|
|
1490
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
1491
|
+
if (!import_typescript.default.isIdentifier(declaration.name)) {
|
|
1492
|
+
return void 0;
|
|
1493
|
+
}
|
|
1494
|
+
const name = declaration.name.text;
|
|
1495
|
+
if (name === "openapi") {
|
|
1496
|
+
if (!declaration.initializer) {
|
|
1497
|
+
return void 0;
|
|
1498
|
+
}
|
|
1499
|
+
const openapi = parseStaticOpenApi(declaration.initializer);
|
|
1500
|
+
if (openapi === void 0) {
|
|
1501
|
+
return void 0;
|
|
1502
|
+
}
|
|
1503
|
+
meta.openapi = openapi;
|
|
1504
|
+
} else if (name === "handle") {
|
|
1505
|
+
if (!declaration.initializer || !import_typescript.default.isArrowFunction(declaration.initializer) && !import_typescript.default.isFunctionExpression(declaration.initializer)) {
|
|
1506
|
+
return void 0;
|
|
1507
|
+
}
|
|
1508
|
+
continue;
|
|
1509
|
+
} else if (name === "middleware") {
|
|
1510
|
+
if (!declaration.initializer || !canSkipMiddlewareRuntime || expressionReferencesImportedMiddleware(declaration.initializer, importedNames)) {
|
|
1511
|
+
return void 0;
|
|
1512
|
+
}
|
|
1513
|
+
meta.middlewareSecurity = false;
|
|
1514
|
+
continue;
|
|
1515
|
+
} else {
|
|
1516
|
+
return void 0;
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
continue;
|
|
1520
|
+
}
|
|
1521
|
+
return void 0;
|
|
1522
|
+
}
|
|
1523
|
+
return meta;
|
|
1524
|
+
}
|
|
1525
|
+
function resolveStaticOpenApi(route, routeModule, loadShared) {
|
|
1271
1526
|
let hidden = false;
|
|
1527
|
+
const tags = [];
|
|
1528
|
+
const meta = {};
|
|
1529
|
+
const apply = (value, isVerb) => {
|
|
1530
|
+
if (value === false) {
|
|
1531
|
+
hidden = true;
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
if (value === true) {
|
|
1535
|
+
hidden = false;
|
|
1536
|
+
return;
|
|
1537
|
+
}
|
|
1538
|
+
if (!value) {
|
|
1539
|
+
return;
|
|
1540
|
+
}
|
|
1541
|
+
if (typeof value.hidden === "boolean") {
|
|
1542
|
+
hidden = value.hidden;
|
|
1543
|
+
}
|
|
1544
|
+
if (value.tags) {
|
|
1545
|
+
tags.push(...value.tags);
|
|
1546
|
+
}
|
|
1547
|
+
if (typeof value.summary === "string") {
|
|
1548
|
+
meta.summary = value.summary;
|
|
1549
|
+
}
|
|
1550
|
+
if (typeof value.description === "string") {
|
|
1551
|
+
meta.description = value.description;
|
|
1552
|
+
}
|
|
1553
|
+
if (typeof value.deprecated === "boolean") {
|
|
1554
|
+
meta.deprecated = value.deprecated;
|
|
1555
|
+
}
|
|
1556
|
+
if (isVerb && typeof value.operationId === "string") {
|
|
1557
|
+
meta.operationId = value.operationId;
|
|
1558
|
+
}
|
|
1559
|
+
};
|
|
1272
1560
|
for (const file of route.sharedFiles) {
|
|
1273
|
-
const
|
|
1274
|
-
if (
|
|
1275
|
-
|
|
1561
|
+
const shared = loadShared(file);
|
|
1562
|
+
if (!shared) {
|
|
1563
|
+
return void 0;
|
|
1276
1564
|
}
|
|
1565
|
+
apply(shared.openapi, false);
|
|
1566
|
+
}
|
|
1567
|
+
apply(routeModule.openapi, true);
|
|
1568
|
+
if (tags.length > 0) {
|
|
1569
|
+
meta.tags = [...new Set(tags)];
|
|
1570
|
+
}
|
|
1571
|
+
return { hidden, meta };
|
|
1572
|
+
}
|
|
1573
|
+
function extractStaticMeta(route, routeModule, loadShared) {
|
|
1574
|
+
const openapi = resolveStaticOpenApi(route, routeModule, loadShared);
|
|
1575
|
+
if (!openapi) {
|
|
1576
|
+
return void 0;
|
|
1577
|
+
}
|
|
1578
|
+
const meta = {};
|
|
1579
|
+
if (openapi.hidden) {
|
|
1580
|
+
meta.hidden = true;
|
|
1581
|
+
}
|
|
1582
|
+
if (Object.keys(openapi.meta).length > 0) {
|
|
1583
|
+
meta.openapi = openapi.meta;
|
|
1277
1584
|
}
|
|
1278
|
-
|
|
1279
|
-
|
|
1585
|
+
return meta;
|
|
1586
|
+
}
|
|
1587
|
+
function extractRuntimeSharedMeta(route, routeModule, loadShared) {
|
|
1588
|
+
const meta = {};
|
|
1589
|
+
const security = collectSecurity(route, {}, loadShared);
|
|
1590
|
+
const { hidden, meta: openapi } = resolveOpenApi(route, { openapi: routeModule.openapi }, loadShared);
|
|
1591
|
+
if (security) {
|
|
1592
|
+
meta.security = security;
|
|
1593
|
+
}
|
|
1594
|
+
if (hidden) {
|
|
1595
|
+
meta.hidden = true;
|
|
1596
|
+
}
|
|
1597
|
+
if (Object.keys(openapi).length > 0) {
|
|
1598
|
+
meta.openapi = openapi;
|
|
1599
|
+
}
|
|
1600
|
+
return meta;
|
|
1601
|
+
}
|
|
1602
|
+
function resolveOpenApi(route, routeModule, loadShared) {
|
|
1603
|
+
let hidden = false;
|
|
1604
|
+
const tags = [];
|
|
1605
|
+
const meta = {};
|
|
1606
|
+
const apply = (value, isVerb) => {
|
|
1607
|
+
if (value === false) {
|
|
1608
|
+
hidden = true;
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1611
|
+
if (value === true) {
|
|
1612
|
+
hidden = false;
|
|
1613
|
+
return;
|
|
1614
|
+
}
|
|
1615
|
+
if (!value || typeof value !== "object") {
|
|
1616
|
+
return;
|
|
1617
|
+
}
|
|
1618
|
+
const o = value;
|
|
1619
|
+
if ("hidden" in o) {
|
|
1620
|
+
hidden = Boolean(o.hidden);
|
|
1621
|
+
}
|
|
1622
|
+
if (Array.isArray(o.tags)) {
|
|
1623
|
+
tags.push(...o.tags.filter((tag2) => typeof tag2 === "string"));
|
|
1624
|
+
}
|
|
1625
|
+
if (typeof o.summary === "string") {
|
|
1626
|
+
meta.summary = o.summary;
|
|
1627
|
+
}
|
|
1628
|
+
if (typeof o.description === "string") {
|
|
1629
|
+
meta.description = o.description;
|
|
1630
|
+
}
|
|
1631
|
+
if (typeof o.deprecated === "boolean") {
|
|
1632
|
+
meta.deprecated = o.deprecated;
|
|
1633
|
+
}
|
|
1634
|
+
if (isVerb && typeof o.operationId === "string") {
|
|
1635
|
+
meta.operationId = o.operationId;
|
|
1636
|
+
}
|
|
1637
|
+
};
|
|
1638
|
+
for (const file of route.sharedFiles) {
|
|
1639
|
+
apply(loadShared(file).openapi, false);
|
|
1640
|
+
}
|
|
1641
|
+
apply(routeModule.openapi, true);
|
|
1642
|
+
if (tags.length > 0) {
|
|
1643
|
+
meta.tags = [...new Set(tags)];
|
|
1644
|
+
}
|
|
1645
|
+
return { hidden, meta };
|
|
1280
1646
|
}
|
|
1281
1647
|
function collectSecurity(route, routeModule, loadShared) {
|
|
1282
1648
|
const skipInherited = Boolean(
|
|
@@ -1308,6 +1674,33 @@ function collectSecurity(route, routeModule, loadShared) {
|
|
|
1308
1674
|
}
|
|
1309
1675
|
async function extractRouteMeta(config, paths, routes) {
|
|
1310
1676
|
const byFile = /* @__PURE__ */ new Map();
|
|
1677
|
+
const remainingRoutes = [];
|
|
1678
|
+
const runtimeSharedRoutes = [];
|
|
1679
|
+
const staticCache = /* @__PURE__ */ new Map();
|
|
1680
|
+
const loadStatic = (file) => {
|
|
1681
|
+
if (!staticCache.has(file)) {
|
|
1682
|
+
staticCache.set(file, readStaticModuleMeta(file));
|
|
1683
|
+
}
|
|
1684
|
+
return staticCache.get(file);
|
|
1685
|
+
};
|
|
1686
|
+
for (const route of routes) {
|
|
1687
|
+
const routeModule = loadStatic(route.file);
|
|
1688
|
+
if (!routeModule) {
|
|
1689
|
+
remainingRoutes.push(route);
|
|
1690
|
+
continue;
|
|
1691
|
+
}
|
|
1692
|
+
const meta = extractStaticMeta(route, routeModule, loadStatic);
|
|
1693
|
+
if (meta) {
|
|
1694
|
+
if (meta.hidden || meta.openapi) {
|
|
1695
|
+
byFile.set(route.file, meta);
|
|
1696
|
+
}
|
|
1697
|
+
continue;
|
|
1698
|
+
}
|
|
1699
|
+
runtimeSharedRoutes.push({ route, routeModule });
|
|
1700
|
+
}
|
|
1701
|
+
if (remainingRoutes.length === 0 && runtimeSharedRoutes.length === 0) {
|
|
1702
|
+
return byFile;
|
|
1703
|
+
}
|
|
1311
1704
|
const { unregister } = await safeRegister();
|
|
1312
1705
|
const unregisterAlias = registerAliasResolver(config.alias, paths.cwd);
|
|
1313
1706
|
const sharedCache = /* @__PURE__ */ new Map();
|
|
@@ -1322,13 +1715,19 @@ async function extractRouteMeta(config, paths, routes) {
|
|
|
1322
1715
|
return sharedCache.get(file);
|
|
1323
1716
|
};
|
|
1324
1717
|
try {
|
|
1325
|
-
for (const route of
|
|
1718
|
+
for (const { route, routeModule } of runtimeSharedRoutes) {
|
|
1719
|
+
const meta = extractRuntimeSharedMeta(route, routeModule, loadShared);
|
|
1720
|
+
if (meta.input || meta.security || meta.hidden || meta.openapi) {
|
|
1721
|
+
byFile.set(route.file, meta);
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
for (const route of remainingRoutes) {
|
|
1326
1725
|
try {
|
|
1327
1726
|
const routeModule = loadModule2(route.file);
|
|
1328
1727
|
const meta = {};
|
|
1329
1728
|
const input = readInput(routeModule);
|
|
1330
1729
|
const security = collectSecurity(route, routeModule, loadShared);
|
|
1331
|
-
const hidden =
|
|
1730
|
+
const { hidden, meta: openapi } = resolveOpenApi(route, routeModule, loadShared);
|
|
1332
1731
|
if (input) {
|
|
1333
1732
|
meta.input = input;
|
|
1334
1733
|
}
|
|
@@ -1338,7 +1737,10 @@ async function extractRouteMeta(config, paths, routes) {
|
|
|
1338
1737
|
if (hidden) {
|
|
1339
1738
|
meta.hidden = true;
|
|
1340
1739
|
}
|
|
1341
|
-
if (
|
|
1740
|
+
if (Object.keys(openapi).length > 0) {
|
|
1741
|
+
meta.openapi = openapi;
|
|
1742
|
+
}
|
|
1743
|
+
if (meta.input || meta.security || meta.hidden || meta.openapi) {
|
|
1342
1744
|
byFile.set(route.file, meta);
|
|
1343
1745
|
}
|
|
1344
1746
|
} catch {
|
|
@@ -1420,10 +1822,11 @@ async function typeFolders(paths, routes) {
|
|
|
1420
1822
|
verbsByDir.set(key, list);
|
|
1421
1823
|
}
|
|
1422
1824
|
const dirs = await scanRouteFolders(paths.routesDir);
|
|
1825
|
+
const sharedCache = /* @__PURE__ */ new Map();
|
|
1423
1826
|
return dirs.map((dir) => ({
|
|
1424
1827
|
dir,
|
|
1425
1828
|
params: routeParamsForDir(paths.routesDir, dir),
|
|
1426
|
-
sharedFiles: sharedFilesForDir(paths.routesDir, dir),
|
|
1829
|
+
sharedFiles: sharedFilesForDir(paths.routesDir, dir, sharedCache),
|
|
1427
1830
|
verbs: verbsByDir.get(slash(dir)) ?? []
|
|
1428
1831
|
}));
|
|
1429
1832
|
}
|
|
@@ -1436,24 +1839,63 @@ async function extractResponses(paths, routes) {
|
|
|
1436
1839
|
const { createSchemaProgram: createSchemaProgram2, extractRouteResponses: extractRouteResponses2 } = await Promise.resolve().then(() => (init_schema(), schema_exports));
|
|
1437
1840
|
const files = [...new Set(routes.map((route) => route.file))];
|
|
1438
1841
|
const appTypes = (0, import_node_path11.join)(paths.outDir, "types", "app.d.ts");
|
|
1439
|
-
const
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
);
|
|
1842
|
+
const roots = (0, import_node_fs7.existsSync)(appTypes) ? [...files, appTypes] : files;
|
|
1843
|
+
const program = createSchemaProgram2(paths, roots, { lean: true });
|
|
1844
|
+
const fallbackFiles = [];
|
|
1443
1845
|
for (const file of files) {
|
|
1444
|
-
|
|
1846
|
+
const responses = extractRouteResponses2(program, file);
|
|
1847
|
+
byFile.set(file, responses);
|
|
1848
|
+
if (hasLooseResponseSchema(responses)) {
|
|
1849
|
+
fallbackFiles.push(file);
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
if (fallbackFiles.length > 0) {
|
|
1853
|
+
const fullProgram = createSchemaProgram2(
|
|
1854
|
+
paths,
|
|
1855
|
+
(0, import_node_fs7.existsSync)(appTypes) ? [...fallbackFiles, appTypes] : fallbackFiles
|
|
1856
|
+
);
|
|
1857
|
+
for (const file of fallbackFiles) {
|
|
1858
|
+
byFile.set(file, extractRouteResponses2(fullProgram, file));
|
|
1859
|
+
}
|
|
1445
1860
|
}
|
|
1446
1861
|
} catch (error) {
|
|
1447
1862
|
console.warn(`giri: skipped response schema generation (${error.message}).`);
|
|
1448
1863
|
}
|
|
1449
1864
|
return byFile;
|
|
1450
1865
|
}
|
|
1866
|
+
function isLooseSchema(value) {
|
|
1867
|
+
if (!value || typeof value !== "object") {
|
|
1868
|
+
return false;
|
|
1869
|
+
}
|
|
1870
|
+
const schema = value;
|
|
1871
|
+
const keys = Object.keys(schema);
|
|
1872
|
+
if (keys.length === 0) {
|
|
1873
|
+
return true;
|
|
1874
|
+
}
|
|
1875
|
+
if (typeof schema.$ref === "string") {
|
|
1876
|
+
return false;
|
|
1877
|
+
}
|
|
1878
|
+
if (Array.isArray(schema.anyOf) && schema.anyOf.some(isLooseSchema)) {
|
|
1879
|
+
return true;
|
|
1880
|
+
}
|
|
1881
|
+
if (schema.items && isLooseSchema(schema.items)) {
|
|
1882
|
+
return true;
|
|
1883
|
+
}
|
|
1884
|
+
if (schema.properties && typeof schema.properties === "object") {
|
|
1885
|
+
return Object.values(schema.properties).some(isLooseSchema);
|
|
1886
|
+
}
|
|
1887
|
+
return false;
|
|
1888
|
+
}
|
|
1889
|
+
function hasLooseResponseSchema(responses) {
|
|
1890
|
+
return responses.responses.some((response) => isLooseSchema(response.schema));
|
|
1891
|
+
}
|
|
1451
1892
|
async function extractMeta(config, paths, routes) {
|
|
1452
1893
|
const inputsByFile = /* @__PURE__ */ new Map();
|
|
1453
1894
|
const securityByFile = /* @__PURE__ */ new Map();
|
|
1454
1895
|
const hiddenFiles = /* @__PURE__ */ new Set();
|
|
1896
|
+
const openapiByFile = /* @__PURE__ */ new Map();
|
|
1455
1897
|
if (routes.length === 0) {
|
|
1456
|
-
return { inputsByFile, securityByFile, hiddenFiles };
|
|
1898
|
+
return { inputsByFile, securityByFile, hiddenFiles, openapiByFile };
|
|
1457
1899
|
}
|
|
1458
1900
|
try {
|
|
1459
1901
|
const meta = await extractRouteMeta(config, paths, routes);
|
|
@@ -1467,15 +1909,19 @@ async function extractMeta(config, paths, routes) {
|
|
|
1467
1909
|
if (entry.hidden) {
|
|
1468
1910
|
hiddenFiles.add(file);
|
|
1469
1911
|
}
|
|
1912
|
+
if (entry.openapi) {
|
|
1913
|
+
openapiByFile.set(file, entry.openapi);
|
|
1914
|
+
}
|
|
1470
1915
|
}
|
|
1471
1916
|
} catch (error) {
|
|
1472
1917
|
console.warn(`giri: skipped input/security generation (${error.message}).`);
|
|
1473
1918
|
}
|
|
1474
|
-
return { inputsByFile, securityByFile, hiddenFiles };
|
|
1919
|
+
return { inputsByFile, securityByFile, hiddenFiles, openapiByFile };
|
|
1475
1920
|
}
|
|
1476
1921
|
async function syncProject(config, options = {}) {
|
|
1477
1922
|
const paths = resolveGiriPaths(config, options.cwd);
|
|
1478
1923
|
assertSafeOutDir(paths);
|
|
1924
|
+
const hadOutDir = (0, import_node_fs7.existsSync)(paths.outDir);
|
|
1479
1925
|
const routes = await scanRoutes(paths.routesDir);
|
|
1480
1926
|
const folders = await typeFolders(paths, routes);
|
|
1481
1927
|
await (0, import_promises3.mkdir)(paths.outDir, { recursive: true });
|
|
@@ -1484,26 +1930,28 @@ async function syncProject(config, options = {}) {
|
|
|
1484
1930
|
await writeAppTypes(paths);
|
|
1485
1931
|
await writeTsConfig(paths, config);
|
|
1486
1932
|
const responsesByFile = await extractResponses(paths, routes);
|
|
1487
|
-
const { inputsByFile, securityByFile, hiddenFiles } = await extractMeta(config, paths, routes);
|
|
1488
|
-
const data = { responsesByFile, inputsByFile, securityByFile, hiddenFiles };
|
|
1933
|
+
const { inputsByFile, securityByFile, hiddenFiles, openapiByFile } = await extractMeta(config, paths, routes);
|
|
1934
|
+
const data = { responsesByFile, inputsByFile, securityByFile, hiddenFiles, openapiByFile };
|
|
1489
1935
|
await writeManifest(paths, routes, data);
|
|
1490
1936
|
await writeOpenApi(paths, routes, data);
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1937
|
+
if (hadOutDir) {
|
|
1938
|
+
await pruneDir(
|
|
1939
|
+
paths.outDir,
|
|
1940
|
+
/* @__PURE__ */ new Set([
|
|
1941
|
+
(0, import_node_path11.join)(paths.outDir, "tsconfig.json"),
|
|
1942
|
+
(0, import_node_path11.join)(paths.outDir, "manifest.json"),
|
|
1943
|
+
(0, import_node_path11.join)(paths.outDir, "openapi.json"),
|
|
1944
|
+
(0, import_node_path11.join)(paths.outDir, "routes.d.ts"),
|
|
1945
|
+
(0, import_node_path11.join)(paths.outDir, "types", "app.d.ts"),
|
|
1946
|
+
...folders.map((folder) => typeFilePath(paths, folder.dir))
|
|
1947
|
+
])
|
|
1948
|
+
);
|
|
1949
|
+
}
|
|
1502
1950
|
return { paths, routes, folders, data };
|
|
1503
1951
|
}
|
|
1504
1952
|
|
|
1505
1953
|
// src/generator/watch.ts
|
|
1506
|
-
var
|
|
1954
|
+
var import_node_fs8 = require("fs");
|
|
1507
1955
|
var import_node_path13 = require("path");
|
|
1508
1956
|
|
|
1509
1957
|
// src/loader/module-loader.ts
|
|
@@ -1592,6 +2040,7 @@ function createWatchUpdater(config, initial) {
|
|
|
1592
2040
|
data.inputsByFile = result.data.inputsByFile;
|
|
1593
2041
|
data.securityByFile = result.data.securityByFile;
|
|
1594
2042
|
data.hiddenFiles = result.data.hiddenFiles;
|
|
2043
|
+
data.openapiByFile = result.data.openapiByFile;
|
|
1595
2044
|
purgeGeneratedModules(paths.outDir);
|
|
1596
2045
|
return "full";
|
|
1597
2046
|
};
|
|
@@ -1600,7 +2049,7 @@ function createWatchUpdater(config, initial) {
|
|
|
1600
2049
|
try {
|
|
1601
2050
|
const { createSchemaProgram: createSchemaProgram2, extractRouteResponses: extractRouteResponses2 } = await Promise.resolve().then(() => (init_schema(), schema_exports));
|
|
1602
2051
|
const appTypes = (0, import_node_path13.join)(paths.outDir, "types", "app.d.ts");
|
|
1603
|
-
const program = createSchemaProgram2(paths, (0,
|
|
2052
|
+
const program = createSchemaProgram2(paths, (0, import_node_fs8.existsSync)(appTypes) ? [key, appTypes] : [key]);
|
|
1604
2053
|
data.responsesByFile.set(key, extractRouteResponses2(program, key));
|
|
1605
2054
|
} catch {
|
|
1606
2055
|
}
|
|
@@ -1610,6 +2059,7 @@ function createWatchUpdater(config, initial) {
|
|
|
1610
2059
|
data.inputsByFile.delete(key);
|
|
1611
2060
|
data.securityByFile.delete(key);
|
|
1612
2061
|
data.hiddenFiles.delete(key);
|
|
2062
|
+
data.openapiByFile.delete(key);
|
|
1613
2063
|
if (entry?.input) {
|
|
1614
2064
|
data.inputsByFile.set(key, entry.input);
|
|
1615
2065
|
}
|
|
@@ -1619,6 +2069,9 @@ function createWatchUpdater(config, initial) {
|
|
|
1619
2069
|
if (entry?.hidden) {
|
|
1620
2070
|
data.hiddenFiles.add(key);
|
|
1621
2071
|
}
|
|
2072
|
+
if (entry?.openapi) {
|
|
2073
|
+
data.openapiByFile.set(key, entry.openapi);
|
|
2074
|
+
}
|
|
1622
2075
|
} catch {
|
|
1623
2076
|
}
|
|
1624
2077
|
};
|
|
@@ -1629,10 +2082,10 @@ function createWatchUpdater(config, initial) {
|
|
|
1629
2082
|
}
|
|
1630
2083
|
const abs = (0, import_node_path13.resolve)((0, import_node_path13.dirname)(paths.routesDir), filename);
|
|
1631
2084
|
const file = slash(abs);
|
|
1632
|
-
if (!(0,
|
|
2085
|
+
if (!(0, import_node_fs8.existsSync)(abs)) {
|
|
1633
2086
|
return fullResync();
|
|
1634
2087
|
}
|
|
1635
|
-
if ((0,
|
|
2088
|
+
if ((0, import_node_fs8.statSync)(abs).isDirectory()) {
|
|
1636
2089
|
return "skip";
|
|
1637
2090
|
}
|
|
1638
2091
|
const graph = buildModuleGraph(paths.cwd);
|
|
@@ -1657,13 +2110,13 @@ function createWatchUpdater(config, initial) {
|
|
|
1657
2110
|
}
|
|
1658
2111
|
|
|
1659
2112
|
// src/lifecycle.ts
|
|
1660
|
-
var
|
|
2113
|
+
var import_node_fs9 = require("fs");
|
|
1661
2114
|
var import_node_path14 = require("path");
|
|
1662
2115
|
var MAIN_EXTENSIONS2 = ["ts", "tsx", "mts", "cts", "js", "jsx", "mjs", "cjs"];
|
|
1663
2116
|
function resolveMainFile(cwd) {
|
|
1664
2117
|
for (const ext of MAIN_EXTENSIONS2) {
|
|
1665
2118
|
const file = (0, import_node_path14.join)(cwd, "src", `main.${ext}`);
|
|
1666
|
-
if ((0,
|
|
2119
|
+
if ((0, import_node_fs9.existsSync)(file)) {
|
|
1667
2120
|
return file;
|
|
1668
2121
|
}
|
|
1669
2122
|
}
|
|
@@ -1830,7 +2283,7 @@ function parseFlags(args) {
|
|
|
1830
2283
|
async function ensureGitignore(cwd) {
|
|
1831
2284
|
const file = (0, import_node_path15.join)(cwd, ".gitignore");
|
|
1832
2285
|
const entry = ".giri";
|
|
1833
|
-
if (!(0,
|
|
2286
|
+
if (!(0, import_node_fs10.existsSync)(file)) {
|
|
1834
2287
|
await (0, import_promises4.writeFile)(file, `${entry}
|
|
1835
2288
|
`);
|
|
1836
2289
|
return;
|
|
@@ -1843,7 +2296,7 @@ async function ensureGitignore(cwd) {
|
|
|
1843
2296
|
}
|
|
1844
2297
|
async function ensureTsConfig(cwd) {
|
|
1845
2298
|
const file = (0, import_node_path15.join)(cwd, "tsconfig.json");
|
|
1846
|
-
if ((0,
|
|
2299
|
+
if ((0, import_node_fs10.existsSync)(file)) {
|
|
1847
2300
|
return;
|
|
1848
2301
|
}
|
|
1849
2302
|
await (0, import_promises4.writeFile)(
|
|
@@ -1941,7 +2394,7 @@ async function selectAdapter(interactive) {
|
|
|
1941
2394
|
return ADAPTERS.find((adapter) => adapter.value === picked) ?? null;
|
|
1942
2395
|
}
|
|
1943
2396
|
async function initProject(cwd, flags) {
|
|
1944
|
-
if (!(0,
|
|
2397
|
+
if (!(0, import_node_fs10.existsSync)((0, import_node_path15.join)(cwd, "package.json"))) {
|
|
1945
2398
|
throw new Error(
|
|
1946
2399
|
"No package.json found. Run `giri init` inside an existing project - set one up first (e.g. `npm init -y` and install typescript), then re-run."
|
|
1947
2400
|
);
|
|
@@ -1967,11 +2420,11 @@ async function initProject(cwd, flags) {
|
|
|
1967
2420
|
return;
|
|
1968
2421
|
}
|
|
1969
2422
|
const configPath = (0, import_node_path15.join)(cwd, "giri.config.ts");
|
|
1970
|
-
if (!(0,
|
|
2423
|
+
if (!(0, import_node_fs10.existsSync)(configPath)) {
|
|
1971
2424
|
await (0, import_promises4.writeFile)(configPath, configSource(adapter));
|
|
1972
2425
|
}
|
|
1973
2426
|
const routePath = (0, import_node_path15.join)(cwd, "src", "routes", "+get.ts");
|
|
1974
|
-
if (!(0,
|
|
2427
|
+
if (!(0, import_node_fs10.existsSync)(routePath)) {
|
|
1975
2428
|
await (0, import_promises4.mkdir)((0, import_node_path15.join)(cwd, "src", "routes"), { recursive: true });
|
|
1976
2429
|
await (0, import_promises4.writeFile)(
|
|
1977
2430
|
routePath,
|
|
@@ -2041,7 +2494,7 @@ function displayHost(address) {
|
|
|
2041
2494
|
}
|
|
2042
2495
|
async function serveProject(config, flags) {
|
|
2043
2496
|
Error.stackTraceLimit = 30;
|
|
2044
|
-
const { watch } = await import("
|
|
2497
|
+
const { watch } = await import("chokidar");
|
|
2045
2498
|
let stop;
|
|
2046
2499
|
const boot = async (cfg) => {
|
|
2047
2500
|
const initial = await syncProject(cfg);
|
|
@@ -2058,7 +2511,7 @@ async function serveProject(config, flags) {
|
|
|
2058
2511
|
const hostname = flags.hostname ?? cfg.server?.hostname;
|
|
2059
2512
|
if (flags.watch) {
|
|
2060
2513
|
const srcDir = (0, import_node_path15.resolve)(current.paths.routesDir, "..");
|
|
2061
|
-
if ((0,
|
|
2514
|
+
if ((0, import_node_fs10.existsSync)(srcDir)) {
|
|
2062
2515
|
let timer;
|
|
2063
2516
|
let syncing = false;
|
|
2064
2517
|
const changed = /* @__PURE__ */ new Set();
|
|
@@ -2078,21 +2531,30 @@ async function serveProject(config, flags) {
|
|
|
2078
2531
|
while (changed.size > 0) {
|
|
2079
2532
|
const batch = [...changed];
|
|
2080
2533
|
changed.clear();
|
|
2081
|
-
let
|
|
2534
|
+
let rebuild = false;
|
|
2535
|
+
let fullSync = false;
|
|
2536
|
+
const dirtySet = /* @__PURE__ */ new Set();
|
|
2082
2537
|
for (const name of batch) {
|
|
2083
2538
|
const outcome = await updater.apply(name || null);
|
|
2084
2539
|
if (outcome === "skip") {
|
|
2085
2540
|
continue;
|
|
2086
2541
|
}
|
|
2087
|
-
|
|
2542
|
+
rebuild = true;
|
|
2543
|
+
if (name) {
|
|
2544
|
+
dirtySet.add((0, import_node_path15.resolve)(srcDir, name));
|
|
2545
|
+
}
|
|
2088
2546
|
const rel = name ? `src/${name.replace(/\\/g, "/")}` : "src";
|
|
2089
2547
|
log2.change(outcome === "full" ? "sync" : "update", rel, bump(rel));
|
|
2090
2548
|
if (outcome === "full") {
|
|
2549
|
+
fullSync = true;
|
|
2091
2550
|
break;
|
|
2092
2551
|
}
|
|
2093
2552
|
}
|
|
2094
|
-
if (
|
|
2095
|
-
current = await buildGiriApp(cfg, {
|
|
2553
|
+
if (rebuild) {
|
|
2554
|
+
current = await buildGiriApp(cfg, {
|
|
2555
|
+
services,
|
|
2556
|
+
dirty: fullSync ? void 0 : dirtySet
|
|
2557
|
+
});
|
|
2096
2558
|
}
|
|
2097
2559
|
}
|
|
2098
2560
|
} catch (error) {
|
|
@@ -2104,11 +2566,15 @@ async function serveProject(config, flags) {
|
|
|
2104
2566
|
void flush();
|
|
2105
2567
|
}
|
|
2106
2568
|
};
|
|
2107
|
-
const watcher = watch(srcDir, {
|
|
2108
|
-
|
|
2569
|
+
const watcher = watch(srcDir, { persistent: true });
|
|
2570
|
+
const onFileChange = (filename) => {
|
|
2571
|
+
changed.add(filename);
|
|
2109
2572
|
clearTimeout(timer);
|
|
2110
2573
|
timer = setTimeout(() => void flush(), 150);
|
|
2111
|
-
}
|
|
2574
|
+
};
|
|
2575
|
+
watcher.on("change", onFileChange);
|
|
2576
|
+
watcher.on("add", onFileChange);
|
|
2577
|
+
watcher.on("unlink", onFileChange);
|
|
2112
2578
|
closers.push(() => {
|
|
2113
2579
|
clearTimeout(timer);
|
|
2114
2580
|
watcher.close();
|
|
@@ -2156,7 +2622,8 @@ async function serveProject(config, flags) {
|
|
|
2156
2622
|
restarting = false;
|
|
2157
2623
|
}
|
|
2158
2624
|
};
|
|
2159
|
-
watch((0, import_node_path15.dirname)(configPath), {
|
|
2625
|
+
const configWatcher = watch((0, import_node_path15.dirname)(configPath), { persistent: true });
|
|
2626
|
+
configWatcher.on("all", (_event, filename) => {
|
|
2160
2627
|
if (filename && (0, import_node_path15.basename)(filename.toString()) === configName) {
|
|
2161
2628
|
clearTimeout(timer);
|
|
2162
2629
|
timer = setTimeout(() => void restart(), 150);
|