File

projects/schematics/src/shared/utils/lib-utils.ts

Index

Properties

Properties

content
content: string
Type : string
Optional
importPath
importPath: string
Type : string
name
name: string
Type : string
import { dasherize } from '@angular-devkit/core/src/utils/strings';
import {
  chain,
  ExecutionOptions,
  externalSchematic,
  noop,
  Rule,
  SchematicContext,
  SchematicsException,
  TaskId,
  Tree,
} from '@angular-devkit/schematics';
import {
  NodePackageInstallTask,
  RunSchematicTask,
} from '@angular-devkit/schematics/tasks';
import { RunSchematicTaskOptions } from '@angular-devkit/schematics/tasks/run-schematic/options';
import {
  addPackageJsonDependency,
  NodeDependency,
  NodeDependencyType,
} from '@schematics/angular/utility/dependencies';
import { CallExpression, Node, SourceFile, ts as tsMorph } from 'ts-morph';
import {
  ANGULAR_CORE,
  CLI_ASM_FEATURE,
  CLI_CART_IMPORT_EXPORT_FEATURE,
  CLI_CART_QUICK_ORDER_FEATURE,
  CLI_CART_SAVED_CART_FEATURE,
  CLI_CDC_FEATURE,
  CLI_CDS_FEATURE,
  CLI_CHECKOUT_FEATURE,
  CLI_DIGITAL_PAYMENTS_FEATURE,
  CLI_ORDER_FEATURE,
  CLI_ORGANIZATION_ADMINISTRATION_FEATURE,
  CLI_ORGANIZATION_ORDER_APPROVAL_FEATURE,
  CLI_PRODUCT_BULK_PRICING_FEATURE,
  CLI_PRODUCT_CONFIGURATOR_CPQ_FEATURE,
  CLI_PRODUCT_CONFIGURATOR_TEXTFIELD_FEATURE,
  CLI_PRODUCT_CONFIGURATOR_VC_FEATURE,
  CLI_PRODUCT_IMAGE_ZOOM_FEATURE,
  CLI_PRODUCT_VARIANTS_FEATURE,
  CLI_QUALTRICS_FEATURE,
  CLI_SMARTEDIT_FEATURE,
  CLI_STOREFINDER_FEATURE,
  CLI_TRACKING_PERSONALIZATION_FEATURE,
  CLI_TRACKING_TMS_AEP_FEATURE,
  CLI_TRACKING_TMS_GTM_FEATURE,
  CLI_USER_ACCOUNT_FEATURE,
  CLI_USER_PROFILE_FEATURE,
  CLI_EPD_VISUALIZATION_FEATURE,
  CMS_CONFIG,
  I18N_CONFIG,
  PROVIDE_CONFIG_FUNCTION,
  SPARTACUS_ASM,
  SPARTACUS_CART,
  SPARTACUS_CDC,
  SPARTACUS_CDS,
  SPARTACUS_EPD_VISUALIZATION,
  SPARTACUS_CHECKOUT,
  SPARTACUS_CONFIGURATION_MODULE,
  SPARTACUS_CORE,
  SPARTACUS_DIGITAL_PAYMENTS,
  SPARTACUS_FEATURES_MODULE,
  SPARTACUS_FEATURES_NG_MODULE,
  SPARTACUS_ORDER,
  SPARTACUS_ORGANIZATION,
  SPARTACUS_PRODUCT,
  SPARTACUS_PRODUCT_CONFIGURATOR,
  SPARTACUS_QUALTRICS,
  SPARTACUS_SETUP,
  SPARTACUS_SMARTEDIT,
  SPARTACUS_STOREFINDER,
  SPARTACUS_TRACKING,
  SPARTACUS_USER,
  UTF_8,
} from '../constants';
import { getB2bConfiguration } from './config-utils';
import { isImportedFrom } from './import-utils';
import {
  addModuleImport,
  addModuleProvider,
  ensureModuleExists,
  Import,
} from './new-module-utils';
import {
  createDependencies,
  createSpartacusDependencies,
  getPrefixedSpartacusSchematicsVersion,
  readPackageJson,
} from './package-utils';
import { createProgram, saveAndFormat } from './program';
import { getProjectTsConfigPaths } from './project-tsconfig-paths';
import {
  getDefaultProjectNameFromWorkspace,
  getSourceRoot,
  getWorkspace,
  scaffoldStructure,
} from './workspace-utils';

