chore: add gulp link-checker task

also fix some of the broken links that it found
This commit is contained in:
Ward Bell 2016-03-10 23:24:28 -08:00
parent 469612f50c
commit 1a154daa2e
16 changed files with 114 additions and 12 deletions

4
.gitignore vendored
View File

@ -27,6 +27,4 @@ plnkr.html
*plnkr.no-link.html *plnkr.no-link.html
public/docs/*/latest/guide/cheatsheet.json public/docs/*/latest/guide/cheatsheet.json
protractor-results.txt protractor-results.txt
link-checker-results.txt

View File

@ -23,6 +23,7 @@ var globby = require("globby");
// Ugh... replacement needed to kill processes on any OS // Ugh... replacement needed to kill processes on any OS
// - because childProcess.kill does not work properly on windows // - because childProcess.kill does not work properly on windows
var treeKill = require("tree-kill"); var treeKill = require("tree-kill");
var blc = require("broken-link-checker");
// TODO: // TODO:
// 1. Think about using runSequence // 1. Think about using runSequence
@ -424,6 +425,13 @@ gulp.task('test-api-builder', function (cb) {
execCommands(['npm run test-api-builder'], {}, cb); execCommands(['npm run test-api-builder'], {}, cb);
}); });
// Usage:
// angular.io: gulp link-checker
// local site: gulp link-checker --url=http://localhost:3000
gulp.task('link-checker', function(done) {
return linkChecker();
});
// Internal tasks // Internal tasks
gulp.task('set-prod-env', function () { gulp.task('set-prod-env', function () {
@ -514,6 +522,80 @@ function harpCompile() {
return deferred.promise; return deferred.promise;
} }
function linkChecker(options) {
var deferred = Q.defer();
var options = options || {};
var blcOptions = options.blcOptions || {};
var customData = options.customData || {};
var excludeBad; // don't bother reporting bad links matching this RegExp
if (argv.excludeBad) {
excludeBad = new RegExp(argv.excludeBad);
} else {
excludeBad = options.excludeBad === undefined ? /docs\/dart\/latest\/api/ : '';
}
var previousPage;
var siteUrl = argv.url || options.url || 'https://angular.io/';
// See https://github.com/stevenvachon/broken-link-checker#blcsitecheckeroptions-handlers
var handlers = {
robots: function(robots, customData){},
html: function(tree, robots, response, pageUrl, customData){
//gutil.log('Scanning ' + pageUrl);docs/ts/latest/api/core/
},
junk: function(result, customData){},
// Analyze links
link: function(result, customData){
if (!result.broken) { return; }
if (excludeBad && excludeBad.test(result.url.resolved)) { return; }
var currentPage = result.base.resolved
if (previousPage !== currentPage) {
previousPage = currentPage;
fs.appendFileSync(outputFile, '\n' + currentPage);
gutil.log('broken: ' + currentPage);
}
var msg = '\n [' + result.html.location.line + ', ' + result.brokenReason + '] ' + result.url.resolved;
fs.appendFileSync(outputFile, msg);
//gutil.log(msg);
//gutil.log(result);
},
page: function(error, pageUrl, customData){},
site: function(error, siteUrl, customData){},
end: function(){
var stopTime = new Date().getTime();
var elapsed = 'Elapsed link-checking time: ' + ((stopTime - startTime)/1000) + ' seconds';
gutil.log(elapsed);
fs.appendFileSync(outputFile, '\n'+elapsed);
gutil.log('Output in file: ' + outputFile);
deferred.resolve(true);
}
};
// create an output file with header.
var outputFile = path.join(process.cwd(), 'link-checker-results.txt');
var header = 'Link checker results for: ' + siteUrl +
'\nStarted: ' + (new Date()).toLocaleString() +
'\nSkipping bad links matching regex: ' +excludeBad.toString() + '\n\n';
gutil.log(header);
fs.writeFileSync(outputFile, header);
var siteChecker = new blc.SiteChecker(blcOptions, handlers);
var startTime = new Date().getTime();
try {
siteChecker.enqueue(siteUrl, customData);
} catch (err) {
deferred.reject(err);
}
return deferred.promise;
}
// harp has issues with node_modules under the public dir // harp has issues with node_modules under the public dir
// but we need them there for example testing and development // but we need them there for example testing and development
// this method allows the node modules folder under '_examples' // this method allows the node modules folder under '_examples'

View File

@ -28,6 +28,7 @@
"devDependencies": { "devDependencies": {
"archiver": "^0.16.0", "archiver": "^0.16.0",
"assert-plus": "^0.1.5", "assert-plus": "^0.1.5",
"broken-link-checker":"0.7.0",
"browser-sync": "^2.9.3", "browser-sync": "^2.9.3",
"canonical-path": "0.0.2", "canonical-path": "0.0.2",
"cross-spawn": "^2.1.0", "cross-spawn": "^2.1.0",

View File

@ -295,7 +295,7 @@ table(width="100%")
In this example, the `table` element is removed from the DOM unless the `movies` array has a length. In this example, the `table` element is removed from the DOM unless the `movies` array has a length.
The (*) before `ngIf` is required in this example. The (*) before `ngIf` is required in this example.
For more information see [Structural Directives](../guide/structural-directives). For more information see [Structural Directives](../guide/structural-directives.html).
tr(style=top) tr(style=top)
td td
:marked :marked
@ -342,7 +342,7 @@ table(width="100%")
the (#) identifies `movie` as a local variable; the (#) identifies `movie` as a local variable;
the list preposition is `of`, not `in`. the list preposition is `of`, not `in`.
For more information see [Structural Directives](../guide/structural-directives). For more information see [Structural Directives](../guide/structural-directives.html).
tr(style=top) tr(style=top)
td td
:marked :marked

View File

@ -38,7 +38,7 @@ include ../_util-fns
## Pass data from parent to child with input binding ## Pass data from parent to child with input binding
`HeroChildComponent` has two ***input properties***, `HeroChildComponent` has two ***input properties***,
typically adorned with [@Input decorations](docs/ts/latest/guide/template-syntax.html#inputs-outputs). typically adorned with [@Input decorations](/docs/ts/latest/guide/template-syntax.html#inputs-outputs).
+makeExample('cb-component-communication/ts/app/hero-child.component.ts') +makeExample('cb-component-communication/ts/app/hero-child.component.ts')
:marked :marked
@ -142,7 +142,7 @@ figure.image-display
The parent binds to that event property and reacts to those events. The parent binds to that event property and reacts to those events.
The child's `EventEmitter` property is an ***output property***, The child's `EventEmitter` property is an ***output property***,
typically adorned with an [@Output decoration](docs/ts/latest/guide/template-syntax.html#inputs-outputs) typically adorned with an [@Output decoration](/docs/ts/latest/guide/template-syntax.html#inputs-outputs)
as seen in this `VoterComponent`: as seen in this `VoterComponent`:
+makeExample('cb-component-communication/ts/app/voter.component.ts') +makeExample('cb-component-communication/ts/app/voter.component.ts')

View File

@ -403,7 +403,7 @@ figure
Here's an example of a service class that logs to the browser console Here's an example of a service class that logs to the browser console
+makeExample('architecture/ts/app/logger.service.ts', 'class', 'app/logger.service.ts (class only)')(format=".") +makeExample('architecture/ts/app/logger.service.ts', 'class', 'app/logger.service.ts (class only)')(format=".")
:marked :marked
Here's a `HeroService` that fetches heroes and returns them in a resolved [promise](http://www.html5rocks.com/en/tutorials/es6/promises/). Here's a `HeroService` that fetches heroes and returns them in a resolved [promise](http://www.2ality.com/2014/10/es6-promises-api.html).
The `HeroService` depends on the `LoggerService` and another `BackendService` that handles the server communication grunt work. The `HeroService` depends on the `LoggerService` and another `BackendService` that handles the server communication grunt work.
+makeExample('architecture/ts/app/hero.service.ts', 'class', 'app/hero.service.ts (class only)')(format=".") +makeExample('architecture/ts/app/hero.service.ts', 'class', 'app/hero.service.ts (class only)')(format=".")
:marked :marked

View File

@ -581,7 +581,7 @@ figure.image-display
.l-sub-section .l-sub-section
:marked :marked
### The NgForm directive ### The NgForm directive
What `NgForm` directive? We didn't add an [NgForm](../api/core/NgForm-class.html) directive! What `NgForm` directive? We didn't add an [NgForm](../api/common/NgForm-directive.html) directive!
Angular did. Angular creates and attaches an `NgForm` directive to the `<form>` tag automatically. Angular did. Angular creates and attaches an `NgForm` directive to the `<form>` tag automatically.

View File

@ -85,7 +85,7 @@ figure.image-display
.l-sub-section .l-sub-section
:marked :marked
Learn more about the `DatePipes` format options in the [API Docs](../api/core/DatePipe-class.html). Learn more about the `DatePipes` format options in the [API Docs](../api/common/DatePipe-class.html).
:marked :marked
## Chaining pipes ## Chaining pipes
We can chain pipes together in potentially useful combinations. We can chain pipes together in potentially useful combinations.

View File

@ -895,7 +895,7 @@ code-example(format="").
<a id="lifecycle-hooks"></a> <a id="lifecycle-hooks"></a>
### Router Lifecycle Hooks ### Router Lifecycle Hooks
Angular components have [lifecycle hooks](lifecycle-hooks.html). For example, Angular calls the hook methods of the Angular components have [lifecycle hooks](lifecycle-hooks.html). For example, Angular calls the hook methods of the
[OnInit](../api/core/OnInit-interface.html) and [OnDestroy]((../api/core/OnDestroy-interface.html) [OnInit](../api/core/OnInit-interface.html) and [OnDestroy](../api/core/OnDestroy-interface.html)
interfaces when it creates and destroys components. interfaces when it creates and destroys components.
The router calls similar hook methods, The router calls similar hook methods,
@ -954,7 +954,7 @@ code-example(format="").
:marked :marked
The `DialogService` (injected in the `AppComponent` for app-wide use) does the asking. The `DialogService` (injected in the `AppComponent` for app-wide use) does the asking.
It returns a [promise](http://www.html5rocks.com/en/tutorials/es6/promises/) that It returns a [promise](http://www.2ality.com/2014/10/es6-promises-api.html) that
*resolves* when the user eventually decides what to do: either *resolves* when the user eventually decides what to do: either
to discard changes and navigate away (`true`) or to preserve the pending changes and stay in the crisis editor (`false`). to discard changes and navigate away (`true`) or to preserve the pending changes and stay in the crisis editor (`false`).

21
public/license Normal file
View File

@ -0,0 +1,21 @@
The MIT License
Copyright (c) 2014-2016 Google, Inc.
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.