@doneisbetter/gds-compliance 2.6.7 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +70 -0
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -15,6 +15,9 @@ const STRICT_COMPLIANCE_FIELDS = [
|
|
|
15
15
|
'approvedDetailPrimitives',
|
|
16
16
|
'approvedListingPrimitives',
|
|
17
17
|
'approvedActionPrimitives',
|
|
18
|
+
'approvedMediaPrimitives',
|
|
19
|
+
'approvedReportingPrimitives',
|
|
20
|
+
'approvedAccessPrimitives',
|
|
18
21
|
'approvedTemporaryExceptions',
|
|
19
22
|
'approvedThemeLanes',
|
|
20
23
|
'themeOwnershipPaths',
|
|
@@ -391,6 +394,9 @@ function inferStrictSurface(contract) {
|
|
|
391
394
|
if (normalized.includes('detail') || normalized.includes('profile')) return 'detail';
|
|
392
395
|
if (normalized.includes('card') || normalized.includes('listing')) return 'listing';
|
|
393
396
|
if (normalized.includes('action') || normalized.includes('button')) return 'action';
|
|
397
|
+
if (normalized.includes('media') || normalized.includes('upload') || normalized.includes('asset')) return 'media';
|
|
398
|
+
if (normalized.includes('report') || normalized.includes('chart') || normalized.includes('evidence') || normalized.includes('metric')) return 'reporting';
|
|
399
|
+
if (normalized.includes('auth') || normalized.includes('access') || normalized.includes('identity') || normalized.includes('login')) return 'access';
|
|
394
400
|
return null;
|
|
395
401
|
}
|
|
396
402
|
|
|
@@ -402,6 +408,9 @@ function runStrictCompliance({ manifest, manifestRoot, sourceFiles }) {
|
|
|
402
408
|
detail: new Set(strict.approvedDetailPrimitives ?? []),
|
|
403
409
|
listing: new Set(strict.approvedListingPrimitives ?? []),
|
|
404
410
|
action: new Set(strict.approvedActionPrimitives ?? []),
|
|
411
|
+
media: new Set(strict.approvedMediaPrimitives ?? []),
|
|
412
|
+
reporting: new Set(strict.approvedReportingPrimitives ?? []),
|
|
413
|
+
access: new Set(strict.approvedAccessPrimitives ?? []),
|
|
405
414
|
};
|
|
406
415
|
const approvedTemporaryExceptions = new Set(strict.approvedTemporaryExceptions ?? []);
|
|
407
416
|
|
|
@@ -449,6 +458,50 @@ function runStrictCompliance({ manifest, manifestRoot, sourceFiles }) {
|
|
|
449
458
|
message: 'Strict mode forbids local button/action wrapper implementations. Use the canonical GDS ActionBar and semantic actions.',
|
|
450
459
|
});
|
|
451
460
|
}
|
|
461
|
+
|
|
462
|
+
if (/export function \w*(Listing|Venue|Event|Community|Product|Card)\w*\s*\(/.test(content)
|
|
463
|
+
&& /from\s+['"]@mantine\/core['"][\s\S]{0,240}\bCard\b/.test(content)
|
|
464
|
+
&& !/from\s+['"]@doneisbetter\/gds-core['"][\s\S]{0,260}\b(ListingCard|PublicProductCard|PublicFoodCard|MediaCard)\b/.test(content)) {
|
|
465
|
+
findings.push({
|
|
466
|
+
rule: 'strict.listing.local-card-wrapper',
|
|
467
|
+
severity: 'error',
|
|
468
|
+
file: filePath,
|
|
469
|
+
message: 'Strict mode forbids local listing/card wrappers backed by Mantine Card. Use ListingCard, PublicProductCard, PublicFoodCard, or MediaCard.',
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (/export function \w*(Upload|Media|Asset|ImagePicker|Dropzone)\w*\s*\(/.test(content)
|
|
474
|
+
&& (/<input[^>]+type=["']file["']/.test(content) || /\bDropzone\b/.test(content))
|
|
475
|
+
&& !/from\s+['"]@doneisbetter\/gds-core['"][\s\S]{0,260}\b(MediaField|UploadDropzone)\b/.test(content)) {
|
|
476
|
+
findings.push({
|
|
477
|
+
rule: 'strict.media.local-upload-wrapper',
|
|
478
|
+
severity: 'error',
|
|
479
|
+
file: filePath,
|
|
480
|
+
message: 'Strict mode forbids local media/upload wrappers. Use MediaField and UploadDropzone while keeping storage logic product-owned.',
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (/export function \w*(Report|Chart|Evidence|Analytics|Metric)\w*\s*\(/.test(content)
|
|
485
|
+
&& (/\bChart\b|recharts|chart\.js|<canvas\b|svg\s+role=["']img["']/.test(content))
|
|
486
|
+
&& !/from\s+['"]@doneisbetter\/gds-core['"][\s\S]{0,320}\b(ReportingSection|PeriodSelector|EvidencePanel|ChartTokenPanel|StatsSection|MetricCard)\b/.test(content)) {
|
|
487
|
+
findings.push({
|
|
488
|
+
rule: 'strict.reporting.local-chart-wrapper',
|
|
489
|
+
severity: 'error',
|
|
490
|
+
file: filePath,
|
|
491
|
+
message: 'Strict mode forbids local reporting/chart wrappers without the GDS reporting contract. Use ReportingSection, EvidencePanel, PeriodSelector, and ChartTokenPanel.',
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (/export function \w*(Auth|Login|Social|AccessDenied|Protected|Recovery)\w*\s*\(/.test(content)
|
|
496
|
+
&& (/\b(google|apple|github|microsoft|facebook)\b/i.test(content) || /Access denied|Sign in required|Session expired/i.test(content))
|
|
497
|
+
&& !/from\s+['"]@doneisbetter\/gds-core['"][\s\S]{0,360}\b(AuthShell|ProviderIdentityButton|ProviderIdentityButtonGroup|SocialAuthButtons|AccessSummary|AccessRecoveryPanel)\b/.test(content)) {
|
|
498
|
+
findings.push({
|
|
499
|
+
rule: 'strict.access.local-auth-wrapper',
|
|
500
|
+
severity: 'error',
|
|
501
|
+
file: filePath,
|
|
502
|
+
message: 'Strict mode forbids local auth/access wrappers. Use AuthShell, provider identity controls, AccessSummary, and AccessRecoveryPanel.',
|
|
503
|
+
});
|
|
504
|
+
}
|
|
452
505
|
}
|
|
453
506
|
|
|
454
507
|
return findings;
|
|
@@ -561,6 +614,9 @@ function scanIdentityProviderBranding({ manifest, manifestRoot, sourceFiles }) {
|
|
|
561
614
|
const forbiddenCustomizations = Array.isArray(policy.forbiddenCustomizations)
|
|
562
615
|
? policy.forbiddenCustomizations
|
|
563
616
|
: [];
|
|
617
|
+
const allowedVariants = Array.isArray(policy.allowedVariants)
|
|
618
|
+
? new Set(policy.allowedVariants.map((variant) => normalizeProviderId(variant)))
|
|
619
|
+
: null;
|
|
564
620
|
const socialAuthUsages = /<(?:SocialAuthButtons|ProviderIdentityButton|ProviderIdentityButtonGroup)[\s\S]*?(?:\/\s*>|>[\s\S]*?<\/(?:SocialAuthButtons|ProviderIdentityButton|ProviderIdentityButtonGroup)>)/g;
|
|
565
621
|
const providerTextRegex = /\b(google|apple|facebook|github|microsoft|linkedin|discord|\bx\b|email)\b/i;
|
|
566
622
|
const mantineButtonImportRegex = /from\s+['"]@mantine\/core['"][\s\S]{0,240}\bButton\b/;
|
|
@@ -596,6 +652,20 @@ function scanIdentityProviderBranding({ manifest, manifestRoot, sourceFiles }) {
|
|
|
596
652
|
});
|
|
597
653
|
}
|
|
598
654
|
|
|
655
|
+
if (allowedVariants) {
|
|
656
|
+
for (const match of usage[0].matchAll(/\bvariant\s*[:=]\s*['"]([^'"]+)['"]/g)) {
|
|
657
|
+
const variant = normalizeProviderId(match[1]);
|
|
658
|
+
if (!allowedVariants.has(variant)) {
|
|
659
|
+
findings.push({
|
|
660
|
+
rule: 'identity.provider.disallowed-variant',
|
|
661
|
+
severity: 'error',
|
|
662
|
+
file: relativePath,
|
|
663
|
+
message: `Social identity usage sets variant "${variant}" outside compliance.identityProviderBranding.allowedVariants.`,
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
599
669
|
for (const providerId of providerIds) {
|
|
600
670
|
if (!approvedProviders.has(providerId)) {
|
|
601
671
|
findings.push({
|