export interface LibraryOptions extends Partial<ExecutionOptions> {
  project: string;
  lazy?: boolean;
  features?: string[];
  // meta, when programmatically installing other Spartacus libraries as dependencies
  options?: LibraryOptions;
}

export interface FeatureConfig {
  /**
   * The folder in which we will generate the feature module. E.g. app/spartacus/features/__organization__ (__NOTE__: just the `organization` part should be provided.).
   */
  folderName: string;
  /**
   * Used as the generated feature module's file name.
   * Also, used as the lazy loading's feature name if the `lazyLoadingChunk` config is not provided.
   */
  moduleName: string;
  /**
   * The feature module configuration.
   */
  featureModule: Module;
  /**
   * The root module configuration.
   */
  rootModule?: Module;
  /**
   * The lazy loading chunk's name. It's usually a constant imported from a library.
   */
  lazyLoadingChunk?: Import;
  /**
   * Translation chunk configuration
   */
  i18n?: I18NConfig;
  /**
   * Styling configuration
   */
  styles?: StylingConfig;
  /**
   * Assets configuration
   */
  assets?: AssetsConfig;
  /**
   * An optional custom configuration to provide to the generated module.
   */
  customConfig?: CustomConfig | CustomConfig[];
  /**
   * Dependency management for the library
   */
  dependencyManagement?: DependencyManagement;
}

/**
 * Dependency management for the library
 */
export interface DependencyManagement {
  /**
   * The name of the feature that's currently being installed.
   */
  featureName: string;
  /**
   * Contains the feature dependencies.
   * The key is a Spartacus scope, while the value is an array of its features.
   */
  featureDependencies: Record<string, string[]>;
}

export interface CustomConfig {
  import: Import[];
  content: string;
}

export interface Module {
  name: string;
  importPath: string;
  content?: string;
}

export interface I18NConfig {
  resources: string;
  chunks: string;
  importPath: string;
}

export interface StylingConfig {
  scssFileName: string;
  importStyle: string;
}

export interface AssetsConfig {
  input: string;
  output?: string;
  glob: string;
}

export const packageSubFeaturesMapping: Record<string, string[]> = {
  [SPARTACUS_ASM]: [CLI_ASM_FEATURE],
  [SPARTACUS_CART]: [
    CLI_CART_IMPORT_EXPORT_FEATURE,
    CLI_CART_QUICK_ORDER_FEATURE,
    CLI_CART_SAVED_CART_FEATURE,
  ],
  [SPARTACUS_ORGANIZATION]: [
    CLI_ORGANIZATION_ADMINISTRATION_FEATURE,
    CLI_ORGANIZATION_ORDER_APPROVAL_FEATURE,
  ],
  [SPARTACUS_CDC]: [CLI_CDC_FEATURE],
  [SPARTACUS_CDS]: [CLI_CDS_FEATURE],
  [SPARTACUS_DIGITAL_PAYMENTS]: [CLI_DIGITAL_PAYMENTS_FEATURE],
  [SPARTACUS_EPD_VISUALIZATION]: [CLI_EPD_VISUALIZATION_FEATURE],
  [SPARTACUS_PRODUCT]: [
    CLI_PRODUCT_BULK_PRICING_FEATURE,
    CLI_PRODUCT_VARIANTS_FEATURE,
    CLI_PRODUCT_IMAGE_ZOOM_FEATURE,
  ],
  [SPARTACUS_PRODUCT_CONFIGURATOR]: [
    CLI_PRODUCT_CONFIGURATOR_VC_FEATURE,
    CLI_PRODUCT_CONFIGURATOR_TEXTFIELD_FEATURE,
    CLI_PRODUCT_CONFIGURATOR_CPQ_FEATURE,
  ],
  [SPARTACUS_QUALTRICS]: [CLI_QUALTRICS_FEATURE],
  [SPARTACUS_SMARTEDIT]: [CLI_SMARTEDIT_FEATURE],
  [SPARTACUS_STOREFINDER]: [CLI_STOREFINDER_FEATURE],
  [SPARTACUS_TRACKING]: [
    CLI_TRACKING_PERSONALIZATION_FEATURE,
    CLI_TRACKING_TMS_GTM_FEATURE,
    CLI_TRACKING_TMS_AEP_FEATURE,
  ],
  [SPARTACUS_USER]: [CLI_USER_ACCOUNT_FEATURE, CLI_USER_PROFILE_FEATURE],
  [SPARTACUS_CHECKOUT]: [CLI_CHECKOUT_FEATURE],
  [SPARTACUS_ORDER]: [CLI_ORDER_FEATURE],
};

