From a77251e441d298f5a7f2945076d1a2e0e60c4150 Mon Sep 17 00:00:00 2001 From: Michael Bromley Date: Thu, 2 May 2024 11:11:48 +0200 Subject: [PATCH] fix(cli): Include plugin options in service constructor --- .../src/commands/add/service/add-service.ts | 37 +++++++++++--- packages/cli/src/shared/vendure-plugin-ref.ts | 51 ++++++++++++++++++- 2 files changed, 80 insertions(+), 8 deletions(-) diff --git a/packages/cli/src/commands/add/service/add-service.ts b/packages/cli/src/commands/add/service/add-service.ts index 3feaa4cf2d..6f9f0a53d1 100644 --- a/packages/cli/src/commands/add/service/add-service.ts +++ b/packages/cli/src/commands/add/service/add-service.ts @@ -1,7 +1,7 @@ import { cancel, isCancel, log, select, spinner, text } from '@clack/prompts'; import { paramCase } from 'change-case'; import path from 'path'; -import { ClassDeclaration, SourceFile } from 'ts-morph'; +import { ClassDeclaration, Scope, SourceFile } from 'ts-morph'; import { Messages, pascalCaseRegex } from '../../../constants'; import { CliCommand, CliCommandReturnVal } from '../../../shared/cli-command'; @@ -77,13 +77,8 @@ async function addService( } const serviceSpinner = spinner(); - const serviceFileName = paramCase(options.serviceName).replace(/-service$/, '.service'); + let serviceSourceFile: SourceFile; - const serviceSourceFilePath = path.join( - vendurePlugin.getPluginDir().getPath(), - 'services', - `${serviceFileName}.ts`, - ); let serviceClassDeclaration: ClassDeclaration; if (options.type === 'basic') { const name = await text({ @@ -106,6 +101,7 @@ async function addService( options.serviceName = name; serviceSpinner.start(`Creating ${options.serviceName}...`); + const serviceSourceFilePath = getServiceFilePath(vendurePlugin, options.serviceName); await pauseForPromptDisplay(); serviceSourceFile = createFile( project, @@ -119,6 +115,7 @@ async function addService( } else { serviceSpinner.start(`Creating ${options.serviceName}...`); await pauseForPromptDisplay(); + const serviceSourceFilePath = getServiceFilePath(vendurePlugin, options.serviceName); serviceSourceFile = createFile( project, path.join(__dirname, 'templates/entity-service.template.ts'), @@ -163,6 +160,27 @@ async function addService( customizeUpdateMethod(serviceClassDeclaration, entityRef); removedUnusedConstructorArgs(serviceClassDeclaration, entityRef); } + const pluginOptions = vendurePlugin.getPluginOptions(); + if (pluginOptions) { + addImportsToFile(serviceSourceFile, { + moduleSpecifier: pluginOptions.constantDeclaration.getSourceFile(), + namedImports: [pluginOptions.constantDeclaration.getName()], + }); + addImportsToFile(serviceSourceFile, { + moduleSpecifier: pluginOptions.typeDeclaration.getSourceFile(), + namedImports: [pluginOptions.typeDeclaration.getName()], + }); + addImportsToFile(serviceSourceFile, { + moduleSpecifier: '@nestjs/common', + namedImports: ['Inject'], + }); + serviceClassDeclaration.getConstructors()[0]?.addParameter({ + scope: Scope.Private, + name: 'options', + type: pluginOptions.typeDeclaration.getName(), + decorators: [{ name: 'Inject', arguments: [pluginOptions.constantDeclaration.getName()] }], + }); + } modifiedSourceFiles.push(serviceSourceFile); serviceSpinner.message(`Registering service with plugin...`); @@ -184,6 +202,11 @@ async function addService( }; } +function getServiceFilePath(plugin: VendurePluginRef, serviceName: string) { + const serviceFileName = paramCase(serviceName).replace(/-service$/, '.service'); + return path.join(plugin.getPluginDir().getPath(), 'services', `${serviceFileName}.ts`); +} + function customizeFindOneMethod(serviceClassDeclaration: ClassDeclaration, entityRef: EntityRef) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const findOneMethod = serviceClassDeclaration.getMethod('findOne')!; diff --git a/packages/cli/src/shared/vendure-plugin-ref.ts b/packages/cli/src/shared/vendure-plugin-ref.ts index f25bc16477..9ec02b5cb9 100644 --- a/packages/cli/src/shared/vendure-plugin-ref.ts +++ b/packages/cli/src/shared/vendure-plugin-ref.ts @@ -1,4 +1,13 @@ -import { ClassDeclaration, Node, StructureKind, SyntaxKind, VariableDeclaration } from 'ts-morph'; +import { + ClassDeclaration, + InterfaceDeclaration, + Node, + PropertyAssignment, + StructureKind, + SyntaxKind, + Type, + VariableDeclaration, +} from 'ts-morph'; import { AdminUiExtensionTypeName } from '../constants'; @@ -31,6 +40,46 @@ export class VendurePluginRef { return pluginOptions; } + getPluginOptions(): + | { typeDeclaration: InterfaceDeclaration; constantDeclaration: VariableDeclaration } + | undefined { + const metadataOptions = this.getMetadataOptions(); + const staticOptions = this.classDeclaration.getStaticProperty('options'); + const typeDeclaration = staticOptions + ?.getType() + .getSymbolOrThrow() + .getDeclarations() + .find(d => Node.isInterfaceDeclaration(d)); + if (!typeDeclaration || !Node.isInterfaceDeclaration(typeDeclaration)) { + return; + } + const providersArray = metadataOptions + .getProperty('providers') + ?.getFirstChildByKind(SyntaxKind.ArrayLiteralExpression); + if (!providersArray) { + return; + } + const elements = providersArray.getElements(); + const optionsProviders = elements + .filter(Node.isObjectLiteralExpression) + .filter(el => el.getProperty('useFactory')?.getText().includes(`${this.name}.options`)); + + if (!optionsProviders.length) { + return; + } + const optionsSymbol = optionsProviders[0].getProperty('provide') as PropertyAssignment; + const initializer = optionsSymbol?.getInitializer(); + if (!initializer || !Node.isIdentifier(initializer)) { + return; + } + const constantDeclaration = initializer.getDefinitions()[0]?.getDeclarationNode(); + if (!constantDeclaration || !Node.isVariableDeclaration(constantDeclaration)) { + return; + } + + return { typeDeclaration, constantDeclaration }; + } + addEntity(entityClassName: string) { const pluginOptions = this.getMetadataOptions(); const entityProperty = pluginOptions.getProperty('entities');