@vendure/cli
Version:
A modern, headless ecommerce framework
343 lines • 17.3 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.addServiceCommand = void 0;
const prompts_1 = require("@clack/prompts");
const change_case_1 = require("change-case");
const path_1 = __importDefault(require("path"));
const ts_morph_1 = require("ts-morph");
const constants_1 = require("../../../constants");
const cli_command_1 = require("../../../shared/cli-command");
const service_ref_1 = require("../../../shared/service-ref");
const shared_prompts_1 = require("../../../shared/shared-prompts");
const vendure_plugin_ref_1 = require("../../../shared/vendure-plugin-ref");
const ast_utils_1 = require("../../../utilities/ast-utils");
const utils_1 = require("../../../utilities/utils");
const add_entity_1 = require("../entity/add-entity");
const cancelledMessage = 'Add service cancelled';
exports.addServiceCommand = new cli_command_1.CliCommand({
id: 'add-service',
category: 'Plugin: Service',
description: 'Add a new service to a plugin',
run: options => addService(options),
});
async function addService(providedOptions) {
var _a, _b, _c, _d;
const providedVendurePlugin = providedOptions === null || providedOptions === void 0 ? void 0 : providedOptions.plugin;
const { project } = await (0, shared_prompts_1.analyzeProject)({
providedVendurePlugin,
cancelledMessage,
config: providedOptions === null || providedOptions === void 0 ? void 0 : providedOptions.config,
});
const isNonInteractive = (providedOptions === null || providedOptions === void 0 ? void 0 : providedOptions.serviceName) !== undefined || (providedOptions === null || providedOptions === void 0 ? void 0 : providedOptions.isNonInteractive);
if (isNonInteractive && !providedVendurePlugin && !(providedOptions === null || providedOptions === void 0 ? void 0 : providedOptions.pluginName)) {
throw new Error('Plugin must be specified when running in non-interactive mode. Use selectPlugin in interactive mode.');
}
let vendurePlugin = providedVendurePlugin;
if ((providedOptions === null || providedOptions === void 0 ? void 0 : providedOptions.pluginName) && !vendurePlugin) {
const pluginClasses = (0, ast_utils_1.getPluginClasses)(project);
const pluginClass = pluginClasses.find((p) => p.getName() === providedOptions.pluginName);
if (!pluginClass) {
const availablePlugins = pluginClasses.map((p) => p.getName()).join(', ');
throw new Error(`Plugin "${providedOptions.pluginName}" not found. Available plugins: ${availablePlugins}`);
}
vendurePlugin = new vendure_plugin_ref_1.VendurePluginRef(pluginClass);
}
vendurePlugin = vendurePlugin !== null && vendurePlugin !== void 0 ? vendurePlugin : (await (0, shared_prompts_1.selectPlugin)(project, cancelledMessage));
const modifiedSourceFiles = [];
const type = (_b = (_a = providedOptions === null || providedOptions === void 0 ? void 0 : providedOptions.type) !== null && _a !== void 0 ? _a : providedOptions === null || providedOptions === void 0 ? void 0 : providedOptions.serviceType) !== null && _b !== void 0 ? _b : (isNonInteractive
? 'basic'
: await (0, utils_1.withInteractiveTimeout)(async () => {
return await (0, prompts_1.select)({
message: 'What type of service would you like to add?',
options: [
{ value: 'basic', label: 'Basic empty service' },
{ value: 'entity', label: 'Service to perform CRUD operations on an entity' },
],
maxItems: 10,
});
}));
if (!isNonInteractive && (0, prompts_1.isCancel)(type)) {
(0, prompts_1.cancel)('Cancelled');
process.exit(0);
}
const options = {
type,
serviceName: (_c = providedOptions === null || providedOptions === void 0 ? void 0 : providedOptions.serviceName) !== null && _c !== void 0 ? _c : 'MyService',
config: providedOptions === null || providedOptions === void 0 ? void 0 : providedOptions.config,
};
if (type === 'entity') {
let entityRef;
if ((providedOptions === null || providedOptions === void 0 ? void 0 : providedOptions.selectedEntityName) && isNonInteractive) {
const entities = vendurePlugin.getEntities();
const foundEntity = entities.find(entity => entity.name === providedOptions.selectedEntityName);
if (!foundEntity) {
const availableEntities = entities.map(entity => entity.name).join(', ');
throw new Error(`Entity "${providedOptions.selectedEntityName}" not found in plugin "${vendurePlugin.name}". Available entities: ${availableEntities || 'none'}`);
}
entityRef = foundEntity;
}
else {
try {
entityRef = await (0, shared_prompts_1.selectEntity)(vendurePlugin);
}
catch (e) {
if (e.message === constants_1.Messages.NoEntitiesFound) {
prompts_1.log.info(`No entities found in plugin ${vendurePlugin.name}. Let's create one first.`);
const result = await add_entity_1.addEntityCommand.run({ plugin: vendurePlugin });
entityRef = result.entityRef;
modifiedSourceFiles.push(...result.modifiedSourceFiles);
}
else {
throw e;
}
}
}
options.entityRef = entityRef;
options.serviceName = `${entityRef.name}Service`;
}
const serviceSpinner = (0, prompts_1.spinner)();
let serviceSourceFile;
let serviceClassDeclaration;
if (options.type === 'basic') {
const name = options.serviceName !== 'MyService'
? options.serviceName
: await (0, prompts_1.text)({
message: 'What is the name of the new service?',
initialValue: 'MyService',
validate: input => {
if (!input) {
return 'The service name cannot be empty';
}
if (!constants_1.pascalCaseRegex.test(input)) {
return 'The service name must be in PascalCase, e.g. "MyService"';
}
},
});
if (!isNonInteractive && (0, prompts_1.isCancel)(name)) {
(0, prompts_1.cancel)(cancelledMessage);
process.exit(0);
}
options.serviceName = name;
serviceSpinner.start(`Creating ${options.serviceName}...`);
const serviceSourceFilePath = getServiceFilePath(vendurePlugin, options.serviceName);
await (0, utils_1.pauseForPromptDisplay)();
serviceSourceFile = (0, ast_utils_1.createFile)(project, path_1.default.join(__dirname, 'templates/basic-service.template.ts'), serviceSourceFilePath);
serviceClassDeclaration = serviceSourceFile
.getClass('BasicServiceTemplate')
.rename(options.serviceName);
}
else {
serviceSpinner.start(`Creating ${options.serviceName}...`);
await (0, utils_1.pauseForPromptDisplay)();
const serviceSourceFilePath = getServiceFilePath(vendurePlugin, options.serviceName);
serviceSourceFile = (0, ast_utils_1.createFile)(project, path_1.default.join(__dirname, 'templates/entity-service.template.ts'), serviceSourceFilePath);
serviceClassDeclaration = serviceSourceFile
.getClass('EntityServiceTemplate')
.rename(options.serviceName);
const entityRef = options.entityRef;
if (!entityRef) {
throw new Error('Entity class not found');
}
const templateEntityClass = serviceSourceFile.getClass('TemplateEntity');
if (templateEntityClass) {
templateEntityClass.rename(entityRef.name);
templateEntityClass.remove();
}
(0, ast_utils_1.addImportsToFile)(serviceClassDeclaration.getSourceFile(), {
moduleSpecifier: entityRef.classDeclaration.getSourceFile(),
namedImports: [entityRef.name],
});
const templateTranslationEntityClass = serviceSourceFile.getClass('TemplateEntityTranslation');
if (entityRef.isTranslatable()) {
const translationEntityClass = entityRef.getTranslationClass();
if (translationEntityClass && templateTranslationEntityClass) {
templateTranslationEntityClass.rename(translationEntityClass === null || translationEntityClass === void 0 ? void 0 : translationEntityClass.getName());
templateTranslationEntityClass.remove();
(0, ast_utils_1.addImportsToFile)(serviceClassDeclaration.getSourceFile(), {
moduleSpecifier: translationEntityClass.getSourceFile(),
namedImports: [translationEntityClass.getName()],
});
}
}
else {
templateTranslationEntityClass === null || templateTranslationEntityClass === void 0 ? void 0 : templateTranslationEntityClass.remove();
}
(0, ast_utils_1.customizeCreateUpdateInputInterfaces)(serviceSourceFile, entityRef);
customizeFindOneMethod(serviceClassDeclaration, entityRef);
customizeFindAllMethod(serviceClassDeclaration, entityRef);
customizeCreateMethod(serviceClassDeclaration, entityRef);
customizeUpdateMethod(serviceClassDeclaration, entityRef);
removedUnusedConstructorArgs(serviceClassDeclaration, entityRef);
}
const pluginOptions = vendurePlugin.getPluginOptions();
if (pluginOptions) {
(0, ast_utils_1.addImportsToFile)(serviceSourceFile, {
moduleSpecifier: pluginOptions.constantDeclaration.getSourceFile(),
namedImports: [pluginOptions.constantDeclaration.getName()],
});
(0, ast_utils_1.addImportsToFile)(serviceSourceFile, {
moduleSpecifier: pluginOptions.typeDeclaration.getSourceFile(),
namedImports: [pluginOptions.typeDeclaration.getName()],
});
(0, ast_utils_1.addImportsToFile)(serviceSourceFile, {
moduleSpecifier: '@nestjs/common',
namedImports: ['Inject'],
});
(_d = serviceClassDeclaration
.getConstructors()[0]) === null || _d === void 0 ? void 0 : _d.addParameter({
scope: ts_morph_1.Scope.Private,
name: 'options',
type: pluginOptions.typeDeclaration.getName(),
decorators: [{ name: 'Inject', arguments: [pluginOptions.constantDeclaration.getName()] }],
}).formatText();
}
modifiedSourceFiles.push(serviceSourceFile);
serviceSpinner.message(`Registering service with plugin...`);
vendurePlugin.addProvider(options.serviceName);
(0, ast_utils_1.addImportsToFile)(vendurePlugin.classDeclaration.getSourceFile(), {
moduleSpecifier: serviceSourceFile,
namedImports: [options.serviceName],
});
await project.save();
serviceSpinner.stop(`${options.serviceName} created`);
return {
project,
modifiedSourceFiles,
serviceRef: new service_ref_1.ServiceRef(serviceClassDeclaration),
};
}
function getServiceFilePath(plugin, serviceName) {
const serviceFileName = (0, change_case_1.paramCase)(serviceName).replace(/-service$/, '.service');
return path_1.default.join(plugin.getPluginDir().getPath(), 'services', `${serviceFileName}.ts`);
}
function customizeFindOneMethod(serviceClassDeclaration, entityRef) {
const findOneMethod = serviceClassDeclaration.getMethod('findOne');
findOneMethod
.setBodyText(writer => {
writer.write(` return this.connection
.getRepository(ctx, ${entityRef.name})
.findOne({
where: { id },
relations,
})`);
if (entityRef.isTranslatable()) {
writer.write(`.then(entity => entity && this.translator.translate(entity, ctx));`);
}
else {
writer.write(`;`);
}
})
.formatText();
if (!entityRef.isTranslatable()) {
findOneMethod.setReturnType(`Promise<${entityRef.name} | null>`);
}
}
function customizeFindAllMethod(serviceClassDeclaration, entityRef) {
const findAllMethod = serviceClassDeclaration.getMethod('findAll');
findAllMethod
.setBodyText(writer => {
writer.writeLine(`return this.listQueryBuilder`);
writer.write(`.build(${entityRef.name}, options,`).block(() => {
writer.writeLine('relations,');
writer.writeLine('ctx,');
});
writer.write(')');
writer.write('.getManyAndCount()');
writer.write('.then(([items, totalItems]) =>').block(() => {
writer.write('return').block(() => {
if (entityRef.isTranslatable()) {
writer.writeLine('items: items.map(item => this.translator.translate(item, ctx)),');
}
else {
writer.writeLine('items,');
}
writer.writeLine('totalItems,');
});
});
writer.write(');');
})
.formatText();
if (!entityRef.isTranslatable()) {
findAllMethod.setReturnType(`Promise<PaginatedList<${entityRef.name}>>`);
}
}
function customizeCreateMethod(serviceClassDeclaration, entityRef) {
const createMethod = serviceClassDeclaration.getMethod('create');
createMethod
.setBodyText(writer => {
var _a;
if (entityRef.isTranslatable()) {
writer.write(`const newEntity = await this.translatableSaver.create({
ctx,
input,
entityType: ${entityRef.name},
translationType: ${(_a = entityRef.getTranslationClass()) === null || _a === void 0 ? void 0 : _a.getName()},
beforeSave: async f => {
// Any pre-save logic can go here
},
});`);
}
else {
writer.writeLine(`const newEntityInstance = new ${entityRef.name}(input);
const newEntity = await this.connection.getRepository(ctx, ${entityRef.name}).save(newEntityInstance);`);
}
if (entityRef.hasCustomFields()) {
writer.writeLine(`await this.customFieldRelationService.updateRelations(ctx, ${entityRef.name}, input, newEntity);`);
}
writer.writeLine(`return assertFound(this.findOne(ctx, newEntity.id));`);
})
.formatText();
if (!entityRef.isTranslatable()) {
createMethod.setReturnType(`Promise<${entityRef.name}>`);
}
}
function customizeUpdateMethod(serviceClassDeclaration, entityRef) {
const updateMethod = serviceClassDeclaration.getMethod('update');
updateMethod
.setBodyText(writer => {
var _a;
if (entityRef.isTranslatable()) {
writer.write(`const updatedEntity = await this.translatableSaver.update({
ctx,
input,
entityType: ${entityRef.name},
translationType: ${(_a = entityRef.getTranslationClass()) === null || _a === void 0 ? void 0 : _a.getName()},
beforeSave: async f => {
// Any pre-save logic can go here
},
});`);
}
else {
writer.writeLine(`const entity = await this.connection.getEntityOrThrow(ctx, ${entityRef.name}, input.id);`);
writer.writeLine(`const updatedEntity = patchEntity(entity, input);`);
writer.writeLine(`await this.connection.getRepository(ctx, ${entityRef.name}).save(updatedEntity, { reload: false });`);
}
if (entityRef.hasCustomFields()) {
writer.writeLine(`await this.customFieldRelationService.updateRelations(ctx, ${entityRef.name}, input, updatedEntity);`);
}
writer.writeLine(`return assertFound(this.findOne(ctx, updatedEntity.id));`);
})
.formatText();
if (!entityRef.isTranslatable()) {
updateMethod.setReturnType(`Promise<${entityRef.name}>`);
}
}
function removedUnusedConstructorArgs(serviceClassDeclaration, entityRef) {
const isTranslatable = entityRef.isTranslatable();
const hasCustomFields = entityRef.hasCustomFields();
serviceClassDeclaration.getConstructors().forEach(constructor => {
constructor.getParameters().forEach(param => {
const paramName = param.getName();
if ((paramName === 'translatableSaver' || paramName === 'translator') && !isTranslatable) {
param.remove();
}
if (paramName === 'customFieldRelationService' && !hasCustomFields) {
param.remove();
}
});
});
}
//# sourceMappingURL=add-service.js.map