Skip to content

Commit 27d3a69

Browse files
authored
Merge pull request #1160 from ckeditor/ck/18670
Added validation for a username provided in the community contributors section.
2 parents ccf6a79 + 4d6c065 commit 27d3a69

File tree

4 files changed

+103
-4
lines changed

4 files changed

+103
-4
lines changed

.changelog/20250618122716_ck_18670.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
---
2+
# Required: Type of change.
3+
# Allowed values:
4+
# - Feature
5+
# - Fix
6+
# - Other
7+
# - Major breaking change
8+
# - Minor breaking change
9+
#
10+
# For guidance on breaking changes, see:
11+
# https://ckeditor.com/docs/ckeditor5/latest/updating/versioning-policy.html#major-and-minor-breaking-changes
12+
type: Fix
13+
14+
# Optional: Affected package(s), using short names.
15+
# Can be skipped when processing a non-mono-repository.
16+
# Example: ckeditor5-core
17+
scope:
18+
- ckeditor5-dev-changelog
19+
20+
# Optional: Issues this change closes.
21+
# Format:
22+
# - {issue-number}
23+
# - {repo-owner}/{repo-name}#{issue-number}
24+
# - Full GitHub URL
25+
closes:
26+
- ckeditor/ckeditor5#18670
27+
28+
# Optional: Related issues.
29+
# Format:
30+
# - {issue-number}
31+
# - {repo-owner}/{repo-name}#{issue-number}
32+
# - Full GitHub URL
33+
see:
34+
-
35+
36+
# Optional: Community contributors.
37+
# Format:
38+
# - {github-username}
39+
communityCredits:
40+
-
41+
---
42+
43+
Added validation for a username provided in the community contributors section.

packages/ckeditor5-dev-changelog/src/utils/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ export const ISSUE_PATTERN = /^\d+$/;
4545
export const ISSUE_URL_PATTERN =
4646
/^(?<base>https:\/\/github\.com)\/(?<owner>[a-z0-9.-]+)\/(?<repository>[a-z0-9.-]+)\/issues\/(?<number>\d+)$/;
4747

