chore(Grunt): switch from Rake to Grunt

Migrates the Angular project from Rake to Grunt.

Benefits:
- Drops Ruby dependency
- Lowers barrier to entry for contributions from JavaScript ninjas
- Simplifies the Angular project setup and build process
- Adopts industry-standard tools specific to JavaScript projects
- Support building angular.js on Windows platform (really?!? why?!?)

BREAKING CHANGE: Rake is completely replaced by Grunt. Below are the deprecated Rake tasks and their Grunt equivalents:

rake --> grunt
rake package --> grunt package
rake init --> N/A
rake clean --> grunt clean
rake concat_scenario --> grunt build:scenario
rake concat --> grunt build
rake concat_scenario --> grunt build:scenario
rake minify --> grunt minify
rake version --> grunt write:version
rake docs --> grunt docs
rake webserver --> grunt webserver
rake test --> grunt test
rake test:unit --> grunt test:unit
rake test:<jqlite|jquery|modules|e2e> --> grunt test:<jqlite|jquery|modules|end2end|e2e>
rake test[Firefox+Safari] --> grunt test --browsers Firefox,Safari
rake test[Safari] --> grunt test --browsers Safari
rake autotest --> grunt autotest

NOTES:
* For convenience grunt test:e2e starts a webserver for you, while grunt test:end2end doesn't.
  Use grunt test:end2end if you already have the webserver running.
* Removes duplicate entry for Describe.js in the angularScenario section of angularFiles.js
* Updates docs/src/gen-docs.js to use #done intead of the deprecated #end
* Uses grunt-contrib-connect instead of lib/nodeserver (removed)
* Removes nodeserver.sh, travis now uses grunt webserver
* Built and minified files are identical to Rake's output, with the exception of one less
  character for git revisions (using --short) and a couple minor whitespace differences

Closes #199

Conflicts:

	Rakefile
This commit is contained in:
Dave Geddes
2012-10-21 00:37:59 -06:00
committed by Igor Minar
parent b13da18e11
commit 7a77fdae4f
18 changed files with 468 additions and 701 deletions

View File

@@ -5,9 +5,9 @@ node_js:
before_script:
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
- npm install -g testacular@canary
- rake package
- ./nodeserver.sh > /dev/null &
- npm install -g grunt-cli
- grunt package
- grunt webserver > /dev/null &
script:
- rake test[Firefox,"--reporters=dots"]
- grunt test --browsers Firefox --reporters=dots

170
Gruntfile.js Normal file
View File

@@ -0,0 +1,170 @@
var files = require('./angularFiles').files;
var util = require('./lib/grunt/utils.js');
module.exports = function(grunt) {
//grunt plugins
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-connect');
grunt.loadNpmTasks('grunt-contrib-compress');
grunt.loadTasks('lib/grunt');
var NG_VERSION = util.getVersion();
//global beforeEach
util.init();
//config
grunt.initConfig({
NG_VERSION: NG_VERSION,
connect: {
devserver: {
options: {
port: 8000,
hostname: 'localhost',
base: '.',
keepalive: true,
middleware: function(connect, options){
return [
//uncomment to enable CSP
// util.csp(),
util.rewrite(),
connect.favicon('images/favicon.ico'),
connect.static(options.base),
connect.directory(options.base)
];
}
}
},
testserver: {}
},
test: {
jqlite: 'testacular-jqlite.conf.js',
jquery: 'testacular-jquery.conf.js',
modules: 'testacular-modules.conf.js',
//NOTE run grunt test:e2e instead and it will start a webserver for you
end2end: 'testacular-e2e.conf.js'
},
autotest: {
jqlite: 'testacular-jqlite.conf.js',
jquery: 'testacular-jquery.conf.js'
},
clean: {build: ['build']},
build: {
scenario: {
dest: 'build/angular-scenario.js',
src: [
'lib/jquery/jquery.js',
util.wrap([files['angularSrc'], files['angularScenario']], 'ngScenario/angular')
],
styles: {
css: ['css/angular.css', 'css/angular-scenario.css']
}
},
angular: {
dest: 'build/angular.js',
src: util.wrap([files['angularSrc']], 'angular'),
styles: {
css: ['css/angular.css'],
minify: true
}
},
loader: {
dest: 'build/angular-loader.js',
src: util.wrap(['src/loader.js'], 'loader')
},
mocks: {
dest: 'build/angular-mocks.js',
src: ['src/ngMock/angular-mocks.js'],
strict: false
},
sanitize: {
dest: 'build/angular-sanitize.js',
src: util.wrap([
'src/ngSanitize/sanitize.js',
'src/ngSanitize/directive/ngBindHtml.js',
'src/ngSanitize/filter/linky.js',
], 'module')
},
resource: {
dest: 'build/angular-resource.js',
src: util.wrap(['src/ngResource/resource.js'], 'module')
},
cookies: {
dest: 'build/angular-cookies.js',
src: util.wrap(['src/ngCookies/cookies.js'], 'module')
},
bootstrap: {
dest: 'build/angular-bootstrap.js',
src: util.wrap(['src/bootstrap/bootstrap.js'], 'module')
},
bootstrapPrettify: {
dest: 'build/angular-bootstrap-prettify.js',
src: util.wrap(['src/bootstrap/bootstrap-prettify.js', 'src/bootstrap/google-prettify/prettify.js'], 'module'),
styles: {
css: ['src/bootstrap/google-prettify/prettify.css'],
minify: true
}
}
},
min: {
angular: 'build/angular.js',
cookies: 'build/angular-cookies.js',
loader: 'build/angular-loader.js',
resource: 'build/angular-resource.js',
sanitize: 'build/angular-sanitize.js',
bootstrap: 'build/angular-bootstrap.js',
bootstrapPrettify: 'build/angular-bootstrap-prettify.js',
},
docs: {
process: ['build/docs/*.html', 'build/docs/.htaccess']
},
copy: {
i18n: {
files: [
{ src: 'src/ngLocale/**', dest: 'build/i18n/', expand: true, flatten: true }
]
}
},
compress: {
build: {
options: {archive: 'build/angular-'+ NG_VERSION.full +'.zip'},
src: ['**'], cwd: 'build', expand: true
}
},
write: {
versionTXT: {file: 'build/version.txt', val: NG_VERSION.full},
versionJSON: {file: 'build/version.json', val: JSON.stringify(NG_VERSION)}
}
});
//alias tasks
grunt.registerTask('test:unit', ['test:jqlite', 'test:jquery', 'test:modules']);
grunt.registerTask('minify', ['clean', 'build', 'minall']);
grunt.registerTask('test:e2e', ['connect:testserver', 'test:end2end']);
grunt.registerTask('webserver', ['connect:devserver']);
grunt.registerTask('package', ['clean', 'buildall', 'minall', 'docs', 'copy', 'write', 'compress']);
grunt.registerTask('default', ['package']);
};

