Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: generate different scripts for different projects if they are a part of the same workspace #165

Merged
merged 4 commits into from
Apr 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
98 changes: 98 additions & 0 deletions src/schematics/ng-add/add-scripts.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
import { join } from 'path';

const collectionPath = join(__dirname, '../../../schematics.json');

const workspaceOptions = {
name: 'workspace',
createApplication: false,
newProjectRoot: 'projects',
version: '9.0.0',
};

describe('ng-add', () => {
let workspaceTree: UnitTestTree;
const testRunner = new SchematicTestRunner('single-spa-angular', collectionPath);

async function generateApplication(name: string, project?: string) {
await testRunner
.runExternalSchematicAsync(
'@schematics/angular',
'application',
{
name,
project,
routing: true,
skipTests: true,
style: 'scss',
},
workspaceTree,
)
.toPromise();
}

beforeEach(async () => {
// Generate workspace w/o application.
workspaceTree = await testRunner
.runExternalSchematicAsync('@schematics/angular', 'workspace', workspaceOptions)
.toPromise();
});

test('should create 2 apps in an empty workspace and generate appropriate scripts', async () => {
// Arrange & act
await generateApplication('first-cool-app', 'first-cool-app');
await generateApplication('second-cool-app', 'second-cool-app');

await testRunner
.runSchematicAsync('ng-add', { project: 'first-cool-app' }, workspaceTree)
.toPromise();

const tree = await testRunner
.runSchematicAsync('ng-add', { project: 'second-cool-app' }, workspaceTree)
.toPromise();

const { scripts } = JSON.parse(tree.get('/package.json').content.toString());

// Assert
expect(scripts['build:single-spa:first-cool-app']).toBe(
'ng build first-cool-app --prod --deploy-url http://localhost:4200/',
);
expect(scripts['serve:single-spa:first-cool-app']).toBe(
'ng s --project first-cool-app --disable-host-check --port 4200 --deploy-url http://localhost:4200/ --live-reload false',
);

expect(scripts['build:single-spa:second-cool-app']).toBe(
'ng build second-cool-app --prod --deploy-url http://localhost:4201/',
);
expect(scripts['serve:single-spa:second-cool-app']).toBe(
'ng s --project second-cool-app --disable-host-check --port 4201 --deploy-url http://localhost:4201/ --live-reload false',
);
});

test('should create 2 apps but one app should be default and second one is additional', async () => {
// Arrange & act
await generateApplication('default-project');
await generateApplication('additional-project', 'additional-project');

await testRunner.runSchematicAsync('ng-add', undefined, workspaceTree).toPromise();

const tree = await testRunner
.runSchematicAsync('ng-add', { project: 'additional-project' }, workspaceTree)
.toPromise();

const { scripts } = JSON.parse(tree.get('/package.json').content.toString());

// Arrange
expect(scripts['build:single-spa']).toBe('ng build --prod --deploy-url http://localhost:4200/');
expect(scripts['serve:single-spa']).toBe(
'ng s --disable-host-check --port 4200 --deploy-url http://localhost:4200/ --live-reload false',
);

expect(scripts['build:single-spa:additional-project']).toBe(
'ng build additional-project --prod --deploy-url http://localhost:4201/',
);
expect(scripts['serve:single-spa:additional-project']).toBe(
'ng s --project additional-project --disable-host-check --port 4201 --deploy-url http://localhost:4201/ --live-reload false',
);
});
});
109 changes: 109 additions & 0 deletions src/schematics/ng-add/add-scripts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { Tree } from '@angular-devkit/schematics';

const DEFAULT_PORT = 4200;

interface Scripts {
[script: string]: string;
}

/**
* The user can have multiple applications inside the same workspace.
* E.g. consider following commands:
*
* - `yarn ng new --createApplication false workspace`
* - `yarn ng generate application first-cool-app`
* - `yarn ng generate application second-cool-app`
* - `yarn ng add single-spa-angular --project first-cool-app`
* - `yarn ng add single-spa-angular --project second-cool-app`
*
* In that case our schematics should respect passed `--project` argument.
* Basically it will create different scripts for different applications, thus the
* user will be able to run them in parallel. Created scripts will be:
*
* - build:single-spa:first-cool-app
* - serve:single-spa:first-cool-app
*
* - build:single-spa:second-cool-app
* - serve:single-spa:second-cool-app
*/
export function addScripts(
tree: Tree,
pkgPath: string,
pkg: any,
project: string | undefined,
): void {
if (project) {
addScriptsForTheSpecificProject(pkg, project);
} else {
addDefaultScripts(pkg);
}

tree.overwrite(pkgPath, JSON.stringify(pkg, null, 2));
}

