Explorar el Código

feat(cli): Implement plugin scaffold command

Michael Bromley hace 2 años
padre
commit
a6df4c1c82

+ 1 - 4
packages/cli/package.json

@@ -26,14 +26,11 @@
     "publishConfig": {
         "access": "public"
     },
-    "main": "dist/index.js",
-    "types": "dist/index.d.ts",
     "bin": {
         "vendure": "dist/cli.js"
     },
     "files": [
-        "dist/**/*",
-        "cli/**/*"
+        "dist/**/*"
     ],
     "dependencies": {
         "@vendure/common": "2.1.0-next.4",

+ 3 - 3
packages/cli/src/cli.ts

@@ -2,7 +2,7 @@
 
 import { Command } from 'commander';
 
-import { registerCommand as registerPluginCommand } from './commands/plugin/index';
+import { registerNewCommand } from './commands/new/new';
 
 const program = new Command();
 
@@ -10,6 +10,6 @@ const program = new Command();
 const version = require('../package.json').version;
 
 program.version(version).description('The Vendure CLI');
-registerPluginCommand(program);
+registerNewCommand(program);
 
-program.parse(process.argv);
+void program.parseAsync(process.argv);

+ 15 - 0
packages/cli/src/commands/new/new.ts

@@ -0,0 +1,15 @@
+import { Argument, Command } from 'commander';
+
+import { newPlugin } from './plugin/new-plugin';
+
+export function registerNewCommand(program: Command) {
+    program
+        .command('new')
+        .addArgument(new Argument('<type>', 'type of scaffold').choices(['plugin']))
+        .description('Generate scaffold for your Vendure project')
+        .action(async (type: string) => {
+            if (type === 'plugin') {
+                await newPlugin();
+            }
+        });
+}

+ 187 - 0
packages/cli/src/commands/new/plugin/new-plugin.ts

@@ -0,0 +1,187 @@
+import { camelCase, constantCase, paramCase, pascalCase } from 'change-case';
+import { Command } from 'commander';
+import * as fs from 'fs-extra';
+import path from 'path';
+
+import { renderAdminResolver, renderAdminResolverWithEntity } from './scaffold/api/admin.resolver';
+import { renderApiExtensions } from './scaffold/api/api-extensions';
+import { renderShopResolver, renderShopResolverWithEntity } from './scaffold/api/shop.resolver';
+import { renderConstants } from './scaffold/constants';
+import { renderEntity } from './scaffold/entities/entity';
+import { renderPlugin } from './scaffold/plugin';
+import { renderService, renderServiceWithEntity } from './scaffold/services/service';
+import { renderTypes } from './scaffold/types';
+import { GeneratePluginOptions, TemplateContext } from './types';
+
+const cancelledMessage = 'Plugin setup cancelled.';
+
+export async function newPlugin() {
+    const { cancel, confirm, intro, isCancel, multiselect, text } = await import('@clack/prompts');
+    const options: GeneratePluginOptions = { name: '', customEntityName: '' } as any;
+    intro('Scaffolding a new Vendure plugin!');
+    if (!options.name) {
+        const name = await text({
+            message: 'What is the name of the plugin?',
+            initialValue: '',
+            validate: input => {
+                if (!/^[a-z][a-z-0-9]+$/.test(input)) {
+                    return 'The plugin name must be lowercase and contain only letters, numbers and dashes';
+                }
+                const proposedPluginDir = getPluginDirName(input);
+                if (fs.existsSync(proposedPluginDir)) {
+                    return `A directory named "${proposedPluginDir}" already exists. Cannot create plugin in this directory.`;
+                }
+            },
+        });
+
+        if (isCancel(name)) {
+            cancel(cancelledMessage);
+            process.exit(0);
+        } else {
+            options.name = name;
+        }
+    }
+    const features = await multiselect({
+        message: 'Which features would you like to include? (use ↑, ↓, space to select)',
+        options: [
+            { value: 'customEntity', label: 'Custom entity' },
+            { value: 'apiExtensions', label: 'GraphQL API extensions' },
+        ],
+        required: false,
+    });
+    if (Array.isArray(features)) {
+        options.withCustomEntity = features.includes('customEntity');
+        options.withApiExtensions = features.includes('apiExtensions');
+    }
+    if (options.withCustomEntity) {
+        const entityName = await text({
+            message: 'What is the name of the custom entity?',
+            initialValue: '',
+            placeholder: '',
+            validate: input => {
+                if (!input) {
+                    return 'The custom entity name cannot be empty';
+                }
+                const pascalCaseRegex = /^[A-Z][a-zA-Z0-9]*$/;
+                if (!pascalCaseRegex.test(input)) {
+                    return 'The custom entity name must be in PascalCase, e.g. "ProductReview"';
+                }
+            },
+        });
+        if (isCancel(entityName)) {
+            cancel(cancelledMessage);
+            process.exit(0);
+        } else {
+            options.customEntityName = pascalCase(entityName);
+        }
+    }
+    const pluginDir = getPluginDirName(options.name);
+    const confirmation = await confirm({
+        message: `Create new plugin in ${pluginDir}?`,
+    });
+
+    if (isCancel(confirmation)) {
+        cancel(cancelledMessage);
+        process.exit(0);
+    } else {
+        if (confirmation === true) {
+            await generatePlugin(options);
+        } else {
+            cancel(cancelledMessage);
+        }
+    }
+}
+
+export async function generatePlugin(options: GeneratePluginOptions) {
+    const nameWithoutPlugin = options.name.replace(/-?plugin$/i, '');
+    const normalizedName = nameWithoutPlugin + '-plugin';
+    const templateContext: TemplateContext = {
+        ...options,
+        pluginName: pascalCase(normalizedName),
+        pluginInitOptionsName: constantCase(normalizedName) + '_OPTIONS',
+        service: {
+            className: pascalCase(nameWithoutPlugin) + 'Service',
+            instanceName: camelCase(nameWithoutPlugin) + 'Service',
+            fileName: paramCase(nameWithoutPlugin) + '.service',
+        },
+        entity: {
+            className: options.customEntityName,
+            instanceName: camelCase(options.customEntityName),
+            fileName: paramCase(options.customEntityName) + '.entity',
+        },
+    };
+
+    const files: Array<{ render: (context: TemplateContext) => string; path: string }> = [
+        {
+            render: renderPlugin,
+            path: paramCase(nameWithoutPlugin) + '.plugin.ts',
+        },
+        {
+            render: renderTypes,
+            path: 'types.ts',
+        },
+        {
+            render: renderConstants,
+            path: 'constants.ts',
+        },
+    ];
+
+    if (options.withApiExtensions) {
+        files.push({
+            render: renderApiExtensions,
+            path: 'api/api-extensions.ts',
+        });
+        if (options.withCustomEntity) {
+            files.push({
+                render: renderShopResolverWithEntity,
+                path: 'api/shop.resolver.ts',
+            });
+            files.push({
+                render: renderAdminResolverWithEntity,
+                path: 'api/admin.resolver.ts',
+            });
+        } else {
+            files.push({
+                render: renderShopResolver,
+                path: 'api/shop.resolver.ts',
+            });
+            files.push({
+                render: renderAdminResolver,
+                path: 'api/admin.resolver.ts',
+            });
+        }
+    }
+
+    if (options.withCustomEntity) {
+        files.push({
+            render: renderEntity,
+            path: `entities/${templateContext.entity.fileName}.ts`,
+        });
+        files.push({
+            render: renderServiceWithEntity,
+            path: `services/${templateContext.service.fileName}.ts`,
+        });
+    } else {
+        files.push({
+            render: renderService,
+            path: `services/${templateContext.service.fileName}.ts`,
+        });
+    }
+
+    const pluginDir = getPluginDirName(options.name);
+    fs.ensureDirSync(pluginDir);
+    files.forEach(file => {
+        const filePath = path.join(pluginDir, file.path);
+        const rendered = file.render(templateContext).trim();
+        fs.ensureFileSync(filePath);
+        fs.writeFileSync(filePath, rendered);
+    });
+
+    const { outro } = await import('@clack/prompts');
+    outro('✅ Plugin scaffolding complete!');
+}
+
+function getPluginDirName(name: string) {
+    const nameWithoutPlugin = name.replace(/-?plugin$/i, '');
+    return path.join(process.cwd(), paramCase(nameWithoutPlugin));
+}

+ 62 - 0
packages/cli/src/commands/new/plugin/scaffold/api/admin.resolver.ts

@@ -0,0 +1,62 @@
+import { TemplateContext } from '../../types';
+
+export function renderAdminResolverWithEntity(context: TemplateContext) {
+    return /* language=TypeScript */ `
+import { Args, Resolver, Mutation } from '@nestjs/graphql';
+import { Allow, Ctx, RequestContext, Transaction, Permission } from '@vendure/core';
+
+import { ${context.service.className} } from '../services/${context.service.fileName}';
+import { ${context.entity.className} } from '../entities/${context.entity.fileName}';
+
+// TODO: Set up graphql-code-generator to generate the types for the following inputs
+type Create${context.customEntityName}Input = any;
+type Update${context.customEntityName}Input = any;
+
+@Resolver()
+export class AdminResolver {
+    constructor(private ${context.service.instanceName}: ${context.service.className}) {
+    }
+
+    @Transaction()
+    @Mutation()
+    @Allow(Permission.SuperAdmin)
+    create${context.entity.className}(@Ctx() ctx: RequestContext, @Args() args: { input: Create${context.customEntityName}Input }): Promise<${context.entity.className}> {
+        return this.${context.service.instanceName}.create(ctx, args.input);
+    }
+
+    @Transaction()
+    @Mutation()
+    @Allow(Permission.SuperAdmin)
+    update${context.entity.className}(
+        @Ctx() ctx: RequestContext,
+        @Args() args: { input: Update${context.customEntityName}Input },
+    ): Promise<${context.entity.className} | undefined> {
+        return this.${context.service.instanceName}.update(ctx, args.input);
+    }
+}`;
+}
+
+export function renderAdminResolver(context: TemplateContext) {
+    return /* language=TypeScript */ `
+import { Args, Query, Mutation, Resolver } from '@nestjs/graphql';
+import { Ctx, PaginatedList, RequestContext, Transaction } from '@vendure/core';
+
+import { ${context.service.className} } from '../services/${context.service.fileName}';
+
+@Resolver()
+export class AdminResolver {
+    constructor(private ${context.service.instanceName}: ${context.service.className}) {
+    }
+
+    @Query()
+    exampleShopQuery(@Ctx() ctx: RequestContext) {
+        return this.${context.service.instanceName}.exampleMethod(ctx);
+    }
+
+    @Mutation()
+    @Transaction()
+    exampleShopMutation(@Ctx() ctx: RequestContext, @Args() args: { input: string }) {
+        return this.${context.service.instanceName}.exampleMethod(ctx, args);
+    }
+}`;
+}

+ 80 - 0
packages/cli/src/commands/new/plugin/scaffold/api/api-extensions.ts

@@ -0,0 +1,80 @@
+import { constantCase, pascalCase } from 'change-case';
+
+import { TemplateContext } from '../../types';
+
+export function renderApiExtensions(context: TemplateContext) {
+    if (!context.withApiExtensions) {
+        return '';
+    }
+    if (!context.withCustomEntity) {
+        return /* language=TypeScript */ `
+import gql from 'graphql-tag';
+export const shopApiExtensions = gql\`
+    extend type Query {
+       exampleShopQuery: String!
+    }
+    extend type Mutation {
+       exampleShopMutation(input: String!): String!
+    }
+\`;
+
+export const adminApiExtensions = gql\`
+     extend type Query {
+       exampleAdminQuery: String!
+    }
+    extend type Mutation {
+       exampleAdminMutation(input: String!): String!
+    }
+\`;
+`;
+    } else {
+        const entityName = context.entity.className;
+        return /* language=TypeScript */ `
+import gql from 'graphql-tag';
+
+export const commonApiExtensions = gql\`
+    type ${entityName} implements Node {
+        id: ID!
+        createdAt: DateTime!
+        updatedAt: DateTime!
+        name: String!
+    }
+
+    type ${entityName}List implements PaginatedList {
+        items: [${entityName}!]!
+        totalItems: Int!
+    }
+
+    extend type Query {
+        ${context.entity.instanceName}s(options: ${entityName}ListOptions): ${entityName}List!
+        ${context.entity.instanceName}(id: ID!): ${entityName}
+    }
+
+    # Auto-generated at runtime
+    input ${entityName}ListOptions
+\`;
+
+export const shopApiExtensions = gql\`
+    \${commonApiExtensions}
+\`;
+
+export const adminApiExtensions = gql\`
+    \${commonApiExtensions}
+
+    extend type Mutation {
+        create${entityName}(input: Create${entityName}Input!): ${entityName}!
+        update${entityName}(input: Update${entityName}Input!): ${entityName}!
+    }
+
+    input Create${entityName}Input {
+        name: String!
+    }
+
+    input Update${entityName}Input {
+        id: ID!
+        name: String!
+    }
+\`;
+`;
+    }
+}

+ 54 - 0
packages/cli/src/commands/new/plugin/scaffold/api/shop.resolver.ts

@@ -0,0 +1,54 @@
+import { TemplateContext } from '../../types';
+
+export function renderShopResolverWithEntity(context: TemplateContext) {
+    return /* language=TypeScript */ `
+import { Args, Query, Resolver } from '@nestjs/graphql';
+import { Ctx, PaginatedList, RequestContext, ListQueryOptions } from '@vendure/core';
+
+import { ${context.service.className} } from '../services/${context.service.fileName}';
+import { ${context.entity.className} } from '../entities/${context.entity.fileName}';
+
+@Resolver()
+export class ShopResolver {
+    constructor(private ${context.service.instanceName}: ${context.service.className}) {
+    }
+
+    @Query()
+    ${context.entity.instanceName}s(
+        @Ctx() ctx: RequestContext,
+        @Args() args: { options: ListQueryOptions<${context.customEntityName}> },
+    ): Promise<PaginatedList<${context.customEntityName}>> {
+        return this.${context.service.instanceName}.findAll(ctx, args.options || undefined);
+    }
+
+    @Query()
+    ${context.entity.instanceName}(@Ctx() ctx: RequestContext, @Args() args: { id: string }): Promise<${context.customEntityName} | null> {
+        return this.${context.service.instanceName}.findOne(ctx, args.id);
+    }
+}`;
+}
+
+export function renderShopResolver(context: TemplateContext) {
+    return /* language=TypeScript */ `
+import { Args, Query, Mutation, Resolver } from '@nestjs/graphql';
+import { Ctx, PaginatedList, RequestContext, Transaction } from '@vendure/core';
+
+import { ${context.service.className} } from '../services/${context.service.fileName}';
+
+@Resolver()
+export class ShopResolver {
+    constructor(private ${context.service.instanceName}: ${context.service.className}) {
+    }
+
+    @Query()
+    exampleShopQuery(@Ctx() ctx: RequestContext) {
+        return this.${context.service.instanceName}.exampleMethod(ctx);
+    }
+
+    @Mutation()
+    @Transaction()
+    exampleShopMutation(@Ctx() ctx: RequestContext, @Args() args: { input: string }) {
+        return this.${context.service.instanceName}.exampleMethod(ctx, args);
+    }
+}`;
+}

+ 10 - 0
packages/cli/src/commands/new/plugin/scaffold/constants.ts

@@ -0,0 +1,10 @@
+import { constantCase, pascalCase } from 'change-case';
+
+import { TemplateContext } from '../types';
+
+export function renderConstants({ pluginName, pluginInitOptionsName }: TemplateContext): string {
+    return /* language=TypeScript */ `
+export const ${pluginInitOptionsName} = Symbol('${pluginInitOptionsName}');
+export const loggerCtx = '${pluginName}';
+`;
+}

+ 18 - 0
packages/cli/src/commands/new/plugin/scaffold/entities/entity.ts

@@ -0,0 +1,18 @@
+import { TemplateContext } from '../../types';
+
+export function renderEntity(context: TemplateContext): string {
+    return /* language=TypeScript */ `
+import { Entity, Column } from 'typeorm';
+import { VendureEntity, DeepPartial } from '@vendure/core';
+
+@Entity()
+export class ${context.entity.className} extends VendureEntity {
+    constructor(input?: DeepPartial<${context.entity.className}>) {
+        super(input);
+    }
+
+    @Column()
+    name: string;
+}
+`;
+}

+ 54 - 0
packages/cli/src/commands/new/plugin/scaffold/plugin.ts

@@ -0,0 +1,54 @@
+import { constantCase, pascalCase } from 'change-case';
+
+import { TemplateContext } from '../types';
+
+export function renderPlugin(context: TemplateContext) {
+    const { pluginName, pluginInitOptionsName } = context;
+    const optionalImports: string[] = [];
+    const optionalMetadata: string[] = [];
+    if (context.withCustomEntity) {
+        optionalImports.push(
+            `import { ${context.entity.className} } from './entities/${context.entity.fileName}';`,
+        );
+        optionalMetadata.push(`entities: [${context.entity.className}],`);
+    } else {
+        optionalMetadata.push(`entities: [],`);
+    }
+    if (context.withApiExtensions) {
+        optionalImports.push(`import { shopApiExtensions, adminApiExtensions } from './api/api-extensions';`);
+        optionalImports.push(`import { ShopResolver } from './api/shop.resolver';`);
+        optionalImports.push(`import { AdminResolver } from './api/admin.resolver';`);
+        optionalMetadata.push(`shopApiExtensions: { resolvers: [ShopResolver], schema: shopApiExtensions },`);
+        optionalMetadata.push(
+            `adminApiExtensions: { resolvers: [AdminResolver], schema: adminApiExtensions },`,
+        );
+    }
+
+    return /* language=TypeScript */ `
+import { PluginCommonModule, Type, VendurePlugin } from '@vendure/core';
+import path from 'path';
+
+import { ${context.service.className} } from './services/${context.service.fileName}';
+import { ${pluginInitOptionsName} } from './constants';
+import { PluginInitOptions } from './types';
+${optionalImports.join('\n')}
+
+@VendurePlugin({
+    imports: [PluginCommonModule],
+    ${optionalMetadata.join('\n    ')}
+    providers: [
+        ${context.service.className},
+        { provide: ${pluginInitOptionsName}, useFactory: () => ${pluginName}.options },
+    ],
+    compatibility: '^2.0.0',
+})
+export class ${pluginName} {
+    static options: PluginInitOptions;
+
+    static init(options: PluginInitOptions): Type<${pluginName}> {
+        this.options = options;
+        return ${pluginName};
+    }
+}
+`;
+}

+ 79 - 0
packages/cli/src/commands/new/plugin/scaffold/services/service.ts

@@ -0,0 +1,79 @@
+import { paramCase } from 'change-case';
+
+import { TemplateContext } from '../../types';
+
+export function renderService(context: TemplateContext) {
+    return /* language=TypeScript */ `
+import { Inject, Injectable } from '@nestjs/common';
+import { RequestContext, TransactionalConnection } from '@vendure/core';
+
+import { ${context.pluginInitOptionsName} } from '../constants';
+import { PluginInitOptions } from '../types';
+
+@Injectable()
+export class ${context.service.className} {
+    constructor(
+        private connection: TransactionalConnection,
+        @Inject(${context.pluginInitOptionsName}) private options: PluginInitOptions,
+    ) {}
+
+    async exampleMethod(
+        ctx: RequestContext,
+        options?: { input?: string },
+    ): Promise<string> {
+        return options?.input ? 'Hello \${options.input}' : 'Hello World';
+    }
+}
+`;
+}
+
+export function renderServiceWithEntity(context: TemplateContext) {
+    return /* language=TypeScript */ `
+import { Inject, Injectable } from '@nestjs/common';
+import { ListQueryBuilder, ListQueryOptions, PaginatedList, RequestContext, TransactionalConnection } from '@vendure/core';
+
+import { ${context.customEntityName} } from '../entities/${context.entity.fileName}';
+import { ${context.pluginInitOptionsName} } from '../constants';
+import { PluginInitOptions } from '../types';
+
+// TODO: Set up graphql-code-generator to generate the types for the following inputs
+type Create${context.customEntityName}Input = any;
+type Update${context.customEntityName}Input = any;
+
+@Injectable()
+export class ${context.service.className} {
+    constructor(
+        private connection: TransactionalConnection,
+        @Inject(${context.pluginInitOptionsName}) private options: PluginInitOptions,
+        private listQueryBuilder: ListQueryBuilder,
+    ) {}
+
+    async findAll(
+        ctx: RequestContext,
+        options?: ListQueryOptions<${context.customEntityName}>,
+    ): Promise<PaginatedList<${context.customEntityName}>> {
+        return this.listQueryBuilder
+            .build(${context.customEntityName}, options, { ctx })
+            .getManyAndCount()
+            .then(([items, totalItems]) => ({
+                items,
+                totalItems,
+            }));
+    }
+
+    async findOne(ctx: RequestContext, id: string): Promise<${context.customEntityName} | null> {
+        return this.connection.getRepository(ctx, ${context.customEntityName}).findOne({ where: { id } });
+    }
+
+    async create(ctx: RequestContext, input: Create${context.customEntityName}Input): Promise<${context.customEntityName}> {
+        return this.connection.getRepository(ctx, ${context.customEntityName}).save(new ${context.customEntityName}(input));
+    }
+
+    async update(ctx: RequestContext, input: Update${context.customEntityName}Input): Promise<${context.customEntityName}> {
+        const example = await this.connection.getEntityOrThrow(ctx, ${context.customEntityName}, input.id);
+        const updated = { ...example, ...input };
+        return this.connection.getRepository(ctx, ${context.customEntityName}).save(updated);
+    }
+}
+`;
+}

+ 1 - 1
packages/cli/src/commands/plugin/scaffold/types.ts → packages/cli/src/commands/new/plugin/scaffold/types.ts

@@ -1,6 +1,6 @@
 import { TemplateContext } from '../types';
 
-export function render(options: TemplateContext): string {
+export function renderTypes(options: TemplateContext): string {
     return /* language=TypeScript */ `
 /**
  * The plugin can be configured using the following options:

+ 21 - 0
packages/cli/src/commands/new/plugin/types.ts

@@ -0,0 +1,21 @@
+export interface GeneratePluginOptions {
+    name: string;
+    withCustomEntity: boolean;
+    withApiExtensions: boolean;
+    customEntityName: string;
+}
+
+export type TemplateContext = GeneratePluginOptions & {
+    pluginName: string;
+    pluginInitOptionsName: string;
+    service: {
+        instanceName: string;
+        className: string;
+        fileName: string;
+    };
+    entity: {
+        instanceName: string;
+        className: string;
+        fileName: string;
+    };
+};

+ 0 - 52
packages/cli/src/commands/plugin/index.ts

@@ -1,52 +0,0 @@
-import { constantCase, paramCase, pascalCase } from 'change-case';
-import { Command } from 'commander';
-import * as fs from 'fs-extra';
-import path from 'path';
-
-import { render as renderConstants } from './scaffold/constants';
-import { render as renderPlugin } from './scaffold/plugin';
-import { render as renderTypes } from './scaffold/types';
-import { GeneratePluginOptions, TemplateContext } from './types';
-
-export function registerCommand(program: Command) {
-    program
-        .command('plugin')
-        .description('Scaffold a new Vendure plugin')
-        .argument('-n, --name <name>', 'The name of the plugin')
-        .alias('p')
-        .action((name: string) => {
-            generatePlugin({ name });
-        });
-}
-
-export function generatePlugin(options: GeneratePluginOptions) {
-    const nameWithoutPlugin = options.name.replace(/-?plugin$/i, '');
-    const normalizedName = nameWithoutPlugin + '-plugin';
-    const templateContext: TemplateContext = {
-        pluginName: pascalCase(normalizedName),
-        pluginInitOptionsName: constantCase(normalizedName) + '_OPTIONS',
-    };
-
-    const files: Array<{ render: (context: TemplateContext) => string; path: string }> = [
-        {
-            render: renderPlugin,
-            path: paramCase(nameWithoutPlugin) + '.plugin.ts',
-        },
-        {
-            render: renderTypes,
-            path: 'types.ts',
-        },
-        {
-            render: renderConstants,
-            path: 'constants.ts',
-        },
-    ];
-
-    const pluginDir = path.join(process.cwd(), paramCase(nameWithoutPlugin));
-    fs.ensureDirSync(pluginDir);
-    files.forEach(file => {
-        const filePath = path.join(pluginDir, file.path);
-        const rendered = file.render(templateContext);
-        fs.writeFileSync(filePath, rendered);
-    });
-}

+ 0 - 10
packages/cli/src/commands/plugin/scaffold/constants.ts

@@ -1,10 +0,0 @@
-import { constantCase, pascalCase } from 'change-case';
-
-import { TemplateContext } from '../types';
-
-export function render(context: TemplateContext): string {
-    return /* language=TypeScript */ `
-export const ${context.pluginInitOptionsName} = Symbol('${context.pluginInitOptionsName}');
-export const loggerCtx = '${context.pluginName}';
-`;
-}

+ 0 - 45
packages/cli/src/commands/plugin/scaffold/plugin.ts

@@ -1,45 +0,0 @@
-import { constantCase, pascalCase } from 'change-case';
-
-import { TemplateContext } from '../types';
-
-export function render(context: TemplateContext) {
-    return /* language=TypeScript */ `
-import { PluginCommonModule, Type, VendurePlugin } from '@vendure/core';
-import { AdminUiExtension } from '@vendure/ui-devkit/compiler';
-import path from 'path';
-
-import { ${context.pluginInitOptionsName} } from './constants';
-import { PluginInitOptions } from './types';
-
-/**
- * An example Vendure plugin.
- */
-@VendurePlugin({
-    // Importing the PluginCommonModule gives all of our plugin's injectables (services, resolvers)
-    // access to the Vendure core providers. See https://www.vendure.io/docs/typescript-api/plugin/plugin-common-module/
-    imports: [PluginCommonModule],
-    entities: [],
-    providers: [
-        // By definiting the \`PLUGIN_INIT_OPTIONS\` symbol as a provider, we can then inject the
-        // user-defined options into other classes, such as the {@link ExampleService}.
-        { provide: ${context.pluginInitOptionsName}, useFactory: () => ${context.pluginName}.options },
-    ],
-})
-export class ${context.pluginName} {
-    static options: PluginInitOptions;
-
-    /**
-     * The static \`init()\` method is a convention used by Vendure plugins which allows options
-     * to be configured by the user.
-     */
-    static init(options: PluginInitOptions): Type<${context.pluginName}> {
-        this.options = options;
-        return ${context.pluginName};
-    }
-
-    static uiExtensions: AdminUiExtension = {
-        extensionPath: path.join(__dirname, 'ui'),
-    };
-}
-`;
-}

+ 0 - 8
packages/cli/src/commands/plugin/types.ts

@@ -1,8 +0,0 @@
-export interface TemplateContext {
-    pluginName: string;
-    pluginInitOptionsName: string;
-}
-
-export interface GeneratePluginOptions {
-    name: string;
-}

+ 6 - 1
yarn.lock

@@ -5058,6 +5058,11 @@
   dependencies:
     "@types/node" "*"
 
+"@types/ejs@^3.1.2":
+  version "3.1.2"
+  resolved "https://registry.yarnpkg.com/@types/ejs/-/ejs-3.1.2.tgz#75d277b030bc11b3be38c807e10071f45ebc78d9"
+  integrity sha512-ZmiaE3wglXVWBM9fyVC17aGPkLo/UgaOjEiI2FXQfyczrCefORPxIe+2dVmnmk3zkVIbizjrlQzmPGhSYGXG5g==
+
 "@types/eslint-scope@^3.7.3":
   version "3.7.4"
   resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16"
@@ -8825,7 +8830,7 @@ ee-first@1.1.1:
   resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
   integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
 
-ejs@^3.1.7:
+ejs@^3.1.7, ejs@^3.1.9:
   version "3.1.9"
   resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.9.tgz#03c9e8777fe12686a9effcef22303ca3d8eeb361"
   integrity sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==