@sphereon/ssi-sdk.dev
Version:
149 lines • 6.62 kB
JavaScript
import { Extractor, ExtractorConfig } from '@microsoft/api-extractor';
import { ApiModel } from '@microsoft/api-extractor-model';
import { Command } from 'commander';
import { writeFileSync } from 'fs';
import { resolve } from 'path';
import * as TJS from 'ts-json-schema-generator';
import module from 'module';
const requireCjs = module.createRequire(import.meta.url);
const genericTypes = ['boolean', 'string', 'number', 'any', 'Array<string>'];
function createSchema(generator, symbol) {
if (genericTypes.includes(symbol)) {
return { components: { schemas: {} } };
}
const fixedSymbol = symbol.replace(/Array\<(.*)\>/gm, '$1').replace(/(\\:[\w]?Certificate)/gm, ': any');
const schema = generator.createSchema(fixedSymbol);
const newSchema = {
components: {
schemas: schema.definitions,
},
};
let schemaStr = JSON.stringify(newSchema, null, 2);
schemaStr = schemaStr.replace(/#\/definitions\//gm, '#/components/schemas/');
schemaStr = schemaStr.replace(/\"patternProperties\":{([^:]*):{[^}]*}}/gm, '"pattern": $1');
schemaStr = schemaStr.replace(/Verifiable\<(.*)\>/gm, 'Verifiable-$1');
schemaStr = schemaStr.replace(/Where\<(.*)\>/gm, 'Where-$1');
schemaStr = schemaStr.replace(/Order\<(.*)\>/gm, 'Order-$1');
schemaStr = schemaStr.replace(/FindArgs\<(.*)\>/gm, 'FindArgs-$1');
schemaStr = schemaStr.replace(/https \:\/\//gm, 'https://');
// a bug in the schema generator stack mangles @link tags with text.
schemaStr = schemaStr.replace(/\{@link\s+([^|}]+?)\s([^|}]+)\s}/g, '{@link $1 | $2 }');
return JSON.parse(schemaStr);
}
function getReference(response) {
if (!response) {
return { type: 'object' };
}
if (response.slice(0, 6) === 'Array<') {
const symbol = response.replace('Array<', '').replace('>', '');
return {
type: 'array',
items: genericTypes.includes(symbol) ? { type: symbol } : { $ref: '#/components/schemas/' + symbol },
};
}
response = response.replace(/(\\:?[\w]*Certificate)/gm, 'any');
if (response === 'any') {
return { type: 'object' };
}
if (['string', 'number', 'boolean', 'object', 'integer'].includes(response)) {
// @ts-ignore
return { type: response };
}
else {
return { $ref: '#/components/schemas/' + response };
}
}
const dev = new Command('dev').description('Plugin developer tools');
dev
.command('generate-plugin-schema')
.description('generate plugin schema')
.option('-c, --extractorConfig <string>', 'API Extractor config file', './api-extractor.json')
.option('-p, --packageConfig <string>', 'package.json file containing a Veramo plugin interface config', './package.json')
.action(async (options) => {
const apiExtractorJsonPath = resolve(options.extractorConfig);
const extractorConfig = ExtractorConfig.loadFileAndPrepare(apiExtractorJsonPath);
const extractorResult = Extractor.invoke(extractorConfig, {
localBuild: true,
showVerboseMessages: true,
});
if (!extractorResult.succeeded) {
console.error(`API Extractor completed with ${extractorResult.errorCount} errors` + ` and ${extractorResult.warningCount} warnings`);
process.exitCode = 1;
}
const packageConfig = requireCjs(resolve(options.packageConfig));
const interfaces = {};
for (const pluginInterfaceName in packageConfig?.veramo?.pluginInterfaces) {
const entryFile = packageConfig.veramo.pluginInterfaces[pluginInterfaceName];
const api = {
components: {
schemas: {},
methods: {},
},
};
const generator = TJS.createGenerator({
path: resolve(entryFile),
encodeRefs: false,
additionalProperties: true,
skipTypeCheck: true,
// functions: 'hide',
});
const apiModel = new ApiModel();
const apiPackage = apiModel.loadPackage(extractorConfig.apiJsonFilePath);
const entry = apiPackage.entryPoints[0];
const pluginInterface = entry.findMembersByName(pluginInterfaceName)[0];
for (const member of pluginInterface.members) {
const method = {};
method.pluginInterfaceName = pluginInterfaceName;
method.operationId = member.displayName;
// console.log(member)
method.parameters = member.parameters[0]?.parameterTypeExcerpt?.text;
method.response = member.returnTypeExcerpt.text.replace('Promise<', '').replace('>', '');
const methodSignature = member;
method.description = methodSignature.tsdocComment?.summarySection
?.getChildNodes()[0]
// @ts-ignore
?.getChildNodes()[0]?.text;
method.description = method.description || '';
if (method.parameters) {
// @ts-ignore
api.components.schemas = {
// @ts-ignore
...api.components.schemas,
...createSchema(generator, method.parameters).components.schemas,
};
}
// @ts-ignore
api.components.schemas = {
// @ts-ignore
...api.components.schemas,
...createSchema(generator, method.response).components.schemas,
};
// @ts-ignore
api.components.methods[method.operationId] = {
description: method.description,
arguments: getReference(method.parameters),
returnType: getReference(method.response),
};
}
interfaces[pluginInterfaceName] = api;
}
writeFileSync(resolve('./plugin.schema.json'), JSON.stringify(interfaces, null, 2));
});
dev
.command('extract-api')
.description('Extract API')
.option('-c, --extractorConfig <string>', 'API Extractor config file', './api-extractor.json')
.action(async (options) => {
const apiExtractorJsonPath = resolve(options.extractorConfig);
const extractorConfig = ExtractorConfig.loadFileAndPrepare(apiExtractorJsonPath);
const extractorResult = Extractor.invoke(extractorConfig, {
localBuild: true,
showVerboseMessages: true,
});
if (!extractorResult.succeeded) {
console.error(`API Extractor completed with ${extractorResult.errorCount} errors` + ` and ${extractorResult.warningCount} warnings`);
process.exitCode = 1;
}
});
export { dev };
//# sourceMappingURL=dev.js.map