@opra/cli 0.14.3 → 0.15.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/api-exporter/api-exporter.js +107 -0
- package/cjs/{oprimp → api-exporter}/index.js +1 -1
- package/cjs/api-exporter/process-resources.js +50 -0
- package/cjs/api-exporter/process-types.js +215 -0
- package/cjs/api-exporter/ts-file.js +90 -0
- package/cjs/index.js +1 -1
- package/cjs/{oprimp.js → oprimp-cli.js} +8 -9
- package/esm/api-exporter/api-exporter.d.ts +46 -0
- package/esm/api-exporter/api-exporter.js +102 -0
- package/esm/api-exporter/index.d.ts +1 -0
- package/esm/api-exporter/index.js +1 -0
- package/esm/api-exporter/process-resources.d.ts +6 -0
- package/esm/api-exporter/process-resources.js +45 -0
- package/esm/api-exporter/process-types.d.ts +54 -0
- package/esm/api-exporter/process-types.js +203 -0
- package/esm/api-exporter/ts-file.d.ts +18 -0
- package/esm/api-exporter/ts-file.js +83 -0
- package/esm/index.d.ts +1 -1
- package/esm/index.js +1 -1
- package/esm/interfaces/service-generation-context.interface.d.ts +2 -2
- package/esm/{oprimp.js → oprimp-cli.js} +8 -9
- package/package.json +8 -9
- package/cjs/oprimp/delete-files.js +0 -27
- package/cjs/oprimp/generate-service.js +0 -54
- package/cjs/oprimp/process-resoruces.js +0 -59
- package/cjs/oprimp/process-types.js +0 -130
- package/cjs/utils/ts-file.js +0 -55
- package/esm/oprimp/delete-files.d.ts +0 -1
- package/esm/oprimp/delete-files.js +0 -22
- package/esm/oprimp/generate-service.d.ts +0 -13
- package/esm/oprimp/generate-service.js +0 -49
- package/esm/oprimp/index.d.ts +0 -1
- package/esm/oprimp/index.js +0 -1
- package/esm/oprimp/process-resoruces.d.ts +0 -2
- package/esm/oprimp/process-resoruces.js +0 -54
- package/esm/oprimp/process-types.d.ts +0 -2
- package/esm/oprimp/process-types.js +0 -125
- package/esm/utils/ts-file.d.ts +0 -11
- package/esm/utils/ts-file.js +0 -50
- /package/cjs/{oprimp → api-exporter}/file-writer.js +0 -0
- /package/esm/{oprimp → api-exporter}/file-writer.d.ts +0 -0
- /package/esm/{oprimp → api-exporter}/file-writer.js +0 -0
- /package/esm/{oprimp.d.ts → oprimp-cli.d.ts} +0 -0
package/bin/oprimp.mjs
CHANGED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ApiExporter = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
6
|
+
const node_fs_1 = tslib_1.__importDefault(require("node:fs"));
|
|
7
|
+
const node_path_1 = tslib_1.__importDefault(require("node:path"));
|
|
8
|
+
const node_process_1 = tslib_1.__importDefault(require("node:process"));
|
|
9
|
+
const node_client_1 = require("@opra/node-client");
|
|
10
|
+
const file_writer_js_1 = require("./file-writer.js");
|
|
11
|
+
const process_resources_js_1 = require("./process-resources.js");
|
|
12
|
+
const process_types_js_1 = require("./process-types.js");
|
|
13
|
+
const ts_file_js_1 = require("./ts-file.js");
|
|
14
|
+
class ApiExporter {
|
|
15
|
+
constructor(config) {
|
|
16
|
+
this.files = {};
|
|
17
|
+
this.client = new node_client_1.OpraHttpClient(config.serviceUrl);
|
|
18
|
+
this.cwd = config.cwd || node_process_1.default.cwd();
|
|
19
|
+
this.outDir = node_path_1.default.resolve(this.cwd, config.outDir);
|
|
20
|
+
this.logger = config.logger || {
|
|
21
|
+
log: () => void 0,
|
|
22
|
+
error: () => void 0,
|
|
23
|
+
debug: () => void 0,
|
|
24
|
+
warn: () => void 0,
|
|
25
|
+
verbose: () => void 0,
|
|
26
|
+
};
|
|
27
|
+
this.fileHeader = config.fileHeader || '';
|
|
28
|
+
this.importExt = config.importExt || '';
|
|
29
|
+
this.writer = config.writer || new file_writer_js_1.FileWriter();
|
|
30
|
+
// this.nsMap = nsMap || new ResponsiveMap(); // implement references later
|
|
31
|
+
}
|
|
32
|
+
async execute() {
|
|
33
|
+
this.logger.log(chalk_1.default.yellow('Fetching service metadata from'), chalk_1.default.whiteBright(this.client.serviceUrl));
|
|
34
|
+
this.document = await this.client.getMetadata();
|
|
35
|
+
this.logger.log(chalk_1.default.yellow('Retrieved service info:\n'), chalk_1.default.white('Title:'), chalk_1.default.magenta(this.document.info.title), '\n', chalk_1.default.white('Version:'), chalk_1.default.magenta(this.document.info.version), '\n', chalk_1.default.white('Resources:'), chalk_1.default.magenta(this.document.resources.size), 'resources found\n', chalk_1.default.white('Types:'), chalk_1.default.magenta(this.document.types.size), 'types found\n');
|
|
36
|
+
this.name = (this.name || this.document.info.title || 'Service1').replace(/[^\w_$]*/g, '');
|
|
37
|
+
this.name = this.name.charAt(0).toUpperCase() + this.name.substring(1);
|
|
38
|
+
this.fileHeader += `/*
|
|
39
|
+
* ${this.document.info.title}
|
|
40
|
+
* Version ${this.document.info.version}
|
|
41
|
+
* ${this.client.serviceUrl}
|
|
42
|
+
*/`;
|
|
43
|
+
this.logger.log(chalk_1.default.yellow('Removing old files..'));
|
|
44
|
+
this.cleanDirectory(this.outDir);
|
|
45
|
+
this.logger.log(chalk_1.default.yellow(`Generating service interface ( ${chalk_1.default.whiteBright(this.name)} )`));
|
|
46
|
+
node_fs_1.default.mkdirSync(this.outDir, { recursive: true });
|
|
47
|
+
await this.processTypes();
|
|
48
|
+
await this.processResources();
|
|
49
|
+
// Write files
|
|
50
|
+
for (const file of Object.values(this.files)) {
|
|
51
|
+
const targetDir = node_path_1.default.dirname(file.filename);
|
|
52
|
+
node_fs_1.default.mkdirSync(targetDir, { recursive: true });
|
|
53
|
+
await this.writer.writeFile(file.filename, file.generate({ importExt: this.importExt }));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
getFile(filePath) {
|
|
57
|
+
return this.files[node_path_1.default.resolve(node_path_1.default.join(this.outDir, filePath))];
|
|
58
|
+
}
|
|
59
|
+
addFile(filePath, returnExists) {
|
|
60
|
+
let file = this.getFile(filePath);
|
|
61
|
+
if (file) {
|
|
62
|
+
if (returnExists)
|
|
63
|
+
return file;
|
|
64
|
+
throw new Error(`File "${filePath}" already exists`);
|
|
65
|
+
}
|
|
66
|
+
file = new ts_file_js_1.TsFile(node_path_1.default.resolve(node_path_1.default.join(this.outDir, filePath)));
|
|
67
|
+
file.header = this.fileHeader;
|
|
68
|
+
this.files[file.filename] = file;
|
|
69
|
+
return file;
|
|
70
|
+
}
|
|
71
|
+
cleanDirectory(dirname) {
|
|
72
|
+
if (!node_fs_1.default.existsSync(dirname))
|
|
73
|
+
return;
|
|
74
|
+
const files = node_fs_1.default.readdirSync(dirname);
|
|
75
|
+
for (const f of files) {
|
|
76
|
+
const absolutePath = node_path_1.default.join(dirname, f);
|
|
77
|
+
if (node_fs_1.default.statSync(absolutePath).isDirectory()) {
|
|
78
|
+
this.cleanDirectory(absolutePath);
|
|
79
|
+
if (!node_fs_1.default.readdirSync(absolutePath).length)
|
|
80
|
+
node_fs_1.default.rmdirSync(absolutePath);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (node_path_1.default.extname(f) === '.ts') {
|
|
84
|
+
const contents = node_fs_1.default.readFileSync(absolutePath, 'utf-8');
|
|
85
|
+
if (contents.includes('#!oprimp_auto_generated!#')) {
|
|
86
|
+
node_fs_1.default.unlinkSync(absolutePath);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
static async execute(config) {
|
|
92
|
+
const exporter = new ApiExporter(config);
|
|
93
|
+
await exporter.execute();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
exports.ApiExporter = ApiExporter;
|
|
97
|
+
(() => {
|
|
98
|
+
ApiExporter.prototype.processResources = process_resources_js_1.processResources;
|
|
99
|
+
ApiExporter.prototype.processTypes = process_types_js_1.processTypes;
|
|
100
|
+
ApiExporter.prototype.generateTypeFile = process_types_js_1.generateTypeFile;
|
|
101
|
+
ApiExporter.prototype.generateComplexTypeDefinition = process_types_js_1.generateComplexTypeDefinition;
|
|
102
|
+
ApiExporter.prototype.generateSimpleTypeDefinition = process_types_js_1.generateSimpleTypeDefinition;
|
|
103
|
+
ApiExporter.prototype.resolveTypeNameOrDef = process_types_js_1.resolveTypeNameOrDef;
|
|
104
|
+
ApiExporter.prototype.generateEnumTypeDefinition = process_types_js_1.generateEnumTypeDefinition;
|
|
105
|
+
ApiExporter.prototype.generateUnionTypeDefinition = process_types_js_1.generateUnionTypeDefinition;
|
|
106
|
+
ApiExporter.prototype.generateMappedTypeDefinition = process_types_js_1.generateMappedTypeDefinition;
|
|
107
|
+
})();
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.processResources = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
6
|
+
const node_path_1 = tslib_1.__importDefault(require("node:path"));
|
|
7
|
+
const common_1 = require("@opra/common");
|
|
8
|
+
const string_utils_js_1 = require("../utils/string-utils.js");
|
|
9
|
+
/**
|
|
10
|
+
*
|
|
11
|
+
* @param targetDir
|
|
12
|
+
*/
|
|
13
|
+
async function processResources(targetDir = '') {
|
|
14
|
+
this.logger.log(chalk_1.default.yellow('Processing resources'));
|
|
15
|
+
const { document } = this;
|
|
16
|
+
const serviceTs = this.addFile(node_path_1.default.join(targetDir, this.name + '.ts'));
|
|
17
|
+
serviceTs.addImportPackage('@opra/client', ['HttpServiceBase']);
|
|
18
|
+
const indexTs = this.addFile('/index.ts', true);
|
|
19
|
+
indexTs.addExportFile(serviceTs.filename);
|
|
20
|
+
serviceTs.content = `\nexport class ${this.name} extends HttpServiceBase {\n`;
|
|
21
|
+
for (const resource of document.resources.values()) {
|
|
22
|
+
serviceTs.content += `\n/**\n * ${(0, string_utils_js_1.wrapJSDocString)(resource.description || resource.name)}
|
|
23
|
+
* @url ${(0, common_1.joinPath)(this.client.serviceUrl, '$metadata#resources/' + resource.name)}
|
|
24
|
+
*/`;
|
|
25
|
+
if (resource instanceof common_1.Collection) {
|
|
26
|
+
const typeName = resource.type.name || '';
|
|
27
|
+
serviceTs.addImportPackage('@opra/client', ['HttpCollectionNode']);
|
|
28
|
+
serviceTs.addImportFile('types/' + typeName, [typeName]);
|
|
29
|
+
const operations = Object.keys(resource.operations)
|
|
30
|
+
.map(x => `'${x}'`).join(' | ');
|
|
31
|
+
serviceTs.content += `
|
|
32
|
+
get ${resource.name}(): Pick<HttpCollectionNode<${typeName}>, ${operations}> {
|
|
33
|
+
return this.$client.collection('${resource.name}');
|
|
34
|
+
}\n`;
|
|
35
|
+
}
|
|
36
|
+
else if (resource instanceof common_1.Singleton) {
|
|
37
|
+
const typeName = resource.type.name || '';
|
|
38
|
+
serviceTs.addImportPackage('@opra/client', ['HttpSingletonNode']);
|
|
39
|
+
serviceTs.addImportFile('types/' + typeName, [typeName]);
|
|
40
|
+
const operations = Object.keys(resource.operations)
|
|
41
|
+
.map(x => `'${x}'`).join(' | ');
|
|
42
|
+
serviceTs.content += `
|
|
43
|
+
get ${resource.name}(): Pick<HttpSingletonNode<${typeName}>, ${operations}> {
|
|
44
|
+
return this.$client.singleton('${resource.name}');
|
|
45
|
+
}\n`;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
serviceTs.content += '}';
|
|
49
|
+
}
|
|
50
|
+
exports.processResources = processResources;
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateMappedTypeDefinition = exports.generateUnionTypeDefinition = exports.generateEnumTypeDefinition = exports.generateSimpleTypeDefinition = exports.generateComplexTypeDefinition = exports.resolveTypeNameOrDef = exports.generateTypeFile = exports.processTypes = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
6
|
+
const node_path_1 = tslib_1.__importDefault(require("node:path"));
|
|
7
|
+
const common_1 = require("@opra/common");
|
|
8
|
+
const string_utils_js_1 = require("../utils/string-utils.js");
|
|
9
|
+
const internalTypeNames = ['boolean', 'bigint', 'number', 'null', 'string'];
|
|
10
|
+
/**
|
|
11
|
+
*
|
|
12
|
+
* @param targetDir
|
|
13
|
+
*/
|
|
14
|
+
async function processTypes(targetDir = '') {
|
|
15
|
+
this.logger.log(chalk_1.default.yellow('Processing types'));
|
|
16
|
+
const { document } = this;
|
|
17
|
+
const typesTs = this.addFile(node_path_1.default.join(targetDir, 'types.ts'));
|
|
18
|
+
for (const dataType of document.types.values()) {
|
|
19
|
+
const expFile = await this.generateTypeFile(dataType, targetDir);
|
|
20
|
+
typesTs.addExportFile(expFile.filename);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
exports.processTypes = processTypes;
|
|
24
|
+
/**
|
|
25
|
+
*
|
|
26
|
+
* @param dataType
|
|
27
|
+
* @param targetDir
|
|
28
|
+
*/
|
|
29
|
+
async function generateTypeFile(dataType, targetDir = '') {
|
|
30
|
+
const typeName = dataType.name;
|
|
31
|
+
if (!typeName)
|
|
32
|
+
throw new TypeError(`DataType has no name`);
|
|
33
|
+
let filePath;
|
|
34
|
+
if (dataType instanceof common_1.SimpleType)
|
|
35
|
+
filePath = '/simple-types.ts';
|
|
36
|
+
else if (dataType instanceof common_1.ComplexType)
|
|
37
|
+
filePath = `/types/${typeName}.ts`;
|
|
38
|
+
else if (dataType instanceof common_1.EnumType) {
|
|
39
|
+
filePath = `/enums/${typeName}.ts`;
|
|
40
|
+
}
|
|
41
|
+
else
|
|
42
|
+
throw new TypeError(`Unimplemented DataType (${dataType.kind})`);
|
|
43
|
+
const file = this.addFile(node_path_1.default.join(targetDir, filePath), true);
|
|
44
|
+
if (file.exportTypes.includes(typeName))
|
|
45
|
+
return file;
|
|
46
|
+
file.exportTypes.push(typeName);
|
|
47
|
+
const indexTs = this.addFile('/index.ts', true);
|
|
48
|
+
indexTs.addExportFile(file.filename);
|
|
49
|
+
file.content += `\n/**\n * ${(0, string_utils_js_1.wrapJSDocString)(dataType.description || typeName)}
|
|
50
|
+
* @type ${typeName}
|
|
51
|
+
* @kind ${dataType.kind}
|
|
52
|
+
* @url ${(0, common_1.joinPath)(this.client.serviceUrl, '$metadata#types/' + typeName)}
|
|
53
|
+
*/\n`;
|
|
54
|
+
if (dataType instanceof common_1.SimpleType) {
|
|
55
|
+
file.content += `export type ${typeName} = ` + await this.generateSimpleTypeDefinition(file, dataType);
|
|
56
|
+
}
|
|
57
|
+
else if (dataType instanceof common_1.EnumType) {
|
|
58
|
+
file.content += `export enum ${typeName} ` + await this.generateEnumTypeDefinition(file, dataType);
|
|
59
|
+
}
|
|
60
|
+
else if (dataType instanceof common_1.ComplexType) {
|
|
61
|
+
file.content += `export class ${typeName} {
|
|
62
|
+
constructor(init?: Partial<I${typeName}>) {
|
|
63
|
+
if (init)
|
|
64
|
+
Object.assign(this, init);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface ${typeName} extends I${typeName} {
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface I${typeName} ${await this.generateComplexTypeDefinition(file, dataType, true)}
|
|
72
|
+
`;
|
|
73
|
+
}
|
|
74
|
+
return file;
|
|
75
|
+
}
|
|
76
|
+
exports.generateTypeFile = generateTypeFile;
|
|
77
|
+
/**
|
|
78
|
+
*
|
|
79
|
+
* @param file
|
|
80
|
+
* @param dataType
|
|
81
|
+
* @param forInterface
|
|
82
|
+
*/
|
|
83
|
+
async function resolveTypeNameOrDef(file, dataType, forInterface) {
|
|
84
|
+
if (dataType.name) {
|
|
85
|
+
if (internalTypeNames.includes(dataType.name))
|
|
86
|
+
return dataType.name;
|
|
87
|
+
const f = await this.generateTypeFile(dataType);
|
|
88
|
+
file.addImportFile(f.filename, [dataType.name]);
|
|
89
|
+
return dataType.name;
|
|
90
|
+
}
|
|
91
|
+
if (dataType instanceof common_1.ComplexType)
|
|
92
|
+
return this.generateComplexTypeDefinition(file, dataType, forInterface);
|
|
93
|
+
if (dataType instanceof common_1.SimpleType)
|
|
94
|
+
return this.generateSimpleTypeDefinition(file, dataType);
|
|
95
|
+
if (dataType instanceof common_1.EnumType)
|
|
96
|
+
return this.generateEnumTypeDefinition(file, dataType);
|
|
97
|
+
if (dataType instanceof common_1.UnionType)
|
|
98
|
+
return this.generateUnionTypeDefinition(file, dataType, forInterface);
|
|
99
|
+
if (dataType instanceof common_1.MappedType)
|
|
100
|
+
return this.generateMappedTypeDefinition(file, dataType, forInterface);
|
|
101
|
+
return 'xxx';
|
|
102
|
+
}
|
|
103
|
+
exports.resolveTypeNameOrDef = resolveTypeNameOrDef;
|
|
104
|
+
/**
|
|
105
|
+
*
|
|
106
|
+
* @param file
|
|
107
|
+
* @param dataType
|
|
108
|
+
* @param forInterface
|
|
109
|
+
*/
|
|
110
|
+
async function generateComplexTypeDefinition(file, dataType, forInterface) {
|
|
111
|
+
let out = '';
|
|
112
|
+
if (dataType.base) {
|
|
113
|
+
const base = await this.resolveTypeNameOrDef(file, dataType.base, forInterface);
|
|
114
|
+
out += forInterface ? `extends ${base} ` : `${base} & `;
|
|
115
|
+
}
|
|
116
|
+
out += '{\n\n\t';
|
|
117
|
+
for (const field of dataType.own.fields.values()) {
|
|
118
|
+
// Print JSDoc
|
|
119
|
+
let jsDoc = '';
|
|
120
|
+
if (field.description)
|
|
121
|
+
jsDoc += ` * ${field.description}\n`;
|
|
122
|
+
if (field.default)
|
|
123
|
+
jsDoc += ` * @default ` + field.default + '\n';
|
|
124
|
+
if (field.format)
|
|
125
|
+
jsDoc += ` * @format ` + field.format + '\n';
|
|
126
|
+
if (field.exclusive)
|
|
127
|
+
jsDoc += ` * @exclusive\n`;
|
|
128
|
+
if (field.deprecated)
|
|
129
|
+
jsDoc += ` * @deprecated ` + (typeof field.deprecated === 'string' ? field.deprecated : '') + '\n';
|
|
130
|
+
if (jsDoc)
|
|
131
|
+
out += `/**\n${jsDoc}*/\n`;
|
|
132
|
+
// Print field name
|
|
133
|
+
out += `${field.name}${field.required ? '' : '?'}: `;
|
|
134
|
+
if (field.fixed)
|
|
135
|
+
out += `${field.fixed}`;
|
|
136
|
+
else {
|
|
137
|
+
out += await this.resolveTypeNameOrDef(file, field.type) +
|
|
138
|
+
`${field.isArray ? '[]' : ''};\n\n`;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (dataType.additionalFields)
|
|
142
|
+
out += '[key: string]: any;\n';
|
|
143
|
+
return out + '\b}';
|
|
144
|
+
}
|
|
145
|
+
exports.generateComplexTypeDefinition = generateComplexTypeDefinition;
|
|
146
|
+
/**
|
|
147
|
+
*
|
|
148
|
+
* @param file
|
|
149
|
+
* @param dataType
|
|
150
|
+
*/
|
|
151
|
+
async function generateSimpleTypeDefinition(file, dataType) {
|
|
152
|
+
if (dataType.ctor === Boolean)
|
|
153
|
+
return 'boolean';
|
|
154
|
+
if (dataType.ctor === String)
|
|
155
|
+
return 'string';
|
|
156
|
+
if (dataType.ctor === Number)
|
|
157
|
+
return 'number';
|
|
158
|
+
if (dataType.ctor === Date)
|
|
159
|
+
return 'Date';
|
|
160
|
+
if (dataType.extendsFrom('bigint'))
|
|
161
|
+
return 'Date';
|
|
162
|
+
if (dataType.extendsFrom('object'))
|
|
163
|
+
return 'object';
|
|
164
|
+
return 'any';
|
|
165
|
+
}
|
|
166
|
+
exports.generateSimpleTypeDefinition = generateSimpleTypeDefinition;
|
|
167
|
+
/**
|
|
168
|
+
*
|
|
169
|
+
* @param file
|
|
170
|
+
* @param dataType
|
|
171
|
+
*/
|
|
172
|
+
async function generateEnumTypeDefinition(file, dataType) {
|
|
173
|
+
let out = '{\n\t';
|
|
174
|
+
for (const [k, v] of Object.entries(dataType.values)) {
|
|
175
|
+
// Print JSDoc
|
|
176
|
+
let jsDoc = '';
|
|
177
|
+
if (dataType.meanings[k])
|
|
178
|
+
jsDoc += ` * ${dataType.meanings[k]}\n`;
|
|
179
|
+
if (jsDoc)
|
|
180
|
+
out += `/**\n${jsDoc}*/\n`;
|
|
181
|
+
out += `${k}`;
|
|
182
|
+
if (v)
|
|
183
|
+
out += ' = ' + (typeof v === 'number' ? v : ('"' + ('' + v).replace('"', '\\"')) + '"');
|
|
184
|
+
out += ',\n\n';
|
|
185
|
+
}
|
|
186
|
+
return out + '\b}';
|
|
187
|
+
}
|
|
188
|
+
exports.generateEnumTypeDefinition = generateEnumTypeDefinition;
|
|
189
|
+
/**
|
|
190
|
+
*
|
|
191
|
+
* @param file
|
|
192
|
+
* @param dataType
|
|
193
|
+
* @param forInterface
|
|
194
|
+
*/
|
|
195
|
+
async function generateUnionTypeDefinition(file, dataType, forInterface) {
|
|
196
|
+
// let out = '';
|
|
197
|
+
return (await Promise.all(dataType.types
|
|
198
|
+
.map(t => this.resolveTypeNameOrDef(file, t, forInterface)))).join(forInterface ? ', ' : ' & ');
|
|
199
|
+
}
|
|
200
|
+
exports.generateUnionTypeDefinition = generateUnionTypeDefinition;
|
|
201
|
+
/**
|
|
202
|
+
*
|
|
203
|
+
* @param file
|
|
204
|
+
* @param dataType
|
|
205
|
+
* @param forInterface
|
|
206
|
+
*/
|
|
207
|
+
async function generateMappedTypeDefinition(file, dataType, forInterface) {
|
|
208
|
+
return (dataType.pick ? 'Pick<' : 'Omit<') +
|
|
209
|
+
(await this.resolveTypeNameOrDef(file, dataType.type, forInterface)) +
|
|
210
|
+
', ' +
|
|
211
|
+
(dataType.pick || dataType.omit || [])
|
|
212
|
+
.map(x => `'${x}'`).join(' | ') +
|
|
213
|
+
'>';
|
|
214
|
+
}
|
|
215
|
+
exports.generateMappedTypeDefinition = generateMappedTypeDefinition;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setExt = exports.relativePath = exports.TsFile = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
|
6
|
+
const putil_flattentext_1 = tslib_1.__importDefault(require("putil-flattentext"));
|
|
7
|
+
class TsFile {
|
|
8
|
+
constructor(filename) {
|
|
9
|
+
this.filename = filename;
|
|
10
|
+
this.importFiles = {};
|
|
11
|
+
this.exportFiles = {};
|
|
12
|
+
this.exportTypes = [];
|
|
13
|
+
this.header = '';
|
|
14
|
+
this.content = '';
|
|
15
|
+
this.addImportPackage = (name, types) => {
|
|
16
|
+
this.importFiles[name] = this.importFiles[name] || [];
|
|
17
|
+
types?.forEach(x => {
|
|
18
|
+
if (!this.importFiles[name].includes(x))
|
|
19
|
+
this.importFiles[name].push(x);
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
this.addImportFile = (filename, types) => {
|
|
23
|
+
filename = path_1.default.resolve(this.dirname, filename);
|
|
24
|
+
this.importFiles[filename] = this.importFiles[filename] || [];
|
|
25
|
+
types?.forEach(x => {
|
|
26
|
+
if (!this.importFiles[filename].includes(x))
|
|
27
|
+
this.importFiles[filename].push(x);
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
this.addExportFile = (filename, types) => {
|
|
31
|
+
filename = path_1.default.resolve(this.dirname, filename);
|
|
32
|
+
if (filename.endsWith('.ts') || filename.endsWith('.js'))
|
|
33
|
+
filename = setExt(filename, '');
|
|
34
|
+
this.exportFiles[filename] = this.exportFiles[filename] || [];
|
|
35
|
+
types?.forEach(x => {
|
|
36
|
+
if (!this.exportFiles[filename].includes(x))
|
|
37
|
+
this.exportFiles[filename].push(x);
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
this.dirname = path_1.default.dirname(filename);
|
|
41
|
+
}
|
|
42
|
+
generate(options) {
|
|
43
|
+
const dirname = path_1.default.dirname(this.filename);
|
|
44
|
+
let output = '/* #!oprimp_auto_generated!# !! Do NOT remove this line */\n' +
|
|
45
|
+
(this.header ? (0, putil_flattentext_1.default)(this.header) + '\n\n' : '\n');
|
|
46
|
+
const importStr = Object.keys(this.importFiles)
|
|
47
|
+
.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))
|
|
48
|
+
.map(filename => {
|
|
49
|
+
const types = this.importFiles[filename];
|
|
50
|
+
let relFile = filename;
|
|
51
|
+
if (path_1.default.isAbsolute(filename)) {
|
|
52
|
+
relFile = relativePath(dirname, filename);
|
|
53
|
+
if (options?.importExt)
|
|
54
|
+
relFile = setExt(relFile, options.importExt);
|
|
55
|
+
}
|
|
56
|
+
return `import ${types.length ? '{' + types.join(', ') + '} from ' : ''}'${relFile}';`;
|
|
57
|
+
})
|
|
58
|
+
.join('\n');
|
|
59
|
+
if (importStr)
|
|
60
|
+
output += (0, putil_flattentext_1.default)(importStr) + '\n';
|
|
61
|
+
output += (0, putil_flattentext_1.default)(this.content);
|
|
62
|
+
const exportStr = Object.keys(this.exportFiles)
|
|
63
|
+
.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))
|
|
64
|
+
.map(filename => {
|
|
65
|
+
const types = this.exportFiles[filename];
|
|
66
|
+
let relFile = filename;
|
|
67
|
+
if (path_1.default.isAbsolute(filename)) {
|
|
68
|
+
relFile = relativePath(dirname, filename);
|
|
69
|
+
if (options?.importExt)
|
|
70
|
+
relFile = setExt(relFile, options.importExt);
|
|
71
|
+
}
|
|
72
|
+
return `export ${types.length ? '{' + types.join(', ') + '}' : '*'} from '${relFile}';`;
|
|
73
|
+
})
|
|
74
|
+
.join('\n');
|
|
75
|
+
if (exportStr)
|
|
76
|
+
output += (0, putil_flattentext_1.default)(exportStr) + '\n';
|
|
77
|
+
return output;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
exports.TsFile = TsFile;
|
|
81
|
+
function relativePath(from, to) {
|
|
82
|
+
const s = path_1.default.relative(from, to);
|
|
83
|
+
return s.startsWith('.') ? s : ('./' + s);
|
|
84
|
+
}
|
|
85
|
+
exports.relativePath = relativePath;
|
|
86
|
+
function setExt(filename, ext) {
|
|
87
|
+
const e = path_1.default.extname(filename);
|
|
88
|
+
return filename.substring(0, filename.length - e.length) + (ext ? '.' + ext : '');
|
|
89
|
+
}
|
|
90
|
+
exports.setExt = setExt;
|
package/cjs/index.js
CHANGED
|
@@ -7,9 +7,10 @@ const console = tslib_1.__importStar(require("console"));
|
|
|
7
7
|
const fs = tslib_1.__importStar(require("fs"));
|
|
8
8
|
const path_1 = tslib_1.__importDefault(require("path"));
|
|
9
9
|
const process = tslib_1.__importStar(require("process"));
|
|
10
|
-
const
|
|
10
|
+
const api_exporter_js_1 = require("./api-exporter/api-exporter.js");
|
|
11
11
|
const get_caller_file_util_js_1 = require("./utils/get-caller-file.util.js");
|
|
12
|
-
const
|
|
12
|
+
const dirname = path_1.default.dirname((0, get_caller_file_util_js_1.getCallerFile)());
|
|
13
|
+
const pkgJson = JSON.parse(fs.readFileSync(path_1.default.resolve(dirname, '../package.json'), 'utf-8'));
|
|
13
14
|
commander_1.program
|
|
14
15
|
.version(pkgJson.version)
|
|
15
16
|
.argument('<serviceUrl>', 'OPRA service url')
|
|
@@ -20,16 +21,14 @@ commander_1.program
|
|
|
20
21
|
.action(async (serviceUrl, outDir, options) => {
|
|
21
22
|
if (!options.color)
|
|
22
23
|
chalk_1.default.level = 0;
|
|
23
|
-
await
|
|
24
|
+
await api_exporter_js_1.ApiExporter.execute({
|
|
24
25
|
serviceUrl,
|
|
26
|
+
logger: console,
|
|
25
27
|
outDir,
|
|
26
28
|
name: options.name,
|
|
27
|
-
|
|
28
|
-
fileHeader: '
|
|
29
|
-
'
|
|
30
|
-
' * Version: ' + pkgJson.version + '\n' +
|
|
31
|
-
' * ' + new Date() + '\n' +
|
|
32
|
-
' */'
|
|
29
|
+
importExt: (options.ext ? 'js' : ''),
|
|
30
|
+
fileHeader: '/* Generated by OPRA Service Generator, Version ' + pkgJson.version + '*/\n' +
|
|
31
|
+
'/* eslint-disable import/extensions,simple-import-sort/imports */\n'
|
|
33
32
|
});
|
|
34
33
|
console.log(chalk_1.default.greenBright('Completed'));
|
|
35
34
|
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ApiDocument } from '@opra/common';
|
|
2
|
+
import { OpraHttpClient } from '@opra/node-client';
|
|
3
|
+
import { IFileWriter } from '../interfaces/file-writer.interface.js';
|
|
4
|
+
import { ILogger } from '../interfaces/logger.interface.js';
|
|
5
|
+
import { processResources } from './process-resources.js';
|
|
6
|
+
import { generateComplexTypeDefinition, generateEnumTypeDefinition, generateMappedTypeDefinition, generateSimpleTypeDefinition, generateTypeFile, generateUnionTypeDefinition, processTypes, resolveTypeNameOrDef } from './process-types.js';
|
|
7
|
+
import { TsFile } from './ts-file.js';
|
|
8
|
+
export declare namespace ApiExporter {
|
|
9
|
+
interface Config {
|
|
10
|
+
serviceUrl: string;
|
|
11
|
+
outDir: string;
|
|
12
|
+
name?: string;
|
|
13
|
+
cwd?: string;
|
|
14
|
+
logger?: ILogger;
|
|
15
|
+
writer?: IFileWriter;
|
|
16
|
+
fileHeader?: string;
|
|
17
|
+
importExt?: string;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export declare class ApiExporter {
|
|
21
|
+
protected client: OpraHttpClient;
|
|
22
|
+
protected document: ApiDocument;
|
|
23
|
+
protected logger: ILogger;
|
|
24
|
+
protected outDir: string;
|
|
25
|
+
protected cwd: string;
|
|
26
|
+
protected name: string;
|
|
27
|
+
protected fileHeader: string;
|
|
28
|
+
protected writer: IFileWriter;
|
|
29
|
+
protected importExt: string;
|
|
30
|
+
protected files: Record<string, TsFile>;
|
|
31
|
+
protected processResources: typeof processResources;
|
|
32
|
+
protected processTypes: typeof processTypes;
|
|
33
|
+
protected generateTypeFile: typeof generateTypeFile;
|
|
34
|
+
protected generateComplexTypeDefinition: typeof generateComplexTypeDefinition;
|
|
35
|
+
protected generateSimpleTypeDefinition: typeof generateSimpleTypeDefinition;
|
|
36
|
+
protected resolveTypeNameOrDef: typeof resolveTypeNameOrDef;
|
|
37
|
+
protected generateEnumTypeDefinition: typeof generateEnumTypeDefinition;
|
|
38
|
+
protected generateUnionTypeDefinition: typeof generateUnionTypeDefinition;
|
|
39
|
+
protected generateMappedTypeDefinition: typeof generateMappedTypeDefinition;
|
|
40
|
+
protected constructor(config: ApiExporter.Config);
|
|
41
|
+
protected execute(): Promise<void>;
|
|
42
|
+
protected getFile(filePath: string): TsFile;
|
|
43
|
+
protected addFile(filePath: string, returnExists?: boolean): TsFile;
|
|
44
|
+
protected cleanDirectory(dirname: string): void;
|
|
45
|
+
static execute(config: ApiExporter.Config): Promise<void>;
|
|
46
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import process from 'node:process';
|
|
5
|
+
import { OpraHttpClient } from '@opra/node-client';
|
|
6
|
+
import { FileWriter } from './file-writer.js';
|
|
7
|
+
import { processResources } from './process-resources.js';
|
|
8
|
+
import { generateComplexTypeDefinition, generateEnumTypeDefinition, generateMappedTypeDefinition, generateSimpleTypeDefinition, generateTypeFile, generateUnionTypeDefinition, processTypes, resolveTypeNameOrDef } from './process-types.js';
|
|
9
|
+
import { TsFile } from './ts-file.js';
|
|
10
|
+
export class ApiExporter {
|
|
11
|
+
constructor(config) {
|
|
12
|
+
this.files = {};
|
|
13
|
+
this.client = new OpraHttpClient(config.serviceUrl);
|
|
14
|
+
this.cwd = config.cwd || process.cwd();
|
|
15
|
+
this.outDir = path.resolve(this.cwd, config.outDir);
|
|
16
|
+
this.logger = config.logger || {
|
|
17
|
+
log: () => void 0,
|
|
18
|
+
error: () => void 0,
|
|
19
|
+
debug: () => void 0,
|
|
20
|
+
warn: () => void 0,
|
|
21
|
+
verbose: () => void 0,
|
|
22
|
+
};
|
|
23
|
+
this.fileHeader = config.fileHeader || '';
|
|
24
|
+
this.importExt = config.importExt || '';
|
|
25
|
+
this.writer = config.writer || new FileWriter();
|
|
26
|
+
// this.nsMap = nsMap || new ResponsiveMap(); // implement references later
|
|
27
|
+
}
|
|
28
|
+
async execute() {
|
|
29
|
+
this.logger.log(chalk.yellow('Fetching service metadata from'), chalk.whiteBright(this.client.serviceUrl));
|
|
30
|
+
this.document = await this.client.getMetadata();
|
|
31
|
+
this.logger.log(chalk.yellow('Retrieved service info:\n'), chalk.white('Title:'), chalk.magenta(this.document.info.title), '\n', chalk.white('Version:'), chalk.magenta(this.document.info.version), '\n', chalk.white('Resources:'), chalk.magenta(this.document.resources.size), 'resources found\n', chalk.white('Types:'), chalk.magenta(this.document.types.size), 'types found\n');
|
|
32
|
+
this.name = (this.name || this.document.info.title || 'Service1').replace(/[^\w_$]*/g, '');
|
|
33
|
+
this.name = this.name.charAt(0).toUpperCase() + this.name.substring(1);
|
|
34
|
+
this.fileHeader += `/*
|
|
35
|
+
* ${this.document.info.title}
|
|
36
|
+
* Version ${this.document.info.version}
|
|
37
|
+
* ${this.client.serviceUrl}
|
|
38
|
+
*/`;
|
|
39
|
+
this.logger.log(chalk.yellow('Removing old files..'));
|
|
40
|
+
this.cleanDirectory(this.outDir);
|
|
41
|
+
this.logger.log(chalk.yellow(`Generating service interface ( ${chalk.whiteBright(this.name)} )`));
|
|
42
|
+
fs.mkdirSync(this.outDir, { recursive: true });
|
|
43
|
+
await this.processTypes();
|
|
44
|
+
await this.processResources();
|
|
45
|
+
// Write files
|
|
46
|
+
for (const file of Object.values(this.files)) {
|
|
47
|
+
const targetDir = path.dirname(file.filename);
|
|
48
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
49
|
+
await this.writer.writeFile(file.filename, file.generate({ importExt: this.importExt }));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
getFile(filePath) {
|
|
53
|
+
return this.files[path.resolve(path.join(this.outDir, filePath))];
|
|
54
|
+
}
|
|
55
|
+
addFile(filePath, returnExists) {
|
|
56
|
+
let file = this.getFile(filePath);
|
|
57
|
+
if (file) {
|
|
58
|
+
if (returnExists)
|
|
59
|
+
return file;
|
|
60
|
+
throw new Error(`File "${filePath}" already exists`);
|
|
61
|
+
}
|
|
62
|
+
file = new TsFile(path.resolve(path.join(this.outDir, filePath)));
|
|
63
|
+
file.header = this.fileHeader;
|
|
64
|
+
this.files[file.filename] = file;
|
|
65
|
+
return file;
|
|
66
|
+
}
|
|
67
|
+
cleanDirectory(dirname) {
|
|
68
|
+
if (!fs.existsSync(dirname))
|
|
69
|
+
return;
|
|
70
|
+
const files = fs.readdirSync(dirname);
|
|
71
|
+
for (const f of files) {
|
|
72
|
+
const absolutePath = path.join(dirname, f);
|
|
73
|
+
if (fs.statSync(absolutePath).isDirectory()) {
|
|
74
|
+
this.cleanDirectory(absolutePath);
|
|
75
|
+
if (!fs.readdirSync(absolutePath).length)
|
|
76
|
+
fs.rmdirSync(absolutePath);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (path.extname(f) === '.ts') {
|
|
80
|
+
const contents = fs.readFileSync(absolutePath, 'utf-8');
|
|
81
|
+
if (contents.includes('#!oprimp_auto_generated!#')) {
|
|
82
|
+
fs.unlinkSync(absolutePath);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
static async execute(config) {
|
|
88
|
+
const exporter = new ApiExporter(config);
|
|
89
|
+
await exporter.execute();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
(() => {
|
|
93
|
+
ApiExporter.prototype.processResources = processResources;
|
|
94
|
+
ApiExporter.prototype.processTypes = processTypes;
|
|
95
|
+
ApiExporter.prototype.generateTypeFile = generateTypeFile;
|
|
96
|
+
ApiExporter.prototype.generateComplexTypeDefinition = generateComplexTypeDefinition;
|
|
97
|
+
ApiExporter.prototype.generateSimpleTypeDefinition = generateSimpleTypeDefinition;
|
|
98
|
+
ApiExporter.prototype.resolveTypeNameOrDef = resolveTypeNameOrDef;
|
|
99
|
+
ApiExporter.prototype.generateEnumTypeDefinition = generateEnumTypeDefinition;
|
|
100
|
+
ApiExporter.prototype.generateUnionTypeDefinition = generateUnionTypeDefinition;
|
|
101
|
+
ApiExporter.prototype.generateMappedTypeDefinition = generateMappedTypeDefinition;
|
|
102
|
+
})();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './api-exporter.js';
|