@boon4681/giri 0.0.1 → 0.0.2-alpha-2
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 +1312 -1171
- 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,1335 @@ 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
|
-
};
|
|
278
|
+
function propertyType(checker, type, name, location) {
|
|
279
|
+
const symbol = checker.getPropertyOfType(type, name);
|
|
280
|
+
return symbol ? checker.getTypeOfSymbolAtLocation(symbol, location) : void 0;
|
|
440
281
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
ensureGiriAliasResolver(paths.outDir);
|
|
446
|
-
const { unregister } = await safeRegister();
|
|
447
|
-
const unregisterAliasResolver = registerAliasResolver(config.alias, paths.cwd);
|
|
448
|
-
try {
|
|
449
|
-
for (const route of routes) {
|
|
450
|
-
const routeModule = loadModule(route.file);
|
|
451
|
-
if (typeof routeModule.handle !== "function") {
|
|
452
|
-
throw new Error(`${route.file} must export a named handle function.`);
|
|
453
|
-
}
|
|
454
|
-
const folderMiddleware = routeModule.config?.skipInherited ? [] : route.sharedFiles.flatMap(
|
|
455
|
-
(file) => normalizeMiddleware(loadModule(file).middleware, file)
|
|
456
|
-
);
|
|
457
|
-
const verbMiddleware = normalizeMiddleware(routeModule.middleware, route.file);
|
|
458
|
-
config.adapter.register(app, {
|
|
459
|
-
method: route.method,
|
|
460
|
-
path: route.path,
|
|
461
|
-
handle: routeModule.handle,
|
|
462
|
-
middleware: [...folderMiddleware, ...verbMiddleware],
|
|
463
|
-
input: routeInput(routeModule, route.file),
|
|
464
|
-
services: options.services
|
|
465
|
-
});
|
|
466
|
-
}
|
|
467
|
-
} finally {
|
|
468
|
-
unregisterAliasResolver();
|
|
469
|
-
unregister();
|
|
470
|
-
}
|
|
471
|
-
return { app, routes, paths };
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
// src/generator/sync.ts
|
|
475
|
-
var import_node_fs6 = require("fs");
|
|
476
|
-
var import_promises3 = require("fs/promises");
|
|
477
|
-
var import_node_path11 = require("path");
|
|
478
|
-
|
|
479
|
-
// src/generator/app-types.ts
|
|
480
|
-
var import_node_fs4 = require("fs");
|
|
481
|
-
var import_node_path5 = require("path");
|
|
482
|
-
|
|
483
|
-
// src/generator/util.ts
|
|
484
|
-
var import_node_fs3 = require("fs");
|
|
485
|
-
var import_promises2 = require("fs/promises");
|
|
486
|
-
var import_node_path4 = require("path");
|
|
487
|
-
var GENERATED_HEADER = "// Generated by giri sync. Do not edit.";
|
|
488
|
-
function slash(path) {
|
|
489
|
-
return path.split(import_node_path4.sep).join("/");
|
|
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
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
return;
|
|
302
|
+
let status = 200;
|
|
303
|
+
if (statusArg) {
|
|
304
|
+
const statusType = checker.getTypeAtLocation(statusArg);
|
|
305
|
+
status = statusType.isNumberLiteral() ? statusType.value : "default";
|
|
526
306
|
}
|
|
527
|
-
|
|
528
|
-
await (0, import_promises2.writeFile)(path, content);
|
|
529
|
-
writeCache.set(path, content);
|
|
530
|
-
}
|
|
531
|
-
async function writeJson(path, value) {
|
|
532
|
-
await writeGenerated(path, `${JSON.stringify(value, null, 2)}
|
|
533
|
-
`);
|
|
307
|
+
return { status, format: method === "text" ? "text" : "json", data: checker.getTypeAtLocation(dataArg) };
|
|
534
308
|
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
return;
|
|
541
|
-
}
|
|
542
|
-
for (const entry of entries) {
|
|
543
|
-
const full = (0, import_node_path4.join)(dir, entry.name);
|
|
544
|
-
if (entry.isDirectory()) {
|
|
545
|
-
await pruneDir(full, keep);
|
|
546
|
-
await (0, import_promises2.rmdir)(full).catch(() => {
|
|
547
|
-
});
|
|
548
|
-
} else if (!keep.has(full)) {
|
|
549
|
-
await (0, import_promises2.rm)(full, { force: true });
|
|
550
|
-
writeCache.delete(full);
|
|
551
|
-
}
|
|
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;
|
|
552
315
|
}
|
|
316
|
+
const status = statusType.isNumberLiteral() ? statusType.value : "default";
|
|
317
|
+
const format = formatType.isStringLiteral() && formatType.value === "text" ? "text" : "json";
|
|
318
|
+
return { status, format, data: dataType };
|
|
553
319
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
var MAIN_EXTENSIONS = ["ts", "tsx", "mts", "cts", "js", "jsx", "mjs", "cjs"];
|
|
557
|
-
function findMainFile(cwd) {
|
|
558
|
-
for (const ext of MAIN_EXTENSIONS) {
|
|
559
|
-
const file = (0, import_node_path5.join)(cwd, "src", `main.${ext}`);
|
|
560
|
-
if ((0, import_node_fs4.existsSync)(file)) {
|
|
561
|
-
return file;
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
return void 0;
|
|
320
|
+
function constituents(type) {
|
|
321
|
+
return type.isUnion() ? type.types : [type];
|
|
565
322
|
}
|
|
566
|
-
function
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
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;
|
|
570
328
|
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
const mainFile = findMainFile(paths.cwd);
|
|
576
|
-
if (!mainFile) {
|
|
577
|
-
await writeGenerated(file, [GENERATED_HEADER, "export {};", ""].join("\n"));
|
|
578
|
-
return;
|
|
329
|
+
const checker = program.getTypeChecker();
|
|
330
|
+
const fn = findHandleFunction(source);
|
|
331
|
+
if (!fn) {
|
|
332
|
+
return result;
|
|
579
333
|
}
|
|
580
|
-
const
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
" interface Register {",
|
|
588
|
-
` app: typeof import(${JSON.stringify(spec)}) extends {`,
|
|
589
|
-
" init: (...args: any[]) => infer R;",
|
|
590
|
-
" }",
|
|
591
|
-
" ? Awaited<R>",
|
|
592
|
-
" : Record<string, unknown>;",
|
|
593
|
-
" }",
|
|
594
|
-
" }",
|
|
595
|
-
"}",
|
|
596
|
-
"export {};",
|
|
597
|
-
""
|
|
598
|
-
].join("\n")
|
|
599
|
-
);
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
// src/generator/manifest.ts
|
|
603
|
-
var import_node_path6 = require("path");
|
|
604
|
-
async function writeManifest(paths, routes, data = {}) {
|
|
605
|
-
const manifest = {
|
|
606
|
-
version: 1,
|
|
607
|
-
routes: routes.map((route) => {
|
|
608
|
-
const responses = data.responsesByFile?.get(route.file);
|
|
609
|
-
const input = data.inputsByFile?.get(route.file);
|
|
610
|
-
const security = data.securityByFile?.get(route.file);
|
|
611
|
-
return {
|
|
612
|
-
method: route.method,
|
|
613
|
-
path: route.path,
|
|
614
|
-
file: slash((0, import_node_path6.relative)(paths.cwd, route.file)),
|
|
615
|
-
params: route.params,
|
|
616
|
-
shared: route.sharedFiles.map((file) => slash((0, import_node_path6.relative)(paths.cwd, file))),
|
|
617
|
-
types: slash((0, import_node_path6.relative)(paths.cwd, typeFilePath(paths, route.routeDir))),
|
|
618
|
-
...data.hiddenFiles?.has(route.file) ? { hidden: true } : {},
|
|
619
|
-
...input ? { input } : {},
|
|
620
|
-
...security && security.security.length > 0 ? { security: security.security } : {},
|
|
621
|
-
responses: responses?.responses ?? [],
|
|
622
|
-
...responses && Object.keys(responses.$defs).length > 0 ? { $defs: responses.$defs } : {}
|
|
623
|
-
};
|
|
624
|
-
})
|
|
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);
|
|
625
341
|
};
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
401: "Unauthorized",
|
|
639
|
-
403: "Forbidden",
|
|
640
|
-
404: "Not Found",
|
|
641
|
-
409: "Conflict",
|
|
642
|
-
422: "Unprocessable Entity",
|
|
643
|
-
500: "Internal Server Error"
|
|
644
|
-
};
|
|
645
|
-
function toOpenApiPath(path) {
|
|
646
|
-
return path.replace(/:([A-Za-z0-9_]+)(?:\{[^}]*\})?/g, "{$1}");
|
|
647
|
-
}
|
|
648
|
-
function rewriteRefs(value) {
|
|
649
|
-
if (Array.isArray(value)) {
|
|
650
|
-
return value.map(rewriteRefs);
|
|
651
|
-
}
|
|
652
|
-
if (value && typeof value === "object") {
|
|
653
|
-
const out = {};
|
|
654
|
-
for (const [key, child] of Object.entries(value)) {
|
|
655
|
-
if (key === "$ref" && typeof child === "string" && child.startsWith("#/$defs/")) {
|
|
656
|
-
out.$ref = child.replace("#/$defs/", "#/components/schemas/");
|
|
657
|
-
} else {
|
|
658
|
-
out[key] = rewriteRefs(child);
|
|
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;
|
|
659
354
|
}
|
|
660
355
|
}
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
}
|
|
665
|
-
function mediaTypeFor(format) {
|
|
666
|
-
return format === "text" ? "text/plain" : "application/json";
|
|
667
|
-
}
|
|
668
|
-
var BODY_MEDIA_TYPE = {
|
|
669
|
-
json: "application/json",
|
|
670
|
-
form: "multipart/form-data",
|
|
671
|
-
urlencoded: "application/x-www-form-urlencoded",
|
|
672
|
-
text: "text/plain"
|
|
673
|
-
};
|
|
674
|
-
function buildResponses(responses) {
|
|
675
|
-
if (responses.length === 0) {
|
|
676
|
-
return { default: { description: "Response" } };
|
|
356
|
+
if (!matched) {
|
|
357
|
+
result.opaque = true;
|
|
358
|
+
}
|
|
677
359
|
}
|
|
678
|
-
const
|
|
679
|
-
|
|
680
|
-
|
|
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
|
-
};
|
|
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 });
|
|
686
363
|
}
|
|
687
|
-
|
|
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;
|
|
688
368
|
}
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
}
|
|
696
|
-
seen.add(param.name);
|
|
697
|
-
params.push({ name: param.name, in: "path", required: true, schema: { type: "string" } });
|
|
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();
|
|
698
375
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
376
|
+
});
|
|
377
|
+
|
|
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();
|
|
704
392
|
}
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
393
|
+
});
|
|
394
|
+
|
|
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
|
+
}
|
|
721
442
|
}
|
|
443
|
+
import_prompts.log.error(e);
|
|
444
|
+
(0, import_node_process.exit)(1);
|
|
722
445
|
}
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
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 };
|
|
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: () => {
|
|
753
458
|
}
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
await assertES5(res.unregister);
|
|
462
|
+
return res;
|
|
463
|
+
};
|
|
464
|
+
var findConfigPath = (cwd = (0, import_node_path.resolve)()) => {
|
|
465
|
+
for (const name of ["giri.config.ts", "giri.config.js"]) {
|
|
466
|
+
const path = (0, import_node_path.resolve)(cwd, name);
|
|
467
|
+
if ((0, import_node_fs.existsSync)(path)) {
|
|
468
|
+
return path;
|
|
754
469
|
}
|
|
755
|
-
if (security && security.security.length > 0) {
|
|
756
|
-
operation.security = security.security;
|
|
757
|
-
}
|
|
758
|
-
if (security) {
|
|
759
|
-
Object.assign(securitySchemes, security.securitySchemes);
|
|
760
|
-
}
|
|
761
|
-
const openApiPath = toOpenApiPath(route.path);
|
|
762
|
-
const pathItem = documentPaths[openApiPath] ?? {};
|
|
763
|
-
pathItem[route.method.toLowerCase()] = operation;
|
|
764
|
-
documentPaths[openApiPath] = pathItem;
|
|
765
470
|
}
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
471
|
+
return void 0;
|
|
472
|
+
};
|
|
473
|
+
var load = async (opts = {}) => {
|
|
474
|
+
const fail = (message) => {
|
|
475
|
+
if (opts.throwOnError) {
|
|
476
|
+
throw new Error(message);
|
|
477
|
+
}
|
|
478
|
+
import_prompts.log.error(message);
|
|
479
|
+
(0, import_node_process.exit)(1);
|
|
770
480
|
};
|
|
771
|
-
const
|
|
772
|
-
if (
|
|
773
|
-
|
|
481
|
+
const path = findConfigPath();
|
|
482
|
+
if (!path) {
|
|
483
|
+
fail("Config file not found.");
|
|
774
484
|
}
|
|
775
|
-
|
|
776
|
-
|
|
485
|
+
const { unregister } = await safeRegister();
|
|
486
|
+
let content;
|
|
487
|
+
try {
|
|
488
|
+
const required = require(`${path}`);
|
|
489
|
+
content = required.default ?? required;
|
|
490
|
+
} finally {
|
|
777
491
|
}
|
|
778
|
-
|
|
779
|
-
|
|
492
|
+
unregister();
|
|
493
|
+
const res = import_value.Value.Check(configSchema, content);
|
|
494
|
+
if (!res) {
|
|
495
|
+
const messages = [...import_value.Value.Errors(configSchema, content)].map((error) => error.message);
|
|
496
|
+
if (!opts.throwOnError) {
|
|
497
|
+
for (const message of messages) {
|
|
498
|
+
import_prompts.log.error(message);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
fail(messages.join("\n"));
|
|
780
502
|
}
|
|
781
|
-
return
|
|
503
|
+
return content;
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
// src/routes.ts
|
|
507
|
+
var import_node_fs2 = require("fs");
|
|
508
|
+
var import_promises = require("fs/promises");
|
|
509
|
+
var import_node_path2 = require("path");
|
|
510
|
+
var import_tinyglobby = require("tinyglobby");
|
|
511
|
+
var METHOD_ORDER = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"];
|
|
512
|
+
var METHOD_FROM_FILE = new Map(
|
|
513
|
+
METHOD_ORDER.map((method) => [`+${method.toLowerCase()}`, method])
|
|
514
|
+
);
|
|
515
|
+
function normalizeSlashes(path) {
|
|
516
|
+
return path.split(import_node_path2.sep).join("/");
|
|
782
517
|
}
|
|
783
|
-
|
|
784
|
-
|
|
518
|
+
function isRouteSourceFile(fileName) {
|
|
519
|
+
return /\.(?:[cm]?[jt]s|[jt]sx)$/.test(fileName) && !fileName.endsWith(".d.ts");
|
|
785
520
|
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
function paramsType(params) {
|
|
790
|
-
if (params.length === 0) {
|
|
791
|
-
return "{}";
|
|
521
|
+
function methodFromFile(fileName) {
|
|
522
|
+
if (!isRouteSourceFile(fileName)) {
|
|
523
|
+
return void 0;
|
|
792
524
|
}
|
|
793
|
-
const
|
|
794
|
-
|
|
795
|
-
|
|
525
|
+
const stem = fileName.replace(/\.(?:[cm]?[jt]s|[jt]sx)$/, "").toLowerCase();
|
|
526
|
+
return METHOD_FROM_FILE.get(stem);
|
|
527
|
+
}
|
|
528
|
+
function sharedFileIn(dir) {
|
|
529
|
+
for (const ext of ["ts", "tsx", "js", "jsx", "mjs", "cjs", "mts", "cts"]) {
|
|
530
|
+
const file = (0, import_node_path2.join)(dir, `+shared.${ext}`);
|
|
531
|
+
if ((0, import_node_fs2.existsSync)(file)) {
|
|
532
|
+
return file;
|
|
533
|
+
}
|
|
796
534
|
}
|
|
797
|
-
|
|
798
|
-
return `{
|
|
799
|
-
${fields}
|
|
800
|
-
}`;
|
|
535
|
+
return void 0;
|
|
801
536
|
}
|
|
802
|
-
function
|
|
803
|
-
|
|
804
|
-
|
|
537
|
+
function physicalRouteSegments(routesDir, routeDir) {
|
|
538
|
+
const rel = (0, import_node_path2.relative)(routesDir, routeDir);
|
|
539
|
+
if (!rel) {
|
|
540
|
+
return [];
|
|
805
541
|
}
|
|
806
|
-
return
|
|
807
|
-
const spec = JSON.stringify(moduleSpecifier(typesDir, file));
|
|
808
|
-
return `(typeof import(${spec}) extends { middleware: infer M } ? import("@boon4681/giri").InferStackVars<M> : {})`;
|
|
809
|
-
}).join("\n & ");
|
|
542
|
+
return normalizeSlashes(rel).split("/").filter(Boolean);
|
|
810
543
|
}
|
|
811
|
-
function
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
544
|
+
function urlSegment(segment) {
|
|
545
|
+
if (/^\(.+\)$/.test(segment)) {
|
|
546
|
+
return {};
|
|
547
|
+
}
|
|
548
|
+
const catchAll = /^\[\.\.\.(.+)\]$/.exec(segment);
|
|
549
|
+
if (catchAll) {
|
|
550
|
+
const name = catchAll[1];
|
|
551
|
+
return {
|
|
552
|
+
value: `:${name}{.*}`,
|
|
553
|
+
param: { name, catchAll: true }
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
const param = /^\[(.+)\]$/.exec(segment);
|
|
557
|
+
if (param) {
|
|
558
|
+
const name = param[1];
|
|
559
|
+
return {
|
|
560
|
+
value: `:${name}`,
|
|
561
|
+
param: { name, catchAll: false }
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
return { value: segment };
|
|
818
565
|
}
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
const
|
|
824
|
-
|
|
825
|
-
|
|
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));
|
|
566
|
+
function pathFromSegments(segments) {
|
|
567
|
+
const pathSegments = [];
|
|
568
|
+
const params = [];
|
|
569
|
+
for (const segment of segments) {
|
|
570
|
+
const converted = urlSegment(segment);
|
|
571
|
+
if (converted.value) {
|
|
572
|
+
pathSegments.push(converted.value);
|
|
835
573
|
}
|
|
836
|
-
|
|
837
|
-
|
|
574
|
+
if (converted.param) {
|
|
575
|
+
params.push(converted.param);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
return {
|
|
579
|
+
path: pathSegments.length > 0 ? `/${pathSegments.join("/")}` : "/",
|
|
580
|
+
params
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
async function scanRouteFolders(routesDir) {
|
|
584
|
+
if (!(0, import_node_fs2.existsSync)(routesDir)) {
|
|
585
|
+
return [];
|
|
838
586
|
}
|
|
587
|
+
const folders = [routesDir];
|
|
588
|
+
const walk = async (dir) => {
|
|
589
|
+
for (const entry of await (0, import_promises.readdir)(dir, { withFileTypes: true })) {
|
|
590
|
+
if (entry.isDirectory() && entry.name !== "node_modules") {
|
|
591
|
+
const full = (0, import_node_path2.join)(dir, entry.name);
|
|
592
|
+
folders.push(full);
|
|
593
|
+
await walk(full);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
await walk(routesDir);
|
|
598
|
+
return folders;
|
|
839
599
|
}
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
function sanitize(schema) {
|
|
843
|
-
const { $schema, ...rest } = schema;
|
|
844
|
-
void $schema;
|
|
845
|
-
return rest;
|
|
600
|
+
function routeParamsForDir(routesDir, dir) {
|
|
601
|
+
return pathFromSegments(physicalRouteSegments(routesDir, dir)).params;
|
|
846
602
|
}
|
|
847
|
-
function
|
|
848
|
-
|
|
849
|
-
|
|
603
|
+
function sharedFilesForDir(routesDir, dir) {
|
|
604
|
+
const segments = physicalRouteSegments(routesDir, dir);
|
|
605
|
+
const dirs = [routesDir];
|
|
606
|
+
let current = routesDir;
|
|
607
|
+
for (const segment of segments) {
|
|
608
|
+
current = (0, import_node_path2.join)(current, segment);
|
|
609
|
+
dirs.push(current);
|
|
850
610
|
}
|
|
851
|
-
return
|
|
611
|
+
return dirs.map(sharedFileIn).filter((file) => Boolean(file));
|
|
852
612
|
}
|
|
853
|
-
function
|
|
854
|
-
if (!
|
|
855
|
-
return
|
|
613
|
+
async function scanRoutes(routesDir) {
|
|
614
|
+
if (!(0, import_node_fs2.existsSync)(routesDir)) {
|
|
615
|
+
return [];
|
|
856
616
|
}
|
|
857
|
-
const
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
617
|
+
const files = await (0, import_tinyglobby.glob)("**/+*.{ts,tsx,js,jsx,mjs,cjs,mts,cts}", {
|
|
618
|
+
cwd: routesDir,
|
|
619
|
+
absolute: true,
|
|
620
|
+
onlyFiles: true
|
|
621
|
+
});
|
|
622
|
+
const routes = [];
|
|
623
|
+
for (const file of files) {
|
|
624
|
+
const method = methodFromFile((0, import_node_path2.basename)(file));
|
|
625
|
+
if (!method) {
|
|
626
|
+
continue;
|
|
862
627
|
}
|
|
628
|
+
const routeDir = (0, import_node_path2.dirname)(file);
|
|
629
|
+
const routeSegments = physicalRouteSegments(routesDir, routeDir);
|
|
630
|
+
const { path, params } = pathFromSegments(routeSegments);
|
|
631
|
+
routes.push({
|
|
632
|
+
method,
|
|
633
|
+
path,
|
|
634
|
+
file,
|
|
635
|
+
routeDir,
|
|
636
|
+
routeSegments,
|
|
637
|
+
params,
|
|
638
|
+
sharedFiles: sharedFilesForDir(routesDir, routeDir)
|
|
639
|
+
});
|
|
863
640
|
}
|
|
864
|
-
return
|
|
641
|
+
return routes.sort((left, right) => {
|
|
642
|
+
const pathOrder = left.path.localeCompare(right.path);
|
|
643
|
+
if (pathOrder !== 0) {
|
|
644
|
+
return pathOrder;
|
|
645
|
+
}
|
|
646
|
+
return METHOD_ORDER.indexOf(left.method) - METHOD_ORDER.indexOf(right.method);
|
|
647
|
+
});
|
|
865
648
|
}
|
|
866
649
|
|
|
867
|
-
// src/
|
|
868
|
-
|
|
650
|
+
// src/types.ts
|
|
651
|
+
var inputSchemaBrand = /* @__PURE__ */ Symbol.for("giri.input-schema");
|
|
652
|
+
var bodySchemaBrand = /* @__PURE__ */ Symbol.for("giri.body-schema");
|
|
653
|
+
|
|
654
|
+
// src/validation.ts
|
|
655
|
+
function isGiriInputSchema(value) {
|
|
656
|
+
return Boolean(
|
|
657
|
+
value && typeof value === "object" && value[inputSchemaBrand] === true
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
function isGiriBodySchema(value) {
|
|
661
|
+
return Boolean(
|
|
662
|
+
value && typeof value === "object" && value[bodySchemaBrand] === true
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// src/app.ts
|
|
667
|
+
function loadModule(file) {
|
|
869
668
|
const resolved = require.resolve(file);
|
|
870
669
|
delete require.cache[resolved];
|
|
871
670
|
return require(resolved);
|
|
872
671
|
}
|
|
873
|
-
function
|
|
672
|
+
function interopDefault(value) {
|
|
874
673
|
if (value && typeof value === "object" && "default" in value) {
|
|
875
674
|
return value.default;
|
|
876
675
|
}
|
|
877
676
|
return value;
|
|
878
677
|
}
|
|
879
|
-
function
|
|
880
|
-
const exported =
|
|
678
|
+
function normalizeMiddleware(value, file) {
|
|
679
|
+
const exported = interopDefault(value);
|
|
680
|
+
if (exported === void 0) {
|
|
681
|
+
return [];
|
|
682
|
+
}
|
|
881
683
|
if (typeof exported === "function") {
|
|
882
684
|
return [exported];
|
|
883
685
|
}
|
|
884
686
|
if (Array.isArray(exported)) {
|
|
885
|
-
|
|
687
|
+
for (const middleware of exported) {
|
|
688
|
+
if (typeof middleware !== "function") {
|
|
689
|
+
throw new Error(`Middleware export in ${file} must contain only functions.`);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
return exported;
|
|
886
693
|
}
|
|
887
|
-
|
|
694
|
+
throw new Error(`Middleware export in ${file} must be a function or an array of functions.`);
|
|
888
695
|
}
|
|
889
|
-
function
|
|
696
|
+
function assertBodySchema(value, file) {
|
|
697
|
+
if (!isGiriBodySchema(value)) {
|
|
698
|
+
throw new Error(
|
|
699
|
+
`${file}: "body" must be wrapped with a validator, e.g. \`export const body = zod.body({ json: ... })\` from @boon4681/giri/validators/zod.`
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
function assertQuerySchema(value, file) {
|
|
704
|
+
if (!isGiriInputSchema(value)) {
|
|
705
|
+
throw new Error(
|
|
706
|
+
`${file}: "query" must be wrapped with a validator, e.g. \`export const query = zod.query(...)\` from @boon4681/giri/validators/zod.`
|
|
707
|
+
);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
function routeInput(routeModule, file) {
|
|
890
711
|
const input = {};
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
input.body = body;
|
|
712
|
+
if (routeModule.body !== void 0) {
|
|
713
|
+
assertBodySchema(routeModule.body, file);
|
|
714
|
+
input.body = routeModule.body;
|
|
895
715
|
}
|
|
896
|
-
if (query) {
|
|
897
|
-
|
|
716
|
+
if (routeModule.query !== void 0) {
|
|
717
|
+
assertQuerySchema(routeModule.query, file);
|
|
718
|
+
input.query = routeModule.query;
|
|
898
719
|
}
|
|
899
720
|
return input.body || input.query ? input : void 0;
|
|
900
721
|
}
|
|
901
|
-
function
|
|
902
|
-
|
|
903
|
-
|
|
722
|
+
function aliasValues(value) {
|
|
723
|
+
return Array.isArray(value) ? value : [value];
|
|
724
|
+
}
|
|
725
|
+
function resolveAliasTarget(cwd, target, capture = "") {
|
|
726
|
+
const replaced = target.includes("*") ? target.replaceAll("*", capture) : target;
|
|
727
|
+
return (0, import_node_path3.isAbsolute)(replaced) ? replaced : (0, import_node_path3.resolve)(cwd, replaced);
|
|
728
|
+
}
|
|
729
|
+
function matchAlias(request, key) {
|
|
730
|
+
if (key.includes("*")) {
|
|
731
|
+
const [prefix2, suffix = ""] = key.split("*");
|
|
732
|
+
if (request.startsWith(prefix2) && request.endsWith(suffix)) {
|
|
733
|
+
return request.slice(prefix2.length, request.length - suffix.length);
|
|
734
|
+
}
|
|
735
|
+
return void 0;
|
|
904
736
|
}
|
|
905
|
-
if (
|
|
906
|
-
return
|
|
737
|
+
if (request === key) {
|
|
738
|
+
return "";
|
|
907
739
|
}
|
|
908
|
-
|
|
909
|
-
|
|
740
|
+
const prefix = `${key}/`;
|
|
741
|
+
if (request.startsWith(prefix)) {
|
|
742
|
+
return request.slice(prefix.length);
|
|
910
743
|
}
|
|
911
744
|
return void 0;
|
|
912
745
|
}
|
|
913
|
-
function
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
746
|
+
function resolveAliasRequest(request, alias, cwd) {
|
|
747
|
+
for (const [key, value] of Object.entries(alias ?? {})) {
|
|
748
|
+
const capture = matchAlias(request, key);
|
|
749
|
+
if (capture === void 0) {
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
const [target] = aliasValues(value);
|
|
753
|
+
if (!target) {
|
|
754
|
+
continue;
|
|
919
755
|
}
|
|
756
|
+
return resolveAliasTarget(cwd, target, capture);
|
|
920
757
|
}
|
|
921
|
-
|
|
922
|
-
return verb ?? hidden;
|
|
758
|
+
return void 0;
|
|
923
759
|
}
|
|
924
|
-
function
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
const middleware = [];
|
|
929
|
-
if (!skipInherited) {
|
|
930
|
-
for (const file of route.sharedFiles) {
|
|
931
|
-
middleware.push(...middlewareFunctions(loadShared(file).middleware));
|
|
932
|
-
}
|
|
760
|
+
function registerAliasResolver(alias, cwd) {
|
|
761
|
+
if (!alias || Object.keys(alias).length === 0) {
|
|
762
|
+
return () => {
|
|
763
|
+
};
|
|
933
764
|
}
|
|
934
|
-
|
|
935
|
-
const
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
765
|
+
const moduleWithResolver = import_node_module.default;
|
|
766
|
+
const originalResolveFilename = moduleWithResolver._resolveFilename;
|
|
767
|
+
moduleWithResolver._resolveFilename = function resolveWithGiriAlias(request, parent, isMain, options) {
|
|
768
|
+
return originalResolveFilename.call(
|
|
769
|
+
this,
|
|
770
|
+
resolveAliasRequest(request, alias, cwd) ?? request,
|
|
771
|
+
parent,
|
|
772
|
+
isMain,
|
|
773
|
+
options
|
|
774
|
+
);
|
|
775
|
+
};
|
|
776
|
+
return () => {
|
|
777
|
+
moduleWithResolver._resolveFilename = originalResolveFilename;
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
var GIRI_ALIAS_PREFIX = "$giri/";
|
|
781
|
+
var giriOutDir;
|
|
782
|
+
var giriResolverInstalled = false;
|
|
783
|
+
function ensureGiriAliasResolver(outDir) {
|
|
784
|
+
giriOutDir = outDir;
|
|
785
|
+
if (giriResolverInstalled) {
|
|
786
|
+
return;
|
|
949
787
|
}
|
|
950
|
-
|
|
788
|
+
giriResolverInstalled = true;
|
|
789
|
+
const moduleWithResolver = import_node_module.default;
|
|
790
|
+
const originalResolveFilename = moduleWithResolver._resolveFilename;
|
|
791
|
+
moduleWithResolver._resolveFilename = function resolveWithGiriInternalAlias(request, parent, isMain, options) {
|
|
792
|
+
const mapped = typeof request === "string" && request.startsWith(GIRI_ALIAS_PREFIX) && giriOutDir ? (0, import_node_path3.join)(giriOutDir, request.slice(GIRI_ALIAS_PREFIX.length)) : request;
|
|
793
|
+
return originalResolveFilename.call(this, mapped, parent, isMain, options);
|
|
794
|
+
};
|
|
951
795
|
}
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
const loadShared = (file) => {
|
|
958
|
-
if (!sharedCache.has(file)) {
|
|
959
|
-
try {
|
|
960
|
-
sharedCache.set(file, loadModule2(file));
|
|
961
|
-
} catch {
|
|
962
|
-
sharedCache.set(file, {});
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
return sharedCache.get(file);
|
|
796
|
+
function resolveGiriPaths(config, cwd = process.cwd()) {
|
|
797
|
+
return {
|
|
798
|
+
cwd: (0, import_node_path3.resolve)(cwd),
|
|
799
|
+
routesDir: (0, import_node_path3.resolve)(cwd, "src/routes"),
|
|
800
|
+
outDir: (0, import_node_path3.resolve)(cwd, config.outDir ?? ".giri")
|
|
966
801
|
};
|
|
802
|
+
}
|
|
803
|
+
async function buildGiriApp(config, options = {}) {
|
|
804
|
+
const paths = resolveGiriPaths(config, options.cwd);
|
|
805
|
+
const routes = await scanRoutes(paths.routesDir);
|
|
806
|
+
const app = config.adapter.createApp();
|
|
807
|
+
ensureGiriAliasResolver(paths.outDir);
|
|
808
|
+
const { unregister } = await safeRegister();
|
|
809
|
+
const unregisterAliasResolver = registerAliasResolver(config.alias, paths.cwd);
|
|
967
810
|
try {
|
|
968
811
|
for (const route of routes) {
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
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 {
|
|
812
|
+
const routeModule = loadModule(route.file);
|
|
813
|
+
if (typeof routeModule.handle !== "function") {
|
|
814
|
+
throw new Error(`${route.file} must export a named handle function.`);
|
|
988
815
|
}
|
|
816
|
+
const folderMiddleware = routeModule.config?.skipInherited ? [] : route.sharedFiles.flatMap(
|
|
817
|
+
(file) => normalizeMiddleware(loadModule(file).middleware, file)
|
|
818
|
+
);
|
|
819
|
+
const verbMiddleware = normalizeMiddleware(routeModule.middleware, route.file);
|
|
820
|
+
config.adapter.register(app, {
|
|
821
|
+
method: route.method,
|
|
822
|
+
path: route.path,
|
|
823
|
+
handle: routeModule.handle,
|
|
824
|
+
middleware: [...folderMiddleware, ...verbMiddleware],
|
|
825
|
+
input: routeInput(routeModule, route.file),
|
|
826
|
+
services: options.services
|
|
827
|
+
});
|
|
989
828
|
}
|
|
990
829
|
} finally {
|
|
991
|
-
|
|
830
|
+
unregisterAliasResolver();
|
|
992
831
|
unregister();
|
|
993
832
|
}
|
|
994
|
-
return
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
// src/generator/route-types.ts
|
|
998
|
-
var import_node_path9 = require("path");
|
|
999
|
-
async function writeRouteTypes(paths, routes) {
|
|
1000
|
-
const file = (0, import_node_path9.join)(paths.outDir, "routes.d.ts");
|
|
1001
|
-
const lines = [
|
|
1002
|
-
GENERATED_HEADER,
|
|
1003
|
-
"export interface RouteParams {"
|
|
1004
|
-
];
|
|
1005
|
-
for (const route of routes) {
|
|
1006
|
-
const typeFile = typeFilePath(paths, route.routeDir);
|
|
1007
|
-
lines.push(
|
|
1008
|
-
` ${JSON.stringify(`${route.method} ${route.path}`)}: import(${JSON.stringify(
|
|
1009
|
-
importPath(file, typeFile)
|
|
1010
|
-
)}).Params;`
|
|
1011
|
-
);
|
|
1012
|
-
}
|
|
1013
|
-
lines.push("}", "");
|
|
1014
|
-
await writeGenerated(file, lines.join("\n"));
|
|
833
|
+
return { app, routes, paths };
|
|
1015
834
|
}
|
|
1016
835
|
|
|
1017
|
-
// src/generator/
|
|
1018
|
-
var
|
|
1019
|
-
var
|
|
1020
|
-
|
|
1021
|
-
module: import_typescript.default.ModuleKind.NodeNext,
|
|
1022
|
-
moduleResolution: import_typescript.default.ModuleResolutionKind.NodeNext,
|
|
1023
|
-
strict: true,
|
|
1024
|
-
skipLibCheck: true,
|
|
1025
|
-
noEmit: true
|
|
1026
|
-
};
|
|
1027
|
-
function createSchemaProgram(paths, routeFiles) {
|
|
1028
|
-
let options = { ...DEFAULT_OPTIONS };
|
|
1029
|
-
const configPath = import_typescript.default.findConfigFile(paths.cwd, import_typescript.default.sys.fileExists, "tsconfig.json");
|
|
1030
|
-
if (configPath) {
|
|
1031
|
-
const parsed = import_typescript.default.getParsedCommandLineOfConfigFile(configPath, {}, {
|
|
1032
|
-
...import_typescript.default.sys,
|
|
1033
|
-
onUnRecoverableConfigFileDiagnostic: () => {
|
|
1034
|
-
}
|
|
1035
|
-
});
|
|
1036
|
-
if (parsed) {
|
|
1037
|
-
options = { ...parsed.options, noEmit: true };
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
return import_typescript.default.createProgram(routeFiles, options);
|
|
1041
|
-
}
|
|
836
|
+
// src/generator/sync.ts
|
|
837
|
+
var import_node_fs6 = require("fs");
|
|
838
|
+
var import_promises3 = require("fs/promises");
|
|
839
|
+
var import_node_path11 = require("path");
|
|
1042
840
|
|
|
1043
|
-
// src/generator/
|
|
1044
|
-
var
|
|
841
|
+
// src/generator/app-types.ts
|
|
842
|
+
var import_node_fs4 = require("fs");
|
|
843
|
+
var import_node_path5 = require("path");
|
|
1045
844
|
|
|
1046
|
-
// src/generator/
|
|
1047
|
-
var
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
inProgress: /* @__PURE__ */ new Map(),
|
|
1054
|
-
usedDefs: /* @__PURE__ */ new Set(),
|
|
1055
|
-
warnings: []
|
|
1056
|
-
};
|
|
1057
|
-
}
|
|
1058
|
-
function typeId(type) {
|
|
1059
|
-
return type.id;
|
|
1060
|
-
}
|
|
1061
|
-
function intrinsicName(type) {
|
|
1062
|
-
return type.intrinsicName;
|
|
1063
|
-
}
|
|
1064
|
-
function isDateType(type) {
|
|
1065
|
-
const symbol = type.getSymbol() ?? type.aliasSymbol;
|
|
1066
|
-
return symbol?.getName() === "Date";
|
|
845
|
+
// src/generator/util.ts
|
|
846
|
+
var import_node_fs3 = require("fs");
|
|
847
|
+
var import_promises2 = require("fs/promises");
|
|
848
|
+
var import_node_path4 = require("path");
|
|
849
|
+
var GENERATED_HEADER = "// Generated by giri sync. Do not edit.";
|
|
850
|
+
function slash(path) {
|
|
851
|
+
return path.split(import_node_path4.sep).join("/");
|
|
1067
852
|
}
|
|
1068
|
-
function
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
values.push(member.value);
|
|
1073
|
-
} else if (member.flags & import_typescript2.default.TypeFlags.BooleanLiteral) {
|
|
1074
|
-
values.push(intrinsicName(member) === "true");
|
|
1075
|
-
} else {
|
|
1076
|
-
return void 0;
|
|
1077
|
-
}
|
|
853
|
+
function importPath(fromFile, toFile) {
|
|
854
|
+
let path = slash((0, import_node_path4.relative)((0, import_node_path4.dirname)(fromFile), toFile)).replace(/\.d\.ts$/, "");
|
|
855
|
+
if (!path.startsWith(".")) {
|
|
856
|
+
path = `./${path}`;
|
|
1078
857
|
}
|
|
1079
|
-
return
|
|
858
|
+
return path;
|
|
1080
859
|
}
|
|
1081
|
-
function
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
return walkType(members[0], ctx);
|
|
860
|
+
function relativeConfigPath(fromDir, toPath) {
|
|
861
|
+
let path = slash((0, import_node_path4.relative)(fromDir, toPath));
|
|
862
|
+
if (!path.startsWith(".")) {
|
|
863
|
+
path = `./${path}`;
|
|
1086
864
|
}
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
865
|
+
return path;
|
|
866
|
+
}
|
|
867
|
+
function moduleSpecifier(fromDir, target) {
|
|
868
|
+
let path = slash((0, import_node_path4.relative)(fromDir, target)).replace(/\.(?:[cm]?[jt]sx?)$/, "");
|
|
869
|
+
if (!path.startsWith(".")) {
|
|
870
|
+
path = `./${path}`;
|
|
1090
871
|
}
|
|
1091
|
-
return
|
|
872
|
+
return path;
|
|
1092
873
|
}
|
|
1093
|
-
function
|
|
1094
|
-
const
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
const optional = Boolean(symbol.getFlags() & import_typescript2.default.SymbolFlags.Optional) || Boolean(propType.flags & import_typescript2.default.TypeFlags.Union && propType.types.some((t) => t.flags & import_typescript2.default.TypeFlags.Undefined));
|
|
1102
|
-
properties[name] = walkType(propType, ctx);
|
|
1103
|
-
if (!optional) {
|
|
1104
|
-
required.push(name);
|
|
1105
|
-
}
|
|
874
|
+
function typeFilePath(paths, routeDir) {
|
|
875
|
+
const sourceDir = (0, import_node_path4.relative)(paths.cwd, routeDir);
|
|
876
|
+
return (0, import_node_path4.join)(paths.outDir, "types", sourceDir, "$types.d.ts");
|
|
877
|
+
}
|
|
878
|
+
function assertSafeOutDir(paths) {
|
|
879
|
+
const rel = (0, import_node_path4.relative)(paths.cwd, paths.outDir);
|
|
880
|
+
if (!rel || rel.startsWith("..") || rel.includes(`..${import_node_path4.sep}`)) {
|
|
881
|
+
throw new Error(`Refusing to sync outside the project root: ${paths.outDir}`);
|
|
1106
882
|
}
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
883
|
+
}
|
|
884
|
+
var writeCache = /* @__PURE__ */ new Map();
|
|
885
|
+
async function writeGenerated(path, content) {
|
|
886
|
+
if (writeCache.get(path) === content && (0, import_node_fs3.existsSync)(path)) {
|
|
887
|
+
return;
|
|
1110
888
|
}
|
|
1111
|
-
|
|
1112
|
-
|
|
889
|
+
await (0, import_promises2.mkdir)((0, import_node_path4.dirname)(path), { recursive: true });
|
|
890
|
+
await (0, import_promises2.writeFile)(path, content);
|
|
891
|
+
writeCache.set(path, content);
|
|
892
|
+
}
|
|
893
|
+
async function writeJson(path, value) {
|
|
894
|
+
await writeGenerated(path, `${JSON.stringify(value, null, 2)}
|
|
895
|
+
`);
|
|
896
|
+
}
|
|
897
|
+
async function pruneDir(dir, keep) {
|
|
898
|
+
let entries;
|
|
899
|
+
try {
|
|
900
|
+
entries = await (0, import_promises2.readdir)(dir, { withFileTypes: true });
|
|
901
|
+
} catch {
|
|
902
|
+
return;
|
|
1113
903
|
}
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
904
|
+
for (const entry of entries) {
|
|
905
|
+
const full = (0, import_node_path4.join)(dir, entry.name);
|
|
906
|
+
if (entry.isDirectory()) {
|
|
907
|
+
await pruneDir(full, keep);
|
|
908
|
+
await (0, import_promises2.rmdir)(full).catch(() => {
|
|
909
|
+
});
|
|
910
|
+
} else if (!keep.has(full)) {
|
|
911
|
+
await (0, import_promises2.rm)(full, { force: true });
|
|
912
|
+
writeCache.delete(full);
|
|
913
|
+
}
|
|
1118
914
|
}
|
|
1119
|
-
return schema;
|
|
1120
915
|
}
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
916
|
+
|
|
917
|
+
// src/generator/app-types.ts
|
|
918
|
+
var MAIN_EXTENSIONS = ["ts", "tsx", "mts", "cts", "js", "jsx", "mjs", "cjs"];
|
|
919
|
+
function findMainFile(cwd) {
|
|
920
|
+
for (const ext of MAIN_EXTENSIONS) {
|
|
921
|
+
const file = (0, import_node_path5.join)(cwd, "src", `main.${ext}`);
|
|
922
|
+
if ((0, import_node_fs4.existsSync)(file)) {
|
|
923
|
+
return file;
|
|
924
|
+
}
|
|
1126
925
|
}
|
|
1127
|
-
return
|
|
926
|
+
return void 0;
|
|
1128
927
|
}
|
|
1129
|
-
function
|
|
1130
|
-
|
|
1131
|
-
if (
|
|
1132
|
-
|
|
1133
|
-
}
|
|
1134
|
-
if (checker.isArrayType(type)) {
|
|
1135
|
-
const [element] = checker.getTypeArguments(type);
|
|
1136
|
-
return { type: "array", items: element ? walkType(element, ctx) : {} };
|
|
928
|
+
function moduleSpecifier2(fromDir, target) {
|
|
929
|
+
let path = slash((0, import_node_path5.relative)(fromDir, target)).replace(/\.(?:[cm]?[jt]sx?)$/, "");
|
|
930
|
+
if (!path.startsWith(".")) {
|
|
931
|
+
path = `./${path}`;
|
|
1137
932
|
}
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
933
|
+
return path;
|
|
934
|
+
}
|
|
935
|
+
async function writeAppTypes(paths) {
|
|
936
|
+
const file = (0, import_node_path5.join)(paths.outDir, "types", "app.d.ts");
|
|
937
|
+
const mainFile = findMainFile(paths.cwd);
|
|
938
|
+
if (!mainFile) {
|
|
939
|
+
await writeGenerated(file, [GENERATED_HEADER, "export {};", ""].join("\n"));
|
|
940
|
+
return;
|
|
1141
941
|
}
|
|
1142
|
-
const
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
942
|
+
const spec = moduleSpecifier2((0, import_node_path5.join)(paths.outDir, "types"), mainFile);
|
|
943
|
+
await writeGenerated(
|
|
944
|
+
file,
|
|
945
|
+
[
|
|
946
|
+
GENERATED_HEADER,
|
|
947
|
+
"declare global {",
|
|
948
|
+
" namespace Giri {",
|
|
949
|
+
" interface Register {",
|
|
950
|
+
` app: typeof import(${JSON.stringify(spec)}) extends {`,
|
|
951
|
+
" init: (...args: any[]) => infer R;",
|
|
952
|
+
" }",
|
|
953
|
+
" ? Awaited<R>",
|
|
954
|
+
" : Record<string, unknown>;",
|
|
955
|
+
" }",
|
|
956
|
+
" }",
|
|
957
|
+
"}",
|
|
958
|
+
"export {};",
|
|
959
|
+
""
|
|
960
|
+
].join("\n")
|
|
961
|
+
);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
// src/generator/manifest.ts
|
|
965
|
+
var import_node_path6 = require("path");
|
|
966
|
+
async function writeManifest(paths, routes, data = {}) {
|
|
967
|
+
const manifest = {
|
|
968
|
+
version: 1,
|
|
969
|
+
routes: routes.map((route) => {
|
|
970
|
+
const responses = data.responsesByFile?.get(route.file);
|
|
971
|
+
const input = data.inputsByFile?.get(route.file);
|
|
972
|
+
const security = data.securityByFile?.get(route.file);
|
|
973
|
+
return {
|
|
974
|
+
method: route.method,
|
|
975
|
+
path: route.path,
|
|
976
|
+
file: slash((0, import_node_path6.relative)(paths.cwd, route.file)),
|
|
977
|
+
params: route.params,
|
|
978
|
+
shared: route.sharedFiles.map((file) => slash((0, import_node_path6.relative)(paths.cwd, file))),
|
|
979
|
+
types: slash((0, import_node_path6.relative)(paths.cwd, typeFilePath(paths, route.routeDir))),
|
|
980
|
+
...data.hiddenFiles?.has(route.file) ? { hidden: true } : {},
|
|
981
|
+
...input ? { input } : {},
|
|
982
|
+
...security && security.security.length > 0 ? { security: security.security } : {},
|
|
983
|
+
responses: responses?.responses ?? [],
|
|
984
|
+
...responses && Object.keys(responses.$defs).length > 0 ? { $defs: responses.$defs } : {}
|
|
985
|
+
};
|
|
986
|
+
})
|
|
987
|
+
};
|
|
988
|
+
await writeJson((0, import_node_path6.join)(paths.outDir, "manifest.json"), manifest);
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
// src/generator/openapi.ts
|
|
992
|
+
var import_node_fs5 = require("fs");
|
|
993
|
+
var import_node_path7 = require("path");
|
|
994
|
+
var REASON = {
|
|
995
|
+
200: "OK",
|
|
996
|
+
201: "Created",
|
|
997
|
+
202: "Accepted",
|
|
998
|
+
204: "No Content",
|
|
999
|
+
400: "Bad Request",
|
|
1000
|
+
401: "Unauthorized",
|
|
1001
|
+
403: "Forbidden",
|
|
1002
|
+
404: "Not Found",
|
|
1003
|
+
409: "Conflict",
|
|
1004
|
+
422: "Unprocessable Entity",
|
|
1005
|
+
500: "Internal Server Error"
|
|
1006
|
+
};
|
|
1007
|
+
function toOpenApiPath(path) {
|
|
1008
|
+
return path.replace(/:([A-Za-z0-9_]+)(?:\{[^}]*\})?/g, "{$1}");
|
|
1009
|
+
}
|
|
1010
|
+
function rewriteRefs(value) {
|
|
1011
|
+
if (Array.isArray(value)) {
|
|
1012
|
+
return value.map(rewriteRefs);
|
|
1147
1013
|
}
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1014
|
+
if (value && typeof value === "object") {
|
|
1015
|
+
const out = {};
|
|
1016
|
+
for (const [key, child] of Object.entries(value)) {
|
|
1017
|
+
if (key === "$ref" && typeof child === "string" && child.startsWith("#/$defs/")) {
|
|
1018
|
+
out.$ref = child.replace("#/$defs/", "#/components/schemas/");
|
|
1019
|
+
} else {
|
|
1020
|
+
out[key] = rewriteRefs(child);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
return out;
|
|
1155
1024
|
}
|
|
1156
|
-
return
|
|
1025
|
+
return value;
|
|
1157
1026
|
}
|
|
1158
|
-
function
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1027
|
+
function mediaTypeFor(format) {
|
|
1028
|
+
return format === "text" ? "text/plain" : "application/json";
|
|
1029
|
+
}
|
|
1030
|
+
var BODY_MEDIA_TYPE = {
|
|
1031
|
+
json: "application/json",
|
|
1032
|
+
form: "multipart/form-data",
|
|
1033
|
+
urlencoded: "application/x-www-form-urlencoded",
|
|
1034
|
+
text: "text/plain"
|
|
1035
|
+
};
|
|
1036
|
+
function buildResponses(responses) {
|
|
1037
|
+
if (responses.length === 0) {
|
|
1038
|
+
return { default: { description: "Response" } };
|
|
1162
1039
|
}
|
|
1163
|
-
|
|
1164
|
-
|
|
1040
|
+
const out = {};
|
|
1041
|
+
for (const response of responses) {
|
|
1042
|
+
const key = response.status === "default" ? "default" : String(response.status);
|
|
1043
|
+
const description = typeof response.status === "number" && REASON[response.status] || "Response";
|
|
1044
|
+
out[key] = {
|
|
1045
|
+
description,
|
|
1046
|
+
content: { [mediaTypeFor(response.format)]: { schema: rewriteRefs(response.schema) } }
|
|
1047
|
+
};
|
|
1165
1048
|
}
|
|
1166
|
-
|
|
1167
|
-
|
|
1049
|
+
return out;
|
|
1050
|
+
}
|
|
1051
|
+
function pathParameters(route) {
|
|
1052
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1053
|
+
const params = [];
|
|
1054
|
+
for (const param of route.params) {
|
|
1055
|
+
if (seen.has(param.name)) {
|
|
1056
|
+
continue;
|
|
1057
|
+
}
|
|
1058
|
+
seen.add(param.name);
|
|
1059
|
+
params.push({ name: param.name, in: "path", required: true, schema: { type: "string" } });
|
|
1168
1060
|
}
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1061
|
+
return params;
|
|
1062
|
+
}
|
|
1063
|
+
function queryParameters(query) {
|
|
1064
|
+
if (!query || query.type !== "object" || typeof query.properties !== "object") {
|
|
1065
|
+
return [];
|
|
1172
1066
|
}
|
|
1173
|
-
|
|
1174
|
-
|
|
1067
|
+
const properties = query.properties;
|
|
1068
|
+
const required = Array.isArray(query.required) ? query.required : [];
|
|
1069
|
+
return Object.entries(properties).map(([name, schema]) => ({
|
|
1070
|
+
name,
|
|
1071
|
+
in: "query",
|
|
1072
|
+
required: required.includes(name),
|
|
1073
|
+
schema: rewriteRefs(schema)
|
|
1074
|
+
}));
|
|
1075
|
+
}
|
|
1076
|
+
function readProjectInfo(cwd) {
|
|
1077
|
+
const file = (0, import_node_path7.join)(cwd, "package.json");
|
|
1078
|
+
if ((0, import_node_fs5.existsSync)(file)) {
|
|
1079
|
+
try {
|
|
1080
|
+
const pkg = JSON.parse((0, import_node_fs5.readFileSync)(file, "utf8"));
|
|
1081
|
+
return { title: pkg.name ?? "giri API", version: pkg.version ?? "0.0.0" };
|
|
1082
|
+
} catch {
|
|
1083
|
+
}
|
|
1175
1084
|
}
|
|
1176
|
-
|
|
1177
|
-
|
|
1085
|
+
return { title: "giri API", version: "0.0.0" };
|
|
1086
|
+
}
|
|
1087
|
+
function buildOpenApiDocument(paths, routes, data = {}) {
|
|
1088
|
+
const documentPaths = {};
|
|
1089
|
+
const schemas = {};
|
|
1090
|
+
const securitySchemes = {};
|
|
1091
|
+
for (const route of routes) {
|
|
1092
|
+
if (data.hiddenFiles?.has(route.file)) {
|
|
1093
|
+
continue;
|
|
1094
|
+
}
|
|
1095
|
+
const responses = data.responsesByFile?.get(route.file);
|
|
1096
|
+
const input = data.inputsByFile?.get(route.file);
|
|
1097
|
+
const security = data.securityByFile?.get(route.file);
|
|
1098
|
+
for (const [name, schema] of Object.entries(responses?.$defs ?? {})) {
|
|
1099
|
+
schemas[name] = rewriteRefs(schema);
|
|
1100
|
+
}
|
|
1101
|
+
const operation = { responses: buildResponses(responses?.responses ?? []) };
|
|
1102
|
+
const parameters = [...pathParameters(route), ...queryParameters(input?.query)];
|
|
1103
|
+
if (parameters.length > 0) {
|
|
1104
|
+
operation.parameters = parameters;
|
|
1105
|
+
}
|
|
1106
|
+
if (input?.body) {
|
|
1107
|
+
const content = {};
|
|
1108
|
+
for (const [contentType, schema] of Object.entries(input.body)) {
|
|
1109
|
+
content[BODY_MEDIA_TYPE[contentType] ?? contentType] = {
|
|
1110
|
+
schema: rewriteRefs(schema)
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
if (Object.keys(content).length > 0) {
|
|
1114
|
+
operation.requestBody = { required: true, content };
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
if (security && security.security.length > 0) {
|
|
1118
|
+
operation.security = security.security;
|
|
1119
|
+
}
|
|
1120
|
+
if (security) {
|
|
1121
|
+
Object.assign(securitySchemes, security.securitySchemes);
|
|
1122
|
+
}
|
|
1123
|
+
const openApiPath = toOpenApiPath(route.path);
|
|
1124
|
+
const pathItem = documentPaths[openApiPath] ?? {};
|
|
1125
|
+
pathItem[route.method.toLowerCase()] = operation;
|
|
1126
|
+
documentPaths[openApiPath] = pathItem;
|
|
1178
1127
|
}
|
|
1179
|
-
|
|
1180
|
-
|
|
1128
|
+
const document = {
|
|
1129
|
+
openapi: "3.1.0",
|
|
1130
|
+
info: readProjectInfo(paths.cwd),
|
|
1131
|
+
paths: documentPaths
|
|
1132
|
+
};
|
|
1133
|
+
const components = {};
|
|
1134
|
+
if (Object.keys(schemas).length > 0) {
|
|
1135
|
+
components.schemas = schemas;
|
|
1181
1136
|
}
|
|
1182
|
-
if (
|
|
1183
|
-
|
|
1137
|
+
if (Object.keys(securitySchemes).length > 0) {
|
|
1138
|
+
components.securitySchemes = securitySchemes;
|
|
1184
1139
|
}
|
|
1185
|
-
if (
|
|
1186
|
-
|
|
1140
|
+
if (Object.keys(components).length > 0) {
|
|
1141
|
+
document.components = components;
|
|
1187
1142
|
}
|
|
1188
|
-
|
|
1189
|
-
|
|
1143
|
+
return document;
|
|
1144
|
+
}
|
|
1145
|
+
async function writeOpenApi(paths, routes, data = {}) {
|
|
1146
|
+
await writeJson((0, import_node_path7.join)(paths.outDir, "openapi.json"), buildOpenApiDocument(paths, routes, data));
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
// src/generator/param-types.ts
|
|
1150
|
+
var import_node_path8 = require("path");
|
|
1151
|
+
function paramsType(params) {
|
|
1152
|
+
if (params.length === 0) {
|
|
1153
|
+
return "{}";
|
|
1190
1154
|
}
|
|
1191
|
-
|
|
1192
|
-
|
|
1155
|
+
const unique = /* @__PURE__ */ new Map();
|
|
1156
|
+
for (const param of params) {
|
|
1157
|
+
unique.set(param.name, param);
|
|
1193
1158
|
}
|
|
1194
|
-
|
|
1195
|
-
|
|
1159
|
+
const fields = [...unique.values()].map((param) => ` ${JSON.stringify(param.name)}: string;`).join("\n");
|
|
1160
|
+
return `{
|
|
1161
|
+
${fields}
|
|
1162
|
+
}`;
|
|
1163
|
+
}
|
|
1164
|
+
function varsType(typesDir, sharedFiles) {
|
|
1165
|
+
if (sharedFiles.length === 0) {
|
|
1166
|
+
return "{}";
|
|
1196
1167
|
}
|
|
1197
|
-
return {
|
|
1168
|
+
return sharedFiles.map((file) => {
|
|
1169
|
+
const spec = JSON.stringify(moduleSpecifier(typesDir, file));
|
|
1170
|
+
return `(typeof import(${spec}) extends { middleware: infer M } ? import("@boon4681/giri").InferStackVars<M> : {})`;
|
|
1171
|
+
}).join("\n & ");
|
|
1198
1172
|
}
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
}
|
|
1173
|
+
function methodExports(typesDir, verbs) {
|
|
1174
|
+
return verbs.map(({ method, file }) => {
|
|
1175
|
+
const spec = JSON.stringify(moduleSpecifier(typesDir, file));
|
|
1176
|
+
const input = `import("@boon4681/giri").RouteInputOf<typeof import(${spec})>`;
|
|
1177
|
+
const vars = `Vars & import("@boon4681/giri").MiddlewareVarsOf<typeof import(${spec})>`;
|
|
1178
|
+
return `export type ${method} = import("@boon4681/giri").Handle<Params, ${input}, ${vars}>;`;
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
async function writeParamTypes(paths, folders) {
|
|
1182
|
+
for (const { dir, params, sharedFiles, verbs } of folders) {
|
|
1183
|
+
const file = typeFilePath(paths, dir);
|
|
1184
|
+
const typesDir = (0, import_node_path8.dirname)(file);
|
|
1185
|
+
const lines = [
|
|
1186
|
+
GENERATED_HEADER,
|
|
1187
|
+
`export type Params = ${paramsType(params)};`,
|
|
1188
|
+
"export type RouteParams = Params;",
|
|
1189
|
+
`type Vars = ${varsType(typesDir, sharedFiles)};`,
|
|
1190
|
+
"export type Middleware<Injects extends Record<string, unknown> = {}> =",
|
|
1191
|
+
' import("@boon4681/giri").Middleware<Params, import("@boon4681/giri").ValidatedInput, Injects>;',
|
|
1192
|
+
'export type Handle<Input extends import("@boon4681/giri").ValidatedInput = import("@boon4681/giri").ValidatedInput> =',
|
|
1193
|
+
' import("@boon4681/giri").Handle<Params, Input, Vars>;'
|
|
1194
|
+
];
|
|
1195
|
+
if (verbs.length > 0) {
|
|
1196
|
+
lines.push(...methodExports(typesDir, verbs));
|
|
1214
1197
|
}
|
|
1198
|
+
lines.push("");
|
|
1199
|
+
await writeGenerated(file, lines.join("\n"));
|
|
1215
1200
|
}
|
|
1216
|
-
return found;
|
|
1217
1201
|
}
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1202
|
+
|
|
1203
|
+
// src/generator/inputs.ts
|
|
1204
|
+
function sanitize(schema) {
|
|
1205
|
+
const { $schema, ...rest } = schema;
|
|
1206
|
+
void $schema;
|
|
1207
|
+
return rest;
|
|
1208
|
+
}
|
|
1209
|
+
function inputToJsonSchema(schema) {
|
|
1210
|
+
if (!isGiriInputSchema(schema)) {
|
|
1211
|
+
return void 0;
|
|
1221
1212
|
}
|
|
1222
|
-
|
|
1223
|
-
|
|
1213
|
+
return sanitize(schema.toJsonSchema());
|
|
1214
|
+
}
|
|
1215
|
+
function bodyToJsonSchemas(value) {
|
|
1216
|
+
if (!isGiriBodySchema(value)) {
|
|
1217
|
+
return void 0;
|
|
1224
1218
|
}
|
|
1225
|
-
const
|
|
1226
|
-
const
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
if (import_typescript3.default.isReturnStatement(node) && node.expression) {
|
|
1231
|
-
expressions.push(node.expression);
|
|
1219
|
+
const out = {};
|
|
1220
|
+
for (const [contentType, schema] of Object.entries(value.contents)) {
|
|
1221
|
+
const json = inputToJsonSchema(schema);
|
|
1222
|
+
if (json) {
|
|
1223
|
+
out[contentType] = json;
|
|
1232
1224
|
}
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
import_typescript3.default.forEachChild(fn.body, visit);
|
|
1236
|
-
return expressions;
|
|
1237
|
-
}
|
|
1238
|
-
function propertyType(checker, type, name, location) {
|
|
1239
|
-
const symbol = checker.getPropertyOfType(type, name);
|
|
1240
|
-
return symbol ? checker.getTypeOfSymbolAtLocation(symbol, location) : void 0;
|
|
1225
|
+
}
|
|
1226
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
1241
1227
|
}
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
);
|
|
1228
|
+
|
|
1229
|
+
// src/generator/route-meta.ts
|
|
1230
|
+
function loadModule2(file) {
|
|
1231
|
+
const resolved = require.resolve(file);
|
|
1232
|
+
delete require.cache[resolved];
|
|
1233
|
+
return require(resolved);
|
|
1246
1234
|
}
|
|
1247
|
-
function
|
|
1248
|
-
if (
|
|
1249
|
-
return
|
|
1235
|
+
function interopDefault2(value) {
|
|
1236
|
+
if (value && typeof value === "object" && "default" in value) {
|
|
1237
|
+
return value.default;
|
|
1250
1238
|
}
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1239
|
+
return value;
|
|
1240
|
+
}
|
|
1241
|
+
function middlewareFunctions(value) {
|
|
1242
|
+
const exported = interopDefault2(value);
|
|
1243
|
+
if (typeof exported === "function") {
|
|
1244
|
+
return [exported];
|
|
1254
1245
|
}
|
|
1255
|
-
if (
|
|
1256
|
-
return
|
|
1246
|
+
if (Array.isArray(exported)) {
|
|
1247
|
+
return exported.filter((fn) => typeof fn === "function");
|
|
1257
1248
|
}
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1249
|
+
return [];
|
|
1250
|
+
}
|
|
1251
|
+
function readInput(routeModule) {
|
|
1252
|
+
const input = {};
|
|
1253
|
+
const body = bodyToJsonSchemas(routeModule.body);
|
|
1254
|
+
const query = inputToJsonSchema(routeModule.query);
|
|
1255
|
+
if (body) {
|
|
1256
|
+
input.body = body;
|
|
1261
1257
|
}
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
const statusType = checker.getTypeAtLocation(statusArg);
|
|
1265
|
-
status = statusType.isNumberLiteral() ? statusType.value : "default";
|
|
1258
|
+
if (query) {
|
|
1259
|
+
input.query = query;
|
|
1266
1260
|
}
|
|
1267
|
-
return
|
|
1261
|
+
return input.body || input.query ? input : void 0;
|
|
1268
1262
|
}
|
|
1269
|
-
function
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
const formatType = propertyType(checker, type, "format", location);
|
|
1273
|
-
if (!dataType || !statusType || !formatType) {
|
|
1274
|
-
return void 0;
|
|
1263
|
+
function hiddenFrom(value) {
|
|
1264
|
+
if (value === false) {
|
|
1265
|
+
return true;
|
|
1275
1266
|
}
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1267
|
+
if (value === true) {
|
|
1268
|
+
return false;
|
|
1269
|
+
}
|
|
1270
|
+
if (value && typeof value === "object" && "hidden" in value) {
|
|
1271
|
+
return Boolean(value.hidden);
|
|
1272
|
+
}
|
|
1273
|
+
return void 0;
|
|
1282
1274
|
}
|
|
1283
|
-
function
|
|
1284
|
-
|
|
1285
|
-
const
|
|
1286
|
-
|
|
1287
|
-
|
|
1275
|
+
function collectHidden(route, routeModule, loadShared) {
|
|
1276
|
+
let hidden = false;
|
|
1277
|
+
for (const file of route.sharedFiles) {
|
|
1278
|
+
const opinion = hiddenFrom(loadShared(file).openapi);
|
|
1279
|
+
if (opinion !== void 0) {
|
|
1280
|
+
hidden = opinion;
|
|
1281
|
+
}
|
|
1288
1282
|
}
|
|
1289
|
-
const
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1283
|
+
const verb = hiddenFrom(routeModule.openapi);
|
|
1284
|
+
return verb ?? hidden;
|
|
1285
|
+
}
|
|
1286
|
+
function collectSecurity(route, routeModule, loadShared) {
|
|
1287
|
+
const skipInherited = Boolean(
|
|
1288
|
+
routeModule.config?.skipInherited
|
|
1289
|
+
);
|
|
1290
|
+
const middleware = [];
|
|
1291
|
+
if (!skipInherited) {
|
|
1292
|
+
for (const file of route.sharedFiles) {
|
|
1293
|
+
middleware.push(...middlewareFunctions(loadShared(file).middleware));
|
|
1294
|
+
}
|
|
1293
1295
|
}
|
|
1294
|
-
|
|
1295
|
-
const
|
|
1296
|
-
const
|
|
1297
|
-
|
|
1298
|
-
const
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
record(fromCall);
|
|
1306
|
-
continue;
|
|
1296
|
+
middleware.push(...middlewareFunctions(routeModule.middleware));
|
|
1297
|
+
const security = [];
|
|
1298
|
+
const securitySchemes = {};
|
|
1299
|
+
for (const fn of middleware) {
|
|
1300
|
+
const openapi = fn.openapi;
|
|
1301
|
+
if (openapi?.security) {
|
|
1302
|
+
for (const requirement of openapi.security) {
|
|
1303
|
+
if (!security.some((seen) => JSON.stringify(seen) === JSON.stringify(requirement))) {
|
|
1304
|
+
security.push(requirement);
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
1307
|
}
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1308
|
+
if (openapi?.securitySchemes) {
|
|
1309
|
+
Object.assign(securitySchemes, openapi.securitySchemes);
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
return security.length > 0 || Object.keys(securitySchemes).length > 0 ? { security, securitySchemes } : void 0;
|
|
1313
|
+
}
|
|
1314
|
+
async function extractRouteMeta(config, paths, routes) {
|
|
1315
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
1316
|
+
const { unregister } = await safeRegister();
|
|
1317
|
+
const unregisterAlias = registerAliasResolver(config.alias, paths.cwd);
|
|
1318
|
+
const sharedCache = /* @__PURE__ */ new Map();
|
|
1319
|
+
const loadShared = (file) => {
|
|
1320
|
+
if (!sharedCache.has(file)) {
|
|
1321
|
+
try {
|
|
1322
|
+
sharedCache.set(file, loadModule2(file));
|
|
1323
|
+
} catch {
|
|
1324
|
+
sharedCache.set(file, {});
|
|
1314
1325
|
}
|
|
1315
1326
|
}
|
|
1316
|
-
|
|
1317
|
-
|
|
1327
|
+
return sharedCache.get(file);
|
|
1328
|
+
};
|
|
1329
|
+
try {
|
|
1330
|
+
for (const route of routes) {
|
|
1331
|
+
try {
|
|
1332
|
+
const routeModule = loadModule2(route.file);
|
|
1333
|
+
const meta = {};
|
|
1334
|
+
const input = readInput(routeModule);
|
|
1335
|
+
const security = collectSecurity(route, routeModule, loadShared);
|
|
1336
|
+
const hidden = collectHidden(route, routeModule, loadShared);
|
|
1337
|
+
if (input) {
|
|
1338
|
+
meta.input = input;
|
|
1339
|
+
}
|
|
1340
|
+
if (security) {
|
|
1341
|
+
meta.security = security;
|
|
1342
|
+
}
|
|
1343
|
+
if (hidden) {
|
|
1344
|
+
meta.hidden = true;
|
|
1345
|
+
}
|
|
1346
|
+
if (meta.input || meta.security || meta.hidden) {
|
|
1347
|
+
byFile.set(route.file, meta);
|
|
1348
|
+
}
|
|
1349
|
+
} catch {
|
|
1350
|
+
}
|
|
1318
1351
|
}
|
|
1352
|
+
} finally {
|
|
1353
|
+
unregisterAlias();
|
|
1354
|
+
unregister();
|
|
1319
1355
|
}
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1356
|
+
return byFile;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
// src/generator/route-types.ts
|
|
1360
|
+
var import_node_path9 = require("path");
|
|
1361
|
+
async function writeRouteTypes(paths, routes) {
|
|
1362
|
+
const file = (0, import_node_path9.join)(paths.outDir, "routes.d.ts");
|
|
1363
|
+
const lines = [
|
|
1364
|
+
GENERATED_HEADER,
|
|
1365
|
+
"export interface RouteParams {"
|
|
1366
|
+
];
|
|
1367
|
+
for (const route of routes) {
|
|
1368
|
+
const typeFile = typeFilePath(paths, route.routeDir);
|
|
1369
|
+
lines.push(
|
|
1370
|
+
` ${JSON.stringify(`${route.method} ${route.path}`)}: import(${JSON.stringify(
|
|
1371
|
+
importPath(file, typeFile)
|
|
1372
|
+
)}).Params;`
|
|
1373
|
+
);
|
|
1323
1374
|
}
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
result.$defs = ctx.defs;
|
|
1327
|
-
return result;
|
|
1375
|
+
lines.push("}", "");
|
|
1376
|
+
await writeGenerated(file, lines.join("\n"));
|
|
1328
1377
|
}
|
|
1329
1378
|
|
|
1330
1379
|
// src/generator/tsconfig.ts
|
|
@@ -1383,20 +1432,21 @@ async function typeFolders(paths, routes) {
|
|
|
1383
1432
|
verbs: verbsByDir.get(slash(dir)) ?? []
|
|
1384
1433
|
}));
|
|
1385
1434
|
}
|
|
1386
|
-
function extractResponses(paths, routes) {
|
|
1435
|
+
async function extractResponses(paths, routes) {
|
|
1387
1436
|
const byFile = /* @__PURE__ */ new Map();
|
|
1388
1437
|
if (routes.length === 0) {
|
|
1389
1438
|
return byFile;
|
|
1390
1439
|
}
|
|
1391
1440
|
try {
|
|
1441
|
+
const { createSchemaProgram: createSchemaProgram2, extractRouteResponses: extractRouteResponses2 } = await Promise.resolve().then(() => (init_schema(), schema_exports));
|
|
1392
1442
|
const files = [...new Set(routes.map((route) => route.file))];
|
|
1393
1443
|
const appTypes = (0, import_node_path11.join)(paths.outDir, "types", "app.d.ts");
|
|
1394
|
-
const program =
|
|
1444
|
+
const program = createSchemaProgram2(
|
|
1395
1445
|
paths,
|
|
1396
1446
|
(0, import_node_fs6.existsSync)(appTypes) ? [...files, appTypes] : files
|
|
1397
1447
|
);
|
|
1398
1448
|
for (const file of files) {
|
|
1399
|
-
byFile.set(file,
|
|
1449
|
+
byFile.set(file, extractRouteResponses2(program, file));
|
|
1400
1450
|
}
|
|
1401
1451
|
} catch (error) {
|
|
1402
1452
|
console.warn(`giri: skipped response schema generation (${error.message}).`);
|
|
@@ -1438,7 +1488,7 @@ async function syncProject(config, options = {}) {
|
|
|
1438
1488
|
await writeRouteTypes(paths, routes);
|
|
1439
1489
|
await writeAppTypes(paths);
|
|
1440
1490
|
await writeTsConfig(paths, config);
|
|
1441
|
-
const responsesByFile = extractResponses(paths, routes);
|
|
1491
|
+
const responsesByFile = await extractResponses(paths, routes);
|
|
1442
1492
|
const { inputsByFile, securityByFile, hiddenFiles } = await extractMeta(config, paths, routes);
|
|
1443
1493
|
const data = { responsesByFile, inputsByFile, securityByFile, hiddenFiles };
|
|
1444
1494
|
await writeManifest(paths, routes, data);
|
|
@@ -1525,6 +1575,14 @@ var purgeProjectModules = (cwd) => {
|
|
|
1525
1575
|
}
|
|
1526
1576
|
}
|
|
1527
1577
|
};
|
|
1578
|
+
var purgeGeneratedModules = (outDir) => {
|
|
1579
|
+
const root = (0, import_node_path12.resolve)(outDir) + import_node_path12.sep;
|
|
1580
|
+
for (const id of Object.keys(require.cache)) {
|
|
1581
|
+
if ((0, import_node_path12.resolve)(id).startsWith(root)) {
|
|
1582
|
+
delete require.cache[id];
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
};
|
|
1528
1586
|
|
|
1529
1587
|
// src/generator/watch.ts
|
|
1530
1588
|
function createWatchUpdater(config, initial) {
|
|
@@ -1539,14 +1597,16 @@ function createWatchUpdater(config, initial) {
|
|
|
1539
1597
|
data.inputsByFile = result.data.inputsByFile;
|
|
1540
1598
|
data.securityByFile = result.data.securityByFile;
|
|
1541
1599
|
data.hiddenFiles = result.data.hiddenFiles;
|
|
1600
|
+
purgeGeneratedModules(paths.outDir);
|
|
1542
1601
|
return "full";
|
|
1543
1602
|
};
|
|
1544
1603
|
const reextractRoute = async (route) => {
|
|
1545
1604
|
const key = route.file;
|
|
1546
1605
|
try {
|
|
1606
|
+
const { createSchemaProgram: createSchemaProgram2, extractRouteResponses: extractRouteResponses2 } = await Promise.resolve().then(() => (init_schema(), schema_exports));
|
|
1547
1607
|
const appTypes = (0, import_node_path13.join)(paths.outDir, "types", "app.d.ts");
|
|
1548
|
-
const program =
|
|
1549
|
-
data.responsesByFile.set(key,
|
|
1608
|
+
const program = createSchemaProgram2(paths, (0, import_node_fs7.existsSync)(appTypes) ? [key, appTypes] : [key]);
|
|
1609
|
+
data.responsesByFile.set(key, extractRouteResponses2(program, key));
|
|
1550
1610
|
} catch {
|
|
1551
1611
|
}
|
|
1552
1612
|
try {
|
|
@@ -1592,6 +1652,7 @@ function createWatchUpdater(config, initial) {
|
|
|
1592
1652
|
}
|
|
1593
1653
|
await writeManifest(paths, routes, data);
|
|
1594
1654
|
await writeOpenApi(paths, routes, data);
|
|
1655
|
+
purgeGeneratedModules(paths.outDir);
|
|
1595
1656
|
return "incremental";
|
|
1596
1657
|
}
|
|
1597
1658
|
};
|
|
@@ -1810,6 +1871,23 @@ async function ensureTsConfig(cwd) {
|
|
|
1810
1871
|
`
|
|
1811
1872
|
);
|
|
1812
1873
|
}
|
|
1874
|
+
async function missingDeps(cwd, candidates) {
|
|
1875
|
+
let pkg = {};
|
|
1876
|
+
try {
|
|
1877
|
+
pkg = JSON.parse(await (0, import_promises4.readFile)((0, import_node_path15.join)(cwd, "package.json"), "utf8"));
|
|
1878
|
+
} catch {
|
|
1879
|
+
}
|
|
1880
|
+
const present = /* @__PURE__ */ new Set();
|
|
1881
|
+
for (const field of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
|
|
1882
|
+
const map = pkg[field];
|
|
1883
|
+
if (map && typeof map === "object") {
|
|
1884
|
+
for (const name of Object.keys(map)) {
|
|
1885
|
+
present.add(name);
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
return candidates.filter((name) => !present.has(name));
|
|
1890
|
+
}
|
|
1813
1891
|
function detectPackageManager() {
|
|
1814
1892
|
const ua = process.env.npm_config_user_agent ?? "";
|
|
1815
1893
|
if (ua.startsWith("yarn")) return "yarn";
|
|
@@ -1865,6 +1943,11 @@ async function selectAdapter(interactive) {
|
|
|
1865
1943
|
return ADAPTERS.find((adapter) => adapter.value === picked) ?? null;
|
|
1866
1944
|
}
|
|
1867
1945
|
async function initProject(cwd, flags) {
|
|
1946
|
+
if (!(0, import_node_fs9.existsSync)((0, import_node_path15.join)(cwd, "package.json"))) {
|
|
1947
|
+
throw new Error(
|
|
1948
|
+
"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."
|
|
1949
|
+
);
|
|
1950
|
+
}
|
|
1868
1951
|
const interactive = Boolean(process.stdout.isTTY) && !flags.yes;
|
|
1869
1952
|
prompts.intro("giri init");
|
|
1870
1953
|
let adapter;
|
|
@@ -1882,7 +1965,7 @@ async function initProject(cwd, flags) {
|
|
|
1882
1965
|
}
|
|
1883
1966
|
}
|
|
1884
1967
|
if (!adapter.available) {
|
|
1885
|
-
prompts.cancel(`The ${adapter.label} adapter isn't available yet
|
|
1968
|
+
prompts.cancel(`The ${adapter.label} adapter isn't available yet - only Hono ships today.`);
|
|
1886
1969
|
return;
|
|
1887
1970
|
}
|
|
1888
1971
|
const configPath = (0, import_node_path15.join)(cwd, "giri.config.ts");
|
|
@@ -1906,6 +1989,16 @@ async function initProject(cwd, flags) {
|
|
|
1906
1989
|
await ensureTsConfig(cwd);
|
|
1907
1990
|
prompts.log.success(`scaffolded a ${adapter.label} project`);
|
|
1908
1991
|
const pm = flags.packageManager ?? detectPackageManager();
|
|
1992
|
+
const deps = await missingDeps(cwd, ["@boon4681/giri", ...adapter.deps, "zod"]);
|
|
1993
|
+
const devDeps = await missingDeps(cwd, ["typescript", "@types/node"]);
|
|
1994
|
+
if (deps.length === 0 && devDeps.length === 0) {
|
|
1995
|
+
prompts.outro("All dependencies already present. Run `giri serve` to start the dev server.");
|
|
1996
|
+
return;
|
|
1997
|
+
}
|
|
1998
|
+
const planLines = [
|
|
1999
|
+
...deps.length ? [` ${pm} ${installArgs(pm, deps, false).join(" ")}`] : [],
|
|
2000
|
+
...devDeps.length ? [` ${pm} ${installArgs(pm, devDeps, true).join(" ")}`] : []
|
|
2001
|
+
];
|
|
1909
2002
|
let install = flags.install;
|
|
1910
2003
|
if (install === void 0) {
|
|
1911
2004
|
if (!interactive) {
|
|
@@ -1913,34 +2006,34 @@ async function initProject(cwd, flags) {
|
|
|
1913
2006
|
} else {
|
|
1914
2007
|
const answer = await prompts.confirm({ message: `Install dependencies with ${pm}?` });
|
|
1915
2008
|
if (prompts.isCancel(answer)) {
|
|
1916
|
-
prompts.cancel("Cancelled
|
|
2009
|
+
prompts.cancel("Cancelled - files written, skipped install.");
|
|
1917
2010
|
return;
|
|
1918
2011
|
}
|
|
1919
2012
|
install = answer;
|
|
1920
2013
|
}
|
|
1921
2014
|
}
|
|
1922
|
-
const deps = ["@boon4681/giri", ...adapter.deps, "zod"];
|
|
1923
|
-
const devDeps = ["typescript", "@types/node"];
|
|
1924
2015
|
if (install) {
|
|
1925
2016
|
try {
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
2017
|
+
if (deps.length) {
|
|
2018
|
+
prompts.log.step(`Installing ${deps.join(", ")}`);
|
|
2019
|
+
await runCommand(pm, installArgs(pm, deps, false), cwd);
|
|
2020
|
+
}
|
|
2021
|
+
if (devDeps.length) {
|
|
2022
|
+
prompts.log.step(`Installing dev deps ${devDeps.join(", ")}`);
|
|
2023
|
+
await runCommand(pm, installArgs(pm, devDeps, true), cwd);
|
|
2024
|
+
}
|
|
1930
2025
|
} catch (error) {
|
|
1931
2026
|
prompts.log.error(error instanceof Error ? error.message : String(error));
|
|
1932
|
-
prompts.outro(`Install failed
|
|
2027
|
+
prompts.outro(`Install failed - run these yourself, then \`giri serve\`:
|
|
2028
|
+
${planLines.join("\n")}`);
|
|
1933
2029
|
return;
|
|
1934
2030
|
}
|
|
1935
2031
|
prompts.outro("Ready. Run `giri serve` to start the dev server.");
|
|
1936
2032
|
return;
|
|
1937
2033
|
}
|
|
1938
|
-
prompts.outro(
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
${pm} ${installArgs(pm, devDeps, true).join(" ")}
|
|
1942
|
-
giri serve`
|
|
1943
|
-
);
|
|
2034
|
+
prompts.outro(`Next:
|
|
2035
|
+
${planLines.join("\n")}
|
|
2036
|
+
giri serve`);
|
|
1944
2037
|
}
|
|
1945
2038
|
function displayHost(address) {
|
|
1946
2039
|
if (!address || address === "::" || address === "0.0.0.0") {
|
|
@@ -1949,69 +2042,120 @@ function displayHost(address) {
|
|
|
1949
2042
|
return address.includes(":") ? `[${address}]` : address;
|
|
1950
2043
|
}
|
|
1951
2044
|
async function serveProject(config, flags) {
|
|
1952
|
-
const
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
const
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
const
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
const
|
|
1973
|
-
hmrCount
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
const
|
|
1987
|
-
|
|
1988
|
-
|
|
2045
|
+
const { watch } = await import("fs");
|
|
2046
|
+
let stop;
|
|
2047
|
+
const boot = async (cfg) => {
|
|
2048
|
+
const initial = await syncProject(cfg);
|
|
2049
|
+
log2.success(
|
|
2050
|
+
`synced ${initial.routes.length} route${initial.routes.length === 1 ? "" : "s"} ${muted(`at ${initial.paths.outDir}`)}`,
|
|
2051
|
+
"sync"
|
|
2052
|
+
);
|
|
2053
|
+
const lifecycle = await loadLifecycle();
|
|
2054
|
+
const services = await runInit(lifecycle);
|
|
2055
|
+
let current = await buildGiriApp(cfg, { services });
|
|
2056
|
+
const port = flags.port ?? cfg.server?.port ?? 3e3;
|
|
2057
|
+
const hostname = flags.hostname ?? cfg.server?.hostname;
|
|
2058
|
+
const closers = [];
|
|
2059
|
+
if (flags.watch) {
|
|
2060
|
+
const srcDir = (0, import_node_path15.resolve)(current.paths.routesDir, "..");
|
|
2061
|
+
if ((0, import_node_fs9.existsSync)(srcDir)) {
|
|
2062
|
+
let timer;
|
|
2063
|
+
let syncing = false;
|
|
2064
|
+
const changed = /* @__PURE__ */ new Set();
|
|
2065
|
+
const updater = createWatchUpdater(cfg, initial);
|
|
2066
|
+
const hmrCount = /* @__PURE__ */ new Map();
|
|
2067
|
+
const bump = (key) => {
|
|
2068
|
+
const next = (hmrCount.get(key) ?? 0) + 1;
|
|
2069
|
+
hmrCount.set(key, next);
|
|
2070
|
+
return next;
|
|
2071
|
+
};
|
|
2072
|
+
const flush = async () => {
|
|
2073
|
+
if (syncing) {
|
|
2074
|
+
return;
|
|
2075
|
+
}
|
|
2076
|
+
syncing = true;
|
|
2077
|
+
try {
|
|
2078
|
+
while (changed.size > 0) {
|
|
2079
|
+
const batch = [...changed];
|
|
2080
|
+
changed.clear();
|
|
2081
|
+
for (const name of batch) {
|
|
2082
|
+
const outcome = await updater.apply(name || null);
|
|
2083
|
+
const rel = name ? `src/${name.replace(/\\/g, "/")}` : "src";
|
|
2084
|
+
log2.change(outcome === "full" ? "sync" : "update", rel, bump(rel));
|
|
2085
|
+
}
|
|
2086
|
+
current = await buildGiriApp(cfg, { services });
|
|
1989
2087
|
}
|
|
1990
|
-
|
|
2088
|
+
} catch (error) {
|
|
2089
|
+
log2.error(error instanceof Error ? error.message : String(error), "watch");
|
|
2090
|
+
} finally {
|
|
2091
|
+
syncing = false;
|
|
1991
2092
|
}
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
}
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
}
|
|
2093
|
+
if (changed.size > 0) {
|
|
2094
|
+
void flush();
|
|
2095
|
+
}
|
|
2096
|
+
};
|
|
2097
|
+
const watcher = watch(srcDir, { recursive: true }, (_event, filename) => {
|
|
2098
|
+
changed.add(filename ? filename.toString() : "");
|
|
2099
|
+
clearTimeout(timer);
|
|
2100
|
+
timer = setTimeout(() => void flush(), 150);
|
|
2101
|
+
});
|
|
2102
|
+
closers.push(() => {
|
|
2103
|
+
clearTimeout(timer);
|
|
2104
|
+
watcher.close();
|
|
2105
|
+
});
|
|
2106
|
+
}
|
|
2006
2107
|
}
|
|
2108
|
+
const handler = (request) => cfg.adapter.fetch(current.app, request);
|
|
2109
|
+
const server = cfg.adapter.serve(handler, { port, hostname }, (info) => {
|
|
2110
|
+
log2.ready(`http://${displayHost(info.address)}:${info.port}`);
|
|
2111
|
+
});
|
|
2112
|
+
stop = async () => {
|
|
2113
|
+
for (const close of closers) {
|
|
2114
|
+
await close();
|
|
2115
|
+
}
|
|
2116
|
+
try {
|
|
2117
|
+
await server.close();
|
|
2118
|
+
} catch {
|
|
2119
|
+
}
|
|
2120
|
+
if (lifecycle.teardown) {
|
|
2121
|
+
await lifecycle.teardown(services);
|
|
2122
|
+
}
|
|
2123
|
+
};
|
|
2124
|
+
};
|
|
2125
|
+
await boot(config);
|
|
2126
|
+
const configPath = flags.watch ? findConfigPath((0, import_node_path15.resolve)(process.cwd())) : void 0;
|
|
2127
|
+
if (configPath) {
|
|
2128
|
+
let timer;
|
|
2129
|
+
let restarting = false;
|
|
2130
|
+
const configName = (0, import_node_path15.basename)(configPath);
|
|
2131
|
+
const restart = async () => {
|
|
2132
|
+
if (restarting) {
|
|
2133
|
+
return;
|
|
2134
|
+
}
|
|
2135
|
+
restarting = true;
|
|
2136
|
+
try {
|
|
2137
|
+
log2.info(`${color.green("restart")} ${configName} changed`, "config");
|
|
2138
|
+
delete require.cache[configPath];
|
|
2139
|
+
const next = await load({ throwOnError: true });
|
|
2140
|
+
await stop?.();
|
|
2141
|
+
await boot(next);
|
|
2142
|
+
} catch (error) {
|
|
2143
|
+
log2.error(error instanceof Error ? error.message : String(error), "config");
|
|
2144
|
+
log2.info("kept the previous server running \u2014 fix the config and save again", "config");
|
|
2145
|
+
} finally {
|
|
2146
|
+
restarting = false;
|
|
2147
|
+
}
|
|
2148
|
+
};
|
|
2149
|
+
watch((0, import_node_path15.dirname)(configPath), { recursive: false }, (_event, filename) => {
|
|
2150
|
+
if (filename && (0, import_node_path15.basename)(filename.toString()) === configName) {
|
|
2151
|
+
clearTimeout(timer);
|
|
2152
|
+
timer = setTimeout(() => void restart(), 150);
|
|
2153
|
+
}
|
|
2154
|
+
});
|
|
2007
2155
|
}
|
|
2008
|
-
|
|
2009
|
-
const server = config.adapter.serve(handler, { port, hostname }, (info) => {
|
|
2010
|
-
log2.ready(`http://${displayHost(info.address)}:${info.port}`);
|
|
2011
|
-
});
|
|
2012
|
-
registerShutdown(server, lifecycle, services);
|
|
2156
|
+
registerShutdown(() => stop?.());
|
|
2013
2157
|
}
|
|
2014
|
-
function registerShutdown(
|
|
2158
|
+
function registerShutdown(cleanup) {
|
|
2015
2159
|
let shuttingDown = false;
|
|
2016
2160
|
const shutdown = async () => {
|
|
2017
2161
|
if (shuttingDown) {
|
|
@@ -2019,10 +2163,7 @@ function registerShutdown(server, lifecycle, services) {
|
|
|
2019
2163
|
}
|
|
2020
2164
|
shuttingDown = true;
|
|
2021
2165
|
try {
|
|
2022
|
-
await
|
|
2023
|
-
if (lifecycle.teardown) {
|
|
2024
|
-
await lifecycle.teardown(services);
|
|
2025
|
-
}
|
|
2166
|
+
await cleanup();
|
|
2026
2167
|
} catch (error) {
|
|
2027
2168
|
log2.error(error instanceof Error ? error.message : String(error));
|
|
2028
2169
|
process.exitCode = 1;
|