export function shouldAddFeature(
  feature: string,
  features: string[] = []
): boolean {
  return features.includes(feature);
}

export function prepareCliPackageAndSubFeature(
  features: string[]
): Record<string, string[]> {
  return features.reduce((cliFeatures, subFeature) => {
    const packageName = getPackageBySubFeature(subFeature);
    const subFeatures = [...(cliFeatures[packageName] ?? []), subFeature];

    return { ...cliFeatures, [packageName]: subFeatures };
  }, {} as Record<string, string[]>);
}

export function getPackageBySubFeature(subFeature: string): string {
  for (const spartacusPackage in packageSubFeaturesMapping) {
    if (!packageSubFeaturesMapping.hasOwnProperty(spartacusPackage)) {
      continue;
    }

    const subFeatures = packageSubFeaturesMapping[spartacusPackage];
    if (subFeatures.includes(subFeature)) {
      return spartacusPackage;
    }
  }

  throw new SchematicsException(
    `The given '${subFeature}' doesn't contain a Spartacus package mapping.
Please check 'packageSubFeaturesMapping' in 'projects/schematics/src/shared/utils/lib-utils.ts'`
  );
}

export function addLibraryFeature<T extends LibraryOptions>(
  options: T,
  config: FeatureConfig
): Rule {
  return (tree: Tree, context: SchematicContext) => {
    const spartacusFeatureModuleExists = checkAppStructure(
      tree,
      options.project
    );
    if (!spartacusFeatureModuleExists) {
      context.logger.info('Scaffolding the new app structure...');
      context.logger.warn(
        'Please migrate manually the rest of your feature modules to the new app structure: https://sap.github.io/spartacus-docs/reference-app-structure/'
      );
    }
    return chain([
      spartacusFeatureModuleExists ? noop() : scaffoldStructure(options),

      handleFeature(options, config),
      config.styles ? addLibraryStyles(config.styles, options) : noop(),
      config.assets ? addLibraryAssets(config.assets, options) : noop(),
      config.dependencyManagement
        ? installRequiredSpartacusFeatures(config.dependencyManagement, options)
        : noop(),
    ]);
  };
}

export function checkAppStructure(tree: Tree, project: string): boolean {
  const { buildPaths } = getProjectTsConfigPaths(tree, project);

  if (!buildPaths.length) {
    throw new SchematicsException(
      `Could not find any tsconfig file. Can't find ${SPARTACUS_FEATURES_NG_MODULE}.`
    );
  }

  const basePath = process.cwd();
  for (const tsconfigPath of buildPaths) {
    if (spartacusFeatureModuleExists(tree, tsconfigPath, basePath)) {
      return true;
    }
  }
  return false;
}

function spartacusFeatureModuleExists(
  tree: Tree,
  tsconfigPath: string,
  basePath: string
): boolean {
  const { appSourceFiles } = createProgram(tree, basePath, tsconfigPath);

  for (const sourceFile of appSourceFiles) {
    if (
      sourceFile
        .getFilePath()
        .includes(`${SPARTACUS_FEATURES_MODULE}.module.ts`)
    ) {
      if (getSpartacusFeaturesModule(sourceFile)) {
        return true;
      }
    }
  }
  return false;
}

function getSpartacusFeaturesModule(
  sourceFile: SourceFile
): CallExpression | undefined {
  let spartacusFeaturesModule;

  function visitor(node: Node) {
    if (Node.isCallExpression(node)) {
      const expression = node.getExpression();
      if (
        Node.isIdentifier(expression) &&
        expression.getText() === 'NgModule' &&
        isImportedFrom(expression, ANGULAR_CORE)
      ) {
        const classDeclaration = node.getFirstAncestorByKind(
          tsMorph.SyntaxKind.ClassDeclaration
        );
        if (classDeclaration) {
          const identifier = classDeclaration.getNameNode();
          if (
            identifier &&
            identifier.getText() === SPARTACUS_FEATURES_NG_MODULE
          ) {
            spartacusFeaturesModule = node;
          }
        }
      }
    }

    node.forEachChild(visitor);
  }

  sourceFile.forEachChild(visitor);
  return spartacusFeaturesModule;
}

