Combine two json files to give a set of variables to use in Gulp - node.js

I have seen lots of posts online about how to use a set of variables defined in a file using a require statement.
I want to know how I can use two files.
For example, in pseudo...
gulp --env=prod
if (env):
defaultConfig = require('./config/default.json')
envConfig = require('./config/prod.json')
config = combine(defaultConfig, envConfig)
else:
config = require('./config/default.json')
// Now i can access everything like so...
console.log(config.name)
console.log(config.minify)
This keeps by config DRY and also means I don't have to create a new file for every environment I have.
I'm new to Gulp but i thought this would be a common requirement however, Google hasn't turned up anything for having defaults merged with env specific settings.
Do i need to write a node module?

You can do it with ES6 function Object.assign:
gulp --env=prod
if (env):
defaultConfig = JSON.parse(require('./config/default.json'))
envConfig = JSON.parse(require('./config/prod.json'))
config = Object.assign(defaultConfig, envConfig)
else:
config = JSON.parse(require('./config/default.json'))
// Now i can access everything like so...
console.log(config.name)
console.log(config.minify)
ES6 is supported in Node so you can use it whenever you want.
EDIT: If you have older versions of Node, you can use extend like Sven Schoenung suggest.

Use yargs to parse command line arguments and extend to combine the two config objects:
var gulp = require('gulp');
var argv = require('yargs').argv;
var extend = require('extend');
var config = extend(
require('./config/default.json'),
(argv.env) ? require('./config/' + argv.env + '.json') : {}
);
gulp.task('default', function() {
console.log(config);
});
Running gulp --env=prod will print the combined config, while simply running gulp will print the default config.

Use the following function :
function combine(a,b){
var temp0 = JSON.stringify(a);
var temp1 = temp0.substring(0, temp0.length-1);
var temp2 = (JSON.stringify(b)).substring(1);
var temp3 = temp1 + "," + temp2;
return JSON.parse(temp3);
}

Related

Get cordova package name from javascript hook

I am writing a cordova plugin with a node hook to run after_prepare .
This is for Android only.
From within this hook I need to get the cordova package name, so I can copy a file to the src/com/example/myproject folder (if the package is com.example.myproject).
If I know the package name I can make this path. I have it working hardcoded now but I need this to work with two different package names.
Is there a way to get the package name from within this code in the plugin hook?
module.exports = function(ctx){
var fs = ctx.requireCordovaModule('fs');
var path = ctx.requireCordovaModule('path');
var deferral = ctx.requireCordovaModule('q').defer();
//get package name here
//do other stuff
}
I have done a lot of research but have not been able to find this.
Thanks.
It doesn't look like it is available off of the context object, but you could try to parse the config.xml.
module.exports = function(context) {
var fs = require('fs');
var path = require('path');
var config_xml = path.join(context.opts.projectRoot, 'config.xml');
var et = context.requireCordovaModule('elementtree');
var data = fs.readFileSync(config_xml).toString();
var etree = et.parse(data);
console.log(etree.getroot().attrib.id);
};
The local-webserver plugin uses a similar strategy for reading config properties.
Here my compilation from different answers that works in 2021.
I use it to update some parameters in Xcode project for plugins compilation.
You can see that I am getting here app id and name from config.xml
And you can add it to after_prepare hook:
<hook src="scripts/addBuildSettingsToXcode.js" type="after_prepare" />
#!/usr/bin/env node
let fs = require('fs');
let xcode = require('xcode');
let path = require('path');
let et = require('elementtree');
module.exports = function (context) {
//console.log(context);
function addBuildPropertyToDebugAndRelease(prop, value) {
console.log('Xcode Adding ' + prop + '=' + value);
myProj.addBuildProperty(prop, value, 'Debug');
myProj.addBuildProperty(prop, value, 'Release');
}
function updateBuildPropertyToDebugAndRelease(prop, value) {
console.log('Xcode Updating ' + prop + '=' + value );
myProj.updateBuildProperty(prop, value, 'Debug');
myProj.updateBuildProperty(prop, value, 'Release');
}
// Getting app id and name from config.xml
let config_xml = path.join(context.opts.projectRoot, 'config.xml');
let data = fs.readFileSync(config_xml).toString();
let etree = et.parse(data);
let appId = etree.getroot().attrib.id ;
let appName = etree.getroot().find('name')['text'];
// Building project path
let projectPath = 'platforms/ios/' + appName + '.xcodeproj/project.pbxproj';
// Opening Xcode project and parsing it
myProj = xcode.project(projectPath);
myProj = myProj.parseSync();
// Common properties
addBuildPropertyToDebugAndRelease('DEVELOPMENT_TEAM', 'CGXXXXXXX');
addBuildPropertyToDebugAndRelease('CODE_SIGN_IDENTITY', '"Apple Development"');
// Compilation properties
addBuildPropertyToDebugAndRelease('ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES', 'YES');
// Save project file
fs.writeFileSync(projectPath, myProj.writeSync());
};

gulp - wrap plugin (which uses through2) output with string

