build(aio): test Firebase hosting redirection configuration (#21763)
PR Close #21763
This commit is contained in:
parent
339ca83f9d
commit
bf29936af9
|
@ -15,7 +15,7 @@
|
|||
"build": "yarn ~~build",
|
||||
"prebuild-local": "yarn setup-local",
|
||||
"build-local": "yarn ~~build",
|
||||
"lint": "yarn check-env && yarn docs-lint && ng lint && yarn example-lint",
|
||||
"lint": "yarn check-env && yarn docs-lint && ng lint && yarn example-lint && yarn tools-lint",
|
||||
"test": "yarn check-env && ng test",
|
||||
"pree2e": "yarn check-env && yarn ~~update-webdriver",
|
||||
"e2e": "ng e2e --no-webdriver-update",
|
||||
|
@ -44,7 +44,10 @@
|
|||
"docs-watch": "node tools/transforms/authors-package/watchr.js",
|
||||
"docs-lint": "eslint --ignore-path=\"tools/transforms/.eslintignore\" tools/transforms",
|
||||
"docs-test": "node tools/transforms/test.js",
|
||||
"tools-test": "./scripts/deploy-to-firebase.test.sh && yarn docs-test && yarn boilerplate:test && jasmine tools/ng-packages-installer/index.spec.js",
|
||||
"deployment-config-test": "jasmine-ts tests/deployment/*.spec.ts",
|
||||
"firebase-utils-test": "jasmine-ts tools/firebase-test-utils/*.spec.ts",
|
||||
"tools-lint": "tslint -c \"tools/tslint.json\" \"tools/firebase-test-utils/**/*.ts\"",
|
||||
"tools-test": "./scripts/deploy-to-firebase.test.sh && yarn docs-test && yarn boilerplate:test && jasmine tools/ng-packages-installer/index.spec.js && yarn firebase-utils-test",
|
||||
"preserve-and-sync": "yarn docs",
|
||||
"serve-and-sync": "concurrently --kill-others \"yarn docs-watch --watch-only\" \"yarn start\"",
|
||||
"boilerplate:add": "node ./tools/examples/example-boilerplate add",
|
||||
|
@ -98,6 +101,7 @@
|
|||
"archiver": "^1.3.0",
|
||||
"canonical-path": "^0.0.2",
|
||||
"chalk": "^2.1.0",
|
||||
"cjson": "^0.5.0",
|
||||
"codelyzer": "~2.0.0",
|
||||
"concurrently": "^3.4.0",
|
||||
"cross-spawn": "^5.1.0",
|
||||
|
@ -118,6 +122,7 @@
|
|||
"image-size": "^0.5.1",
|
||||
"jasmine-core": "^2.8.0",
|
||||
"jasmine-spec-reporter": "^4.1.0",
|
||||
"jasmine-ts": "^0.2.1",
|
||||
"jsdom": "^9.12.0",
|
||||
"karma": "^1.7.0",
|
||||
"karma-chrome-launcher": "^2.1.1",
|
||||
|
@ -147,6 +152,7 @@
|
|||
"unist-util-visit-parents": "^1.1.1",
|
||||
"vrsource-tslint-rules": "^4.0.1",
|
||||
"watchr": "^3.0.1",
|
||||
"xregexp": "^4.0.0",
|
||||
"yargs": "^7.0.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,181 @@
|
|||
/api/api/core/ElementRef /api/core/ElementRef
|
||||
/api/common/Control-class /api/forms/FormControl
|
||||
/api/common/CORE_DIRECTIVES /api/common/CommonModule
|
||||
/api/common/DatePipe-class /api/common/DatePipe
|
||||
/api/common/NgClass-directive.html /api/common/NgClass
|
||||
/api/common/NgFor-directive.html /api/common/NgForOf
|
||||
/api/common/NgModel-directive /api/forms/NgModel
|
||||
/api/common/SelectControlValueAccessor-directive /api/forms/SelectControlValueAccessor
|
||||
/api/common/SlicePipe-class.html /api/common/SlicePipe
|
||||
/api/core/AnimationStateDeclarationMetadata /api/animations
|
||||
/api/core/DoCheck-interface.html /api/core/DoCheck
|
||||
/api/core/HostBinding-var /api/core/HostBinding
|
||||
/api/core/OpaqueToken-class /api/core/OpaqueToken
|
||||
/api/core/OptionalMetadata-class /api/core/Optional
|
||||
/api/core/PLATFORM_PIPES /api/common/CommonModule
|
||||
/api/core/Provider-class.html /api/core/Provider
|
||||
/api/core/Renderer-class /api/core/Renderer
|
||||
/api/core/testing/async-function /api/core/testing/async
|
||||
/api/core/testing/index/async-function /api/core/testing/async
|
||||
/api/core/testing/index/TestBed-class.html /api/core/testing/TestBed
|
||||
/api/core/testing/inject-function /api/core/testing/inject
|
||||
/api/core/testing/inject-function.html /api/core/testing/inject
|
||||
/api/http/Headers-class /api/http/Headers
|
||||
/api/http/Headers-class.html /api/http/Headers
|
||||
/api/http/HTTP_PROVIDERS-let /api/http/HttpModule
|
||||
/api/http/testing/index/MockBackend-class /api/http/testing/MockBackend
|
||||
/api/http/testing/index/MockBackend-class.html /api/http/testing/MockBackend
|
||||
/api/platform-browser-dynamic/testing/index/platformBrowserDynamicTesting-let.html /api/platform-browser-dynamic/testing/platformBrowserDynamicTesting
|
||||
/api/platform-browser/AnimationDriver /api/animations/browser/AnimationDriver
|
||||
/api/router/Route-class /api/router/Route
|
||||
/api/router/RouterLink-directive /api/router/RouterLink
|
||||
/api/testing/tick-function /api/core/testing/tick
|
||||
/api/upgrade/static/downgradeComponent-function /api/upgrade/static/downgradeComponent
|
||||
/api/upgrade/static/downgradeComponent-function.html /api/upgrade/static/downgradeComponent
|
||||
/api/upgrade/static/index/downgradeComponent-function /api/upgrade/static/downgradeComponent
|
||||
/api/upgrade/static/UpgradeModule-class /api/upgrade/static/UpgradeModule
|
||||
/docs/js/latest/api/ /api
|
||||
/docs/js/latest/api/animate/AnimationBuilder-class /api/animations/AnimationBuilder
|
||||
/docs/js/latest/api/animate/CssAnimationBuilder-class.html /api/animations/CssAnimationBuilder
|
||||
/docs/js/latest/api/animations/index/trigger-function /api/animations/trigger
|
||||
/docs/js/latest/api/common/ControlGroup-class.html /api/forms/FormGroup
|
||||
/docs/js/latest/api/common/index/DatePipe-pipe /api/common/DatePipe
|
||||
/docs/js/latest/api/common/index/Location-class.html /api/common/Location
|
||||
/docs/js/latest/api/common/index/MaxLengthValidator-directive.html /api/forms/MaxLengthValidator
|
||||
/docs/js/latest/api/common/index/NgFor-directive /api/common/NgForOf
|
||||
/docs/js/latest/api/common/index/NgFor-directive.html /api/common/NgForOf
|
||||
/docs/js/latest/api/common/index/NgFor-type-alias /api/common/NgForOf
|
||||
/docs/js/latest/api/common/index/NgFor-type-alias.html /api/common/NgForOf
|
||||
/docs/js/latest/api/common/index/NgTemplateOutlet-directive /api/common/NgTemplateOutlet
|
||||
/docs/js/latest/api/common/NgStyle-directive /api/common/NgStyle
|
||||
/docs/js/latest/api/core/DynamicComponentLoader-class.html /api/core/DynamicComponentLoader
|
||||
/docs/js/latest/api/core/HostListener-var /api/core/HostListener
|
||||
/docs/js/latest/api/core/index/AfterViewChecked-class /api/core/AfterViewChecked
|
||||
/docs/js/latest/api/core/index/AnimationStateTransitionMetadata-class.html /api/core/AnimationStateTransitionMetadata
|
||||
/docs/js/latest/api/core/index/AnimationStateTransitionMetadata-type-alias /api/core/AnimationStateTransitionMetadata
|
||||
/docs/js/latest/api/core/index/ApplicationModule-class /api/core/ApplicationModule
|
||||
/docs/js/latest/api/core/index/ApplicationRef-class /api/core/ApplicationRef
|
||||
/docs/js/latest/api/core/index/ChangeDetectorRef-class /api/core/ChangeDetectorRef
|
||||
/docs/js/latest/api/core/index/ComponentFactory-class /api/core/ComponentFactory
|
||||
/docs/js/latest/api/core/index/DebugNode-class /api/core/DebugNode
|
||||
/docs/js/latest/api/core/index/destroyPlatform-function /api/core/destroyPlatform
|
||||
/docs/js/latest/api/core/index/DirectiveMetadata-class /api/core/Directive
|
||||
/docs/js/latest/api/core/index/ErrorHandler-class /api/core/ErrorHandler
|
||||
/docs/js/latest/api/core/index/EventEmitter-class /api/core/EventEmitter
|
||||
/docs/js/latest/api/core/index/EventEmitter-class.html /api/core/EventEmitter
|
||||
/docs/js/latest/api/core/index/getPlatform-function /api/core/getPlatform
|
||||
/docs/js/latest/api/core/index/NgModule-interface /api/core/NgModule
|
||||
/docs/js/latest/api/core/index/NgModuleRef-class /api/core/NgModuleRef
|
||||
/docs/js/latest/api/core/index/NgModuleRef-class.html /api/core/NgModuleRef
|
||||
/docs/js/latest/api/core/index/OnInit-class /api/core/OnInit
|
||||
/docs/js/latest/api/core/index/OnInit-class.html /api/core/OnInit
|
||||
/docs/js/latest/api/core/index/OnInit-interface /api/core/OnInit
|
||||
/docs/js/latest/api/core/index/PlatformRef-class /api/core/PlatformRef
|
||||
/docs/js/latest/api/core/index/QueryList-class.html /api/core/QueryList
|
||||
/docs/js/latest/api/core/index/RenderComponentType-class /api/core/RenderComponentType
|
||||
/docs/js/latest/api/core/index/Renderer2-class.html /api/core/Renderer2
|
||||
/docs/js/latest/api/core/index/state-function.html /api/core/state
|
||||
/docs/js/latest/api/core/index/transition-function.html /api/core/transition
|
||||
/docs/js/latest/api/core/index/trigger-function /api/core/trigger
|
||||
/docs/js/latest/api/core/index/trigger-function.html /api/core/trigger
|
||||
/docs/js/latest/api/core/index/ViewChild-decorator /api/core/ViewChild
|
||||
/docs/js/latest/api/core/index/wtfLeave-let /api/core/wtfLeave
|
||||
/docs/js/latest/api/core/Injector-class.html /api/core/Injector
|
||||
/docs/js/latest/api/core/Query-var /api/core/Query
|
||||
/docs/js/latest/api/core/ResolvedProvider-interface.html /api/core/ResolvedProvider
|
||||
/docs/js/latest/api/core/testing/index/TestBed-class /api/core/testing/TestBed
|
||||
/docs/js/latest/api/core/testing/index/tick-function /api/core/testing/tick
|
||||
/docs/js/latest/api/core/testing/index/tick-function.html /api/core/testing/tick
|
||||
/docs/js/latest/api/core/ViewContainerRef-class.html /api/core/ViewContainerRef
|
||||
/docs/js/latest/api/forms/index/AbstractControl-class /api/forms/AbstractControl
|
||||
/docs/js/latest/api/forms/index/AbstractControl-class.html /api/forms/AbstractControl
|
||||
/docs/js/latest/api/forms/index/FormArray-class.html /api/forms/FormArray
|
||||
/docs/js/latest/api/forms/index/FormBuilder-class.html /api/forms/FormBuilder
|
||||
/docs/js/latest/api/forms/index/NG_VALIDATORS-let /api/forms/NG_VALIDATORS
|
||||
/docs/js/latest/api/forms/index/Validator-interface.html /api/forms/Validator
|
||||
/docs/js/latest/api/http/ConnectionBackend-class /api/http/ConnectionBackend
|
||||
/docs/js/latest/api/http/index/Http-class.html /api/http/Http
|
||||
/docs/js/latest/api/http/index/Jsonp-class.html /api/http/Jsonp
|
||||
/docs/js/latest/api/http/index/ResponseOptions-class.html /api/http/ResponseOptions
|
||||
/docs/js/latest/api/http/index/URLSearchParams-class /api/http/URLSearchParams
|
||||
/docs/js/latest/api/http/index/XHRConnection-class /api/http/XHRConnection
|
||||
/docs/js/latest/api/http/index/XHRConnection-class.html /api/http/XHRConnection
|
||||
/docs/js/latest/api/http/testing/index/MockConnection-class.html /api/http/testing/MockConnection
|
||||
/docs/js/latest/api/http/testing/MockBackend-class /api/http/testing/MockBackend
|
||||
/docs/js/latest/api/platform-browser-dynamic/index/platformBrowserDynamic-let.html /api/platform-browser-dynamic/platformBrowserDynamic
|
||||
/docs/js/latest/api/platform-browser-dynamic/testing/index/BrowserDynamicTestingModule-class.html /api/platform-browser-dynamic/testing/BrowserDynamicTestingModule
|
||||
/docs/js/latest/api/platform-browser/animations/index/BrowserAnimationsModule-class /api/platform-browser/animations/BrowserAnimationsModule
|
||||
/docs/js/latest/api/platform-browser/animations/index/BrowserAnimationsModule-class.html /api/platform-browser/animations/BrowserAnimationsModule
|
||||
/docs/js/latest/api/platform-browser/animations/index/NoopAnimationsModule-class.html /api/platform-browser/animations/NoopAnimationsModule
|
||||
/docs/js/latest/api/platform-browser/index/DomSanitizer-class /api/platform-browser/DomSanitizer
|
||||
/docs/js/latest/api/platform-browser/index/Meta-class /api/platform-browser/Meta
|
||||
/docs/js/latest/api/platform-browser/index/MetaDefinition-type-alias /api/platform-browser/MetaDefinition
|
||||
/docs/js/latest/api/platform-browser/index/SafeUrl-interface /api/platform-browser/SafeUrl
|
||||
/docs/js/latest/api/platform-browser/index/Title-class /api/platform-browser/Title
|
||||
/docs/js/latest/api/platform-browser/index/Title-class.html /api/platform-browser/Title
|
||||
/docs/js/latest/api/platform-server/index/PlatformState-class /api/platform-server/PlatformState
|
||||
/docs/js/latest/api/platform-server/index/PlatformState-class.html /api/platform-server/PlatformState
|
||||
/docs/js/latest/api/platform-webworker /api/platform-webworker
|
||||
/docs/js/latest/api/platform-webworker/index/MessageBus-class /api/platform-webworker/MessageBus
|
||||
/docs/js/latest/api/router/index/ActivatedRoute-interface /api/router/ActivatedRoute
|
||||
/docs/js/latest/api/router/index/CanActivate-interface.html /api/router/CanActivate
|
||||
/docs/js/latest/api/router/index/CanActivateChild-interface /api/router/CanActivateChild
|
||||
/docs/js/latest/api/router/index/CanDeactivate-interface.html /api/router/CanDeactivate
|
||||
/docs/js/latest/api/router/index/CanLoad-interface /api/router/CanLoad
|
||||
/docs/js/latest/api/router/index/CanLoad-interface.html /api/router/CanLoad
|
||||
/docs/js/latest/api/router/index/NavigationEnd-class /api/router/NavigationEnd
|
||||
/docs/js/latest/api/router/index/provideRoutes-function /api/router/provideRoutes
|
||||
/docs/js/latest/api/router/index/provideRoutes-function.html /api/router/provideRoutes
|
||||
/docs/js/latest/api/router/index/Router-class /api/router/Router
|
||||
/docs/js/latest/api/router/index/RouterLink-directive /api/router/RouterLink
|
||||
/docs/js/latest/api/router/index/Routes-type-alias /api/router/Routes
|
||||
/docs/js/latest/api/router/index/UrlSegment-class /api/router/UrlSegment
|
||||
/docs/js/latest/api/router/index/UrlSerializer-class.html /api/router/UrlSerializer
|
||||
/docs/js/latest/api/router/Instruction-class /api/router/Instruction
|
||||
/docs/js/latest/api/router/Location-class /api/router/Location
|
||||
/docs/js/latest/api/router/OnActivate-interface /api/router/OnActivate
|
||||
/docs/js/latest/api/router/Redirect-class.html /api/router/Redirect
|
||||
/docs/js/latest/api/router/RouteDefinition-interface.html /api/router/RouteDefinition
|
||||
/docs/js/latest/api/router/RouteParams-class /api/router/RouteParams
|
||||
/docs/js/latest/api/router/RouteParams-class.html /api/router/RouteParams
|
||||
/docs/js/latest/api/router/Router-class.html /api/router/Router
|
||||
/docs/js/latest/api/router/testing /api/router/testing
|
||||
/docs/js/latest/api/upgrade/index/UpgradeAdapter-class.html /api/upgrade/UpgradeAdapter
|
||||
/docs/js/latest/api/upgrade/static/UpgradeModule-class /api/upgrade/static/UpgradeModule
|
||||
/docs/js/latest/api/upgrade/static/UpgradeModule-class.html /api/upgrade/static/UpgradeModule
|
||||
/docs/js/latest/cookbook/ts-to-js.html https://github.com/angular/angular/blob/master/aio/content/guide/change-log.md#es6--described-in-typescript-to-javascript-2016-11-14
|
||||
/docs/js/latest/glossary /guide/glossary
|
||||
/docs/js/latest/guide/ /docs
|
||||
/docs/js/latest/guide/lifecycle-hooks /guide/lifecycle-hooks
|
||||
/docs/js/latest/guide/ngmodule /guide/ngmodules
|
||||
/docs/js/latest/resources /resources
|
||||
/docs/latest/tutorial /tutorial
|
||||
/docs/styleguide /guide/styleguide
|
||||
/docs/styleguide.html /guide/styleguide
|
||||
/docs/ts/latest/api/core/HostBinding-var.html /api/core/HostBinding
|
||||
/docs/ts/latest/api/core/index/BaseException-class.html /api/core/BaseException
|
||||
/docs/ts/latest/api/core/index/PLATFORM_PIPES-let.html /api/common/CommonModule
|
||||
/docs/ts/latest/api/core/OnInit-interface.html /api/core/OnInit
|
||||
/docs/ts/latest/api/core/OpaqueToken-class.html /api/core/OpaqueToken
|
||||
/docs/ts/latest/api/core/OptionalMetadata-class.html /api/core/Optional
|
||||
/docs/ts/latest/api/core/testing/index/async-function.html /api/core/testing/async
|
||||
/docs/ts/latest/api/core/testing/index/fakeAsync-function.html /api/core/testing/fakeAsync
|
||||
/docs/ts/latest/api/core/testing/index/TestComponentRenderer-class.html /api/core/testing/TestComponentRenderer
|
||||
/docs/ts/latest/api/core/testing/index/tick-function.html /api/core/testing/tick
|
||||
/docs/ts/latest/api/http/Connection-class.html /api/http/Connection
|
||||
/docs/ts/latest/api/http/testing/index/MockBackend-class.html /api/http/testing/MockBackend
|
||||
/docs/ts/latest/api/http/testing/index/MockConnection-class.html /api/http/testing/MockConnection
|
||||
/docs/ts/latest/api/platform-browser-dynamic/index/workerAppDynamicPlatform-let.html /api/platform-browser-dynamic/workerAppDynamicPlatform
|
||||
/docs/ts/latest/api/testing/fakeAsync-function.html /api/core/testing/fakeAsync
|
||||
/docs/ts/latest/cookbook/ts-to-js.html https://github.com/angular/angular/blob/master/aio/content/guide/change-log.md#es6--described-in-typescript-to-javascript-2016-11-14
|
||||
/guide/cli-quickstart /guide/quickstart
|
||||
/guide/learning-angular /guide/quickstart
|
||||
/guide/learning-angular.html /guide/quickstart
|
||||
/guide/metadata /guide/aot-compiler
|
||||
/guide/service-worker-getstart /guide/service-worker-getting-started
|
||||
/guide/service-worker-comm /guide/service-worker-communications
|
||||
/guide/service-worker-configref /guide/service-worker-config
|
||||
/news https://blog.angular.io/
|
||||
/news.html https://blog.angular.io/
|
||||
/testing /guide/testing
|
||||
/testing/first-app-tests.html /guide/testing
|
|
@ -0,0 +1,29 @@
|
|||
const { readFileSync } = require('fs');
|
||||
const path = require('canonical-path');
|
||||
const cjson = require('cjson');
|
||||
|
||||
import { FirebaseRedirector, FirebaseRedirectConfig } from '../../tools/firebase-test-utils/FirebaseRedirector';
|
||||
|
||||
export function getRedirector() {
|
||||
return new FirebaseRedirector(loadRedirects());
|
||||
}
|
||||
|
||||
export function loadRedirects(): FirebaseRedirectConfig[] {
|
||||
const pathToFirebaseJSON = path.resolve(__dirname, '../../firebase.json');
|
||||
const contents = cjson.load(pathToFirebaseJSON);
|
||||
return contents.hosting.redirects;
|
||||
}
|
||||
|
||||
export function loadSitemapUrls() {
|
||||
const pathToSiteMap = path.resolve(__dirname, '../../src/generated/sitemap.xml');
|
||||
const xml = readFileSync(pathToSiteMap, 'utf8');
|
||||
const urls: string[] = [];
|
||||
xml.replace(/<loc>([^<]+)<\/loc>/g, (_, loc) => urls.push(loc.replace('%%DEPLOYMENT_HOST%%', '')));
|
||||
return urls;
|
||||
}
|
||||
|
||||
export function loadLegacyUrls() {
|
||||
const pathToLegacyUrls = path.resolve(__dirname, 'URLS_TO_REDIRECT.txt');
|
||||
const urls = readFileSync(pathToLegacyUrls, 'utf8').split('\n').map(line => line.split('\t'));
|
||||
return urls;
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
import { getRedirector, loadLegacyUrls, loadRedirects, loadSitemapUrls } from './helpers';
|
||||
|
||||
describe('firebase.json redirect config', () => {
|
||||
describe('with sitemap urls', () => {
|
||||
loadSitemapUrls().forEach(url => {
|
||||
it('should not redirect any urls in the sitemap', () => {
|
||||
expect(getRedirector().redirect(url)).toEqual(url);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with legacy urls', () => {
|
||||
loadLegacyUrls().forEach(urlPair => {
|
||||
it('should redirect the legacy urls', () => {
|
||||
const redirector = getRedirector();
|
||||
expect(redirector.redirect(urlPair[0])).not.toEqual(urlPair[0]);
|
||||
if (urlPair[1]) {
|
||||
expect(redirector.redirect(urlPair[0])).toEqual(urlPair[1]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('destinations', () => {
|
||||
loadRedirects().forEach(redirect => {
|
||||
it('should match pattern "^(https?:/)?/.*"', () => {
|
||||
expect(redirect.destination).toMatch(/^(https?:\/)?\/.*/);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,189 @@
|
|||
import { FirebaseGlob } from './FirebaseGlob';
|
||||
describe('FirebaseGlob', () => {
|
||||
|
||||
describe('test', () => {
|
||||
it('should match * parts', () => {
|
||||
testGlob('asdf/*.jpg',
|
||||
['asdf/.jpg', 'asdf/asdf.jpg', 'asdf/asdf_asdf.jpg'],
|
||||
['asdf/asdf/asdf.jpg', 'xxxasdf/asdf.jpgxxx']);
|
||||
});
|
||||
|
||||
it('should match ** parts', () => {
|
||||
testGlob('asdf/**.jpg', // treated like two consecutive single `*`s
|
||||
['asdf/.jpg', 'asdf/asdf.jpg', 'asdf/asdf_asdf.jpg'],
|
||||
['asdf/a/.jpg', 'asdf/a/b.jpg', '/asdf/asdf.jpg', 'asdff/asdf.jpg', 'xxxasdf/asdf.jpg', 'asdf/asdf.jpgxxx']);
|
||||
});
|
||||
|
||||
it('should match **/ and /**/', () => {
|
||||
testGlob('**/*.js',
|
||||
['asdf.js', 'asdf/asdf.js', 'asdf/asdf/asdfasdf_asdf.js', '/asdf/asdf.js', '/asdf/aasdf-asdf.2.1.4.js'],
|
||||
['asdf/asdf.jpg', '/asdf/asdf.jpg']);
|
||||
testGlob('aaa/**/bbb',
|
||||
['aaa/xxx/bbb', 'aaa/xxx/yyy/bbb', 'aaa/bbb'],
|
||||
['/aaa/xxx/bbb', 'aaa/x/bbb/', 'aaa/bbb/ccc']);
|
||||
});
|
||||
|
||||
it('should match choice groups', () => {
|
||||
testGlob('aaa/*.@(bbb|ccc)',
|
||||
['aaa/aaa.bbb', 'aaa/aaa_aaa.ccc'],
|
||||
['/aaa/aaa.bbb', 'aaaf/aaa.bbb', 'aaa/aaa.ddd']);
|
||||
|
||||
testGlob('aaa/*(bbb|ccc)',
|
||||
['aaa/', 'aaa/bbb', 'aaa/ccc', 'aaa/bbbbbb', 'aaa/bbbccc', 'aaa/cccbbb', 'aaa/bbbcccbbb'],
|
||||
['aaa/aaa', 'aaa/bbbb']);
|
||||
|
||||
testGlob('aaa/+(bbb|ccc)',
|
||||
['aaa/bbb', 'aaa/ccc', 'aaa/bbbbbb', 'aaa/bbbccc', 'aaa/cccbbb', 'aaa/bbbcccbbb'],
|
||||
['aaa/', 'aaa/aaa', 'aaa/bbbb']);
|
||||
|
||||
testGlob('aaa/?(bbb|ccc)',
|
||||
['aaa/', 'aaa/bbb', 'aaa/ccc'],
|
||||
['aaa/aaa', 'aaa/bbbb', 'aaa/bbbbbb', 'aaa/bbbccc', 'aaa/cccbbb', 'aaa/bbbcccbbb']);
|
||||
});
|
||||
|
||||
it('should error on non-supported choice groups', () => {
|
||||
expect(() => new FirebaseGlob('/!(a|b)/c'))
|
||||
.toThrowError('Error in FirebaseGlob: "/!(a|b)/c" - "not" expansions are not supported: "!(a|b)"');
|
||||
expect(() => new FirebaseGlob('/(a|b)/c'))
|
||||
.toThrowError('Error in FirebaseGlob: "/(a|b)/c" - unknown expansion type: "/" in "/(a|b)"');
|
||||
expect(() => new FirebaseGlob('/&(a|b)/c'))
|
||||
.toThrowError('Error in FirebaseGlob: "/&(a|b)/c" - unknown expansion type: "&" in "&(a|b)"');
|
||||
});
|
||||
|
||||
// Globs that contain params tested via the match tests below
|
||||
});
|
||||
|
||||
describe('match', () => {
|
||||
it('should match patterns with no parameters', () => {
|
||||
testMatch('/abc/def/*', {
|
||||
}, {
|
||||
'/abc/def/': {},
|
||||
'/abc/def/ghi': {},
|
||||
'/': undefined,
|
||||
'/abc': undefined,
|
||||
'/abc/def/ghi/jk;': undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should capture a simple named param', () => {
|
||||
testMatch('/:abc', {
|
||||
named: ['abc']
|
||||
}, {
|
||||
'/a': {abc: 'a'},
|
||||
'/abc': {abc: 'abc'},
|
||||
'/': undefined,
|
||||
'/a/': undefined,
|
||||
'/a/b/': undefined,
|
||||
'/a/a/b': undefined,
|
||||
'/a/a/b/': undefined,
|
||||
});
|
||||
testMatch('/a/:b', {
|
||||
named: ['b']
|
||||
}, {
|
||||
'/a/b': {b: 'b'},
|
||||
'/a/bcd': {b: 'bcd'},
|
||||
'/a/': undefined,
|
||||
'/a/b/': undefined,
|
||||
'/a': undefined,
|
||||
'/a//': undefined,
|
||||
'/a/a/b': undefined,
|
||||
'/a/a/b/': undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should capture a named param followed by non-word chars', () => {
|
||||
testMatch('/a/:x-', {
|
||||
named: ['x']
|
||||
}, {
|
||||
'/a/b-': {x: 'b'},
|
||||
'/a/bcd-': {x: 'bcd'},
|
||||
'/a/--': {x: '-'},
|
||||
'/a': undefined,
|
||||
'/a/-': undefined,
|
||||
'/a/-/': undefined,
|
||||
'/a/': undefined,
|
||||
'/a/b/-': undefined,
|
||||
'/a/b-c': undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should capture multiple named params', () => {
|
||||
testMatch('/a/:b/:c', {
|
||||
named: ['b', 'c']
|
||||
}, {
|
||||
'/a/b/c': {b: 'b', c: 'c'},
|
||||
'/a/bcd/efg': {b: 'bcd', c: 'efg'},
|
||||
'/a/b/c-': {b: 'b', c: 'c-'},
|
||||
'/a/': undefined,
|
||||
'/a/b/': undefined,
|
||||
'/a/b/c/': undefined,
|
||||
});
|
||||
testMatch('/:a/b/:c', {
|
||||
named: ['a', 'c']
|
||||
}, {
|
||||
'/a/b/c': {a: 'a', c: 'c'},
|
||||
'/abc/b/efg': {a: 'abc', c: 'efg'},
|
||||
'/a/b/c-': {a: 'a', c: 'c-'},
|
||||
'/a/': undefined,
|
||||
'/a/b/': undefined,
|
||||
'/a/b/c/': undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should capture a simple rest param', () => {
|
||||
testMatch('/:abc*', {
|
||||
rest: ['abc']
|
||||
}, {
|
||||
'/a': {abc: 'a'},
|
||||
'/a/b': {abc: 'a/b'},
|
||||
'/a/bcd': {abc: 'a/bcd'},
|
||||
'/a/': {abc: 'a/'},
|
||||
'/a/b/': {abc: 'a/b/'},
|
||||
'/a//': {abc: 'a//'},
|
||||
'/a/b/c': {abc: 'a/b/c'},
|
||||
'/a/b/c/': {abc: 'a/b/c/'},
|
||||
});
|
||||
testMatch('/a/:b*', {
|
||||
rest: ['b']
|
||||
}, {
|
||||
'/a/b': {b: 'b'},
|
||||
'/a/bcd': {b: 'bcd'},
|
||||
'/a/': {b: ''},
|
||||
'/a/b/': {b: 'b/'},
|
||||
'/a': {b: undefined},
|
||||
'/a//': {b: '/'},
|
||||
'/a/a/b': {b: 'a/b'},
|
||||
'/a/a/b/': {b: 'a/b/'},
|
||||
});
|
||||
});
|
||||
|
||||
it('should capture a rest param mixed with a named param', () => {
|
||||
testMatch('/:abc/:rest*', {
|
||||
named: ['abc'],
|
||||
rest: ['rest']
|
||||
}, {
|
||||
'/a': {abc: 'a', rest: undefined},
|
||||
'/a/b': {abc: 'a', rest: 'b'},
|
||||
'/a/bcd': {abc: 'a', rest: 'bcd'},
|
||||
'/a/': {abc: 'a', rest: ''},
|
||||
'/a/b/': {abc: 'a', rest: 'b/'},
|
||||
'/a//': {abc: 'a', rest: '/'},
|
||||
'/a/b/c': {abc: 'a', rest: 'b/c'},
|
||||
'/a/b/c/': {abc: 'a', rest: 'b/c/'},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function testGlob(pattern: string, matches: string[], nonMatches: string[]) {
|
||||
const glob = new FirebaseGlob(pattern);
|
||||
matches.forEach(url => expect(glob.test(url)).toBe(true, url));
|
||||
nonMatches.forEach(url => expect(glob.test(url)).toBe(false, url));
|
||||
}
|
||||
|
||||
function testMatch(pattern: string, captures: { named?: string[], rest?: string[] }, matches: { [url: string]: object|undefined }) {
|
||||
const glob = new FirebaseGlob(pattern);
|
||||
expect(Object.keys(glob.namedParams)).toEqual(captures.named || []);
|
||||
expect(Object.keys(glob.restParams)).toEqual(captures.rest || []);
|
||||
Object.keys(matches).forEach(url => expect(glob.match(url)).toEqual(matches[url]));
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
import * as XRegExp from 'xregexp';
|
||||
|
||||
const dot = /\./g;
|
||||
const star = /\*/g;
|
||||
const doubleStar = /(^|\/)\*\*($|\/)/g; // e.g. a/**/b or **/b or a/** but not a**b
|
||||
const modifiedPatterns = /(.)\(([^)]+)\)/g; // e.g. `@(a|b)
|
||||
const restParam = /\/:([A-Za-z]+)\*/g; // e.g. `:rest*`
|
||||
const namedParam = /\/:([A-Za-z]+)/g; // e.g. `:api`
|
||||
const possiblyEmptyInitialSegments = /^\.🐷\//g; // e.g. `**/a` can also match `a`
|
||||
const possiblyEmptySegments = /\/\.🐷\//g; // e.g. `a/**/b` can also match `a/b`
|
||||
const willBeStar = /🐷/g; // e.g. `a**b` not matched by previous rule
|
||||
|
||||
export class FirebaseGlob {
|
||||
pattern: string;
|
||||
regex: XRegExp;
|
||||
namedParams: { [key: string]: boolean } = {};
|
||||
restParams: { [key: string]: boolean } = {};
|
||||
constructor(glob: string) {
|
||||
try {
|
||||
const pattern = glob
|
||||
.replace(dot, '\\.')
|
||||
.replace(modifiedPatterns, replaceModifiedPattern)
|
||||
.replace(restParam, (_, param) => {
|
||||
// capture the rest of the string
|
||||
this.restParams[param] = true;
|
||||
return `(?:/(?<${param}>.🐷))?`;
|
||||
})
|
||||
.replace(namedParam, (_, param) => {
|
||||
// capture the named parameter
|
||||
this.namedParams[param] = true;
|
||||
return `/(?<${param}>[^/]+)`;
|
||||
})
|
||||
.replace(doubleStar, '$1.🐷$2') // use the pig to avoid replacing ** in next rule
|
||||
.replace(star, '[^/]*') // match a single segment
|
||||
.replace(possiblyEmptyInitialSegments, '(?:.*)')// deal with **/ special cases
|
||||
.replace(possiblyEmptySegments, '(?:/|/.*/)') // deal with /**/ special cases
|
||||
.replace(willBeStar, '*'); // other ** matches
|
||||
this.pattern = `^${pattern}$`;
|
||||
this.regex = XRegExp(this.pattern);
|
||||
} catch (e) {
|
||||
throw new Error(`Error in FirebaseGlob: "${glob}" - ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
test(url: string) {
|
||||
return XRegExp.test(url, this.regex);
|
||||
}
|
||||
|
||||
match(url: string) {
|
||||
const match = XRegExp.exec(url, this.regex);
|
||||
if (match) {
|
||||
const result = {};
|
||||
const names = (this.regex as any).xregexp.captureNames || [];
|
||||
names.forEach(name => result[name] = match[name]);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function replaceModifiedPattern(_, modifier, pattern) {
|
||||
switch (modifier) {
|
||||
case '!':
|
||||
throw new Error(`"not" expansions are not supported: "${_}"`);
|
||||
case '?':
|
||||
case '+':
|
||||
return `(${pattern})${modifier}`;
|
||||
case '*':
|
||||
return `(${pattern})🐷`; // it will become a star
|
||||
case '@':
|
||||
return `(${pattern})`;
|
||||
default:
|
||||
throw new Error(`unknown expansion type: "${modifier}" in "${_}"`);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
import { FirebaseRedirect } from './FirebaseRedirect';
|
||||
|
||||
describe('FirebaseRedirect', () => {
|
||||
describe('replace', () => {
|
||||
it('should return undefined if the redirect does not match the url', () => {
|
||||
const redirect = new FirebaseRedirect('/a/b/c', '/x/y/z');
|
||||
expect(redirect.replace('/1/2/3')).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should return the destination if there is a match', () => {
|
||||
const redirect = new FirebaseRedirect('/a/b/c', '/x/y/z');
|
||||
expect(redirect.replace('/a/b/c')).toBe('/x/y/z');
|
||||
});
|
||||
|
||||
it('should inject name params into the destination', () => {
|
||||
const redirect = new FirebaseRedirect('/api/:package/:api-*', '<:package><:api>');
|
||||
expect(redirect.replace('/api/common/NgClass-directive')).toEqual('<common><NgClass>');
|
||||
});
|
||||
|
||||
it('should inject rest params into the destination', () => {
|
||||
const redirect = new FirebaseRedirect('/a/:rest*', '/x/:rest*/y');
|
||||
expect(redirect.replace('/a/b/c')).toEqual('/x/b/c/y');
|
||||
});
|
||||
|
||||
it('should inject both named and rest parameters into the destination', () => {
|
||||
const redirect = new FirebaseRedirect('/:a/:rest*', '/x/:a/y/:rest*/z');
|
||||
expect(redirect.replace('/a/b/c')).toEqual('/x/a/y/b/c/z');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,16 @@
|
|||
import * as XRegExp from 'xregexp';
|
||||
import { FirebaseGlob } from './FirebaseGlob';
|
||||
|
||||
export class FirebaseRedirect {
|
||||
glob = new FirebaseGlob(this.source);
|
||||
constructor(public source: string, public destination: string) {}
|
||||
|
||||
replace(url: string) {
|
||||
const match = this.glob.match(url);
|
||||
if (match) {
|
||||
const paramReplacers = Object.keys(this.glob.namedParams).map(name => [ XRegExp(`:${name}`, 'g'), match[name] ]);
|
||||
const restReplacers = Object.keys(this.glob.restParams).map(name => [ XRegExp(`:${name}\\*`, 'g'), match[name] ]);
|
||||
return XRegExp.replaceEach(this.destination, [...paramReplacers, ...restReplacers]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
import { FirebaseRedirector } from './FirebaseRedirector';
|
||||
|
||||
describe('FirebaseRedirector', () => {
|
||||
it('should replace with the first matching redirect', () => {
|
||||
const redirector = new FirebaseRedirector([
|
||||
{ source: '/a/b/c', destination: '/X/Y/Z' },
|
||||
{ source: '/a/:foo/c', destination: '/X/:foo/Z' },
|
||||
{ source: '/**/:foo/c', destination: '/A/:foo/zzz' },
|
||||
]);
|
||||
expect(redirector.redirect('/a/b/c')).toEqual('/X/Y/Z');
|
||||
expect(redirector.redirect('/a/moo/c')).toEqual('/X/moo/Z');
|
||||
expect(redirector.redirect('/x/y/a/b/c')).toEqual('/A/b/zzz');
|
||||
expect(redirector.redirect('/x/y/c')).toEqual('/A/y/zzz');
|
||||
});
|
||||
|
||||
it('should return the original url if no redirect matches', () => {
|
||||
const redirector = new FirebaseRedirector([
|
||||
{ source: 'x', destination: 'X' },
|
||||
{ source: 'y', destination: 'Y' },
|
||||
{ source: 'z', destination: 'Z' },
|
||||
]);
|
||||
expect(redirector.redirect('a')).toEqual('a');
|
||||
});
|
||||
|
||||
it('should recursively redirect', () => {
|
||||
const redirector = new FirebaseRedirector([
|
||||
{ source: 'a', destination: 'b' },
|
||||
{ source: 'b', destination: 'c' },
|
||||
{ source: 'c', destination: 'd' },
|
||||
]);
|
||||
expect(redirector.redirect('a')).toEqual('d');
|
||||
});
|
||||
|
||||
it('should throw if stuck in an infinite loop', () => {
|
||||
const redirector = new FirebaseRedirector([
|
||||
{ source: 'a', destination: 'b' },
|
||||
{ source: 'b', destination: 'c' },
|
||||
{ source: 'c', destination: 'a' },
|
||||
]);
|
||||
expect(() => redirector.redirect('a')).toThrowError('infinite redirect loop');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,36 @@
|
|||
import { FirebaseRedirect } from './FirebaseRedirect';
|
||||
|
||||
export interface FirebaseRedirectConfig {
|
||||
source: string;
|
||||
destination: string;
|
||||
}
|
||||
|
||||
export class FirebaseRedirector {
|
||||
private redirects: FirebaseRedirect[];
|
||||
constructor(redirects: FirebaseRedirectConfig[]) {
|
||||
this.redirects = redirects.map(redirect => new FirebaseRedirect(redirect.source, redirect.destination));
|
||||
}
|
||||
|
||||
redirect(url: string) {
|
||||
let ttl = 50;
|
||||
while (ttl > 0) {
|
||||
const newUrl = this.doRedirect(url);
|
||||
if (newUrl === url) {
|
||||
return url;
|
||||
} else {
|
||||
url = newUrl;
|
||||
ttl--;
|
||||
}
|
||||
}
|
||||
throw new Error('infinite redirect loop');
|
||||
}
|
||||
private doRedirect(url: string) {
|
||||
for (let i = 0; i < this.redirects.length; i++) {
|
||||
const newUrl = this.redirects[i].replace(url);
|
||||
if (newUrl !== undefined) {
|
||||
return newUrl;
|
||||
}
|
||||
}
|
||||
return url;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
{
|
||||
"rulesDirectory": [
|
||||
"../node_modules/codelyzer"
|
||||
],
|
||||
"rules": {
|
||||
"class-name": true,
|
||||
"comment-format": [
|
||||
true,
|
||||
"check-space"
|
||||
],
|
||||
"curly": true,
|
||||
"eofline": true,
|
||||
"forin": true,
|
||||
"indent": [
|
||||
true,
|
||||
"spaces"
|
||||
],
|
||||
"label-position": true,
|
||||
"max-line-length": [
|
||||
true,
|
||||
140
|
||||
],
|
||||
"member-access": false,
|
||||
"no-arg": true,
|
||||
"no-bitwise": true,
|
||||
"no-console": [
|
||||
true,
|
||||
"debug",
|
||||
"info",
|
||||
"time",
|
||||
"timeEnd",
|
||||
"trace"
|
||||
],
|
||||
"no-construct": true,
|
||||
"no-debugger": true,
|
||||
"no-duplicate-variable": true,
|
||||
"no-empty": false,
|
||||
"no-eval": true,
|
||||
"no-inferrable-types": true,
|
||||
"no-string-literal": false,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-trailing-whitespace": true,
|
||||
"no-unused-expression": true,
|
||||
"no-use-before-declare": true,
|
||||
"no-var-keyword": true,
|
||||
"object-literal-sort-keys": false,
|
||||
"one-line": [
|
||||
true,
|
||||
"check-open-brace",
|
||||
"check-catch",
|
||||
"check-else",
|
||||
"check-whitespace"
|
||||
],
|
||||
"quotemark": [
|
||||
true,
|
||||
"single"
|
||||
],
|
||||
"radix": true,
|
||||
"semicolon": [
|
||||
true
|
||||
],
|
||||
"triple-equals": [
|
||||
true,
|
||||
"allow-null-check"
|
||||
],
|
||||
"typedef-whitespace": [
|
||||
true,
|
||||
{
|
||||
"call-signature": "nospace",
|
||||
"index-signature": "nospace",
|
||||
"parameter": "nospace",
|
||||
"property-declaration": "nospace",
|
||||
"variable-declaration": "nospace"
|
||||
}
|
||||
],
|
||||
"variable-name": false,
|
||||
"whitespace": [
|
||||
true,
|
||||
"check-branch",
|
||||
"check-decl",
|
||||
"check-operator",
|
||||
"check-separator",
|
||||
"check-type"
|
||||
],
|
||||
|
||||
"import-destructuring-spacing": true
|
||||
}
|
||||
}
|
|
@ -1426,6 +1426,12 @@ cjson@^0.3.1:
|
|||
dependencies:
|
||||
json-parse-helpfulerror "^1.0.3"
|
||||
|
||||
cjson@^0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/cjson/-/cjson-0.5.0.tgz#a0f48601e016164dfb2c6d891e380c96cada9839"
|
||||
dependencies:
|
||||
json-parse-helpfulerror "^1.0.3"
|
||||
|
||||
clap@^1.0.9:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/clap/-/clap-1.2.3.tgz#4f36745b32008492557f46412d66d50cb99bce51"
|
||||
|
@ -4635,6 +4641,15 @@ jasmine-spec-reporter@^4.1.0:
|
|||
dependencies:
|
||||
colors "1.1.2"
|
||||
|
||||
jasmine-ts@^0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/jasmine-ts/-/jasmine-ts-0.2.1.tgz#7fe5911d98ea22cc257efe98f9728ab66ac0e90e"
|
||||
dependencies:
|
||||
jasmine "^2.6.0"
|
||||
ts-node "^3.2.0"
|
||||
typescript "^2.4.1"
|
||||
yargs "^8.0.2"
|
||||
|
||||
jasmine@^2.5.3, jasmine@^2.6.0:
|
||||
version "2.8.0"
|
||||
resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-2.8.0.tgz#6b089c0a11576b1f16df11b80146d91d4e8b8a3e"
|
||||
|
@ -8352,7 +8367,7 @@ tryit@^1.0.1:
|
|||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/tryit/-/tryit-1.0.3.tgz#393be730a9446fd1ead6da59a014308f36c289cb"
|
||||
|
||||
ts-node@^3.0.2, ts-node@^3.3.0:
|
||||
ts-node@^3.0.2, ts-node@^3.2.0, ts-node@^3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-3.3.0.tgz#c13c6a3024e30be1180dd53038fc209289d4bf69"
|
||||
dependencies:
|
||||
|
@ -8446,14 +8461,14 @@ typescript@2.4:
|
|||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.4.2.tgz#f8395f85d459276067c988aa41837a8f82870844"
|
||||
|
||||
typescript@^2.4.1, typescript@~2.6.1:
|
||||
version "2.6.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4"
|
||||
|
||||
typescript@^2.5.3:
|
||||
version "2.5.3"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.5.3.tgz#df3dcdc38f3beb800d4bc322646b04a3f6ca7f0d"
|
||||
|
||||
typescript@~2.6.1:
|
||||
version "2.6.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4"
|
||||
|
||||
uglify-es@^3.3.4:
|
||||
version "3.3.5"
|
||||
resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.5.tgz#cf7e695da81999f85196b15e2978862f13212f88"
|
||||
|
@ -9301,6 +9316,10 @@ xmlhttprequest-ssl@1.5.3:
|
|||
version "1.5.3"
|
||||
resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz#185a888c04eca46c3e4070d99f7b49de3528992d"
|
||||
|
||||
xregexp@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.0.0.tgz#e698189de49dd2a18cc5687b05e17c8e43943020"
|
||||
|
||||
xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.0, xtend@~4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
|
||||
|
|
|
@ -37,6 +37,10 @@ source ${thisDir}/_travis-fold.sh
|
|||
yarn e2e-prod
|
||||
travisFoldEnd "test.aio.e2e"
|
||||
|
||||
# Test Firebase redirects
|
||||
travisFoldStart "test.aio.deployment-config"
|
||||
yarn deployment-config-test
|
||||
travisFoldEnd "test.aio.deployment-config"
|
||||
|
||||
# Run unit tests for aio/aio-builds-setup
|
||||
travisFoldStart "test.aio.aio-builds-setup"
|
||||
|
|
Loading…
Reference in New Issue