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

541
node_modules/decap-cms-backend-github/CHANGELOG.md generated vendored Normal file
View File

@@ -0,0 +1,541 @@
# 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-github@3.2.1...decap-cms-backend-github@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-github@3.2.0...decap-cms-backend-github@3.2.1) (2024-08-13)
**Note:** Version bump only for package decap-cms-backend-github
# [3.2.0](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-github@3.1.2...decap-cms-backend-github@3.2.0) (2024-08-07)
### Bug Fixes
- fetch GitHub PR author name, fixes [#7232](https://github.com/decaporg/decap-cms/issues/7232) ([#7253](https://github.com/decaporg/decap-cms/issues/7253)) ([0e5335d](https://github.com/decaporg/decap-cms/commit/0e5335daba1b67816b4a0c24d1a2d9a185e3b54f))
## [3.1.2](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-github@3.1.1...decap-cms-backend-github@3.1.2) (2024-04-03)
**Note:** Version bump only for package decap-cms-backend-github
## [3.1.1](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-github@3.1.0-beta.2...decap-cms-backend-github@3.1.1) (2024-03-21)
**Note:** Version bump only for package decap-cms-backend-github
# [3.1.0](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-github@3.1.0-beta.2...decap-cms-backend-github@3.1.0) (2024-02-01)
**Note:** Version bump only for package decap-cms-backend-github
# [3.1.0-beta.2](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-github@3.1.0-beta.1...decap-cms-backend-github@3.1.0-beta.2) (2024-01-31)
**Note:** Version bump only for package decap-cms-backend-github
# [3.1.0-beta.1](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-github@3.1.0-beta.0...decap-cms-backend-github@3.1.0-beta.1) (2023-11-15)
### Bug Fixes
- used merge-upstream to sync fork & upstream ([#6504](https://github.com/decaporg/decap-cms/issues/6504)) ([931399d](https://github.com/decaporg/decap-cms/commit/931399dd6eb675e06d59ac57ecfefc1b82467271))
# [3.1.0-beta.0](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-github@3.1.0...decap-cms-backend-github@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-github@3.0.2...decap-cms-backend-github@3.0.3) (2023-10-13)
**Note:** Version bump only for package decap-cms-backend-github
## [3.0.2](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-github@3.0.1...decap-cms-backend-github@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-github@3.0.0...decap-cms-backend-github@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-github@2.15.0...decap-cms-backend-github@3.0.0) (2023-08-18)
**Note:** Version bump only for package decap-cms-backend-github
# [2.15.0](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-github@2.15.0-beta.0...decap-cms-backend-github@2.15.0) (2023-08-18)
**Note:** Version bump only for package decap-cms-backend-github
# 2.15.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.14.2-beta.0](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-github@2.14.1...decap-cms-backend-github@2.14.2-beta.0) (2023-07-27)
**Note:** Version bump only for package decap-cms-backend-github
## [2.14.1](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-github@2.14.0...decap-cms-backend-github@2.14.1) (2022-04-13)
**Note:** Version bump only for package decap-cms-backend-github
# [2.14.0](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-github@2.13.5...decap-cms-backend-github@2.14.0) (2021-10-18)
### Features
- display author of changes in workflow tab ([#5780](https://github.com/decaporg/decap-cms/issues/5780)) ([3f607e4](https://github.com/decaporg/decap-cms/commit/3f607e41d9c4d8fe5329a9ab6841cada7742825e))
## [2.13.5](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-github@2.13.4...decap-cms-backend-github@2.13.5) (2021-10-15)
### Bug Fixes
- remove "Don't fork the repo"-Button - fixes [#5723](https://github.com/decaporg/decap-cms/issues/5723) ([#5872](https://github.com/decaporg/decap-cms/issues/5872)) ([05d8923](https://github.com/decaporg/decap-cms/commit/05d89230dca315ddcc734b1dc6223df1d8dc1ede))
## [2.13.4](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-github@2.13.3...decap-cms-backend-github@2.13.4) (2021-07-20)
### Bug Fixes
- add updated_at to graphql query ([#5611](https://github.com/decaporg/decap-cms/issues/5611)) ([8989550](https://github.com/decaporg/decap-cms/commit/89895508b2ccc8f07019abb6bc2d0162c0d86266))
## [2.13.3](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.13.2...decap-cms-backend-github@2.13.3) (2021-06-01)
**Note:** Version bump only for package decap-cms-backend-github
## [2.13.2](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.13.1...decap-cms-backend-github@2.13.2) (2021-05-31)
**Note:** Version bump only for package decap-cms-backend-github
## [2.13.1](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.13.0...decap-cms-backend-github@2.13.1) (2021-05-19)
**Note:** Version bump only for package decap-cms-backend-github
# [2.13.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.12.0...decap-cms-backend-github@2.13.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-github/issues/5316)) ([9e42380](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/9e423805707321396eec137f5b732a5b07a0dd3f))
# [2.12.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.11.9...decap-cms-backend-github@2.12.0) (2021-04-04)
### Features
- **open-authoring:** add alwaysFork option ([#5204](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/5204)) ([7b19e30](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/7b19e30dd2a310dbc20ccb6fcca45d5cbde1014b))
## [2.11.9](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.11.8...decap-cms-backend-github@2.11.9) (2021-02-23)
**Note:** Version bump only for package decap-cms-backend-github
## [2.11.8](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.11.7...decap-cms-backend-github@2.11.8) (2021-02-10)
**Note:** Version bump only for package decap-cms-backend-github
## [2.11.7](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.11.6...decap-cms-backend-github@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-github/issues/4678)) ([7697b90](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/7697b907d7bae750f4ec041a184188aa46995320))
## [2.11.6](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.11.5...decap-cms-backend-github@2.11.6) (2020-09-20)
**Note:** Version bump only for package decap-cms-backend-github
## [2.11.5](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.11.4...decap-cms-backend-github@2.11.5) (2020-09-15)
**Note:** Version bump only for package decap-cms-backend-github
## 2.11.4 (2020-09-08)
### Reverts
- Revert "chore(release): publish" ([828bb16](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/828bb16415b8c22a34caa19c50c38b24ffe9ceae))
## 2.11.3 (2020-08-20)
### Reverts
- Revert "chore(release): publish" ([8262487](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/82624879ccbcb16610090041db28f00714d924c8))
## 2.11.2 (2020-07-27)
### Reverts
- Revert "chore(release): publish" ([118d50a](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/118d50a7a70295f25073e564b5161aa2b9883056))
## [2.11.1](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.11.0...decap-cms-backend-github@2.11.1) (2020-07-14)
### Bug Fixes
- **backend-github:** use workflow branch when listing files to move ([#4019](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/4019)) ([8720a42](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/8720a4233db16d91d6b86ee8653d05f8953cb430))
# [2.11.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.10.6...decap-cms-backend-github@2.11.0) (2020-06-18)
### Bug Fixes
- handle token expiry ([#3847](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/3847)) ([285c940](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/285c940562548d7bc88de244123ba87ff66fba65))
### Features
- add backend status down indicator ([#3889](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/3889)) ([a50edc7](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/a50edc70553ad6afa1acee6a51996ad226443f8c))
- **backend-gitgateway:** improve deploy preview visibility ([#3882](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/3882)) ([afc9bf4](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/afc9bf4f3fe14ccb60851fc24e68922a6e4a85a9))
## [2.10.6](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.10.5...decap-cms-backend-github@2.10.6) (2020-05-19)
**Note:** Version bump only for package decap-cms-backend-github
## [2.10.5](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.10.4...decap-cms-backend-github@2.10.5) (2020-04-21)
**Note:** Version bump only for package decap-cms-backend-github
## [2.10.4](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.10.3...decap-cms-backend-github@2.10.4) (2020-04-07)
### Bug Fixes
- **backend-github:** add fallback for diff errors/warnings ([#3558](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/3558)) ([1705c79](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/1705c79a9297d844d5421d685a7785e1e210e39e))
## [2.10.3](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.10.2...decap-cms-backend-github@2.10.3) (2020-04-01)
### Bug Fixes
- **open-authoring:** properly delete open authoring branches ([#3512](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/3512)) ([cc89aa5](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/cc89aa5c430a6bee51483cda91d0f92e7437f29e))
## [2.10.2](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.10.1...decap-cms-backend-github@2.10.2) (2020-04-01)
### Bug Fixes
- **open-authoring:** prevent workflow view from breaking on entry error ([#3508](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/3508)) ([cbb3927](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/cbb39271012fc3beecfdf180e573e343ee48fe26))
## [2.10.1](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.10.0...decap-cms-backend-github@2.10.1) (2020-03-20)
### Bug Fixes
- missing workflow timestamp ([#3445](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/3445)) ([9616cdb](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/9616cdb8bb0a564771e5755bcd3718a07f2e2072))
# [2.10.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.9.3...decap-cms-backend-github@2.10.0) (2020-03-12)
### Bug Fixes
- **backend-github:** don't create new commits on empty diff when rebasing ([#3411](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/3411)) ([70de9f6](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/70de9f6b4b89dd8e23205929033745572562e8fc))
- update repo owner from GitHub API to match casing ([#3410](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/3410)) ([c2e7a24](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/c2e7a24dc20dfea5b1289c5705095d2cf8b04c54))
### Features
- **backend-github:** add pagination ([#3379](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/3379)) ([39f1307](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/39f1307e3a36447da8c9b3ca79b1d7db52ea1a19))
## [2.9.3](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.9.2...decap-cms-backend-github@2.9.3) (2020-03-03)
### Bug Fixes
- **locale:** Remove hard coded string literals ([#3333](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/3333)) ([7c45a3c](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/7c45a3cda983be427864a56e58791565eb9232e2))
- **open-authoring:** use origin repo when calling compare API ([#3363](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/3363)) ([e40b81a](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/e40b81a5647d45487d6ddf17245beddd354e0f39))
## [2.9.2](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.9.1...decap-cms-backend-github@2.9.2) (2020-02-27)
**Note:** Version bump only for package decap-cms-backend-github
## [2.9.1](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.9.0...decap-cms-backend-github@2.9.1) (2020-02-25)
### Bug Fixes
- **backend-github:** fail workflow migrations gracefully ([#3325](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/3325)) ([83e0383](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/83e0383b690fb452ea40cb165a56f65a695dc83c))
# [2.9.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.8.1...decap-cms-backend-github@2.9.0) (2020-02-25)
### Bug Fixes
- **backend-github:** improve workflow migration edge cases/messaging ([#3319](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/3319)) ([684b79e](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/684b79e43bebb63ce1e844eae5c8c0e76087687b))
### Features
- **core:** align GitHub metadata handling with other backends ([#3316](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/3316)) ([7e0a8ad](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/7e0a8ad532012576dc5e40bd4e9d54522e307123)), closes [#3292](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/3292)
## [2.8.1](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.8.0...decap-cms-backend-github@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-github/commit/5bdd3df9ccbb5149c22d79987ebdcd6cab4b261f)), closes [#3292](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/3292)
# [2.8.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.7.1...decap-cms-backend-github@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-github/issues/3292)) ([8193b5a](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/8193b5ace89d6f14a6c756235a50b186a763b6b1))
## [2.7.1](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.7.0...decap-cms-backend-github@2.7.1) (2020-02-17)
**Note:** Version bump only for package decap-cms-backend-github
# [2.7.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.6.6...decap-cms-backend-github@2.7.0) (2020-02-10)
### Features
- field based media/public folders ([#3208](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/3208)) ([97bc0c8](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/97bc0c8dc489e736f89d748ba832d78400fe4332))
### Reverts
- Revert "chore(release): publish" ([a015d1d](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/a015d1d92a4b1c0130c44fcef1c9ecdb157a0f07))
## [2.6.6](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.6.5...decap-cms-backend-github@2.6.6) (2020-02-06)
### Bug Fixes
- **locale:** remove hard coded strings ([#3193](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/3193)) ([fc91bf8](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/fc91bf8781e65ce1dc946363dbb10419a145c66b))
## [2.6.5](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.6.4...decap-cms-backend-github@2.6.5) (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-github/issues/3135)) ([834f6b9](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/834f6b9e457f3738ce0f240ddd4cc160aff9e2f5))
## [2.6.4](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.6.3...decap-cms-backend-github@2.6.4) (2020-01-16)
### Bug Fixes
- **backend-github-graphql:** handle trailing paths in collection folder ([#3099](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/3099)) ([bc80804](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/bc808040661d345e65d49d64693cd6da3b6816fb))
## [2.6.3](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.6.2...decap-cms-backend-github@2.6.3) (2020-01-14)
**Note:** Version bump only for package decap-cms-backend-github
## [2.6.2](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.6.1...decap-cms-backend-github@2.6.2) (2020-01-14)
### Bug Fixes
- **backend-github-graphql:** return empty array on non existent folder ([#3079](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/3079)) ([69b130a](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/69b130a3f239590f828f0e4f6f6c0a872b17548b))
## [2.6.1](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.6.0...decap-cms-backend-github@2.6.1) (2020-01-09)
### Bug Fixes
- trim '/' from folder ([#3052](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/3052)) ([4b6c8de](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/4b6c8de6b2e3de28f0989b9a012cb302d4de4358))
# [2.6.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.6.0-beta.0...decap-cms-backend-github@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-github/issues/2975)) ([8c175f6](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/8c175f6132fa18a13763cc563f7d3201c1e3580e))
# [2.6.0-beta.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.5.0...decap-cms-backend-github@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-github/issues/2958)) ([2b41d8a](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/2b41d8a838a9c8a6b21cde2ddd16b9288334e298))
# [2.5.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.5.0-beta.8...decap-cms-backend-github@2.5.0) (2019-12-18)
**Note:** Version bump only for package decap-cms-backend-github
# [2.5.0-beta.8](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.5.0-beta.7...decap-cms-backend-github@2.5.0-beta.8) (2019-12-16)
### Bug Fixes
- don't fail on deleting non existent branch ([1e77d4b](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/1e77d4b7688de795ab1b01c6ce2483a0383bbfb6))
# [2.5.0-beta.7](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.5.0-beta.6...decap-cms-backend-github@2.5.0-beta.7) (2019-12-02)
### Features
- content in sub folders ([#2897](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/2897)) ([afcfe5b](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/afcfe5b6d5f32669e9061ec596bd35ad545d61a3))
# [2.5.0-beta.6](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.5.0-beta.5...decap-cms-backend-github@2.5.0-beta.6) (2019-11-26)
### Bug Fixes
- **backend-github:** prepend collection name ([#2878](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/2878)) ([465f463](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/465f4639597f258d5aa2c1b65e9d2c16023ee7ae))
### Features
- workflow unpublished entry ([#2914](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/2914)) ([41bb9aa](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/41bb9aac0dd6fd9f8ff157bb0b29c85aa87fe04d))
# [2.5.0-beta.5](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.5.0-beta.4...decap-cms-backend-github@2.5.0-beta.5) (2019-11-18)
### Bug Fixes
- **backend-github:** editorial workflow commits ([#2867](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/2867)) ([86adca3](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/86adca3a18f25ab74d1c6702bafab250f005ceec))
- make forkExists name matching case-insensitive ([#2869](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/2869)) ([9978769](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/9978769ece9262265d3efa77357f9e8b46ad9a1e))
- **backend-github:** loaded entries limit ([#2873](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/2873)) ([68a8c8a](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/68a8c8a693646ebd33fae791aaaec47b050e0186))
- **git-gateway:** unpublished entries not loaded for git-gateway(GitHub) ([#2856](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/2856)) ([4a2328b](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/4a2328b2f10ea678184391e4caf235b41323cd3e))
### Features
- commit media with post ([#2851](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/2851)) ([6515dee](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/6515dee8715d8571ea19484a7dfab7cfd0cc40be))
# [2.5.0-beta.4](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.5.0-beta.3...decap-cms-backend-github@2.5.0-beta.4) (2019-11-07)
### Bug Fixes
- **github-backend:** load media URLs via API ([#2817](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/2817)) ([eaeaf44](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/eaeaf4483287a1f724ee60ef321ff749f1c20acf))
- change default open authoring scope, make it configurable ([#2821](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/2821)) ([002cdd7](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/002cdd77a856bde3672e75dde6d3a2b246e1035f))
- display UI to fork a repo only when fork doesn't exist ([#2802](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/2802)) ([7f90d0e](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/7f90d0e065315b9073d21fd733f42f3838ecfe09))
### Features
- add go back to site button ([#2538](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/2538)) ([f206e7e](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/f206e7e5a13fb48ec6b27dce0dbb3a59b61de8f9))
# [2.5.0-beta.3](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.5.0-beta.2...decap-cms-backend-github@2.5.0-beta.3) (2019-09-26)
### Bug Fixes
- **backend-github:** update Open Authoring branches with no PR ([#2618](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/2618)) ([6817033](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/6817033))
- **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-github/issues/2631)) ([922c0f3](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/922c0f3))
- **github-backend:** handle race condition in editorial workflow ([#2658](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/2658)) ([97f1f84](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/97f1f84))
# [2.5.0-beta.2](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.5.0-beta.1...decap-cms-backend-github@2.5.0-beta.2) (2019-09-04)
### Bug Fixes
- **github-graphql:** use getMediaDisplayURL to load media with auth header ([#2652](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/2652)) ([e674e43](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/e674e43))
### Features
- **backend-github:** GitHub GraphQL API support ([#2456](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/2456)) ([ece136c](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/ece136c))
# [2.5.0-beta.1](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.5.0-beta.0...decap-cms-backend-github@2.5.0-beta.1) (2019-08-24)
**Note:** Version bump only for package decap-cms-backend-github
# [2.5.0-beta.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.4.2...decap-cms-backend-github@2.5.0-beta.0) (2019-07-24)
### Features
- **backend-github:** Open Authoring ([#2430](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/2430)) ([edf0a3a](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/edf0a3a))
## [2.4.2](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.4.2-beta.0...decap-cms-backend-github@2.4.2) (2019-04-10)
**Note:** Version bump only for package decap-cms-backend-github
## [2.4.2-beta.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.4.1...decap-cms-backend-github@2.4.2-beta.0) (2019-04-05)
**Note:** Version bump only for package decap-cms-backend-github
## [2.4.1](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.4.1-beta.1...decap-cms-backend-github@2.4.1) (2019-03-29)
**Note:** Version bump only for package decap-cms-backend-github
## [2.4.1-beta.1](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.4.1-beta.0...decap-cms-backend-github@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-github/issues/2244)) ([6ffd13b](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/6ffd13b))
## [2.4.1-beta.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.4.0...decap-cms-backend-github@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-github/issues/2234)) ([7987091](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/7987091))
# [2.4.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.3.0...decap-cms-backend-github@2.4.0) (2019-03-22)
### Features
- add ES module builds ([#2215](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/2215)) ([d142b32](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/d142b32))
# [2.3.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.3.0-beta.0...decap-cms-backend-github@2.3.0) (2019-03-22)
**Note:** Version bump only for package decap-cms-backend-github
# [2.3.0-beta.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.2.3-beta.0...decap-cms-backend-github@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-github/issues/2214)) ([e04f6be](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/e04f6be))
### Features
- provide usable UMD builds for all packages ([#2141](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/2141)) ([82cc794](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/82cc794))
## [2.2.3-beta.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.2.2...decap-cms-backend-github@2.2.3-beta.0) (2019-03-15)
### Features
- upgrade to Emotion 10 ([#2166](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/2166)) ([ccef446](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/ccef446))
## [2.2.2](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.2.1...decap-cms-backend-github@2.2.2) (2019-03-08)
**Note:** Version bump only for package decap-cms-backend-github
## [2.2.1](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.2.0...decap-cms-backend-github@2.2.1) (2019-02-26)
**Note:** Version bump only for package decap-cms-backend-github
# [2.2.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.1.0...decap-cms-backend-github@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-github/issues/2028)) ([15d221d](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/15d221d))
# [2.1.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.0.9...decap-cms-backend-github@2.1.0) (2018-11-12)
### Features
- allow custom logo on auth page ([#1818](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/1818)) ([c6ae1e8](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/c6ae1e8))
<a name="2.0.9"></a>
## [2.0.9](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.0.8...decap-cms-backend-github@2.0.9) (2018-09-17)
**Note:** Version bump only for package decap-cms-backend-github
<a name="2.0.8"></a>
## [2.0.8](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.0.7...decap-cms-backend-github@2.0.8) (2018-09-06)
**Note:** Version bump only for package decap-cms-backend-github
<a name="2.0.7"></a>
## [2.0.7](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.0.6...decap-cms-backend-github@2.0.7) (2018-08-27)
**Note:** Version bump only for package decap-cms-backend-github
<a name="2.0.6"></a>
## [2.0.6](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.0.5...decap-cms-backend-github@2.0.6) (2018-08-24)
**Note:** Version bump only for package decap-cms-backend-github
<a name="2.0.5"></a>
## [2.0.5](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.0.4...decap-cms-backend-github@2.0.5) (2018-08-07)
### Bug Fixes
- **backends:** fix commit message handling ([#1568](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/1568)) ([f7e7120](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/f7e7120))
<a name="2.0.4"></a>
## [2.0.4](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.0.3...decap-cms-backend-github@2.0.4) (2018-08-01)
### Bug Fixes
- **workflow:** enable workflow per method ([#1569](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/1569)) ([90b8156](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/90b8156))
<a name="2.0.3"></a>
## [2.0.3](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.0.2...decap-cms-backend-github@2.0.3) (2018-08-01)
### Bug Fixes
- **github:** fix image uploading ([#1561](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/1561)) ([ddc8f04](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/ddc8f04))
- **workflow:** fix status not set on new workflow entries ([#1558](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/issues/1558)) ([0aa085f](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/commit/0aa085f))
<a name="2.0.2"></a>
## [2.0.2](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github/compare/decap-cms-backend-github@2.0.1...decap-cms-backend-github@2.0.2) (2018-07-28)
**Note:** Version bump only for package decap-cms-backend-github
<a name="2.0.1"></a>
## 2.0.1 (2018-07-26)
<a name="2.0.0"></a>
# 2.0.0 (2018-07-26)
**Note:** Version bump only for package decap-cms-backend-github

22
node_modules/decap-cms-backend-github/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.

17
node_modules/decap-cms-backend-github/README.md generated vendored Normal file
View File

@@ -0,0 +1,17 @@
# GitHub backend
An abstraction layer between the CMS and [GitHub](https://docs.github.com/en/rest)
## 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` - A wrapper for GitHub REST API.
`GraphQLApi` - `Api` with `ApolloClient`. [Api docs](https://docs.github.com/en/graphql) and [netlify docs](https://www.decapcms.org/docs/beta-features/#github-graphql-api).
`AuthenticationPage` - uses [lib-auth](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-lib-auth/README.md) to facilitate OAuth and implicit authentication.
`scripts` - use `createFragmentTypes.js` to create GitHub GraphQL API fragment types.
Look at tests or types for more info.

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,23 @@
/*!
* The buffer module from node.js, for the browser.
*
* @author Feross Aboukhadijeh <https://feross.org>
* @license MIT
*/
/*! *****************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */

File diff suppressed because one or more lines are too long

1219
node_modules/decap-cms-backend-github/dist/esm/API.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,804 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _trimStart2 = _interopRequireDefault(require("lodash/trimStart"));
var _trim2 = _interopRequireDefault(require("lodash/trim"));
var _apolloClient = require("apollo-client");
var _apolloCacheInmemory = require("apollo-cache-inmemory");
var _apolloLinkHttp = require("apollo-link-http");
var _apolloLinkContext = require("apollo-link-context");
var _decapCmsLibUtil = require("decap-cms-lib-util");
var _fragmentTypes = _interopRequireDefault(require("./fragmentTypes"));
var _API = _interopRequireWildcard(require("./API"));
var queries = _interopRequireWildcard(require("./queries"));
var mutations = _interopRequireWildcard(require("./mutations"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
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 NO_CACHE = 'no-cache';
const CACHE_FIRST = 'cache-first';
const fragmentMatcher = new _apolloCacheInmemory.IntrospectionFragmentMatcher({
introspectionQueryResultData: _fragmentTypes.default
});
function transformPullRequest(pr) {
return _objectSpread(_objectSpread({}, pr), {}, {
labels: pr.labels.nodes,
head: {
ref: pr.headRefName,
sha: pr.headRefOid,
repo: {
fork: pr.repository.isFork
}
},
base: {
ref: pr.baseRefName,
sha: pr.baseRefOid
}
});
}
class GraphQLAPI extends _API.default {
constructor(config) {
super(config);
_defineProperty(this, "client", void 0);
this.client = this.getApolloClient();
}
getApolloClient() {
const authLink = (0, _apolloLinkContext.setContext)((_, {
headers
}) => {
return {
headers: _objectSpread(_objectSpread({
'Content-Type': 'application/json; charset=utf-8'
}, headers), {}, {
authorization: this.token ? `${this.tokenKeyword} ${this.token}` : ''
})
};
});
const httpLink = (0, _apolloLinkHttp.createHttpLink)({
uri: `${this.apiRoot}/graphql`
});
return new _apolloClient.ApolloClient({
link: authLink.concat(httpLink),
cache: new _apolloCacheInmemory.InMemoryCache({
fragmentMatcher
}),
defaultOptions: {
watchQuery: {
fetchPolicy: NO_CACHE,
errorPolicy: 'ignore'
},
query: {
fetchPolicy: NO_CACHE,
errorPolicy: 'all'
}
}
});
}
reset() {
return this.client.resetStore();
}
async getRepository(owner, name) {
const {
data
} = await this.query({
query: queries.repository,
variables: {
owner,
name
},
fetchPolicy: CACHE_FIRST // repository id doesn't change
});
return data.repository;
}
query(options) {
return this.client.query(options).catch(error => {
throw new _decapCmsLibUtil.APIError(error.message, 500, 'GitHub');
});
}
async mutate(options) {
try {
const result = await this.client.mutate(options);
return result;
} catch (error) {
const errors = error.graphQLErrors;
if (Array.isArray(errors) && errors.some(e => e.message === 'Ref cannot be created.')) {
var _options$variables, _options$variables$cr;
const refName = (options === null || options === void 0 ? void 0 : (_options$variables = options.variables) === null || _options$variables === void 0 ? void 0 : (_options$variables$cr = _options$variables.createRefInput) === null || _options$variables$cr === void 0 ? void 0 : _options$variables$cr.name) || '';
const branchName = (0, _trimStart2.default)(refName, 'refs/heads/');
if (branchName) {
await (0, _decapCmsLibUtil.throwOnConflictingBranches)(branchName, name => this.getBranch(name), _API.API_NAME);
}
} else if (Array.isArray(errors) && errors.some(e => new RegExp(`A ref named "refs/heads/${_decapCmsLibUtil.CMS_BRANCH_PREFIX}/.+?" already exists in the repository.`).test(e.message))) {
var _options$variables2, _options$variables2$c, _options$variables3, _options$variables3$c;
const refName = (options === null || options === void 0 ? void 0 : (_options$variables2 = options.variables) === null || _options$variables2 === void 0 ? void 0 : (_options$variables2$c = _options$variables2.createRefInput) === null || _options$variables2$c === void 0 ? void 0 : _options$variables2$c.name) || '';
const sha = (options === null || options === void 0 ? void 0 : (_options$variables3 = options.variables) === null || _options$variables3 === void 0 ? void 0 : (_options$variables3$c = _options$variables3.createRefInput) === null || _options$variables3$c === void 0 ? void 0 : _options$variables3$c.oid) || '';
const branchName = (0, _trimStart2.default)(refName, 'refs/heads/');
if (branchName && branchName.startsWith(`${_decapCmsLibUtil.CMS_BRANCH_PREFIX}/`) && sha) {
try {
// this can happen if the branch wasn't deleted when the PR was merged
// we backup the existing branch just in case an re-run the mutation
await this.backupBranch(branchName);
await this.deleteBranch(branchName);
const result = await this.client.mutate(options);
return result;
} catch (e) {
console.log(e);
}
}
}
throw new _decapCmsLibUtil.APIError(error.message, 500, 'GitHub');
}
}
async hasWriteAccess() {
const {
repoOwner: owner,
repoName: name
} = this;
try {
const {
data
} = await this.query({
query: queries.repoPermission,
variables: {
owner,
name
},
fetchPolicy: CACHE_FIRST // we can assume permission doesn't change often
});
// https://developer.github.com/v4/enum/repositorypermission/
const {
viewerPermission
} = data.repository;
return ['ADMIN', 'MAINTAIN', 'WRITE'].includes(viewerPermission);
} catch (error) {
console.error('Problem fetching repo data from GitHub');
throw error;
}
}
async user() {
const {
data
} = await this.query({
query: queries.user,
fetchPolicy: CACHE_FIRST // we can assume user details don't change often
});
return data.viewer;
}
async retrieveBlobObject(owner, name, expression, options = {}) {
const {
data
} = await this.query(_objectSpread({
query: queries.blob,
variables: {
owner,
name,
expression
}
}, options));
// https://developer.github.com/v4/object/blob/
if (data.repository.object) {
const {
is_binary: isBinary,
text
} = data.repository.object;
return {
isNull: false,
isBinary,
text
};
} else {
return {
isNull: true
};
}
}
getOwnerAndNameFromRepoUrl(repoURL) {
let {
repoOwner: owner,
repoName: name
} = this;
if (repoURL === this.originRepoURL) {
({
originRepoOwner: owner,
originRepoName: name
} = this);
}
return {
owner,
name
};
}
async readFile(path, sha, {
branch = this.branch,
repoURL = this.repoURL,
parseText = true
} = {}) {
if (!sha) {
sha = await this.getFileSha(path, {
repoURL,
branch
});
}
const fetchContent = () => this.fetchBlobContent({
sha: sha,
repoURL,
parseText
});
const content = await (0, _decapCmsLibUtil.readFile)(sha, fetchContent, _decapCmsLibUtil.localForage, parseText);
return content;
}
async fetchBlobContent({
sha,
repoURL,
parseText
}) {
if (!parseText) {
return super.fetchBlobContent({
sha,
repoURL,
parseText
});
}
const {
owner,
name
} = this.getOwnerAndNameFromRepoUrl(repoURL);
const {
isNull,
isBinary,
text
} = await this.retrieveBlobObject(owner, name, sha, {
fetchPolicy: CACHE_FIRST
} // blob sha is derived from file content
);
if (isNull) {
throw new _decapCmsLibUtil.APIError('Not Found', 404, 'GitHub');
} else if (!isBinary) {
return text;
} else {
return super.fetchBlobContent({
sha,
repoURL,
parseText
});
}
}
async getPullRequestAuthor(pullRequest) {
const user = pullRequest.user;
return (user === null || user === void 0 ? void 0 : user.name) || (user === null || user === void 0 ? void 0 : user.login);
}
async getPullRequests(head, state, predicate) {
const {
originRepoOwner: owner,
originRepoName: name
} = this;
let states;
if (state === _API.PullRequestState.Open) {
states = ['OPEN'];
} else if (state === _API.PullRequestState.Closed) {
states = ['CLOSED', 'MERGED'];
} else {
states = ['OPEN', 'CLOSED', 'MERGED'];
}
const {
data
} = await this.query({
query: queries.pullRequests,
variables: _objectSpread(_objectSpread({
owner,
name
}, head ? {
head
} : {}), {}, {
states
})
});
const {
pullRequests
} = data.repository;
const mapped = pullRequests.nodes.map(transformPullRequest);
return mapped.filter(pr => pr.head.ref.startsWith(`${_decapCmsLibUtil.CMS_BRANCH_PREFIX}/`) && predicate(pr));
}
async getOpenAuthoringBranches() {
const {
repoOwner: owner,
repoName: name
} = this;
const {
data
} = await this.query({
query: queries.openAuthoringBranches,
variables: {
owner,
name,
refPrefix: `refs/heads/cms/${this.repo}/`
}
});
return data.repository.refs.nodes.map(({
name,
prefix
}) => ({
ref: `${prefix}${name}`
}));
}
async getStatuses(collectionName, slug) {
const contentKey = this.generateContentKey(collectionName, slug);
const branch = (0, _decapCmsLibUtil.branchFromContentKey)(contentKey);
const pullRequest = await this.getBranchPullRequest(branch);
const sha = pullRequest.head.sha;
const {
originRepoOwner: owner,
originRepoName: name
} = this;
const {
data
} = await this.query({
query: queries.statues,
variables: {
owner,
name,
sha
}
});
if (data.repository.object) {
const {
status
} = data.repository.object;
const {
contexts
} = status || {
contexts: []
};
return contexts;
} else {
return [];
}
}
getAllFiles(entries, path) {
const allFiles = entries.reduce((acc, item) => {
if (item.type === 'tree') {
var _item$object;
const entries = ((_item$object = item.object) === null || _item$object === void 0 ? void 0 : _item$object.entries) || [];
return [...acc, ...this.getAllFiles(entries, `${path}/${item.name}`)];
} else if (item.type === 'blob') {
return [...acc, {
name: item.name,
type: item.type,
id: item.sha,
path: `${path}/${item.name}`,
size: item.blob ? item.blob.size : 0
}];
}
return acc;
}, []);
return allFiles;
}
async listFiles(path, {
repoURL = this.repoURL,
branch = this.branch,
depth = 1
} = {}) {
const {
owner,
name
} = this.getOwnerAndNameFromRepoUrl(repoURL);
const folder = (0, _trim2.default)(path, '/');
const {
data
} = await this.query({
query: queries.files(depth),
variables: {
owner,
name,
expression: `${branch}:${folder}`
}
});
if (data.repository.object) {
const allFiles = this.getAllFiles(data.repository.object.entries, folder);
return allFiles;
} else {
return [];
}
}
getBranchQualifiedName(branch) {
return `refs/heads/${branch}`;
}
getBranchQuery(branch, owner, name) {
return {
query: queries.branch,
variables: {
owner,
name,
qualifiedName: this.getBranchQualifiedName(branch)
}
};
}
async getDefaultBranch() {
const {
data
} = await this.query(_objectSpread({}, this.getBranchQuery(this.branch, this.originRepoOwner, this.originRepoName)));
return data.repository.branch;
}
async getBranch(branch) {
const {
data
} = await this.query(_objectSpread(_objectSpread({}, this.getBranchQuery(branch, this.repoOwner, this.repoName)), {}, {
fetchPolicy: CACHE_FIRST
}));
if (!data.repository.branch) {
throw new _decapCmsLibUtil.APIError('Branch not found', 404, _API.API_NAME);
}
return data.repository.branch;
}
async patchRef(type, name, sha, opts = {}) {
if (type !== 'heads') {
return super.patchRef(type, name, sha, opts);
}
const force = opts.force || false;
const branch = await this.getBranch(name);
const {
data
} = await this.mutate({
mutation: mutations.updateBranch,
variables: {
input: {
oid: sha,
refId: branch.id,
force
}
}
});
return data.updateRef.branch;
}
async deleteBranch(branchName) {
const branch = await this.getBranch(branchName);
const {
data
} = await this.mutate({
mutation: mutations.deleteBranch,
variables: {
deleteRefInput: {
refId: branch.id
}
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
update: store => store.data.delete((0, _apolloCacheInmemory.defaultDataIdFromObject)(branch))
});
return data.deleteRef;
}
getPullRequestQuery(number) {
const {
originRepoOwner: owner,
originRepoName: name
} = this;
return {
query: queries.pullRequest,
variables: {
owner,
name,
number
}
};
}
async getPullRequest(number) {
const {
data
} = await this.query(_objectSpread(_objectSpread({}, this.getPullRequestQuery(number)), {}, {
fetchPolicy: CACHE_FIRST
}));
// https://developer.github.com/v4/enum/pullrequeststate/
// GraphQL state: [CLOSED, MERGED, OPEN]
// REST API state: [closed, open]
const state = data.repository.pullRequest.state === 'OPEN' ? _API.PullRequestState.Open : _API.PullRequestState.Closed;
return _objectSpread(_objectSpread({}, data.repository.pullRequest), {}, {
state
});
}
getPullRequestAndBranchQuery(branch, number) {
const {
repoOwner: owner,
repoName: name
} = this;
const {
originRepoOwner,
originRepoName
} = this;
return {
query: queries.pullRequestAndBranch,
variables: {
owner,
name,
originRepoOwner,
originRepoName,
number,
qualifiedName: this.getBranchQualifiedName(branch)
}
};
}
async getPullRequestAndBranch(branch, number) {
const {
data
} = await this.query(_objectSpread(_objectSpread({}, this.getPullRequestAndBranchQuery(branch, number)), {}, {
fetchPolicy: CACHE_FIRST
}));
const {
repository,
origin
} = data;
return {
branch: repository.branch,
pullRequest: origin.pullRequest
};
}
async openPR(number) {
const pullRequest = await this.getPullRequest(number);
const {
data
} = await this.mutate({
mutation: mutations.reopenPullRequest,
variables: {
reopenPullRequestInput: {
pullRequestId: pullRequest.id
}
},
update: (store, {
data: mutationResult
}) => {
const {
pullRequest
} = mutationResult.reopenPullRequest;
const pullRequestData = {
repository: _objectSpread(_objectSpread({}, pullRequest.repository), {}, {
pullRequest
})
};
store.writeQuery(_objectSpread(_objectSpread({}, this.getPullRequestQuery(pullRequest.number)), {}, {
data: pullRequestData
}));
}
});
return data.reopenPullRequest;
}
async closePR(number) {
const pullRequest = await this.getPullRequest(number);
const {
data
} = await this.mutate({
mutation: mutations.closePullRequest,
variables: {
closePullRequestInput: {
pullRequestId: pullRequest.id
}
},
update: (store, {
data: mutationResult
}) => {
const {
pullRequest
} = mutationResult.closePullRequest;
const pullRequestData = {
repository: _objectSpread(_objectSpread({}, pullRequest.repository), {}, {
pullRequest
})
};
store.writeQuery(_objectSpread(_objectSpread({}, this.getPullRequestQuery(pullRequest.number)), {}, {
data: pullRequestData
}));
}
});
return data.closePullRequest;
}
async deleteUnpublishedEntry(collectionName, slug) {
try {
const contentKey = this.generateContentKey(collectionName, slug);
const branchName = (0, _decapCmsLibUtil.branchFromContentKey)(contentKey);
const pr = await this.getBranchPullRequest(branchName);
if (pr.number !== _API.MOCK_PULL_REQUEST) {
const {
branch,
pullRequest
} = await this.getPullRequestAndBranch(branchName, pr.number);
const {
data
} = await this.mutate({
mutation: mutations.closePullRequestAndDeleteBranch,
variables: {
deleteRefInput: {
refId: branch.id
},
closePullRequestInput: {
pullRequestId: pullRequest.id
}
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
update: store => {
store.data.delete((0, _apolloCacheInmemory.defaultDataIdFromObject)(branch));
store.data.delete((0, _apolloCacheInmemory.defaultDataIdFromObject)(pullRequest));
}
});
return data.closePullRequest;
} else {
return await this.deleteBranch(branchName);
}
} catch (e) {
const {
graphQLErrors
} = e;
if (graphQLErrors && graphQLErrors.length > 0) {
const branchNotFound = graphQLErrors.some(e => e.type === 'NOT_FOUND');
if (branchNotFound) {
return;
}
}
throw e;
}
}
async createPR(title, head) {
const [repository, headReference] = await Promise.all([this.getRepository(this.originRepoOwner, this.originRepoName), this.useOpenAuthoring ? `${(await this.user()).login}:${head}` : head]);
const {
data
} = await this.mutate({
mutation: mutations.createPullRequest,
variables: {
createPullRequestInput: {
baseRefName: this.branch,
body: _decapCmsLibUtil.DEFAULT_PR_BODY,
title,
headRefName: headReference,
repositoryId: repository.id
}
},
update: (store, {
data: mutationResult
}) => {
const {
pullRequest
} = mutationResult.createPullRequest;
const pullRequestData = {
repository: _objectSpread(_objectSpread({}, pullRequest.repository), {}, {
pullRequest
})
};
store.writeQuery(_objectSpread(_objectSpread({}, this.getPullRequestQuery(pullRequest.number)), {}, {
data: pullRequestData
}));
}
});
const {
pullRequest
} = data.createPullRequest;
return _objectSpread(_objectSpread({}, pullRequest), {}, {
head: {
sha: pullRequest.headRefOid
}
});
}
async createBranch(branchName, sha) {
const owner = this.repoOwner;
const name = this.repoName;
const repository = await this.getRepository(owner, name);
const {
data
} = await this.mutate({
mutation: mutations.createBranch,
variables: {
createRefInput: {
name: this.getBranchQualifiedName(branchName),
oid: sha,
repositoryId: repository.id
}
},
update: (store, {
data: mutationResult
}) => {
const {
branch
} = mutationResult.createRef;
const branchData = {
repository: _objectSpread(_objectSpread({}, branch.repository), {}, {
branch
})
};
store.writeQuery(_objectSpread(_objectSpread({}, this.getBranchQuery(branchName, owner, name)), {}, {
data: branchData
}));
}
});
const {
branch
} = data.createRef;
return _objectSpread(_objectSpread({}, branch), {}, {
ref: `${branch.prefix}${branch.name}`
});
}
async createBranchAndPullRequest(branchName, sha, title) {
const owner = this.originRepoOwner;
const name = this.originRepoName;
const repository = await this.getRepository(owner, name);
const {
data
} = await this.mutate({
mutation: mutations.createBranchAndPullRequest,
variables: {
createRefInput: {
name: this.getBranchQualifiedName(branchName),
oid: sha,
repositoryId: repository.id
},
createPullRequestInput: {
baseRefName: this.branch,
body: _decapCmsLibUtil.DEFAULT_PR_BODY,
title,
headRefName: branchName,
repositoryId: repository.id
}
},
update: (store, {
data: mutationResult
}) => {
const {
branch
} = mutationResult.createRef;
const {
pullRequest
} = mutationResult.createPullRequest;
const branchData = {
repository: _objectSpread(_objectSpread({}, branch.repository), {}, {
branch
})
};
const pullRequestData = {
repository: _objectSpread(_objectSpread({}, pullRequest.repository), {}, {
branch
}),
origin: _objectSpread(_objectSpread({}, pullRequest.repository), {}, {
pullRequest
})
};
store.writeQuery(_objectSpread(_objectSpread({}, this.getBranchQuery(branchName, owner, name)), {}, {
data: branchData
}));
store.writeQuery(_objectSpread(_objectSpread({}, this.getPullRequestAndBranchQuery(branchName, pullRequest.number)), {}, {
data: pullRequestData
}));
}
});
const {
pullRequest
} = data.createPullRequest;
return transformPullRequest(pullRequest);
}
async getFileSha(path, {
repoURL = this.repoURL,
branch = this.branch
} = {}) {
const {
owner,
name
} = this.getOwnerAndNameFromRepoUrl(repoURL);
const {
data
} = await this.query({
query: queries.fileSha,
variables: {
owner,
name,
expression: `${branch}:${path}`
}
});
if (data.repository.file) {
return data.repository.file.sha;
}
throw new _decapCmsLibUtil.APIError('Not Found', 404, _API.API_NAME);
}
}
exports.default = GraphQLAPI;

View File

@@ -0,0 +1,947 @@
"use strict";
module.exports = {
"__schema": {
"types": [{
"kind": "INTERFACE",
"name": "Node",
"possibleTypes": [{
"name": "AddedToProjectEvent"
}, {
"name": "App"
}, {
"name": "AssignedEvent"
}, {
"name": "BaseRefChangedEvent"
}, {
"name": "BaseRefForcePushedEvent"
}, {
"name": "Blob"
}, {
"name": "Bot"
}, {
"name": "BranchProtectionRule"
}, {
"name": "ClosedEvent"
}, {
"name": "CodeOfConduct"
}, {
"name": "CommentDeletedEvent"
}, {
"name": "Commit"
}, {
"name": "CommitComment"
}, {
"name": "CommitCommentThread"
}, {
"name": "ConvertedNoteToIssueEvent"
}, {
"name": "CrossReferencedEvent"
}, {
"name": "DemilestonedEvent"
}, {
"name": "DeployKey"
}, {
"name": "DeployedEvent"
}, {
"name": "Deployment"
}, {
"name": "DeploymentEnvironmentChangedEvent"
}, {
"name": "DeploymentStatus"
}, {
"name": "ExternalIdentity"
}, {
"name": "Gist"
}, {
"name": "GistComment"
}, {
"name": "HeadRefDeletedEvent"
}, {
"name": "HeadRefForcePushedEvent"
}, {
"name": "HeadRefRestoredEvent"
}, {
"name": "Issue"
}, {
"name": "IssueComment"
}, {
"name": "Label"
}, {
"name": "LabeledEvent"
}, {
"name": "Language"
}, {
"name": "License"
}, {
"name": "LockedEvent"
}, {
"name": "Mannequin"
}, {
"name": "MarketplaceCategory"
}, {
"name": "MarketplaceListing"
}, {
"name": "MentionedEvent"
}, {
"name": "MergedEvent"
}, {
"name": "Milestone"
}, {
"name": "MilestonedEvent"
}, {
"name": "MovedColumnsInProjectEvent"
}, {
"name": "Organization"
}, {
"name": "OrganizationIdentityProvider"
}, {
"name": "OrganizationInvitation"
}, {
"name": "PinnedEvent"
}, {
"name": "Project"
}, {
"name": "ProjectCard"
}, {
"name": "ProjectColumn"
}, {
"name": "PublicKey"
}, {
"name": "PullRequest"
}, {
"name": "PullRequestCommit"
}, {
"name": "PullRequestCommitCommentThread"
}, {
"name": "PullRequestReview"
}, {
"name": "PullRequestReviewComment"
}, {
"name": "PullRequestReviewThread"
}, {
"name": "PushAllowance"
}, {
"name": "Reaction"
}, {
"name": "ReadyForReviewEvent"
}, {
"name": "Ref"
}, {
"name": "ReferencedEvent"
}, {
"name": "RegistryPackage"
}, {
"name": "RegistryPackageDependency"
}, {
"name": "RegistryPackageFile"
}, {
"name": "RegistryPackageTag"
}, {
"name": "RegistryPackageVersion"
}, {
"name": "Release"
}, {
"name": "ReleaseAsset"
}, {
"name": "RemovedFromProjectEvent"
}, {
"name": "RenamedTitleEvent"
}, {
"name": "ReopenedEvent"
}, {
"name": "Repository"
}, {
"name": "RepositoryInvitation"
}, {
"name": "RepositoryTopic"
}, {
"name": "ReviewDismissalAllowance"
}, {
"name": "ReviewDismissedEvent"
}, {
"name": "ReviewRequest"
}, {
"name": "ReviewRequestRemovedEvent"
}, {
"name": "ReviewRequestedEvent"
}, {
"name": "SavedReply"
}, {
"name": "SecurityAdvisory"
}, {
"name": "SponsorsListing"
}, {
"name": "Sponsorship"
}, {
"name": "Status"
}, {
"name": "StatusContext"
}, {
"name": "SubscribedEvent"
}, {
"name": "Tag"
}, {
"name": "Team"
}, {
"name": "Topic"
}, {
"name": "TransferredEvent"
}, {
"name": "Tree"
}, {
"name": "UnassignedEvent"
}, {
"name": "UnlabeledEvent"
}, {
"name": "UnlockedEvent"
}, {
"name": "UnpinnedEvent"
}, {
"name": "UnsubscribedEvent"
}, {
"name": "User"
}, {
"name": "UserBlockedEvent"
}, {
"name": "UserContentEdit"
}, {
"name": "UserStatus"
}]
}, {
"kind": "INTERFACE",
"name": "UniformResourceLocatable",
"possibleTypes": [{
"name": "Bot"
}, {
"name": "ClosedEvent"
}, {
"name": "Commit"
}, {
"name": "CrossReferencedEvent"
}, {
"name": "Gist"
}, {
"name": "Issue"
}, {
"name": "Mannequin"
}, {
"name": "MergedEvent"
}, {
"name": "Milestone"
}, {
"name": "Organization"
}, {
"name": "PullRequest"
}, {
"name": "PullRequestCommit"
}, {
"name": "ReadyForReviewEvent"
}, {
"name": "Release"
}, {
"name": "Repository"
}, {
"name": "RepositoryTopic"
}, {
"name": "ReviewDismissedEvent"
}, {
"name": "User"
}]
}, {
"kind": "INTERFACE",
"name": "Actor",
"possibleTypes": [{
"name": "Bot"
}, {
"name": "Mannequin"
}, {
"name": "Organization"
}, {
"name": "User"
}]
}, {
"kind": "INTERFACE",
"name": "RegistryPackageOwner",
"possibleTypes": [{
"name": "Organization"
}, {
"name": "Repository"
}, {
"name": "User"
}]
}, {
"kind": "INTERFACE",
"name": "ProjectOwner",
"possibleTypes": [{
"name": "Organization"
}, {
"name": "Repository"
}, {
"name": "User"
}]
}, {
"kind": "INTERFACE",
"name": "Closable",
"possibleTypes": [{
"name": "Issue"
}, {
"name": "Milestone"
}, {
"name": "Project"
}, {
"name": "PullRequest"
}]
}, {
"kind": "INTERFACE",
"name": "Updatable",
"possibleTypes": [{
"name": "CommitComment"
}, {
"name": "GistComment"
}, {
"name": "Issue"
}, {
"name": "IssueComment"
}, {
"name": "Project"
}, {
"name": "PullRequest"
}, {
"name": "PullRequestReview"
}, {
"name": "PullRequestReviewComment"
}]
}, {
"kind": "UNION",
"name": "ProjectCardItem",
"possibleTypes": [{
"name": "Issue"
}, {
"name": "PullRequest"
}]
}, {
"kind": "INTERFACE",
"name": "Assignable",
"possibleTypes": [{
"name": "Issue"
}, {
"name": "PullRequest"
}]
}, {
"kind": "INTERFACE",
"name": "Comment",
"possibleTypes": [{
"name": "CommitComment"
}, {
"name": "GistComment"
}, {
"name": "Issue"
}, {
"name": "IssueComment"
}, {
"name": "PullRequest"
}, {
"name": "PullRequestReview"
}, {
"name": "PullRequestReviewComment"
}]
}, {
"kind": "INTERFACE",
"name": "UpdatableComment",
"possibleTypes": [{
"name": "CommitComment"
}, {
"name": "GistComment"
}, {
"name": "Issue"
}, {
"name": "IssueComment"
}, {
"name": "PullRequest"
}, {
"name": "PullRequestReview"
}, {
"name": "PullRequestReviewComment"
}]
}, {
"kind": "INTERFACE",
"name": "Labelable",
"possibleTypes": [{
"name": "Issue"
}, {
"name": "PullRequest"
}]
}, {
"kind": "INTERFACE",
"name": "Lockable",
"possibleTypes": [{
"name": "Issue"
}, {
"name": "PullRequest"
}]
}, {
"kind": "INTERFACE",
"name": "RegistryPackageSearch",
"possibleTypes": [{
"name": "Organization"
}, {
"name": "User"
}]
}, {
"kind": "INTERFACE",
"name": "RepositoryOwner",
"possibleTypes": [{
"name": "Organization"
}, {
"name": "User"
}]
}, {
"kind": "INTERFACE",
"name": "MemberStatusable",
"possibleTypes": [{
"name": "Organization"
}, {
"name": "Team"
}]
}, {
"kind": "INTERFACE",
"name": "ProfileOwner",
"possibleTypes": [{
"name": "Organization"
}, {
"name": "User"
}]
}, {
"kind": "UNION",
"name": "PinnableItem",
"possibleTypes": [{
"name": "Gist"
}, {
"name": "Repository"
}]
}, {
"kind": "INTERFACE",
"name": "Starrable",
"possibleTypes": [{
"name": "Gist"
}, {
"name": "Repository"
}, {
"name": "Topic"
}]
}, {
"kind": "INTERFACE",
"name": "RepositoryInfo",
"possibleTypes": [{
"name": "Repository"
}]
}, {
"kind": "INTERFACE",
"name": "GitObject",
"possibleTypes": [{
"name": "Blob"
}, {
"name": "Commit"
}, {
"name": "Tag"
}, {
"name": "Tree"
}]
}, {
"kind": "INTERFACE",
"name": "RepositoryNode",
"possibleTypes": [{
"name": "CommitComment"
}, {
"name": "CommitCommentThread"
}, {
"name": "Issue"
}, {
"name": "IssueComment"
}, {
"name": "PullRequest"
}, {
"name": "PullRequestCommitCommentThread"
}, {
"name": "PullRequestReview"
}, {
"name": "PullRequestReviewComment"
}]
}, {
"kind": "INTERFACE",
"name": "Subscribable",
"possibleTypes": [{
"name": "Commit"
}, {
"name": "Issue"
}, {
"name": "PullRequest"
}, {
"name": "Repository"
}, {
"name": "Team"
}]
}, {
"kind": "INTERFACE",
"name": "Deletable",
"possibleTypes": [{
"name": "CommitComment"
}, {
"name": "GistComment"
}, {
"name": "IssueComment"
}, {
"name": "PullRequestReview"
}, {
"name": "PullRequestReviewComment"
}]
}, {
"kind": "INTERFACE",
"name": "Reactable",
"possibleTypes": [{
"name": "CommitComment"
}, {
"name": "Issue"
}, {
"name": "IssueComment"
}, {
"name": "PullRequest"
}, {
"name": "PullRequestReview"
}, {
"name": "PullRequestReviewComment"
}]
}, {
"kind": "INTERFACE",
"name": "GitSignature",
"possibleTypes": [{
"name": "GpgSignature"
}, {
"name": "SmimeSignature"
}, {
"name": "UnknownSignature"
}]
}, {
"kind": "UNION",
"name": "RequestedReviewer",
"possibleTypes": [{
"name": "User"
}, {
"name": "Team"
}, {
"name": "Mannequin"
}]
}, {
"kind": "UNION",
"name": "PullRequestTimelineItem",
"possibleTypes": [{
"name": "Commit"
}, {
"name": "CommitCommentThread"
}, {
"name": "PullRequestReview"
}, {
"name": "PullRequestReviewThread"
}, {
"name": "PullRequestReviewComment"
}, {
"name": "IssueComment"
}, {
"name": "ClosedEvent"
}, {
"name": "ReopenedEvent"
}, {
"name": "SubscribedEvent"
}, {
"name": "UnsubscribedEvent"
}, {
"name": "MergedEvent"
}, {
"name": "ReferencedEvent"
}, {
"name": "CrossReferencedEvent"
}, {
"name": "AssignedEvent"
}, {
"name": "UnassignedEvent"
}, {
"name": "LabeledEvent"
}, {
"name": "UnlabeledEvent"
}, {
"name": "MilestonedEvent"
}, {
"name": "DemilestonedEvent"
}, {
"name": "RenamedTitleEvent"
}, {
"name": "LockedEvent"
}, {
"name": "UnlockedEvent"
}, {
"name": "DeployedEvent"
}, {
"name": "DeploymentEnvironmentChangedEvent"
}, {
"name": "HeadRefDeletedEvent"
}, {
"name": "HeadRefRestoredEvent"
}, {
"name": "HeadRefForcePushedEvent"
}, {
"name": "BaseRefForcePushedEvent"
}, {
"name": "ReviewRequestedEvent"
}, {
"name": "ReviewRequestRemovedEvent"
}, {
"name": "ReviewDismissedEvent"
}, {
"name": "UserBlockedEvent"
}]
}, {
"kind": "UNION",
"name": "Closer",
"possibleTypes": [{
"name": "Commit"
}, {
"name": "PullRequest"
}]
}, {
"kind": "UNION",
"name": "ReferencedSubject",
"possibleTypes": [{
"name": "Issue"
}, {
"name": "PullRequest"
}]
}, {
"kind": "UNION",
"name": "Assignee",
"possibleTypes": [{
"name": "Bot"
}, {
"name": "Mannequin"
}, {
"name": "Organization"
}, {
"name": "User"
}]
}, {
"kind": "UNION",
"name": "MilestoneItem",
"possibleTypes": [{
"name": "Issue"
}, {
"name": "PullRequest"
}]
}, {
"kind": "UNION",
"name": "RenamedTitleSubject",
"possibleTypes": [{
"name": "Issue"
}, {
"name": "PullRequest"
}]
}, {
"kind": "UNION",
"name": "PullRequestTimelineItems",
"possibleTypes": [{
"name": "PullRequestCommit"
}, {
"name": "PullRequestCommitCommentThread"
}, {
"name": "PullRequestReview"
}, {
"name": "PullRequestReviewThread"
}, {
"name": "PullRequestRevisionMarker"
}, {
"name": "BaseRefChangedEvent"
}, {
"name": "BaseRefForcePushedEvent"
}, {
"name": "DeployedEvent"
}, {
"name": "DeploymentEnvironmentChangedEvent"
}, {
"name": "HeadRefDeletedEvent"
}, {
"name": "HeadRefForcePushedEvent"
}, {
"name": "HeadRefRestoredEvent"
}, {
"name": "MergedEvent"
}, {
"name": "ReviewDismissedEvent"
}, {
"name": "ReviewRequestedEvent"
}, {
"name": "ReviewRequestRemovedEvent"
}, {
"name": "ReadyForReviewEvent"
}, {
"name": "IssueComment"
}, {
"name": "CrossReferencedEvent"
}, {
"name": "AddedToProjectEvent"
}, {
"name": "AssignedEvent"
}, {
"name": "ClosedEvent"
}, {
"name": "CommentDeletedEvent"
}, {
"name": "ConvertedNoteToIssueEvent"
}, {
"name": "DemilestonedEvent"
}, {
"name": "LabeledEvent"
}, {
"name": "LockedEvent"
}, {
"name": "MentionedEvent"
}, {
"name": "MilestonedEvent"
}, {
"name": "MovedColumnsInProjectEvent"
}, {
"name": "PinnedEvent"
}, {
"name": "ReferencedEvent"
}, {
"name": "RemovedFromProjectEvent"
}, {
"name": "RenamedTitleEvent"
}, {
"name": "ReopenedEvent"
}, {
"name": "SubscribedEvent"
}, {
"name": "TransferredEvent"
}, {
"name": "UnassignedEvent"
}, {
"name": "UnlabeledEvent"
}, {
"name": "UnlockedEvent"
}, {
"name": "UserBlockedEvent"
}, {
"name": "UnpinnedEvent"
}, {
"name": "UnsubscribedEvent"
}]
}, {
"kind": "UNION",
"name": "IssueOrPullRequest",
"possibleTypes": [{
"name": "Issue"
}, {
"name": "PullRequest"
}]
}, {
"kind": "UNION",
"name": "IssueTimelineItem",
"possibleTypes": [{
"name": "Commit"
}, {
"name": "IssueComment"
}, {
"name": "CrossReferencedEvent"
}, {
"name": "ClosedEvent"
}, {
"name": "ReopenedEvent"
}, {
"name": "SubscribedEvent"
}, {
"name": "UnsubscribedEvent"
}, {
"name": "ReferencedEvent"
}, {
"name": "AssignedEvent"
}, {
"name": "UnassignedEvent"
}, {
"name": "LabeledEvent"
}, {
"name": "UnlabeledEvent"
}, {
"name": "UserBlockedEvent"
}, {
"name": "MilestonedEvent"
}, {
"name": "DemilestonedEvent"
}, {
"name": "RenamedTitleEvent"
}, {
"name": "LockedEvent"
}, {
"name": "UnlockedEvent"
}, {
"name": "TransferredEvent"
}]
}, {
"kind": "UNION",
"name": "IssueTimelineItems",
"possibleTypes": [{
"name": "IssueComment"
}, {
"name": "CrossReferencedEvent"
}, {
"name": "AddedToProjectEvent"
}, {
"name": "AssignedEvent"
}, {
"name": "ClosedEvent"
}, {
"name": "CommentDeletedEvent"
}, {
"name": "ConvertedNoteToIssueEvent"
}, {
"name": "DemilestonedEvent"
}, {
"name": "LabeledEvent"
}, {
"name": "LockedEvent"
}, {
"name": "MentionedEvent"
}, {
"name": "MilestonedEvent"
}, {
"name": "MovedColumnsInProjectEvent"
}, {
"name": "PinnedEvent"
}, {
"name": "ReferencedEvent"
}, {
"name": "RemovedFromProjectEvent"
}, {
"name": "RenamedTitleEvent"
}, {
"name": "ReopenedEvent"
}, {
"name": "SubscribedEvent"
}, {
"name": "TransferredEvent"
}, {
"name": "UnassignedEvent"
}, {
"name": "UnlabeledEvent"
}, {
"name": "UnlockedEvent"
}, {
"name": "UserBlockedEvent"
}, {
"name": "UnpinnedEvent"
}, {
"name": "UnsubscribedEvent"
}]
}, {
"kind": "UNION",
"name": "ReviewDismissalAllowanceActor",
"possibleTypes": [{
"name": "User"
}, {
"name": "Team"
}]
}, {
"kind": "UNION",
"name": "PushAllowanceActor",
"possibleTypes": [{
"name": "User"
}, {
"name": "Team"
}]
}, {
"kind": "UNION",
"name": "PermissionGranter",
"possibleTypes": [{
"name": "Organization"
}, {
"name": "Repository"
}, {
"name": "Team"
}]
}, {
"kind": "INTERFACE",
"name": "Sponsorable",
"possibleTypes": [{
"name": "User"
}]
}, {
"kind": "INTERFACE",
"name": "Contribution",
"possibleTypes": [{
"name": "CreatedCommitContribution"
}, {
"name": "CreatedIssueContribution"
}, {
"name": "CreatedPullRequestContribution"
}, {
"name": "CreatedPullRequestReviewContribution"
}, {
"name": "CreatedRepositoryContribution"
}, {
"name": "JoinedGitHubContribution"
}, {
"name": "RestrictedContribution"
}]
}, {
"kind": "UNION",
"name": "CreatedRepositoryOrRestrictedContribution",
"possibleTypes": [{
"name": "CreatedRepositoryContribution"
}, {
"name": "RestrictedContribution"
}]
}, {
"kind": "UNION",
"name": "CreatedIssueOrRestrictedContribution",
"possibleTypes": [{
"name": "CreatedIssueContribution"
}, {
"name": "RestrictedContribution"
}]
}, {
"kind": "UNION",
"name": "CreatedPullRequestOrRestrictedContribution",
"possibleTypes": [{
"name": "CreatedPullRequestContribution"
}, {
"name": "RestrictedContribution"
}]
}, {
"kind": "UNION",
"name": "SearchResultItem",
"possibleTypes": [{
"name": "Issue"
}, {
"name": "PullRequest"
}, {
"name": "Repository"
}, {
"name": "User"
}, {
"name": "Organization"
}, {
"name": "MarketplaceListing"
}, {
"name": "App"
}]
}, {
"kind": "UNION",
"name": "CollectionItemContent",
"possibleTypes": [{
"name": "Repository"
}, {
"name": "Organization"
}, {
"name": "User"
}]
}]
}
};

View File

@@ -0,0 +1,91 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.treeEntry = exports.repository = exports.pullRequest = exports.object = exports.fileEntry = exports.branch = exports.blobWithText = void 0;
var _graphqlTag = require("graphql-tag");
const repository = exports.repository = (0, _graphqlTag.gql)`
fragment RepositoryParts on Repository {
id
isFork
}
`;
const blobWithText = exports.blobWithText = (0, _graphqlTag.gql)`
fragment BlobWithTextParts on Blob {
id
text
is_binary: isBinary
}
`;
const object = exports.object = (0, _graphqlTag.gql)`
fragment ObjectParts on GitObject {
id
sha: oid
}
`;
const branch = exports.branch = (0, _graphqlTag.gql)`
fragment BranchParts on Ref {
commit: target {
...ObjectParts
}
id
name
prefix
repository {
...RepositoryParts
}
}
${object}
${repository}
`;
const pullRequest = exports.pullRequest = (0, _graphqlTag.gql)`
fragment PullRequestParts on PullRequest {
id
baseRefName
baseRefOid
body
headRefName
headRefOid
number
state
title
merged_at: mergedAt
updated_at: updatedAt
user: author {
login
... on User {
name
}
}
repository {
...RepositoryParts
}
labels(last: 100) {
nodes {
name
}
}
}
${repository}
`;
const treeEntry = exports.treeEntry = (0, _graphqlTag.gql)`
fragment TreeEntryParts on TreeEntry {
path: name
sha: oid
type
mode
}
`;
const fileEntry = exports.fileEntry = (0, _graphqlTag.gql)`
fragment FileEntryParts on TreeEntry {
name
sha: oid
type
blob: object {
... on Blob {
size: byteSize
}
}
}
`;

View File

@@ -0,0 +1,633 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var React = _interopRequireWildcard(require("react"));
var _semaphore = _interopRequireDefault(require("semaphore"));
var _trimStart = _interopRequireDefault(require("lodash/trimStart"));
var _commonTags = require("common-tags");
var _decapCmsLibUtil = require("decap-cms-lib-util");
var _AuthenticationPage = _interopRequireDefault(require("./AuthenticationPage"));
var _API = _interopRequireWildcard(require("./API"));
var _GraphQLAPI = _interopRequireDefault(require("./GraphQLAPI"));
var _react2 = require("@emotion/react");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
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 MAX_CONCURRENT_DOWNLOADS = 10;
const {
fetchWithTimeout: fetch
} = _decapCmsLibUtil.unsentRequest;
const STATUS_PAGE = 'https://www.githubstatus.com';
const GITHUB_STATUS_ENDPOINT = `${STATUS_PAGE}/api/v2/components.json`;
const GITHUB_OPERATIONAL_UNITS = ['API Requests', 'Issues, Pull Requests, Projects'];
class GitHub {
constructor(config, options = {}) {
var _config$backend$branc;
_defineProperty(this, "lock", void 0);
_defineProperty(this, "api", void 0);
_defineProperty(this, "options", void 0);
_defineProperty(this, "originRepo", void 0);
_defineProperty(this, "isBranchConfigured", void 0);
_defineProperty(this, "repo", void 0);
_defineProperty(this, "openAuthoringEnabled", void 0);
_defineProperty(this, "useOpenAuthoring", void 0);
_defineProperty(this, "alwaysForkEnabled", void 0);
_defineProperty(this, "branch", void 0);
_defineProperty(this, "apiRoot", void 0);
_defineProperty(this, "mediaFolder", void 0);
_defineProperty(this, "previewContext", void 0);
_defineProperty(this, "token", void 0);
_defineProperty(this, "tokenKeyword", void 0);
_defineProperty(this, "squashMerges", void 0);
_defineProperty(this, "cmsLabelPrefix", void 0);
_defineProperty(this, "useGraphql", void 0);
_defineProperty(this, "baseUrl", void 0);
_defineProperty(this, "bypassWriteAccessCheckForAppTokens", false);
_defineProperty(this, "_currentUserPromise", void 0);
_defineProperty(this, "_userIsOriginMaintainerPromises", void 0);
_defineProperty(this, "_mediaDisplayURLSem", void 0);
_defineProperty(this, "getCursorAndFiles", (files, page) => {
const pageSize = 20;
const count = files.length;
const pageCount = Math.ceil(files.length / pageSize);
const actions = [];
if (page > 1) {
actions.push('prev');
actions.push('first');
}
if (page < pageCount) {
actions.push('next');
actions.push('last');
}
const cursor = _decapCmsLibUtil.Cursor.create({
actions,
meta: {
page,
count,
pageSize,
pageCount
},
data: {
files
}
});
const pageFiles = files.slice((page - 1) * pageSize, page * pageSize);
return {
cursor,
files: pageFiles
};
});
this.options = _objectSpread({
proxied: false,
API: null,
initialWorkflowStatus: ''
}, options);
if (!this.options.proxied && (config.backend.repo === null || config.backend.repo === undefined)) {
throw new Error('The GitHub backend needs a "repo" in the backend configuration.');
}
this.api = this.options.API || null;
this.isBranchConfigured = config.backend.branch ? true : false;
this.openAuthoringEnabled = config.backend.open_authoring || false;
if (this.openAuthoringEnabled) {
if (!this.options.useWorkflow) {
throw new Error('backend.open_authoring is true but publish_mode is not set to editorial_workflow.');
}
this.originRepo = config.backend.repo || '';
} else {
this.repo = this.originRepo = config.backend.repo || '';
}
this.alwaysForkEnabled = config.backend.always_fork || false;
this.branch = ((_config$backend$branc = config.backend.branch) === null || _config$backend$branc === void 0 ? void 0 : _config$backend$branc.trim()) || 'master';
this.apiRoot = config.backend.api_root || 'https://api.github.com';
this.token = '';
this.tokenKeyword = 'token';
this.baseUrl = config.backend.base_url;
this.squashMerges = config.backend.squash_merges || false;
this.cmsLabelPrefix = config.backend.cms_label_prefix || '';
this.useGraphql = config.backend.use_graphql || false;
this.mediaFolder = config.media_folder;
this.previewContext = config.backend.preview_context || '';
this.lock = (0, _decapCmsLibUtil.asyncLock)();
}
isGitBackend() {
return true;
}
async status() {
const api = await fetch(GITHUB_STATUS_ENDPOINT).then(res => res.json()).then(res => {
return res['components'].filter(statusComponent => GITHUB_OPERATIONAL_UNITS.includes(statusComponent.name)).every(statusComponent => statusComponent.status === 'operational');
}).catch(e => {
console.warn('Failed getting GitHub status', e);
return true;
});
let auth = false;
// no need to check auth if api is down
if (api) {
var _this$api, _this$token;
auth = (await ((_this$api = this.api) === null || _this$api === void 0 ? void 0 : _this$api.getUser({
token: (_this$token = this.token) !== null && _this$token !== void 0 ? _this$token : ''
}).then(user => !!user).catch(e => {
console.warn('Failed getting GitHub user', e);
return false;
}))) || false;
}
return {
auth: {
status: auth
},
api: {
status: api,
statusPage: STATUS_PAGE
}
};
}
authComponent() {
const wrappedAuthenticationPage = props => (0, _react2.jsx)(_AuthenticationPage.default, _extends({}, props, {
backend: this
}));
wrappedAuthenticationPage.displayName = 'AuthenticationPage';
return wrappedAuthenticationPage;
}
restoreUser(user) {
return this.openAuthoringEnabled ? this.authenticateWithFork({
userData: user,
getPermissionToFork: () => true
}).then(() => this.authenticate(user)) : this.authenticate(user);
}
async pollUntilForkExists({
repo,
token
}) {
const pollDelay = 250; // milliseconds
let repoExists = false;
while (!repoExists) {
repoExists = await fetch(`${this.apiRoot}/repos/${repo}`, {
headers: {
Authorization: `${this.tokenKeyword} ${token}`
}
}).then(() => true).catch(err => {
if (err && err.status === 404) {
console.log('This 404 was expected and handled appropriately.');
return false;
} else {
return Promise.reject(err);
}
});
// wait between polls
if (!repoExists) {
await new Promise(resolve => setTimeout(resolve, pollDelay));
}
}
return Promise.resolve();
}
async currentUser({
token
}) {
if (!this._currentUserPromise) {
this._currentUserPromise = fetch(`${this.apiRoot}/user`, {
headers: {
Authorization: `${this.tokenKeyword} ${token}`
}
}).then(res => res.json());
}
return this._currentUserPromise;
}
async userIsOriginMaintainer({
username: usernameArg,
token
}) {
const username = usernameArg || (await this.currentUser({
token
})).login;
this._userIsOriginMaintainerPromises = this._userIsOriginMaintainerPromises || {};
if (!this._userIsOriginMaintainerPromises[username]) {
this._userIsOriginMaintainerPromises[username] = fetch(`${this.apiRoot}/repos/${this.originRepo}/collaborators/${username}/permission`, {
headers: {
Authorization: `${this.tokenKeyword} ${token}`
}
}).then(res => res.json()).then(({
permission
}) => permission === 'admin' || permission === 'write');
}
return this._userIsOriginMaintainerPromises[username];
}
async forkExists({
token
}) {
try {
const currentUser = await this.currentUser({
token
});
const repoName = this.originRepo.split('/')[1];
const repo = await fetch(`${this.apiRoot}/repos/${currentUser.login}/${repoName}`, {
method: 'GET',
headers: {
Authorization: `${this.tokenKeyword} ${token}`
}
}).then(res => res.json());
// https://developer.github.com/v3/repos/#get
// The parent and source objects are present when the repository is a fork.
// parent is the repository this repository was forked from, source is the ultimate source for the network.
const forkExists = repo.fork === true && repo.parent && repo.parent.full_name.toLowerCase() === this.originRepo.toLowerCase();
return forkExists;
} catch {
return false;
}
}
async authenticateWithFork({
userData,
getPermissionToFork
}) {
if (!this.openAuthoringEnabled) {
throw new Error('Cannot authenticate with fork; Open Authoring is turned off.');
}
const token = userData.token;
// Origin maintainers should be able to use the CMS normally. If alwaysFork
// is enabled we always fork (and avoid the origin maintainer check)
if (!this.alwaysForkEnabled && (await this.userIsOriginMaintainer({
token
}))) {
this.repo = this.originRepo;
this.useOpenAuthoring = false;
return Promise.resolve();
}
// If a fork exists merge it with upstream
// otherwise create a new fork.
const currentUser = await this.currentUser({
token
});
const repoName = this.originRepo.split('/')[1];
this.repo = `${currentUser.login}/${repoName}`;
this.useOpenAuthoring = true;
if (await this.forkExists({
token
})) {
return fetch(`${this.apiRoot}/repos/${this.repo}/merge-upstream`, {
method: 'POST',
headers: {
Authorization: `${this.tokenKeyword} ${token}`
},
body: JSON.stringify({
branch: this.branch
})
});
} else {
await getPermissionToFork();
const fork = await fetch(`${this.apiRoot}/repos/${this.originRepo}/forks`, {
method: 'POST',
headers: {
Authorization: `${this.tokenKeyword} ${token}`
}
}).then(res => res.json());
return this.pollUntilForkExists({
repo: fork.full_name,
token
});
}
}
async authenticate(state) {
this.token = state.token;
// Query the default branch name when the `branch` property is missing
// in the config file
if (!this.isBranchConfigured) {
const repoInfo = await fetch(`${this.apiRoot}/repos/${this.originRepo}`, {
headers: {
Authorization: `token ${this.token}`
}
}).then(res => res.json()).catch(() => null);
if (repoInfo && repoInfo.default_branch) {
this.branch = repoInfo.default_branch;
}
}
const apiCtor = this.useGraphql ? _GraphQLAPI.default : _API.default;
this.api = new apiCtor({
token: this.token,
tokenKeyword: this.tokenKeyword,
branch: this.branch,
repo: this.repo,
originRepo: this.originRepo,
apiRoot: this.apiRoot,
squashMerges: this.squashMerges,
cmsLabelPrefix: this.cmsLabelPrefix,
useOpenAuthoring: this.useOpenAuthoring,
initialWorkflowStatus: this.options.initialWorkflowStatus,
baseUrl: this.baseUrl,
getUser: this.currentUser
});
const user = await this.api.user();
const isCollab = await this.api.hasWriteAccess().catch(error => {
error.message = (0, _commonTags.stripIndent)`
Repo "${this.repo}" not found.
Please ensure the repo information is spelled correctly.
If the repo is private, make sure you're logged into a GitHub account with access.
If your repo is under an organization, ensure the organization has granted access to Decap CMS.
`;
throw error;
});
// Unauthorized user
if (!isCollab && !this.bypassWriteAccessCheckForAppTokens) {
throw new Error('Your GitHub user account does not have access to this repo.');
}
// if (!this.isBranchConfigured) {
// const defaultBranchName = await this.api.getDefaultBranchName()
// if (defaultBranchName) {
// this.branch = defaultBranchName;
// }
// }
// Authorized user
return _objectSpread(_objectSpread({}, user), {}, {
token: state.token,
useOpenAuthoring: this.useOpenAuthoring
});
}
logout() {
this.token = null;
if (this.api && this.api.reset && typeof this.api.reset === 'function') {
return this.api.reset();
}
}
getToken() {
return Promise.resolve(this.token);
}
async entriesByFolder(folder, extension, depth) {
const repoURL = this.api.originRepoURL;
let cursor;
const listFiles = () => this.api.listFiles(folder, {
repoURL,
depth
}).then(files => {
const filtered = files.filter(file => (0, _decapCmsLibUtil.filterByExtension)(file, extension));
const result = this.getCursorAndFiles(filtered, 1);
cursor = result.cursor;
return result.files;
});
const readFile = (path, id) => this.api.readFile(path, id, {
repoURL
});
const files = await (0, _decapCmsLibUtil.entriesByFolder)(listFiles, readFile, this.api.readFileMetadata.bind(this.api), _API.API_NAME);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
files[_decapCmsLibUtil.CURSOR_COMPATIBILITY_SYMBOL] = cursor;
return files;
}
async allEntriesByFolder(folder, extension, depth, pathRegex) {
const repoURL = this.api.originRepoURL;
const listFiles = () => this.api.listFiles(folder, {
repoURL,
depth
}).then(files => files.filter(file => (!pathRegex || pathRegex.test(file.path)) && (0, _decapCmsLibUtil.filterByExtension)(file, extension)));
const readFile = (path, id) => {
return this.api.readFile(path, id, {
repoURL
});
};
const files = await (0, _decapCmsLibUtil.entriesByFolder)(listFiles, readFile, this.api.readFileMetadata.bind(this.api), _API.API_NAME);
return files;
}
entriesByFiles(files) {
const repoURL = this.useOpenAuthoring ? this.api.originRepoURL : this.api.repoURL;
const readFile = (path, id) => this.api.readFile(path, id, {
repoURL
}).catch(() => '');
return (0, _decapCmsLibUtil.entriesByFiles)(files, readFile, this.api.readFileMetadata.bind(this.api), _API.API_NAME);
}
// Fetches a single entry.
getEntry(path) {
const repoURL = this.api.originRepoURL;
return this.api.readFile(path, null, {
repoURL
}).then(data => ({
file: {
path,
id: null
},
data: data
})).catch(() => ({
file: {
path,
id: null
},
data: ''
}));
}
getMedia(mediaFolder = this.mediaFolder) {
return this.api.listFiles(mediaFolder).then(files => files.map(({
id,
name,
size,
path
}) => {
// load media using getMediaDisplayURL to avoid token expiration with GitHub raw content urls
// for private repositories
return {
id,
name,
size,
displayURL: {
id,
path
},
path
};
}));
}
async getMediaFile(path) {
const blob = await (0, _decapCmsLibUtil.getMediaAsBlob)(path, null, this.api.readFile.bind(this.api));
const name = (0, _decapCmsLibUtil.basename)(path);
const fileObj = (0, _decapCmsLibUtil.blobToFileObj)(name, blob);
const url = URL.createObjectURL(fileObj);
const id = await (0, _decapCmsLibUtil.getBlobSHA)(blob);
return {
id,
displayURL: url,
path,
name,
size: fileObj.size,
file: fileObj,
url
};
}
getMediaDisplayURL(displayURL) {
this._mediaDisplayURLSem = this._mediaDisplayURLSem || (0, _semaphore.default)(MAX_CONCURRENT_DOWNLOADS);
return (0, _decapCmsLibUtil.getMediaDisplayURL)(displayURL, this.api.readFile.bind(this.api), this._mediaDisplayURLSem);
}
persistEntry(entry, options) {
// persistEntry is a transactional operation
return (0, _decapCmsLibUtil.runWithLock)(this.lock, () => this.api.persistFiles(entry.dataFiles, entry.assets, options), 'Failed to acquire persist entry lock');
}
async persistMedia(mediaFile, options) {
try {
await this.api.persistFiles([], [mediaFile], options);
const {
sha,
path,
fileObj
} = mediaFile;
const displayURL = fileObj ? URL.createObjectURL(fileObj) : '';
return {
id: sha,
name: fileObj.name,
size: fileObj.size,
displayURL,
path: (0, _trimStart.default)(path, '/')
};
} catch (error) {
console.error(error);
throw error;
}
}
deleteFiles(paths, commitMessage) {
return this.api.deleteFiles(paths, commitMessage);
}
async traverseCursor(cursor, action) {
const meta = cursor.meta;
const files = cursor.data.get('files').toJS();
let result;
switch (action) {
case 'first':
{
result = this.getCursorAndFiles(files, 1);
break;
}
case 'last':
{
result = this.getCursorAndFiles(files, meta.get('pageCount'));
break;
}
case 'next':
{
result = this.getCursorAndFiles(files, meta.get('page') + 1);
break;
}
case 'prev':
{
result = this.getCursorAndFiles(files, meta.get('page') - 1);
break;
}
default:
{
result = this.getCursorAndFiles(files, 1);
break;
}
}
const readFile = (path, id) => this.api.readFile(path, id, {
repoURL: this.api.originRepoURL
}).catch(() => '');
const entries = await (0, _decapCmsLibUtil.entriesByFiles)(result.files, readFile, this.api.readFileMetadata.bind(this.api), _API.API_NAME);
return {
entries,
cursor: result.cursor
};
}
async loadMediaFile(branch, file) {
const readFile = (path, id, {
parseText
}) => this.api.readFile(path, id, {
branch,
parseText
});
const blob = await (0, _decapCmsLibUtil.getMediaAsBlob)(file.path, file.id, readFile);
const name = (0, _decapCmsLibUtil.basename)(file.path);
const fileObj = (0, _decapCmsLibUtil.blobToFileObj)(name, blob);
return {
id: file.id,
displayURL: URL.createObjectURL(fileObj),
path: file.path,
name,
size: fileObj.size,
file: fileObj
};
}
async unpublishedEntries() {
const listEntriesKeys = () => this.api.listUnpublishedBranches().then(branches => branches.map(branch => (0, _decapCmsLibUtil.contentKeyFromBranch)(branch)));
const ids = await (0, _decapCmsLibUtil.unpublishedEntries)(listEntriesKeys);
return ids;
}
async unpublishedEntry({
id,
collection,
slug
}) {
if (id) {
const data = await this.api.retrieveUnpublishedEntryData(id);
return data;
} else if (collection && slug) {
const entryId = this.api.generateContentKey(collection, slug);
const data = await this.api.retrieveUnpublishedEntryData(entryId);
return data;
} else {
throw new Error('Missing unpublished entry id or collection and slug');
}
}
getBranch(collection, slug) {
const contentKey = this.api.generateContentKey(collection, slug);
const branch = (0, _decapCmsLibUtil.branchFromContentKey)(contentKey);
return branch;
}
async unpublishedEntryDataFile(collection, slug, path, id) {
const branch = this.getBranch(collection, slug);
const data = await this.api.readFile(path, id, {
branch
});
return data;
}
async unpublishedEntryMediaFile(collection, slug, path, id) {
const branch = this.getBranch(collection, slug);
const mediaFile = await this.loadMediaFile(branch, {
path,
id
});
return mediaFile;
}
async getDeployPreview(collection, slug) {
try {
const statuses = await this.api.getStatuses(collection, slug);
const deployStatus = (0, _decapCmsLibUtil.getPreviewStatus)(statuses, this.previewContext);
if (deployStatus) {
const {
target_url: url,
state
} = deployStatus;
return {
url,
status: state
};
} else {
return null;
}
} catch (e) {
return null;
}
}
updateUnpublishedEntryStatus(collection, slug, newStatus) {
// updateUnpublishedEntryStatus is a transactional operation
return (0, _decapCmsLibUtil.runWithLock)(this.lock, () => this.api.updateUnpublishedEntryStatus(collection, slug, newStatus), 'Failed to acquire update entry status lock');
}
deleteUnpublishedEntry(collection, slug) {
// deleteUnpublishedEntry is a transactional operation
return (0, _decapCmsLibUtil.runWithLock)(this.lock, () => this.api.deleteUnpublishedEntry(collection, slug), 'Failed to acquire delete entry lock');
}
publishUnpublishedEntry(collection, slug) {
// publishUnpublishedEntry is a transactional operation
return (0, _decapCmsLibUtil.runWithLock)(this.lock, () => this.api.publishUnpublishedEntry(collection, slug), 'Failed to acquire publish entry lock');
}
}
exports.default = GitHub;

View File

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

View File

@@ -0,0 +1,109 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.updateBranch = exports.reopenPullRequest = exports.deleteBranch = exports.createPullRequest = exports.createBranchAndPullRequest = exports.createBranch = exports.closePullRequestAndDeleteBranch = exports.closePullRequest = void 0;
var _graphqlTag = require("graphql-tag");
var fragments = _interopRequireWildcard(require("./fragments"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
// updateRef only works for branches at the moment
const updateBranch = exports.updateBranch = (0, _graphqlTag.gql)`
mutation updateRef($input: UpdateRefInput!) {
updateRef(input: $input) {
branch: ref {
...BranchParts
}
}
}
${fragments.branch}
`;
// deleteRef only works for branches at the moment
const deleteRefMutationPart = `
deleteRef(input: $deleteRefInput) {
clientMutationId
}
`;
const deleteBranch = exports.deleteBranch = (0, _graphqlTag.gql)`
mutation deleteRef($deleteRefInput: DeleteRefInput!) {
${deleteRefMutationPart}
}
`;
const closePullRequestMutationPart = `
closePullRequest(input: $closePullRequestInput) {
clientMutationId
pullRequest {
...PullRequestParts
}
}
`;
const closePullRequest = exports.closePullRequest = (0, _graphqlTag.gql)`
mutation closePullRequestAndDeleteBranch($closePullRequestInput: ClosePullRequestInput!) {
${closePullRequestMutationPart}
}
${fragments.pullRequest}
`;
const closePullRequestAndDeleteBranch = exports.closePullRequestAndDeleteBranch = (0, _graphqlTag.gql)`
mutation closePullRequestAndDeleteBranch(
$closePullRequestInput: ClosePullRequestInput!
$deleteRefInput: DeleteRefInput!
) {
${closePullRequestMutationPart}
${deleteRefMutationPart}
}
${fragments.pullRequest}
`;
const createPullRequestMutationPart = `
createPullRequest(input: $createPullRequestInput) {
clientMutationId
pullRequest {
...PullRequestParts
}
}
`;
const createPullRequest = exports.createPullRequest = (0, _graphqlTag.gql)`
mutation createPullRequest($createPullRequestInput: CreatePullRequestInput!) {
${createPullRequestMutationPart}
}
${fragments.pullRequest}
`;
const createBranch = exports.createBranch = (0, _graphqlTag.gql)`
mutation createBranch($createRefInput: CreateRefInput!) {
createRef(input: $createRefInput) {
branch: ref {
...BranchParts
}
}
}
${fragments.branch}
`;
// createRef only works for branches at the moment
const createBranchAndPullRequest = exports.createBranchAndPullRequest = (0, _graphqlTag.gql)`
mutation createBranchAndPullRequest(
$createRefInput: CreateRefInput!
$createPullRequestInput: CreatePullRequestInput!
) {
createRef(input: $createRefInput) {
branch: ref {
...BranchParts
}
}
${createPullRequestMutationPart}
}
${fragments.branch}
${fragments.pullRequest}
`;
const reopenPullRequest = exports.reopenPullRequest = (0, _graphqlTag.gql)`
mutation reopenPullRequest($reopenPullRequestInput: ReopenPullRequestInput!) {
reopenPullRequest(input: $reopenPullRequestInput) {
clientMutationId
pullRequest {
...PullRequestParts
}
}
}
${fragments.pullRequest}
`;

View File

@@ -0,0 +1,201 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.fileSha = exports.branch = exports.blob = void 0;
exports.files = files;
exports.user = exports.statues = exports.repository = exports.repoPermission = exports.pullRequests = exports.pullRequestAndBranch = exports.pullRequest = exports.openAuthoringBranches = void 0;
var _graphqlTag = require("graphql-tag");
var _commonTags = require("common-tags");
var fragments = _interopRequireWildcard(require("./fragments"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
const repoPermission = exports.repoPermission = (0, _graphqlTag.gql)`
query repoPermission($owner: String!, $name: String!) {
repository(owner: $owner, name: $name) {
...RepositoryParts
viewerPermission
}
}
${fragments.repository}
`;
const user = exports.user = (0, _graphqlTag.gql)`
query {
viewer {
id
avatar_url: avatarUrl
name
login
}
}
`;
const blob = exports.blob = (0, _graphqlTag.gql)`
query blob($owner: String!, $name: String!, $expression: String!) {
repository(owner: $owner, name: $name) {
...RepositoryParts
object(expression: $expression) {
... on Blob {
...BlobWithTextParts
}
}
}
}
${fragments.repository}
${fragments.blobWithText}
`;
const statues = exports.statues = (0, _graphqlTag.gql)`
query statues($owner: String!, $name: String!, $sha: GitObjectID!) {
repository(owner: $owner, name: $name) {
...RepositoryParts
object(oid: $sha) {
...ObjectParts
... on Commit {
status {
id
contexts {
id
context
state
target_url: targetUrl
}
}
}
}
}
}
${fragments.repository}
${fragments.object}
`;
function buildFilesQuery(depth = 1) {
const PLACE_HOLDER = 'PLACE_HOLDER';
let query = (0, _commonTags.oneLine)`
...ObjectParts
... on Tree {
entries {
...FileEntryParts
${PLACE_HOLDER}
}
}
`;
for (let i = 0; i < depth - 1; i++) {
query = query.replace(PLACE_HOLDER, (0, _commonTags.oneLine)`
object {
... on Tree {
entries {
...FileEntryParts
${PLACE_HOLDER}
}
}
}
`);
}
query = query.replace(PLACE_HOLDER, '');
return query;
}
function files(depth) {
return (0, _graphqlTag.gql)`
query files($owner: String!, $name: String!, $expression: String!) {
repository(owner: $owner, name: $name) {
...RepositoryParts
object(expression: $expression) {
${buildFilesQuery(depth)}
}
}
}
${fragments.repository}
${fragments.object}
${fragments.fileEntry}
`;
}
const branchQueryPart = `
branch: ref(qualifiedName: $qualifiedName) {
...BranchParts
}
`;
const branch = exports.branch = (0, _graphqlTag.gql)`
query branch($owner: String!, $name: String!, $qualifiedName: String!) {
repository(owner: $owner, name: $name) {
...RepositoryParts
${branchQueryPart}
}
}
${fragments.repository}
${fragments.branch}
`;
const openAuthoringBranches = exports.openAuthoringBranches = (0, _graphqlTag.gql)`
query openAuthoringBranches($owner: String!, $name: String!, $refPrefix: String!) {
repository(owner: $owner, name: $name) {
...RepositoryParts
refs(refPrefix: $refPrefix, last: 100) {
nodes {
...BranchParts
}
}
}
}
${fragments.repository}
${fragments.branch}
`;
const repository = exports.repository = (0, _graphqlTag.gql)`
query repository($owner: String!, $name: String!) {
repository(owner: $owner, name: $name) {
...RepositoryParts
}
}
${fragments.repository}
`;
const pullRequestQueryPart = `
pullRequest(number: $number) {
...PullRequestParts
}
`;
const pullRequest = exports.pullRequest = (0, _graphqlTag.gql)`
query pullRequest($owner: String!, $name: String!, $number: Int!) {
repository(owner: $owner, name: $name) {
id
${pullRequestQueryPart}
}
}
${fragments.pullRequest}
`;
const pullRequests = exports.pullRequests = (0, _graphqlTag.gql)`
query pullRequests($owner: String!, $name: String!, $head: String, $states: [PullRequestState!]) {
repository(owner: $owner, name: $name) {
id
pullRequests(last: 100, headRefName: $head, states: $states) {
nodes {
...PullRequestParts
}
}
}
}
${fragments.pullRequest}
`;
const pullRequestAndBranch = exports.pullRequestAndBranch = (0, _graphqlTag.gql)`
query pullRequestAndBranch($owner: String!, $name: String!, $originRepoOwner: String!, $originRepoName: String!, $qualifiedName: String!, $number: Int!) {
repository(owner: $owner, name: $name) {
...RepositoryParts
${branchQueryPart}
}
origin: repository(owner: $originRepoOwner, name: $originRepoName) {
...RepositoryParts
${pullRequestQueryPart}
}
}
${fragments.repository}
${fragments.branch}
${fragments.pullRequest}
`;
const fileSha = exports.fileSha = (0, _graphqlTag.gql)`
query fileSha($owner: String!, $name: String!, $expression: String!) {
repository(owner: $owner, name: $name) {
...RepositoryParts
file: object(expression: $expression) {
...ObjectParts
}
}
}
${fragments.repository}
${fragments.object}
`;

View File

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

44
node_modules/decap-cms-backend-github/package.json generated vendored Normal file
View File

@@ -0,0 +1,44 @@
{
"name": "decap-cms-backend-github",
"description": "GitHub backend for Decap CMS",
"version": "3.2.2",
"license": "MIT",
"repository": "https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-github",
"bugs": "https://github.com/decaporg/decap-cms/issues",
"module": "dist/esm/index.js",
"main": "dist/decap-cms-backend-github.js",
"keywords": [
"decap-cms",
"backend",
"github"
],
"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\"",
"createFragmentTypes": "node scripts/createFragmentTypes.js"
},
"dependencies": {
"apollo-cache-inmemory": "^1.6.2",
"apollo-client": "^2.6.3",
"apollo-link-context": "^1.0.18",
"apollo-link-http": "^1.5.15",
"common-tags": "^1.8.0",
"graphql": "^15.0.0",
"graphql-tag": "^2.10.1",
"js-base64": "^3.0.0",
"semaphore": "^1.1.0"
},
"peerDependencies": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.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"
},
"gitHead": "64d91b8bb3d0a93dd36c53800cdac4ba2e435000"
}

View File

@@ -0,0 +1,48 @@
const fetch = require('node-fetch');
const fs = require('fs');
const path = require('path');
const API_HOST = process.env.GITHUB_HOST || 'https://api.github.com';
const API_TOKEN = process.env.GITHUB_API_TOKEN;
if (!API_TOKEN) {
throw new Error('Missing environment variable GITHUB_API_TOKEN');
}
fetch(`${API_HOST}/graphql`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `bearer ${API_TOKEN}` },
body: JSON.stringify({
variables: {},
query: `
{
__schema {
types {
kind
name
possibleTypes {
name
}
}
}
}
`,
}),
})
.then(result => result.json())
.then(result => {
// here we're filtering out any type information unrelated to unions or interfaces
const filteredData = result.data.__schema.types.filter(type => type.possibleTypes !== null);
result.data.__schema.types = filteredData;
fs.writeFile(
path.join(__dirname, '..', 'src', 'fragmentTypes.js'),
`module.exports = ${JSON.stringify(result.data)}`,
err => {
if (err) {
console.error('Error writing fragmentTypes file', err);
} else {
console.log('Fragment types successfully extracted!');
}
},
);
});

1472
node_modules/decap-cms-backend-github/src/API.ts generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,151 @@
import React from 'react';
import PropTypes from 'prop-types';
import styled from '@emotion/styled';
import { NetlifyAuthenticator } from 'decap-cms-lib-auth';
import { AuthenticationPage, Icon } from 'decap-cms-ui-default';
const LoginButtonIcon = styled(Icon)`
margin-right: 18px;
`;
const ForkApprovalContainer = styled.div`
display: flex;
flex-flow: column nowrap;
justify-content: space-around;
flex-grow: 0.2;
`;
const ForkButtonsContainer = styled.div`
display: flex;
flex-flow: column nowrap;
justify-content: space-around;
align-items: center;
`;
export default class GitHubAuthenticationPage extends React.Component {
static propTypes = {
onLogin: PropTypes.func.isRequired,
inProgress: PropTypes.bool,
base_url: PropTypes.string,
siteId: PropTypes.string,
authEndpoint: PropTypes.string,
config: PropTypes.object.isRequired,
clearHash: PropTypes.func,
t: PropTypes.func.isRequired,
};
state = {};
getPermissionToFork = () => {
return new Promise((resolve, reject) => {
this.setState({
requestingFork: true,
approveFork: () => {
this.setState({ requestingFork: false });
resolve();
},
refuseFork: () => {
this.setState({ requestingFork: false });
reject();
},
});
});
};
loginWithOpenAuthoring(data) {
const { backend } = this.props;
this.setState({ findingFork: true });
return backend
.authenticateWithFork({ userData: data, getPermissionToFork: this.getPermissionToFork })
.catch(err => {
this.setState({ findingFork: false });
console.error(err);
throw err;
});
}
handleLogin = e => {
e.preventDefault();
const cfg = {
base_url: this.props.base_url,
site_id:
document.location.host.split(':')[0] === 'localhost'
? 'demo.decapcms.org'
: this.props.siteId,
auth_endpoint: this.props.authEndpoint,
};
const auth = new NetlifyAuthenticator(cfg);
const { open_authoring: openAuthoring = false, auth_scope: authScope = '' } =
this.props.config.backend;
const scope = authScope || (openAuthoring ? 'public_repo' : 'repo');
auth.authenticate({ provider: 'github', scope }, (err, data) => {
if (err) {
this.setState({ loginError: err.toString() });
return;
}
if (openAuthoring) {
return this.loginWithOpenAuthoring(data).then(() => this.props.onLogin(data));
}
this.props.onLogin(data);
});
};
renderLoginButton = () => {
const { inProgress, t } = this.props;
return inProgress || this.state.findingFork ? (
t('auth.loggingIn')
) : (
<React.Fragment>
<LoginButtonIcon type="github" />
{t('auth.loginWithGitHub')}
</React.Fragment>
);
};
getAuthenticationPageRenderArgs() {
const { requestingFork } = this.state;
if (requestingFork) {
const { approveFork, refuseFork } = this.state;
return {
renderPageContent: ({ LoginButton, TextButton, showAbortButton }) => (
<ForkApprovalContainer>
<p>
Open Authoring is enabled: we need to use a fork on your github account. (If a fork
already exists, we&#39;ll use that.)
</p>
<ForkButtonsContainer>
<LoginButton onClick={approveFork}>Fork the repo</LoginButton>
{showAbortButton && (
<TextButton onClick={refuseFork}>Don&#39;t fork the repo</TextButton>
)}
</ForkButtonsContainer>
</ForkApprovalContainer>
),
};
}
return {
renderButtonContent: this.renderLoginButton,
};
}
render() {
const { inProgress, config, t } = this.props;
const { loginError, requestingFork, findingFork } = this.state;
return (
<AuthenticationPage
onLogin={this.handleLogin}
loginDisabled={inProgress || findingFork || requestingFork}
loginErrorMessage={loginError}
logoUrl={config.logo_url}
siteUrl={config.site_url}
{...this.getAuthenticationPageRenderArgs()}
t={t}
/>
);
}
}

709
node_modules/decap-cms-backend-github/src/GraphQLAPI.ts generated vendored Normal file
View File

@@ -0,0 +1,709 @@
import { ApolloClient } from 'apollo-client';
import {
InMemoryCache,
defaultDataIdFromObject,
IntrospectionFragmentMatcher,
} from 'apollo-cache-inmemory';
import { createHttpLink } from 'apollo-link-http';
import { setContext } from 'apollo-link-context';
import {
APIError,
readFile,
localForage,
DEFAULT_PR_BODY,
branchFromContentKey,
CMS_BRANCH_PREFIX,
throwOnConflictingBranches,
} from 'decap-cms-lib-util';
import { trim, trimStart } from 'lodash';
import introspectionQueryResultData from './fragmentTypes';
import API, { API_NAME, PullRequestState, MOCK_PULL_REQUEST } from './API';
import * as queries from './queries';
import * as mutations from './mutations';
import type { Config, BlobArgs } from './API';
import type { NormalizedCacheObject } from 'apollo-cache-inmemory';
import type { QueryOptions, MutationOptions, OperationVariables } from 'apollo-client';
import type { GraphQLError } from 'graphql';
import type { Octokit } from '@octokit/rest';
const NO_CACHE = 'no-cache';
const CACHE_FIRST = 'cache-first';
const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData,
});
interface TreeEntry {
object?: {
entries: TreeEntry[];
};
type: 'blob' | 'tree';
name: string;
sha: string;
blob?: {
size: number;
};
}
interface TreeFile {
path: string;
id: string;
size: number;
type: string;
name: string;
}
type GraphQLPullRequest = {
id: string;
baseRefName: string;
baseRefOid: string;
body: string;
headRefName: string;
headRefOid: string;
number: number;
state: string;
title: string;
mergedAt: string | null;
updatedAt: string | null;
labels: { nodes: { name: string }[] };
repository: {
id: string;
isFork: boolean;
};
user: GraphQLPullsListResponseItemUser;
};
type GraphQLPullsListResponseItemUser = {
avatar_url: string;
login: string;
url: string;
name: string;
};
function transformPullRequest(pr: GraphQLPullRequest) {
return {
...pr,
labels: pr.labels.nodes,
head: { ref: pr.headRefName, sha: pr.headRefOid, repo: { fork: pr.repository.isFork } },
base: { ref: pr.baseRefName, sha: pr.baseRefOid },
};
}
type Error = GraphQLError & { type: string };
export default class GraphQLAPI extends API {
client: ApolloClient<NormalizedCacheObject>;
constructor(config: Config) {
super(config);
this.client = this.getApolloClient();
}
getApolloClient() {
const authLink = setContext((_, { headers }) => {
return {
headers: {
'Content-Type': 'application/json; charset=utf-8',
...headers,
authorization: this.token ? `${this.tokenKeyword} ${this.token}` : '',
},
};
});
const httpLink = createHttpLink({ uri: `${this.apiRoot}/graphql` });
return new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache({ fragmentMatcher }),
defaultOptions: {
watchQuery: {
fetchPolicy: NO_CACHE,
errorPolicy: 'ignore',
},
query: {
fetchPolicy: NO_CACHE,
errorPolicy: 'all',
},
},
});
}
reset() {
return this.client.resetStore();
}
async getRepository(owner: string, name: string) {
const { data } = await this.query({
query: queries.repository,
variables: { owner, name },
fetchPolicy: CACHE_FIRST, // repository id doesn't change
});
return data.repository;
}
query(options: QueryOptions<OperationVariables>) {
return this.client.query(options).catch(error => {
throw new APIError(error.message, 500, 'GitHub');
});
}
async mutate(options: MutationOptions<OperationVariables>) {
try {
const result = await this.client.mutate(options);
return result;
} catch (error) {
const errors = error.graphQLErrors;
if (Array.isArray(errors) && errors.some(e => e.message === 'Ref cannot be created.')) {
const refName = options?.variables?.createRefInput?.name || '';
const branchName = trimStart(refName, 'refs/heads/');
if (branchName) {
await throwOnConflictingBranches(branchName, name => this.getBranch(name), API_NAME);
}
} else if (
Array.isArray(errors) &&
errors.some(e =>
new RegExp(
`A ref named "refs/heads/${CMS_BRANCH_PREFIX}/.+?" already exists in the repository.`,
).test(e.message),
)
) {
const refName = options?.variables?.createRefInput?.name || '';
const sha = options?.variables?.createRefInput?.oid || '';
const branchName = trimStart(refName, 'refs/heads/');
if (branchName && branchName.startsWith(`${CMS_BRANCH_PREFIX}/`) && sha) {
try {
// this can happen if the branch wasn't deleted when the PR was merged
// we backup the existing branch just in case an re-run the mutation
await this.backupBranch(branchName);
await this.deleteBranch(branchName);
const result = await this.client.mutate(options);
return result;
} catch (e) {
console.log(e);
}
}
}
throw new APIError(error.message, 500, 'GitHub');
}
}
async hasWriteAccess() {
const { repoOwner: owner, repoName: name } = this;
try {
const { data } = await this.query({
query: queries.repoPermission,
variables: { owner, name },
fetchPolicy: CACHE_FIRST, // we can assume permission doesn't change often
});
// https://developer.github.com/v4/enum/repositorypermission/
const { viewerPermission } = data.repository;
return ['ADMIN', 'MAINTAIN', 'WRITE'].includes(viewerPermission);
} catch (error) {
console.error('Problem fetching repo data from GitHub');
throw error;
}
}
async user() {
const { data } = await this.query({
query: queries.user,
fetchPolicy: CACHE_FIRST, // we can assume user details don't change often
});
return data.viewer;
}
async retrieveBlobObject(owner: string, name: string, expression: string, options = {}) {
const { data } = await this.query({
query: queries.blob,
variables: { owner, name, expression },
...options,
});
// https://developer.github.com/v4/object/blob/
if (data.repository.object) {
const { is_binary: isBinary, text } = data.repository.object;
return { isNull: false, isBinary, text };
} else {
return { isNull: true };
}
}
getOwnerAndNameFromRepoUrl(repoURL: string) {
let { repoOwner: owner, repoName: name } = this;
if (repoURL === this.originRepoURL) {
({ originRepoOwner: owner, originRepoName: name } = this);
}
return { owner, name };
}
async readFile(
path: string,
sha?: string | null,
{
branch = this.branch,
repoURL = this.repoURL,
parseText = true,
}: {
branch?: string;
repoURL?: string;
parseText?: boolean;
} = {},
) {
if (!sha) {
sha = await this.getFileSha(path, { repoURL, branch });
}
const fetchContent = () => this.fetchBlobContent({ sha: sha as string, repoURL, parseText });
const content = await readFile(sha, fetchContent, localForage, parseText);
return content;
}
async fetchBlobContent({ sha, repoURL, parseText }: BlobArgs) {
if (!parseText) {
return super.fetchBlobContent({ sha, repoURL, parseText });
}
const { owner, name } = this.getOwnerAndNameFromRepoUrl(repoURL);
const { isNull, isBinary, text } = await this.retrieveBlobObject(
owner,
name,
sha,
{ fetchPolicy: CACHE_FIRST }, // blob sha is derived from file content
);
if (isNull) {
throw new APIError('Not Found', 404, 'GitHub');
} else if (!isBinary) {
return text;
} else {
return super.fetchBlobContent({ sha, repoURL, parseText });
}
}
async getPullRequestAuthor(pullRequest: Octokit.PullsListResponseItem) {
const user = pullRequest.user as unknown as GraphQLPullsListResponseItemUser;
return user?.name || user?.login;
}
async getPullRequests(
head: string | undefined,
state: PullRequestState,
predicate: (pr: Octokit.PullsListResponseItem) => boolean,
) {
const { originRepoOwner: owner, originRepoName: name } = this;
let states;
if (state === PullRequestState.Open) {
states = ['OPEN'];
} else if (state === PullRequestState.Closed) {
states = ['CLOSED', 'MERGED'];
} else {
states = ['OPEN', 'CLOSED', 'MERGED'];
}
const { data } = await this.query({
query: queries.pullRequests,
variables: {
owner,
name,
...(head ? { head } : {}),
states,
},
});
const {
pullRequests,
}: {
pullRequests: {
nodes: GraphQLPullRequest[];
};
} = data.repository;
const mapped = pullRequests.nodes.map(transformPullRequest);
return (mapped as unknown as Octokit.PullsListResponseItem[]).filter(
pr => pr.head.ref.startsWith(`${CMS_BRANCH_PREFIX}/`) && predicate(pr),
);
}
async getOpenAuthoringBranches() {
const { repoOwner: owner, repoName: name } = this;
const { data } = await this.query({
query: queries.openAuthoringBranches,
variables: {
owner,
name,
refPrefix: `refs/heads/cms/${this.repo}/`,
},
});
return data.repository.refs.nodes.map(({ name, prefix }: { name: string; prefix: string }) => ({
ref: `${prefix}${name}`,
}));
}
async getStatuses(collectionName: string, slug: string) {
const contentKey = this.generateContentKey(collectionName, slug);
const branch = branchFromContentKey(contentKey);
const pullRequest = await this.getBranchPullRequest(branch);
const sha = pullRequest.head.sha;
const { originRepoOwner: owner, originRepoName: name } = this;
const { data } = await this.query({ query: queries.statues, variables: { owner, name, sha } });
if (data.repository.object) {
const { status } = data.repository.object;
const { contexts } = status || { contexts: [] };
return contexts;
} else {
return [];
}
}
getAllFiles(entries: TreeEntry[], path: string) {
const allFiles: TreeFile[] = entries.reduce((acc, item) => {
if (item.type === 'tree') {
const entries = item.object?.entries || [];
return [...acc, ...this.getAllFiles(entries, `${path}/${item.name}`)];
} else if (item.type === 'blob') {
return [
...acc,
{
name: item.name,
type: item.type,
id: item.sha,
path: `${path}/${item.name}`,
size: item.blob ? item.blob.size : 0,
},
];
}
return acc;
}, [] as TreeFile[]);
return allFiles;
}
async listFiles(path: string, { repoURL = this.repoURL, branch = this.branch, depth = 1 } = {}) {
const { owner, name } = this.getOwnerAndNameFromRepoUrl(repoURL);
const folder = trim(path, '/');
const { data } = await this.query({
query: queries.files(depth),
variables: { owner, name, expression: `${branch}:${folder}` },
});
if (data.repository.object) {
const allFiles = this.getAllFiles(data.repository.object.entries, folder);
return allFiles;
} else {
return [];
}
}
getBranchQualifiedName(branch: string) {
return `refs/heads/${branch}`;
}
getBranchQuery(branch: string, owner: string, name: string) {
return {
query: queries.branch,
variables: {
owner,
name,
qualifiedName: this.getBranchQualifiedName(branch),
},
};
}
async getDefaultBranch() {
const { data } = await this.query({
...this.getBranchQuery(this.branch, this.originRepoOwner, this.originRepoName),
});
return data.repository.branch;
}
async getBranch(branch: string) {
const { data } = await this.query({
...this.getBranchQuery(branch, this.repoOwner, this.repoName),
fetchPolicy: CACHE_FIRST,
});
if (!data.repository.branch) {
throw new APIError('Branch not found', 404, API_NAME);
}
return data.repository.branch;
}
async patchRef(type: string, name: string, sha: string, opts: { force?: boolean } = {}) {
if (type !== 'heads') {
return super.patchRef(type, name, sha, opts);
}
const force = opts.force || false;
const branch = await this.getBranch(name);
const { data } = await this.mutate({
mutation: mutations.updateBranch,
variables: {
input: { oid: sha, refId: branch.id, force },
},
});
return data!.updateRef.branch;
}
async deleteBranch(branchName: string) {
const branch = await this.getBranch(branchName);
const { data } = await this.mutate({
mutation: mutations.deleteBranch,
variables: {
deleteRefInput: { refId: branch.id },
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
update: (store: any) => store.data.delete(defaultDataIdFromObject(branch)),
});
return data!.deleteRef;
}
getPullRequestQuery(number: number) {
const { originRepoOwner: owner, originRepoName: name } = this;
return {
query: queries.pullRequest,
variables: { owner, name, number },
};
}
async getPullRequest(number: number) {
const { data } = await this.query({
...this.getPullRequestQuery(number),
fetchPolicy: CACHE_FIRST,
});
// https://developer.github.com/v4/enum/pullrequeststate/
// GraphQL state: [CLOSED, MERGED, OPEN]
// REST API state: [closed, open]
const state =
data.repository.pullRequest.state === 'OPEN'
? PullRequestState.Open
: PullRequestState.Closed;
return {
...data.repository.pullRequest,
state,
};
}
getPullRequestAndBranchQuery(branch: string, number: number) {
const { repoOwner: owner, repoName: name } = this;
const { originRepoOwner, originRepoName } = this;
return {
query: queries.pullRequestAndBranch,
variables: {
owner,
name,
originRepoOwner,
originRepoName,
number,
qualifiedName: this.getBranchQualifiedName(branch),
},
};
}
async getPullRequestAndBranch(branch: string, number: number) {
const { data } = await this.query({
...this.getPullRequestAndBranchQuery(branch, number),
fetchPolicy: CACHE_FIRST,
});
const { repository, origin } = data;
return { branch: repository.branch, pullRequest: origin.pullRequest };
}
async openPR(number: number) {
const pullRequest = await this.getPullRequest(number);
const { data } = await this.mutate({
mutation: mutations.reopenPullRequest,
variables: {
reopenPullRequestInput: { pullRequestId: pullRequest.id },
},
update: (store, { data: mutationResult }) => {
const { pullRequest } = mutationResult!.reopenPullRequest;
const pullRequestData = { repository: { ...pullRequest.repository, pullRequest } };
store.writeQuery({
...this.getPullRequestQuery(pullRequest.number),
data: pullRequestData,
});
},
});
return data!.reopenPullRequest;
}
async closePR(number: number) {
const pullRequest = await this.getPullRequest(number);
const { data } = await this.mutate({
mutation: mutations.closePullRequest,
variables: {
closePullRequestInput: { pullRequestId: pullRequest.id },
},
update: (store, { data: mutationResult }) => {
const { pullRequest } = mutationResult!.closePullRequest;
const pullRequestData = { repository: { ...pullRequest.repository, pullRequest } };
store.writeQuery({
...this.getPullRequestQuery(pullRequest.number),
data: pullRequestData,
});
},
});
return data!.closePullRequest;
}
async deleteUnpublishedEntry(collectionName: string, slug: string) {
try {
const contentKey = this.generateContentKey(collectionName, slug);
const branchName = branchFromContentKey(contentKey);
const pr = await this.getBranchPullRequest(branchName);
if (pr.number !== MOCK_PULL_REQUEST) {
const { branch, pullRequest } = await this.getPullRequestAndBranch(branchName, pr.number);
const { data } = await this.mutate({
mutation: mutations.closePullRequestAndDeleteBranch,
variables: {
deleteRefInput: { refId: branch.id },
closePullRequestInput: { pullRequestId: pullRequest.id },
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
update: (store: any) => {
store.data.delete(defaultDataIdFromObject(branch));
store.data.delete(defaultDataIdFromObject(pullRequest));
},
});
return data!.closePullRequest;
} else {
return await this.deleteBranch(branchName);
}
} catch (e) {
const { graphQLErrors } = e;
if (graphQLErrors && graphQLErrors.length > 0) {
const branchNotFound = graphQLErrors.some((e: Error) => e.type === 'NOT_FOUND');
if (branchNotFound) {
return;
}
}
throw e;
}
}
async createPR(title: string, head: string) {
const [repository, headReference] = await Promise.all([
this.getRepository(this.originRepoOwner, this.originRepoName),
this.useOpenAuthoring ? `${(await this.user()).login}:${head}` : head,
]);
const { data } = await this.mutate({
mutation: mutations.createPullRequest,
variables: {
createPullRequestInput: {
baseRefName: this.branch,
body: DEFAULT_PR_BODY,
title,
headRefName: headReference,
repositoryId: repository.id,
},
},
update: (store, { data: mutationResult }) => {
const { pullRequest } = mutationResult!.createPullRequest;
const pullRequestData = { repository: { ...pullRequest.repository, pullRequest } };
store.writeQuery({
...this.getPullRequestQuery(pullRequest.number),
data: pullRequestData,
});
},
});
const { pullRequest } = data!.createPullRequest;
return { ...pullRequest, head: { sha: pullRequest.headRefOid } };
}
async createBranch(branchName: string, sha: string) {
const owner = this.repoOwner;
const name = this.repoName;
const repository = await this.getRepository(owner, name);
const { data } = await this.mutate({
mutation: mutations.createBranch,
variables: {
createRefInput: {
name: this.getBranchQualifiedName(branchName),
oid: sha,
repositoryId: repository.id,
},
},
update: (store, { data: mutationResult }) => {
const { branch } = mutationResult!.createRef;
const branchData = { repository: { ...branch.repository, branch } };
store.writeQuery({
...this.getBranchQuery(branchName, owner, name),
data: branchData,
});
},
});
const { branch } = data!.createRef;
return { ...branch, ref: `${branch.prefix}${branch.name}` };
}
async createBranchAndPullRequest(branchName: string, sha: string, title: string) {
const owner = this.originRepoOwner;
const name = this.originRepoName;
const repository = await this.getRepository(owner, name);
const { data } = await this.mutate({
mutation: mutations.createBranchAndPullRequest,
variables: {
createRefInput: {
name: this.getBranchQualifiedName(branchName),
oid: sha,
repositoryId: repository.id,
},
createPullRequestInput: {
baseRefName: this.branch,
body: DEFAULT_PR_BODY,
title,
headRefName: branchName,
repositoryId: repository.id,
},
},
update: (store, { data: mutationResult }) => {
const { branch } = mutationResult!.createRef;
const { pullRequest } = mutationResult!.createPullRequest;
const branchData = { repository: { ...branch.repository, branch } };
const pullRequestData = {
repository: { ...pullRequest.repository, branch },
origin: { ...pullRequest.repository, pullRequest },
};
store.writeQuery({
...this.getBranchQuery(branchName, owner, name),
data: branchData,
});
store.writeQuery({
...this.getPullRequestAndBranchQuery(branchName, pullRequest.number),
data: pullRequestData,
});
},
});
const { pullRequest } = data!.createPullRequest;
return transformPullRequest(pullRequest) as unknown as Octokit.PullsCreateResponse;
}
async getFileSha(path: string, { repoURL = this.repoURL, branch = this.branch } = {}) {
const { owner, name } = this.getOwnerAndNameFromRepoUrl(repoURL);
const { data } = await this.query({
query: queries.fileSha,
variables: { owner, name, expression: `${branch}:${path}` },
});
if (data.repository.file) {
return data.repository.file.sha;
}
throw new APIError('Not Found', 404, API_NAME);
}
}

View File

@@ -0,0 +1,833 @@
import { Base64 } from 'js-base64';
import API from '../API';
global.fetch = jest.fn().mockRejectedValue(new Error('should not call fetch inside tests'));
describe('github API', () => {
beforeEach(() => {
jest.resetAllMocks();
});
function mockAPI(api, responses) {
api.request = jest.fn().mockImplementation((path, options = {}) => {
const normalizedPath = path.indexOf('?') !== -1 ? path.slice(0, path.indexOf('?')) : path;
const response = responses[normalizedPath];
return typeof response === 'function'
? Promise.resolve(response(options))
: Promise.reject(new Error(`No response for path '${normalizedPath}'`));
});
}
describe('editorialWorkflowGit', () => {
it('should create PR with correct base branch name when publishing with editorial workflow', () => {
let prBaseBranch = null;
let labels = null;
const api = new API({
branch: 'gh-pages',
repo: 'owner/my-repo',
initialWorkflowStatus: 'draft',
});
const responses = {
'/repos/owner/my-repo/branches/gh-pages': () => ({ commit: { sha: 'def' } }),
'/repos/owner/my-repo/git/trees/def': () => ({ tree: [] }),
'/repos/owner/my-repo/git/trees': () => ({}),
'/repos/owner/my-repo/git/commits': () => ({}),
'/repos/owner/my-repo/git/refs': () => ({}),
'/repos/owner/my-repo/pulls': req => {
prBaseBranch = JSON.parse(req.body).base;
return { head: { sha: 'cbd' }, labels: [], number: 1 };
},
'/repos/owner/my-repo/issues/1/labels': req => {
labels = JSON.parse(req.body).labels;
return {};
},
};
mockAPI(api, responses);
return expect(
api.editorialWorkflowGit([], { slug: 'entry', sha: 'abc' }, null, {}).then(() => ({
prBaseBranch,
labels,
})),
).resolves.toEqual({ prBaseBranch: 'gh-pages', labels: ['decap-cms/draft'] });
});
it('should create PR with correct base branch name with custom prefix when publishing with editorial workflow', () => {
let prBaseBranch = null;
let labels = null;
const api = new API({
branch: 'gh-pages',
repo: 'owner/my-repo',
initialWorkflowStatus: 'draft',
cmsLabelPrefix: 'other/',
});
const responses = {
'/repos/owner/my-repo/branches/gh-pages': () => ({ commit: { sha: 'def' } }),
'/repos/owner/my-repo/git/trees/def': () => ({ tree: [] }),
'/repos/owner/my-repo/git/trees': () => ({}),
'/repos/owner/my-repo/git/commits': () => ({}),
'/repos/owner/my-repo/git/refs': () => ({}),
'/repos/owner/my-repo/pulls': req => {
prBaseBranch = JSON.parse(req.body).base;
return { head: { sha: 'cbd' }, labels: [], number: 1 };
},
'/repos/owner/my-repo/issues/1/labels': req => {
labels = JSON.parse(req.body).labels;
return {};
},
};
mockAPI(api, responses);
return expect(
api.editorialWorkflowGit([], { slug: 'entry', sha: 'abc' }, null, {}).then(() => ({
prBaseBranch,
labels,
})),
).resolves.toEqual({ prBaseBranch: 'gh-pages', labels: ['other/draft'] });
});
});
describe('updateTree', () => {
it('should create tree with nested paths', async () => {
const api = new API({ branch: 'master', repo: 'owner/repo' });
api.createTree = jest.fn().mockImplementation(() => Promise.resolve({ sha: 'newTreeSha' }));
const files = [
{ path: '/static/media/new-image.jpeg', sha: null },
{ path: 'content/posts/new-post.md', sha: 'new-post.md' },
];
const baseTreeSha = 'baseTreeSha';
await expect(api.updateTree(baseTreeSha, files)).resolves.toEqual({
sha: 'newTreeSha',
parentSha: baseTreeSha,
});
expect(api.createTree).toHaveBeenCalledTimes(1);
expect(api.createTree).toHaveBeenCalledWith(baseTreeSha, [
{
path: 'static/media/new-image.jpeg',
mode: '100644',
type: 'blob',
sha: null,
},
{
path: 'content/posts/new-post.md',
mode: '100644',
type: 'blob',
sha: 'new-post.md',
},
]);
});
});
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({ branch: 'gh-pages', repo: 'my-repo', token: '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://api.github.com/some-path', {
cache: 'no-cache',
headers: {
Authorization: 'token token',
'Content-Type': 'application/json; charset=utf-8',
},
signal: expect.any(AbortSignal),
});
});
it('should throw error on not ok response', async () => {
const api = new API({ branch: 'gh-pages', repo: 'my-repo', token: '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: 'GitHub',
}),
);
});
it('should allow overriding requestHeaders to return a promise ', async () => {
const api = new API({ branch: 'gh-pages', repo: 'my-repo', token: 'token' });
api.requestHeaders = jest.fn().mockResolvedValue({
Authorization: 'promise-token',
'Content-Type': 'application/json; charset=utf-8',
});
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://api.github.com/some-path', {
cache: 'no-cache',
headers: {
Authorization: 'promise-token',
'Content-Type': 'application/json; charset=utf-8',
},
signal: expect.any(AbortSignal),
});
});
});
describe('persistFiles', () => {
it('should update tree, commit and patch branch when useWorkflow is false', async () => {
const api = new API({ branch: 'master', repo: 'owner/repo' });
const responses = {
// upload the file
'/repos/owner/repo/git/blobs': () => ({ sha: 'new-file-sha' }),
// get the branch
'/repos/owner/repo/branches/master': () => ({ commit: { sha: 'root' } }),
// create new tree
'/repos/owner/repo/git/trees': options => {
const data = JSON.parse(options.body);
return { sha: data.base_tree };
},
// update the commit with the tree
'/repos/owner/repo/git/commits': () => ({ sha: 'commit-sha' }),
// patch the branch
'/repos/owner/repo/git/refs/heads/master': () => ({}),
};
mockAPI(api, responses);
const entry = {
dataFiles: [
{
slug: 'entry',
sha: 'abc',
path: 'content/posts/new-post.md',
raw: 'content',
},
],
assets: [],
};
await api.persistFiles(entry.dataFiles, entry.assets, { commitMessage: 'commitMessage' });
expect(api.request).toHaveBeenCalledTimes(5);
expect(api.request.mock.calls[0]).toEqual([
'/repos/owner/repo/git/blobs',
{
method: 'POST',
body: JSON.stringify({
content: Base64.encode(entry.dataFiles[0].raw),
encoding: 'base64',
}),
},
]);
expect(api.request.mock.calls[1]).toEqual(['/repos/owner/repo/branches/master']);
expect(api.request.mock.calls[2]).toEqual([
'/repos/owner/repo/git/trees',
{
body: JSON.stringify({
base_tree: 'root',
tree: [
{
path: 'content/posts/new-post.md',
mode: '100644',
type: 'blob',
sha: 'new-file-sha',
},
],
}),
method: 'POST',
},
]);
expect(api.request.mock.calls[3]).toEqual([
'/repos/owner/repo/git/commits',
{
body: JSON.stringify({
message: 'commitMessage',
tree: 'root',
parents: ['root'],
}),
method: 'POST',
},
]);
expect(api.request.mock.calls[4]).toEqual([
'/repos/owner/repo/git/refs/heads/master',
{
body: JSON.stringify({
sha: 'commit-sha',
force: false,
}),
method: 'PATCH',
},
]);
});
it('should call editorialWorkflowGit when useWorkflow is true', async () => {
const api = new API({ branch: 'master', repo: 'owner/repo' });
api.uploadBlob = jest.fn();
api.editorialWorkflowGit = jest.fn();
const entry = {
dataFiles: [
{
slug: 'entry',
sha: 'abc',
path: 'content/posts/new-post.md',
raw: 'content',
},
],
assets: [
{
path: '/static/media/image-1.png',
sha: 'image-1.png',
},
{
path: '/static/media/image-2.png',
sha: 'image-2.png',
},
],
};
await api.persistFiles(entry.dataFiles, entry.assets, { useWorkflow: true });
expect(api.uploadBlob).toHaveBeenCalledTimes(3);
expect(api.uploadBlob).toHaveBeenCalledWith(entry.dataFiles[0]);
expect(api.uploadBlob).toHaveBeenCalledWith(entry.assets[0]);
expect(api.uploadBlob).toHaveBeenCalledWith(entry.assets[1]);
expect(api.editorialWorkflowGit).toHaveBeenCalledTimes(1);
expect(api.editorialWorkflowGit).toHaveBeenCalledWith(
entry.assets.concat(entry.dataFiles),
entry.dataFiles[0].slug,
[
{ path: 'static/media/image-1.png', sha: 'image-1.png' },
{ path: 'static/media/image-2.png', sha: 'image-2.png' },
],
{ useWorkflow: true },
);
});
});
describe('migratePullRequest', () => {
it('should migrate to pull request labels when no version', async () => {
const api = new API({ branch: 'master', repo: 'owner/repo' });
const pr = {
head: { ref: 'cms/2019-11-11-post-title' },
title: 'pr title',
number: 1,
labels: [],
};
const metadata = { type: 'PR' };
api.retrieveMetadataOld = jest.fn().mockResolvedValue(metadata);
const newBranch = 'cms/posts/2019-11-11-post-title';
const migrateToVersion1Result = {
metadata: { ...metadata, branch: newBranch, version: '1' },
pullRequest: { ...pr, number: 2 },
};
api.migrateToVersion1 = jest.fn().mockResolvedValue(migrateToVersion1Result);
api.migrateToPullRequestLabels = jest.fn();
await api.migratePullRequest(pr);
expect(api.migrateToVersion1).toHaveBeenCalledTimes(1);
expect(api.migrateToVersion1).toHaveBeenCalledWith(pr, metadata);
expect(api.migrateToPullRequestLabels).toHaveBeenCalledTimes(1);
expect(api.migrateToPullRequestLabels).toHaveBeenCalledWith(
migrateToVersion1Result.pullRequest,
migrateToVersion1Result.metadata,
);
expect(api.retrieveMetadataOld).toHaveBeenCalledTimes(1);
expect(api.retrieveMetadataOld).toHaveBeenCalledWith('2019-11-11-post-title');
});
it('should migrate to pull request labels when version is 1', async () => {
const api = new API({ branch: 'master', repo: 'owner/repo' });
api.migrateToVersion1 = jest.fn();
const pr = {
head: { ref: 'cms/posts/2019-11-11-post-title' },
title: 'pr title',
number: 1,
labels: [],
};
const metadata = { type: 'PR', version: '1' };
api.retrieveMetadataOld = jest.fn().mockResolvedValue(metadata);
api.migrateToPullRequestLabels = jest.fn().mockResolvedValue(pr, metadata);
await api.migratePullRequest(pr);
expect(api.migrateToVersion1).toHaveBeenCalledTimes(0);
expect(api.migrateToPullRequestLabels).toHaveBeenCalledTimes(1);
expect(api.migrateToPullRequestLabels).toHaveBeenCalledWith(pr, metadata);
expect(api.retrieveMetadataOld).toHaveBeenCalledTimes(1);
expect(api.retrieveMetadataOld).toHaveBeenCalledWith('posts/2019-11-11-post-title');
});
});
describe('migrateToVersion1', () => {
it('should migrate to version 1', async () => {
const api = new API({ branch: 'master', repo: 'owner/repo' });
const pr = {
head: { ref: 'cms/2019-11-11-post-title', sha: 'pr_head' },
title: 'pr title',
number: 1,
labels: [],
};
const newBranch = { ref: 'refs/heads/cms/posts/2019-11-11-post-title' };
api.createBranch = jest.fn().mockResolvedValue(newBranch);
api.getBranch = jest.fn().mockRejectedValue(new Error('Branch not found'));
const newPr = { ...pr, number: 2 };
api.createPR = jest.fn().mockResolvedValue(newPr);
api.getPullRequests = jest.fn().mockResolvedValue([]);
api.storeMetadata = jest.fn();
api.closePR = jest.fn();
api.deleteBranch = jest.fn();
api.deleteMetadata = jest.fn();
const branch = 'cms/2019-11-11-post-title';
const metadata = {
branch,
type: 'PR',
pr: { head: pr.head.sha },
commitMessage: 'commitMessage',
collection: 'posts',
};
const expectedMetadata = {
type: 'PR',
pr: { head: newPr.head.sha, number: 2 },
commitMessage: 'commitMessage',
collection: 'posts',
branch: 'cms/posts/2019-11-11-post-title',
version: '1',
};
await expect(api.migrateToVersion1(pr, metadata)).resolves.toEqual({
metadata: expectedMetadata,
pullRequest: newPr,
});
expect(api.getBranch).toHaveBeenCalledTimes(1);
expect(api.getBranch).toHaveBeenCalledWith('cms/posts/2019-11-11-post-title');
expect(api.createBranch).toHaveBeenCalledTimes(1);
expect(api.createBranch).toHaveBeenCalledWith('cms/posts/2019-11-11-post-title', 'pr_head');
expect(api.getPullRequests).toHaveBeenCalledTimes(1);
expect(api.getPullRequests).toHaveBeenCalledWith(
'cms/posts/2019-11-11-post-title',
'all',
expect.any(Function),
);
expect(api.createPR).toHaveBeenCalledTimes(1);
expect(api.createPR).toHaveBeenCalledWith('pr title', 'cms/posts/2019-11-11-post-title');
expect(api.storeMetadata).toHaveBeenCalledTimes(1);
expect(api.storeMetadata).toHaveBeenCalledWith(
'posts/2019-11-11-post-title',
expectedMetadata,
);
expect(api.closePR).toHaveBeenCalledTimes(1);
expect(api.closePR).toHaveBeenCalledWith(pr.number);
expect(api.deleteBranch).toHaveBeenCalledTimes(1);
expect(api.deleteBranch).toHaveBeenCalledWith('cms/2019-11-11-post-title');
expect(api.deleteMetadata).toHaveBeenCalledTimes(1);
expect(api.deleteMetadata).toHaveBeenCalledWith('2019-11-11-post-title');
});
it('should not create new branch if exists', async () => {
const api = new API({ branch: 'master', repo: 'owner/repo' });
const pr = {
head: { ref: 'cms/2019-11-11-post-title', sha: 'pr_head' },
title: 'pr title',
number: 1,
labels: [],
};
const newBranch = { ref: 'refs/heads/cms/posts/2019-11-11-post-title' };
api.createBranch = jest.fn();
api.getBranch = jest.fn().mockResolvedValue(newBranch);
const newPr = { ...pr, number: 2 };
api.createPR = jest.fn().mockResolvedValue(newPr);
api.getPullRequests = jest.fn().mockResolvedValue([]);
api.storeMetadata = jest.fn();
api.closePR = jest.fn();
api.deleteBranch = jest.fn();
api.deleteMetadata = jest.fn();
const branch = 'cms/2019-11-11-post-title';
const metadata = {
branch,
type: 'PR',
pr: { head: pr.head.sha },
commitMessage: 'commitMessage',
collection: 'posts',
};
const expectedMetadata = {
type: 'PR',
pr: { head: newPr.head.sha, number: 2 },
commitMessage: 'commitMessage',
collection: 'posts',
branch: 'cms/posts/2019-11-11-post-title',
version: '1',
};
await expect(api.migrateToVersion1(pr, metadata)).resolves.toEqual({
metadata: expectedMetadata,
pullRequest: newPr,
});
expect(api.getBranch).toHaveBeenCalledTimes(1);
expect(api.getBranch).toHaveBeenCalledWith('cms/posts/2019-11-11-post-title');
expect(api.createBranch).toHaveBeenCalledTimes(0);
expect(api.getPullRequests).toHaveBeenCalledTimes(1);
expect(api.getPullRequests).toHaveBeenCalledWith(
'cms/posts/2019-11-11-post-title',
'all',
expect.any(Function),
);
expect(api.createPR).toHaveBeenCalledTimes(1);
expect(api.createPR).toHaveBeenCalledWith('pr title', 'cms/posts/2019-11-11-post-title');
expect(api.storeMetadata).toHaveBeenCalledTimes(1);
expect(api.storeMetadata).toHaveBeenCalledWith(
'posts/2019-11-11-post-title',
expectedMetadata,
);
expect(api.closePR).toHaveBeenCalledTimes(1);
expect(api.closePR).toHaveBeenCalledWith(pr.number);
expect(api.deleteBranch).toHaveBeenCalledTimes(1);
expect(api.deleteBranch).toHaveBeenCalledWith('cms/2019-11-11-post-title');
expect(api.deleteMetadata).toHaveBeenCalledTimes(1);
expect(api.deleteMetadata).toHaveBeenCalledWith('2019-11-11-post-title');
});
it('should not create new pr if exists', async () => {
const api = new API({ branch: 'master', repo: 'owner/repo' });
const pr = {
head: { ref: 'cms/2019-11-11-post-title', sha: 'pr_head' },
title: 'pr title',
number: 1,
labels: [],
};
const newBranch = { ref: 'refs/heads/cms/posts/2019-11-11-post-title' };
api.createBranch = jest.fn();
api.getBranch = jest.fn().mockResolvedValue(newBranch);
const newPr = { ...pr, number: 2 };
api.createPR = jest.fn();
api.getPullRequests = jest.fn().mockResolvedValue([newPr]);
api.storeMetadata = jest.fn();
api.closePR = jest.fn();
api.deleteBranch = jest.fn();
api.deleteMetadata = jest.fn();
const branch = 'cms/2019-11-11-post-title';
const metadata = {
branch,
type: 'PR',
pr: { head: pr.head.sha },
commitMessage: 'commitMessage',
collection: 'posts',
};
const expectedMetadata = {
type: 'PR',
pr: { head: newPr.head.sha, number: 2 },
commitMessage: 'commitMessage',
collection: 'posts',
branch: 'cms/posts/2019-11-11-post-title',
version: '1',
};
await expect(api.migrateToVersion1(pr, metadata)).resolves.toEqual({
metadata: expectedMetadata,
pullRequest: newPr,
});
expect(api.getBranch).toHaveBeenCalledTimes(1);
expect(api.getBranch).toHaveBeenCalledWith('cms/posts/2019-11-11-post-title');
expect(api.createBranch).toHaveBeenCalledTimes(0);
expect(api.getPullRequests).toHaveBeenCalledTimes(1);
expect(api.getPullRequests).toHaveBeenCalledWith(
'cms/posts/2019-11-11-post-title',
'all',
expect.any(Function),
);
expect(api.createPR).toHaveBeenCalledTimes(0);
expect(api.storeMetadata).toHaveBeenCalledTimes(1);
expect(api.storeMetadata).toHaveBeenCalledWith(
'posts/2019-11-11-post-title',
expectedMetadata,
);
expect(api.closePR).toHaveBeenCalledTimes(1);
expect(api.closePR).toHaveBeenCalledWith(pr.number);
expect(api.deleteBranch).toHaveBeenCalledTimes(1);
expect(api.deleteBranch).toHaveBeenCalledWith('cms/2019-11-11-post-title');
expect(api.deleteMetadata).toHaveBeenCalledTimes(1);
expect(api.deleteMetadata).toHaveBeenCalledWith('2019-11-11-post-title');
});
});
describe('migrateToPullRequestLabels', () => {
it('should migrate to pull request labels', async () => {
const api = new API({ branch: 'master', repo: 'owner/repo' });
const pr = {
head: { ref: 'cms/posts/2019-11-11-post-title', sha: 'pr_head' },
title: 'pr title',
number: 1,
labels: [],
};
api.setPullRequestStatus = jest.fn();
api.deleteMetadata = jest.fn();
const metadata = {
branch: pr.head.ref,
type: 'PR',
pr: { head: pr.head.sha },
commitMessage: 'commitMessage',
collection: 'posts',
status: 'pending_review',
};
await api.migrateToPullRequestLabels(pr, metadata);
expect(api.setPullRequestStatus).toHaveBeenCalledTimes(1);
expect(api.setPullRequestStatus).toHaveBeenCalledWith(pr, 'pending_review');
expect(api.deleteMetadata).toHaveBeenCalledTimes(1);
expect(api.deleteMetadata).toHaveBeenCalledWith('posts/2019-11-11-post-title');
});
});
describe('rebaseSingleCommit', () => {
it('should create updated tree and commit', async () => {
const api = new API({ branch: 'master', repo: 'owner/repo' });
api.getDifferences = jest.fn().mockResolvedValueOnce({
files: [
{ filename: 'removed.md', status: 'removed', sha: 'removed_sha' },
{
filename: 'renamed.md',
status: 'renamed',
previous_filename: 'previous_filename.md',
sha: 'renamed_sha',
},
{ filename: 'added.md', status: 'added', sha: 'added_sha' },
],
});
const newTree = { sha: 'new_tree_sha' };
api.updateTree = jest.fn().mockResolvedValueOnce(newTree);
const newCommit = { sha: 'newCommit' };
api.createCommit = jest.fn().mockResolvedValueOnce(newCommit);
const baseCommit = { sha: 'base_commit_sha' };
const commit = {
sha: 'sha',
parents: [{ sha: 'parent_sha' }],
commit: {
message: 'message',
author: { name: 'author' },
committer: { name: 'committer' },
},
};
await expect(api.rebaseSingleCommit(baseCommit, commit)).resolves.toBe(newCommit);
expect(api.getDifferences).toHaveBeenCalledTimes(1);
expect(api.getDifferences).toHaveBeenCalledWith('parent_sha', 'sha');
expect(api.updateTree).toHaveBeenCalledTimes(1);
expect(api.updateTree).toHaveBeenCalledWith('base_commit_sha', [
{ path: 'removed.md', sha: null },
{ path: 'previous_filename.md', sha: null },
{ path: 'renamed.md', sha: 'renamed_sha' },
{ path: 'added.md', sha: 'added_sha' },
]);
expect(api.createCommit).toHaveBeenCalledTimes(1);
expect(api.createCommit).toHaveBeenCalledWith(
'message',
newTree.sha,
[baseCommit.sha],
{ name: 'author' },
{ name: 'committer' },
);
});
});
describe('listFiles', () => {
it('should get files by depth', async () => {
const api = new API({ branch: 'master', repo: 'owner/repo' });
const tree = [
{
path: 'post.md',
type: 'blob',
},
{
path: 'dir1',
type: 'tree',
},
{
path: 'dir1/nested-post.md',
type: 'blob',
},
{
path: 'dir1/dir2',
type: 'tree',
},
{
path: 'dir1/dir2/nested-post.md',
type: 'blob',
},
];
api.request = jest.fn().mockResolvedValue({ tree });
await expect(api.listFiles('posts', { depth: 1 })).resolves.toEqual([
{
path: 'posts/post.md',
type: 'blob',
name: 'post.md',
},
]);
expect(api.request).toHaveBeenCalledTimes(1);
expect(api.request).toHaveBeenCalledWith('/repos/owner/repo/git/trees/master:posts', {
params: {},
});
jest.clearAllMocks();
await expect(api.listFiles('posts', { depth: 2 })).resolves.toEqual([
{
path: 'posts/post.md',
type: 'blob',
name: 'post.md',
},
{
path: 'posts/dir1/nested-post.md',
type: 'blob',
name: 'nested-post.md',
},
]);
expect(api.request).toHaveBeenCalledTimes(1);
expect(api.request).toHaveBeenCalledWith('/repos/owner/repo/git/trees/master:posts', {
params: { recursive: 1 },
});
jest.clearAllMocks();
await expect(api.listFiles('posts', { depth: 3 })).resolves.toEqual([
{
path: 'posts/post.md',
type: 'blob',
name: 'post.md',
},
{
path: 'posts/dir1/nested-post.md',
type: 'blob',
name: 'nested-post.md',
},
{
path: 'posts/dir1/dir2/nested-post.md',
type: 'blob',
name: 'nested-post.md',
},
]);
expect(api.request).toHaveBeenCalledTimes(1);
expect(api.request).toHaveBeenCalledWith('/repos/owner/repo/git/trees/master:posts', {
params: { recursive: 1 },
});
});
});
test('should get preview statuses', async () => {
const api = new API({ repo: 'repo' });
const statuses = [
{ context: 'deploy', state: 'success', target_url: 'deploy-url' },
{ context: 'build', state: 'error' },
];
api.request = jest.fn(() => Promise.resolve({ statuses }));
const sha = 'sha';
api.getBranchPullRequest = jest.fn(() => Promise.resolve({ head: { sha } }));
const collection = 'collection';
const slug = 'slug';
await expect(api.getStatuses(collection, slug)).resolves.toEqual([
{ context: 'deploy', state: 'success', target_url: 'deploy-url' },
{ context: 'build', state: 'other' },
]);
expect(api.getBranchPullRequest).toHaveBeenCalledTimes(1);
expect(api.getBranchPullRequest).toHaveBeenCalledWith('cms/collection/slug');
expect(api.request).toHaveBeenCalledTimes(1);
expect(api.request).toHaveBeenCalledWith(`/repos/repo/commits/${sha}/status`);
});
});

View File

@@ -0,0 +1,69 @@
import GraphQLAPI from '../GraphQLAPI';
global.fetch = jest.fn().mockRejectedValue(new Error('should not call fetch inside tests'));
describe('github GraphQL API', () => {
beforeEach(() => {
jest.resetAllMocks();
});
describe('editorialWorkflowGit', () => {
it('should should flatten nested tree into a list of files', () => {
const api = new GraphQLAPI({ branch: 'gh-pages', repo: 'owner/my-repo' });
const entries = [
{
name: 'post-1.md',
sha: 'sha-1',
type: 'blob',
blob: { size: 1 },
},
{
name: 'post-2.md',
sha: 'sha-2',
type: 'blob',
blob: { size: 2 },
},
{
name: '2019',
sha: 'dir-sha',
type: 'tree',
object: {
entries: [
{
name: 'nested-post.md',
sha: 'nested-post-sha',
type: 'blob',
blob: { size: 3 },
},
],
},
},
];
const path = 'posts';
expect(api.getAllFiles(entries, path)).toEqual([
{
name: 'post-1.md',
id: 'sha-1',
type: 'blob',
size: 1,
path: 'posts/post-1.md',
},
{
name: 'post-2.md',
id: 'sha-2',
type: 'blob',
size: 2,
path: 'posts/post-2.md',
},
{
name: 'nested-post.md',
id: 'nested-post-sha',
type: 'blob',
size: 3,
path: 'posts/2019/nested-post.md',
},
]);
});
});
});

View File

@@ -0,0 +1,361 @@
import { Cursor, CURSOR_COMPATIBILITY_SYMBOL } from 'decap-cms-lib-util';
import GitHubImplementation from '../implementation';
jest.spyOn(console, 'error').mockImplementation(() => {});
describe('github backend implementation', () => {
const config = {
backend: {
repo: 'owner/repo',
open_authoring: false,
api_root: 'https://api.github.com',
},
};
const createObjectURL = jest.fn();
global.URL = {
createObjectURL,
};
createObjectURL.mockReturnValue('displayURL');
beforeEach(() => {
jest.clearAllMocks();
});
describe('forkExists', () => {
it('should return true when repo is fork and parent matches originRepo', async () => {
const gitHubImplementation = new GitHubImplementation(config);
gitHubImplementation.currentUser = jest.fn().mockResolvedValue({ login: 'login' });
global.fetch = jest.fn().mockResolvedValue({
// matching should be case-insensitive
json: () => ({ fork: true, parent: { full_name: 'OWNER/REPO' } }),
});
await expect(gitHubImplementation.forkExists({ token: 'token' })).resolves.toBe(true);
expect(gitHubImplementation.currentUser).toHaveBeenCalledTimes(1);
expect(gitHubImplementation.currentUser).toHaveBeenCalledWith({ token: 'token' });
expect(global.fetch).toHaveBeenCalledTimes(1);
expect(global.fetch).toHaveBeenCalledWith('https://api.github.com/repos/login/repo', {
method: 'GET',
headers: {
Authorization: 'token token',
},
signal: expect.any(AbortSignal),
});
});
it('should return false when repo is not a fork', async () => {
const gitHubImplementation = new GitHubImplementation(config);
gitHubImplementation.currentUser = jest.fn().mockResolvedValue({ login: 'login' });
global.fetch = jest.fn().mockResolvedValue({
// matching should be case-insensitive
json: () => ({ fork: false }),
});
expect.assertions(1);
await expect(gitHubImplementation.forkExists({ token: 'token' })).resolves.toBe(false);
});
it("should return false when parent doesn't match originRepo", async () => {
const gitHubImplementation = new GitHubImplementation(config);
gitHubImplementation.currentUser = jest.fn().mockResolvedValue({ login: 'login' });
global.fetch = jest.fn().mockResolvedValue({
json: () => ({ fork: true, parent: { full_name: 'owner/other_repo' } }),
});
expect.assertions(1);
await expect(gitHubImplementation.forkExists({ token: 'token' })).resolves.toBe(false);
});
});
describe('persistMedia', () => {
const persistFiles = jest.fn();
const mockAPI = {
persistFiles,
};
persistFiles.mockImplementation((_, files) => {
files.forEach((file, index) => {
file.sha = index;
});
});
it('should persist media file', async () => {
const gitHubImplementation = new GitHubImplementation(config);
gitHubImplementation.api = mockAPI;
const mediaFile = {
fileObj: { size: 100, name: 'image.png' },
path: '/media/image.png',
};
expect.assertions(5);
await expect(gitHubImplementation.persistMedia(mediaFile, {})).resolves.toEqual({
id: 0,
name: 'image.png',
size: 100,
displayURL: 'displayURL',
path: 'media/image.png',
});
expect(persistFiles).toHaveBeenCalledTimes(1);
expect(persistFiles).toHaveBeenCalledWith([], [mediaFile], {});
expect(createObjectURL).toHaveBeenCalledTimes(1);
expect(createObjectURL).toHaveBeenCalledWith(mediaFile.fileObj);
});
it('should log and throw error on "persistFiles" error', async () => {
const gitHubImplementation = new GitHubImplementation(config);
gitHubImplementation.api = mockAPI;
const error = new Error('failed to persist files');
persistFiles.mockRejectedValue(error);
const mediaFile = {
value: 'image.png',
fileObj: { size: 100 },
path: '/media/image.png',
};
expect.assertions(5);
await expect(gitHubImplementation.persistMedia(mediaFile)).rejects.toThrowError(error);
expect(persistFiles).toHaveBeenCalledTimes(1);
expect(createObjectURL).toHaveBeenCalledTimes(0);
expect(console.error).toHaveBeenCalledTimes(1);
expect(console.error).toHaveBeenCalledWith(error);
});
});
describe('unpublishedEntry', () => {
const generateContentKey = jest.fn();
const retrieveUnpublishedEntryData = jest.fn();
const mockAPI = {
generateContentKey,
retrieveUnpublishedEntryData,
};
it('should return unpublished entry data', async () => {
const gitHubImplementation = new GitHubImplementation(config);
gitHubImplementation.api = mockAPI;
gitHubImplementation.loadEntryMediaFiles = jest
.fn()
.mockResolvedValue([{ path: 'image.png', id: 'sha' }]);
generateContentKey.mockReturnValue('contentKey');
const data = {
collection: 'collection',
slug: 'slug',
status: 'draft',
diffs: [],
updatedAt: 'updatedAt',
};
retrieveUnpublishedEntryData.mockResolvedValue(data);
const collection = 'posts';
const slug = 'slug';
await expect(gitHubImplementation.unpublishedEntry({ collection, slug })).resolves.toEqual(
data,
);
expect(generateContentKey).toHaveBeenCalledTimes(1);
expect(generateContentKey).toHaveBeenCalledWith('posts', 'slug');
expect(retrieveUnpublishedEntryData).toHaveBeenCalledTimes(1);
expect(retrieveUnpublishedEntryData).toHaveBeenCalledWith('contentKey');
});
});
describe('entriesByFolder', () => {
const listFiles = jest.fn();
const readFile = jest.fn();
const readFileMetadata = jest.fn(() => Promise.resolve({ author: '', updatedOn: '' }));
const mockAPI = {
listFiles,
readFile,
readFileMetadata,
originRepoURL: 'originRepoURL',
};
it('should return entries and cursor', async () => {
const gitHubImplementation = new GitHubImplementation(config);
gitHubImplementation.api = mockAPI;
const files = [];
const count = 1501;
for (let i = 0; i < count; i++) {
const id = `${i}`.padStart(`${count}`.length, '0');
files.push({
id,
path: `posts/post-${id}.md`,
});
}
listFiles.mockResolvedValue(files);
readFile.mockImplementation((path, id) => Promise.resolve(`${id}`));
const expectedEntries = files
.slice(0, 20)
.map(({ id, path }) => ({ data: id, file: { path, id, author: '', updatedOn: '' } }));
const expectedCursor = Cursor.create({
actions: ['next', 'last'],
meta: { page: 1, count, pageSize: 20, pageCount: 76 },
data: { files },
});
expectedEntries[CURSOR_COMPATIBILITY_SYMBOL] = expectedCursor;
const result = await gitHubImplementation.entriesByFolder('posts', 'md', 1);
expect(result).toEqual(expectedEntries);
expect(listFiles).toHaveBeenCalledTimes(1);
expect(listFiles).toHaveBeenCalledWith('posts', { depth: 1, repoURL: 'originRepoURL' });
expect(readFile).toHaveBeenCalledTimes(20);
});
});
describe('traverseCursor', () => {
const listFiles = jest.fn();
const readFile = jest.fn((path, id) => Promise.resolve(`${id}`));
const readFileMetadata = jest.fn(() => Promise.resolve({}));
const mockAPI = {
listFiles,
readFile,
originRepoURL: 'originRepoURL',
readFileMetadata,
};
const files = [];
const count = 1501;
for (let i = 0; i < count; i++) {
const id = `${i}`.padStart(`${count}`.length, '0');
files.push({
id,
path: `posts/post-${id}.md`,
});
}
it('should handle next action', async () => {
const gitHubImplementation = new GitHubImplementation(config);
gitHubImplementation.api = mockAPI;
const cursor = Cursor.create({
actions: ['next', 'last'],
meta: { page: 1, count, pageSize: 20, pageCount: 76 },
data: { files },
});
const expectedEntries = files
.slice(20, 40)
.map(({ id, path }) => ({ data: id, file: { path, id } }));
const expectedCursor = Cursor.create({
actions: ['prev', 'first', 'next', 'last'],
meta: { page: 2, count, pageSize: 20, pageCount: 76 },
data: { files },
});
const result = await gitHubImplementation.traverseCursor(cursor, 'next');
expect(result).toEqual({
entries: expectedEntries,
cursor: expectedCursor,
});
});
it('should handle prev action', async () => {
const gitHubImplementation = new GitHubImplementation(config);
gitHubImplementation.api = mockAPI;
const cursor = Cursor.create({
actions: ['prev', 'first', 'next', 'last'],
meta: { page: 2, count, pageSize: 20, pageCount: 76 },
data: { files },
});
const expectedEntries = files
.slice(0, 20)
.map(({ id, path }) => ({ data: id, file: { path, id } }));
const expectedCursor = Cursor.create({
actions: ['next', 'last'],
meta: { page: 1, count, pageSize: 20, pageCount: 76 },
data: { files },
});
const result = await gitHubImplementation.traverseCursor(cursor, 'prev');
expect(result).toEqual({
entries: expectedEntries,
cursor: expectedCursor,
});
});
it('should handle last action', async () => {
const gitHubImplementation = new GitHubImplementation(config);
gitHubImplementation.api = mockAPI;
const cursor = Cursor.create({
actions: ['next', 'last'],
meta: { page: 1, count, pageSize: 20, pageCount: 76 },
data: { files },
});
const expectedEntries = files
.slice(1500)
.map(({ id, path }) => ({ data: id, file: { path, id } }));
const expectedCursor = Cursor.create({
actions: ['prev', 'first'],
meta: { page: 76, count, pageSize: 20, pageCount: 76 },
data: { files },
});
const result = await gitHubImplementation.traverseCursor(cursor, 'last');
expect(result).toEqual({
entries: expectedEntries,
cursor: expectedCursor,
});
});
it('should handle first action', async () => {
const gitHubImplementation = new GitHubImplementation(config);
gitHubImplementation.api = mockAPI;
const cursor = Cursor.create({
actions: ['prev', 'first'],
meta: { page: 76, count, pageSize: 20, pageCount: 76 },
data: { files },
});
const expectedEntries = files
.slice(0, 20)
.map(({ id, path }) => ({ data: id, file: { path, id } }));
const expectedCursor = Cursor.create({
actions: ['next', 'last'],
meta: { page: 1, count, pageSize: 20, pageCount: 76 },
data: { files },
});
const result = await gitHubImplementation.traverseCursor(cursor, 'first');
expect(result).toEqual({
entries: expectedEntries,
cursor: expectedCursor,
});
});
});
});

File diff suppressed because one or more lines are too long

92
node_modules/decap-cms-backend-github/src/fragments.ts generated vendored Normal file
View File

@@ -0,0 +1,92 @@
import { gql } from 'graphql-tag';
export const repository = gql`
fragment RepositoryParts on Repository {
id
isFork
}
`;
export const blobWithText = gql`
fragment BlobWithTextParts on Blob {
id
text
is_binary: isBinary
}
`;
export const object = gql`
fragment ObjectParts on GitObject {
id
sha: oid
}
`;
export const branch = gql`
fragment BranchParts on Ref {
commit: target {
...ObjectParts
}
id
name
prefix
repository {
...RepositoryParts
}
}
${object}
${repository}
`;
export const pullRequest = gql`
fragment PullRequestParts on PullRequest {
id
baseRefName
baseRefOid
body
headRefName
headRefOid
number
state
title
merged_at: mergedAt
updated_at: updatedAt
user: author {
login
... on User {
name
}
}
repository {
...RepositoryParts
}
labels(last: 100) {
nodes {
name
}
}
}
${repository}
`;
export const treeEntry = gql`
fragment TreeEntryParts on TreeEntry {
path: name
sha: oid
type
mode
}
`;
export const fileEntry = gql`
fragment FileEntryParts on TreeEntry {
name
sha: oid
type
blob: object {
... on Blob {
size: byteSize
}
}
}
`;

View File

@@ -0,0 +1,719 @@
import * as React from 'react';
import semaphore from 'semaphore';
import trimStart from 'lodash/trimStart';
import { stripIndent } from 'common-tags';
import {
CURSOR_COMPATIBILITY_SYMBOL,
Cursor,
asyncLock,
basename,
getBlobSHA,
entriesByFolder,
entriesByFiles,
unpublishedEntries,
getMediaDisplayURL,
getMediaAsBlob,
filterByExtension,
getPreviewStatus,
runWithLock,
blobToFileObj,
contentKeyFromBranch,
unsentRequest,
branchFromContentKey,
} from 'decap-cms-lib-util';
import AuthenticationPage from './AuthenticationPage';
import API, { API_NAME } from './API';
import GraphQLAPI from './GraphQLAPI';
import type { Octokit } from '@octokit/rest';
import type {
AsyncLock,
Implementation,
AssetProxy,
PersistOptions,
DisplayURL,
User,
Credentials,
Config,
ImplementationFile,
UnpublishedEntryMediaFile,
Entry,
} from 'decap-cms-lib-util';
import type { Semaphore } from 'semaphore';
export type GitHubUser = Octokit.UsersGetAuthenticatedResponse;
const MAX_CONCURRENT_DOWNLOADS = 10;
type ApiFile = { id: string; type: string; name: string; path: string; size: number };
const { fetchWithTimeout: fetch } = unsentRequest;
const STATUS_PAGE = 'https://www.githubstatus.com';
const GITHUB_STATUS_ENDPOINT = `${STATUS_PAGE}/api/v2/components.json`;
const GITHUB_OPERATIONAL_UNITS = ['API Requests', 'Issues, Pull Requests, Projects'];
type GitHubStatusComponent = {
id: string;
name: string;
status: string;
};
export default class GitHub implements Implementation {
lock: AsyncLock;
api: API | null;
options: {
proxied: boolean;
API: API | null;
useWorkflow?: boolean;
initialWorkflowStatus: string;
};
originRepo: string;
isBranchConfigured: boolean;
repo?: string;
openAuthoringEnabled: boolean;
useOpenAuthoring?: boolean;
alwaysForkEnabled: boolean;
branch: string;
apiRoot: string;
mediaFolder: string;
previewContext: string;
token: string | null;
tokenKeyword: string;
squashMerges: boolean;
cmsLabelPrefix: string;
useGraphql: boolean;
baseUrl?: string;
bypassWriteAccessCheckForAppTokens = false;
_currentUserPromise?: Promise<GitHubUser>;
_userIsOriginMaintainerPromises?: {
[key: string]: Promise<boolean>;
};
_mediaDisplayURLSem?: Semaphore;
constructor(config: Config, options = {}) {
this.options = {
proxied: false,
API: null,
initialWorkflowStatus: '',
...options,
};
if (
!this.options.proxied &&
(config.backend.repo === null || config.backend.repo === undefined)
) {
throw new Error('The GitHub backend needs a "repo" in the backend configuration.');
}
this.api = this.options.API || null;
this.isBranchConfigured = config.backend.branch ? true : false;
this.openAuthoringEnabled = config.backend.open_authoring || false;
if (this.openAuthoringEnabled) {
if (!this.options.useWorkflow) {
throw new Error(
'backend.open_authoring is true but publish_mode is not set to editorial_workflow.',
);
}
this.originRepo = config.backend.repo || '';
} else {
this.repo = this.originRepo = config.backend.repo || '';
}
this.alwaysForkEnabled = config.backend.always_fork || false;
this.branch = config.backend.branch?.trim() || 'master';
this.apiRoot = config.backend.api_root || 'https://api.github.com';
this.token = '';
this.tokenKeyword = 'token';
this.baseUrl = config.backend.base_url;
this.squashMerges = config.backend.squash_merges || false;
this.cmsLabelPrefix = config.backend.cms_label_prefix || '';
this.useGraphql = config.backend.use_graphql || false;
this.mediaFolder = config.media_folder;
this.previewContext = config.backend.preview_context || '';
this.lock = asyncLock();
}
isGitBackend() {
return true;
}
async status() {
const api = await fetch(GITHUB_STATUS_ENDPOINT)
.then(res => res.json())
.then(res => {
return res['components']
.filter((statusComponent: GitHubStatusComponent) =>
GITHUB_OPERATIONAL_UNITS.includes(statusComponent.name),
)
.every(
(statusComponent: GitHubStatusComponent) => statusComponent.status === 'operational',
);
})
.catch(e => {
console.warn('Failed getting GitHub status', e);
return true;
});
let auth = false;
// no need to check auth if api is down
if (api) {
auth =
(await this.api
?.getUser({ token: this.token ?? '' })
.then(user => !!user)
.catch(e => {
console.warn('Failed getting GitHub user', e);
return false;
})) || false;
}
return { auth: { status: auth }, api: { status: api, statusPage: STATUS_PAGE } };
}
authComponent() {
const wrappedAuthenticationPage = (props: Record<string, unknown>) => (
<AuthenticationPage {...props} backend={this} />
);
wrappedAuthenticationPage.displayName = 'AuthenticationPage';
return wrappedAuthenticationPage;
}
restoreUser(user: User) {
return this.openAuthoringEnabled
? this.authenticateWithFork({ userData: user, getPermissionToFork: () => true }).then(() =>
this.authenticate(user),
)
: this.authenticate(user);
}
async pollUntilForkExists({ repo, token }: { repo: string; token: string }) {
const pollDelay = 250; // milliseconds
let repoExists = false;
while (!repoExists) {
repoExists = await fetch(`${this.apiRoot}/repos/${repo}`, {
headers: { Authorization: `${this.tokenKeyword} ${token}` },
})
.then(() => true)
.catch(err => {
if (err && err.status === 404) {
console.log('This 404 was expected and handled appropriately.');
return false;
} else {
return Promise.reject(err);
}
});
// wait between polls
if (!repoExists) {
await new Promise(resolve => setTimeout(resolve, pollDelay));
}
}
return Promise.resolve();
}
async currentUser({ token }: { token: string }) {
if (!this._currentUserPromise) {
this._currentUserPromise = fetch(`${this.apiRoot}/user`, {
headers: {
Authorization: `${this.tokenKeyword} ${token}`,
},
}).then(res => res.json());
}
return this._currentUserPromise;
}
async userIsOriginMaintainer({
username: usernameArg,
token,
}: {
username?: string;
token: string;
}) {
const username = usernameArg || (await this.currentUser({ token })).login;
this._userIsOriginMaintainerPromises = this._userIsOriginMaintainerPromises || {};
if (!this._userIsOriginMaintainerPromises[username]) {
this._userIsOriginMaintainerPromises[username] = fetch(
`${this.apiRoot}/repos/${this.originRepo}/collaborators/${username}/permission`,
{
headers: {
Authorization: `${this.tokenKeyword} ${token}`,
},
},
)
.then(res => res.json())
.then(({ permission }) => permission === 'admin' || permission === 'write');
}
return this._userIsOriginMaintainerPromises[username];
}
async forkExists({ token }: { token: string }) {
try {
const currentUser = await this.currentUser({ token });
const repoName = this.originRepo.split('/')[1];
const repo = await fetch(`${this.apiRoot}/repos/${currentUser.login}/${repoName}`, {
method: 'GET',
headers: {
Authorization: `${this.tokenKeyword} ${token}`,
},
}).then(res => res.json());
// https://developer.github.com/v3/repos/#get
// The parent and source objects are present when the repository is a fork.
// parent is the repository this repository was forked from, source is the ultimate source for the network.
const forkExists =
repo.fork === true &&
repo.parent &&
repo.parent.full_name.toLowerCase() === this.originRepo.toLowerCase();
return forkExists;
} catch {
return false;
}
}
async authenticateWithFork({
userData,
getPermissionToFork,
}: {
userData: User;
getPermissionToFork: () => Promise<boolean> | boolean;
}) {
if (!this.openAuthoringEnabled) {
throw new Error('Cannot authenticate with fork; Open Authoring is turned off.');
}
const token = userData.token as string;
// Origin maintainers should be able to use the CMS normally. If alwaysFork
// is enabled we always fork (and avoid the origin maintainer check)
if (!this.alwaysForkEnabled && (await this.userIsOriginMaintainer({ token }))) {
this.repo = this.originRepo;
this.useOpenAuthoring = false;
return Promise.resolve();
}
// If a fork exists merge it with upstream
// otherwise create a new fork.
const currentUser = await this.currentUser({ token });
const repoName = this.originRepo.split('/')[1];
this.repo = `${currentUser.login}/${repoName}`;
this.useOpenAuthoring = true;
if (await this.forkExists({ token })) {
return fetch(`${this.apiRoot}/repos/${this.repo}/merge-upstream`, {
method: 'POST',
headers: {
Authorization: `${this.tokenKeyword} ${token}`,
},
body: JSON.stringify({
branch: this.branch,
}),
});
} else {
await getPermissionToFork();
const fork = await fetch(`${this.apiRoot}/repos/${this.originRepo}/forks`, {
method: 'POST',
headers: {
Authorization: `${this.tokenKeyword} ${token}`,
},
}).then(res => res.json());
return this.pollUntilForkExists({ repo: fork.full_name, token });
}
}
async authenticate(state: Credentials) {
this.token = state.token as string;
// Query the default branch name when the `branch` property is missing
// in the config file
if (!this.isBranchConfigured) {
const repoInfo = await fetch(`${this.apiRoot}/repos/${this.originRepo}`, {
headers: { Authorization: `token ${this.token}` },
})
.then(res => res.json())
.catch(() => null);
if (repoInfo && repoInfo.default_branch) {
this.branch = repoInfo.default_branch;
}
}
const apiCtor = this.useGraphql ? GraphQLAPI : API;
this.api = new apiCtor({
token: this.token,
tokenKeyword: this.tokenKeyword,
branch: this.branch,
repo: this.repo,
originRepo: this.originRepo,
apiRoot: this.apiRoot,
squashMerges: this.squashMerges,
cmsLabelPrefix: this.cmsLabelPrefix,
useOpenAuthoring: this.useOpenAuthoring,
initialWorkflowStatus: this.options.initialWorkflowStatus,
baseUrl: this.baseUrl,
getUser: this.currentUser,
});
const user = await this.api!.user();
const isCollab = await this.api!.hasWriteAccess().catch(error => {
error.message = stripIndent`
Repo "${this.repo}" not found.
Please ensure the repo information is spelled correctly.
If the repo is private, make sure you're logged into a GitHub account with access.
If your repo is under an organization, ensure the organization has granted access to Decap CMS.
`;
throw error;
});
// Unauthorized user
if (!isCollab && !this.bypassWriteAccessCheckForAppTokens) {
throw new Error('Your GitHub user account does not have access to this repo.');
}
// if (!this.isBranchConfigured) {
// const defaultBranchName = await this.api.getDefaultBranchName()
// if (defaultBranchName) {
// this.branch = defaultBranchName;
// }
// }
// Authorized user
return { ...user, token: state.token as string, useOpenAuthoring: this.useOpenAuthoring };
}
logout() {
this.token = null;
if (this.api && this.api.reset && typeof this.api.reset === 'function') {
return this.api.reset();
}
}
getToken() {
return Promise.resolve(this.token);
}
getCursorAndFiles = (files: ApiFile[], page: number) => {
const pageSize = 20;
const count = files.length;
const pageCount = Math.ceil(files.length / pageSize);
const actions = [] as string[];
if (page > 1) {
actions.push('prev');
actions.push('first');
}
if (page < pageCount) {
actions.push('next');
actions.push('last');
}
const cursor = Cursor.create({
actions,
meta: { page, count, pageSize, pageCount },
data: { files },
});
const pageFiles = files.slice((page - 1) * pageSize, page * pageSize);
return { cursor, files: pageFiles };
};
async entriesByFolder(folder: string, extension: string, depth: number) {
const repoURL = this.api!.originRepoURL;
let cursor: Cursor;
const listFiles = () =>
this.api!.listFiles(folder, {
repoURL,
depth,
}).then(files => {
const filtered = files.filter(file => filterByExtension(file, extension));
const result = this.getCursorAndFiles(filtered, 1);
cursor = result.cursor;
return result.files;
});
const readFile = (path: string, id: string | null | undefined) =>
this.api!.readFile(path, id, { repoURL }) as Promise<string>;
const files = await entriesByFolder(
listFiles,
readFile,
this.api!.readFileMetadata.bind(this.api),
API_NAME,
);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
files[CURSOR_COMPATIBILITY_SYMBOL] = cursor;
return files;
}
async allEntriesByFolder(folder: string, extension: string, depth: number, pathRegex?: RegExp) {
const repoURL = this.api!.originRepoURL;
const listFiles = () =>
this.api!.listFiles(folder, {
repoURL,
depth,
}).then(files =>
files.filter(
file => (!pathRegex || pathRegex.test(file.path)) && filterByExtension(file, extension),
),
);
const readFile = (path: string, id: string | null | undefined) => {
return this.api!.readFile(path, id, { repoURL }) as Promise<string>;
};
const files = await entriesByFolder(
listFiles,
readFile,
this.api!.readFileMetadata.bind(this.api),
API_NAME,
);
return files;
}
entriesByFiles(files: ImplementationFile[]) {
const repoURL = this.useOpenAuthoring ? this.api!.originRepoURL : this.api!.repoURL;
const readFile = (path: string, id: string | null | undefined) =>
this.api!.readFile(path, id, { repoURL }).catch(() => '') as Promise<string>;
return entriesByFiles(files, readFile, this.api!.readFileMetadata.bind(this.api), API_NAME);
}
// Fetches a single entry.
getEntry(path: string) {
const repoURL = this.api!.originRepoURL;
return this.api!.readFile(path, null, { repoURL })
.then(data => ({
file: { path, id: null },
data: data as string,
}))
.catch(() => ({ file: { path, id: null }, data: '' }));
}
getMedia(mediaFolder = this.mediaFolder) {
return this.api!.listFiles(mediaFolder).then(files =>
files.map(({ id, name, size, path }) => {
// load media using getMediaDisplayURL to avoid token expiration with GitHub raw content urls
// for private repositories
return { id, name, size, displayURL: { id, path }, path };
}),
);
}
async getMediaFile(path: string) {
const blob = await getMediaAsBlob(path, null, this.api!.readFile.bind(this.api!));
const name = basename(path);
const fileObj = blobToFileObj(name, blob);
const url = URL.createObjectURL(fileObj);
const id = await getBlobSHA(blob);
return {
id,
displayURL: url,
path,
name,
size: fileObj.size,
file: fileObj,
url,
};
}
getMediaDisplayURL(displayURL: DisplayURL) {
this._mediaDisplayURLSem = this._mediaDisplayURLSem || semaphore(MAX_CONCURRENT_DOWNLOADS);
return getMediaDisplayURL(
displayURL,
this.api!.readFile.bind(this.api!),
this._mediaDisplayURLSem,
);
}
persistEntry(entry: Entry, options: PersistOptions) {
// persistEntry is a transactional operation
return runWithLock(
this.lock,
() => this.api!.persistFiles(entry.dataFiles, entry.assets, options),
'Failed to acquire persist entry lock',
);
}
async persistMedia(mediaFile: AssetProxy, options: PersistOptions) {
try {
await this.api!.persistFiles([], [mediaFile], options);
const { sha, path, fileObj } = mediaFile as AssetProxy & { sha: string };
const displayURL = fileObj ? URL.createObjectURL(fileObj) : '';
return {
id: sha,
name: fileObj!.name,
size: fileObj!.size,
displayURL,
path: trimStart(path, '/'),
};
} catch (error) {
console.error(error);
throw error;
}
}
deleteFiles(paths: string[], commitMessage: string) {
return this.api!.deleteFiles(paths, commitMessage);
}
async traverseCursor(cursor: Cursor, action: string) {
const meta = cursor.meta!;
const files = cursor.data!.get('files')!.toJS() as ApiFile[];
let result: { cursor: Cursor; files: ApiFile[] };
switch (action) {
case 'first': {
result = this.getCursorAndFiles(files, 1);
break;
}
case 'last': {
result = this.getCursorAndFiles(files, meta.get('pageCount'));
break;
}
case 'next': {
result = this.getCursorAndFiles(files, meta.get('page') + 1);
break;
}
case 'prev': {
result = this.getCursorAndFiles(files, meta.get('page') - 1);
break;
}
default: {
result = this.getCursorAndFiles(files, 1);
break;
}
}
const readFile = (path: string, id: string | null | undefined) =>
this.api!.readFile(path, id, { repoURL: this.api!.originRepoURL }).catch(
() => '',
) as Promise<string>;
const entries = await entriesByFiles(
result.files,
readFile,
this.api!.readFileMetadata.bind(this.api),
API_NAME,
);
return {
entries,
cursor: result.cursor,
};
}
async loadMediaFile(branch: string, file: UnpublishedEntryMediaFile) {
const readFile = (
path: string,
id: string | null | undefined,
{ parseText }: { parseText: boolean },
) => this.api!.readFile(path, id, { branch, parseText });
const blob = await getMediaAsBlob(file.path, file.id, readFile);
const name = basename(file.path);
const fileObj = blobToFileObj(name, blob);
return {
id: file.id,
displayURL: URL.createObjectURL(fileObj),
path: file.path,
name,
size: fileObj.size,
file: fileObj,
};
}
async unpublishedEntries() {
const listEntriesKeys = () =>
this.api!.listUnpublishedBranches().then(branches =>
branches.map(branch => contentKeyFromBranch(branch)),
);
const ids = await unpublishedEntries(listEntriesKeys);
return ids;
}
async unpublishedEntry({
id,
collection,
slug,
}: {
id?: string;
collection?: string;
slug?: string;
}) {
if (id) {
const data = await this.api!.retrieveUnpublishedEntryData(id);
return data;
} else if (collection && slug) {
const entryId = this.api!.generateContentKey(collection, slug);
const data = await this.api!.retrieveUnpublishedEntryData(entryId);
return data;
} else {
throw new Error('Missing unpublished entry id or collection and slug');
}
}
getBranch(collection: string, slug: string) {
const contentKey = this.api!.generateContentKey(collection, slug);
const branch = branchFromContentKey(contentKey);
return branch;
}
async unpublishedEntryDataFile(collection: string, slug: string, path: string, id: string) {
const branch = this.getBranch(collection, slug);
const data = (await this.api!.readFile(path, id, { branch })) as string;
return data;
}
async unpublishedEntryMediaFile(collection: string, slug: string, path: string, id: string) {
const branch = this.getBranch(collection, slug);
const mediaFile = await this.loadMediaFile(branch, { path, id });
return mediaFile;
}
async getDeployPreview(collection: string, slug: string) {
try {
const statuses = await this.api!.getStatuses(collection, slug);
const deployStatus = getPreviewStatus(statuses, this.previewContext);
if (deployStatus) {
const { target_url: url, state } = deployStatus;
return { url, status: state };
} else {
return null;
}
} catch (e) {
return null;
}
}
updateUnpublishedEntryStatus(collection: string, slug: string, newStatus: string) {
// updateUnpublishedEntryStatus is a transactional operation
return runWithLock(
this.lock,
() => this.api!.updateUnpublishedEntryStatus(collection, slug, newStatus),
'Failed to acquire update entry status lock',
);
}
deleteUnpublishedEntry(collection: string, slug: string) {
// deleteUnpublishedEntry is a transactional operation
return runWithLock(
this.lock,
() => this.api!.deleteUnpublishedEntry(collection, slug),
'Failed to acquire delete entry lock',
);
}
publishUnpublishedEntry(collection: string, slug: string) {
// publishUnpublishedEntry is a transactional operation
return runWithLock(
this.lock,
() => this.api!.publishUnpublishedEntry(collection, slug),
'Failed to acquire publish entry lock',
);
}
}

10
node_modules/decap-cms-backend-github/src/index.ts generated vendored Normal file
View File

@@ -0,0 +1,10 @@
import GitHubBackend from './implementation';
import API from './API';
import AuthenticationPage from './AuthenticationPage';
export const DecapCmsBackendGithub = {
GitHubBackend,
API,
AuthenticationPage,
};
export { GitHubBackend, API, AuthenticationPage };

110
node_modules/decap-cms-backend-github/src/mutations.ts generated vendored Normal file
View File

@@ -0,0 +1,110 @@
import { gql } from 'graphql-tag';
import * as fragments from './fragments';
// updateRef only works for branches at the moment
export const updateBranch = gql`
mutation updateRef($input: UpdateRefInput!) {
updateRef(input: $input) {
branch: ref {
...BranchParts
}
}
}
${fragments.branch}
`;
// deleteRef only works for branches at the moment
const deleteRefMutationPart = `
deleteRef(input: $deleteRefInput) {
clientMutationId
}
`;
export const deleteBranch = gql`
mutation deleteRef($deleteRefInput: DeleteRefInput!) {
${deleteRefMutationPart}
}
`;
const closePullRequestMutationPart = `
closePullRequest(input: $closePullRequestInput) {
clientMutationId
pullRequest {
...PullRequestParts
}
}
`;
export const closePullRequest = gql`
mutation closePullRequestAndDeleteBranch($closePullRequestInput: ClosePullRequestInput!) {
${closePullRequestMutationPart}
}
${fragments.pullRequest}
`;
export const closePullRequestAndDeleteBranch = gql`
mutation closePullRequestAndDeleteBranch(
$closePullRequestInput: ClosePullRequestInput!
$deleteRefInput: DeleteRefInput!
) {
${closePullRequestMutationPart}
${deleteRefMutationPart}
}
${fragments.pullRequest}
`;
const createPullRequestMutationPart = `
createPullRequest(input: $createPullRequestInput) {
clientMutationId
pullRequest {
...PullRequestParts
}
}
`;
export const createPullRequest = gql`
mutation createPullRequest($createPullRequestInput: CreatePullRequestInput!) {
${createPullRequestMutationPart}
}
${fragments.pullRequest}
`;
export const createBranch = gql`
mutation createBranch($createRefInput: CreateRefInput!) {
createRef(input: $createRefInput) {
branch: ref {
...BranchParts
}
}
}
${fragments.branch}
`;
// createRef only works for branches at the moment
export const createBranchAndPullRequest = gql`
mutation createBranchAndPullRequest(
$createRefInput: CreateRefInput!
$createPullRequestInput: CreatePullRequestInput!
) {
createRef(input: $createRefInput) {
branch: ref {
...BranchParts
}
}
${createPullRequestMutationPart}
}
${fragments.branch}
${fragments.pullRequest}
`;
export const reopenPullRequest = gql`
mutation reopenPullRequest($reopenPullRequestInput: ReopenPullRequestInput!) {
reopenPullRequest(input: $reopenPullRequestInput) {
clientMutationId
pullRequest {
...PullRequestParts
}
}
}
${fragments.pullRequest}
`;

213
node_modules/decap-cms-backend-github/src/queries.ts generated vendored Normal file
View File

@@ -0,0 +1,213 @@
import { gql } from 'graphql-tag';
import { oneLine } from 'common-tags';
import * as fragments from './fragments';
export const repoPermission = gql`
query repoPermission($owner: String!, $name: String!) {
repository(owner: $owner, name: $name) {
...RepositoryParts
viewerPermission
}
}
${fragments.repository}
`;
export const user = gql`
query {
viewer {
id
avatar_url: avatarUrl
name
login
}
}
`;
export const blob = gql`
query blob($owner: String!, $name: String!, $expression: String!) {
repository(owner: $owner, name: $name) {
...RepositoryParts
object(expression: $expression) {
... on Blob {
...BlobWithTextParts
}
}
}
}
${fragments.repository}
${fragments.blobWithText}
`;
export const statues = gql`
query statues($owner: String!, $name: String!, $sha: GitObjectID!) {
repository(owner: $owner, name: $name) {
...RepositoryParts
object(oid: $sha) {
...ObjectParts
... on Commit {
status {
id
contexts {
id
context
state
target_url: targetUrl
}
}
}
}
}
}
${fragments.repository}
${fragments.object}
`;
function buildFilesQuery(depth = 1) {
const PLACE_HOLDER = 'PLACE_HOLDER';
let query = oneLine`
...ObjectParts
... on Tree {
entries {
...FileEntryParts
${PLACE_HOLDER}
}
}
`;
for (let i = 0; i < depth - 1; i++) {
query = query.replace(
PLACE_HOLDER,
oneLine`
object {
... on Tree {
entries {
...FileEntryParts
${PLACE_HOLDER}
}
}
}
`,
);
}
query = query.replace(PLACE_HOLDER, '');
return query;
}
export function files(depth: number) {
return gql`
query files($owner: String!, $name: String!, $expression: String!) {
repository(owner: $owner, name: $name) {
...RepositoryParts
object(expression: $expression) {
${buildFilesQuery(depth)}
}
}
}
${fragments.repository}
${fragments.object}
${fragments.fileEntry}
`;
}
const branchQueryPart = `
branch: ref(qualifiedName: $qualifiedName) {
...BranchParts
}
`;
export const branch = gql`
query branch($owner: String!, $name: String!, $qualifiedName: String!) {
repository(owner: $owner, name: $name) {
...RepositoryParts
${branchQueryPart}
}
}
${fragments.repository}
${fragments.branch}
`;
export const openAuthoringBranches = gql`
query openAuthoringBranches($owner: String!, $name: String!, $refPrefix: String!) {
repository(owner: $owner, name: $name) {
...RepositoryParts
refs(refPrefix: $refPrefix, last: 100) {
nodes {
...BranchParts
}
}
}
}
${fragments.repository}
${fragments.branch}
`;
export const repository = gql`
query repository($owner: String!, $name: String!) {
repository(owner: $owner, name: $name) {
...RepositoryParts
}
}
${fragments.repository}
`;
const pullRequestQueryPart = `
pullRequest(number: $number) {
...PullRequestParts
}
`;
export const pullRequest = gql`
query pullRequest($owner: String!, $name: String!, $number: Int!) {
repository(owner: $owner, name: $name) {
id
${pullRequestQueryPart}
}
}
${fragments.pullRequest}
`;
export const pullRequests = gql`
query pullRequests($owner: String!, $name: String!, $head: String, $states: [PullRequestState!]) {
repository(owner: $owner, name: $name) {
id
pullRequests(last: 100, headRefName: $head, states: $states) {
nodes {
...PullRequestParts
}
}
}
}
${fragments.pullRequest}
`;
export const pullRequestAndBranch = gql`
query pullRequestAndBranch($owner: String!, $name: String!, $originRepoOwner: String!, $originRepoName: String!, $qualifiedName: String!, $number: Int!) {
repository(owner: $owner, name: $name) {
...RepositoryParts
${branchQueryPart}
}
origin: repository(owner: $originRepoOwner, name: $originRepoName) {
...RepositoryParts
${pullRequestQueryPart}
}
}
${fragments.repository}
${fragments.branch}
${fragments.pullRequest}
`;
export const fileSha = gql`
query fileSha($owner: String!, $name: String!, $expression: String!) {
repository(owner: $owner, name: $name) {
...RepositoryParts
file: object(expression: $expression) {
...ObjectParts
}
}
}
${fragments.repository}
${fragments.object}
`;

View File

@@ -0,0 +1,5 @@
declare module 'semaphore' {
export type Semaphore = { take: (f: Function) => void; leave: () => void };
const semaphore: (count: number) => Semaphore;
export default semaphore;
}

View File

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