11var path = require ( 'path' ) ;
22var fs = require ( 'fs' ) ;
33var through = require ( 'through2' ) ;
4- var minimatch = require ( 'minimatch' ) ;
54var sass = require ( 'node-sass' ) ;
65var gutil = require ( 'gulp-util' ) ;
76var slash = require ( 'gulp-slash' ) ;
@@ -12,52 +11,76 @@ var SourceMapConsumer = require('source-map').SourceMapConsumer;
1211var mime = require ( 'mime' ) ;
1312
1413/**
15- * Search for the relative file reference from the <code>startPath</code> up to, but not including, the process
16- * working directory.
17- * @param {string } startPath The path to which the <code>relative</code> path might be specified
18- * @param {string } relative A possibly relative file path
19- * @param {string } [callerPath] The full path of the invoking function instance where recursion has occured
14+ * Search for the relative file reference from the <code>startPath</code> up to the process
15+ * working directory, avoiding any other directories with a <code>package.json</code> or <code>bower.json</code>.
16+ * @param {string } startPath The location of the uri declaration and the place to start the search from
17+ * @param {string } uri The content of the url() statement, expected to be a relative file path
2018 * @returns {string } dataURI of the file where found or <code>undefined</code> otherwise
2119 */
22- function encodeRelativeURL ( startPath , relative , callerPath ) {
23-
24- // ensure we are at a valid start path that is not process working directory
25- var absStart = path . resolve ( startPath ) ;
26- var parentDir = path . resolve ( path . join ( absStart , '..' ) ) ;
27- if ( fs . existsSync ( absStart ) ) {
28- var fullPath = path . resolve ( path . join ( startPath , relative ) ) ;
29- if ( fs . existsSync ( fullPath ) ) {
30-
31- // file exists so get the dataURI
32- var type = mime . lookup ( fullPath ) ;
33- var contents = fs . readFileSync ( fullPath ) ;
34- var base64 = new Buffer ( contents ) . toString ( 'base64' ) ;
35- return 'url(data:' + type + ';base64,' + base64 + ')' ;
36-
37- } else if ( parentDir !== process . cwd ( ) ) { // will not jump across bower or npm packages
38-
39- // find parent and child directories
40- var childDirs = fs . readdirSync ( absStart )
41- . map ( function toAbsolute ( filename ) {
42- return path . join ( absStart , filename ) ;
43- } ) . filter ( function directoriesOnly ( absolute ) {
44- return fs . statSync ( absolute ) . isDirectory ( ) ;
45- } ) ;
20+ function encodeRelativeURL ( startPath , uri ) {
21+ 'use strict' ;
4622
47- // recurse paths down and up except the caller path
48- // return the first success
49- var result ;
50- childDirs . concat ( parentDir )
51- . filter ( function excludeCallerDir ( absoluteDir ) {
52- return ( absoluteDir !== callerPath ) ;
53- } )
54- . some ( function encode ( absoluteDir ) {
55- result = encodeRelativeURL ( absoluteDir , relative , absStart ) ;
56- return result ;
57- } ) ;
23+ /**
24+ * Test whether the given directory is the root of its own package
25+ * @param {string } absolutePath An absolute path
26+ * @returns {boolean } True where a package.json or bower.json exists, else False
27+ */
28+ function notPackage ( absolutePath ) {
29+ return [ 'package.json' , 'bower.json' ] . every ( function fileNotFound ( file ) {
30+ return ! ( fs . existsSync ( path . resolve ( absolutePath , file ) ) ) ;
31+ } ) ;
32+ }
33+
34+ // ignore data uris and ensure we are at a valid start path
35+ var absoluteStart = ! ( / ^ d a t a \: / . test ( uri ) ) && path . resolve ( startPath ) ;
36+ if ( absoluteStart ) {
37+
38+ // find path to the root, stopping at cwd, package.json or bower.json
39+ var pathToRoot = [ ] ;
40+ do {
41+ pathToRoot . push ( absoluteStart ) ;
42+ var isWorking = ( absoluteStart !== process . cwd ( ) ) && notPackage ( absoluteStart ) ;
43+ absoluteStart = path . resolve ( absoluteStart , '..' ) ;
44+ } while ( isWorking ) ;
5845
59- // may be undefined
60- return result ;
46+ // start a queue with the path to the root
47+ var queue = pathToRoot . concat ( ) ;
48+
49+ // process the queue until empty
50+ // the queue pattern ensures that we favour paths closest the the start path
51+ while ( queue . length ) {
52+
53+ // shift the first item off the queue, consider it the base for our relative uri
54+ var basePath = queue . shift ( ) ;
55+ var fullPath = path . resolve ( basePath , uri ) ;
56+
57+ // file exists so convert to a dataURI and end
58+ if ( fs . existsSync ( fullPath ) ) {
59+ var type = mime . lookup ( fullPath ) ;
60+ var contents = fs . readFileSync ( fullPath ) ;
61+ var base64 = new Buffer ( contents ) . toString ( 'base64' ) ;
62+ return 'url(data:' + type + ';base64,' + base64 + ')' ;
63+ }
64+ // enqueue subdirectories that are not packages and are not in the root path
65+ else {
66+ fs . readdirSync ( basePath )
67+ . filter ( function notHidden ( filename ) {
68+ return ( filename . charAt ( 0 ) !== '.' ) ;
69+ } )
70+ . map ( function toAbsolute ( filename ) {
71+ return path . join ( basePath , filename ) ;
72+ } )
73+ . filter ( function directoriesOnly ( absolutePath ) {
74+ return fs . statSync ( absolutePath ) . isDirectory ( ) ;
75+ } )
76+ . filter ( function notInRootPath ( absolutePath ) {
77+ return ( pathToRoot . indexOf ( absolutePath ) < 0 ) ;
78+ } )
79+ . filter ( notPackage )
80+ . forEach ( function enqueue ( absolutePath ) {
81+ queue . push ( absolutePath ) ;
82+ } ) ;
83+ }
6184 }
6285 }
6386}
@@ -124,10 +147,13 @@ module.exports = function (bannerWidth, libraryPaths) {
124147 line : cssStart . line ,
125148 column : cssStart . column
126149 } ) ;
150+ if ( ! sassStart . source ) {
151+ throw new Error ( 'failed to decode node-sass source map' ) ; // this can occur with regressions in libsass
152+ }
127153 var sassDir = path . dirname ( sassStart . source ) ;
128154
129155 // allow multiple url() values in the declaration
130- // the url will be every second value (i % 2)
156+ // the uri will be every second value (i % 2)
131157 declaration . value = declaration . value
132158 . split ( / u r l \s * \( \s * [ ' " ] ? ( [ ^ ' " ? # ] * ) [ ^ ) ] * \) / g) // split by url statements
133159 . map ( function ( token , i ) {
@@ -139,21 +165,20 @@ module.exports = function (bannerWidth, libraryPaths) {
139165
140166 /**
141167 * Handler for successful transpilation using node-sass.
168+ * This functions gets called with an object containing a CSS file and its source-map,
169+ * which is modified and passed through CSS rework, before being pushed to the results.
142170 * @param {string } css Compiled css
143171 * @param {string } map The source-map for the compiled css
144172 */
145173 function successHandler ( css , map ) {
146174
147- // adjust sourcemap
148- var source = minimatch . makeRe ( file . cwd ) . source
149- . replace ( / ^ \^ | \$ $ / g, '' ) // match text anywhere on the line by removing line start/end
150- . replace ( / \\ \/ / g, '[\\\\\\/]' ) + // detect any platform path format
151- '|\\.\\.\\/' ; // relative paths are an artifact and must be removed
152- var parsable = slash ( map . replace ( new RegExp ( source , 'g' ) , '' ) ) ;
153- var sourceMap = JSON . parse ( parsable ) ;
175+ // adjust source-map
176+ var sourceMap = convert . fromJSON ( map ) . toObject ( ) ;
154177 sourceMap . sources . forEach ( function ( value , i , array ) {
155178 array [ i ] = path . resolve ( value . replace ( / ^ \/ / , '' ) . replace ( / \b \/ + \b / g, '/' ) ) ; // ensure single slash absolute
156179 } ) ;
180+
181+ // prepare the adjusted sass source-map for later look-ups
157182 sourceMapConsumer = new SourceMapConsumer ( sourceMap ) ;
158183
159184 // embed sourcemap in css
@@ -179,7 +204,7 @@ module.exports = function (bannerWidth, libraryPaths) {
179204
180205 // write stream output
181206 pushResult ( '.css' , reworked . code + '\n/*# sourceMappingURL=' + mapName + ' */' ) ;
182- pushResult ( '.css.map' , JSON . stringify ( reworked . map , null , ' ' ) ) ;
207+ pushResult ( '.css.map' , JSON . stringify ( reworked . map , null , 2 ) ) ;
183208 done ( ) ;
184209 }
185210
@@ -208,19 +233,20 @@ module.exports = function (bannerWidth, libraryPaths) {
208233 }
209234
210235 // perform the sass render
211- sass . render ( {
212- file : file . path ,
213- data : file . contents . toString ( ) ,
214- success : successHandler ,
215- error : errorHandler ,
236+ sass . render ( {
237+ file : file . path ,
238+ data : file . contents . toString ( ) ,
239+ success : successHandler ,
240+ error : errorHandler ,
216241 includePaths : libList ,
217- outputStyle : 'compressed' ,
218- stats : { } ,
219- sourceMap : mapName
242+ outputStyle : 'compressed' ,
243+ stats : { } ,
244+ sourceMap : mapName
220245 } ) ;
221246
222- // display the output buffer with padding before and after and between each item
223247 } , function ( done ) {
248+
249+ // display the output buffer with padding before and after and between each item
224250 if ( output . length ) {
225251 var width = Number ( bannerWidth ) || 0 ;
226252 var hr = new Array ( width + 1 ) ; // this is a good trick to repeat a character N times
0 commit comments