Feat: editorial workflow bitbucket gitlab (#3014)
* refactor: typescript the backends * feat: support multiple files upload for GitLab and BitBucket * fix: load entry media files from media folder or UI state * chore: cleanup log message * chore: code cleanup * refactor: typescript the test backend * refactor: cleanup getEntry unsued variables * refactor: moved shared backend code to lib util * chore: rename files to preserve history * fix: bind readFile method to API classes * test(e2e): switch to chrome in cypress tests * refactor: extract common api methods * refactor: remove most of immutable js usage from backends * feat(backend-gitlab): initial editorial workflow support * feat(backend-gitlab): implement missing workflow methods * chore: fix lint error * feat(backend-gitlab): support files deletion * test(e2e): add gitlab cypress tests * feat(backend-bitbucket): implement missing editorial workflow methods * test(e2e): add BitBucket backend e2e tests * build: update node version to 12 on netlify builds * fix(backend-bitbucket): extract BitBucket avatar url * test: fix git-gateway AuthenticationPage test * test(e2e): fix some backend tests * test(e2e): fix tests * test(e2e): add git-gateway editorial workflow test * chore: code cleanup * test(e2e): revert back to electron * test(e2e): add non editorial workflow tests * fix(git-gateway-gitlab): don't call unpublishedEntry in simple workflow gitlab git-gateway doesn't support editorial workflow APIs yet. This change makes sure not to call them in simple workflow * refactor(backend-bitbucket): switch to diffstat API instead of raw diff * chore: fix test * test(e2e): add more git-gateway tests * fix: post rebase typescript fixes * test(e2e): fix tests * fix: fix parsing of content key and add tests * refactor: rename test file * test(unit): add getStatues unit tests * chore: update cypress * docs: update beta docs
This commit is contained in:
committed by
Shawn Erquhart
parent
4ff5bc2ee0
commit
6f221ab3c1
@ -9,14 +9,21 @@ import {
|
||||
flowAsync,
|
||||
localForage,
|
||||
onlySuccessfulPromises,
|
||||
resolvePromiseProperties,
|
||||
ResponseParser,
|
||||
basename,
|
||||
AssetProxy,
|
||||
Entry as LibEntry,
|
||||
PersistOptions,
|
||||
readFile,
|
||||
CMS_BRANCH_PREFIX,
|
||||
generateContentKey,
|
||||
DEFAULT_PR_BODY,
|
||||
MERGE_COMMIT_MESSAGE,
|
||||
PreviewState,
|
||||
FetchError,
|
||||
} from 'netlify-cms-lib-util';
|
||||
import {
|
||||
UsersGetAuthenticatedResponse as GitHubUser,
|
||||
ReposGetResponse as GitHubRepo,
|
||||
ReposGetContentsResponseItem as GitHubFile,
|
||||
ReposGetBranchResponse as GitHubBranch,
|
||||
GitGetBlobResponse as GitHubBlob,
|
||||
GitCreateTreeResponse as GitHubTree,
|
||||
@ -28,35 +35,33 @@ import {
|
||||
ReposCompareCommitsResponseBaseCommit as GitHubCompareBaseCommit,
|
||||
GitCreateCommitResponseAuthor as GitHubAuthor,
|
||||
GitCreateCommitResponseCommitter as GitHubCommiter,
|
||||
ReposListStatusesForRefResponseItem,
|
||||
} from '@octokit/rest';
|
||||
|
||||
const CMS_BRANCH_PREFIX = 'cms';
|
||||
const CURRENT_METADATA_VERSION = '1';
|
||||
|
||||
interface FetchError extends Error {
|
||||
status: number;
|
||||
}
|
||||
export const API_NAME = 'GitHub';
|
||||
|
||||
interface Config {
|
||||
api_root?: string;
|
||||
export interface Config {
|
||||
apiRoot?: string;
|
||||
token?: string;
|
||||
branch?: string;
|
||||
useOpenAuthoring: boolean;
|
||||
useOpenAuthoring?: boolean;
|
||||
repo?: string;
|
||||
originRepo?: string;
|
||||
squash_merges?: string;
|
||||
squashMerges: boolean;
|
||||
initialWorkflowStatus: string;
|
||||
}
|
||||
|
||||
interface File {
|
||||
interface TreeFile {
|
||||
type: 'blob' | 'tree';
|
||||
sha: string;
|
||||
path: string;
|
||||
raw?: string;
|
||||
}
|
||||
|
||||
interface Entry extends File {
|
||||
slug: string;
|
||||
export interface Entry extends LibEntry {
|
||||
sha?: string;
|
||||
}
|
||||
|
||||
type Override<T, U> = Pick<T, Exclude<keyof T, keyof U>> & U;
|
||||
@ -69,18 +74,20 @@ type GitHubCompareFile = ReposCompareCommitsResponseFilesItem & { previous_filen
|
||||
|
||||
type GitHubCompareFiles = GitHubCompareFile[];
|
||||
|
||||
interface CommitFields {
|
||||
parents: { sha: string }[];
|
||||
sha: string;
|
||||
message: string;
|
||||
author: string;
|
||||
committer: string;
|
||||
tree: { sha: string };
|
||||
enum GitHubCommitStatusState {
|
||||
Error = 'error',
|
||||
Failure = 'failure',
|
||||
Pending = 'pending',
|
||||
Success = 'success',
|
||||
}
|
||||
|
||||
interface PR {
|
||||
type GitHubCommitStatus = ReposListStatusesForRefResponseItem & {
|
||||
state: GitHubCommitStatusState;
|
||||
};
|
||||
|
||||
export interface PR {
|
||||
number: number;
|
||||
head: string;
|
||||
head: string | { sha: string };
|
||||
}
|
||||
|
||||
interface MetaDataObjects {
|
||||
@ -88,7 +95,7 @@ interface MetaDataObjects {
|
||||
files: MediaFile[];
|
||||
}
|
||||
|
||||
interface Metadata {
|
||||
export interface Metadata {
|
||||
type: string;
|
||||
objects: MetaDataObjects;
|
||||
branch: string;
|
||||
@ -103,23 +110,16 @@ interface Metadata {
|
||||
timeStamp: string;
|
||||
}
|
||||
|
||||
interface Branch {
|
||||
export interface Branch {
|
||||
ref: string;
|
||||
}
|
||||
|
||||
interface BlobArgs {
|
||||
export interface BlobArgs {
|
||||
sha: string;
|
||||
repoURL: string;
|
||||
parseText: boolean;
|
||||
}
|
||||
|
||||
interface ContentArgs {
|
||||
path: string;
|
||||
branch: string;
|
||||
repoURL: string;
|
||||
parseText: boolean;
|
||||
}
|
||||
|
||||
type Param = string | number | undefined;
|
||||
|
||||
type Options = RequestInit & { params?: Record<string, Param | Record<string, Param>> };
|
||||
@ -133,30 +133,21 @@ const replace404WithEmptyArray = (err: FetchError) => {
|
||||
}
|
||||
};
|
||||
|
||||
type PersistOptions = {
|
||||
useWorkflow: boolean;
|
||||
commitMessage: string;
|
||||
collectionName: string;
|
||||
unpublished: boolean;
|
||||
parsedData?: { title: string; description: string };
|
||||
status: string;
|
||||
};
|
||||
|
||||
type MediaFile = {
|
||||
sha: string;
|
||||
path: string;
|
||||
};
|
||||
|
||||
export default class API {
|
||||
api_root: string;
|
||||
apiRoot: string;
|
||||
token: string;
|
||||
branch: string;
|
||||
useOpenAuthoring: boolean;
|
||||
useOpenAuthoring?: boolean;
|
||||
repo: string;
|
||||
originRepo: string;
|
||||
repoURL: string;
|
||||
originRepoURL: string;
|
||||
merge_method: string;
|
||||
mergeMethod: string;
|
||||
initialWorkflowStatus: string;
|
||||
|
||||
_userPromise?: Promise<GitHubUser>;
|
||||
@ -165,8 +156,7 @@ export default class API {
|
||||
commitAuthor?: {};
|
||||
|
||||
constructor(config: Config) {
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
this.api_root = config.api_root || 'https://api.github.com';
|
||||
this.apiRoot = config.apiRoot || 'https://api.github.com';
|
||||
this.token = config.token || '';
|
||||
this.branch = config.branch || 'master';
|
||||
this.useOpenAuthoring = config.useOpenAuthoring;
|
||||
@ -175,15 +165,13 @@ export default class API {
|
||||
this.repoURL = `/repos/${this.repo}`;
|
||||
// when not in 'useOpenAuthoring' mode originRepoURL === repoURL
|
||||
this.originRepoURL = `/repos/${this.originRepo}`;
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
this.merge_method = config.squash_merges ? 'squash' : 'merge';
|
||||
this.mergeMethod = config.squashMerges ? 'squash' : 'merge';
|
||||
this.initialWorkflowStatus = config.initialWorkflowStatus;
|
||||
}
|
||||
|
||||
static DEFAULT_COMMIT_MESSAGE = 'Automatically generated by Netlify CMS';
|
||||
static DEFAULT_PR_BODY = 'Automatically generated by Netlify CMS';
|
||||
|
||||
user() {
|
||||
user(): Promise<{ name: string; login: string }> {
|
||||
if (!this._userPromise) {
|
||||
this._userPromise = this.request('/user') as Promise<GitHubUser>;
|
||||
}
|
||||
@ -199,6 +187,10 @@ export default class API {
|
||||
});
|
||||
}
|
||||
|
||||
reset() {
|
||||
// no op
|
||||
}
|
||||
|
||||
requestHeaders(headers = {}) {
|
||||
const baseHeader: Record<string, string> = {
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
@ -207,10 +199,10 @@ export default class API {
|
||||
|
||||
if (this.token) {
|
||||
baseHeader.Authorization = `token ${this.token}`;
|
||||
return baseHeader;
|
||||
return Promise.resolve(baseHeader);
|
||||
}
|
||||
|
||||
return baseHeader;
|
||||
return Promise.resolve(baseHeader);
|
||||
}
|
||||
|
||||
parseJsonResponse(response: Response) {
|
||||
@ -234,7 +226,7 @@ export default class API {
|
||||
if (params.length) {
|
||||
path += `?${params.join('&')}`;
|
||||
}
|
||||
return this.api_root + path;
|
||||
return this.apiRoot + path;
|
||||
}
|
||||
|
||||
parseResponse(response: Response) {
|
||||
@ -252,16 +244,15 @@ export default class API {
|
||||
}
|
||||
|
||||
handleRequestError(error: FetchError, responseStatus: number) {
|
||||
throw new APIError(error.message, responseStatus, 'GitHub');
|
||||
throw new APIError(error.message, responseStatus, API_NAME);
|
||||
}
|
||||
|
||||
async request(
|
||||
path: string,
|
||||
options: Options = {},
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
parser: ResponseParser<any> = response => this.parseResponse(response),
|
||||
parser = (response: Response) => this.parseResponse(response),
|
||||
) {
|
||||
// overriding classes can return a promise from requestHeaders
|
||||
const headers = await this.requestHeaders(options.headers || {});
|
||||
const url = this.urlFor(path, options);
|
||||
let responseStatus: number;
|
||||
@ -274,7 +265,6 @@ export default class API {
|
||||
}
|
||||
|
||||
async requestAllPages<T>(url: string, options: Options = {}) {
|
||||
// overriding classes can return a promise from requestHeaders
|
||||
const headers = await this.requestHeaders(options.headers || {});
|
||||
const processedURL = this.urlFor(url, options);
|
||||
const allResponses = await getAllResponses(processedURL, { ...options, headers });
|
||||
@ -286,7 +276,7 @@ export default class API {
|
||||
|
||||
generateContentKey(collectionName: string, slug: string) {
|
||||
if (!this.useOpenAuthoring) {
|
||||
return `${collectionName}/${slug}`;
|
||||
return generateContentKey(collectionName, slug);
|
||||
}
|
||||
|
||||
return `${this.repo}/${collectionName}/${slug}`;
|
||||
@ -353,7 +343,7 @@ export default class API {
|
||||
const file = { path: `${key}.json`, raw: JSON.stringify(data) };
|
||||
|
||||
await this.uploadBlob(file);
|
||||
const changeTree = await this.updateTree(branchData.sha, [file as File]);
|
||||
const changeTree = await this.updateTree(branchData.sha, [file as TreeFile]);
|
||||
const { sha } = await this.commit(`Updating “${key}” metadata`, changeTree);
|
||||
await this.patchRef('meta', '_netlify_cms', sha);
|
||||
localForage.setItem(`gh.meta.${key}`, {
|
||||
@ -433,16 +423,9 @@ export default class API {
|
||||
});
|
||||
}
|
||||
|
||||
retrieveContent({ path, branch, repoURL, parseText }: ContentArgs) {
|
||||
return this.request(`${repoURL}/contents/${path}`, {
|
||||
params: { ref: branch },
|
||||
cache: 'no-store',
|
||||
}).then((file: GitHubFile) => this.getBlob({ sha: file.sha, repoURL, parseText }));
|
||||
}
|
||||
|
||||
readFile(
|
||||
async readFile(
|
||||
path: string,
|
||||
sha: string | null,
|
||||
sha?: string | null,
|
||||
{
|
||||
branch = this.branch,
|
||||
repoURL = this.repoURL,
|
||||
@ -453,11 +436,12 @@ export default class API {
|
||||
parseText?: boolean;
|
||||
} = {},
|
||||
) {
|
||||
if (sha) {
|
||||
return this.getBlob({ sha, repoURL, parseText });
|
||||
} else {
|
||||
return this.retrieveContent({ path, branch, repoURL, parseText });
|
||||
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) {
|
||||
@ -479,38 +463,10 @@ export default class API {
|
||||
}
|
||||
}
|
||||
|
||||
async getMediaAsBlob(sha: string | null, path: string) {
|
||||
let blob: Blob;
|
||||
if (path.match(/.svg$/)) {
|
||||
const text = (await this.readFile(path, sha, { parseText: true })) as string;
|
||||
blob = new Blob([text], { type: 'image/svg+xml' });
|
||||
} else {
|
||||
blob = (await this.readFile(path, sha, { parseText: false })) as Blob;
|
||||
}
|
||||
return blob;
|
||||
}
|
||||
|
||||
async getMediaDisplayURL(sha: string, path: string) {
|
||||
const blob = await this.getMediaAsBlob(sha, path);
|
||||
|
||||
return URL.createObjectURL(blob);
|
||||
}
|
||||
|
||||
getBlob({ sha, repoURL = this.repoURL, parseText = true }: BlobArgs) {
|
||||
const key = parseText ? `gh.${sha}` : `gh.${sha}.blob`;
|
||||
return localForage.getItem<string | Blob>(key).then(cached => {
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
return this.fetchBlobContent({ sha, repoURL, parseText }).then(result => {
|
||||
localForage.setItem(key, result);
|
||||
return result;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async listFiles(path: string, { repoURL = this.repoURL, branch = this.branch, depth = 1 } = {}) {
|
||||
async listFiles(
|
||||
path: string,
|
||||
{ repoURL = this.repoURL, branch = this.branch, depth = 1 } = {},
|
||||
): Promise<{ type: string; id: string; name: string; path: string; size: number }[]> {
|
||||
const folder = trim(path, '/');
|
||||
return this.request(`${repoURL}/git/trees/${branch}:${folder}`, {
|
||||
// GitHub API supports recursive=1 for getting the entire recursive tree
|
||||
@ -522,43 +478,50 @@ export default class API {
|
||||
// filter only files and up to the required depth
|
||||
.filter(file => file.type === 'blob' && file.path.split('/').length <= depth)
|
||||
.map(file => ({
|
||||
...file,
|
||||
type: file.type,
|
||||
id: file.sha,
|
||||
name: basename(file.path),
|
||||
path: `${folder}/${file.path}`,
|
||||
size: file.size,
|
||||
})),
|
||||
)
|
||||
.catch(replace404WithEmptyArray);
|
||||
}
|
||||
|
||||
readUnpublishedBranchFile(contentKey: string) {
|
||||
const metaDataPromise = this.retrieveMetadata(contentKey).then(data =>
|
||||
data.objects.entry.path ? data : Promise.reject(null),
|
||||
);
|
||||
const repoURL = this.useOpenAuthoring
|
||||
? `/repos/${contentKey
|
||||
.split('/')
|
||||
.slice(0, 2)
|
||||
.join('/')}`
|
||||
: this.repoURL;
|
||||
return resolvePromiseProperties({
|
||||
metaData: metaDataPromise,
|
||||
fileData: metaDataPromise.then(data =>
|
||||
this.readFile(data.objects.entry.path, null, {
|
||||
branch: data.branch,
|
||||
async readUnpublishedBranchFile(contentKey: string) {
|
||||
try {
|
||||
const metaData = await this.retrieveMetadata(contentKey).then(data =>
|
||||
data.objects.entry.path ? data : Promise.reject(null),
|
||||
);
|
||||
const repoURL = this.useOpenAuthoring
|
||||
? `/repos/${contentKey
|
||||
.split('/')
|
||||
.slice(0, 2)
|
||||
.join('/')}`
|
||||
: this.repoURL;
|
||||
|
||||
const [fileData, isModification] = await Promise.all([
|
||||
this.readFile(metaData.objects.entry.path, null, {
|
||||
branch: metaData.branch,
|
||||
repoURL,
|
||||
}),
|
||||
),
|
||||
isModification: metaDataPromise.then(data =>
|
||||
this.isUnpublishedEntryModification(data.objects.entry.path, this.branch),
|
||||
),
|
||||
}).catch(() => {
|
||||
}) as Promise<string>,
|
||||
this.isUnpublishedEntryModification(metaData.objects.entry.path),
|
||||
]);
|
||||
|
||||
return {
|
||||
metaData,
|
||||
fileData,
|
||||
isModification,
|
||||
slug: this.slugFromContentKey(contentKey, metaData.collection),
|
||||
};
|
||||
} catch (e) {
|
||||
throw new EditorialWorkflowError('content is not under editorial workflow', true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
isUnpublishedEntryModification(path: string, branch: string) {
|
||||
isUnpublishedEntryModification(path: string) {
|
||||
return this.readFile(path, null, {
|
||||
branch,
|
||||
branch: this.branch,
|
||||
repoURL: this.originRepoURL,
|
||||
})
|
||||
.then(() => true)
|
||||
@ -635,7 +598,7 @@ export default class API {
|
||||
const newBranchName = `cms/${newContentKey}`;
|
||||
|
||||
// create new branch and pull request in new format
|
||||
const newBranch = await this.createBranch(newBranchName, (metaData.pr as PR).head);
|
||||
const newBranch = await this.createBranch(newBranchName, (metaData.pr as PR).head as string);
|
||||
const pr = await this.createPR(metaData.commitMessage, newBranchName);
|
||||
|
||||
// store new metadata
|
||||
@ -667,7 +630,7 @@ export default class API {
|
||||
return branch;
|
||||
}
|
||||
|
||||
async listUnpublishedBranches() {
|
||||
async listUnpublishedBranches(): Promise<Branch[]> {
|
||||
console.log(
|
||||
'%c Checking for Unpublished entries',
|
||||
'line-height: 30px;text-align: center;font-weight: bold',
|
||||
@ -720,8 +683,16 @@ export default class API {
|
||||
*/
|
||||
async getStatuses(sha: string) {
|
||||
try {
|
||||
const resp = await this.request(`${this.originRepoURL}/commits/${sha}/status`);
|
||||
return resp.statuses;
|
||||
const resp: { statuses: GitHubCommitStatus[] } = await this.request(
|
||||
`${this.originRepoURL}/commits/${sha}/status`,
|
||||
);
|
||||
return resp.statuses.map(s => ({
|
||||
context: s.context,
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
target_url: s.target_url,
|
||||
state:
|
||||
s.state === GitHubCommitStatusState.Success ? PreviewState.Success : PreviewState.Other,
|
||||
}));
|
||||
} catch (err) {
|
||||
if (err && err.message && err.message === 'Ref not found') {
|
||||
return [];
|
||||
@ -730,26 +701,35 @@ export default class API {
|
||||
}
|
||||
}
|
||||
|
||||
async persistFiles(entry: Entry, mediaFiles: File[], options: PersistOptions) {
|
||||
async persistFiles(entry: Entry | null, mediaFiles: AssetProxy[], options: PersistOptions) {
|
||||
const files = entry ? mediaFiles.concat(entry) : mediaFiles;
|
||||
const uploadPromises = files.map(file => this.uploadBlob(file));
|
||||
await Promise.all(uploadPromises);
|
||||
|
||||
if (!options.useWorkflow) {
|
||||
return this.getDefaultBranch()
|
||||
.then(branchData => this.updateTree(branchData.commit.sha, files))
|
||||
.then(branchData =>
|
||||
this.updateTree(branchData.commit.sha, files as { sha: string; path: string }[]),
|
||||
)
|
||||
.then(changeTree => this.commit(options.commitMessage, changeTree))
|
||||
.then(response => this.patchBranch(this.branch, response.sha));
|
||||
} else {
|
||||
const mediaFilesList = mediaFiles.map(({ sha, path }) => ({
|
||||
path: trimStart(path, '/'),
|
||||
sha,
|
||||
}));
|
||||
return this.editorialWorkflowGit(files, entry, mediaFilesList, options);
|
||||
const mediaFilesList = (mediaFiles as { sha: string; path: string }[]).map(
|
||||
({ sha, path }) => ({
|
||||
path: trimStart(path, '/'),
|
||||
sha,
|
||||
}),
|
||||
);
|
||||
return this.editorialWorkflowGit(
|
||||
files as TreeFile[],
|
||||
entry as Entry,
|
||||
mediaFilesList,
|
||||
options,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
getFileSha(path: string, branch: string) {
|
||||
getFileSha(path: string, { repoURL = this.repoURL, branch = this.branch } = {}) {
|
||||
/**
|
||||
* We need to request the tree first to get the SHA. We use extended SHA-1
|
||||
* syntax (<rev>:<path>) to get a blob from a tree without having to recurse
|
||||
@ -760,22 +740,25 @@ export default class API {
|
||||
const filename = last(pathArray);
|
||||
const directory = initial(pathArray).join('/');
|
||||
const fileDataPath = encodeURIComponent(directory);
|
||||
const fileDataURL = `${this.repoURL}/git/trees/${branch}:${fileDataPath}`;
|
||||
const fileDataURL = `${repoURL}/git/trees/${branch}:${fileDataPath}`;
|
||||
|
||||
return this.request(fileDataURL, { cache: 'no-store' }).then(resp => {
|
||||
const { sha } = resp.tree.find((file: File) => file.path === filename);
|
||||
return sha;
|
||||
return this.request(fileDataURL, { cache: 'no-store' }).then((resp: GitHubTree) => {
|
||||
const file = resp.tree.find(file => file.path === filename);
|
||||
if (file) {
|
||||
return file.sha;
|
||||
}
|
||||
throw new APIError('Not Found', 404, API_NAME);
|
||||
});
|
||||
}
|
||||
|
||||
deleteFile(path: string, message: string, options: { branch?: string } = {}) {
|
||||
deleteFile(path: string, message: string) {
|
||||
if (this.useOpenAuthoring) {
|
||||
return Promise.reject('Cannot delete published entries as an Open Authoring user!');
|
||||
}
|
||||
|
||||
const branch = options.branch || this.branch;
|
||||
const branch = this.branch;
|
||||
|
||||
return this.getFileSha(path, branch).then(sha => {
|
||||
return this.getFileSha(path, { branch }).then(sha => {
|
||||
const params: { sha: string; message: string; branch: string; author?: { date: string } } = {
|
||||
sha,
|
||||
message,
|
||||
@ -799,12 +782,12 @@ export default class API {
|
||||
}
|
||||
|
||||
async editorialWorkflowGit(
|
||||
files: File[],
|
||||
files: TreeFile[],
|
||||
entry: Entry,
|
||||
mediaFilesList: MediaFile[],
|
||||
options: PersistOptions,
|
||||
) {
|
||||
const contentKey = this.generateContentKey(options.collectionName, entry.slug);
|
||||
const contentKey = this.generateContentKey(options.collectionName as string, entry.slug);
|
||||
const branchName = this.generateBranchName(contentKey);
|
||||
const unpublished = options.unpublished || false;
|
||||
if (!unpublished) {
|
||||
@ -837,14 +820,14 @@ export default class API {
|
||||
user: user.name || user.login,
|
||||
status: options.status || this.initialWorkflowStatus,
|
||||
branch: branchName,
|
||||
collection: options.collectionName,
|
||||
collection: options.collectionName as string,
|
||||
commitMessage: options.commitMessage,
|
||||
title: options.parsedData && options.parsedData.title,
|
||||
description: options.parsedData && options.parsedData.description,
|
||||
objects: {
|
||||
entry: {
|
||||
path: entry.path,
|
||||
sha: entry.sha,
|
||||
sha: entry.sha as string,
|
||||
},
|
||||
files: mediaFilesList,
|
||||
},
|
||||
@ -871,7 +854,7 @@ export default class API {
|
||||
|
||||
const pr = metadata.pr ? { ...metadata.pr, head: commit.sha } : undefined;
|
||||
const objects = {
|
||||
entry: { path: entry.path, sha: entry.sha },
|
||||
entry: { path: entry.path, sha: entry.sha as string },
|
||||
files: mediaFilesList,
|
||||
};
|
||||
|
||||
@ -1114,7 +1097,7 @@ export default class API {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
title,
|
||||
body: API.DEFAULT_PR_BODY,
|
||||
body: DEFAULT_PR_BODY,
|
||||
head: headReference,
|
||||
base: this.branch,
|
||||
}),
|
||||
@ -1150,10 +1133,10 @@ export default class API {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
commit_message: 'Automatically generated. Merged on Netlify CMS.',
|
||||
commit_message: MERGE_COMMIT_MESSAGE,
|
||||
sha: headSha,
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
merge_method: this.merge_method,
|
||||
merge_method: this.mergeMethod,
|
||||
}),
|
||||
}).catch(error => {
|
||||
if (error instanceof APIError && error.status === 405) {
|
||||
@ -1184,7 +1167,7 @@ export default class API {
|
||||
return Promise.resolve(Base64.encode(str));
|
||||
}
|
||||
|
||||
uploadBlob(item: { raw?: string; sha?: string }) {
|
||||
uploadBlob(item: { raw?: string; sha?: string; toBase64?: () => Promise<string> }) {
|
||||
const content = result(item, 'toBase64', partial(this.toBase64, item.raw as string));
|
||||
|
||||
return content.then(contentBase64 =>
|
||||
|
Reference in New Issue
Block a user