Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

initial commit - version 0.0.1

  • Loading branch information...
commit 890b2e40fb99d03bfe8cafb7531a6e2f8bf67cc6 0 parents
@mateuszwozniak authored
Showing with 1,443 additions and 0 deletions.
  1. +4 −0 .gitignore
  2. +190 −0 README.md
  3. +52 −0 bin/glint
  4. +3 −0  index.js
  5. +35 −0 package.json
  6. +58 −0 src/BundleParser.js
  7. +80 −0 src/FileProcessor.js
  8. +84 −0 src/Glint.js
  9. +50 −0 src/IndexBuilder.js
  10. +87 −0 src/PackageBuilder.js
  11. +182 −0 src/PackageWriter.js
  12. +83 −0 src/adapters/express.js
  13. +5 −0 src/adapters/index.js
  14. +8 −0 src/index.js
  15. +9 −0 src/postprocessors/clean-css.js
  16. +4 −0 src/postprocessors/index.js
  17. +10 −0 src/postprocessors/uglify-js.js
  18. +4 −0 src/preprocessors/index.js
  19. +29 −0 src/preprocessors/jade.js
  20. +28 −0 src/preprocessors/stylus.js
  21. +111 −0 test/e2e/createPackages.specs.js
  22. +2 −0  test/fixtures/assets/file1.js
  23. +2 −0  test/fixtures/assets/file2.js
  24. +4 −0 test/fixtures/assets/gallery.css
  25. +2 −0  test/fixtures/assets/gallery.js
  26. +12 −0 test/fixtures/assets/styles.styl
  27. +6 −0 test/fixtures/assets/template.jade
  28. +1 −0  test/fixtures/assets/variables.styl
  29. +48 −0 test/fixtures/config.js
  30. +10 −0 test/fixtures/filesIndex.js
  31. +9 −0 test/fixtures/filesList.js
  32. +14 −0 test/fixtures/finalPackages.js
  33. +20 −0 test/fixtures/manifestContent.js
  34. +34 −0 test/fixtures/parsedPackages.js
  35. +4 −0 test/fixtures/processedAssets/gallery.css
  36. +2 −0  test/fixtures/processedAssets/gallery.js
  37. +1 −0  test/fixtures/processedAssets/gallery.package.css
  38. +1 −0  test/fixtures/processedAssets/gallery.package.js
  39. +11 −0 test/fixtures/processedAssets/styles.styl.css
  40. +57 −0 test/fixtures/processedAssets/template.jade.js
  41. +16 −0 test/setup.js
  42. +32 −0 test/unit/BundleParser.specs.js
  43. +24 −0 test/unit/IndexBuilder.specs.js
  44. +15 −0 test/unit/PackageBuilder.specs.js
