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

98
node_modules/decap-cms-backend-azure/CHANGELOG.md generated vendored Normal file
View File

@@ -0,0 +1,98 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.1.3](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-azure@3.1.2...decap-cms-backend-azure@3.1.3) (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.1.2](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-azure@3.1.1...decap-cms-backend-azure@3.1.2) (2024-08-13)
**Note:** Version bump only for package decap-cms-backend-azure
## [3.1.1](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-azure@3.1.0-beta.1...decap-cms-backend-azure@3.1.1) (2024-03-21)
**Note:** Version bump only for package decap-cms-backend-azure
# [3.1.0](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-azure@3.1.0-beta.1...decap-cms-backend-azure@3.1.0) (2024-02-01)
**Note:** Version bump only for package decap-cms-backend-azure
# [3.1.0-beta.1](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-azure@3.1.0-beta.0...decap-cms-backend-azure@3.1.0-beta.1) (2024-01-31)
**Note:** Version bump only for package decap-cms-backend-azure
# [3.1.0-beta.0](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-azure@3.1.0...decap-cms-backend-azure@3.1.0-beta.0) (2023-10-20)
### Reverts
- Revert "chore(release): publish" ([b89fc89](https://github.com/decaporg/decap-cms/commit/b89fc894dfbb5f4136b2e5427fd25a29378a58c6))
## [3.0.2](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-azure@3.0.1...decap-cms-backend-azure@3.0.2) (2023-10-13)
**Note:** Version bump only for package decap-cms-backend-azure
## [3.0.1](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-azure@3.0.0...decap-cms-backend-azure@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-azure@1.4.0...decap-cms-backend-azure@3.0.0) (2023-08-18)
**Note:** Version bump only for package decap-cms-backend-azure
# [1.4.0](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-azure@1.4.0-beta.0...decap-cms-backend-azure@1.4.0) (2023-08-18)
**Note:** Version bump only for package decap-cms-backend-azure
# 1.4.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))
## [1.3.2-beta.0](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-azure@1.3.1...decap-cms-backend-azure@1.3.2-beta.0) (2023-07-27)
**Note:** Version bump only for package decap-cms-backend-azure
## [1.3.1](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-azure@1.3.0...decap-cms-backend-azure@1.3.1) (2022-04-13)
**Note:** Version bump only for package decap-cms-backend-azure
# [1.3.0](https://github.com/decaporg/decap-cms/compare/decap-cms-backend-azure@1.2.2...decap-cms-backend-azure@1.3.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))
## [1.2.2](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-azure/compare/decap-cms-backend-azure@1.2.1...decap-cms-backend-azure@1.2.2) (2021-06-01)
**Note:** Version bump only for package decap-cms-backend-azure
## [1.2.1](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-azure/compare/decap-cms-backend-azure@1.2.0...decap-cms-backend-azure@1.2.1) (2021-05-31)
**Note:** Version bump only for package decap-cms-backend-azure
# [1.2.0](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-azure/compare/decap-cms-backend-azure@1.1.2...decap-cms-backend-azure@1.2.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-azure/issues/5316)) ([9e42380](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-azure/commit/9e423805707321396eec137f5b732a5b07a0dd3f))
## [1.1.2](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-azure/compare/decap-cms-backend-azure@1.1.1...decap-cms-backend-azure@1.1.2) (2021-02-23)
**Note:** Version bump only for package decap-cms-backend-azure
## [1.1.1](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-azure/compare/decap-cms-backend-azure@1.1.0...decap-cms-backend-azure@1.1.1) (2021-02-10)
**Note:** Version bump only for package decap-cms-backend-azure
# 1.1.0 (2020-11-26)
### Features
- add azure devops backend ([#4427](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-azure/issues/4427)) ([4e6dc88](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-azure/commit/4e6dc88efb1dae4cf6137730c3b4fb6d0f75a8cc))

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

13
node_modules/decap-cms-backend-azure/README.md generated vendored Normal file
View File

@@ -0,0 +1,13 @@
# Azure backend
An abstraction layer between the CMS and [Azure DevOps](https://docs.microsoft.com/en-us/rest/api/azure/devops/git/)
## 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 Azure DevOps REST API.
`AuthenticationPage` - facilitates implicit authentication flow. Uses [lib-auth](https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-lib-auth/README.md).
Look at tests or types for more info.

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,8 @@
/*!
* The buffer module from node.js, for the browser.
*
* @author Feross Aboukhadijeh <https://feross.org>
* @license MIT
*/
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */

File diff suppressed because one or more lines are too long

625
node_modules/decap-cms-backend-azure/dist/esm/API.js generated vendored Normal file
View File

@@ -0,0 +1,625 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = exports.API_NAME = void 0;
var _trimStart2 = _interopRequireDefault(require("lodash/trimStart"));
var _trim2 = _interopRequireDefault(require("lodash/trim"));
var _result2 = _interopRequireDefault(require("lodash/result"));
var _partial2 = _interopRequireDefault(require("lodash/partial"));
var _jsBase = require("js-base64");
var _decapCmsLibUtil = require("decap-cms-lib-util");
var _path = require("path");
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 API_NAME = exports.API_NAME = 'Azure DevOps';
const API_VERSION = 'api-version';
// https://docs.microsoft.com/en-us/rest/api/azure/devops/git/pull%20requests/get%20pull%20request?view=azure-devops-rest-6.1#gitpullrequest
var AzureCommitStatusState = /*#__PURE__*/function (AzureCommitStatusState) {
AzureCommitStatusState["ERROR"] = "error";
AzureCommitStatusState["FAILED"] = "failed";
AzureCommitStatusState["NOT_APPLICABLE"] = "notApplicable";
AzureCommitStatusState["NOT_SET"] = "notSet";
AzureCommitStatusState["PENDING"] = "pending";
AzureCommitStatusState["SUCCEEDED"] = "succeeded";
return AzureCommitStatusState;
}(AzureCommitStatusState || {}); // This does not match Azure documentation, but it is what comes back from some calls
// PullRequest as an example is documented as returning PullRequest[], but it actually
// returns that inside of this value prop in the json
var AzureCommitChangeType = /*#__PURE__*/function (AzureCommitChangeType) {
AzureCommitChangeType["ADD"] = "add";
AzureCommitChangeType["DELETE"] = "delete";
AzureCommitChangeType["RENAME"] = "rename";
AzureCommitChangeType["EDIT"] = "edit";
return AzureCommitChangeType;
}(AzureCommitChangeType || {});
var AzureItemContentType = /*#__PURE__*/function (AzureItemContentType) {
AzureItemContentType["BASE64"] = "base64encoded";
return AzureItemContentType;
}(AzureItemContentType || {});
var AzurePullRequestStatus = /*#__PURE__*/function (AzurePullRequestStatus) {
AzurePullRequestStatus["ACTIVE"] = "active";
AzurePullRequestStatus["COMPLETED"] = "completed";
AzurePullRequestStatus["ABANDONED"] = "abandoned";
return AzurePullRequestStatus;
}(AzurePullRequestStatus || {});
var AzureAsyncPullRequestStatus = /*#__PURE__*/function (AzureAsyncPullRequestStatus) {
AzureAsyncPullRequestStatus["CONFLICTS"] = "conflicts";
AzureAsyncPullRequestStatus["FAILURE"] = "failure";
AzureAsyncPullRequestStatus["QUEUED"] = "queued";
AzureAsyncPullRequestStatus["REJECTED"] = "rejectedByPolicy";
AzureAsyncPullRequestStatus["SUCCEEDED"] = "succeeded";
return AzureAsyncPullRequestStatus;
}(AzureAsyncPullRequestStatus || {});
var AzureObjectType = /*#__PURE__*/function (AzureObjectType) {
AzureObjectType["BLOB"] = "blob";
AzureObjectType["TREE"] = "tree";
return AzureObjectType;
}(AzureObjectType || {}); // https://docs.microsoft.com/en-us/rest/api/azure/devops/git/diffs/get?view=azure-devops-rest-6.1#gitcommitdiffs
// https://docs.microsoft.com/en-us/rest/api/azure/devops/git/diffs/get?view=azure-devops-rest-6.1#gitchange
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function getChangeItem(item) {
switch (item.action) {
case AzureCommitChangeType.ADD:
return {
changeType: AzureCommitChangeType.ADD,
item: {
path: item.path
},
newContent: {
content: item.base64Content,
contentType: AzureItemContentType.BASE64
}
};
case AzureCommitChangeType.EDIT:
return {
changeType: AzureCommitChangeType.EDIT,
item: {
path: item.path
},
newContent: {
content: item.base64Content,
contentType: AzureItemContentType.BASE64
}
};
case AzureCommitChangeType.DELETE:
return {
changeType: AzureCommitChangeType.DELETE,
item: {
path: item.path
}
};
case AzureCommitChangeType.RENAME:
return {
changeType: AzureCommitChangeType.RENAME,
item: {
path: item.path
},
sourceServerItem: item.oldPath
};
default:
return {};
}
}
class API {
constructor(config, token) {
_defineProperty(this, "apiVersion", void 0);
_defineProperty(this, "token", void 0);
_defineProperty(this, "branch", void 0);
_defineProperty(this, "mergeStrategy", void 0);
_defineProperty(this, "endpointUrl", void 0);
_defineProperty(this, "initialWorkflowStatus", void 0);
_defineProperty(this, "cmsLabelPrefix", void 0);
_defineProperty(this, "withHeaders", req => {
const withHeaders = _decapCmsLibUtil.unsentRequest.withHeaders({
Authorization: `Bearer ${this.token}`,
'Content-Type': 'application/json; charset=utf-8'
}, req);
return withHeaders;
});
_defineProperty(this, "withAzureFeatures", req => {
if (req.hasIn(['params', API_VERSION])) {
return req;
}
const withParams = _decapCmsLibUtil.unsentRequest.withParams({
[API_VERSION]: `${this.apiVersion}`
}, req);
return withParams;
});
_defineProperty(this, "buildRequest", req => {
const withHeaders = this.withHeaders(req);
const withAzureFeatures = this.withAzureFeatures(withHeaders);
if (withAzureFeatures.has('cache')) {
return withAzureFeatures;
} else {
const withNoCache = _decapCmsLibUtil.unsentRequest.withNoCache(withAzureFeatures);
return withNoCache;
}
});
_defineProperty(this, "request", req => {
try {
return (0, _decapCmsLibUtil.requestWithBackoff)(this, req);
} catch (err) {
throw new _decapCmsLibUtil.APIError(err.message, null, API_NAME);
}
});
_defineProperty(this, "responseToJSON", (0, _decapCmsLibUtil.responseParser)({
format: 'json',
apiName: API_NAME
}));
_defineProperty(this, "responseToBlob", (0, _decapCmsLibUtil.responseParser)({
format: 'blob',
apiName: API_NAME
}));
_defineProperty(this, "responseToText", (0, _decapCmsLibUtil.responseParser)({
format: 'text',
apiName: API_NAME
}));
_defineProperty(this, "requestJSON", req => this.request(req).then(this.responseToJSON));
_defineProperty(this, "requestText", req => this.request(req).then(this.responseToText));
_defineProperty(this, "toBase64", str => Promise.resolve(_jsBase.Base64.encode(str)));
_defineProperty(this, "fromBase64", str => _jsBase.Base64.decode(str));
_defineProperty(this, "branchToRef", branch => `refs/heads/${branch}`);
_defineProperty(this, "refToBranch", ref => ref.slice('refs/heads/'.length));
_defineProperty(this, "user", async () => {
var _result$coreAttribute, _result$coreAttribute2, _result$coreAttribute3, _result$coreAttribute4, _result$coreAttribute5, _result$coreAttribute6, _result$coreAttribute7;
const result = await this.requestJSON({
url: 'https://app.vssps.visualstudio.com/_apis/profile/profiles/me',
params: {
[API_VERSION]: '6.1-preview.2'
}
});
const name = (_result$coreAttribute = result.coreAttributes) === null || _result$coreAttribute === void 0 ? void 0 : (_result$coreAttribute2 = _result$coreAttribute.DisplayName) === null || _result$coreAttribute2 === void 0 ? void 0 : _result$coreAttribute2.value;
const email = (_result$coreAttribute3 = result.coreAttributes) === null || _result$coreAttribute3 === void 0 ? void 0 : (_result$coreAttribute4 = _result$coreAttribute3.EmailAddress) === null || _result$coreAttribute4 === void 0 ? void 0 : _result$coreAttribute4.value;
const url = (_result$coreAttribute5 = result.coreAttributes) === null || _result$coreAttribute5 === void 0 ? void 0 : (_result$coreAttribute6 = _result$coreAttribute5.Avatar) === null || _result$coreAttribute6 === void 0 ? void 0 : (_result$coreAttribute7 = _result$coreAttribute6.value) === null || _result$coreAttribute7 === void 0 ? void 0 : _result$coreAttribute7.value;
const user = {
name: name || email || '',
avatar_url: `data:image/png;base64,${url}`,
email
};
return user;
});
_defineProperty(this, "readFile", (path, sha, {
parseText = true,
branch = this.branch
} = {}) => {
const fetchContent = () => {
return this.request({
url: `${this.endpointUrl}/items/`,
params: {
version: branch,
path
},
cache: 'no-store'
}).then(parseText ? this.responseToText : this.responseToBlob);
};
return (0, _decapCmsLibUtil.readFile)(sha, fetchContent, _decapCmsLibUtil.localForage, parseText);
});
_defineProperty(this, "listFiles", async (path, recursive, branch = this.branch) => {
try {
const {
value: items
} = await this.requestJSON({
url: `${this.endpointUrl}/items/`,
params: {
version: branch,
scopePath: path,
recursionLevel: recursive ? 'full' : 'oneLevel'
}
});
const files = items.filter(item => item.gitObjectType === AzureObjectType.BLOB).map(file => ({
id: file.objectId,
path: (0, _trimStart2.default)(file.path, '/'),
name: (0, _path.basename)(file.path)
}));
return files;
} catch (err) {
if (err && err.status === 404) {
console.log('This 404 was expected and handled appropriately.');
return [];
} else {
throw err;
}
}
});
const {
repo
} = config;
const apiRoot = (0, _trim2.default)(config.apiRoot, '/');
this.endpointUrl = `${apiRoot}/${repo.org}/${repo.project}/_apis/git/repositories/${repo.repoName}`;
this.token = token;
this.branch = config.branch;
this.mergeStrategy = config.squashMerges ? 'squash' : 'noFastForward';
this.initialWorkflowStatus = config.initialWorkflowStatus;
this.apiVersion = config.apiVersion;
this.cmsLabelPrefix = config.cmsLabelPrefix;
}
async readFileMetadata(path, sha, {
branch = this.branch
} = {}) {
const fetchFileMetadata = async () => {
try {
const {
value
} = await this.requestJSON({
url: `${this.endpointUrl}/commits/`,
params: {
'searchCriteria.itemPath': path,
'searchCriteria.itemVersion.version': branch,
'searchCriteria.$top': 1
}
});
const [commit] = value;
return {
author: commit.author.name || commit.author.email,
updatedOn: commit.author.date
};
} catch (error) {
return {
author: '',
updatedOn: ''
};
}
};
const fileMetadata = await (0, _decapCmsLibUtil.readFileMetadata)(sha, fetchFileMetadata, _decapCmsLibUtil.localForage);
return fileMetadata;
}
async getRef(branch = this.branch) {
const {
value: refs
} = await this.requestJSON({
url: `${this.endpointUrl}/refs`,
params: {
$top: '1',
// There's only one ref, so keep the payload small
filter: 'heads/' + branch
}
});
return refs.find(b => b.name == this.branchToRef(branch));
}
async deleteRef(ref) {
const deleteBranchPayload = [{
name: ref.name,
oldObjectId: ref.objectId,
newObjectId: '0000000000000000000000000000000000000000'
}];
await this.requestJSON({
method: 'POST',
url: `${this.endpointUrl}/refs`,
body: JSON.stringify(deleteBranchPayload)
});
}
async uploadAndCommit(items, comment, branch, newBranch) {
const ref = await this.getRef(newBranch ? this.branch : branch);
const refUpdate = [{
name: this.branchToRef(branch),
oldObjectId: ref.objectId
}];
const changes = items.map(item => getChangeItem(item));
const commits = [{
comment,
changes
}];
const push = {
refUpdates: refUpdate,
commits
};
return this.requestJSON({
url: `${this.endpointUrl}/pushes`,
method: 'POST',
body: JSON.stringify(push)
});
}
async retrieveUnpublishedEntryData(contentKey) {
var _pullRequest$createdB, _pullRequest$createdB2;
const {
collection,
slug
} = (0, _decapCmsLibUtil.parseContentKey)(contentKey);
const branch = (0, _decapCmsLibUtil.branchFromContentKey)(contentKey);
const pullRequest = await this.getBranchPullRequest(branch);
const diffs = await this.getDifferences(pullRequest.sourceRefName);
const diffsWithIds = await Promise.all(diffs.map(async d => {
const path = (0, _trimStart2.default)(d.item.path, '/');
const newFile = d.changeType === AzureCommitChangeType.ADD;
const id = d.item.objectId;
return {
id,
path,
newFile
};
}));
const label = pullRequest.labels.find(l => (0, _decapCmsLibUtil.isCMSLabel)(l.name, this.cmsLabelPrefix));
const labelName = label && label.name ? label.name : this.cmsLabelPrefix;
const status = (0, _decapCmsLibUtil.labelToStatus)(labelName, this.cmsLabelPrefix);
// Uses creationDate, as we do not have direct access to the updated date
const updatedAt = pullRequest.closedDate ? pullRequest.closedDate : pullRequest.creationDate;
const pullRequestAuthor = ((_pullRequest$createdB = pullRequest.createdBy) === null || _pullRequest$createdB === void 0 ? void 0 : _pullRequest$createdB.displayName) || ((_pullRequest$createdB2 = pullRequest.createdBy) === null || _pullRequest$createdB2 === void 0 ? void 0 : _pullRequest$createdB2.uniqueName);
return {
collection,
slug,
status,
diffs: diffsWithIds,
updatedAt,
pullRequestAuthor
};
}
async getPullRequestStatues(pullRequest) {
const {
value: commits
} = await this.requestJSON({
url: `${this.endpointUrl}/pullrequests/${pullRequest.pullRequestId}/commits`,
params: {
$top: 1
}
});
const {
value: statuses
} = await this.requestJSON({
url: `${this.endpointUrl}/commits/${commits[0].commitId}/statuses`,
params: {
latestOnly: true
}
});
return statuses;
}
async getStatuses(collection, slug) {
const contentKey = (0, _decapCmsLibUtil.generateContentKey)(collection, slug);
const branch = (0, _decapCmsLibUtil.branchFromContentKey)(contentKey);
const pullRequest = await this.getBranchPullRequest(branch);
const statuses = await this.getPullRequestStatues(pullRequest);
return statuses.map(({
context,
state,
targetUrl
}) => ({
context: context.name,
state: state === AzureCommitStatusState.SUCCEEDED ? _decapCmsLibUtil.PreviewState.Success : _decapCmsLibUtil.PreviewState.Other,
target_url: targetUrl
}));
}
async getCommitItems(files, branch) {
const items = await Promise.all(files.map(async file => {
const [base64Content, fileExists] = await Promise.all([(0, _result2.default)(file, 'toBase64', (0, _partial2.default)(this.toBase64, file.raw)), this.isFileExists(file.path, branch)]);
const path = file.newPath || file.path;
const oldPath = file.path;
const renameOrEdit = path !== oldPath ? AzureCommitChangeType.RENAME : AzureCommitChangeType.EDIT;
const action = fileExists ? renameOrEdit : AzureCommitChangeType.ADD;
return {
action,
base64Content,
path,
oldPath
};
}));
// move children
for (const item of items.filter(i => i.oldPath && i.action === AzureCommitChangeType.RENAME)) {
const sourceDir = (0, _path.dirname)(item.oldPath);
const destDir = (0, _path.dirname)(item.path);
const children = await this.listFiles(sourceDir, true, branch);
children.filter(file => file.path !== item.oldPath).forEach(file => {
items.push({
action: AzureCommitChangeType.RENAME,
path: file.path.replace(sourceDir, destDir),
oldPath: file.path
});
});
}
return items;
}
async persistFiles(dataFiles, mediaFiles, options) {
const files = [...dataFiles, ...mediaFiles];
if (options.useWorkflow) {
const slug = dataFiles[0].slug;
return this.editorialWorkflowGit(files, slug, options);
} else {
const items = await this.getCommitItems(files, this.branch);
return this.uploadAndCommit(items, options.commitMessage, this.branch, true);
}
}
async deleteFiles(paths, comment) {
const ref = await this.getRef(this.branch);
const refUpdate = {
name: ref.name,
oldObjectId: ref.objectId
};
const changes = paths.map(path => getChangeItem({
action: AzureCommitChangeType.DELETE,
path
}));
const commits = [{
comment,
changes
}];
const push = {
refUpdates: [refUpdate],
commits
};
return this.requestJSON({
url: `${this.endpointUrl}/pushes`,
method: 'POST',
body: JSON.stringify(push)
});
}
async getPullRequests(sourceBranch) {
const {
value: pullRequests
} = await this.requestJSON({
url: `${this.endpointUrl}/pullrequests`,
params: _objectSpread({
'searchCriteria.status': 'active',
'searchCriteria.targetRefName': this.branchToRef(this.branch),
'searchCriteria.includeLinks': false
}, sourceBranch ? {
'searchCriteria.sourceRefName': this.branchToRef(sourceBranch)
} : {})
});
const filtered = pullRequests.filter(pr => {
return pr.labels.some(label => (0, _decapCmsLibUtil.isCMSLabel)(label.name, this.cmsLabelPrefix));
});
return filtered;
}
async listUnpublishedBranches() {
const pullRequests = await this.getPullRequests();
const branches = pullRequests.map(pr => this.refToBranch(pr.sourceRefName));
return branches;
}
async isFileExists(path, branch) {
try {
await this.requestText({
url: `${this.endpointUrl}/items/`,
params: {
version: branch,
path
},
cache: 'no-store'
});
return true;
} catch (error) {
if (error instanceof _decapCmsLibUtil.APIError && error.status === 404) {
return false;
}
throw error;
}
}
async createPullRequest(branch, commitMessage, status) {
const pr = {
sourceRefName: this.branchToRef(branch),
targetRefName: this.branchToRef(this.branch),
title: commitMessage,
description: _decapCmsLibUtil.DEFAULT_PR_BODY,
labels: [{
name: (0, _decapCmsLibUtil.statusToLabel)(status, this.cmsLabelPrefix)
}]
};
await this.requestJSON({
method: 'POST',
url: `${this.endpointUrl}/pullrequests`,
params: {
supportsIterations: false
},
body: JSON.stringify(pr)
});
}
async getBranchPullRequest(branch) {
const pullRequests = await this.getPullRequests(branch);
if (pullRequests.length <= 0) {
throw new _decapCmsLibUtil.EditorialWorkflowError('content is not under editorial workflow', true);
}
return pullRequests[0];
}
async getDifferences(to) {
const result = await this.requestJSON({
url: `${this.endpointUrl}/diffs/commits`,
params: {
baseVersion: this.branch,
targetVersion: this.refToBranch(to)
}
});
return result.changes.filter(d => d.item.gitObjectType === AzureObjectType.BLOB && Object.values(AzureCommitChangeType).includes(d.changeType));
}
async editorialWorkflowGit(files, slug, options) {
const contentKey = (0, _decapCmsLibUtil.generateContentKey)(options.collectionName, slug);
const branch = (0, _decapCmsLibUtil.branchFromContentKey)(contentKey);
const unpublished = options.unpublished || false;
if (!unpublished) {
const items = await this.getCommitItems(files, this.branch);
await this.uploadAndCommit(items, options.commitMessage, branch, true);
await this.createPullRequest(branch, options.commitMessage, options.status || this.initialWorkflowStatus);
} else {
const items = await this.getCommitItems(files, branch);
await this.uploadAndCommit(items, options.commitMessage, branch, false);
}
}
async updateUnpublishedEntryStatus(collection, slug, newStatus) {
const contentKey = (0, _decapCmsLibUtil.generateContentKey)(collection, slug);
const branch = (0, _decapCmsLibUtil.branchFromContentKey)(contentKey);
const pullRequest = await this.getBranchPullRequest(branch);
const nonCmsLabels = pullRequest.labels.filter(label => !(0, _decapCmsLibUtil.isCMSLabel)(label.name, this.cmsLabelPrefix)).map(label => label.name);
const labels = [...nonCmsLabels, (0, _decapCmsLibUtil.statusToLabel)(newStatus, this.cmsLabelPrefix)];
await this.updatePullRequestLabels(pullRequest, labels);
}
async deleteUnpublishedEntry(collectionName, slug) {
const contentKey = (0, _decapCmsLibUtil.generateContentKey)(collectionName, slug);
const branch = (0, _decapCmsLibUtil.branchFromContentKey)(contentKey);
const pullRequest = await this.getBranchPullRequest(branch);
await this.abandonPullRequest(pullRequest);
}
async publishUnpublishedEntry(collectionName, slug) {
const contentKey = (0, _decapCmsLibUtil.generateContentKey)(collectionName, slug);
const branch = (0, _decapCmsLibUtil.branchFromContentKey)(contentKey);
const pullRequest = await this.getBranchPullRequest(branch);
await this.completePullRequest(pullRequest);
}
async updatePullRequestLabels(pullRequest, labels) {
const cmsLabels = pullRequest.labels.filter(l => (0, _decapCmsLibUtil.isCMSLabel)(l.name, this.cmsLabelPrefix));
await Promise.all(cmsLabels.map(l => {
return this.requestText({
method: 'DELETE',
url: `${this.endpointUrl}/pullrequests/${encodeURIComponent(pullRequest.pullRequestId)}/labels/${encodeURIComponent(l.id)}`
});
}));
await Promise.all(labels.map(l => {
return this.requestText({
method: 'POST',
url: `${this.endpointUrl}/pullrequests/${encodeURIComponent(pullRequest.pullRequestId)}/labels`,
body: JSON.stringify({
name: l
})
});
}));
}
async completePullRequest(pullRequest) {
const pullRequestCompletion = {
status: AzurePullRequestStatus.COMPLETED,
lastMergeSourceCommit: pullRequest.lastMergeSourceCommit,
completionOptions: {
deleteSourceBranch: true,
mergeCommitMessage: _decapCmsLibUtil.MERGE_COMMIT_MESSAGE,
mergeStrategy: this.mergeStrategy
}
};
let response = await this.requestJSON({
method: 'PATCH',
url: `${this.endpointUrl}/pullrequests/${encodeURIComponent(pullRequest.pullRequestId)}`,
body: JSON.stringify(pullRequestCompletion)
});
// We need to wait for Azure to complete the pull request to actually complete
// Sometimes this is instant, but frequently it is 1-3 seconds
const DELAY_MILLISECONDS = 500;
const MAX_ATTEMPTS = 10;
let attempt = 1;
while (response.mergeStatus === AzureAsyncPullRequestStatus.QUEUED && attempt <= MAX_ATTEMPTS) {
await delay(DELAY_MILLISECONDS);
response = await this.requestJSON({
url: `${this.endpointUrl}/pullrequests/${encodeURIComponent(pullRequest.pullRequestId)}`
});
attempt = attempt + 1;
}
}
async abandonPullRequest(pullRequest) {
const pullRequestAbandon = {
status: AzurePullRequestStatus.ABANDONED
};
await this.requestJSON({
method: 'PATCH',
url: `${this.endpointUrl}/pullrequests/${encodeURIComponent(pullRequest.pullRequestId)}`,
body: JSON.stringify(pullRequestAbandon)
});
await this.deleteRef({
name: pullRequest.sourceRefName,
objectId: pullRequest.lastMergeSourceCommit.commitId
});
}
}
exports.default = API;

View File

@@ -0,0 +1,95 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _base = _interopRequireDefault(require("@emotion/styled/base"));
var _react = _interopRequireDefault(require("react"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _decapCmsLibAuth = require("decap-cms-lib-auth");
var _decapCmsUiDefault = require("decap-cms-ui-default");
var _react2 = require("@emotion/react");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : String(i); }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _EMOTION_STRINGIFIED_CSS_ERROR__() { return "You have tried to stringify object returned from `css` function. It isn't supposed to be used directly (e.g. as value of the `className` prop), but rather handed to emotion so it can handle it (e.g. as value of `css` prop)."; }
const LoginButtonIcon = /*#__PURE__*/(0, _base.default)(_decapCmsUiDefault.Icon, {
target: "e1n0346u0",
label: "LoginButtonIcon"
})(process.env.NODE_ENV === "production" ? {
name: "1gnqu05",
styles: "margin-right:18px"
} : {
name: "1gnqu05",
styles: "margin-right:18px",
map: "/*# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9BdXRoZW50aWNhdGlvblBhZ2UuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBTW9DIiwiZmlsZSI6Ii4uLy4uL3NyYy9BdXRoZW50aWNhdGlvblBhZ2UuanMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgUmVhY3QgZnJvbSAncmVhY3QnO1xuaW1wb3J0IFByb3BUeXBlcyBmcm9tICdwcm9wLXR5cGVzJztcbmltcG9ydCBzdHlsZWQgZnJvbSAnQGVtb3Rpb24vc3R5bGVkJztcbmltcG9ydCB7IEltcGxpY2l0QXV0aGVudGljYXRvciB9IGZyb20gJ2RlY2FwLWNtcy1saWItYXV0aCc7XG5pbXBvcnQgeyBBdXRoZW50aWNhdGlvblBhZ2UsIEljb24gfSBmcm9tICdkZWNhcC1jbXMtdWktZGVmYXVsdCc7XG5cbmNvbnN0IExvZ2luQnV0dG9uSWNvbiA9IHN0eWxlZChJY29uKWBcbiAgbWFyZ2luLXJpZ2h0OiAxOHB4O1xuYDtcblxuZXhwb3J0IGRlZmF1bHQgY2xhc3MgQXp1cmVBdXRoZW50aWNhdGlvblBhZ2UgZXh0ZW5kcyBSZWFjdC5Db21wb25lbnQge1xuICBzdGF0aWMgcHJvcFR5cGVzID0ge1xuICAgIG9uTG9naW46IFByb3BUeXBlcy5mdW5jLmlzUmVxdWlyZWQsXG4gICAgaW5Qcm9ncmVzczogUHJvcFR5cGVzLmJvb2wsXG4gICAgYmFzZV91cmw6IFByb3BUeXBlcy5zdHJpbmcsXG4gICAgc2l0ZUlkOiBQcm9wVHlwZXMuc3RyaW5nLFxuICAgIGF1dGhFbmRwb2ludDogUHJvcFR5cGVzLnN0cmluZyxcbiAgICBjb25maWc6IFByb3BUeXBlcy5vYmplY3QuaXNSZXF1aXJlZCxcbiAgICBjbGVhckhhc2g6IFByb3BUeXBlcy5mdW5jLFxuICAgIHQ6IFByb3BUeXBlcy5mdW5jLmlzUmVxdWlyZWQsXG4gIH07XG5cbiAgc3RhdGUgPSB7fTtcblxuICBjb21wb25lbnREaWRNb3VudCgpIHtcbiAgICB0aGlzLmF1dGggPSBuZXcgSW1wbGljaXRBdXRoZW50aWNhdG9yKHtcbiAgICAgIGJhc2VfdXJsOiBgaHR0cHM6Ly9sb2dpbi5taWNyb3NvZnRvbmxpbmUuY29tLyR7dGhpcy5wcm9wcy5jb25maWcuYmFja2VuZC50ZW5hbnRfaWR9YCxcbiAgICAgIGF1dGhfZW5kcG9pbnQ6ICdvYXV0aDIvYXV0aG9yaXplJyxcbiAgICAgIGFwcF9pZDogdGhpcy5wcm9wcy5jb25maWcuYmFja2VuZC5hcHBfaWQsXG4gICAgICBjbGVhckhhc2g6IHRoaXMucHJvcHMuY2xlYXJIYXNoLFxuICAgIH0pO1xuICAgIC8vIENvbXBsZXRlIGltcGxpY2l0IGF1dGhlbnRpY2F0aW9uIGlmIHdlIHdlcmUgcmVkaXJlY3RlZCBiYWNrIHRvIGZyb20gdGhlIHByb3ZpZGVyLlxuICAgIHRoaXMuYXV0aC5jb21wbGV0ZUF1dGgoKGVyciwgZGF0YSkgPT4ge1xuICAgICAgaWYgKGVycikge1xuICAgICAgICBhbGVydChlcnIpO1xuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICB0aGlzLnByb3BzLm9uTG9naW4oZGF0YSk7XG4gICAgfSk7XG4gIH1cblxuICBoYW5kbGVMb2dpbiA9IGUgPT4ge1xuICAgIGUucHJldmVudERlZmF1bHQoKTtcbiAgICB0aGlzLmF1dGguYXV0aGVudGljYXRlKFxuICAgICAge1xuICAgICAgICBzY29wZTogJ3Zzby5jb2RlX2Z1bGwsdXNlci5yZWFkJyxcbiAgICAgICAgcmVzb3VyY2U6ICc0OTliODRhYy0xMzIxLTQyN2YtYWExNy0yNjdjYTY5NzU3OTgnLFxuICAgICAgICBwcm9tcHQ6ICdzZWxlY3RfYWNjb3VudCcsXG4gICAgICB9LFxuICAgICAgKGVyciwgZGF0YSkgPT4ge1xuICAgICAgICBpZiAoZXJyKSB7XG4gICAgICAgICAgdGhpcy5zZXRTdGF0ZSh7IGxvZ2luRXJyb3I6IGVyci50b1N0cmluZygpIH0pO1xuICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgICAgICB0aGlzLnByb3BzLm9uTG9naW4oZGF0YSk7XG4gICAgICB9LFxuICAgICk7XG4gIH07XG5cbiAgcmVuZGVyKCkge1xuICAgIGNvbnN0IHsgaW5Qcm9ncmVzcywgY29uZmlnLCB0IH0gPSB0aGlzLnByb3BzO1xuXG4gICAgcmV0dXJuIChcbiAgICAgIDxBdXRoZW50aWNhdGlvblBhZ2VcbiAgICAgICAgb25Mb2dpbj17dGhpcy5oYW5kbGVMb2dpbn1cbiAgICAgICAgbG9naW5EaXNhYmxlZD17aW5Qcm9ncmVzc31cbiAgICAgICAgbG9naW5FcnJvck1lc3NhZ2U9e3RoaXMuc3RhdGUubG9naW5FcnJvcn1cbiAgICAgICAgbG9nb1VybD17Y29uZmlnLmxvZ29fdXJsfVxuICAgICAgICByZW5kZXJCdXR0b25Db250ZW50PXsoKSA9PiAoXG4gICAgICAgICAgPFJlYWN0LkZyYWdtZW50PlxuICAgICAgICAgICAgPExvZ2luQnV0dG9uSWNvbiB0eXBlPVwiYXp1cmVcIiAvPlxuICAgICAgICAgICAge2luUHJvZ3Jlc3MgPyB0KCdhdXRoLmxvZ2dpbmdJbicpIDogdCgnYXV0aC5sb2dpbldpdGhBenVyZScpfVxuICAgICAgICAgIDwvUmVhY3QuRnJhZ21lbnQ+XG4gICAgICAgICl9XG4gICAgICAgIHQ9e3R9XG4gICAgICAvPlxuICAgICk7XG4gIH1cbn1cbiJdfQ== */",
toString: _EMOTION_STRINGIFIED_CSS_ERROR__
});
class AzureAuthenticationPage extends _react.default.Component {
constructor(...args) {
super(...args);
_defineProperty(this, "state", {});
_defineProperty(this, "handleLogin", e => {
e.preventDefault();
this.auth.authenticate({
scope: 'vso.code_full,user.read',
resource: '499b84ac-1321-427f-aa17-267ca6975798',
prompt: 'select_account'
}, (err, data) => {
if (err) {
this.setState({
loginError: err.toString()
});
return;
}
this.props.onLogin(data);
});
});
}
componentDidMount() {
this.auth = new _decapCmsLibAuth.ImplicitAuthenticator({
base_url: `https://login.microsoftonline.com/${this.props.config.backend.tenant_id}`,
auth_endpoint: 'oauth2/authorize',
app_id: this.props.config.backend.app_id,
clearHash: this.props.clearHash
});
// Complete implicit authentication if we were redirected back to from the provider.
this.auth.completeAuth((err, data) => {
if (err) {
alert(err);
return;
}
this.props.onLogin(data);
});
}
render() {
const {
inProgress,
config,
t
} = this.props;
return (0, _react2.jsx)(_decapCmsUiDefault.AuthenticationPage, {
onLogin: this.handleLogin,
loginDisabled: inProgress,
loginErrorMessage: this.state.loginError,
logoUrl: config.logo_url,
renderButtonContent: () => (0, _react2.jsx)(_react.default.Fragment, null, (0, _react2.jsx)(LoginButtonIcon, {
type: "azure"
}), inProgress ? t('auth.loggingIn') : t('auth.loginWithAzure')),
t: t
});
}
}
exports.default = AzureAuthenticationPage;
_defineProperty(AzureAuthenticationPage, "propTypes", {
onLogin: _propTypes.default.func.isRequired,
inProgress: _propTypes.default.bool,
base_url: _propTypes.default.string,
siteId: _propTypes.default.string,
authEndpoint: _propTypes.default.string,
config: _propTypes.default.object.isRequired,
clearHash: _propTypes.default.func,
t: _propTypes.default.func.isRequired
});

View File

@@ -0,0 +1,309 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _trim2 = _interopRequireDefault(require("lodash/trim"));
var _trimStart2 = _interopRequireDefault(require("lodash/trimStart"));
var _semaphore = _interopRequireDefault(require("semaphore"));
var _decapCmsLibUtil = require("decap-cms-lib-util");
var _AuthenticationPage = _interopRequireDefault(require("./AuthenticationPage"));
var _API = _interopRequireWildcard(require("./API"));
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 MAX_CONCURRENT_DOWNLOADS = 10;
function parseAzureRepo(config) {
const {
repo
} = config.backend;
if (typeof repo !== 'string') {
throw new Error('The Azure backend needs a "repo" in the backend configuration.');
}
const parts = repo.split('/');
if (parts.length !== 3) {
throw new Error('The Azure backend must be in a the format of {org}/{project}/{repo}');
}
const [org, project, repoName] = parts;
return {
org,
project,
repoName
};
}
class Azure {
constructor(config, options = {}) {
_defineProperty(this, "lock", void 0);
_defineProperty(this, "api", void 0);
_defineProperty(this, "options", void 0);
_defineProperty(this, "repo", void 0);
_defineProperty(this, "branch", void 0);
_defineProperty(this, "apiRoot", void 0);
_defineProperty(this, "apiVersion", void 0);
_defineProperty(this, "token", void 0);
_defineProperty(this, "squashMerges", void 0);
_defineProperty(this, "cmsLabelPrefix", void 0);
_defineProperty(this, "mediaFolder", void 0);
_defineProperty(this, "previewContext", void 0);
_defineProperty(this, "_mediaDisplayURLSem", void 0);
this.options = _objectSpread({
initialWorkflowStatus: ''
}, options);
this.repo = parseAzureRepo(config);
this.branch = config.backend.branch || 'master';
this.apiRoot = config.backend.api_root || 'https://dev.azure.com';
this.apiVersion = config.backend.api_version || '6.1-preview';
this.token = '';
this.squashMerges = config.backend.squash_merges || false;
this.cmsLabelPrefix = config.backend.cms_label_prefix || '';
this.mediaFolder = (0, _trim2.default)(config.media_folder, '/');
this.previewContext = config.backend.preview_context || '';
this.lock = (0, _decapCmsLibUtil.asyncLock)();
}
isGitBackend() {
return true;
}
async status() {
const auth = (await this.api.user().then(user => !!user).catch(e => {
console.warn('Failed getting Azure user', e);
return false;
})) || false;
return {
auth: {
status: auth
},
api: {
status: true,
statusPage: ''
}
};
}
authComponent() {
return _AuthenticationPage.default;
}
restoreUser(user) {
return this.authenticate(user);
}
async authenticate(state) {
this.token = state.token;
this.api = new _API.default({
apiRoot: this.apiRoot,
apiVersion: this.apiVersion,
repo: this.repo,
branch: this.branch,
squashMerges: this.squashMerges,
cmsLabelPrefix: this.cmsLabelPrefix,
initialWorkflowStatus: this.options.initialWorkflowStatus
}, this.token);
const user = await this.api.user();
return _objectSpread({
token: state.token
}, user);
}
/**
* Log the user out by forgetting their access token.
* TODO: *Actual* logout by redirecting to:
* https://login.microsoftonline.com/{tenantId}/oauth2/logout?client_id={clientId}&post_logout_redirect_uri={baseUrl}
*/
logout() {
this.token = null;
return;
}
getToken() {
return Promise.resolve(this.token);
}
async entriesByFolder(folder, extension, depth) {
const listFiles = async () => {
const files = await this.api.listFiles(folder, depth > 1);
const filtered = files.filter(file => (0, _decapCmsLibUtil.filterByExtension)({
path: file.path
}, extension));
return filtered.map(file => ({
id: file.id,
path: file.path
}));
};
const entries = await (0, _decapCmsLibUtil.entriesByFolder)(listFiles, this.api.readFile.bind(this.api), this.api.readFileMetadata.bind(this.api), _API.API_NAME);
return entries;
}
entriesByFiles(files) {
return (0, _decapCmsLibUtil.entriesByFiles)(files, this.api.readFile.bind(this.api), this.api.readFileMetadata.bind(this.api), _API.API_NAME);
}
async getEntry(path) {
const data = await this.api.readFile(path);
return {
file: {
path
},
data
};
}
async getMedia() {
const files = await this.api.listFiles(this.mediaFolder, false);
const mediaFiles = await Promise.all(files.map(async ({
id,
path,
name
}) => {
const blobUrl = await this.getMediaDisplayURL({
id,
path
});
return {
id,
name,
displayURL: blobUrl,
path
};
}));
return mediaFiles;
}
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);
}
async getMediaFile(path) {
const name = (0, _decapCmsLibUtil.basename)(path);
const blob = await (0, _decapCmsLibUtil.getMediaAsBlob)(path, null, this.api.readFile.bind(this.api));
const fileObj = new File([blob], name);
const url = URL.createObjectURL(fileObj);
const id = await (0, _decapCmsLibUtil.getBlobSHA)(blob);
return {
id,
displayURL: url,
path,
name,
size: fileObj.size,
file: fileObj,
url
};
}
async persistEntry(entry, options) {
const mediaFiles = entry.assets;
await this.api.persistFiles(entry.dataFiles, mediaFiles, options);
}
async persistMedia(mediaFile, options) {
const fileObj = mediaFile.fileObj;
const [id] = await Promise.all([(0, _decapCmsLibUtil.getBlobSHA)(fileObj), this.api.persistFiles([], [mediaFile], options)]);
const {
path
} = mediaFile;
const url = URL.createObjectURL(fileObj);
return {
displayURL: url,
path: (0, _trimStart2.default)(path, '/'),
name: fileObj.name,
size: fileObj.size,
file: fileObj,
url,
id: id
};
}
async deleteFiles(paths, commitMessage) {
await this.api.deleteFiles(paths, commitMessage);
}
async loadMediaFile(branch, file) {
const readFile = (path, id, {
parseText
}) => this.api.readFile(path, id, {
branch,
parseText
});
const blob = await (0, _decapCmsLibUtil.getMediaAsBlob)(file.path, null, readFile);
const name = (0, _decapCmsLibUtil.basename)(file.path);
const fileObj = new File([blob], name);
return {
id: file.path,
displayURL: URL.createObjectURL(fileObj),
path: file.path,
name,
size: fileObj.size,
file: fileObj
};
}
async loadEntryMediaFiles(branch, files) {
const mediaFiles = await Promise.all(files.map(file => this.loadMediaFile(branch, file)));
return mediaFiles;
}
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 contentKey = (0, _decapCmsLibUtil.generateContentKey)(collection, slug);
const data = await this.api.retrieveUnpublishedEntryData(contentKey);
return data;
} else {
throw new Error('Missing unpublished entry id or collection and slug');
}
}
getBranch(collection, slug) {
const contentKey = (0, _decapCmsLibUtil.generateContentKey)(collection, slug);
const branch = (0, _decapCmsLibUtil.branchFromContentKey)(contentKey);
return branch;
}
async unpublishedEntryMediaFile(collection, slug, path, id) {
const branch = this.getBranch(collection, slug);
const mediaFile = await this.loadMediaFile(branch, {
path,
id
});
return mediaFile;
}
async unpublishedEntryDataFile(collection, slug, path, id) {
const branch = this.getBranch(collection, slug);
const data = await this.api.readFile(path, id, {
branch
});
return data;
}
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');
}
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;
}
}
}
exports.default = Azure;