function handleFeature<T extends LibraryOptions>(
  options: T,
  config: FeatureConfig
): Rule {
  return (tree: Tree, _context: SchematicContext) => {
    const { buildPaths } = getProjectTsConfigPaths(tree, options.project);

    const basePath = process.cwd();
    const rules: Rule[] = [];
    for (const tsconfigPath of buildPaths) {
      rules.push(
        ensureModuleExists({
          name: `${dasherize(config.moduleName)}-feature`,
          path: `app/spartacus/features/${config.folderName}`,
          module: SPARTACUS_FEATURES_MODULE,
          project: options.project,
        })
      );
      rules.push(addRootModule(tsconfigPath, basePath, config));
      rules.push(addFeatureModule(tsconfigPath, basePath, config, options));
      rules.push(addFeatureTranslations(tsconfigPath, basePath, config));
      rules.push(addCustomConfig(tsconfigPath, basePath, config));
    }
    return chain(rules);
  };
}

function addRootModule(
  tsconfigPath: string,
  basePath: string,
  config: FeatureConfig
): Rule {
  return (tree: Tree): Tree => {
    if (!config.rootModule) {
      return tree;
    }

    const { appSourceFiles } = createProgram(tree, basePath, tsconfigPath);
    const moduleFileName = createModuleFileName(config);
    for (const sourceFile of appSourceFiles) {
      if (sourceFile.getFilePath().endsWith('/' + moduleFileName)) {
        addModuleImport(sourceFile, {
          import: {
            moduleSpecifier: config.rootModule.importPath,
            namedImports: [config.rootModule.name],
          },
          content: config.rootModule.content || config.rootModule.name,
        });
        saveAndFormat(sourceFile);
        break;
      }
    }
    return tree;
  };
}

function addFeatureModule(
  tsconfigPath: string,
  basePath: string,
  config: FeatureConfig,
  options: LibraryOptions
): Rule {
  return (tree: Tree): Tree => {
    const { appSourceFiles } = createProgram(tree, basePath, tsconfigPath);
    const moduleFileName = createModuleFileName(config);
    for (const sourceFile of appSourceFiles) {
      if (sourceFile.getFilePath().endsWith('/' + moduleFileName)) {
        if (options.lazy) {
          let lazyLoadingChunkName = config.moduleName;
          if (config.lazyLoadingChunk) {
            const content = config.lazyLoadingChunk.namedImports[0];
            lazyLoadingChunkName = `[${content}]`;
            sourceFile.addImportDeclaration(config.lazyLoadingChunk);
          }

          addModuleProvider(sourceFile, {
            import: [
              {
                moduleSpecifier: SPARTACUS_CORE,
                namedImports: [PROVIDE_CONFIG_FUNCTION, CMS_CONFIG],
              },
            ],
            content: `${PROVIDE_CONFIG_FUNCTION}(<${CMS_CONFIG}>{
              featureModules: {
                ${lazyLoadingChunkName}: {
                  module: () =>
                    import('${config.featureModule.importPath}').then((m) => m.${config.featureModule.name}),
                },
              }
            })`,
          });
        } else {
          addModuleImport(sourceFile, {
            import: {
              moduleSpecifier: config.featureModule.importPath,
              namedImports: [config.featureModule.name],
            },
            content: config.featureModule.content || config.featureModule.name,
          });
        }
        saveAndFormat(sourceFile);
        break;
      }
    }
    return tree;
  };
}

