Skip to content

Commit

Permalink
fix(@schematics/angular): Allow for scoped library names
Browse files Browse the repository at this point in the history
  • Loading branch information
Brocco committed Apr 25, 2018
1 parent 54d5d56 commit bbd62fe
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 51 deletions.
40 changes: 2 additions & 38 deletions packages/schematics/angular/application/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { JsonObject, normalize, relative, strings, tags } from '@angular-devkit/core';
import { JsonObject, normalize, relative, strings } from '@angular-devkit/core';
import {
MergeStrategy,
Rule,
Expand All @@ -30,6 +30,7 @@ import {
getWorkspace,
} from '../utility/config';
import { latestVersions } from '../utility/latest-versions';
import { validateProjectName } from '../utility/validation';
import { Schema as ApplicationOptions } from './schema';


Expand Down Expand Up @@ -249,43 +250,6 @@ function addAppToWorkspaceFile(options: ApplicationOptions, workspace: Workspace

return addProjectToWorkspace(workspace, options.name, project);
}
const projectNameRegexp = /^[a-zA-Z][.0-9a-zA-Z]*(-[.0-9a-zA-Z]*)*$/;
const unsupportedProjectNames = ['test', 'ember', 'ember-cli', 'vendor', 'app'];

function getRegExpFailPosition(str: string): number | null {
const parts = str.indexOf('-') >= 0 ? str.split('-') : [str];
const matched: string[] = [];

parts.forEach(part => {
if (part.match(projectNameRegexp)) {
matched.push(part);
}
});

const compare = matched.join('-');

return (str !== compare) ? compare.length : null;
}

function validateProjectName(projectName: string) {
const errorIndex = getRegExpFailPosition(projectName);
if (errorIndex !== null) {
const firstMessage = tags.oneLine`
Project name "${projectName}" is not valid. New project names must
start with a letter, and must contain only alphanumeric characters or dashes.
When adding a dash the segment after the dash must also start with a letter.
`;
const msg = tags.stripIndent`
${firstMessage}
${projectName}
${Array(errorIndex + 1).join(' ') + '^'}
`;
throw new SchematicsException(msg);
} else if (unsupportedProjectNames.indexOf(projectName) !== -1) {
throw new SchematicsException(`Project name "${projectName}" is not a supported name.`);
}

}

export default function (options: ApplicationOptions): Rule {
return (host: Tree, context: SchematicContext) => {
Expand Down
1 change: 0 additions & 1 deletion packages/schematics/angular/application/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
"name": {
"description": "The name of the application.",
"type": "string",
"format": "html-selector",
"$default": {
"$source": "argv",
"index": 0
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "<%= projectRoot.split('/').map(x => '..').join('/') %>/node_modules/ng-packagr/ng-package.schema.json",
"dest": "<%= projectRoot.split('/').map(x => '..').join('/') %>/dist/<%= dasherize(name) %>",
"dest": "<%= projectRoot.split('/').map(x => '..').join('/') %>/dist/<%= dasherize(packageName) %>",
"deleteDestPath": false,
"lib": {
"entryFile": "src/<%= entryFile %>.ts"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "<%= dasherize(name) %>",
"name": "<%= dasherize(packageName) %>",
"version": "0.0.1",
"peerDependencies": {
"@angular/common": "^6.0.0-rc.0 || ^6.0.0",
Expand Down
37 changes: 27 additions & 10 deletions packages/schematics/angular/library/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
getWorkspace,
} from '../utility/config';
import { latestVersions } from '../utility/latest-versions';
import { validateProjectName } from '../utility/validation';
import { Schema as LibraryOptions } from './schema';


Expand Down Expand Up @@ -125,7 +126,7 @@ function addDependenciesToPackageJson() {
}

function addAppToWorkspaceFile(options: LibraryOptions, workspace: WorkspaceSchema,
projectRoot: string): Rule {
projectRoot: string, packageName: string): Rule {

const project: WorkspaceProject = {
root: `${projectRoot}`,
Expand Down Expand Up @@ -166,27 +167,43 @@ function addAppToWorkspaceFile(options: LibraryOptions, workspace: WorkspaceSche
},
};

return addProjectToWorkspace(workspace, options.name, project);
return addProjectToWorkspace(workspace, packageName, project);
}

export default function (options: LibraryOptions): Rule {
return (host: Tree, context: SchematicContext) => {
if (!options.name) {
throw new SchematicsException(`Invalid options, "name" is required.`);
}
const name = options.name;
const prefix = options.prefix || 'lib';

validateProjectName(options.name);

// If scoped project (i.e. "@foo/bar"), convert projectDir to "foo/bar".
const packageName = options.name;
let scopeName = '';
if (/^@.*\/.*/.test(options.name)) {
const [scope, name] = options.name.split('/');
scopeName = scope.replace(/^@/, '');
options.name = name;
}

const workspace = getWorkspace(host);
const newProjectRoot = workspace.newProjectRoot;
const projectRoot = `${newProjectRoot}/${strings.dasherize(options.name)}`;
let projectRoot = `${newProjectRoot}/${strings.dasherize(options.name)}`;
if (scopeName) {
projectRoot =
`${newProjectRoot}/${strings.dasherize(scopeName)}/${strings.dasherize(options.name)}`;
}

const sourceDir = `${projectRoot}/src/lib`;
const relativeTsLintPath = projectRoot.split('/').map(x => '..').join('/');

const templateSource = apply(url('./files'), [
template({
...strings,
...options,
packageName,
projectRoot,
relativeTsLintPath,
prefix,
Expand All @@ -198,27 +215,27 @@ export default function (options: LibraryOptions): Rule {

return chain([
branchAndMerge(mergeWith(templateSource)),
addAppToWorkspaceFile(options, workspace, projectRoot),
addAppToWorkspaceFile(options, workspace, projectRoot, packageName),
options.skipPackageJson ? noop() : addDependenciesToPackageJson(),
options.skipTsConfig ? noop() : updateTsConfig(name),
options.skipTsConfig ? noop() : updateTsConfig(options.name),
schematic('module', {
name: name,
name: options.name,
commonModule: false,
flat: true,
path: sourceDir,
spec: false,
}),
schematic('component', {
name: name,
selector: `${prefix}-${name}`,
name: options.name,
selector: `${prefix}-${options.name}`,
inlineStyle: true,
inlineTemplate: true,
flat: true,
path: sourceDir,
export: true,
}),
schematic('service', {
name: name,
name: options.name,
flat: true,
path: sourceDir,
}),
Expand Down
39 changes: 39 additions & 0 deletions packages/schematics/angular/library/index_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,4 +215,43 @@ describe('Library Schematic', () => {
tree = schematicRunner.runSchematic('component', componentOptions, tree);
expect(tree.exists('/projects/foo/src/lib/comp/comp.component.ts')).toBe(true);
});

it(`should support creating scoped libraries`, () => {
const scopedName = '@myscope/mylib';
const options = { ...defaultOptions, name: scopedName };
const tree = schematicRunner.runSchematic('library', options, workspaceTree);

const pkgJsonPath = '/projects/myscope/mylib/package.json';
expect(tree.files).toContain(pkgJsonPath);
expect(tree.files).toContain('/projects/myscope/mylib/src/lib/mylib.module.ts');
expect(tree.files).toContain('/projects/myscope/mylib/src/lib/mylib.component.ts');

const pkgJson = JSON.parse(tree.readContent(pkgJsonPath));
expect(pkgJson.name).toEqual(scopedName);

const tsConfigJson = JSON.parse(tree.readContent('/projects/myscope/mylib/tsconfig.spec.json'));
expect(tsConfigJson.extends).toEqual('../../../tsconfig.json');

const cfg = JSON.parse(tree.readContent('/angular.json'));
expect(cfg.projects['@myscope/mylib']).toBeDefined();
});

it(`should dasherize scoped libraries`, () => {
const scopedName = '@myScope/myLib';
const expectedScopeName = '@my-scope/my-lib';
const options = { ...defaultOptions, name: scopedName };
const tree = schematicRunner.runSchematic('library', options, workspaceTree);

const pkgJsonPath = '/projects/my-scope/my-lib/package.json';
expect(tree.readContent(pkgJsonPath)).toContain(expectedScopeName);

const ngPkgJsonPath = '/projects/my-scope/my-lib/ng-package.json';
expect(tree.readContent(ngPkgJsonPath)).toContain(expectedScopeName);

const pkgJson = JSON.parse(tree.readContent(pkgJsonPath));
expect(pkgJson.name).toEqual(expectedScopeName);

const cfg = JSON.parse(tree.readContent('/angular.json'));
expect(cfg.projects['@myScope/myLib']).toBeDefined();
});
});
46 changes: 46 additions & 0 deletions packages/schematics/angular/utility/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,49 @@ export function validateHtmlSelector(selector: string): void {
is invalid.`);
}
}


export function validateProjectName(projectName: string) {
const errorIndex = getRegExpFailPosition(projectName);
const unsupportedProjectNames = ['test', 'ember', 'ember-cli', 'vendor', 'app'];
if (errorIndex !== null) {
const firstMessage = tags.oneLine`
Project name "${projectName}" is not valid. New project names must
start with a letter, and must contain only alphanumeric characters or dashes.
When adding a dash the segment after the dash must also start with a letter.
`;
const msg = tags.stripIndent`
${firstMessage}
${projectName}
${Array(errorIndex + 1).join(' ') + '^'}
`;
throw new SchematicsException(msg);
} else if (unsupportedProjectNames.indexOf(projectName) !== -1) {
throw new SchematicsException(`Project name "${projectName}" is not a supported name.`);
}
}

function getRegExpFailPosition(str: string): number | null {
const isScope = /^@.*\/.*/.test(str);
if (isScope) {
// Remove starting @
str = str.replace(/^@/, '');
// Change / to - for validation
str = str.replace(/\//g, '-');
}

const parts = str.indexOf('-') >= 0 ? str.split('-') : [str];
const matched: string[] = [];

const projectNameRegexp = /^[a-zA-Z][.0-9a-zA-Z]*(-[.0-9a-zA-Z]*)*$/;

parts.forEach(part => {
if (part.match(projectNameRegexp)) {
matched.push(part);
}
});

const compare = matched.join('-');

return (str !== compare) ? compare.length : null;
}

0 comments on commit bbd62fe

Please sign in to comment.