33
node_modules/decap-cms-backend-azure/dist/esm/index.js generated vendored Normal file
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;
}
});
Object.defineProperty(exports, "AzureBackend", {
enumerable: true,
get: function () {
return _implementation.default;
}
});
exports.DecapCmsBackendAzure = void 0;
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 DecapCmsBackendAzure = exports.DecapCmsBackendAzure = {
AzureBackend: _implementation.default,
API: _API.default,
AuthenticationPage: _AuthenticationPage.default
};

38
node_modules/decap-cms-backend-azure/package.json generated vendored Normal file
View File

@@ -0,0 +1,38 @@
{
"name": "decap-cms-backend-azure",
"description": "Azure DevOps backend for Decap CMS",
"version": "3.1.3",
"license": "MIT",
"repository": "https://github.com/decaporg/decap-cms/tree/main/packages/decap-cms-backend-azure",
"bugs": "https://github.com/decaporg/decap-cms/issues",
"module": "dist/esm/index.js",
"main": "dist/decap-cms-backend-azure.js",
"keywords": [
"decap-cms",
"backend",
"azure",
"devops"
],
"sideEffects": false,
"scripts": {
"develop": "npm run build:esm -- --watch",
"build": "cross-env NODE_ENV=production webpack",
"build:esm": "cross-env NODE_ENV=esm babel src --out-dir dist/esm --ignore **/__tests__ --root-mode upward --extensions \".js,.jsx,.ts,.tsx\""
},
"dependencies": {
"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",
"immutable": "^3.7.6",
"lodash": "^4.17.11",
"prop-types": "^15.7.2",
"react": "^18.2.0"
},
"gitHead": "64d91b8bb3d0a93dd36c53800cdac4ba2e435000"
}

