├── .gitignore ├── .htaccess ├── LICENSE ├── README.md ├── appcache.manifest ├── lib ├── appcache.js └── squirrel.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /.htaccess: -------------------------------------------------------------------------------- 1 | AddType text/cache-manifest .manifest -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Mark McDonnell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Squirrel 2 | 3 | Node based cli tool using PhantomJS to automate generation of an Application Cache manifest file for a specified URL 4 | 5 | ## Requirements 6 | 7 | You'll need Node.js installed... 8 | 9 | `brew install node` 10 | 11 | If you're not using [Homebrew](http://brew.sh/) you can install Node using [Nave](https://github.com/isaacs/nave#nave) 12 | 13 | ## Installation 14 | 15 | `npm install -g squirrel-js` 16 | 17 | ## Usage 18 | 19 | `squirrel [url]` 20 | 21 | ## Example 22 | 23 | `squirrel bbc.co.uk/news` 24 | 25 | ## Output 26 | 27 | Creates a `appcache.manifest` file in the current directory you run the command. 28 | -------------------------------------------------------------------------------- /appcache.manifest: -------------------------------------------------------------------------------- 1 | CACHE MANIFEST 2 | # Timestamp: 123 3 | 4 | # Explicitly cached 'master entries'. 5 | CACHE: 6 | 7 | # Images 8 | # content will go here 9 | 10 | # Internal HTML documents 11 | # content will go here 12 | 13 | # Style Sheets 14 | # content will go here 15 | 16 | # JavaScript 17 | # content will go here 18 | 19 | # Resources that require the user to be online. 20 | NETWORK: 21 | * 22 | 23 | FALLBACK: 24 | / /offline.html -------------------------------------------------------------------------------- /lib/appcache.js: -------------------------------------------------------------------------------- 1 | var unique = require('lodash.uniq'); 2 | var system = require('system'); 3 | var fs = require('fs'); 4 | var page = require('webpage').create(); 5 | var args = system.args; 6 | var manifest = args[2]; 7 | var css = []; 8 | var images = []; 9 | var javascript = []; 10 | var links; 11 | var url; 12 | var path; 13 | 14 | bootstrap(); 15 | pageSetUp(); 16 | openPage(); 17 | 18 | function bootstrap() { 19 | if (urlProvided()) { 20 | url = cleanUrl(args[1]); 21 | } else { 22 | var error = new Error('Sorry a valid URL could not be recognised'); 23 | error.additional = 'Valid URL Example: bbc.co.uk/news'; 24 | 25 | throw error; 26 | 27 | phantom.exit(); 28 | } 29 | 30 | if (bbcNews()) { 31 | // We want to serve up the responsive code base... 32 | phantom.addCookie({ 33 | 'name' : 'ckps_d', 34 | 'value' : 'm', 35 | 'domain': '.bbc.co.uk' 36 | }); 37 | } 38 | } 39 | 40 | function pageSetUp() { 41 | page.onResourceRequested = function(request) { 42 | if (/\.(?:png|jpeg|jpg|gif)$/i.test(request.url)) { 43 | images.push(request.url); 44 | } 45 | 46 | if (/\.(?:js)$/i.test(request.url)) { 47 | javascript.push(request.url); 48 | } 49 | 50 | if (/\.(?:css)$/i.test(request.url)) { 51 | css.push(request.url); 52 | } 53 | }; 54 | 55 | page.onError = function(msg, trace) { 56 | console.log('Error :', msg); 57 | 58 | trace.forEach(function(item) { 59 | console.log('Trace: ', item.file, ':', item.line); 60 | }); 61 | } 62 | 63 | page.viewportSize = { width: 1920, height: 800 }; 64 | } 65 | 66 | function openPage() { 67 | page.open(url, function(status) { 68 | links = unique(getLinks()); 69 | images = unique(images); 70 | css = unique(css); 71 | javascript = unique(javascript); 72 | 73 | populateManifest(); 74 | 75 | // Anything written to `stdout` is actually passed back to our Node script callback 76 | console.log(JSON.stringify({ 77 | links : links.length, 78 | images : images.length, 79 | css : css.length, 80 | javascript : javascript.length, 81 | manifestContent : manifest 82 | })); 83 | 84 | phantom.exit(); 85 | }); 86 | } 87 | 88 | function urlProvided() { 89 | return args.length > 1 && /(?:www\.)?[a-z1-9]+\./i.test(args[1]); 90 | } 91 | 92 | function cleanUrl(providedUrl) { 93 | // If no http or https found at the start of the url... 94 | if (/^(?!https?:\/\/)[\w\d]/i.test(providedUrl)) { 95 | return 'http://' + providedUrl + '/'; 96 | } 97 | } 98 | 99 | function bbcNews(){ 100 | if (/bbc.co.uk\/news/i.test(url)) { 101 | return true; 102 | } 103 | } 104 | 105 | function getLinks() { 106 | var results = page.evaluate(function() { 107 | return Array.prototype.slice.call(document.getElementsByTagName('a')).map(function(item) { 108 | return item.href; 109 | }); 110 | }); 111 | 112 | return results; 113 | } 114 | 115 | function writeVersion() { 116 | manifest = manifest.replace(/# Timestamp: \d+/i, '# Timestamp: ' + (new Date()).getTime()); 117 | } 118 | 119 | function writeListContentFor(str, type) { 120 | manifest = manifest.replace(new RegExp('(# ' + str + ')\\n[\\s\\S]+?\\n\\n', 'igm'), function(match, cg) { 121 | return cg + '\n' + type.join('\n') + '\n\n'; 122 | }); 123 | } 124 | 125 | function populateManifest() { 126 | writeVersion(); 127 | 128 | writeListContentFor('Images', images); 129 | writeListContentFor('Internal HTML documents', links); 130 | writeListContentFor('Style Sheets', css); 131 | writeListContentFor('JavaScript', javascript); 132 | } 133 | -------------------------------------------------------------------------------- /lib/squirrel.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var userArguments = process.argv.slice(2); // copies arguments list but removes first two options (script exec type & exec location) 4 | 5 | if (userArguments.length > 1) { 6 | throw new Error('Only one argument should be specified (the url you want to generate the appcache for)'); 7 | } 8 | 9 | var fs = require('fs'); 10 | var shell = require('child_process').execFile; 11 | var phantomjs = require('phantomjs').path; 12 | var scriptToExecute = __dirname + '/appcache.js'; 13 | var manifest = __dirname + '/../appcache.manifest'; 14 | var url = userArguments[0]; 15 | var manifestContent; 16 | var data; 17 | 18 | fs.readFile(manifest, bootstrap); 19 | 20 | function bootstrap(err, contentAsBuffer) { 21 | if (err) throw err; 22 | 23 | manifestContent = contentAsBuffer.toString('utf8'); 24 | 25 | shell(phantomjs, [scriptToExecute, url, manifestContent], function(err, stdout, stderr) { 26 | if (err) throw err; 27 | 28 | // Sometimes an error in the loaded page's JavaScript doesn't get picked up or thrown? 29 | // But the error comes in via stdout and causes JSON parsing to break 30 | try { 31 | data = JSON.parse(stdout); 32 | } catch(err) { 33 | log('Whoops! Seems there was an error? You\'ll find the stack trace below.'); 34 | error(err); 35 | } 36 | 37 | displayStatistics(); 38 | createManifestFile(); 39 | }); 40 | } 41 | 42 | function displayStatistics() { 43 | log(''); // adds extra line of spacing when displaying the results 44 | log('Links: ' + data.links); 45 | log('Images: ' + data.images); 46 | log('CSS: ' + data.css); 47 | log('JavaScript: ' + data.javascript); 48 | } 49 | 50 | function createManifestFile() { 51 | fs.writeFile(process.cwd() + '/appcache.manifest', data.manifestContent, function(err) { 52 | if (err) throw err; 53 | 54 | log('\nManifest file created'); 55 | }); 56 | } 57 | 58 | function log(message) { 59 | process.stdout.write(message + '\n'); 60 | } 61 | 62 | function error(err) { 63 | process.stderr.write(err); 64 | } 65 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "squirrel-js", 3 | "version": "0.1.4", 4 | "description": "Node based cli tool using PhantomJS to automate generation of an Application Cache manifest file for a specified URL", 5 | "main": "lib/squirrel", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "engines": { 10 | "node": ">=0.10" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git://github.com/Integralist/Squirrel.git" 15 | }, 16 | "preferGlobal": "true", 17 | "bin": { 18 | "squirrel": "lib/squirrel.js" 19 | }, 20 | "dependencies": { 21 | "phantomjs": "~1.9.2-6", 22 | "lodash.uniq": "~2.4.1" 23 | }, 24 | "keywords": [ 25 | "appcache", 26 | "phantomjs", 27 | "cli" 28 | ], 29 | "author": "Mark McDonnell (http://www.integralist.co.uk/)", 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/Integralist/Squirrel/issues" 33 | }, 34 | "homepage": "https://github.com/Integralist/Squirrel" 35 | } 36 | --------------------------------------------------------------------------------