diff --git a/cli/build-public-library.js b/cli/build-public-library.js new file mode 100644 index 00000000..f51c9cfd --- /dev/null +++ b/cli/build-public-library.js @@ -0,0 +1,78 @@ +/*jshint node: true*/ +'use strict'; + +const fs = require('fs-extra'); +const rimraf = require('rimraf'); + +const stageTypeScriptFiles = require('./utils/stage-library-ts'); +const preparePackage = require('./utils/prepare-library-package'); +const webpackConfig = require('../config/webpack/build-public-library.webpack.config.js'); +const skyPagesConfigUtil = require('../config/sky-pages/sky-pages.config'); +const runCompiler = require('./utils/run-compiler'); + +function cleanTemp() { + rimraf.sync(skyPagesConfigUtil.spaPathTemp()); +} + +function cleanDist() { + rimraf.sync(skyPagesConfigUtil.spaPath('dist')); +} + +function cleanAll() { + cleanTemp(); + cleanDist(); +} + +function writeTSConfig() { + var config = { + 'compilerOptions': { + 'target': 'es5', + 'module': 'es2015', + 'moduleResolution': 'node', + 'emitDecoratorMetadata': true, + 'experimentalDecorators': true, + 'allowSyntheticDefaultImports': true, + 'sourceMap': true, + 'noImplicitAny': true, + 'declaration': true, + 'skipLibCheck': true, + 'lib': [ + 'dom', + 'es6' + ], + 'types': [ + 'jasmine', + 'node' + ], + 'outDir': skyPagesConfigUtil.spaPath('dist'), + 'rootDir': skyPagesConfigUtil.spaPathTemp() + }, + 'files': [ + skyPagesConfigUtil.spaPathTemp('index.ts') + ] + }; + + fs.writeJSONSync(skyPagesConfigUtil.spaPathTemp('tsconfig.json'), config); +} + +function transpile(skyPagesConfig, webpack) { + const config = webpackConfig.getWebpackConfig(skyPagesConfig); + return runCompiler(webpack, config); +} + +module.exports = (skyPagesConfig, webpack) => { + cleanAll(); + stageTypeScriptFiles(); + writeTSConfig(); + + return transpile(skyPagesConfig, webpack) + .then(() => { + preparePackage(); + cleanTemp(); + process.exit(0); + }) + .catch(() => { + cleanAll(); + process.exit(1); + }); +}; diff --git a/cli/build.js b/cli/build.js index 594e14a1..4660810e 100644 --- a/cli/build.js +++ b/cli/build.js @@ -1,13 +1,13 @@ /*jshint node: true*/ 'use strict'; -const logger = require('winston'); const fs = require('fs-extra'); const merge = require('merge'); const skyPagesConfigUtil = require('../config/sky-pages/sky-pages.config'); const generator = require('../lib/sky-pages-module-generator'); const assetsConfig = require('../lib/assets-configuration'); const pluginFileProcessor = require('../lib/plugin-file-processor'); +const runCompiler = require('./utils/run-compiler'); function writeTSConfig() { var config = { @@ -128,38 +128,14 @@ function build(argv, skyPagesConfig, webpack) { assetsConfig.setSkyAssetsLoaderUrl(config, skyPagesConfig, assetsBaseUrl); - const compiler = webpack(config); - - return new Promise((resolve, reject) => { - compiler.run((err, stats) => { - if (err) { - logger.error(err); - reject(err); - return; - } - - const jsonStats = stats.toJson(); - - if (jsonStats.errors.length) { - logger.error(jsonStats.errors); - } - - if (jsonStats.warnings.length) { - logger.warn(jsonStats.warnings); - } - - logger.info(stats.toString({ - chunks: false, - colors: false - })); - + return runCompiler(webpack, config) + .then(stats => { if (compileModeIsAoT) { cleanupAot(); } - resolve(stats); + return Promise.resolve(stats); }); - }); } module.exports = build; diff --git a/cli/utils/prepare-library-package.js b/cli/utils/prepare-library-package.js new file mode 100644 index 00000000..64dc9c0b --- /dev/null +++ b/cli/utils/prepare-library-package.js @@ -0,0 +1,35 @@ +/*jshint node: true*/ +'use strict'; + +const fs = require('fs-extra'); +const skyPagesConfigUtil = require('../../config/sky-pages/sky-pages.config'); + +function makePackageFileForDist() { + const packageJson = fs.readJSONSync( + skyPagesConfigUtil.spaPath('package.json') + ); + packageJson.module = 'index.js'; + packageJson.main = 'bundles/bundle.umd.js'; + fs.writeJSONSync( + skyPagesConfigUtil.spaPath('dist', 'package.json'), + packageJson, + { spaces: 2 } + ); +} + +function copyFilesToDist() { + fs.copySync( + skyPagesConfigUtil.spaPath('README.md'), + skyPagesConfigUtil.spaPath('dist', 'README.md') + ); + + fs.copySync( + skyPagesConfigUtil.spaPath('CHANGELOG.md'), + skyPagesConfigUtil.spaPath('dist', 'CHANGELOG.md') + ); +} + +module.exports = () => { + makePackageFileForDist(); + copyFilesToDist(); +}; diff --git a/cli/utils/run-compiler.js b/cli/utils/run-compiler.js new file mode 100644 index 00000000..5e8447ac --- /dev/null +++ b/cli/utils/run-compiler.js @@ -0,0 +1,37 @@ +/*jshint node: true*/ +'use strict'; + +const logger = require('winston'); + +const runCompiler = (webpack, config) => { + const compiler = webpack(config); + + return new Promise((resolve, reject) => { + compiler.run((err, stats) => { + if (err) { + logger.error(err); + reject(err); + return; + } + + const jsonStats = stats.toJson(); + + if (jsonStats.errors.length) { + logger.error(jsonStats.errors); + } + + if (jsonStats.warnings.length) { + logger.warn(jsonStats.warnings); + } + + logger.info(stats.toString({ + chunks: false, + colors: false + })); + + resolve(stats); + }); + }); +}; + +module.exports = runCompiler; diff --git a/cli/utils/stage-library-ts.js b/cli/utils/stage-library-ts.js new file mode 100644 index 00000000..8aba720b --- /dev/null +++ b/cli/utils/stage-library-ts.js @@ -0,0 +1,98 @@ +/*jshint node: true*/ +'use strict'; + +const fs = require('fs-extra'); +const glob = require('glob'); +const path = require('path'); +const sass = require('node-sass'); +const skyPagesConfigUtil = require('../../config/sky-pages/sky-pages.config'); +const spaPathTempSrc = skyPagesConfigUtil.spaPathTempSrc(); + +function copySource() { + fs.copySync( + skyPagesConfigUtil.spaPath('src', 'app', 'public'), + skyPagesConfigUtil.spaPathTemp() + ); +} + +function deleteNonDistFiles() { + let files = glob.sync(`${spaPathTempSrc}/**/*.spec.ts`); + files.forEach(file => fs.removeSync(file)); +} + +function inlineHtmlCss() { + const templateUrlRegEx = /templateUrl\:\s*'(.+?\.html)'/gi; + const styleUrlsRegEx = /styleUrls\:\s*\[\s*'(.+?\.scss)']/gi; + + let files = glob.sync(`${spaPathTempSrc}/**/*.ts`); + + files.forEach((file) => { + let fileContents = fs.readFileSync(file, { encoding: 'utf8' }); + let dirname = path.dirname(file); + let matches; + + // templateUrl + matches = templateUrlRegEx.exec(fileContents); + while (matches) { + let requireFile = path.join(dirname, matches[1]); + let requireContents = getFileContents(requireFile); + requireContents = `template: ${requireContents}`; + fileContents = fileContents.replace(matches[0], requireContents); + matches = templateUrlRegEx.exec(fileContents); + + // Since we're changing the file contents in each iteration and since the regex is stateful + // we need to reset the regex; otherwise it might not be able to locate subsequent matches + // after the first replacement. + templateUrlRegEx.lastIndex = 0; + } + + // styleUrls + matches = styleUrlsRegEx.exec(fileContents); + while (matches) { + let requireFile = path.join(dirname, matches[1]); + let requireContents = getFileContents(requireFile); + requireContents = `styles: [${requireContents}]`; + fileContents = fileContents.replace(matches[0], requireContents); + styleUrlsRegEx.lastIndex = 0; + matches = styleUrlsRegEx.exec(fileContents); + } + + fs.writeFileSync(file, fileContents, { encoding: 'utf8' }); + }); +} + +function getFileContents(filePath) { + let contents = ''; + switch (path.extname(filePath)) { + case '.scss': + contents = compileSass(filePath); + break; + case '.html': + contents = getHtmlContents(filePath); + break; + } + + contents = contents + .toString() + .replace(/\\f/g, '\\\\f') + .replace(/`/g, '\\`'); + + return '`' + contents + '`'; +} + +function getHtmlContents(filePath) { + return fs.readFileSync(filePath).toString(); +} + +function compileSass(filePath) { + return sass.renderSync({ + file: filePath, + outputStyle: 'compressed' + }).css; +} + +module.exports = () => { + copySource(); + deleteNonDistFiles(); + inlineHtmlCss(); +}; diff --git a/config/webpack/build-public-library.webpack.config.js b/config/webpack/build-public-library.webpack.config.js new file mode 100644 index 00000000..80c0f8cc --- /dev/null +++ b/config/webpack/build-public-library.webpack.config.js @@ -0,0 +1,68 @@ +/*jslint node: true */ +'use strict'; + +const webpack = require('webpack'); +const ngcWebpack = require('ngc-webpack'); +const skyPagesConfigUtil = require('../sky-pages/sky-pages.config'); +const ProcessExitCode = require('../../plugin/process-exit-code'); + +function getWebpackConfig(skyPagesConfig) { + const libraryName = skyPagesConfig.skyux.name || 'SkyAppLibrary'; + return { + entry: skyPagesConfigUtil.spaPathTemp('index.ts'), + output: { + path: skyPagesConfigUtil.spaPath('dist', 'bundles'), + filename: 'bundle.umd.js', + libraryTarget: 'umd', + library: libraryName + }, + externals: [ + /^@angular\//, + /^@blackbaud\//, + /^rxjs\// + ], + resolve: { + extensions: ['.js', '.ts'] + }, + module: { + rules: [ + { + test: /\.ts$/, + use: ['awesome-typescript-loader', 'angular2-template-loader'], + exclude: [/\.(spec|e2e)\.ts$/] + }, + { + test: /\.html$/, + use: 'raw-loader' + }, + { + test: /\.scss$/, + use: ['raw-loader', 'sass-loader'] + }, + { + test: /\.css$/, + use: ['raw-loader', 'style-loader'] + } + ] + }, + plugins: [ + new ngcWebpack.NgcWebpackPlugin({ + tsConfig: skyPagesConfigUtil.spaPathTemp('tsconfig.json') + }), + + new webpack.optimize.UglifyJsPlugin({ + beautify: false, + comments: false, + compress: { warnings: false }, + mangle: { screw_ie8: true, keep_fnames: true } + }), + + // Webpack 2 behavior does not correctly return non-zero exit code. + new ProcessExitCode() + ] + }; +} + +module.exports = { + getWebpackConfig: getWebpackConfig +}; diff --git a/index.js b/index.js index 990f90f2..574b266d 100644 --- a/index.js +++ b/index.js @@ -13,6 +13,9 @@ module.exports = { case 'build': require('./cli/build')(argv, skyPagesConfig, webpack); break; + case 'build-public-library': + require('./cli/build-public-library')(skyPagesConfig, webpack); + break; case 'e2e': require('./cli/e2e')(argv, skyPagesConfig, webpack); break; diff --git a/package.json b/package.json index 1062f276..809e787d 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "karma-webpack": "2.0.3", "loader-utils": "1.1.0", "merge": "1.2.0", + "ngc-webpack": "3.0.0", "node-sass": "4.5.3", "open": "0.0.5", "portfinder": "1.0.13", diff --git a/test/cli-build-public-library.spec.js b/test/cli-build-public-library.spec.js new file mode 100644 index 00000000..d2d96f01 --- /dev/null +++ b/test/cli-build-public-library.spec.js @@ -0,0 +1,98 @@ +/*jshint jasmine: true, node: true */ +'use strict'; + +const mock = require('mock-require'); +const fs = require('fs-extra'); +const rimraf = require('rimraf'); +const logger = require('winston'); +const skyPagesConfigUtil = require('../config/sky-pages/sky-pages.config'); + +describe('cli build-public-library', () => { + const requirePath = '../cli/build-public-library'; + let webpackConfig; + let mockWebpack; + + beforeEach(() => { + mockWebpack = () => { + return { + run: (cb) => { + cb(null, { + toJson: () => ({ + errors: [], + warnings: [] + }) + }); + } + }; + }; + mock('../cli/utils/stage-library-ts', () => {}); + mock('../cli/utils/prepare-library-package', () => {}); + mock('../config/webpack/build-public-library.webpack.config.js', { + getWebpackConfig: () => { + webpackConfig = { + entry: '' + }; + return webpackConfig; + } + }); + spyOn(process, 'exit').and.callFake(() => {}); + spyOn(skyPagesConfigUtil, 'spaPath').and.returnValue(''); + spyOn(skyPagesConfigUtil, 'spaPathTemp').and.callFake((fileName = '') => { + return fileName; + }); + spyOn(rimraf, 'sync').and.callFake(() => {}); + spyOn(fs, 'writeJSONSync').and.callFake(() => {}); + }); + + afterEach(() => { + mock.stopAll(); + }); + + it('should return a function', () => { + const cliCommand = require(requirePath); + expect(cliCommand).toEqual(jasmine.any(Function)); + }); + + it('should clean the dist and temp directories', (done) => { + const cliCommand = require(requirePath); + cliCommand({}, mockWebpack).then(() => { + expect(rimraf.sync).toHaveBeenCalled(); + expect(skyPagesConfigUtil.spaPathTemp).toHaveBeenCalled(); + expect(skyPagesConfigUtil.spaPath).toHaveBeenCalledWith('dist'); + done(); + }); + }); + + it('should write a tsconfig.json file', (done) => { + const cliCommand = require(requirePath); + cliCommand({}, mockWebpack).then(() => { + const firstArg = fs.writeJSONSync.calls.argsFor(0)[0]; + expect(firstArg).toEqual('tsconfig.json'); + done(); + }); + }); + + it('should pass config to webpack', (done) => { + const cliCommand = require(requirePath); + cliCommand({}, mockWebpack).then(() => { + expect(webpackConfig).toEqual(jasmine.any(Object)); + expect(webpackConfig.entry).toEqual(jasmine.any(String)); + done(); + }); + }); + + it('should handle errors thrown by webpack', (done) => { + const errorMessage = 'Something bad happened.'; + spyOn(logger, 'error'); + mockWebpack = () => { + return { + run: (cb) => cb(errorMessage) + }; + }; + const cliCommand = mock.reRequire(requirePath); + cliCommand({}, mockWebpack).then(() => { + expect(logger.error).toHaveBeenCalledWith(errorMessage); + done(); + }); + }); +}); diff --git a/test/cli-build.spec.js b/test/cli-build.spec.js index 2ebbadc4..f0a51df2 100644 --- a/test/cli-build.spec.js +++ b/test/cli-build.spec.js @@ -145,17 +145,17 @@ describe('cli build', () => { }) } ); - - // The temp folder should be deleted after the build is complete. - expect(removeSpy).toHaveBeenCalledWith( - skyPagesConfigUtil.spaPathTemp() - ); - expect(passedConfig.hasOwnProperty('skyuxPathAlias')).toBe(false); - - done(); } }) - ); + ).then(() => { + // The temp folder should be deleted after the build is complete. + expect(removeSpy).toHaveBeenCalledWith( + skyPagesConfigUtil.spaPathTemp() + ); + expect(passedConfig.hasOwnProperty('skyuxPathAlias')).toBe(false); + + done(); + }); // The default SKY UX Builder source files should be written first. expect(copySpy.calls.argsFor(1)).toEqual([ diff --git a/test/cli-utils-prepare-library-package.spec.js b/test/cli-utils-prepare-library-package.spec.js new file mode 100644 index 00000000..d5720866 --- /dev/null +++ b/test/cli-utils-prepare-library-package.spec.js @@ -0,0 +1,49 @@ +/*jshint jasmine: true, node: true */ +'use strict'; + +const fs = require('fs-extra'); +const glob = require('glob'); +const mock = require('mock-require'); +const sass = require('node-sass'); + +describe('cli utils prepare-library-package', () => { + let util; + + beforeEach(() => { + mock('../config/sky-pages/sky-pages.config', { + spaPath: (...segments) => segments.join('/'), + spaPathTempSrc: () => '', + spaPathTemp: () => '' + }); + util = require('../cli/utils/prepare-library-package'); + }); + + afterEach(() => { + mock.stopAll(); + }); + + it('should return a function', () => { + expect(typeof util).toEqual('function'); + }); + + it('should update the module property of package.json and write it to dist', () => { + spyOn(fs, 'copySync').and.returnValue(); + spyOn(fs, 'readJSONSync').and.returnValue({}); + spyOn(fs, 'writeJSONSync').and.callFake((filePath, contents) => { + expect(filePath.match('dist')).not.toEqual(null); + expect(contents.module).toEqual('index.js'); + }); + util(); + expect(fs.readJSONSync).toHaveBeenCalled(); + expect(fs.writeJSONSync).toHaveBeenCalled(); + }); + + it('should copy contributing and changelog to dist', () => { + spyOn(fs, 'readJSONSync').and.returnValue({}); + spyOn(fs, 'writeJSONSync').and.returnValue(); + spyOn(fs, 'copySync').and.returnValue(); + util(); + expect(fs.copySync).toHaveBeenCalledWith('README.md', 'dist/README.md'); + expect(fs.copySync).toHaveBeenCalledWith('CHANGELOG.md', 'dist/CHANGELOG.md'); + }); +}); diff --git a/test/cli-utils-run-compiler.spec.js b/test/cli-utils-run-compiler.spec.js new file mode 100644 index 00000000..89eb620a --- /dev/null +++ b/test/cli-utils-run-compiler.spec.js @@ -0,0 +1,66 @@ +/*jshint jasmine: true, node: true */ +'use strict'; + +const mock = require('mock-require'); +const logger = require('winston'); + +describe('cli utils run compiler', () => { + const requirePath = '../cli/utils/run-compiler'; + let mockWebpack; + + beforeEach(() => { + mockWebpack = () => { + return { + run: (cb) => cb(null, { + toJson: () => ({ + errors: [], + warnings: [] + }) + }) + }; + }; + }); + + it('should handle stats errors and warnings', (done) => { + const errs = ['custom-error2']; + const wrns = ['custom-warning1']; + + mockWebpack = () => { + return { + run: (cb) => cb(null, { + toJson: () => ({ + errors: errs, + warnings: wrns + }) + }) + }; + }; + + spyOn(logger, 'error'); + spyOn(logger, 'warn'); + spyOn(logger, 'info'); + + const runCompiler = mock.reRequire(requirePath); + + runCompiler(mockWebpack, {}).then(() => { + expect(logger.error).toHaveBeenCalledWith(errs); + expect(logger.warn).toHaveBeenCalledWith(wrns); + expect(logger.info).toHaveBeenCalled(); + done(); + }); + }); + + it('should handle no stats errors and warnings', (done) => { + spyOn(logger, 'error'); + spyOn(logger, 'warn'); + spyOn(logger, 'info'); + + const runCompiler = mock.reRequire(requirePath); + + runCompiler(mockWebpack, {}).then(() => { + expect(logger.error).not.toHaveBeenCalled(); + expect(logger.warn).not.toHaveBeenCalled(); + done(); + }); + }); +}); diff --git a/test/cli-utils-stage-library-ts.spec.js b/test/cli-utils-stage-library-ts.spec.js new file mode 100644 index 00000000..4e1b4a5f --- /dev/null +++ b/test/cli-utils-stage-library-ts.spec.js @@ -0,0 +1,88 @@ +/*jshint jasmine: true, node: true */ +'use strict'; + +const fs = require('fs-extra'); +const glob = require('glob'); +const mock = require('mock-require'); +const sass = require('node-sass'); + +describe('cli utils prepare-library-package', () => { + let util; + + beforeEach(() => { + spyOn(fs, 'copySync').and.returnValue(); + spyOn(fs, 'removeSync').and.returnValue(); + mock('../config/sky-pages/sky-pages.config', { + spaPath: () => '', + spaPathTempSrc: () => '', + spaPathTemp: () => '' + }); + util = require('../cli/utils/stage-library-ts'); + }); + + afterEach(() => { + mock.stopAll(); + }); + + it('should return a function', () => { + expect(typeof util).toEqual('function'); + }); + + it('should copy source files from the public folder', () => { + spyOn(glob, 'sync').and.returnValue([]); + util(); + expect(fs.copySync).toHaveBeenCalled(); + }); + + it('should delete non-dist files', () => { + spyOn(glob, 'sync').and.callFake((pattern) => { + if (pattern.match('.spec.')) { + return ['index.spec.ts']; + } else { + return []; + } + }); + + util(); + expect(fs.removeSync).toHaveBeenCalled(); + }); + + it('should fetch file contents for html and css files within angular components', () => { + let finalContents; + + spyOn(glob, 'sync').and.callFake(pattern => { + if (pattern.match('.spec.')) { + return []; + } else { + return ['index.component.ts']; + } + }); + + spyOn(fs, 'readFileSync').and.callFake(filePath => { + if (filePath === 'index.component.ts') { + return ` + @Component({ + templateUrl: 'template.component.html', + styleUrls: ['template.component.scss'] + }) + export class SampleComponent { } + `; + } + + if (filePath === 'template.component.html') { + return '

'; + } + }); + spyOn(fs, 'writeFileSync').and.callFake((file, contents) => { + finalContents = contents; + }); + + spyOn(sass, 'renderSync').and.returnValue({ + css: 'p { color: black; }' + }); + + util(); + expect(finalContents.match('

')).not.toEqual(null); + expect(finalContents.match('p { color: black; }')).not.toEqual(null); + }); +}); diff --git a/test/config-sky-pages.spec.js b/test/config-sky-pages.spec.js index 26c6a2a1..ea75e834 100644 --- a/test/config-sky-pages.spec.js +++ b/test/config-sky-pages.spec.js @@ -25,7 +25,7 @@ describe('config sky-pages', () => { it('should load the config files that exist in order', () => { const tempSpaReference = 'SPA_REFERENCE'; - const readFileSync = fs.readJsonSync; + const readJsonSync = fs.readJsonSync; const existsSync = fs.existsSync; spyOn(logger, 'info'); diff --git a/test/config-webpack-build-public-library.spec.js b/test/config-webpack-build-public-library.spec.js new file mode 100644 index 00000000..c2d77302 --- /dev/null +++ b/test/config-webpack-build-public-library.spec.js @@ -0,0 +1,42 @@ +/*jshint jasmine: true, node: true */ +'use strict'; + +const runtimeUtils = require('../utils/runtime-test-utils'); + +describe('config webpack build public library', () => { + const configPath = '../config/webpack/build-public-library.webpack.config'; + let skyPagesConfig; + + beforeEach(() => { + skyPagesConfig = { + skyux: { + mode: 'advanced' + }, + runtime: runtimeUtils.getDefaultRuntime() + }; + }); + + it('should expose a getWebpackConfig method', () => { + const lib = require(configPath); + expect(typeof lib.getWebpackConfig).toEqual('function'); + }); + + it('should return a config object', () => { + const lib = require(configPath); + const config = lib.getWebpackConfig(skyPagesConfig); + expect(config).toEqual(jasmine.any(Object)); + }); + + it('should use the name from skyuxconfig for the name of the module', () => { + skyPagesConfig.skyux.name = 'sample-app'; + const lib = require(configPath); + const config = lib.getWebpackConfig(skyPagesConfig); + expect(config.output.library).toBe('sample-app'); + }); + + it('should use a default name if it is not set in skyuxconfig', () => { + const lib = require(configPath); + const config = lib.getWebpackConfig(skyPagesConfig); + expect(config.output.library).toBe('SkyAppLibrary'); + }); +}); diff --git a/test/config-webpack-test.spec.js b/test/config-webpack-test.spec.js index f4ae25fe..41ca45e6 100644 --- a/test/config-webpack-test.spec.js +++ b/test/config-webpack-test.spec.js @@ -3,7 +3,7 @@ const runtimeUtils = require('../utils/runtime-test-utils'); -describe('config webpack common', () => { +describe('config webpack test', () => { let argv; let skyPagesConfig; diff --git a/test/host-utils.spec.js b/test/host-utils.spec.js index 296128de..99f6167f 100644 --- a/test/host-utils.spec.js +++ b/test/host-utils.spec.js @@ -73,7 +73,7 @@ describe('host-utils', () => { }; } - return readFileSync(filename, encoding); + return readJsonSync(filename, encoding); }); const externals = { diff --git a/test/sky-pages-out-skyux2.spec.js b/test/sky-pages-out-skyux2.spec.js index f90e0a55..8990297e 100644 --- a/test/sky-pages-out-skyux2.spec.js +++ b/test/sky-pages-out-skyux2.spec.js @@ -18,6 +18,10 @@ describe('@blackbaud/skyux-builder', () => { cmd: 'build', lib: 'build' }, + 'build-public-library': { + cmd: 'build-public-library', + lib: 'build-public-library' + }, 'e2e': { cmd: 'e2e', lib: 'e2e'