793
node_modules/decap-cms-backend-azure/src/API.ts generated vendored Normal file
View File

@@ -0,0 +1,793 @@
import { Base64 } from 'js-base64';
import { partial, result, trim, trimStart } from 'lodash';
import {
localForage,
APIError,
unsentRequest,
requestWithBackoff,
responseParser,
readFile,
DEFAULT_PR_BODY,
MERGE_COMMIT_MESSAGE,
generateContentKey,
parseContentKey,
labelToStatus,
isCMSLabel,
EditorialWorkflowError,
statusToLabel,
PreviewState,
readFileMetadata,
branchFromContentKey,
} from 'decap-cms-lib-util';
import { dirname, basename } from 'path';
import type { ApiRequest, AssetProxy, PersistOptions, DataFile } from 'decap-cms-lib-util';
import type { Map } from 'immutable';
export const API_NAME = 'Azure DevOps';
const API_VERSION = 'api-version';
type AzureUser = {
coreAttributes?: {
Avatar?: { value?: { value?: string } };
DisplayName?: { value?: string };
EmailAddress?: { value?: string };
};
};
type AzureGitItem = {
objectId: string;
gitObjectType: AzureObjectType;
path: string;
};
// https://docs.microsoft.com/en-us/rest/api/azure/devops/git/pull%20requests/get%20pull%20request?view=azure-devops-rest-6.1#gitpullrequest
type AzureWebApiTagDefinition = {
active: boolean;
id: string;
name: string;
url: string;
};
type AzurePullRequest = {
title: string;
artifactId: string;
closedDate: string;
creationDate: string;
isDraft: string;
status: AzurePullRequestStatus;
lastMergeSourceCommit: AzureGitChangeItem;
mergeStatus: AzureAsyncPullRequestStatus;
pullRequestId: number;
labels: AzureWebApiTagDefinition[];
sourceRefName: string;
createdBy?: {
displayName?: string;
uniqueName: string;
};
};
type AzurePullRequestCommit = { commitId: string };
enum AzureCommitStatusState {
ERROR = 'error',
FAILED = 'failed',
NOT_APPLICABLE = 'notApplicable',
NOT_SET = 'notSet',
PENDING = 'pending',
SUCCEEDED = 'succeeded',
}
type AzureCommitStatus = {
context: { genre?: string | null; name: string };
state: AzureCommitStatusState;
targetUrl: string;
};
// This does not match Azure documentation, but it is what comes back from some calls
// PullRequest as an example is documented as returning PullRequest[], but it actually
// returns that inside of this value prop in the json
interface AzureArray<T> {
value: T[];
}
enum AzureCommitChangeType {
ADD = 'add',
DELETE = 'delete',
RENAME = 'rename',
EDIT = 'edit',
}
enum AzureItemContentType {
BASE64 = 'base64encoded',
}
enum AzurePullRequestStatus {
ACTIVE = 'active',
COMPLETED = 'completed',
ABANDONED = 'abandoned',
}
enum AzureAsyncPullRequestStatus {
CONFLICTS = 'conflicts',
FAILURE = 'failure',
QUEUED = 'queued',
REJECTED = 'rejectedByPolicy',
SUCCEEDED = 'succeeded',
}
enum AzureObjectType {
BLOB = 'blob',
TREE = 'tree',
}
// https://docs.microsoft.com/en-us/rest/api/azure/devops/git/diffs/get?view=azure-devops-rest-6.1#gitcommitdiffs
interface AzureGitCommitDiffs {
changes: AzureGitChange[];
}
// https://docs.microsoft.com/en-us/rest/api/azure/devops/git/diffs/get?view=azure-devops-rest-6.1#gitchange
interface AzureGitChange {
changeId: number;
item: AzureGitChangeItem;
changeType: AzureCommitChangeType;
originalPath: string;
url: string;
}
interface AzureGitChangeItem {
objectId: string;
originalObjectId: string;
gitObjectType: string;
commitId: string;
path: string;
isFolder: string;
url: string;
}
type AzureRef = {
name: string;
objectId: string;
};
type AzureCommit = {
author: {
date: string;
email: string;
name: string;
};
};
function delay(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function getChangeItem(item: AzureCommitItem) {
switch (item.action) {
case AzureCommitChangeType.ADD:
return {
changeType: AzureCommitChangeType.ADD,
item: { path: item.path },
newContent: {
content: item.base64Content,
contentType: AzureItemContentType.BASE64,
},
};
case AzureCommitChangeType.EDIT:
return {
changeType: AzureCommitChangeType.EDIT,
item: { path: item.path },
newContent: {
content: item.base64Content,
contentType: AzureItemContentType.BASE64,
},
};
case AzureCommitChangeType.DELETE:
return {
changeType: AzureCommitChangeType.DELETE,
item: { path: item.path },
};
case AzureCommitChangeType.RENAME:
return {
changeType: AzureCommitChangeType.RENAME,
item: { path: item.path },
sourceServerItem: item.oldPath,
};
default:
return {};
}
}
type AzureCommitItem = {
action: AzureCommitChangeType;
base64Content?: string;
text?: string;
path: string;
oldPath?: string;
};
interface AzureApiConfig {
apiRoot: string;
repo: { org: string; project: string; repoName: string };
branch: string;
squashMerges: boolean;
initialWorkflowStatus: string;
cmsLabelPrefix: string;
apiVersion: string;
}
export default class API {
apiVersion: string;
token: string;
branch: string;
mergeStrategy: string;
endpointUrl: string;
initialWorkflowStatus: string;
cmsLabelPrefix: string;
constructor(config: AzureApiConfig, token: string) {
const { repo } = config;
const apiRoot = trim(config.apiRoot, '/');
this.endpointUrl = `${apiRoot}/${repo.org}/${repo.project}/_apis/git/repositories/${repo.repoName}`;
this.token = token;
this.branch = config.branch;
this.mergeStrategy = config.squashMerges ? 'squash' : 'noFastForward';
this.initialWorkflowStatus = config.initialWorkflowStatus;
this.apiVersion = config.apiVersion;
this.cmsLabelPrefix = config.cmsLabelPrefix;
}
withHeaders = (req: ApiRequest) => {
const withHeaders = unsentRequest.withHeaders(
{
Authorization: `Bearer ${this.token}`,
'Content-Type': 'application/json; charset=utf-8',
},
req,
);
return withHeaders;
};
withAzureFeatures = (req: Map<string, Map<string, string>>) => {
if (req.hasIn(['params', API_VERSION])) {
return req;
}
const withParams = unsentRequest.withParams(
{
[API_VERSION]: `${this.apiVersion}`,
},
req,
);
return withParams;
};
buildRequest = (req: ApiRequest) => {
const withHeaders = this.withHeaders(req);
const withAzureFeatures = this.withAzureFeatures(withHeaders);
if (withAzureFeatures.has('cache')) {
return withAzureFeatures;
} else {
const withNoCache = unsentRequest.withNoCache(withAzureFeatures);
return withNoCache;
}
};
request = (req: ApiRequest): Promise<Response> => {
try {
return requestWithBackoff(this, req);
} catch (err) {
throw new APIError(err.message, null, API_NAME);
}
};
responseToJSON = responseParser({ format: 'json', apiName: API_NAME });
responseToBlob = responseParser({ format: 'blob', apiName: API_NAME });
responseToText = responseParser({ format: 'text', apiName: API_NAME });
requestJSON = <T>(req: ApiRequest) => this.request(req).then(this.responseToJSON) as Promise<T>;
requestText = (req: ApiRequest) => this.request(req).then(this.responseToText) as Promise<string>;
toBase64 = (str: string) => Promise.resolve(Base64.encode(str));
fromBase64 = (str: string) => Base64.decode(str);
branchToRef = (branch: string): string => `refs/heads/${branch}`;
refToBranch = (ref: string): string => ref.slice('refs/heads/'.length);
user = async () => {
const result = await this.requestJSON<AzureUser>({
url: 'https://app.vssps.visualstudio.com/_apis/profile/profiles/me',
params: { [API_VERSION]: '6.1-preview.2' },
});
const name = result.coreAttributes?.DisplayName?.value;
const email = result.coreAttributes?.EmailAddress?.value;
const url = result.coreAttributes?.Avatar?.value?.value;
const user = {
name: name || email || '',
avatar_url: `data:image/png;base64,${url}`,
email,
};
return user;
};
async readFileMetadata(
path: string,
sha: string | null | undefined,
{ branch = this.branch } = {},
) {
const fetchFileMetadata = async () => {
try {
const { value } = await this.requestJSON<AzureArray<AzureCommit>>({
url: `${this.endpointUrl}/commits/`,
params: {
'searchCriteria.itemPath': path,
'searchCriteria.itemVersion.version': branch,
'searchCriteria.$top': 1,
},
});
const [commit] = value;
return {
author: commit.author.name || commit.author.email,
updatedOn: commit.author.date,
};
} catch (error) {
return { author: '', updatedOn: '' };
}
};
const fileMetadata = await readFileMetadata(sha, fetchFileMetadata, localForage);
return fileMetadata;
}
readFile = (
path: string,
sha?: string | null,
{ parseText = true, branch = this.branch } = {},
) => {
const fetchContent = () => {
return this.request({
url: `${this.endpointUrl}/items/`,
params: { version: branch, path },
cache: 'no-store',
}).then<Blob | string>(parseText ? this.responseToText : this.responseToBlob);
};
return readFile(sha, fetchContent, localForage, parseText);
};
listFiles = async (path: string, recursive: boolean, branch = this.branch) => {
try {
const { value: items } = await this.requestJSON<AzureArray<AzureGitItem>>({
url: `${this.endpointUrl}/items/`,
params: {
version: branch,
scopePath: path,
recursionLevel: recursive ? 'full' : 'oneLevel',
},
});
const files = items
.filter(item => item.gitObjectType === AzureObjectType.BLOB)
.map(file => ({
id: file.objectId,
path: trimStart(file.path, '/'),
name: basename(file.path),
}));
return files;
} catch (err) {
if (err && err.status === 404) {
console.log('This 404 was expected and handled appropriately.');
return [];
} else {
throw err;
}
}
};
async getRef(branch: string = this.branch) {
const { value: refs } = await this.requestJSON<AzureArray<AzureRef>>({
url: `${this.endpointUrl}/refs`,
params: {
$top: '1', // There's only one ref, so keep the payload small
filter: 'heads/' + branch,
},
});
return refs.find(b => b.name == this.branchToRef(branch))!;
}
async deleteRef(ref: AzureRef): Promise<void> {
const deleteBranchPayload = [
{
name: ref.name,
oldObjectId: ref.objectId,
newObjectId: '0000000000000000000000000000000000000000',
},
];
await this.requestJSON({
method: 'POST',
url: `${this.endpointUrl}/refs`,
body: JSON.stringify(deleteBranchPayload),
});
}
async uploadAndCommit(
items: AzureCommitItem[],
comment: string,
branch: string,
newBranch: boolean,
) {
const ref = await this.getRef(newBranch ? this.branch : branch);
const refUpdate = [
{
name: this.branchToRef(branch),
oldObjectId: ref.objectId,
},
];
const changes = items.map(item => getChangeItem(item));
const commits = [{ comment, changes }];
const push = {
refUpdates: refUpdate,
commits,
};
return this.requestJSON({
url: `${this.endpointUrl}/pushes`,
method: 'POST',
body: JSON.stringify(push),
});
}
async retrieveUnpublishedEntryData(contentKey: string) {
const { collection, slug } = parseContentKey(contentKey);
const branch = branchFromContentKey(contentKey);
const pullRequest = await this.getBranchPullRequest(branch);
const diffs = await this.getDifferences(pullRequest.sourceRefName);
const diffsWithIds = await Promise.all(
diffs.map(async d => {
const path = trimStart(d.item.path, '/');
const newFile = d.changeType === AzureCommitChangeType.ADD;
const id = d.item.objectId;
return { id, path, newFile };
}),
);
const label = pullRequest.labels.find(l => isCMSLabel(l.name, this.cmsLabelPrefix));
const labelName = label && label.name ? label.name : this.cmsLabelPrefix;
const status = labelToStatus(labelName, this.cmsLabelPrefix);
// Uses creationDate, as we do not have direct access to the updated date
const updatedAt = pullRequest.closedDate ? pullRequest.closedDate : pullRequest.creationDate;
const pullRequestAuthor =
pullRequest.createdBy?.displayName || pullRequest.createdBy?.uniqueName;
return {
collection,
slug,
status,
diffs: diffsWithIds,
updatedAt,
pullRequestAuthor,
};
}
async getPullRequestStatues(pullRequest: AzurePullRequest) {
const { value: commits } = await this.requestJSON<AzureArray<AzurePullRequestCommit>>({
url: `${this.endpointUrl}/pullrequests/${pullRequest.pullRequestId}/commits`,
params: {
$top: 1,
},
});
const { value: statuses } = await this.requestJSON<AzureArray<AzureCommitStatus>>({
url: `${this.endpointUrl}/commits/${commits[0].commitId}/statuses`,
params: { latestOnly: true },
});
return statuses;
}
async getStatuses(collection: string, slug: string) {
const contentKey = generateContentKey(collection, slug);
const branch = branchFromContentKey(contentKey);
const pullRequest = await this.getBranchPullRequest(branch);
const statuses = await this.getPullRequestStatues(pullRequest);
return statuses.map(({ context, state, targetUrl }) => ({
context: context.name,
state: state === AzureCommitStatusState.SUCCEEDED ? PreviewState.Success : PreviewState.Other,
target_url: targetUrl,
}));
}
async getCommitItems(files: { path: string; newPath?: string }[], branch: string) {
const items = await Promise.all(
files.map(async file => {
const [base64Content, fileExists] = await Promise.all([
result(file, 'toBase64', partial(this.toBase64, (file as DataFile).raw)),
this.isFileExists(file.path, branch),
]);
const path = file.newPath || file.path;
const oldPath = file.path;
const renameOrEdit =
path !== oldPath ? AzureCommitChangeType.RENAME : AzureCommitChangeType.EDIT;
const action = fileExists ? renameOrEdit : AzureCommitChangeType.ADD;
return {
action,
base64Content,
path,
oldPath,
} as AzureCommitItem;
}),
);
// move children
for (const item of items.filter(i => i.oldPath && i.action === AzureCommitChangeType.RENAME)) {
const sourceDir = dirname(item.oldPath as string);
const destDir = dirname(item.path);
const children = await this.listFiles(sourceDir, true, branch);
children
.filter(file => file.path !== item.oldPath)
.forEach(file => {
items.push({
action: AzureCommitChangeType.RENAME,
path: file.path.replace(sourceDir, destDir),
oldPath: file.path,
});
});
}
return items;
}
async persistFiles(dataFiles: DataFile[], mediaFiles: AssetProxy[], options: PersistOptions) {
const files = [...dataFiles, ...mediaFiles];
if (options.useWorkflow) {
const slug = dataFiles[0].slug;
return this.editorialWorkflowGit(files, slug, options);
} else {
const items = await this.getCommitItems(files, this.branch);
return this.uploadAndCommit(items, options.commitMessage, this.branch, true);
}
}
async deleteFiles(paths: string[], comment: string) {
const ref = await this.getRef(this.branch);
const refUpdate = {
name: ref.name,
oldObjectId: ref.objectId,
};
const changes = paths.map(path =>
getChangeItem({ action: AzureCommitChangeType.DELETE, path }),
);
const commits = [{ comment, changes }];
const push = {
refUpdates: [refUpdate],
commits,
};
return this.requestJSON({
url: `${this.endpointUrl}/pushes`,
method: 'POST',
body: JSON.stringify(push),
});
}
async getPullRequests(sourceBranch?: string) {
const { value: pullRequests } = await this.requestJSON<AzureArray<AzurePullRequest>>({
url: `${this.endpointUrl}/pullrequests`,
params: {
'searchCriteria.status': 'active',
'searchCriteria.targetRefName': this.branchToRef(this.branch),
'searchCriteria.includeLinks': false,
...(sourceBranch ? { 'searchCriteria.sourceRefName': this.branchToRef(sourceBranch) } : {}),
},
});
const filtered = pullRequests.filter(pr => {
return pr.labels.some(label => isCMSLabel(label.name, this.cmsLabelPrefix));
});
return filtered;
}
async listUnpublishedBranches(): Promise<string[]> {
const pullRequests = await this.getPullRequests();
const branches = pullRequests.map(pr => this.refToBranch(pr.sourceRefName));
return branches;
}
async isFileExists(path: string, branch: string) {
try {
await this.requestText({
url: `${this.endpointUrl}/items/`,
params: { version: branch, path },
cache: 'no-store',
});
return true;
} catch (error) {
if (error instanceof APIError && error.status === 404) {
return false;
}
throw error;
}
}
async createPullRequest(branch: string, commitMessage: string, status: string) {
const pr = {
sourceRefName: this.branchToRef(branch),
targetRefName: this.branchToRef(this.branch),
title: commitMessage,
description: DEFAULT_PR_BODY,
labels: [
{
name: statusToLabel(status, this.cmsLabelPrefix),
},
],
};
await this.requestJSON({
method: 'POST',
url: `${this.endpointUrl}/pullrequests`,
params: {
supportsIterations: false,
},
body: JSON.stringify(pr),
});
}
async getBranchPullRequest(branch: string) {
const pullRequests = await this.getPullRequests(branch);
if (pullRequests.length <= 0) {
throw new EditorialWorkflowError('content is not under editorial workflow', true);
}
return pullRequests[0];
}
async getDifferences(to: string) {
const result = await this.requestJSON<AzureGitCommitDiffs>({
url: `${this.endpointUrl}/diffs/commits`,
params: {
baseVersion: this.branch,
targetVersion: this.refToBranch(to),
},
});
return result.changes.filter(
d =>
d.item.gitObjectType === AzureObjectType.BLOB &&
Object.values(AzureCommitChangeType).includes(d.changeType),
);
}
async editorialWorkflowGit(
files: (DataFile | AssetProxy)[],
slug: string,
options: PersistOptions,
) {
const contentKey = generateContentKey(options.collectionName as string, slug);
const branch = branchFromContentKey(contentKey);
const unpublished = options.unpublished || false;
if (!unpublished) {
const items = await this.getCommitItems(files, this.branch);
await this.uploadAndCommit(items, options.commitMessage, branch, true);
await this.createPullRequest(
branch,
options.commitMessage,
options.status || this.initialWorkflowStatus,
);
} else {
const items = await this.getCommitItems(files, branch);
await this.uploadAndCommit(items, options.commitMessage, branch, false);
}
}
async updateUnpublishedEntryStatus(collection: string, slug: string, newStatus: string) {
const contentKey = generateContentKey(collection, slug);
const branch = branchFromContentKey(contentKey);
const pullRequest = await this.getBranchPullRequest(branch);
const nonCmsLabels = pullRequest.labels
.filter(label => !isCMSLabel(label.name, this.cmsLabelPrefix))
.map(label => label.name);
const labels = [...nonCmsLabels, statusToLabel(newStatus, this.cmsLabelPrefix)];
await this.updatePullRequestLabels(pullRequest, labels);
}
async deleteUnpublishedEntry(collectionName: string, slug: string) {
const contentKey = generateContentKey(collectionName, slug);
const branch = branchFromContentKey(contentKey);
const pullRequest = await this.getBranchPullRequest(branch);
await this.abandonPullRequest(pullRequest);
}
async publishUnpublishedEntry(collectionName: string, slug: string) {
const contentKey = generateContentKey(collectionName, slug);
const branch = branchFromContentKey(contentKey);
const pullRequest = await this.getBranchPullRequest(branch);
await this.completePullRequest(pullRequest);
}
async updatePullRequestLabels(pullRequest: AzurePullRequest, labels: string[]) {
const cmsLabels = pullRequest.labels.filter(l => isCMSLabel(l.name, this.cmsLabelPrefix));
await Promise.all(
cmsLabels.map(l => {
return this.requestText({
method: 'DELETE',
url: `${this.endpointUrl}/pullrequests/${encodeURIComponent(
pullRequest.pullRequestId,
)}/labels/${encodeURIComponent(l.id)}`,
});
}),
);
await Promise.all(
labels.map(l => {
return this.requestText({
method: 'POST',
url: `${this.endpointUrl}/pullrequests/${encodeURIComponent(
pullRequest.pullRequestId,
)}/labels`,
body: JSON.stringify({ name: l }),
});
}),
);
}
async completePullRequest(pullRequest: AzurePullRequest) {
const pullRequestCompletion = {
status: AzurePullRequestStatus.COMPLETED,
lastMergeSourceCommit: pullRequest.lastMergeSourceCommit,
completionOptions: {
deleteSourceBranch: true,
mergeCommitMessage: MERGE_COMMIT_MESSAGE,
mergeStrategy: this.mergeStrategy,
},
};
let response = await this.requestJSON<AzurePullRequest>({
method: 'PATCH',
url: `${this.endpointUrl}/pullrequests/${encodeURIComponent(pullRequest.pullRequestId)}`,
body: JSON.stringify(pullRequestCompletion),
});
// We need to wait for Azure to complete the pull request to actually complete
// Sometimes this is instant, but frequently it is 1-3 seconds
const DELAY_MILLISECONDS = 500;
const MAX_ATTEMPTS = 10;
let attempt = 1;
while (response.mergeStatus === AzureAsyncPullRequestStatus.QUEUED && attempt <= MAX_ATTEMPTS) {
await delay(DELAY_MILLISECONDS);
response = await this.requestJSON({
url: `${this.endpointUrl}/pullrequests/${encodeURIComponent(pullRequest.pullRequestId)}`,
});
attempt = attempt + 1;
}
}
async abandonPullRequest(pullRequest: AzurePullRequest) {
const pullRequestAbandon = {
status: AzurePullRequestStatus.ABANDONED,
};
await this.requestJSON({
method: 'PATCH',
url: `${this.endpointUrl}/pullrequests/${encodeURIComponent(pullRequest.pullRequestId)}`,
body: JSON.stringify(pullRequestAbandon),
});
await this.deleteRef({
name: pullRequest.sourceRefName,
objectId: pullRequest.lastMergeSourceCommit.commitId,
});
}
}

View File

@@ -0,0 +1,79 @@
import React from 'react';
import PropTypes from 'prop-types';
import styled from '@emotion/styled';
import { ImplicitAuthenticator } from 'decap-cms-lib-auth';
import { AuthenticationPage, Icon } from 'decap-cms-ui-default';
const LoginButtonIcon = styled(Icon)`
margin-right: 18px;
`;
export default class AzureAuthenticationPage 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 = {};
componentDidMount() {
this.auth = new ImplicitAuthenticator({
base_url: `https://login.microsoftonline.com/${this.props.config.backend.tenant_id}`,
auth_endpoint: 'oauth2/authorize',
app_id: this.props.config.backend.app_id,
clearHash: this.props.clearHash,
});
// Complete implicit authentication if we were redirected back to from the provider.
this.auth.completeAuth((err, data) => {
if (err) {
alert(err);
return;
}
this.props.onLogin(data);
});
}
handleLogin = e => {
e.preventDefault();
this.auth.authenticate(
{
scope: 'vso.code_full,user.read',
resource: '499b84ac-1321-427f-aa17-267ca6975798',
prompt: 'select_account',
},
(err, data) => {
if (err) {
this.setState({ loginError: err.toString() });
return;
}
this.props.onLogin(data);
},
);
};
render() {
const { inProgress, config, t } = this.props;
return (
<AuthenticationPage
onLogin={this.handleLogin}
loginDisabled={inProgress}
loginErrorMessage={this.state.loginError}
logoUrl={config.logo_url}
renderButtonContent={() => (
<React.Fragment>
<LoginButtonIcon type="azure" />
{inProgress ? t('auth.loggingIn') : t('auth.loginWithAzure')}
</React.Fragment>
)}
t={t}
/>
);
}
}

