import { strings } from '@angular-devkit/core'; import { Rule, apply, applyTemplates, chain, filter, mergeWith, move, url } from '@angular-devkit/schematics'; import { createProjectSchematic } from '@schematics/angular/utility/project'; import { buildDefaultPath } from '@schematics/angular/utility/workspace'; import { parseName } from '@schematics/angular/utility/parse-name'; import { validateClassName, validateHtmlSelector } from '@schematics/angular/utility/validation'; export interface CompositionSchematicOptions { name: string; project: string; path?: string; kind?: string; } const knownKinds = new Set([ 'command-list', 'footer-navigation', 'header-navbar', 'hero', 'section-alert', 'section-carousel', 'section-card', 'section-faq', 'section-form', 'section-list-group', 'section-pricing', 'section-progress', 'section-stats', 'section-table', 'section-tabs', ]); interface SupportingComponent { templatePath: string; variable: string; } function buildSupportName(name: string, variable: string): string { const variableName = strings.dasherize(variable); return name === variableName || name.endsWith(`-${variableName}`) ? `${name}-ui` : `${name}-${variableName}`; } function getSupportingComponent(kind: string): SupportingComponent | null { switch (kind) { case 'section-card': case 'section-pricing': return { templatePath: '../card/files', variable: 'card' }; case 'section-faq': return { templatePath: '../accordion/files', variable: 'accordion' }; case 'section-table': return { templatePath: '../table/files', variable: 'table' }; case 'header-navbar': return { templatePath: '../navbar/files', variable: 'navbar' }; case 'section-form': return { templatePath: '../form/files', variable: 'form' }; case 'section-tabs': return { templatePath: '../tabs/files', variable: 'tabs' }; case 'section-list-group': return { templatePath: '../list-group/files', variable: 'listGroup' }; case 'section-alert': return { templatePath: '../alert/files', variable: 'alert' }; case 'section-progress': return { templatePath: '../progress-bar/files', variable: 'progressBar' }; case 'section-carousel': return { templatePath: '../carousel/files', variable: 'carousel' }; default: return null; } } export function createCompositionSchematic(defaultKind: string): (options: CompositionSchematicOptions) => Rule { return createProjectSchematic((options, { project }) => { options.path ??= buildDefaultPath(project); const parsedPath = parseName(options.path, options.name); const name = parsedPath.name; const kind = knownKinds.has(name) && (options.kind === undefined || options.kind === defaultKind) ? name : options.kind ?? defaultKind; const selector = `${project.prefix ? `${project.prefix}-` : ''}${strings.dasherize(name)}`; const supportingComponent = getSupportingComponent(kind); const supportName = supportingComponent ? buildSupportName(name, supportingComponent.variable) : ''; const supportSelector = supportName ? `${project.prefix ? `${project.prefix}-` : ''}${strings.dasherize(supportName)}` : ''; const usesCardActions = kind === 'section-pricing'; validateHtmlSelector(selector); validateClassName(strings.classify(name)); if (supportingComponent) { validateHtmlSelector(supportSelector); validateClassName(strings.classify(supportName)); } const templateSource = apply(url('./files'), [ applyTemplates({ ...strings, name, selector, kind, supportName, supportSelector, supportVariable: supportingComponent?.variable ?? '', usesSupportingComponent: Boolean(supportingComponent), usesCardActions, }), move(parsedPath.path), ]); const rules: Rule[] = [mergeWith(templateSource)]; if (supportingComponent) { const supportSource = apply(url(supportingComponent.templatePath), [ filter((filePath) => !filePath.endsWith('.spec.ts.template') && !filePath.endsWith('.e2e-spec.ts.template')), applyTemplates({ ...strings, name: supportName, selector: supportSelector }), move(`${parsedPath.path}/${strings.dasherize(name)}`), ]); rules.push(mergeWith(supportSource)); } return chain(rules); }); }