48+
export const NICK_NAME_PATTERN = /^@[a-z0-9-_]+$/i;
49+
4850
export const TYPES = [
4951
{ name: 'Feature' },
5052
{ name: 'Other' },

packages/ckeditor5-dev-changelog/src/utils/validateentry.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55

66
import type { ParsedFile } from '../types.js';
7-
import { ISSUE_PATTERN, ISSUE_SLUG_PATTERN, ISSUE_URL_PATTERN, TYPES } from './constants.js';
7+
import { ISSUE_PATTERN, ISSUE_SLUG_PATTERN, ISSUE_URL_PATTERN, NICK_NAME_PATTERN, TYPES } from './constants.js';
88

99
/**
1010
* Validates a changelog entry against expected types, scopes, and issue references.
@@ -100,6 +100,18 @@ export function validateEntry( entry: ParsedFile, packagesNames: Array<string>,
100100

101101
data.closes = closesValidated;
102102

103+
const communityCreditsValidated = [];
104+
105+
for ( const nickName of data.communityCredits ) {
106+
if ( !nickName.match( NICK_NAME_PATTERN ) ) {
107+
validations.push( `Community username "${ nickName }" is not valid GitHub username.` );
108+
} else {
109+
communityCreditsValidated.push( nickName );
110+
}
111+
}
112+
113+
data.communityCredits = communityCreditsValidated;
114+
103115
const validatedEntry = {
104116
...entry,
105117
data: {

packages/ckeditor5-dev-changelog/tests/utils/validateentry.ts

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ vi.mock( '../../src/utils/constants.js', () => {
1212
ISSUE_PATTERN: /^\d+$/,
1313
ISSUE_SLUG_PATTERN: /^(?<owner>[a-z0-9.-]+)\/(?<repository>[a-z0-9.-]+)#(?<number>\d+)$/,
1414
ISSUE_URL_PATTERN: /^(?<base>https:\/\/github\.com)\/(?<owner>[a-z0-9.-]+)\/(?<repository>[a-z0-9.-]+)\/issues\/(?<number>\d+)$/,
15+
NICK_NAME_PATTERN: /^@[a-z0-9-_]+$/i,
1516
TYPES: [
1617
{ name: 'Feature' },
1718
{ name: 'Other' },
@@ -317,30 +318,71 @@ describe( 'validateEntry()', () => {
317318
} );
318319
} );
319320

321+
describe( 'communityCredits validation', () => {
322+
it( 'should add validation message but remain valid when community username is not valid GitHub username', () => {
323+
const entry: ParsedFile = createEntry( { type: 'Feature', communityCredits: [ '@i n v a l i d n a m e' ] } );
324+
325+
const { isValid, validatedEntry } = validateEntry( entry, packageNames, false );
326+
327+
expect( isValid ).toBeTruthy();
328+
expect( validatedEntry.data.validations ).toContain(
329+
'Community username "@i n v a l i d n a m e" is not valid GitHub username.'
330+
);
331+
expect( validatedEntry.data.communityCredits ).toEqual( [] );
332+
} );
333+
334+
it( 'should return valid when community username is valid GitHub username', () => {
335+
const entry: ParsedFile = createEntry( { type: 'Feature', communityCredits: [ '@exampleName123' ] } );
336+
337+
const { isValid, validatedEntry } = validateEntry( entry, packageNames, false );
338+
339+
expect( isValid ).toBeTruthy();
340+
expect( validatedEntry.data.communityCredits ).toEqual( [ '@exampleName123' ] );
341+
} );
342+
343+
it( 'should filter out invalid community usernames while keeping valid ones', () => {
344+
const entry: ParsedFile = createEntry( {
345+
type: 'Feature',
346+
communityCredits: [ '@i n v a l i d n a m e', '@exampleName123' ]
347+
} );
348+
349+
const { isValid, validatedEntry } = validateEntry( entry, packageNames, false );
350+
351+
expect( isValid ).toBeTruthy();
352+
expect( validatedEntry.data.validations ).toContain(
353+
'Community username "@i n v a l i d n a m e" is not valid GitHub username.'
354+
);
355+
expect( validatedEntry.data.communityCredits ).toEqual( [ '@exampleName123' ] );
356+
} );
357+
} );
358+
320359
describe( 'multiple validations', () => {
321360
it( 'should collect multiple validation errors but only mark as invalid for critical errors', () => {
322361
const entry: ParsedFile = createEntry( {
323362
type: 'Unknown',
324363
scope: [ 'unknown-package' ],
325364
see: [ 'invalid-reference' ],
326-
closes: [ 'invalid-reference' ]
365+
closes: [ 'invalid-reference' ],
366+
communityCredits: [ '@i n v a l i d n a m e' ]
327367
} );
328368

329369
const { isValid, validatedEntry } = validateEntry( entry, packageNames, false );
330370

331371
expect( isValid ).toBeFalsy();
332-
expect( validatedEntry.data.validations?.length ).toBe( 4 );
372+
expect( validatedEntry.data.validations?.length ).toBe( 5 );
333373
expect( validatedEntry.data.scope ).toEqual( [] );
334374
expect( validatedEntry.data.see ).toEqual( [] );
335375
expect( validatedEntry.data.closes ).toEqual( [] );
376+
expect( validatedEntry.data.communityCredits ).toEqual( [] );
336377
} );
337378

338379
it( 'should return valid for a completely valid entry', () => {
339380
const entry: ParsedFile = createEntry( {
340381
type: 'Feature',
341382
scope: [ 'ckeditor5-engine' ],
342383
see: [ '1234' ],
343-
closes: [ 'ckeditor/ckeditor5#5678' ]
384+
closes: [ 'ckeditor/ckeditor5#5678' ],
385+
communityCredits: [ '@exampleName123' ]
344386
} );
345387

346388
const { isValid, validatedEntry } = validateEntry( entry, packageNames, false );

0 commit comments

Comments
 (0)