#!/usr/bin/env node

// Imports
const {existsSync, readFileSync, statSync} = require('fs');
const {join, resolve} = require('path');

// Constants
const MAX_IMAGE_SIZE = 30 * 1024;  // 30kb
const CONTENT_DIR = resolve(__dirname, '../../content');
const IMAGES_DIR = join(CONTENT_DIR, 'images/bios');
const CONTRIBUTORS_PATH = join(CONTENT_DIR, 'marketing/contributors.json');
const EXISTING_GROUPS = new Set(['Angular', 'GDE', 'Collaborators']);

// Run
_main();

// Functions - Definitions
function _main() {
  const contributors = JSON.parse(readFileSync(CONTRIBUTORS_PATH, 'utf8'));

  // Check that there are no missing images.
  const expectedImages = Object.keys(contributors)
      .filter(key => !!contributors[key].picture)
      .map(key => join(IMAGES_DIR, contributors[key].picture));
  const missingImages = expectedImages.filter(path => !existsSync(path));

  if (missingImages.length > 0) {
    throw new Error(
        'The following pictures are referenced in \'contributors.json\' but do not exist:' +
        missingImages.map(path => `\n  - ${path}`).join(''));
  }

  // Check that there are no images that exceed the size limit.
  const tooLargeImages = expectedImages.filter(path => statSync(path).size > MAX_IMAGE_SIZE);
  if (tooLargeImages.length > 0) {
    throw new Error(
        `The following pictures exceed maximum size limit of ${MAX_IMAGE_SIZE / 1024}kb:` +
        tooLargeImages.map(path => `\n  - ${path}`).join(''));
  }

  // Verify that all keys are sorted alphabetically
  const keys = Object.keys(contributors);
  for (let i = 1; i < keys.length; i++) {
    if (keys[i - 1].toLowerCase() > keys[i].toLowerCase()) {
      throw new Error(
        `The following keys in 'contributors.json' are not in alphabetical order: '${keys[i - 1]}' and '${keys[i]}'.`
      );
    }
  }

  Object.entries(contributors).forEach(([key, entry]) => {
    // Make sure `lead` and `mentor` fields refer to existing entries
    if (entry.lead && !contributors[entry.lead]) {
      throw new Error(`The '${key}' entry contains 'lead' field, but it refers to non-existing entry ('${entry.lead}').`);
    }
    if (entry.mentor && !contributors[entry.mentor]) {
      throw new Error(`The '${key}' entry contains 'mentor' field, but it refers to non-existing entry ('${entry.mentor}').`);
    }

    // Verify that `groups` field is always present and contains existing groups
    if (!entry.groups || !Array.isArray(entry.groups) || entry.groups.length === 0) {
      throw new Error(`The 'groups' field should be defined as a non-empty array (entry: '${key}').`);
    }
    if (entry.groups.some(group => !EXISTING_GROUPS.has(group))) {
      throw new Error(`The '${key}' entry contains 'groups' field with unknown values ` +
        `(groups: ${JSON.stringify(entry.groups)}, known values: ${JSON.stringify(Array.from(EXISTING_GROUPS))}).`);
    }
  });
}

@@ -32,22 +32,23 @@
 %0Aconst %7B
-exists
+readdir
 Sync, re
@@ -583,54 +583,8 @@
 '));
-%0A%0A  // Check that there are no missing images.
 %0A  c
@@ -741,24 +741,209 @@
 .picture));%0A
+  const existingImages = readdirSync(IMAGES_DIR)%0A      .filter(name =%3E name !== '_no-one.jpg')%0A      .map(name =%3E join(IMAGES_DIR, name));%0A%0A  // Check that there are no missing images.%0A
   const miss
@@ -994,13 +994,26 @@
 xist
-sSync
+ingImages.includes
 (pat
@@ -1017,17 +1017,16 @@
 path));%0A
-%0A
   if (mi
@@ -1228,24 +1228,376 @@
 (''));%0A  %7D%0A%0A
+  // Check that there are no unused images.%0A  const unusedImages = existingImages.filter(path =%3E !expectedImages.includes(path));%0A  if (unusedImages.length %3E 0) %7B%0A    throw new Error(%0A        'The following pictures are not referenced in %5C'contributors.json%5C' and should be deleted:' +%0A        unusedImages.map(path =%3E %60%5Cn  - $%7Bpath%7D%60).join(''));%0A  %7D%0A%0A
   // Check t

check that there are no unused contributor images (#41290)