feat: allow setting editorial workflow PR label prefix (#4181)

This commit is contained in:
Kancer (Nilay) Gökırmak 2020-09-06 20:13:46 +02:00 committed by GitHub
parent c0fc423040
commit 6b8fa3fc45
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 170 additions and 44 deletions

View File

@ -41,6 +41,7 @@ interface Config {
hasWriteAccess?: () => Promise<boolean>;
squashMerges: boolean;
initialWorkflowStatus: string;
cmsLabelPrefix: string;
}
interface CommitAuthor {
@ -203,6 +204,7 @@ export default class API {
commitAuthor?: CommitAuthor;
mergeStrategy: string;
initialWorkflowStatus: string;
cmsLabelPrefix: string;
constructor(config: Config) {
this.apiRoot = config.apiRoot || 'https://api.bitbucket.org/2.0';
@ -214,6 +216,7 @@ export default class API {
this.repoURL = this.repo ? `/repositories/${this.repo}` : '';
this.mergeStrategy = config.squashMerges ? 'squash' : 'merge_commit';
this.initialWorkflowStatus = config.initialWorkflowStatus;
this.cmsLabelPrefix = config.cmsLabelPrefix;
}
buildRequest = (req: ApiRequest) => {
@ -554,7 +557,7 @@ export default class API {
}),
});
// use comments for status labels
await this.addPullRequestComment(pullRequest, statusToLabel(status));
await this.addPullRequestComment(pullRequest, statusToLabel(status, this.cmsLabelPrefix));
}
async getDifferences(source: string, destination: string = this.branch) {
@ -656,7 +659,7 @@ export default class API {
pullRequests.values.map(pr => this.getPullRequestLabel(pr.id)),
);
return pullRequests.values.filter((_, index) => isCMSLabel(labels[index]));
return pullRequests.values.filter((_, index) => isCMSLabel(labels[index], this.cmsLabelPrefix));
}
async getBranchPullRequest(branch: string) {
@ -686,7 +689,7 @@ export default class API {
const pullRequest = await this.getBranchPullRequest(branch);
const diffs = await this.getDifferences(branch);
const label = await this.getPullRequestLabel(pullRequest.id);
const status = labelToStatus(label);
const status = labelToStatus(label, this.cmsLabelPrefix);
const updatedAt = pullRequest.updated_on;
return {
collection,
@ -705,7 +708,7 @@ export default class API {
const branch = branchFromContentKey(contentKey);
const pullRequest = await this.getBranchPullRequest(branch);
await this.addPullRequestComment(pullRequest, statusToLabel(newStatus));
await this.addPullRequestComment(pullRequest, statusToLabel(newStatus, this.cmsLabelPrefix));
}
async mergePullRequest(pullRequest: BitBucketPullRequest) {

View File

@ -78,6 +78,7 @@ export default class BitbucketBackend implements Implementation {
authenticator?: NetlifyAuthenticator;
_mediaDisplayURLSem?: Semaphore;
squashMerges: boolean;
cmsLabelPrefix: string;
previewContext: string;
largeMediaURL: string;
_largeMediaClientPromise?: Promise<GitLfsClient>;
@ -113,6 +114,7 @@ export default class BitbucketBackend implements Implementation {
this.token = '';
this.mediaFolder = config.media_folder;
this.squashMerges = config.backend.squash_merges || false;
this.cmsLabelPrefix = config.backend.cms_label_prefix || '';
this.previewContext = config.backend.preview_context || '';
this.lock = asyncLock();
this.authType = config.backend.auth_type || '';
@ -166,6 +168,7 @@ export default class BitbucketBackend implements Implementation {
branch: this.branch,
repo: this.repo,
squashMerges: this.squashMerges,
cmsLabelPrefix: this.cmsLabelPrefix,
initialWorkflowStatus: this.options.initialWorkflowStatus,
});
}
@ -189,6 +192,7 @@ export default class BitbucketBackend implements Implementation {
repo: this.repo,
apiRoot: this.apiRoot,
squashMerges: this.squashMerges,
cmsLabelPrefix: this.cmsLabelPrefix,
initialWorkflowStatus: this.options.initialWorkflowStatus,
});

View File

@ -132,6 +132,7 @@ export default class GitGateway implements Implementation {
api?: GitHubAPI | GitLabAPI | BitBucketAPI;
branch: string;
squashMerges: boolean;
cmsLabelPrefix: string;
mediaFolder: string;
transformImages: boolean;
gatewayUrl: string;
@ -159,6 +160,7 @@ export default class GitGateway implements Implementation {
this.config = config;
this.branch = config.backend.branch?.trim() || 'master';
this.squashMerges = config.backend.squash_merges || false;
this.cmsLabelPrefix = config.backend.cms_label_prefix || '';
this.mediaFolder = config.media_folder;
this.transformImages = config.backend.use_large_media_transforms_in_media_library || true;
@ -332,6 +334,7 @@ export default class GitGateway implements Implementation {
tokenPromise: this.tokenPromise!,
commitAuthor: pick(userData, ['name', 'email']),
squashMerges: this.squashMerges,
cmsLabelPrefix: this.cmsLabelPrefix,
initialWorkflowStatus: this.options.initialWorkflowStatus,
};

View File

@ -52,6 +52,7 @@ export interface Config {
originRepo?: string;
squashMerges: boolean;
initialWorkflowStatus: string;
cmsLabelPrefix: string;
}
interface TreeFile {
@ -132,8 +133,10 @@ type MediaFile = {
path: string;
};
const withCmsLabel = (pr: GitHubPull) => pr.labels.some(l => isCMSLabel(l.name));
const withoutCmsLabel = (pr: GitHubPull) => pr.labels.every(l => !isCMSLabel(l.name));
const withCmsLabel = (pr: GitHubPull, cmsLabelPrefix: string) =>
pr.labels.some(l => isCMSLabel(l.name, cmsLabelPrefix));
const withoutCmsLabel = (pr: GitHubPull, cmsLabelPrefix: string) =>
pr.labels.every(l => !isCMSLabel(l.name, cmsLabelPrefix));
const getTreeFiles = (files: GitHubCompareFiles) => {
const treeFiles = files.reduce((arr, file) => {
@ -190,6 +193,7 @@ export default class API {
originRepoURL: string;
mergeMethod: string;
initialWorkflowStatus: string;
cmsLabelPrefix: string;
_userPromise?: Promise<GitHubUser>;
_metadataSemaphore?: Semaphore;
@ -215,6 +219,7 @@ export default class API {
this.originRepoName = originRepoParts[1];
this.mergeMethod = config.squashMerges ? 'squash' : 'merge';
this.cmsLabelPrefix = config.cmsLabelPrefix;
this.initialWorkflowStatus = config.initialWorkflowStatus;
}
@ -527,18 +532,18 @@ export default class API {
return {
head: { sha: data.commit.sha },
number: MOCK_PULL_REQUEST,
labels: [{ name: statusToLabel(this.initialWorkflowStatus) }],
labels: [{ name: statusToLabel(this.initialWorkflowStatus, this.cmsLabelPrefix) }],
state: PullRequestState.Open,
} as GitHubPull;
} catch (e) {
throw new EditorialWorkflowError('content is not under editorial workflow', true);
}
} else {
pullRequest.labels = pullRequest.labels.filter(l => !isCMSLabel(l.name));
pullRequest.labels = pullRequest.labels.filter(l => !isCMSLabel(l.name, this.cmsLabelPrefix));
const cmsLabel =
pullRequest.state === PullRequestState.Closed
? { name: statusToLabel(this.initialWorkflowStatus) }
: { name: statusToLabel('pending_review') };
? { name: statusToLabel(this.initialWorkflowStatus, this.cmsLabelPrefix) }
: { name: statusToLabel('pending_review', this.cmsLabelPrefix) };
pullRequest.labels.push(cmsLabel as Octokit.PullsGetResponseLabelsItem);
return pullRequest;
@ -550,7 +555,9 @@ export default class API {
const pullRequests = await this.getPullRequests(branch, PullRequestState.All, () => true);
return this.getOpenAuthoringPullRequest(branch, pullRequests);
} else {
const pullRequests = await this.getPullRequests(branch, PullRequestState.Open, withCmsLabel);
const pullRequests = await this.getPullRequests(branch, PullRequestState.Open, pr =>
withCmsLabel(pr, this.cmsLabelPrefix),
);
if (pullRequests.length <= 0) {
throw new EditorialWorkflowError('content is not under editorial workflow', true);
}
@ -579,8 +586,10 @@ export default class API {
const pullRequest = await this.getBranchPullRequest(branch);
const { files } = await this.getDifferences(this.branch, pullRequest.head.sha);
const diffs = files.map(diffFromFile);
const label = pullRequest.labels.find(l => isCMSLabel(l.name)) as { name: string };
const status = labelToStatus(label.name);
const label = pullRequest.labels.find(l => isCMSLabel(l.name, this.cmsLabelPrefix)) as {
name: string;
};
const status = labelToStatus(label.name, this.cmsLabelPrefix);
const updatedAt = pullRequest.updated_at;
return {
collection,
@ -823,7 +832,7 @@ export default class API {
const pullRequests = await this.getPullRequests(
undefined,
PullRequestState.Open,
pr => !pr.head.repo.fork && withoutCmsLabel(pr),
pr => !pr.head.repo.fork && withoutCmsLabel(pr, this.cmsLabelPrefix),
);
let prCount = 0;
for (const pr of pullRequests) {
@ -838,10 +847,8 @@ export default class API {
prCount = prCount + 1;
await this.migratePullRequest(pr, `${prCount} of ${pullRequests.length}`);
}
const cmsPullRequests = await this.getPullRequests(
undefined,
PullRequestState.Open,
withCmsLabel,
const cmsPullRequests = await this.getPullRequests(undefined, PullRequestState.Open, pr =>
withCmsLabel(pr, this.cmsLabelPrefix),
);
branches = cmsPullRequests.map(pr => pr.head.ref);
}
@ -1098,8 +1105,10 @@ export default class API {
async setPullRequestStatus(pullRequest: GitHubPull, newStatus: string) {
const labels = [
...pullRequest.labels.filter(label => !isCMSLabel(label.name)).map(l => l.name),
statusToLabel(newStatus),
...pullRequest.labels
.filter(label => !isCMSLabel(label.name, this.cmsLabelPrefix))
.map(l => l.name),
statusToLabel(newStatus, this.cmsLabelPrefix),
];
await this.updatePullRequestLabels(pullRequest.number, labels);
}

View File

@ -51,6 +51,40 @@ describe('github API', () => {
})),
).resolves.toEqual({ prBaseBranch: 'gh-pages', labels: ['netlify-cms/draft'] });
});
it('should create PR with correct base branch name with custom prefix when publishing with editorial workflow', () => {
let prBaseBranch = null;
let labels = null;
const api = new API({
branch: 'gh-pages',
repo: 'owner/my-repo',
initialWorkflowStatus: 'draft',
cmsLabelPrefix: 'other/',
});
const responses = {
'/repos/owner/my-repo/branches/gh-pages': () => ({ commit: { sha: 'def' } }),
'/repos/owner/my-repo/git/trees/def': () => ({ tree: [] }),
'/repos/owner/my-repo/git/trees': () => ({}),
'/repos/owner/my-repo/git/commits': () => ({}),
'/repos/owner/my-repo/git/refs': () => ({}),
'/repos/owner/my-repo/pulls': req => {
prBaseBranch = JSON.parse(req.body).base;
return { head: { sha: 'cbd' }, labels: [], number: 1 };
},
'/repos/owner/my-repo/issues/1/labels': req => {
labels = JSON.parse(req.body).labels;
return {};
},
};
mockAPI(api, responses);
return expect(
api.editorialWorkflowGit([], { slug: 'entry', sha: 'abc' }, null, {}).then(() => ({
prBaseBranch,
labels,
})),
).resolves.toEqual({ prBaseBranch: 'gh-pages', labels: ['other/draft'] });
});
});
describe('updateTree', () => {

View File

@ -72,6 +72,7 @@ export default class GitHub implements Implementation {
previewContext: string;
token: string | null;
squashMerges: boolean;
cmsLabelPrefix: string;
useGraphql: boolean;
_currentUserPromise?: Promise<GitHubUser>;
_userIsOriginMaintainerPromises?: {
@ -111,6 +112,7 @@ export default class GitHub implements Implementation {
this.apiRoot = config.backend.api_root || 'https://api.github.com';
this.token = '';
this.squashMerges = config.backend.squash_merges || false;
this.cmsLabelPrefix = config.backend.cms_label_prefix || '';
this.useGraphql = config.backend.use_graphql || false;
this.mediaFolder = config.media_folder;
this.previewContext = config.backend.preview_context || '';
@ -297,6 +299,7 @@ export default class GitHub implements Implementation {
originRepo: this.originRepo,
apiRoot: this.apiRoot,
squashMerges: this.squashMerges,
cmsLabelPrefix: this.cmsLabelPrefix,
useOpenAuthoring: this.useOpenAuthoring,
initialWorkflowStatus: this.options.initialWorkflowStatus,
});

View File

@ -41,6 +41,7 @@ export interface Config {
repo?: string;
squashMerges: boolean;
initialWorkflowStatus: string;
cmsLabelPrefix: string;
}
export interface CommitAuthor {
@ -189,6 +190,7 @@ export default class API {
commitAuthor?: CommitAuthor;
squashMerges: boolean;
initialWorkflowStatus: string;
cmsLabelPrefix: string;
constructor(config: Config) {
this.apiRoot = config.apiRoot || 'https://gitlab.com/api/v4';
@ -198,6 +200,7 @@ export default class API {
this.repoURL = `/projects/${encodeURIComponent(this.repo)}`;
this.squashMerges = config.squashMerges;
this.initialWorkflowStatus = config.initialWorkflowStatus;
this.cmsLabelPrefix = config.cmsLabelPrefix;
}
withAuthorizationHeaders = (req: ApiRequest) => {
@ -557,7 +560,9 @@ export default class API {
});
return mergeRequests.filter(
mr => mr.source_branch.startsWith(CMS_BRANCH_PREFIX) && mr.labels.some(isCMSLabel),
mr =>
mr.source_branch.startsWith(CMS_BRANCH_PREFIX) &&
mr.labels.some(l => isCMSLabel(l, this.cmsLabelPrefix)),
);
}
@ -658,8 +663,8 @@ export default class API {
return { id, path, newFile };
}),
);
const label = mergeRequest.labels.find(isCMSLabel) as string;
const status = labelToStatus(label);
const label = mergeRequest.labels.find(l => isCMSLabel(l, this.cmsLabelPrefix)) as string;
const status = labelToStatus(label, this.cmsLabelPrefix);
const updatedAt = mergeRequest.updated_at;
return {
collection,
@ -710,7 +715,7 @@ export default class API {
target_branch: this.branch,
title: commitMessage,
description: DEFAULT_PR_BODY,
labels: statusToLabel(status),
labels: statusToLabel(status, this.cmsLabelPrefix),
// eslint-disable-next-line @typescript-eslint/camelcase
remove_source_branch: true,
squash: this.squashMerges,
@ -771,8 +776,8 @@ export default class API {
const mergeRequest = await this.getBranchMergeRequest(branch);
const labels = [
...mergeRequest.labels.filter(label => !isCMSLabel(label)),
statusToLabel(newStatus),
...mergeRequest.labels.filter(label => !isCMSLabel(label, this.cmsLabelPrefix)),
statusToLabel(newStatus, this.cmsLabelPrefix),
];
await this.updateMergeRequestLabels(mergeRequest, labels);
}

View File

@ -52,6 +52,7 @@ export default class GitLab implements Implementation {
apiRoot: string;
token: string | null;
squashMerges: boolean;
cmsLabelPrefix: string;
mediaFolder: string;
previewContext: string;
@ -79,6 +80,7 @@ export default class GitLab implements Implementation {
this.apiRoot = config.backend.api_root || 'https://gitlab.com/api/v4';
this.token = '';
this.squashMerges = config.backend.squash_merges || false;
this.cmsLabelPrefix = config.backend.cms_label_prefix || '';
this.mediaFolder = config.media_folder;
this.previewContext = config.backend.preview_context || '';
this.lock = asyncLock();
@ -117,6 +119,7 @@ export default class GitLab implements Implementation {
repo: this.repo,
apiRoot: this.apiRoot,
squashMerges: this.squashMerges,
cmsLabelPrefix: this.cmsLabelPrefix,
initialWorkflowStatus: this.options.initialWorkflowStatus,
});
const user = await this.api.user();

View File

@ -50,6 +50,7 @@ export default class ProxyBackend implements Implementation {
mediaFolder: string;
options: { initialWorkflowStatus?: string };
branch: string;
cmsLabelPrefix?: string;
constructor(config: Config, options = {}) {
if (!config.backend.proxy_url) {
@ -60,6 +61,7 @@ export default class ProxyBackend implements Implementation {
this.proxyUrl = config.backend.proxy_url;
this.mediaFolder = config.media_folder;
this.options = options;
this.cmsLabelPrefix = config.backend.cms_label_prefix;
}
isGitBackend() {
@ -146,7 +148,7 @@ export default class ProxyBackend implements Implementation {
try {
const entry: UnpublishedEntry = await this.request({
action: 'unpublishedEntry',
params: { branch: this.branch, id, collection, slug },
params: { branch: this.branch, id, collection, slug, cmsLabelPrefix: this.cmsLabelPrefix },
});
return entry;
@ -190,6 +192,7 @@ export default class ProxyBackend implements Implementation {
entry,
assets,
options: { ...options, status: options.status || this.options.initialWorkflowStatus },
cmsLabelPrefix: this.cmsLabelPrefix,
},
});
}
@ -197,7 +200,13 @@ export default class ProxyBackend implements Implementation {
updateUnpublishedEntryStatus(collection: string, slug: string, newStatus: string) {
return this.request({
action: 'updateUnpublishedEntryStatus',
params: { branch: this.branch, collection, slug, newStatus },
params: {
branch: this.branch,
collection,
slug,
newStatus,
cmsLabelPrefix: this.cmsLabelPrefix,
},
});
}

View File

@ -213,6 +213,7 @@ declare module 'netlify-cms-core' {
site_domain?: string;
base_url?: string;
auth_endpoint?: string;
cms_label_prefix?: string;
}
export interface CmsSlug {

View File

@ -78,6 +78,7 @@ const getConfigSchema = () => ({
examples: ['repo', 'public_repo'],
enum: ['repo', 'public_repo'],
},
cms_label_prefix: { type: 'string', minLength: 1 },
open_authoring: { type: 'boolean', examples: [true] },
},
required: ['name'],

View File

@ -2,10 +2,15 @@ export const CMS_BRANCH_PREFIX = 'cms';
export const DEFAULT_PR_BODY = 'Automatically generated by Netlify CMS';
export const MERGE_COMMIT_MESSAGE = 'Automatically generated. Merged on Netlify CMS.';
const NETLIFY_CMS_LABEL_PREFIX = 'netlify-cms/';
export const isCMSLabel = (label: string) => label.startsWith(NETLIFY_CMS_LABEL_PREFIX);
export const labelToStatus = (label: string) => label.substr(NETLIFY_CMS_LABEL_PREFIX.length);
export const statusToLabel = (status: string) => `${NETLIFY_CMS_LABEL_PREFIX}${status}`;
const DEFAULT_NETLIFY_CMS_LABEL_PREFIX = 'netlify-cms/';
const getLabelPrefix = (labelPrefix: string) => labelPrefix || DEFAULT_NETLIFY_CMS_LABEL_PREFIX;
export const isCMSLabel = (label: string, labelPrefix: string) =>
label.startsWith(getLabelPrefix(labelPrefix));
export const labelToStatus = (label: string, labelPrefix: string) =>
label.substr(getLabelPrefix(labelPrefix).length);
export const statusToLabel = (status: string, labelPrefix: string) =>
`${getLabelPrefix(labelPrefix)}${status}`;
export const generateContentKey = (collectionName: string, slug: string) =>
`${collectionName}/${slug}`;

View File

@ -19,23 +19,56 @@ describe('APIUtils', () => {
describe('isCMSLabel', () => {
it('should return true for CMS label', () => {
expect(apiUtils.isCMSLabel('netlify-cms/draft')).toBe(true);
expect(apiUtils.isCMSLabel('netlify-cms/draft', 'netlify-cms/')).toBe(true);
});
it('should return false for non CMS label', () => {
expect(apiUtils.isCMSLabel('other/label')).toBe(false);
expect(apiUtils.isCMSLabel('other/label', 'netlify-cms/')).toBe(false);
});
it('should return true if the prefix not provided for CMS label', () => {
expect(apiUtils.isCMSLabel('netlify-cms/draft', '')).toBe(true);
});
it('should return false if a different prefix provided for CMS label', () => {
expect(apiUtils.isCMSLabel('netlify-cms/draft', 'other/')).toBe(false);
});
it('should return true for CMS label when undefined prefix is passed', () => {
expect(apiUtils.isCMSLabel('netlify-cms/draft', undefined)).toBe(true);
});
});
describe('labelToStatus', () => {
it('should get status from label', () => {
expect(apiUtils.labelToStatus('netlify-cms/draft')).toBe('draft');
it('should get status from label when default prefix is passed', () => {
expect(apiUtils.labelToStatus('netlify-cms/draft', 'netlify-cms/')).toBe('draft');
});
it('should get status from label when custom prefix is passed', () => {
expect(apiUtils.labelToStatus('other/draft', 'other/')).toBe('draft');
});
it('should get status from label when empty prefix is passed', () => {
expect(apiUtils.labelToStatus('netlify-cms/draft', '')).toBe('draft');
});
it('should get status from label when undefined prefix is passed', () => {
expect(apiUtils.labelToStatus('netlify-cms/draft', undefined)).toBe('draft');
});
});
describe('statusToLabel', () => {
it('should generate label from status', () => {
expect(apiUtils.statusToLabel('draft')).toBe('netlify-cms/draft');
it('should generate label from status when default prefix is passed', () => {
expect(apiUtils.statusToLabel('draft', 'netlify-cms/')).toBe('netlify-cms/draft');
});
it('should generate label from status when custom prefix is passed', () => {
expect(apiUtils.statusToLabel('draft', 'other/')).toBe('other/draft');
});
it('should generate label from status when empty prefix is passed', () => {
expect(apiUtils.statusToLabel('draft', '')).toBe('netlify-cms/draft');
});
it('should generate label from status when undefined prefix is passed', () => {
expect(apiUtils.statusToLabel('draft', undefined)).toBe('netlify-cms/draft');
});
});
});

View File

@ -89,6 +89,7 @@ export type Config = {
proxy_url?: string;
auth_type?: string;
app_id?: string;
cms_label_prefix?: string;
};
media_folder: string;
base_url?: string;

View File

@ -82,6 +82,7 @@ export const defaultSchema = ({ path = requiredString } = {}) => {
id: Joi.string().optional(),
collection: Joi.string().optional(),
slug: Joi.string().optional(),
cmsLabelPrefix: Joi.string().optional(),
})
.required(),
},
@ -120,6 +121,7 @@ export const defaultSchema = ({ path = requiredString } = {}) => {
is: 'persistEntry',
then: defaultParams
.keys({
cmsLabelPrefix: Joi.string().optional(),
entry: Joi.object({
slug: requiredString,
path,
@ -145,6 +147,7 @@ export const defaultSchema = ({ path = requiredString } = {}) => {
collection,
slug,
newStatus: requiredString,
cmsLabelPrefix: Joi.string().optional(),
})
.required(),
},

View File

@ -222,7 +222,7 @@ export const localGitMiddleware = ({ repoPath, logger }: GitOptions) => {
break;
}
case 'unpublishedEntry': {
let { id, collection, slug } = body.params as UnpublishedEntryParams;
let { id, collection, slug, cmsLabelPrefix } = body.params as UnpublishedEntryParams;
if (id) {
({ collection, slug } = parseContentKey(id));
}
@ -232,7 +232,7 @@ export const localGitMiddleware = ({ repoPath, logger }: GitOptions) => {
if (branchExists) {
const diffs = await getDiffs(git, branch, cmsBranch);
const label = await git.raw(['config', branchDescription(cmsBranch)]);
const status = label && labelToStatus(label.trim());
const status = label && labelToStatus(label.trim(), cmsLabelPrefix || '');
const unpublishedEntry = {
collection,
slug,
@ -276,7 +276,7 @@ export const localGitMiddleware = ({ repoPath, logger }: GitOptions) => {
break;
}
case 'persistEntry': {
const { entry, assets, options } = body.params as PersistEntryParams;
const { entry, assets, options, cmsLabelPrefix } = body.params as PersistEntryParams;
if (!options.useWorkflow) {
await runOnBranch(git, branch, async () => {
await commitEntry(git, repoPath, entry, assets, options.commitMessage);
@ -304,7 +304,7 @@ export const localGitMiddleware = ({ repoPath, logger }: GitOptions) => {
// add status for new entries
if (!branchExists) {
const description = statusToLabel(options.status);
const description = statusToLabel(options.status, cmsLabelPrefix || '');
await git.addConfig(branchDescription(cmsBranch), description);
}
});
@ -313,10 +313,15 @@ export const localGitMiddleware = ({ repoPath, logger }: GitOptions) => {
break;
}
case 'updateUnpublishedEntryStatus': {
const { collection, slug, newStatus } = body.params as UpdateUnpublishedEntryStatusParams;
const {
collection,
slug,
newStatus,
cmsLabelPrefix,
} = body.params as UpdateUnpublishedEntryStatusParams;
const contentKey = generateContentKey(collection, slug);
const cmsBranch = branchFromContentKey(contentKey);
const description = statusToLabel(newStatus);
const description = statusToLabel(newStatus, cmsLabelPrefix || '');
await git.addConfig(branchDescription(cmsBranch), description);
res.json({ message: `${branch} description was updated to ${description}` });
break;

View File

@ -20,6 +20,7 @@ export type UnpublishedEntryParams = {
id?: string;
collection?: string;
slug?: string;
cmsLabelPrefix?: string;
};
export type UnpublishedEntryDataFileParams = {
@ -45,6 +46,7 @@ export type UpdateUnpublishedEntryStatusParams = {
collection: string;
slug: string;
newStatus: string;
cmsLabelPrefix?: string;
};
export type PublishUnpublishedEntryParams = {
@ -57,6 +59,7 @@ export type Entry = { slug: string; path: string; raw: string; newPath?: string
export type Asset = { path: string; content: string; encoding: 'base64' };
export type PersistEntryParams = {
cmsLabelPrefix?: string;
entry: Entry;
assets: Asset[];
options: {

View File

@ -18,6 +18,7 @@ Individual backends should provide their own configuration documentation, but th
| `site_domain` | `location.hostname` (or `cms.netlify.com` when on `localhost`) | Sets the `site_id` query param sent to the API endpoint. Non-Netlify auth setups will often need to set this for local development to work properly. |
| `base_url` | `https://api.netlify.com` (GitHub, Bitbucket) or `https://gitlab.com` (GitLab) | OAuth client hostname (just the base domain, no path). **Required** when using an external OAuth server or self-hosted GitLab. |
| `auth_endpoint` | `auth` (GitHub, Bitbucket) or `oauth/authorize` (GitLab) | Path to append to `base_url` for authentication requests. Optional. |
| `cms_label_prefix` | `netlify-cms/` | Pull (or Merge) Requests label prefix when using editorial workflow. Optional. |
## Creating a New Backend