Permalink
Browse files

first commit

  • Loading branch information...
techjacker committed Feb 20, 2013
0 parents commit b27539a4b8091e7a8719ac5a963aa5f96d5e999a
@@ -0,0 +1 @@
+node_modules
22 LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2013 Andrew Griffiths
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
131 README.md
@@ -0,0 +1,131 @@
+# node-version-assets
+
+- Versions your static assets using the power of streams!
+- Version number produced from md5hash of each file -> so unchanged assets will keep the same file name and not blow the browser cache.
+- Add to your build scripts/favourite build tool.
+- See below for grunt integration example.
+
+
+## What it does:
+1. Renames assets on filesystem
+
+ ## BEFORE
+ /www/project-x/public/css$ ls -l
+ > all-min.css
+
+ ## AFTER
+ /www/project-x/public/css$ ls -l
+ > all-min.44d0440440442524c6d667900275e.css
+
+2. Find and replaces references to them in files:
+
+ <!-- index.html: BEFORE -->
+ <link rel="stylesheet" type="text/css" href="css/all-min.css">
+
+ <!-- index.html: AFTER -->
+ <link rel="stylesheet" type="text/css" href="css/all-min.44d0440440442524c6d667900275e.css">
+
+
+## How this module fits into your build process:
+1. you: generate fresh asset and output to the location specified in options.assets (> /public/css/all.min.css)
+2. module: renames fresh asset file to versioned name (> all.min.__newversion__.css)
+3. module: deletes original fresh asset file (all.min.css > deleted)
+4. module: deletes old versioned asset files in the same dir (all.min.__oldversion__.css > deleted)
+
+
+## Example
+
+#### Input options in script:
+ /* ~/www/project-x/version.js */
+ var Version = require("node-version-assets");
+ var versionInstance = new Version({
+ assets: ['public/css/all-min.css', 'public/js/app.js'],
+ grepFiles: ['views/prod/index.html']
+ });
+ versionInstance.run();
+
+#### Run the script:
+ andy@bada55:~/www/project-x$ node version.js
+
+ Deleted Assets:
+ public/css/all-min.css
+ public/js/app.js
+
+ Versioned Assets Created:
+ public/css/all-min.44d0440440442524c6d667900275e.css: file unchanged > version number re-used
+ public/js/app.12d070550742574e8d87900er34.js: file unchanged > version number re-used
+
+ Files whose contents were updated with refs to renamed asset files:
+ views/prod/index.html
+
+
+## Grunt Example
+
+ grunt.registerTask('version-assets', 'version the static assets just created', function() {
+
+ var Version = require("node-version-assets");
+ var versionInstance = new Version({
+ assets: ['public/css/all-min.css', 'public/js/app.js'],
+ grepFiles: ['views/prod/index.html']
+ });
+
+ var cb = this.async(); // grunt async callback
+ versionInstance.run(cb);
+ });
+
+ // make sure versioning is final task
+ grunt.registerTask('default', 'lint rjs jpgmin gifmin pngmin concat cssmin version-assets');
+
+
+## Options
+
+#### @param {options}
+- accepts: object
+
+#### @param {options.assets}
+- accepts: array of strings
+- required: each item of the array is the relative file path to the static asset
+- each static asset listed will have the new version number inserted before the file type prefix, eg:
+ - all-min.js > all.min.01135498.js
+- if there is a previous version number in the same position then it will be replaced, eg:
+ - all-min.oldversion234.js > all.min.01135498.js
+
+
+## Optional, ahem, Options
+
+#### @param {options.grepFiles}
+- accepts: array of strings
+- list of files (relative filepaths) containing references to the {options.assets} which need to be renamed
+
+
+#### @param {options.newVersion}
+- accepts: string (only numbers or letters)
+- not required: defaults to generating an md5 hash of the file (recommended to leave as default as md5 hashing means that assets will not blow browser cache if they're unchanged)
+
+
+#### @param {options.requireJs}
+- accepts: boolean
+- not required: defaults to false
+
+If set to true then unsuffixed js assets (listed in the assets array) will be updated to the new version, eg:
+
+ <script type="text/javascript">
+ require.config({
+ paths: {
+
+ <!-- BEFORE: -->
+ main: "js/app.newie"
+
+ <!-- AFTER: -->
+ main: "js/app.newie.001"
+ }
+ });
+ </script>
+
+
+
+## Potential Gotchas
+- Assets that you want versioned must be listed in the assets array
+- Check all asset paths are correct: they must be relative to the dir where you are executing the command.
+- The new assets must exist in an unversioned form on the filesystem to be renamed and be listed in the {options.assets} array
+- If you specify a version number it can only contain letters or numbers (ie no special characters)
@@ -0,0 +1,7 @@
+/*jslint nomen: true, plusplus: false, sloppy: true, white:true*/
+/*jshint nomen: false, curly: true, plusplus: false, expr:true, undef:true, newcap:true, latedef:true, camelcase:true */
+/*global iScroll:false, setTimeout: false, document:false, WebKitCSSMatrix:false, _: false, Backbone: false, backbone: false, $: false, define: false, require: false, console: false, window:false */
+
+var main = require('./lib/main');
+
+module.exports = main;
@@ -0,0 +1,78 @@
+var util = require('util');
+var fs = require('fs');
+var _ = require('underscore');
+var crypto = require('crypto');
+
+// my libs
+var Replacer = require('./replace-text');
+
+
+var Creator = function (opts) {
+
+ var validReplacer = opts.replacer instanceof Replacer;
+
+ // minimal error checking
+ if (!_.isObject(opts)) {
+ throw new Error('an opts obj must be passed to Deleter contructor');
+ }
+
+ // streaming error evts
+ if (_.isFunction(opts.cb)) {
+ this.cb = opts.cb;
+ }
+
+ if (validReplacer && _.isString(opts.filePath)) {
+ this.filePath = opts.filePath;
+ this.replacer = opts.replacer;
+ } else {
+ throw new Error({
+ message: "invalid replacers"
+ });
+ }
+
+ this.outputFilePath = this.replacer.filePathBeg + '.' + this.replacer.newVersion + this.replacer.filePathSuffix;
+
+ return _(this).bindAll('run');
+};
+
+
+
+
+Creator.prototype.run = function (callback) {
+
+ var cb = (_.isFunction(callback)) ? callback : function() {};
+
+ var output = fs.createWriteStream(this.outputFilePath);
+
+ // attach events
+ output.on('error', cb);
+ output.end = function () {
+ cb(null, this.outputFilePath);
+ }.bind(this);
+
+ // bombs away!
+ output.write(this.replacer.fileContents);
+ output.end();
+};
+
+module.exports = Creator;
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Doesn't work because we've already deleted original file by the time we run this //
+// Solution: cache the filecontents result when calculating md5hash = first task to be run after checking that files exist //
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Creator.prototype.run = function (callback) {
+// var cb = (_.isFunction(callback)) ? callback : function() {};
+// // file streams
+// this.inputFile = fs.createReadStream(this.filePath);
+// this.outputFile = fs.createWriteStream(this.outputFilePath);
+// // attach error handlers
+// this.inputFile.on('error', cb);
+// this.outputFile.on('error', cb);
+ // attach callback
+ // this.outputFile.end = function () {
+ // cb(null);
+ // };
+// // bombs away!!!
+// this.inputFile.pipe(this.outputFile);
+// };
@@ -0,0 +1,99 @@
+var util = require('util');
+var fs = require('fs');
+var _ = require('underscore');
+var async = require('async');
+// my lib
+var Replacer = require('./replace-text');
+
+
+var Deleter = function (opts) {
+
+ var validReplacer = opts.replacer instanceof Replacer;
+
+ // minimal error checking
+ if (!_.isObject(opts)) {
+ throw new Error('an opts obj must be passed to Deleter contructor');
+ }
+
+ // streaming error evts
+ if (_.isFunction(opts.cb)) {
+ this.cb = opts.cb;
+ }
+
+ if (validReplacer && _.isString(opts.filePath)) {
+ this.filePath = opts.filePath;
+ this.replacer = opts.replacer;
+ } else {
+ throw new Error({
+ message: "invalid replacers"
+ });
+ }
+
+ return _(this).bindAll('run', 'dirList', 'markForDeletion', 'deleteFiles');
+};
+
+Deleter.prototype.dirList = function (callback) {
+
+ this.directory = this.filePath.split('/').slice(0, -1).join('/');
+
+ fs.readdir(this.directory, function (err, files) {
+
+ this.directoryContents = _(files).map(function (fileName) {
+ // return this.directory + '/' + fileName;
+ return fileName;
+ }.bind(this));
+
+ _.isFunction(callback) && callback(null, this.directoryContents);
+
+ }.bind(this))
+};
+
+Deleter.prototype.markForDeletion = function (callback) {
+
+ this.toBeDeleted = [];
+
+ this.directoryContents.length && this.directoryContents.forEach(function (fileName, index) {
+
+ // var oldVersion = this.replacer.oldVersionRegex.test(fileName);
+ var oldVersion = this.replacer.oldVersionFileNameRegex.test(fileName);
+ var originalVersion = (fileName === this.replacer.fileName);
+
+ if (oldVersion || originalVersion) {
+ // this.toBeDeleted.push(fileName);
+ this.toBeDeleted.push(this.directory + '/' + fileName);
+ }
+
+ }.bind(this));
+
+ _.isFunction(callback) && callback(null, this.toBeDeleted);
+};
+
+Deleter.prototype.deleteFiles = function (callback) {
+
+ // fs.stat(path, callback(err, stats)) and stats.isDirectory()
+ // currently this code will also delete directories
+ // add above fn to check before deleting if this is an issue
+ if (this.toBeDeleted.length) {
+ async.map(this.toBeDeleted, fs.unlink, callback);
+ // console.log('this.toBeDeleted', this.toBeDeleted);
+ } else {
+ _.isFunction(callback) && callback(null, this.toBeDeleted);
+ }
+};
+
+
+Deleter.prototype.run = function (callback) {
+
+ // call passed callback or do no-op
+ var cb = (_.isFunction(callback)) ? callback : function() {};
+
+ async.series([
+ this.dirList,
+ this.markForDeletion,
+ this.deleteFiles
+ ], function (err, results) {
+ cb(err, this.toBeDeleted);
+ }.bind(this));
+};
+
+module.exports = Deleter;
Oops, something went wrong.

0 comments on commit b27539a

Please sign in to comment.