|
@@ -2,11 +2,13 @@ import { cancel, intro, isCancel, log, select, spinner, text } from '@clack/prom
|
|
|
import { constantCase, paramCase, pascalCase } from 'change-case';
|
|
import { constantCase, paramCase, pascalCase } from 'change-case';
|
|
|
import * as fs from 'fs-extra';
|
|
import * as fs from 'fs-extra';
|
|
|
import path from 'path';
|
|
import path from 'path';
|
|
|
|
|
+import { Project, SourceFile } from 'ts-morph';
|
|
|
|
|
|
|
|
import { CliCommand, CliCommandReturnVal } from '../../../shared/cli-command';
|
|
import { CliCommand, CliCommandReturnVal } from '../../../shared/cli-command';
|
|
|
|
|
+import { analyzeProject } from '../../../shared/shared-prompts';
|
|
|
import { VendureConfigRef } from '../../../shared/vendure-config-ref';
|
|
import { VendureConfigRef } from '../../../shared/vendure-config-ref';
|
|
|
import { VendurePluginRef } from '../../../shared/vendure-plugin-ref';
|
|
import { VendurePluginRef } from '../../../shared/vendure-plugin-ref';
|
|
|
-import { addImportsToFile, createFile, getTsMorphProject } from '../../../utilities/ast-utils';
|
|
|
|
|
|
|
+import { addImportsToFile, createFile, getPluginClasses } from '../../../utilities/ast-utils';
|
|
|
import { pauseForPromptDisplay } from '../../../utilities/utils';
|
|
import { pauseForPromptDisplay } from '../../../utilities/utils';
|
|
|
import { addApiExtensionCommand } from '../api-extension/add-api-extension';
|
|
import { addApiExtensionCommand } from '../api-extension/add-api-extension';
|
|
|
import { addCodegenCommand } from '../codegen/add-codegen';
|
|
import { addCodegenCommand } from '../codegen/add-codegen';
|
|
@@ -29,6 +31,7 @@ const cancelledMessage = 'Plugin setup cancelled.';
|
|
|
export async function createNewPlugin(): Promise<CliCommandReturnVal> {
|
|
export async function createNewPlugin(): Promise<CliCommandReturnVal> {
|
|
|
const options: GeneratePluginOptions = { name: '', customEntityName: '', pluginDir: '' } as any;
|
|
const options: GeneratePluginOptions = { name: '', customEntityName: '', pluginDir: '' } as any;
|
|
|
intro('Adding a new Vendure plugin!');
|
|
intro('Adding a new Vendure plugin!');
|
|
|
|
|
+ const { project } = await analyzeProject({ cancelledMessage });
|
|
|
if (!options.name) {
|
|
if (!options.name) {
|
|
|
const name = await text({
|
|
const name = await text({
|
|
|
message: 'What is the name of the plugin?',
|
|
message: 'What is the name of the plugin?',
|
|
@@ -47,7 +50,8 @@ export async function createNewPlugin(): Promise<CliCommandReturnVal> {
|
|
|
options.name = name;
|
|
options.name = name;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- const pluginDir = getPluginDirName(options.name);
|
|
|
|
|
|
|
+ const existingPluginDir = findExistingPluginsDir(project);
|
|
|
|
|
+ const pluginDir = getPluginDirName(options.name, existingPluginDir);
|
|
|
const confirmation = await text({
|
|
const confirmation = await text({
|
|
|
message: 'Plugin location',
|
|
message: 'Plugin location',
|
|
|
initialValue: pluginDir,
|
|
initialValue: pluginDir,
|
|
@@ -65,7 +69,7 @@ export async function createNewPlugin(): Promise<CliCommandReturnVal> {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
options.pluginDir = confirmation;
|
|
options.pluginDir = confirmation;
|
|
|
- const { plugin, project, modifiedSourceFiles } = await generatePlugin(options);
|
|
|
|
|
|
|
+ const { plugin, modifiedSourceFiles } = await generatePlugin(project, options);
|
|
|
|
|
|
|
|
const configSpinner = spinner();
|
|
const configSpinner = spinner();
|
|
|
configSpinner.start('Updating VendureConfig...');
|
|
configSpinner.start('Updating VendureConfig...');
|
|
@@ -89,9 +93,6 @@ export async function createNewPlugin(): Promise<CliCommandReturnVal> {
|
|
|
addCodegenCommand,
|
|
addCodegenCommand,
|
|
|
];
|
|
];
|
|
|
let allModifiedSourceFiles = [...modifiedSourceFiles];
|
|
let allModifiedSourceFiles = [...modifiedSourceFiles];
|
|
|
- const pluginClassName = plugin.name;
|
|
|
|
|
- let workingPlugin = plugin;
|
|
|
|
|
- let workingProject = project;
|
|
|
|
|
while (!done) {
|
|
while (!done) {
|
|
|
const featureType = await select({
|
|
const featureType = await select({
|
|
|
message: `Add features to ${options.name}?`,
|
|
message: `Add features to ${options.name}?`,
|
|
@@ -109,20 +110,11 @@ export async function createNewPlugin(): Promise<CliCommandReturnVal> {
|
|
|
if (featureType === 'no') {
|
|
if (featureType === 'no') {
|
|
|
done = true;
|
|
done = true;
|
|
|
} else {
|
|
} else {
|
|
|
- const { project: newProject } = await getTsMorphProject();
|
|
|
|
|
- workingProject = newProject;
|
|
|
|
|
- const newPlugin = newProject
|
|
|
|
|
- .getSourceFile(workingPlugin.getSourceFile().getFilePath())
|
|
|
|
|
- ?.getClass(pluginClassName);
|
|
|
|
|
- if (!newPlugin) {
|
|
|
|
|
- throw new Error(`Could not find class "${pluginClassName}" in the new project`);
|
|
|
|
|
- }
|
|
|
|
|
- workingPlugin = new VendurePluginRef(newPlugin);
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
|
const command = followUpCommands.find(c => c.id === featureType)!;
|
|
const command = followUpCommands.find(c => c.id === featureType)!;
|
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
|
try {
|
|
try {
|
|
|
- const result = await command.run({ plugin: new VendurePluginRef(newPlugin) });
|
|
|
|
|
|
|
+ const result = await command.run({ plugin });
|
|
|
allModifiedSourceFiles = result.modifiedSourceFiles;
|
|
allModifiedSourceFiles = result.modifiedSourceFiles;
|
|
|
// We format all modified source files and re-load the
|
|
// We format all modified source files and re-load the
|
|
|
// project to avoid issues with the project state
|
|
// project to avoid issues with the project state
|
|
@@ -133,8 +125,6 @@ export async function createNewPlugin(): Promise<CliCommandReturnVal> {
|
|
|
log.error(`Error adding feature "${command.id}"`);
|
|
log.error(`Error adding feature "${command.id}"`);
|
|
|
log.error(e.stack);
|
|
log.error(e.stack);
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- await workingProject.save();
|
|
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -145,8 +135,9 @@ export async function createNewPlugin(): Promise<CliCommandReturnVal> {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
export async function generatePlugin(
|
|
export async function generatePlugin(
|
|
|
|
|
+ project: Project,
|
|
|
options: GeneratePluginOptions,
|
|
options: GeneratePluginOptions,
|
|
|
-): Promise<CliCommandReturnVal<{ plugin: VendurePluginRef }>> {
|
|
|
|
|
|
|
+): Promise<{ plugin: VendurePluginRef; modifiedSourceFiles: SourceFile[] }> {
|
|
|
const nameWithoutPlugin = options.name.replace(/-?plugin$/i, '');
|
|
const nameWithoutPlugin = options.name.replace(/-?plugin$/i, '');
|
|
|
const normalizedName = nameWithoutPlugin + '-plugin';
|
|
const normalizedName = nameWithoutPlugin + '-plugin';
|
|
|
const templateContext: NewPluginTemplateContext = {
|
|
const templateContext: NewPluginTemplateContext = {
|
|
@@ -158,7 +149,6 @@ export async function generatePlugin(
|
|
|
const projectSpinner = spinner();
|
|
const projectSpinner = spinner();
|
|
|
projectSpinner.start('Generating plugin scaffold...');
|
|
projectSpinner.start('Generating plugin scaffold...');
|
|
|
await pauseForPromptDisplay();
|
|
await pauseForPromptDisplay();
|
|
|
- const { project } = await getTsMorphProject({ skipAddingFilesFromTsConfig: false });
|
|
|
|
|
|
|
|
|
|
const pluginFile = createFile(
|
|
const pluginFile = createFile(
|
|
|
project,
|
|
project,
|
|
@@ -169,6 +159,8 @@ export async function generatePlugin(
|
|
|
if (!pluginClass) {
|
|
if (!pluginClass) {
|
|
|
throw new Error('Could not find the plugin class in the generated file');
|
|
throw new Error('Could not find the plugin class in the generated file');
|
|
|
}
|
|
}
|
|
|
|
|
+ pluginFile.getImportDeclaration('./constants.template')?.setModuleSpecifier('./constants');
|
|
|
|
|
+ pluginFile.getImportDeclaration('./types.template')?.setModuleSpecifier('./types');
|
|
|
pluginClass.rename(templateContext.pluginName);
|
|
pluginClass.rename(templateContext.pluginName);
|
|
|
|
|
|
|
|
const typesFile = createFile(
|
|
const typesFile = createFile(
|
|
@@ -193,24 +185,72 @@ export async function generatePlugin(
|
|
|
projectSpinner.stop('Generated plugin scaffold');
|
|
projectSpinner.stop('Generated plugin scaffold');
|
|
|
await project.save();
|
|
await project.save();
|
|
|
return {
|
|
return {
|
|
|
- project,
|
|
|
|
|
modifiedSourceFiles: [pluginFile, typesFile, constantsFile],
|
|
modifiedSourceFiles: [pluginFile, typesFile, constantsFile],
|
|
|
plugin: new VendurePluginRef(pluginClass),
|
|
plugin: new VendurePluginRef(pluginClass),
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function getPluginDirName(name: string) {
|
|
|
|
|
|
|
+function findExistingPluginsDir(project: Project): { prefix: string; suffix: string } | undefined {
|
|
|
|
|
+ const pluginClasses = getPluginClasses(project);
|
|
|
|
|
+ if (pluginClasses.length === 0) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const pluginDirs = pluginClasses.map(c => {
|
|
|
|
|
+ return c.getSourceFile().getDirectoryPath();
|
|
|
|
|
+ });
|
|
|
|
|
+ const prefix = findCommonPath(pluginDirs);
|
|
|
|
|
+ const suffixStartIndex = prefix.length;
|
|
|
|
|
+ const rest = pluginDirs[0].substring(suffixStartIndex).replace(/^\//, '').split('/');
|
|
|
|
|
+ const suffix = rest.length > 1 ? rest.slice(1).join('/') : '';
|
|
|
|
|
+ return { prefix, suffix };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function getPluginDirName(
|
|
|
|
|
+ name: string,
|
|
|
|
|
+ existingPluginDirPattern: { prefix: string; suffix: string } | undefined,
|
|
|
|
|
+) {
|
|
|
const cwd = process.cwd();
|
|
const cwd = process.cwd();
|
|
|
- const pathParts = cwd.split(path.sep);
|
|
|
|
|
- const currentlyInPluginsDir = pathParts[pathParts.length - 1] === 'plugins';
|
|
|
|
|
- const currentlyInRootDir = fs.pathExistsSync(path.join(cwd, 'package.json'));
|
|
|
|
|
const nameWithoutPlugin = name.replace(/-?plugin$/i, '');
|
|
const nameWithoutPlugin = name.replace(/-?plugin$/i, '');
|
|
|
|
|
+ if (existingPluginDirPattern) {
|
|
|
|
|
+ return path.join(
|
|
|
|
|
+ existingPluginDirPattern.prefix,
|
|
|
|
|
+ paramCase(nameWithoutPlugin),
|
|
|
|
|
+ existingPluginDirPattern.suffix,
|
|
|
|
|
+ );
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return path.join(cwd, 'src', 'plugins', paramCase(nameWithoutPlugin));
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- if (currentlyInPluginsDir) {
|
|
|
|
|
- return path.join(cwd, paramCase(nameWithoutPlugin));
|
|
|
|
|
|
|
+function findCommonPath(paths: string[]): string {
|
|
|
|
|
+ if (paths.length === 0) {
|
|
|
|
|
+ return ''; // If no paths provided, return empty string
|
|
|
}
|
|
}
|
|
|
- if (currentlyInRootDir) {
|
|
|
|
|
- return path.join(cwd, 'src', 'plugins', paramCase(nameWithoutPlugin));
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // Split each path into segments
|
|
|
|
|
+ const pathSegmentsList = paths.map(p => p.split('/'));
|
|
|
|
|
+
|
|
|
|
|
+ // Find the minimum length of path segments (to avoid out of bounds)
|
|
|
|
|
+ const minLength = Math.min(...pathSegmentsList.map(segments => segments.length));
|
|
|
|
|
+
|
|
|
|
|
+ // Initialize the common path
|
|
|
|
|
+ const commonPath: string[] = [];
|
|
|
|
|
+
|
|
|
|
|
+ // Loop through each segment index up to the minimum length
|
|
|
|
|
+ for (let i = 0; i < minLength; i++) {
|
|
|
|
|
+ // Get the segment at the current index from the first path
|
|
|
|
|
+ const currentSegment = pathSegmentsList[0][i];
|
|
|
|
|
+ // Check if this segment is common across all paths
|
|
|
|
|
+ const isCommon = pathSegmentsList.every(segments => segments[i] === currentSegment);
|
|
|
|
|
+ if (isCommon) {
|
|
|
|
|
+ // If it's common, add this segment to the common path
|
|
|
|
|
+ commonPath.push(currentSegment);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // If it's not common, break out of the loop
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- return path.join(cwd, paramCase(nameWithoutPlugin));
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // Join the common path segments back into a string
|
|
|
|
|
+ return commonPath.join('/');
|
|
|
}
|
|
}
|