Repository: electron/electron
Pull request: build: improve `gen-libc++-filenames` output

#### Description of Change

At the moment the output of this file isn't particularly easy to read - it just dumps the full contents from both the old and new files to demonstrate that the file contents differ. What we want is to know what files were added and what files were removed as a result of e.g. a Chromium bump - this PR reworks the logic to show that more succinctly and directly.

<details><summary>Details</summary>
<p>

```diff
diff --git a/filenames.libcxx.gni b/filenames.libcxx.gni
index d680308876..9e88c13444 100644
--- a/filenames.libcxx.gni
+++ b/filenames.libcxx.gni
@@ -14,7 +14,7 @@ libcxx_headers = [
   "//third_party/libc++/src/include/__algorithm/copy_n.h",
   "//third_party/libc++/src/include/__algorithm/count.h",
   "//third_party/libc++/src/include/__algorithm/count_if.h",
-  "//third_party/libc++/src/include/__algorithm/equal.h",
+  "//third_party/libc++/src/include/__algorithm/unequal.h",
   "//third_party/libc++/src/include/__algorithm/equal_range.h",
   "//third_party/libc++/src/include/__algorithm/fill.h",
   "//third_party/libc++/src/include/__algorithm/fill_n.h",
```

<img width="570" alt="Screenshot 2024-10-28 at 12 24 49 PM" src="https://github.com/user-attachments/assets/c2bb8c65-0b25-4147-8e3b-0a74f1d36508">

</p>
</details> 

#### Checklist
<!-- Remove items that do not apply. For completed items, change [ ] to [x]. -->

- [x] PR description included and stakeholders cc'd
- [x] `npm test` passes
- [x] [PR release notes](https://github.com/electron/clerk/blob/main/README.md) describe the change in a way relevant to app developers, and are [capitalized, punctuated, and past tense](https://github.com/electron/clerk/blob/main/README.md#examples).

#### Release Notes

Notes: none


Changed files:
filename: .github/workflows/pipeline-segment-electron-gn-check.yml
patch: @@ -132,6 +132,10 @@ jobs:
       with:
         path: src/electron
         fetch-depth: 0
+    - name: Install Dependencies
+      run: |
+        cd src/electron
+        node script/yarn install --frozen-lockfile
     - name: Default GN gen
       run: |
         cd src/electron

filename: script/gen-libc++-filenames.js
patch: @@ -1,3 +1,5 @@
+const chalk = require('chalk');
+
 const fs = require('node:fs');
 const path = require('node:path');
 
@@ -17,6 +19,29 @@ function findAllHeaders (basePath) {
   return toReturn;
 }
 
+const diff = (array1, array2) => {
+  const set1 = new Set(array1);
+  const set2 = new Set(array2);
+
+  const added = array1.filter(item => !set2.has(item));
+  const removed = array2.filter(item => !set1.has(item));
+
+  console.log(chalk.white.bgGreen.bold('Files Added:'));
+  added.forEach(item => console.log(chalk.green.bold(`+ ${item}`)));
+
+  console.log(chalk.white.bgRed.bold('Files Removed:'));
+  removed.forEach(item => console.log(chalk.red.bold(`- ${item}`)));
+};
+
+const parseHeaders = (name, content) => {
+  const pattern = new RegExp(`${name}_headers\\s*=\\s*\\[(.*?)\\]`, 's');
+  const headers = content.match(pattern);
+  if (!headers) return [];
+  return headers[1].split(',')
+    .map(item => item.trim().replace(/"/g, ''))
+    .filter(item => item.length > 0);
+};
+
 for (const folder of ['libc++', 'libc++abi']) {
   const prettyName = folder.replace(/\+/g, 'x');
 
@@ -25,21 +50,26 @@ for (const folder of ['libc++', 'libc++abi']) {
 
   const headers = findAllHeaders(libcxxIncludeDir).map(absPath => path.relative(path.resolve(__dirname, '../..', gclientPath), absPath));
 
+  const newHeaders = headers.map(f => `//${path.posix.join(gclientPath, f)}`);
   const content = `${prettyName}_headers = [
-  ${headers.map(f => `"//${path.posix.join(gclientPath, f)}"`).join(',\n  ')},
+  ${newHeaders.map(h => `"${h}"`).join(',\n  ')},
 ]
 
 ${prettyName}_licenses = [ "//third_party/${folder}/src/LICENSE.TXT" ]
 `;
 
-  const filenamesPath = path.resolve(__dirname, '..', `filenames.${prettyName}.gni`);
+  const file = `filenames.${prettyName}.gni`;
+  const filenamesPath = path.resolve(__dirname, '..', file);
 
   if (check) {
     const currentContent = fs.readFileSync(filenamesPath, 'utf8');
     if (currentContent !== content) {
-      console.log('currentContent: ', currentContent);
-      console.log('content: ', content);
-      throw new Error(`${prettyName} filenames need to be regenerated, latest generation does not match current file.  Please run node gen-libc++-filenames.js`);
+      const currentHeaders = parseHeaders(prettyName, currentContent);
+
+      console.error(chalk.bold(`${file} contents are not up to date:\n`));
+      diff(currentHeaders, newHeaders);
+      console.error(chalk.bold(`\nRun node gen-libc++-filenames.js to regenerate ${file}`));
+      process.exit(1);
     }
   } else {
     console.log(filenamesPath);