Compare commits

..

85 Commits

Author SHA1 Message Date
Martin Staffa 30ab202fa7 fixup! fix(ngAria.ngClick): restrict preventDefault on space / enter to non-interactive elements 2018-09-05 19:33:33 +02:00
Martin Staffa 190bc1d52b fixup! fix(ngAria.ngClick): restrict preventDefault on space / enter to non-interactive elements 2018-09-03 09:32:08 +02:00
Martin Staffa 4be80aefa2 fix(ngAria.ngClick): restrict preventDefault on space / enter to non-interactive elements
Fixes #16664
2018-09-01 11:14:20 +02:00
Martin Staffa 837e519acc fix(ngHref): allow numbers and other objects in interpolation
Interpolated content in ngHref must be stringified before being passed to $$sanitizeUri by $sce. Before 1.7.x, the sanitization had happened on the already interpolated value inside $compile.

Closes #16652
Fixes #16626
2018-08-20 20:04:28 +02:00
John Mantas ad7ea95386 docs(ngRepeat): redundant "and" on line 77
Closes #16657
2018-08-07 16:20:40 +03:00
George Kalpakas fd4a284c85 docs(guide/migration): fix typos, format inline code 2018-08-06 15:51:03 +03:00
Martin Staffa 79ca0f1e91 fix(select): allow to select first option with value undefined
Previously, the value observer incorrectly assumed a value had changed even if
it was the first time it was set, which caused it to remove an option with
the value `undefined` from the internal option map.

Fixes #16653
Closes #16656
2018-08-06 12:47:43 +02:00
Martin Staffa 848c9b51f1 docs(select): remove solved known issue
The issue in question has been resolved some time in 2017.
The bug report is still open, but the behavior has changed:
https://bugzilla.mozilla.org/show_bug.cgi?id=126379

Let's hope they have tests for this!
2018-08-03 18:16:19 +02:00
Martin Staffa 16b0692885 docs(CHANGELOG.md): add changes for 1.7.3 2018-08-03 13:36:59 +02:00
Martin Staffa af3b71ef46 docs(\$compile): add docs for ngProp and ngOn bindings
The docs are written as if ngProp and ngOn were regular directives,
which makes them easier to find.

Closes #16627
2018-08-03 12:17:03 +02:00
Jason Bedard dedb10c0b8 feat($compile): add support for arbitrary DOM property and event bindings
Properties:

Previously only arbitrary DOM attribute bindings were supported via interpolation such as
`my-attribute="{{expression}}"` or `ng-attr-my-attribute="{{expression}}"`, and only a set of
distinct properties could be bound. `ng-prop-*` adds support for binding expressions to any DOM
properties. For example `ng-prop-foo="x"` will assign the value of the expression `x` to the
`foo` property, and re-assign whenever the expression `x` changes.

Events:

Previously only a distinct set of DOM events could be bound using directives such as `ng-click`,
`ng-blur` etc. `ng-on-*` adds support for binding expressions to any DOM event. For example
`ng-on-bar="barOccured($event)"` will add a listener to the "bar" event and invoke the
`barOccured($event)` expression.

Since HTML attributes are case-insensitive, property and event names are specified in snake_case
for `ng-prop-*` and `ng-on-*`. For example, to bind property `fooBar` use `ng-prop-foo_bar`, to
listen to event `fooBar` use `ng-on-foo_bar`.

Fixes #16428
Fixes #16235
Closes #16614
2018-08-01 17:50:25 -07:00
Jason Bedard 88a12f8623 refactor($compile): move img[srcset] sanitizing to helper method 2018-08-01 17:50:25 -07:00
Georgii Dolzhykov ebeb1c9491 fix(ngMock): pass failed HTTP expectations to $exceptionHandler
This was only partially fixed in f18dd2957.

Closes #16644
2018-07-31 14:35:39 +03:00
George Kalpakas 864c7f00c4 fix($location): avoid unnecessary $locationChange* events due to empty hash
Fixes #16632

Closes #16636
2018-07-30 23:36:31 +03:00
George Kalpakas 2907798ca5 test($location): add assertion 2018-07-30 23:36:12 +03:00
George Kalpakas 8562a62197 refactor($browser): correctly export helper used in specs
The helper is used in `fakeWindow.location.hash`. ATM, no test is using
the `hash` getter, so there were no errors.
2018-07-30 23:36:12 +03:00
George Kalpakas fdfba097da refactor($location): remove unnecessary capturing group in RegExp 2018-07-30 23:36:12 +03:00
George Kalpakas a07191727d refactor($location): minor changes (unused deps, exported globals, unused deps, etc) 2018-07-30 23:36:12 +03:00
George Kalpakas c133ef8360 fix($animate): avoid memory leak with $animate.enabled(element, enabled)
When disabling/enabling animations on a specific element (via
`$animate.enabled(element, enabled)`), the element is added in a map to
track its state. Previously, the element was never removed from the map,
causing AngularJS to hold on to the element even after it is removed
from the DOM, thus preventing it from being garbage collected.

This commit fixes it by removing the element from the map on `$destroy`.

Fixes #16637.

Closes #16649
2018-07-27 20:46:25 +03:00
Martin Staffa aa7d45e804 fix($compile): use correct parent element when requiring on html element
Fixes #16535
Closes #16647
2018-07-27 17:10:25 +02:00
Martin Staffa 6b915ad9db fix(Angular): add workaround for Safari / Webdriver problem
Closes #16645
2018-07-26 10:43:14 +02:00
Martin Staffa a42f8a0d5b fix(ngEventDirs): pass error in handler to $exceptionHandler when event was triggered in a digest
This ensures that the error handling is the same for events triggered inside and outside a digest.
2018-07-25 12:43:14 +02:00
Mark Gardner 6b0193e4d9 fix(ngEventDirs): don't wrap the event handler in $apply if already in $digest
Digest cycle already in progress error can inadvertently be caused when triggering an
element's click event while within an active digest cycle. This is due to the ngEventsDirs
event handler always calling $rootScope.$apply regardless of the status of $rootScope.$$phase.
Checking the phase and calling the function immediately if within an active digest cycle
will prevent the problem without reducing current functionality.

Closes #14673
Closes #14674
2018-07-25 12:43:14 +02:00
George Kalpakas e500fb6ddb fix(angular.element): do not break on cleanData() if _data() returns undefined
This shouldn't happen in supported jQuery versions (2+), but if someone
uses the unsupported 1.x version the app will break. The change that
causes this new behavior was introduced in b7d396b8b.

