Skip to content

Commit

Permalink
Skyux 'pact' CLI command (blackbaud#319)
Browse files Browse the repository at this point in the history
* trying to bypass some stuff

* overriding some methods for testing

* workring on changes to configs and cli commands.  Still need to get config and tokenProvider to be overriden

* dynamically adding pact ports

* adding more karma config settings, adding a service for use my consuming apps, and a util to help with passing information to user in AppConfig

* Adding documentation, watch option for command, and making interface for pact settings in config file

* supporting looking for only pact-spec files

* respecting a bunch of jslint errors when running npm run test

* optionally let use define directory for pact json and pact logs

* changing import statement

* formatting changes and moving dynamically set config settings to runtime

* karma-pact depends on the latest pact-node, but our config doesn't ensure the latest is updated.

* letting karma-pact take care of it's pact-node dependency.  Still need pact as the DSL and pact-web

* cleaning up some formatting so build passes

* noticed ports weren't being retreived across processes.  portfinder doesn't look at host 0:0:0:0 and also promises need to be resolved sequentially to prevent promises resolving on same port since they don't get consumed until after portfinder starts.

* adding spec file for pacts

* writing tests for pact command, made some changes based on my findings during testing.

* tests on karma config, and further changes for thing found during testing

* for whatever reason, not including this causes errors.

* 100% coverage

* some linting errors getting fixed

* removing that one extra line

* thinking not stopping all mocks here could've caused the issue

* weird merge conflict that didn't cause build to fail, and removing carats from package.json

* possibly no need to have the pact-web package

* right, this test needs to be updated
  • Loading branch information
blackbaud-joshlandi authored and Blackbaud-MikitaYankouski committed May 3, 2019
1 parent 1b51168 commit de1fd17
Show file tree
Hide file tree
Showing 18 changed files with 1,268 additions and 14 deletions.
153 changes: 153 additions & 0 deletions cli/pact.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*jslint node: true */
'use strict';

/**
* Spawns the skyux pact command.
* @name pact
*/
function pact(command, argv) {
const async = require('async');
const configResolver = require('./utils/config-resolver');
const karma = require('karma');
const Server = karma.Server;
const portfinder = require('portfinder');
const url = require('url');
const logger = require('../utils/logger');
const tsLinter = require('./utils/ts-linter');
const skyPagesConfigUtil = require('../config/sky-pages/sky-pages.config');
const pactServers = require('../utils/pact-servers');
let skyPagesConfig = skyPagesConfigUtil.getSkyPagesConfig(command);
let http = require('http');
let httpProxy = require('http-proxy');

argv = argv || process.argv;
argv.command = command;

let pactPortSeries = [];
let consumedPorts = [];
// get a free port for every config entry, plus one for the proxy
if (!skyPagesConfig.skyux.pacts) {
logger.error('skyux pact failed! pacts does not exist on configuration file.');
process.exit();
}

let getAsyncFunction = (callback) => {

// ports are not consumed until karma starts, so need to keep track of future consumed ports
portfinder.getPortPromise(
{ port: consumedPorts.length === 0 ? 8000 : Math.max(consumedPorts) + 1 }
)
.then((port) => {
consumedPorts.push(port);
callback(null, port);
})
.catch((error) => {
callback(error, null);
});

};

for (let i = 0; i < skyPagesConfig.skyux.pacts.length + 1; i++) {

pactPortSeries.push(getAsyncFunction);

}

// make sure ports are resolved in order
async.series(pactPortSeries,
(err, ports) => {
if (err) {
logger.error(err);
process.exit();
}

for (let i = 0; i < skyPagesConfig.skyux.pacts.length; i++) {

let serverHost = (skyPagesConfig.skyux.pacts[i].host || 'localhost');
let serverPort = (skyPagesConfig.skyux.pacts[i].port || ports[i]);
// saving pact server information so it can carry over into karma config
pactServers
.savePactServer(skyPagesConfig.skyux.pacts[i].provider, serverHost, serverPort);
}

let proxy = httpProxy.createProxyServer({});

// proxy requests to pact server to contain actual host url rather than the karma url
proxy.on('proxyReq', function (proxyReq) {
let origin;
origin = (skyPagesConfig.skyux.host || {}).url || 'https://host.nxt.blackbaud.com';
proxyReq.setHeader('Origin', origin);
});
// revert CORS header value back to karma url so that requests are successful
proxy.on('proxyRes', function (proxyRes, req) {
proxyRes.headers['Access-Control-Allow-Origin'] = req.headers.origin;
});

http.createServer((req, res) => {

// provider is part of path so that consuming app can make requests to multiple pact
// servers from one proxy server. Use that value to identify proper pact server and then
// remove from request url.
let provider = url.parse(req.url).pathname.split('/')[1];
req.url = req.url.split(provider)[1];
if (Object.keys(pactServers.getAllPactServers()).indexOf(provider) !== -1) {
proxy.web(req, res, {
target: pactServers.getPactServer(provider).fullUrl
});
} else {
logger
.error(`Pact proxy path is invalid. Expected format is base/provider-name/api-path.`);
}
})
.on('connect', () => {
logger
.info(
`Pact proxy server successfully started on http://localhost:${ports[ports.length - 1]}`
);
})
.listen(ports[ports.length - 1], 'localhost');

// for use by consuming app
pactServers.savePactProxyServer(`http://localhost:${ports[ports.length - 1]}`);

const karmaConfigUtil = karma.config;
const karmaConfigPath = configResolver.resolve(command, argv);
const karmaConfig = karmaConfigUtil.parseConfig(karmaConfigPath);

let lintResult;

const onRunStart = () => {
lintResult = tsLinter.lintSync();
};

const onRunComplete = () => {
if (lintResult && lintResult.exitCode > 0) {
// Pull the logger out of the execution stream to let it print
// after karma's coverage reporter.
setTimeout(() => {
logger.error('Process failed due to linting errors:');
lintResult.errors.forEach(error => logger.error(error));
}, 10);
}
};

const onExit = (exitCode) => {
if (exitCode === 0) {
if (lintResult) {
exitCode = lintResult.exitCode;
}
}

logger.info(`Karma has exited with ${exitCode}.`);
process.exit(exitCode);
};

const server = new Server(karmaConfig, onExit);
server.on('run_start', onRunStart);
server.on('run_complete', onRunComplete);
server.start();
});

}

module.exports = pact;
48 changes: 48 additions & 0 deletions config/karma/pact.karma.conf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*jshint node: true*/
'use strict';

/**
* Requires the shared karma config and sets any local properties.
* @name getConfig
* @param {Object} config
*/
function getConfig(config) {
const minimist = require('minimist');
const argv = minimist(process.argv.slice(2));
require(`./${argv.watch ? 'watch' : 'test'}.karma.conf`)(config);
let skyPagesConfig = require('../sky-pages/sky-pages.config').getSkyPagesConfig(argv._[0]);
let testWebpackConfig = require('../webpack/test.webpack.config');
const logger = require('../../utils/logger');
const path = require('path');
const pactServers = require('../../utils/pact-servers');

skyPagesConfig.runtime.pactConfig = {};
skyPagesConfig.runtime.pactConfig.providers = pactServers.getAllPactServers();
skyPagesConfig.runtime.pactConfig.pactProxyServer = pactServers.getPactProxyServer();

if (skyPagesConfig.skyux.pacts) {
var i = 0;
skyPagesConfig.skyux.pacts.forEach((pact) => {
// set pact settings not specified in config file
pact.log = pact.log || path.resolve(process.cwd(), 'logs', `pact-${pact.provider}.log`);
pact.dir = pact.dir || path.resolve(process.cwd(), 'pacts');
pact.host = pactServers.getPactServer(pact.provider).host;
pact.port = pactServers.getPactServer(pact.provider).port;
i++;
});
} else {
logger.error('No pact entry in configuration!');
}

config.set({
frameworks: config.frameworks.concat('pact'),
files: config.files.concat(path.resolve(process.cwd(), 'node_modules/pact/src', `pact-web.js`)),
pact: skyPagesConfig.skyux.pacts,
plugins: config.plugins.concat('@pact-foundation/karma-pact'),
webpack: testWebpackConfig.getWebpackConfig(skyPagesConfig, argv)

});

}

module.exports = getConfig;
1 change: 0 additions & 1 deletion config/karma/shared.karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ function getConfig(config) {
// This file is spawned so we'll need to read the args again
const minimist = require('minimist');
const argv = minimist(process.argv.slice(2));

const path = require('path');
let testWebpackConfig = require('../webpack/test.webpack.config');
let remapIstanbul = require('remap-istanbul');
Expand Down
1 change: 0 additions & 1 deletion config/webpack/test.webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ function outPath() {
function getWebpackConfig(skyPagesConfig, argv) {
const runCoverage = (!argv || argv.coverage !== false);
skyPagesConfig.runtime.includeRouteModule = false;

const ENV = process.env.ENV = process.env.NODE_ENV = 'test';
const srcPath = path.resolve(process.cwd(), 'src', 'app');

Expand Down
3 changes: 3 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ module.exports = {
case 'lint':
require('./cli/lint')();
break;
case 'pact':
require('./cli/pact')(command, argv);
break;
case 'test':
case 'watch':
require('./cli/test')(command, argv);
Expand Down
26 changes: 22 additions & 4 deletions lib/sky-pages-module-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ function getSource(skyAppConfig) {
let runtimeProviders = [
'SkyAppWindowRef',
'SkyAppStyleLoader',
'SkyAuthTokenProvider',
`{
provide: SkyAppConfig,
deps: [
Expand All @@ -67,6 +66,25 @@ function getSource(skyAppConfig) {
'SkyAppViewportService'
];

let authTokenProvider = 'SkyAuthTokenProvider';

if (skyAppConfig.runtime.command === 'pact') {
runtimeProviders
.push(`{
provide: SkyPactService,
useClass: SkyPactService,
deps: [SkyAppConfig]
}`);
runtimeImports.push('SkyPactAuthTokenProvider');
runtimeImports.push('SkyPactService');
authTokenProvider = `{
provide: SkyAuthTokenProvider,
useClass: SkyPactAuthTokenProvider
}`;
}

runtimeProviders.push(authTokenProvider);

let nodeModuleImports = [
`import { Component, NgModule, OnDestroy, OnInit } from '@angular/core';`,
`import { CommonModule } from '@angular/common';`,
Expand Down Expand Up @@ -122,13 +140,13 @@ function getSource(skyAppConfig) {
switch (skyAppConfig.runtime.command) {
case 'build':
enableProdMode =
`import { enableProdMode } from '@angular/core';
`import { enableProdMode } from '@angular/core';
enableProdMode();`;
break;

case 'e2e':
useMockAuth =
`import { BBAuth } from '@blackbaud/auth-client';
`import { BBAuth } from '@blackbaud/auth-client';
BBAuth.mock = true;
`;
break;
Expand All @@ -137,7 +155,7 @@ BBAuth.mock = true;
let useHashRouting = skyAppConfig.skyux.useHashRouting === true;

let moduleSource =
`${useMockAuth}
`${useMockAuth}
${nodeModuleImports.join('\n')}
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@
"ngc-webpack": "3.0.0",
"node-sass": "4.5.3",
"open": "0.0.5",
"@pact-foundation/karma-pact": "2.1.1",
"pact": "4.2.1",
"portfinder": "1.0.13",
"progress-bar-webpack-plugin": "1.9.3",
"protractor": "5.1.2",
Expand Down
15 changes: 14 additions & 1 deletion runtime/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,25 @@ export interface RuntimeConfigApp {
template: string;
}

export class SkyuxPactConfig {
public providers?: {
[provider: string]: {
host?: string;
port?: string;
fullUrl?: string;
}
};
public pactProxyServer?: string;
}

export interface RuntimeConfig {
app: RuntimeConfigApp;
command?: string; // Dynamically added in "higher up" webpacks
componentsPattern: string;
componentsIgnorePattern: string;
handle404?: boolean; // Dynamically added in sky-pages-module-generator.js
includeRouteModule: boolean;
pactConfig?: SkyuxPactConfig;
params: SkyAppRuntimeConfigParams;
routes?: Object[]; // Dynamically added in sky-pages-module-generator.js
routesPattern: string;
Expand All @@ -41,7 +53,7 @@ export interface SkyuxConfigHost {
}

export interface SkyuxConfig {
a11y?: SkyuxConfigA11y|boolean;
a11y?: SkyuxConfigA11y | boolean;
app?: SkyuxConfigApp;
appSettings?: any;
auth?: boolean;
Expand All @@ -53,6 +65,7 @@ export interface SkyuxConfig {
importPath?: string;
mode?: string;
name?: string;
pacts?: any[];
params?: SkyuxConfigParams; // List of allowed params
plugins?: string[];
redirects?: any;
Expand Down
2 changes: 2 additions & 0 deletions runtime/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export * from './assets.service';
export * from './auth-http';
export * from './auth-token-provider';
export * from './pact-auth-token-provider';
export * from './pact.service';
export * from './bootstrapper';
export * from './config';
export * from './params';
Expand Down
9 changes: 9 additions & 0 deletions runtime/pact-auth-token-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { BBAuthGetTokenArgs } from '@blackbaud/auth-client';

export class SkyPactAuthTokenProvider {

public getToken(args?: BBAuthGetTokenArgs): Promise<string> {
return Promise.resolve('mock_access_token_auth-client@blackbaud.com');
}

}
Loading

0 comments on commit de1fd17

Please sign in to comment.