vovk-cli 0.0.1-draft.35 → 0.0.1-draft.350
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/LICENSE +1 -1
- package/README.md +29 -1
- package/client-templates/cjs/index.cjs.ejs +20 -0
- package/client-templates/cjs/index.d.cts.ejs +22 -0
- package/client-templates/mixins/mixins.d.ts.ejs +64 -0
- package/client-templates/mixins/mixins.json.ejs +1 -0
- package/client-templates/mjs/index.d.mts.ejs +22 -0
- package/client-templates/mjs/index.mjs.ejs +18 -0
- package/client-templates/openapiCjs/openapi.cjs.ejs +4 -0
- package/client-templates/openapiCjs/openapi.d.cts.ejs +4 -0
- package/client-templates/openapiJson/openapi.json.ejs +1 -0
- package/client-templates/openapiTs/openapi.ts.ejs +4 -0
- package/client-templates/packageJson/package.json.ejs +1 -0
- package/client-templates/readme/README.md.ejs +39 -0
- package/client-templates/schemaCjs/schema.cjs.ejs +23 -0
- package/client-templates/schemaCjs/schema.d.cts.ejs +10 -0
- package/client-templates/schemaJson/schema.json.ejs +1 -0
- package/client-templates/schemaTs/schema.ts.ejs +31 -0
- package/client-templates/ts/index.ts.ejs +27 -0
- package/dist/bundle/index.d.mts +8 -0
- package/dist/bundle/index.mjs +102 -0
- package/dist/dev/diffSegmentSchema.d.mts +36 -0
- package/dist/dev/{diffSchema.mjs → diffSegmentSchema.mjs} +3 -11
- package/dist/dev/ensureSchemaFiles.d.mts +4 -1
- package/dist/dev/ensureSchemaFiles.mjs +15 -31
- package/dist/dev/index.d.mts +5 -1
- package/dist/dev/index.mjs +192 -80
- package/dist/dev/logDiffResult.d.mts +1 -1
- package/dist/dev/logDiffResult.mjs +6 -43
- package/dist/dev/writeMetaJson.d.mts +2 -0
- package/dist/dev/writeMetaJson.mjs +20 -0
- package/dist/dev/writeOneSegmentSchemaFile.d.mts +12 -0
- package/dist/dev/{writeOneSchemaFile.mjs → writeOneSegmentSchemaFile.mjs} +10 -6
- package/dist/generate/ensureClient.d.mts +3 -0
- package/dist/generate/ensureClient.mjs +28 -0
- package/dist/generate/generate.d.mts +13 -0
- package/dist/generate/generate.mjs +296 -0
- package/dist/generate/getClientTemplateFiles.d.mts +20 -0
- package/dist/generate/getClientTemplateFiles.mjs +85 -0
- package/dist/generate/getProjectFullSchema.d.mts +8 -0
- package/dist/generate/getProjectFullSchema.mjs +64 -0
- package/dist/generate/getTemplateClientImports.d.mts +19 -0
- package/dist/generate/getTemplateClientImports.mjs +49 -0
- package/dist/generate/index.d.mts +33 -0
- package/dist/generate/index.mjs +190 -0
- package/dist/generate/writeOneClientFile.d.mts +41 -0
- package/dist/generate/writeOneClientFile.mjs +136 -0
- package/dist/getProjectInfo/getConfig/getConfigAbsolutePaths.d.mts +5 -0
- package/dist/getProjectInfo/{getConfigAbsolutePaths.mjs → getConfig/getConfigAbsolutePaths.mjs} +4 -1
- package/dist/getProjectInfo/{getRelativeSrcRoot.d.mts → getConfig/getRelativeSrcRoot.d.mts} +1 -1
- package/dist/getProjectInfo/{getRelativeSrcRoot.mjs → getConfig/getRelativeSrcRoot.mjs} +2 -2
- package/dist/getProjectInfo/getConfig/getTemplateDefs.d.mts +24 -0
- package/dist/getProjectInfo/getConfig/getTemplateDefs.mjs +165 -0
- package/dist/getProjectInfo/{getUserConfig.d.mts → getConfig/getUserConfig.d.mts} +3 -2
- package/dist/getProjectInfo/{getUserConfig.mjs → getConfig/getUserConfig.mjs} +3 -3
- package/dist/getProjectInfo/{importUncachedModule.mjs → getConfig/importUncachedModule.mjs} +1 -4
- package/dist/getProjectInfo/getConfig/index.d.mts +76 -0
- package/dist/getProjectInfo/getConfig/index.mjs +91 -0
- package/dist/getProjectInfo/getMetaSchema.d.mts +8 -0
- package/dist/getProjectInfo/getMetaSchema.mjs +14 -0
- package/dist/getProjectInfo/index.d.mts +14 -9
- package/dist/getProjectInfo/index.mjs +24 -22
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +118 -36
- package/dist/init/createConfig.d.mts +2 -2
- package/dist/init/createConfig.mjs +39 -13
- package/dist/init/createStandardSchemaValidatorFile.d.mts +4 -0
- package/dist/init/createStandardSchemaValidatorFile.mjs +39 -0
- package/dist/init/getTemplateFilesFromPackage.mjs +10 -5
- package/dist/init/index.d.mts +2 -2
- package/dist/init/index.mjs +114 -66
- package/dist/init/installDependencies.mjs +4 -2
- package/dist/init/logUpdateDependenciesError.d.mts +3 -1
- package/dist/init/logUpdateDependenciesError.mjs +7 -1
- package/dist/init/updateDependenciesWithoutInstalling.mjs +39 -9
- package/dist/init/updateNPMScripts.d.mts +3 -1
- package/dist/init/updateNPMScripts.mjs +10 -7
- package/dist/init/updateTypeScriptConfig.d.mts +4 -1
- package/dist/init/updateTypeScriptConfig.mjs +11 -7
- package/dist/initProgram.d.mts +1 -1
- package/dist/initProgram.mjs +17 -17
- package/dist/locateSegments.d.mts +8 -1
- package/dist/locateSegments.mjs +13 -3
- package/dist/new/addClassToSegmentCode.d.mts +1 -2
- package/dist/new/addClassToSegmentCode.mjs +3 -3
- package/dist/new/index.d.mts +2 -1
- package/dist/new/index.mjs +4 -2
- package/dist/new/newModule.d.mts +4 -1
- package/dist/new/newModule.mjs +18 -17
- package/dist/new/newSegment.d.mts +4 -1
- package/dist/new/newSegment.mjs +19 -11
- package/dist/new/render.d.mts +7 -3
- package/dist/new/render.mjs +29 -8
- package/dist/types.d.mts +64 -42
- package/dist/utils/compileJSONSchemaToTypeScriptType.d.mts +5 -0
- package/dist/utils/compileJSONSchemaToTypeScriptType.mjs +9 -0
- package/dist/utils/compileTs.d.mts +12 -0
- package/dist/utils/compileTs.mjs +261 -0
- package/dist/utils/debounceWithArgs.d.mts +2 -2
- package/dist/utils/debounceWithArgs.mjs +24 -6
- package/dist/utils/formatLoggedSegmentName.d.mts +3 -1
- package/dist/utils/formatLoggedSegmentName.mjs +3 -2
- package/dist/utils/generateFnName.d.mts +23 -0
- package/dist/utils/generateFnName.mjs +76 -0
- package/dist/utils/getPackageJson.d.mts +3 -0
- package/dist/utils/getPackageJson.mjs +22 -0
- package/dist/utils/getPublicModuleNameFromPath.d.mts +4 -0
- package/dist/utils/getPublicModuleNameFromPath.mjs +9 -0
- package/dist/utils/normalizeOpenAPIMixin.d.mts +14 -0
- package/dist/utils/normalizeOpenAPIMixin.mjs +114 -0
- package/dist/utils/pickSegmentFullSchema.d.mts +3 -0
- package/dist/utils/pickSegmentFullSchema.mjs +15 -0
- package/dist/utils/removeUnlistedDirectories.d.mts +10 -0
- package/dist/utils/removeUnlistedDirectories.mjs +61 -0
- package/dist/utils/resolveAbsoluteModulePath.d.mts +2 -0
- package/dist/utils/resolveAbsoluteModulePath.mjs +32 -0
- package/module-templates/arktype/controller.ts.ejs +68 -0
- package/module-templates/type/controller.ts.ejs +56 -0
- package/module-templates/type/service.ts.ejs +28 -0
- package/module-templates/valibot/controller.ts.ejs +68 -0
- package/package.json +35 -22
- package/dist/dev/diffSchema.d.mts +0 -43
- package/dist/dev/ensureClient.d.mts +0 -5
- package/dist/dev/ensureClient.mjs +0 -31
- package/dist/dev/isMetadataEmpty.d.mts +0 -2
- package/dist/dev/isMetadataEmpty.mjs +0 -4
- package/dist/dev/writeOneSchemaFile.d.mts +0 -11
- package/dist/generateClient.d.mts +0 -7
- package/dist/generateClient.mjs +0 -97
- package/dist/getProjectInfo/getConfig.d.mts +0 -11
- package/dist/getProjectInfo/getConfig.mjs +0 -29
- package/dist/getProjectInfo/getConfigAbsolutePaths.d.mts +0 -4
- package/dist/postinstall.d.mts +0 -1
- package/dist/postinstall.mjs +0 -24
- package/templates/controller.ejs +0 -52
- package/templates/service.ejs +0 -27
- package/templates/worker.ejs +0 -24
- /package/dist/getProjectInfo/{importUncachedModule.d.mts → getConfig/importUncachedModule.d.mts} +0 -0
- /package/dist/getProjectInfo/{importUncachedModuleWorker.d.mts → getConfig/importUncachedModuleWorker.d.mts} +0 -0
- /package/dist/getProjectInfo/{importUncachedModuleWorker.mjs → getConfig/importUncachedModuleWorker.mjs} +0 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import upperFirst from 'lodash/upperFirst.js';
|
|
2
|
+
import camelCase from 'lodash/camelCase.js';
|
|
3
|
+
export function compileTs(options) {
|
|
4
|
+
const context = {
|
|
5
|
+
refs: options.refs || new Map(),
|
|
6
|
+
compiledRefs: new Map(),
|
|
7
|
+
refsInProgress: new Set(),
|
|
8
|
+
};
|
|
9
|
+
// Collect all definitions from the schema
|
|
10
|
+
collectDefinitions(options.schema, context.refs);
|
|
11
|
+
// Ensure the main type name is valid
|
|
12
|
+
const mainTypeName = sanitizeTypeName(options.name);
|
|
13
|
+
const mainType = compileSchema(options.schema, mainTypeName, context);
|
|
14
|
+
// Compile all referenced types, unless dontCreateRefTypes is set
|
|
15
|
+
const compiledRefs = options.dontCreateRefTypes
|
|
16
|
+
? ''
|
|
17
|
+
: Array.from(context.compiledRefs.entries())
|
|
18
|
+
.map(([, typeDecl]) => typeDecl)
|
|
19
|
+
.join('\n\n');
|
|
20
|
+
return compiledRefs
|
|
21
|
+
? `${compiledRefs}\n\n${options.schema.description ? `/** ${escapeJSDocComment(options.schema.description)} */\n` : ''}export type ${mainTypeName} = ${mainType};`
|
|
22
|
+
: `${options.schema.description ? `/** ${escapeJSDocComment(options.schema.description)} */\n` : ''}export type ${mainTypeName} = ${mainType};`;
|
|
23
|
+
}
|
|
24
|
+
function collectDefinitions(schema, refs) {
|
|
25
|
+
// Collect from $defs
|
|
26
|
+
if (schema.$defs) {
|
|
27
|
+
Object.entries(schema.$defs).forEach(([key, def]) => {
|
|
28
|
+
if (typeof def === 'object') {
|
|
29
|
+
refs.set(`#/$defs/${key}`, def);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
// Collect from definitions (older spec)
|
|
34
|
+
if (schema.definitions) {
|
|
35
|
+
Object.entries(schema.definitions).forEach(([key, def]) => {
|
|
36
|
+
if (typeof def === 'object') {
|
|
37
|
+
refs.set(`#/definitions/${key}`, def);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
// Collect from components/schemas (OpenAPI spec)
|
|
42
|
+
if (schema?.components?.schemas) {
|
|
43
|
+
Object.entries(schema?.components?.schemas ?? {}).forEach(([key, def]) => {
|
|
44
|
+
if (typeof def === 'object') {
|
|
45
|
+
refs.set(`#/components/schemas/${key}`, def);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
// Recursively collect from nested schemas
|
|
50
|
+
const schemasToProcess = [];
|
|
51
|
+
if (schema.properties) {
|
|
52
|
+
schemasToProcess.push(...Object.values(schema.properties).filter(isSchema));
|
|
53
|
+
}
|
|
54
|
+
if (schema.items) {
|
|
55
|
+
if (Array.isArray(schema.items)) {
|
|
56
|
+
schemasToProcess.push(...schema.items.filter(isSchema));
|
|
57
|
+
}
|
|
58
|
+
else if (isSchema(schema.items)) {
|
|
59
|
+
schemasToProcess.push(schema.items);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (schema.additionalProperties && isSchema(schema.additionalProperties)) {
|
|
63
|
+
schemasToProcess.push(schema.additionalProperties);
|
|
64
|
+
}
|
|
65
|
+
if (schema.allOf)
|
|
66
|
+
schemasToProcess.push(...schema.allOf.filter(isSchema));
|
|
67
|
+
if (schema.anyOf)
|
|
68
|
+
schemasToProcess.push(...schema.anyOf.filter(isSchema));
|
|
69
|
+
if (schema.oneOf)
|
|
70
|
+
schemasToProcess.push(...schema.oneOf.filter(isSchema));
|
|
71
|
+
schemasToProcess.forEach((s) => collectDefinitions(s, refs));
|
|
72
|
+
}
|
|
73
|
+
function isSchema(value) {
|
|
74
|
+
return typeof value === 'object' && !Array.isArray(value);
|
|
75
|
+
}
|
|
76
|
+
function compileSchema(schema, name, context) {
|
|
77
|
+
if (typeof schema === 'boolean') {
|
|
78
|
+
return schema ? 'any' : 'never';
|
|
79
|
+
}
|
|
80
|
+
// Handle x-tsType extension
|
|
81
|
+
if ('x-tsType' in schema && typeof schema['x-tsType'] === 'string') {
|
|
82
|
+
return schema['x-tsType'];
|
|
83
|
+
}
|
|
84
|
+
// Handle $ref
|
|
85
|
+
if (schema.$ref) {
|
|
86
|
+
return handleRef(schema.$ref, context);
|
|
87
|
+
}
|
|
88
|
+
// Handle combinators
|
|
89
|
+
if (schema.allOf) {
|
|
90
|
+
return handleAllOf(schema.allOf, name, context);
|
|
91
|
+
}
|
|
92
|
+
if (schema.anyOf) {
|
|
93
|
+
return handleAnyOf(schema.anyOf, name, context);
|
|
94
|
+
}
|
|
95
|
+
if (schema.oneOf) {
|
|
96
|
+
return handleOneOf(schema.oneOf, name, context);
|
|
97
|
+
}
|
|
98
|
+
// Handle type-specific compilation
|
|
99
|
+
if (schema.enum) {
|
|
100
|
+
return handleEnum(schema.enum);
|
|
101
|
+
}
|
|
102
|
+
if (schema.const !== undefined) {
|
|
103
|
+
return handleConst(schema.const);
|
|
104
|
+
}
|
|
105
|
+
if (!schema.type) {
|
|
106
|
+
// No type specified, could be object
|
|
107
|
+
if (schema.properties || schema.additionalProperties) {
|
|
108
|
+
return handleObject(schema, name, context);
|
|
109
|
+
}
|
|
110
|
+
return 'any';
|
|
111
|
+
}
|
|
112
|
+
if (Array.isArray(schema.type)) {
|
|
113
|
+
return schema.type.map((t) => compileSchemaWithType({ ...schema, type: t }, name, context)).join(' | ');
|
|
114
|
+
}
|
|
115
|
+
return compileSchemaWithType(schema, name, context);
|
|
116
|
+
}
|
|
117
|
+
function compileSchemaWithType(schema, name, context) {
|
|
118
|
+
switch (schema.type) {
|
|
119
|
+
case 'null':
|
|
120
|
+
return 'null';
|
|
121
|
+
case 'boolean':
|
|
122
|
+
return 'boolean';
|
|
123
|
+
case 'string':
|
|
124
|
+
return 'string';
|
|
125
|
+
case 'number':
|
|
126
|
+
return 'number';
|
|
127
|
+
case 'integer':
|
|
128
|
+
return 'number';
|
|
129
|
+
case 'array':
|
|
130
|
+
return handleArray(schema, name, context);
|
|
131
|
+
case 'object':
|
|
132
|
+
return handleObject(schema, name, context);
|
|
133
|
+
default:
|
|
134
|
+
return 'any';
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function handleRef(ref, context) {
|
|
138
|
+
const typeName = refToTypeName(ref);
|
|
139
|
+
// Check if we're already compiling this ref (circular reference)
|
|
140
|
+
if (context.refsInProgress.has(ref)) {
|
|
141
|
+
return typeName;
|
|
142
|
+
}
|
|
143
|
+
// Check if already compiled
|
|
144
|
+
if (context.compiledRefs.has(ref)) {
|
|
145
|
+
return typeName;
|
|
146
|
+
}
|
|
147
|
+
// Find the referenced schema
|
|
148
|
+
const referencedSchema = context.refs.get(ref);
|
|
149
|
+
if (!referencedSchema) {
|
|
150
|
+
return 'any'; // Reference not found
|
|
151
|
+
}
|
|
152
|
+
// Mark as in progress
|
|
153
|
+
context.refsInProgress.add(ref);
|
|
154
|
+
// Compile the referenced schema
|
|
155
|
+
const compiledType = compileSchema(referencedSchema, typeName, context);
|
|
156
|
+
const description = referencedSchema.description
|
|
157
|
+
? `/** ${escapeJSDocComment(referencedSchema.description)} */\n`
|
|
158
|
+
: '';
|
|
159
|
+
context.compiledRefs.set(ref, `${description}export type ${typeName} = ${compiledType};`);
|
|
160
|
+
// Mark as completed
|
|
161
|
+
context.refsInProgress.delete(ref);
|
|
162
|
+
return typeName;
|
|
163
|
+
}
|
|
164
|
+
function handleAllOf(schemas, name, context) {
|
|
165
|
+
const types = schemas.map((s, i) => compileSchema(s, sanitizeTypeName(`${name}-all-of-${i}`), context));
|
|
166
|
+
// For allOf, we need to intersect types
|
|
167
|
+
// If they're all objects, we can merge them properly
|
|
168
|
+
const objectTypes = types.filter((t) => t.startsWith('{') && t.endsWith('}'));
|
|
169
|
+
if (objectTypes.length === types.length) {
|
|
170
|
+
// Merge object types
|
|
171
|
+
const merged = objectTypes.map((t) => t.slice(1, -1).trim()).filter((t) => t.length > 0);
|
|
172
|
+
return merged.length > 0 ? `{ ${merged.join('; ')} }` : '{}';
|
|
173
|
+
}
|
|
174
|
+
return types.join(' & ');
|
|
175
|
+
}
|
|
176
|
+
function handleAnyOf(schemas, name, context) {
|
|
177
|
+
const types = schemas.map((s, i) => compileSchema(s, sanitizeTypeName(`${name}-any-of-${i}`), context));
|
|
178
|
+
return types.join(' | ');
|
|
179
|
+
}
|
|
180
|
+
function handleOneOf(schemas, name, context) {
|
|
181
|
+
// For TypeScript, oneOf behaves like anyOf
|
|
182
|
+
const types = schemas.map((s, i) => compileSchema(s, sanitizeTypeName(`${name}-one-of-${i}`), context));
|
|
183
|
+
return types.join(' | ');
|
|
184
|
+
}
|
|
185
|
+
function handleEnum(enumValues) {
|
|
186
|
+
return enumValues.map((v) => JSON.stringify(v)).join(' | ');
|
|
187
|
+
}
|
|
188
|
+
function handleConst(value) {
|
|
189
|
+
return JSON.stringify(value);
|
|
190
|
+
}
|
|
191
|
+
function handleArray(schema, name, context) {
|
|
192
|
+
if (!schema.items) {
|
|
193
|
+
return 'any[]';
|
|
194
|
+
}
|
|
195
|
+
if (Array.isArray(schema.items)) {
|
|
196
|
+
// Tuple (ignoring min/max as requested)
|
|
197
|
+
const types = schema.items.map((item, i) => compileSchema(item, `${name}Item${i}`, context));
|
|
198
|
+
return `[${types.join(', ')}]`;
|
|
199
|
+
}
|
|
200
|
+
const itemType = compileSchema(schema.items, `${name}Item`, context);
|
|
201
|
+
return `${wrapUnionType(itemType)}[]`;
|
|
202
|
+
}
|
|
203
|
+
function handleObject(schema, name, context) {
|
|
204
|
+
const props = [];
|
|
205
|
+
// Handle known properties
|
|
206
|
+
if (schema.properties) {
|
|
207
|
+
const required = new Set(schema.required || []);
|
|
208
|
+
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
209
|
+
if (!isSchema(propSchema))
|
|
210
|
+
continue;
|
|
211
|
+
const isRequired = required.has(propName);
|
|
212
|
+
// Ensure the generated type name for nested properties is valid
|
|
213
|
+
const nestedTypeName = sanitizeTypeName(`${name}-${propName}`);
|
|
214
|
+
const propType = compileSchema(propSchema, nestedTypeName, context);
|
|
215
|
+
const safePropName = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(propName) ? propName : `"${propName}"`;
|
|
216
|
+
// Add JSDoc comment if description is present
|
|
217
|
+
const comment = propSchema.description ? `\n/** ${escapeJSDocComment(propSchema.description)} */\n` : '';
|
|
218
|
+
props.push(`${comment}${safePropName}${isRequired ? '' : '?'}: ${propType}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// Handle additional properties
|
|
222
|
+
if (schema.additionalProperties === true) {
|
|
223
|
+
props.push('[key: string]: any');
|
|
224
|
+
}
|
|
225
|
+
else if (schema.additionalProperties && isSchema(schema.additionalProperties)) {
|
|
226
|
+
const additionalTypeName = sanitizeTypeName(`${name}-additional`);
|
|
227
|
+
const additionalType = compileSchema(schema.additionalProperties, additionalTypeName, context);
|
|
228
|
+
props.push(`[key: string]: ${additionalType}`);
|
|
229
|
+
}
|
|
230
|
+
// Handle pattern properties
|
|
231
|
+
if (schema.patternProperties) {
|
|
232
|
+
// For simplicity, treat pattern properties as string index signature
|
|
233
|
+
const patternTypes = Object.values(schema.patternProperties)
|
|
234
|
+
.filter(isSchema)
|
|
235
|
+
.map((s, i) => compileSchema(s, sanitizeTypeName(`${name}-pattern-${i}`), context));
|
|
236
|
+
if (patternTypes.length > 0) {
|
|
237
|
+
props.push(`[key: string]: ${patternTypes.join(' | ')}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return props.length > 0 ? `{ ${props.join('; ')} }` : '{}';
|
|
241
|
+
}
|
|
242
|
+
function refToTypeName(ref) {
|
|
243
|
+
// Extract the last part of the reference as the type name
|
|
244
|
+
const parts = ref.split('/');
|
|
245
|
+
const name = parts[parts.length - 1];
|
|
246
|
+
// Convert kebab-case to PascalCase
|
|
247
|
+
return upperFirst(camelCase(name));
|
|
248
|
+
}
|
|
249
|
+
function wrapUnionType(type) {
|
|
250
|
+
// Wrap union types in parentheses for array types
|
|
251
|
+
return type.includes('|') ? `(${type})` : type;
|
|
252
|
+
}
|
|
253
|
+
function sanitizeTypeName(name) {
|
|
254
|
+
return upperFirst(camelCase(name));
|
|
255
|
+
}
|
|
256
|
+
// Utility function to escape JSDoc comment terminators in descriptions
|
|
257
|
+
function escapeJSDocComment(description) {
|
|
258
|
+
if (!description)
|
|
259
|
+
return '';
|
|
260
|
+
return description.replace(/\*\//g, '*\\/');
|
|
261
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { KnownAny } from '
|
|
2
|
-
export default function debounceWithArgs<
|
|
1
|
+
import type { KnownAny } from 'vovk';
|
|
2
|
+
export default function debounceWithArgs<Callback extends (...args: KnownAny[]) => KnownAny>(callback: Callback, wait: number): (...args: Parameters<Callback>) => Promise<Awaited<ReturnType<Callback>>>;
|
|
@@ -1,11 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const
|
|
1
|
+
export default function debounceWithArgs(callback, wait) {
|
|
2
|
+
// Stores timeouts keyed by the stringified arguments
|
|
3
|
+
const timeouts = new Map();
|
|
4
4
|
return (...args) => {
|
|
5
|
+
// Convert arguments to a JSON string (or any other stable key generation)
|
|
5
6
|
const key = JSON.stringify(args);
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
// Clear any existing timer for this specific key
|
|
8
|
+
if (timeouts.has(key)) {
|
|
9
|
+
clearTimeout(timeouts.get(key));
|
|
8
10
|
}
|
|
9
|
-
|
|
11
|
+
// Return a promise that resolves/rejects after the debounce delay
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
const timeoutId = setTimeout(async () => {
|
|
14
|
+
try {
|
|
15
|
+
const result = await callback(...args);
|
|
16
|
+
resolve(result);
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
reject(error);
|
|
20
|
+
}
|
|
21
|
+
finally {
|
|
22
|
+
// Remove the entry once the callback is invoked
|
|
23
|
+
timeouts.delete(key);
|
|
24
|
+
}
|
|
25
|
+
}, wait);
|
|
26
|
+
timeouts.set(key, timeoutId);
|
|
27
|
+
});
|
|
10
28
|
};
|
|
11
29
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
export default function formatLoggedSegmentName(segmentName: string, { withChalk, upperFirst }?: {
|
|
1
|
+
export default function formatLoggedSegmentName(segmentName: string, { withChalk, upperFirst, isStatic, segmentType, }?: {
|
|
2
2
|
withChalk?: boolean;
|
|
3
3
|
upperFirst?: boolean;
|
|
4
|
+
isStatic?: boolean;
|
|
5
|
+
segmentType?: 'segment' | 'mixin';
|
|
4
6
|
}): string;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import upperFirstLodash from 'lodash/upperFirst.js';
|
|
2
2
|
import chalkHighlightThing from './chalkHighlightThing.mjs';
|
|
3
|
-
export default function formatLoggedSegmentName(segmentName, { withChalk = true, upperFirst = false
|
|
4
|
-
|
|
3
|
+
export default function formatLoggedSegmentName(segmentName, { withChalk = true, upperFirst = false, isStatic = false, segmentType = 'segment', // TODO: Apply to all formatLoggedSegmentName invocations
|
|
4
|
+
} = {}) {
|
|
5
|
+
let text = segmentName ? `${isStatic ? 'static ' : ''}${segmentType} "${segmentName}"` : 'the root segment';
|
|
5
6
|
text = upperFirst ? upperFirstLodash(text) : text;
|
|
6
7
|
return withChalk ? chalkHighlightThing(text) : text;
|
|
7
8
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { HttpMethod } from 'vovk';
|
|
2
|
+
export interface VerbMapEntry {
|
|
3
|
+
noParams?: string;
|
|
4
|
+
withParams?: string;
|
|
5
|
+
default?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const VERB_MAP: Record<HttpMethod, VerbMapEntry>;
|
|
8
|
+
export declare function capitalize(str: string): string;
|
|
9
|
+
interface GenerateFnNameOptions {
|
|
10
|
+
/** Segments to strip out (e.g. ['api','v1']) */
|
|
11
|
+
ignoreSegments?: string[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Turn an HTTP method + OpenAPI path into a camelCased function name.
|
|
15
|
+
*
|
|
16
|
+
* Examples:
|
|
17
|
+
* generateFnName('GET', '/users') // "listUsers"
|
|
18
|
+
* generateFnName('GET', '/users/{id}') // "getUsersById"
|
|
19
|
+
* generateFnName('POST', '/v1/api/orders') // "createOrders"
|
|
20
|
+
* generateFnName('PATCH', '/users/{userId}/profile') // "patchUsersProfileByUserId"
|
|
21
|
+
*/
|
|
22
|
+
export declare function generateFnName(method: HttpMethod, rawPath: string, opts?: GenerateFnNameOptions): string;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export const VERB_MAP = {
|
|
2
|
+
GET: { noParams: 'list', withParams: 'get' },
|
|
3
|
+
POST: { default: 'create' },
|
|
4
|
+
PUT: { default: 'update' },
|
|
5
|
+
PATCH: { default: 'patch' },
|
|
6
|
+
DELETE: { default: 'delete' },
|
|
7
|
+
HEAD: { default: 'head' },
|
|
8
|
+
OPTIONS: { default: 'options' },
|
|
9
|
+
};
|
|
10
|
+
export function capitalize(str) {
|
|
11
|
+
if (str.length === 0)
|
|
12
|
+
return '';
|
|
13
|
+
return str[0].toUpperCase() + str.slice(1);
|
|
14
|
+
}
|
|
15
|
+
const DEFAULT_OPTIONS = {
|
|
16
|
+
ignoreSegments: ['api'],
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Turn an HTTP method + OpenAPI path into a camelCased function name.
|
|
20
|
+
*
|
|
21
|
+
* Examples:
|
|
22
|
+
* generateFnName('GET', '/users') // "listUsers"
|
|
23
|
+
* generateFnName('GET', '/users/{id}') // "getUsersById"
|
|
24
|
+
* generateFnName('POST', '/v1/api/orders') // "createOrders"
|
|
25
|
+
* generateFnName('PATCH', '/users/{userId}/profile') // "patchUsersProfileByUserId"
|
|
26
|
+
*/
|
|
27
|
+
export function generateFnName(method, rawPath, opts = {}) {
|
|
28
|
+
const { ignoreSegments } = {
|
|
29
|
+
...DEFAULT_OPTIONS,
|
|
30
|
+
...opts,
|
|
31
|
+
};
|
|
32
|
+
// 1. Clean & split path
|
|
33
|
+
const parts = rawPath
|
|
34
|
+
.replace(/^\/|\/$/g, '') // strip leading/trailing slash
|
|
35
|
+
.split('/')
|
|
36
|
+
.filter((seg) => !ignoreSegments?.includes(seg.toLowerCase()))
|
|
37
|
+
.filter(Boolean);
|
|
38
|
+
// 2. Separate resource tokens from path-params
|
|
39
|
+
const resources = [];
|
|
40
|
+
const params = [];
|
|
41
|
+
parts.forEach((seg) => {
|
|
42
|
+
const match = seg.match(/^{?([^}]+)}?$/);
|
|
43
|
+
if (match) {
|
|
44
|
+
params.push(match[1]);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
resources.push(seg);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
// 3. Pick base verb from VERB_MAP
|
|
51
|
+
let baseVerb;
|
|
52
|
+
if (method === 'GET') {
|
|
53
|
+
baseVerb = params.length ? VERB_MAP.GET.withParams : VERB_MAP.GET.noParams;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
baseVerb = VERB_MAP[method].default;
|
|
57
|
+
}
|
|
58
|
+
// 4. Build the “resource” part
|
|
59
|
+
const resourcePart = resources.map(capitalize).join('');
|
|
60
|
+
// 5. Build the “ByParam” suffix
|
|
61
|
+
const byParams = params.length ? 'By' + params.map(capitalize).join('') : '';
|
|
62
|
+
// 6. Combine and ensure camelCase
|
|
63
|
+
const rawName = `${baseVerb}${resourcePart}${byParams}`;
|
|
64
|
+
return rawName[0].toLowerCase() + rawName.slice(1);
|
|
65
|
+
}
|
|
66
|
+
/*
|
|
67
|
+
// --- Example usage ---
|
|
68
|
+
console.log(generateFnName('GET', '/users')); // listUsers
|
|
69
|
+
console.log(generateFnName('GET', '/users/{id}')); // getUsersById
|
|
70
|
+
console.log(generateFnName('POST', '/users')); // createUsers
|
|
71
|
+
console.log(generateFnName('PATCH', '/users/{userId}/profile')); // patchUsersProfileByUserId
|
|
72
|
+
console.log(generateFnName('DELETE', '/v1/api/orders/{orderId}')); // deleteOrdersByOrderId
|
|
73
|
+
|
|
74
|
+
// You can also enable singularization:
|
|
75
|
+
console.log(generateFnName('GET', '/users/{userId}/orders', { singularizeResources: true })); // getUserOrderByUserId
|
|
76
|
+
*/
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
let cachedPromise;
|
|
4
|
+
export function getPackageJson(cwd, log) {
|
|
5
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
6
|
+
// If we have a cached promise, return it
|
|
7
|
+
if (cachedPromise) {
|
|
8
|
+
return cachedPromise;
|
|
9
|
+
}
|
|
10
|
+
const promise = fs
|
|
11
|
+
.readFile(pkgPath, 'utf8')
|
|
12
|
+
.then((content) => JSON.parse(content))
|
|
13
|
+
.catch(() => {
|
|
14
|
+
cachedPromise = undefined;
|
|
15
|
+
log.warn(`Failed to read package.json at ${pkgPath}. Using a fallback.`);
|
|
16
|
+
return {
|
|
17
|
+
name: 'unknown',
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
cachedPromise = promise;
|
|
21
|
+
return promise;
|
|
22
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export default function getPublicModuleNameFromPath(modulePath) {
|
|
2
|
+
if (modulePath && !modulePath.startsWith('.') && !modulePath.startsWith('/')) {
|
|
3
|
+
const pathParts = modulePath.split('/');
|
|
4
|
+
const moduleName = pathParts[0].startsWith('@') ? `${pathParts[0]}/${pathParts[1]}` : pathParts[0];
|
|
5
|
+
const restPath = pathParts.slice(pathParts[0].startsWith('@') ? 2 : 1).join('/');
|
|
6
|
+
return { moduleName, restPath };
|
|
7
|
+
}
|
|
8
|
+
return { moduleName: null, restPath: modulePath };
|
|
9
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { HttpMethod, VovkOperationObject, VovkStrictConfig, type VovkConfig } from 'vovk';
|
|
2
|
+
import { OpenAPIObject } from 'openapi3-ts/oas31';
|
|
3
|
+
import type { ProjectInfo } from '../getProjectInfo/index.mjs';
|
|
4
|
+
export type GetOpenAPINameFn = (config: {
|
|
5
|
+
operationObject: VovkOperationObject;
|
|
6
|
+
method: HttpMethod;
|
|
7
|
+
path: string;
|
|
8
|
+
openAPIObject: OpenAPIObject;
|
|
9
|
+
}) => string;
|
|
10
|
+
export declare function normalizeOpenAPIMixin({ mixinModule, log, cwd, }: {
|
|
11
|
+
mixinModule: NonNullable<NonNullable<NonNullable<NonNullable<VovkConfig['generatorConfig']>['segments']>[string]>['openAPIMixin']>;
|
|
12
|
+
log: ProjectInfo['log'];
|
|
13
|
+
cwd?: string;
|
|
14
|
+
}): Promise<NonNullable<NonNullable<NonNullable<VovkStrictConfig['generatorConfig']>['segments']>[string]>['openAPIMixin']>;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import * as YAML from 'yaml';
|
|
4
|
+
import chalkHighlightThing from './chalkHighlightThing.mjs';
|
|
5
|
+
import camelCase from 'lodash/camelCase.js';
|
|
6
|
+
import { generateFnName } from './generateFnName.mjs';
|
|
7
|
+
const getNamesNestJS = (operationObject) => {
|
|
8
|
+
const operationId = operationObject.operationId;
|
|
9
|
+
if (!operationId) {
|
|
10
|
+
throw new Error('Operation ID is required for NestJS module name generation');
|
|
11
|
+
}
|
|
12
|
+
const controllerHandlerMatch = operationId?.match(/^([A-Z][a-zA-Z0-9]*)_([a-zA-Z0-9_]+)/);
|
|
13
|
+
if (!controllerHandlerMatch) {
|
|
14
|
+
throw new Error(`Invalid operationId format for NestJS: ${operationId}`);
|
|
15
|
+
}
|
|
16
|
+
const [controllerName, handlerName] = controllerHandlerMatch.slice(1, 3);
|
|
17
|
+
return [controllerName.replace(/Controller$/, 'RPC'), handlerName];
|
|
18
|
+
};
|
|
19
|
+
const normalizeGetModuleName = (getModuleName) => {
|
|
20
|
+
if (getModuleName === 'nestjs-operation-id') {
|
|
21
|
+
getModuleName = ({ operationObject }) => getNamesNestJS(operationObject)[0];
|
|
22
|
+
}
|
|
23
|
+
else if (typeof getModuleName === 'string') {
|
|
24
|
+
const moduleName = getModuleName;
|
|
25
|
+
getModuleName = () => moduleName;
|
|
26
|
+
}
|
|
27
|
+
else if (typeof getModuleName !== 'function') {
|
|
28
|
+
throw new Error('getModuleName must be a function or one of the predefined strings');
|
|
29
|
+
}
|
|
30
|
+
return getModuleName;
|
|
31
|
+
};
|
|
32
|
+
const normalizeGetMethodName = (getMethodName) => {
|
|
33
|
+
if (getMethodName === 'nestjs-operation-id') {
|
|
34
|
+
getMethodName = ({ operationObject }) => getNamesNestJS(operationObject)[1];
|
|
35
|
+
}
|
|
36
|
+
else if (getMethodName === 'camel-case-operation-id') {
|
|
37
|
+
getMethodName = ({ operationObject }) => {
|
|
38
|
+
const operationId = operationObject.operationId;
|
|
39
|
+
if (!operationId) {
|
|
40
|
+
throw new Error('Operation ID is required for camel-case method name generation');
|
|
41
|
+
}
|
|
42
|
+
return camelCase(operationId);
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
else if (getMethodName === 'auto') {
|
|
46
|
+
getMethodName = ({ operationObject, method, path }) => {
|
|
47
|
+
const operationId = operationObject.operationId;
|
|
48
|
+
const isCamelCase = operationId && /^[a-z][a-zA-Z0-9]*$/.test(operationId);
|
|
49
|
+
const isSnakeCase = operationId && /^[a-z][a-z0-9_]+$/.test(operationId);
|
|
50
|
+
return isCamelCase ? operationId : isSnakeCase ? camelCase(operationId) : generateFnName(method, path);
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
else if (typeof getMethodName !== 'function') {
|
|
54
|
+
throw new Error('getMethodName must be a function or one of the predefined strings');
|
|
55
|
+
}
|
|
56
|
+
return getMethodName;
|
|
57
|
+
};
|
|
58
|
+
async function getOpenApiSpecLocal(openApiSpecFilePath, cwd) {
|
|
59
|
+
const openApiSpecAbsolutePath = path.resolve(cwd, openApiSpecFilePath);
|
|
60
|
+
const fileName = path.basename(openApiSpecAbsolutePath);
|
|
61
|
+
if (!fileName.endsWith('.json') && !fileName.endsWith('.yaml') && !fileName.endsWith('.yml')) {
|
|
62
|
+
throw new Error(`Invalid OpenAPI spec file format: ${fileName}. Please provide a JSON or YAML file.`);
|
|
63
|
+
}
|
|
64
|
+
const openApiSpecContent = await fs.readFile(openApiSpecAbsolutePath, 'utf8');
|
|
65
|
+
return (fileName.endsWith('.json') ? JSON.parse(openApiSpecContent) : YAML.parse(openApiSpecContent));
|
|
66
|
+
}
|
|
67
|
+
async function getOpenApiSpecRemote({ cwd, url, fallback, log, }) {
|
|
68
|
+
const resp = await fetch(url);
|
|
69
|
+
const text = await resp.text();
|
|
70
|
+
if (!resp.ok) {
|
|
71
|
+
if (fallback) {
|
|
72
|
+
log.warn(`Failed to fetch OpenAPI spec from ${chalkHighlightThing(url)}: ${resp.status} ${resp.statusText}. Falling back to ${chalkHighlightThing(fallback)}`);
|
|
73
|
+
return getOpenApiSpecLocal(fallback, cwd);
|
|
74
|
+
}
|
|
75
|
+
throw new Error(`Failed to fetch OpenAPI spec from ${chalkHighlightThing(url)}: ${resp.status} ${resp.statusText}`);
|
|
76
|
+
}
|
|
77
|
+
if (fallback) {
|
|
78
|
+
const fallbackAbsolutePath = path.resolve(cwd, fallback);
|
|
79
|
+
const existingFallback = await fs.readFile(fallbackAbsolutePath, 'utf8').catch(() => null);
|
|
80
|
+
if (existingFallback !== text) {
|
|
81
|
+
await fs.mkdir(path.dirname(fallbackAbsolutePath), { recursive: true });
|
|
82
|
+
await fs.writeFile(fallbackAbsolutePath, text);
|
|
83
|
+
log.info(`Saved OpenAPI spec to fallback file ${chalkHighlightThing(fallbackAbsolutePath)}`);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
log.debug(`OpenAPI spec at ${chalkHighlightThing(url)} is unchanged. Skipping write to fallback file ${chalkHighlightThing(fallbackAbsolutePath)}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return (text.trim().startsWith('{') || text.trim().startsWith('[') ? JSON.parse(text) : YAML.parse(text));
|
|
90
|
+
}
|
|
91
|
+
export async function normalizeOpenAPIMixin({
|
|
92
|
+
// mixinName,
|
|
93
|
+
mixinModule, log, cwd = process.cwd(), }) {
|
|
94
|
+
const { source, getModuleName, getMethodName } = mixinModule;
|
|
95
|
+
let openAPIObject;
|
|
96
|
+
if ('url' in source) {
|
|
97
|
+
openAPIObject = await getOpenApiSpecRemote({ url: source.url, fallback: source.fallback, log, cwd });
|
|
98
|
+
}
|
|
99
|
+
else if ('file' in source) {
|
|
100
|
+
openAPIObject = await getOpenApiSpecLocal(source.file, cwd);
|
|
101
|
+
}
|
|
102
|
+
else if ('object' in source) {
|
|
103
|
+
openAPIObject = source.object;
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
throw new Error('Invalid source type for OpenAPI configuration');
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
...mixinModule,
|
|
110
|
+
source: { object: openAPIObject },
|
|
111
|
+
getModuleName: normalizeGetModuleName(getModuleName),
|
|
112
|
+
getMethodName: normalizeGetMethodName(getMethodName),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { VovkSchemaIdEnum } from 'vovk';
|
|
2
|
+
export default function pickSegmentFullSchema(schema, segmentNames) {
|
|
3
|
+
return {
|
|
4
|
+
$schema: VovkSchemaIdEnum.SCHEMA,
|
|
5
|
+
meta: schema.meta,
|
|
6
|
+
segments: Object.fromEntries(segmentNames.map((segmentName) => [segmentName, schema.segments[segmentName]])),
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export function omitSegmentFullSchema(schema, segmentNames) {
|
|
10
|
+
return {
|
|
11
|
+
$schema: VovkSchemaIdEnum.SCHEMA,
|
|
12
|
+
meta: schema.meta,
|
|
13
|
+
segments: Object.fromEntries(Object.entries(schema.segments).filter(([segmentName]) => !segmentNames.includes(segmentName))),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Removes all directories in a folder that aren't in the provided allowlist
|
|
3
|
+
* Supports nested directory paths like 'foo/bar/baz'
|
|
4
|
+
*
|
|
5
|
+
* @param folderPath - The path to the folder to process
|
|
6
|
+
* @param allowedDirs - Array of relative directory paths to keep
|
|
7
|
+
* @returns Promise that resolves when all operations are complete
|
|
8
|
+
*/
|
|
9
|
+
declare function removeUnlistedDirectories(folderPath: string, allowedDirs: string[]): Promise<void>;
|
|
10
|
+
export default removeUnlistedDirectories;
|