Grunt watch: only upload files that have changed - ssh

related
I was able to set up a Grunt task to SFTP files up to my dev server using grunt-ssh:
sftp: {
dev: {
files: {
'./': ['**','!{node_modules,artifacts,sql,logs}/**'],
},
options: {
path: '/path/to/project',
privateKey: grunt.file.read(process.env.HOME+'/.ssh/id_rsa'),
host: '111.111.111.111',
port: 22,
username: 'marksthebest',
}
}
},
But this uploads everything when I run it. There are thousands of files. I don't have time to wait for them to upload one-by-one every time I modify a file.
How can I set up a watch to upload only the files I've changed, as soon as I've changed them?
(For the curious, the server is a VM on the local network. It runs on a different OS and the setup is more similar to production than my local machine. Uploads should be lightning quick if I can get this working correctly)

What you need is grunt-newer, a task designed especially to update the configuration of any task depending on what file just changed, then run it. An example configuration could look like the following:
watch: {
all: {
files: ['**','!{node_modules,artifacts,sql,logs}/**'],
tasks: ['newer:sftp:dev']
}
}

You can do that using the watch event of grunt-contrib-watch.
You basically need to handle the watch event, modify the sftp files config to only include the changed files, and then let grunt run the sftp task.
Something like this:
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
secret: grunt.file.readJSON('secret.json'),
watch: {
test: {
files: 'files/**/*',
tasks: 'sftp',
options: {
spawn: false
}
}
},
sftp: {
test: {
files: {
"./": "files/**/*"
},
options: {
path: '/path/on/the/server/',
srcBasePath: 'files/',
host: 'hostname.com',
username: '<%= secret.username %>',
password: '<%= secret.password %>',
showProgress: true
}
}
}
}); // end grunt.initConfig
// on watch events configure sftp.test.files to only run on changed file
grunt.event.on('watch', function(action, filepath) {
grunt.config('sftp.test.files', {"./": filepath});
});
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-ssh');
};
Note the "spawn: false" option, and the way you need to set the config inside the event handler.
Note2: this code will upload one file at a time, there's a more robust method in the same link.

You can achieve that with Grunt:
grunt-contrib-watch
grunt-rsync
First things first: I am using a Docker Container. I also added a public SSH key into my Docker Container. So I am uploading into my "remote" container only the files that have changed in my local environment with this Grunt Task:
'use strict';
module.exports = function(grunt) {
grunt.initConfig({
rsync: {
options: {
args: ['-avz', '--verbose', '--delete'],
exclude: ['.git*', 'cache', 'log'],
recursive: true
},
development: {
options: {
src: './',
dest: '/var/www/development',
host: 'root#www.localhost.com',
port: 2222
}
}
},
sshexec: {
development: {
command: 'chown -R www-data:www-data /var/www/development',
options: {
host: 'www.localhost.com',
username: 'root',
port: 2222,
privateKey: grunt.file.read("/Users/YOUR_USER/.ssh/id_containers_rsa")
}
}
},
watch: {
development: {
files: [
'node_modules',
'package.json',
'Gruntfile.js',
'.gitignore',
'.htaccess',
'README.md',
'config/*',
'modules/*',
'themes/*',
'!cache/*',
'!log/*'
],
tasks: ['rsync:development', 'sshexec:development']
}
},
});
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-rsync');
grunt.loadNpmTasks('grunt-ssh');
grunt.registerTask('default', ['watch:development']);
};
Good Luck and Happy Hacking!

