From d07a3789771cdcdf758af3836c4de89c5fe5d1e7 Mon Sep 17 00:00:00 2001 From: Danilo Hoffmann Date: Fri, 15 May 2020 13:39:37 +0200 Subject: [PATCH] feat(schematics): add lazy option to page schematic --- schematics/src/page/factory.ts | 79 ++-- schematics/src/page/factory_spec.ts | 337 ++++++++++-------- .../__name@dasherize__-page.module.__tsext__ | 6 +- schematics/src/page/schema.json | 9 + schematics/src/utils/common.ts | 13 +- 5 files changed, 266 insertions(+), 178 deletions(-) diff --git a/schematics/src/page/factory.ts b/schematics/src/page/factory.ts index 8131ecc50b..83da28fc33 100644 --- a/schematics/src/page/factory.ts +++ b/schematics/src/page/factory.ts @@ -18,35 +18,47 @@ import * as ts from 'typescript'; import { applyNameAndPath, detectExtension, determineArtifactName } from '../utils/common'; import { readIntoSourceFile } from '../utils/filesystem'; import { applyLintFix } from '../utils/lint-fix'; +import { addImportToFile } from '../utils/registration'; import { PWAPageOptionsSchema as Options } from './schema'; function addRouteToArray( - options: { name?: string; routingModule?: string; child?: string }, + options: { name?: string; routingModule?: string; child?: string; lazy?: boolean }, host: Tree, position: number, insertComma: boolean ) { const dasherizedName = strings.dasherize(options.name); + const path = options.child ? options.child : dasherizedName.replace(/-/, '/'); + if (options.lazy) { + const loadChildren = `() => import('${ + options.child ? '..' : '.' + }/${dasherizedName}/${dasherizedName}-page.module').then(m => m.${strings.classify(dasherizedName)}PageModule)`; - const loadChildren = `() => import('${ - options.child ? '..' : '.' - }/${dasherizedName}/${dasherizedName}-page.module').then(m => m.${strings.classify(dasherizedName)}PageModule)`; - const path = options.child ? options.child : dasherizedName; - - const recorder = host.beginUpdate(options.routingModule); - recorder.insertRight(position, `${insertComma ? ', ' : ''}{ path: '${path}', loadChildren: ${loadChildren} }`); - host.commitUpdate(recorder); + const recorder = host.beginUpdate(options.routingModule); + recorder.insertRight(position, `${insertComma ? ', ' : ''}{ path: '${path}', loadChildren: ${loadChildren} }`); + host.commitUpdate(recorder); + } else { + const recorder = host.beginUpdate(options.routingModule); + recorder.insertRight( + position, + `${insertComma ? ', ' : ''}{ path: '${path}', component: ${strings.classify(options.name)}PageComponent }` + ); + host.commitUpdate(recorder); + } } -function determineRoutingModule(host: Tree, options: { name?: string; project?: string; extension?: string }) { +function determineRoutingModule( + host: Tree, + options: { name?: string; project?: string; extension?: string; lazy?: boolean } +) { const project = getProject(host, options.project); let routingModuleLocation: string; let child: string; const match = options.name.match(/(.*)\-([a-z0-9]+)/); - if (match && match[1] && match[2]) { + if (options.lazy && match && match[1] && match[2]) { const parent = match[1]; child = match[2]; // tslint:disable-next-line:no-console @@ -68,10 +80,14 @@ function determineRoutingModule(host: Tree, options: { name?: string; project?: }; } -export function addRouteToRoutingModule(options: { extension?: string; project?: string; name?: string }): Rule { +export function addRouteToRoutingModule(options: { + extension?: string; + project?: string; + name?: string; + routingModule?: string; +}): Rule { return host => { - const newOptions = determineRoutingModule(host, options); - const source = readIntoSourceFile(host, newOptions.routingModule); + const source = readIntoSourceFile(host, options.routingModule); forEachToken(source, node => { if (node.kind === ts.SyntaxKind.Identifier && /^[a-zA-Z0-9]*(R|r)outes$/.test(node.getText())) { const parent = node.parent; @@ -82,9 +98,9 @@ export function addRouteToRoutingModule(options: { extension?: string; project?: if (routes.getChildCount() > 0) { const lastChild = routes.getChildren()[routes.getChildCount() - 1]; const insertComma = lastChild.kind !== ts.SyntaxKind.CommaToken; - addRouteToArray(newOptions, host, lastChild.end, insertComma); + addRouteToArray(options, host, lastChild.end, insertComma); } else { - addRouteToArray(newOptions, host, routes.end, false); + addRouteToArray(options, host, routes.end, false); } } } @@ -102,19 +118,26 @@ export function createPage(options: Options): Rule { options = detectExtension('page', host, options); options = applyNameAndPath('page', host, options); options = determineArtifactName('page', host, options); + options = determineRoutingModule(host, options); const operations: Rule[] = []; - operations.push( - mergeWith( - apply(url('./files'), [ - template({ - ...strings, - ...options, - }), - move(options.path), - ]) - ) - ); + + if (options.lazy) { + operations.push( + mergeWith( + apply(url('./files'), [ + template({ + ...strings, + ...options, + }), + move(options.path), + ]) + ) + ); + } else { + operations.push(addImportToFile({ ...options, module: options.routingModule })); + } + operations.push( schematic('component', { ...options, @@ -123,8 +146,8 @@ export function createPage(options: Options): Rule { flat: true, }) ); - operations.push(addRouteToRoutingModule(options)); + operations.push(addRouteToRoutingModule(options)); operations.push(applyLintFix()); return chain(operations); diff --git a/schematics/src/page/factory_spec.ts b/schematics/src/page/factory_spec.ts index 13e717f482..34ede5a0ed 100644 --- a/schematics/src/page/factory_spec.ts +++ b/schematics/src/page/factory_spec.ts @@ -6,10 +6,6 @@ import { PWAPageOptionsSchema as Options } from './schema'; describe('Page Schematic', () => { const schematicRunner = createSchematicRunner(); - const defaultOptions: Options = { - name: 'foo', - project: 'bar', - }; let appTree: UnitTestTree; beforeEach(async () => { @@ -63,147 +59,200 @@ describe('Page Schematic', () => { ); }); - it('should create a page in root by default', async () => { - const options = { ...defaultOptions }; - - const tree = await schematicRunner.runSchematicAsync('page', options, appTree).toPromise(); - const files = tree.files.filter(x => x.search('foo-page') >= 0); - expect(files).toMatchInlineSnapshot(` - Array [ - "/src/app/pages/foo/foo-page.module.ts", - "/src/app/pages/foo/foo-page.component.ts", - "/src/app/pages/foo/foo-page.component.html", - "/src/app/pages/foo/foo-page.component.spec.ts", - ] - `); - expect(tree.readContent('/src/app/pages/foo/foo-page.module.ts')).toMatchInlineSnapshot(` - "import { NgModule } from '@angular/core'; - import { RouterModule, Routes } from '@angular/router'; - - import { SharedModule } from 'ish-shared/shared.module'; - import { FooPageComponent } from './foo-page.component'; - - const fooPageRoutes: Routes = [{ path: '', component: FooPageComponent }]; - - @NgModule({ - imports: [RouterModule.forChild(fooPageRoutes), SharedModule], - declarations: [FooPageComponent], - }) - export class FooPageModule { } - " - `); - expect(tree.readContent('/src/app/pages/foo/foo-page.component.html')).toMatchInlineSnapshot(` - "

- foo-page works! -

- " - `); - }); - - it('should create a correct test for the component', async () => { - const options = { ...defaultOptions }; - - const tree = await schematicRunner.runSchematicAsync('page', options, appTree).toPromise(); - const componentSpecContent = tree.readContent('/src/app/pages/foo/foo-page.component.spec.ts'); - expect(componentSpecContent).toContain(`import { FooPageComponent } from './foo-page.component'`); - }); - - it('should register route in app routing module by default', async () => { - const options = { ...defaultOptions }; - - const tree = await schematicRunner.runSchematicAsync('page', options, appTree).toPromise(); - const appRoutingModule = tree.readContent('/src/app/pages/app-routing.module.ts'); - expect(appRoutingModule).toContain(`path: 'foo'`); - expect(appRoutingModule).toContain('foo-page.module'); - expect(appRoutingModule).toContain('FooPageModule'); - }); - - it('should create a page in extension if supplied', async () => { - const options = { ...defaultOptions, extension: 'feature' }; - - const tree = await schematicRunner.runSchematicAsync('page', options, appTree).toPromise(); - const files = tree.files.filter(x => x.search('foo-page') >= 0); - expect(files).toMatchInlineSnapshot(` - Array [ - "/src/app/extensions/feature/pages/foo/foo-page.module.ts", - "/src/app/extensions/feature/pages/foo/foo-page.component.ts", - "/src/app/extensions/feature/pages/foo/foo-page.component.html", - "/src/app/extensions/feature/pages/foo/foo-page.component.spec.ts", - ] - `); - expect(tree.readContent('/src/app/extensions/feature/pages/foo/foo-page.module.ts')).toMatchInlineSnapshot(` - "import { NgModule } from '@angular/core'; - import { RouterModule, Routes } from '@angular/router'; - - import { FeatureModule } from '../../feature.module'; - import { FooPageComponent } from './foo-page.component'; - - const fooPageRoutes: Routes = [{ path: '', component: FooPageComponent }]; - - @NgModule({ - imports: [RouterModule.forChild(fooPageRoutes), FeatureModule], - declarations: [FooPageComponent], - }) - export class FooPageModule { } - " - `); - expect(tree.readContent('/src/app/extensions/feature/pages/foo/foo-page.component.html')).toMatchInlineSnapshot(` - "

- foo-page works! -

- " - `); - }); - - it('should create a page in extension if implied by name', async () => { - const options = { ...defaultOptions, name: 'extensions/feature/pages/foo' }; - - const tree = await schematicRunner.runSchematicAsync('page', options, appTree).toPromise(); - const files = tree.files.filter(x => x.search('foo-page') >= 0); - expect(files).toMatchInlineSnapshot(` - Array [ - "/src/app/extensions/feature/pages/foo/foo-page.module.ts", - "/src/app/extensions/feature/pages/foo/foo-page.component.ts", - "/src/app/extensions/feature/pages/foo/foo-page.component.html", - "/src/app/extensions/feature/pages/foo/foo-page.component.spec.ts", - ] - `); - }); - - it('should register route in feature routing module', async () => { - const options = { ...defaultOptions, extension: 'feature' }; - - const tree = await schematicRunner.runSchematicAsync('page', options, appTree).toPromise(); - const appRoutingModule = tree.readContent('/src/app/extensions/feature/pages/feature-routing.module.ts'); - expect(appRoutingModule).toContain(`path: 'foo'`); - expect(appRoutingModule).toContain('foo-page.module'); - expect(appRoutingModule).toContain('FooPageModule'); - }); - - it('should register route in feature routing module when it is the first', async () => { - const options = { ...defaultOptions, extension: 'feature2' }; - - const tree = await schematicRunner.runSchematicAsync('page', options, appTree).toPromise(); - const appRoutingModule = tree.readContent('/src/app/extensions/feature2/pages/feature2-routing.module.ts'); - expect(appRoutingModule).toContain(`path: 'foo'`); - expect(appRoutingModule).toContain('foo-page.module'); - expect(appRoutingModule).toContain('FooPageModule'); + describe('with lazy === true', () => { + const defaultOptions: Options = { + name: 'foo', + project: 'bar', + }; + it('should create a page in root by default', async () => { + const tree = await schematicRunner.runSchematicAsync('page', defaultOptions, appTree).toPromise(); + const files = tree.files.filter(x => x.search('foo-page') >= 0); + + expect(files).toIncludeAllMembers([ + '/src/app/pages/foo/foo-page.component.ts', + '/src/app/pages/foo/foo-page.component.html', + '/src/app/pages/foo/foo-page.component.spec.ts', + ]); + }); + + it('should create a page module with page component declaration', async () => { + const tree = await schematicRunner.runSchematicAsync('page', defaultOptions, appTree).toPromise(); + + expect(tree.exists('/src/app/pages/foo/foo-page.module.ts')).toBeTrue(); + const pageModule = tree.readContent('/src/app/pages/foo/foo-page.module.ts'); + expect(pageModule).toContain("import { FooPageComponent } from './foo-page.component';"); + expect(pageModule).toContain("{ path: '', component: FooPageComponent }"); + expect(pageModule).toContain('declarations: [FooPageComponent]'); + expect(pageModule).toContain('export class FooPageModule'); + expect(pageModule).toMatch(/imports: .*SharedModule/); + + expect(tree.readContent('/src/app/pages/foo/foo-page.component.html')).toContain('foo-page works!'); + }); + + it('should create a correct test for the component', async () => { + const tree = await schematicRunner.runSchematicAsync('page', defaultOptions, appTree).toPromise(); + const componentSpecContent = tree.readContent('/src/app/pages/foo/foo-page.component.spec.ts'); + expect(componentSpecContent).toContain(`import { FooPageComponent } from './foo-page.component'`); + }); + + it('should lazily register route in app routing module by default', async () => { + const tree = await schematicRunner.runSchematicAsync('page', defaultOptions, appTree).toPromise(); + const appRoutingModule = tree.readContent('/src/app/pages/app-routing.module.ts'); + expect(appRoutingModule).toContain(`path: 'foo'`); + expect(appRoutingModule).toContain('foo-page.module'); + expect(appRoutingModule).toContain('FooPageModule'); + }); + + it('should create a page in extension if supplied', async () => { + const options = { ...defaultOptions, extension: 'feature' }; + + const tree = await schematicRunner.runSchematicAsync('page', options, appTree).toPromise(); + const files = tree.files.filter(x => x.search('foo-page') >= 0); + expect(files).toIncludeAllMembers([ + '/src/app/extensions/feature/pages/foo/foo-page.module.ts', + '/src/app/extensions/feature/pages/foo/foo-page.component.ts', + '/src/app/extensions/feature/pages/foo/foo-page.component.html', + '/src/app/extensions/feature/pages/foo/foo-page.component.spec.ts', + ]); + + const pageModule = tree.readContent('/src/app/extensions/feature/pages/foo/foo-page.module.ts'); + expect(pageModule).toContain("import { FooPageComponent } from './foo-page.component';"); + expect(pageModule).toContain("{ path: '', component: FooPageComponent }"); + expect(pageModule).toContain('declarations: [FooPageComponent]'); + expect(pageModule).toContain('export class FooPageModule'); + expect(pageModule).toMatch(/imports: .*FeatureModule/); + + expect(tree.readContent('/src/app/extensions/feature/pages/foo/foo-page.component.html')).toContain( + 'foo-page works!' + ); + }); + + it('should create a page in extension if implied by name', async () => { + const options = { ...defaultOptions, name: 'extensions/feature/pages/foo' }; + + const tree = await schematicRunner.runSchematicAsync('page', options, appTree).toPromise(); + const files = tree.files.filter(x => x.search('foo-page') >= 0); + expect(files).toIncludeAllMembers([ + '/src/app/extensions/feature/pages/foo/foo-page.module.ts', + '/src/app/extensions/feature/pages/foo/foo-page.component.ts', + '/src/app/extensions/feature/pages/foo/foo-page.component.html', + '/src/app/extensions/feature/pages/foo/foo-page.component.spec.ts', + ]); + }); + + it('should lazily register route in feature routing module', async () => { + const options = { ...defaultOptions, extension: 'feature' }; + + const tree = await schematicRunner.runSchematicAsync('page', options, appTree).toPromise(); + const appRoutingModule = tree.readContent('/src/app/extensions/feature/pages/feature-routing.module.ts'); + expect(appRoutingModule).toContain(`path: 'foo'`); + expect(appRoutingModule).toContain('foo-page.module'); + expect(appRoutingModule).toContain('FooPageModule'); + }); + + it('should lazily register route in feature routing module when it is the first', async () => { + const options = { ...defaultOptions, extension: 'feature2' }; + + const tree = await schematicRunner.runSchematicAsync('page', options, appTree).toPromise(); + const appRoutingModule = tree.readContent('/src/app/extensions/feature2/pages/feature2-routing.module.ts'); + expect(appRoutingModule).toContain(`path: 'foo'`); + expect(appRoutingModule).toContain('foo-page.module'); + expect(appRoutingModule).toContain('FooPageModule'); + }); + + it('should register route in page routing module when subpaging is detected', async () => { + appTree = await schematicRunner.runSchematicAsync('page', defaultOptions, appTree).toPromise(); + const tree = await schematicRunner + .runSchematicAsync('page', { ...defaultOptions, name: 'foo-bar' }, appTree) + .toPromise(); + const appRoutingModule = tree.readContent('/src/app/pages/app-routing.module.ts'); + expect(appRoutingModule).toContain(`path: 'foo'`); + expect(appRoutingModule).toContain('foo-page.module'); + expect(appRoutingModule).toContain('FooPageModule'); + expect(appRoutingModule).not.toContain('FooBar'); + + const fooRoutingModule = tree.readContent('/src/app/pages/foo/foo-page.module.ts'); + expect(fooRoutingModule).toContain(`path: 'bar'`); + expect(fooRoutingModule).toContain('foo-bar-page.module'); + expect(fooRoutingModule).toContain('FooBarPageModule'); + }); }); - it('should register route in page routing module when subpaging is detected', async () => { - const options = { ...defaultOptions }; - - appTree = await schematicRunner.runSchematicAsync('page', options, appTree).toPromise(); - const tree = await schematicRunner.runSchematicAsync('page', { ...options, name: 'foo-bar' }, appTree).toPromise(); - const appRoutingModule = tree.readContent('/src/app/pages/app-routing.module.ts'); - expect(appRoutingModule).toContain(`path: 'foo'`); - expect(appRoutingModule).toContain('foo-page.module'); - expect(appRoutingModule).toContain('FooPageModule'); - expect(appRoutingModule).not.toContain('FooBar'); - - const fooRoutingModule = tree.readContent('/src/app/pages/foo/foo-page.module.ts'); - expect(fooRoutingModule).toContain(`path: 'bar'`); - expect(fooRoutingModule).toContain('foo-bar-page.module'); - expect(fooRoutingModule).toContain('FooBarPageModule'); + describe('with lazy === false', () => { + const defaultOptions: Options = { + name: 'foo', + project: 'bar', + lazy: false, + }; + + it('should create a page in root by default', async () => { + const tree = await schematicRunner.runSchematicAsync('page', defaultOptions, appTree).toPromise(); + const files = tree.files.filter(x => x.search('foo-page') >= 0); + expect(files).toIncludeAllMembers([ + '/src/app/pages/foo/foo-page.component.ts', + '/src/app/pages/foo/foo-page.component.html', + '/src/app/pages/foo/foo-page.component.spec.ts', + ]); + expect(tree.readContent('/src/app/pages/foo/foo-page.component.html')).toContain('foo-page works!'); + }); + + it('should not create a page module', async () => { + const tree = await schematicRunner.runSchematicAsync('page', defaultOptions, appTree).toPromise(); + + expect(tree.exists('/src/app/pages/foo/foo-page.module.ts')).toBeFalse(); + }); + + it('should register page component in app module', async () => { + const tree = await schematicRunner.runSchematicAsync('page', defaultOptions, appTree).toPromise(); + + const appModule = tree.readContent('/src/app/app.module.ts'); + + expect(appModule).toContain("import { FooPageComponent } from './pages/foo/foo-page.component';"); + expect(appModule).toMatch(/declarations: \[[^\]]*FooPageComponent/); + }); + + it('should create a correct test for the component', async () => { + const tree = await schematicRunner.runSchematicAsync('page', defaultOptions, appTree).toPromise(); + const componentSpecContent = tree.readContent('/src/app/pages/foo/foo-page.component.spec.ts'); + expect(componentSpecContent).toContain(`import { FooPageComponent } from './foo-page.component'`); + }); + + it('should statically register route in app routing module by default', async () => { + const tree = await schematicRunner.runSchematicAsync('page', defaultOptions, appTree).toPromise(); + const appRoutingModule = tree.readContent('/src/app/pages/app-routing.module.ts'); + expect(appRoutingModule).toContain("{ path: 'foo', component: FooPageComponent }"); + expect(appRoutingModule).toContain("import { FooPageComponent } from './foo/foo-page.component'"); + }); + + it('should statically register route in feature routing module', async () => { + const options = { ...defaultOptions, extension: 'feature' }; + + const tree = await schematicRunner.runSchematicAsync('page', options, appTree).toPromise(); + const appRoutingModule = tree.readContent('/src/app/extensions/feature/pages/feature-routing.module.ts'); + expect(appRoutingModule).toContain(`{ path: 'foo', component: FooPageComponent }`); + expect(appRoutingModule).toContain("import { FooPageComponent } from './foo/foo-page.component'"); + }); + + it('should statically register route in feature routing module when it is the first', async () => { + const options = { ...defaultOptions, extension: 'feature2' }; + + const tree = await schematicRunner.runSchematicAsync('page', options, appTree).toPromise(); + const appRoutingModule = tree.readContent('/src/app/extensions/feature2/pages/feature2-routing.module.ts'); + expect(appRoutingModule).toContain(`{ path: 'foo', component: FooPageComponent }`); + expect(appRoutingModule).toContain("import { FooPageComponent } from './foo/foo-page.component'"); + }); + + it('should ignore subpaging and register page in same module', async () => { + appTree = await schematicRunner.runSchematicAsync('page', defaultOptions, appTree).toPromise(); + const tree = await schematicRunner + .runSchematicAsync('page', { ...defaultOptions, name: 'foo-bar' }, appTree) + .toPromise(); + const appRoutingModule = tree.readContent('/src/app/pages/app-routing.module.ts'); + expect(appRoutingModule).toContain(`{ path: 'foo', component: FooPageComponent }`); + expect(appRoutingModule).toContain("import { FooPageComponent } from './foo/foo-page.component'"); + + expect(appRoutingModule).toContain(`{ path: 'foo/bar', component: FooBarPageComponent }`); + expect(appRoutingModule).toContain("import { FooBarPageComponent } from './foo-bar/foo-bar-page.component'"); + }); }); }); diff --git a/schematics/src/page/files/__name@dasherize__/__name@dasherize__-page.module.__tsext__ b/schematics/src/page/files/__name@dasherize__/__name@dasherize__-page.module.__tsext__ index 453efda3a5..cb8985dbfb 100644 --- a/schematics/src/page/files/__name@dasherize__/__name@dasherize__-page.module.__tsext__ +++ b/schematics/src/page/files/__name@dasherize__/__name@dasherize__-page.module.__tsext__ @@ -3,10 +3,10 @@ import { RouterModule, Routes } from '@angular/router'; <% if (!extension) { %>import { SharedModule } from 'ish-shared/shared.module';<% } else { %>import { <%= classify(extension) %>Module } from '../../<%= dasherize(extension) %>.module';<% } %> -const <%= camelize(artifactName) %>Routes: Routes = [{ path: '', component: <%= artifactName %>Component }]; +const <%= camelize(name) %>PageRoutes: Routes = [{ path: '', component: <%= artifactName %> }]; @NgModule({ - imports: [RouterModule.forChild(<%= camelize(artifactName) %>Routes), <% if (!extension) { %>SharedModule<% } else { %><%= classify(extension) %>Module<% } %>], + imports: [RouterModule.forChild(<%= camelize(name) %>PageRoutes), <% if (!extension) { %>SharedModule<% } else { %><%= classify(extension) %>Module<% } %>], declarations: [], }) -export class <%= artifactName %>Module { } +export class <%= classify(name) %>PageModule { } diff --git a/schematics/src/page/schema.json b/schematics/src/page/schema.json index e553f51f55..7771228531 100644 --- a/schematics/src/page/schema.json +++ b/schematics/src/page/schema.json @@ -20,6 +20,10 @@ "default": "ts", "visible": false }, + "routingModule": { + "type": "string", + "visible": false + }, "project": { "type": "string", "$default": { @@ -39,6 +43,11 @@ "type": "string", "description": "The extension to generate the page in. If empty it is generated in the project root.", "alias": "e" + }, + "lazy": { + "type": "boolean", + "description": "Lazy-load the created page. (default is true)", + "default": "true" } } } diff --git a/schematics/src/utils/common.ts b/schematics/src/utils/common.ts index 93dcfb488a..2cbda60daf 100644 --- a/schematics/src/utils/common.ts +++ b/schematics/src/utils/common.ts @@ -68,9 +68,16 @@ export function determineArtifactName( } ) { const kebab = strings.dasherize(options.name); - const moduleImportPath = `/${options.path}${options.flat ? '' : `/${kebab}`}/${kebab}.${artifact}`; - - const artifactName = strings.classify(`${options.name}-${artifact}`); + let moduleImportPath; + let artifactName; + + if (artifact === 'page') { + moduleImportPath = `/${options.path}${kebab}/${kebab}-page.component`; + artifactName = strings.classify(`${options.name}PageComponent`); + } else { + moduleImportPath = `/${options.path}${options.flat ? '' : `/${kebab}`}/${kebab}.${artifact}`; + artifactName = strings.classify(`${options.name}-${artifact}`); + } return { ...options,