function addScriptsForTheSpecificProject(pkg: any, project: string): void {
const port = parseExistingScriptsAndChoosePort(pkg.scripts);

pkg.scripts[
`build:single-spa:${project}`
] = `ng build ${project} --prod --deploy-url http://localhost:${port}/`;

pkg.scripts[
`serve:single-spa:${project}`
] = `ng s --project ${project} --disable-host-check --port ${port} --deploy-url http://localhost:${port}/ --live-reload false`;
}

/**
* In that case the user didn't provide any `--project` argument, that probably means
* that he has a single project in his workspace and we want to provide a default script.
*/
function addDefaultScripts(pkg: any): void {
pkg.scripts[
'build:single-spa'
] = `ng build --prod --deploy-url http://localhost:${DEFAULT_PORT}/`;

pkg.scripts[
'serve:single-spa'
] = `ng s --disable-host-check --port ${DEFAULT_PORT} --deploy-url http://localhost:${DEFAULT_PORT}/ --live-reload false`;
}

function parseExistingScriptsAndChoosePort(scripts: Scripts): number {
const collectedScripts: string[] = collectExistingServeSingleSpaScripts(scripts);
// For instance `[4200, 4201, 4202]`.
const ports: number[] = getPortsFromCollectedScripts(collectedScripts);

if (ports.length === 0) {
return DEFAULT_PORT;
}

const lastPort = ports.pop();
// `4202 + 1 -> 4203` next port for the new project.
return lastPort! + 1;
}

function collectExistingServeSingleSpaScripts(scripts: Scripts): string[] {
return Object.keys(scripts)
.filter(key => key.startsWith('serve:single-spa'))
.map(key => scripts[key]);
}

function getPortsFromCollectedScripts(collectedScripts: string[]): number[] {
return (
collectedScripts
.reduce((ports: number[], script: string) => {
const match: RegExpMatchArray | null = script.match(/--port \d+/);

if (match !== null) {
// `match[0]` will be a string e.g. `--port 4200`,
// we split by space to get that `4200`.
const [, port] = match[0].split(' ');
ports.push(+port);
}

return ports;
}, <number[]>[])
// Sorts all numbers for ascending order. For example we will get
// `[4200, 4201, 4202]` sorted numbers. We will need `4202` to get next port.
.sort()
);
}
13 changes: 3 additions & 10 deletions src/schematics/ng-add/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {

import { normalize, join } from 'path';

import { addScripts } from './add-scripts';
import { Schema as NgAddOptions } from './schema';
import {
getSingleSpaAngularDependency,
Expand Down Expand Up @@ -149,7 +150,7 @@ function updateTSConfig(host: Tree, clientProject: WorkspaceProject): void {
host.overwrite(tsConfigFileName, JSON.stringify(json, null, 2));
}

export function addNPMScripts(options: NgAddOptions) {
export function addNPMScripts(options: NgAddOptions): Rule {
return (host: Tree, context: SchematicContext) => {
const pkgPath = '/package.json';
const buffer = host.read(pkgPath);
Expand All @@ -158,15 +159,7 @@ export function addNPMScripts(options: NgAddOptions) {
throw new SchematicsException('Could not find package.json');
}

const pkg = JSON.parse(buffer.toString());

pkg.scripts['build:single-spa'] = `ng build --prod --deploy-url http://localhost:4200/`;

pkg.scripts[
'serve:single-spa'
] = `ng serve --disable-host-check --port 4200 --deploy-url http://localhost:4200/ --live-reload false`;

host.overwrite(pkgPath, JSON.stringify(pkg, null, 2));
addScripts(host, pkgPath, JSON.parse(buffer.toString()), options.project);
};
}

Expand Down