I would like to know how exactly can I manipulate the output of my Gulp plugin so, for example, no matter how many files are passed to the plugin, it will wrap the output with a string. Currently I cannot know when does the last file is done.
The super simplified example below will iterate on 3 files and will create a new file named output.js and in it there will be three times the string xxx (xxxxxxxxx).
I would like the plugin itself to wrap the contents so the output will
be: +xxxxxxxxx+.
How can I do this?
Thanks!
Gulpfile
var gulp = require('gulp');
var concat = require('gulp-concat');
var foo = require('./index');
gulp.task('default', function() {
gulp.src([a.html, b.html, c.html])
.pipe(foo())
.pipe(concat('output.js'))
.pipe(gulp.dest('./test/output'))
});
The most basic gulp plugin (index.js):
var through2 = require('through2'),
gutil = require('gulp-util');
var PLUGIN_NAME = 'foo';
module.exports = function( options ){
// through2.obj(fn) is a convenience wrapper around
// through2({ objectMode: true }, fn)
return through2.obj(function( file, enc, callback ){
file.contents = new Buffer( 'xxx' );
this.push(file);
callback();
});
}
I understand the files are currently simply returned modified, but what I don't understand is how to append text and return the concatenated result that I want, while keeping it OK with Gulp working standards.
The "real" plugin should actually wrap the files results with:
var foo = { FILES_CONTENT }
where FILES_CONTENT will actually be a a concatenated string of all the files:
"file_name" : "file_content",
"file_name" : "file_content",
...
I would make the following changes to your gulpfile.js:
var gulp = require('gulp');
var foo = require('./index.js');
gulp.task('default', function() {
return gulp.src(['a.html', 'b.html', 'c.html'])
.pipe(foo({fileName:'output.js', varName:'bar'}))
.pipe(gulp.dest('./test/output'))
});
Since your foo() plugin itself will concatenate all the files, there's no need to use gulp-concat at all. Instead your plugin should accept an option fileName that provides the name of the generated file. I've also added another option varName that will provide the name of the var in the output file.
I'll assume that a.html, b.html and c.html are simple HTML files, something like this:
<h1 class="header">a</h1>
As you've already realized you need to concat all the files in the plugin itself. That's not really difficult however and doesn't require a lot of code. Here's a index.js which does exactly that:
var through2 = require('through2'),
gutil = require('gulp-util'),
path = require('path'),
File = require('vinyl');
var PLUGIN_NAME = 'foo';
module.exports = function(options) {
var files = { };
var outputFile = null;
return through2.obj(function(file, enc, callback){
outputFile = outputFile || file;
var filePath = path.relative(file.base, file.path);
files[filePath] = file.contents.toString();
callback();
}, function(callback) {
outputFile = outputFile ? outputFile.clone() : new File();
outputFile.path = path.resolve(outputFile.base, options.fileName);
outputFile.contents = new Buffer(
'var ' + options.varName + ' = ' +
JSON.stringify(files, null, 2) + ';'
);
this.push(outputFile);
callback();
});
}
Since you want to output a key/value mapping from file names to file contents our transformFunction just stores both of those things in a regular JavaScript object files. None of the input files themselves are emitted. Their names and contents are just stored until we have all of them.
The only tricky part is making sure that we respect the .base property of each file as is customary for gulp plugins. This allows the user to provide a custom base folder using the base option in gulp.src().
Once all files have been processed through2 calls the flushFunction. In there we create our output file with the provided fileName (once again making sure we respect the .base property).
Creating the output file contents is then just a matter of serializing our files object using JSON.stringify() (which automatically takes care of any escaping that has to be done).
The resulting ./test/output/output.js will then look like this:
var bar = {
"a.html": "<h1 class=\"header\">a</h1>\n",
"b.html": "<h1 class=\"header\">b</h1>\n",
"c.html": "<h1 class=\"header\">c</h1>\n"
};
You should use the gulp pipeline technique (standard).
This means that you can use the gulp-insert package in order
to add the string xxx.
var insert = require('gulp-insert');
.pipe(insert.append('xxx')); // Appends 'xxx' to the contents of every file
You can also prepend, append and wrap with this package and it support of course the gulp standards.
So the full example will be:
var gulp = require('gulp');
var concat = require('gulp-concat');
var foo = require('./index');
var insert = require('gulp-insert');
gulp.task('default', function() {
gulp.src([a.html, b.html, c.html])
.pipe(foo()
.pipe(insert.append('xxx'))
.pipe(concat('output.js'))
.pipe(gulp.dest('./test/output'))
});

Having hierarchical node.js config files

I checked here first before I come here.
My question is a little different than that.
I have following folder structure.
app.js
global.config.js
node-modules
my module
module.config.js
index.js
global.config.js:
var config = {};
config.loggingLevel: "error"; // global setting for all custom modules
module.config.js:
var config = {};
config.loggingLevel: "info"; // ideally should override global setting
index.js
var globalDir = path.dirname(require.main.filename) + "\\global.config.js";
var globalConfig = require(globalDir);
var moduleConfig = require("./module.config.js");
// merge configs here and use only one config object
As seen here, I have a global and module specific config files. I would like to merge them into one config file and use that way.
Is there any way to achieve this easily? (Like a pre-written module) Or should I iterate through each property and override if same key exists?
I wrote a module located under https://github.com/quimsy/espresso which contains a Config Tree Builder. Its (partly) a port of Symfony's Config Component.
You can define a configuration tree:
var TreeBuilder = require("Espresso/Config/Definition/Builder/TreeBuilder");
var builder = new TreeBuilder();
var root = builder.root("connection");
root
.children()
.scalarNode("host").defaultValue("localhost").end()
.end();
And with a Processor You can merge multiple configurations respecting these rules:
var Processor = require("Espresso/Config/Definition/Processor");
var processor = new Processor();
var mergedConfiguration = processor.process( root.getNode(), [ config1, config2 ] );
Regarding the example above, You would do something like:
var builder = new (require("Espresso/Config/Definition/Builder/TreeBuilder")();
var root = builder.root("myConfigRoot");
/* some definitions */
var processor = new (require("Espresso/Config/Definition/Processor"))();
var config = processor.process( root.getNode(), [ globalConfig, moduleConfig ] );
Look at Defining and Processing Configuration Values, since this module is 90% compatible - some different naming conventions and taking respect to Array/Objects (in PHP there is only the Array type).

how to use gulp-filter with main-bower-files to filter on directory in the middle

I want to filter just the files which include the directory ui-router somewhere in the middle of the path.
I have the following code:
var gulp = require('gulp');
var mainBowerFiles = require('main-bower-files');
var debug = require('gulp-debug');
var gulpFilter = require('gulp-filter');
gulp.task('default',function() {
var bower_files = mainBowerFiles();
var js_filter = gulpFilter(['**/*.js']);
gulp.src(bower_files)
.pipe(js_filter)
.pipe(debug({title: 'unicorn:'}))
var js_filter = gulpFilter(['**/ui-router/**']);
gulp.src(bower_files)
.pipe(js_filter)
.pipe(debug({title: 'unicorn1:'}))
});
The output is:
[12:10:53] unicorn: bower_components\ngstorage\ngStorage.js
[12:10:53] unicorn: bower_components\ui-router\release\angular-ui-router.js
[12:10:53] unicorn: bower_components\x2js\xml2json.min.js [12:10:53]
unicorn1: 0 items
Meaning that ['**/*.js'] works to filter out all js files.
But ['**/ui-router/**'] does not work. What is problematic with this pattern?
I read the following doc https://github.com/isaacs/node-glob and i don't see why it should not work.
You can filter the result of main-bower-files:
var files = mainBowerFiles(/\/ui\-router\//);
After hacking with this a long time i found the issue.
In gulp-filter the vinyl file.relative property is sent.Comment from Sindre Sorhus
In our case the files are without globs(What i understand) and therefore we get just the name of the file without the directory.
The solution is to write instead of gulp.src(bower_files) gulp.src(bower_files,{base:__dirname})
Here we say gulp from where to start the relative file.

Using Yeoman programmatically inside nodejs project

I want to use an yeoman generator inside a NodeJS project
I installed yeoman-generatorand generator-git (the generator that I want use) as locally dependency, and, at this moment my code is like this:
var env = require('yeoman-generator')();
var path = require('path');
var gitGenerator = require('generator-git');
var workingDirectory = path.join(process.cwd(), 'install_here/');
generator = env.create(gitGenerator);
obviously the last line doesn't work and doesn't generate the scaffold.
The question: How to?
Importantly, I want to stay in local dependency level!
#simon-boudrias's solution does work, but after I changed the process.chdir(), this.templatePath() and this.destinationPath() returns same path.
I could have use this.sourcePath() to tweak the template path, but having to change this to each yeoman generator is not so useful. After digging to yo-cli, I found the following works without affecting the path.
var env = require('yeoman-environment').createEnv();
env.lookup(function() {
env.run('generator-name');
});
env.create() only instantiate a generator - it doesn't run it.
To run it, you could call generator.run(). But that's not ideal.
The best way IMO would be this way:
var path = require('path');
var env = require('yeoman-generator')();
var gitGenerator = require('generator-git');
// Optionnal: look every generator in your system. That'll allow composition if needed:
// env.lookup();
env.registerStub(gitGenerator, 'git:app');
env.run('git:app');
If necessary, make sure to process.chdir() in the right directory before launching your generator.
Relevant documentation on the Yeoman Environment class can be found here: http://yeoman.io/environment/Environment.html
Also see: http://yeoman.io/authoring/integrating-yeoman.html
The yeoman-test module is also very useful if you want to pass predefined answers to your prompts. This worked for me.
var yeomanTest = require('yeoman-test');
var answers = require('from/some/file.json');
var context = yeomanTest.run(path.resolve('path/to/generator'));
context.settings.tmpdir = false; // don't run in tempdir
context.withGenerators([
'paths/to/subgenerators',
'more/of/them'
])
.withOptions({ // execute with options
'skip-install': true,
'skip-sdk': true
})
.withPrompts(answers) // answer prompts
.on('end', function () {
// do some stuff here
});

Resources