I have recently ran into a similar issue where I wanted to only upload files that have changed. I'm only using grunt-exec. Providing you have ssh access to your server, you can do this task with much greater efficiency. I also created an rsync.json that is ignored by git, so collaborators can have their own rsync data.
The benefit is that if anyone makes a change it automatically uploads to their stage.
// Watch - runs tasks when any changes are detected.
watch: {
scripts: {
files: '**/*',
tasks: ['deploy'],
options: {
spawn: false
}
}
}
My deploy task is a registered task that compiles scripts then runs exec:deploy
// Showing exec:deploy task
// Using rsync with ssh keys instead of login/pass
exec: {
deploy: {
cmd: 'rsync public_html/* <%= rsync.options %> <%= rsync.user %>#<%= rsync.host %>:<%=rsync.path %>'
}
You see a lot of the <%= rsync %> stuff? I use that to grab info from rysnc.json which is ingored by git. I only have this because this is a team workflow.
// rsync.json
{
"options": "-rvp --progress -a --delete -e 'ssh -q'",
"user": "mmcfarland",
"host": "example.com",
"path": "~/stage/public_html"
}
Make sure you rsync.json is defined in grunt:
module.exports = function(grunt) {
var rsync = grunt.file.readJSON('path/to/rsync.json');
var pkg = grunt.file.readJSON('path/to/package.json');
grunt.initConfig({
pkg: pkg,
rsync: rsync,

I think it's not good idea to upload everything that changed at once to staging server. And working on the staging server is not a good idea too. You have to configure your local machine server, to be the same as staging/production
It's better to upload 1 time, when you do deployment.
You can archive all the files using grunt-contrib-compress. And push them using grunt-ssh as 1 file, then extract it on the server, that will be much faster.
that's example of compress task:
compress: {
main: {
options:{
archive:'build/build.tar.gz',
mode: 'tgz'
},
files: [
{cwd: 'build/', src: ['sites/all/modules/**'], dest:'./'},
{cwd: 'build/', src: ['sites/all/themes/**'], dest:'./'},
{cwd: 'build/', src: ['sites/default/files/**'], dest:'./'}
]
}
}
PS: Didn't ever look to rsync grunt modules.
I understand that it's might not what you are looking for. But i decided to create my answer as standalone answer.

Related

PhantomJS timeout issue when running in headless mode in GitLab CI

I am trying to use GitLab CI to run some client-side unit test written using QUnit. Now to run the Qunit test I am using the grunt-contrib-qunit plugin. To run these tests in headless mode I am using this plugin which hosts it on a localhost server in a console and runs all unit tests. When running this project locally I am successfully able to run all the unit tests but when I checking in my code which kicks of the CI process, on GitLab, it fails on starting the phantomjs server and gives timeout error. I am also providing the jsbin link of the two text files which are basically the output of the unit test from my console. One file is of my local system and another is from the GitLab CI that runs on GitLab website when I check-in my code.
Local Console Output File Dump
Gitlab CI Output Dump
Adding my gitlab-ci.yaml file
image: node:4.2.2
before_script:
- dir
- cd sapui5_repo
- dir
- cd app-with-tests
build:
stage: build
script:
- npm i
- npm run test
cache:
policy: push
paths:
- node_modules
artifacts:
paths:
- built
Also adding my gruntfile if that helps
/* global module */
module.exports = function (grunt) {
grunt.initConfig({
qunit: {
all: {
options: {
timeout: 9000,
urls: [
"http://localhost:9000/webcontent/test/unit/unitTests.qunit.html"
]
}
},
//all: ["webcontent/test/unit/unitTests.qunit.html"],
options: {
timeout: 2000,
}
},
connect: {
options: {
//open: true,
},
first: {
options: {
port: 9000,
//livereload: 3500,
base: "./"
}
},
second: {
options: {
open: {
target: "http://localhost:9000/webcontent"
},
keepalive: true,
port: 9000,
livereload: 3501,
base: "./",
}
}
},
});
grunt.loadNpmTasks("grunt-contrib-connect");
grunt.loadNpmTasks("grunt-contrib-qunit");
grunt.registerTask("test", [
"connect:first", "qunit"
]);
grunt.registerTask("default", [
"connect:second"
]);
};

grunt-bowercopy produces warning - No files copied

I've set Grunt to compile compass with watch task and now I'd like to copy useful files from bower_components/... - e.g. bower_components/jquery/jquery.min.js, because bower produces a lot of unnecessary files, which I want to get rid of, when uploading to server.
CMD produces warning and stops process;
Reading C:\Users\sjiamnocna\Documents\NetBeansProjects\PM_new\node_modules\grunt-bowercopy\package.json...OK
Parsing C:\Users\sjiamnocna\Documents\NetBeansProjects\PM_new\node_modules\grunt-bowercopy\package.json...OK
Reading bower.json...OK
Parsing bower.json...OK
Loading "bowercopy.js" tasks...OK
+ bowercopy
Loading "gruntfile.js" tasks...OK
+ default, dog
No tasks specified, running default tasks.
Running tasks: default
Running "default" task
Running "bowercopy" task
Running "bowercopy:copythat" (bowercopy) task
Verifying property bowercopy.copythat exists in config...OK
File: [no files]
Options: srcPrefix="bower_components", destPrefix="files", ignore=[], report, runBower=false, clean=false, copyOptions={}
Using srcPrefix: bower_components
Using destPrefix: files
Warning: Nothing was copied for the "copythat" target Use --force to continue.
My gruntfile:
module.exports = function (grunt) {
grunt.initConfig({
watch: {
scss: {
files: ['files/style/sass/*.scss'],
tasks: ['compass']
}
},
compass: {
dist: {
options: {
sassDir: 'files/style/sass',
cssDir: 'files/style',
environment: 'development'
}
}
},
bowercopy: {
copythat: {
options: {
runBower: false,
srcPrefix: 'bower_components',
destPrefix: 'files'
},
script: {
'jquery/dist/jquery.min.js': 'script/lib/jquery.min.js',
'jquery-ui/jquery-ui.min.js': 'script/lib/jquery-ui.min.js',
'masonry/dist/masonry.pkgd.min.js': 'script/lib/masonry.pkgd.min.js',
'sweetalert/dist/sweetalert.min.js': 'script/lib/sweetalert.min.js'
}
}
}
});
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-compass');
grunt.loadNpmTasks('grunt-bowercopy');
grunt.registerTask('default', ['bowercopy']);
grunt.registerTask('dog', ['watch']);
};
Can anyone tell me what's wrong? Or, is there any other way to do it with grunt (automatically :) )?
Thanks #cartant for comment, it was one of the mistakes I've made - using whatever instead of "files"
I've changed position of resource and target
Wrong:
'source':'target'
improved:
'target':'source'
Works!

Protractor pointing to Sauce Labs Selenium Server

I'm trying to integrate Protractor with Sauce Labs from Travis. I can get the sauce_connect server running correctly but am unable to get Travis to point to that particular remote server.
Travis will get to the point where it initiates sauce_connect but when I run "protractor:analytics" it doesn't point to the correct server and fails.
Travis.yml:
language: python
python:
- 3.2_with_system_site_packages
branches:
only:
- develop
before_install:
- sudo apt-get update -qq
- sudo apt-get install python-numpy
install:
- cd lib && python setup.py install
- cd .. && pip install -r requirements/travis_requirements.txt
- npm install
script:
- grunt karma:single
- grunt protractor:analytics
env:
global:
- secure: <string>
- secure: <string>
sauce_connect: true
Gruntfile:
protractor: {
options: {
configFile: './webapp/static/test/e2e/protractor.conf.js',
keepAlive: true
},
singlerun: {},
analytics: {
options: {
//debug : true,
args:{
specs: ['./webapp/static/test/e2e/analytics_spec.js']
}
}
},
},
Protractor Conf:
exports.config = {
chromeOnly: false,
seleniumArgs: [],
// If sauceUser and sauceKey are specified, seleniumServerJar will be ignored.
// The tests will be run remotely using SauceLabs.
sauceUser: process.env.SAUCE_USER,
sauceKey: process.env.SAUCE_KEY,
baseUrl: 'http://localhost:8000',
specs: [
'./*_spec.js',
],
// Patterns to exclude.
exclude: [],
multiCapabilities: [],
// ----- More information for your tests ----
//
// A base URL for your application under test. Calls to protractor.get()
// with relative paths will be prepended with this.
baseUrl: process.env.SN_BASE_URL,
// Selector for the element housing the angular app - this defaults to
// body, but is necessary if ng-app is on a descendant of <body>
rootElement: 'body',
// A callback function called once protractor is ready and available, and
// before the specs are executed
// You can specify a file containing code to run by setting onPrepare to
// the filename string.
onPrepare: function() {
// At this point, global 'protractor' object will be set up, and jasmine
// will be available. For example, you can add a Jasmine reporter with:
// jasmine.getEnv().addReporter(new jasmine.JUnitXmlReporter(
// 'outputdir/', true, true));
},
// The params object will be passed directly to the protractor instance,
// and can be accessed from your test. It is an arbitrary object and can
// contain anything you may need in your test.
// This can be changed via the command line as:
// --params.login.user 'Joe'
params: {
login: {
user: process.env.SN_TEST_USERNAME,
password: process.env.SN_TEST_PASSWORD
}
},
framework: 'jasmine',
// ----- Options to be passed to minijasminenode -----
//
// See the full list at https://github.com/juliemr/minijasminenode
jasmineNodeOpts: {
// onComplete will be called just before the driver quits.
onComplete: null,
// If true, display spec names.
isVerbose: false,
// If true, print colors to the terminal.
showColors: true,
// If true, include stack traces in failures.
includeStackTrace: true,
// Default time to wait in ms before a test fails.
defaultTimeoutInterval: 30000
},
onCleanUp: function() {}
};
If I did understand well : Sauce connect tool is not used by protractor/selenium when running a test suite.
Well I had this problem, travis requires sauce credentials and protractor requires those credentials and a tunnel id:
.travis.yml:
addons:
sauce_connect:
username: xxx
access_key: xxx
protractor.conf.js:
exports.config = {
...
sauceUser: process.env.SAUCE_USERNAME,
sauceKey: process.env.SAUCE_ACCESS_KEY,
capabilities: {
...
'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
}
}

Bootstrap "grunt watch" wont create bootstrap.min.css

I am almost absolutely new to grunt.
I used grunt with bootstrap 3.1.1 and the grunt watch-command worked out great. My bootstrap.min.css file was compiled every time. (i upload my bootstrap.min.css)
Later on i lost my 3.1.1 grunt file (long story short my computer crashed).
So now with Bootstrap 3.2.0 i was going to restablish my grunt-work-flow.
But now when i use grunt watch i only get the "bootstrap.css" and "bootstrap.theme.css" compiled.
I have spent the last hours to figure this out without success.
WHAT I WANT
I want grunt watch to compile the minified "bootstrap.min.css"
So how do i call the min.css-function on the watch?
I will be glad to get some help.
Grunt watch will only watch the files then run tasks you have set. I am assuming in your gruntfile you ahve something like this:
css: {
files: [
'bootstrap.css',
],
tasks: ['less'],
},
In the less task you should have something like below. Note the cleancss option being set to true:
options: {
cleancss: true
},
files: {
"dest/bootstrap.min.css": "src/bootstrap.css",
"dest/bootstrap.theme.min.css": "src/bootstrap.theme.css"
}
UPDATE:
Based on the file you uploaded you should be running the cssmin:core task when the watch is triggered.
UPDATE 2:
To update the watch task you can just add the cssmin:core task to the less subtask:
less: {
files: 'less/**/*.less',
tasks: ['less', 'cssmin:core]
}
Here you are telling it to run the less task, followed by the cssmin task whenever one of the less files is changed while watching.
Your gruntfile.js will have a 'watch' section as below
watch: {
src: {
files: '<%= jshint.core.src %>',
tasks: ['jshint:src', 'qunit', 'concat']
},
test: {
files: '<%= jshint.test.src %>',
tasks: ['jshint:test', 'qunit']
},
less: {
files: 'less/**/*.less',
tasks: 'less'
}
},
The tasks under less subsection define the tasks that 'grunt watch' will run. In Bootstrap 3.3.2 (I guess in 3.1.1 also it would be the same) is the 'cssmin' task that minifies the core bootstrap css. So you need to add the task to less so the code above becomes
watch: {
src: {
files: '<%= jshint.core.src %>',
tasks: ['jshint:src', 'qunit', 'concat']
},
test: {
files: '<%= jshint.test.src %>',
tasks: ['jshint:test', 'qunit']
},
less: {
files: 'less/**/*.less',
tasks: ['less', 'cssmin']
}
},
None of the above worked for me. Looks like the css minification command changed to cssmin:minifyCore so I have updated my watch task to the following:
watch: {
src: {
files: '<%= jshint.core.src %>',
tasks: ['jshint:core', 'qunit', 'concat']
},
test: {
files: '<%= jshint.test.src %>',
tasks: ['jshint:test', 'qunit']
},
less: {
files: 'less/**/*.less',
tasks: ['less', 'cssmin:minifyCore']
}
},
Hopefully this helps others!

Why isn't grunt reloading my updated less files?

I'm trying to get Grunt to process my less files every time I make a change to one of the files.
I have a 'watch' task working, and it says it is processing files when I make a change, and it outputs the correct files which change, so my watch is working, but the changes I make are not being made on the css file.
My file structure is like this
-styles
globals.less
components.less
-menu
menu.less
-header
header.less
The components.less file is a group of less mixins to grab the other .less files like #import "menu/menu.less";
In my grunt file, I have
less: {
development: {
options: {
paths: ['./app/vendor/modern-touch-less','./app/styles']
},
files: {
'./app/styles/modern-touch.css':'./app/vendor/modern-touch-less/style.less',
'./app/styles/components.css':'./app/styles/components.less'
}
}
},
watch: {
js: {
files: ['/scripts/{,*/}*.js','/styles/{,*/}*.less'],
tasks: ['newer:jshint:all, less'],
options: {
livereload: true
}
},
styles: {
files: ['./styles/{,*/}*.*','./vendor/{,*/}*.less'],
tasks: ['less','newer:copy:styles', 'autoprefixer'],
options: {
nospawn: true,
livereload: true
}
},
}
When I first start the app, the less files are built into the correct components.css file, but after changing a file, the components.css file is not updated.
Grunt is started with
grunt.task.run([
'clean:server',
'bower-install',
'concurrent:server',
'less',
'autoprefixer',
'connect:livereload',
'watch',
'karma'
]);
Perhaps you can try simplifying your configuration, by splitting tasks out to run on the appropriate set of files.
watch: {
js: {
files: ['/scripts/{,*/}*.js'],
tasks: ['newer:jshint:all'],
options: {
livereload: true
}
},
styles: {
files: ['./styles/{,*/}*.*','./vendor/{,*/}*.less'],
tasks: ['less','newer:copy:styles', 'autoprefixer'],
options: {
nospawn: true,
livereload: true
}
},
}
In your original watch config, the watch.js.tasks array was ['newer:jshint:all, less'], which looks like a typo, should be ['newer:jshint:all', 'less']. This may fix the problem too, but I would consider just running tasks on the files that they are affected by.