4 .gitignore
@@ -0,0 +1,4 @@
+.DS_Store
+node_modules/
+.idea/
+test/fixtures/output/
190 README.md
@@ -0,0 +1,190 @@
+#Glint
+Glint is an asset (css, js, whatever-you-need) management tool for your project.
+It helps you better organize files you serve to browser, as well as process them with any processor you need, even with your own one.
+
+For now it works with express.js but you can simply adjust it to your project and technology.
+
+##Features:
+- creating named 'packages' from your assets (e.g. jquery-ui, my-tools, etc.)
+- merging, processing, compressing files for production environment
+- generating separate files for development process
+- watching for changes and rebuilding suitable packages
+- switching between merged, compressed files and separate files with query parameter (in express.js)
+- support for jade and stylus (more in progress)
+
+##How it works:
+1. Create config file with packages (or simply files) you want to serve to your application
+2. Specify output directory in config
+3. Specify preprocessors (optional) and postprocessors (optional)
+4. Run glint
+5. Inject created packages to your project.
+
+##How to do that:
+###Installation
+```javascript
+npm install glint
+```
+###Creating config file
+```javascript
+
+module.exports = {
+ /** directory with files you want to use in your project (relative path to this file) */
+ assetsDir: 'assets',
+ /** directory where processed files will be saved (relative path to this file) */
+ outputDir: 'output',
+ /** path to file with information about generated packages (relative path to this file) */
+ manifest: 'output/manifest.json',
+ /** preprocessors you want to use (explained below) */
+ preprocessors: {},
+ /** postprocessors you want to use (explained below) */
+ postprocessors: {},
+
+ /** definitions with your packages */
+ packages: [
+ {
+ /** package name - you can use it as requirement for another package */
+ name: 'core',
+ /** generate output file for this package - only packages with final set to true will be saved to output directory
+ this gives you possibility to create a bunch of small, reusable packages which will be included in final packages */
+ final: false,
+ /* list of files for this package - files paths are relative to directory passed as 'assetsDir', so in this case
+ we are using files: assets/file1.js, assets/file2.js */
+ files: [
+ 'file1.js',
+ 'file2.js'
+ ]
+ },
+ {
+ name: 'gallery',
+ final: true,
+ /** list of required packages - files from packages listed here will be included before files from this package */
+ require: ['templates'],
+ files: [
+ 'gallery.js',
+ 'gallery.css'
+ ]
+ },
+ {
+ name: 'templates',
+ final: false,
+ files: [
+ 'template.jade',
+ 'styles.styl'
+ ]
+ }
+ ]
+};
+```
+
+###Preprocessors
+Preprocessors are functions that will receive raw content of your files, and will generate from this content final file content (e.g. generate css files from stylus files).
+For now jade and stylus are supported.
+Definition looks like this:
+```javascript
+var preprocessors = require('glint').preprocessors;
+var stylus = preprocessors.stylus;
+var jade = preprocessors.jade;
+
+module.exports = {
+ ...
+ preprocessors: {
+ /** for files with extensions 'styl' use stylus */
+ styl: stylus,
+
+ /** for files with extensions 'jade' use jade
+ you can also pass object with preprocessor as fn and options for this preprocessor */
+ jade: {
+ fn: jade,
+ options: {
+ pretty: true
+ }
+ }
+ },
+ ...
+};
+
+```
+For now you can use only one preprocessor per file extension.
+
+###Postprocessors
+Postprocessors are functions that will receive content of your merged packages, and will generate from this content final package content (e.g. uglify/compress merged package)
+Definition looks like this:
+```javascript
+var postprocessors = require('glint').postprocessors;
+var cleanCss = postprocessors['clean-css'];
+var uglifyJs = postprocessors['uglify-js'];
+
+module.exports = {
+ ...
+ postprocessors: {
+ /** for css files use clean-css */
+ css: [cleanCss],
+ /** for js files use uglify-js - you can also pass options here */
+ js: [{
+ fn: uglifyJs,
+ options: {
+ unsafe: true
+ }
+ }]
+ },
+ ...
+};
+
+
+```
+
+###Run Glint
+```javascript
+./node_modules/.bin/glint --config path/to/config/file.js
+```
+This will create your packages, preprocess and postprocess them, save them to output directories and create manifest.json file.
+Now you have your packages ready to use. You just have to use them in your application
+
+###Using packages with express.js
+```javascript
+// file clientlibs/config.js
+module.exports = {
+ assetsDir: 'assets',
+ outputDir: 'output',
+ manifest: 'output/manifest.json',
+ packages: [
+ {
+ name: 'gallery',
+ final: true,
+ files: [
+ 'gallery.css',
+ 'jquery.js',
+ 'gallery.js'
+ ]
+ }
+ ]
+};
+
+```
+
+```javascript
+// file server.js
+
+var express = require('express');
+
+// import glint adapter for express
+var glintForExpress = require('glint').adapters.express;
+
+var app = express();
+// add glint to express application
+app.use(glintForExpress('output/mainfest.json'));
+
+app.get('/', function (req, res) {
+ // now you have assets variable in res.locals
+ // you can access final packages via package_name.files_category
+ var body = '<html><head>' + res.locals.assets.gallery.css + '</head><body>' + res.locals.assets.gallery.js + '</body></html>';
+ res.setHeader('Content-Type', 'text/html');
+ res.setHeader('Content-Length', body.length);
+ res.end(body);
+});
+
+app.listen(3000);
+console.log('Listening on port 3000');
+```
+
+TBC...
52 bin/glint
@@ -0,0 +1,52 @@
+#!/usr/bin/env node
+
+'use strict';
+
+var path = require('path');
+var program = require('commander');
+var package_data = require('../package.json');
+var Glint = require('../src/Glint');
+
+
+program
+ .version(package_data.version)
+ .option('-c, --config <path>', 'path to config file', String)
+ .option('-w, --watch', 'watch for file changes', Boolean)
+ .option('-d, --dev', 'use development mode - do not merge and process files', Boolean);
+
+program.parse(process.argv);
+var config = initConfig();
+var glint = new Glint(config);
+glint.run();
+
+function initConfig() {
+ var config;
+ if (!program.config) {
+ console.error('[ERROR] Can not start without config file!');
+ process.exit(-1);
+ }
+
+ var absConfigPath = path.normalize(path.join(process.cwd(), program.config));
+ try {
+ config = require(absConfigPath);
+ } catch (ex) {
+ console.log('[ERROR] Can not open config file: ', configPath);
+ process.exit(-1);
+ }
+
+ var configDir = path.dirname(absConfigPath);
+ var outputPath = path.join(configDir, config.outputDir);
+ config.outputDir = path.normalize(outputPath);
+ config.baseDir = configDir;
+
+ var assetsPath = path.join(configDir, config.assetsDir);
+ config.assetsDir = path.normalize(assetsPath);
+
+ config.watch = program.watch;
+ config.dev = program.dev;
+
+ config.preprocessors = config.preprocessors || {};
+ config.postprocessors = config.postprocessors || {};
+
+ return config;
+}
3  index.js
@@ -0,0 +1,3 @@
+'use strict';
+
+module.exports = require('./src');
35 package.json
@@ -0,0 +1,35 @@
+{
+ "name": "glint",
+ "version": "0.0.1",
+ "author": "Mateusz Wozniak <mateusz@wozniak.io>",
+ "description": "Glint is an asset (css, js, whatever-you-need) management that helps you organize and manage your client side files",
+ "keywords": ["packer", "build", "compress", "glint", "assets"],
+ "dependencies": {
+ "async": "0.2.6",
+ "commander": "1.1.x",
+ "fs.notify": "0.0.x",
+ "fs-extra": "0.5.x",
+ "nib": "0.9.x",
+ "stylus": "0.29.x",
+ "jade": "0.28.x",
+ "clean-css": "0.10.x",
+ "uglify-js": "2.2.x"
+ },
+ "devDependencies": {
+ "mocha": "*",
+ "chai": "*"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/mateuszwozniak/glint.git"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ },
+ "scripts": {
+ "test": "./node_modules/.bin/mocha --timeout 5000 --reporter spec test/*.js test/unit/*.js test/e2e/*.js"
+ },
+ "bin": {
+ "glint": "./bin/glint"
+ }
+}
58 src/BundleParser.js
@@ -0,0 +1,58 @@
+'use strict';
+module.exports = BundleParser;
+
+var fs = require('fs');
+var path = require('path');
+
+function BundleParser(rawBundle, assetsDir) {
+
+ this.parse = function () {
+ updateFilesPaths();
+ checkFiles();
+ };
+
+ function updateFilesPaths() {
+ rawBundle.forEach(updateFilesPathsInPackage);
+ }
+
+ function updateFilesPathsInPackage(pkg) {
+ var files = pkg.files.map(function updateFilePath(filePath) {
+ var pth = path.join(assetsDir, filePath);
+ return path.normalize(pth);
+ });
+ pkg.files = files;
+ }
+
+ function checkFiles() {
+ var fileChecker = new FileChecker(rawBundle);
+ if (!fileChecker.check()) {
+ throw new Error('File : ' + fileChecker.getLastFailedFile() + 'does not exists!');
+ }
+ }
+}
+
+function FileChecker(packages) {
+ var lastFailedFile;
+
+ this.getLastFailedFile = function () {
+ return lastFailedFile;
+ };
+
+ this.check = function () {
+ return packages.every(checkFilesFromPackage);
+ };
+
+ function checkFilesFromPackage(pkg) {
+ var files = pkg.files;
+
+ for (var i = 0, len = files.length; i < len; i++) {
+ if (!fs.existsSync(files[i])) {
+ lastFailedFile = files[i];
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
+
80 src/FileProcessor.js
@@ -0,0 +1,80 @@
+'use strict';
+module.exports = FileProcessor;
+
+var fs = require('fs');
+var path = require('path');
+
+function FileProcessor(files, config) {
+ var filesCount = files.length;
+ var doneCallback = function () {};
+ var readyFiles = 0;
+ var processedFiles = {};
+
+ this.getProcessedFiles = function () {
+ return processedFiles;
+ };
+
+ this.process = function (done) {
+ doneCallback = done;
+ files.forEach(processFile);
+ };
+
+ this.processFile = processFile;
+
+ function processFile(file, cb) {
+ var preprocessors = config.preprocessors;
+ var fileContent = fs.readFileSync(file, 'utf-8');
+ var extension = file.split('.').pop();
+ if (preprocessors.hasOwnProperty(extension)) {
+ processWithProcessor(file, fileContent, preprocessors[extension], cb);
+ } else {
+ storeProcessedFile(fileContent, file);
+ typeof cb === 'function' && cb();
+ }
+ }
+
+ function storeProcessedFile(content, filePath, outputExtension) {
+ var newFileName = path.basename(filePath);
+ var extension = newFileName.split('.').pop();
+ if (outputExtension && (extension !== outputExtension)) {
+ newFileName += '.' + outputExtension;
+ extension = outputExtension;
+ }
+
+ processedFiles[filePath] = {
+ content: content,
+ name: newFileName,
+ extension: extension
+ };
+
+ readyFiles++;
+
+ if (readyFiles === filesCount) {
+ doneCallback();
+ }
+ }
+
+ function processWithProcessor(filePath, fileContent, processor, cb) {
+ var processorFn;
+ var processorOptions = {};
+
+ if (typeof processor === 'function') {
+ processorFn = processor;
+ } else if (processor.fn === 'function') {
+ processorFn = processor.fn;
+ processorOptions = processor.options || {};
+ } else {
+ throw new Error('Can not use preprocessor because it is not callable:', processor);
+ }
+
+ processorFn(filePath, fileContent, processorOptions, function (err, output) {
+ if (err) {
+ console.log('[ERROR] Preprocessing error', err);
+ process.exit(-1);
+ }
+
+ storeProcessedFile(output, filePath, processor.outputExtension);
+ typeof cb === 'function' && cb();
+ });
+ }
+}
84 src/Glint.js
@@ -0,0 +1,84 @@
+'use strict';
+module.exports = Glint;
+
+var FsNotifier = require('fs.notify');
+
+var PackageBuilder = require('./PackageBuilder');
+var BundleParser = require('./BundleParser');
+var IndexBuilder = require('./IndexBuilder');
+var FileProcessor = require('./FileProcessor');
+var PackageWriter = require('./PackageWriter');
+
+function Glint(config) {
+ if (config.dev) {
+ console.log('[INFO] Running in development mode');
+ }
+
+ var packageWriter;
+ var processedFiles;
+ var fileProcessor;
+ var finalPackages;
+
+ this.run = run;
+
+ function run() {
+ console.log('[INFO] Parsing packages');
+ var bundleParser = new BundleParser(config.packages, config.assetsDir);
+ bundleParser.parse();
+
+ console.log('[INFO] Building target packages');
+ var packageBuilder = new PackageBuilder(config.packages);
+ packageBuilder.build();
+ finalPackages = packageBuilder.getPackages();
+
+ console.log('[INFO] Building file indexes');
+ var indexBuilder = new IndexBuilder(finalPackages);
+ indexBuilder.build();
+ var index = indexBuilder.getIndex();
+ var files = indexBuilder.getFileList();
+
+ console.log('[INFO] Processing files');
+ fileProcessor = new FileProcessor(files, config);
+ fileProcessor.process(writePackages);
+ }
+
+ function writePackages() {
+ console.log('[INFO] Writing packages');
+ processedFiles = fileProcessor.getProcessedFiles();
+ packageWriter = new PackageWriter(finalPackages, processedFiles, config);
+ packageWriter.writeFiles();
+ packageWriter.writePackages();
+ packageWriter.writeManifest();
+
+ if (config.watch) {
+ console.log('[INFO] Watching for file changes...');
+ var notifier = new FsNotifier(files);
+ notifier.on('change', fileChanged);
+ }
+ }
+
+ function fileChanged(filePath) {
+ console.log('[INFO] File changed:', filePath);
+ fileProcessor.processFile(filePath, updatePackagesForChangedFile);
+
+ function updatePackagesForChangedFile() {
+ packageWriter.writeFile(filePath);
+
+ var packagesWithChangedFile = index[filePath];
+
+ // save only js or css bundle
+ var extensionToWrite = {};
+ if (processedFiles[filePath].name.split('.').pop() === 'js') {
+ extensionToWrite.js = true;
+ } else {
+ extensionToWrite.css = true;
+ }
+
+ if (packagesWithChangedFile) {
+ packageWriter.writePackages(packagesWithChangedFile, extensionToWrite);
+ packageWriter.writeManifest();
+ }
+ }
+ }
+
+}
50 src/IndexBuilder.js
@@ -0,0 +1,50 @@
+'use strict';
+
+module.exports = IndexBuilder;
+
+function IndexBuilder(packages) {
+ var index = {};
+ var files = [];
+
+ this.getIndex = function () {
+ return index;
+ };
+
+ this.getFileList = function () {
+ return files;
+ };
+
+ this.build = function () {
+ packages.forEach(buildDependenciesForPackage);
+ packages.forEach(storePackageFiles);
+ };
+
+ function buildDependenciesForPackage(pkg) {
+ var packageFiles = pkg.files;
+ var packageName = pkg.name;
+
+ for (var i = 0, fCount = packageFiles.length; i < fCount; i++) {
+ addDependency(packageFiles[i], packageName);
+ }
+ }
+
+ function addDependency(key, packageName) {
+ if (!index.hasOwnProperty(key)) {
+ index[key] = [];
+ }
+
+ if (index[key].indexOf(packageName) === -1) {
+ index[key].push(packageName);
+ }
+ }
+
+ function storePackageFiles(pkg) {
+ var packageFiles = pkg.files;
+ for (var i = 0, len = packageFiles.length; i < len; i++) {
+ if (files.indexOf(packageFiles[i]) === -1) {
+ files.push(packageFiles[i]);
+ }
+ }
+ }
+
+}
87 src/PackageBuilder.js
@@ -0,0 +1,87 @@
+'use strict';
+
+module.exports = PackageBuilder;
+
+function PackageBuilder(packages) {
+ packages = packages.slice();
+
+ var packagesByName = {};
+ var packagesTree = {};
+ var flatPackages = {};
+ var finalPackages = [];
+
+ this.getPackages = function () {
+ return finalPackages;
+ };
+
+ this.build = function () {
+ indexPackagesByName();
+ buildPackagesTree();
+ flattenPackages();
+ buildFinalPackages();
+ };
+
+ function indexPackagesByName() {
+ for (var i = 0, len = packages.length; i < len; i++) {
+ packagesByName[packages[i].name] = packages[i];
+ }
+ }
+
+ function buildPackagesTree() {
+ for (var i = 0, len = packages.length; i < len; i++) {
+ buildPackageRequirements(packages[i].name);
+ }
+ }
+
+ function buildPackageRequirements(pkgName) {
+ if (packagesTree.hasOwnProperty(pkgName)) {
+ return packagesTree[pkgName];
+ }
+
+ var pkg = packagesByName[pkgName];
+ if (pkg.require) {
+ var requirements = [];
+ for (var i = 0, len = pkg.require.length; i < len; i++) {
+ requirements.push(buildPackageRequirements(pkg.require[i]));
+ }
+ // replace requirement names with build tree
+ pkg.require = requirements;
+ }
+ packagesTree[pkgName] = pkg;
+ return pkg;
+ }
+
+ function flattenPackages() {
+ for (var i = 0, len = packages.length; i < len; i++) {
+ squashPackage(packagesTree[packages[i].name]);
+ }
+ }
+
+ function squashPackage(pkg) {
+ var files = [];
+ getRequirementsFiles(pkg)
+ flatPackages[pkg.name] = files;
+
+ // deep first search based squashing
+ function getRequirementsFiles(requirement) {
+ if (requirement.require) {
+ for (var i = 0, len = requirement.require.length; i < len; i++) {
+ getRequirementsFiles(requirement.require[i]);
+ }
+ }
+ files = files.concat(requirement.files);
+ }
+ }
+
+ function buildFinalPackages() {
+ packages.forEach(function buildIfFinal(pkg) {
+ if (pkg.final) {
+ finalPackages.push({
+ name: pkg.name,
+ files: flatPackages[pkg.name]
+ });
+ }
+ });
+ }
+
+}
182 src/PackageWriter.js
@@ -0,0 +1,182 @@
+/**
+ * @todo move merging and postprocessing outside PackageWriter - it's not its responsibility
+ */
+'use strict';
+
+module.exports = PackageWriter;
+
+var path = require('path');
+var fs = require('fs');
+var createHash = require('crypto').createHash;
+var fsx = require('fs-extra');
+
+function PackageWriter(packages, processedFiles, config) {
+ var manifest = {dev: {}, prod: {}};
+ initDirectoryStructure();
+
+ function initDirectoryStructure() {
+ removeOldDirectories();
+ createNewDirectories();
+ }
+
+ function removeOldDirectories() {
+ fsx.removeSync(path.join(config.outputDir, 'sources/css'));
+ fsx.removeSync(path.join(config.outputDir, 'sources/js'));
+ fsx.removeSync(path.join(config.outputDir, 'build/css'));
+ fsx.removeSync(path.join(config.outputDir, 'build/js'));
+ }
+
+ function createNewDirectories() {
+ fsx.mkdirsSync(path.join(config.outputDir, 'sources/css'));
+ fsx.mkdirsSync(path.join(config.outputDir, 'sources/js'));
+ fsx.mkdirsSync(path.join(config.outputDir, 'build/css'));
+ fsx.mkdirsSync(path.join(config.outputDir, 'build/js'));
+ }
+
+ this.writeManifest = function () {
+ updateDevPackagesInManifest();
+ if (config.dev) {
+ manifest.prod = manifest.dev;
+ }
+ var pth = path.join(config.baseDir, config.manifest);
+ fsx.writeJsonSync(pth, manifest);
+ };
+
+ function updateDevPackagesInManifest() {
+ packages.forEach(function (pkg) {
+ var files = splitPackageFiles(pkg);
+ manifest.dev[pkg.name] = {
+ css: getPathsForFiles(files.css),
+ js: getPathsForFiles(files.js)
+ };
+ });
+ }
+
+ function getPathsForFiles(files) {
+ return files.map(function (originalFilePath) {
+ var outputFile = processedFiles[originalFilePath];
+ return path.relative(config.outputDir, createDevFilePath(outputFile.name, outputFile.extension));
+ });
+ }
+
+ this.writeFiles = function () {
+ Object.keys(processedFiles).forEach(writeFile);
+ };
+
+ this.writeFile = writeFile;
+
+ function writeFile(originalFilePath) {
+ var outputFile = processedFiles[originalFilePath];
+ var outputPath = createDevFilePath(outputFile.name, outputFile.extension);
+ fs.writeFileSync(outputPath, outputFile.content, 'utf-8');
+ }
+
+ function createDevFilePath(name, extension) {
+ return path.join(config.outputDir, 'sources', extension, name);
+ }
+
+ this.writePackages = function (packageNames, fileTypes) {
+ if (config.dev) {
+ return;
+ }
+
+ var packagesToWrite = getPackagesToWrite(packageNames);
+ var fileTypesToWrite = fileTypes || {js: true, css: true};
+
+ packagesToWrite.forEach(function (pkg) {
+ writePackage(pkg, fileTypesToWrite);
+ });
+ };
+
+ function getPackagesToWrite(packageNames) {
+ if (packageNames) {
+ return packages.filter(function (pkg) {
+ return packageNames.indexOf(pkg.name) !== -1;
+ });
+ }
+ return packages;
+ }
+
+ function writePackage(pkg, extensions) {
+ var packageFiles = splitPackageFiles(pkg);
+ var css = mergeFiles(packageFiles.css);
+ var js = mergeFiles(packageFiles.js, ';');
+
+ css = postProcess(css, 'css');
+ js = postProcess(js, 'js');
+
+ manifest.prod[pkg.name] = manifest.prod[pkg.name] || {};
+ if (extensions.css) {
+ writePackageFile(pkg.name, css, 'css');
+ }
+ if (extensions.js) {
+ writePackageFile(pkg.name, js, 'js');
+ }
+ console.log('[INFO] Package', pkg.name, 'written.');
+ }
+
+ function splitPackageFiles(pkg) {
+ var css = [];
+ var js = [];
+ var files = pkg.files;
+
+ for (var i = 0, len = files.length; i < len; i++) {
+ if (processedFiles[files[i]].extension === 'css') {
+ css.push(files[i]);
+ } else {
+ js.push(files[i]);
+ }
+ }
+
+ return {
+ css: css,
+ js: js
+ };
+ }
+
+ function mergeFiles(files, separator) {
+ var output = [];
+ for (var i = 0, len = files.length; i < len; i++) {
+ output.push(processedFiles[files[i]].content);
+ }
+
+ return output.join(separator || '\n');
+ }
+
+ function writePackageFile(packageName, content, type) {
+ var filePath = createProdFilePath(packageName, type, createHashForContent(content));
+ manifest.prod[packageName][type] = path.relative(config.outputDir, filePath);
+ fs.writeFileSync(filePath, content, 'utf-8');
+ }
+
+ function createProdFilePath(packageName, extension, hash) {
+ var fileName = [packageName, hash, extension].join('.');
+ var filePath = path.join(config.outputDir, 'build', extension, fileName);
+ return filePath;
+ }
+
+
+ function postProcess(content, category) {
+ var processors = config.postprocessors[category];
+ if (!processors) {
+ return content;
+ }
+
+ processors.forEach(function processContent(postprocessor) {
+ if (typeof postprocessor === 'function') {
+ content = postprocessor(content, {});
+ } else if (typeof postprocessor === 'object' && postprocessor.fn === 'function') {
+ var options = postprocessor.options || {};
+ postprocessor.fn(content, postprocessor, options);
+ } else {
+ console.log('[WARNING] Can not use postprocessor: ', postprocessor, '(' + category + ')');
+ }
+ });
+
+ return content;
+ }
+
+ function createHashForContent(content) {
+ return createHash('md5').update(content).digest('hex')
+ }
+}
83 src/adapters/express.js
@@ -0,0 +1,83 @@
+'use strict';
+
+var path = require('path');
+var fs = require('fs');
+var FsNotifier = require('fs.notify');
+
+module.exports = function (manifestPath) {
+ if (manifestPath[0] !== '/') {
+ manifestPath = path.normalize(path.join(process.cwd(), manifestPath));
+ }
+
+ var assets;
+ var htmlAssets;
+
+ initAssets();
+ watchForManifestChanges();
+
+ function initAssets() {
+ assets = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
+ initHtmlAssets();
+ }
+
+ function initHtmlAssets() {
+ htmlAssets = {
+ dev: {},
+ prod: {}
+ };
+
+ Object.keys(assets.dev).forEach(function (pkgName) {
+ htmlAssets.dev[pkgName] = createPackageHtmls(assets.dev[pkgName]);
+ });
+
+ Object.keys(assets.prod).forEach(function (pkgName) {
+ htmlAssets.prod[pkgName] = createPackageHtmls(assets.prod[pkgName]);
+ });
+ }
+
+ function watchForManifestChanges() {
+ var notifier = new FsNotifier([manifestPath]);
+ notifier.on('change', initAssets);
+ }
+
+ function injectAssets(req, res, next) {
+ if (req.query.debugGlintAssets) {
+ res.locals.assets = htmlAssets.dev;
+ } else {
+ res.locals.assets = htmlAssets.prod;
+ }
+ next();
+ }
+
+ return injectAssets;
+};
+
+function createPackageHtmls(pkg) {
+ var output = {
+ css: createCssAssets(pkg.css),
+ js: createJsAssets(pkg.js)
+ };
+ return output;
+}
+
+function createCssAssets(files) {
+ if (!Array.isArray(files)) {
+ files = [files];
+ }
+
+ var output = files.map(function createLinkNode(file) {
+ return '<link type="text/css" rel="stylesheet" href="/' + file + '" />';
+ });
+ return output.join('\n');
+}
+
+function createJsAssets(files) {
+ if (!Array.isArray(files)) {
+ files = [files];
+ }
+
+ var output = files.map(function createLinkNode(file) {
+ return '<script type="text/javascript" src="/' + file + '"></script>';
+ });
+ return output.join('\n');
+}
5 src/adapters/index.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = {
+ express: require('./express.js')
+};
8 src/index.js
@@ -0,0 +1,8 @@
+'use strict';
+
+module.exports = {
+ Glint: require('./Glint'),
+ adapters: require('./adapters'),
+ preprocessors: require('./preprocessors'),
+ postprocessors: require('./postprocessors')
+};
9 src/postprocessors/clean-css.js
@@ -0,0 +1,9 @@
+'use strict';
+
+var cleanCss = require('clean-css');
+
+module.exports = function (content, options) {
+ options = options || {};
+ return cleanCss.process(content, options);
+};
+
4 src/postprocessors/index.js
@@ -0,0 +1,4 @@
+module.exports = {
+ 'clean-css': require('./clean-css'),
+ 'uglify-js': require('./uglify-js')
+};
10 src/postprocessors/uglify-js.js
@@ -0,0 +1,10 @@
+'use strict';
+
+var uglify = require('uglify-js');
+
+module.exports = function (content, options) {
+ options = options || {};
+ options.fromString = true;
+
+ return uglify.minify(content, options).code;
+};
4 src/preprocessors/index.js
@@ -0,0 +1,4 @@
+module.exports = {
+ jade: require('./jade'),
+ stylus: require('./stylus')
+};
29 src/preprocessors/jade.js
@@ -0,0 +1,29 @@
+'use strict';
+
+var path = require('path');
+var jade = require('jade');
+
+function Jade(filePath, content, options, cb) {
+ var fileName = path.basename(filePath);
+ var name = fileName.substr(0, fileName.lastIndexOf('.'));
+
+ options = options || {};
+ options.client = true;
+ options.filename = name;
+
+ var output;
+ try {
+ output = jade.compile(content, options).toString();
+ } catch (ex) {
+ console.log('[ERROR] Jade processing failed, file:', filePath, ex);
+ process.exit(-1);
+ }
+
+ output = output.replace('anonymous', name);
+ output = 'window.jade.' + name + ' = ' + output;
+ cb(null, output);
+}
+
+Jade.outputExtension = 'js';
+
+module.exports = Jade;
28 src/preprocessors/stylus.js
@@ -0,0 +1,28 @@
+'use strict';
+
+var stylus = require('stylus');
+var nib = require('nib');
+var path = require('path');
+
+function Stylus(filePath, content, options, cb) {
+ var styl = stylus(content)
+ .set('filename', filePath)
+ .use(nib())
+ .import('nib');
+
+ if (options.url) {
+ styl.define('url', stylus.url({ paths: options.url }));
+ }
+
+ styl.render(function (err, css) {
+ if (err) {
+ console.log('[ERROR] Stylus processing error: ', err);
+ process.exit(-1);
+ }
+ cb(null, css);
+ });
+}
+
+Stylus.outputExtension = 'css';
+
+module.exports = Stylus;
111 test/e2e/createPackages.specs.js
@@ -0,0 +1,111 @@
+var fs = require('fs');
+var path = require('path');
+var child_process = require('child_process');
+
+var cwd = process.cwd();
+var manifestPath = path.join(cwd, 'test/fixtures/output/manifest.json');
+var expectedManifestContent = require('../fixtures/manifestContent');
+
+before(function (done) {
+ var glint = child_process.exec('./bin/glint -c test/fixtures/config.js', function (err, stdout, stderr) {
+ if (err) {
+ throw err;
+ }
+ });
+ glint.on('exit', function () {
+ done();
+ });
+});
+
+describe('E2E: create packages', function () {
+
+ it('should write manifest file', function () {
+ expect(fs.existsSync(manifestPath)).to.be.true;
+ });
+
+ it('manifest should have correct content', function () {
+ var manifest = getManifestContent();
+ expect(manifest.dev).to.deep.equal(expectedManifestContent.dev);
+ expect(manifest.prod.gallery.js).to.match(expectedManifestContent.prod.gallery.js);
+ expect(manifest.prod.gallery.css).to.match(expectedManifestContent.prod.gallery.css);
+ });
+
+ it('should write files embeded in manifest', function () {
+ var manifest = getManifestContent();
+ expect(fs.existsSync(createOutputFilePath(manifest.dev.gallery.css[0])));
+ expect(fs.existsSync(createOutputFilePath(manifest.dev.gallery.css[1])));
+ expect(fs.existsSync(createOutputFilePath(manifest.dev.gallery.js[0])));
+ expect(fs.existsSync(createOutputFilePath(manifest.dev.gallery.js[1])));
+ expect(fs.existsSync(createOutputFilePath(manifest.prod.gallery.js)));
+ expect(fs.existsSync(createOutputFilePath(manifest.prod.gallery.css)));
+ });
+
+ it('should save sources as separate processed files', function () {
+ var file;
+ var fname;
+ var expectedFileContent;
+ var processedFileContent;
+ var manifest = getManifestContent();
+
+ file = manifest.dev.gallery.css[0];
+ fname = file.split('/').pop();
+ var expectedFileContent = getExpectedFileContent(fname);
+ var processedFileContent = getFileContent(createOutputFilePath(file));
+ expect(processedFileContent).to.equal(expectedFileContent);
+
+ file = manifest.dev.gallery.css[1];
+ fname = file.split('/').pop();
+ expectedFileContent = getExpectedFileContent(fname);
+ processedFileContent = getFileContent(createOutputFilePath(file));
+ expect(processedFileContent).to.equal(expectedFileContent);
+
+ file = manifest.dev.gallery.js[0];
+ fname = file.split('/').pop();
+ expectedFileContent = getExpectedFileContent(fname);
+ processedFileContent = getFileContent(createOutputFilePath(file));
+ expect(processedFileContent).to.equal(expectedFileContent);
+
+ file = manifest.dev.gallery.js[1];
+ fname = file.split('/').pop();
+ expectedFileContent = getExpectedFileContent(fname);
+ processedFileContent = getFileContent(createOutputFilePath(file));
+ expect(processedFileContent).to.equal(expectedFileContent);
+ });
+
+ it('should save processed files with correct content', function () {
+ var expectedFileContent;
+ var processedFileContent;
+ var manifest = getManifestContent();
+
+ expectedFileContent = getExpectedFileContent('gallery.package.css');
+ processedFileContent = getFileContent(createOutputFilePath(manifest.prod.gallery.css));
+ expect(processedFileContent).to.equal(expectedFileContent);
+
+ expectedFileContent = getExpectedFileContent('gallery.package.js');
+ processedFileContent = getFileContent(createOutputFilePath(manifest.prod.gallery.js));
+ expect(processedFileContent).to.equal(expectedFileContent);
+ });
+});
+
+function getManifestContent() {
+ var manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
+ return manifest;
+}
+
+function createOutputFilePath(pth) {
+ return path.join(cwd, 'test/fixtures/output', pth);
+}
+
+function getExpectedFileContent(fname) {
+ var path = createExpectedFilePath(fname);
+ return getFileContent(path);
+}
+
+function getFileContent(fpath) {
+ return fs.readFileSync(fpath, 'utf-8');
+}
+
+function createExpectedFilePath(fname) {
+ return path.join(cwd, 'test/fixtures/processedAssets', fname);
+}
+
2  test/fixtures/assets/file1.js
@@ -0,0 +1,2 @@
+/** file1.js */
+function file1() {}
2  test/fixtures/assets/file2.js
@@ -0,0 +1,2 @@
+/** file2.js */
+function file2() {}
4 test/fixtures/assets/gallery.css
@@ -0,0 +1,4 @@
+/** gallery.css */
+.gallery-css {
+ color: blue;
+}
2  test/fixtures/assets/gallery.js
@@ -0,0 +1,2 @@
+/** gallery.js */
+function gallery() {}
12 test/fixtures/assets/styles.styl
@@ -0,0 +1,12 @@
+@import 'variables'
+
+/** styles.styl */
+.first
+ background-color $color
+ font-size 12px
+ font-color blue
+ line-height 13px
+
+.second:after
+ border-radius 10px
+
6 test/fixtures/assets/template.jade
@@ -0,0 +1,6 @@
+// jade file
+div.test
+ ul#test-id
+ li first
+ li second
+ li third
1  test/fixtures/assets/variables.styl
@@ -0,0 +1 @@
+$color = red
48 test/fixtures/config.js
@@ -0,0 +1,48 @@
+var preprocessors = require('../../').preprocessors;
+var jade = preprocessors.jade;
+var stylus = preprocessors.stylus;
+
+var postprocessors = require('../../').postprocessors;
+var cleanCss = postprocessors['clean-css'];
+var uglifyJs = postprocessors['uglify-js'];
+
+module.exports = {
+ assetsDir: 'assets',
+ outputDir: 'output',
+ manifest: 'output/manifest.json',
+ preprocessors: {
+ styl: stylus,
+ jade: jade
+ },
+ postprocessors: {
+ css: [cleanCss],
+ js: [uglifyJs]
+ },
+ packages: [
+ {
+ name: 'core',
+ final: false,
+ files: [
+ 'file1.js',
+ 'file2.js'
+ ]
+ },
+ {
+ name: 'gallery',
+ final: true,
+ require: ['templates'],
+ files: [
+ 'gallery.js',
+ 'gallery.css'
+ ]
+ },
+ {
+ name: 'templates',
+ final: false,
+ files: [
+ 'template.jade',
+ 'styles.styl'
+ ]
+ }
+ ]
+};
10 test/fixtures/filesIndex.js
@@ -0,0 +1,10 @@
+var path = require('path');
+var cwd = process.cwd();
+
+var index = {};
+index[path.join(cwd, "test/fixtures/assets/template.jade")] = [ "gallery" ];
+index[path.join(cwd, "test/fixtures/assets/styles.styl")] = [ "gallery" ];
+index[path.join(cwd, "test/fixtures/assets/gallery.js")] = [ "gallery" ];
+index[path.join(cwd, "test/fixtures/assets/gallery.css")] = [ "gallery" ];
+
+module.exports = index;
9 test/fixtures/filesList.js
@@ -0,0 +1,9 @@
+var path = require('path');
+var cwd = process.cwd();
+
+module.exports = [
+ path.join(cwd, "test/fixtures/assets/template.jade"),
+ path.join(cwd, "test/fixtures/assets/styles.styl"),
+ path.join(cwd, "test/fixtures/assets/gallery.js"),
+ path.join(cwd, "test/fixtures/assets/gallery.css")
+];
14 test/fixtures/finalPackages.js
@@ -0,0 +1,14 @@
+var path = require('path');
+var cwd = process.cwd();
+
+module.exports = [
+ {
+ "name": "gallery",
+ "files": [
+ path.join(cwd, "test/fixtures/assets/template.jade"),
+ path.join(cwd, "test/fixtures/assets/styles.styl"),
+ path.join(cwd, "test/fixtures/assets/gallery.js"),
+ path.join(cwd, "test/fixtures/assets/gallery.css")
+ ]
+ }
+];
20 test/fixtures/manifestContent.js
@@ -0,0 +1,20 @@
+module.exports = {
+ dev: {
+ gallery: {
+ css: [
+ "sources/css/styles.styl.css",
+ "sources/css/gallery.css"
+ ],
+ js: [
+ "sources/js/template.jade.js",
+ "sources/js/gallery.js"
+ ]
+ }
+ },
+ prod: {
+ gallery: {
+ css: /build\/css\/gallery\.([0-9a-f]{32})\.css/,
+ js: /build\/js\/gallery\.([0-9a-f]{32})\.js/
+ }
+ }
+}
34 test/fixtures/parsedPackages.js
@@ -0,0 +1,34 @@
+'use strict';
+
+var path = require('path');
+var cwd = process.cwd();
+
+module.exports = [
+ {
+ "name": "core",
+ "final": false,
+ "files": [
+ path.join(cwd, "test/fixtures/assets/file1.js"),
+ path.join(cwd, "test/fixtures/assets/file2.js")
+ ]
+ },
+ {
+ "name": "gallery",
+ "final": true,
+ "require": [
+ "templates"
+ ],
+ "files": [
+ path.join(cwd, "test/fixtures/assets/gallery.js"),
+ path.join(cwd, "test/fixtures/assets/gallery.css")
+ ]
+ },
+ {
+ "name": "templates",
+ "final": false,
+ "files": [
+ path.join(cwd, "test/fixtures/assets/template.jade"),
+ path.join(cwd, "test/fixtures/assets/styles.styl")
+ ]
+ }
+];
4 test/fixtures/processedAssets/gallery.css
@@ -0,0 +1,4 @@
+/** gallery.css */
+.gallery-css {
+ color: blue;
+}
2  test/fixtures/processedAssets/gallery.js
@@ -0,0 +1,2 @@
+/** gallery.js */
+function gallery() {}
1  test/fixtures/processedAssets/gallery.package.css
@@ -0,0 +1 @@
+.first{background-color:red;font-size:12px;font-color:#00f;line-height:13px}.second:after{-webkit-border-radius:10px;border-radius:10px}.gallery-css{color:#00f}
1  test/fixtures/processedAssets/gallery.package.js
@@ -0,0 +1 @@
+function gallery(){}window.jade.template=function template(locals,attrs,escape,rethrow,merge){attrs=attrs||jade.attrs,escape=escape||jade.escape,rethrow=rethrow||jade.rethrow,merge=merge||jade.merge;var __jade=[{lineno:1,filename:"template"}];try{var buf=[];with(locals||{})__jade.unshift({lineno:1,filename:__jade[0].filename}),__jade.unshift({lineno:2,filename:__jade[0].filename}),buf.push("<!-- jade file-->"),__jade.shift(),__jade.unshift({lineno:2,filename:__jade[0].filename}),buf.push('<div class="test">'),__jade.unshift({lineno:void 0,filename:__jade[0].filename}),__jade.unshift({lineno:3,filename:__jade[0].filename}),buf.push('<ul id="test-id">'),__jade.unshift({lineno:void 0,filename:__jade[0].filename}),__jade.unshift({lineno:4,filename:__jade[0].filename}),buf.push("<li>"),__jade.unshift({lineno:void 0,filename:__jade[0].filename}),__jade.unshift({lineno:4,filename:__jade[0].filename}),buf.push("first"),__jade.shift(),__jade.shift(),buf.push("</li>"),__jade.shift(),__jade.unshift({lineno:5,filename:__jade[0].filename}),buf.push("<li>"),__jade.unshift({lineno:void 0,filename:__jade[0].filename}),__jade.unshift({lineno:5,filename:__jade[0].filename}),buf.push("second"),__jade.shift(),__jade.shift(),buf.push("</li>"),__jade.shift(),__jade.unshift({lineno:6,filename:__jade[0].filename}),buf.push("<li>"),__jade.unshift({lineno:void 0,filename:__jade[0].filename}),__jade.unshift({lineno:6,filename:__jade[0].filename}),buf.push("third"),__jade.shift(),__jade.shift(),buf.push("</li>"),__jade.shift(),__jade.shift(),buf.push("</ul>"),__jade.shift(),__jade.shift(),buf.push("</div>"),__jade.shift(),__jade.shift();return buf.join("")}catch(err){rethrow(err,__jade[0].filename,__jade[0].lineno)}};
11 test/fixtures/processedAssets/styles.styl.css
@@ -0,0 +1,11 @@
+/** styles.styl */
+.first {
+ background-color: #f00;
+ font-size: 12px;
+ font-color: #00f;
+ line-height: 13px;
+}
+.second:after {
+ -webkit-border-radius: 10px;
+ border-radius: 10px;
+}
57 test/fixtures/processedAssets/template.jade.js
@@ -0,0 +1,57 @@
+window.jade.template = function template(locals, attrs, escape, rethrow, merge) {
+attrs = attrs || jade.attrs; escape = escape || jade.escape; rethrow = rethrow || jade.rethrow; merge = merge || jade.merge;
+var __jade = [{ lineno: 1, filename: "template" }];
+try {
+var buf = [];
+with (locals || {}) {
+var interp;
+__jade.unshift({ lineno: 1, filename: __jade[0].filename });
+__jade.unshift({ lineno: 2, filename: __jade[0].filename });
+buf.push('<!-- jade file-->');
+__jade.shift();
+__jade.unshift({ lineno: 2, filename: __jade[0].filename });
+buf.push('<div class="test">');
+__jade.unshift({ lineno: undefined, filename: __jade[0].filename });
+__jade.unshift({ lineno: 3, filename: __jade[0].filename });
+buf.push('<ul id="test-id">');
+__jade.unshift({ lineno: undefined, filename: __jade[0].filename });
+__jade.unshift({ lineno: 4, filename: __jade[0].filename });
+buf.push('<li>');
+__jade.unshift({ lineno: undefined, filename: __jade[0].filename });
+__jade.unshift({ lineno: 4, filename: __jade[0].filename });
+buf.push('first');
+__jade.shift();
+__jade.shift();
+buf.push('</li>');
+__jade.shift();
+__jade.unshift({ lineno: 5, filename: __jade[0].filename });
+buf.push('<li>');
+__jade.unshift({ lineno: undefined, filename: __jade[0].filename });
+__jade.unshift({ lineno: 5, filename: __jade[0].filename });
+buf.push('second');
+__jade.shift();
+__jade.shift();
+buf.push('</li>');
+__jade.shift();
+__jade.unshift({ lineno: 6, filename: __jade[0].filename });
+buf.push('<li>');
+__jade.unshift({ lineno: undefined, filename: __jade[0].filename });
+__jade.unshift({ lineno: 6, filename: __jade[0].filename });
+buf.push('third');
+__jade.shift();
+__jade.shift();
+buf.push('</li>');
+__jade.shift();
+__jade.shift();
+buf.push('</ul>');
+__jade.shift();
+__jade.shift();
+buf.push('</div>');
+__jade.shift();
+__jade.shift();
+}
+return buf.join("");
+} catch (err) {
+ rethrow(err, __jade[0].filename, __jade[0].lineno);
+}
+}
16 test/setup.js
@@ -0,0 +1,16 @@
+var chai = require('chai');
+global.expect = chai.expect;
+var fsx = require('fs-extra');
+
+before(function () {
+ removeOutputDirectories();
+});
+after(function () {
+ removeOutputDirectories();
+});
+
+function removeOutputDirectories() {
+ fsx.removeSync('./test/fixtures/output/build');
+ fsx.removeSync('./test/fixtures/output/sources');
+ fsx.removeSync('./test/fixtures/output/manifest.json');
+}
32 test/unit/BundleParser.specs.js
@@ -0,0 +1,32 @@
+'use strict';
+
+var path = require('path');
+var cwd = process.cwd();
+
+var config = require('../fixtures/config');
+var configDir = path.join(cwd, 'test/fixtures');
+var assetsPath = path.join(configDir, config.assetsDir);
+config.assetsDir = path.normalize(assetsPath);
+var originalPackages = config.packages;
+
+var BundleParser = require('../../src/BundleParser');
+var parsedPackages = require('../fixtures/parsedPackages');
+
+describe('BundleParser', function () {
+
+ it('should throw error when file from package does not exists', function () {
+ config.packages = [{name: 'test', files: ['someNonExistingFile.js']}];
+ var bp = new BundleParser(config.packages, config.assetsDir);
+ expect(function () {
+ bp.parse();
+ }).to.throw(Error);
+ });
+
+ it('should update files paths in packages to absolute paths', function () {
+ config.packages = originalPackages;
+ var bp = new BundleParser(config.packages, config.assetsDir);
+ bp.parse();
+ expect(config.packages).to.deep.equal(parsedPackages);
+ });
+
+});
24 test/unit/IndexBuilder.specs.js
@@ -0,0 +1,24 @@
+'use strict';
+
+var IndexBuilder = require('../../src/IndexBuilder');
+var finalPackages = require('../fixtures/finalPackages');
+var filesIndex = require('../fixtures/filesIndex');
+var filesList = require('../fixtures/filesList');
+
+describe('IndexBuilder', function () {
+
+ it('should build index with package names for each file', function () {
+ var indexBuilder = new IndexBuilder(finalPackages);
+ indexBuilder.build();
+ var index = indexBuilder.getIndex();
+ expect(index).to.deep.equal(filesIndex);
+ });
+
+ it('should build list with all files to watch', function () {
+ var indexBuilder = new IndexBuilder(finalPackages);
+ indexBuilder.build();
+ var files = indexBuilder.getFileList();
+ expect(files).to.deep.equal(filesList);
+ });
+
+});
15 test/unit/PackageBuilder.specs.js
@@ -0,0 +1,15 @@
+'use strict';
+
+var PackageBuilder = require('../../src/PackageBuilder');
+var parsedPackages = require('../fixtures/parsedPackages');
+var finalPackages = require('../fixtures/finalPackages');
+
+describe('PackageBuilder', function () {
+
+ it('should build final packages with its dependencies', function () {
+ var packageBuilder = new PackageBuilder(parsedPackages);
+ packageBuilder.build();
+ var builtPackages = packageBuilder.getPackages();
+ expect(builtPackages).to.deep.equal(finalPackages);
+ });
+});
Please sign in to comment.
Something went wrong with that request. Please try again.