@opra/cli 0.14.2 → 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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './api-exporter.js';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { Collection, joinPath, Singleton } from '@opra/common';
|
|
4
|
+
import { wrapJSDocString } from '../utils/string-utils.js';
|
|
5
|
+
/**
|
|
6
|
+
*
|
|
7
|
+
* @param targetDir
|
|
8
|
+
*/
|
|
9
|
+
export async function processResources(targetDir = '') {
|
|
10
|
+
this.logger.log(chalk.yellow('Processing resources'));
|
|
11
|
+
const { document } = this;
|
|
12
|
+
const serviceTs = this.addFile(path.join(targetDir, this.name + '.ts'));
|
|
13
|
+
serviceTs.addImportPackage('@opra/client', ['HttpServiceBase']);
|
|
14
|
+
const indexTs = this.addFile('/index.ts', true);
|
|
15
|
+
indexTs.addExportFile(serviceTs.filename);
|
|
16
|
+
serviceTs.content = `\nexport class ${this.name} extends HttpServiceBase {\n`;
|
|
17
|
+
for (const resource of document.resources.values()) {
|
|
18
|
+
serviceTs.content += `\n/**\n * ${wrapJSDocString(resource.description || resource.name)}
|
|
19
|
+
* @url ${joinPath(this.client.serviceUrl, '$metadata#resources/' + resource.name)}
|
|
20
|
+
*/`;
|
|
21
|
+
if (resource instanceof Collection) {
|
|
22
|
+
const typeName = resource.type.name || '';
|
|
23
|
+
serviceTs.addImportPackage('@opra/client', ['HttpCollectionNode']);
|
|
24
|
+
serviceTs.addImportFile('types/' + typeName, [typeName]);
|
|
25
|
+
const operations = Object.keys(resource.operations)
|
|
26
|
+
.map(x => `'${x}'`).join(' | ');
|
|
27
|
+
serviceTs.content += `
|
|
28
|
+
get ${resource.name}(): Pick<HttpCollectionNode<${typeName}>, ${operations}> {
|
|
29
|
+
return this.$client.collection('${resource.name}');
|
|
30
|
+
}\n`;
|
|
31
|
+
}
|
|
32
|
+
else if (resource instanceof Singleton) {
|
|
33
|
+
const typeName = resource.type.name || '';
|
|
34
|
+
serviceTs.addImportPackage('@opra/client', ['HttpSingletonNode']);
|
|
35
|
+
serviceTs.addImportFile('types/' + typeName, [typeName]);
|
|
36
|
+
const operations = Object.keys(resource.operations)
|
|
37
|
+
.map(x => `'${x}'`).join(' | ');
|
|
38
|
+
serviceTs.content += `
|
|
39
|
+
get ${resource.name}(): Pick<HttpSingletonNode<${typeName}>, ${operations}> {
|
|
40
|
+
return this.$client.singleton('${resource.name}');
|
|
41
|
+
}\n`;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
serviceTs.content += '}';
|
|
45
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { ComplexType, DataType, EnumType, MappedType, SimpleType, UnionType } from '@opra/common';
|
|
2
|
+
import type { ApiExporter } from './api-exporter.js';
|
|
3
|
+
import { TsFile } from './ts-file.js';
|
|
4
|
+
/**
|
|
5
|
+
*
|
|
6
|
+
* @param targetDir
|
|
7
|
+
*/
|
|
8
|
+
export declare function processTypes(this: ApiExporter, targetDir?: string): Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
*
|
|
11
|
+
* @param dataType
|
|
12
|
+
* @param targetDir
|
|
13
|
+
*/
|
|
14
|
+
export declare function generateTypeFile(this: ApiExporter, dataType: DataType, targetDir?: string): Promise<TsFile>;
|
|
15
|
+
/**
|
|
16
|
+
*
|
|
17
|
+
* @param file
|
|
18
|
+
* @param dataType
|
|
19
|
+
* @param forInterface
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveTypeNameOrDef(this: ApiExporter, file: TsFile, dataType: DataType, forInterface?: boolean): Promise<string>;
|
|
22
|
+
/**
|
|
23
|
+
*
|
|
24
|
+
* @param file
|
|
25
|
+
* @param dataType
|
|
26
|
+
* @param forInterface
|
|
27
|
+
*/
|
|
28
|
+
export declare function generateComplexTypeDefinition(this: ApiExporter, file: TsFile, dataType: ComplexType, forInterface?: boolean): Promise<string>;
|
|
29
|
+
/**
|
|
30
|
+
*
|
|
31
|
+
* @param file
|
|
32
|
+
* @param dataType
|
|
33
|
+
*/
|
|
34
|
+
export declare function generateSimpleTypeDefinition(this: ApiExporter, file: TsFile, dataType: SimpleType): Promise<string>;
|
|
35
|
+
/**
|
|
36
|
+
*
|
|
37
|
+
* @param file
|
|
38
|
+
* @param dataType
|
|
39
|
+
*/
|
|
40
|
+
export declare function generateEnumTypeDefinition(this: ApiExporter, file: TsFile, dataType: EnumType): Promise<string>;
|
|
41
|
+
/**
|
|
42
|
+
*
|
|
43
|
+
* @param file
|
|
44
|
+
* @param dataType
|
|
45
|
+
* @param forInterface
|
|
46
|
+
*/
|
|
47
|
+
export declare function generateUnionTypeDefinition(this: ApiExporter, file: TsFile, dataType: UnionType, forInterface?: boolean): Promise<string>;
|
|
48
|
+
/**
|
|
49
|
+
*
|
|
50
|
+
* @param file
|
|
51
|
+
* @param dataType
|
|
52
|
+
* @param forInterface
|
|
53
|
+
*/
|
|
54
|
+
export declare function generateMappedTypeDefinition(this: ApiExporter, file: TsFile, dataType: MappedType, forInterface?: boolean): Promise<string>;
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { ComplexType, EnumType, joinPath, MappedType, SimpleType, UnionType } from '@opra/common';
|
|
4
|
+
import { wrapJSDocString } from '../utils/string-utils.js';
|
|
5
|
+
const internalTypeNames = ['boolean', 'bigint', 'number', 'null', 'string'];
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
* @param targetDir
|
|
9
|
+
*/
|
|
10
|
+
export async function processTypes(targetDir = '') {
|
|
11
|
+
this.logger.log(chalk.yellow('Processing types'));
|
|
12
|
+
const { document } = this;
|
|
13
|
+
const typesTs = this.addFile(path.join(targetDir, 'types.ts'));
|
|
14
|
+
for (const dataType of document.types.values()) {
|
|
15
|
+
const expFile = await this.generateTypeFile(dataType, targetDir);
|
|
16
|
+
typesTs.addExportFile(expFile.filename);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
*
|
|
21
|
+
* @param dataType
|
|
22
|
+
* @param targetDir
|
|
23
|
+
*/
|
|
24
|
+
export async function generateTypeFile(dataType, targetDir = '') {
|
|
25
|
+
const typeName = dataType.name;
|
|
26
|
+
if (!typeName)
|
|
27
|
+
throw new TypeError(`DataType has no name`);
|
|
28
|
+
let filePath;
|
|
29
|
+
if (dataType instanceof SimpleType)
|
|
30
|
+
filePath = '/simple-types.ts';
|
|
31
|
+
else if (dataType instanceof ComplexType)
|
|
32
|
+
filePath = `/types/${typeName}.ts`;
|
|
33
|
+
else if (dataType instanceof EnumType) {
|
|
34
|
+
filePath = `/enums/${typeName}.ts`;
|
|
35
|
+
}
|
|
36
|
+
else
|
|
37
|
+
throw new TypeError(`Unimplemented DataType (${dataType.kind})`);
|
|
38
|
+
const file = this.addFile(path.join(targetDir, filePath), true);
|
|
39
|
+
if (file.exportTypes.includes(typeName))
|
|
40
|
+
return file;
|
|
41
|
+
file.exportTypes.push(typeName);
|
|
42
|
+
const indexTs = this.addFile('/index.ts', true);
|
|
43
|
+
indexTs.addExportFile(file.filename);
|
|
44
|
+
file.content += `\n/**\n * ${wrapJSDocString(dataType.description || typeName)}
|
|
45
|
+
* @type ${typeName}
|
|
46
|
+
* @kind ${dataType.kind}
|
|
47
|
+
* @url ${joinPath(this.client.serviceUrl, '$metadata#types/' + typeName)}
|
|
48
|
+
*/\n`;
|
|
49
|
+
if (dataType instanceof SimpleType) {
|
|
50
|
+
file.content += `export type ${typeName} = ` + await this.generateSimpleTypeDefinition(file, dataType);
|
|
51
|
+
}
|
|
52
|
+
else if (dataType instanceof EnumType) {
|
|
53
|
+
file.content += `export enum ${typeName} ` + await this.generateEnumTypeDefinition(file, dataType);
|
|
54
|
+
}
|
|
55
|
+
else if (dataType instanceof ComplexType) {
|
|
56
|
+
file.content += `export class ${typeName} {
|
|
57
|
+
constructor(init?: Partial<I${typeName}>) {
|
|
58
|
+
if (init)
|
|
59
|
+
Object.assign(this, init);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface ${typeName} extends I${typeName} {
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface I${typeName} ${await this.generateComplexTypeDefinition(file, dataType, true)}
|
|
67
|
+
`;
|
|
68
|
+
}
|
|
69
|
+
return file;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
*
|
|
73
|
+
* @param file
|
|
74
|
+
* @param dataType
|
|
75
|
+
* @param forInterface
|
|
76
|
+
*/
|
|
77
|
+
export async function resolveTypeNameOrDef(file, dataType, forInterface) {
|
|
78
|
+
if (dataType.name) {
|
|
79
|
+
if (internalTypeNames.includes(dataType.name))
|
|
80
|
+
return dataType.name;
|
|
81
|
+
const f = await this.generateTypeFile(dataType);
|
|
82
|
+
file.addImportFile(f.filename, [dataType.name]);
|
|
83
|
+
return dataType.name;
|
|
84
|
+
}
|
|
85
|
+
if (dataType instanceof ComplexType)
|
|
86
|
+
return this.generateComplexTypeDefinition(file, dataType, forInterface);
|
|
87
|
+
if (dataType instanceof SimpleType)
|
|
88
|
+
return this.generateSimpleTypeDefinition(file, dataType);
|
|
89
|
+
if (dataType instanceof EnumType)
|
|
90
|
+
return this.generateEnumTypeDefinition(file, dataType);
|
|
91
|
+
if (dataType instanceof UnionType)
|
|
92
|
+
return this.generateUnionTypeDefinition(file, dataType, forInterface);
|
|
93
|
+
if (dataType instanceof MappedType)
|
|
94
|
+
return this.generateMappedTypeDefinition(file, dataType, forInterface);
|
|
95
|
+
return 'xxx';
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
*
|
|
99
|
+
* @param file
|
|
100
|
+
* @param dataType
|
|
101
|
+
* @param forInterface
|
|
102
|
+
*/
|
|
103
|
+
export async function generateComplexTypeDefinition(file, dataType, forInterface) {
|
|
104
|
+
let out = '';
|
|
105
|
+
if (dataType.base) {
|
|
106
|
+
const base = await this.resolveTypeNameOrDef(file, dataType.base, forInterface);
|
|
107
|
+
out += forInterface ? `extends ${base} ` : `${base} & `;
|
|
108
|
+
}
|
|
109
|
+
out += '{\n\n\t';
|
|
110
|
+
for (const field of dataType.own.fields.values()) {
|
|
111
|
+
// Print JSDoc
|
|
112
|
+
let jsDoc = '';
|
|
113
|
+
if (field.description)
|
|
114
|
+
jsDoc += ` * ${field.description}\n`;
|
|
115
|
+
if (field.default)
|
|
116
|
+
jsDoc += ` * @default ` + field.default + '\n';
|
|
117
|
+
if (field.format)
|
|
118
|
+
jsDoc += ` * @format ` + field.format + '\n';
|
|
119
|
+
if (field.exclusive)
|
|
120
|
+
jsDoc += ` * @exclusive\n`;
|
|
121
|
+
if (field.deprecated)
|
|
122
|
+
jsDoc += ` * @deprecated ` + (typeof field.deprecated === 'string' ? field.deprecated : '') + '\n';
|
|
123
|
+
if (jsDoc)
|
|
124
|
+
out += `/**\n${jsDoc}*/\n`;
|
|
125
|
+
// Print field name
|
|
126
|
+
out += `${field.name}${field.required ? '' : '?'}: `;
|
|
127
|
+
if (field.fixed)
|
|
128
|
+
out += `${field.fixed}`;
|
|
129
|
+
else {
|
|
130
|
+
out += await this.resolveTypeNameOrDef(file, field.type) +
|
|
131
|
+
`${field.isArray ? '[]' : ''};\n\n`;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (dataType.additionalFields)
|
|
135
|
+
out += '[key: string]: any;\n';
|
|
136
|
+
return out + '\b}';
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
*
|
|
140
|
+
* @param file
|
|
141
|
+
* @param dataType
|
|
142
|
+
*/
|
|
143
|
+
export async function generateSimpleTypeDefinition(file, dataType) {
|
|
144
|
+
if (dataType.ctor === Boolean)
|
|
145
|
+
return 'boolean';
|
|
146
|
+
if (dataType.ctor === String)
|
|
147
|
+
return 'string';
|
|
148
|
+
if (dataType.ctor === Number)
|
|
149
|
+
return 'number';
|
|
150
|
+
if (dataType.ctor === Date)
|
|
151
|
+
return 'Date';
|
|
152
|
+
if (dataType.extendsFrom('bigint'))
|
|
153
|
+
return 'Date';
|
|
154
|
+
if (dataType.extendsFrom('object'))
|
|
155
|
+
return 'object';
|
|
156
|
+
return 'any';
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
*
|
|
160
|
+
* @param file
|
|
161
|
+
* @param dataType
|
|
162
|
+
*/
|
|
163
|
+
export async function generateEnumTypeDefinition(file, dataType) {
|
|
164
|
+
let out = '{\n\t';
|
|
165
|
+
for (const [k, v] of Object.entries(dataType.values)) {
|
|
166
|
+
// Print JSDoc
|
|
167
|
+
let jsDoc = '';
|
|
168
|
+
if (dataType.meanings[k])
|
|
169
|
+
jsDoc += ` * ${dataType.meanings[k]}\n`;
|
|
170
|
+
if (jsDoc)
|
|
171
|
+
out += `/**\n${jsDoc}*/\n`;
|
|
172
|
+
out += `${k}`;
|
|
173
|
+
if (v)
|
|
174
|
+
out += ' = ' + (typeof v === 'number' ? v : ('"' + ('' + v).replace('"', '\\"')) + '"');
|
|
175
|
+
out += ',\n\n';
|
|
176
|
+
}
|
|
177
|
+
return out + '\b}';
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
*
|
|
181
|
+
* @param file
|
|
182
|
+
* @param dataType
|
|
183
|
+
* @param forInterface
|
|
184
|
+
*/
|
|
185
|
+
export async function generateUnionTypeDefinition(file, dataType, forInterface) {
|
|
186
|
+
// let out = '';
|
|
187
|
+
return (await Promise.all(dataType.types
|
|
188
|
+
.map(t => this.resolveTypeNameOrDef(file, t, forInterface)))).join(forInterface ? ', ' : ' & ');
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
*
|
|
192
|
+
* @param file
|
|
193
|
+
* @param dataType
|
|
194
|
+
* @param forInterface
|
|
195
|
+
*/
|
|
196
|
+
export async function generateMappedTypeDefinition(file, dataType, forInterface) {
|
|
197
|
+
return (dataType.pick ? 'Pick<' : 'Omit<') +
|
|
198
|
+
(await this.resolveTypeNameOrDef(file, dataType.type, forInterface)) +
|
|
199
|
+
', ' +
|
|
200
|
+
(dataType.pick || dataType.omit || [])
|
|
201
|
+
.map(x => `'${x}'`).join(' | ') +
|
|
202
|
+
'>';
|
|
203
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare class TsFile {
|
|
2
|
+
filename: string;
|
|
3
|
+
dirname: string;
|
|
4
|
+
importFiles: Record<string, string[]>;
|
|
5
|
+
exportFiles: Record<string, string[]>;
|
|
6
|
+
exportTypes: string[];
|
|
7
|
+
header: string;
|
|
8
|
+
content: string;
|
|
9
|
+
constructor(filename: string);
|
|
10
|
+
addImportPackage: (name: string, types?: string[]) => void;
|
|
11
|
+
addImportFile: (filename: string, types?: string[]) => void;
|
|
12
|
+
addExportFile: (filename: string, types?: string[]) => void;
|
|
13
|
+
generate(options?: {
|
|
14
|
+
importExt?: string;
|
|
15
|
+
}): string;
|
|
16
|
+
}
|
|
17
|
+
export declare function relativePath(from: string, to: string): string;
|
|
18
|
+
export declare function setExt(filename: string, ext: string): string;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import flattenText from 'putil-flattentext';
|
|
3
|
+
export class TsFile {
|
|
4
|
+
constructor(filename) {
|
|
5
|
+
this.filename = filename;
|
|
6
|
+
this.importFiles = {};
|
|
7
|
+
this.exportFiles = {};
|
|
8
|
+
this.exportTypes = [];
|
|
9
|
+
this.header = '';
|
|
10
|
+
this.content = '';
|
|
11
|
+
this.addImportPackage = (name, types) => {
|
|
12
|
+
this.importFiles[name] = this.importFiles[name] || [];
|
|
13
|
+
types?.forEach(x => {
|
|
14
|
+
if (!this.importFiles[name].includes(x))
|
|
15
|
+
this.importFiles[name].push(x);
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
this.addImportFile = (filename, types) => {
|
|
19
|
+
filename = path.resolve(this.dirname, filename);
|
|
20
|
+
this.importFiles[filename] = this.importFiles[filename] || [];
|
|
21
|
+
types?.forEach(x => {
|
|
22
|
+
if (!this.importFiles[filename].includes(x))
|
|
23
|
+
this.importFiles[filename].push(x);
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
this.addExportFile = (filename, types) => {
|
|
27
|
+
filename = path.resolve(this.dirname, filename);
|
|
28
|
+
if (filename.endsWith('.ts') || filename.endsWith('.js'))
|
|
29
|
+
filename = setExt(filename, '');
|
|
30
|
+
this.exportFiles[filename] = this.exportFiles[filename] || [];
|
|
31
|
+
types?.forEach(x => {
|
|
32
|
+
if (!this.exportFiles[filename].includes(x))
|
|
33
|
+
this.exportFiles[filename].push(x);
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
this.dirname = path.dirname(filename);
|
|
37
|
+
}
|
|
38
|
+
generate(options) {
|
|
39
|
+
const dirname = path.dirname(this.filename);
|
|
40
|
+
let output = '/* #!oprimp_auto_generated!# !! Do NOT remove this line */\n' +
|
|
41
|
+
(this.header ? flattenText(this.header) + '\n\n' : '\n');
|
|
42
|
+
const importStr = Object.keys(this.importFiles)
|
|
43
|
+
.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))
|
|
44
|
+
.map(filename => {
|
|
45
|
+
const types = this.importFiles[filename];
|
|
46
|
+
let relFile = filename;
|
|
47
|
+
if (path.isAbsolute(filename)) {
|
|
48
|
+
relFile = relativePath(dirname, filename);
|
|
49
|
+
if (options?.importExt)
|
|
50
|
+
relFile = setExt(relFile, options.importExt);
|
|
51
|
+
}
|
|
52
|
+
return `import ${types.length ? '{' + types.join(', ') + '} from ' : ''}'${relFile}';`;
|
|
53
|
+
})
|
|
54
|
+
.join('\n');
|
|
55
|
+
if (importStr)
|
|
56
|
+
output += flattenText(importStr) + '\n';
|
|
57
|
+
output += flattenText(this.content);
|
|
58
|
+
const exportStr = Object.keys(this.exportFiles)
|
|
59
|
+
.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))
|
|
60
|
+
.map(filename => {
|
|
61
|
+
const types = this.exportFiles[filename];
|
|
62
|
+
let relFile = filename;
|
|
63
|
+
if (path.isAbsolute(filename)) {
|
|
64
|
+
relFile = relativePath(dirname, filename);
|
|
65
|
+
if (options?.importExt)
|
|
66
|
+
relFile = setExt(relFile, options.importExt);
|
|
67
|
+
}
|
|
68
|
+
return `export ${types.length ? '{' + types.join(', ') + '}' : '*'} from '${relFile}';`;
|
|
69
|
+
})
|
|
70
|
+
.join('\n');
|
|
71
|
+
if (exportStr)
|
|
72
|
+
output += flattenText(exportStr) + '\n';
|
|
73
|
+
return output;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export function relativePath(from, to) {
|
|
77
|
+
const s = path.relative(from, to);
|
|
78
|
+
return s.startsWith('.') ? s : ('./' + s);
|
|
79
|
+
}
|
|
80
|
+
export function setExt(filename, ext) {
|
|
81
|
+
const e = path.extname(filename);
|
|
82
|
+
return filename.substring(0, filename.length - e.length) + (ext ? '.' + ext : '');
|
|
83
|
+
}
|
package/esm/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from './
|
|
1
|
+
export * from './api-exporter/index.js';
|
package/esm/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from './
|
|
1
|
+
export * from './api-exporter/index.js';
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ApiDocument } from '@opra/common';
|
|
2
2
|
import { IFileWriter } from './file-writer.interface.js';
|
|
3
3
|
import { ILogger } from './logger.interface.js';
|
|
4
4
|
export interface ServiceGenerationContext {
|
|
5
5
|
serviceUrl: string;
|
|
6
|
-
document:
|
|
6
|
+
document: ApiDocument;
|
|
7
7
|
name: string;
|
|
8
8
|
cwd: string;
|
|
9
9
|
logger: ILogger;
|
|
@@ -4,9 +4,10 @@ import * as console from 'console';
|
|
|
4
4
|
import * as fs from 'fs';
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import * as process from 'process';
|
|
7
|
-
import {
|
|
7
|
+
import { ApiExporter } from './api-exporter/api-exporter.js';
|
|
8
8
|
import { getCallerFile } from './utils/get-caller-file.util.js';
|
|
9
|
-
const
|
|
9
|
+
const dirname = path.dirname(getCallerFile());
|
|
10
|
+
const pkgJson = JSON.parse(fs.readFileSync(path.resolve(dirname, '../package.json'), 'utf-8'));
|
|
10
11
|
program
|
|
11
12
|
.version(pkgJson.version)
|
|
12
13
|
.argument('<serviceUrl>', 'OPRA service url')
|
|
@@ -17,16 +18,14 @@ program
|
|
|
17
18
|
.action(async (serviceUrl, outDir, options) => {
|
|
18
19
|
if (!options.color)
|
|
19
20
|
chalk.level = 0;
|
|
20
|
-
await
|
|
21
|
+
await ApiExporter.execute({
|
|
21
22
|
serviceUrl,
|
|
23
|
+
logger: console,
|
|
22
24
|
outDir,
|
|
23
25
|
name: options.name,
|
|
24
|
-
|
|
25
|
-
fileHeader: '
|
|
26
|
-
'
|
|
27
|
-
' * Version: ' + pkgJson.version + '\n' +
|
|
28
|
-
' * ' + new Date() + '\n' +
|
|
29
|
-
' */'
|
|
26
|
+
importExt: (options.ext ? 'js' : ''),
|
|
27
|
+
fileHeader: '/* Generated by OPRA Service Generator, Version ' + pkgJson.version + '*/\n' +
|
|
28
|
+
'/* eslint-disable import/extensions,simple-import-sort/imports */\n'
|
|
30
29
|
});
|
|
31
30
|
console.log(chalk.greenBright('Completed'));
|
|
32
31
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opra/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Opra CLI tools",
|
|
5
5
|
"author": "Panates",
|
|
6
6
|
"license": "MIT",
|
|
@@ -20,22 +20,21 @@
|
|
|
20
20
|
"copy:bin": "cp -R bin ../../build/cli/bin",
|
|
21
21
|
"lint": "eslint . --max-warnings=0",
|
|
22
22
|
"check": "madge --circular src/**",
|
|
23
|
-
"test": "
|
|
24
|
-
"cover": "
|
|
23
|
+
"test": "jest",
|
|
24
|
+
"cover": "jest --collect-coverage",
|
|
25
25
|
"clean": "npm run clean:src && npm run clean:dist && npm run clean:cover",
|
|
26
26
|
"clean:src": "ts-cleanup -s src --all",
|
|
27
27
|
"clean:dist": "rimraf ../../build/cli ../../build/client",
|
|
28
28
|
"clean:cover": "rimraf ../../coverage/client"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@opra/node-client": "^0.
|
|
31
|
+
"@opra/node-client": "^0.15.0",
|
|
32
32
|
"chalk": "^5.2.0",
|
|
33
|
-
"commander": "^10.0.
|
|
33
|
+
"commander": "^10.0.1",
|
|
34
|
+
"js-string-escape": "^1.0.1",
|
|
34
35
|
"putil-flattentext": "^2.1.1",
|
|
35
|
-
"putil-varhelpers": "^1.6.5"
|
|
36
|
-
"js-string-escape": "^1.0.1"
|
|
36
|
+
"putil-varhelpers": "^1.6.5"
|
|
37
37
|
},
|
|
38
|
-
"devDependencies": {},
|
|
39
38
|
"type": "module",
|
|
40
39
|
"types": "esm/index.d.ts",
|
|
41
40
|
"exports": {
|
|
@@ -64,4 +63,4 @@
|
|
|
64
63
|
"tool",
|
|
65
64
|
"oprimp"
|
|
66
65
|
]
|
|
67
|
-
}
|
|
66
|
+
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.deleteFiles = void 0;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
|
-
const fs_1 = tslib_1.__importDefault(require("fs"));
|
|
6
|
-
const path_1 = tslib_1.__importDefault(require("path"));
|
|
7
|
-
function deleteFiles(dirname) {
|
|
8
|
-
if (!fs_1.default.existsSync(dirname))
|
|
9
|
-
return;
|
|
10
|
-
const files = fs_1.default.readdirSync(dirname);
|
|
11
|
-
for (const f of files) {
|
|
12
|
-
const filename = path_1.default.join(dirname, f);
|
|
13
|
-
if (fs_1.default.statSync(filename).isDirectory()) {
|
|
14
|
-
deleteFiles(filename);
|
|
15
|
-
if (!fs_1.default.readdirSync(filename).length)
|
|
16
|
-
fs_1.default.rmdirSync(filename);
|
|
17
|
-
continue;
|
|
18
|
-
}
|
|
19
|
-
if (path_1.default.extname(f) === '.ts') {
|
|
20
|
-
const contents = fs_1.default.readFileSync(filename, 'utf-8');
|
|
21
|
-
if (contents.includes('#!oprimp_auto_generated!#')) {
|
|
22
|
-
fs_1.default.unlinkSync(filename);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
exports.deleteFiles = deleteFiles;
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.generateService = void 0;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
|
-
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
6
|
-
const console_1 = tslib_1.__importDefault(require("console"));
|
|
7
|
-
const fs = tslib_1.__importStar(require("fs"));
|
|
8
|
-
const path_1 = tslib_1.__importDefault(require("path"));
|
|
9
|
-
const process = tslib_1.__importStar(require("process"));
|
|
10
|
-
const node_client_1 = require("@opra/node-client");
|
|
11
|
-
const ts_file_js_1 = require("../utils/ts-file.js");
|
|
12
|
-
const delete_files_js_1 = require("./delete-files.js");
|
|
13
|
-
const file_writer_js_1 = require("./file-writer.js");
|
|
14
|
-
const process_resoruces_js_1 = require("./process-resoruces.js");
|
|
15
|
-
const process_types_js_1 = require("./process-types.js");
|
|
16
|
-
async function generateService(config) {
|
|
17
|
-
const cwd = config.cwd || process.cwd();
|
|
18
|
-
const logger = config.logger || console_1.default;
|
|
19
|
-
try {
|
|
20
|
-
console_1.default.log(chalk_1.default.yellow('Fetching service metadata from'), chalk_1.default.whiteBright(config.serviceUrl));
|
|
21
|
-
const client = await node_client_1.OpraHttpClient.create(config.serviceUrl);
|
|
22
|
-
const metadata = client.metadata;
|
|
23
|
-
console_1.default.log(chalk_1.default.yellow('Retrieved service info:'), chalk_1.default.whiteBright(metadata.info.title), '-', chalk_1.default.whiteBright(metadata.info.version));
|
|
24
|
-
console_1.default.log(chalk_1.default.yellow('Removing old files..'));
|
|
25
|
-
(0, delete_files_js_1.deleteFiles)(config.outDir);
|
|
26
|
-
let name = (metadata.info.title || 'Service1').replace(/[^\w_$]*/g, '');
|
|
27
|
-
name = name.charAt(0).toUpperCase() + name.substring(1);
|
|
28
|
-
const ctx = {
|
|
29
|
-
serviceUrl: config.serviceUrl,
|
|
30
|
-
document: client.metadata,
|
|
31
|
-
name,
|
|
32
|
-
logger,
|
|
33
|
-
cwd,
|
|
34
|
-
relativeDir: config.outDir,
|
|
35
|
-
absoluteDir: path_1.default.resolve(cwd, config.outDir),
|
|
36
|
-
fileHeader: config.fileHeader || '',
|
|
37
|
-
extension: config.extension,
|
|
38
|
-
writer: config.writer || new file_writer_js_1.FileWriter()
|
|
39
|
-
};
|
|
40
|
-
fs.mkdirSync(ctx.absoluteDir, { recursive: true });
|
|
41
|
-
await (0, process_types_js_1.processTypes)(ctx);
|
|
42
|
-
await (0, process_resoruces_js_1.processResources)(ctx);
|
|
43
|
-
const indexTs = new ts_file_js_1.TsFile();
|
|
44
|
-
indexTs.header = ctx.fileHeader;
|
|
45
|
-
indexTs.addExport('./' + ctx.name + ctx.extension);
|
|
46
|
-
indexTs.addExport('./types' + ctx.extension);
|
|
47
|
-
await indexTs.writeFile(ctx, path_1.default.join(ctx.absoluteDir, 'index.ts'));
|
|
48
|
-
}
|
|
49
|
-
catch (error) {
|
|
50
|
-
logger.error(chalk_1.default.red(error.message));
|
|
51
|
-
process.exit(1);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
exports.generateService = generateService;
|
|
@@ -1,59 +0,0 @@
|
|
|
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 path_1 = tslib_1.__importDefault(require("path"));
|
|
7
|
-
const common_1 = require("@opra/common");
|
|
8
|
-
const string_utils_js_1 = require("../utils/string-utils.js");
|
|
9
|
-
const ts_file_js_1 = require("../utils/ts-file.js");
|
|
10
|
-
async function processResources(ctx) {
|
|
11
|
-
const targetDir = ctx.absoluteDir;
|
|
12
|
-
ctx.logger.log(chalk_1.default.yellow('Processing resources'));
|
|
13
|
-
const serviceTs = new ts_file_js_1.TsFile();
|
|
14
|
-
serviceTs.header = ctx.fileHeader;
|
|
15
|
-
serviceTs.addImport('@opra/client', 'OpraHttpClient');
|
|
16
|
-
serviceTs.content = `
|
|
17
|
-
const kClient = Symbol('client');
|
|
18
|
-
|
|
19
|
-
export class ${ctx.name} {
|
|
20
|
-
static kClient = kClient;
|
|
21
|
-
[kClient]: OpraHttpClient;
|
|
22
|
-
|
|
23
|
-
constructor(client: OpraHttpClient) {
|
|
24
|
-
this[kClient] = client;
|
|
25
|
-
}
|
|
26
|
-
`;
|
|
27
|
-
const resourceNames = Array.from(ctx.document.resources.keys()).sort();
|
|
28
|
-
for (const resourceName of resourceNames) {
|
|
29
|
-
const resource = ctx.document.getResource(resourceName);
|
|
30
|
-
serviceTs.content += `\n/**\n * ${(0, string_utils_js_1.wrapJSDocString)(resource.description || resource.name)}
|
|
31
|
-
* @url ${(0, common_1.joinPath)(ctx.serviceUrl, '$metadata/resources/' + resource.name)}
|
|
32
|
-
*/`;
|
|
33
|
-
if (resource instanceof common_1.CollectionResourceInfo) {
|
|
34
|
-
serviceTs.addImport('@opra/client', 'HttpCollectionService');
|
|
35
|
-
serviceTs.addImport('./types/' + resource.dataType.name + ctx.extension, resource.dataType.name);
|
|
36
|
-
const methods = resource.getHandlerNames()
|
|
37
|
-
.filter(x => x !== 'count')
|
|
38
|
-
.map(x => `'${x}'`).join(' | ');
|
|
39
|
-
serviceTs.content += `
|
|
40
|
-
get ${resource.name}(): Pick<HttpCollectionService<${resource.dataType.name}, never>, ${methods}> {
|
|
41
|
-
return this[kClient].collection('${resource.name}');
|
|
42
|
-
}\n`;
|
|
43
|
-
}
|
|
44
|
-
else if (resource instanceof common_1.SingletonResourceInfo) {
|
|
45
|
-
serviceTs.addImport('@opra/client', 'HttpSingletonService');
|
|
46
|
-
serviceTs.addImport('./types/' + resource.dataType.name + ctx.extension, resource.dataType.name);
|
|
47
|
-
const methods = resource.getHandlerNames()
|
|
48
|
-
.map(x => `'${x}'`).join(' | ');
|
|
49
|
-
serviceTs.content += `
|
|
50
|
-
get ${resource.name}(): Pick<HttpSingletonService<${resource.dataType.name}, never>, ${methods}> {
|
|
51
|
-
return this[kClient].singleton('${resource.name}');
|
|
52
|
-
}\n`;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
serviceTs.content += `
|
|
56
|
-
}\n`;
|
|
57
|
-
await serviceTs.writeFile(ctx, path_1.default.join(targetDir, ctx.name + '.ts'));
|
|
58
|
-
}
|
|
59
|
-
exports.processResources = processResources;
|