@doneisbetter/gds-compliance 2.6.7 → 3.0.1

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.
Files changed (2) hide show
  1. package/index.js +70 -0
  2. 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({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doneisbetter/gds-compliance",
3
- "version": "2.6.7",
3
+ "version": "3.0.1",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "bin": {