Просмотр исходного кода

feat(cli): Implement "add" command for ui extensions

Michael Bromley 1 год назад
Родитель
Сommit
795b013c51
26 измененных файлов с 1601 добавлено и 692 удалено
  1. 4 2
      packages/cli/package.json
  2. 16 1
      packages/cli/src/cli.ts
  3. 29 0
      packages/cli/src/commands/add/add.ts
  4. 109 0
      packages/cli/src/commands/add/ui-extensions/add-ui-extensions.ts
  5. 25 0
      packages/cli/src/commands/add/ui-extensions/codemods/add-ui-extension-static-prop/add-ui-extension-static-prop.spec.ts
  6. 39 0
      packages/cli/src/commands/add/ui-extensions/codemods/add-ui-extension-static-prop/add-ui-extension-static-prop.ts
  7. 24 0
      packages/cli/src/commands/add/ui-extensions/codemods/add-ui-extension-static-prop/fixtures/no-ui-prop.expected
  8. 16 0
      packages/cli/src/commands/add/ui-extensions/codemods/add-ui-extension-static-prop/fixtures/no-ui-prop.fixture.ts
  9. 52 0
      packages/cli/src/commands/add/ui-extensions/codemods/update-admin-ui-plugin-init/fixtures/existing-app-prop.expected
  10. 50 0
      packages/cli/src/commands/add/ui-extensions/codemods/update-admin-ui-plugin-init/fixtures/existing-app-prop.fixture.ts
  11. 47 0
      packages/cli/src/commands/add/ui-extensions/codemods/update-admin-ui-plugin-init/fixtures/no-app-prop.expected
  12. 37 0
      packages/cli/src/commands/add/ui-extensions/codemods/update-admin-ui-plugin-init/fixtures/no-app-prop.fixture.ts
  13. 46 0
      packages/cli/src/commands/add/ui-extensions/codemods/update-admin-ui-plugin-init/update-admin-ui-plugin-init.spec.ts
  14. 71 0
      packages/cli/src/commands/add/ui-extensions/codemods/update-admin-ui-plugin-init/update-admin-ui-plugin-init.ts
  15. 8 0
      packages/cli/src/commands/add/ui-extensions/scaffold/providers.ts
  16. 8 0
      packages/cli/src/commands/add/ui-extensions/scaffold/routes.ts
  17. 19 54
      packages/cli/src/commands/new/plugin/new-plugin.ts
  18. 6 0
      packages/cli/src/constants.ts
  19. 3 0
      packages/cli/src/types.ts
  20. 23 0
      packages/cli/src/utilities/logger.ts
  21. 102 0
      packages/cli/src/utilities/package-utils.ts
  22. 20 0
      packages/cli/src/utilities/scaffolder.ts
  23. 124 0
      packages/cli/src/utilities/utils.ts
  24. 11 0
      packages/cli/vitest.config.ts
  25. 643 622
      packages/common/src/generated-types.ts
  26. 69 13
      yarn.lock

+ 4 - 2
packages/cli/package.json

