@boon4681/giri 0.0.1 → 0.0.2-alpha-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/README.md +19 -17
- package/dist/adapters/hono.d.ts +26 -3
- package/dist/adapters/hono.js +99 -7
- package/dist/adapters/hono.js.map +1 -1
- package/dist/cli.js +1197 -1119
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +10 -3
- package/dist/index.js +1368 -1285
- package/dist/index.js.map +1 -1
- package/dist/{types-BrUMxh5u.d.ts → types-C4J1_iLZ.d.ts} +24 -3
- 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 +1 -1
package/dist/cli.js
CHANGED
|
@@ -45,1286 +45,1320 @@ var init_es5 = __esm({
|
|
|
45
45
|
}
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
-
// src/
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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);
|
|
48
|
+
// src/generator/schema/program.ts
|
|
49
|
+
function createSchemaProgram(paths, routeFiles) {
|
|
50
|
+
let options = { ...DEFAULT_OPTIONS };
|
|
51
|
+
const configPath = import_typescript.default.findConfigFile(paths.cwd, import_typescript.default.sys.fileExists, "tsconfig.json");
|
|
52
|
+
if (configPath) {
|
|
53
|
+
const parsed = import_typescript.default.getParsedCommandLineOfConfigFile(configPath, {}, {
|
|
54
|
+
...import_typescript.default.sys,
|
|
55
|
+
onUnRecoverableConfigFileDiagnostic: () => {
|
|
94
56
|
}
|
|
57
|
+
});
|
|
58
|
+
if (parsed) {
|
|
59
|
+
options = { ...parsed.options, noEmit: true };
|
|
95
60
|
}
|
|
96
|
-
import_prompts.log.error(e);
|
|
97
|
-
(0, import_node_process.exit)(1);
|
|
98
61
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
62
|
+
return import_typescript.default.createProgram(routeFiles, options);
|
|
63
|
+
}
|
|
64
|
+
var import_typescript, DEFAULT_OPTIONS;
|
|
65
|
+
var init_program = __esm({
|
|
66
|
+
"src/generator/schema/program.ts"() {
|
|
67
|
+
"use strict";
|
|
68
|
+
import_typescript = __toESM(require("typescript"));
|
|
69
|
+
DEFAULT_OPTIONS = {
|
|
70
|
+
target: import_typescript.default.ScriptTarget.ES2022,
|
|
71
|
+
module: import_typescript.default.ModuleKind.NodeNext,
|
|
72
|
+
moduleResolution: import_typescript.default.ModuleResolutionKind.NodeNext,
|
|
73
|
+
strict: true,
|
|
74
|
+
skipLibCheck: true,
|
|
75
|
+
noEmit: true
|
|
112
76
|
};
|
|
113
77
|
}
|
|
114
|
-
|
|
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
|
-
};
|
|
78
|
+
});
|
|
143
79
|
|
|
144
|
-
// src/
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
return path.split(import_node_path2.sep).join("/");
|
|
80
|
+
// src/generator/schema/json-schema.ts
|
|
81
|
+
function createWalkContext(checker, location) {
|
|
82
|
+
return {
|
|
83
|
+
checker,
|
|
84
|
+
location,
|
|
85
|
+
defs: {},
|
|
86
|
+
inProgress: /* @__PURE__ */ new Map(),
|
|
87
|
+
usedDefs: /* @__PURE__ */ new Set(),
|
|
88
|
+
warnings: []
|
|
89
|
+
};
|
|
155
90
|
}
|
|
156
|
-
function
|
|
157
|
-
return
|
|
91
|
+
function typeId(type) {
|
|
92
|
+
return type.id;
|
|
158
93
|
}
|
|
159
|
-
function
|
|
160
|
-
|
|
161
|
-
return void 0;
|
|
162
|
-
}
|
|
163
|
-
const stem = fileName.replace(/\.(?:[cm]?[jt]s|[jt]sx)$/, "").toLowerCase();
|
|
164
|
-
return METHOD_FROM_FILE.get(stem);
|
|
94
|
+
function intrinsicName(type) {
|
|
95
|
+
return type.intrinsicName;
|
|
165
96
|
}
|
|
166
|
-
function
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
97
|
+
function isDateType(type) {
|
|
98
|
+
const symbol = type.getSymbol() ?? type.aliasSymbol;
|
|
99
|
+
return symbol?.getName() === "Date";
|
|
100
|
+
}
|
|
101
|
+
function literalValuesOf(types) {
|
|
102
|
+
const values = [];
|
|
103
|
+
for (const member of types) {
|
|
104
|
+
if (member.isStringLiteral() || member.isNumberLiteral()) {
|
|
105
|
+
values.push(member.value);
|
|
106
|
+
} else if (member.flags & import_typescript2.default.TypeFlags.BooleanLiteral) {
|
|
107
|
+
values.push(intrinsicName(member) === "true");
|
|
108
|
+
} else {
|
|
109
|
+
return void 0;
|
|
171
110
|
}
|
|
172
111
|
}
|
|
173
|
-
return
|
|
112
|
+
return values;
|
|
174
113
|
}
|
|
175
|
-
function
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
114
|
+
function walkUnion(type, ctx) {
|
|
115
|
+
const flag = import_typescript2.default.TypeFlags.Undefined | import_typescript2.default.TypeFlags.Void | import_typescript2.default.TypeFlags.Never;
|
|
116
|
+
const members = type.types.filter((member) => !(member.flags & flag));
|
|
117
|
+
if (members.length === 1) {
|
|
118
|
+
return walkType(members[0], ctx);
|
|
179
119
|
}
|
|
180
|
-
|
|
120
|
+
const enumValues = literalValuesOf(members);
|
|
121
|
+
if (enumValues) {
|
|
122
|
+
return { enum: enumValues };
|
|
123
|
+
}
|
|
124
|
+
return { anyOf: members.map((member) => walkType(member, ctx)) };
|
|
181
125
|
}
|
|
182
|
-
function
|
|
183
|
-
|
|
184
|
-
|
|
126
|
+
function buildObjectSchema(type, ctx) {
|
|
127
|
+
const { checker } = ctx;
|
|
128
|
+
const indexInfo = checker.getIndexInfoOfType(type, import_typescript2.default.IndexKind.String) ?? checker.getIndexInfoOfType(type, import_typescript2.default.IndexKind.Number);
|
|
129
|
+
const properties = {};
|
|
130
|
+
const required = [];
|
|
131
|
+
for (const symbol of checker.getPropertiesOfType(type)) {
|
|
132
|
+
const name = symbol.getName();
|
|
133
|
+
const propType = checker.getTypeOfSymbolAtLocation(symbol, ctx.location);
|
|
134
|
+
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));
|
|
135
|
+
properties[name] = walkType(propType, ctx);
|
|
136
|
+
if (!optional) {
|
|
137
|
+
required.push(name);
|
|
138
|
+
}
|
|
185
139
|
}
|
|
186
|
-
const
|
|
187
|
-
if (
|
|
188
|
-
|
|
189
|
-
return {
|
|
190
|
-
value: `:${name}{.*}`,
|
|
191
|
-
param: { name, catchAll: true }
|
|
192
|
-
};
|
|
140
|
+
const schema = { type: "object" };
|
|
141
|
+
if (Object.keys(properties).length > 0) {
|
|
142
|
+
schema.properties = properties;
|
|
193
143
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const name = param[1];
|
|
197
|
-
return {
|
|
198
|
-
value: `:${name}`,
|
|
199
|
-
param: { name, catchAll: false }
|
|
200
|
-
};
|
|
144
|
+
if (required.length > 0) {
|
|
145
|
+
schema.required = required;
|
|
201
146
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
}
|
|
147
|
+
if (indexInfo) {
|
|
148
|
+
schema.additionalProperties = walkType(indexInfo.type, ctx);
|
|
149
|
+
} else if (Object.keys(properties).length > 0) {
|
|
150
|
+
schema.additionalProperties = false;
|
|
215
151
|
}
|
|
216
|
-
return
|
|
217
|
-
path: pathSegments.length > 0 ? `/${pathSegments.join("/")}` : "/",
|
|
218
|
-
params
|
|
219
|
-
};
|
|
152
|
+
return schema;
|
|
220
153
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
154
|
+
function defName(type) {
|
|
155
|
+
const symbol = type.getSymbol() ?? type.aliasSymbol;
|
|
156
|
+
const name = symbol?.getName();
|
|
157
|
+
if (name && name !== "__type" && name !== "__object") {
|
|
158
|
+
return name;
|
|
224
159
|
}
|
|
225
|
-
|
|
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;
|
|
160
|
+
return `Anonymous${typeId(type)}`;
|
|
240
161
|
}
|
|
241
|
-
function
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
for (const segment of segments) {
|
|
246
|
-
current = (0, import_node_path2.join)(current, segment);
|
|
247
|
-
dirs.push(current);
|
|
162
|
+
function walkObject(type, ctx) {
|
|
163
|
+
const { checker } = ctx;
|
|
164
|
+
if (isDateType(type)) {
|
|
165
|
+
return { type: "string", format: "date-time" };
|
|
248
166
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
if (!(0, import_node_fs2.existsSync)(routesDir)) {
|
|
253
|
-
return [];
|
|
167
|
+
if (checker.isArrayType(type)) {
|
|
168
|
+
const [element] = checker.getTypeArguments(type);
|
|
169
|
+
return { type: "array", items: element ? walkType(element, ctx) : {} };
|
|
254
170
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
-
});
|
|
171
|
+
if (checker.isTupleType(type)) {
|
|
172
|
+
const elements = checker.getTypeArguments(type);
|
|
173
|
+
return { type: "array", items: elements.map((element) => walkType(element, ctx)) };
|
|
278
174
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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;
|
|
175
|
+
const id = typeId(type);
|
|
176
|
+
const existing = ctx.inProgress.get(id);
|
|
177
|
+
if (existing) {
|
|
178
|
+
ctx.usedDefs.add(existing);
|
|
179
|
+
return { $ref: `#/$defs/${existing}` };
|
|
313
180
|
}
|
|
314
|
-
|
|
181
|
+
const name = defName(type);
|
|
182
|
+
ctx.inProgress.set(id, name);
|
|
183
|
+
const schema = buildObjectSchema(type, ctx);
|
|
184
|
+
ctx.inProgress.delete(id);
|
|
185
|
+
if (ctx.usedDefs.has(name)) {
|
|
186
|
+
ctx.defs[name] = schema;
|
|
187
|
+
return { $ref: `#/$defs/${name}` };
|
|
188
|
+
}
|
|
189
|
+
return schema;
|
|
315
190
|
}
|
|
316
|
-
function
|
|
317
|
-
const
|
|
318
|
-
if (
|
|
319
|
-
return
|
|
191
|
+
function walkType(type, ctx) {
|
|
192
|
+
const flags = type.flags;
|
|
193
|
+
if (flags & (import_typescript2.default.TypeFlags.Any | import_typescript2.default.TypeFlags.Unknown)) {
|
|
194
|
+
return {};
|
|
320
195
|
}
|
|
321
|
-
if (
|
|
322
|
-
return
|
|
196
|
+
if (flags & import_typescript2.default.TypeFlags.Null) {
|
|
197
|
+
return { type: "null" };
|
|
323
198
|
}
|
|
324
|
-
if (
|
|
325
|
-
|
|
326
|
-
if (typeof middleware !== "function") {
|
|
327
|
-
throw new Error(`Middleware export in ${file} must contain only functions.`);
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
return exported;
|
|
199
|
+
if (flags & (import_typescript2.default.TypeFlags.Undefined | import_typescript2.default.TypeFlags.Void)) {
|
|
200
|
+
return {};
|
|
331
201
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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
|
-
);
|
|
202
|
+
if (flags & (import_typescript2.default.TypeFlags.BigInt | import_typescript2.default.TypeFlags.BigIntLiteral)) {
|
|
203
|
+
ctx.warnings.push("bigint is not JSON-serializable (JSON.stringify throws); documented as string.");
|
|
204
|
+
return { type: "string" };
|
|
339
205
|
}
|
|
340
|
-
|
|
341
|
-
|
|
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
|
-
);
|
|
206
|
+
if (type.isStringLiteral()) {
|
|
207
|
+
return { type: "string", const: type.value };
|
|
346
208
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
const input = {};
|
|
350
|
-
if (routeModule.body !== void 0) {
|
|
351
|
-
assertBodySchema(routeModule.body, file);
|
|
352
|
-
input.body = routeModule.body;
|
|
209
|
+
if (type.isNumberLiteral()) {
|
|
210
|
+
return { type: "number", const: type.value };
|
|
353
211
|
}
|
|
354
|
-
if (
|
|
355
|
-
|
|
356
|
-
input.query = routeModule.query;
|
|
212
|
+
if (flags & import_typescript2.default.TypeFlags.BooleanLiteral) {
|
|
213
|
+
return { type: "boolean", const: intrinsicName(type) === "true" };
|
|
357
214
|
}
|
|
358
|
-
|
|
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;
|
|
215
|
+
if (flags & import_typescript2.default.TypeFlags.String) {
|
|
216
|
+
return { type: "string" };
|
|
374
217
|
}
|
|
375
|
-
if (
|
|
376
|
-
return "";
|
|
218
|
+
if (flags & import_typescript2.default.TypeFlags.Number) {
|
|
219
|
+
return { type: "number" };
|
|
377
220
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
return request.slice(prefix.length);
|
|
221
|
+
if (flags & import_typescript2.default.TypeFlags.Boolean) {
|
|
222
|
+
return { type: "boolean" };
|
|
381
223
|
}
|
|
382
|
-
|
|
224
|
+
if (type.isUnion()) {
|
|
225
|
+
return walkUnion(type, ctx);
|
|
226
|
+
}
|
|
227
|
+
if (flags & import_typescript2.default.TypeFlags.Object || type.isIntersection()) {
|
|
228
|
+
return walkObject(type, ctx);
|
|
229
|
+
}
|
|
230
|
+
return {};
|
|
383
231
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
232
|
+
var import_typescript2;
|
|
233
|
+
var init_json_schema = __esm({
|
|
234
|
+
"src/generator/schema/json-schema.ts"() {
|
|
235
|
+
"use strict";
|
|
236
|
+
import_typescript2 = __toESM(require("typescript"));
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// src/generator/schema/responses.ts
|
|
241
|
+
function findHandleFunction(source) {
|
|
242
|
+
let found;
|
|
243
|
+
const isExported = (node) => import_typescript3.default.canHaveModifiers(node) && (import_typescript3.default.getModifiers(node)?.some((m) => m.kind === import_typescript3.default.SyntaxKind.ExportKeyword) ?? false);
|
|
244
|
+
for (const statement of source.statements) {
|
|
245
|
+
if (import_typescript3.default.isFunctionDeclaration(statement) && statement.name?.text === "handle" && isExported(statement)) {
|
|
246
|
+
found = statement;
|
|
389
247
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
248
|
+
if (import_typescript3.default.isVariableStatement(statement) && isExported(statement)) {
|
|
249
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
250
|
+
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))) {
|
|
251
|
+
found = declaration.initializer;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
393
254
|
}
|
|
394
|
-
return resolveAliasTarget(cwd, target, capture);
|
|
395
255
|
}
|
|
396
|
-
return
|
|
256
|
+
return found;
|
|
397
257
|
}
|
|
398
|
-
function
|
|
399
|
-
if (
|
|
400
|
-
return
|
|
401
|
-
};
|
|
258
|
+
function collectReturnExpressions(fn) {
|
|
259
|
+
if (import_typescript3.default.isArrowFunction(fn) && !import_typescript3.default.isBlock(fn.body)) {
|
|
260
|
+
return [fn.body];
|
|
402
261
|
}
|
|
403
|
-
|
|
404
|
-
|
|
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;
|
|
262
|
+
if (!fn.body) {
|
|
263
|
+
return [];
|
|
425
264
|
}
|
|
426
|
-
|
|
427
|
-
const
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
265
|
+
const expressions = [];
|
|
266
|
+
const visit = (node) => {
|
|
267
|
+
if (import_typescript3.default.isFunctionDeclaration(node) || import_typescript3.default.isFunctionExpression(node) || import_typescript3.default.isArrowFunction(node)) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
if (import_typescript3.default.isReturnStatement(node) && node.expression) {
|
|
271
|
+
expressions.push(node.expression);
|
|
272
|
+
}
|
|
273
|
+
import_typescript3.default.forEachChild(node, visit);
|
|
432
274
|
};
|
|
275
|
+
import_typescript3.default.forEachChild(fn.body, visit);
|
|
276
|
+
return expressions;
|
|
433
277
|
}
|
|
434
|
-
function
|
|
435
|
-
|
|
436
|
-
|
|
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 };
|
|
278
|
+
function propertyType(checker, type, name, location) {
|
|
279
|
+
const symbol = checker.getPropertyOfType(type, name);
|
|
280
|
+
return symbol ? checker.getTypeOfSymbolAtLocation(symbol, location) : void 0;
|
|
472
281
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
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("/");
|
|
282
|
+
function isTypedResponse(checker, type) {
|
|
283
|
+
return Boolean(
|
|
284
|
+
checker.getPropertyOfType(type, "data") && checker.getPropertyOfType(type, "status") && checker.getPropertyOfType(type, "format")
|
|
285
|
+
);
|
|
490
286
|
}
|
|
491
|
-
function
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
path = `./${path}`;
|
|
287
|
+
function readFromCall(checker, expression) {
|
|
288
|
+
if (!import_typescript3.default.isCallExpression(expression) || !import_typescript3.default.isPropertyAccessExpression(expression.expression)) {
|
|
289
|
+
return void 0;
|
|
495
290
|
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
let path = slash((0, import_node_path4.relative)(fromDir, toPath));
|
|
500
|
-
if (!path.startsWith(".")) {
|
|
501
|
-
path = `./${path}`;
|
|
291
|
+
const method = expression.expression.name.text;
|
|
292
|
+
if (method !== "json" && method !== "text") {
|
|
293
|
+
return void 0;
|
|
502
294
|
}
|
|
503
|
-
|
|
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}`;
|
|
295
|
+
if (!isTypedResponse(checker, checker.getTypeAtLocation(expression))) {
|
|
296
|
+
return void 0;
|
|
509
297
|
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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}`);
|
|
298
|
+
const [dataArg, statusArg] = expression.arguments;
|
|
299
|
+
if (!dataArg) {
|
|
300
|
+
return void 0;
|
|
520
301
|
}
|
|
302
|
+
let status = 200;
|
|
303
|
+
if (statusArg) {
|
|
304
|
+
const statusType = checker.getTypeAtLocation(statusArg);
|
|
305
|
+
status = statusType.isNumberLiteral() ? statusType.value : "default";
|
|
306
|
+
}
|
|
307
|
+
return { status, format: method === "text" ? "text" : "json", data: checker.getTypeAtLocation(dataArg) };
|
|
521
308
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
309
|
+
function readFromType(checker, type, location) {
|
|
310
|
+
const dataType = propertyType(checker, type, "data", location);
|
|
311
|
+
const statusType = propertyType(checker, type, "status", location);
|
|
312
|
+
const formatType = propertyType(checker, type, "format", location);
|
|
313
|
+
if (!dataType || !statusType || !formatType) {
|
|
314
|
+
return void 0;
|
|
526
315
|
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
316
|
+
const status = statusType.isNumberLiteral() ? statusType.value : "default";
|
|
317
|
+
const format = formatType.isStringLiteral() && formatType.value === "text" ? "text" : "json";
|
|
318
|
+
return { status, format, data: dataType };
|
|
530
319
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
`);
|
|
320
|
+
function constituents(type) {
|
|
321
|
+
return type.isUnion() ? type.types : [type];
|
|
534
322
|
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
return;
|
|
323
|
+
function extractRouteResponses(program, file) {
|
|
324
|
+
const result = { responses: [], opaque: false, warnings: [], $defs: {} };
|
|
325
|
+
const source = program.getSourceFile(file);
|
|
326
|
+
if (!source) {
|
|
327
|
+
return result;
|
|
541
328
|
}
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
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
|
-
}
|
|
329
|
+
const checker = program.getTypeChecker();
|
|
330
|
+
const fn = findHandleFunction(source);
|
|
331
|
+
if (!fn) {
|
|
332
|
+
return result;
|
|
552
333
|
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
334
|
+
const ctx = createWalkContext(checker, fn);
|
|
335
|
+
const byStatus = /* @__PURE__ */ new Map();
|
|
336
|
+
const record = (hit) => {
|
|
337
|
+
const schema = walkType(hit.data, ctx);
|
|
338
|
+
const bucket = byStatus.get(hit.status) ?? { format: hit.format, schemas: [] };
|
|
339
|
+
bucket.schemas.push(schema);
|
|
340
|
+
byStatus.set(hit.status, bucket);
|
|
341
|
+
};
|
|
342
|
+
for (const expression of collectReturnExpressions(fn)) {
|
|
343
|
+
const fromCall = readFromCall(checker, expression);
|
|
344
|
+
if (fromCall) {
|
|
345
|
+
record(fromCall);
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
let matched = false;
|
|
349
|
+
for (const member of constituents(checker.getTypeAtLocation(expression))) {
|
|
350
|
+
const hit = readFromType(checker, member, expression);
|
|
351
|
+
if (hit) {
|
|
352
|
+
record(hit);
|
|
353
|
+
matched = true;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
if (!matched) {
|
|
357
|
+
result.opaque = true;
|
|
562
358
|
}
|
|
563
359
|
}
|
|
564
|
-
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
let path = slash((0, import_node_path5.relative)(fromDir, target)).replace(/\.(?:[cm]?[jt]sx?)$/, "");
|
|
568
|
-
if (!path.startsWith(".")) {
|
|
569
|
-
path = `./${path}`;
|
|
360
|
+
for (const [status, { format, schemas }] of byStatus) {
|
|
361
|
+
const schema = schemas.length === 1 ? schemas[0] : { anyOf: schemas };
|
|
362
|
+
result.responses.push({ status, format, schema });
|
|
570
363
|
}
|
|
571
|
-
|
|
364
|
+
result.responses.sort((a, b) => Number(a.status) - Number(b.status));
|
|
365
|
+
result.warnings = ctx.warnings;
|
|
366
|
+
result.$defs = ctx.defs;
|
|
367
|
+
return result;
|
|
572
368
|
}
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
369
|
+
var import_typescript3;
|
|
370
|
+
var init_responses = __esm({
|
|
371
|
+
"src/generator/schema/responses.ts"() {
|
|
372
|
+
"use strict";
|
|
373
|
+
import_typescript3 = __toESM(require("typescript"));
|
|
374
|
+
init_json_schema();
|
|
579
375
|
}
|
|
580
|
-
|
|
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
|
-
}
|
|
376
|
+
});
|
|
601
377
|
|
|
602
|
-
// src/generator/
|
|
603
|
-
var
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
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
|
-
}
|
|
378
|
+
// src/generator/schema/index.ts
|
|
379
|
+
var schema_exports = {};
|
|
380
|
+
__export(schema_exports, {
|
|
381
|
+
createSchemaProgram: () => createSchemaProgram,
|
|
382
|
+
createWalkContext: () => createWalkContext,
|
|
383
|
+
extractRouteResponses: () => extractRouteResponses,
|
|
384
|
+
walkType: () => walkType
|
|
385
|
+
});
|
|
386
|
+
var init_schema = __esm({
|
|
387
|
+
"src/generator/schema/index.ts"() {
|
|
388
|
+
"use strict";
|
|
389
|
+
init_program();
|
|
390
|
+
init_responses();
|
|
391
|
+
init_json_schema();
|
|
392
|
+
}
|
|
393
|
+
});
|
|
628
394
|
|
|
629
|
-
// src/
|
|
630
|
-
var
|
|
631
|
-
var
|
|
632
|
-
var
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
395
|
+
// src/cli.ts
|
|
396
|
+
var import_node_child_process = require("child_process");
|
|
397
|
+
var import_node_fs9 = require("fs");
|
|
398
|
+
var import_promises4 = require("fs/promises");
|
|
399
|
+
var import_node_path15 = require("path");
|
|
400
|
+
var prompts = __toESM(require("@clack/prompts"));
|
|
401
|
+
|
|
402
|
+
// src/app.ts
|
|
403
|
+
var import_node_module = __toESM(require("module"));
|
|
404
|
+
var import_node_path3 = require("path");
|
|
405
|
+
|
|
406
|
+
// src/loader/loader.ts
|
|
407
|
+
var import_prompts = require("@clack/prompts");
|
|
408
|
+
var import_node_fs = require("fs");
|
|
409
|
+
var import_node_path = require("path");
|
|
410
|
+
var import_node_process = require("process");
|
|
411
|
+
|
|
412
|
+
// src/config/schema.ts
|
|
413
|
+
var import_typebox = require("@sinclair/typebox");
|
|
414
|
+
var configSchema = import_typebox.Type.Object({
|
|
415
|
+
adapter: import_typebox.Type.Any(),
|
|
416
|
+
alias: import_typebox.Type.Optional(import_typebox.Type.Record(
|
|
417
|
+
import_typebox.Type.String(),
|
|
418
|
+
import_typebox.Type.Union([import_typebox.Type.String(), import_typebox.Type.Array(import_typebox.Type.String())])
|
|
419
|
+
)),
|
|
420
|
+
outDir: import_typebox.Type.Optional(import_typebox.Type.String()),
|
|
421
|
+
server: import_typebox.Type.Optional(import_typebox.Type.Object({
|
|
422
|
+
port: import_typebox.Type.Optional(import_typebox.Type.Number()),
|
|
423
|
+
hostname: import_typebox.Type.Optional(import_typebox.Type.String())
|
|
424
|
+
}, { additionalProperties: false })),
|
|
425
|
+
errorSchema: import_typebox.Type.Optional(import_typebox.Type.Any())
|
|
426
|
+
}, { additionalProperties: false });
|
|
427
|
+
|
|
428
|
+
// src/loader/loader.ts
|
|
429
|
+
var import_value = require("@sinclair/typebox/value");
|
|
430
|
+
var assertES5 = async (unregister) => {
|
|
431
|
+
try {
|
|
432
|
+
init_es5();
|
|
433
|
+
} catch (e) {
|
|
434
|
+
if ("errors" in e && Array.isArray(e.errors) && e.errors.length > 0) {
|
|
435
|
+
const es5Error = e.errors.filter((it) => it.text?.includes(`("es5") is not supported yet`)).length > 0;
|
|
436
|
+
if (es5Error) {
|
|
437
|
+
import_prompts.log.error(
|
|
438
|
+
`Please change compilerOptions.target from 'es5' to 'es6' or above in your tsconfig.json`
|
|
439
|
+
);
|
|
440
|
+
(0, import_node_process.exit)(1);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
import_prompts.log.error(e);
|
|
444
|
+
(0, import_node_process.exit)(1);
|
|
651
445
|
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
446
|
+
};
|
|
447
|
+
var safeRegister = async () => {
|
|
448
|
+
const { register } = await import("esbuild-register/dist/node");
|
|
449
|
+
let res;
|
|
450
|
+
try {
|
|
451
|
+
res = register({
|
|
452
|
+
format: "cjs",
|
|
453
|
+
loader: "ts"
|
|
454
|
+
});
|
|
455
|
+
} catch {
|
|
456
|
+
res = {
|
|
457
|
+
unregister: () => {
|
|
659
458
|
}
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
await assertES5(res.unregister);
|
|
462
|
+
return res;
|
|
463
|
+
};
|
|
464
|
+
var load = async () => {
|
|
465
|
+
const defaultTsConfigExists = (0, import_node_fs.existsSync)((0, import_node_path.resolve)("giri.config.ts"));
|
|
466
|
+
const defaultJsConfigExists = (0, import_node_fs.existsSync)((0, import_node_path.resolve)("giri.config.js"));
|
|
467
|
+
const defaultConfigPath = defaultTsConfigExists ? "giri.config.ts" : defaultJsConfigExists ? "giri.config.js" : void 0;
|
|
468
|
+
if (!defaultConfigPath) {
|
|
469
|
+
import_prompts.log.error("Config file not found.");
|
|
470
|
+
(0, import_node_process.exit)(1);
|
|
471
|
+
}
|
|
472
|
+
const path = (0, import_node_path.resolve)(defaultConfigPath);
|
|
473
|
+
if (!(0, import_node_fs.existsSync)(path)) {
|
|
474
|
+
import_prompts.log.error(`${path} file does not exist`);
|
|
475
|
+
(0, import_node_process.exit)(1);
|
|
476
|
+
}
|
|
477
|
+
const { unregister } = await safeRegister();
|
|
478
|
+
const required = require(`${path}`);
|
|
479
|
+
const content = required.default ?? required;
|
|
480
|
+
unregister();
|
|
481
|
+
const res = import_value.Value.Check(configSchema, content);
|
|
482
|
+
if (!res) {
|
|
483
|
+
for (const error of [...import_value.Value.Errors(configSchema, content)]) {
|
|
484
|
+
import_prompts.log.error(error.message);
|
|
660
485
|
}
|
|
661
|
-
|
|
486
|
+
(0, import_node_process.exit)(1);
|
|
662
487
|
}
|
|
663
|
-
return
|
|
488
|
+
return content;
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
// src/routes.ts
|
|
492
|
+
var import_node_fs2 = require("fs");
|
|
493
|
+
var import_promises = require("fs/promises");
|
|
494
|
+
var import_node_path2 = require("path");
|
|
495
|
+
var import_tinyglobby = require("tinyglobby");
|
|
496
|
+
var METHOD_ORDER = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"];
|
|
497
|
+
var METHOD_FROM_FILE = new Map(
|
|
498
|
+
METHOD_ORDER.map((method) => [`+${method.toLowerCase()}`, method])
|
|
499
|
+
);
|
|
500
|
+
function normalizeSlashes(path) {
|
|
501
|
+
return path.split(import_node_path2.sep).join("/");
|
|
664
502
|
}
|
|
665
|
-
function
|
|
666
|
-
return
|
|
503
|
+
function isRouteSourceFile(fileName) {
|
|
504
|
+
return /\.(?:[cm]?[jt]s|[jt]sx)$/.test(fileName) && !fileName.endsWith(".d.ts");
|
|
667
505
|
}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
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
|
-
};
|
|
506
|
+
function methodFromFile(fileName) {
|
|
507
|
+
if (!isRouteSourceFile(fileName)) {
|
|
508
|
+
return void 0;
|
|
686
509
|
}
|
|
687
|
-
|
|
510
|
+
const stem = fileName.replace(/\.(?:[cm]?[jt]s|[jt]sx)$/, "").toLowerCase();
|
|
511
|
+
return METHOD_FROM_FILE.get(stem);
|
|
688
512
|
}
|
|
689
|
-
function
|
|
690
|
-
const
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
continue;
|
|
513
|
+
function sharedFileIn(dir) {
|
|
514
|
+
for (const ext of ["ts", "tsx", "js", "jsx", "mjs", "cjs", "mts", "cts"]) {
|
|
515
|
+
const file = (0, import_node_path2.join)(dir, `+shared.${ext}`);
|
|
516
|
+
if ((0, import_node_fs2.existsSync)(file)) {
|
|
517
|
+
return file;
|
|
695
518
|
}
|
|
696
|
-
seen.add(param.name);
|
|
697
|
-
params.push({ name: param.name, in: "path", required: true, schema: { type: "string" } });
|
|
698
519
|
}
|
|
699
|
-
return
|
|
520
|
+
return void 0;
|
|
700
521
|
}
|
|
701
|
-
function
|
|
702
|
-
|
|
522
|
+
function physicalRouteSegments(routesDir, routeDir) {
|
|
523
|
+
const rel = (0, import_node_path2.relative)(routesDir, routeDir);
|
|
524
|
+
if (!rel) {
|
|
703
525
|
return [];
|
|
704
526
|
}
|
|
705
|
-
|
|
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
|
-
}));
|
|
527
|
+
return normalizeSlashes(rel).split("/").filter(Boolean);
|
|
713
528
|
}
|
|
714
|
-
function
|
|
715
|
-
|
|
716
|
-
|
|
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
|
-
}
|
|
529
|
+
function urlSegment(segment) {
|
|
530
|
+
if (/^\(.+\)$/.test(segment)) {
|
|
531
|
+
return {};
|
|
722
532
|
}
|
|
723
|
-
|
|
533
|
+
const catchAll = /^\[\.\.\.(.+)\]$/.exec(segment);
|
|
534
|
+
if (catchAll) {
|
|
535
|
+
const name = catchAll[1];
|
|
536
|
+
return {
|
|
537
|
+
value: `:${name}{.*}`,
|
|
538
|
+
param: { name, catchAll: true }
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
const param = /^\[(.+)\]$/.exec(segment);
|
|
542
|
+
if (param) {
|
|
543
|
+
const name = param[1];
|
|
544
|
+
return {
|
|
545
|
+
value: `:${name}`,
|
|
546
|
+
param: { name, catchAll: false }
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
return { value: segment };
|
|
724
550
|
}
|
|
725
|
-
function
|
|
726
|
-
const
|
|
727
|
-
const
|
|
728
|
-
const
|
|
729
|
-
|
|
730
|
-
if (
|
|
731
|
-
|
|
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;
|
|
551
|
+
function pathFromSegments(segments) {
|
|
552
|
+
const pathSegments = [];
|
|
553
|
+
const params = [];
|
|
554
|
+
for (const segment of segments) {
|
|
555
|
+
const converted = urlSegment(segment);
|
|
556
|
+
if (converted.value) {
|
|
557
|
+
pathSegments.push(converted.value);
|
|
757
558
|
}
|
|
758
|
-
if (
|
|
759
|
-
|
|
559
|
+
if (converted.param) {
|
|
560
|
+
params.push(converted.param);
|
|
760
561
|
}
|
|
761
|
-
const openApiPath = toOpenApiPath(route.path);
|
|
762
|
-
const pathItem = documentPaths[openApiPath] ?? {};
|
|
763
|
-
pathItem[route.method.toLowerCase()] = operation;
|
|
764
|
-
documentPaths[openApiPath] = pathItem;
|
|
765
562
|
}
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
paths: documentPaths
|
|
563
|
+
return {
|
|
564
|
+
path: pathSegments.length > 0 ? `/${pathSegments.join("/")}` : "/",
|
|
565
|
+
params
|
|
770
566
|
};
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
if (Object.keys(securitySchemes).length > 0) {
|
|
776
|
-
components.securitySchemes = securitySchemes;
|
|
777
|
-
}
|
|
778
|
-
if (Object.keys(components).length > 0) {
|
|
779
|
-
document.components = components;
|
|
567
|
+
}
|
|
568
|
+
async function scanRouteFolders(routesDir) {
|
|
569
|
+
if (!(0, import_node_fs2.existsSync)(routesDir)) {
|
|
570
|
+
return [];
|
|
780
571
|
}
|
|
781
|
-
|
|
572
|
+
const folders = [routesDir];
|
|
573
|
+
const walk = async (dir) => {
|
|
574
|
+
for (const entry of await (0, import_promises.readdir)(dir, { withFileTypes: true })) {
|
|
575
|
+
if (entry.isDirectory() && entry.name !== "node_modules") {
|
|
576
|
+
const full = (0, import_node_path2.join)(dir, entry.name);
|
|
577
|
+
folders.push(full);
|
|
578
|
+
await walk(full);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
await walk(routesDir);
|
|
583
|
+
return folders;
|
|
782
584
|
}
|
|
783
|
-
|
|
784
|
-
|
|
585
|
+
function routeParamsForDir(routesDir, dir) {
|
|
586
|
+
return pathFromSegments(physicalRouteSegments(routesDir, dir)).params;
|
|
785
587
|
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
const unique = /* @__PURE__ */ new Map();
|
|
794
|
-
for (const param of params) {
|
|
795
|
-
unique.set(param.name, param);
|
|
588
|
+
function sharedFilesForDir(routesDir, dir) {
|
|
589
|
+
const segments = physicalRouteSegments(routesDir, dir);
|
|
590
|
+
const dirs = [routesDir];
|
|
591
|
+
let current = routesDir;
|
|
592
|
+
for (const segment of segments) {
|
|
593
|
+
current = (0, import_node_path2.join)(current, segment);
|
|
594
|
+
dirs.push(current);
|
|
796
595
|
}
|
|
797
|
-
|
|
798
|
-
return `{
|
|
799
|
-
${fields}
|
|
800
|
-
}`;
|
|
596
|
+
return dirs.map(sharedFileIn).filter((file) => Boolean(file));
|
|
801
597
|
}
|
|
802
|
-
function
|
|
803
|
-
if (
|
|
804
|
-
return
|
|
598
|
+
async function scanRoutes(routesDir) {
|
|
599
|
+
if (!(0, import_node_fs2.existsSync)(routesDir)) {
|
|
600
|
+
return [];
|
|
805
601
|
}
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
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}>;`;
|
|
602
|
+
const files = await (0, import_tinyglobby.glob)("**/+*.{ts,tsx,js,jsx,mjs,cjs,mts,cts}", {
|
|
603
|
+
cwd: routesDir,
|
|
604
|
+
absolute: true,
|
|
605
|
+
onlyFiles: true
|
|
817
606
|
});
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
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));
|
|
607
|
+
const routes = [];
|
|
608
|
+
for (const file of files) {
|
|
609
|
+
const method = methodFromFile((0, import_node_path2.basename)(file));
|
|
610
|
+
if (!method) {
|
|
611
|
+
continue;
|
|
835
612
|
}
|
|
836
|
-
|
|
837
|
-
|
|
613
|
+
const routeDir = (0, import_node_path2.dirname)(file);
|
|
614
|
+
const routeSegments = physicalRouteSegments(routesDir, routeDir);
|
|
615
|
+
const { path, params } = pathFromSegments(routeSegments);
|
|
616
|
+
routes.push({
|
|
617
|
+
method,
|
|
618
|
+
path,
|
|
619
|
+
file,
|
|
620
|
+
routeDir,
|
|
621
|
+
routeSegments,
|
|
622
|
+
params,
|
|
623
|
+
sharedFiles: sharedFilesForDir(routesDir, routeDir)
|
|
624
|
+
});
|
|
838
625
|
}
|
|
626
|
+
return routes.sort((left, right) => {
|
|
627
|
+
const pathOrder = left.path.localeCompare(right.path);
|
|
628
|
+
if (pathOrder !== 0) {
|
|
629
|
+
return pathOrder;
|
|
630
|
+
}
|
|
631
|
+
return METHOD_ORDER.indexOf(left.method) - METHOD_ORDER.indexOf(right.method);
|
|
632
|
+
});
|
|
839
633
|
}
|
|
840
634
|
|
|
841
|
-
// src/
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
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;
|
|
635
|
+
// src/types.ts
|
|
636
|
+
var inputSchemaBrand = /* @__PURE__ */ Symbol.for("giri.input-schema");
|
|
637
|
+
var bodySchemaBrand = /* @__PURE__ */ Symbol.for("giri.body-schema");
|
|
638
|
+
|
|
639
|
+
// src/validation.ts
|
|
640
|
+
function isGiriInputSchema(value) {
|
|
641
|
+
return Boolean(
|
|
642
|
+
value && typeof value === "object" && value[inputSchemaBrand] === true
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
function isGiriBodySchema(value) {
|
|
646
|
+
return Boolean(
|
|
647
|
+
value && typeof value === "object" && value[bodySchemaBrand] === true
|
|
648
|
+
);
|
|
865
649
|
}
|
|
866
650
|
|
|
867
|
-
// src/
|
|
868
|
-
function
|
|
651
|
+
// src/app.ts
|
|
652
|
+
function loadModule(file) {
|
|
869
653
|
const resolved = require.resolve(file);
|
|
870
654
|
delete require.cache[resolved];
|
|
871
655
|
return require(resolved);
|
|
872
656
|
}
|
|
873
|
-
function
|
|
657
|
+
function interopDefault(value) {
|
|
874
658
|
if (value && typeof value === "object" && "default" in value) {
|
|
875
659
|
return value.default;
|
|
876
660
|
}
|
|
877
661
|
return value;
|
|
878
662
|
}
|
|
879
|
-
function
|
|
880
|
-
const exported =
|
|
663
|
+
function normalizeMiddleware(value, file) {
|
|
664
|
+
const exported = interopDefault(value);
|
|
665
|
+
if (exported === void 0) {
|
|
666
|
+
return [];
|
|
667
|
+
}
|
|
881
668
|
if (typeof exported === "function") {
|
|
882
669
|
return [exported];
|
|
883
670
|
}
|
|
884
671
|
if (Array.isArray(exported)) {
|
|
885
|
-
|
|
672
|
+
for (const middleware of exported) {
|
|
673
|
+
if (typeof middleware !== "function") {
|
|
674
|
+
throw new Error(`Middleware export in ${file} must contain only functions.`);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
return exported;
|
|
886
678
|
}
|
|
887
|
-
|
|
679
|
+
throw new Error(`Middleware export in ${file} must be a function or an array of functions.`);
|
|
888
680
|
}
|
|
889
|
-
function
|
|
681
|
+
function assertBodySchema(value, file) {
|
|
682
|
+
if (!isGiriBodySchema(value)) {
|
|
683
|
+
throw new Error(
|
|
684
|
+
`${file}: "body" must be wrapped with a validator, e.g. \`export const body = zod.body({ json: ... })\` from @boon4681/giri/validators/zod.`
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
function assertQuerySchema(value, file) {
|
|
689
|
+
if (!isGiriInputSchema(value)) {
|
|
690
|
+
throw new Error(
|
|
691
|
+
`${file}: "query" must be wrapped with a validator, e.g. \`export const query = zod.query(...)\` from @boon4681/giri/validators/zod.`
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
function routeInput(routeModule, file) {
|
|
890
696
|
const input = {};
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
input.body = body;
|
|
697
|
+
if (routeModule.body !== void 0) {
|
|
698
|
+
assertBodySchema(routeModule.body, file);
|
|
699
|
+
input.body = routeModule.body;
|
|
895
700
|
}
|
|
896
|
-
if (query) {
|
|
897
|
-
|
|
701
|
+
if (routeModule.query !== void 0) {
|
|
702
|
+
assertQuerySchema(routeModule.query, file);
|
|
703
|
+
input.query = routeModule.query;
|
|
898
704
|
}
|
|
899
705
|
return input.body || input.query ? input : void 0;
|
|
900
706
|
}
|
|
901
|
-
function
|
|
902
|
-
|
|
903
|
-
|
|
707
|
+
function aliasValues(value) {
|
|
708
|
+
return Array.isArray(value) ? value : [value];
|
|
709
|
+
}
|
|
710
|
+
function resolveAliasTarget(cwd, target, capture = "") {
|
|
711
|
+
const replaced = target.includes("*") ? target.replaceAll("*", capture) : target;
|
|
712
|
+
return (0, import_node_path3.isAbsolute)(replaced) ? replaced : (0, import_node_path3.resolve)(cwd, replaced);
|
|
713
|
+
}
|
|
714
|
+
function matchAlias(request, key) {
|
|
715
|
+
if (key.includes("*")) {
|
|
716
|
+
const [prefix2, suffix = ""] = key.split("*");
|
|
717
|
+
if (request.startsWith(prefix2) && request.endsWith(suffix)) {
|
|
718
|
+
return request.slice(prefix2.length, request.length - suffix.length);
|
|
719
|
+
}
|
|
720
|
+
return void 0;
|
|
904
721
|
}
|
|
905
|
-
if (
|
|
906
|
-
return
|
|
722
|
+
if (request === key) {
|
|
723
|
+
return "";
|
|
907
724
|
}
|
|
908
|
-
|
|
909
|
-
|
|
725
|
+
const prefix = `${key}/`;
|
|
726
|
+
if (request.startsWith(prefix)) {
|
|
727
|
+
return request.slice(prefix.length);
|
|
910
728
|
}
|
|
911
729
|
return void 0;
|
|
912
730
|
}
|
|
913
|
-
function
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
731
|
+
function resolveAliasRequest(request, alias, cwd) {
|
|
732
|
+
for (const [key, value] of Object.entries(alias ?? {})) {
|
|
733
|
+
const capture = matchAlias(request, key);
|
|
734
|
+
if (capture === void 0) {
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
const [target] = aliasValues(value);
|
|
738
|
+
if (!target) {
|
|
739
|
+
continue;
|
|
740
|
+
}
|
|
741
|
+
return resolveAliasTarget(cwd, target, capture);
|
|
742
|
+
}
|
|
743
|
+
return void 0;
|
|
744
|
+
}
|
|
745
|
+
function registerAliasResolver(alias, cwd) {
|
|
746
|
+
if (!alias || Object.keys(alias).length === 0) {
|
|
747
|
+
return () => {
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
const moduleWithResolver = import_node_module.default;
|
|
751
|
+
const originalResolveFilename = moduleWithResolver._resolveFilename;
|
|
752
|
+
moduleWithResolver._resolveFilename = function resolveWithGiriAlias(request, parent, isMain, options) {
|
|
753
|
+
return originalResolveFilename.call(
|
|
754
|
+
this,
|
|
755
|
+
resolveAliasRequest(request, alias, cwd) ?? request,
|
|
756
|
+
parent,
|
|
757
|
+
isMain,
|
|
758
|
+
options
|
|
759
|
+
);
|
|
760
|
+
};
|
|
761
|
+
return () => {
|
|
762
|
+
moduleWithResolver._resolveFilename = originalResolveFilename;
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
var GIRI_ALIAS_PREFIX = "$giri/";
|
|
766
|
+
var giriOutDir;
|
|
767
|
+
var giriResolverInstalled = false;
|
|
768
|
+
function ensureGiriAliasResolver(outDir) {
|
|
769
|
+
giriOutDir = outDir;
|
|
770
|
+
if (giriResolverInstalled) {
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
giriResolverInstalled = true;
|
|
774
|
+
const moduleWithResolver = import_node_module.default;
|
|
775
|
+
const originalResolveFilename = moduleWithResolver._resolveFilename;
|
|
776
|
+
moduleWithResolver._resolveFilename = function resolveWithGiriInternalAlias(request, parent, isMain, options) {
|
|
777
|
+
const mapped = typeof request === "string" && request.startsWith(GIRI_ALIAS_PREFIX) && giriOutDir ? (0, import_node_path3.join)(giriOutDir, request.slice(GIRI_ALIAS_PREFIX.length)) : request;
|
|
778
|
+
return originalResolveFilename.call(this, mapped, parent, isMain, options);
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
function resolveGiriPaths(config, cwd = process.cwd()) {
|
|
782
|
+
return {
|
|
783
|
+
cwd: (0, import_node_path3.resolve)(cwd),
|
|
784
|
+
routesDir: (0, import_node_path3.resolve)(cwd, "src/routes"),
|
|
785
|
+
outDir: (0, import_node_path3.resolve)(cwd, config.outDir ?? ".giri")
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
async function buildGiriApp(config, options = {}) {
|
|
789
|
+
const paths = resolveGiriPaths(config, options.cwd);
|
|
790
|
+
const routes = await scanRoutes(paths.routesDir);
|
|
791
|
+
const app = config.adapter.createApp();
|
|
792
|
+
ensureGiriAliasResolver(paths.outDir);
|
|
793
|
+
const { unregister } = await safeRegister();
|
|
794
|
+
const unregisterAliasResolver = registerAliasResolver(config.alias, paths.cwd);
|
|
795
|
+
try {
|
|
796
|
+
for (const route of routes) {
|
|
797
|
+
const routeModule = loadModule(route.file);
|
|
798
|
+
if (typeof routeModule.handle !== "function") {
|
|
799
|
+
throw new Error(`${route.file} must export a named handle function.`);
|
|
800
|
+
}
|
|
801
|
+
const folderMiddleware = routeModule.config?.skipInherited ? [] : route.sharedFiles.flatMap(
|
|
802
|
+
(file) => normalizeMiddleware(loadModule(file).middleware, file)
|
|
803
|
+
);
|
|
804
|
+
const verbMiddleware = normalizeMiddleware(routeModule.middleware, route.file);
|
|
805
|
+
config.adapter.register(app, {
|
|
806
|
+
method: route.method,
|
|
807
|
+
path: route.path,
|
|
808
|
+
handle: routeModule.handle,
|
|
809
|
+
middleware: [...folderMiddleware, ...verbMiddleware],
|
|
810
|
+
input: routeInput(routeModule, route.file),
|
|
811
|
+
services: options.services
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
} finally {
|
|
815
|
+
unregisterAliasResolver();
|
|
816
|
+
unregister();
|
|
817
|
+
}
|
|
818
|
+
return { app, routes, paths };
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// src/generator/sync.ts
|
|
822
|
+
var import_node_fs6 = require("fs");
|
|
823
|
+
var import_promises3 = require("fs/promises");
|
|
824
|
+
var import_node_path11 = require("path");
|
|
825
|
+
|
|
826
|
+
// src/generator/app-types.ts
|
|
827
|
+
var import_node_fs4 = require("fs");
|
|
828
|
+
var import_node_path5 = require("path");
|
|
829
|
+
|
|
830
|
+
// src/generator/util.ts
|
|
831
|
+
var import_node_fs3 = require("fs");
|
|
832
|
+
var import_promises2 = require("fs/promises");
|
|
833
|
+
var import_node_path4 = require("path");
|
|
834
|
+
var GENERATED_HEADER = "// Generated by giri sync. Do not edit.";
|
|
835
|
+
function slash(path) {
|
|
836
|
+
return path.split(import_node_path4.sep).join("/");
|
|
837
|
+
}
|
|
838
|
+
function importPath(fromFile, toFile) {
|
|
839
|
+
let path = slash((0, import_node_path4.relative)((0, import_node_path4.dirname)(fromFile), toFile)).replace(/\.d\.ts$/, "");
|
|
840
|
+
if (!path.startsWith(".")) {
|
|
841
|
+
path = `./${path}`;
|
|
842
|
+
}
|
|
843
|
+
return path;
|
|
844
|
+
}
|
|
845
|
+
function relativeConfigPath(fromDir, toPath) {
|
|
846
|
+
let path = slash((0, import_node_path4.relative)(fromDir, toPath));
|
|
847
|
+
if (!path.startsWith(".")) {
|
|
848
|
+
path = `./${path}`;
|
|
849
|
+
}
|
|
850
|
+
return path;
|
|
851
|
+
}
|
|
852
|
+
function moduleSpecifier(fromDir, target) {
|
|
853
|
+
let path = slash((0, import_node_path4.relative)(fromDir, target)).replace(/\.(?:[cm]?[jt]sx?)$/, "");
|
|
854
|
+
if (!path.startsWith(".")) {
|
|
855
|
+
path = `./${path}`;
|
|
856
|
+
}
|
|
857
|
+
return path;
|
|
858
|
+
}
|
|
859
|
+
function typeFilePath(paths, routeDir) {
|
|
860
|
+
const sourceDir = (0, import_node_path4.relative)(paths.cwd, routeDir);
|
|
861
|
+
return (0, import_node_path4.join)(paths.outDir, "types", sourceDir, "$types.d.ts");
|
|
862
|
+
}
|
|
863
|
+
function assertSafeOutDir(paths) {
|
|
864
|
+
const rel = (0, import_node_path4.relative)(paths.cwd, paths.outDir);
|
|
865
|
+
if (!rel || rel.startsWith("..") || rel.includes(`..${import_node_path4.sep}`)) {
|
|
866
|
+
throw new Error(`Refusing to sync outside the project root: ${paths.outDir}`);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
var writeCache = /* @__PURE__ */ new Map();
|
|
870
|
+
async function writeGenerated(path, content) {
|
|
871
|
+
if (writeCache.get(path) === content && (0, import_node_fs3.existsSync)(path)) {
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
await (0, import_promises2.mkdir)((0, import_node_path4.dirname)(path), { recursive: true });
|
|
875
|
+
await (0, import_promises2.writeFile)(path, content);
|
|
876
|
+
writeCache.set(path, content);
|
|
877
|
+
}
|
|
878
|
+
async function writeJson(path, value) {
|
|
879
|
+
await writeGenerated(path, `${JSON.stringify(value, null, 2)}
|
|
880
|
+
`);
|
|
881
|
+
}
|
|
882
|
+
async function pruneDir(dir, keep) {
|
|
883
|
+
let entries;
|
|
884
|
+
try {
|
|
885
|
+
entries = await (0, import_promises2.readdir)(dir, { withFileTypes: true });
|
|
886
|
+
} catch {
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
for (const entry of entries) {
|
|
890
|
+
const full = (0, import_node_path4.join)(dir, entry.name);
|
|
891
|
+
if (entry.isDirectory()) {
|
|
892
|
+
await pruneDir(full, keep);
|
|
893
|
+
await (0, import_promises2.rmdir)(full).catch(() => {
|
|
894
|
+
});
|
|
895
|
+
} else if (!keep.has(full)) {
|
|
896
|
+
await (0, import_promises2.rm)(full, { force: true });
|
|
897
|
+
writeCache.delete(full);
|
|
919
898
|
}
|
|
920
899
|
}
|
|
921
|
-
const verb = hiddenFrom(routeModule.openapi);
|
|
922
|
-
return verb ?? hidden;
|
|
923
900
|
}
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
const
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
901
|
+
|
|
902
|
+
// src/generator/app-types.ts
|
|
903
|
+
var MAIN_EXTENSIONS = ["ts", "tsx", "mts", "cts", "js", "jsx", "mjs", "cjs"];
|
|
904
|
+
function findMainFile(cwd) {
|
|
905
|
+
for (const ext of MAIN_EXTENSIONS) {
|
|
906
|
+
const file = (0, import_node_path5.join)(cwd, "src", `main.${ext}`);
|
|
907
|
+
if ((0, import_node_fs4.existsSync)(file)) {
|
|
908
|
+
return file;
|
|
932
909
|
}
|
|
933
910
|
}
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
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
|
-
}
|
|
911
|
+
return void 0;
|
|
912
|
+
}
|
|
913
|
+
function moduleSpecifier2(fromDir, target) {
|
|
914
|
+
let path = slash((0, import_node_path5.relative)(fromDir, target)).replace(/\.(?:[cm]?[jt]sx?)$/, "");
|
|
915
|
+
if (!path.startsWith(".")) {
|
|
916
|
+
path = `./${path}`;
|
|
949
917
|
}
|
|
950
|
-
return
|
|
918
|
+
return path;
|
|
951
919
|
}
|
|
952
|
-
async function
|
|
953
|
-
const
|
|
954
|
-
const
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
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();
|
|
920
|
+
async function writeAppTypes(paths) {
|
|
921
|
+
const file = (0, import_node_path5.join)(paths.outDir, "types", "app.d.ts");
|
|
922
|
+
const mainFile = findMainFile(paths.cwd);
|
|
923
|
+
if (!mainFile) {
|
|
924
|
+
await writeGenerated(file, [GENERATED_HEADER, "export {};", ""].join("\n"));
|
|
925
|
+
return;
|
|
993
926
|
}
|
|
994
|
-
|
|
927
|
+
const spec = moduleSpecifier2((0, import_node_path5.join)(paths.outDir, "types"), mainFile);
|
|
928
|
+
await writeGenerated(
|
|
929
|
+
file,
|
|
930
|
+
[
|
|
931
|
+
GENERATED_HEADER,
|
|
932
|
+
"declare global {",
|
|
933
|
+
" namespace Giri {",
|
|
934
|
+
" interface Register {",
|
|
935
|
+
` app: typeof import(${JSON.stringify(spec)}) extends {`,
|
|
936
|
+
" init: (...args: any[]) => infer R;",
|
|
937
|
+
" }",
|
|
938
|
+
" ? Awaited<R>",
|
|
939
|
+
" : Record<string, unknown>;",
|
|
940
|
+
" }",
|
|
941
|
+
" }",
|
|
942
|
+
"}",
|
|
943
|
+
"export {};",
|
|
944
|
+
""
|
|
945
|
+
].join("\n")
|
|
946
|
+
);
|
|
995
947
|
}
|
|
996
948
|
|
|
997
|
-
// src/generator/
|
|
998
|
-
var
|
|
999
|
-
async function
|
|
1000
|
-
const
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
949
|
+
// src/generator/manifest.ts
|
|
950
|
+
var import_node_path6 = require("path");
|
|
951
|
+
async function writeManifest(paths, routes, data = {}) {
|
|
952
|
+
const manifest = {
|
|
953
|
+
version: 1,
|
|
954
|
+
routes: routes.map((route) => {
|
|
955
|
+
const responses = data.responsesByFile?.get(route.file);
|
|
956
|
+
const input = data.inputsByFile?.get(route.file);
|
|
957
|
+
const security = data.securityByFile?.get(route.file);
|
|
958
|
+
return {
|
|
959
|
+
method: route.method,
|
|
960
|
+
path: route.path,
|
|
961
|
+
file: slash((0, import_node_path6.relative)(paths.cwd, route.file)),
|
|
962
|
+
params: route.params,
|
|
963
|
+
shared: route.sharedFiles.map((file) => slash((0, import_node_path6.relative)(paths.cwd, file))),
|
|
964
|
+
types: slash((0, import_node_path6.relative)(paths.cwd, typeFilePath(paths, route.routeDir))),
|
|
965
|
+
...data.hiddenFiles?.has(route.file) ? { hidden: true } : {},
|
|
966
|
+
...input ? { input } : {},
|
|
967
|
+
...security && security.security.length > 0 ? { security: security.security } : {},
|
|
968
|
+
responses: responses?.responses ?? [],
|
|
969
|
+
...responses && Object.keys(responses.$defs).length > 0 ? { $defs: responses.$defs } : {}
|
|
970
|
+
};
|
|
971
|
+
})
|
|
972
|
+
};
|
|
973
|
+
await writeJson((0, import_node_path6.join)(paths.outDir, "manifest.json"), manifest);
|
|
1015
974
|
}
|
|
1016
975
|
|
|
1017
|
-
// src/generator/
|
|
1018
|
-
var
|
|
1019
|
-
var
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
976
|
+
// src/generator/openapi.ts
|
|
977
|
+
var import_node_fs5 = require("fs");
|
|
978
|
+
var import_node_path7 = require("path");
|
|
979
|
+
var REASON = {
|
|
980
|
+
200: "OK",
|
|
981
|
+
201: "Created",
|
|
982
|
+
202: "Accepted",
|
|
983
|
+
204: "No Content",
|
|
984
|
+
400: "Bad Request",
|
|
985
|
+
401: "Unauthorized",
|
|
986
|
+
403: "Forbidden",
|
|
987
|
+
404: "Not Found",
|
|
988
|
+
409: "Conflict",
|
|
989
|
+
422: "Unprocessable Entity",
|
|
990
|
+
500: "Internal Server Error"
|
|
1026
991
|
};
|
|
1027
|
-
function
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
992
|
+
function toOpenApiPath(path) {
|
|
993
|
+
return path.replace(/:([A-Za-z0-9_]+)(?:\{[^}]*\})?/g, "{$1}");
|
|
994
|
+
}
|
|
995
|
+
function rewriteRefs(value) {
|
|
996
|
+
if (Array.isArray(value)) {
|
|
997
|
+
return value.map(rewriteRefs);
|
|
998
|
+
}
|
|
999
|
+
if (value && typeof value === "object") {
|
|
1000
|
+
const out = {};
|
|
1001
|
+
for (const [key, child] of Object.entries(value)) {
|
|
1002
|
+
if (key === "$ref" && typeof child === "string" && child.startsWith("#/$defs/")) {
|
|
1003
|
+
out.$ref = child.replace("#/$defs/", "#/components/schemas/");
|
|
1004
|
+
} else {
|
|
1005
|
+
out[key] = rewriteRefs(child);
|
|
1034
1006
|
}
|
|
1035
|
-
});
|
|
1036
|
-
if (parsed) {
|
|
1037
|
-
options = { ...parsed.options, noEmit: true };
|
|
1038
1007
|
}
|
|
1008
|
+
return out;
|
|
1039
1009
|
}
|
|
1040
|
-
return
|
|
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;
|
|
1010
|
+
return value;
|
|
1060
1011
|
}
|
|
1061
|
-
function
|
|
1062
|
-
return
|
|
1012
|
+
function mediaTypeFor(format) {
|
|
1013
|
+
return format === "text" ? "text/plain" : "application/json";
|
|
1063
1014
|
}
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1015
|
+
var BODY_MEDIA_TYPE = {
|
|
1016
|
+
json: "application/json",
|
|
1017
|
+
form: "multipart/form-data",
|
|
1018
|
+
urlencoded: "application/x-www-form-urlencoded",
|
|
1019
|
+
text: "text/plain"
|
|
1020
|
+
};
|
|
1021
|
+
function buildResponses(responses) {
|
|
1022
|
+
if (responses.length === 0) {
|
|
1023
|
+
return { default: { description: "Response" } };
|
|
1024
|
+
}
|
|
1025
|
+
const out = {};
|
|
1026
|
+
for (const response of responses) {
|
|
1027
|
+
const key = response.status === "default" ? "default" : String(response.status);
|
|
1028
|
+
const description = typeof response.status === "number" && REASON[response.status] || "Response";
|
|
1029
|
+
out[key] = {
|
|
1030
|
+
description,
|
|
1031
|
+
content: { [mediaTypeFor(response.format)]: { schema: rewriteRefs(response.schema) } }
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
return out;
|
|
1067
1035
|
}
|
|
1068
|
-
function
|
|
1069
|
-
const
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
values.push(intrinsicName(member) === "true");
|
|
1075
|
-
} else {
|
|
1076
|
-
return void 0;
|
|
1036
|
+
function pathParameters(route) {
|
|
1037
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1038
|
+
const params = [];
|
|
1039
|
+
for (const param of route.params) {
|
|
1040
|
+
if (seen.has(param.name)) {
|
|
1041
|
+
continue;
|
|
1077
1042
|
}
|
|
1043
|
+
seen.add(param.name);
|
|
1044
|
+
params.push({ name: param.name, in: "path", required: true, schema: { type: "string" } });
|
|
1078
1045
|
}
|
|
1079
|
-
return
|
|
1046
|
+
return params;
|
|
1080
1047
|
}
|
|
1081
|
-
function
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
if (members.length === 1) {
|
|
1085
|
-
return walkType(members[0], ctx);
|
|
1048
|
+
function queryParameters(query) {
|
|
1049
|
+
if (!query || query.type !== "object" || typeof query.properties !== "object") {
|
|
1050
|
+
return [];
|
|
1086
1051
|
}
|
|
1087
|
-
const
|
|
1088
|
-
|
|
1089
|
-
|
|
1052
|
+
const properties = query.properties;
|
|
1053
|
+
const required = Array.isArray(query.required) ? query.required : [];
|
|
1054
|
+
return Object.entries(properties).map(([name, schema]) => ({
|
|
1055
|
+
name,
|
|
1056
|
+
in: "query",
|
|
1057
|
+
required: required.includes(name),
|
|
1058
|
+
schema: rewriteRefs(schema)
|
|
1059
|
+
}));
|
|
1060
|
+
}
|
|
1061
|
+
function readProjectInfo(cwd) {
|
|
1062
|
+
const file = (0, import_node_path7.join)(cwd, "package.json");
|
|
1063
|
+
if ((0, import_node_fs5.existsSync)(file)) {
|
|
1064
|
+
try {
|
|
1065
|
+
const pkg = JSON.parse((0, import_node_fs5.readFileSync)(file, "utf8"));
|
|
1066
|
+
return { title: pkg.name ?? "giri API", version: pkg.version ?? "0.0.0" };
|
|
1067
|
+
} catch {
|
|
1068
|
+
}
|
|
1090
1069
|
}
|
|
1091
|
-
return {
|
|
1070
|
+
return { title: "giri API", version: "0.0.0" };
|
|
1092
1071
|
}
|
|
1093
|
-
function
|
|
1094
|
-
const
|
|
1095
|
-
const
|
|
1096
|
-
const
|
|
1097
|
-
const
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
const
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1072
|
+
function buildOpenApiDocument(paths, routes, data = {}) {
|
|
1073
|
+
const documentPaths = {};
|
|
1074
|
+
const schemas = {};
|
|
1075
|
+
const securitySchemes = {};
|
|
1076
|
+
for (const route of routes) {
|
|
1077
|
+
if (data.hiddenFiles?.has(route.file)) {
|
|
1078
|
+
continue;
|
|
1079
|
+
}
|
|
1080
|
+
const responses = data.responsesByFile?.get(route.file);
|
|
1081
|
+
const input = data.inputsByFile?.get(route.file);
|
|
1082
|
+
const security = data.securityByFile?.get(route.file);
|
|
1083
|
+
for (const [name, schema] of Object.entries(responses?.$defs ?? {})) {
|
|
1084
|
+
schemas[name] = rewriteRefs(schema);
|
|
1085
|
+
}
|
|
1086
|
+
const operation = { responses: buildResponses(responses?.responses ?? []) };
|
|
1087
|
+
const parameters = [...pathParameters(route), ...queryParameters(input?.query)];
|
|
1088
|
+
if (parameters.length > 0) {
|
|
1089
|
+
operation.parameters = parameters;
|
|
1090
|
+
}
|
|
1091
|
+
if (input?.body) {
|
|
1092
|
+
const content = {};
|
|
1093
|
+
for (const [contentType, schema] of Object.entries(input.body)) {
|
|
1094
|
+
content[BODY_MEDIA_TYPE[contentType] ?? contentType] = {
|
|
1095
|
+
schema: rewriteRefs(schema)
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
if (Object.keys(content).length > 0) {
|
|
1099
|
+
operation.requestBody = { required: true, content };
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
if (security && security.security.length > 0) {
|
|
1103
|
+
operation.security = security.security;
|
|
1105
1104
|
}
|
|
1105
|
+
if (security) {
|
|
1106
|
+
Object.assign(securitySchemes, security.securitySchemes);
|
|
1107
|
+
}
|
|
1108
|
+
const openApiPath = toOpenApiPath(route.path);
|
|
1109
|
+
const pathItem = documentPaths[openApiPath] ?? {};
|
|
1110
|
+
pathItem[route.method.toLowerCase()] = operation;
|
|
1111
|
+
documentPaths[openApiPath] = pathItem;
|
|
1106
1112
|
}
|
|
1107
|
-
const
|
|
1108
|
-
|
|
1109
|
-
|
|
1113
|
+
const document = {
|
|
1114
|
+
openapi: "3.1.0",
|
|
1115
|
+
info: readProjectInfo(paths.cwd),
|
|
1116
|
+
paths: documentPaths
|
|
1117
|
+
};
|
|
1118
|
+
const components = {};
|
|
1119
|
+
if (Object.keys(schemas).length > 0) {
|
|
1120
|
+
components.schemas = schemas;
|
|
1110
1121
|
}
|
|
1111
|
-
if (
|
|
1112
|
-
|
|
1122
|
+
if (Object.keys(securitySchemes).length > 0) {
|
|
1123
|
+
components.securitySchemes = securitySchemes;
|
|
1113
1124
|
}
|
|
1114
|
-
if (
|
|
1115
|
-
|
|
1116
|
-
} else if (Object.keys(properties).length > 0) {
|
|
1117
|
-
schema.additionalProperties = false;
|
|
1125
|
+
if (Object.keys(components).length > 0) {
|
|
1126
|
+
document.components = components;
|
|
1118
1127
|
}
|
|
1119
|
-
return
|
|
1128
|
+
return document;
|
|
1120
1129
|
}
|
|
1121
|
-
function
|
|
1122
|
-
|
|
1123
|
-
const name = symbol?.getName();
|
|
1124
|
-
if (name && name !== "__type" && name !== "__object") {
|
|
1125
|
-
return name;
|
|
1126
|
-
}
|
|
1127
|
-
return `Anonymous${typeId(type)}`;
|
|
1130
|
+
async function writeOpenApi(paths, routes, data = {}) {
|
|
1131
|
+
await writeJson((0, import_node_path7.join)(paths.outDir, "openapi.json"), buildOpenApiDocument(paths, routes, data));
|
|
1128
1132
|
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
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}` };
|
|
1133
|
+
|
|
1134
|
+
// src/generator/param-types.ts
|
|
1135
|
+
var import_node_path8 = require("path");
|
|
1136
|
+
function paramsType(params) {
|
|
1137
|
+
if (params.length === 0) {
|
|
1138
|
+
return "{}";
|
|
1147
1139
|
}
|
|
1148
|
-
const
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
ctx.inProgress.delete(id);
|
|
1152
|
-
if (ctx.usedDefs.has(name)) {
|
|
1153
|
-
ctx.defs[name] = schema;
|
|
1154
|
-
return { $ref: `#/$defs/${name}` };
|
|
1140
|
+
const unique = /* @__PURE__ */ new Map();
|
|
1141
|
+
for (const param of params) {
|
|
1142
|
+
unique.set(param.name, param);
|
|
1155
1143
|
}
|
|
1156
|
-
|
|
1144
|
+
const fields = [...unique.values()].map((param) => ` ${JSON.stringify(param.name)}: string;`).join("\n");
|
|
1145
|
+
return `{
|
|
1146
|
+
${fields}
|
|
1147
|
+
}`;
|
|
1157
1148
|
}
|
|
1158
|
-
function
|
|
1159
|
-
|
|
1160
|
-
|
|
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);
|
|
1149
|
+
function varsType(typesDir, sharedFiles) {
|
|
1150
|
+
if (sharedFiles.length === 0) {
|
|
1151
|
+
return "{}";
|
|
1196
1152
|
}
|
|
1197
|
-
return {
|
|
1153
|
+
return sharedFiles.map((file) => {
|
|
1154
|
+
const spec = JSON.stringify(moduleSpecifier(typesDir, file));
|
|
1155
|
+
return `(typeof import(${spec}) extends { middleware: infer M } ? import("@boon4681/giri").InferStackVars<M> : {})`;
|
|
1156
|
+
}).join("\n & ");
|
|
1198
1157
|
}
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
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;
|
|
1158
|
+
function methodExports(typesDir, verbs) {
|
|
1159
|
+
return verbs.map(({ method, file }) => {
|
|
1160
|
+
const spec = JSON.stringify(moduleSpecifier(typesDir, file));
|
|
1161
|
+
const input = `import("@boon4681/giri").RouteInputOf<typeof import(${spec})>`;
|
|
1162
|
+
const vars = `Vars & import("@boon4681/giri").MiddlewareVarsOf<typeof import(${spec})>`;
|
|
1163
|
+
return `export type ${method} = import("@boon4681/giri").Handle<Params, ${input}, ${vars}>;`;
|
|
1164
|
+
});
|
|
1217
1165
|
}
|
|
1218
|
-
function
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1166
|
+
async function writeParamTypes(paths, folders) {
|
|
1167
|
+
for (const { dir, params, sharedFiles, verbs } of folders) {
|
|
1168
|
+
const file = typeFilePath(paths, dir);
|
|
1169
|
+
const typesDir = (0, import_node_path8.dirname)(file);
|
|
1170
|
+
const lines = [
|
|
1171
|
+
GENERATED_HEADER,
|
|
1172
|
+
`export type Params = ${paramsType(params)};`,
|
|
1173
|
+
"export type RouteParams = Params;",
|
|
1174
|
+
`type Vars = ${varsType(typesDir, sharedFiles)};`,
|
|
1175
|
+
"export type Middleware<Injects extends Record<string, unknown> = {}> =",
|
|
1176
|
+
' import("@boon4681/giri").Middleware<Params, import("@boon4681/giri").ValidatedInput, Injects>;',
|
|
1177
|
+
'export type Handle<Input extends import("@boon4681/giri").ValidatedInput = import("@boon4681/giri").ValidatedInput> =',
|
|
1178
|
+
' import("@boon4681/giri").Handle<Params, Input, Vars>;'
|
|
1179
|
+
];
|
|
1180
|
+
if (verbs.length > 0) {
|
|
1181
|
+
lines.push(...methodExports(typesDir, verbs));
|
|
1232
1182
|
}
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
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;
|
|
1183
|
+
lines.push("");
|
|
1184
|
+
await writeGenerated(file, lines.join("\n"));
|
|
1185
|
+
}
|
|
1241
1186
|
}
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1187
|
+
|
|
1188
|
+
// src/generator/inputs.ts
|
|
1189
|
+
function sanitize(schema) {
|
|
1190
|
+
const { $schema, ...rest } = schema;
|
|
1191
|
+
void $schema;
|
|
1192
|
+
return rest;
|
|
1246
1193
|
}
|
|
1247
|
-
function
|
|
1248
|
-
if (!
|
|
1194
|
+
function inputToJsonSchema(schema) {
|
|
1195
|
+
if (!isGiriInputSchema(schema)) {
|
|
1249
1196
|
return void 0;
|
|
1250
1197
|
}
|
|
1251
|
-
|
|
1252
|
-
|
|
1198
|
+
return sanitize(schema.toJsonSchema());
|
|
1199
|
+
}
|
|
1200
|
+
function bodyToJsonSchemas(value) {
|
|
1201
|
+
if (!isGiriBodySchema(value)) {
|
|
1253
1202
|
return void 0;
|
|
1254
1203
|
}
|
|
1255
|
-
|
|
1256
|
-
|
|
1204
|
+
const out = {};
|
|
1205
|
+
for (const [contentType, schema] of Object.entries(value.contents)) {
|
|
1206
|
+
const json = inputToJsonSchema(schema);
|
|
1207
|
+
if (json) {
|
|
1208
|
+
out[contentType] = json;
|
|
1209
|
+
}
|
|
1257
1210
|
}
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1211
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
// src/generator/route-meta.ts
|
|
1215
|
+
function loadModule2(file) {
|
|
1216
|
+
const resolved = require.resolve(file);
|
|
1217
|
+
delete require.cache[resolved];
|
|
1218
|
+
return require(resolved);
|
|
1219
|
+
}
|
|
1220
|
+
function interopDefault2(value) {
|
|
1221
|
+
if (value && typeof value === "object" && "default" in value) {
|
|
1222
|
+
return value.default;
|
|
1261
1223
|
}
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1224
|
+
return value;
|
|
1225
|
+
}
|
|
1226
|
+
function middlewareFunctions(value) {
|
|
1227
|
+
const exported = interopDefault2(value);
|
|
1228
|
+
if (typeof exported === "function") {
|
|
1229
|
+
return [exported];
|
|
1266
1230
|
}
|
|
1267
|
-
|
|
1231
|
+
if (Array.isArray(exported)) {
|
|
1232
|
+
return exported.filter((fn) => typeof fn === "function");
|
|
1233
|
+
}
|
|
1234
|
+
return [];
|
|
1268
1235
|
}
|
|
1269
|
-
function
|
|
1270
|
-
const
|
|
1271
|
-
const
|
|
1272
|
-
const
|
|
1273
|
-
if (
|
|
1274
|
-
|
|
1236
|
+
function readInput(routeModule) {
|
|
1237
|
+
const input = {};
|
|
1238
|
+
const body = bodyToJsonSchemas(routeModule.body);
|
|
1239
|
+
const query = inputToJsonSchema(routeModule.query);
|
|
1240
|
+
if (body) {
|
|
1241
|
+
input.body = body;
|
|
1275
1242
|
}
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1243
|
+
if (query) {
|
|
1244
|
+
input.query = query;
|
|
1245
|
+
}
|
|
1246
|
+
return input.body || input.query ? input : void 0;
|
|
1279
1247
|
}
|
|
1280
|
-
function
|
|
1281
|
-
|
|
1248
|
+
function hiddenFrom(value) {
|
|
1249
|
+
if (value === false) {
|
|
1250
|
+
return true;
|
|
1251
|
+
}
|
|
1252
|
+
if (value === true) {
|
|
1253
|
+
return false;
|
|
1254
|
+
}
|
|
1255
|
+
if (value && typeof value === "object" && "hidden" in value) {
|
|
1256
|
+
return Boolean(value.hidden);
|
|
1257
|
+
}
|
|
1258
|
+
return void 0;
|
|
1282
1259
|
}
|
|
1283
|
-
function
|
|
1284
|
-
|
|
1285
|
-
const
|
|
1286
|
-
|
|
1287
|
-
|
|
1260
|
+
function collectHidden(route, routeModule, loadShared) {
|
|
1261
|
+
let hidden = false;
|
|
1262
|
+
for (const file of route.sharedFiles) {
|
|
1263
|
+
const opinion = hiddenFrom(loadShared(file).openapi);
|
|
1264
|
+
if (opinion !== void 0) {
|
|
1265
|
+
hidden = opinion;
|
|
1266
|
+
}
|
|
1288
1267
|
}
|
|
1289
|
-
const
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1268
|
+
const verb = hiddenFrom(routeModule.openapi);
|
|
1269
|
+
return verb ?? hidden;
|
|
1270
|
+
}
|
|
1271
|
+
function collectSecurity(route, routeModule, loadShared) {
|
|
1272
|
+
const skipInherited = Boolean(
|
|
1273
|
+
routeModule.config?.skipInherited
|
|
1274
|
+
);
|
|
1275
|
+
const middleware = [];
|
|
1276
|
+
if (!skipInherited) {
|
|
1277
|
+
for (const file of route.sharedFiles) {
|
|
1278
|
+
middleware.push(...middlewareFunctions(loadShared(file).middleware));
|
|
1279
|
+
}
|
|
1293
1280
|
}
|
|
1294
|
-
|
|
1295
|
-
const
|
|
1296
|
-
const
|
|
1297
|
-
|
|
1298
|
-
const
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
record(fromCall);
|
|
1306
|
-
continue;
|
|
1281
|
+
middleware.push(...middlewareFunctions(routeModule.middleware));
|
|
1282
|
+
const security = [];
|
|
1283
|
+
const securitySchemes = {};
|
|
1284
|
+
for (const fn of middleware) {
|
|
1285
|
+
const openapi = fn.openapi;
|
|
1286
|
+
if (openapi?.security) {
|
|
1287
|
+
for (const requirement of openapi.security) {
|
|
1288
|
+
if (!security.some((seen) => JSON.stringify(seen) === JSON.stringify(requirement))) {
|
|
1289
|
+
security.push(requirement);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1307
1292
|
}
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1293
|
+
if (openapi?.securitySchemes) {
|
|
1294
|
+
Object.assign(securitySchemes, openapi.securitySchemes);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
return security.length > 0 || Object.keys(securitySchemes).length > 0 ? { security, securitySchemes } : void 0;
|
|
1298
|
+
}
|
|
1299
|
+
async function extractRouteMeta(config, paths, routes) {
|
|
1300
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
1301
|
+
const { unregister } = await safeRegister();
|
|
1302
|
+
const unregisterAlias = registerAliasResolver(config.alias, paths.cwd);
|
|
1303
|
+
const sharedCache = /* @__PURE__ */ new Map();
|
|
1304
|
+
const loadShared = (file) => {
|
|
1305
|
+
if (!sharedCache.has(file)) {
|
|
1306
|
+
try {
|
|
1307
|
+
sharedCache.set(file, loadModule2(file));
|
|
1308
|
+
} catch {
|
|
1309
|
+
sharedCache.set(file, {});
|
|
1314
1310
|
}
|
|
1315
1311
|
}
|
|
1316
|
-
|
|
1317
|
-
|
|
1312
|
+
return sharedCache.get(file);
|
|
1313
|
+
};
|
|
1314
|
+
try {
|
|
1315
|
+
for (const route of routes) {
|
|
1316
|
+
try {
|
|
1317
|
+
const routeModule = loadModule2(route.file);
|
|
1318
|
+
const meta = {};
|
|
1319
|
+
const input = readInput(routeModule);
|
|
1320
|
+
const security = collectSecurity(route, routeModule, loadShared);
|
|
1321
|
+
const hidden = collectHidden(route, routeModule, loadShared);
|
|
1322
|
+
if (input) {
|
|
1323
|
+
meta.input = input;
|
|
1324
|
+
}
|
|
1325
|
+
if (security) {
|
|
1326
|
+
meta.security = security;
|
|
1327
|
+
}
|
|
1328
|
+
if (hidden) {
|
|
1329
|
+
meta.hidden = true;
|
|
1330
|
+
}
|
|
1331
|
+
if (meta.input || meta.security || meta.hidden) {
|
|
1332
|
+
byFile.set(route.file, meta);
|
|
1333
|
+
}
|
|
1334
|
+
} catch {
|
|
1335
|
+
}
|
|
1318
1336
|
}
|
|
1337
|
+
} finally {
|
|
1338
|
+
unregisterAlias();
|
|
1339
|
+
unregister();
|
|
1319
1340
|
}
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1341
|
+
return byFile;
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
// src/generator/route-types.ts
|
|
1345
|
+
var import_node_path9 = require("path");
|
|
1346
|
+
async function writeRouteTypes(paths, routes) {
|
|
1347
|
+
const file = (0, import_node_path9.join)(paths.outDir, "routes.d.ts");
|
|
1348
|
+
const lines = [
|
|
1349
|
+
GENERATED_HEADER,
|
|
1350
|
+
"export interface RouteParams {"
|
|
1351
|
+
];
|
|
1352
|
+
for (const route of routes) {
|
|
1353
|
+
const typeFile = typeFilePath(paths, route.routeDir);
|
|
1354
|
+
lines.push(
|
|
1355
|
+
` ${JSON.stringify(`${route.method} ${route.path}`)}: import(${JSON.stringify(
|
|
1356
|
+
importPath(file, typeFile)
|
|
1357
|
+
)}).Params;`
|
|
1358
|
+
);
|
|
1323
1359
|
}
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
result.$defs = ctx.defs;
|
|
1327
|
-
return result;
|
|
1360
|
+
lines.push("}", "");
|
|
1361
|
+
await writeGenerated(file, lines.join("\n"));
|
|
1328
1362
|
}
|
|
1329
1363
|
|
|
1330
1364
|
// src/generator/tsconfig.ts
|
|
@@ -1383,20 +1417,21 @@ async function typeFolders(paths, routes) {
|
|
|
1383
1417
|
verbs: verbsByDir.get(slash(dir)) ?? []
|
|
1384
1418
|
}));
|
|
1385
1419
|
}
|
|
1386
|
-
function extractResponses(paths, routes) {
|
|
1420
|
+
async function extractResponses(paths, routes) {
|
|
1387
1421
|
const byFile = /* @__PURE__ */ new Map();
|
|
1388
1422
|
if (routes.length === 0) {
|
|
1389
1423
|
return byFile;
|
|
1390
1424
|
}
|
|
1391
1425
|
try {
|
|
1426
|
+
const { createSchemaProgram: createSchemaProgram2, extractRouteResponses: extractRouteResponses2 } = await Promise.resolve().then(() => (init_schema(), schema_exports));
|
|
1392
1427
|
const files = [...new Set(routes.map((route) => route.file))];
|
|
1393
1428
|
const appTypes = (0, import_node_path11.join)(paths.outDir, "types", "app.d.ts");
|
|
1394
|
-
const program =
|
|
1429
|
+
const program = createSchemaProgram2(
|
|
1395
1430
|
paths,
|
|
1396
1431
|
(0, import_node_fs6.existsSync)(appTypes) ? [...files, appTypes] : files
|
|
1397
1432
|
);
|
|
1398
1433
|
for (const file of files) {
|
|
1399
|
-
byFile.set(file,
|
|
1434
|
+
byFile.set(file, extractRouteResponses2(program, file));
|
|
1400
1435
|
}
|
|
1401
1436
|
} catch (error) {
|
|
1402
1437
|
console.warn(`giri: skipped response schema generation (${error.message}).`);
|
|
@@ -1438,7 +1473,7 @@ async function syncProject(config, options = {}) {
|
|
|
1438
1473
|
await writeRouteTypes(paths, routes);
|
|
1439
1474
|
await writeAppTypes(paths);
|
|
1440
1475
|
await writeTsConfig(paths, config);
|
|
1441
|
-
const responsesByFile = extractResponses(paths, routes);
|
|
1476
|
+
const responsesByFile = await extractResponses(paths, routes);
|
|
1442
1477
|
const { inputsByFile, securityByFile, hiddenFiles } = await extractMeta(config, paths, routes);
|
|
1443
1478
|
const data = { responsesByFile, inputsByFile, securityByFile, hiddenFiles };
|
|
1444
1479
|
await writeManifest(paths, routes, data);
|
|
@@ -1525,6 +1560,14 @@ var purgeProjectModules = (cwd) => {
|
|
|
1525
1560
|
}
|
|
1526
1561
|
}
|
|
1527
1562
|
};
|
|
1563
|
+
var purgeGeneratedModules = (outDir) => {
|
|
1564
|
+
const root = (0, import_node_path12.resolve)(outDir) + import_node_path12.sep;
|
|
1565
|
+
for (const id of Object.keys(require.cache)) {
|
|
1566
|
+
if ((0, import_node_path12.resolve)(id).startsWith(root)) {
|
|
1567
|
+
delete require.cache[id];
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
};
|
|
1528
1571
|
|
|
1529
1572
|
// src/generator/watch.ts
|
|
1530
1573
|
function createWatchUpdater(config, initial) {
|
|
@@ -1539,14 +1582,16 @@ function createWatchUpdater(config, initial) {
|
|
|
1539
1582
|
data.inputsByFile = result.data.inputsByFile;
|
|
1540
1583
|
data.securityByFile = result.data.securityByFile;
|
|
1541
1584
|
data.hiddenFiles = result.data.hiddenFiles;
|
|
1585
|
+
purgeGeneratedModules(paths.outDir);
|
|
1542
1586
|
return "full";
|
|
1543
1587
|
};
|
|
1544
1588
|
const reextractRoute = async (route) => {
|
|
1545
1589
|
const key = route.file;
|
|
1546
1590
|
try {
|
|
1591
|
+
const { createSchemaProgram: createSchemaProgram2, extractRouteResponses: extractRouteResponses2 } = await Promise.resolve().then(() => (init_schema(), schema_exports));
|
|
1547
1592
|
const appTypes = (0, import_node_path13.join)(paths.outDir, "types", "app.d.ts");
|
|
1548
|
-
const program =
|
|
1549
|
-
data.responsesByFile.set(key,
|
|
1593
|
+
const program = createSchemaProgram2(paths, (0, import_node_fs7.existsSync)(appTypes) ? [key, appTypes] : [key]);
|
|
1594
|
+
data.responsesByFile.set(key, extractRouteResponses2(program, key));
|
|
1550
1595
|
} catch {
|
|
1551
1596
|
}
|
|
1552
1597
|
try {
|
|
@@ -1592,6 +1637,7 @@ function createWatchUpdater(config, initial) {
|
|
|
1592
1637
|
}
|
|
1593
1638
|
await writeManifest(paths, routes, data);
|
|
1594
1639
|
await writeOpenApi(paths, routes, data);
|
|
1640
|
+
purgeGeneratedModules(paths.outDir);
|
|
1595
1641
|
return "incremental";
|
|
1596
1642
|
}
|
|
1597
1643
|
};
|
|
@@ -1810,6 +1856,23 @@ async function ensureTsConfig(cwd) {
|
|
|
1810
1856
|
`
|
|
1811
1857
|
);
|
|
1812
1858
|
}
|
|
1859
|
+
async function missingDeps(cwd, candidates) {
|
|
1860
|
+
let pkg = {};
|
|
1861
|
+
try {
|
|
1862
|
+
pkg = JSON.parse(await (0, import_promises4.readFile)((0, import_node_path15.join)(cwd, "package.json"), "utf8"));
|
|
1863
|
+
} catch {
|
|
1864
|
+
}
|
|
1865
|
+
const present = /* @__PURE__ */ new Set();
|
|
1866
|
+
for (const field of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
|
|
1867
|
+
const map = pkg[field];
|
|
1868
|
+
if (map && typeof map === "object") {
|
|
1869
|
+
for (const name of Object.keys(map)) {
|
|
1870
|
+
present.add(name);
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
return candidates.filter((name) => !present.has(name));
|
|
1875
|
+
}
|
|
1813
1876
|
function detectPackageManager() {
|
|
1814
1877
|
const ua = process.env.npm_config_user_agent ?? "";
|
|
1815
1878
|
if (ua.startsWith("yarn")) return "yarn";
|
|
@@ -1865,6 +1928,11 @@ async function selectAdapter(interactive) {
|
|
|
1865
1928
|
return ADAPTERS.find((adapter) => adapter.value === picked) ?? null;
|
|
1866
1929
|
}
|
|
1867
1930
|
async function initProject(cwd, flags) {
|
|
1931
|
+
if (!(0, import_node_fs9.existsSync)((0, import_node_path15.join)(cwd, "package.json"))) {
|
|
1932
|
+
throw new Error(
|
|
1933
|
+
"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."
|
|
1934
|
+
);
|
|
1935
|
+
}
|
|
1868
1936
|
const interactive = Boolean(process.stdout.isTTY) && !flags.yes;
|
|
1869
1937
|
prompts.intro("giri init");
|
|
1870
1938
|
let adapter;
|
|
@@ -1882,7 +1950,7 @@ async function initProject(cwd, flags) {
|
|
|
1882
1950
|
}
|
|
1883
1951
|
}
|
|
1884
1952
|
if (!adapter.available) {
|
|
1885
|
-
prompts.cancel(`The ${adapter.label} adapter isn't available yet
|
|
1953
|
+
prompts.cancel(`The ${adapter.label} adapter isn't available yet - only Hono ships today.`);
|
|
1886
1954
|
return;
|
|
1887
1955
|
}
|
|
1888
1956
|
const configPath = (0, import_node_path15.join)(cwd, "giri.config.ts");
|
|
@@ -1906,6 +1974,16 @@ async function initProject(cwd, flags) {
|
|
|
1906
1974
|
await ensureTsConfig(cwd);
|
|
1907
1975
|
prompts.log.success(`scaffolded a ${adapter.label} project`);
|
|
1908
1976
|
const pm = flags.packageManager ?? detectPackageManager();
|
|
1977
|
+
const deps = await missingDeps(cwd, ["@boon4681/giri", ...adapter.deps, "zod"]);
|
|
1978
|
+
const devDeps = await missingDeps(cwd, ["typescript", "@types/node"]);
|
|
1979
|
+
if (deps.length === 0 && devDeps.length === 0) {
|
|
1980
|
+
prompts.outro("All dependencies already present. Run `giri serve` to start the dev server.");
|
|
1981
|
+
return;
|
|
1982
|
+
}
|
|
1983
|
+
const planLines = [
|
|
1984
|
+
...deps.length ? [` ${pm} ${installArgs(pm, deps, false).join(" ")}`] : [],
|
|
1985
|
+
...devDeps.length ? [` ${pm} ${installArgs(pm, devDeps, true).join(" ")}`] : []
|
|
1986
|
+
];
|
|
1909
1987
|
let install = flags.install;
|
|
1910
1988
|
if (install === void 0) {
|
|
1911
1989
|
if (!interactive) {
|
|
@@ -1913,34 +1991,34 @@ async function initProject(cwd, flags) {
|
|
|
1913
1991
|
} else {
|
|
1914
1992
|
const answer = await prompts.confirm({ message: `Install dependencies with ${pm}?` });
|
|
1915
1993
|
if (prompts.isCancel(answer)) {
|
|
1916
|
-
prompts.cancel("Cancelled
|
|
1994
|
+
prompts.cancel("Cancelled - files written, skipped install.");
|
|
1917
1995
|
return;
|
|
1918
1996
|
}
|
|
1919
1997
|
install = answer;
|
|
1920
1998
|
}
|
|
1921
1999
|
}
|
|
1922
|
-
const deps = ["@boon4681/giri", ...adapter.deps, "zod"];
|
|
1923
|
-
const devDeps = ["typescript", "@types/node"];
|
|
1924
2000
|
if (install) {
|
|
1925
2001
|
try {
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
2002
|
+
if (deps.length) {
|
|
2003
|
+
prompts.log.step(`Installing ${deps.join(", ")}`);
|
|
2004
|
+
await runCommand(pm, installArgs(pm, deps, false), cwd);
|
|
2005
|
+
}
|
|
2006
|
+
if (devDeps.length) {
|
|
2007
|
+
prompts.log.step(`Installing dev deps ${devDeps.join(", ")}`);
|
|
2008
|
+
await runCommand(pm, installArgs(pm, devDeps, true), cwd);
|
|
2009
|
+
}
|
|
1930
2010
|
} catch (error) {
|
|
1931
2011
|
prompts.log.error(error instanceof Error ? error.message : String(error));
|
|
1932
|
-
prompts.outro(`Install failed
|
|
2012
|
+
prompts.outro(`Install failed - run these yourself, then \`giri serve\`:
|
|
2013
|
+
${planLines.join("\n")}`);
|
|
1933
2014
|
return;
|
|
1934
2015
|
}
|
|
1935
2016
|
prompts.outro("Ready. Run `giri serve` to start the dev server.");
|
|
1936
2017
|
return;
|
|
1937
2018
|
}
|
|
1938
|
-
prompts.outro(
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
${pm} ${installArgs(pm, devDeps, true).join(" ")}
|
|
1942
|
-
giri serve`
|
|
1943
|
-
);
|
|
2019
|
+
prompts.outro(`Next:
|
|
2020
|
+
${planLines.join("\n")}
|
|
2021
|
+
giri serve`);
|
|
1944
2022
|
}
|
|
1945
2023
|
function displayHost(address) {
|
|
1946
2024
|
if (!address || address === "::" || address === "0.0.0.0") {
|