gulp task to process files that are writable - javascript

I'm using Gulp in a VS2015 project to run jscs on JavaScript files with the fix option set. The intention is to modify the same file that is read (viz., source and destination are the same).
var gulp = require('gulp');
var jscs = require('gulp-jscs');
var chmod = require('gulp-chmod');
var exec = require('gulp-exec');
var ourJsFiles = // an array of files and globbed paths
gulp.task('jscs', function (callback) {
ourJsFiles.forEach(function (fn) {
gulp.src(fn, { base: './' })
.pipe(jscs({
"preset": "google",
"maximumLineLength": 160,
"validateIndentation": 3,
"fix": true
}))
.pipe(gulp.dest('./'));
});
callback();
});
But I do not want to process any files that are read-only. Is there already a way to detect this in Gulp on Windows?

There is a plugin which allows you to work with subset of files: gulp-filter.
One of options is to pass filter function which will receive vinyl file object, so for e.g. you could use stat.mode property of that object which holds permissions and do something like:
var filter = require('gulp-filter');
...
var writableFiles = filter(function (file) {
//https://github.com/nodejs/node-v0.x-archive/issues/3045
var numericPermission = '0'+(e.stat.mode & parseInt('777', 8)).toString(8);
return numericPermission[1]==='6'
});
...
gulp.src(....)
.pipe(writableFiles)

Related

Why is merge-stream requiring async completion signal?