function addFeatureTranslations(
  tsconfigPath: string,
  basePath: string,
  config: FeatureConfig
): Rule {
  return (tree: Tree): Tree => {
    const { appSourceFiles } = createProgram(tree, basePath, tsconfigPath);
    const moduleFileName = createModuleFileName(config);
    for (const sourceFile of appSourceFiles) {
      if (sourceFile.getFilePath().endsWith('/' + moduleFileName)) {
        if (config.i18n) {
          addModuleProvider(sourceFile, {
            import: [
              {
                moduleSpecifier: SPARTACUS_CORE,
                namedImports: [PROVIDE_CONFIG_FUNCTION, I18N_CONFIG],
              },
              {
                moduleSpecifier: config.i18n.importPath,
                namedImports: [config.i18n.chunks, config.i18n.resources],
              },
            ],
            content: `${PROVIDE_CONFIG_FUNCTION}(<${I18N_CONFIG}>{
              i18n: {
                resources: ${config.i18n.resources},
                chunks: ${config.i18n.chunks},
              },
            })`,
          });
          saveAndFormat(sourceFile);
        }
        break;
      }
    }
    return tree;
  };
}

function addCustomConfig(
  tsconfigPath: string,
  basePath: string,
  config: FeatureConfig
): Rule {
  return (tree: Tree): Tree => {
    const { appSourceFiles } = createProgram(tree, basePath, tsconfigPath);
    const moduleFileName = createModuleFileName(config);
    for (const sourceFile of appSourceFiles) {
      if (sourceFile.getFilePath().endsWith('/' + moduleFileName)) {
        if (config.customConfig) {
          const customConfigs = ([] as CustomConfig[]).concat(
            config.customConfig
          );
          customConfigs.forEach((customConfig) => {
            addModuleProvider(sourceFile, {
              import: [
                {
                  moduleSpecifier: SPARTACUS_CORE,
                  namedImports: [PROVIDE_CONFIG_FUNCTION],
                },
                ...customConfig.import,
              ],
              content: `${PROVIDE_CONFIG_FUNCTION}(${customConfig.content})`,
            });
          });
          saveAndFormat(sourceFile);
        }
        break;
      }
    }
    return tree;
  };
}

function addLibraryAssets(
  assetsConfig: AssetsConfig,
  options: LibraryOptions
): Rule {
  return (tree: Tree) => {
    const { path, workspace: angularJson } = getWorkspace(tree);
    const defaultProject = getDefaultProjectNameFromWorkspace(tree);
    const project = options.project || defaultProject;
    const architect = angularJson.projects[project].architect;

    // `build` architect section
    const architectBuild = architect?.build;
    const buildAssets = createAssetsArray(
      assetsConfig,
      (architectBuild?.options as any)?.assets
    );
    const buildOptions = {
      ...architectBuild?.options,
      assets: buildAssets,
    };

    // `test` architect section
    const architectTest = architect?.test;
    const testAssets = createAssetsArray(
      assetsConfig,
      (architectTest?.options as any)?.assets
    );
    const testOptions = {
      ...architectTest?.options,
      assets: testAssets,
    };

    const updatedAngularJson = {
      ...angularJson,
      projects: {
        ...angularJson.projects,
        [project]: {
          ...angularJson.projects[project],
          architect: {
            ...architect,
            build: {
              ...architectBuild,
              options: buildOptions,
            },
            test: {
              ...architectTest,
              options: testOptions,
            },
          },
        },
      },
    };

    const initialContent = tree.read(path)?.toString(UTF_8) ?? '';
    const toUpdate = JSON.stringify(updatedAngularJson, null, 2);
    // prevent the unnecessary Angular logs about the files being updated
    if (initialContent !== toUpdate) {
      tree.overwrite(path, toUpdate);
    }
  };
}

function createAssetsArray(
  assetsConfig: AssetsConfig,
  angularJsonAssets: any[] = []
): unknown[] {
  for (const asset of angularJsonAssets) {
    if (typeof asset === 'object') {
      if (
        asset.glob === assetsConfig.glob &&
        asset.input === `./node_modules/@spartacus/${assetsConfig.input}` &&
        asset.output === (assetsConfig.output || 'assets/')
      ) {
        return angularJsonAssets;
      }
    }
  }

  angularJsonAssets = [
    ...angularJsonAssets,
    {
      glob: assetsConfig.glob,
      input: `./node_modules/@spartacus/${assetsConfig.input}`,
      output: assetsConfig.output || 'assets/',
    },
  ];

  return angularJsonAssets;
}

