Browse Source

fix(cli): Improve detection of migration file location

Michael Bromley 1 year ago
parent
commit
359b236bba

+ 1 - 1
packages/cli/src/commands/add/api-extension/add-api-extension.ts

@@ -43,7 +43,7 @@ async function addApiExtension(
     options?: AddApiExtensionOptions,
     options?: AddApiExtensionOptions,
 ): Promise<CliCommandReturnVal<{ serviceRef: ServiceRef }>> {
 ): Promise<CliCommandReturnVal<{ serviceRef: ServiceRef }>> {
     const providedVendurePlugin = options?.plugin;
     const providedVendurePlugin = options?.plugin;
-    const project = await analyzeProject({ providedVendurePlugin, cancelledMessage });
+    const { project } = await analyzeProject({ providedVendurePlugin, cancelledMessage });
     const plugin = providedVendurePlugin ?? (await selectPlugin(project, cancelledMessage));
     const plugin = providedVendurePlugin ?? (await selectPlugin(project, cancelledMessage));
     const serviceRef = await selectServiceRef(project, plugin, false);
     const serviceRef = await selectServiceRef(project, plugin, false);
     const serviceEntityRef = serviceRef.crudEntityRef;
     const serviceEntityRef = serviceRef.crudEntityRef;

+ 1 - 1
packages/cli/src/commands/add/codegen/add-codegen.ts

@@ -24,7 +24,7 @@ export const addCodegenCommand = new CliCommand({
 
 
 async function addCodegen(options?: AddCodegenOptions): Promise<CliCommandReturnVal> {
 async function addCodegen(options?: AddCodegenOptions): Promise<CliCommandReturnVal> {
     const providedVendurePlugin = options?.plugin;
     const providedVendurePlugin = options?.plugin;
-    const project = await analyzeProject({
+    const { project } = await analyzeProject({
         providedVendurePlugin,
         providedVendurePlugin,
         cancelledMessage: 'Add codegen cancelled',
         cancelledMessage: 'Add codegen cancelled',
     });
     });

+ 1 - 1
packages/cli/src/commands/add/entity/add-entity.ts

@@ -37,7 +37,7 @@ async function addEntity(
     options?: Partial<AddEntityOptions>,
     options?: Partial<AddEntityOptions>,
 ): Promise<CliCommandReturnVal<{ entityRef: EntityRef }>> {
 ): Promise<CliCommandReturnVal<{ entityRef: EntityRef }>> {
     const providedVendurePlugin = options?.plugin;
     const providedVendurePlugin = options?.plugin;
-    const project = await analyzeProject({ providedVendurePlugin, cancelledMessage });
+    const { project } = await analyzeProject({ providedVendurePlugin, cancelledMessage });
     const vendurePlugin = providedVendurePlugin ?? (await selectPlugin(project, cancelledMessage));
     const vendurePlugin = providedVendurePlugin ?? (await selectPlugin(project, cancelledMessage));
     const modifiedSourceFiles: SourceFile[] = [];
     const modifiedSourceFiles: SourceFile[] = [];
 
 

+ 5 - 5
packages/cli/src/commands/add/job-queue/add-job-queue.ts

@@ -25,7 +25,7 @@ async function addJobQueue(
     options?: AddJobQueueOptions,
     options?: AddJobQueueOptions,
 ): Promise<CliCommandReturnVal<{ serviceRef: ServiceRef }>> {
 ): Promise<CliCommandReturnVal<{ serviceRef: ServiceRef }>> {
     const providedVendurePlugin = options?.plugin;
     const providedVendurePlugin = options?.plugin;
-    const project = await analyzeProject({ providedVendurePlugin, cancelledMessage });
+    const { project } = await analyzeProject({ providedVendurePlugin, cancelledMessage });
     const plugin = providedVendurePlugin ?? (await selectPlugin(project, cancelledMessage));
     const plugin = providedVendurePlugin ?? (await selectPlugin(project, cancelledMessage));
     const serviceRef = await selectServiceRef(project, plugin);
     const serviceRef = await selectServiceRef(project, plugin);
 
 
@@ -107,19 +107,19 @@ async function addJobQueue(
                     const totalItems = 10;
                     const totalItems = 10;
                     for (let i = 0; i < totalItems; i++) {
                     for (let i = 0; i < totalItems; i++) {
                         await new Promise(resolve => setTimeout(resolve, 500));
                         await new Promise(resolve => setTimeout(resolve, 500));
-                        
+
                         // You can optionally respond to the job being cancelled
                         // You can optionally respond to the job being cancelled
                         // during processing. This can be useful for very long-running
                         // during processing. This can be useful for very long-running
                         // tasks which can be cancelled by the user.
                         // tasks which can be cancelled by the user.
                         if (job.state === JobState.CANCELLED) {
                         if (job.state === JobState.CANCELLED) {
                             throw new Error('Job was cancelled');
                             throw new Error('Job was cancelled');
                         }
                         }
-                        
+
                         // Progress can be reported as a percentage like this
                         // Progress can be reported as a percentage like this
                         job.setProgress(Math.floor(i / totalItems * 100));
                         job.setProgress(Math.floor(i / totalItems * 100));
                     }
                     }
-                  
-                    // The value returned from the \`process\` function is stored 
+
+                    // The value returned from the \`process\` function is stored
                     // as the "result" field of the job
                     // as the "result" field of the job
                     return {
                     return {
                         processedCount: totalItems,
                         processedCount: totalItems,

+ 2 - 2
packages/cli/src/commands/add/plugin/create-new-plugin.ts

@@ -109,7 +109,7 @@ export async function createNewPlugin(): Promise<CliCommandReturnVal> {
         if (featureType === 'no') {
         if (featureType === 'no') {
             done = true;
             done = true;
         } else {
         } else {
-            const newProject = await getTsMorphProject();
+            const { project: newProject } = await getTsMorphProject();
             workingProject = newProject;
             workingProject = newProject;
             const newPlugin = newProject
             const newPlugin = newProject
                 .getSourceFile(workingPlugin.getSourceFile().getFilePath())
                 .getSourceFile(workingPlugin.getSourceFile().getFilePath())
@@ -158,7 +158,7 @@ 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: true });
+    const { project } = await getTsMorphProject({ skipAddingFilesFromTsConfig: true });
 
 
     const pluginFile = createFile(project, path.join(__dirname, 'templates/plugin.template.ts'));
     const pluginFile = createFile(project, path.join(__dirname, 'templates/plugin.template.ts'));
     const pluginClass = pluginFile.getClass('TemplatePlugin');
     const pluginClass = pluginFile.getClass('TemplatePlugin');

+ 1 - 1
packages/cli/src/commands/add/service/add-service.ts

@@ -37,7 +37,7 @@ async function addService(
     providedOptions?: Partial<AddServiceOptions>,
     providedOptions?: Partial<AddServiceOptions>,
 ): Promise<CliCommandReturnVal<{ serviceRef: ServiceRef }>> {
 ): Promise<CliCommandReturnVal<{ serviceRef: ServiceRef }>> {
     const providedVendurePlugin = providedOptions?.plugin;
     const providedVendurePlugin = providedOptions?.plugin;
-    const project = await analyzeProject({ providedVendurePlugin, cancelledMessage });
+    const { project } = await analyzeProject({ providedVendurePlugin, cancelledMessage });
     const vendurePlugin = providedVendurePlugin ?? (await selectPlugin(project, cancelledMessage));
     const vendurePlugin = providedVendurePlugin ?? (await selectPlugin(project, cancelledMessage));
     const modifiedSourceFiles: SourceFile[] = [];
     const modifiedSourceFiles: SourceFile[] = [];
     const type =
     const type =

+ 1 - 1
packages/cli/src/commands/add/ui-extensions/add-ui-extensions.ts

@@ -24,7 +24,7 @@ export const addUiExtensionsCommand = new CliCommand<AddUiExtensionsOptions>({
 
 
 async function addUiExtensions(options?: AddUiExtensionsOptions): Promise<CliCommandReturnVal> {
 async function addUiExtensions(options?: AddUiExtensionsOptions): Promise<CliCommandReturnVal> {
     const providedVendurePlugin = options?.plugin;
     const providedVendurePlugin = options?.plugin;
-    const project = await analyzeProject({ providedVendurePlugin });
+    const { project } = await analyzeProject({ providedVendurePlugin });
     const vendurePlugin =
     const vendurePlugin =
         providedVendurePlugin ?? (await selectPlugin(project, 'Add UI extensions cancelled'));
         providedVendurePlugin ?? (await selectPlugin(project, 'Add UI extensions cancelled'));
     const packageJson = new PackageJson(project);
     const packageJson = new PackageJson(project);

+ 71 - 5
packages/cli/src/commands/migrate/generate-migration/generate-migration.ts

@@ -1,5 +1,8 @@
-import { cancel, isCancel, log, spinner, text } from '@clack/prompts';
-import { generateMigration } from '@vendure/core';
+import { cancel, isCancel, log, multiselect, select, spinner, text } from '@clack/prompts';
+import { unique } from '@vendure/common/lib/unique';
+import { generateMigration, VendureConfig } from '@vendure/core';
+import * as fs from 'fs-extra';
+import path from 'path';
 
 
 import { CliCommand, CliCommandReturnVal } from '../../../shared/cli-command';
 import { CliCommand, CliCommandReturnVal } from '../../../shared/cli-command';
 import { analyzeProject } from '../../../shared/shared-prompts';
 import { analyzeProject } from '../../../shared/shared-prompts';
@@ -16,7 +19,7 @@ export const generateMigrationCommand = new CliCommand({
 });
 });
 
 
 async function runGenerateMigration(): Promise<CliCommandReturnVal> {
 async function runGenerateMigration(): Promise<CliCommandReturnVal> {
-    const project = await analyzeProject({ cancelledMessage });
+    const { project, tsConfigPath } = await analyzeProject({ cancelledMessage });
     const vendureConfig = new VendureConfigRef(project);
     const vendureConfig = new VendureConfigRef(project);
     log.info('Using VendureConfig from ' + vendureConfig.getPathRelativeToProjectRoot());
     log.info('Using VendureConfig from ' + vendureConfig.getPathRelativeToProjectRoot());
 
 
@@ -34,10 +37,47 @@ async function runGenerateMigration(): Promise<CliCommandReturnVal> {
         cancel(cancelledMessage);
         cancel(cancelledMessage);
         process.exit(0);
         process.exit(0);
     }
     }
-    const config = await loadVendureConfigFile(vendureConfig);
+    const config = await loadVendureConfigFile(vendureConfig, tsConfigPath);
+
+    const migrationsDirs = getMigrationsDir(vendureConfig, config);
+    let migrationDir = migrationsDirs[0];
+
+    if (migrationsDirs.length > 1) {
+        const migrationDirSelect = await select({
+            message: 'Migration file location',
+            options: migrationsDirs
+                .map(c => ({
+                    value: c,
+                    label: c,
+                }))
+                .concat({
+                    value: 'other',
+                    label: 'Other',
+                }),
+        });
+        if (isCancel(migrationDirSelect)) {
+            cancel(cancelledMessage);
+            process.exit(0);
+        }
+        migrationDir = migrationDirSelect as string;
+    }
+
+    if (migrationsDirs.length === 1 || migrationDir === 'other') {
+        const confirmation = await text({
+            message: 'Migration file location',
+            initialValue: migrationsDirs[0],
+            placeholder: '',
+        });
+        if (isCancel(confirmation)) {
+            cancel(cancelledMessage);
+            process.exit(0);
+        }
+        migrationDir = confirmation;
+    }
+
     const migrationSpinner = spinner();
     const migrationSpinner = spinner();
     migrationSpinner.start('Generating migration...');
     migrationSpinner.start('Generating migration...');
-    const migrationName = await generateMigration(config, { name, outputDir: './src/migrations' });
+    const migrationName = await generateMigration(config, { name, outputDir: migrationDir });
     const report =
     const report =
         typeof migrationName === 'string'
         typeof migrationName === 'string'
             ? `New migration generated: ${migrationName}`
             ? `New migration generated: ${migrationName}`
@@ -48,3 +88,29 @@ async function runGenerateMigration(): Promise<CliCommandReturnVal> {
         modifiedSourceFiles: [],
         modifiedSourceFiles: [],
     };
     };
 }
 }
+
+function getMigrationsDir(vendureConfigRef: VendureConfigRef, config: VendureConfig): string[] {
+    const options: string[] = [];
+    if (
+        Array.isArray(config.dbConnectionOptions.migrations) &&
+        config.dbConnectionOptions.migrations.length
+    ) {
+        const firstEntry = config.dbConnectionOptions.migrations[0];
+        if (typeof firstEntry === 'string') {
+            options.push(path.dirname(firstEntry));
+        }
+    }
+    const migrationFile = vendureConfigRef.sourceFile
+        .getProject()
+        .getSourceFiles()
+        .find(sf => {
+            return sf
+                .getClasses()
+                .find(c => c.getImplements().find(i => i.getText() === 'MigrationInterface'));
+        });
+    if (migrationFile) {
+        options.push(migrationFile.getDirectory().getPath());
+    }
+    options.push(path.join(vendureConfigRef.sourceFile.getDirectory().getPath(), '../migrations'));
+    return unique(options.map(p => path.normalize(p)));
+}

+ 13 - 3
packages/cli/src/commands/migrate/load-vendure-config-file.ts

@@ -1,3 +1,4 @@
+import { VendureConfig } from '@vendure/core';
 import path from 'node:path';
 import path from 'node:path';
 import { register } from 'ts-node';
 import { register } from 'ts-node';
 
 
@@ -5,12 +6,21 @@ import { VendureConfigRef } from '../../shared/vendure-config-ref';
 import { selectTsConfigFile } from '../../utilities/ast-utils';
 import { selectTsConfigFile } from '../../utilities/ast-utils';
 import { isRunningInTsNode } from '../../utilities/utils';
 import { isRunningInTsNode } from '../../utilities/utils';
 
 
-export async function loadVendureConfigFile(vendureConfig: VendureConfigRef) {
+export async function loadVendureConfigFile(
+    vendureConfig: VendureConfigRef,
+    providedTsConfigPath?: string,
+): Promise<VendureConfig> {
     await import('dotenv/config');
     await import('dotenv/config');
     if (!isRunningInTsNode()) {
     if (!isRunningInTsNode()) {
-        const tsConfigPath = await selectTsConfigFile();
+        let tsConfigPath: string;
+        if (providedTsConfigPath) {
+            tsConfigPath = providedTsConfigPath;
+        } else {
+            const tsConfigFile = await selectTsConfigFile();
+            tsConfigPath = path.join(process.cwd(), tsConfigFile);
+        }
         // eslint-disable-next-line @typescript-eslint/no-var-requires
         // eslint-disable-next-line @typescript-eslint/no-var-requires
-        const compilerOptions = require(path.join(process.cwd(), tsConfigPath)).compilerOptions;
+        const compilerOptions = require(tsConfigPath).compilerOptions;
         register({
         register({
             compilerOptions: { ...compilerOptions, moduleResolution: 'NodeNext', module: 'NodeNext' },
             compilerOptions: { ...compilerOptions, moduleResolution: 'NodeNext', module: 'NodeNext' },
             transpileOnly: true,
             transpileOnly: true,

+ 1 - 1
packages/cli/src/commands/migrate/revert-migration/revert-migration.ts

@@ -16,7 +16,7 @@ export const revertMigrationCommand = new CliCommand({
 });
 });
 
 
 async function runRevertMigration(): Promise<CliCommandReturnVal> {
 async function runRevertMigration(): Promise<CliCommandReturnVal> {
-    const project = await analyzeProject({ cancelledMessage });
+    const { project } = await analyzeProject({ cancelledMessage });
     const vendureConfig = new VendureConfigRef(project);
     const vendureConfig = new VendureConfigRef(project);
     log.info('Using VendureConfig from ' + vendureConfig.getPathRelativeToProjectRoot());
     log.info('Using VendureConfig from ' + vendureConfig.getPathRelativeToProjectRoot());
     const config = await loadVendureConfigFile(vendureConfig);
     const config = await loadVendureConfigFile(vendureConfig);

+ 1 - 1
packages/cli/src/commands/migrate/run-migration/run-migration.ts

@@ -16,7 +16,7 @@ export const runMigrationCommand = new CliCommand({
 });
 });
 
 
 async function runRunMigration(): Promise<CliCommandReturnVal> {
 async function runRunMigration(): Promise<CliCommandReturnVal> {
-    const project = await analyzeProject({ cancelledMessage });
+    const { project } = await analyzeProject({ cancelledMessage });
     const vendureConfig = new VendureConfigRef(project);
     const vendureConfig = new VendureConfigRef(project);
     log.info('Using VendureConfig from ' + vendureConfig.getPathRelativeToProjectRoot());
     log.info('Using VendureConfig from ' + vendureConfig.getPathRelativeToProjectRoot());
     const config = await loadVendureConfigFile(vendureConfig);
     const config = await loadVendureConfigFile(vendureConfig);

+ 6 - 2
packages/cli/src/shared/shared-prompts.ts

@@ -16,14 +16,18 @@ export async function analyzeProject(options: {
 }) {
 }) {
     const providedVendurePlugin = options.providedVendurePlugin;
     const providedVendurePlugin = options.providedVendurePlugin;
     let project = providedVendurePlugin?.classDeclaration.getProject();
     let project = providedVendurePlugin?.classDeclaration.getProject();
+    let tsConfigPath: string | undefined;
+
     if (!providedVendurePlugin) {
     if (!providedVendurePlugin) {
         const projectSpinner = spinner();
         const projectSpinner = spinner();
         projectSpinner.start('Analyzing project...');
         projectSpinner.start('Analyzing project...');
         await pauseForPromptDisplay();
         await pauseForPromptDisplay();
-        project = await getTsMorphProject();
+        const { project: _project, tsConfigPath: _tsConfigPath } = await getTsMorphProject();
+        project = _project;
+        tsConfigPath = _tsConfigPath;
         projectSpinner.stop('Project analyzed');
         projectSpinner.stop('Project analyzed');
     }
     }
-    return project as Project;
+    return { project: project as Project, tsConfigPath };
 }
 }
 
 
 export async function selectPlugin(project: Project, cancelledMessage: string): Promise<VendurePluginRef> {
 export async function selectPlugin(project: Project, cancelledMessage: string): Promise<VendurePluginRef> {

+ 1 - 1
packages/cli/src/utilities/ast-utils.ts

@@ -45,7 +45,7 @@ export async function getTsMorphProject(options: ProjectOptions = {}) {
         ...options,
         ...options,
     });
     });
     project.enableLogging(false);
     project.enableLogging(false);
-    return project;
+    return { project, tsConfigPath };
 }
 }
 
 
 export function getPluginClasses(project: Project) {
 export function getPluginClasses(project: Project) {