Even though jQuery 1.x is not supported, it is worth avoiding the
unnecessary breakage (given how simple).

Fixes #16641

Closes #16642
2018-07-23 14:20:31 +03:00
George Kalpakas 204f9ffebe docs(browserTrigger): document eventData.data property 2018-07-22 19:09:19 +03:00
George Kalpakas 7eee0c2c13 docs($route): fix typo (inluding --> including) 2018-07-22 18:44:59 +03:00
George Kalpakas 2dc83b2f04 docs(ngMock/$interval.flush): fix param type (not optional)
Closes #16640
2018-07-22 17:14:55 +03:00
George Kalpakas 3154111bb4 chore(saucelabs): upgrade sauce-connect to latest version
Closes #16639
2018-07-21 13:36:24 +03:00
George Kalpakas 2f6f1a7d70 chore(karma): upgrade karma and related dependencies to latest versions 2018-07-21 13:35:50 +03:00
Jason Bedard ff60b3f47a refactor($location): move repeated path normalization code into helper method (#16618)
Closes #16618
2018-07-21 02:39:38 -07:00
George Kalpakas c2d0783ca9 docs(ngMocks): fix type for $flushPendingTasks/$verifyPendingsTasks
Closes #16638
2018-07-21 10:44:57 +03:00
George Kalpakas 6706353a71 docs(ngMock/$timeout): deprecate flush() and verifyNoPendingTasks()
Closes #16603
2018-07-13 13:31:50 +03:00
George Kalpakas 24eeab0ebd docs(ngMock/$timeout): recommend $verifyNoPendingTasks() when appropriate
For historical reasons, `$timeout.verifyNoPendingTasks()` throws if
there is any type of task pending (even if it is not a timeout). When
calling `$timeout.verifyNoPendingTasks()`, the user is most likely
interested in verifying pending timeouts only, which is now possible
with `$verifyNoPendingTasks('$timeout')`.

To raise awareness of `$verifyNoPendingTasks()`, it is mentioned in the
error message thrown by `$timeoutverifyNoPendingTasks()` if none of the
pending tasks is a timeout.
2018-07-13 13:31:48 +03:00
George Kalpakas b14f67f4ae feat(ngMock): add $flushPendingTasks() and $verifyNoPendingTasks()
`$flushPendingTasks([delay])` allows flushing all pending tasks (or up
to a specific delay). This includes `$timeout`s, `$q` promises and tasks
scheduled via `$rootScope.$applyAsync()` and `$rootScope.$evalAsync()`.
(ATM, it only flushes tasks scheduled via `$browser.defer()`, which does
not include `$http` requests and `$route` transitions.)

`$verifyNoPendingTasks([taskType])` allows verifying that there are no
pending tasks (in general or of a specific type). This includes tasks
flushed by `$flushPendingTasks()` as well as pending `$http` requests
and in-progress `$route` transitions.

Background:
`ngMock/$timeout` has `flush()` and `verifyNoPendingTasks()` methods,
but they take all kinds of tasks into account which is confusing. For
example, `$timeout.verifyNoPendingTasks()` can fail (even if there are
no pending timeouts) because of an unrelated pending `$http` request.

This behavior is retained for backwards compatibility, but the new
methods are more generic (and thus less confusing) and also allow
more fine-grained control (when appropriate).

Closes #14336
2018-07-13 13:31:48 +03:00
George Kalpakas 411e35472b refactor($browser): share task-tracking code between $browser and ngMock/$browser
This avoids code/logic duplication and helps the implementations stay
in-sync.
2018-07-13 13:31:46 +03:00
George Kalpakas af59a0a00b refactor($interval): share code between $interval and ngMock/$interval
This avoids code/logic duplication and helps the implementations stay
in-sync.
2018-07-13 13:31:44 +03:00
George Kalpakas 1c380b7671 refactor(ngMock/$interval): more closely follow actual $interval's internal implementation 2018-07-13 13:31:44 +03:00
George Kalpakas 84fb041a89 feat(*): implement more granular pending task tracking
Previously, all pending async tasks (tracked via `$browser`) are treated
the same. I.e. things like `$$testability.whenStable()` and
`ngMock#$timeout.verifyNoPendingTasks()` take all tasks into account.

Yet, in some cases we might be interested in specific tasks only. For
example, if one wants to verify there are no pending `$timeout`s, they
don't care if there are other pending tasks, such as `$http` requests.
Similarly, one might want to get notified when all `$http` requests have
completed and does not care about pending promises.

This commit adds support for more granular task tracking, by enabling
callers to specify the type of task that is being added/removed from the
queue and enabling listeners to be triggered when specific types of
tasks are completed (even if there are more pending tasks of different
types).

The change is backwards compatible. I.e. calling the affected methods
with no explicit task-type, behaves the same as before.

Related to #14336.
2018-07-13 13:31:43 +03:00
Jason Bedard aee7d53a6b fix($location): fix infinite recursion/digest on URLs with special characters
Some characters are treated differently by `$location` compared to `$browser` and
the native browser. When comparing URLs across these two services this must be
taken into account.

Fixes #16592
Closes #16611
2018-07-09 19:39:25 -07:00
George Kalpakas bfdc9170f2 fix($compile): work around Firefox DocumentFragment bug
DOM nodes passed to `compilationGenerator()` will eventually be wrapped
in `jqLite`, when the compilation actually happens. In Firefox 60+,
there seems to be a `DocumentFragment`-related bug that sometimes causes
the `childNodes` to be empty at the time the compilation happens.

This commit works around this bug by eagerly wrapping `childNodes` in
`jqLite`.

NOTE:
The wrapped nodes have references to their `DocumentFragment` container.
This is "by design", since we want to be able to traverse the nodes via
`nextSibling` (in order to correctly handle multi-element directives).

Once the nodes are compiled, they will be either moved to a new
container element or the `jqLite` wrapper is release making them
eligible for garbage collection. In both cases, the original
`DocumentFragment` container should be eligible for garbage collection
too.

Fixes #16607

Closes #16615
2018-07-09 21:21:24 +03:00
Martin Staffa 494277e3d3 docs($animate): clarify possible options and fired events 2018-07-09 17:10:03 +02:00
Martin Staffa e4339bc512 feat($animate): add option data to event callbacks
Closes #12697
Closes #13059
2018-07-09 17:10:03 +02:00
Matias Niemelä 0e26197623 perf(ngAnimate): avoid repeated calls to addClass/removeClass when animation has no duration
Background:
ngAnimate writes helper classes to DOM elements to see if animations are defined on them. If many
elements have the same definition, and the same parent, we can cache the definition and skip the
application of the helper classes altogether. This helps particularly with large ngRepeat
collections.

Closes #14165
Closes #14166
Closes #16613
2018-07-05 19:44:34 +02:00
Martin Staffa 74726b4f79 docs(changelog, guide/Migration): add info about $sce BC in 1.7
Closes #16593
Closes #16622
2018-07-04 14:49:40 +02:00
Martin Staffa 3ad07eadef docs(guide/Using Location): change / remove obsolete information 2018-07-04 14:46:26 +02:00
Martin Staffa 7920f7140f docs(*): fix headlines 2018-07-04 14:46:26 +02:00
Atef Ben Ali 66fdf2a8a7 docs(guide/component): add missing :
Add `:` to `Components have a well-defined lifecycle` title.

Closes #16620
2018-06-29 11:10:01 +03:00
Martin Staffa 26a5779cdd chore(benchpress): add ngRepeat animation benchmark
Closes #13976
2018-06-25 16:54:33 +02:00
Martin Staffa a90d0cbbba chore: fix eslint error 2018-06-23 17:56:45 +02:00
Pete Bacon Darwin 9307ccf6ee chore(docs-app): ensure ToC links contain the path
Without the path the link is always pointing to the
root page, rather than the current page, which means
that copying the link address or opening the page in
a new tab is broken.

Closes #16608
2018-06-22 21:54:51 +01:00
Ilia Choly 2c7aa68ca8 fix(grunt-utils): correctly detect java 32bit support
Closes #16605
2018-06-20 21:06:25 +03:00
Georgii Dolzhykov 840b5f0a76 fix(ngMock/$httpBackend): correctly ignore query params in {expect,when}Route
Previously, a route definition such as
`$httpBackend.whenRoute('GET', '/route/:id')` matched against a URL with
query params, for example `/route/1?q=foo`, would incorrectly include
the query params in `id`: `{id: '1?q=foo', q: 'foo'}`.

This commit fixes it, so that the extracted `params` will now be:
`{id: '1', q: 'foo'}`.

Fixes #14173

Closes #16589
2018-06-18 19:42:50 +03:00
George Kalpakas 6c224a2a60 fix(ngAria): do not scroll when pressing spacebar on custom buttons
By default, pressing spacebar causes the browser to scroll down.
However, when a native button is focused, the button is clicked instead.

`ngAria`'s `ngClick` directive, sets elements up to behave like buttons.
For example, it adds `role="button"` and forwards `ENTER` and `SPACEBAR`
keypresses to the `click` handler (to emulate the behavior of native
buttons).

Yet, pressing spacebar on such an element, still invokes the default
browser behavior of scrolling down.

This commit fixes this, by calling `preventDefault()` on the keyboard
event, thus preventing the default scrolling behavior and making custom
buttons behave closer to native ones.

Closes #14665

Closes #16604
2018-06-18 18:44:46 +03:00
George Kalpakas af1e6a38b7 refactor(ngAria): clean up accessible actions tests 2018-06-18 18:44:27 +03:00
Martin Staffa 5b11145473 feat(form.FormController): add $getControls()
Closes #16601
Fixes #14749
Closes #14517
Closes #13202
2018-06-18 16:02:19 +02:00
Martin Staffa d7d64cc363 docs(downloading.ngdoc): remove link to Google CDN overview page
AngularJS is no longer listed on the CDN page, because the available versions
were almost always out of date due to the need to manually update the list
2018-06-14 18:06:00 +02:00
Pete Bacon Darwin 998d8b4b55 chore(code.angularjs.org): fix directory list paging 2018-06-14 10:48:59 +01:00
Pete Bacon Darwin 2472d621d7 chore(code.angularjs.org): update firebase libraries 2018-06-14 08:37:22 +01:00
Martin Staffa 83f7980e2f feat(ngModelOptions): add timeStripZeroSeconds and timeSecondsFormat
Closes #10721
Closes #16510
Closes #16584
2018-06-12 22:45:17 +01:00
George Kalpakas c9a92fcad5 docs(CHANGELOG): add release notes for 1.7.2 2018-06-12 16:33:59 +03:00
George Kalpakas f7b2fffa51 chore(doc-gen): error on unmatched links
Closes #16597
2018-06-12 14:40:32 +03:00
George Kalpakas 9a065c0fa9 docs(*): fix dangling or ambiguous links 2018-06-12 14:40:21 +03:00
George Kalpakas 42e622b751 revert: refactor($compile): remove preAssignBindingsEnabled leftovers
This reverts commit 8e104ee508.

This internal clean-up turned out to break popular UI libraries (e.g.
`ngMaterial`, `ui-bootstrap`) and cause pain to developers.

Fixes #16594

Closes #16595
2018-06-11 16:58:54 +03:00
George Kalpakas ad0ba99d8a docs(CHANGELOG): fix links to issues/PRs 2018-06-08 17:09:30 +03:00
George Kalpakas 05170bf371 docs(CHANGELOG): add release notes for 1.7.1 2018-06-08 16:25:08 +03:00
Georgios Kalpakas db584f7835 feat(ngAria): add support for ignoring a specific element
Fixes #14602
Fixes #14672

Closes #14833
2018-06-08 11:11:55 +03:00
Georgios Kalpakas 83d1229c87 refactor(ngAria): move test helpers inside of closure 2018-06-08 11:02:49 +03:00
Georgios Kalpakas 60069e67ae feat($route): add support for the reloadOnUrl configuration option
Enables users to specify that a particular route should not be reloaded after a
URL change (including a change in `$location.path()`), if the new URL maps to
the same route.
The default behavior is still to always load the matched route when any part of
the URL changes.

Related to #1699, #5860, #14999 (potentially closing the first two).

Fixes #7925

Closes #15002
2018-06-08 10:58:53 +03:00
Martin Staffa 84d80be2b4 feat(ngMessages): add support for default message
add support for showing default message when a truthy value is not matched by an ng-message directive.

Closes #12008
Closes #12213
Closes #16587
2018-06-06 17:40:55 +02:00
Martin Staffa 03a4782a35 feat(errorHandlingConfig): add option to exclude error params from url
Specific errors, such as those during nested module loading, can create very long
error urls because the error message includes the error stack. These urls create visual
clutter in the browser console, are often not clickable, and may be rejected
by the docs page because they are simply too long.

We've already made improvements to the error display in #16283, which excludes
the error url from error parameters, which results in cleaner error messages.

Further, modern browsers restrict console message length intelligently.

This option can still be useful for older browsers like Internet Explorer, or
in general to reduce visual clutter in the console.

Closes #14744
Closes #15707
Closes #16283
Closes #16299 
Closes #16591
2018-06-06 15:03:09 +02:00
Jakub Freisler 0ed36430da docs(ngAnimate): add "animating between value changes" section
Add a section which covers use case when users need to animate upon
a variable's value changes (not between two states).

Refers #16561

Closes #16582
2018-06-05 23:22:25 +03:00
Martin Staffa aa6adc77ae feat(ngRef): add directive to publish controller, or element into scope
Thanks to @drpicox for the original implementation: PR #14080 

Closes #16511
2018-06-05 15:00:12 +02:00
Georgii Dolzhykov a7de87d3e8 docs($httpBackend): headers param of expect* can be function
Closes #16588
2018-06-04 14:31:06 +03:00
Christian Oliff 1bb9aa0523 docs(.editorconfig): change link to use https 2018-06-04 10:13:43 +02:00
Jakub Freisler 586b6e8b73 docs(ngAnimate): refactor of ngAnimate docs
- Synced "animation aware" directives tables in API docs and "Animation"
  guide.
- Sorted tables alphabetically.
- Added info about `ngAnimateSwap` directive.

References #16561

Closes #16581
2018-06-02 11:48:20 +03:00
George Kalpakas 459997b482 fix(ngModel): do not throw if view value changes on destroyed scope
This could for example happen if updating the value is debounced (either
by asynchronously calling `$setViewValue()` or via `ngModelOptions`).

Fixes #16583

Closes #16585
2018-05-31 10:51:53 +03:00
Georgii Dolzhykov 8e104ee508 refactor($compile): remove preAssignBindingsEnabled leftovers
Now that we don't need to support `preAssignBindingsEnabled` (removed in #15782),
complexity introduced in `$controller` by #7645 can be removed.

One difference with the previous implementation is that all non-ES2015-class controller instances
were available on the element before calling their constructors. Now it depends on the relative
order of controllers. Controller constructors shouldn't be used to access other controllers
(e.g. via `$element.controller(directiveName)`). The recommended way is to use the `require`
property of the directive definition object and the life cycle hooks `$onChanges` or `$onInit`.

See
https://docs.angularjs.org/api/ng/service/$compile#-require-
https://docs.angularjs.org/api/ng/service/$compile#life-cycle-hooks

Closes #16580
2018-05-30 23:01:52 +03:00
George Kalpakas 78b9f61366 fix($compile): support transcluding multi-element directives
Previously, transcluding multi-element directives (e.g. `foo-start`/`foo-end`)
was not supported on elements with multi-slot transclusion (a `uterdir` error
was thrown).
This commit fixes it by putting the transcluded nodes into a DocumentFragment,
where they can be traversed via `.nextSibling`.

Fixes #15554
Closes #15555
2018-05-26 16:11:09 +02:00
Martin Staffa b9f19ad0d4 docs(ngMockE2E): add docs for $httpBackend.matchLatestDefinitionEnabled()
Closes #16577
2018-05-26 16:05:51 +02:00
Martin Staffa 0b1adbb2e7 chore: try ios 11.2 2018-05-25 21:17:55 +02:00
Martin Staffa 6a1d7dd7dc chore(saucelabs): remove platform for ios11 2018-05-25 20:18:30 +02:00
George Kalpakas 5262039096 refactor(ngModelOptions): fix ng-closure-runner warning
Without this fix `grunt minall` emits the following warning:
> WARNING - Parse error. Non-JSDoc comment has annotations.
> Did you mean to start it with '/**'?

Closes #16575
2018-05-24 12:59:30 +03:00
Martin Staffa 96c4cb6694 chore: set ios 11 in saucelabs to 11.3 2018-05-23 23:36:48 +02:00
Martin Staffa 9fe596422f chore: add platform to ios saucelabs 2018-05-23 22:42:14 +02:00
Pete Bacon Darwin e22c5d3dba chore(package.json): update dist-tag for master branch 2018-05-23 19:20:29 +01:00
16 changed files with 3887 additions and 296 deletions
-24
View File
@@ -1,27 +1,3 @@
<a name="1.7.4"></a>
# 1.7.4 interstellar-exploration (2018-09-07)
## Bug Fixes
- **ngAria.ngClick:** prevent default event on space/enter only for non-interactive elements
([61b335](https://github.com/angular/angular.js/commit/61b33543ff8e7f32464dec98a46bf0a35e9b03a4),
[#16664](https://github.com/angular/angular.js/issues/16664),
[#16680](https://github.com/angular/angular.js/issues/16680))
- **ngAnimate:** remove the "prepare" classes with multiple structural animations
([3105b2](https://github.com/angular/angular.js/commit/3105b2c26a71594c4e7904efc18f4b2e9da25b1b),
[#16681](https://github.com/angular/angular.js/issues/16681),
[#16677](https://github.com/angular/angular.js/issues/16677))
- **$route:** correctly extract path params if the path contains a question mark or a hash
([2ceeb7](https://github.com/angular/angular.js/commit/2ceeb739f35e01fcebcabac4beeeb7684ae9f86d))
- **ngHref:** allow numbers and other objects in interpolation
([30084c](https://github.com/angular/angular.js/commit/30084c13699c814ff6703d7aa2d3947a9b2f7067),
[#16652](https://github.com/angular/angular.js/issues/16652),
[#16626](https://github.com/angular/angular.js/issues/16626))
- **select:** allow to select first option with value `undefined`
([668a33](https://github.com/angular/angular.js/commit/668a33da3439f17e61dfa8f6d9b114ebde8c9d87),
[#16653](https://github.com/angular/angular.js/issues/16653),
[#16656](https://github.com/angular/angular.js/issues/16656))
<a name="1.7.3"></a>
# 1.7.3 eventful-proposal (2018-08-03)
+1 -1
View File
@@ -102,7 +102,7 @@ For more information please visit {@link $http#json-vulnerability-protection JSO
Bear in mind that calling `$http.jsonp` gives the remote server (and, if the request is not secured, any Man-in-the-Middle attackers)
instant remote code execution in your application: the result of these requests is handed off
to the browser as a regular `<script>` tag.
to the browser as regular `<script>` tag.
## Strict Contextual Escaping
+21 -6
View File
@@ -7,9 +7,26 @@
This page describes the support status of the significant versions of AngularJS.
<div class="alert alert-info">
On July 1, 2018 AngularJS entered a 3 year Long Term Support period.
AngularJS is planning one more significant release, version 1.7, and on July 1, 2018 it will enter a 3 year Long Term Support period.
</div>
### Until July 1st 2018
Any version branch not shown in the following table (e.g. 1.5.x) is no longer being developed.
<table class="dev-status table table-bordered">
<thead>
<tr><th>Version</th><th>Status</th><th>Comments</th></tr>
</thead>
<tbody>
<tr class="security"><td><span>1.2.x</span></td><td>Security patches only</td><td>Last version to provide IE 8 support</td></tr>
<tr class="stable"><td><span>1.6.x</span></td><td>Patch Releases</td><td>Minor features, bug fixes, security patches - no breaking changes</td></tr>
<tr class="current"><td><span>1.7.x</span></td><td>Active Development</td><td>1.7.0 (not yet released) will be the last release of AngularJS to contain breaking changes</td></tr>
</tbody>
</table>
### After July 1st 2018
Any version branch not shown in the following table (e.g. 1.6.x) is no longer being developed.
<table class="dev-status table table-bordered">
@@ -19,7 +36,7 @@ Any version branch not shown in the following table (e.g. 1.6.x) is no longer be
<tbody>
<tr class="security">
<td><span>1.2.x</span></td>
<td>Security patches only</td>
<td>Long Term Support</td>
<td>Last version to provide IE 8 support</td>
</tr>
<tr class="stable">
@@ -32,16 +49,14 @@ Any version branch not shown in the following table (e.g. 1.6.x) is no longer be
### Long Term Support
On July 1st 2018, AngularJS entered a Long Term Support period for AngularJS.
On July 1st 2018, we will enter a Long Term Support period for AngularJS.
We now focus exclusively on providing fixes to bugs that satisfy at least one of the following criteria:
At this time we will focus exclusively on providing fixes to bugs that satisfy at least one of the following criteria:
* A security flaw is detected in the 1.7.x branch of the framework
* One of the major browsers releases a version that will cause current production applications using AngularJS 1.7.x to stop working
* The jQuery library releases a version that will cause current production applications using AngularJS 1.7.x to stop working.
AngularJS 1.2.x will get a new version if and only if a new severe security issue is discovered.
### Blog Post
You can read more about these plans in our [blog post announcement](https://blog.angular.io/stable-angularjs-and-long-term-support-7e077635ee9c).
+2 -2
View File
@@ -3,7 +3,7 @@
"license": "MIT",
"branchVersion": "^1.7.0",
"branchPattern": "1.7.*",
"distTag": "latest",
"distTag": "next",
"repository": {
"type": "git",
"url": "https://github.com/angular/angular.js.git"
@@ -33,7 +33,7 @@
"cross-spawn": "^4.0.0",
"cz-conventional-changelog": "1.1.4",
"dgeni": "^0.4.9",
"dgeni-packages": "^0.26.5",
"dgeni-packages": "^0.26.2",
"eslint-plugin-promise": "^3.6.0",
"event-stream": "~3.1.0",
"glob": "^6.0.1",
@@ -10,7 +10,8 @@ const BROWSER_CACHE_DURATION = 60 * 10;
const CDN_CACHE_DURATION = 60 * 60 * 12;
function sendStoredFile(request, response) {
let filePathSegments = request.path.split('/').filter((segment) => {
const requestPath = request.path || '/';
let filePathSegments = requestPath.split('/').filter((segment) => {
// Remove empty leading or trailing path parts
return segment !== '';
});
@@ -159,7 +160,11 @@ function sendStoredFile(request, response) {
const nextQuery = data[1];
const apiResponse = data[2];
if (!files.length && (!apiResponse || !apiResponse.prefixes)) {
if (
// we got no files or directories from previous query pages
!fileList.length && !directoryList.length &&
// this query page has no file or directories
!files.length && (!apiResponse || !apiResponse.prefixes)) {
return Promise.reject({
code: 404
});
@@ -190,22 +195,16 @@ const snapshotRegex = /^snapshot(-stable)?\//;
* When a new zip file is uploaded into snapshot or snapshot-stable,
* delete the previous zip file.
*/
function deleteOldSnapshotZip(event) {
const object = event.data;
function deleteOldSnapshotZip(object, context) {
const bucketId = object.bucket;
const filePath = object.name;
const contentType = object.contentType;
const resourceState = object.resourceState;
const bucket = gcs.bucket(bucketId);
const snapshotFolderMatch = filePath.match(snapshotRegex);
if (!snapshotFolderMatch ||
contentType !== 'application/zip' ||
resourceState === 'not_exists' // Deletion event
) {
if (!snapshotFolderMatch || contentType !== 'application/zip') {
return;
}
@@ -230,4 +229,4 @@ function deleteOldSnapshotZip(event) {
}
exports.sendStoredFile = functions.https.onRequest(sendStoredFile);
exports.deleteOldSnapshotZip = functions.storage.object().onChange(deleteOldSnapshotZip);
exports.deleteOldSnapshotZip = functions.storage.object().onFinalize(deleteOldSnapshotZip);
File diff suppressed because it is too large Load Diff
@@ -3,8 +3,8 @@
"description": "Cloud Functions to serve files from gcs to code.angularjs.org",
"dependencies": {
"@google-cloud/storage": "^1.1.1",
"firebase-admin": "^4.2.1",
"firebase-functions": "^0.5.9"
"firebase-admin": "^5.11.0",
"firebase-functions": "^1.0.4"
},
"private": true
}
+4 -5
View File
@@ -792,16 +792,15 @@ function arrayRemove(array, value) {
* * If `source` is identical to `destination` an exception will be thrown.
*
* <br />
*
* <div class="alert alert-warning">
* Only enumerable properties are taken into account. Non-enumerable properties (both on `source`
* and on `destination`) will be ignored.
* </div>
*
* @param {*} source The source that will be used to make a copy. Can be any type, including
* primitives, `null`, and `undefined`.
* @param {(Object|Array)=} destination Destination into which the source is copied. If provided,
* must be of the same type as `source`.
* @param {*} source The source that will be used to make a copy.
* Can be any type, including primitives, `null`, and `undefined`.
* @param {(Object|Array)=} destination Destination into which the source is copied. If
* provided, must be of the same type as `source`.
* @returns {*} The copy or updated `destination`, if `destination` was specified.
*
* @example
+3 -2
View File
@@ -113,6 +113,8 @@ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animatePro
// TODO(matsko): document the signature in a better way
return function(element, event, options) {
var node = getDomNode(element);
options = prepareAnimationOptions(options);
var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0;
@@ -184,9 +186,8 @@ var $$AnimationProvider = ['$animateProvider', /** @this */ function($animatePro
forEach(groupedAnimations, function(animationEntry) {
var element = animationEntry.from ? animationEntry.from.element : animationEntry.element;
var extraClasses = options.addClass;
extraClasses = (extraClasses ? (extraClasses + ' ') : '') + NG_ANIMATE_CLASSNAME;
var cacheKey = $$animateCache.cacheKey(element[0], animationEntry.event, extraClasses, options.removeClass);
var cacheKey = $$animateCache.cacheKey(node, event, extraClasses, options.removeClass);
toBeSortedAnimations.push({
element: element,
+85 -105
View File
@@ -1771,8 +1771,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* See {@link ngMock.$httpBackend#when `when`} for more info.
*/
$httpBackend.whenRoute = function(method, url) {
var parsed = parseRouteUrl(url);
return $httpBackend.when(method, parsed.regexp, undefined, undefined, parsed.keys);
var pathObj = routeToRegExp(url, {caseInsensitiveMatch: true, ignoreTrailingSlashes: true});
return $httpBackend.when(method, pathObj.regexp, undefined, undefined, pathObj.keys);
};
/**
@@ -1955,8 +1955,8 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* See {@link ngMock.$httpBackend#expect `expect`} for more info.
*/
$httpBackend.expectRoute = function(method, url) {
var parsed = parseRouteUrl(url);
return $httpBackend.expect(method, parsed.regexp, undefined, undefined, parsed.keys);
var pathObj = routeToRegExp(url, {caseInsensitiveMatch: true, ignoreTrailingSlashes: true});
return $httpBackend.expect(method, pathObj.regexp, undefined, undefined, pathObj.keys);
};
@@ -2084,12 +2084,6 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
};
});
}
function parseRouteUrl(url) {
var strippedUrl = stripQueryAndHash(url);
var parseOptions = {caseInsensitiveMatch: true, ignoreTrailingSlashes: true};
return routeToRegExp(strippedUrl, parseOptions);
}
}
function assertArgDefined(args, index, name) {
@@ -2098,124 +2092,110 @@ function assertArgDefined(args, index, name) {
}
}
function stripQueryAndHash(url) {
return url.replace(/[?#].*$/, '');
}
function MockHttpExpectation(expectedMethod, expectedUrl, expectedData, expectedHeaders,
expectedKeys) {
function MockHttpExpectation(method, url, data, headers, keys) {
this.data = expectedData;
this.headers = expectedHeaders;
function getUrlParams(u) {
var params = u.slice(u.indexOf('?') + 1).split('&');
return params.sort();
}
this.match = function(method, url, data, headers) {
if (expectedMethod !== method) return false;
if (!this.matchUrl(url)) return false;
if (angular.isDefined(data) && !this.matchData(data)) return false;
if (angular.isDefined(headers) && !this.matchHeaders(headers)) return false;
function compareUrl(u) {
return (url.slice(0, url.indexOf('?')) === u.slice(0, u.indexOf('?')) &&
getUrlParams(url).join() === getUrlParams(u).join());
}
this.data = data;
this.headers = headers;
this.match = function(m, u, d, h) {
if (method !== m) return false;
if (!this.matchUrl(u)) return false;
if (angular.isDefined(d) && !this.matchData(d)) return false;
if (angular.isDefined(h) && !this.matchHeaders(h)) return false;
return true;
};
this.matchUrl = function(url) {
if (!expectedUrl) return true;
if (angular.isFunction(expectedUrl.test)) return expectedUrl.test(url);
if (angular.isFunction(expectedUrl)) return expectedUrl(url);
return (expectedUrl === url || compareUrlWithQuery(url));
this.matchUrl = function(u) {
if (!url) return true;
if (angular.isFunction(url.test)) return url.test(u);
if (angular.isFunction(url)) return url(u);
return (url === u || compareUrl(u));
};
this.matchHeaders = function(headers) {
if (angular.isUndefined(expectedHeaders)) return true;
if (angular.isFunction(expectedHeaders)) return expectedHeaders(headers);
return angular.equals(expectedHeaders, headers);
this.matchHeaders = function(h) {
if (angular.isUndefined(headers)) return true;
if (angular.isFunction(headers)) return headers(h);
return angular.equals(headers, h);
};
this.matchData = function(data) {
if (angular.isUndefined(expectedData)) return true;
if (expectedData && angular.isFunction(expectedData.test)) return expectedData.test(data);
if (expectedData && angular.isFunction(expectedData)) return expectedData(data);
if (expectedData && !angular.isString(expectedData)) {
return angular.equals(angular.fromJson(angular.toJson(expectedData)), angular.fromJson(data));
this.matchData = function(d) {
if (angular.isUndefined(data)) return true;
if (data && angular.isFunction(data.test)) return data.test(d);
if (data && angular.isFunction(data)) return data(d);
if (data && !angular.isString(data)) {
return angular.equals(angular.fromJson(angular.toJson(data)), angular.fromJson(d));
}
// eslint-disable-next-line eqeqeq
return expectedData == data;
return data == d;
};
this.toString = function() {
return expectedMethod + ' ' + expectedUrl;
return method + ' ' + url;
};
this.params = function(url) {
var queryStr = url.indexOf('?') === -1 ? '' : url.substring(url.indexOf('?') + 1);
var strippedUrl = stripQueryAndHash(url);
this.params = function(u) {
return angular.extend(parseQuery(), pathParams());
return angular.extend(extractParamsFromQuery(queryStr), extractParamsFromPath(strippedUrl));
};
function pathParams() {
var keyObj = {};
if (!url || !angular.isFunction(url.test) || !keys || keys.length === 0) return keyObj;
function compareUrlWithQuery(url) {
var urlWithQueryRe = /^([^?]*)\?(.*)$/;
var expectedMatch = urlWithQueryRe.exec(expectedUrl);
var actualMatch = urlWithQueryRe.exec(url);
return !!(expectedMatch && actualMatch) &&
(expectedMatch[1] === actualMatch[1]) &&
(normalizeQuery(expectedMatch[2]) === normalizeQuery(actualMatch[2]));
}
function normalizeQuery(queryStr) {
return queryStr.split('&').sort().join('&');
}
function extractParamsFromPath(strippedUrl) {
var keyObj = {};
if (!expectedUrl || !angular.isFunction(expectedUrl.test) ||
!expectedKeys || !expectedKeys.length) return keyObj;
var match = expectedUrl.exec(strippedUrl);
if (!match) return keyObj;
for (var i = 1, len = match.length; i < len; ++i) {
var key = expectedKeys[i - 1];
var val = match[i];
if (key && val) {
keyObj[key.name || key] = val;
}
}
return keyObj;
}
function extractParamsFromQuery(queryStr) {
var obj = {},
keyValuePairs = queryStr.split('&').
filter(angular.identity). // Ignore empty segments.
map(function(keyValue) { return keyValue.replace(/\+/g, '%20').split('='); });
angular.forEach(keyValuePairs, function(pair) {
var key = tryDecodeURIComponent(pair[0]);
if (angular.isDefined(key)) {
var val = angular.isDefined(pair[1]) ? tryDecodeURIComponent(pair[1]) : true;
if (!hasOwnProperty.call(obj, key)) {
obj[key] = val;
} else if (angular.isArray(obj[key])) {
obj[key].push(val);
} else {
obj[key] = [obj[key], val];
var m = url.exec(u);
if (!m) return keyObj;
for (var i = 1, len = m.length; i < len; ++i) {
var key = keys[i - 1];
var val = m[i];
if (key && val) {
keyObj[key.name || key] = val;
}
}
});
return obj;
}
function tryDecodeURIComponent(value) {
try {
return decodeURIComponent(value);
} catch (e) {
// Ignore any invalid uri component
return keyObj;
}
}
function parseQuery() {
var obj = {}, key_value, key,
queryStr = u.indexOf('?') > -1
? u.substring(u.indexOf('?') + 1)
: '';
angular.forEach(queryStr.split('&'), function(keyValue) {
if (keyValue) {
key_value = keyValue.replace(/\+/g,'%20').split('=');
key = tryDecodeURIComponent(key_value[0]);
if (angular.isDefined(key)) {
var val = angular.isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true;
if (!hasOwnProperty.call(obj, key)) {
obj[key] = val;
} else if (angular.isArray(obj[key])) {
obj[key].push(val);
} else {
obj[key] = [obj[key],val];
}
}
}
});
return obj;
}
function tryDecodeURIComponent(value) {
try {
return decodeURIComponent(value);
} catch (e) {
// Ignore any invalid uri component
}
}
};
}
function createMockXhr() {
+1 -2
View File
@@ -225,7 +225,6 @@ function $RouteProvider() {
}
routes[path] = angular.extend(
routeCopy,
{originalPath: path},
path && routeToRegExp(path, routeCopy)
);
@@ -236,7 +235,7 @@ function $RouteProvider() {
: path + '/';
routes[redirectPath] = angular.extend(
{originalPath: path, redirectTo: path},
{redirectTo: path},
routeToRegExp(redirectPath, routeCopy)
);
}
+9 -9
View File
@@ -3,16 +3,15 @@
/* global routeToRegExp: true */
/**
* @param {string} path - The path to parse. (It is assumed to have query and hash stripped off.)
* @param {Object} opts - Options.
* @return {Object} - An object containing an array of path parameter names (`keys`) and a regular
* expression (`regexp`) that can be used to identify a matching URL and extract the path
* parameter values.
* @param path {string} path
* @param opts {Object} options
* @return {?Object}
*
* @description
* Parses the given path, extracting path parameter names and a regular expression to match URLs.
* Normalizes the given path, returning a regular expression
* and the original path.
*
* Originally inspired by `pathRexp` in `visionmedia/express/lib/utils.js`.
* Inspired by pathRexp in visionmedia/express/lib/utils.js.
*/
function routeToRegExp(path, opts) {
var keys = [];
@@ -22,11 +21,11 @@ function routeToRegExp(path, opts) {
.replace(/(\/)?:(\w+)(\*\?|[?*])?/g, function(_, slash, key, option) {
var optional = option === '?' || option === '*?';
var star = option === '*' || option === '*?';
keys.push({name: key, optional: optional});
keys.push({ name: key, optional: optional });
slash = slash || '';
return (
(optional ? '(?:' + slash : slash + '(?:') +
(star ? '(.+?)' : '([^/]+)') +
(star ? '([^?#]+?)' : '([^/?#]+)') +
(optional ? '?)?' : ')')
);
})
@@ -37,6 +36,7 @@ function routeToRegExp(path, opts) {
}
return {
originalPath: path,
keys: keys,
regexp: new RegExp(
'^' + pattern + '(?:[?#]|$)',
-81
View File
@@ -25,7 +25,6 @@ describe('ngAnimate integration tests', function() {
ss.destroy();
});
it('should cancel a running and started removeClass animation when a follow-up addClass animation adds the same class',
inject(function($animate, $rootScope, $$rAF, $document, $rootElement) {
@@ -363,7 +362,6 @@ describe('ngAnimate integration tests', function() {
});
});
it('should add the preparation class for an enter animation before a parent class-based animation is applied', function() {
module('ngAnimateMock');
inject(function($animate, $compile, $rootScope, $rootElement, $document) {
@@ -399,7 +397,6 @@ describe('ngAnimate integration tests', function() {
});
});
it('should avoid adding the ng-enter-prepare method to a parent structural animation that contains child animations', function() {
module('ngAnimateMock');
inject(function($animate, $compile, $rootScope, $rootElement, $document, $$rAF) {
@@ -471,84 +468,6 @@ describe('ngAnimate integration tests', function() {
});
});
it('should remove the prepare classes when different structural animations happen in the same digest', function() {
module('ngAnimateMock');
inject(function($animate, $compile, $rootScope, $rootElement, $document, $$animateCache) {
element = jqLite(
// Class animation on parent element is neeeded so the child elements get the prepare class
'<div id="outer" ng-class="{blue: cond}" ng-switch="cond">' +
'<div id="default" ng-switch-default></div>' +
'<div id="truthy" ng-switch-when="true"></div>' +
'</div>'
);
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
$compile(element)($rootScope);
$rootScope.cond = false;
$rootScope.$digest();
$rootScope.cond = true;
$rootScope.$digest();
var parent = element;
var truthySwitch = jqLite(parent[0].querySelector('#truthy'));
var defaultSwitch = jqLite(parent[0].querySelector('#default'));
expect(parent).not.toHaveClass('blue');
expect(parent).toHaveClass('blue-add');
expect(truthySwitch).toHaveClass('ng-enter-prepare');
expect(defaultSwitch).toHaveClass('ng-leave-prepare');
$animate.flush();
expect(parent).toHaveClass('blue');
expect(parent).not.toHaveClass('blue-add');
expect(truthySwitch).not.toHaveClass('ng-enter-prepare');
expect(defaultSwitch).not.toHaveClass('ng-leave-prepare');
});
});
it('should respect the element node for caching when animations with the same type happen in the same digest', function() {
module('ngAnimateMock');
inject(function($animate, $compile, $rootScope, $rootElement, $document, $$animateCache) {
ss.addRule('.animate.ng-enter', 'transition:2s linear all;');
element = jqLite(
'<div>' +
'<div>' +
'<div id="noanimate" ng-if="cond"></div>' +
'</div>' +
'<div>' +
'<div id="animate" class="animate" ng-if="cond"></div>' +
'</div>' +
'</div>'
);
$rootElement.append(element);
jqLite($document[0].body).append($rootElement);
$compile(element)($rootScope);
$rootScope.cond = true;
$rootScope.$digest();
var parent = element;
var noanimate = jqLite(parent[0].querySelector('#noanimate'));
var animate = jqLite(parent[0].querySelector('#animate'));
expect(noanimate).not.toHaveClass('ng-enter');
expect(animate).toHaveClass('ng-enter');
$animate.closeAndFlush();
expect(noanimate).not.toHaveClass('ng-enter');
expect(animate).not.toHaveClass('ng-enter');
});
});
it('should pack level elements into their own RAF flush', function() {
module('ngAnimateMock');
inject(function($animate, $compile, $rootScope, $rootElement, $document) {
+3 -3
View File
@@ -2251,7 +2251,7 @@ describe('ngMock', function() {
}
);
they('should ignore query params when matching in ' + routeShortcut + ' $prop method', methods,
function(method) {
function() {
angular.forEach([
{route: '/route1/:id', url: '/route1/Alpha', expectedParams: {id: 'Alpha'}},
{route: '/route2/:id', url: '/route2/Bravo/?', expectedParams: {id: 'Bravo'}},
@@ -2268,14 +2268,14 @@ describe('ngMock', function() {
], function(testDataEntry) {
callback.calls.reset();
var paramsSpy = jasmine.createSpy('params');
hb[routeShortcut](method, testDataEntry.route).respond(
hb[routeShortcut](this, testDataEntry.route).respond(
function(method, url, data, headers, params) {
paramsSpy(params);
// status, response, headers, statusText, xhrStatus
return [200, 'path', { 'x-header': 'foo' }, 'OK', 'complete'];
}
);
hb(method, testDataEntry.url, undefined, callback);
hb(this, testDataEntry.url, undefined, callback);
hb.flush();
expect(callback).toHaveBeenCalledOnceWith(200, 'path', 'x-header: foo', 'OK', 'complete');
expect(paramsSpy).toHaveBeenCalledOnceWith(testDataEntry.expectedParams);
-40
View File
@@ -77,45 +77,5 @@ describe('$routeParams', function() {
});
});
it('should correctly extract path params containing hashes and/or question marks', function() {
module(function($routeProvider) {
$routeProvider.when('/foo/:bar', {});
$routeProvider.when('/zoo/:bar/:baz/:qux', {});
});
inject(function($location, $rootScope, $routeParams) {
$location.path('/foo/bar?baz');
$rootScope.$digest();
expect($routeParams).toEqual({bar: 'bar?baz'});
$location.path('/foo/bar?baz=val');
$rootScope.$digest();
expect($routeParams).toEqual({bar: 'bar?baz=val'});
$location.path('/foo/bar#baz');
$rootScope.$digest();
expect($routeParams).toEqual({bar: 'bar#baz'});
$location.path('/foo/bar?baz#qux');
$rootScope.$digest();
expect($routeParams).toEqual({bar: 'bar?baz#qux'});
$location.path('/foo/bar?baz=val#qux');
$rootScope.$digest();
expect($routeParams).toEqual({bar: 'bar?baz=val#qux'});
$location.path('/foo/bar#baz?qux');
$rootScope.$digest();
expect($routeParams).toEqual({bar: 'bar#baz?qux'});
$location.path('/zoo/bar?p1=v1#h1/baz?p2=v2#h2/qux?p3=v3#h3');
$rootScope.$digest();
expect($routeParams).toEqual({
bar: 'bar?p1=v1#h1',
baz: 'baz?p2=v2#h2',
qux: 'qux?p3=v3#h3'
});
});
});
});
+3 -3
View File
@@ -1727,9 +1727,9 @@ detective@^4.0.0:
acorn "^3.1.0"
defined "^1.0.0"
dgeni-packages@^0.26.5:
version "0.26.5"
resolved "https://registry.yarnpkg.com/dgeni-packages/-/dgeni-packages-0.26.5.tgz#a76da27b40ce92dfc37a9e629ef9f1d3897f6bde"
dgeni-packages@^0.26.2:
version "0.26.2"
resolved "https://registry.yarnpkg.com/dgeni-packages/-/dgeni-packages-0.26.2.tgz#dac22d7e861d4d72ed42af5272714f42b6b5bf3d"
dependencies:
canonical-path "0.0.2"
catharsis "^0.8.1"