UNPKG

@spartacus/schematics

Version:
547 lines 27.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.runExternalSpartacusLibrary = exports.addSchematicsTasks = exports.createSpartacusFeatureOptionsForLibrary = exports.configureB2bFeatures = exports.dependencyExists = exports.addPackageJsonDependenciesForLibrary = exports.addPackageJsonDependencies = exports.installPackageJsonDependencies = exports.createNodePackageInstallationTask = exports.addLibraryStyles = exports.checkAppStructure = exports.addLibraryFeature = exports.getPackageBySubFeature = exports.prepareCliPackageAndSubFeature = exports.shouldAddFeature = exports.packageSubFeaturesMapping = void 0; const strings_1 = require("@angular-devkit/core/src/utils/strings"); const schematics_1 = require("@angular-devkit/schematics"); const tasks_1 = require("@angular-devkit/schematics/tasks"); const dependencies_1 = require("@schematics/angular/utility/dependencies"); const ts_morph_1 = require("ts-morph"); const constants_1 = require("../constants"); const config_utils_1 = require("./config-utils"); const import_utils_1 = require("./import-utils"); const new_module_utils_1 = require("./new-module-utils"); const package_utils_1 = require("./package-utils"); const program_1 = require("./program"); const project_tsconfig_paths_1 = require("./project-tsconfig-paths"); const workspace_utils_1 = require("./workspace-utils"); exports.packageSubFeaturesMapping = { [constants_1.SPARTACUS_ASM]: [constants_1.CLI_ASM_FEATURE], [constants_1.SPARTACUS_CART]: [ constants_1.CLI_CART_IMPORT_EXPORT_FEATURE, constants_1.CLI_CART_QUICK_ORDER_FEATURE, constants_1.CLI_CART_SAVED_CART_FEATURE, ], [constants_1.SPARTACUS_ORGANIZATION]: [ constants_1.CLI_ORGANIZATION_ADMINISTRATION_FEATURE, constants_1.CLI_ORGANIZATION_ORDER_APPROVAL_FEATURE, ], [constants_1.SPARTACUS_CDC]: [constants_1.CLI_CDC_FEATURE], [constants_1.SPARTACUS_CDS]: [constants_1.CLI_CDS_FEATURE], [constants_1.SPARTACUS_DIGITAL_PAYMENTS]: [constants_1.CLI_DIGITAL_PAYMENTS_FEATURE], [constants_1.SPARTACUS_EPD_VISUALIZATION]: [constants_1.CLI_EPD_VISUALIZATION_FEATURE], [constants_1.SPARTACUS_PRODUCT]: [ constants_1.CLI_PRODUCT_BULK_PRICING_FEATURE, constants_1.CLI_PRODUCT_VARIANTS_FEATURE, constants_1.CLI_PRODUCT_IMAGE_ZOOM_FEATURE, ], [constants_1.SPARTACUS_PRODUCT_CONFIGURATOR]: [ constants_1.CLI_PRODUCT_CONFIGURATOR_VC_FEATURE, constants_1.CLI_PRODUCT_CONFIGURATOR_TEXTFIELD_FEATURE, constants_1.CLI_PRODUCT_CONFIGURATOR_CPQ_FEATURE, ], [constants_1.SPARTACUS_QUALTRICS]: [constants_1.CLI_QUALTRICS_FEATURE], [constants_1.SPARTACUS_SMARTEDIT]: [constants_1.CLI_SMARTEDIT_FEATURE], [constants_1.SPARTACUS_STOREFINDER]: [constants_1.CLI_STOREFINDER_FEATURE], [constants_1.SPARTACUS_TRACKING]: [ constants_1.CLI_TRACKING_PERSONALIZATION_FEATURE, constants_1.CLI_TRACKING_TMS_GTM_FEATURE, constants_1.CLI_TRACKING_TMS_AEP_FEATURE, ], [constants_1.SPARTACUS_USER]: [constants_1.CLI_USER_ACCOUNT_FEATURE, constants_1.CLI_USER_PROFILE_FEATURE], [constants_1.SPARTACUS_CHECKOUT]: [constants_1.CLI_CHECKOUT_FEATURE], [constants_1.SPARTACUS_ORDER]: [constants_1.CLI_ORDER_FEATURE], }; function shouldAddFeature(feature, features = []) { return features.includes(feature); } exports.shouldAddFeature = shouldAddFeature; function prepareCliPackageAndSubFeature(features) { return features.reduce((cliFeatures, subFeature) => { var _a; const packageName = getPackageBySubFeature(subFeature); const subFeatures = [...((_a = cliFeatures[packageName]) !== null && _a !== void 0 ? _a : []), subFeature]; return Object.assign(Object.assign({}, cliFeatures), { [packageName]: subFeatures }); }, {}); } exports.prepareCliPackageAndSubFeature = prepareCliPackageAndSubFeature; function getPackageBySubFeature(subFeature) { for (const spartacusPackage in exports.packageSubFeaturesMapping) { if (!exports.packageSubFeaturesMapping.hasOwnProperty(spartacusPackage)) { continue; } const subFeatures = exports.packageSubFeaturesMapping[spartacusPackage]; if (subFeatures.includes(subFeature)) { return spartacusPackage; } } throw new schematics_1.SchematicsException(`The given '${subFeature}' doesn't contain a Spartacus package mapping. Please check 'packageSubFeaturesMapping' in 'projects/schematics/src/shared/utils/lib-utils.ts'`); } exports.getPackageBySubFeature = getPackageBySubFeature; function addLibraryFeature(options, config) { return (tree, context) => { 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 schematics_1.chain([ spartacusFeatureModuleExists ? schematics_1.noop() : workspace_utils_1.scaffoldStructure(options), handleFeature(options, config), config.styles ? addLibraryStyles(config.styles, options) : schematics_1.noop(), config.assets ? addLibraryAssets(config.assets, options) : schematics_1.noop(), config.dependencyManagement ? installRequiredSpartacusFeatures(config.dependencyManagement, options) : schematics_1.noop(), ]); }; } exports.addLibraryFeature = addLibraryFeature; function checkAppStructure(tree, project) { const { buildPaths } = project_tsconfig_paths_1.getProjectTsConfigPaths(tree, project); if (!buildPaths.length) { throw new schematics_1.SchematicsException(`Could not find any tsconfig file. Can't find ${constants_1.SPARTACUS_FEATURES_NG_MODULE}.`); } const basePath = process.cwd(); for (const tsconfigPath of buildPaths) { if (spartacusFeatureModuleExists(tree, tsconfigPath, basePath)) { return true; } } return false; } exports.checkAppStructure = checkAppStructure; function spartacusFeatureModuleExists(tree, tsconfigPath, basePath) { const { appSourceFiles } = program_1.createProgram(tree, basePath, tsconfigPath); for (const sourceFile of appSourceFiles) { if (sourceFile .getFilePath() .includes(`${constants_1.SPARTACUS_FEATURES_MODULE}.module.ts`)) { if (getSpartacusFeaturesModule(sourceFile)) { return true; } } } return false; } function getSpartacusFeaturesModule(sourceFile) { let spartacusFeaturesModule; function visitor(node) { if (ts_morph_1.Node.isCallExpression(node)) { const expression = node.getExpression(); if (ts_morph_1.Node.isIdentifier(expression) && expression.getText() === 'NgModule' && import_utils_1.isImportedFrom(expression, constants_1.ANGULAR_CORE)) { const classDeclaration = node.getFirstAncestorByKind(ts_morph_1.ts.SyntaxKind.ClassDeclaration); if (classDeclaration) { const identifier = classDeclaration.getNameNode(); if (identifier && identifier.getText() === constants_1.SPARTACUS_FEATURES_NG_MODULE) { spartacusFeaturesModule = node; } } } } node.forEachChild(visitor); } sourceFile.forEachChild(visitor); return spartacusFeaturesModule; } function handleFeature(options, config) { return (tree, _context) => { const { buildPaths } = project_tsconfig_paths_1.getProjectTsConfigPaths(tree, options.project); const basePath = process.cwd(); const rules = []; for (const tsconfigPath of buildPaths) { rules.push(new_module_utils_1.ensureModuleExists({ name: `${strings_1.dasherize(config.moduleName)}-feature`, path: `app/spartacus/features/${config.folderName}`, module: constants_1.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 schematics_1.chain(rules); }; } function addRootModule(tsconfigPath, basePath, config) { return (tree) => { if (!config.rootModule) { return tree; } const { appSourceFiles } = program_1.createProgram(tree, basePath, tsconfigPath); const moduleFileName = createModuleFileName(config); for (const sourceFile of appSourceFiles) { if (sourceFile.getFilePath().endsWith('/' + moduleFileName)) { new_module_utils_1.addModuleImport(sourceFile, { import: { moduleSpecifier: config.rootModule.importPath, namedImports: [config.rootModule.name], }, content: config.rootModule.content || config.rootModule.name, }); program_1.saveAndFormat(sourceFile); break; } } return tree; }; } function addFeatureModule(tsconfigPath, basePath, config, options) { return (tree) => { const { appSourceFiles } = program_1.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); } new_module_utils_1.addModuleProvider(sourceFile, { import: [ { moduleSpecifier: constants_1.SPARTACUS_CORE, namedImports: [constants_1.PROVIDE_CONFIG_FUNCTION, constants_1.CMS_CONFIG], }, ], content: `${constants_1.PROVIDE_CONFIG_FUNCTION}(<${constants_1.CMS_CONFIG}>{ featureModules: { ${lazyLoadingChunkName}: { module: () => import('${config.featureModule.importPath}').then((m) => m.${config.featureModule.name}), }, } })`, }); } else { new_module_utils_1.addModuleImport(sourceFile, { import: { moduleSpecifier: config.featureModule.importPath, namedImports: [config.featureModule.name], }, content: config.featureModule.content || config.featureModule.name, }); } program_1.saveAndFormat(sourceFile); break; } } return tree; }; } function addFeatureTranslations(tsconfigPath, basePath, config) { return (tree) => { const { appSourceFiles } = program_1.createProgram(tree, basePath, tsconfigPath); const moduleFileName = createModuleFileName(config); for (const sourceFile of appSourceFiles) { if (sourceFile.getFilePath().endsWith('/' + moduleFileName)) { if (config.i18n) { new_module_utils_1.addModuleProvider(sourceFile, { import: [ { moduleSpecifier: constants_1.SPARTACUS_CORE, namedImports: [constants_1.PROVIDE_CONFIG_FUNCTION, constants_1.I18N_CONFIG], }, { moduleSpecifier: config.i18n.importPath, namedImports: [config.i18n.chunks, config.i18n.resources], }, ], content: `${constants_1.PROVIDE_CONFIG_FUNCTION}(<${constants_1.I18N_CONFIG}>{ i18n: { resources: ${config.i18n.resources}, chunks: ${config.i18n.chunks}, }, })`, }); program_1.saveAndFormat(sourceFile); } break; } } return tree; }; } function addCustomConfig(tsconfigPath, basePath, config) { return (tree) => { const { appSourceFiles } = program_1.createProgram(tree, basePath, tsconfigPath); const moduleFileName = createModuleFileName(config); for (const sourceFile of appSourceFiles) { if (sourceFile.getFilePath().endsWith('/' + moduleFileName)) { if (config.customConfig) { const customConfigs = [].concat(config.customConfig); customConfigs.forEach((customConfig) => { new_module_utils_1.addModuleProvider(sourceFile, { import: [ { moduleSpecifier: constants_1.SPARTACUS_CORE, namedImports: [constants_1.PROVIDE_CONFIG_FUNCTION], }, ...customConfig.import, ], content: `${constants_1.PROVIDE_CONFIG_FUNCTION}(${customConfig.content})`, }); }); program_1.saveAndFormat(sourceFile); } break; } } return tree; }; } function addLibraryAssets(assetsConfig, options) { return (tree) => { var _a, _b, _c, _d; const { path, workspace: angularJson } = workspace_utils_1.getWorkspace(tree); const defaultProject = workspace_utils_1.getDefaultProjectNameFromWorkspace(tree); const project = options.project || defaultProject; const architect = angularJson.projects[project].architect; // `build` architect section const architectBuild = architect === null || architect === void 0 ? void 0 : architect.build; const buildAssets = createAssetsArray(assetsConfig, (_a = architectBuild === null || architectBuild === void 0 ? void 0 : architectBuild.options) === null || _a === void 0 ? void 0 : _a.assets); const buildOptions = Object.assign(Object.assign({}, architectBuild === null || architectBuild === void 0 ? void 0 : architectBuild.options), { assets: buildAssets }); // `test` architect section const architectTest = architect === null || architect === void 0 ? void 0 : architect.test; const testAssets = createAssetsArray(assetsConfig, (_b = architectTest === null || architectTest === void 0 ? void 0 : architectTest.options) === null || _b === void 0 ? void 0 : _b.assets); const testOptions = Object.assign(Object.assign({}, architectTest === null || architectTest === void 0 ? void 0 : architectTest.options), { assets: testAssets }); const updatedAngularJson = Object.assign(Object.assign({}, angularJson), { projects: Object.assign(Object.assign({}, angularJson.projects), { [project]: Object.assign(Object.assign({}, angularJson.projects[project]), { architect: Object.assign(Object.assign({}, architect), { build: Object.assign(Object.assign({}, architectBuild), { options: buildOptions }), test: Object.assign(Object.assign({}, architectTest), { options: testOptions }) }) }) }) }); const initialContent = (_d = (_c = tree.read(path)) === null || _c === void 0 ? void 0 : _c.toString(constants_1.UTF_8)) !== null && _d !== void 0 ? _d : ''; 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, angularJsonAssets = []) { 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; } function addLibraryStyles(stylingConfig, options) { return (tree, _context) => { var _a, _b, _c, _d, _e, _f; const defaultProject = workspace_utils_1.getDefaultProjectNameFromWorkspace(tree); const project = options.project || defaultProject; const libraryScssPath = `${workspace_utils_1.getSourceRoot(tree, { project: project, })}/styles/spartacus/${stylingConfig.scssFileName}`; const toAdd = `@import "${stylingConfig.importStyle}";`; if (tree.exists(libraryScssPath)) { const initialContent = (_b = (_a = tree.read(libraryScssPath)) === null || _a === void 0 ? void 0 : _a.toString(constants_1.UTF_8)) !== null && _b !== void 0 ? _b : ''; 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 } = workspace_utils_1.getWorkspace(tree); const architect = angularJson.projects[project].architect; // `build` architect section const architectBuild = architect === null || architect === void 0 ? void 0 : architect.build; const buildOptions = Object.assign(Object.assign({}, architectBuild === null || architectBuild === void 0 ? void 0 : architectBuild.options), { styles: [ ...(((_c = architectBuild === null || architectBuild === void 0 ? void 0 : architectBuild.options) === null || _c === void 0 ? void 0 : _c.styles) ? (_d = architectBuild === null || architectBuild === void 0 ? void 0 : architectBuild.options) === null || _d === void 0 ? void 0 : _d.styles : []), libraryScssPath, ] }); // `test` architect section const architectTest = architect === null || architect === void 0 ? void 0 : architect.test; const testOptions = Object.assign(Object.assign({}, architectTest === null || architectTest === void 0 ? void 0 : architectTest.options), { styles: [ ...(((_e = architectTest === null || architectTest === void 0 ? void 0 : architectTest.options) === null || _e === void 0 ? void 0 : _e.styles) ? (_f = architectTest === null || architectTest === void 0 ? void 0 : architectTest.options) === null || _f === void 0 ? void 0 : _f.styles : []), libraryScssPath, ] }); const updatedAngularJson = Object.assign(Object.assign({}, angularJson), { projects: Object.assign(Object.assign({}, angularJson.projects), { [project]: Object.assign(Object.assign({}, angularJson.projects[project]), { architect: Object.assign(Object.assign({}, architect), { build: Object.assign(Object.assign({}, architectBuild), { options: buildOptions }), test: Object.assign(Object.assign({}, architectTest), { options: testOptions }) }) }) }) }); tree.overwrite(path, JSON.stringify(updatedAngularJson, null, 2)); }; } exports.addLibraryStyles = addLibraryStyles; function createNodePackageInstallationTask(context) { return context.addTask(new tasks_1.NodePackageInstallTask()); } exports.createNodePackageInstallationTask = createNodePackageInstallationTask; function installPackageJsonDependencies() { return (tree, context) => { createNodePackageInstallationTask(context); return tree; }; } exports.installPackageJsonDependencies = installPackageJsonDependencies; function addPackageJsonDependencies(dependencies, packageJson) { return (tree, context) => { for (const dependency of dependencies) { if (!dependencyExists(dependency, packageJson)) { dependencies_1.addPackageJsonDependency(tree, dependency); context.logger.info(`✅️ Added '${dependency.name}' into ${dependency.type}`); } } return tree; }; } exports.addPackageJsonDependencies = addPackageJsonDependencies; function addPackageJsonDependenciesForLibrary(dependencies, options) { return (tree, context) => { const packageJson = package_utils_1.readPackageJson(tree); const spartacusLibraries = package_utils_1.createSpartacusDependencies(dependencies); const thirdPartyLibraries = package_utils_1.createDependencies(dependencies); const libraries = spartacusLibraries.concat(thirdPartyLibraries); const cliFeatures = spartacusLibraries .map((dependency) => dependency.name) .reduce((previous, current) => { return Object.assign(Object.assign({}, previous), { // just install the Spartacus library, without any sub-features [current]: [] }); }, {}); const featureOptions = createSpartacusFeatureOptionsForLibrary(options, cliFeatures, false); addSchematicsTasks(featureOptions, context); return schematics_1.chain([ addPackageJsonDependencies(libraries, packageJson), installPackageJsonDependencies(), ]); }; } exports.addPackageJsonDependenciesForLibrary = addPackageJsonDependenciesForLibrary; function installRequiredSpartacusFeatures(dependencyManagement, options) { return (_tree, context) => { if (!dependencyManagement) { return; } logFeatureInstallation(dependencyManagement, context); const featureOptions = createSpartacusFeatureOptionsForLibrary(options, dependencyManagement.featureDependencies); addSchematicsTasks(featureOptions, context); }; } function logFeatureInstallation(dependencyManagement, context) { 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}`); } } function dependencyExists(dependency, packageJson) { var _a; return (_a = packageJson[dependency.type]) === null || _a === void 0 ? void 0 : _a.hasOwnProperty(dependency.name); } exports.dependencyExists = dependencyExists; function configureB2bFeatures(options, packageJson) { return (_tree, _context) => { const spartacusVersion = package_utils_1.getPrefixedSpartacusSchematicsVersion(); return schematics_1.chain([ addB2bProviders(options), addPackageJsonDependencies([ { type: dependencies_1.NodeDependencyType.Default, version: spartacusVersion, name: constants_1.SPARTACUS_SETUP, }, ], packageJson), ]); }; } exports.configureB2bFeatures = configureB2bFeatures; function addB2bProviders(options) { return (tree, _context) => { const { buildPaths } = project_tsconfig_paths_1.getProjectTsConfigPaths(tree, options.project); if (!buildPaths.length) { throw new schematics_1.SchematicsException('Could not find any tsconfig file. Cannot configure SpartacusConfigurationModule.'); } const basePath = process.cwd(); for (const tsconfigPath of buildPaths) { const { appSourceFiles } = program_1.createProgram(tree, basePath, tsconfigPath); for (const sourceFile of appSourceFiles) { if (sourceFile .getFilePath() .includes(`${constants_1.SPARTACUS_CONFIGURATION_MODULE}.module.ts`)) { config_utils_1.getB2bConfiguration().forEach((provider) => new_module_utils_1.addModuleProvider(sourceFile, provider)); program_1.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 */ function createSpartacusFeatureOptionsForLibrary(options, cliFeatures, interactive = true) { return Object.keys(cliFeatures).map((spartacusLibrary) => { var _a; return ({ feature: spartacusLibrary, options: Object.assign(Object.assign({}, options), { // an empty array means that no library features will be installed. features: (_a = cliFeatures[spartacusLibrary]) !== null && _a !== void 0 ? _a : [], interactive }), }); }); } exports.createSpartacusFeatureOptionsForLibrary = createSpartacusFeatureOptionsForLibrary; function addSchematicsTasks(featureOptions, context) { const installationTaskId = createNodePackageInstallationTask(context); featureOptions.forEach((featureOption) => { const runSchematicTaskOptions = { collection: featureOption.feature, name: 'add', options: featureOption.options, }; context.addTask(new tasks_1.RunSchematicTask('add-spartacus-library', runSchematicTaskOptions), [installationTaskId]); }); } exports.addSchematicsTasks = addSchematicsTasks; function runExternalSpartacusLibrary(taskOptions) { return (tree, context) => { if (!taskOptions.collection) { throw new schematics_1.SchematicsException(`Can't run the Spartacus library schematic, please specify the 'collection' argument.`); } const executionOptions = { interactive: taskOptions.options.interactive, }; return schematics_1.chain([ schematics_1.externalSchematic(taskOptions.collection, taskOptions.name, taskOptions.options, executionOptions), ])(tree, context); }; } exports.runExternalSpartacusLibrary = runExternalSpartacusLibrary; function createModuleFileName(config) { return `${strings_1.dasherize(config.moduleName)}-feature.module.ts`; } //# sourceMappingURL=lib-utils.js.map