tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

update_browser_revision.mjs (7114B)


      1 /**
      2 * @license
      3 * Copyright 2023 Google Inc.
      4 * SPDX-License-Identifier: Apache-2.0
      5 */
      6 
      7 import {execSync, exec} from 'child_process';
      8 import {writeFile, readFile} from 'fs/promises';
      9 import {promisify} from 'util';
     10 
     11 import actions from '@actions/core';
     12 import {resolveBuildId} from '@puppeteer/browsers';
     13 import {SemVer} from 'semver';
     14 
     15 import packageJson from '../packages/puppeteer-core/package.json' with {type: 'json'};
     16 import versionData from '../versions.json' with {type: 'json'};
     17 
     18 import {PUPPETEER_REVISIONS} from 'puppeteer-core/internal/revisions.js';
     19 
     20 const execAsync = promisify(exec);
     21 
     22 const BROWSER = process.env.BROWSER_TO_UPDATE;
     23 
     24 if (!BROWSER) {
     25  console.error('No BROWSER_TO_UPDATE env variable supplied!');
     26  process.exit(1);
     27 }
     28 
     29 const BROWSER_CURRENT_VERSION = PUPPETEER_REVISIONS[BROWSER];
     30 
     31 const touchedFiles = [];
     32 
     33 function getCapitalize(text) {
     34  return text.charAt(0).toUpperCase() + text.slice(1);
     35 }
     36 
     37 /**
     38 *
     39 * @param {string} version
     40 * @returns {string}
     41 */
     42 function normalizeVersionForCommit(browser, version) {
     43  switch (browser) {
     44    case 'firefox':
     45      // Splits the prefix of `stable_` for Firefox
     46      return version.split('_').at(-1);
     47    case 'chrome':
     48      return version;
     49  }
     50 
     51  throw new Error(`Unrecognized browser ${browser}`);
     52 }
     53 
     54 /**
     55 *
     56 * @param {string} version
     57 * @returns {string}
     58 */
     59 function normalizeVersionToSemVer(browser, version) {
     60  switch (browser) {
     61    case 'firefox':
     62      // Splits the prefix of `stable_` for Firefox
     63      version = version.split('_').at(-1);
     64      // Firefox reports 129.0 instead of 129.0.0
     65      // Patch have the correct number 128.0.2
     66      if (version.split('.').length <= 2) {
     67        return `${version}.0`;
     68      }
     69 
     70      return version;
     71    case 'chrome':
     72      // For Chrome (example: 127.0.6533.99) is allowed as SemVer
     73      // as long as we use the loose option.
     74      return version;
     75  }
     76 
     77  throw new Error(`Unrecognized browser ${browser}`);
     78 }
     79 
     80 function checkIfNeedsUpdate(browser, oldVersion, newVersion) {
     81  const oldSemVer = new SemVer(
     82    normalizeVersionToSemVer(browser, oldVersion),
     83    true,
     84  );
     85  const newSemVer = new SemVer(
     86    normalizeVersionToSemVer(browser, newVersion),
     87    true,
     88  );
     89  let message = `roll to ${getCapitalize(browser)} ${normalizeVersionForCommit(browser, newVersion)}`;
     90 
     91  if (newSemVer.compare(oldSemVer) <= 0) {
     92    // Exit the process without setting up version
     93    console.warn(
     94      `Version ${newVersion} is older or the same as the current ${oldVersion}`,
     95    );
     96    process.exit(0);
     97  } else if (newSemVer.major === oldSemVer.major) {
     98    message = `fix: ${message}`;
     99  } else {
    100    message = `feat: ${message}`;
    101  }
    102  actions.setOutput('commit', message);
    103 }
    104 
    105 /**
    106 * We cant use `npm run format` as it's too slow
    107 * so we only scope the files we updated
    108 */
    109 async function formatUpdateFiles() {
    110  await Promise.all(
    111    touchedFiles.map(file => {
    112      return execAsync(`npx eslint --fix ${file}`);
    113    }),
    114  );
    115  await Promise.all(
    116    touchedFiles.map(file => {
    117      return execAsync(`npx prettier --write ${file}`);
    118    }),
    119  );
    120 }
    121 
    122 async function replaceInFile(filePath, search, replace) {
    123  const buffer = await readFile(filePath);
    124  const update = buffer.toString().replaceAll(search, replace);
    125 
    126  await writeFile(filePath, update);
    127 
    128  touchedFiles.push(filePath);
    129 }
    130 
    131 async function getVersionForStable(browser) {
    132  return await resolveBuildId(browser, 'linux', 'stable');
    133 }
    134 
    135 async function updateDevToolsProtocolVersion(browserVersion) {
    136  const result = await fetch(
    137    'https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions.json',
    138  ).then(response => {
    139    return response.json();
    140  });
    141 
    142  const {version, revision} = result.channels['Stable'];
    143  if (browserVersion !== version) {
    144    console.error(
    145      'The version from CfT website and @puppeteer/browser mismatch.',
    146    );
    147    process.exit(1);
    148  }
    149 
    150  const currentProtocol = packageJson.dependencies['devtools-protocol'];
    151  const command = `npm view "devtools-protocol@<=0.0.${revision}" version | tail -1`;
    152 
    153  const bestNewProtocol = execSync(command, {
    154    encoding: 'utf8',
    155  })
    156    .split(' ')[1]
    157    .replace(/'|\n/g, '');
    158 
    159  await replaceInFile(
    160    './packages/puppeteer-core/package.json',
    161    `"devtools-protocol": "${currentProtocol}"`,
    162    `"devtools-protocol": "${bestNewProtocol}"`,
    163  );
    164 
    165  await replaceInFile(
    166    './packages/puppeteer/package.json',
    167    `"devtools-protocol": "${currentProtocol}"`,
    168    `"devtools-protocol": "${bestNewProtocol}"`,
    169  );
    170 }
    171 
    172 async function saveVersionData() {
    173  await writeFile('./versions.json', JSON.stringify(versionData, null, 2));
    174  touchedFiles.push('./versions.json');
    175 }
    176 
    177 async function updateVersionData(browser, oldVersion, newVersion) {
    178  const browserVersions = versionData.versions.map(
    179    ([_puppeteerVersion, browserVersions]) => {
    180      return browserVersions[browser];
    181    },
    182  );
    183  if (browserVersions.indexOf(newVersion) !== -1) {
    184    // Already updated.
    185    return;
    186  }
    187 
    188  const nextVersionConfig = versionData.versions.find(([puppeteerVersion]) => {
    189    return puppeteerVersion === 'NEXT';
    190  });
    191 
    192  // If we have manually rolled Chrome but not yet released
    193  // We will have NEXT as value in the Map
    194  if (nextVersionConfig) {
    195    nextVersionConfig[1][browser] = newVersion;
    196    return;
    197  }
    198 
    199  versionData.versions.unshift([
    200    'NEXT',
    201    {
    202      ...versionData.versions.at(0).at(1),
    203      [browser]: newVersion,
    204    },
    205  ]);
    206 }
    207 
    208 async function updateLastMaintainedChromeVersion(oldVersion, newVersion) {
    209  const browserVersions = versionData.versions.map(
    210    ([_puppeteerVersion, browserVersions]) => {
    211      return browserVersions['chrome'];
    212    },
    213  );
    214  if (browserVersions.indexOf(newVersion) !== -1) {
    215    // Already updated.
    216    return;
    217  }
    218 
    219  const oldSemVer = new SemVer(oldVersion, true);
    220  const newSemVer = new SemVer(newVersion, true);
    221 
    222  if (newSemVer.compareMain(oldSemVer) !== 0) {
    223    const lastMaintainedSemVer = new SemVer(
    224      versionData.lastMaintainedChromeVersion,
    225      true,
    226    );
    227    const newLastMaintainedMajor = lastMaintainedSemVer.major + 1;
    228 
    229    const nextMaintainedVersion = browserVersions.find(version => {
    230      return new SemVer(version, true).major === newLastMaintainedMajor;
    231    });
    232 
    233    versionData.lastMaintainedChromeVersion = nextMaintainedVersion;
    234  }
    235 }
    236 
    237 const version = await getVersionForStable(BROWSER);
    238 
    239 checkIfNeedsUpdate(BROWSER, BROWSER_CURRENT_VERSION, version);
    240 
    241 await replaceInFile(
    242  './packages/puppeteer-core/src/revisions.ts',
    243  BROWSER_CURRENT_VERSION,
    244  version,
    245 );
    246 
    247 await updateVersionData(BROWSER, BROWSER_CURRENT_VERSION, version);
    248 
    249 if (BROWSER === 'chrome') {
    250  await updateLastMaintainedChromeVersion(BROWSER_CURRENT_VERSION, version);
    251  await updateDevToolsProtocolVersion(version);
    252  // Create new `package-lock.json` as we update devtools-protocol
    253  execSync('npm install --ignore-scripts');
    254 }
    255 
    256 await saveVersionData();
    257 
    258 // Make sure we pass CI formatter check by running all the new files though it
    259 await formatUpdateFiles();
    260 
    261 // Keep this as they can be used to debug GitHub Actions if needed
    262 actions.setOutput('version', version);