export function addLibraryStyles(
  stylingConfig: StylingConfig,
  options: LibraryOptions
): Rule {
  return (tree: Tree, _context: SchematicContext) => {
    const defaultProject = getDefaultProjectNameFromWorkspace(tree);
    const project = options.project || defaultProject;
    const libraryScssPath = `${getSourceRoot(tree, {
      project: project,
    })}/styles/spartacus/${stylingConfig.scssFileName}`;
    const toAdd = `@import "${stylingConfig.importStyle}";`;

    if (tree.exists(libraryScssPath)) {
      const initialContent = tree.read(libraryScssPath)?.toString(UTF_8) ?? '';
      let content = initialContent;

      if (!content.includes(toAdd)) {
        content += `\n${toAdd}`;
      }

      // prevent the unnecessary Angular logs about the files being updated
      if (initialContent !== content) {
        tree.overwrite(libraryScssPath, content);
      }
      return tree;
    }

    tree.create(libraryScssPath, toAdd);

    const { path, workspace: angularJson } = getWorkspace(tree);
    const architect = angularJson.projects[project].architect;

    // `build` architect section
    const architectBuild = architect?.build;
    const buildOptions = {
      ...architectBuild?.options,
      styles: [
        ...((architectBuild?.options as any)?.styles
          ? (architectBuild?.options as any)?.styles
          : []),
        libraryScssPath,
      ],
    };

    // `test` architect section
    const architectTest = architect?.test;
    const testOptions = {
      ...architectTest?.options,
      styles: [
        ...(architectTest?.options?.styles
          ? architectTest?.options?.styles
          : []),
        libraryScssPath,
      ],
    };

    const updatedAngularJson = {
      ...angularJson,
      projects: {
        ...angularJson.projects,
        [project]: {
          ...angularJson.projects[project],
          architect: {
            ...architect,
            build: {
              ...architectBuild,
              options: buildOptions,
            },
            test: {
              ...architectTest,
              options: testOptions,
            },
          },
        },
      },
    };
    tree.overwrite(path, JSON.stringify(updatedAngularJson, null, 2));
  };
}

export function createNodePackageInstallationTask(
  context: SchematicContext
): TaskId {
  return context.addTask(new NodePackageInstallTask());
}

export function installPackageJsonDependencies(): Rule {
  return (tree: Tree, context: SchematicContext) => {
    createNodePackageInstallationTask(context);
    return tree;
  };
}

export function addPackageJsonDependencies(
  dependencies: NodeDependency[],
  packageJson: any
): Rule {
  return (tree: Tree, context: SchematicContext): Tree => {
    for (const dependency of dependencies) {
      if (!dependencyExists(dependency, packageJson)) {
        addPackageJsonDependency(tree, dependency);
        context.logger.info(
          `✅️ Added '${dependency.name}' into ${dependency.type}`
        );
      }
    }
    return tree;
  };
}

export function addPackageJsonDependenciesForLibrary<
  OPTIONS extends LibraryOptions
>(dependencies: Record<string, string>, options: OPTIONS): Rule {
  return (tree: Tree, context: SchematicContext): Rule => {
    const packageJson = readPackageJson(tree);
    const spartacusLibraries = createSpartacusDependencies(dependencies);
    const thirdPartyLibraries = createDependencies(dependencies);
    const libraries = spartacusLibraries.concat(thirdPartyLibraries);

    const cliFeatures = spartacusLibraries
      .map((dependency) => dependency.name)
      .reduce((previous, current) => {
        return {
          ...previous,
          // just install the Spartacus library, without any sub-features
          [current]: [],
        };
      }, {} as Record<string, string[]>);
    const featureOptions = createSpartacusFeatureOptionsForLibrary(
      options,
      cliFeatures,
      false
    );
    addSchematicsTasks(featureOptions, context);

    return chain([
      addPackageJsonDependencies(libraries, packageJson),
      installPackageJsonDependencies(),
    ]);
  };
}

function installRequiredSpartacusFeatures<OPTIONS extends LibraryOptions>(
  dependencyManagement: DependencyManagement,
  options: OPTIONS
): Rule {
  return (_tree: Tree, context: SchematicContext): void => {
    if (!dependencyManagement) {
      return;
    }

    logFeatureInstallation(dependencyManagement, context);
    const featureOptions = createSpartacusFeatureOptionsForLibrary(
      options,
      dependencyManagement.featureDependencies
    );
    addSchematicsTasks(featureOptions, context);
  };
}

