@opra/cli 1.4.3 → 1.5.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/bin/oprimp.mjs +1 -1
- package/cjs/ts-generator/generators/generate-data-type.js +25 -7
- package/cjs/ts-generator/generators/generate-document.js +14 -5
- package/cjs/ts-generator/generators/generate-http-api.js +3 -1
- package/cjs/ts-generator/generators/generate-http-controller.js +29 -11
- package/cjs/ts-generator/ts-file.js +9 -2
- package/cjs/ts-generator/ts-generator.js +8 -2
- package/cjs/ts-generator/utils/string-utils.js +7 -3
- package/esm/ts-generator/generators/generate-data-type.js +26 -8
- package/esm/ts-generator/generators/generate-document.js +14 -5
- package/esm/ts-generator/generators/generate-http-api.js +3 -1
- package/esm/ts-generator/generators/generate-http-controller.js +30 -12
- package/esm/ts-generator/ts-file.js +9 -2
- package/esm/ts-generator/ts-generator.js +8 -2
- package/esm/ts-generator/utils/locate-named-type.js +1 -1
- package/esm/ts-generator/utils/string-utils.js +7 -3
- package/package.json +3 -3
package/bin/oprimp.mjs
CHANGED
|
@@ -12,7 +12,15 @@ const node_path_1 = tslib_1.__importDefault(require("node:path"));
|
|
|
12
12
|
const common_1 = require("@opra/common");
|
|
13
13
|
const code_block_js_1 = require("../../code-block.js");
|
|
14
14
|
const string_utils_js_1 = require("../utils/string-utils.js");
|
|
15
|
-
const internalTypeNames = [
|
|
15
|
+
const internalTypeNames = [
|
|
16
|
+
'any',
|
|
17
|
+
'boolean',
|
|
18
|
+
'bigint',
|
|
19
|
+
'number',
|
|
20
|
+
'null',
|
|
21
|
+
'string',
|
|
22
|
+
'object',
|
|
23
|
+
];
|
|
16
24
|
async function generateDataType(dataType, intent, currentFile) {
|
|
17
25
|
const doc = dataType.node.getDocument();
|
|
18
26
|
if (doc.id !== this._document?.id) {
|
|
@@ -53,7 +61,8 @@ async function generateDataType(dataType, intent, currentFile) {
|
|
|
53
61
|
* @url ${node_path_1.default.posix.join(doc.url || this.serviceUrl, '$schema', '#types/' + typeName)}
|
|
54
62
|
*/
|
|
55
63
|
export `;
|
|
56
|
-
codeBlock.typeDef =
|
|
64
|
+
codeBlock.typeDef =
|
|
65
|
+
(await this._generateTypeCode(file, dataType, 'root')) + '\n\n';
|
|
57
66
|
typesIndexTs.addExport(file.filename);
|
|
58
67
|
if (currentFile)
|
|
59
68
|
currentFile.addImport(file.filename, [typeName]);
|
|
@@ -109,7 +118,9 @@ async function _generateEnumTypeCode(currentFile, dataType, intent) {
|
|
|
109
118
|
out += `/**\n${jsDoc} */\n`;
|
|
110
119
|
out +=
|
|
111
120
|
`${info.alias || value} = ` +
|
|
112
|
-
(typeof value === 'number'
|
|
121
|
+
(typeof value === 'number'
|
|
122
|
+
? value
|
|
123
|
+
: "'" + String(value).replace("'", "\\'") + "'");
|
|
113
124
|
out += ',\n\n';
|
|
114
125
|
}
|
|
115
126
|
return out + '\b}';
|
|
@@ -159,7 +170,10 @@ async function _generateComplexTypeCode(currentFile, dataType, intent) {
|
|
|
159
170
|
if (field.writeonly)
|
|
160
171
|
out += ` * @writeonly\n`;
|
|
161
172
|
if (field.deprecated) {
|
|
162
|
-
out +=
|
|
173
|
+
out +=
|
|
174
|
+
` * @deprecated ` +
|
|
175
|
+
(typeof field.deprecated === 'string' ? field.deprecated : '') +
|
|
176
|
+
'\n';
|
|
163
177
|
}
|
|
164
178
|
out += ' */\n';
|
|
165
179
|
// Print field name
|
|
@@ -172,7 +186,9 @@ async function _generateComplexTypeCode(currentFile, dataType, intent) {
|
|
|
172
186
|
}
|
|
173
187
|
else {
|
|
174
188
|
const x = await this.generateDataType(field.type, 'typeDef', currentFile);
|
|
175
|
-
out +=
|
|
189
|
+
out +=
|
|
190
|
+
(x.kind === 'embedded' ? x.code : x.typeName) +
|
|
191
|
+
`${field.isArray ? '[]' : ''};\n`;
|
|
176
192
|
}
|
|
177
193
|
}
|
|
178
194
|
if (dataType.additionalFields)
|
|
@@ -215,10 +231,12 @@ async function _generateMappedTypeCode(currentFile, dataType, intent) {
|
|
|
215
231
|
const typeDef = base.kind === 'embedded' ? base.code : base.typeName;
|
|
216
232
|
const pick = dataType.pick?.length ? dataType.pick : undefined;
|
|
217
233
|
const omit = !pick && dataType.omit?.length ? dataType.omit : undefined;
|
|
218
|
-
const partial = dataType.partial === true ||
|
|
234
|
+
const partial = dataType.partial === true ||
|
|
235
|
+
(Array.isArray(dataType.partial) && dataType.partial.length > 0)
|
|
219
236
|
? dataType.partial
|
|
220
237
|
: undefined;
|
|
221
|
-
const required = dataType.required === true ||
|
|
238
|
+
const required = dataType.required === true ||
|
|
239
|
+
(Array.isArray(dataType.required) && dataType.required.length > 0)
|
|
222
240
|
? dataType.required
|
|
223
241
|
: undefined;
|
|
224
242
|
if (!(pick || omit || partial || required))
|
|
@@ -14,7 +14,8 @@ async function generateDocument(document, options) {
|
|
|
14
14
|
if (out)
|
|
15
15
|
return out;
|
|
16
16
|
}
|
|
17
|
-
this.emit('log', ansi_colors_1.default.cyan('Fetching document schema from ') +
|
|
17
|
+
this.emit('log', ansi_colors_1.default.cyan('Fetching document schema from ') +
|
|
18
|
+
ansi_colors_1.default.blueBright(this.serviceUrl));
|
|
18
19
|
const client = new client_1.OpraHttpClient(this.serviceUrl);
|
|
19
20
|
document = await client.fetchDocument({ documentId: document });
|
|
20
21
|
}
|
|
@@ -32,15 +33,23 @@ async function generateDocument(document, options) {
|
|
|
32
33
|
ansi_colors_1.default.magenta(document.info.title || ''));
|
|
33
34
|
if (document.references.size) {
|
|
34
35
|
let refIdGenerator = options?.refIdGenerator || 1;
|
|
35
|
-
this.emit('log', ansi_colors_1.default.white('[' + document.id + '] ') +
|
|
36
|
+
this.emit('log', ansi_colors_1.default.white('[' + document.id + '] ') +
|
|
37
|
+
ansi_colors_1.default.cyan(`Processing references`));
|
|
36
38
|
for (const ref of document.references.values()) {
|
|
37
39
|
const generator = this.extend();
|
|
38
40
|
generator._document = ref;
|
|
39
|
-
const typesNamespace = ref.api?.name ||
|
|
41
|
+
const typesNamespace = ref.api?.name ||
|
|
42
|
+
(ref.info.title
|
|
43
|
+
? (0, putil_varhelpers_1.pascalCase)(ref.info.title)
|
|
44
|
+
: `Reference${refIdGenerator++}`);
|
|
40
45
|
generator._documentRoot = '/references/' + typesNamespace;
|
|
41
46
|
generator._typesRoot = node_path_1.default.join(generator._documentRoot, 'models');
|
|
42
|
-
generator._typesNamespace =
|
|
43
|
-
|
|
47
|
+
generator._typesNamespace =
|
|
48
|
+
!this.options.referenceNamespaces || ref[common_1.BUILTIN] ? '' : typesNamespace;
|
|
49
|
+
await generator.generateDocument(ref, {
|
|
50
|
+
typesOnly: true,
|
|
51
|
+
refIdGenerator,
|
|
52
|
+
});
|
|
44
53
|
}
|
|
45
54
|
}
|
|
46
55
|
this._fileHeaderDocInfo = `/*
|
|
@@ -35,7 +35,9 @@ async function generateHttpApi(api) {
|
|
|
35
35
|
const f = await generator.generateHttpController(controller);
|
|
36
36
|
const childClassName = (0, putil_varhelpers_1.pascalCase)(controller.name) + 'Controller';
|
|
37
37
|
file.addImport('.' + f.filename, [childClassName]);
|
|
38
|
-
const property = '$' +
|
|
38
|
+
const property = '$' +
|
|
39
|
+
controller.name.charAt(0).toLowerCase() +
|
|
40
|
+
(0, putil_varhelpers_1.camelCase)(controller.name.substring(1));
|
|
39
41
|
classBlock.properties += `\nreadonly ${property}: ${childClassName};`;
|
|
40
42
|
classConstBlock.body += `\nthis.${property} = new ${childClassName}(client);`;
|
|
41
43
|
}
|
|
@@ -35,8 +35,14 @@ async function generateHttpController(controller) {
|
|
|
35
35
|
};
|
|
36
36
|
const className = (0, putil_varhelpers_1.pascalCase)(controller.name) + 'Controller';
|
|
37
37
|
file = this.addFile(node_path_1.default.join(this._apiPath, className + '.ts'));
|
|
38
|
-
file.addImport('@opra/client', [
|
|
39
|
-
|
|
38
|
+
file.addImport('@opra/client', [
|
|
39
|
+
'HttpRequestObservable',
|
|
40
|
+
'kClient',
|
|
41
|
+
'OpraHttpClient',
|
|
42
|
+
]);
|
|
43
|
+
file.addImport(node_path_1.default.relative(file.dirname, '/http-controller-node.ts'), [
|
|
44
|
+
'HttpControllerNode',
|
|
45
|
+
]);
|
|
40
46
|
const classBlock = (file.code[className] = new code_block_js_1.CodeBlock());
|
|
41
47
|
classBlock.doc = `/**
|
|
42
48
|
* ${(0, string_utils_js_1.wrapJSDocString)(controller.description || '')}
|
|
@@ -60,7 +66,9 @@ constructor(client: OpraHttpClient) {`;
|
|
|
60
66
|
const f = await generator.generateHttpController(child);
|
|
61
67
|
const childClassName = (0, putil_varhelpers_1.pascalCase)(child.name) + 'Controller';
|
|
62
68
|
file.addImport(f.filename, [childClassName]);
|
|
63
|
-
const property = '$' +
|
|
69
|
+
const property = '$' +
|
|
70
|
+
child.name.charAt(0).toLowerCase() +
|
|
71
|
+
(0, putil_varhelpers_1.camelCase)(child.name.substring(1));
|
|
64
72
|
classBlock.properties += `\nreadonly ${property}: ${childClassName};`;
|
|
65
73
|
classConstBlock.body += `\nthis.${property} = new ${childClassName}(client);`;
|
|
66
74
|
}
|
|
@@ -70,11 +78,12 @@ constructor(client: OpraHttpClient) {`;
|
|
|
70
78
|
let _base = controller;
|
|
71
79
|
while (_base.owner instanceof common_1.HttpController) {
|
|
72
80
|
_base = _base.owner;
|
|
73
|
-
mergedControllerParams.unshift(..._base
|
|
81
|
+
mergedControllerParams.unshift(..._base.parameters);
|
|
74
82
|
}
|
|
75
83
|
for (const operation of controller.operations.values()) {
|
|
76
84
|
const mergedParams = [...mergedControllerParams, ...operation.parameters];
|
|
77
|
-
const operationBlock = (classBlock['operation_' + operation.name] =
|
|
85
|
+
const operationBlock = (classBlock['operation_' + operation.name] =
|
|
86
|
+
new code_block_js_1.CodeBlock());
|
|
78
87
|
operationBlock.doc = new code_block_js_1.CodeBlock();
|
|
79
88
|
operationBlock.doc.header = `
|
|
80
89
|
/**
|
|
@@ -181,7 +190,8 @@ constructor(client: OpraHttpClient) {`;
|
|
|
181
190
|
typeArr.push(typeDef);
|
|
182
191
|
continue;
|
|
183
192
|
}
|
|
184
|
-
else if (content.contentType &&
|
|
193
|
+
else if (content.contentType &&
|
|
194
|
+
type_is_1.default.is(String(content.contentType), ['multipart/*'])) {
|
|
185
195
|
const typeDef = 'FormData';
|
|
186
196
|
if (!typeArr.includes(typeDef))
|
|
187
197
|
typeArr.push(typeDef);
|
|
@@ -201,8 +211,12 @@ constructor(client: OpraHttpClient) {`;
|
|
|
201
211
|
if (queryParams.length) {
|
|
202
212
|
if (argIndex++ > 0)
|
|
203
213
|
operationBlock.head += ', ';
|
|
204
|
-
operationBlock.head +=
|
|
205
|
-
|
|
214
|
+
operationBlock.head +=
|
|
215
|
+
'\n\t$params' +
|
|
216
|
+
(isHeadersRequired || isQueryRequired ? '' : '?') +
|
|
217
|
+
': {\n\t';
|
|
218
|
+
operationBlock.doc.parameters +=
|
|
219
|
+
'\n * @param {object} $params - Available parameters for the operation';
|
|
206
220
|
let hasAdditionalFields = false;
|
|
207
221
|
for (const prm of queryParams) {
|
|
208
222
|
if (typeof prm.name !== 'string') {
|
|
@@ -230,7 +244,8 @@ constructor(client: OpraHttpClient) {`;
|
|
|
230
244
|
if (headerParams.length) {
|
|
231
245
|
if (argIndex++ > 0)
|
|
232
246
|
operationBlock.head += ', \n';
|
|
233
|
-
operationBlock.head +=
|
|
247
|
+
operationBlock.head +=
|
|
248
|
+
'\t$headers' + (isHeadersRequired ? '' : '?') + ': {\n\t';
|
|
234
249
|
for (const prm of headerParams) {
|
|
235
250
|
operationBlock.head += `/**\n * ${prm.description || ''}\n */\n`;
|
|
236
251
|
operationBlock.head += `${prm.name}${prm.required ? '' : '?'}: `;
|
|
@@ -271,7 +286,8 @@ constructor(client: OpraHttpClient) {`;
|
|
|
271
286
|
}
|
|
272
287
|
if (typeDef && resp.isArray)
|
|
273
288
|
typeDef += '[]';
|
|
274
|
-
if (resp.contentType &&
|
|
289
|
+
if (resp.contentType &&
|
|
290
|
+
type_is_1.default.is(String(resp.contentType), [common_1.MimeTypes.opra_response_json])) {
|
|
275
291
|
file.addImport('@opra/common', ['OperationResult']);
|
|
276
292
|
typeDef = typeDef ? `OperationResult<${typeDef}>` : 'OperationResult';
|
|
277
293
|
}
|
|
@@ -282,7 +298,9 @@ constructor(client: OpraHttpClient) {`;
|
|
|
282
298
|
operationBlock.head += `\n): HttpRequestObservable<${returnTypes.join(' | ') || 'any'}>{`;
|
|
283
299
|
operationBlock.body = `\n\t`;
|
|
284
300
|
operationBlock.body +=
|
|
285
|
-
`const url = this._prepareUrl('${operation.getFullUrl()}', {` +
|
|
301
|
+
`const url = this._prepareUrl('${operation.getFullUrl()}', {` +
|
|
302
|
+
pathParams.map(p => p.name).join(', ') +
|
|
303
|
+
'});';
|
|
286
304
|
operationBlock.body +=
|
|
287
305
|
`\nreturn this[kClient].request(url, { method: '${operation.method}'` +
|
|
288
306
|
(hasBody ? ', body: $body' : '') +
|
|
@@ -27,7 +27,10 @@ class TsFile {
|
|
|
27
27
|
filename = filename.substring(0, filename.length - 5);
|
|
28
28
|
if (filename.endsWith('.ts') || filename.endsWith('.js'))
|
|
29
29
|
filename = filename.substring(0, filename.length - 3);
|
|
30
|
-
const imp = (this.imports[filename] = this.imports[filename] || {
|
|
30
|
+
const imp = (this.imports[filename] = this.imports[filename] || {
|
|
31
|
+
items: [],
|
|
32
|
+
typeImport,
|
|
33
|
+
});
|
|
31
34
|
if (!typeImport)
|
|
32
35
|
imp.typeImport = false;
|
|
33
36
|
items?.forEach(x => {
|
|
@@ -46,7 +49,11 @@ class TsFile {
|
|
|
46
49
|
if (filename.endsWith('.ts') || filename.endsWith('.js'))
|
|
47
50
|
filename = filename.substring(0, filename.length - 3);
|
|
48
51
|
const key = (namespace ? namespace + ':' : '') + filename;
|
|
49
|
-
this.exportFiles[key] = this.exportFiles[key] || {
|
|
52
|
+
this.exportFiles[key] = this.exportFiles[key] || {
|
|
53
|
+
filename,
|
|
54
|
+
items: [],
|
|
55
|
+
namespace,
|
|
56
|
+
};
|
|
50
57
|
types?.forEach(x => {
|
|
51
58
|
if (!this.exportFiles[filename].items.includes(x))
|
|
52
59
|
this.exportFiles[filename].items.push(x);
|
|
@@ -31,7 +31,10 @@ class TsGenerator extends node_events_1.EventEmitter {
|
|
|
31
31
|
this.outDir = init.outDir ? node_path_1.default.resolve(this.cwd, init.outDir) : this.cwd;
|
|
32
32
|
this.fileHeader = init.fileHeader || '';
|
|
33
33
|
this.writer = init.writer || new file_writer_js_1.FileWriter();
|
|
34
|
-
this.options = {
|
|
34
|
+
this.options = {
|
|
35
|
+
importExt: !!init.importExt,
|
|
36
|
+
referenceNamespaces: init.referenceNamespaces,
|
|
37
|
+
};
|
|
35
38
|
this._documentsMap = new Map();
|
|
36
39
|
this._filesMap = new WeakMap();
|
|
37
40
|
this.on('log', (message, ...args) => init.logger?.log?.(message, ...args));
|
|
@@ -81,7 +84,10 @@ class TsGenerator extends node_events_1.EventEmitter {
|
|
|
81
84
|
throw new Error(`File "${filePath}" already exists`);
|
|
82
85
|
}
|
|
83
86
|
file = new ts_file_js_1.TsFile(filePath);
|
|
84
|
-
file.code.header =
|
|
87
|
+
file.code.header =
|
|
88
|
+
this.fileHeader +
|
|
89
|
+
(this._fileHeaderDocInfo ? '\n' + this._fileHeaderDocInfo : '') +
|
|
90
|
+
'\n\n';
|
|
85
91
|
this._files[file.filename] = file;
|
|
86
92
|
return file;
|
|
87
93
|
}
|
|
@@ -9,7 +9,7 @@ const js_string_escape_1 = tslib_1.__importDefault(require("js-string-escape"));
|
|
|
9
9
|
function wrapJSDocString(s, indent, currentColumn) {
|
|
10
10
|
const arr = (s || '')
|
|
11
11
|
.split(/[ \n\r]/)
|
|
12
|
-
.map((x, i, a) =>
|
|
12
|
+
.map((x, i, a) => i < a.length - 1 ? x + ' ' : x);
|
|
13
13
|
return _printLines(arr, {
|
|
14
14
|
indent,
|
|
15
15
|
currentColumn,
|
|
@@ -20,7 +20,7 @@ function wrapJSDocString(s, indent, currentColumn) {
|
|
|
20
20
|
function wrapQuotedString(s, indent, currentColumn) {
|
|
21
21
|
const arr = (0, js_string_escape_1.default)(s || '')
|
|
22
22
|
.split(' ')
|
|
23
|
-
.map((x, i, a) =>
|
|
23
|
+
.map((x, i, a) => i < a.length - 1 ? x + ' ' : x);
|
|
24
24
|
return ("'" +
|
|
25
25
|
_printLines(arr, {
|
|
26
26
|
indent,
|
|
@@ -46,7 +46,11 @@ function _printLines(arr, opts = {}) {
|
|
|
46
46
|
const l = arr.length;
|
|
47
47
|
const printLine = (eof) => {
|
|
48
48
|
s +=
|
|
49
|
-
(s
|
|
49
|
+
(s
|
|
50
|
+
? '\n' +
|
|
51
|
+
' '.repeat(indent || 0) +
|
|
52
|
+
(opts.lineStart ? opts.lineStart : '')
|
|
53
|
+
: '') +
|
|
50
54
|
line +
|
|
51
55
|
(!eof ? (opts.lineEnd ? opts.lineEnd : '') : '');
|
|
52
56
|
line = '';
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { ComplexType, EnumType, MappedType, MixinType, SimpleType } from '@opra/common';
|
|
2
|
+
import { ComplexType, EnumType, MappedType, MixinType, SimpleType, } from '@opra/common';
|
|
3
3
|
import { CodeBlock } from '../../code-block.js';
|
|
4
4
|
import { wrapJSDocString } from '../utils/string-utils.js';
|
|
5
|
-
const internalTypeNames = [
|
|
5
|
+
const internalTypeNames = [
|
|
6
|
+
'any',
|
|
7
|
+
'boolean',
|
|
8
|
+
'bigint',
|
|
9
|
+
'number',
|
|
10
|
+
'null',
|
|
11
|
+
'string',
|
|
12
|
+
'object',
|
|
13
|
+
];
|
|
6
14
|
export async function generateDataType(dataType, intent, currentFile) {
|
|
7
15
|
const doc = dataType.node.getDocument();
|
|
8
16
|
if (doc.id !== this._document?.id) {
|
|
@@ -43,7 +51,8 @@ export async function generateDataType(dataType, intent, currentFile) {
|
|
|
43
51
|
* @url ${path.posix.join(doc.url || this.serviceUrl, '$schema', '#types/' + typeName)}
|
|
44
52
|
*/
|
|
45
53
|
export `;
|
|
46
|
-
codeBlock.typeDef =
|
|
54
|
+
codeBlock.typeDef =
|
|
55
|
+
(await this._generateTypeCode(file, dataType, 'root')) + '\n\n';
|
|
47
56
|
typesIndexTs.addExport(file.filename);
|
|
48
57
|
if (currentFile)
|
|
49
58
|
currentFile.addImport(file.filename, [typeName]);
|
|
@@ -99,7 +108,9 @@ export async function _generateEnumTypeCode(currentFile, dataType, intent) {
|
|
|
99
108
|
out += `/**\n${jsDoc} */\n`;
|
|
100
109
|
out +=
|
|
101
110
|
`${info.alias || value} = ` +
|
|
102
|
-
(typeof value === 'number'
|
|
111
|
+
(typeof value === 'number'
|
|
112
|
+
? value
|
|
113
|
+
: "'" + String(value).replace("'", "\\'") + "'");
|
|
103
114
|
out += ',\n\n';
|
|
104
115
|
}
|
|
105
116
|
return out + '\b}';
|
|
@@ -149,7 +160,10 @@ export async function _generateComplexTypeCode(currentFile, dataType, intent) {
|
|
|
149
160
|
if (field.writeonly)
|
|
150
161
|
out += ` * @writeonly\n`;
|
|
151
162
|
if (field.deprecated) {
|
|
152
|
-
out +=
|
|
163
|
+
out +=
|
|
164
|
+
` * @deprecated ` +
|
|
165
|
+
(typeof field.deprecated === 'string' ? field.deprecated : '') +
|
|
166
|
+
'\n';
|
|
153
167
|
}
|
|
154
168
|
out += ' */\n';
|
|
155
169
|
// Print field name
|
|
@@ -162,7 +176,9 @@ export async function _generateComplexTypeCode(currentFile, dataType, intent) {
|
|
|
162
176
|
}
|
|
163
177
|
else {
|
|
164
178
|
const x = await this.generateDataType(field.type, 'typeDef', currentFile);
|
|
165
|
-
out +=
|
|
179
|
+
out +=
|
|
180
|
+
(x.kind === 'embedded' ? x.code : x.typeName) +
|
|
181
|
+
`${field.isArray ? '[]' : ''};\n`;
|
|
166
182
|
}
|
|
167
183
|
}
|
|
168
184
|
if (dataType.additionalFields)
|
|
@@ -205,10 +221,12 @@ export async function _generateMappedTypeCode(currentFile, dataType, intent) {
|
|
|
205
221
|
const typeDef = base.kind === 'embedded' ? base.code : base.typeName;
|
|
206
222
|
const pick = dataType.pick?.length ? dataType.pick : undefined;
|
|
207
223
|
const omit = !pick && dataType.omit?.length ? dataType.omit : undefined;
|
|
208
|
-
const partial = dataType.partial === true ||
|
|
224
|
+
const partial = dataType.partial === true ||
|
|
225
|
+
(Array.isArray(dataType.partial) && dataType.partial.length > 0)
|
|
209
226
|
? dataType.partial
|
|
210
227
|
: undefined;
|
|
211
|
-
const required = dataType.required === true ||
|
|
228
|
+
const required = dataType.required === true ||
|
|
229
|
+
(Array.isArray(dataType.required) && dataType.required.length > 0)
|
|
212
230
|
? dataType.required
|
|
213
231
|
: undefined;
|
|
214
232
|
if (!(pick || omit || partial || required))
|
|
@@ -10,7 +10,8 @@ export async function generateDocument(document, options) {
|
|
|
10
10
|
if (out)
|
|
11
11
|
return out;
|
|
12
12
|
}
|
|
13
|
-
this.emit('log', colors.cyan('Fetching document schema from ') +
|
|
13
|
+
this.emit('log', colors.cyan('Fetching document schema from ') +
|
|
14
|
+
colors.blueBright(this.serviceUrl));
|
|
14
15
|
const client = new OpraHttpClient(this.serviceUrl);
|
|
15
16
|
document = await client.fetchDocument({ documentId: document });
|
|
16
17
|
}
|
|
@@ -28,15 +29,23 @@ export async function generateDocument(document, options) {
|
|
|
28
29
|
colors.magenta(document.info.title || ''));
|
|
29
30
|
if (document.references.size) {
|
|
30
31
|
let refIdGenerator = options?.refIdGenerator || 1;
|
|
31
|
-
this.emit('log', colors.white('[' + document.id + '] ') +
|
|
32
|
+
this.emit('log', colors.white('[' + document.id + '] ') +
|
|
33
|
+
colors.cyan(`Processing references`));
|
|
32
34
|
for (const ref of document.references.values()) {
|
|
33
35
|
const generator = this.extend();
|
|
34
36
|
generator._document = ref;
|
|
35
|
-
const typesNamespace = ref.api?.name ||
|
|
37
|
+
const typesNamespace = ref.api?.name ||
|
|
38
|
+
(ref.info.title
|
|
39
|
+
? pascalCase(ref.info.title)
|
|
40
|
+
: `Reference${refIdGenerator++}`);
|
|
36
41
|
generator._documentRoot = '/references/' + typesNamespace;
|
|
37
42
|
generator._typesRoot = path.join(generator._documentRoot, 'models');
|
|
38
|
-
generator._typesNamespace =
|
|
39
|
-
|
|
43
|
+
generator._typesNamespace =
|
|
44
|
+
!this.options.referenceNamespaces || ref[BUILTIN] ? '' : typesNamespace;
|
|
45
|
+
await generator.generateDocument(ref, {
|
|
46
|
+
typesOnly: true,
|
|
47
|
+
refIdGenerator,
|
|
48
|
+
});
|
|
40
49
|
}
|
|
41
50
|
}
|
|
42
51
|
this._fileHeaderDocInfo = `/*
|
|
@@ -31,7 +31,9 @@ export async function generateHttpApi(api) {
|
|
|
31
31
|
const f = await generator.generateHttpController(controller);
|
|
32
32
|
const childClassName = pascalCase(controller.name) + 'Controller';
|
|
33
33
|
file.addImport('.' + f.filename, [childClassName]);
|
|
34
|
-
const property = '$' +
|
|
34
|
+
const property = '$' +
|
|
35
|
+
controller.name.charAt(0).toLowerCase() +
|
|
36
|
+
camelCase(controller.name.substring(1));
|
|
35
37
|
classBlock.properties += `\nreadonly ${property}: ${childClassName};`;
|
|
36
38
|
classConstBlock.body += `\nthis.${property} = new ${childClassName}(client);`;
|
|
37
39
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import typeIs from '@browsery/type-is';
|
|
3
|
-
import { ComplexType, HttpController, MimeTypes } from '@opra/common';
|
|
3
|
+
import { ComplexType, HttpController, MimeTypes, } from '@opra/common';
|
|
4
4
|
import { camelCase, pascalCase } from 'putil-varhelpers';
|
|
5
5
|
import { CodeBlock } from '../../code-block.js';
|
|
6
6
|
import { locateNamedType } from '../utils/locate-named-type.js';
|
|
@@ -31,8 +31,14 @@ export async function generateHttpController(controller) {
|
|
|
31
31
|
};
|
|
32
32
|
const className = pascalCase(controller.name) + 'Controller';
|
|
33
33
|
file = this.addFile(path.join(this._apiPath, className + '.ts'));
|
|
34
|
-
file.addImport('@opra/client', [
|
|
35
|
-
|
|
34
|
+
file.addImport('@opra/client', [
|
|
35
|
+
'HttpRequestObservable',
|
|
36
|
+
'kClient',
|
|
37
|
+
'OpraHttpClient',
|
|
38
|
+
]);
|
|
39
|
+
file.addImport(path.relative(file.dirname, '/http-controller-node.ts'), [
|
|
40
|
+
'HttpControllerNode',
|
|
41
|
+
]);
|
|
36
42
|
const classBlock = (file.code[className] = new CodeBlock());
|
|
37
43
|
classBlock.doc = `/**
|
|
38
44
|
* ${wrapJSDocString(controller.description || '')}
|
|
@@ -56,7 +62,9 @@ constructor(client: OpraHttpClient) {`;
|
|
|
56
62
|
const f = await generator.generateHttpController(child);
|
|
57
63
|
const childClassName = pascalCase(child.name) + 'Controller';
|
|
58
64
|
file.addImport(f.filename, [childClassName]);
|
|
59
|
-
const property = '$' +
|
|
65
|
+
const property = '$' +
|
|
66
|
+
child.name.charAt(0).toLowerCase() +
|
|
67
|
+
camelCase(child.name.substring(1));
|
|
60
68
|
classBlock.properties += `\nreadonly ${property}: ${childClassName};`;
|
|
61
69
|
classConstBlock.body += `\nthis.${property} = new ${childClassName}(client);`;
|
|
62
70
|
}
|
|
@@ -66,11 +74,12 @@ constructor(client: OpraHttpClient) {`;
|
|
|
66
74
|
let _base = controller;
|
|
67
75
|
while (_base.owner instanceof HttpController) {
|
|
68
76
|
_base = _base.owner;
|
|
69
|
-
mergedControllerParams.unshift(..._base
|
|
77
|
+
mergedControllerParams.unshift(..._base.parameters);
|
|
70
78
|
}
|
|
71
79
|
for (const operation of controller.operations.values()) {
|
|
72
80
|
const mergedParams = [...mergedControllerParams, ...operation.parameters];
|
|
73
|
-
const operationBlock = (classBlock['operation_' + operation.name] =
|
|
81
|
+
const operationBlock = (classBlock['operation_' + operation.name] =
|
|
82
|
+
new CodeBlock());
|
|
74
83
|
operationBlock.doc = new CodeBlock();
|
|
75
84
|
operationBlock.doc.header = `
|
|
76
85
|
/**
|
|
@@ -177,7 +186,8 @@ constructor(client: OpraHttpClient) {`;
|
|
|
177
186
|
typeArr.push(typeDef);
|
|
178
187
|
continue;
|
|
179
188
|
}
|
|
180
|
-
else if (content.contentType &&
|
|
189
|
+
else if (content.contentType &&
|
|
190
|
+
typeIs.is(String(content.contentType), ['multipart/*'])) {
|
|
181
191
|
const typeDef = 'FormData';
|
|
182
192
|
if (!typeArr.includes(typeDef))
|
|
183
193
|
typeArr.push(typeDef);
|
|
@@ -197,8 +207,12 @@ constructor(client: OpraHttpClient) {`;
|
|
|
197
207
|
if (queryParams.length) {
|
|
198
208
|
if (argIndex++ > 0)
|
|
199
209
|
operationBlock.head += ', ';
|
|
200
|
-
operationBlock.head +=
|
|
201
|
-
|
|
210
|
+
operationBlock.head +=
|
|
211
|
+
'\n\t$params' +
|
|
212
|
+
(isHeadersRequired || isQueryRequired ? '' : '?') +
|
|
213
|
+
': {\n\t';
|
|
214
|
+
operationBlock.doc.parameters +=
|
|
215
|
+
'\n * @param {object} $params - Available parameters for the operation';
|
|
202
216
|
let hasAdditionalFields = false;
|
|
203
217
|
for (const prm of queryParams) {
|
|
204
218
|
if (typeof prm.name !== 'string') {
|
|
@@ -226,7 +240,8 @@ constructor(client: OpraHttpClient) {`;
|
|
|
226
240
|
if (headerParams.length) {
|
|
227
241
|
if (argIndex++ > 0)
|
|
228
242
|
operationBlock.head += ', \n';
|
|
229
|
-
operationBlock.head +=
|
|
243
|
+
operationBlock.head +=
|
|
244
|
+
'\t$headers' + (isHeadersRequired ? '' : '?') + ': {\n\t';
|
|
230
245
|
for (const prm of headerParams) {
|
|
231
246
|
operationBlock.head += `/**\n * ${prm.description || ''}\n */\n`;
|
|
232
247
|
operationBlock.head += `${prm.name}${prm.required ? '' : '?'}: `;
|
|
@@ -267,7 +282,8 @@ constructor(client: OpraHttpClient) {`;
|
|
|
267
282
|
}
|
|
268
283
|
if (typeDef && resp.isArray)
|
|
269
284
|
typeDef += '[]';
|
|
270
|
-
if (resp.contentType &&
|
|
285
|
+
if (resp.contentType &&
|
|
286
|
+
typeIs.is(String(resp.contentType), [MimeTypes.opra_response_json])) {
|
|
271
287
|
file.addImport('@opra/common', ['OperationResult']);
|
|
272
288
|
typeDef = typeDef ? `OperationResult<${typeDef}>` : 'OperationResult';
|
|
273
289
|
}
|
|
@@ -278,7 +294,9 @@ constructor(client: OpraHttpClient) {`;
|
|
|
278
294
|
operationBlock.head += `\n): HttpRequestObservable<${returnTypes.join(' | ') || 'any'}>{`;
|
|
279
295
|
operationBlock.body = `\n\t`;
|
|
280
296
|
operationBlock.body +=
|
|
281
|
-
`const url = this._prepareUrl('${operation.getFullUrl()}', {` +
|
|
297
|
+
`const url = this._prepareUrl('${operation.getFullUrl()}', {` +
|
|
298
|
+
pathParams.map(p => p.name).join(', ') +
|
|
299
|
+
'});';
|
|
282
300
|
operationBlock.body +=
|
|
283
301
|
`\nreturn this[kClient].request(url, { method: '${operation.method}'` +
|
|
284
302
|
(hasBody ? ', body: $body' : '') +
|
|
@@ -23,7 +23,10 @@ export class TsFile {
|
|
|
23
23
|
filename = filename.substring(0, filename.length - 5);
|
|
24
24
|
if (filename.endsWith('.ts') || filename.endsWith('.js'))
|
|
25
25
|
filename = filename.substring(0, filename.length - 3);
|
|
26
|
-
const imp = (this.imports[filename] = this.imports[filename] || {
|
|
26
|
+
const imp = (this.imports[filename] = this.imports[filename] || {
|
|
27
|
+
items: [],
|
|
28
|
+
typeImport,
|
|
29
|
+
});
|
|
27
30
|
if (!typeImport)
|
|
28
31
|
imp.typeImport = false;
|
|
29
32
|
items?.forEach(x => {
|
|
@@ -42,7 +45,11 @@ export class TsFile {
|
|
|
42
45
|
if (filename.endsWith('.ts') || filename.endsWith('.js'))
|
|
43
46
|
filename = filename.substring(0, filename.length - 3);
|
|
44
47
|
const key = (namespace ? namespace + ':' : '') + filename;
|
|
45
|
-
this.exportFiles[key] = this.exportFiles[key] || {
|
|
48
|
+
this.exportFiles[key] = this.exportFiles[key] || {
|
|
49
|
+
filename,
|
|
50
|
+
items: [],
|
|
51
|
+
namespace,
|
|
52
|
+
};
|
|
46
53
|
types?.forEach(x => {
|
|
47
54
|
if (!this.exportFiles[filename].items.includes(x))
|
|
48
55
|
this.exportFiles[filename].items.push(x);
|
|
@@ -27,7 +27,10 @@ export class TsGenerator extends EventEmitter {
|
|
|
27
27
|
this.outDir = init.outDir ? path.resolve(this.cwd, init.outDir) : this.cwd;
|
|
28
28
|
this.fileHeader = init.fileHeader || '';
|
|
29
29
|
this.writer = init.writer || new FileWriter();
|
|
30
|
-
this.options = {
|
|
30
|
+
this.options = {
|
|
31
|
+
importExt: !!init.importExt,
|
|
32
|
+
referenceNamespaces: init.referenceNamespaces,
|
|
33
|
+
};
|
|
31
34
|
this._documentsMap = new Map();
|
|
32
35
|
this._filesMap = new WeakMap();
|
|
33
36
|
this.on('log', (message, ...args) => init.logger?.log?.(message, ...args));
|
|
@@ -77,7 +80,10 @@ export class TsGenerator extends EventEmitter {
|
|
|
77
80
|
throw new Error(`File "${filePath}" already exists`);
|
|
78
81
|
}
|
|
79
82
|
file = new TsFile(filePath);
|
|
80
|
-
file.code.header =
|
|
83
|
+
file.code.header =
|
|
84
|
+
this.fileHeader +
|
|
85
|
+
(this._fileHeaderDocInfo ? '\n' + this._fileHeaderDocInfo : '') +
|
|
86
|
+
'\n\n';
|
|
81
87
|
this._files[file.filename] = file;
|
|
82
88
|
return file;
|
|
83
89
|
}
|
|
@@ -2,7 +2,7 @@ import jsStringEscape from 'js-string-escape';
|
|
|
2
2
|
export function wrapJSDocString(s, indent, currentColumn) {
|
|
3
3
|
const arr = (s || '')
|
|
4
4
|
.split(/[ \n\r]/)
|
|
5
|
-
.map((x, i, a) =>
|
|
5
|
+
.map((x, i, a) => i < a.length - 1 ? x + ' ' : x);
|
|
6
6
|
return _printLines(arr, {
|
|
7
7
|
indent,
|
|
8
8
|
currentColumn,
|
|
@@ -13,7 +13,7 @@ export function wrapJSDocString(s, indent, currentColumn) {
|
|
|
13
13
|
export function wrapQuotedString(s, indent, currentColumn) {
|
|
14
14
|
const arr = jsStringEscape(s || '')
|
|
15
15
|
.split(' ')
|
|
16
|
-
.map((x, i, a) =>
|
|
16
|
+
.map((x, i, a) => i < a.length - 1 ? x + ' ' : x);
|
|
17
17
|
return ("'" +
|
|
18
18
|
_printLines(arr, {
|
|
19
19
|
indent,
|
|
@@ -39,7 +39,11 @@ function _printLines(arr, opts = {}) {
|
|
|
39
39
|
const l = arr.length;
|
|
40
40
|
const printLine = (eof) => {
|
|
41
41
|
s +=
|
|
42
|
-
(s
|
|
42
|
+
(s
|
|
43
|
+
? '\n' +
|
|
44
|
+
' '.repeat(indent || 0) +
|
|
45
|
+
(opts.lineStart ? opts.lineStart : '')
|
|
46
|
+
: '') +
|
|
43
47
|
line +
|
|
44
48
|
(!eof ? (opts.lineEnd ? opts.lineEnd : '') : '');
|
|
45
49
|
line = '';
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opra/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Opra CLI tools",
|
|
5
5
|
"author": "Panates",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"dependencies": {
|
|
8
8
|
"@browsery/type-is": "^1.6.18-r5",
|
|
9
|
-
"@opra/client": "^1.
|
|
10
|
-
"@opra/common": "^1.
|
|
9
|
+
"@opra/client": "^1.5.0",
|
|
10
|
+
"@opra/common": "^1.5.0",
|
|
11
11
|
"ansi-colors": "^4.1.3",
|
|
12
12
|
"commander": "^12.0.0",
|
|
13
13
|
"js-string-escape": "^1.0.1",
|