View File

@@ -21,21 +21,19 @@ Building AngularJS
---------
[Once you have your environment setup](http://docs.angularjs.org/misc/contribute) just run:
rake package
grunt package
Running Tests
-------------
To execute all unit tests, use:
rake test:unit
grunt test:unit
To execute end-to-end (e2e) tests, use:
rake package
rake webserver &
rake test:e2e
grunt package
grunt test:e2e
To learn more about the rake tasks, run `rake -T` and also read our
[contribution guidelines](http://docs.angularjs.org/misc/contribute) and instructions in this
[commit message](https://github.com/angular/angular.js/commit/9d168f058f9c6d7eeae0daa7cb72ea4e02a0003a).
To learn more about the grunt tasks, run `grunt --help` and also read our
[contribution guidelines](http://docs.angularjs.org/misc/contribute).

374
Rakefile
View File

@@ -1,374 +0,0 @@
require 'yaml'
include FileUtils
## High level flow of the build:
##
## clean -> init -> concat -> minify -> package
##
content = File.open('angularFiles.js', 'r') {|f| f.read }
files = eval(content.gsub(/\};(\s|\S)*/, '}').
gsub(/angularFiles = /, '').
gsub(/:/, '=>').
gsub(/\/\//, '#'));
BUILD_DIR = 'build'
task :default => [:package]
desc 'Init the build workspace'
task :init do
%x(npm install)
FileUtils.mkdir(BUILD_DIR) unless File.directory?(BUILD_DIR)
v = YAML::load( File.open( 'version.yaml' ) )
match = v['version'].match(/^([^-]*)(-snapshot)?$/)
NG_VERSION = Struct.new(:full, :major, :minor, :dot, :codename, :stable).
new(match[1] + (match[2] ? ('-' + %x(git rev-parse HEAD)[0..7]) : ''),
match[1].split('.')[0],
match[1].split('.')[1],
match[1].split('.')[2].sub(/\D+.*$/, ''),
v['codename'],
v['stable'])
end
desc 'Clean Generated Files'
task :clean do
FileUtils.rm_r(BUILD_DIR, :force => true)
FileUtils.mkdir(BUILD_DIR)
FileUtils.rm_r('test_out', :force => true)
end
desc 'Concat Scenario'
task :concat_scenario => :init do
concat_file('angular-scenario.js', [
'lib/jquery/jquery.js',
'src/ngScenario/angular.prefix',
files['angularSrc'],
files['angularScenario'],
'src/ngScenario/angular.suffix',
], gen_css('css/angular.css') + "\n" + gen_css('css/angular-scenario.css'))
end
desc 'Concat JSTD Scenario Adapter'
task :concat_jstd_scenario_adapter => :init do
concat_file('jstd-scenario-adapter.js', [
'src/ngScenario/jstd-scenario-adapter/angular.prefix',
'src/ngScenario/jstd-scenario-adapter/Adapter.js',
'src/ngScenario/jstd-scenario-adapter/angular.suffix',
])
# TODO(vojta) use jstd configuration when implemented
# (instead of including jstd-adapter-config.js)
File.open(path_to('jstd-scenario-adapter-config.js'), 'w') do |f|
f.write("/**\r\n" +
" * Configuration for jstd scenario adapter \n */\n" +
"var jstdScenarioAdapter = {\n relativeUrlPrefix: '/build/docs/'\n};\n")
end
end
desc 'Concat AngularJS files'
task :concat => :init do
concat_file('angular.js', [
'src/angular.prefix',
files['angularSrc'],
'src/angular.suffix',
], gen_css('css/angular.css', true))
FileUtils.cp_r 'src/ngLocale', path_to('i18n')
concat_file('angular-loader.js', [
'src/loader.prefix',
'src/loader.js',
'src/loader.suffix'])
concat_module('sanitize', [
'src/ngSanitize/sanitize.js',
'src/ngSanitize/directive/ngBindHtml.js',
'src/ngSanitize/filter/linky.js'])
concat_module('resource', ['src/ngResource/resource.js'])
concat_module('cookies', ['src/ngCookies/cookies.js'])
concat_module('bootstrap', ['src/bootstrap/bootstrap.js'])
concat_module('bootstrap-prettify', ['src/bootstrap/bootstrap-prettify.js',
'src/bootstrap/google-prettify/prettify.js'],
gen_css('src/bootstrap/google-prettify/prettify.css', true))
FileUtils.cp 'src/ngMock/angular-mocks.js', path_to('angular-mocks.js')
rewrite_file(path_to('angular-mocks.js')) do |content|
content.sub!('"NG_VERSION_FULL"', NG_VERSION.full)
end
end
desc 'Minify JavaScript'
task :minify => [:init, :concat, :concat_scenario, :concat_jstd_scenario_adapter] do
[ 'angular.js',
'angular-cookies.js',
'angular-loader.js',
'angular-resource.js',
'angular-sanitize.js',
'angular-bootstrap.js',
'angular-bootstrap-prettify.js'
].each do |file|
fork { closure_compile(file) }
end
Process.waitall
end
desc 'Generate version.txt and version.json files'
task :version => [:init] do
`echo #{NG_VERSION.full} > #{path_to('version.txt')}`
`echo '{
"full": "#{NG_VERSION.full}",
"major": "#{NG_VERSION.major}",
"minor": "#{NG_VERSION.minor}",
"dot": "#{NG_VERSION.dot}",
"codename": "#{NG_VERSION.codename}"\n}' > #{path_to('version.json')}`
end
desc 'Generate docs'
task :docs => [:init] do
`node docs/src/gen-docs.js`
[ path_to('docs/.htaccess'),
path_to('docs/index.html'),
path_to('docs/index-debug.html'),
path_to('docs/index-nocache.html'),
path_to('docs/index-jq.html'),
path_to('docs/index-jq-debug.html'),
path_to('docs/index-jq-nocache.html'),
path_to('docs/docs-scenario.html')
].each do |src|
rewrite_file(src) do |content|
content.sub!('"NG_VERSION_FULL"', NG_VERSION.full).
sub('"NG_VERSION_STABLE"', NG_VERSION.stable)
end
end
end
desc 'Create angular distribution'
task :package => [:clean, :minify, :version, :docs] do
zip_dir = "angular-#{NG_VERSION.full}"
zip_file = "#{zip_dir}.zip"
FileUtils.ln_s BUILD_DIR, zip_dir
%x(zip -r #{zip_file} #{zip_dir})
FileUtils.rm zip_dir
FileUtils.mv zip_file, path_to(zip_file)
puts "Package created: #{path_to(zip_file)}"
end
desc 'Start development webserver'
task :webserver, :port do |t, args|
exec "node lib/nodeserver/server.js #{args[:port]}"
end
desc 'Run all AngularJS tests'
task :test, :browsers, :misc_options do |t, args|
[ 'test:jqlite',
'test:jquery',
'test:modules',
'test:e2e'
].each do |task|
Rake::Task[task].invoke(args[:browsers], args[:misc_options])
end
end
namespace :test do
desc 'Run all unit tests (single run)'
task :unit, :browsers, :misc_options do |t, args|
[ 'test:jqlite',
'test:jquery',
'test:modules'
].each do |task|
Rake::Task[task].invoke(args[:browsers], args[:misc_options])
end
end
desc 'Run jqLite-based unit test suite (single run)'
task :jqlite, :browsers, :misc_options do |t, args|
start_testacular('testacular-jqlite.conf.js', true, args[:browsers], args[:misc_options])
end
desc 'Run jQuery-based unit test suite (single run)'
task :jquery, :browsers, :misc_options do |t, args|
start_testacular('testacular-jquery.conf.js', true, args[:browsers], args[:misc_options])
end
desc 'Run bundled modules unit test suite (single run)'
task :modules, :browsers, :misc_options do |t, args|
start_testacular('testacular-modules.conf.js', true, args[:browsers], args[:misc_options])
end
desc 'Run e2e test suite (single run)'
task :e2e, :browsers, :misc_options do |t, args|
start_testacular('testacular-e2e.conf.js', true, args[:browsers], args[:misc_options])
end
end
namespace :autotest do
desc 'Run jqLite-based unit test suite (autowatch)'
task :jqlite, :browsers, :misc_options do |t, args|
start_testacular('testacular-jqlite.conf.js', false, args[:browsers], args[:misc_options])
end
desc 'Run jQuery-based unit test suite (autowatch)'
task :jquery, :browsers, :misc_options do |t, args|
start_testacular('testacular-jquery.conf.js', false, args[:browsers], args[:misc_options])
end
end
###################
# utility methods #
###################
##
# generates css snippet from a given files and optionally applies simple minification rules
#
def gen_css(cssFile, minify = false)
css = ''
File.open(cssFile, 'r') do |f|
css = f.read
end
if minify
css.gsub! /\n/, ''
css.gsub! /\/\*.*?\*\//, ''
css.gsub! /:\s+/, ':'
css.gsub! /\s*\{\s*/, '{'
css.gsub! /\s*\}\s*/, '}'
css.gsub! /\s*\,\s*/, ','
css.gsub! /\s*\;\s*/, ';'
end
#escape for js
css.gsub! /\\/, "\\\\\\"
css.gsub! /'/, "\\\\'"
css.gsub! /\n/, "\\n"
return %Q{angular.element(document).find('head').append('<style type="text/css">#{css}</style>');}
end
##
# returns path to the file in the build directory
#
def path_to(filename)
return File.join(BUILD_DIR, *filename)
end
##
# returns the 32-bit mode force flags for java compiler if supported, this makes the build much
# faster
#
def java32flags
return '-d32 -client' unless Rake::Win32.windows? || `java -version -d32 2>&1`.match(/Error/i)
end
def closure_compile(filename)
puts "Minifying #{filename} ..."
min_path = path_to(filename.gsub(/\.js$/, '.min.js'))
%x(java \
#{java32flags()} \
-jar lib/closure-compiler/compiler.jar \
--compilation_level SIMPLE_OPTIMIZATIONS \
--language_in ECMASCRIPT5_STRICT \
--js #{path_to(filename)} \
--js_output_file #{min_path})
rewrite_file(min_path) do |content|
content.sub!("'use strict';", "").
sub!(/\(function\([^)]*\)\{/, "\\0'use strict';")
end
end
def concat_file(filename, deps, footer='')
puts "Creating #{filename} ..."
File.open(path_to(filename), 'w') do |f|
concat = 'cat ' + deps.flatten.join(' ')
content = %x{#{concat}}.
gsub('"NG_VERSION_FULL"', NG_VERSION.full).
gsub('"NG_VERSION_MAJOR"', NG_VERSION.major).
gsub('"NG_VERSION_MINOR"', NG_VERSION.minor).
gsub('"NG_VERSION_DOT"', NG_VERSION.dot).
gsub('"NG_VERSION_CODENAME"', NG_VERSION.codename).
gsub(/^\s*['"]use strict['"];?\s*$/, ''). # remove all file-specific strict mode flags
sub(/\(function\([^)]*\)\s*\{/, "\\0\n'use strict';") # add single strict mode flag
f.write(content)
f.write(footer)
end
end
def concat_module(name, files, footer='')
concat_file('angular-' + name + '.js', ['src/module.prefix'] + files + ['src/module.suffix'], footer)
end
def rewrite_file(filename)
File.open(filename, File::RDWR) do |f|
content = f.read
content = yield content
raise "File rewrite failed - No content!" unless content
f.truncate 0
f.rewind
f.write content
end
end
def start_testacular(config, singleRun, browsers, misc_options)
Rake::Task[:init].invoke
sh "./node_modules/testacular/bin/testacular start " +
"#{config} " +
"#{'--single-run=true' if singleRun} " +
"#{'--browsers=' + browsers.gsub('+', ',') if browsers} " +
"#{(misc_options || '').gsub('+', ',')}"
end

View File

@@ -1,5 +1,5 @@
#!/bin/bash
rake minify
grunt minify
gzip -c < build/angular.min.js > build/angular.min.js.gzip
ls -l build/angular.min.*

View File

@@ -81,15 +81,11 @@ Several steps are needed to check out and build AngularJS:
Before you can build AngularJS, you must install or configure the following dependencies on your
machine:
* {@link http://rake.rubyforge.org Rake}: We use Rake as our build system, which is pre-installed
on most Macintosh and Linux machines. If that is not true in your case, you can grab it from the
Rake website.
* Git: The {@link http://help.github.com/mac-git-installation Github Guide to Installing Git} is
quite a good source for information on Git.
* {@link http://nodejs.org Node.js}: We use Node to generate the documentation and to run a
development web server. Depending on your system, you can install Node either from source or as a
* {@link http://nodejs.org Node.js}: We use Node to generate the documentation, run a
development web server, run tests, and generate a build. Depending on your system, you can install Node either from source or as a
pre-packaged bundle.
Once installed, you'll also need several npms (node packages), which you can install once you checked out a local copy
@@ -98,6 +94,10 @@ pre-packaged bundle.
* `cd angular.js`
* `npm install`
* {@link http://gruntjs.com Grunt}: We use Grunt as our build system. Install the grunt command-line tool globally with:
* `sudo npm install -g grunt-cli`
## Creating a Github Account and Forking Angular
@@ -108,7 +108,7 @@ https://github.com/angular/angular.js main angular repository}.
## Building AngularJS
To build AngularJS, you check out the source code and use Rake to generate the non-minified and
To build AngularJS, you check out the source code and use Grunt to generate the non-minified and
minified AngularJS files:
1. To clone your Github repository, run:
@@ -129,7 +129,7 @@ minified AngularJS files:
5. To build AngularJS, run:
rake package
grunt package
The build output can be located under the `build` directory. It consists of the following files and
directories:
@@ -158,7 +158,7 @@ made available a local web server based on Node.js.
1. To start the web server, run:
rake webserver
grunt webserver
2. To access the local server, go to this website:
@@ -173,18 +173,20 @@ made available a local web server based on Node.js.
Our unit and integration tests are written with Jasmine and executed with Testacular. To run all of the
tests once on Chrome run:
rake test:unit
grunt test:unit
To run the tests on other browsers (Chrome, ChromeCanary, Firefox, Opera and Safari are pre-configured) use:
rake test:unit[Opera+Firefox]
grunt test:unit --browsers Opera,Firefox
Note there should be _no spaces between browsers_. `Opera, Firefox` is INVALID.
During development it's however more productive to continuously run unit tests every time the source or test files
change. To execute tests in this mode run:
1. To start the Testacular server, capture Chrome browser and run unit tests, run:
rake autotest:jqlite
grunt autotest:jqlite
2. To capture more browsers, open this url in the desired browser (url might be different if you have multiple instance
of Testacular running, read Testacular's console output for the correct url):
@@ -194,9 +196,9 @@ change. To execute tests in this mode run:
3. To re-run tests just change any source or test file.
To learn more about all of the preconfigured Rake tasks run:
To learn more about all of the preconfigured Grunt tasks run:
rake -T
grunt --help
## Running the end-to-end Test Suite
@@ -205,7 +207,7 @@ To run the E2E test suite:
1. Start the local web server if it's not running already.
rake webserver
grunt webserver
2. In a browser, go to:
@@ -213,7 +215,13 @@ To run the E2E test suite:
or in terminal run:
rake test:e2e
grunt test:end2end
For convenience you can also simply run:
grunt test:e2e
This will start the webserver for you and run the tests.

View File

@@ -5,10 +5,6 @@ var reader = require('./reader.js'),
appCache = require('./appCache.js').appCache,
Q = require('qq');
process.on('uncaughtException', function(err) {
console.error(err.stack || err);
});
var start = now();
var docs;
@@ -42,10 +38,10 @@ writer.makeDir('build/docs/', true).then(function() {
function writeTheRest(writesFuture) {
var metadata = ngdoc.metadata(docs);
writesFuture.push(writer.symlinkTemplate('css'));
writesFuture.push(writer.symlinkTemplate('font'));
writesFuture.push(writer.symlink('../../docs/img', 'build/docs/img'));
writesFuture.push(writer.symlinkTemplate('js'));
writesFuture.push(writer.symlinkTemplate('css', 'dir'));
writesFuture.push(writer.symlinkTemplate('font', 'dir'));
writesFuture.push(writer.symlink('../../docs/img', 'build/docs/img', 'dir'));
writesFuture.push(writer.symlinkTemplate('js', 'dir'));
var manifest = 'manifest="/build/docs/appcache.manifest"';

View File

@@ -203,7 +203,7 @@ Doc.prototype = {
flush();
this.shortName = this.name.split(/[\.:#]/).pop().trim();
this.id = this.id || // if we have an id just use it
(((this.file||'').match(/.*\/([^\/]*)\.ngdoc/)||{})[1]) || // try to extract it from file name
(((this.file||'').match(/.*(\/|\\)([^(\/|\\)]*)\.ngdoc/)||{})[2]) || // try to extract it from file name
this.name; // default to name
this.description = this.markdown(this.description);
this.example = this.markdown(this.example);

View File

@@ -7,7 +7,8 @@ exports.collect = collect;
var ngdoc = require('./ngdoc.js'),
Q = require('qq'),
qfs = require('q-fs');
qfs = require('q-fs'),
PATH = require('path');
var NEW_LINE = /\n\r?/;
@@ -43,7 +44,7 @@ function collect() {
var work2;
if (file.match(/\.ngdoc$/)) {
work2 = Q.when(qfs.read(file, 'b'), function(content){
var section = '@section ' + file.split('/')[2] + '\n';
var section = '@section ' + file.split(PATH.sep)[2] + '\n';
allDocs.push(new ngdoc.Doc(section + content.toString(),file, 1).parse());
});
}

View File

@@ -4,7 +4,7 @@
# current angular version. If this rule matches the appcache-offline.manifest will be served for
# requests to appcache.manifest
#
# This file must be processed by Rake in order to replace %ANGULAR_VERSION% with the actual version.
# This file must be processed by Grunt in order to replace %ANGULAR_VERSION% with the actual version.
Options -Indexes
RewriteEngine on

View File

@@ -61,22 +61,21 @@ exports.copy = function(from, to, transform) {
exports.symlink = symlink;
function symlink(from, to) {
function symlink(from, to, type) {
return qfs.exists(to).then(function(exists) {
if (!exists) {
return qfs.symbolicLink(to, from);
return qfs.symbolicLink(to, from, type);
}
});
}
exports.symlinkTemplate = symlinkTemplate;
function symlinkTemplate(filename) {
function symlinkTemplate(filename, type) {
var dest = OUTPUT_DIR + filename,
dirDepth = dest.split('/').length,
src = Array(dirDepth).join('../') + 'docs/src/templates/' + filename;
return symlink(src, dest);
return symlink(src, dest, type);
}

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

61
lib/grunt/plugins.js Normal file
View File

@@ -0,0 +1,61 @@
var util = require('./utils.js');
var spawn = require('child_process').spawn;
module.exports = function(grunt) {
grunt.registerMultiTask('min', 'minify JS files', function(){
util.min.call(util, this.data, this.async());
});
grunt.registerTask('minall', 'minify all the JS files in parallel', function(){
var files = grunt.config('min');
files = Object.keys(files).map(function(key){ return files[key]; });
grunt.util.async.forEach(files, util.min.bind(util), this.async());
});
grunt.registerMultiTask('build', 'build JS files', function(){
util.build.call(util, this.data, this.async());
});
grunt.registerTask('buildall', 'build all the JS files in parallel', function(){
var builds = grunt.config('build');
builds = Object.keys(builds).map(function(key){ return builds[key]; });
grunt.util.async.forEach(builds, util.build.bind(util), this.async());
});
grunt.registerMultiTask('write', 'write content to a file', function(){
grunt.file.write(this.data.file, this.data.val);
grunt.log.ok('wrote to ' + this.data.file);
});
grunt.registerMultiTask('docs', 'create angular docs', function(){
var done = this.async();
var files = this.data;
var docs = spawn('node', ['docs/src/gen-docs.js']);
docs.stdout.pipe(process.stdout);
docs.stderr.pipe(process.stderr);
docs.on('exit', function(code){
if(code !== 0) grunt.fail.warn('Error creating docs');
grunt.file.expand(files).forEach(function(file){
grunt.file.write(file, util.process(grunt.file.read(file), grunt.config('NG_VERSION'), false));
});
grunt.log.ok('docs created');
done();
});
});
grunt.registerMultiTask('test', 'Run the unit tests with testacular', function(){
util.startTestacular.call(util, this.data, true, this.async());
});
grunt.registerMultiTask('autotest', 'Run and watch the unit tests with testacular', function(){
util.startTestacular.call(util, this.data, false, this.async());
});
};

175
lib/grunt/utils.js Normal file
View File

@@ -0,0 +1,175 @@
var fs = require('fs');
var shell = require('shelljs');
var yaml = require('yaml-js');
var grunt = require('grunt');
var spawn = require('child_process').spawn;
module.exports = {
init: function() {
shell.exec('npm install');
},
getVersion: function(){
var versionYaml = yaml.load(fs.readFileSync('version.yaml', 'UTF-8'));
var match = versionYaml.version.match(/^([^\-]*)(-snapshot)?$/);
var semver = match[1].split('.');
var hash = shell.exec('git rev-parse --short HEAD', {silent: true}).output.replace('\n', '');
var version = {
full: (match[1] + (match[2] ? '-' + hash : '')),
major: semver[0],
minor: semver[1],
dot: semver[2],
codename: versionYaml.codename,
stable: versionYaml.stable
};
return version;
},
startTestacular: function(config, singleRun, done){
var browsers = grunt.option('browsers');
var reporters = grunt.option('reporters');
var noColor = grunt.option('no-colors');
var p = spawn('node', ['node_modules/testacular/bin/testacular', 'start', config,
singleRun ? '--single-run=true' : '',
reporters ? '--reporters=' + reporters : '',
browsers ? '--browsers=' + browsers : '',
noColor ? '--no-colors' : ''
]);
p.stdout.pipe(process.stdout);
p.stderr.pipe(process.stderr);
p.on('exit', function(code){
if(code !== 0) grunt.fail.warn("Test(s) failed");
done();
});
},
wrap: function(src, name){
src.unshift('src/' + name + '.prefix');
src.push('src/' + name + '.suffix');
return src;
},
addStyle: function(src, styles, minify){
styles = styles.map(processCSS.bind(this)).join('\n');
src += styles;
return src;
function processCSS(file){
var css = fs.readFileSync(file).toString();
if(minify){
css = css
.replace(/\n/g, '')
.replace(/\/\*.*?\*\//g, '')
.replace(/:\s+/g, ':')
.replace(/\s*\{\s*/g, '{')
.replace(/\s*\}\s*/g, '}')
.replace(/\s*\,\s*/g, ',')
.replace(/\s*\;\s*/g, ';');
}
//espace for js
css = css
.replace(/\\/g, '\\\\')
.replace(/'/g, "\\'")
.replace(/\n/g, '\\n');
return "angular.element(document).find('head').append('<style type=\"text/css\">" + css + "</style>');";
}
},
process: function(src, NG_VERSION, strict){
var processed = src
.replace(/"NG_VERSION_FULL"/g, NG_VERSION.full)
.replace(/"NG_VERSION_MAJOR"/, NG_VERSION.major)
.replace(/"NG_VERSION_MINOR"/, NG_VERSION.minor)
.replace(/"NG_VERSION_DOT"/, NG_VERSION.dot)
.replace(/"NG_VERSION_STABLE"/, NG_VERSION.stable)
.replace(/"NG_VERSION_CODENAME"/, NG_VERSION.codename);
if (strict !== false) processed = this.singleStrict(processed, '\n\n', true);
return processed;
},
build: function(config, fn){
var files = grunt.file.expand(config.src);
var styles = config.styles;
//concat
var src = files.map(function(filepath){
return grunt.file.read(filepath);
}).join(grunt.util.normalizelf('\n'));
//process
var processed = this.process(src, grunt.config('NG_VERSION'), config.strict);
if (styles) processed = this.addStyle(processed, styles.css, styles.minify);
//write
grunt.file.write(config.dest, processed);
grunt.log.ok('File ' + config.dest + ' created.');
fn();
},
singleStrict: function(src, insert, newline){
var useStrict = newline ? "$1\n'use strict';" : "$1'use strict';";
return src
.replace(/\s*("|')use strict("|');\s*/g, insert) // remove all file-specific strict mode flags
.replace(/(\(function\([^)]*\)\s*\{)/, useStrict); // add single strict mode flag
},
min: function(file, done) {
var minFile = file.replace(/\.js$/, '.min.js');
shell.exec(
'java ' +
this.java32flags() + ' ' +
'-jar lib/closure-compiler/compiler.jar ' +
'--compilation_level SIMPLE_OPTIMIZATIONS ' +
'--language_in ECMASCRIPT5_STRICT ' +
'--js ' + file + ' ' +
'--js_output_file ' + minFile,
function(code) {
if (code !== 0) grunt.fail.warn('Error minifying ' + file);
grunt.file.write(minFile, this.singleStrict(grunt.file.read(minFile), '\n'));
grunt.log.ok(file + ' minified into ' + minFile);
done();
}.bind(this));
},
//returns the 32-bit mode force flags for java compiler if supported, this makes the build much faster
java32flags: function(){
if (process.platform === "win32") return '';
if (shell.exec('java -version -d32 2>&1', {silent: true}).code !== 0) return '';
return ' -d32 -client';
},
//csp connect middleware
csp: function(){
return function(req, res, next){
res.setHeader("X-WebKit-CSP", "default-src 'self';");
res.setHeader("X-Content-Security-Policy", "default-src 'self'");
next();
};
},
//rewrite connect middleware
rewrite: function(){
return function(req, res, next){
var REWRITE = /\/(guide|api|cookbook|misc|tutorial).*$/,
IGNORED = /(\.(css|js|png|jpg)$|partials\/.*\.html$)/,
match;
if (!IGNORED.test(req.url) && (match = req.url.match(REWRITE))) {
console.log('rewriting', req.url);
req.url = req.url.replace(match[0], '/index.html');
}
next();
};
}
};

View File

@@ -1,273 +0,0 @@
var sys = require('sys'),
http = require('http'),
fs = require('fs'),
url = require('url'),
events = require('events');
var DEFAULT_PORT = 8000;
function main(argv) {
new HttpServer({
'GET': createServlet(StaticServlet),
'HEAD': createServlet(StaticServlet)
}).start(Number(argv[2]) || DEFAULT_PORT);
}
function escapeHtml(value) {
return value.toString().
replace('<', '&lt;').
replace('>', '&gt').
replace('"', '&quot;');
}
function createServlet(Class) {
var servlet = new Class();
return servlet.handleRequest.bind(servlet);
}
/**
* An Http server implementation that uses a map of methods to decide
* action routing.
*
* @param {Object} Map of method => Handler function
*/
function HttpServer(handlers) {
this.handlers = handlers;
this.server = http.createServer(this.handleRequest_.bind(this));
}
HttpServer.prototype.start = function(port) {
this.port = port;
this.server.listen(port);
sys.puts('Http Server running at http://127.0.0.1:' + port + '/');
};
HttpServer.prototype.parseUrl_ = function(urlString) {
var parsed = url.parse(urlString);
parsed.pathname = url.resolve('/', parsed.pathname);
return url.parse(url.format(parsed), true);
};
HttpServer.prototype.handleRequest_ = function(req, res) {
var logEntry = req.method + ' ' + req.url;
if (req.headers['user-agent']) {
logEntry += ' ' + req.headers['user-agent'];
}
sys.puts(logEntry);
req.url = this.parseUrl_(req.url);
var handler = this.handlers[req.method];
if (!handler) {
res.writeHead(501);
res.end();
} else {
handler.call(this, req, res);
}
};
/**
* Handles static content.
*/
function StaticServlet() {}
StaticServlet.MimeMap = {
'txt': 'text/plain',
'html': 'text/html',
'css': 'text/css',
'xml': 'application/xml',
'json': 'application/json',
'js': 'application/javascript',
'jpg': 'image/jpeg',
'jpeg': 'image/jpeg',
'gif': 'image/gif',
'png': 'image/png',
'manifest': 'text/cache-manifest',
// it should be application/font-woff
// but only this silences chrome warnings
'woff': 'font/opentype'
};
StaticServlet.prototype.handleRequest = function(req, res) {
var self = this;
var path = ('./' + req.url.pathname).replace('//','/').replace(/%(..)/g, function(match, hex){
return String.fromCharCode(parseInt(hex, 16));
});
var parts = path.split('/');
if (parts[parts.length-1].charAt(0) === '.')
return self.sendForbidden_(req, res, path);
// favicon rewriting
if (path === './favicon.ico')
return self.sendFile_(req, res, './lib/nodeserver/favicon.ico');
// docs rewriting
var REWRITE = /\/(guide|api|cookbook|misc|tutorial).*$/,
IGNORED = /(\.(css|js|png|jpg)$|partials\/.*\.html$)/,
match;
if (!IGNORED.test(path) && (match = path.match(REWRITE))) {
path = path.replace(match[0], '/index.html');
sys.puts('Rewrite to ' + path);
}
// end of docs rewriting
fs.stat(path, function(err, stat) {
if (err)
return self.sendMissing_(req, res, path);
if (stat.isDirectory())
return fs.stat(path + 'index.html', function(err, stat) {
// send index.html if exists
if (!err)
return self.sendFile_(req, res, path + 'index.html');
// list files otherwise
return self.sendDirectory_(req, res, path);
});
return self.sendFile_(req, res, path);
});
};
StaticServlet.prototype.sendError_ = function(req, res, error) {
res.writeHead(500, {
'Content-Type': 'text/html'
});
res.write('<!doctype html>\n');
res.write('<title>Internal Server Error</title>\n');
res.write('<h1>Internal Server Error</h1>');
res.write('<pre>' + escapeHtml(sys.inspect(error)) + '</pre>');
sys.puts('500 Internal Server Error');
sys.puts(sys.inspect(error));
};
StaticServlet.prototype.sendMissing_ = function(req, res, path) {
path = path.substring(1);
res.writeHead(404, {
'Content-Type': 'text/html'
});
res.write('<!doctype html>\n');
res.write('<title>404 Not Found</title>\n');
res.write('<h1>Not Found</h1>');
res.write(
'<p>The requested URL ' +
escapeHtml(path) +
' was not found on this server.</p>'
);
res.end();
sys.puts('404 Not Found: ' + path);
};
StaticServlet.prototype.sendForbidden_ = function(req, res, path) {
path = path.substring(1);
res.writeHead(403, {
'Content-Type': 'text/html'
});
res.write('<!doctype html>\n');
res.write('<title>403 Forbidden</title>\n');
res.write('<h1>Forbidden</h1>');
res.write(
'<p>You do not have permission to access ' +
escapeHtml(path) + ' on this server.</p>'
);
res.end();
sys.puts('403 Forbidden: ' + path);
};
StaticServlet.prototype.sendRedirect_ = function(req, res, redirectUrl) {
res.writeHead(301, {
'Content-Type': 'text/html',
'Location': redirectUrl
});
res.write('<!doctype html>\n');
res.write('<title>301 Moved Permanently</title>\n');
res.write('<h1>Moved Permanently</h1>');
res.write(
'<p>The document has moved <a href="' +
redirectUrl +
'">here</a>.</p>'
);
res.end();
sys.puts('301 Moved Permanently: ' + redirectUrl);
};
StaticServlet.prototype.sendFile_ = function(req, res, path) {
var self = this;
var file = fs.createReadStream(path);
res.writeHead(200, {
// CSP headers, uncomment to enable CSP
//"X-WebKit-CSP": "default-src 'self';",
//"X-Content-Security-Policy": "default-src 'self'",
'Content-Type': StaticServlet.
MimeMap[path.split('.').pop()] || 'text/plain'
});
if (req.method === 'HEAD') {
res.end();
} else {
file.on('data', res.write.bind(res));
file.on('close', function() {
res.end();
});
file.on('error', function(error) {
self.sendError_(req, res, error);
});
}
};
StaticServlet.prototype.sendDirectory_ = function(req, res, path) {
var self = this;
if (path.match(/[^\/]$/)) {
req.url.pathname += '/';
var redirectUrl = url.format(url.parse(url.format(req.url)));
return self.sendRedirect_(req, res, redirectUrl);
}
fs.readdir(path, function(err, files) {
if (err)
return self.sendError_(req, res, error);
if (!files.length)
return self.writeDirectoryIndex_(req, res, path, []);
var remaining = files.length;
files.forEach(function(fileName, index) {
fs.stat(path + '/' + fileName, function(err, stat) {
if (err)
return self.sendError_(req, res, err);
if (stat.isDirectory()) {
files[index] = fileName + '/';
}
if (!(--remaining))
return self.writeDirectoryIndex_(req, res, path, files);
});
});
});
};
StaticServlet.prototype.writeDirectoryIndex_ = function(req, res, path, files) {
path = path.substring(1);
res.writeHead(200, {
'Content-Type': 'text/html'
});
if (req.method === 'HEAD') {
res.end();
return;
}
res.write('<!doctype html>\n');
res.write('<title>' + escapeHtml(path) + '</title>\n');
res.write('<style>\n');
res.write(' ol { list-style-type: none; font-size: 1.2em; }\n');
res.write('</style>\n');
res.write('<h1>Directory: ' + escapeHtml(path) + '</h1>');
res.write('<ol>');
files.forEach(function(fileName) {
if (fileName.charAt(0) !== '.') {
res.write('<li><a href="' +
escapeHtml(fileName) + '">' +
escapeHtml(fileName) + '</a></li>');
}
});
res.write('</ol>');
res.end();
};
// Must be last,
main(process.argv);

View File

@@ -1 +0,0 @@
node lib/nodeserver/server.js $1

View File

@@ -1,10 +1,17 @@
{
"name": "AngularJS",
"version": "0.0.0",
"dependencies" : {
"testacular" : "0.5.9",
"jasmine-node" : "1.2.3",
"q-fs" : "0.1.36",
"qq" : "0.3.5"
"dependencies": {
"testacular": "0.5.9",
"jasmine-node": "1.2.3",
"q-fs": "0.1.36",
"qq": "0.3.5",
"grunt": "0.4.0",
"grunt-contrib-clean": "0.4.0",
"grunt-contrib-copy": "0.4.0",
"grunt-contrib-connect": "0.1.2",
"grunt-contrib-compress": "0.4.1",
"shelljs": "0.1.2",
"yaml-js": "0.0.5"
}
}

View File

@@ -14,8 +14,8 @@
* - `codeName` `{string}` Code name of the release, such as "jiggling-armfat".
*/
var version = {
full: '"NG_VERSION_FULL"', // all of these placeholder strings will be replaced by rake's
major: "NG_VERSION_MAJOR", // compile task
full: '"NG_VERSION_FULL"', // all of these placeholder strings will be replaced by grunt's
major: "NG_VERSION_MAJOR", // package task
minor: "NG_VERSION_MINOR",
dot: "NG_VERSION_DOT",
codeName: '"NG_VERSION_CODENAME"'