@@ -21,7 +21,8 @@
     "scripts": {
         "build": "rimraf dist && tsc -p ./tsconfig.cli.json",
         "watch": "tsc -p ./tsconfig.cli.json --watch",
-        "ci": "yarn build"
+        "ci": "yarn build",
+        "test": "vitest --config ./vitest.config.ts --run"
     },
     "publishConfig": {
         "access": "public"
@@ -38,7 +39,8 @@
         "change-case": "^4.1.2",
         "commander": "^11.0.0",
         "fs-extra": "^11.1.1",
-        "picocolors": "^1.0.0"
+        "picocolors": "^1.0.0",
+        "ts-morph": "^21.0.1"
     },
     "devDependencies": {
         "typescript": "4.9.5"

+ 16 - 1
packages/cli/src/cli.ts

@@ -2,14 +2,29 @@
 
 import { Command } from 'commander';
 
+import { registerAddCommand } from './commands/add/add';
 import { registerNewCommand } from './commands/new/new';
+import { Logger } from './utilities/logger';
 
 const program = new Command();
 
 // eslint-disable-next-line @typescript-eslint/no-var-requires
 const version = require('../package.json').version;
 
-program.version(version).description('The Vendure CLI');
+program
+    .version(version)
+    .description('The Vendure CLI')
+    .option(
+        '--log-level <logLevel>',
+        "Log level, either 'silent', 'info', or 'verbose'. Default: 'info'",
+        'info',
+    );
+
+const options = program.opts();
+if (options.logLevel) {
+    Logger.setLogLevel(options.logLevel);
+}
 registerNewCommand(program);
+registerAddCommand(program);
 
 void program.parseAsync(process.argv);

+ 29 - 0
packages/cli/src/commands/add/add.ts

@@ -0,0 +1,29 @@
+import { cancel, isCancel, select } from '@clack/prompts';
+import { Command } from 'commander';
+
+import { addUiExtensions } from './ui-extensions/add-ui-extensions';
+
+const cancelledMessage = 'Add feature cancelled.';
+
+export function registerAddCommand(program: Command) {
+    program
+        .command('add')
+        .description('Add a feature to your Vendure project')
+        .action(async () => {
+            const featureType = await select({
+                message: 'Which feature would you like to add?',
+                options: [
+                    { value: 'uiExtensions', label: 'Set up Admin UI extensions' },
+                    { value: 'other', label: 'Other' },
+                ],
+            });
+            if (isCancel(featureType)) {
+                cancel(cancelledMessage);
+                process.exit(0);
+            }
+            if (featureType === 'uiExtensions') {
+                await addUiExtensions();
+            }
+            process.exit(0);
+        });
+}

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

@@ -0,0 +1,109 @@
+import { note, outro, spinner, log } from '@clack/prompts';
+import path from 'path';
+import { ClassDeclaration } from 'ts-morph';
+import { Logger } from '../../../utilities/logger';
+import { determineVendureVersion, installRequiredPackages } from '../../../utilities/package-utils';
+
+import { Scaffolder } from '../../../utilities/scaffolder';
+import { getTsMorphProject, getVendureConfig, selectPluginClass } from '../../../utilities/utils';
+
+import { addUiExtensionStaticProp } from './codemods/add-ui-extension-static-prop/add-ui-extension-static-prop';
+import { updateAdminUiPluginInit } from './codemods/update-admin-ui-plugin-init/update-admin-ui-plugin-init';
+import { renderProviders } from './scaffold/providers';
+import { renderRoutes } from './scaffold/routes';
+
+export async function addUiExtensions() {
+    const projectSpinner = spinner();
+    projectSpinner.start('Analyzing project...');
+
+    await new Promise(resolve => setTimeout(resolve, 100));
+    const project = getTsMorphProject();
+    projectSpinner.stop('Project analyzed');
+
+    const pluginClass = await selectPluginClass(project, 'Add UI extensions cancelled');
+    if (pluginAlreadyHasUiExtensionProp(pluginClass)) {
+        outro('This plugin already has a UI extension configured');
+        return;
+    }
+    addUiExtensionStaticProp(pluginClass);
+
+    log.success('Updated the plugin class');
+    const installSpinner = spinner();
+    installSpinner.start(`Installing dependencies...`);
+    try {
+        const version = determineVendureVersion();
+        await installRequiredPackages([
+            {
+                pkg: '@vendure/ui-devkit',
+                isDevDependency: true,
+                version,
+            },
+        ]);
+    } catch (e: any) {
+        log.error(
+            `Failed to install dependencies: ${
+                e.message as string
+            }. Run with --log-level=verbose to see more details.`,
+        );
+        Logger.verbose(e.stack);
+    }
+    installSpinner.stop('Dependencies installed');
+
+    const scaffolder = new Scaffolder();
+    scaffolder.addFile(renderProviders, 'providers.ts');
+    scaffolder.addFile(renderRoutes, 'routes.ts');
+    log.success('Created UI extension scaffold');
+
+    const pluginDir = pluginClass.getSourceFile().getDirectory().getPath();
+    scaffolder.createScaffold({
+        dir: path.join(pluginDir, 'ui'),
+        context: {},
+    });
+    const vendureConfig = getVendureConfig(project);
+    if (!vendureConfig) {
+        log.warning(
+            `Could not find the VendureConfig declaration in your project. You will need to manually set up the compileUiExtensions function.`,
+        );
+    } else {
+        const pluginClassName = pluginClass.getName() as string;
+        const pluginPath = convertPathToRelativeImport(
+            path.relative(
+                vendureConfig.getSourceFile().getDirectory().getPath(),
+                pluginClass.getSourceFile().getFilePath(),
+            ),
+        );
+        const updated = updateAdminUiPluginInit(vendureConfig, { pluginClassName, pluginPath });
+        if (updated) {
+            log.success('Updated VendureConfig file');
+        } else {
+            log.warning(`Could not update \`AdminUiPlugin.init()\` options.`);
+            note(
+                `You will need to manually set up the compileUiExtensions function,\nadding ` +
+                    `the \`${pluginClassName}.ui\` object to the \`extensions\` array.`,
+                'Info',
+            );
+        }
+    }
+
+    project.saveSync();
+    outro('✅  Done!');
+}
+
+function pluginAlreadyHasUiExtensionProp(pluginClass: ClassDeclaration) {
+    const uiProperty = pluginClass.getProperty('ui');
+    if (!uiProperty) {
+        return false;
+    }
+    if (uiProperty.isStatic()) {
+        return true;
+    }
+}
+
+function convertPathToRelativeImport(filePath: string) {
+    // Normalize the path separators
+    const normalizedPath = filePath.replace(/\\/g, '/');
+
+    // Remove the file extension
+    const parsedPath = path.parse(normalizedPath);
+    return `./${parsedPath.dir}/${parsedPath.name}`;
+}

+ 25 - 0
packages/cli/src/commands/add/ui-extensions/codemods/add-ui-extension-static-prop/add-ui-extension-static-prop.spec.ts

@@ -0,0 +1,25 @@
+import fs from 'fs-extra';
+import path from 'path';
+import { Project, QuoteKind } from 'ts-morph';
+import { describe, expect, it } from 'vitest';
+import { defaultManipulationSettings } from '../../../../../constants';
+
+import { getPluginClasses } from '../../../../../utilities/utils';
+
+import { addUiExtensionStaticProp } from './add-ui-extension-static-prop';
+
+describe('addUiExtensionStaticProp', () => {
+    it('add ui prop and imports', () => {
+        const project = new Project({
+            manipulationSettings: defaultManipulationSettings,
+        });
+        project.addSourceFileAtPath(path.join(__dirname, 'fixtures', 'no-ui-prop.fixture.ts'));
+        const pluginClasses = getPluginClasses(project);
+        expect(pluginClasses.length).toBe(1);
+        addUiExtensionStaticProp(pluginClasses[0]);
+
+        const result = pluginClasses[0].getSourceFile().getText();
+        const expected = fs.readFileSync(path.join(__dirname, 'fixtures', 'no-ui-prop.expected'), 'utf-8');
+        expect(result).toBe(expected);
+    });
+});

+ 39 - 0
packages/cli/src/commands/add/ui-extensions/codemods/add-ui-extension-static-prop/add-ui-extension-static-prop.ts

@@ -0,0 +1,39 @@
+import { ClassDeclaration } from 'ts-morph';
+
+import { addImportsToFile, kebabize } from '../../../../../utilities/utils';
+
+/**
+ * @description
+ * Adds the static `ui` property to the plugin class, and adds the required imports.
+ */
+export function addUiExtensionStaticProp(pluginClass: ClassDeclaration) {
+    const adminUiExtensionType = 'AdminUiExtension';
+    const extensionId = kebabize(pluginClass.getName() as string).replace(/-plugin$/, '');
+    pluginClass
+        .addProperty({
+            name: 'ui',
+            isStatic: true,
+            type: adminUiExtensionType,
+            initializer: `{
+                        id: '${extensionId}-ui',
+                        extensionPath: path.join(__dirname, 'ui'),
+                        routes: [{ route: '${extensionId}', filePath: 'routes.ts' }],
+                        providers: ['providers.ts'],
+                    }`,
+        })
+        .formatText();
+
+    // Add the AdminUiExtension import if it doesn't already exist
+    addImportsToFile(pluginClass.getSourceFile(), {
+        moduleSpecifier: '@vendure/ui-devkit/compiler',
+        namedImports: [adminUiExtensionType],
+        order: 0,
+    });
+
+    // Add the path import if it doesn't already exist
+    addImportsToFile(pluginClass.getSourceFile(), {
+        moduleSpecifier: 'path',
+        namespaceImport: 'path',
+        order: 0,
+    });
+}

+ 24 - 0
packages/cli/src/commands/add/ui-extensions/codemods/add-ui-extension-static-prop/fixtures/no-ui-prop.expected

@@ -0,0 +1,24 @@
+import * as path from 'path';
+import { AdminUiExtension } from '@vendure/ui-devkit/compiler';
+import { PluginCommonModule, Type, VendurePlugin } from '@vendure/core';
+type PluginInitOptions = any;
+
+@VendurePlugin({
+    imports: [PluginCommonModule],
+    compatibility: '^2.0.0',
+})
+export class TestOnePlugin {
+    static options: PluginInitOptions;
+
+    static init(options: PluginInitOptions): Type<TestOnePlugin> {
+        this.options = options;
+        return TestOnePlugin;
+    }
+
+    static ui: AdminUiExtension = {
+        id: 'test-one-ui',
+        extensionPath: path.join(__dirname, 'ui'),
+        routes: [{ route: 'test-one', filePath: 'routes.ts' }],
+        providers: ['providers.ts'],
+    };
+}

+ 16 - 0
packages/cli/src/commands/add/ui-extensions/codemods/add-ui-extension-static-prop/fixtures/no-ui-prop.fixture.ts

@@ -0,0 +1,16 @@
+import { PluginCommonModule, Type, VendurePlugin } from '@vendure/core';
+
+type PluginInitOptions = any;
+
+@VendurePlugin({
+    imports: [PluginCommonModule],
+    compatibility: '^2.0.0',
+})
+export class TestOnePlugin {
+    static options: PluginInitOptions;
+
+    static init(options: PluginInitOptions): Type<TestOnePlugin> {
+        this.options = options;
+        return TestOnePlugin;
+    }
+}

+ 52 - 0
packages/cli/src/commands/add/ui-extensions/codemods/update-admin-ui-plugin-init/fixtures/existing-app-prop.expected

@@ -0,0 +1,52 @@
+import { AdminUiPlugin } from '@vendure/admin-ui-plugin';
+import { DefaultJobQueuePlugin, dummyPaymentHandler, VendureConfig } from '@vendure/core';
+import { compileUiExtensions } from '@vendure/ui-devkit/compiler';
+import path from 'path';
+import { TestPlugin } from './plugins/test-plugin/test.plugin';
+
+export const config: VendureConfig = {
+    apiOptions: {
+        port: 3000,
+        adminApiPath: 'admin-api',
+    },
+    authOptions: {
+        tokenMethod: ['bearer', 'cookie'],
+    },
+    dbConnectionOptions: {
+        synchronize: true,
+        type: 'mariadb',
+        host: '127.0.0.1',
+        port: 3306,
+        username: 'root',
+        password: '',
+        database: 'vendure-dev',
+    },
+    paymentOptions: {
+        paymentMethodHandlers: [dummyPaymentHandler],
+    },
+    // When adding or altering custom field definitions, the database will
+    // need to be updated. See the "Migrations" section in README.md.
+    customFields: {},
+    plugins: [
+        DefaultJobQueuePlugin.init({ useDatabaseForBuffer: true }),
+        AdminUiPlugin.init({
+            route: 'admin',
+            port: 3002,
+            adminUiConfig: {
+                apiPort: 3000,
+            },
+            app: compileUiExtensions({
+                outputPath: path.join(__dirname, '../admin-ui'),
+                extensions: [
+                    {
+                        id: 'existing-ui-plugin',
+                        extensionPath: path.join(__dirname, 'existing-ui-plugin'),
+                        providers: ['providers.ts'],
+                    },
+                    TestPlugin.ui,
+                ],
+                devMode: true,
+            }),
+        }),
+    ],
+};

+ 50 - 0
packages/cli/src/commands/add/ui-extensions/codemods/update-admin-ui-plugin-init/fixtures/existing-app-prop.fixture.ts

@@ -0,0 +1,50 @@
+import { AdminUiPlugin } from '@vendure/admin-ui-plugin';
+import { DefaultJobQueuePlugin, dummyPaymentHandler, VendureConfig } from '@vendure/core';
+import { compileUiExtensions } from '@vendure/ui-devkit/compiler';
+import path from 'path';
+
+export const config: VendureConfig = {
+    apiOptions: {
+        port: 3000,
+        adminApiPath: 'admin-api',
+    },
+    authOptions: {
+        tokenMethod: ['bearer', 'cookie'],
+    },
+    dbConnectionOptions: {
+        synchronize: true,
+        type: 'mariadb',
+        host: '127.0.0.1',
+        port: 3306,
+        username: 'root',
+        password: '',
+        database: 'vendure-dev',
+    },
+    paymentOptions: {
+        paymentMethodHandlers: [dummyPaymentHandler],
+    },
+    // When adding or altering custom field definitions, the database will
+    // need to be updated. See the "Migrations" section in README.md.
+    customFields: {},
+    plugins: [
+        DefaultJobQueuePlugin.init({ useDatabaseForBuffer: true }),
+        AdminUiPlugin.init({
+            route: 'admin',
+            port: 3002,
+            adminUiConfig: {
+                apiPort: 3000,
+            },
+            app: compileUiExtensions({
+                outputPath: path.join(__dirname, '../admin-ui'),
+                extensions: [
+                    {
+                        id: 'existing-ui-plugin',
+                        extensionPath: path.join(__dirname, 'existing-ui-plugin'),
+                        providers: ['providers.ts'],
+                    },
+                ],
+                devMode: true,
+            }),
+        }),
+    ],
+};

+ 47 - 0
packages/cli/src/commands/add/ui-extensions/codemods/update-admin-ui-plugin-init/fixtures/no-app-prop.expected

@@ -0,0 +1,47 @@
+import * as path from 'path';
+import { compileUiExtensions } from '@vendure/ui-devkit/compiler';
+import { AdminUiPlugin } from '@vendure/admin-ui-plugin';
+import { DefaultJobQueuePlugin, dummyPaymentHandler, VendureConfig } from '@vendure/core';
+import { TestPlugin } from './plugins/test-plugin/test.plugin';
+
+export const config: VendureConfig = {
+    apiOptions: {
+        port: 3000,
+        adminApiPath: 'admin-api',
+    },
+    authOptions: {
+        tokenMethod: ['bearer', 'cookie'],
+    },
+    dbConnectionOptions: {
+        synchronize: true,
+        type: 'mariadb',
+        host: '127.0.0.1',
+        port: 3306,
+        username: 'root',
+        password: '',
+        database: 'vendure-dev',
+    },
+    paymentOptions: {
+        paymentMethodHandlers: [dummyPaymentHandler],
+    },
+    // When adding or altering custom field definitions, the database will
+    // need to be updated. See the "Migrations" section in README.md.
+    customFields: {},
+    plugins: [
+        DefaultJobQueuePlugin.init({ useDatabaseForBuffer: true }),
+        AdminUiPlugin.init({
+            route: 'admin',
+            port: 3002,
+            adminUiConfig: {
+                apiPort: 3000,
+            },
+            app: compileUiExtensions({
+                outputPath: path.join(__dirname, '../admin-ui'),
+                extensions: [
+                    TestPlugin.ui,
+                ],
+                devMode: true,
+            }),
+        }),
+    ],
+};

+ 37 - 0
packages/cli/src/commands/add/ui-extensions/codemods/update-admin-ui-plugin-init/fixtures/no-app-prop.fixture.ts

@@ -0,0 +1,37 @@
+import { AdminUiPlugin } from '@vendure/admin-ui-plugin';
+import { DefaultJobQueuePlugin, dummyPaymentHandler, VendureConfig } from '@vendure/core';
+
+export const config: VendureConfig = {
+    apiOptions: {
+        port: 3000,
+        adminApiPath: 'admin-api',
+    },
+    authOptions: {
+        tokenMethod: ['bearer', 'cookie'],
+    },
+    dbConnectionOptions: {
+        synchronize: true,
+        type: 'mariadb',
+        host: '127.0.0.1',
+        port: 3306,
+        username: 'root',
+        password: '',
+        database: 'vendure-dev',
+    },
+    paymentOptions: {
+        paymentMethodHandlers: [dummyPaymentHandler],
+    },
+    // When adding or altering custom field definitions, the database will
+    // need to be updated. See the "Migrations" section in README.md.
+    customFields: {},
+    plugins: [
+        DefaultJobQueuePlugin.init({ useDatabaseForBuffer: true }),
+        AdminUiPlugin.init({
+            route: 'admin',
+            port: 3002,
+            adminUiConfig: {
+                apiPort: 3000,
+            },
+        }),
+    ],
+};

+ 46 - 0
packages/cli/src/commands/add/ui-extensions/codemods/update-admin-ui-plugin-init/update-admin-ui-plugin-init.spec.ts

@@ -0,0 +1,46 @@
+import fs from 'fs-extra';
+import path from 'path';
+import { Project, QuoteKind } from 'ts-morph';
+import { describe, expect, it } from 'vitest';
+import { defaultManipulationSettings } from '../../../../../constants';
+
+import { getVendureConfig } from '../../../../../utilities/utils';
+
+import { updateAdminUiPluginInit } from './update-admin-ui-plugin-init';
+
+describe('updateAdminUiPluginInit', () => {
+    it('adds app prop', () => {
+        const project = new Project({
+            manipulationSettings: defaultManipulationSettings,
+        });
+        project.addSourceFileAtPath(path.join(__dirname, 'fixtures', 'no-app-prop.fixture.ts'));
+        const vendureConfig = getVendureConfig(project, { checkFileName: false });
+        updateAdminUiPluginInit(vendureConfig, {
+            pluginClassName: 'TestPlugin',
+            pluginPath: './plugins/test-plugin/test.plugin',
+        });
+
+        const result = project.getSourceFiles()[0].getText();
+        const expected = fs.readFileSync(path.join(__dirname, 'fixtures', 'no-app-prop.expected'), 'utf-8');
+        expect(result).toBe(expected);
+    });
+
+    it('adds to existing ui extensions array', () => {
+        const project = new Project({
+            manipulationSettings: defaultManipulationSettings,
+        });
+        project.addSourceFileAtPath(path.join(__dirname, 'fixtures', 'existing-app-prop.fixture.ts'));
+        const vendureConfig = getVendureConfig(project, { checkFileName: false });
+        updateAdminUiPluginInit(vendureConfig, {
+            pluginClassName: 'TestPlugin',
+            pluginPath: './plugins/test-plugin/test.plugin',
+        });
+
+        const result = project.getSourceFiles()[0].getText();
+        const expected = fs.readFileSync(
+            path.join(__dirname, 'fixtures', 'existing-app-prop.expected'),
+            'utf-8',
+        );
+        expect(result).toBe(expected);
+    });
+});

+ 71 - 0
packages/cli/src/commands/add/ui-extensions/codemods/update-admin-ui-plugin-init/update-admin-ui-plugin-init.ts

@@ -0,0 +1,71 @@
+import { Node, ObjectLiteralExpression, StructureKind, SyntaxKind } from 'ts-morph';
+
+import { addImportsToFile } from '../../../../../utilities/utils';
+
+export function updateAdminUiPluginInit(
+    vendureConfig: ObjectLiteralExpression,
+    options: { pluginClassName: string; pluginPath: string },
+): boolean {
+    const plugins = vendureConfig
+        .getProperty('plugins')
+        ?.getFirstChildByKind(SyntaxKind.ArrayLiteralExpression)
+        ?.getFirstChildByKind(SyntaxKind.SyntaxList);
+
+    const adminUiPlugin = plugins?.getChildrenOfKind(SyntaxKind.CallExpression).find(c => {
+        return c.getExpression().getText() === 'AdminUiPlugin.init';
+    });
+    if (adminUiPlugin) {
+        const initObject = adminUiPlugin
+            .getArguments()
+            .find((a): a is ObjectLiteralExpression => a.isKind(SyntaxKind.ObjectLiteralExpression));
+        const appProperty = initObject?.getProperty('app');
+        if (!appProperty) {
+            initObject
+                ?.addProperty({
+                    name: 'app',
+                    kind: StructureKind.PropertyAssignment,
+                    initializer: `compileUiExtensions({
+                        outputPath: path.join(__dirname, '../admin-ui'),
+                        extensions: [
+                            ${options.pluginClassName}.ui,
+                        ],
+                        devMode: true,
+                    }),`,
+                })
+                .formatText();
+        } else {
+            const computeFnCall = appProperty.getFirstChildByKind(SyntaxKind.CallExpression);
+            if (computeFnCall?.getType().getText().includes('AdminUiAppConfig')) {
+                const arg = computeFnCall.getArguments()[0];
+                if (arg && Node.isObjectLiteralExpression(arg)) {
+                    const extensionsProp = arg.getProperty('extensions');
+                    if (extensionsProp) {
+                        extensionsProp
+                            .getFirstChildByKind(SyntaxKind.ArrayLiteralExpression)
+                            ?.addElement(`${options.pluginClassName}.ui`)
+                            .formatText();
+                    }
+                }
+            }
+        }
+
+        addImportsToFile(vendureConfig.getSourceFile(), {
+            moduleSpecifier: '@vendure/ui-devkit/compiler',
+            namedImports: ['compileUiExtensions'],
+            order: 0,
+        });
+
+        addImportsToFile(vendureConfig.getSourceFile(), {
+            moduleSpecifier: 'path',
+            namespaceImport: 'path',
+            order: 0,
+        });
+
+        addImportsToFile(vendureConfig.getSourceFile(), {
+            moduleSpecifier: options.pluginPath,
+            namedImports: [options.pluginClassName],
+        });
+        return true;
+    }
+    return false;
+}

+ 8 - 0
packages/cli/src/commands/add/ui-extensions/scaffold/providers.ts

@@ -0,0 +1,8 @@
+export function renderProviders() {
+    return /* language=TypeScript */ `
+
+export default [
+    // Add your providers here
+];
+    `;
+}

+ 8 - 0
packages/cli/src/commands/add/ui-extensions/scaffold/routes.ts

@@ -0,0 +1,8 @@
+export function renderRoutes() {
+    return /* language=TypeScript */ `
+
+export default [
+    // Add your custom routes here
+];
+`;
+}

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

@@ -1,8 +1,10 @@
-import { cancel, confirm, intro, isCancel, multiselect, outro, text } from '@clack/prompts';
+import { cancel, intro, isCancel, multiselect, outro, text } from '@clack/prompts';
 import { camelCase, constantCase, paramCase, pascalCase } from 'change-case';
 import * as fs from 'fs-extra';
 import path from 'path';
 
+import { Scaffolder } from '../../../utilities/scaffolder';
+
 import { renderAdminResolver, renderAdminResolverWithEntity } from './scaffold/api/admin.resolver';
 import { renderApiExtensions } from './scaffold/api/api-extensions';
 import { renderShopResolver, renderShopResolverWithEntity } from './scaffold/api/shop.resolver';
@@ -20,7 +22,7 @@ export async function newPlugin() {
     intro('Scaffolding a new Vendure plugin!');
     if (!options.name) {
         const name = await text({
-            message: 'What is the name of the bobby plugin?',
+            message: 'What is the name of the plugin?',
             initialValue: '',
             validate: input => {
                 if (!/^[a-z][a-z-0-9]+$/.test(input)) {
@@ -110,70 +112,33 @@ export function generatePlugin(options: GeneratePluginOptions) {
         },
     };
 
-    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 scaffolder = new Scaffolder<TemplateContext>();
+    scaffolder.addFile(renderPlugin, paramCase(nameWithoutPlugin) + '.plugin.ts');
+    scaffolder.addFile(renderTypes, 'types.ts');
+    scaffolder.addFile(renderConstants, 'constants.ts');
 
     if (options.withApiExtensions) {
-        files.push({
-            render: renderApiExtensions,
-            path: 'api/api-extensions.ts',
-        });
+        scaffolder.addFile(renderApiExtensions, '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',
-            });
+            scaffolder.addFile(renderShopResolverWithEntity, 'api/shop.resolver.ts');
+            scaffolder.addFile(renderAdminResolverWithEntity, 'api/admin.resolver.ts');
         } else {
-            files.push({
-                render: renderShopResolver,
-                path: 'api/shop.resolver.ts',
-            });
-            files.push({
-                render: renderAdminResolver,
-                path: 'api/admin.resolver.ts',
-            });
+            scaffolder.addFile(renderShopResolver, 'api/shop.resolver.ts');
+            scaffolder.addFile(renderAdminResolver, '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`,
-        });
+        scaffolder.addFile(renderEntity, `entities/${templateContext.entity.fileName}.ts`);
+        scaffolder.addFile(renderServiceWithEntity, `services/${templateContext.service.fileName}.ts`);
     } else {
-        files.push({
-            render: renderService,
-            path: `services/${templateContext.service.fileName}.ts`,
-        });
+        scaffolder.addFile(renderService, `services/${templateContext.service.fileName}.ts`);
     }
 
     const pluginDir = options.pluginDir;
-    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);
+    scaffolder.createScaffold({
+        dir: pluginDir,
+        context: templateContext,
     });
 
     outro('✅ Plugin scaffolding complete!');

+ 6 - 0
packages/cli/src/constants.ts

@@ -0,0 +1,6 @@
+import { ManipulationSettings, QuoteKind } from 'ts-morph';
+
+export const defaultManipulationSettings: Partial<ManipulationSettings> = {
+    quoteKind: QuoteKind.Single,
+    useTrailingCommas: true,
+};

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

@@ -0,0 +1,3 @@
+export interface CliOptions {
+    logLevel: 'silent' | 'info' | 'verbose';
+}

+ 23 - 0
packages/cli/src/utilities/logger.ts

@@ -0,0 +1,23 @@
+import pc from 'picocolors';
+
+export class Logger {
+    static logLevel: 'silent' | 'info' | 'verbose' = 'info';
+
+    static setLogLevel(level: 'silent' | 'info' | 'verbose') {
+        this.logLevel = level;
+    }
+
+    static info(message: string) {
+        if (this.logLevel === 'info' || this.logLevel === 'verbose') {
+            // eslint-disable-next-line no-console
+            console.log(pc.blue(message));
+        }
+    }
+
+    static verbose(message: string) {
+        if (this.logLevel === 'verbose') {
+            // eslint-disable-next-line no-console
+            console.log(pc.cyan(message));
+        }
+    }
+}

+ 102 - 0
packages/cli/src/utilities/package-utils.ts

@@ -0,0 +1,102 @@
+import { note } from '@clack/prompts';
+import spawn from 'cross-spawn';
+import fs from 'fs-extra';
+import path from 'path';
+
+import { Logger } from './logger';
+
+export interface PackageToInstall {
+    pkg: string;
+    version?: string;
+    isDevDependency?: boolean;
+}
+
+export function determineVendureVersion(): string | undefined {
+    const packageJson = getPackageJsonContent();
+    return packageJson.dependencies['@vendure/core'];
+}
+
+export async function installRequiredPackages(requiredPackages: PackageToInstall[]) {
+    const packageJson = getPackageJsonContent();
+    const packagesToInstall = requiredPackages.filter(({ pkg, version, isDevDependency }) => {
+        const hasDependency = isDevDependency
+            ? packageJson.devDependencies[pkg]
+            : packageJson.dependencies[pkg];
+        return !hasDependency;
+    });
+
+    const depsToInstall = packagesToInstall
+        .filter(p => !p.isDevDependency)
+        .map(p => `${p.pkg}${p.version ? `@${p.version}` : ''}`);
+    const devDepsToInstall = packagesToInstall
+        .filter(p => p.isDevDependency)
+        .map(p => `${p.pkg}${p.version ? `@${p.version}` : ''}`);
+    if (depsToInstall.length) {
+        await installPackages(depsToInstall, false);
+    }
+    if (devDepsToInstall.length) {
+        await installPackages(devDepsToInstall, true);
+    }
+}
+
+export async function installPackages(dependencies: string[], isDev: boolean) {
+    return new Promise<void>((resolve, reject) => {
+        const packageManager = determinePackageManagerBasedOnLockFile();
+        let command = '';
+        let args: string[] = [];
+        if (packageManager === 'yarn') {
+            command = 'yarnpkg';
+            args = ['add', '--exact', '--ignore-engines'];
+            if (isDev) {
+                args.push('--dev');
+            }
+
+            args = args.concat(dependencies);
+        } else {
+            command = 'npm';
+            args = ['install', '--save', '--save-exact', '--loglevel', 'error'].concat(dependencies);
+            if (isDev) {
+                args.push('--save-dev');
+            }
+        }
+        const child = spawn(command, args, { stdio: Logger.logLevel === 'verbose' ? 'inherit' : 'ignore' });
+        child.on('close', code => {
+            if (code !== 0) {
+                const message = 'An error occurred when installing dependencies.';
+                reject({
+                    message,
+                    command: `${command} ${args.join(' ')}`,
+                });
+                return;
+            }
+            resolve();
+        });
+    });
+}
+
+function determinePackageManagerBasedOnLockFile(): 'yarn' | 'npm' | 'pnpm' {
+    const yarnLockPath = path.join(process.cwd(), 'yarn.lock');
+    const npmLockPath = path.join(process.cwd(), 'package-lock.json');
+    const pnpmLockPath = path.join(process.cwd(), 'pnpm-lock.yaml');
+    if (fs.existsSync(yarnLockPath)) {
+        return 'yarn';
+    }
+    if (fs.existsSync(npmLockPath)) {
+        return 'npm';
+    }
+    if (fs.existsSync(pnpmLockPath)) {
+        return 'pnpm';
+    }
+    return 'npm';
+}
+
+function getPackageJsonContent() {
+    const packageJsonPath = path.join(process.cwd(), 'package.json');
+    if (!fs.existsSync(packageJsonPath)) {
+        note(
+            `Could not find a package.json in the current directory. Please run this command from the root of a Vendure project.`,
+        );
+        return false;
+    }
+    return fs.readJsonSync(packageJsonPath);
+}

+ 20 - 0
packages/cli/src/utilities/scaffolder.ts

@@ -0,0 +1,20 @@
+import * as fs from 'fs-extra';
+import path from 'path';
+
+export class Scaffolder<TemplateContext = Record<string, any>> {
+    private files: Array<{ render: (context: TemplateContext) => string; path: string }> = [];
+
+    addFile(render: (context: TemplateContext) => string, filePath: string) {
+        this.files.push({ render, path: filePath });
+    }
+
+    createScaffold(options: { dir: string; context: TemplateContext }) {
+        fs.ensureDirSync(options.dir);
+        this.files.forEach(file => {
+            const filePath = path.join(options.dir, file.path);
+            const rendered = file.render(options.context).trim();
+            fs.ensureFileSync(filePath);
+            fs.writeFileSync(filePath, rendered);
+        });
+    }
+}

+ 124 - 0
packages/cli/src/utilities/utils.ts

@@ -0,0 +1,124 @@
+import { cancel, isCancel, select } from '@clack/prompts';
+import fs from 'fs-extra';
+import path from 'node:path';
+import {
+    ClassDeclaration,
+    Node,
+    ObjectLiteralExpression,
+    Project,
+    QuoteKind,
+    SourceFile,
+    VariableDeclaration,
+} from 'ts-morph';
+import { defaultManipulationSettings } from '../constants';
+
+export async function selectPluginClass(project: Project, cancelledMessage: string) {
+    const pluginClasses = getPluginClasses(project);
+    const targetPlugin = await select({
+        message: 'To which plugin would you like to add the feature?',
+        options: pluginClasses.map(c => ({
+            value: c,
+            label: c.getName() as string,
+        })),
+    });
+    if (isCancel(targetPlugin)) {
+        cancel(cancelledMessage);
+        process.exit(0);
+    }
+    return targetPlugin as ClassDeclaration;
+}
+
+export function getTsMorphProject() {
+    const tsConfigPath = path.join(process.cwd(), 'tsconfig.json');
+    if (!fs.existsSync(tsConfigPath)) {
+        throw new Error('No tsconfig.json found in current directory');
+    }
+    return new Project({
+        tsConfigFilePath: tsConfigPath,
+        manipulationSettings: defaultManipulationSettings,
+        compilerOptions: {
+            skipLibCheck: true,
+        },
+    });
+}
+
+export function getPluginClasses(project: Project) {
+    const sourceFiles = project.getSourceFiles();
+
+    const pluginClasses = sourceFiles
+        .flatMap(sf => {
+            return sf.getClasses();
+        })
+        .filter(c => {
+            const hasPluginDecorator = c.getModifiers().find(m => {
+                return Node.isDecorator(m) && m.getName() === 'VendurePlugin';
+            });
+            return !!hasPluginDecorator;
+        });
+    return pluginClasses;
+}
+
+export function getVendureConfig(project: Project, options: { checkFileName?: boolean } = {}) {
+    const sourceFiles = project.getSourceFiles();
+    const checkFileName = options.checkFileName ?? true;
+    function isVendureConfigVariableDeclaration(v: VariableDeclaration) {
+        return v.getType().getText(v) === 'VendureConfig';
+    }
+    const vendureConfigFile = sourceFiles.find(sf => {
+        return (
+            (checkFileName ? sf.getFilePath().endsWith('vendure-config.ts') : true) &&
+            sf.getVariableDeclarations().find(isVendureConfigVariableDeclaration)
+        );
+    });
+    return vendureConfigFile
+        ?.getVariableDeclarations()
+        .find(isVendureConfigVariableDeclaration)
+        ?.getChildren()
+        .find(Node.isObjectLiteralExpression) as ObjectLiteralExpression;
+}
+
+export function addImportsToFile(
+    sourceFile: SourceFile,
+    options: { moduleSpecifier: string; namedImports?: string[]; namespaceImport?: string; order?: number },
+) {
+    const existingDeclaration = sourceFile.getImportDeclaration(
+        declaration => declaration.getModuleSpecifier().getLiteralValue() === options.moduleSpecifier,
+    );
+    if (!existingDeclaration) {
+        const importDeclaration = sourceFile.addImportDeclaration({
+            moduleSpecifier: options.moduleSpecifier,
+            ...(options.namespaceImport ? { namespaceImport: options.namespaceImport } : {}),
+            ...(options.namedImports ? { namedImports: options.namedImports } : {}),
+        });
+        if (options.order != null) {
+            importDeclaration.setOrder(options.order);
+        }
+    } else {
+        if (
+            options.namespaceImport &&
+            !existingDeclaration.getNamespaceImport() &&
+            !existingDeclaration.getDefaultImport()
+        ) {
+            existingDeclaration.setNamespaceImport(options.namespaceImport);
+        }
+        if (options.namedImports) {
+            const existingNamedImports = existingDeclaration.getNamedImports();
+            for (const namedImport of options.namedImports) {
+                if (!existingNamedImports.find(ni => ni.getName() === namedImport)) {
+                    existingDeclaration.addNamedImport(namedImport);
+                }
+            }
+        }
+    }
+}
+
+export function kebabize(str: string) {
+    return str
+        .split('')
+        .map((letter, idx) => {
+            return letter.toUpperCase() === letter
+                ? `${idx !== 0 ? '-' : ''}${letter.toLowerCase()}`
+                : letter;
+        })
+        .join('');
+}

+ 11 - 0
packages/cli/vitest.config.ts

@@ -0,0 +1,11 @@
+import swc from 'unplugin-swc';
+import { defineConfig } from 'vitest/config';
+
+export default defineConfig({
+    plugins: [
+        // SWC required to support decorators used in test plugins
+        // See https://github.com/vitest-dev/vitest/issues/708#issuecomment-1118628479
+        // Vite plugin
+        swc.vite(),
+    ],
+});

Разница между файлами не показана из-за своего большого размера
+ 643 - 622
packages/common/src/generated-types.ts


+ 69 - 13
yarn.lock

@@ -5316,6 +5316,16 @@
   resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
   integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==
 
+"@ts-morph/common@~0.22.0":
+  version "0.22.0"
+  resolved "https://registry.yarnpkg.com/@ts-morph/common/-/common-0.22.0.tgz#8951d451622a26472fbc3a227d6c3a90e687a683"
+  integrity sha512-HqNBuV/oIlMKdkLshXd1zKBqNQCsuPEsgQOkfFQ/eUKjRlwndXW1AjN9LVkBEIukm00gGXSRmfkl0Wv5VXLnlw==
+  dependencies:
+    fast-glob "^3.3.2"
+    minimatch "^9.0.3"
+    mkdirp "^3.0.1"
+    path-browserify "^1.0.1"
+
 "@tsconfig/node10@^1.0.7":
   version "1.0.9"
   resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2"
@@ -8112,6 +8122,11 @@ cmd-shim@6.0.1:
   resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-6.0.1.tgz#a65878080548e1dca760b3aea1e21ed05194da9d"
   integrity sha512-S9iI9y0nKR4hwEQsVWpyxld/6kRfGepGfzff83FcaiEBpmvlbA2nnGe7Cylgrx2f/p1P5S5wpRm9oL8z1PbS3Q==
 
+code-block-writer@^12.0.0:
+  version "12.0.0"
+  resolved "https://registry.yarnpkg.com/code-block-writer/-/code-block-writer-12.0.0.tgz#4dd58946eb4234105aff7f0035977b2afdc2a770"
+  integrity sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w==
+
 code-point-at@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
@@ -10200,7 +10215,7 @@ fast-glob@3.3.1:
     merge2 "^1.3.0"
     micromatch "^4.0.4"
 
-fast-glob@^3.2.11, fast-glob@^3.2.12, fast-glob@^3.2.9, fast-glob@^3.3.0:
+fast-glob@^3.2.11, fast-glob@^3.2.12, fast-glob@^3.2.9, fast-glob@^3.3.0, fast-glob@^3.3.2:
   version "3.3.2"
   resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129"
   integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==
@@ -11735,12 +11750,19 @@ ieee754@^1.1.13, ieee754@^1.1.4, ieee754@^1.2.1:
   resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
   integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
 
-ignore-walk@^3.0.1:
-  version "3.0.4"
-  resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.4.tgz#c9a09f69b7c7b479a5d74ac1a3c0d4236d2a6335"
-  integrity sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ==
+ignore-walk@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-5.0.1.tgz#5f199e23e1288f518d90358d461387788a154776"
+  integrity sha512-yemi4pMf51WKT7khInJqAvsIGzoqYXblnsz0ql8tM+yi1EKYTY1evX4NAbJrLL/Aanr2HyZeluqU+Oi7MGHokw==
   dependencies:
-    minimatch "^3.0.4"
+    minimatch "^5.0.1"
+
+ignore-walk@^6.0.0, ignore-walk@^6.0.4:
+  version "6.0.4"
+  resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-6.0.4.tgz#89950be94b4f522225eb63a13c56badb639190e9"
+  integrity sha512-t7sv42WkwFkyKbivUCglsQW5YWMskWtbEf4MNKX5u/CCWHKSPzN4FtBQGsQZgCLbxOzpVlcbWVK5KB3auIOjSw==
+  dependencies:
+    minimatch "^9.0.0"
 
 ignore@5.2.4:
   version "5.2.4"
@@ -14429,6 +14451,11 @@ mkdirp@^1.0.3, mkdirp@^1.0.4:
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
   integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
 
+mkdirp@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-3.0.1.tgz#e44e4c5607fb279c168241713cc6e0fea9adcb50"
+  integrity sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==
+
 mlly@^1.2.0, mlly@^1.4.0:
   version "1.5.0"
   resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.5.0.tgz#8428a4617d54cc083d3009030ac79739a0e5447a"
@@ -14924,7 +14951,7 @@ now-and-later@^2.0.0:
   dependencies:
     once "^1.3.2"
 
-npm-bundled@^1.0.1:
+npm-bundled@^1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.2.tgz#944c78789bd739035b70baa2ca5cc32b8d860bc1"
   integrity sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==
@@ -14984,13 +15011,29 @@ npm-package-arg@^11.0.0:
     semver "^7.3.5"
     validate-npm-package-name "^5.0.0"
 
-npm-packlist@1.1.12, npm-packlist@5.1.1, npm-packlist@^7.0.0, npm-packlist@^8.0.0:
-  version "1.1.12"
-  resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.12.tgz#22bde2ebc12e72ca482abd67afc51eb49377243a"
-  integrity sha512-WJKFOVMeAlsU/pjXuqVdzU0WfgtIBCupkEVwn+1Y0ERAbUfWw8R4GjgVbaKnUjRoD2FoQbHOCbOyT5Mbs9Lw4g==
+npm-packlist@5.1.1:
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-5.1.1.tgz#79bcaf22a26b6c30aa4dd66b976d69cc286800e0"
+  integrity sha512-UfpSvQ5YKwctmodvPPkK6Fwk603aoVsf8AEbmVKAEECrfvL8SSe1A2YIwrJ6xmTHAITKPwwZsWo7WwEbNk0kxw==
+  dependencies:
+    glob "^8.0.1"
+    ignore-walk "^5.0.1"
+    npm-bundled "^1.1.2"
+    npm-normalize-package-bin "^1.0.1"
+
+npm-packlist@^7.0.0:
+  version "7.0.4"
+  resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-7.0.4.tgz#033bf74110eb74daf2910dc75144411999c5ff32"
+  integrity sha512-d6RGEuRrNS5/N84iglPivjaJPxhDbZmlbTwTDX2IbcRHG5bZCdtysYMhwiPvcF4GisXHGn7xsxv+GQ7T/02M5Q==
   dependencies:
-    ignore-walk "^3.0.1"
-    npm-bundled "^1.0.1"
+    ignore-walk "^6.0.0"
+
+npm-packlist@^8.0.0:
+  version "8.0.2"
+  resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-8.0.2.tgz#5b8d1d906d96d21c85ebbeed2cf54147477c8478"
+  integrity sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==
+  dependencies:
+    ignore-walk "^6.0.4"
 
 npm-pick-manifest@8.0.1:
   version "8.0.1"
@@ -15787,6 +15830,11 @@ pascalcase@^0.1.1:
   resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14"
   integrity sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==
 
+path-browserify@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd"
+  integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==
+
 path-case@^3.0.4:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/path-case/-/path-case-3.0.4.tgz#9168645334eb942658375c56f80b4c0cb5f82c6f"
@@ -18763,6 +18811,14 @@ ts-log@^2.2.3:
   resolved "https://registry.yarnpkg.com/ts-log/-/ts-log-2.2.5.tgz#aef3252f1143d11047e2cb6f7cfaac7408d96623"
   integrity sha512-PGcnJoTBnVGy6yYNFxWVNkdcAuAMstvutN9MgDJIV6L0oG8fB+ZNNy1T+wJzah8RPGor1mZuPQkVfXNDpy9eHA==
 
+ts-morph@^21.0.1:
+  version "21.0.1"
+  resolved "https://registry.yarnpkg.com/ts-morph/-/ts-morph-21.0.1.tgz#712302a0f6e9dbf1aa8d9cf33a4386c4b18c2006"
+  integrity sha512-dbDtVdEAncKctzrVZ+Nr7kHpHkv+0JDJb2MjjpBaj8bFeCkePU9rHfMklmhuLFnpeq/EJZk2IhStY6NzqgjOkg==
+  dependencies:
+    "@ts-morph/common" "~0.22.0"
+    code-block-writer "^12.0.0"
+
 ts-node@^10.9.1:
   version "10.9.2"
   resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f"

Некоторые файлы не были показаны из-за большого количества измененных файлов