Skip to content

Commit

Permalink
feat(cli): Add API extension command
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelbromley committed Apr 3, 2024
1 parent 455698f commit 41675a4
Show file tree
Hide file tree
Showing 16 changed files with 676 additions and 41 deletions.
9 changes: 7 additions & 2 deletions packages/cli/src/commands/add/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Command } from 'commander';

import { CliCommand } from '../../shared/cli-command';

import { addApiExtensionCommand } from './api-extension/add-api-extension';
import { addCodegenCommand } from './codegen/add-codegen';
import { addEntityCommand } from './entity/add-entity';
import { createNewPluginCommand } from './plugin/create-new-plugin';
Expand All @@ -16,10 +17,11 @@ export function registerAddCommand(program: Command) {
.command('add')
.description('Add a feature to your Vendure project')
.action(async () => {
const addCommands: Array<CliCommand<any, any>> = [
const addCommands: Array<CliCommand<any>> = [
createNewPluginCommand,
addEntityCommand,
addServiceCommand,
addApiExtensionCommand,
addUiExtensionsCommand,
addCodegenCommand,
];
Expand All @@ -39,8 +41,11 @@ export function registerAddCommand(program: Command) {
if (!command) {
throw new Error(`Could not find command with id "${featureType as string}"`);
}
await command.run();
const { modifiedSourceFiles } = await command.run();

for (const sourceFile of modifiedSourceFiles) {
sourceFile.organizeImports();
}
outro('✅ Done!');
} catch (e: any) {
log.error(e.message as string);
Expand Down
449 changes: 449 additions & 0 deletions packages/cli/src/commands/add/api-extension/add-api-extension.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { DocumentNode } from 'graphql/language/index';
import gql from 'graphql-tag';

const adminApiExtensionDocuments: DocumentNode[] = [];

export const adminApiExtensions = gql`
${adminApiExtensionDocuments.join('\n')}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
import { DeletionResponse, Permission } from '@vendure/common/lib/generated-types';
import { Allow, Ctx, PaginatedList, RequestContext, Transaction, VendureEntity } from '@vendure/core';

class TemplateEntity extends VendureEntity {}

class TemplateService {
findAll(ctx: RequestContext, options?: any): Promise<PaginatedList<TemplateEntity>> {
throw new Error('Method not implemented.');
}

findOne(ctx: RequestContext, id: string): Promise<TemplateEntity | null> {
throw new Error('Method not implemented.');
}

create(ctx: RequestContext, input: any): Promise<TemplateEntity> {
throw new Error('Method not implemented.');
}

update(ctx: RequestContext, input: any): Promise<TemplateEntity> {
throw new Error('Method not implemented.');
}

delete(ctx: RequestContext, id: string): Promise<DeletionResponse> {
throw new Error('Method not implemented.');
}
}

@Resolver()
export class EntityAdminResolver {
constructor(private templateService: TemplateService) {}

@Query()
@Allow(Permission.SuperAdmin)
async entity(@Ctx() ctx: RequestContext, @Args() args: any): Promise<TemplateEntity | null> {
return this.templateService.findOne(ctx, args.id);
}

@Query()
@Allow(Permission.SuperAdmin)
async entities(@Ctx() ctx: RequestContext, @Args() args: any): Promise<PaginatedList<TemplateEntity>> {
return this.templateService.findAll(ctx, args.options || undefined);
}

@Mutation()
@Transaction()
@Allow(Permission.SuperAdmin)
async createEntity(@Ctx() ctx: RequestContext, @Args() args: any): Promise<TemplateEntity> {
return this.templateService.create(ctx, args.input);
}

@Mutation()
@Transaction()
@Allow(Permission.SuperAdmin)
async updateEntity(@Ctx() ctx: RequestContext, @Args() args: any): Promise<TemplateEntity> {
return this.templateService.update(ctx, args.input);
}

@Mutation()
@Transaction()
@Allow(Permission.SuperAdmin)
async deleteEntity(@Ctx() ctx: RequestContext, @Args() args: any): Promise<DeletionResponse> {
return this.templateService.delete(ctx, args.id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
import { Permission } from '@vendure/common/lib/generated-types';
import { Allow, Ctx, RequestContext } from '@vendure/core';

class TemplateService {}

@Resolver()
export class SimpleAdminResolver {
constructor(private templateService: TemplateService) {}

@Query()
@Allow(Permission.SuperAdmin)
async exampleQuery(@Ctx() ctx: RequestContext, @Args() args: { id: string }): Promise<boolean> {
return true;
}

@Mutation()
@Allow(Permission.SuperAdmin)
async exampleMutation(@Ctx() ctx: RequestContext, @Args() args: { id: string }): Promise<boolean> {
return true;
}
}
11 changes: 6 additions & 5 deletions packages/cli/src/commands/add/codegen/add-codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { log, note, outro, spinner } from '@clack/prompts';
import path from 'path';
import { StructureKind } from 'ts-morph';

import { CliCommand } from '../../../shared/cli-command';
import { CliCommand, CliCommandReturnVal } from '../../../shared/cli-command';
import { PackageJson } from '../../../shared/package-json-ref';
import { analyzeProject, selectMultiplePluginClasses } from '../../../shared/shared-prompts';
import { VendurePluginRef } from '../../../shared/vendure-plugin-ref';
Expand All @@ -21,7 +21,7 @@ export const addCodegenCommand = new CliCommand({
run: addCodegen,
});

async function addCodegen(options?: AddCodegenOptions) {
async function addCodegen(options?: AddCodegenOptions): Promise<CliCommandReturnVal> {
const providedVendurePlugin = options?.plugin;
const project = await analyzeProject({
providedVendurePlugin,
Expand Down Expand Up @@ -109,7 +109,8 @@ async function addCodegen(options?: AddCodegenOptions) {
];
note(nextSteps.join('\n'));

if (!providedVendurePlugin) {
outro('✅ Codegen setup complete!');
}
return {
project,
modifiedSourceFiles: [codegenFile.sourceFile],
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { createFile, getTsMorphProject } from '../../../utilities/ast-utils';

export class CodegenConfigRef {
private readonly tempProject: Project;
private readonly sourceFile: SourceFile;
public readonly sourceFile: SourceFile;
private configObject: ObjectLiteralExpression | undefined;
constructor(rootDir: Directory) {
this.tempProject = getTsMorphProject({ skipAddingFilesFromTsConfig: true });
Expand Down
15 changes: 9 additions & 6 deletions packages/cli/src/commands/add/entity/add-entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import path from 'path';
import { ClassDeclaration } from 'ts-morph';

import { pascalCaseRegex } from '../../../constants';
import { CliCommand } from '../../../shared/cli-command';
import { CliCommand, CliCommandReturnVal } from '../../../shared/cli-command';
import { EntityRef } from '../../../shared/entity-ref';
import { analyzeProject, selectPlugin } from '../../../shared/shared-prompts';
import { VendurePluginRef } from '../../../shared/vendure-plugin-ref';
Expand Down Expand Up @@ -32,7 +32,9 @@ export const addEntityCommand = new CliCommand({
run: options => addEntity(options),
});

async function addEntity(options?: Partial<AddEntityOptions>) {
async function addEntity(
options?: Partial<AddEntityOptions>,
): Promise<CliCommandReturnVal<{ entityRef: EntityRef }>> {
const providedVendurePlugin = options?.plugin;
const project = await analyzeProject({ providedVendurePlugin, cancelledMessage });
const vendurePlugin = providedVendurePlugin ?? (await selectPlugin(project, cancelledMessage));
Expand All @@ -56,10 +58,11 @@ async function addEntity(options?: Partial<AddEntityOptions>) {

await project.save();

if (!providedVendurePlugin) {
outro('✅ Done!');
}
return new EntityRef(entityClass);
return {
project,
modifiedSourceFiles: [entityClass.getSourceFile()],
entityRef: new EntityRef(entityClass),
};
}

async function getFeatures(options?: Partial<AddEntityOptions>): Promise<AddEntityOptions['features']> {
Expand Down
27 changes: 21 additions & 6 deletions packages/cli/src/commands/add/plugin/create-new-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { cancel, intro, isCancel, outro, select, spinner, text } from '@clack/pr
import { constantCase, paramCase, pascalCase } from 'change-case';
import * as fs from 'fs-extra';
import path from 'path';
import { SourceFile } from 'ts-morph';

import { CliCommand } from '../../../shared/cli-command';
import { CliCommand, CliCommandReturnVal } from '../../../shared/cli-command';
import { VendureConfigRef } from '../../../shared/vendure-config-ref';
import { VendurePluginRef } from '../../../shared/vendure-plugin-ref';
import { addImportsToFile, createFile, getTsMorphProject } from '../../../utilities/ast-utils';
Expand All @@ -23,7 +24,7 @@ export const createNewPluginCommand = new CliCommand({

const cancelledMessage = 'Plugin setup cancelled.';

export async function createNewPlugin() {
export async function createNewPlugin(): Promise<CliCommandReturnVal> {
const options: GeneratePluginOptions = { name: '', customEntityName: '', pluginDir: '' } as any;
intro('Adding a new Vendure plugin!');
if (!options.name) {
Expand Down Expand Up @@ -62,7 +63,7 @@ export async function createNewPlugin() {
}

options.pluginDir = confirmation;
const plugin = await generatePlugin(options);
const { plugin, project, modifiedSourceFiles } = await generatePlugin(options);

const configSpinner = spinner();
configSpinner.start('Updating VendureConfig...');
Expand All @@ -78,6 +79,7 @@ export async function createNewPlugin() {

let done = false;
const followUpCommands = [addEntityCommand, addServiceCommand, addUiExtensionsCommand, addCodegenCommand];
const allModifiedSourceFiles = [...modifiedSourceFiles];
while (!done) {
const featureType = await select({
message: `Add features to ${options.name}?`,
Expand All @@ -96,12 +98,21 @@ export async function createNewPlugin() {
done = true;
} else {
const command = followUpCommands.find(c => c.id === featureType);
await command?.run({ plugin });
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const result = await command!.run({ plugin });
allModifiedSourceFiles.push(...result.modifiedSourceFiles);
}
}

return {
project,
modifiedSourceFiles,
};
}

export async function generatePlugin(options: GeneratePluginOptions): Promise<VendurePluginRef> {
export async function generatePlugin(
options: GeneratePluginOptions,
): Promise<CliCommandReturnVal<{ plugin: VendurePluginRef }>> {
const nameWithoutPlugin = options.name.replace(/-?plugin$/i, '');
const normalizedName = nameWithoutPlugin + '-plugin';
const templateContext: NewPluginTemplateContext = {
Expand Down Expand Up @@ -139,7 +150,11 @@ export async function generatePlugin(options: GeneratePluginOptions): Promise<Ve

projectSpinner.stop('Generated plugin scaffold');
await project.save();
return new VendurePluginRef(pluginClass);
return {
project,
modifiedSourceFiles: [pluginFile, typesFile, constantsFile],
plugin: new VendurePluginRef(pluginClass),
};
}

function getPluginDirName(name: string) {
Expand Down
20 changes: 11 additions & 9 deletions packages/cli/src/commands/add/service/add-service.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { cancel, isCancel, outro, select, text } from '@clack/prompts';
import { cancel, isCancel, select, text } from '@clack/prompts';
import path from 'path';
import { ClassDeclaration, SourceFile } from 'ts-morph';

import { pascalCaseRegex } from '../../../constants';
import { CliCommand } from '../../../shared/cli-command';
import { CliCommand, CliCommandReturnVal } from '../../../shared/cli-command';
import { EntityRef } from '../../../shared/entity-ref';
import { ServiceRef } from '../../../shared/service-ref';
import { analyzeProject, selectEntity, selectPlugin } from '../../../shared/shared-prompts';
Expand All @@ -19,14 +19,16 @@ interface AddServiceOptions {
entityRef?: EntityRef;
}

export const addServiceCommand = new CliCommand<AddServiceOptions, ServiceRef>({
export const addServiceCommand = new CliCommand({
id: 'add-service',
category: 'Plugin: Service',
description: 'Add a new service to a plugin',
run: options => addService(options),
});

async function addService(providedOptions?: Partial<AddServiceOptions>) {
async function addService(
providedOptions?: Partial<AddServiceOptions>,
): Promise<CliCommandReturnVal<{ serviceRef: ServiceRef }>> {
const providedVendurePlugin = providedOptions?.plugin;
const project = await analyzeProject({ providedVendurePlugin, cancelledMessage });
const vendurePlugin = providedVendurePlugin ?? (await selectPlugin(project, cancelledMessage));
Expand Down Expand Up @@ -133,13 +135,13 @@ async function addService(providedOptions?: Partial<AddServiceOptions>) {
namedImports: [options.serviceName],
});

serviceSourceFile.organizeImports();
await project.save();

if (!providedVendurePlugin) {
outro('✅ Done!');
}
return new ServiceRef(serviceClassDeclaration);
return {
project,
modifiedSourceFiles: [serviceSourceFile],
serviceRef: new ServiceRef(serviceClassDeclaration),
};
}

function customizeFindOneMethod(serviceClassDeclaration: ClassDeclaration, entityRef: EntityRef) {
Expand Down
10 changes: 4 additions & 6 deletions packages/cli/src/commands/add/ui-extensions/add-ui-extensions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { log, note, outro, spinner } from '@clack/prompts';
import path from 'path';

import { CliCommand } from '../../../shared/cli-command';
import { CliCommand, CliCommandReturnVal } from '../../../shared/cli-command';
import { PackageJson } from '../../../shared/package-json-ref';
import { analyzeProject, selectPlugin } from '../../../shared/shared-prompts';
import { VendureConfigRef } from '../../../shared/vendure-config-ref';
Expand All @@ -22,7 +22,7 @@ export const addUiExtensionsCommand = new CliCommand<AddUiExtensionsOptions>({
run: options => addUiExtensions(options),
});

async function addUiExtensions(options?: AddUiExtensionsOptions) {
async function addUiExtensions(options?: AddUiExtensionsOptions): Promise<CliCommandReturnVal> {
const providedVendurePlugin = options?.plugin;
const project = await analyzeProject({ providedVendurePlugin });
const vendurePlugin =
Expand All @@ -31,7 +31,7 @@ async function addUiExtensions(options?: AddUiExtensionsOptions) {

if (vendurePlugin.hasUiExtensions()) {
outro('This plugin already has UI extensions configured');
return;
return { project, modifiedSourceFiles: [] };
}
addUiExtensionStaticProp(vendurePlugin);

Expand Down Expand Up @@ -85,7 +85,5 @@ async function addUiExtensions(options?: AddUiExtensionsOptions) {
}

await project.save();
if (!providedVendurePlugin) {
outro('✅ Done!');
}
return { project, modifiedSourceFiles: [vendurePlugin.classDeclaration.getSourceFile()] };
}
Loading

0 comments on commit 41675a4

Please sign in to comment.