planning
All checks were successful
Publish To Prod / deploy_and_publish (push) Successful in 35s

This commit is contained in:
2024-10-14 09:15:30 +02:00
parent bcba00a730
commit 6e64e138e2
21059 changed files with 2317811 additions and 1 deletions

472
node_modules/decap-cms-backend-git-gateway/CHANGELOG.md generated vendored Normal file
View File

@@ -0,0 +1,472 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.2.2](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-git-gateway@3.2.1...decap-cms-backend-git-gateway@3.2.2) (2024-08-13)
### Reverts
- Revert "Update dependencies (#7264)" ([22d483a](https://github.com/decaporg/decap-cms/commit/22d483a5b0c654071ae05735ac4f49abdc13d38c)), closes [#7264](https://github.com/decaporg/decap-cms/issues/7264)
## [3.2.1](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-git-gateway@3.2.0...decap-cms-backend-git-gateway@3.2.1) (2024-08-13)
**Note:** Version bump only for package decap-cms-backend-git-gateway
# [3.2.0](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-git-gateway@3.1.1...decap-cms-backend-git-gateway@3.2.0) (2024-08-07)
**Note:** Version bump only for package decap-cms-backend-git-gateway
## [3.1.1](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-git-gateway@3.1.0-beta.1...decap-cms-backend-git-gateway@3.1.1) (2024-03-21)
**Note:** Version bump only for package decap-cms-backend-git-gateway
# [3.1.0](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-git-gateway@3.1.0-beta.1...decap-cms-backend-git-gateway@3.1.0) (2024-02-01)
**Note:** Version bump only for package decap-cms-backend-git-gateway
# [3.1.0-beta.1](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-git-gateway@3.1.0-beta.0...decap-cms-backend-git-gateway@3.1.0-beta.1) (2024-01-31)
**Note:** Version bump only for package decap-cms-backend-git-gateway
# [3.1.0-beta.0](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-git-gateway@3.1.0...decap-cms-backend-git-gateway@3.1.0-beta.0) (2023-10-20)
### Reverts
- Revert "chore(release): publish" ([b89fc89](https://github.com/decaporg/decap-cms/commit/b89fc894dfbb5f4136b2e5427fd25a29378a58c6))
## [3.0.3](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-git-gateway@3.0.2...decap-cms-backend-git-gateway@3.0.3) (2023-10-13)
**Note:** Version bump only for package decap-cms-backend-git-gateway
## [3.0.2](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-git-gateway@3.0.1...decap-cms-backend-git-gateway@3.0.2) (2023-09-06)
### Performance Improvements
- filter by path when loading collection from github backend ([#6898](https://github.com/decaporg/decap-cms/issues/6898)) ([18ef773](https://github.com/decaporg/decap-cms/commit/18ef773f35db1b7ef3ab5a0f25527d87745b9c73))
## [3.0.1](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-git-gateway@3.0.0...decap-cms-backend-git-gateway@3.0.1) (2023-08-25)
### Bug Fixes
- update peer dependencies ([#6886](https://github.com/decaporg/decap-cms/issues/6886)) ([e580ce5](https://github.com/decaporg/decap-cms/commit/e580ce52ce5f80fa040e8fbcab7fed0744f4f695))
# [3.0.0](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-git-gateway@2.14.0...decap-cms-backend-git-gateway@3.0.0) (2023-08-18)
**Note:** Version bump only for package decap-cms-backend-git-gateway
# [2.14.0](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-git-gateway@2.14.0-beta.0...decap-cms-backend-git-gateway@2.14.0) (2023-08-18)
**Note:** Version bump only for package decap-cms-backend-git-gateway
# 2.14.0-beta.0 (2023-08-18)
### Features
- rename packages ([#6863](https://github.com/decaporg/decap-cms/issues/6863)) ([d515e7b](https://github.com/decaporg/decap-cms/commit/d515e7bd33216a775d96887b08c4f7b1962941bb))
## [2.13.2-beta.0](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-git-gateway@2.13.1...decap-cms-backend-git-gateway@2.13.2-beta.0) (2023-07-27)
**Note:** Version bump only for package decap-cms-backend-git-gateway
## [2.13.1](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-git-gateway@2.13.0...decap-cms-backend-git-gateway@2.13.1) (2022-03-08)
**Note:** Version bump only for package decap-cms-backend-git-gateway
# [2.13.0](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-git-gateway@2.12.2...decap-cms-backend-git-gateway@2.13.0) (2021-12-28)
### Bug Fixes
- **decap-cms-ui-default:** use grayDark for button ([#6069](https://github.com/decaporg/decap-cms/issues/6069)) ([ad85514](https://github.com/decaporg/decap-cms/commit/ad85514cba607f066ab7071bee5932b2192466ee)), closes [/github.com/decaporg/decap-cms/issues/1333#issuecomment-998115794](https://github.com//github.com/decaporg/decap-cms/issues/1333/issues/issuecomment-998115794)
### Features
- **backend-gitlab:** initial GraphQL support ([#6059](https://github.com/decaporg/decap-cms/issues/6059)) ([1523a41](https://github.com/decaporg/decap-cms/commit/1523a4140a3d2f4cc01a1548514ae17bc1ad504e))
- disable 'Save' button when there are no changes ([#5595](https://github.com/decaporg/decap-cms/issues/5595)) ([4b566a7](https://github.com/decaporg/decap-cms/commit/4b566a78f4282a6f04caf3deafaaac4d74acfd63))
## [2.12.2](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.12.1...decap-cms-backend-git-gateway@2.12.2) (2021-06-01)
**Note:** Version bump only for package decap-cms-backend-git-gateway
## [2.12.1](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.12.0...decap-cms-backend-git-gateway@2.12.1) (2021-05-31)
**Note:** Version bump only for package decap-cms-backend-git-gateway
# [2.12.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.11.11...decap-cms-backend-git-gateway@2.12.0) (2021-05-04)
### Features
- added react 17 as peer dependency in packages ([#5316](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/5316)) ([9e42380](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/9e423805707321396eec137f5b732a5b07a0dd3f))
## [2.11.11](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.11.10...decap-cms-backend-git-gateway@2.11.11) (2021-02-25)
**Note:** Version bump only for package decap-cms-backend-git-gateway
## [2.11.10](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.11.9...decap-cms-backend-git-gateway@2.11.10) (2021-02-23)
**Note:** Version bump only for package decap-cms-backend-git-gateway
## [2.11.9](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.11.8...decap-cms-backend-git-gateway@2.11.9) (2021-02-10)
**Note:** Version bump only for package decap-cms-backend-git-gateway
## [2.11.8](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.11.7...decap-cms-backend-git-gateway@2.11.8) (2020-12-15)
### Bug Fixes
- **deps:** update dependency ini to v2 ([#4722](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/4722)) ([e14ace3](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/e14ace373b11510159a9b4d3f977d27ed886b288))
## [2.11.7](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.11.6...decap-cms-backend-git-gateway@2.11.7) (2020-12-06)
### Bug Fixes
- **large-media:** mark pointer files as binary ([#4678](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/4678)) ([7697b90](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/7697b907d7bae750f4ec041a184188aa46995320))
## [2.11.6](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.11.5...decap-cms-backend-git-gateway@2.11.6) (2020-10-12)
### Bug Fixes
- **deps:** update dependency jwt-decode to v3 ([#4408](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/4408)) ([03492e4](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/03492e4e684ffce3a541ef15edb591d1fd5b5854))
## [2.11.5](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.11.4...decap-cms-backend-git-gateway@2.11.5) (2020-09-20)
**Note:** Version bump only for package decap-cms-backend-git-gateway
## [2.11.4](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.11.3...decap-cms-backend-git-gateway@2.11.4) (2020-09-15)
**Note:** Version bump only for package decap-cms-backend-git-gateway
## 2.11.3 (2020-09-08)
### Reverts
- Revert "chore(release): publish" ([828bb16](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/828bb16415b8c22a34caa19c50c38b24ffe9ceae))
## 2.11.2 (2020-08-20)
### Reverts
- Revert "chore(release): publish" ([8262487](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/82624879ccbcb16610090041db28f00714d924c8))
## 2.11.1 (2020-07-27)
### Reverts
- Revert "chore(release): publish" ([118d50a](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/118d50a7a70295f25073e564b5161aa2b9883056))
# [2.11.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.10.5...decap-cms-backend-git-gateway@2.11.0) (2020-06-18)
### Bug Fixes
- handle token expiry ([#3847](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/3847)) ([285c940](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/285c940562548d7bc88de244123ba87ff66fba65))
### Features
- add backend status down indicator ([#3889](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/3889)) ([a50edc7](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/a50edc70553ad6afa1acee6a51996ad226443f8c))
- **backend-gitgateway:** improve deploy preview visibility ([#3882](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/3882)) ([afc9bf4](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/afc9bf4f3fe14ccb60851fc24e68922a6e4a85a9))
## [2.10.5](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.10.4...decap-cms-backend-git-gateway@2.10.5) (2020-05-19)
**Note:** Version bump only for package decap-cms-backend-git-gateway
## [2.10.4](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.10.3...decap-cms-backend-git-gateway@2.10.4) (2020-05-04)
### Bug Fixes
- **git-gateway:** wait for identity widget to initialize ([#3660](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/3660)) ([6c229c5](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/6c229c5149e3beff05bcfb42ca286d3e9170e54e))
## [2.10.3](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.10.2...decap-cms-backend-git-gateway@2.10.3) (2020-04-21)
### Bug Fixes
- **large-media:** match netlify.app as lfs host ([#3642](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/3642)) ([9b79623](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/9b79623bc8b8fe212fb2d15dec8a75328cde9c64))
## [2.10.2](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.10.1...decap-cms-backend-git-gateway@2.10.2) (2020-04-01)
**Note:** Version bump only for package decap-cms-backend-git-gateway
## [2.10.1](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.10.0...decap-cms-backend-git-gateway@2.10.1) (2020-03-30)
**Note:** Version bump only for package decap-cms-backend-git-gateway
# [2.10.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.9.1...decap-cms-backend-git-gateway@2.10.0) (2020-03-12)
### Features
- add media lib virtualization ([#3381](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/3381)) ([92e7601](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/92e76011e7a9e8b5370088b0a2c065df66b5f7fb))
- **backend-github:** add pagination ([#3379](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/3379)) ([39f1307](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/39f1307e3a36447da8c9b3ca79b1d7db52ea1a19))
## [2.9.1](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.9.0...decap-cms-backend-git-gateway@2.9.1) (2020-03-03)
### Bug Fixes
- **locale:** Remove hard coded string literals ([#3333](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/3333)) ([7c45a3c](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/7c45a3cda983be427864a56e58791565eb9232e2))
# [2.9.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.8.1...decap-cms-backend-git-gateway@2.9.0) (2020-02-25)
### Features
- **core:** align GitHub metadata handling with other backends ([#3316](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/3316)) ([7e0a8ad](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/7e0a8ad532012576dc5e40bd4e9d54522e307123)), closes [#3292](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/3292)
## [2.8.1](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.8.0...decap-cms-backend-git-gateway@2.8.1) (2020-02-22)
### Reverts
- Revert "feat(core): Align GitHub metadata handling with other backends (#3292)" ([5bdd3df](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/5bdd3df9ccbb5149c22d79987ebdcd6cab4b261f)), closes [#3292](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/3292)
# [2.8.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.7.2...decap-cms-backend-git-gateway@2.8.0) (2020-02-22)
### Features
- **core:** Align GitHub metadata handling with other backends ([#3292](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/3292)) ([8193b5a](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/8193b5ace89d6f14a6c756235a50b186a763b6b1))
## [2.7.2](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.7.1...decap-cms-backend-git-gateway@2.7.2) (2020-02-11)
### Bug Fixes
- stringify error message ([#3233](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/3233)) ([249bd7e](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/249bd7ec1ed2197106cbb01f8c05e1b8830aa5bc))
## [2.7.1](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.7.0...decap-cms-backend-git-gateway@2.7.1) (2020-01-24)
### Bug Fixes
- **backend-git-gateway:** re-write GitHub pagination links ([#3135](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/3135)) ([834f6b9](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/834f6b9e457f3738ce0f240ddd4cc160aff9e2f5))
# [2.7.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.6.2...decap-cms-backend-git-gateway@2.7.0) (2020-01-21)
### Bug Fixes
- **git-gateway-gitlab:** fix large media support for editorial workflow ([#3105](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/3105)) ([038803c](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/038803c9f249de386812652372c35c4c53935295))
### Features
- **backend-bitbucket:** Add Git-LFS support ([#3118](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/3118)) ([a48c02d](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/a48c02d852ca5e11055da3a14cefae8d17a68498))
## [2.6.2](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.6.1...decap-cms-backend-git-gateway@2.6.2) (2020-01-16)
### Bug Fixes
- don't fail on malformed pointer files ([#3095](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/3095)) ([9210843](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/92108431f0c3df3e99b5aa7f462006ec3fa7777e))
## [2.6.1](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.6.0...decap-cms-backend-git-gateway@2.6.1) (2020-01-14)
**Note:** Version bump only for package decap-cms-backend-git-gateway
# [2.6.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.6.0-beta.0...decap-cms-backend-git-gateway@2.6.0) (2020-01-07)
### Bug Fixes
- rebase open authoring branches ([#2975](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/2975)) ([8c175f6](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/8c175f6132fa18a13763cc563f7d3201c1e3580e))
### Features
- **backend-git-gateway:** handle identity disabled error message ([#3002](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/3002)) ([b5ffccd](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/b5ffccdac506db416c09aaebb38611783487c52a))
# [2.6.0-beta.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.5.1...decap-cms-backend-git-gateway@2.6.0-beta.0) (2019-12-18)
### Features
- bundle assets with content ([#2958](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/2958)) ([2b41d8a](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/2b41d8a838a9c8a6b21cde2ddd16b9288334e298))
## [2.5.1](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.5.0...decap-cms-backend-git-gateway@2.5.1) (2019-11-18)
### Bug Fixes
- **git-gateway:** unpublished entries not loaded for git-gateway(GitHub) ([#2856](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/2856)) ([4a2328b](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/4a2328b2f10ea678184391e4caf235b41323cd3e))
# [2.5.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.4.6...decap-cms-backend-git-gateway@2.5.0) (2019-11-07)
### Bug Fixes
- **backend-git-gateway:** omit /repos/ when no repo ([#2846](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/2846)) ([da2dab3](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/da2dab305ab7f0655791ef0fb5376e3d5e72897c))
### Features
- add go back to site button ([#2538](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/2538)) ([f206e7e](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/f206e7e5a13fb48ec6b27dce0dbb3a59b61de8f9))
- enable specifying custom open authoring commit message ([#2810](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/2810)) ([2841ff9](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/2841ff9ffe58afcf4dba45514a84a262ad370f1d))
## [2.4.6](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.4.5...decap-cms-backend-git-gateway@2.4.6) (2019-09-26)
### Bug Fixes
- **git-gateway:** pass api URL instead of constructing it from repo value ([#2631](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/2631)) ([922c0f3](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/922c0f3))
## [2.4.5](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.4.4...decap-cms-backend-git-gateway@2.4.5) (2019-07-24)
**Note:** Version bump only for package decap-cms-backend-git-gateway
## [2.4.4](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.4.3...decap-cms-backend-git-gateway@2.4.4) (2019-06-26)
**Note:** Version bump only for package decap-cms-backend-git-gateway
## [2.4.3](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.4.2...decap-cms-backend-git-gateway@2.4.3) (2019-06-18)
### Bug Fixes
- **core:** address new entries error for non-github backends ([#2390](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/2390)) ([a5bd6b3](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/a5bd6b3))
## [2.4.2](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.4.2-beta.0...decap-cms-backend-git-gateway@2.4.2) (2019-04-10)
**Note:** Version bump only for package decap-cms-backend-git-gateway
## [2.4.2-beta.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.4.1...decap-cms-backend-git-gateway@2.4.2-beta.0) (2019-04-05)
### Bug Fixes
- **backend-git-gateway:** fix image display w/o large media ([#2271](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/2271)) ([6c3506b](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/6c3506b))
## [2.4.1](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.4.1-beta.1...decap-cms-backend-git-gateway@2.4.1) (2019-03-29)
**Note:** Version bump only for package decap-cms-backend-git-gateway
## [2.4.1-beta.1](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.4.1-beta.0...decap-cms-backend-git-gateway@2.4.1-beta.1) (2019-03-26)
### Bug Fixes
- export on decap-cms and maps on esm ([#2244](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/2244)) ([6ffd13b](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/6ffd13b))
## [2.4.1-beta.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.4.0...decap-cms-backend-git-gateway@2.4.1-beta.0) (2019-03-25)
### Bug Fixes
- update peer dep versions ([#2234](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/2234)) ([7987091](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/7987091))
# [2.4.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.3.1-beta.0...decap-cms-backend-git-gateway@2.4.0) (2019-03-22)
### Features
- add ES module builds ([#2215](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/2215)) ([d142b32](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/d142b32))
## [2.3.1-beta.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.3.0...decap-cms-backend-git-gateway@2.3.1-beta.0) (2019-03-22)
### Bug Fixes
- **editorial-workflow:** fix LM pointers changing to binary files ([#2228](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/2228)) ([d39a361](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/d39a361))
# [2.3.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.3.0-beta.0...decap-cms-backend-git-gateway@2.3.0) (2019-03-22)
**Note:** Version bump only for package decap-cms-backend-git-gateway
# [2.3.0-beta.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.2.5-beta.0...decap-cms-backend-git-gateway@2.3.0-beta.0) (2019-03-21)
### Bug Fixes
- fix umd builds ([#2214](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/2214)) ([e04f6be](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/e04f6be))
### Features
- provide usable UMD builds for all packages ([#2141](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/2141)) ([82cc794](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/82cc794))
## [2.2.5-beta.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.2.4...decap-cms-backend-git-gateway@2.2.5-beta.0) (2019-03-15)
### Features
- upgrade to Emotion 10 ([#2166](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/2166)) ([ccef446](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/ccef446))
## [2.2.4](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.2.3...decap-cms-backend-git-gateway@2.2.4) (2019-03-11)
### Bug Fixes
- **backend-github:** make non-Large Media previews work with Git Gateway+GitHub ([#2151](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/2151)) ([63582dc](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/63582dc))
## [2.2.3](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.2.2...decap-cms-backend-git-gateway@2.2.3) (2019-03-08)
**Note:** Version bump only for package decap-cms-backend-git-gateway
## [2.2.2](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.2.1...decap-cms-backend-git-gateway@2.2.2) (2019-02-28)
### Bug Fixes
- **git-gateway:** fix previews for GitHub images not in Large Media ([#2125](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/2125)) ([d17f896](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/d17f896))
## [2.2.1](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.2.0...decap-cms-backend-git-gateway@2.2.1) (2019-02-26)
**Note:** Version bump only for package decap-cms-backend-git-gateway
# [2.2.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.1.2...decap-cms-backend-git-gateway@2.2.0) (2019-02-08)
### Features
- **workflow:** add deploy preview links ([#2028](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/2028)) ([15d221d](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/15d221d))
## [2.1.2](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.1.1...decap-cms-backend-git-gateway@2.1.2) (2018-12-11)
### Bug Fixes
- **decap-cms-backend-git-gateway:** content-type may have charset ([#1951](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/1951)) ([c74dbae](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/c74dbae))
## [2.1.1](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.1.0...decap-cms-backend-git-gateway@2.1.1) (2018-11-29)
### Bug Fixes
- **backend-git-gateway:** double slashes when gateway_url contained a backend ([#1712](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/1712)) ([6de47cd](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/6de47cd))
# [2.1.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.0.8...decap-cms-backend-git-gateway@2.1.0) (2018-11-12)
### Bug Fixes
- **identity:** switch user name reference to full_name ([#1809](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/1809)) ([55d45a8](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/55d45a8))
### Features
- allow custom logo on auth page ([#1818](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/1818)) ([c6ae1e8](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/c6ae1e8))
<a name="2.0.8"></a>
## [2.0.8](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.0.7...decap-cms-backend-git-gateway@2.0.8) (2018-09-06)
**Note:** Version bump only for package decap-cms-backend-git-gateway
<a name="2.0.7"></a>
## [2.0.7](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.0.6...decap-cms-backend-git-gateway@2.0.7) (2018-08-27)
**Note:** Version bump only for package decap-cms-backend-git-gateway
<a name="2.0.6"></a>
## [2.0.6](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.0.5...decap-cms-backend-git-gateway@2.0.6) (2018-08-24)
**Note:** Version bump only for package decap-cms-backend-git-gateway
<a name="2.0.5"></a>
## [2.0.5](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.0.4...decap-cms-backend-git-gateway@2.0.5) (2018-08-07)
### Bug Fixes
- **workflow:** fix workflow entries not appearing ([#1581](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/1581)) ([95c8de0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/95c8de0))
<a name="2.0.4"></a>
## [2.0.4](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.0.3...decap-cms-backend-git-gateway@2.0.4) (2018-08-01)
**Note:** Version bump only for package decap-cms-backend-git-gateway
<a name="2.0.3"></a>
## [2.0.3](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/compare/decap-cms-backend-git-gateway@2.0.2...decap-cms-backend-git-gateway@2.0.3) (2018-07-28)
### Bug Fixes
- **git-gateway:** correct `proxied` value for proxied backends ([#1540](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/issues/1540)) ([f7dba87](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway/commit/f7dba87))
<a name="2.0.2"></a>
## 2.0.2 (2018-07-27)
### Bug Fixes
- **git-gateway:** pass options through git-gateway backend ([#1532](https://github.com/decaporg/decap-cms/issues/1532)) ([4c5436a](https://github.com/decaporg/decap-cms/commit/4c5436a))
<a name="2.0.1"></a>
## 2.0.1 (2018-07-26)
<a name="2.0.0"></a>
# 2.0.0 (2018-07-26)
### Bug Fixes
- **bitbucket:** fix rebasing mistakes in bitbucket backend and deps ([#1522](https://github.com/decaporg/decap-cms/issues/1522)) ([bdfd944](https://github.com/decaporg/decap-cms/commit/bdfd944))

22
node_modules/decap-cms-backend-git-gateway/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,22 @@
Copyright (c) 2016 Netlify <decap@p-m.si>
MIT License
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.

26
node_modules/decap-cms-backend-git-gateway/README.md generated vendored Normal file
View File

@@ -0,0 +1,26 @@
# Git Gateway
Netlify's [gateway](https://github.com/netlify/git-gateway) to hosted git APIs.
## Code structure
`Implementation` for [File Management System API](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-lib-util/README.md) based on `Api`.
`Api` and `Implementation` from backend-[github](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/README.md)/[gitlab](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-gitlab/README.md)/[bitbacket](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-bitbacket/README.md) extended with Netlify-specific `LargeMedia(LFS)` and `JWT` auth.
`AuthenticationPage` - uses [lib-auth](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-lib-auth/README.md) and implements Netlify Identity authentication flow.
Look at tests or types for more info.
## Debugging
When debugging the CMS with Git Gateway you must:
1. Have a Netlify site with [Git Gateway](https://docs.netlify.com/visitor-access/git-gateway/) and [Netlify Identity](https://docs.netlify.com/visitor-access/identity/) enabled. An easy way to create such a site is to use a [template](https://www.decapcms.org/docs/start-with-a-template/), for example the [Gatsby template](https://app.netlify.com/start/deploy?repository=https://github.com/decaporg/gatsby-starter-decap-cms&stack=cms)
2. Tell the CMS the URL of your Netlify site using a local storage item. To do so:
1. Open `http://localhost:8080/` in the browser
2. Write the below command and press enter: `localStorage.setItem('netlifySiteURL', 'https://yourwebsiteurl.netlify.app/')`
3. To be sure, you can run this command as well: `localStorage.getItem('netlifySiteURL')`
4. Refresh the page
5. You should be able to log in via your Netlify Identity email/password

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,101 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _decapCmsBackendGithub = require("decap-cms-backend-github");
var _decapCmsLibUtil = require("decap-cms-lib-util");
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : String(i); }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
class API extends _decapCmsBackendGithub.API {
constructor(config) {
super(_objectSpread({
getUser: () => Promise.reject('Never used')
}, config));
_defineProperty(this, "tokenPromise", void 0);
_defineProperty(this, "commitAuthor", void 0);
_defineProperty(this, "isLargeMedia", void 0);
this.apiRoot = config.apiRoot;
this.tokenPromise = config.tokenPromise;
this.commitAuthor = config.commitAuthor;
this.isLargeMedia = config.isLargeMedia;
this.repoURL = '';
this.originRepoURL = '';
}
hasWriteAccess() {
return this.getDefaultBranch().then(() => true).catch(error => {
if (error.status === 401) {
if (error.message === 'Bad credentials') {
throw new _decapCmsLibUtil.APIError('Git Gateway Error: Please ask your site administrator to reissue the Git Gateway token.', error.status, 'Git Gateway');
} else {
return false;
}
} else if (error.status === 404 && (error.message === undefined || error.message === 'Unable to locate site configuration')) {
throw new _decapCmsLibUtil.APIError(`Git Gateway Error: Please make sure Git Gateway is enabled on your site.`, error.status, 'Git Gateway');
} else {
console.error('Problem fetching repo data from Git Gateway');
throw error;
}
});
}
requestHeaders(headers = {}) {
return this.tokenPromise().then(jwtToken => {
const baseHeader = _objectSpread({
Authorization: `Bearer ${jwtToken}`,
'Content-Type': 'application/json; charset=utf-8'
}, headers);
return baseHeader;
});
}
handleRequestError(error, responseStatus) {
throw new _decapCmsLibUtil.APIError(error.message || error.msg, responseStatus, 'Git Gateway');
}
user() {
return Promise.resolve(_objectSpread({
login: ''
}, this.commitAuthor));
}
async getHeadReference(head) {
if (!this.repoOwner) {
// get the repo owner from the branch url
// this is required for returning the full head reference, e.g. owner:head
// when filtering pull requests based on the head
const branch = await this.getDefaultBranch();
const self = branch._links.self;
const regex = new RegExp('https?://.+?/repos/(.+?)/');
const owner = self.match(regex);
this.repoOwner = owner ? owner[1] : '';
}
return super.getHeadReference(head);
}
commit(message, changeTree) {
const commitParams = {
message,
tree: changeTree.sha,
parents: changeTree.parentSha ? [changeTree.parentSha] : []
};
if (this.commitAuthor) {
commitParams.author = _objectSpread(_objectSpread({}, this.commitAuthor), {}, {
date: new Date().toISOString()
});
}
return this.request('/git/commits', {
method: 'POST',
body: JSON.stringify(commitParams)
});
}
nextUrlProcessor() {
return url => url.replace(/^(?:[a-z]+:\/\/.+?\/.+?\/.+?\/)/, `${this.apiRoot}/`);
}
async diffFromFile(file) {
const diff = await super.diffFromFile(file);
return _objectSpread(_objectSpread({}, diff), {}, {
binary: diff.binary || (await this.isLargeMedia(file.filename))
});
}
}
exports.default = API;

View File

@@ -0,0 +1,28 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _decapCmsBackendGitlab = require("decap-cms-backend-gitlab");
var _decapCmsLibUtil = require("decap-cms-lib-util");
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : String(i); }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
class API extends _decapCmsBackendGitlab.API {
constructor(config) {
super(config);
_defineProperty(this, "tokenPromise", void 0);
_defineProperty(this, "withAuthorizationHeaders", async req => {
const token = await this.tokenPromise();
return _decapCmsLibUtil.unsentRequest.withHeaders({
Authorization: `Bearer ${token}`
}, req);
});
_defineProperty(this, "hasWriteAccess", () => Promise.resolve(true));
this.tokenPromise = config.tokenPromise;
this.commitAuthor = config.commitAuthor;
this.repoURL = '';
}
}
exports.default = API;

View File

@@ -0,0 +1,589 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _intersection2 = _interopRequireDefault(require("lodash/intersection"));
var _pick2 = _interopRequireDefault(require("lodash/pick"));
var _get2 = _interopRequireDefault(require("lodash/get"));
var _gotrueJs = _interopRequireDefault(require("gotrue-js"));
var _jwtDecode = _interopRequireDefault(require("jwt-decode"));
var _ini = _interopRequireDefault(require("ini"));
var _decapCmsLibUtil = require("decap-cms-lib-util");
var _decapCmsBackendGithub = require("decap-cms-backend-github");
var _decapCmsBackendGitlab = require("decap-cms-backend-gitlab");
var _decapCmsBackendBitbucket = require("decap-cms-backend-bitbucket");
var _GitHubAPI = _interopRequireDefault(require("./GitHubAPI"));
var _GitLabAPI = _interopRequireDefault(require("./GitLabAPI"));
var _AuthenticationPage = _interopRequireDefault(require("./AuthenticationPage"));
var _netlifyLfsClient = require("./netlify-lfs-client");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : String(i); }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
const STATUS_PAGE = 'https://www.netlifystatus.com';
const GIT_GATEWAY_STATUS_ENDPOINT = `${STATUS_PAGE}/api/v2/components.json`;
const GIT_GATEWAY_OPERATIONAL_UNITS = ['Git Gateway'];
const localHosts = {
localhost: true,
'127.0.0.1': true,
'0.0.0.0': true
};
const defaults = {
identity: '/.netlify/identity',
gateway: '/.netlify/git',
largeMedia: '/.netlify/large-media'
};
function getEndpoint(endpoint, netlifySiteURL) {
if (localHosts[document.location.host.split(':').shift()] && netlifySiteURL && endpoint.match(/^\/\.netlify\//)) {
const parts = [];
if (netlifySiteURL) {
parts.push(netlifySiteURL);
if (!netlifySiteURL.match(/\/$/)) {
parts.push('/');
}
}
parts.push(endpoint.replace(/^\//, ''));
return parts.join('');
}
return endpoint;
}
// wait for identity widget to initialize
// force init on timeout
let initPromise = Promise.resolve();
if (window.netlifyIdentity) {
let initialized = false;
initPromise = Promise.race([new Promise(resolve => {
var _window$netlifyIdenti;
(_window$netlifyIdenti = window.netlifyIdentity) === null || _window$netlifyIdenti === void 0 ? void 0 : _window$netlifyIdenti.on('init', () => {
initialized = true;
resolve();
});
}), new Promise(resolve => setTimeout(resolve, 2500)).then(() => {
if (!initialized) {
var _window$netlifyIdenti2;
console.log('Manually initializing identity widget');
(_window$netlifyIdenti2 = window.netlifyIdentity) === null || _window$netlifyIdenti2 === void 0 ? void 0 : _window$netlifyIdenti2.init();
}
})]);
}
async function apiGet(path) {
const apiRoot = 'https://api.netlify.com/api/v1/sites';
const response = await fetch(`${apiRoot}/${path}`).then(res => res.json());
return response;
}
class GitGateway {
constructor(config, options = {}) {
var _config$backend$branc;
_defineProperty(this, "config", void 0);
_defineProperty(this, "api", void 0);
_defineProperty(this, "branch", void 0);
_defineProperty(this, "squashMerges", void 0);
_defineProperty(this, "cmsLabelPrefix", void 0);
_defineProperty(this, "mediaFolder", void 0);
_defineProperty(this, "transformImages", void 0);
_defineProperty(this, "gatewayUrl", void 0);
_defineProperty(this, "netlifyLargeMediaURL", void 0);
_defineProperty(this, "backendType", void 0);
_defineProperty(this, "apiUrl", void 0);
_defineProperty(this, "authClient", void 0);
_defineProperty(this, "backend", void 0);
_defineProperty(this, "acceptRoles", void 0);
_defineProperty(this, "tokenPromise", void 0);
_defineProperty(this, "_largeMediaClientPromise", void 0);
_defineProperty(this, "options", void 0);
_defineProperty(this, "requestFunction", req => this.tokenPromise().then(token => _decapCmsLibUtil.unsentRequest.withHeaders({
Authorization: `Bearer ${token}`
}, req)).then(_decapCmsLibUtil.unsentRequest.performRequest));
this.options = _objectSpread({
proxied: true,
API: null,
initialWorkflowStatus: ''
}, options);
this.config = config;
this.branch = ((_config$backend$branc = config.backend.branch) === null || _config$backend$branc === void 0 ? void 0 : _config$backend$branc.trim()) || 'master';
this.squashMerges = config.backend.squash_merges || false;
this.cmsLabelPrefix = config.backend.cms_label_prefix || '';
this.mediaFolder = config.media_folder;
const {
use_large_media_transforms_in_media_library: transformImages = true
} = config.backend;
this.transformImages = transformImages;
const netlifySiteURL = localStorage.getItem('netlifySiteURL');
this.apiUrl = getEndpoint(config.backend.identity_url || defaults.identity, netlifySiteURL);
this.gatewayUrl = getEndpoint(config.backend.gateway_url || defaults.gateway, netlifySiteURL);
this.netlifyLargeMediaURL = getEndpoint(config.backend.large_media_url || defaults.largeMedia, netlifySiteURL);
const backendTypeRegex = /\/(github|gitlab|bitbucket)\/?$/;
const backendTypeMatches = this.gatewayUrl.match(backendTypeRegex);
if (backendTypeMatches) {
this.backendType = backendTypeMatches[1];
this.gatewayUrl = this.gatewayUrl.replace(backendTypeRegex, '');
} else {
this.backendType = null;
}
this.backend = null;
_AuthenticationPage.default.authClient = () => this.getAuthClient();
}
isGitBackend() {
return true;
}
async status() {
const api = await fetch(GIT_GATEWAY_STATUS_ENDPOINT).then(res => res.json()).then(res => {
return res['components'].filter(statusComponent => GIT_GATEWAY_OPERATIONAL_UNITS.includes(statusComponent.name)).every(statusComponent => statusComponent.status === 'operational');
}).catch(e => {
console.warn('Failed getting Git Gateway status', e);
return true;
});
let auth = false;
// no need to check auth if api is down
if (api) {
var _this$tokenPromise;
auth = (await ((_this$tokenPromise = this.tokenPromise) === null || _this$tokenPromise === void 0 ? void 0 : _this$tokenPromise.call(this).then(token => !!token).catch(e => {
console.warn('Failed getting Identity token', e);
return false;
}))) || false;
}
return {
auth: {
status: auth
},
api: {
status: api,
statusPage: STATUS_PAGE
}
};
}
async getAuthClient() {
if (this.authClient) {
return this.authClient;
}
await initPromise;
if (window.netlifyIdentity) {
this.authClient = {
logout: () => {
var _window$netlifyIdenti3;
return (_window$netlifyIdenti3 = window.netlifyIdentity) === null || _window$netlifyIdenti3 === void 0 ? void 0 : _window$netlifyIdenti3.logout();
},
currentUser: () => {
var _window$netlifyIdenti4;
return (_window$netlifyIdenti4 = window.netlifyIdentity) === null || _window$netlifyIdenti4 === void 0 ? void 0 : _window$netlifyIdenti4.currentUser();
},
clearStore: () => {
var _window$netlifyIdenti5;
const store = (_window$netlifyIdenti5 = window.netlifyIdentity) === null || _window$netlifyIdenti5 === void 0 ? void 0 : _window$netlifyIdenti5.store;
if (store) {
store.user = null;
store.modal.page = 'login';
store.saving = false;
}
}
};
} else {
const goTrue = new _gotrueJs.default({
APIUrl: this.apiUrl
});
this.authClient = {
logout: () => {
const user = goTrue.currentUser();
if (user) {
return user.logout();
}
},
currentUser: () => goTrue.currentUser(),
login: goTrue.login.bind(goTrue),
clearStore: () => undefined
};
}
return this.authClient;
}
authenticate(credentials) {
const user = credentials;
this.tokenPromise = async () => {
try {
const func = user.jwt.bind(user);
const token = await func();
return token;
} catch (error) {
throw new _decapCmsLibUtil.AccessTokenError(`Failed getting access token: ${error.message}`);
}
};
return this.tokenPromise().then(async token => {
if (!this.backendType) {
const {
github_enabled: githubEnabled,
gitlab_enabled: gitlabEnabled,
bitbucket_enabled: bitbucketEnabled,
roles
} = await _decapCmsLibUtil.unsentRequest.fetchWithTimeout(`${this.gatewayUrl}/settings`, {
headers: {
Authorization: `Bearer ${token}`
}
}).then(async res => {
const contentType = res.headers.get('Content-Type') || '';
if (!contentType.includes('application/json') && !contentType.includes('text/json')) {
throw new _decapCmsLibUtil.APIError(`Your Git Gateway backend is not returning valid settings. Please make sure it is enabled.`, res.status, 'Git Gateway');
}
const body = await res.json();
if (!res.ok) {
throw new _decapCmsLibUtil.APIError(`Git Gateway Error: ${body.message ? body.message : body}`, res.status, 'Git Gateway');
}
return body;
});
this.acceptRoles = roles;
if (githubEnabled) {
this.backendType = 'github';
} else if (gitlabEnabled) {
this.backendType = 'gitlab';
} else if (bitbucketEnabled) {
this.backendType = 'bitbucket';
}
}
if (this.acceptRoles && this.acceptRoles.length > 0) {
const userRoles = (0, _get2.default)((0, _jwtDecode.default)(token), 'app_metadata.roles', []);
const validRole = (0, _intersection2.default)(userRoles, this.acceptRoles).length > 0;
if (!validRole) {
throw new Error("You don't have sufficient permissions to access Decap CMS");
}
}
const userData = {
name: user.user_metadata.full_name || user.email.split('@').shift(),
email: user.email,
avatar_url: user.user_metadata.avatar_url,
metadata: user.user_metadata
};
const apiConfig = {
apiRoot: `${this.gatewayUrl}/${this.backendType}`,
branch: this.branch,
tokenPromise: this.tokenPromise,
commitAuthor: (0, _pick2.default)(userData, ['name', 'email']),
isLargeMedia: filename => this.isLargeMediaFile(filename),
squashMerges: this.squashMerges,
cmsLabelPrefix: this.cmsLabelPrefix,
initialWorkflowStatus: this.options.initialWorkflowStatus
};
if (this.backendType === 'github') {
this.api = new _GitHubAPI.default(apiConfig);
this.backend = new _decapCmsBackendGithub.GitHubBackend(this.config, _objectSpread(_objectSpread({}, this.options), {}, {
API: this.api
}));
} else if (this.backendType === 'gitlab') {
this.api = new _GitLabAPI.default(apiConfig);
this.backend = new _decapCmsBackendGitlab.GitLabBackend(this.config, _objectSpread(_objectSpread({}, this.options), {}, {
API: this.api
}));
} else if (this.backendType === 'bitbucket') {
this.api = new _decapCmsBackendBitbucket.API(_objectSpread(_objectSpread({}, apiConfig), {}, {
requestFunction: this.requestFunction,
hasWriteAccess: async () => true
}));
this.backend = new _decapCmsBackendBitbucket.BitbucketBackend(this.config, _objectSpread(_objectSpread({}, this.options), {}, {
API: this.api
}));
}
if (!(await this.api.hasWriteAccess())) {
throw new Error("You don't have sufficient permissions to access Decap CMS");
}
return {
name: userData.name,
login: userData.email,
avatar_url: userData.avatar_url
};
});
}
async restoreUser() {
const client = await this.getAuthClient();
const user = client.currentUser();
if (!user) return Promise.reject();
return this.authenticate(user);
}
authComponent() {
return _AuthenticationPage.default;
}
async logout() {
const client = await this.getAuthClient();
try {
client.logout();
} catch (e) {
// due to a bug in the identity widget (gotrue-js actually) the store is not reset if logout fails
// TODO: remove after https://github.com/netlify/gotrue-js/pull/83 is merged
client.clearStore();
}
}
getToken() {
return this.tokenPromise();
}
async entriesByFolder(folder, extension, depth) {
return this.backend.entriesByFolder(folder, extension, depth);
}
allEntriesByFolder(folder, extension, depth, pathRegex) {
return this.backend.allEntriesByFolder(folder, extension, depth, pathRegex);
}
entriesByFiles(files) {
return this.backend.entriesByFiles(files);
}
getEntry(path) {
return this.backend.getEntry(path);
}
async unpublishedEntryDataFile(collection, slug, path, id) {
return this.backend.unpublishedEntryDataFile(collection, slug, path, id);
}
async isLargeMediaFile(path) {
const client = await this.getLargeMediaClient();
return client.enabled && client.matchPath(path);
}
async unpublishedEntryMediaFile(collection, slug, path, id) {
const isLargeMedia = await this.isLargeMediaFile(path);
if (isLargeMedia) {
const branch = this.backend.getBranch(collection, slug);
const {
url,
blob
} = await this.getLargeMediaDisplayURL({
path,
id
}, branch);
return {
id,
name: (0, _decapCmsLibUtil.basename)(path),
path,
url,
displayURL: url,
file: new File([blob], (0, _decapCmsLibUtil.basename)(path)),
size: blob.size
};
} else {
return this.backend.unpublishedEntryMediaFile(collection, slug, path, id);
}
}
getMedia(mediaFolder = this.mediaFolder) {
return this.backend.getMedia(mediaFolder);
}
// this method memoizes this._getLargeMediaClient so that there can
// only be one client at a time
getLargeMediaClient() {
if (this._largeMediaClientPromise) {
return this._largeMediaClientPromise;
}
this._largeMediaClientPromise = this._getLargeMediaClient();
return this._largeMediaClientPromise;
}
_getLargeMediaClient() {
const netlifyLargeMediaEnabledPromise = this.api.readFile('.lfsconfig').then(config => _ini.default.decode(config)).then(({
lfs: {
url
}
}) => new URL(url)).then(lfsURL => ({
enabled: lfsURL.hostname.endsWith('netlify.com') || lfsURL.hostname.endsWith('netlify.app')
})).catch(err => ({
enabled: false,
err
}));
const lfsPatternsPromise = this.api.readFile('.gitattributes').then(attributes => (0, _decapCmsLibUtil.getLargeMediaPatternsFromGitAttributesFile)(attributes)).then(patterns => ({
err: null,
patterns
})).catch(err => {
if (err.message.includes('404')) {
console.log('This 404 was expected and handled appropriately.');
return {
err: null,
patterns: []
};
} else {
return {
err,
patterns: []
};
}
});
return Promise.all([netlifyLargeMediaEnabledPromise, lfsPatternsPromise]).then(([{
enabled: maybeEnabled
}, {
patterns,
err: patternsErr
}]) => {
const enabled = maybeEnabled && !patternsErr;
// We expect LFS patterns to exist when the .lfsconfig states
// that we're using Netlify Large Media
if (maybeEnabled && patternsErr) {
console.error(patternsErr);
}
return (0, _netlifyLfsClient.getClient)({
enabled,
rootURL: this.netlifyLargeMediaURL,
makeAuthorizedRequest: this.requestFunction,
patterns,
transformImages: this.transformImages ? {
nf_resize: 'fit',
w: 560,
h: 320
} : false
});
});
}
async getLargeMediaDisplayURL({
path,
id
}, branch = this.branch) {
const readFile = (path, id, {
parseText
}) => this.api.readFile(path, id, {
branch,
parseText
});
const items = await (0, _decapCmsLibUtil.entriesByFiles)([{
path,
id
}], readFile, this.api.readFileMetadata.bind(this.api), 'Git-Gateway');
const entry = items[0];
const pointerFile = (0, _decapCmsLibUtil.parsePointerFile)(entry.data);
if (!pointerFile.sha) {
console.warn(`Failed parsing pointer file ${path}`);
return {
url: path,
blob: new Blob()
};
}
const client = await this.getLargeMediaClient();
const {
url,
blob
} = await client.getDownloadURL(pointerFile);
return {
url,
blob
};
}
async getMediaDisplayURL(displayURL) {
const {
path,
id
} = displayURL;
const isLargeMedia = await this.isLargeMediaFile(path);
if (isLargeMedia) {
const {
url
} = await this.getLargeMediaDisplayURL({
path,
id
});
return url;
}
if (typeof displayURL === 'string') {
return displayURL;
}
const url = await this.backend.getMediaDisplayURL(displayURL);
return url;
}
async getMediaFile(path) {
const isLargeMedia = await this.isLargeMediaFile(path);
if (isLargeMedia) {
const {
url,
blob
} = await this.getLargeMediaDisplayURL({
path,
id: null
});
return {
id: url,
name: (0, _decapCmsLibUtil.basename)(path),
path,
url,
displayURL: url,
file: new File([blob], (0, _decapCmsLibUtil.basename)(path)),
size: blob.size
};
}
return this.backend.getMediaFile(path);
}
async persistEntry(entry, options) {
const client = await this.getLargeMediaClient();
if (client.enabled) {
const assets = await (0, _decapCmsLibUtil.getLargeMediaFilteredMediaFiles)(client, entry.assets);
return this.backend.persistEntry(_objectSpread(_objectSpread({}, entry), {}, {
assets
}), options);
} else {
return this.backend.persistEntry(entry, options);
}
}
async persistMedia(mediaFile, options) {
const {
fileObj,
path
} = mediaFile;
const displayURL = fileObj ? URL.createObjectURL(fileObj) : '';
const client = await this.getLargeMediaClient();
const fixedPath = path.startsWith('/') ? path.slice(1) : path;
const isLargeMedia = await this.isLargeMediaFile(fixedPath);
if (isLargeMedia) {
const persistMediaArgument = await (0, _decapCmsLibUtil.getPointerFileForMediaFileObj)(client, fileObj, path);
return _objectSpread(_objectSpread({}, await this.backend.persistMedia(persistMediaArgument, options)), {}, {
displayURL
});
}
return await this.backend.persistMedia(mediaFile, options);
}
deleteFiles(paths, commitMessage) {
return this.backend.deleteFiles(paths, commitMessage);
}
async getDeployPreview(collection, slug) {
let preview = await this.backend.getDeployPreview(collection, slug);
if (!preview) {
try {
// if the commit doesn't have a status, try to use Netlify API directly
// this is useful when builds are queue up in Netlify and don't have a commit status yet
// and only works with public logs at the moment
// TODO: get Netlify API Token and use it to access private logs
const siteId = new URL(localStorage.getItem('netlifySiteURL') || '').hostname;
const site = await apiGet(siteId);
const deploys = await apiGet(`${site.id}/deploys?per_page=100`);
if (deploys.length > 0) {
const ref = await this.api.getUnpublishedEntrySha(collection, slug);
const deploy = deploys.find(d => d.commit_ref === ref);
if (deploy) {
preview = {
status: deploy.state === 'ready' ? _decapCmsLibUtil.PreviewState.Success : _decapCmsLibUtil.PreviewState.Other,
url: deploy.deploy_url
};
}
}
// eslint-disable-next-line no-empty
} catch (e) {}
}
return preview;
}
unpublishedEntries() {
return this.backend.unpublishedEntries();
}
unpublishedEntry({
id,
collection,
slug
}) {
return this.backend.unpublishedEntry({
id,
collection,
slug
});
}
updateUnpublishedEntryStatus(collection, slug, newStatus) {
return this.backend.updateUnpublishedEntryStatus(collection, slug, newStatus);
}
deleteUnpublishedEntry(collection, slug) {
return this.backend.deleteUnpublishedEntry(collection, slug);
}
publishUnpublishedEntry(collection, slug) {
return this.backend.publishUnpublishedEntry(collection, slug);
}
traverseCursor(cursor, action) {
return this.backend.traverseCursor(cursor, action);
}
}
exports.default = GitGateway;

View File

@@ -0,0 +1,25 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "AuthenticationPage", {
enumerable: true,
get: function () {
return _AuthenticationPage.default;
}
});
exports.DecapCmsBackendGitGateway = void 0;
Object.defineProperty(exports, "GitGatewayBackend", {
enumerable: true,
get: function () {
return _implementation.default;
}
});
var _implementation = _interopRequireDefault(require("./implementation"));
var _AuthenticationPage = _interopRequireDefault(require("./AuthenticationPage"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const DecapCmsBackendGitGateway = exports.DecapCmsBackendGitGateway = {
GitGatewayBackend: _implementation.default,
AuthenticationPage: _AuthenticationPage.default
};

View File

@@ -0,0 +1,186 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getClient = getClient;
exports.matchPath = matchPath;
var _isEmpty2 = _interopRequireDefault(require("lodash/isEmpty"));
var _isPlainObject2 = _interopRequireDefault(require("lodash/isPlainObject"));
var _map2 = _interopRequireDefault(require("lodash/fp/map"));
var _fromPairs2 = _interopRequireDefault(require("lodash/fp/fromPairs"));
var _flow2 = _interopRequireDefault(require("lodash/fp/flow"));
var _minimatch = _interopRequireDefault(require("minimatch"));
var _decapCmsLibUtil = require("decap-cms-lib-util");
const _excluded = ["sha"];
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : String(i); }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
function matchPath({
patterns
}, path) {
return patterns.some(pattern => (0, _minimatch.default)(path, pattern, {
matchBase: true
}));
}
//
// API interactions
const defaultContentHeaders = {
Accept: 'application/vnd.git-lfs+json',
['Content-Type']: 'application/vnd.git-lfs+json'
};
async function resourceExists({
rootURL,
makeAuthorizedRequest
}, {
sha,
size
}) {
const response = await makeAuthorizedRequest({
url: `${rootURL}/verify`,
method: 'POST',
headers: defaultContentHeaders,
body: JSON.stringify({
oid: sha,
size
})
});
if (response.ok) {
return true;
}
if (response.status === 404) {
return false;
}
// TODO: what kind of error to throw here? APIError doesn't seem
// to fit
}
function getTransofrmationsParams(t) {
if ((0, _isPlainObject2.default)(t) && !(0, _isEmpty2.default)(t)) {
const {
nf_resize: resize,
w,
h
} = t;
return `?nf_resize=${resize}&w=${w}&h=${h}`;
}
return '';
}
async function getDownloadURL({
rootURL,
transformImages: t,
makeAuthorizedRequest
}, {
sha
}) {
try {
const transformation = getTransofrmationsParams(t);
const transformedPromise = makeAuthorizedRequest(`${rootURL}/origin/${sha}${transformation}`);
const [transformed, original] = await Promise.all([transformedPromise,
// if transformation is defined, we need to load the original so we have the correct meta data
transformation ? makeAuthorizedRequest(`${rootURL}/origin/${sha}`) : transformedPromise]);
if (!transformed.ok) {
const error = await transformed.json();
throw new Error(`Failed getting large media for sha '${sha}': '${error.code} - ${error.msg}'`);
}
const transformedBlob = await transformed.blob();
const url = URL.createObjectURL(transformedBlob);
return {
url,
blob: transformation ? await original.blob() : transformedBlob
};
} catch (error) {
console.error(error);
return {
url: '',
blob: new Blob()
};
}
}
function uploadOperation(objects) {
return {
operation: 'upload',
transfers: ['basic'],
objects: objects.map(_ref => {
let {
sha
} = _ref,
rest = _objectWithoutProperties(_ref, _excluded);
return _objectSpread(_objectSpread({}, rest), {}, {
oid: sha
});
})
};
}
async function getResourceUploadURLs({
rootURL,
makeAuthorizedRequest
}, pointerFiles) {
const response = await makeAuthorizedRequest({
url: `${rootURL}/objects/batch`,
method: 'POST',
headers: defaultContentHeaders,
body: JSON.stringify(uploadOperation(pointerFiles))
});
const {
objects
} = await response.json();
const uploadUrls = objects.map(object => {
if (object.error) {
throw new Error(object.error.message);
}
return object.actions.upload.href;
});
return uploadUrls;
}
function uploadBlob(uploadURL, blob) {
return _decapCmsLibUtil.unsentRequest.fetchWithTimeout(uploadURL, {
method: 'PUT',
body: blob
});
}
async function uploadResource(clientConfig, {
sha,
size
}, resource) {
const existingFile = await resourceExists(clientConfig, {
sha,
size
});
if (existingFile) {
return sha;
}
const [uploadURL] = await getResourceUploadURLs(clientConfig, [{
sha,
size
}]);
await uploadBlob(uploadURL, resource);
return sha;
}
//
// Create Large Media client
function configureFn(config, fn) {
return (...args) => fn(config, ...args);
}
const clientFns = {
resourceExists,
getResourceUploadURLs,
getDownloadURL,
uploadResource,
matchPath
};
function getClient(clientConfig) {
return (0, _flow2.default)([Object.keys, (0, _map2.default)(key => [key, configureFn(clientConfig, clientFns[key])]), _fromPairs2.default, configuredFns => _objectSpread(_objectSpread({}, configuredFns), {}, {
patterns: clientConfig.patterns,
enabled: clientConfig.enabled
})])(clientFns);
}

View File

@@ -0,0 +1 @@
"use strict";

View File

@@ -0,0 +1,45 @@
{
"name": "decap-cms-backend-git-gateway",
"description": "Git Gateway backend for Decap CMS",
"version": "3.2.2",
"repository": "https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-git-gateway",
"bugs": "https://github.com/decaporg/decap-cms/issues",
"module": "dist/esm/index.js",
"main": "dist/decap-cms-backend-git-gateway.js",
"license": "MIT",
"keywords": [
"decap-cms",
"backend",
"git-gateway",
"gateway"
],
"sideEffects": false,
"scripts": {
"develop": "npm run build:esm -- --watch",
"build": "cross-env NODE_ENV=production webpack",
"build:esm": "cross-env NODE_ENV=esm babel src --out-dir dist/esm --ignore \"**/__tests__\" --root-mode upward --extensions \".js,.jsx,.ts,.tsx\""
},
"dependencies": {
"gotrue-js": "^0.9.24",
"ini": "^2.0.0",
"jwt-decode": "^3.0.0",
"minimatch": "^3.0.4"
},
"peerDependencies": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"decap-cms-backend-bitbucket": "^3.0.0",
"decap-cms-backend-github": "^3.0.0",
"decap-cms-backend-gitlab": "^3.0.0",
"decap-cms-lib-auth": "^3.0.0",
"decap-cms-lib-util": "^3.0.0",
"decap-cms-ui-default": "^3.0.0",
"lodash": "^4.17.11",
"prop-types": "^15.7.2",
"react": "^18.2.0"
},
"devDependencies": {
"@types/minimatch": "^5.1.2"
},
"gitHead": "64d91b8bb3d0a93dd36c53800cdac4ba2e435000"
}

View File

@@ -0,0 +1,227 @@
import PropTypes from 'prop-types';
import React from 'react';
import styled from '@emotion/styled';
import { partial } from 'lodash';
import {
AuthenticationPage,
buttons,
shadows,
colors,
colorsRaw,
lengths,
zIndex,
} from 'decap-cms-ui-default';
const LoginButton = styled.button`
${buttons.button};
${shadows.dropDeep};
${buttons.default};
${buttons.gray};
padding: 0 30px;
display: block;
margin-top: 20px;
margin-left: auto;
`;
const AuthForm = styled.form`
width: 350px;
margin-top: -80px;
`;
const AuthInput = styled.input`
background-color: ${colorsRaw.white};
border-radius: ${lengths.borderRadius};
font-size: 14px;
padding: 10px;
margin-bottom: 15px;
margin-top: 6px;
width: 100%;
position: relative;
z-index: ${zIndex.zIndex1};
&:focus {
outline: none;
box-shadow: inset 0 0 0 2px ${colors.active};
}
`;
const ErrorMessage = styled.p`
color: ${colors.errorText};
`;
let component = null;
if (window.netlifyIdentity) {
window.netlifyIdentity.on('login', user => {
component && component.handleIdentityLogin(user);
});
window.netlifyIdentity.on('logout', () => {
component && component.handleIdentityLogout();
});
window.netlifyIdentity.on('error', err => {
component && component.handleIdentityError(err);
});
}
export default class GitGatewayAuthenticationPage extends React.Component {
static authClient;
constructor(props) {
super(props);
component = this;
}
componentDidMount() {
if (!this.loggedIn && window.netlifyIdentity && window.netlifyIdentity.currentUser()) {
this.props.onLogin(window.netlifyIdentity.currentUser());
window.netlifyIdentity.close();
}
}
componentWillUnmount() {
component = null;
}
handleIdentityLogin = user => {
this.props.onLogin(user);
window.netlifyIdentity.close();
};
handleIdentityLogout = () => {
window.netlifyIdentity.open();
};
handleIdentityError = err => {
if (err?.message?.match(/^Failed to load settings from.+\.netlify\/identity$/)) {
window.netlifyIdentity.close();
this.setState({
errors: { identity: this.props.t('auth.errors.identitySettings') },
});
}
};
handleIdentity = () => {
const user = window.netlifyIdentity.currentUser();
if (user) {
this.props.onLogin(user);
} else {
window.netlifyIdentity.open();
}
};
static propTypes = {
onLogin: PropTypes.func.isRequired,
inProgress: PropTypes.bool.isRequired,
error: PropTypes.node,
config: PropTypes.object.isRequired,
t: PropTypes.func.isRequired,
};
state = { email: '', password: '', errors: {} };
handleChange = (name, e) => {
this.setState({ ...this.state, [name]: e.target.value });
};
handleLogin = async e => {
e.preventDefault();
const { email, password } = this.state;
const { t } = this.props;
const errors = {};
if (!email) {
errors.email = t('auth.errors.email');
}
if (!password) {
errors.password = t('auth.errors.password');
}
if (Object.keys(errors).length > 0) {
this.setState({ errors });
return;
}
try {
const client = await GitGatewayAuthenticationPage.authClient();
const user = await client.login(this.state.email, this.state.password, true);
this.props.onLogin(user);
} catch (error) {
this.setState({
errors: { server: error.description || error.msg || error },
loggingIn: false,
});
}
};
render() {
const { errors } = this.state;
const { error, inProgress, config, t } = this.props;
if (window.netlifyIdentity) {
if (errors.identity) {
return (
<AuthenticationPage
logoUrl={config.logo_url}
siteUrl={config.site_url}
onLogin={this.handleIdentity}
renderPageContent={() => (
<a
href="https://docs.netlify.com/visitor-access/git-gateway/#setup-and-settings"
target="_blank"
rel="noopener noreferrer"
>
{errors.identity}
</a>
)}
t={t}
/>
);
} else {
return (
<AuthenticationPage
logoUrl={config.logo_url}
siteUrl={config.site_url}
onLogin={this.handleIdentity}
renderButtonContent={() => t('auth.loginWithNetlifyIdentity')}
t={t}
/>
);
}
}
return (
<AuthenticationPage
logoUrl={config.logo_url}
siteUrl={config.site_url}
renderPageContent={() => (
<AuthForm onSubmit={this.handleLogin}>
{!error ? null : <ErrorMessage>{error}</ErrorMessage>}
{!errors.server ? null : <ErrorMessage>{String(errors.server)}</ErrorMessage>}
<ErrorMessage>{errors.email || null}</ErrorMessage>
<AuthInput
type="text"
name="email"
placeholder="Email"
value={this.state.email}
onChange={partial(this.handleChange, 'email')}
/>
<ErrorMessage>{errors.password || null}</ErrorMessage>
<AuthInput
type="password"
name="password"
placeholder="Password"
value={this.state.password}
onChange={partial(this.handleChange, 'password')}
/>
<LoginButton disabled={inProgress}>
{inProgress ? t('auth.loggingIn') : t('auth.login')}
</LoginButton>
</AuthForm>
)}
t={t}
/>
);
}
}

View File

@@ -0,0 +1,133 @@
import { API as GithubAPI } from 'decap-cms-backend-github';
import { APIError } from 'decap-cms-lib-util';
import type { Config as GitHubConfig, Diff } from 'decap-cms-backend-github/src/API';
import type { FetchError } from 'decap-cms-lib-util';
import type { Octokit } from '@octokit/rest';
type Config = Omit<GitHubConfig, 'getUser'> & {
apiRoot: string;
tokenPromise: () => Promise<string>;
commitAuthor: { name: string };
isLargeMedia: (filename: string) => Promise<boolean>;
};
export default class API extends GithubAPI {
tokenPromise: () => Promise<string>;
commitAuthor: { name: string };
isLargeMedia: (filename: string) => Promise<boolean>;
constructor(config: Config) {
super({
getUser: () => Promise.reject('Never used'),
...config,
});
this.apiRoot = config.apiRoot;
this.tokenPromise = config.tokenPromise;
this.commitAuthor = config.commitAuthor;
this.isLargeMedia = config.isLargeMedia;
this.repoURL = '';
this.originRepoURL = '';
}
hasWriteAccess() {
return this.getDefaultBranch()
.then(() => true)
.catch((error: FetchError) => {
if (error.status === 401) {
if (error.message === 'Bad credentials') {
throw new APIError(
'Git Gateway Error: Please ask your site administrator to reissue the Git Gateway token.',
error.status,
'Git Gateway',
);
} else {
return false;
}
} else if (
error.status === 404 &&
(error.message === undefined || error.message === 'Unable to locate site configuration')
) {
throw new APIError(
`Git Gateway Error: Please make sure Git Gateway is enabled on your site.`,
error.status,
'Git Gateway',
);
} else {
console.error('Problem fetching repo data from Git Gateway');
throw error;
}
});
}
requestHeaders(headers = {}) {
return this.tokenPromise().then(jwtToken => {
const baseHeader = {
Authorization: `Bearer ${jwtToken}`,
'Content-Type': 'application/json; charset=utf-8',
...headers,
};
return baseHeader;
});
}
handleRequestError(error: FetchError & { msg: string }, responseStatus: number) {
throw new APIError(error.message || error.msg, responseStatus, 'Git Gateway');
}
user() {
return Promise.resolve({ login: '', ...this.commitAuthor });
}
async getHeadReference(head: string) {
if (!this.repoOwner) {
// get the repo owner from the branch url
// this is required for returning the full head reference, e.g. owner:head
// when filtering pull requests based on the head
const branch = await this.getDefaultBranch();
const self = branch._links.self;
const regex = new RegExp('https?://.+?/repos/(.+?)/');
const owner = self.match(regex);
this.repoOwner = owner ? owner[1] : '';
}
return super.getHeadReference(head);
}
commit(message: string, changeTree: { parentSha?: string; sha: string }) {
const commitParams: {
message: string;
tree: string;
parents: string[];
author?: { name: string; date: string };
} = {
message,
tree: changeTree.sha,
parents: changeTree.parentSha ? [changeTree.parentSha] : [],
};
if (this.commitAuthor) {
commitParams.author = {
...this.commitAuthor,
date: new Date().toISOString(),
};
}
return this.request('/git/commits', {
method: 'POST',
body: JSON.stringify(commitParams),
});
}
nextUrlProcessor() {
return (url: string) => url.replace(/^(?:[a-z]+:\/\/.+?\/.+?\/.+?\/)/, `${this.apiRoot}/`);
}
async diffFromFile(file: Octokit.ReposCompareCommitsResponseFilesItem): Promise<Diff> {
const diff = await super.diffFromFile(file);
return {
...diff,
binary: diff.binary || (await this.isLargeMedia(file.filename)),
};
}
}

View File

@@ -0,0 +1,30 @@
import { API as GitlabAPI } from 'decap-cms-backend-gitlab';
import { unsentRequest } from 'decap-cms-lib-util';
import type { Config as GitLabConfig, CommitAuthor } from 'decap-cms-backend-gitlab/src/API';
import type { ApiRequest } from 'decap-cms-lib-util';
type Config = GitLabConfig & { tokenPromise: () => Promise<string>; commitAuthor: CommitAuthor };
export default class API extends GitlabAPI {
tokenPromise: () => Promise<string>;
constructor(config: Config) {
super(config);
this.tokenPromise = config.tokenPromise;
this.commitAuthor = config.commitAuthor;
this.repoURL = '';
}
withAuthorizationHeaders = async (req: ApiRequest) => {
const token = await this.tokenPromise();
return unsentRequest.withHeaders(
{
Authorization: `Bearer ${token}`,
},
req,
);
};
hasWriteAccess = () => Promise.resolve(true);
}

View File

@@ -0,0 +1,54 @@
import React from 'react';
import { render } from '@testing-library/react';
import GitGatewayAuthenticationPage from '../AuthenticationPage';
window.netlifyIdentity = {
currentUser: jest.fn(),
on: jest.fn(),
close: jest.fn(),
};
describe('GitGatewayAuthenticationPage', () => {
const props = {
config: { logo_url: 'logo_url' },
t: jest.fn(key => key),
onLogin: jest.fn(),
inProgress: false,
};
beforeEach(() => {
jest.clearAllMocks();
jest.resetModules();
});
it('should render with identity error', () => {
// obtain mock calls
require('../AuthenticationPage');
function TestComponent() {
const { asFragment } = render(<GitGatewayAuthenticationPage {...props} />);
const errorCallback = window.netlifyIdentity.on.mock.calls.find(
call => call[0] === 'error',
)[1];
errorCallback(
new Error('Failed to load settings from https://site.netlify.com/.netlify/identity'),
);
expect(asFragment()).toMatchSnapshot();
}
TestComponent();
});
test('should render with no identity error', () => {
function TestComponent() {
const { asFragment } = render(<GitGatewayAuthenticationPage {...props} />);
expect(asFragment()).toMatchSnapshot();
}
TestComponent();
});
});

View File

@@ -0,0 +1,97 @@
import API from '../GitHubAPI';
describe('github API', () => {
describe('request', () => {
beforeEach(() => {
const fetch = jest.fn();
global.fetch = fetch;
});
afterEach(() => {
jest.resetAllMocks();
});
it('should fetch url with authorization header', async () => {
const api = new API({
apiRoot: 'https://site.netlify.com/.netlify/git/github',
tokenPromise: () => Promise.resolve('token'),
});
fetch.mockResolvedValue({
text: jest.fn().mockResolvedValue('some response'),
ok: true,
status: 200,
headers: { get: () => '' },
});
const result = await api.request('/some-path');
expect(result).toEqual('some response');
expect(fetch).toHaveBeenCalledTimes(1);
expect(fetch).toHaveBeenCalledWith('https://site.netlify.com/.netlify/git/github/some-path', {
cache: 'no-cache',
headers: {
Authorization: 'Bearer token',
'Content-Type': 'application/json; charset=utf-8',
},
signal: expect.any(AbortSignal),
});
});
it('should throw error on not ok response with message property', async () => {
const api = new API({
apiRoot: 'https://site.netlify.com/.netlify/git/github',
tokenPromise: () => Promise.resolve('token'),
});
fetch.mockResolvedValue({
text: jest.fn().mockResolvedValue({ message: 'some error' }),
ok: false,
status: 404,
headers: { get: () => '' },
});
await expect(api.request('some-path')).rejects.toThrow(
expect.objectContaining({
message: 'some error',
name: 'API_ERROR',
status: 404,
api: 'Git Gateway',
}),
);
});
it('should throw error on not ok response with msg property', async () => {
const api = new API({
apiRoot: 'https://site.netlify.com/.netlify/git/github',
tokenPromise: () => Promise.resolve('token'),
});
fetch.mockResolvedValue({
text: jest.fn().mockResolvedValue({ msg: 'some error' }),
ok: false,
status: 404,
headers: { get: () => '' },
});
await expect(api.request('some-path')).rejects.toThrow(
expect.objectContaining({
message: 'some error',
name: 'API_ERROR',
status: 404,
api: 'Git Gateway',
}),
);
});
});
describe('nextUrlProcessor', () => {
it('should re-write github url', () => {
const api = new API({
apiRoot: 'https://site.netlify.com/.netlify/git/github',
});
expect(api.nextUrlProcessor()('https://api.github.com/repositories/10000/pulls')).toEqual(
'https://site.netlify.com/.netlify/git/github/pulls',
);
});
});
});

View File

@@ -0,0 +1,277 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`GitGatewayAuthenticationPage should render with identity error 1`] = `
<DocumentFragment>
.emotion-0 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-flex-flow: column nowrap;
-webkit-flex-flow: column nowrap;
-ms-flex-flow: column nowrap;
flex-flow: column nowrap;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-ms-flex-pack: center;
-webkit-justify-content: center;
justify-content: center;
gap: 50px;
height: 100vh;
}
.emotion-2 {
width: 300px;
height: auto;
}
.emotion-4 {
border: 0;
border-radius: 5px;
cursor: pointer;
box-shadow: 0 4px 12px 0 rgba(68, 74, 87, 0.15),0 1px 3px 0 rgba(68, 74, 87, 0.25);
height: 36px;
line-height: 36px;
font-weight: 500;
padding: 0 15px;
background-color: #798291;
color: #fff;
background-color: #313d3e;
color: #fff;
padding: 0 12px;
margin-top: 0;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
position: relative;
}
.emotion-4:focus,
.emotion-4:hover {
color: #fff;
background-color: #555a65;
}
.emotion-4[disabled] {
background-color: #eff0f4;
color: #798291;
cursor: default;
}
.emotion-7 {
display: inline-block;
line-height: 0;
width: 100px;
height: 100px;
-webkit-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-ms-transform: rotate(0deg);
transform: rotate(0deg);
color: #c4c6d2;
position: absolute;
bottom: 10px;
}
.emotion-7 path:not(.no-fill),
.emotion-7 circle:not(.no-fill),
.emotion-7 polygon:not(.no-fill),
.emotion-7 rect:not(.no-fill) {
fill: currentColor;
}
.emotion-7 path.clipped {
fill: transparent;
}
.emotion-7 svg {
width: 100%;
height: 100%;
}
<section
class="emotion-0 emotion-1"
>
<span
class="emotion-2 emotion-3"
>
<img
alt="Logo"
src="logo_url"
/>
</span>
<button
class="emotion-4 emotion-5"
>
auth.loginWithNetlifyIdentity
</button>
<span
class="emotion-6 emotion-7 emotion-8"
>
<svg
fill="none"
height="90"
viewBox="0 0 335 90"
width="335"
xmlns="http://www.w3.org/2000/svg"
>
<path
class="no-fill"
d="M30.73.15 0 2.95l3.67 40.21 20.03-1.83-1.99-21.86 10.71-.98c10.61-.97 19.14 7.53 20.29 20.19l19.67-1.79C70.34 14.72 51.64-1.75 30.73.15ZM73.61 49.51c0 12.72-7.73 21.95-18.37 21.95H44.49V49.47H24.38v40.42h30.86c21.02 0 38.12-18.11 38.12-40.38v-.04H73.61v.04Z"
fill="#FF0082"
/>
<path
class="no-fill"
d="M131.65 23.71h20.01c14.41 0 24.29 9.09 24.29 23.06 0 13.97-9.88 23.06-24.29 23.06h-20.01V23.71Zm19.51 37.35c8.75 0 14.47-5.47 14.47-14.29s-5.73-14.29-14.47-14.29h-9.31v28.59h9.31v-.01ZM207.61 58.69l5.22 5.93c-3.15 3.75-7.87 5.73-13.97 5.73-11.7 0-19.32-7.71-19.32-18.25s7.68-18.25 18.12-18.25c9.56 0 17.43 6.59 17.49 17.92l-25.04 5.07c1.45 3.49 4.59 5.27 9 5.27 3.59 0 6.17-1.12 8.5-3.43v.01Zm-18.44-7.64 16.49-3.36c-.94-3.62-3.9-6.06-7.99-6.06-4.91 0-8.31 3.43-8.5 9.42ZM218.25 52.1c0-10.67 7.87-18.25 18.88-18.25 7.11 0 12.71 3.23 15.17 9.02l-7.61 4.28c-1.83-3.36-4.53-4.87-7.61-4.87-4.97 0-8.87 3.62-8.87 9.81s3.9 9.81 8.87 9.81c3.08 0 5.79-1.45 7.61-4.87l7.61 4.35c-2.45 5.67-8.05 8.96-15.17 8.96-11.01 0-18.88-7.58-18.88-18.25v.01ZM290.93 34.38v35.44h-9.38v-4.08c-2.45 3.1-6.04 4.61-10.57 4.61-9.57 0-16.93-7.11-16.93-18.25s7.36-18.25 16.93-18.25c4.15 0 7.68 1.38 10.13 4.28v-3.75h9.82ZM281.3 52.1c0-6.13-3.78-9.81-8.62-9.81S264 45.98 264 52.1c0 6.12 3.78 9.81 8.68 9.81s8.62-3.69 8.62-9.81ZM334.54 52.1c0 11.13-7.36 18.25-16.86 18.25-4.22 0-7.68-1.38-10.19-4.28V82.6h-9.82V34.38h9.38v4.08c2.45-3.1 6.1-4.61 10.63-4.61 9.5 0 16.86 7.11 16.86 18.25Zm-9.94 0c0-6.13-3.71-9.81-8.62-9.81-4.91 0-8.62 3.69-8.62 9.81 0 6.12 3.71 9.81 8.62 9.81 4.91 0 8.62-3.69 8.62-9.81Z"
fill="#000"
/>
</svg>
</span>
</section>
</DocumentFragment>
`;
exports[`GitGatewayAuthenticationPage should render with no identity error 1`] = `
<DocumentFragment>
.emotion-0 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-flex-flow: column nowrap;
-webkit-flex-flow: column nowrap;
-ms-flex-flow: column nowrap;
flex-flow: column nowrap;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-ms-flex-pack: center;
-webkit-justify-content: center;
justify-content: center;
gap: 50px;
height: 100vh;
}
.emotion-2 {
width: 300px;
height: auto;
}
.emotion-4 {
border: 0;
border-radius: 5px;
cursor: pointer;
box-shadow: 0 4px 12px 0 rgba(68, 74, 87, 0.15),0 1px 3px 0 rgba(68, 74, 87, 0.25);
height: 36px;
line-height: 36px;
font-weight: 500;
padding: 0 15px;
background-color: #798291;
color: #fff;
background-color: #313d3e;
color: #fff;
padding: 0 12px;
margin-top: 0;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
position: relative;
}
.emotion-4:focus,
.emotion-4:hover {
color: #fff;
background-color: #555a65;
}
.emotion-4[disabled] {
background-color: #eff0f4;
color: #798291;
cursor: default;
}
.emotion-7 {
display: inline-block;
line-height: 0;
width: 100px;
height: 100px;
-webkit-transform: rotate(0deg);
-moz-transform: rotate(0deg);
-ms-transform: rotate(0deg);
transform: rotate(0deg);
color: #c4c6d2;
position: absolute;
bottom: 10px;
}
.emotion-7 path:not(.no-fill),
.emotion-7 circle:not(.no-fill),
.emotion-7 polygon:not(.no-fill),
.emotion-7 rect:not(.no-fill) {
fill: currentColor;
}
.emotion-7 path.clipped {
fill: transparent;
}
.emotion-7 svg {
width: 100%;
height: 100%;
}
<section
class="emotion-0 emotion-1"
>
<span
class="emotion-2 emotion-3"
>
<img
alt="Logo"
src="logo_url"
/>
</span>
<button
class="emotion-4 emotion-5"
>
auth.loginWithNetlifyIdentity
</button>
<span
class="emotion-6 emotion-7 emotion-8"
>
<svg
fill="none"
height="90"
viewBox="0 0 335 90"
width="335"
xmlns="http://www.w3.org/2000/svg"
>
<path
class="no-fill"
d="M30.73.15 0 2.95l3.67 40.21 20.03-1.83-1.99-21.86 10.71-.98c10.61-.97 19.14 7.53 20.29 20.19l19.67-1.79C70.34 14.72 51.64-1.75 30.73.15ZM73.61 49.51c0 12.72-7.73 21.95-18.37 21.95H44.49V49.47H24.38v40.42h30.86c21.02 0 38.12-18.11 38.12-40.38v-.04H73.61v.04Z"
fill="#FF0082"
/>
<path
class="no-fill"
d="M131.65 23.71h20.01c14.41 0 24.29 9.09 24.29 23.06 0 13.97-9.88 23.06-24.29 23.06h-20.01V23.71Zm19.51 37.35c8.75 0 14.47-5.47 14.47-14.29s-5.73-14.29-14.47-14.29h-9.31v28.59h9.31v-.01ZM207.61 58.69l5.22 5.93c-3.15 3.75-7.87 5.73-13.97 5.73-11.7 0-19.32-7.71-19.32-18.25s7.68-18.25 18.12-18.25c9.56 0 17.43 6.59 17.49 17.92l-25.04 5.07c1.45 3.49 4.59 5.27 9 5.27 3.59 0 6.17-1.12 8.5-3.43v.01Zm-18.44-7.64 16.49-3.36c-.94-3.62-3.9-6.06-7.99-6.06-4.91 0-8.31 3.43-8.5 9.42ZM218.25 52.1c0-10.67 7.87-18.25 18.88-18.25 7.11 0 12.71 3.23 15.17 9.02l-7.61 4.28c-1.83-3.36-4.53-4.87-7.61-4.87-4.97 0-8.87 3.62-8.87 9.81s3.9 9.81 8.87 9.81c3.08 0 5.79-1.45 7.61-4.87l7.61 4.35c-2.45 5.67-8.05 8.96-15.17 8.96-11.01 0-18.88-7.58-18.88-18.25v.01ZM290.93 34.38v35.44h-9.38v-4.08c-2.45 3.1-6.04 4.61-10.57 4.61-9.57 0-16.93-7.11-16.93-18.25s7.36-18.25 16.93-18.25c4.15 0 7.68 1.38 10.13 4.28v-3.75h9.82ZM281.3 52.1c0-6.13-3.78-9.81-8.62-9.81S264 45.98 264 52.1c0 6.12 3.78 9.81 8.68 9.81s8.62-3.69 8.62-9.81ZM334.54 52.1c0 11.13-7.36 18.25-16.86 18.25-4.22 0-7.68-1.38-10.19-4.28V82.6h-9.82V34.38h9.38v4.08c2.45-3.1 6.1-4.61 10.63-4.61 9.5 0 16.86 7.11 16.86 18.25Zm-9.94 0c0-6.13-3.71-9.81-8.62-9.81-4.91 0-8.62 3.69-8.62 9.81 0 6.12 3.71 9.81 8.62 9.81 4.91 0 8.62-3.69 8.62-9.81Z"
fill="#000"
/>
</svg>
</span>
</section>
</DocumentFragment>
`;

View File

@@ -0,0 +1,629 @@
import GoTrue from 'gotrue-js';
import jwtDecode from 'jwt-decode';
import { get, pick, intersection } from 'lodash';
import ini from 'ini';
import {
APIError,
unsentRequest,
basename,
entriesByFiles,
parsePointerFile,
getLargeMediaPatternsFromGitAttributesFile,
getPointerFileForMediaFileObj,
getLargeMediaFilteredMediaFiles,
AccessTokenError,
PreviewState,
} from 'decap-cms-lib-util';
import { GitHubBackend } from 'decap-cms-backend-github';
import { GitLabBackend } from 'decap-cms-backend-gitlab';
import { BitbucketBackend, API as BitBucketAPI } from 'decap-cms-backend-bitbucket';
import GitHubAPI from './GitHubAPI';
import GitLabAPI from './GitLabAPI';
import AuthenticationPage from './AuthenticationPage';
import { getClient } from './netlify-lfs-client';
import type { Client } from './netlify-lfs-client';
import type {
ApiRequest,
AssetProxy,
PersistOptions,
Entry,
Cursor,
Implementation,
DisplayURL,
User,
Credentials,
Config,
ImplementationFile,
DisplayURLObject,
} from 'decap-cms-lib-util';
const STATUS_PAGE = 'https://www.netlifystatus.com';
const GIT_GATEWAY_STATUS_ENDPOINT = `${STATUS_PAGE}/api/v2/components.json`;
const GIT_GATEWAY_OPERATIONAL_UNITS = ['Git Gateway'];
type GitGatewayStatus = {
id: string;
name: string;
status: string;
};
type NetlifyIdentity = {
logout: () => void;
currentUser: () => User;
on: (event: string, args: unknown) => void;
init: () => void;
store: { user: unknown; modal: { page: string }; saving: boolean };
};
type AuthClient = {
logout: () => void;
currentUser: () => unknown;
login?(email: string, password: string, remember?: boolean): Promise<unknown>;
clearStore: () => void;
};
declare global {
interface Window {
netlifyIdentity?: NetlifyIdentity;
}
}
const localHosts: Record<string, boolean> = {
localhost: true,
'127.0.0.1': true,
'0.0.0.0': true,
};
const defaults = {
identity: '/.netlify/identity',
gateway: '/.netlify/git',
largeMedia: '/.netlify/large-media',
};
function getEndpoint(endpoint: string, netlifySiteURL: string | null) {
if (
localHosts[document.location.host.split(':').shift() as string] &&
netlifySiteURL &&
endpoint.match(/^\/\.netlify\//)
) {
const parts = [];
if (netlifySiteURL) {
parts.push(netlifySiteURL);
if (!netlifySiteURL.match(/\/$/)) {
parts.push('/');
}
}
parts.push(endpoint.replace(/^\//, ''));
return parts.join('');
}
return endpoint;
}
// wait for identity widget to initialize
// force init on timeout
let initPromise = Promise.resolve() as Promise<unknown>;
if (window.netlifyIdentity) {
let initialized = false;
initPromise = Promise.race([
new Promise<void>(resolve => {
window.netlifyIdentity?.on('init', () => {
initialized = true;
resolve();
});
}),
new Promise(resolve => setTimeout(resolve, 2500)).then(() => {
if (!initialized) {
console.log('Manually initializing identity widget');
window.netlifyIdentity?.init();
}
}),
]);
}
interface NetlifyUser extends Credentials {
jwt: () => Promise<string>;
email: string;
user_metadata: { full_name: string; avatar_url: string };
}
async function apiGet(path: string) {
const apiRoot = 'https://api.netlify.com/api/v1/sites';
const response = await fetch(`${apiRoot}/${path}`).then(res => res.json());
return response;
}
export default class GitGateway implements Implementation {
config: Config;
api?: GitHubAPI | GitLabAPI | BitBucketAPI;
branch: string;
squashMerges: boolean;
cmsLabelPrefix: string;
mediaFolder: string;
transformImages: boolean;
gatewayUrl: string;
netlifyLargeMediaURL: string;
backendType: string | null;
apiUrl: string;
authClient?: AuthClient;
backend: GitHubBackend | GitLabBackend | BitbucketBackend | null;
acceptRoles?: string[];
tokenPromise?: () => Promise<string>;
_largeMediaClientPromise?: Promise<Client>;
options: {
proxied: boolean;
API: GitHubAPI | GitLabAPI | BitBucketAPI | null;
initialWorkflowStatus: string;
};
constructor(config: Config, options = {}) {
this.options = {
proxied: true,
API: null,
initialWorkflowStatus: '',
...options,
};
this.config = config;
this.branch = config.backend.branch?.trim() || 'master';
this.squashMerges = config.backend.squash_merges || false;
this.cmsLabelPrefix = config.backend.cms_label_prefix || '';
this.mediaFolder = config.media_folder;
const { use_large_media_transforms_in_media_library: transformImages = true } = config.backend;
this.transformImages = transformImages;
const netlifySiteURL = localStorage.getItem('netlifySiteURL');
this.apiUrl = getEndpoint(config.backend.identity_url || defaults.identity, netlifySiteURL);
this.gatewayUrl = getEndpoint(config.backend.gateway_url || defaults.gateway, netlifySiteURL);
this.netlifyLargeMediaURL = getEndpoint(
config.backend.large_media_url || defaults.largeMedia,
netlifySiteURL,
);
const backendTypeRegex = /\/(github|gitlab|bitbucket)\/?$/;
const backendTypeMatches = this.gatewayUrl.match(backendTypeRegex);
if (backendTypeMatches) {
this.backendType = backendTypeMatches[1];
this.gatewayUrl = this.gatewayUrl.replace(backendTypeRegex, '');
} else {
this.backendType = null;
}
this.backend = null;
AuthenticationPage.authClient = () => this.getAuthClient();
}
isGitBackend() {
return true;
}
async status() {
const api = await fetch(GIT_GATEWAY_STATUS_ENDPOINT)
.then(res => res.json())
.then(res => {
return res['components']
.filter((statusComponent: GitGatewayStatus) =>
GIT_GATEWAY_OPERATIONAL_UNITS.includes(statusComponent.name),
)
.every((statusComponent: GitGatewayStatus) => statusComponent.status === 'operational');
})
.catch(e => {
console.warn('Failed getting Git Gateway status', e);
return true;
});
let auth = false;
// no need to check auth if api is down
if (api) {
auth =
(await this.tokenPromise?.()
.then(token => !!token)
.catch(e => {
console.warn('Failed getting Identity token', e);
return false;
})) || false;
}
return { auth: { status: auth }, api: { status: api, statusPage: STATUS_PAGE } };
}
async getAuthClient() {
if (this.authClient) {
return this.authClient;
}
await initPromise;
if (window.netlifyIdentity) {
this.authClient = {
logout: () => window.netlifyIdentity?.logout(),
currentUser: () => window.netlifyIdentity?.currentUser(),
clearStore: () => {
const store = window.netlifyIdentity?.store;
if (store) {
store.user = null;
store.modal.page = 'login';
store.saving = false;
}
},
};
} else {
const goTrue = new GoTrue({ APIUrl: this.apiUrl });
this.authClient = {
logout: () => {
const user = goTrue.currentUser();
if (user) {
return user.logout();
}
},
currentUser: () => goTrue.currentUser(),
login: goTrue.login.bind(goTrue),
clearStore: () => undefined,
};
}
return this.authClient;
}
requestFunction = (req: ApiRequest) =>
this.tokenPromise!()
.then(
token => unsentRequest.withHeaders({ Authorization: `Bearer ${token}` }, req) as ApiRequest,
)
.then(unsentRequest.performRequest);
authenticate(credentials: Credentials) {
const user = credentials as NetlifyUser;
this.tokenPromise = async () => {
try {
const func = user.jwt.bind(user);
const token = await func();
return token;
} catch (error) {
throw new AccessTokenError(`Failed getting access token: ${error.message}`);
}
};
return this.tokenPromise!().then(async token => {
if (!this.backendType) {
const {
github_enabled: githubEnabled,
gitlab_enabled: gitlabEnabled,
bitbucket_enabled: bitbucketEnabled,
roles,
} = await unsentRequest
.fetchWithTimeout(`${this.gatewayUrl}/settings`, {
headers: { Authorization: `Bearer ${token}` },
})
.then(async res => {
const contentType = res.headers.get('Content-Type') || '';
if (!contentType.includes('application/json') && !contentType.includes('text/json')) {
throw new APIError(
`Your Git Gateway backend is not returning valid settings. Please make sure it is enabled.`,
res.status,
'Git Gateway',
);
}
const body = await res.json();
if (!res.ok) {
throw new APIError(
`Git Gateway Error: ${body.message ? body.message : body}`,
res.status,
'Git Gateway',
);
}
return body;
});
this.acceptRoles = roles;
if (githubEnabled) {
this.backendType = 'github';
} else if (gitlabEnabled) {
this.backendType = 'gitlab';
} else if (bitbucketEnabled) {
this.backendType = 'bitbucket';
}
}
if (this.acceptRoles && this.acceptRoles.length > 0) {
const userRoles = get(jwtDecode(token), 'app_metadata.roles', []);
const validRole = intersection(userRoles, this.acceptRoles).length > 0;
if (!validRole) {
throw new Error("You don't have sufficient permissions to access Decap CMS");
}
}
const userData = {
name: user.user_metadata.full_name || user.email.split('@').shift()!,
email: user.email,
avatar_url: user.user_metadata.avatar_url,
metadata: user.user_metadata,
};
const apiConfig = {
apiRoot: `${this.gatewayUrl}/${this.backendType}`,
branch: this.branch,
tokenPromise: this.tokenPromise!,
commitAuthor: pick(userData, ['name', 'email']),
isLargeMedia: (filename: string) => this.isLargeMediaFile(filename),
squashMerges: this.squashMerges,
cmsLabelPrefix: this.cmsLabelPrefix,
initialWorkflowStatus: this.options.initialWorkflowStatus,
};
if (this.backendType === 'github') {
this.api = new GitHubAPI(apiConfig);
this.backend = new GitHubBackend(this.config, { ...this.options, API: this.api });
} else if (this.backendType === 'gitlab') {
this.api = new GitLabAPI(apiConfig);
this.backend = new GitLabBackend(this.config, { ...this.options, API: this.api });
} else if (this.backendType === 'bitbucket') {
this.api = new BitBucketAPI({
...apiConfig,
requestFunction: this.requestFunction,
hasWriteAccess: async () => true,
});
this.backend = new BitbucketBackend(this.config, { ...this.options, API: this.api });
}
if (!(await this.api!.hasWriteAccess())) {
throw new Error("You don't have sufficient permissions to access Decap CMS");
}
return {
name: userData.name,
login: userData.email,
avatar_url: userData.avatar_url,
} as unknown as User;
});
}
async restoreUser() {
const client = await this.getAuthClient();
const user = client.currentUser();
if (!user) return Promise.reject();
return this.authenticate(user as Credentials);
}
authComponent() {
return AuthenticationPage;
}
async logout() {
const client = await this.getAuthClient();
try {
client.logout();
} catch (e) {
// due to a bug in the identity widget (gotrue-js actually) the store is not reset if logout fails
// TODO: remove after https://github.com/netlify/gotrue-js/pull/83 is merged
client.clearStore();
}
}
getToken() {
return this.tokenPromise!();
}
async entriesByFolder(folder: string, extension: string, depth: number) {
return this.backend!.entriesByFolder(folder, extension, depth);
}
allEntriesByFolder(folder: string, extension: string, depth: number, pathRegex?: RegExp) {
return this.backend!.allEntriesByFolder(folder, extension, depth, pathRegex);
}
entriesByFiles(files: ImplementationFile[]) {
return this.backend!.entriesByFiles(files);
}
getEntry(path: string) {
return this.backend!.getEntry(path);
}
async unpublishedEntryDataFile(collection: string, slug: string, path: string, id: string) {
return this.backend!.unpublishedEntryDataFile(collection, slug, path, id);
}
async isLargeMediaFile(path: string) {
const client = await this.getLargeMediaClient();
return client.enabled && client.matchPath(path);
}
async unpublishedEntryMediaFile(collection: string, slug: string, path: string, id: string) {
const isLargeMedia = await this.isLargeMediaFile(path);
if (isLargeMedia) {
const branch = this.backend!.getBranch(collection, slug);
const { url, blob } = await this.getLargeMediaDisplayURL({ path, id }, branch);
return {
id,
name: basename(path),
path,
url,
displayURL: url,
file: new File([blob], basename(path)),
size: blob.size,
};
} else {
return this.backend!.unpublishedEntryMediaFile(collection, slug, path, id);
}
}
getMedia(mediaFolder = this.mediaFolder) {
return this.backend!.getMedia(mediaFolder);
}
// this method memoizes this._getLargeMediaClient so that there can
// only be one client at a time
getLargeMediaClient() {
if (this._largeMediaClientPromise) {
return this._largeMediaClientPromise;
}
this._largeMediaClientPromise = this._getLargeMediaClient();
return this._largeMediaClientPromise;
}
_getLargeMediaClient() {
const netlifyLargeMediaEnabledPromise = this.api!.readFile('.lfsconfig')
.then(config => ini.decode<{ lfs: { url: string } }>(config as string))
.then(({ lfs: { url } }) => new URL(url))
.then(lfsURL => ({
enabled: lfsURL.hostname.endsWith('netlify.com') || lfsURL.hostname.endsWith('netlify.app'),
}))
.catch((err: Error) => ({ enabled: false, err }));
const lfsPatternsPromise = this.api!.readFile('.gitattributes')
.then(attributes => getLargeMediaPatternsFromGitAttributesFile(attributes as string))
.then((patterns: string[]) => ({ err: null, patterns }))
.catch((err: Error) => {
if (err.message.includes('404')) {
console.log('This 404 was expected and handled appropriately.');
return { err: null, patterns: [] as string[] };
} else {
return { err, patterns: [] as string[] };
}
});
return Promise.all([netlifyLargeMediaEnabledPromise, lfsPatternsPromise]).then(
([{ enabled: maybeEnabled }, { patterns, err: patternsErr }]) => {
const enabled = maybeEnabled && !patternsErr;
// We expect LFS patterns to exist when the .lfsconfig states
// that we're using Netlify Large Media
if (maybeEnabled && patternsErr) {
console.error(patternsErr);
}
return getClient({
enabled,
rootURL: this.netlifyLargeMediaURL,
makeAuthorizedRequest: this.requestFunction,
patterns,
transformImages: this.transformImages ? { nf_resize: 'fit', w: 560, h: 320 } : false,
});
},
);
}
async getLargeMediaDisplayURL(
{ path, id }: { path: string; id: string | null },
branch = this.branch,
) {
const readFile = (
path: string,
id: string | null | undefined,
{ parseText }: { parseText: boolean },
) => this.api!.readFile(path, id, { branch, parseText });
const items = await entriesByFiles(
[{ path, id }],
readFile,
this.api!.readFileMetadata.bind(this.api),
'Git-Gateway',
);
const entry = items[0];
const pointerFile = parsePointerFile(entry.data);
if (!pointerFile.sha) {
console.warn(`Failed parsing pointer file ${path}`);
return { url: path, blob: new Blob() };
}
const client = await this.getLargeMediaClient();
const { url, blob } = await client.getDownloadURL(pointerFile);
return { url, blob };
}
async getMediaDisplayURL(displayURL: DisplayURL) {
const { path, id } = displayURL as DisplayURLObject;
const isLargeMedia = await this.isLargeMediaFile(path);
if (isLargeMedia) {
const { url } = await this.getLargeMediaDisplayURL({ path, id });
return url;
}
if (typeof displayURL === 'string') {
return displayURL;
}
const url = await this.backend!.getMediaDisplayURL(displayURL);
return url;
}
async getMediaFile(path: string) {
const isLargeMedia = await this.isLargeMediaFile(path);
if (isLargeMedia) {
const { url, blob } = await this.getLargeMediaDisplayURL({ path, id: null });
return {
id: url,
name: basename(path),
path,
url,
displayURL: url,
file: new File([blob], basename(path)),
size: blob.size,
};
}
return this.backend!.getMediaFile(path);
}
async persistEntry(entry: Entry, options: PersistOptions) {
const client = await this.getLargeMediaClient();
if (client.enabled) {
const assets = await getLargeMediaFilteredMediaFiles(client, entry.assets);
return this.backend!.persistEntry({ ...entry, assets }, options);
} else {
return this.backend!.persistEntry(entry, options);
}
}
async persistMedia(mediaFile: AssetProxy, options: PersistOptions) {
const { fileObj, path } = mediaFile;
const displayURL = fileObj ? URL.createObjectURL(fileObj) : '';
const client = await this.getLargeMediaClient();
const fixedPath = path.startsWith('/') ? path.slice(1) : path;
const isLargeMedia = await this.isLargeMediaFile(fixedPath);
if (isLargeMedia) {
const persistMediaArgument = await getPointerFileForMediaFileObj(
client,
fileObj as File,
path,
);
return {
...(await this.backend!.persistMedia(persistMediaArgument, options)),
displayURL,
};
}
return await this.backend!.persistMedia(mediaFile, options);
}
deleteFiles(paths: string[], commitMessage: string) {
return this.backend!.deleteFiles(paths, commitMessage);
}
async getDeployPreview(collection: string, slug: string) {
let preview = await this.backend!.getDeployPreview(collection, slug);
if (!preview) {
try {
// if the commit doesn't have a status, try to use Netlify API directly
// this is useful when builds are queue up in Netlify and don't have a commit status yet
// and only works with public logs at the moment
// TODO: get Netlify API Token and use it to access private logs
const siteId = new URL(localStorage.getItem('netlifySiteURL') || '').hostname;
const site = await apiGet(siteId);
const deploys: { state: string; commit_ref: string; deploy_url: string }[] = await apiGet(
`${site.id}/deploys?per_page=100`,
);
if (deploys.length > 0) {
const ref = await this.api!.getUnpublishedEntrySha(collection, slug);
const deploy = deploys.find(d => d.commit_ref === ref);
if (deploy) {
preview = {
status: deploy.state === 'ready' ? PreviewState.Success : PreviewState.Other,
url: deploy.deploy_url,
};
}
}
// eslint-disable-next-line no-empty
} catch (e) {}
}
return preview;
}
unpublishedEntries() {
return this.backend!.unpublishedEntries();
}
unpublishedEntry({ id, collection, slug }: { id?: string; collection?: string; slug?: string }) {
return this.backend!.unpublishedEntry({ id, collection, slug });
}
updateUnpublishedEntryStatus(collection: string, slug: string, newStatus: string) {
return this.backend!.updateUnpublishedEntryStatus(collection, slug, newStatus);
}
deleteUnpublishedEntry(collection: string, slug: string) {
return this.backend!.deleteUnpublishedEntry(collection, slug);
}
publishUnpublishedEntry(collection: string, slug: string) {
return this.backend!.publishUnpublishedEntry(collection, slug);
}
traverseCursor(cursor: Cursor, action: string) {
return this.backend!.traverseCursor!(cursor, action);
}
}

View File

@@ -0,0 +1,8 @@
import GitGatewayBackend from './implementation';
import AuthenticationPage from './AuthenticationPage';
export const DecapCmsBackendGitGateway = {
GitGatewayBackend,
AuthenticationPage,
};
export { GitGatewayBackend, AuthenticationPage };

View File

@@ -0,0 +1,180 @@
import { flow, fromPairs, map } from 'lodash/fp';
import { isPlainObject, isEmpty } from 'lodash';
import minimatch from 'minimatch';
import { unsentRequest } from 'decap-cms-lib-util';
import type { ApiRequest, PointerFile } from 'decap-cms-lib-util';
type MakeAuthorizedRequest = (req: ApiRequest) => Promise<Response>;
type ImageTransformations = { nf_resize: string; w: number; h: number };
type ClientConfig = {
rootURL: string;
makeAuthorizedRequest: MakeAuthorizedRequest;
patterns: string[];
enabled: boolean;
transformImages: ImageTransformations | boolean;
};
export function matchPath({ patterns }: ClientConfig, path: string) {
return patterns.some(pattern => minimatch(path, pattern, { matchBase: true }));
}
//
// API interactions
const defaultContentHeaders = {
Accept: 'application/vnd.git-lfs+json',
['Content-Type']: 'application/vnd.git-lfs+json',
};
async function resourceExists(
{ rootURL, makeAuthorizedRequest }: ClientConfig,
{ sha, size }: PointerFile,
) {
const response = await makeAuthorizedRequest({
url: `${rootURL}/verify`,
method: 'POST',
headers: defaultContentHeaders,
body: JSON.stringify({ oid: sha, size }),
});
if (response.ok) {
return true;
}
if (response.status === 404) {
return false;
}
// TODO: what kind of error to throw here? APIError doesn't seem
// to fit
}
function getTransofrmationsParams(t: boolean | ImageTransformations) {
if (isPlainObject(t) && !isEmpty(t)) {
const { nf_resize: resize, w, h } = t as ImageTransformations;
return `?nf_resize=${resize}&w=${w}&h=${h}`;
}
return '';
}
async function getDownloadURL(
{ rootURL, transformImages: t, makeAuthorizedRequest }: ClientConfig,
{ sha }: PointerFile,
) {
try {
const transformation = getTransofrmationsParams(t);
const transformedPromise = makeAuthorizedRequest(`${rootURL}/origin/${sha}${transformation}`);
const [transformed, original] = await Promise.all([
transformedPromise,
// if transformation is defined, we need to load the original so we have the correct meta data
transformation ? makeAuthorizedRequest(`${rootURL}/origin/${sha}`) : transformedPromise,
]);
if (!transformed.ok) {
const error = await transformed.json();
throw new Error(
`Failed getting large media for sha '${sha}': '${error.code} - ${error.msg}'`,
);
}
const transformedBlob = await transformed.blob();
const url = URL.createObjectURL(transformedBlob);
return { url, blob: transformation ? await original.blob() : transformedBlob };
} catch (error) {
console.error(error);
return { url: '', blob: new Blob() };
}
}
function uploadOperation(objects: PointerFile[]) {
return {
operation: 'upload',
transfers: ['basic'],
objects: objects.map(({ sha, ...rest }) => ({ ...rest, oid: sha })),
};
}
async function getResourceUploadURLs(
{
rootURL,
makeAuthorizedRequest,
}: { rootURL: string; makeAuthorizedRequest: MakeAuthorizedRequest },
pointerFiles: PointerFile[],
) {
const response = await makeAuthorizedRequest({
url: `${rootURL}/objects/batch`,
method: 'POST',
headers: defaultContentHeaders,
body: JSON.stringify(uploadOperation(pointerFiles)),
});
const { objects } = await response.json();
const uploadUrls = objects.map(
(object: { error?: { message: string }; actions: { upload: { href: string } } }) => {
if (object.error) {
throw new Error(object.error.message);
}
return object.actions.upload.href;
},
);
return uploadUrls;
}
function uploadBlob(uploadURL: string, blob: Blob) {
return unsentRequest.fetchWithTimeout(uploadURL, {
method: 'PUT',
body: blob,
});
}
async function uploadResource(
clientConfig: ClientConfig,
{ sha, size }: PointerFile,
resource: Blob,
) {
const existingFile = await resourceExists(clientConfig, { sha, size });
if (existingFile) {
return sha;
}
const [uploadURL] = await getResourceUploadURLs(clientConfig, [{ sha, size }]);
await uploadBlob(uploadURL, resource);
return sha;
}
//
// Create Large Media client
function configureFn(config: ClientConfig, fn: Function) {
return (...args: unknown[]) => fn(config, ...args);
}
const clientFns: Record<string, Function> = {
resourceExists,
getResourceUploadURLs,
getDownloadURL,
uploadResource,
matchPath,
};
export type Client = {
resourceExists: (pointer: PointerFile) => Promise<boolean | undefined>;
getResourceUploadURLs: (objects: PointerFile[]) => Promise<string>;
getDownloadURL: (pointer: PointerFile) => Promise<{ url: string; blob: Blob }>;
uploadResource: (pointer: PointerFile, blob: Blob) => Promise<string>;
matchPath: (path: string) => boolean;
patterns: string[];
enabled: boolean;
};
export function getClient(clientConfig: ClientConfig) {
return flow([
Object.keys,
map((key: string) => [key, configureFn(clientConfig, clientFns[key])]),
fromPairs,
configuredFns => ({
...configuredFns,
patterns: clientConfig.patterns,
enabled: clientConfig.enabled,
}),
])(clientFns);
}

View File

@@ -0,0 +1,4 @@
declare module 'ini' {
const ini: { decode: <T>(ini: string) => T };
export default ini;
}

View File

@@ -0,0 +1,3 @@
const { getConfig } = require('../../scripts/webpack.js');
module.exports = getConfig();