View File

@@ -0,0 +1,383 @@
import { trimStart, trim } from 'lodash';
import semaphore from 'semaphore';
import {
basename,
getMediaDisplayURL,
generateContentKey,
getMediaAsBlob,
getPreviewStatus,
asyncLock,
runWithLock,
unpublishedEntries,
entriesByFiles,
filterByExtension,
branchFromContentKey,
entriesByFolder,
contentKeyFromBranch,
getBlobSHA,
} from 'decap-cms-lib-util';
import AuthenticationPage from './AuthenticationPage';
import API, { API_NAME } from './API';
import type { Semaphore } from 'semaphore';
import type {
Credentials,
Implementation,
ImplementationFile,
ImplementationMediaFile,
DisplayURL,
Entry,
AssetProxy,
PersistOptions,
Config,
AsyncLock,
User,
UnpublishedEntryMediaFile,
} from 'decap-cms-lib-util';
const MAX_CONCURRENT_DOWNLOADS = 10;
function parseAzureRepo(config: Config) {
const { repo } = config.backend;
if (typeof repo !== 'string') {
throw new Error('The Azure backend needs a "repo" in the backend configuration.');
}
const parts = repo.split('/');
if (parts.length !== 3) {
throw new Error('The Azure backend must be in a the format of {org}/{project}/{repo}');
}
const [org, project, repoName] = parts;
return {
org,
project,
repoName,
};
}
export default class Azure implements Implementation {
lock: AsyncLock;
api?: API;
options: {
initialWorkflowStatus: string;
};
repo: {
org: string;
project: string;
repoName: string;
};
branch: string;
apiRoot: string;
apiVersion: string;
token: string | null;
squashMerges: boolean;
cmsLabelPrefix: string;
mediaFolder: string;
previewContext: string;
_mediaDisplayURLSem?: Semaphore;
constructor(config: Config, options = {}) {
this.options = {
initialWorkflowStatus: '',
...options,
};
this.repo = parseAzureRepo(config);
this.branch = config.backend.branch || 'master';
this.apiRoot = config.backend.api_root || 'https://dev.azure.com';
this.apiVersion = config.backend.api_version || '6.1-preview';
this.token = '';
this.squashMerges = config.backend.squash_merges || false;
this.cmsLabelPrefix = config.backend.cms_label_prefix || '';
this.mediaFolder = trim(config.media_folder, '/');
this.previewContext = config.backend.preview_context || '';
this.lock = asyncLock();
}
isGitBackend() {
return true;
}
async status() {
const auth =
(await this.api!.user()
.then(user => !!user)
.catch(e => {
console.warn('Failed getting Azure user', e);
return false;
})) || false;
return { auth: { status: auth }, api: { status: true, statusPage: '' } };
}
authComponent() {
return AuthenticationPage;
}
restoreUser(user: User) {
return this.authenticate(user);
}
async authenticate(state: Credentials) {
this.token = state.token as string;
this.api = new API(
{
apiRoot: this.apiRoot,
apiVersion: this.apiVersion,
repo: this.repo,
branch: this.branch,
squashMerges: this.squashMerges,
cmsLabelPrefix: this.cmsLabelPrefix,
initialWorkflowStatus: this.options.initialWorkflowStatus,
},
this.token,
);
const user = await this.api.user();
return { token: state.token as string, ...user };
}
/**
* Log the user out by forgetting their access token.
* TODO: *Actual* logout by redirecting to:
* https://login.microsoftonline.com/{tenantId}/oauth2/logout?client_id={clientId}&post_logout_redirect_uri={baseUrl}
*/
logout() {
this.token = null;
return;
}
getToken() {
return Promise.resolve(this.token);
}
async entriesByFolder(folder: string, extension: string, depth: number) {
const listFiles = async () => {
const files = await this.api!.listFiles(folder, depth > 1);
const filtered = files.filter(file => filterByExtension({ path: file.path }, extension));
return filtered.map(file => ({
id: file.id,
path: file.path,
}));
};
const entries = await entriesByFolder(
listFiles,
this.api!.readFile.bind(this.api!),
this.api!.readFileMetadata.bind(this.api),
API_NAME,
);
return entries;
}
entriesByFiles(files: ImplementationFile[]) {
return entriesByFiles(
files,
this.api!.readFile.bind(this.api!),
this.api!.readFileMetadata.bind(this.api),
API_NAME,
);
}
async getEntry(path: string) {
const data = (await this.api!.readFile(path)) as string;
return {
file: { path },
data,
};
}
async getMedia() {
const files = await this.api!.listFiles(this.mediaFolder, false);
const mediaFiles = await Promise.all(
files.map(async ({ id, path, name }) => {
const blobUrl = await this.getMediaDisplayURL({ id, path });
return { id, name, displayURL: blobUrl, path };
}),
);
return mediaFiles;
}
getMediaDisplayURL(displayURL: DisplayURL) {
this._mediaDisplayURLSem = this._mediaDisplayURLSem || semaphore(MAX_CONCURRENT_DOWNLOADS);
return getMediaDisplayURL(
displayURL,
this.api!.readFile.bind(this.api!),
this._mediaDisplayURLSem,
);
}
async getMediaFile(path: string) {
const name = basename(path);
const blob = await getMediaAsBlob(path, null, this.api!.readFile.bind(this.api!));
const fileObj = new File([blob], name);
const url = URL.createObjectURL(fileObj);
const id = await getBlobSHA(blob);
return {
id,
displayURL: url,
path,
name,
size: fileObj.size,
file: fileObj,
url,
};
}
async persistEntry(entry: Entry, options: PersistOptions): Promise<void> {
const mediaFiles: AssetProxy[] = entry.assets;
await this.api!.persistFiles(entry.dataFiles, mediaFiles, options);
}
async persistMedia(
mediaFile: AssetProxy,
options: PersistOptions,
): Promise<ImplementationMediaFile> {
const fileObj = mediaFile.fileObj as File;
const [id] = await Promise.all([
getBlobSHA(fileObj),
this.api!.persistFiles([], [mediaFile], options),
]);
const { path } = mediaFile;
const url = URL.createObjectURL(fileObj);
return {
displayURL: url,
path: trimStart(path, '/'),
name: fileObj!.name,
size: fileObj!.size,
file: fileObj,
url,
id: id as string,
};
}
async deleteFiles(paths: string[], commitMessage: string) {
await this.api!.deleteFiles(paths, commitMessage);
}
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, null, readFile);
const name = basename(file.path);
const fileObj = new File([blob], name);
return {
id: file.path,
displayURL: URL.createObjectURL(fileObj),
path: file.path,
name,
size: fileObj.size,
file: fileObj,
};
}
async loadEntryMediaFiles(branch: string, files: UnpublishedEntryMediaFile[]) {
const mediaFiles = await Promise.all(files.map(file => this.loadMediaFile(branch, file)));
return mediaFiles;
}
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 contentKey = generateContentKey(collection, slug);
const data = await this.api!.retrieveUnpublishedEntryData(contentKey);
return data;
} else {
throw new Error('Missing unpublished entry id or collection and slug');
}
}
getBranch(collection: string, slug: string) {
const contentKey = generateContentKey(collection, slug);
const branch = branchFromContentKey(contentKey);
return branch;
}
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 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;
}
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',
);
}
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;
}
}
}

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

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

View File

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