Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/automatic-updates.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
result-encoding: string
script: |
const { default: script } = await import(`${process.env.GITHUB_WORKSPACE}/build-automation.mjs`);
return script(github);
return script();

- name: Create update PR
id: cpr
Expand Down
210 changes: 86 additions & 124 deletions build-automation.mjs
Original file line number Diff line number Diff line change
@@ -1,140 +1,102 @@
import { promisify } from 'util';

import child_process from 'child_process';

const exec = promisify(child_process.exec);

// a function that queries the Node.js release website for new versions,
// compare the available ones with the ones we use in this repo
// and returns whether we should update or not
const checkIfThereAreNewVersions = async (github) => {
try {
const { stdout: versionsOutput } = await exec(
'. ./functions.sh && get_versions',
{ shell: 'bash' },
);
import { readdirSync, readFileSync } from 'node:fs';
import { join } from 'node:path';

import shell from 'shelljs';

// Track the built versions to output for the GitHub PR
const updatedVersions = [];

// TODO: since we have the full version, and could pass the CHECKSUM value (till
// it goes Tier 2), the update.sh script shouldn't have to look it all up again
async function runUpdate(fullVersion, isSecurityRelease, hasMusl) {
let majorVersion = fullVersion.split('v')[1].split('.')[0];
if (hasMusl || isSecurityRelease) {
let updateStatement = `bash update.sh ${isSecurityRelease ? '-s ' : ''}${majorVersion}`;
console.log(`Updating ${fullVersion} with '${updateStatement}'.`);
shell.exec(updateStatement);
updatedVersions.push(fullVersion);
} else {
console.error(`There's no musl build for version ${fullVersion} yet.`);
}
}

const supportedVersions = versionsOutput.trim().split(' ');
try {
// get the folders with a digit, assuming they're the Node.js major versions
const supportedVersions = readdirSync('./').filter((file) => {
return file.match(/\d/);
});

let latestSupportedVersions = {};
console.log(`Found major versions in repo: ${supportedVersions}`);

for (let supportedVersion of supportedVersions) {
const { stdout } = await exec(`ls ${supportedVersion}`);
console.log('Grabbing Index.json files');
const availableVersions = await fetch(
'https://nodejs.org/download/release/index.json',
);
const officialIndexJson = await availableVersions.json();

const { stdout: fullVersionOutput } = await exec(
`. ./functions.sh && get_full_version ./${supportedVersion}/${stdout.trim().split('\n')[0]}`,
{ shell: 'bash' },
);
const unofficialVersions = await fetch(
'https://unofficial-builds.nodejs.org/download/release/index.json',
);
const unofficialBuildsIndexJson = await unofficialVersions.json();

console.log(fullVersionOutput);
for (let supportedVersion of supportedVersions) {
console.log(`Checking for updates for ${supportedVersion}`);
const folders = readdirSync(join('.', supportedVersion));

latestSupportedVersions[supportedVersion] = {
fullVersion: fullVersionOutput.trim(),
};
}
const alpineFolder = folders[0];

const { data: availableVersionsJson } = await github.request(
'https://nodejs.org/download/release/index.json',
const alpineDockerFile = readFileSync(
join('.', supportedVersion, alpineFolder, 'Dockerfile'),
'utf-8',
);

// filter only more recent versions of availableVersionsJson for each major version in latestSupportedVersions' keys
// e.g. if latestSupportedVersions = { "12": "12.22.10", "14": "14.19.0", "16": "16.14.0", "17": "17.5.0" }
// and availableVersions = ["Node.js 12.22.10", "Node.js 12.24.0", "Node.js 14.19.0", "Node.js 14.22.0", "Node.js 16.14.0", "Node.js 16.16.0", "Node.js 17.5.0", "Node.js 17.8.0"]
// return { "12": "12.24.0", "14": "14.22.0", "16": "16.16.0", "17": "17.8.0" }

let filteredNewerVersions = {};

for (let availableVersion of availableVersionsJson) {
const [availableMajor, availableMinor, availablePatch] =
availableVersion.version.split('v')[1].split('.');
if (latestSupportedVersions[availableMajor] == null) {
continue;
}
const [_latestMajor, latestMinor, latestPatch] =
latestSupportedVersions[availableMajor].fullVersion.split('.');
if (
latestSupportedVersions[availableMajor] &&
(Number(availableMinor) > Number(latestMinor) ||
(availableMinor === latestMinor &&
Number(availablePatch) > Number(latestPatch)))
) {
filteredNewerVersions[availableMajor] = {
fullVersion: `${availableMajor}.${availableMinor}.${availablePatch}`,
};
}
}

return {
shouldUpdate:
Object.keys(filteredNewerVersions).length > 0 &&
JSON.stringify(filteredNewerVersions) !==
JSON.stringify(latestSupportedVersions),
versions: filteredNewerVersions,
};
} catch (error) {
console.error(error);
process.exit(1);
}
};

// a function that queries the Node.js unofficial release website for new musl versions and security releases,
// and returns relevant information
const checkForMuslVersionsAndSecurityReleases = async (github, versions) => {
try {
const { data: unofficialBuildsIndexText } = await github.request(
'https://unofficial-builds.nodejs.org/download/release/index.json',
const alpineVersion =
'v' +
alpineDockerFile.match(/NODE_VERSION=(?<version>\d*\.\d*\.\d)/).groups[
'version'
];
console.log(`Read Alpine version ${alpineVersion} from ${alpineFolder}`);

const debianFolder = folders.at(-1);
const debianDockerFile = readFileSync(
join('.', supportedVersion, debianFolder, 'Dockerfile'),
'utf-8',
);

for (let version of Object.keys(versions)) {
const buildVersion = unofficialBuildsIndexText.find(
(indexVersion) =>
indexVersion.version === `v${versions[version].fullVersion}`,
);
const debianVersion =
'v' +
debianDockerFile.match(/NODE_VERSION=(?<version>\d*\.\d*\.\d)/).groups[
'version'
];
console.log(`Read Debian version ${alpineVersion} from ${debianFolder}`);

versions[version].muslBuildExists =
buildVersion?.files.includes('linux-x64-musl') ?? false;
versions[version].isSecurityRelease = buildVersion?.security ?? false;
}
return versions;
} catch (error) {
console.error(error);
process.exit(1);
}
};
let latestDebian = officialIndexJson.find((indexVersion) =>
indexVersion.version.startsWith(`v${supportedVersion}`),
);

export default async function (github) {
// if there are no new versions, exit gracefully
// if there are new versions,
// check for musl builds
// then run update.sh
const { shouldUpdate, versions } = await checkIfThereAreNewVersions(github);
let hasMusl =
unofficialBuildsIndexJson.find(
(indexVersion) => indexVersion.version === latestDebian,
) !== null;

if (!shouldUpdate) {
console.log('No new versions found. No update required.');
process.exit(0);
} else {
const newVersions = await checkForMuslVersionsAndSecurityReleases(
github,
versions,
);
let updatedVersions = [];
for (const [version, newVersion] of Object.entries(newVersions)) {
if (newVersion.muslBuildExists) {
const { stdout } = await exec(
`./update.sh ${newVersion.isSecurityRelease ? '-s ' : ''}${version}`,
);
console.log(stdout);
updatedVersions.push(newVersion.fullVersion);
} else {
console.log(
`There's no musl build for version ${newVersion.fullVersion} yet.`,
);
process.exit(0);
}
if (latestDebian.version !== debianVersion) {
console.warn(
`Found new version ${latestDebian.version}, released on ${latestDebian.date}!`,
);
await runUpdate(latestDebian.version, latestDebian.security, hasMusl);
console.warn(`Alpine and Debian versions do not match!`);
} else if (debianVersion !== alpineVersion) {
console.warn(`Alpine ${alpineVersion} ${latestDebian.version}!`);
await runUpdate(latestDebian.version, latestDebian.security, hasMusl);
} else {
console.log(`Everything up to date for ${latestDebian.version}!
Released: ${latestDebian.date}
Security release: ${latestDebian.security}
Has musl: ${hasMusl}`);
}
const { stdout } = await exec(`git diff`);
console.log(stdout);

return updatedVersions.join(', ');
}
console.log('Finish the run.');
updatedVersions.join(', ');
} catch (error) {
console.error(error);
process.exit(1);
}
Loading