<commit_before>#!/usr/bin/env node

const childProcess = require('child_process')
const fs = require('fs')
const os = require('os')
const path = require('path')

const { GitProcess } = require('dugite')
const octokit = require('@octokit/rest')()
const semver = require('semver')

const CACHE_DIR = path.resolve(__dirname, '.cache')
const NO_NOTES = 'No notes'
const FOLLOW_REPOS = [ 'electron/electron', 'electron/libchromiumcontent', 'electron/node' ]
const gitDir = path.resolve(__dirname, '..', '..')

octokit.authenticate({ type: 'token', token: process.env.ELECTRON_GITHUB_TOKEN })

const breakTypes = new Set(['breaking-change'])
const docTypes = new Set(['doc', 'docs'])
const featTypes = new Set(['feat', 'feature'])
const fixTypes = new Set(['fix'])
const otherTypes = new Set(['spec', 'build', 'test', 'chore', 'deps', 'refactor', 'tools', 'vendor', 'perf', 'style', 'ci'])
const knownTypes = new Set([...breakTypes.keys(), ...docTypes.keys(), ...featTypes.keys(), ...fixTypes.keys(), ...otherTypes.keys()])

const runGit = async (dir, args) => {
  const response = await GitProcess.exec(args, dir)
  if (response.exitCode !== 0) {
    throw new Error(response.stderr.trim())
  }
  return response.stdout.trim()
}

const getCommonAncestor = async (dir, point1, point2) => {
  return runGit(dir, ['merge-base', point1, point2])
}

const setPullRequest = (commit, owner, repo, number) => {
  if (!owner || !repo || !number) {
    throw new Error(JSON.stringify({ owner, repo, number }, null, 2))
  }

  if (!commit.originalPr) {
    commit.originalPr = commit.pr
  }

  commit.pr = { owner, repo, number }

  if (!commit.originalPr) {
    commit.originalPr = commit.pr
  }
}

const getNoteFromClerk = async (number, owner, repo) => {
  const comments = await getComments(number, owner, repo)
  if (!comments || !comments.data) return

  const CLERK_LOGIN = 'release-clerk[bot]'
  const PERSIST_LEAD = '**Release Notes Persisted**\n\n'
  const QUOTE_LEAD = '> '

  for (const comment of comments.data.reverse()) {
    if (comment.user.login !== CLERK_LOGIN) {
      continue
    }
    if (!comment.body.startsWith(PERSIST_LEAD)) {
      continue
    }
    const note = comment.body
      .slice(PERSIST_LEAD.length).trim() // remove PERSIST_LEAD
      .split('\r?\n') // break into lines
      .map(line => line.trim())
      .filter(line => line.startsWith(QUOTE_LEAD)) // notes are quoted
      .map(line => line.slice(QUOTE_LEAD.length)) // unquote the lines
      .join(' ') // join the note lines
      .trim()
    return note
  }
}<commit_msg>fix: release-notes plays more nicely with clerk (#16887)<commit_after>#!/usr/bin/env node

const childProcess = require('child_process')
const fs = require('fs')
const os = require('os')
const path = require('path')

const { GitProcess } = require('dugite')
const octokit = require('@octokit/rest')()
const semver = require('semver')

const CACHE_DIR = path.resolve(__dirname, '.cache')
const NO_NOTES = 'No notes'
const FOLLOW_REPOS = [ 'electron/electron', 'electron/libchromiumcontent', 'electron/node' ]
const gitDir = path.resolve(__dirname, '..', '..')

octokit.authenticate({ type: 'token', token: process.env.ELECTRON_GITHUB_TOKEN })

const breakTypes = new Set(['breaking-change'])
const docTypes = new Set(['doc', 'docs'])
const featTypes = new Set(['feat', 'feature'])
const fixTypes = new Set(['fix'])
const otherTypes = new Set(['spec', 'build', 'test', 'chore', 'deps', 'refactor', 'tools', 'vendor', 'perf', 'style', 'ci'])
const knownTypes = new Set([...breakTypes.keys(), ...docTypes.keys(), ...featTypes.keys(), ...fixTypes.keys(), ...otherTypes.keys()])

const runGit = async (dir, args) => {
  const response = await GitProcess.exec(args, dir)
  if (response.exitCode !== 0) {
    throw new Error(response.stderr.trim())
  }
  return response.stdout.trim()
}

const getCommonAncestor = async (dir, point1, point2) => {
  return runGit(dir, ['merge-base', point1, point2])
}

const setPullRequest = (commit, owner, repo, number) => {
  if (!owner || !repo || !number) {
    throw new Error(JSON.stringify({ owner, repo, number }, null, 2))
  }

  if (!commit.originalPr) {
    commit.originalPr = commit.pr
  }

  commit.pr = { owner, repo, number }

  if (!commit.originalPr) {
    commit.originalPr = commit.pr
  }
}

const getNoteFromClerk = async (number, owner, repo) => {
  const comments = await getComments(number, owner, repo)
  if (!comments || !comments.data) return

  const CLERK_LOGIN = 'release-clerk[bot]'
  const CLERK_NO_NOTES = '**No Release Notes**'
  const PERSIST_LEAD = '**Release Notes Persisted**\n\n'
  const QUOTE_LEAD = '> '

  for (const comment of comments.data.reverse()) {
    if (comment.user.login !== CLERK_LOGIN) {
      continue
    }
    if (comment.body === CLERK_NO_NOTES) {
      return NO_NOTES
    }
    if (comment.body.startsWith(PERSIST_LEAD)) {
      return comment.body
        .slice(PERSIST_LEAD.length).trim() // remove PERSIST_LEAD
        .split('\r?\n') // break into lines
        .map(line => line.trim())
        .filter(line => line.startsWith(QUOTE_LEAD)) // notes are quoted
        .map(line => line.slice(QUOTE_LEAD.length)) // unquote the lines
        .join(' ') // join the note lines
        .trim()
    }
  }
}
{'fraction_non_alphanumeric': 0.17212490479817213, 'fraction_numerical': 0.003236862147753237, 'mean_word_length': 3.797260273972603, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4486417', 'n_tokens_mistral': 1787, 'n_tokens_neox': 1724, 'n_words': 435}