broccoli-eyeglass
Advanced tools
Comparing version 2.1.1 to 2.1.2
var path = require("path"); | ||
var fs = require("fs"); | ||
var mkdirp = require("mkdirp"); | ||
var CachingWriter = require("broccoli-caching-writer"); | ||
var BroccoliPlugin = require("broccoli-plugin"); | ||
var MergeTrees = require("broccoli-merge-trees"); | ||
var Eyeglass = require("eyeglass"); | ||
var sass = require("node-sass"); | ||
@@ -10,3 +12,6 @@ var glob = require("glob"); | ||
var EventEmitter = require("chained-emitter").EventEmitter; | ||
var FSTree = require("fs-tree-diff"); | ||
var walkSync = require("walk-sync"); | ||
function unique(array) { | ||
@@ -160,4 +165,17 @@ var o = {}; | ||
// | ||
function BroccoliSassCompiler(inputTrees, options) { | ||
CachingWriter.call(this, inputTrees, options); | ||
function BroccoliSassCompiler(inputTree, options) { | ||
if (Array.isArray(inputTree)) { | ||
if (inputTree.length > 1) { | ||
console.warn("Support for passing several trees to BroccoliSassCompiler has been removed.\n" + | ||
"Passing the trees to broccoli-merge-trees with the overwrite option set,\n" + | ||
"but you should do this yourself if you need to compile CSS files from them\n" + | ||
"or use the node-sass includePaths option if you need to import from them."); | ||
inputTree = new MergeTrees(inputTree, {overwrite: true, annotation: "Sass Trees"}); | ||
} else { | ||
inputTree = inputTree[0]; | ||
} | ||
} | ||
options = options || {}; | ||
options.persistentOutput = true; | ||
BroccoliPlugin.call(this, [inputTree], options); | ||
@@ -168,2 +186,5 @@ this.sass = sass; | ||
this.currentTree = new FSTree(); | ||
this.dependencies = {}; | ||
moveOption(this.options, this, "cssDir", "sassDir", | ||
@@ -197,3 +218,3 @@ "optionsGenerator", "fullException", | ||
} | ||
BroccoliSassCompiler.prototype = Object.create(CachingWriter.prototype); | ||
BroccoliSassCompiler.prototype = Object.create(BroccoliPlugin.prototype); | ||
BroccoliSassCompiler.prototype.constructor = BroccoliSassCompiler; | ||
@@ -310,23 +331,65 @@ BroccoliSassCompiler.prototype.logCompilationSuccess = function(details, result) { | ||
// function timeSince(time) { | ||
// var delta = process.hrtime(time); | ||
// var deltaNS = delta[0] * 1e9 + delta[1]; | ||
// return (deltaNS / 1e6).toFixed(2) + " ms"; | ||
// } | ||
BroccoliSassCompiler.prototype.build = function() { | ||
var inputPaths = this.inputPaths; | ||
var inputPath = this.inputPaths[0]; | ||
var outputPath = this.outputPath; | ||
// TODO: add inputPaths (incl sassDir for the first) to the includePaths option | ||
var self = this; | ||
var files = {}; | ||
for (var i = 0; i < inputPaths.length; i++) { | ||
// TODO: handle indented syntax files. | ||
files[inputPaths[i]] = removePathPrefix(inputPaths[i], this.filesInTree(inputPaths[i])); | ||
// var startTime = process.hrtime(); | ||
var entries = walkSync.entries(inputPath); | ||
if (this.options.includePaths) { | ||
this.options.includePaths.forEach(function(p) { | ||
entries = entries.concat(walkSync.entries(p)); | ||
}); | ||
} | ||
// TODO: limit concurrency? | ||
var promises = inputPaths.reduce(function (results, srcPath) { | ||
return results.concat(self.compileTree(srcPath, files[srcPath], outputPath)); | ||
}, []); | ||
var eyeglass = new Eyeglass(this.options); | ||
var moduleKeys = Object.keys(eyeglass.modules.collection); | ||
for (var m = 0; m < moduleKeys.length; m++) { | ||
var moduleSassEntries = walkSync.entries(eyeglass.modules.collection[moduleKeys[m]].sassDir); | ||
entries = entries.concat(moduleSassEntries); | ||
} | ||
return RSVP.all(promises); | ||
var nextTree = new FSTree.fromEntries(entries, {sortAndExpand: true}); | ||
var currentTree = this.currentTree; | ||
this.currentTree = nextTree; | ||
var patches = currentTree.calculatePatch(nextTree); | ||
//console.log("Building Cache State took", timeSince(startTime)); | ||
var self = this; | ||
this.events.on("compiled", function(details, result) { | ||
var depFiles = result.stats.includedFiles; | ||
for (var i = 0; i < depFiles.length; i++) { | ||
self.dependencies[details.sassFilename] = | ||
self.dependencies[details.sassFilename] || new Set(); | ||
self.dependencies[details.sassFilename].add(depFiles[i]); | ||
} | ||
}); | ||
// TODO: handle indented syntax files. | ||
var treeFiles = removePathPrefix(inputPath, this.filesInTree(inputPath)); | ||
treeFiles = treeFiles.filter(function(f, i) { | ||
if (self.dependencies[f] === undefined) { | ||
return true; | ||
} | ||
for (var p = 0; p < patches.length; p++) { | ||
var entry = patches[p][2]; | ||
if (self.dependencies[f].has(path.join(entry.basePath, entry.relativePath))) { | ||
return true; | ||
} | ||
} | ||
}); | ||
return RSVP.all(self.compileTree(inputPath, treeFiles, outputPath)); | ||
}; | ||
module.exports = BroccoliSassCompiler; |
{ | ||
"name": "broccoli-eyeglass", | ||
"description": "Sass compiler for Broccoli with Eyeglass Integration", | ||
"version": "2.1.1", | ||
"version": "2.1.2", | ||
"author": "Chris Eppstein <chris@eppsteins.net>", | ||
@@ -23,10 +23,13 @@ "main": "lib/index.js", | ||
"dependencies": { | ||
"broccoli-caching-writer": "^3.0.3", | ||
"broccoli-merge-trees": "^1.1.4", | ||
"broccoli-plugin": "^1.2.2", | ||
"chained-emitter": "^0.1.2", | ||
"colors": "^1.0.3", | ||
"eyeglass": "^1.0.0", | ||
"eyeglass": "^1.1.0", | ||
"fs-tree-diff": "^0.5.2", | ||
"glob": "^5.0.3", | ||
"mkdirp": "^0.5.1", | ||
"node-sass": "^3.9.3", | ||
"rsvp": "^3.0.21" | ||
"rsvp": "^3.0.21", | ||
"walk-sync": "^0.3.1" | ||
}, | ||
@@ -37,2 +40,3 @@ "devDependencies": { | ||
"eyeglass-dev-eslint": "*", | ||
"fixturify": "^0.3.0", | ||
"grunt": "^0.4.5", | ||
@@ -44,4 +48,6 @@ "grunt-release": "^0.12.0", | ||
"gulp-mocha": "^2.2.0", | ||
"jshint": "^2.8.0" | ||
"jshint": "^2.8.0", | ||
"mocha": "^3.0.2", | ||
"rimraf": "^2.5.4" | ||
} | ||
} |
@@ -15,2 +15,4 @@ /* Copyright 2016 LinkedIn Corp. Licensed under the Apache License, Version 2.0 (the "License"); | ||
var fs = require("fs"); | ||
var rimraf = require("rimraf"); | ||
var fixturify = require("fixturify"); | ||
var broccoli = require("broccoli"); | ||
@@ -26,10 +28,17 @@ var RSVP = require("rsvp"); | ||
function fixtureSourceDir(name) { | ||
return path.resolve(__dirname, "fixtures", name, "input"); | ||
return path.resolve(fixtureDir(name), "input"); | ||
} | ||
function fixtureOutputDir(name) { | ||
return path.resolve(__dirname, "fixtures", name, "output"); | ||
return path.resolve(fixtureDir(name), "output"); | ||
} | ||
function makeFixtures(name, files) { | ||
var dirname = fixtureDir(name); | ||
fs.mkdirSync(dirname); | ||
fixturify.writeSync(dirname, files); | ||
return dirname; | ||
} | ||
function build(builder) { | ||
@@ -164,8 +173,236 @@ return RSVP.Promise.resolve() | ||
/* | ||
moveOption(this.options, this, "cssDir", "sassDir", | ||
"optionsGenerator", "fullException", | ||
"verbose", "renderSync", | ||
"discover", "sourceFiles"); | ||
*/ | ||
describe("caching", function() { | ||
afterEach(function() { | ||
var tmpDirs = glob.sync(path.join(path.resolve(__dirname, "fixtures"),"**", "*.tmp")); | ||
tmpDirs.forEach(function(tmpDir) { | ||
rimraf.sync(tmpDir); | ||
}); | ||
}); | ||
it("caches when an unrelated file changes", function() { | ||
var sourceDir = fixtureSourceDir("basicProject"); | ||
var compiledFiles = []; | ||
var compiler = new EyeglassCompiler(sourceDir, { | ||
cssDir: ".", | ||
optionsGenerator: function(sassFile, cssFile, options, cb) { | ||
compiledFiles.push(sassFile); | ||
cb(cssFile, options); | ||
} | ||
}); | ||
var builder = new broccoli.Builder(compiler); | ||
return build(builder) | ||
.then(function(outputDir) { | ||
assertEqualDirs(outputDir, fixtureOutputDir("basicProject")); | ||
assert.equal(1, compiledFiles.length); | ||
var unusedSourceFile = path.join(sourceDir, "styles", "_unused.scss"); | ||
fs.writeFileSync(unusedSourceFile, "// changed but still not used."); | ||
return build(builder).then(function(outputDir2) { | ||
assert.equal(outputDir, outputDir2); | ||
assert.equal(1, compiledFiles.length); | ||
}); | ||
}); | ||
}); | ||
it("doesn't cache when there's a change", function() { | ||
var sourceDir = fixtureSourceDir("basicProject"); | ||
var compiledFiles = []; | ||
var compiler = new EyeglassCompiler(sourceDir, { | ||
cssDir: ".", | ||
optionsGenerator: function(sassFile, cssFile, options, cb) { | ||
compiledFiles.push(sassFile); | ||
cb(cssFile, options); | ||
} | ||
}); | ||
var builder = new broccoli.Builder(compiler); | ||
return build(builder) | ||
.then(function(outputDir) { | ||
assertEqualDirs(outputDir, fixtureOutputDir("basicProject")); | ||
assert.equal(1, compiledFiles.length); | ||
var sourceFile = path.join(sourceDir, "styles", "foo.scss"); | ||
var originalSource = fs.readFileSync(sourceFile); | ||
var newSource = "@import \"used\";\n" + | ||
"$color: blue;\n" + | ||
".foo {\n" + | ||
" color: $color;\n" + | ||
"}\n"; | ||
var newExpectedOutput = ".foo {\n" + | ||
" color: blue; }\n"; | ||
fs.writeFileSync(sourceFile, newSource); | ||
return build(builder) | ||
.then(function(outputDir2) { | ||
assert.equal(outputDir, outputDir2); | ||
var outputFile = path.join(outputDir2, "styles", "foo.css"); | ||
assert.equal(newExpectedOutput, fs.readFileSync(outputFile)); | ||
assert.equal(2, compiledFiles.length); | ||
}) | ||
.finally(function() { | ||
fs.writeFileSync(sourceFile, originalSource); | ||
}); | ||
}); | ||
}); | ||
it("busts cache when file reached via includePaths changes", function() { | ||
var projectDir = makeFixtures("projectDir.tmp", { | ||
"project.scss": '@import "external";' | ||
}); | ||
var includeDir = makeFixtures("includeDir.tmp", { | ||
"external.scss": ".external { float: left; }" | ||
}); | ||
var expectedOutputDir = makeFixtures("expectedOutputDir.tmp", { | ||
"project.css": ".external {\n float: left; }\n" | ||
}); | ||
var compiledFiles = []; | ||
var compiler = new EyeglassCompiler(projectDir, { | ||
cssDir: ".", | ||
includePaths: [includeDir], | ||
optionsGenerator: function(sassFile, cssFile, options, cb) { | ||
compiledFiles.push(sassFile); | ||
cb(cssFile, options); | ||
} | ||
}); | ||
var builder = new broccoli.Builder(compiler); | ||
return build(builder) | ||
.then(function(outputDir) { | ||
assertEqualDirs(outputDir, expectedOutputDir); | ||
assert.equal(1, compiledFiles.length); | ||
compiledFiles = []; | ||
fixturify.writeSync(includeDir, { | ||
"external.scss": ".external { float: right; }" | ||
}); | ||
fixturify.writeSync(expectedOutputDir, { | ||
"project.css": ".external {\n float: right; }\n" | ||
}); | ||
return build(builder) | ||
.then(function(outputDir2) { | ||
assert.equal(outputDir, outputDir2); | ||
assert.equal(compiledFiles.length, 1); | ||
assertEqualDirs(outputDir2, expectedOutputDir); | ||
}); | ||
}); | ||
}); | ||
it("busts cache when file mode changes", function() { | ||
var projectDir = makeFixtures("projectDir.tmp", { | ||
"project.scss": '@import "external";' | ||
}); | ||
var includeDir = makeFixtures("includeDir.tmp", { | ||
"external.scss": ".external { float: left; }" | ||
}); | ||
var expectedOutputDir = makeFixtures("expectedOutputDir.tmp", { | ||
"project.css": ".external {\n float: left; }\n" | ||
}); | ||
var compiledFiles = []; | ||
var compiler = new EyeglassCompiler(projectDir, { | ||
cssDir: ".", | ||
includePaths: [includeDir], | ||
optionsGenerator: function(sassFile, cssFile, options, cb) { | ||
compiledFiles.push(sassFile); | ||
cb(cssFile, options); | ||
} | ||
}); | ||
var builder = new broccoli.Builder(compiler); | ||
return build(builder) | ||
.then(function(outputDir) { | ||
assertEqualDirs(outputDir, expectedOutputDir); | ||
assert.equal(1, compiledFiles.length); | ||
compiledFiles = []; | ||
fs.chmodSync(path.join(includeDir, "external.scss"), parseInt("755", 8)); | ||
return build(builder) | ||
.then(function(outputDir2) { | ||
assert.equal(outputDir, outputDir2); | ||
assert.equal(compiledFiles.length, 1); | ||
assertEqualDirs(outputDir2, expectedOutputDir); | ||
}); | ||
}); | ||
}); | ||
it("busts cache when an eyeglass module is upgraded", function() { | ||
var projectDir = makeFixtures("projectDir.tmp", { | ||
"project.scss": '@import "eyeglass-module";' | ||
}); | ||
var eyeglassModDir = makeFixtures("eyeglassmod.tmp", { | ||
"package.json": "{\n" + | ||
' "name": "is_a_module",\n' + | ||
' "keywords": ["eyeglass-module"],\n' + | ||
' "private": true,\n' + | ||
' "eyeglass": {\n' + | ||
' "exports": false,\n' + | ||
' "name": "eyeglass-module",\n' + | ||
' "sassDir": "sass",\n' + | ||
' "needs": "*"\n' + | ||
" }\n" + | ||
"}", | ||
"sass": { | ||
"index.scss": ".eyeglass-mod { content: eyeglass }" | ||
} | ||
}); | ||
var expectedOutputDir = makeFixtures("expectedOutputDir.tmp", { | ||
"project.css": ".eyeglass-mod {\n content: eyeglass; }\n" | ||
}); | ||
var compiledFiles = []; | ||
var compiler = new EyeglassCompiler(projectDir, { | ||
cssDir: ".", | ||
optionsGenerator: function(sassFile, cssFile, options, cb) { | ||
compiledFiles.push(sassFile); | ||
cb(cssFile, options); | ||
}, | ||
eyeglass: { | ||
modules: [ | ||
{path: eyeglassModDir} | ||
] | ||
} | ||
}); | ||
var builder = new broccoli.Builder(compiler); | ||
return build(builder) | ||
.then(function(outputDir) { | ||
assertEqualDirs(outputDir, expectedOutputDir); | ||
assert.equal(1, compiledFiles.length); | ||
compiledFiles = []; | ||
fixturify.writeSync(eyeglassModDir, { | ||
"sass": { | ||
"index.scss": ".eyeglass-mod { content: eyeglass-changed }" | ||
} | ||
}); | ||
fixturify.writeSync(expectedOutputDir, { | ||
"project.css": ".eyeglass-mod {\n content: eyeglass-changed; }\n" | ||
}); | ||
return build(builder) | ||
.then(function(outputDir2) { | ||
assert.equal(outputDir, outputDir2); | ||
assert.equal(compiledFiles.length, 1); | ||
assertEqualDirs(outputDir2, expectedOutputDir); | ||
}); | ||
}); | ||
}); | ||
it("busts cache when an eyeglass asset changes"); | ||
it("busts cache when options used for compilation are different"); | ||
it("busts cache when a file higher in the load path order is added"); | ||
it("removes a css file when the corresponding sass file is removed"); | ||
it("preserves cache across builder instances?"); | ||
}); | ||
}); |
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
46192
45
871
11
13
3
+ Addedbroccoli-merge-trees@^1.1.4
+ Addedbroccoli-plugin@^1.2.2
+ Addedfs-tree-diff@^0.5.2
+ Addedwalk-sync@^0.3.1
+ Addedblank-object@1.0.2(transitive)
+ Addedbroccoli-merge-trees@1.2.4(transitive)
+ Addedcan-symlink@1.0.0(transitive)
+ Addedfast-ordered-set@1.0.3(transitive)
+ Addedfs-tree-diff@0.5.9(transitive)
+ Addedheimdalljs@0.2.6(transitive)
+ Addedheimdalljs-logger@0.1.10(transitive)
+ Addedpath-posix@1.0.0(transitive)
+ Addedrsvp@3.2.1(transitive)
+ Addedtmp@0.0.28(transitive)
- Removedbroccoli-caching-writer@^3.0.3
- Removedbroccoli-caching-writer@3.0.3(transitive)
- Removedbroccoli-kitchen-sink-helpers@0.3.1(transitive)
Updatedeyeglass@^1.1.0