feat: allow setting editorial workflow PR label prefix (#4181)
This commit is contained in:
parent
c0fc423040
commit
6b8fa3fc45
@ -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) {
|
||||
|
@ -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,
|
||||
});
|
||||
|
||||
|
@ -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,
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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', () => {
|
||||
|
@ -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,
|
||||
});
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
1
packages/netlify-cms-core/index.d.ts
vendored
1
packages/netlify-cms-core/index.d.ts
vendored
@ -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 {
|
||||
|
@ -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'],
|
||||
|
@ -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}`;
|
||||
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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;
|
||||
|
@ -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(),
|
||||
},
|
||||
|
@ -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;
|
||||
|
@ -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: {
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user