I'm in the process of migrating from gulp#3.9.1 to gulp#4.0.2 and upgrading my gulp dependencies in the process. I have the following task in my gulpfile, where you can assume directories is just an array of directories I want to perform this operation on:
var gulp = require('gulp');
var ngAnnotate = require('gulp-ng-annotate'); //annotates dependencies in Angular components
var rev = require('gulp-rev'); //appends a hash to the end of file names to eliminate stale cached files
var revReplace = require('gulp-rev-replace');
var uglify = require('gulp-uglify'); // minimizes javascript files
var compressCss = require('gulp-minify-css');
var useref = require('gulp-useref'); // replaces style and script blocks in HTML files
var filter = require('gulp-filter');
var merge = require('merge-stream');
var sourcemaps = require('gulp-sourcemaps');
function minify() {
var tasks = directories.map(function (directory) {
var cssFilter = filter("**/all.min.css", {restore:true});
var jsAppFilter = filter("**/app.min.js", {restore:true});
var jsFilter = filter("**/*.js", {restore:true});
return gulp.src(dstBasePath + directory + "index.html", {allowEmpty: true})
.pipe(useref())
.pipe(cssFilter)
.pipe(compressCss({keepSpecialComments:false}))
.pipe(rev())
.pipe(cssFilter.restore)
.pipe(jsAppFilter)
.pipe(sourcemaps.init())
.pipe(ngAnnotate({add:true, single_quotes:true}))
.pipe(jsAppFilter.restore)
.pipe(jsFilter)
.pipe(uglify())
.pipe(rev())
.pipe(jsFilter.restore)
.pipe(revReplace())
.pipe(sourcemaps.write('.')) // sourcemaps need to be written to same folder for Datadog upload to work
.pipe(gulp.dest(dstBasePath + directory))
});
return merge(tasks);
}
Why would this result in the error "Did you forget to signal async completion?" from Gulp when running the task? Note that I'm using Gulp 4. I've tried passing a callback done to this task, and adding .addListener('end', done) to the final pipe, but this causes my merged stream to end prematurely (presumably when the first one ends). So perhaps one of these plugins is not signaling when it's completed, but how would you even get this to work otherwise? Thanks for any insight you can provide.
return merge(folders.map(function (folder) { // this has worked for me in the past
as has this form without merge
gulp.task('init', function (done) {
var zips = getZips(zipsPath);
var tasks = zips.map(function (zip) {
return gulp.src(zipsPath + "/" + zip)
.pipe(unzip({ keepEmpty: true }))
.pipe(gulp.dest(path.join("src", path.basename(zip, ".zip"))))
.on('end', function() { // this bit is necessary
done();
});
});
return tasks;
});
Gulp 4 requires that you signal async completion. There's some good information about it in this answer to a similar question:
Gulp error: The following tasks did not complete: Did you forget to signal async completion?
I had a similar case where I was returning a merged set of tasks, and I was able to resolve the error by making the function async and awaiting the merge. My case looked something like this:
gulp.task("build", async function () {
...
return await merge(tasks);
});
so I think you should be able to do something like
async function minify(){
...
return await merge(tasks);
}

Gulp 4 watch doesn't detect changes

Is it possible with gulp v.4.0.0 to watch for files outside of the folder where gulpfile.js is located?
In older gulp it was possible to set file path to ../../someFile.css and watch for its changes, but in v4 it doesn't detect changes for the same path.
// snip....
let rootFolder = '..\..\root\';
// Watch function
let watchThis = (filePath, gulpTasks) => {
gulp.watch(filePath, gulp.series(gulpTasks))
.on('change', function(path) {
console.log(`${chalk.blue('Changed')}: ${chalk.yellow(path)}`);
})
.on('unlink', function(path) {
console.log(`${chalk.red('Deleted')}: ${chalk.yellow(path)}`);
});
}
// Watch task configuration
let watchFiles = () => {
watchThis([`${rootFolder}/style1.scss`, `${rootFolder}/style2.scss`], 'scss')
watchThis('./js/main.js', 'js')
}
// Final watch task
gulp.task('watch', gulp.series(
'development',
gulp.parallel(
watchFiles,
'startLiveServers'
)
));
// ...snip
Changes to files ['../../style1.scss', '../../style2.scss'] will not be detected, but they will be for './js/main.js'. Am I missing something?
Problem with new Gulp 4 watch task is in paths. Unlike watch task from Gulp 3, new version is unforgiving and requires correct path structure with correct paths separators.
So instead of using paths that may result in ..\\..\\root\\/style1.scss, we must convert paths to proper structure like ../../root/style1.scss.
This simple function helps and the rest is handled by gulp and nodejs
let fixPath = (oldPath) => {
const pathString = oldPath;
const fix = /\\{1,}|\/{1,}/;
return pathString.replace(new RegExp(fix, 'gm'), '/').replace(new RegExp(fix, 'gm'), '/')
}

Accessing typescript file variable values using gulp

I have several typescript files, some of them export a const named APIS.
I'm trying to access those exports (I want to concatenated all of them to a single file), but it doesn't seem to work. I'm obviously doing something wrong, but I'm not sure what.
For example, I have a folder named services, with 2 files: service1.ts, service2.ts.
service1.ts:
...
export const APIS = [ { "field1" : "blabla" } ];
service2.ts: does not contain the APIS var.
This is my gulpfile.js:
var gulp = require('gulp');
var concat = require('gulp-concat');
var map = require('gulp-map');
gulp.task('default', function() {
return gulp.src('.../services/*.ts')
.pipe(map(function(file) {
return file.APIS;
}))
.pipe(concat('all.js'))
.pipe(gulp.dest('./test/'));
});
When I run this task, I get nothing. When I added console.log(file.APIS); to the map function, I get undefined for all the values (although it is defined in service1.ts!).
This is following to: Extracting typescript exports to json file using gulp
EDIT: OK, so I tried saving the exports in a .js file instead of a .ts file, and now I can access those vars using require:
gulp.task('default', function() {
return gulp.src('./**/*.service.export.js')
.pipe(map(function(file) {
var fileObj = require(file.path);
...
}))
Now if I try console.log(fileObj.APIS); I get the correct values. What I'm still confused about is how I can pass these value on, and create a single file out of all these vars. Is it possible to push them into an array?
This will not work as you think it would work. Gulp itself knows nothing about typescript files, that file is a vinyl-file and has no knowledge about the typescript code within its content.
Edit
Based on your example, you can do something like this:
var gulp = require('gulp');
var concat = require('gulp-concat');
var map = require('gulp-map');
var fs = require('fs');
gulp.task('test', function ()
{
var allConstants = [];
var stream = gulp.src('./**/*.service.export.js')
.pipe(map(function(file)
{
var obj = require(file.path);
if (obj.APIS != null)
allConstants = allConstants.concat(obj.APIS);
return file;
}));
stream.on("end", function (cb)
{
// Do your own formatting here
var content = allConstants.map(function (constants)
{
return Object.keys(constants).reduce(function (aggregatedString, key)
{
return aggregatedString + key + " : " + constants[key];
}, "");
}).join(", ");
fs.writeFile('filename.txt', content, cb);
});
return stream;
});
Suggestion
If you want to collect multiple variables into a single file i.e. a common variables file I suggest gulp-replace.
Steps
Create a file, require it and use tags within that file to place your variables.
Advice
If you are already using services don't create an array. Instead create an object (JSON) where every property is a constant. i.e.
var constants = {
const_1: 0,
const_2: 1,
const_3: 2,
}

Set working directory in gulpfile.js?

Is there a way to set the working directory for Gulp within a gulpfile, so that I can run a gulp command from a subdirectory without running into any issues? I ran a search for this and didn't find what I was looking for.
To clarify, I'm aware of adding a prefix to the files I'm using. However, instead of this -
var gulp = require('gulp');
var jshint = require('gulp-jshint');
...
var paths = {
js: [__dirname + 'app/*/*.js', __dirname + '!app/lib/**'],
css: __dirname + 'app/*/*.styl',
img: __dirname + 'app/img/*',
index: __dirname + '*.html',
dist: __dirname + 'dist'
};
I'd like to do something like this:
var gulp = require('gulp');
var jshint = require('gulp-jshint');
...
gulp.cwd(__dirname); // This would be much easier to understand, and would make future edits a bit safer.
var paths = {
js: ['app/*/*.js', '!app/lib/**'],
css: 'app/*/*.styl',
img: 'app/img/*',
index: '*.html',
dist: 'dist'
};
I'm wondering if Gulp exposes this functionality. Perhaps node itself allows this.
(I realize that there is likely a way to do command line itself when I run the command, but I would like to include it in the gulp file, especially for distribution purposes. I want the working directory for gulp to match the directory in which the gulpfile resides.)
Thanks!
Besides option.cwd, you can also use process.chdir(yourDir)
it could be used anywhere in a gulpfile. e.g.
process.chdir(yourDir);
var gulp = require('gulp');
Make sure your gulp is up-to-date( > 3.8.10), this may not work in older gulp.
Instead of concatenating strings by yourself, you should be using path.join since it will take care of the proper slash, and following that path you can add a shorcut:
var path = require('path'),
p = function () {
Array
.prototype
.unshift
.call(arguments, __dirname);
return path.join.apply(path, arguments);
};
console.log(p('a', 'b', 'c'));
Or, well, you can just:
gulp.src(..., {cwd: __dirname})
gulp.dest(..., {cwd: __dirname})
Something like:
var src = function (globs, options) {
options = options || {};
options.cwd = __dirname;
return gulp.src(globs, options);
};
var dest = function (folder, options) {
options = options || {};
options.cwd = __dirname;
return gulp.dest(folder, options);
};
Look here and here.

How to add a path prefix to globs passed to gulp.src?

Consider the following two files:
config.json
{
"vendorFiles": [
"vendor/angular/angular.js",
"vendor/angular-ui-router/release/angular-ui-router.js",
"vendor/angular-ui-utils/modules/utils.js"
]
}
gulpfile.js
var gulp = require("gulp"),
concat = require("gulp-concat"),
config = require("./config");
gulp.task("build-vendor", function() {
gulp.src(config.vendorFiles)
.pipe(concat("vendor.js"))
.pipe(gulp.dest("build"));
});
How can I eliminate the need to specify vendor/ for each file in config.json? That file is one that is manually edited by other developers by hand, so I want to make it as hassle-free on them as possible.
Ideally I'd like my gulpfile.js to take care of adding that prefix (somehow), and for my config.json to look like this:
{
"vendorFiles": [
"angular/angular.js",
"angular-ui-router/release/angular-ui-router.js",
"angular-ui-utils/modules/utils.js"
]
}
There may be a better way with a Gulp specific solution, but this should work.
var gulp = require("gulp"),
concat = require("gulp-concat"),
config = require("./config");
gulp.task("build-vendor", function() {
gulp.src(config.vendorFiles.map(function(a) {return 'vendor/' + a}))
.pipe(concat("vendor.js"))
.pipe(gulp.dest("build"));
});
Demo:
http://jsfiddle.net/AK4tP/
Can't you just do
var gulp = require("gulp"),
concat = require("gulp-concat"),
config = require("./config");
gulp.task("build-vendor", function() {
gulp.src(config.vendorFiles, {root: 'vendor/'})
.pipe(concat("vendor.js"))
.pipe(gulp.dest("build"));
});
Gulp should accept root option in src() although it's not documented.

Categories