@rexeus/typeweaver-gen 0.10.5 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +384 -45
- package/dist/index.d.cts +25 -3
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +25 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +384 -45
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/errors/DerivedResponseCycleError.ts","../src/errors/DuplicateOperationIdError.ts","../src/errors/DuplicateRouteError.ts","../src/errors/EmptyOperationResponsesError.ts","../src/errors/EmptyResourceOperationsError.ts","../src/errors/EmptySpecResourcesError.ts","../src/errors/InvalidDerivedResponseError.ts","../src/errors/InvalidOperationIdError.ts","../src/NormalizedSpec.ts","../src/errors/InvalidRequestSchemaError.ts","../src/errors/InvalidResourceNameError.ts","../src/errors/MissingDerivedResponseParentError.ts","../src/errors/PathParameterMismatchError.ts","../src/normalizeSpec.ts","../src/plugins/types.ts","../src/plugins/BasePlugin.ts","../src/plugins/pluginRegistry.ts","../src/plugins/pluginContext.ts","../src/helpers/namingUtils.ts","../src/helpers/jsdoc.ts","../src/helpers/path.ts","../src/helpers/routePath.ts","../src/helpers/routeSort.ts","../src/helpers/templateEngine.ts"],"mappings":";;;cAAa,yBAAA,SAAkC,KAAA;cAC1B,YAAA;AAAA;;;cCDR,yBAAA,SAAkC,KAAA;cAC1B,WAAA;AAAA;;;cCDR,mBAAA,SAA4B,KAAA;cACpB,MAAA,UAAgB,IAAA,UAAc,cAAA;AAAA;;;cCDtC,4BAAA,SAAqC,KAAA;cAC7B,WAAA;AAAA;;;cCDR,4BAAA,SAAqC,KAAA;cAC7B,YAAA;AAAA;;;cCDR,uBAAA,SAAgC,KAAA;EAAA,WAAA,CAAA;AAAA;;;cCAhC,2BAAA,SAAoC,KAAA;cAC5B,YAAA;AAAA;;;cCDR,uBAAA,SAAgC,KAAA;cACxB,WAAA;AAAA;;;KCQT,cAAA;EAAA,SACD,SAAA,WAAoB,kBAAA;EAAA,SACpB,SAAA,WAAoB,kBAAA;AAAA;AAAA,
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/errors/DerivedResponseCycleError.ts","../src/errors/DuplicateOperationIdError.ts","../src/errors/DuplicateRouteError.ts","../src/errors/EmptyOperationResponsesError.ts","../src/errors/EmptyResourceOperationsError.ts","../src/errors/EmptySpecResourcesError.ts","../src/errors/InvalidDerivedResponseError.ts","../src/errors/InvalidOperationIdError.ts","../src/NormalizedSpec.ts","../src/errors/InvalidRequestSchemaError.ts","../src/errors/InvalidResourceNameError.ts","../src/errors/MissingDerivedResponseParentError.ts","../src/errors/PathParameterMismatchError.ts","../src/normalizeSpec.ts","../src/plugins/types.ts","../src/plugins/BasePlugin.ts","../src/plugins/pluginRegistry.ts","../src/plugins/pluginContext.ts","../src/helpers/namingUtils.ts","../src/helpers/jsdoc.ts","../src/helpers/path.ts","../src/helpers/routePath.ts","../src/helpers/routeSort.ts","../src/helpers/templateEngine.ts"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -99,6 +99,175 @@ var PathParameterMismatchError = class extends Error {
|
|
|
99
99
|
}
|
|
100
100
|
};
|
|
101
101
|
//#endregion
|
|
102
|
+
//#region src/bodyNormalization.ts
|
|
103
|
+
const JSON_BODY_SCHEMA_TYPES = new Set([
|
|
104
|
+
"array",
|
|
105
|
+
"boolean",
|
|
106
|
+
"enum",
|
|
107
|
+
"intersection",
|
|
108
|
+
"literal",
|
|
109
|
+
"null",
|
|
110
|
+
"number",
|
|
111
|
+
"object",
|
|
112
|
+
"record",
|
|
113
|
+
"tuple",
|
|
114
|
+
"union"
|
|
115
|
+
]);
|
|
116
|
+
const normalizeBody = (input) => {
|
|
117
|
+
if (input.bodySchema === void 0) return { warnings: [] };
|
|
118
|
+
const warnings = [];
|
|
119
|
+
const contentTypeHeader = extractContentTypeHeader(input.headerSchema);
|
|
120
|
+
if (contentTypeHeader.kind === "literal") return {
|
|
121
|
+
body: createNormalizedBody({
|
|
122
|
+
schema: input.bodySchema,
|
|
123
|
+
mediaType: contentTypeHeader.value,
|
|
124
|
+
mediaTypeSource: "content-type-header"
|
|
125
|
+
}),
|
|
126
|
+
warnings
|
|
127
|
+
};
|
|
128
|
+
if (contentTypeHeader.kind === "ambiguous") warnings.push({
|
|
129
|
+
code: "ambiguous-content-type-header",
|
|
130
|
+
message: "Content-Type header is present but does not have one unambiguous literal value; inferred body media type instead.",
|
|
131
|
+
location: input.location
|
|
132
|
+
});
|
|
133
|
+
else warnings.push({
|
|
134
|
+
code: "missing-content-type-header",
|
|
135
|
+
message: "Body schema is present without a Content-Type header; inferred body media type from schema.",
|
|
136
|
+
location: input.location
|
|
137
|
+
});
|
|
138
|
+
const inferred = inferBodyMediaType(input.bodySchema);
|
|
139
|
+
if (inferred.mediaTypeSource === "raw-fallback") warnings.push({
|
|
140
|
+
code: "raw-body-media-type-fallback",
|
|
141
|
+
message: "Body schema does not imply a concrete media type; used application/octet-stream raw transport fallback.",
|
|
142
|
+
location: input.location
|
|
143
|
+
});
|
|
144
|
+
return {
|
|
145
|
+
body: createNormalizedBody({
|
|
146
|
+
schema: input.bodySchema,
|
|
147
|
+
...inferred
|
|
148
|
+
}),
|
|
149
|
+
warnings
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
const createNormalizedBody = (input) => {
|
|
153
|
+
return {
|
|
154
|
+
schema: input.schema,
|
|
155
|
+
mediaType: input.mediaType,
|
|
156
|
+
mediaTypeSource: input.mediaTypeSource,
|
|
157
|
+
transport: resolveTransport(input.mediaType)
|
|
158
|
+
};
|
|
159
|
+
};
|
|
160
|
+
const extractContentTypeHeader = (headerSchema) => {
|
|
161
|
+
const headerObject = unwrapOptional(headerSchema);
|
|
162
|
+
if (headerObject === void 0 || !isZodObject$1(headerObject)) return { kind: "absent" };
|
|
163
|
+
const contentTypeEntries = Object.entries(headerObject.shape).filter(([headerName]) => headerName.toLowerCase() === "content-type");
|
|
164
|
+
if (contentTypeEntries.length === 0) return { kind: "absent" };
|
|
165
|
+
const literalValues = contentTypeEntries.flatMap(([, schema]) => extractStringLiteralValues(schema));
|
|
166
|
+
const distinctValues = new Set(literalValues);
|
|
167
|
+
if (contentTypeEntries.length === 1 && distinctValues.size === 1) {
|
|
168
|
+
const [value] = distinctValues;
|
|
169
|
+
return value === void 0 ? { kind: "ambiguous" } : {
|
|
170
|
+
kind: "literal",
|
|
171
|
+
value
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
return { kind: "ambiguous" };
|
|
175
|
+
};
|
|
176
|
+
const inferBodyMediaType = (schema) => {
|
|
177
|
+
const inferenceSchema = unwrapMediaInferenceSchema(schema);
|
|
178
|
+
const schemaType = getSchemaType(inferenceSchema);
|
|
179
|
+
if (isTextBodySchema(inferenceSchema)) return {
|
|
180
|
+
mediaType: "text/plain",
|
|
181
|
+
mediaTypeSource: "body-schema"
|
|
182
|
+
};
|
|
183
|
+
if (schemaType === "any" || schemaType === "unknown") return {
|
|
184
|
+
mediaType: "application/octet-stream",
|
|
185
|
+
mediaTypeSource: "raw-fallback"
|
|
186
|
+
};
|
|
187
|
+
if (schemaType !== void 0 && JSON_BODY_SCHEMA_TYPES.has(schemaType)) return {
|
|
188
|
+
mediaType: "application/json",
|
|
189
|
+
mediaTypeSource: "body-schema"
|
|
190
|
+
};
|
|
191
|
+
return {
|
|
192
|
+
mediaType: "application/octet-stream",
|
|
193
|
+
mediaTypeSource: "raw-fallback"
|
|
194
|
+
};
|
|
195
|
+
};
|
|
196
|
+
const isTextBodySchema = (schema) => {
|
|
197
|
+
const schemaType = getSchemaType(schema);
|
|
198
|
+
if (schemaType === "string") return true;
|
|
199
|
+
if (schemaType === "literal") {
|
|
200
|
+
const values = literalSchemaValues(schema);
|
|
201
|
+
return values.length > 0 && values.every((value) => typeof value === "string");
|
|
202
|
+
}
|
|
203
|
+
if (schemaType === "enum") {
|
|
204
|
+
const values = enumSchemaValues(schema);
|
|
205
|
+
return values.length > 0 && values.every((value) => typeof value === "string");
|
|
206
|
+
}
|
|
207
|
+
return false;
|
|
208
|
+
};
|
|
209
|
+
const resolveTransport = (mediaType) => {
|
|
210
|
+
const normalizedMediaType = mediaType.split(";")[0]?.trim().toLowerCase();
|
|
211
|
+
if (normalizedMediaType === "application/json" || normalizedMediaType?.endsWith("+json") === true) return "json";
|
|
212
|
+
if (normalizedMediaType?.startsWith("text/") === true) return "text";
|
|
213
|
+
if (normalizedMediaType === "application/x-www-form-urlencoded") return "form-url-encoded";
|
|
214
|
+
if (normalizedMediaType === "multipart/form-data") return "multipart";
|
|
215
|
+
return "raw";
|
|
216
|
+
};
|
|
217
|
+
const unwrapMediaInferenceSchema = (schema) => {
|
|
218
|
+
const visitedSchemas = /* @__PURE__ */ new Set();
|
|
219
|
+
let current = schema;
|
|
220
|
+
while (current !== void 0 && !visitedSchemas.has(current)) {
|
|
221
|
+
visitedSchemas.add(current);
|
|
222
|
+
const definition = getSchemaDefinition(current);
|
|
223
|
+
const schemaType = definition?.type;
|
|
224
|
+
if (schemaType === "optional" || schemaType === "nullable" || schemaType === "default" || schemaType === "catch" || schemaType === "prefault" || schemaType === "readonly") {
|
|
225
|
+
current = definition?.innerType;
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
if (schemaType === "pipe") {
|
|
229
|
+
const outputType = getSchemaType(definition?.out);
|
|
230
|
+
if (outputType === void 0 || outputType === "transform") return;
|
|
231
|
+
current = definition?.out;
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (schemaType === "effects") {
|
|
235
|
+
current = definition?.schema;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
return current;
|
|
239
|
+
}
|
|
240
|
+
return current;
|
|
241
|
+
};
|
|
242
|
+
const unwrapOptional = (schema) => {
|
|
243
|
+
return schema instanceof z.ZodOptional ? schema.unwrap() : schema;
|
|
244
|
+
};
|
|
245
|
+
const isZodObject$1 = (schema) => {
|
|
246
|
+
return getSchemaType(schema) === "object" && "shape" in schema;
|
|
247
|
+
};
|
|
248
|
+
const extractStringLiteralValues = (schema) => {
|
|
249
|
+
const unwrappedSchema = unwrapOptional(schema);
|
|
250
|
+
if (unwrappedSchema === void 0) return [];
|
|
251
|
+
if (getSchemaType(unwrappedSchema) === "literal") return literalSchemaValues(unwrappedSchema).filter((value) => typeof value === "string");
|
|
252
|
+
if (getSchemaType(unwrappedSchema) === "enum") return enumSchemaValues(unwrappedSchema).filter((value) => typeof value === "string");
|
|
253
|
+
return [];
|
|
254
|
+
};
|
|
255
|
+
const literalSchemaValues = (schema) => {
|
|
256
|
+
const literalSchema = schema;
|
|
257
|
+
return Array.from(literalSchema?.values ?? []);
|
|
258
|
+
};
|
|
259
|
+
const enumSchemaValues = (schema) => {
|
|
260
|
+
const enumSchema = schema;
|
|
261
|
+
return enumSchema?.options ?? enumSchema?.def?.values ?? Object.values(enumSchema?.def?.entries ?? enumSchema?.enum ?? {});
|
|
262
|
+
};
|
|
263
|
+
const getSchemaType = (schema) => {
|
|
264
|
+
return getSchemaDefinition(schema)?.type;
|
|
265
|
+
};
|
|
266
|
+
const getSchemaDefinition = (schema) => {
|
|
267
|
+
const schemaWithDefinition = schema;
|
|
268
|
+
return schemaWithDefinition?.def ?? schemaWithDefinition?._def;
|
|
269
|
+
};
|
|
270
|
+
//#endregion
|
|
102
271
|
//#region src/helpers/namingUtils.ts
|
|
103
272
|
const startsWithDigit = (value) => /^[0-9]/u.test(value);
|
|
104
273
|
const isSupportedIdentifierName = (value) => {
|
|
@@ -173,25 +342,49 @@ const validateInlineDerivedResponses = (definition, canonicalResponses) => {
|
|
|
173
342
|
validateDerivedResponseAgainstCanonicalGraph(response, canonicalResponses);
|
|
174
343
|
}
|
|
175
344
|
};
|
|
176
|
-
const normalizeResponseDefinition = (response) => {
|
|
345
|
+
const normalizeResponseDefinition = (response, location) => {
|
|
346
|
+
const body = normalizeBody({
|
|
347
|
+
bodySchema: response.body,
|
|
348
|
+
headerSchema: response.header,
|
|
349
|
+
location: {
|
|
350
|
+
...location,
|
|
351
|
+
part: "response.body"
|
|
352
|
+
}
|
|
353
|
+
});
|
|
177
354
|
return {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
355
|
+
response: {
|
|
356
|
+
name: response.name,
|
|
357
|
+
statusCode: response.statusCode,
|
|
358
|
+
statusCodeName: HttpStatusCodeNameMap[response.statusCode],
|
|
359
|
+
description: response.description,
|
|
360
|
+
header: response.header,
|
|
361
|
+
body: body.body,
|
|
362
|
+
kind: response.derived === void 0 ? "response" : "derived-response",
|
|
363
|
+
derivedFrom: response.derived?.parentName,
|
|
364
|
+
lineage: response.derived?.lineage,
|
|
365
|
+
depth: response.derived?.depth
|
|
366
|
+
},
|
|
367
|
+
warnings: body.warnings
|
|
188
368
|
};
|
|
189
369
|
};
|
|
190
370
|
const collectCanonicalResponses = (definition) => {
|
|
191
371
|
const canonicalResponseDefinitions = collectCanonicalResponseDefinitions(definition);
|
|
372
|
+
const warnings = [];
|
|
192
373
|
validateDerivedResponseGraph(canonicalResponseDefinitions);
|
|
193
374
|
validateInlineDerivedResponses(definition, canonicalResponseDefinitions);
|
|
194
|
-
|
|
375
|
+
const responses = /* @__PURE__ */ new Map();
|
|
376
|
+
for (const [responseName, response] of canonicalResponseDefinitions) {
|
|
377
|
+
const normalized = normalizeResponseDefinition(response, {
|
|
378
|
+
responseName,
|
|
379
|
+
statusCode: response.statusCode
|
|
380
|
+
});
|
|
381
|
+
responses.set(responseName, normalized.response);
|
|
382
|
+
warnings.push(...normalized.warnings);
|
|
383
|
+
}
|
|
384
|
+
return {
|
|
385
|
+
responses,
|
|
386
|
+
warnings
|
|
387
|
+
};
|
|
195
388
|
};
|
|
196
389
|
//#endregion
|
|
197
390
|
//#region src/normalizeSpec.ts
|
|
@@ -205,7 +398,7 @@ const validateRequestSchema = (operationId, requestPart, schema) => {
|
|
|
205
398
|
if (!isZodType(schema)) throw new InvalidRequestSchemaError(operationId, requestPart);
|
|
206
399
|
if (requestPart === "param" && !isZodObject(schema)) throw new InvalidRequestSchemaError(operationId, requestPart);
|
|
207
400
|
};
|
|
208
|
-
const validateRequest = (operationId, path, request) => {
|
|
401
|
+
const validateRequest = (resourceName, operationId, path, request) => {
|
|
209
402
|
if (request.header !== void 0) validateRequestSchema(operationId, "header", request.header);
|
|
210
403
|
if (request.param !== void 0) validateRequestSchema(operationId, "param", request.param);
|
|
211
404
|
if (request.query !== void 0) validateRequestSchema(operationId, "query", request.query);
|
|
@@ -213,28 +406,51 @@ const validateRequest = (operationId, path, request) => {
|
|
|
213
406
|
const pathParams = getPathParameterNames(path);
|
|
214
407
|
const requestParams = request.param === void 0 ? [] : Object.keys(request.param.shape);
|
|
215
408
|
if (pathParams.length !== requestParams.length || pathParams.some((pathParam) => !requestParams.includes(pathParam))) throw new PathParameterMismatchError(operationId, path, pathParams, requestParams);
|
|
216
|
-
if (request.header === void 0 && request.param === void 0 && request.query === void 0 && request.body === void 0) return;
|
|
409
|
+
if (request.header === void 0 && request.param === void 0 && request.query === void 0 && request.body === void 0) return { warnings: [] };
|
|
410
|
+
const body = normalizeBody({
|
|
411
|
+
bodySchema: request.body,
|
|
412
|
+
headerSchema: request.header,
|
|
413
|
+
location: {
|
|
414
|
+
resourceName,
|
|
415
|
+
operationId,
|
|
416
|
+
part: "request.body"
|
|
417
|
+
}
|
|
418
|
+
});
|
|
217
419
|
return {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
420
|
+
request: {
|
|
421
|
+
header: request.header,
|
|
422
|
+
param: request.param,
|
|
423
|
+
query: request.query,
|
|
424
|
+
body: body.body
|
|
425
|
+
},
|
|
426
|
+
warnings: body.warnings
|
|
222
427
|
};
|
|
223
428
|
};
|
|
224
|
-
const normalizeOperationResponses = (responses) => {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
429
|
+
const normalizeOperationResponses = (resourceName, operationId, responses) => {
|
|
430
|
+
const warnings = [];
|
|
431
|
+
return {
|
|
432
|
+
responses: responses.map((response) => {
|
|
433
|
+
if (isNamedResponseDefinition(response)) return {
|
|
434
|
+
responseName: response.name,
|
|
435
|
+
source: "canonical"
|
|
436
|
+
};
|
|
437
|
+
const normalized = normalizeResponseDefinition(response, {
|
|
438
|
+
resourceName,
|
|
439
|
+
operationId,
|
|
440
|
+
responseName: response.name,
|
|
441
|
+
statusCode: response.statusCode
|
|
442
|
+
});
|
|
443
|
+
warnings.push(...normalized.warnings);
|
|
444
|
+
return {
|
|
445
|
+
responseName: response.name,
|
|
446
|
+
source: "inline",
|
|
447
|
+
response: normalized.response
|
|
448
|
+
};
|
|
449
|
+
}),
|
|
450
|
+
warnings
|
|
451
|
+
};
|
|
236
452
|
};
|
|
237
|
-
const normalizeOperation = (operationIds, routeKeys, operation) => {
|
|
453
|
+
const normalizeOperation = (resourceName, operationIds, routeKeys, operation) => {
|
|
238
454
|
if (!isSupportedOperationId(operation.operationId)) throw new InvalidOperationIdError(operation.operationId);
|
|
239
455
|
if (operationIds.has(operation.operationId)) throw new DuplicateOperationIdError(operation.operationId);
|
|
240
456
|
operationIds.add(operation.operationId);
|
|
@@ -243,13 +459,18 @@ const normalizeOperation = (operationIds, routeKeys, operation) => {
|
|
|
243
459
|
if (routeKeys.has(routeKey)) throw new DuplicateRouteError(operation.method, operation.path, normalizedPath);
|
|
244
460
|
routeKeys.add(routeKey);
|
|
245
461
|
if (operation.responses.length === 0) throw new EmptyOperationResponsesError(operation.operationId);
|
|
462
|
+
const request = validateRequest(resourceName, operation.operationId, operation.path, operation.request);
|
|
463
|
+
const responses = normalizeOperationResponses(resourceName, operation.operationId, operation.responses);
|
|
246
464
|
return {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
465
|
+
operation: {
|
|
466
|
+
operationId: operation.operationId,
|
|
467
|
+
method: operation.method,
|
|
468
|
+
path: operation.path,
|
|
469
|
+
summary: operation.summary,
|
|
470
|
+
request: request.request,
|
|
471
|
+
responses: responses.responses
|
|
472
|
+
},
|
|
473
|
+
warnings: [...request.warnings, ...responses.warnings]
|
|
253
474
|
};
|
|
254
475
|
};
|
|
255
476
|
const normalizeSpec = (definition) => {
|
|
@@ -259,16 +480,22 @@ const normalizeSpec = (definition) => {
|
|
|
259
480
|
const canonicalResponses = collectCanonicalResponses(definition);
|
|
260
481
|
const operationIds = /* @__PURE__ */ new Set();
|
|
261
482
|
const routeKeys = /* @__PURE__ */ new Set();
|
|
483
|
+
const warnings = [...canonicalResponses.warnings];
|
|
262
484
|
return {
|
|
263
485
|
resources: resourceEntries.map(([resourceName, resource]) => {
|
|
264
486
|
if (!isSupportedResourceName(resourceName)) throw new InvalidResourceNameError(resourceName);
|
|
265
487
|
if (resource.operations.length === 0) throw new EmptyResourceOperationsError(resourceName);
|
|
266
488
|
return {
|
|
267
489
|
name: resourceName,
|
|
268
|
-
operations: resource.operations.map((operation) =>
|
|
490
|
+
operations: resource.operations.map((operation) => {
|
|
491
|
+
const normalized = normalizeOperation(resourceName, operationIds, routeKeys, operation);
|
|
492
|
+
warnings.push(...normalized.warnings);
|
|
493
|
+
return normalized.operation;
|
|
494
|
+
})
|
|
269
495
|
};
|
|
270
496
|
}),
|
|
271
|
-
responses: Array.from(canonicalResponses.values())
|
|
497
|
+
responses: Array.from(canonicalResponses.responses.values()),
|
|
498
|
+
warnings
|
|
272
499
|
};
|
|
273
500
|
};
|
|
274
501
|
//#endregion
|
|
@@ -453,6 +680,112 @@ var MissingCanonicalResponseError = class extends Error {
|
|
|
453
680
|
};
|
|
454
681
|
//#endregion
|
|
455
682
|
//#region src/plugins/pluginContext.ts
|
|
683
|
+
const WINDOWS_DRIVE_PREFIX_PATTERN = /^[a-zA-Z]:/;
|
|
684
|
+
function pathContainsParentTraversal(projectPath) {
|
|
685
|
+
return projectPath.split("/").includes("..");
|
|
686
|
+
}
|
|
687
|
+
function pathEndsWithDirectorySeparator(projectPath) {
|
|
688
|
+
return projectPath.endsWith("/");
|
|
689
|
+
}
|
|
690
|
+
function pathNamesCurrentDirectory(projectPath) {
|
|
691
|
+
return projectPath === "." || projectPath.endsWith("/.");
|
|
692
|
+
}
|
|
693
|
+
function resolveSafeGeneratedFilePath(outputDir, requestedPath) {
|
|
694
|
+
if (requestedPath.length === 0) throwUnsafeGeneratedFilePath(requestedPath, "path must not be empty");
|
|
695
|
+
const projectPath = requestedPath.replace(/\\/g, "/");
|
|
696
|
+
if (path.isAbsolute(requestedPath) || path.posix.isAbsolute(projectPath) || path.win32.isAbsolute(requestedPath) || path.win32.isAbsolute(projectPath) || WINDOWS_DRIVE_PREFIX_PATTERN.test(requestedPath)) throwUnsafeGeneratedFilePath(requestedPath, "absolute paths are not allowed");
|
|
697
|
+
if (pathContainsParentTraversal(projectPath)) throwUnsafeGeneratedFilePath(requestedPath, "path contains parent-directory traversal");
|
|
698
|
+
if (pathEndsWithDirectorySeparator(projectPath)) throwUnsafeGeneratedFilePath(requestedPath, "path must name a file inside the output directory");
|
|
699
|
+
if (pathNamesCurrentDirectory(projectPath)) throwUnsafeGeneratedFilePath(requestedPath, "path must name a file inside the output directory");
|
|
700
|
+
const generatedPath = path.posix.normalize(projectPath);
|
|
701
|
+
if (generatedPath === ".") throwUnsafeGeneratedFilePath(requestedPath, "path must name a file inside the output directory");
|
|
702
|
+
if (pathContainsParentTraversal(generatedPath)) throwUnsafeGeneratedFilePath(requestedPath, "path contains parent-directory traversal");
|
|
703
|
+
const outputRoot = path.resolve(outputDir);
|
|
704
|
+
const fullPath = path.resolve(outputRoot, toNativePath(generatedPath));
|
|
705
|
+
if (!isStrictlyInsidePath(fullPath, outputRoot)) throwUnsafeGeneratedFilePath(requestedPath, "path escapes the output directory");
|
|
706
|
+
assertGeneratedPathHasNoSymlinkComponents({
|
|
707
|
+
outputRoot,
|
|
708
|
+
generatedPath,
|
|
709
|
+
requestedPath
|
|
710
|
+
});
|
|
711
|
+
return {
|
|
712
|
+
fullPath,
|
|
713
|
+
generatedPath
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
function toNativePath(projectPath) {
|
|
717
|
+
return projectPath.split("/").join(path.sep);
|
|
718
|
+
}
|
|
719
|
+
function assertGeneratedPathHasNoSymlinkComponents(config) {
|
|
720
|
+
assertExistingPathIsNotSymlink(config.outputRoot, config.requestedPath);
|
|
721
|
+
let currentPath = config.outputRoot;
|
|
722
|
+
for (const segment of config.generatedPath.split("/")) {
|
|
723
|
+
currentPath = path.join(currentPath, segment);
|
|
724
|
+
const pathStats = getExistingPathStats(currentPath);
|
|
725
|
+
if (pathStats === void 0) return;
|
|
726
|
+
assertPathStatsIsNotSymlink(pathStats, config.requestedPath);
|
|
727
|
+
if (!pathStats.isDirectory()) return;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
function assertExistingPathIsNotSymlink(absolutePath, requestedPath) {
|
|
731
|
+
const pathStats = getExistingPathStats(absolutePath);
|
|
732
|
+
if (pathStats === void 0) return;
|
|
733
|
+
assertPathStatsIsNotSymlink(pathStats, requestedPath);
|
|
734
|
+
}
|
|
735
|
+
function assertPathStatsIsNotSymlink(pathStats, requestedPath) {
|
|
736
|
+
if (!pathStats.isSymbolicLink()) return;
|
|
737
|
+
throwUnsafeGeneratedFilePath(requestedPath, "path contains a symbolic link");
|
|
738
|
+
}
|
|
739
|
+
function getExistingPathStats(absolutePath) {
|
|
740
|
+
try {
|
|
741
|
+
return fs.lstatSync(absolutePath);
|
|
742
|
+
} catch (error) {
|
|
743
|
+
if (isMissingPathError(error)) return;
|
|
744
|
+
throw error;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
function isMissingPathError(error) {
|
|
748
|
+
if (!(error instanceof Error)) return false;
|
|
749
|
+
return ["ENOENT", "ENOTDIR"].includes(error.code ?? "");
|
|
750
|
+
}
|
|
751
|
+
function isStrictlyInsidePath(childPath, parentPath) {
|
|
752
|
+
const relativePath = path.relative(parentPath, childPath);
|
|
753
|
+
return relativePath !== "" && relativePath !== ".." && !relativePath.startsWith(`..${path.sep}`) && !path.isAbsolute(relativePath);
|
|
754
|
+
}
|
|
755
|
+
function throwUnsafeGeneratedFilePath(requestedPath, reason) {
|
|
756
|
+
throw new Error(`Unsafe generated file path '${requestedPath}': ${reason}. Generated writes must stay inside the output directory.`);
|
|
757
|
+
}
|
|
758
|
+
function revalidateGeneratedWritePath(outputDir, generatedPath) {
|
|
759
|
+
return resolveSafeGeneratedFilePath(outputDir, generatedPath);
|
|
760
|
+
}
|
|
761
|
+
function writeGeneratedFileByReplacingDestination(config) {
|
|
762
|
+
const existingFileMode = getExistingFileMode(revalidateGeneratedWritePath(config.outputDir, config.generatedPath).fullPath);
|
|
763
|
+
const tempParentPath = revalidateGeneratedWritePath(config.outputDir, config.generatedPath);
|
|
764
|
+
const destinationDir = path.dirname(tempParentPath.fullPath);
|
|
765
|
+
const tempDir = fs.mkdtempSync(path.join(destinationDir, ".typeweaver-"));
|
|
766
|
+
const tempFile = path.join(tempDir, "generated.tmp");
|
|
767
|
+
try {
|
|
768
|
+
revalidateGeneratedWritePath(config.outputDir, config.generatedPath);
|
|
769
|
+
fs.writeFileSync(tempFile, config.content, {
|
|
770
|
+
flag: "wx",
|
|
771
|
+
mode: existingFileMode ?? 438
|
|
772
|
+
});
|
|
773
|
+
if (existingFileMode !== void 0) fs.chmodSync(tempFile, existingFileMode);
|
|
774
|
+
const writablePath = revalidateGeneratedWritePath(config.outputDir, config.generatedPath);
|
|
775
|
+
fs.renameSync(tempFile, writablePath.fullPath);
|
|
776
|
+
return writablePath;
|
|
777
|
+
} finally {
|
|
778
|
+
fs.rmSync(tempDir, {
|
|
779
|
+
recursive: true,
|
|
780
|
+
force: true
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
function getExistingFileMode(absolutePath) {
|
|
785
|
+
const pathStats = getExistingPathStats(absolutePath);
|
|
786
|
+
if (pathStats?.isFile() !== true) return;
|
|
787
|
+
return pathStats.mode & 511;
|
|
788
|
+
}
|
|
456
789
|
function createPluginContextBuilder() {
|
|
457
790
|
const generatedFiles = /* @__PURE__ */ new Set();
|
|
458
791
|
const createPluginContext = (params) => {
|
|
@@ -518,19 +851,25 @@ function createPluginContextBuilder() {
|
|
|
518
851
|
getOperationOutputPaths,
|
|
519
852
|
getResourceOutputDir,
|
|
520
853
|
writeFile: (relativePath, content) => {
|
|
521
|
-
const
|
|
522
|
-
const dir = path.dirname(fullPath);
|
|
854
|
+
const safePath = resolveSafeGeneratedFilePath(params.outputDir, relativePath);
|
|
855
|
+
const dir = path.dirname(safePath.fullPath);
|
|
523
856
|
fs.mkdirSync(dir, { recursive: true });
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
857
|
+
const writablePath = revalidateGeneratedWritePath(params.outputDir, safePath.generatedPath);
|
|
858
|
+
const generatedFile = writeGeneratedFileByReplacingDestination({
|
|
859
|
+
outputDir: params.outputDir,
|
|
860
|
+
generatedPath: writablePath.generatedPath,
|
|
861
|
+
content
|
|
862
|
+
});
|
|
863
|
+
generatedFiles.add(generatedFile.generatedPath);
|
|
864
|
+
console.info(`Generated: ${generatedFile.generatedPath}`);
|
|
527
865
|
},
|
|
528
866
|
renderTemplate: (templatePath, data) => {
|
|
529
867
|
const fullTemplatePath = path.isAbsolute(templatePath) ? templatePath : path.join(params.templateDir, templatePath);
|
|
530
868
|
return renderTemplate(fs.readFileSync(fullTemplatePath, "utf8"), data ?? {});
|
|
531
869
|
},
|
|
532
870
|
addGeneratedFile: (relativePath) => {
|
|
533
|
-
|
|
871
|
+
const safePath = resolveSafeGeneratedFilePath(params.outputDir, relativePath);
|
|
872
|
+
generatedFiles.add(safePath.generatedPath);
|
|
534
873
|
},
|
|
535
874
|
getGeneratedFiles: () => {
|
|
536
875
|
return Array.from(generatedFiles);
|