function logFeatureInstallation(
  dependencyManagement: DependencyManagement,
  context: SchematicContext
): void {
  const cliFeatures = dependencyManagement.featureDependencies;
  for (const spartacusScope in cliFeatures) {
    if (!cliFeatures.hasOwnProperty(spartacusScope)) {
      continue;
    }

    const requiredFeatures = cliFeatures[spartacusScope].join(',');
    context.logger.info(
      `⚙️  ${dependencyManagement.featureName} requires the following features from ${spartacusScope}: ${requiredFeatures}`
    );
  }
}

export function dependencyExists(
  dependency: NodeDependency,
  packageJson: any
): boolean {
  return packageJson[dependency.type]?.hasOwnProperty(dependency.name);
}

export function configureB2bFeatures<T extends LibraryOptions>(
  options: T,
  packageJson: any
): Rule {
  return (_tree: Tree, _context: SchematicContext): Rule => {
    const spartacusVersion = getPrefixedSpartacusSchematicsVersion();
    return chain([
      addB2bProviders(options),
      addPackageJsonDependencies(
        [
          {
            type: NodeDependencyType.Default,
            version: spartacusVersion,
            name: SPARTACUS_SETUP,
          },
        ],
        packageJson
      ),
    ]);
  };
}

function addB2bProviders<T extends LibraryOptions>(options: T): Rule {
  return (tree: Tree, _context: SchematicContext): Tree => {
    const { buildPaths } = getProjectTsConfigPaths(tree, options.project);
    if (!buildPaths.length) {
      throw new SchematicsException(
        'Could not find any tsconfig file. Cannot configure SpartacusConfigurationModule.'
      );
    }

    const basePath = process.cwd();
    for (const tsconfigPath of buildPaths) {
      const { appSourceFiles } = createProgram(tree, basePath, tsconfigPath);

      for (const sourceFile of appSourceFiles) {
        if (
          sourceFile
            .getFilePath()
            .includes(`${SPARTACUS_CONFIGURATION_MODULE}.module.ts`)
        ) {
          getB2bConfiguration().forEach((provider) =>
            addModuleProvider(sourceFile, provider)
          );
          saveAndFormat(sourceFile);

          break;
        }
      }
    }

    return tree;
  };
}

/**
 * A helper method that creates the default options for the given Spartacus' libraries.
 *
 * All `features` options will be set to an empty array, meaning that no features should be installed.
 *
 * @param spartacusLibraries
 * @param options
 * @returns
 */
export function createSpartacusFeatureOptionsForLibrary<
  OPTIONS extends LibraryOptions
>(
  options: OPTIONS,
  cliFeatures: Record<string, string[]>,
  interactive = true
): {
  feature: string;
  options: LibraryOptions;
}[] {
  return Object.keys(cliFeatures).map((spartacusLibrary) => ({
    feature: spartacusLibrary,
    options: {
      ...options,
      // an empty array means that no library features will be installed.
      features: cliFeatures[spartacusLibrary] ?? [],
      interactive,
    },
  }));
}

export function addSchematicsTasks(
  featureOptions: {
    feature: string;
    options: LibraryOptions;
  }[],
  context: SchematicContext
): void {
  const installationTaskId = createNodePackageInstallationTask(context);

  featureOptions.forEach((featureOption) => {
    const runSchematicTaskOptions: RunSchematicTaskOptions<LibraryOptions> = {
      collection: featureOption.feature,
      name: 'add',
      options: featureOption.options,
    };

    context.addTask(
      new RunSchematicTask('add-spartacus-library', runSchematicTaskOptions),
      [installationTaskId]
    );
  });
}

export function runExternalSpartacusLibrary(
  taskOptions: RunSchematicTaskOptions<LibraryOptions>
): Rule {
  return (tree: Tree, context: SchematicContext) => {
    if (!taskOptions.collection) {
      throw new SchematicsException(
        `Can't run the Spartacus library schematic, please specify the 'collection' argument.`
      );
    }

    const executionOptions: Partial<ExecutionOptions> = {
      interactive: taskOptions.options.interactive,
    };

    return chain([
      externalSchematic(
        taskOptions.collection,
        taskOptions.name,
        taskOptions.options,
        executionOptions
      ),
    ])(tree, context);
  };
}

function createModuleFileName(config: FeatureConfig): string {
  return `${dasherize(config.moduleName)}-feature.module.ts`;
}

result-matching ""

    No results matching ""