electron/script/release/publish-to-npm.js
Keeley Hammond 361b37592a
build: add GitHub Actions publishing pipeline for macOS (#42236)
* build: add publishing workflow for GHActions

* build: add test repo/bucket for uploads

* build: clean up conditionals, add macos-14-large, review comments

* build: remove host_cpu var from GCLIENT_EXTRA_ARGS
2024-05-31 10:58:39 -07:00

226 lines
7.9 KiB
JavaScript

const temp = require('temp');
const fs = require('node:fs');
const path = require('node:path');
const childProcess = require('node:child_process');
const semver = require('semver');
const { getCurrentBranch, ELECTRON_DIR } = require('../lib/utils');
const { getElectronVersion } = require('../lib/get-version');
const rootPackageJson = require('../../package.json');
const { Octokit } = require('@octokit/rest');
const { getAssetContents } = require('./get-asset');
const octokit = new Octokit({
userAgent: 'electron-npm-publisher',
auth: process.env.ELECTRON_GITHUB_TOKEN
});
if (!process.env.ELECTRON_NPM_OTP) {
console.error('Please set ELECTRON_NPM_OTP');
process.exit(1);
}
let tempDir;
temp.track(); // track and cleanup files at exit
const files = [
'cli.js',
'index.js',
'install.js',
'package.json',
'README.md',
'LICENSE'
];
const jsonFields = [
'name',
'repository',
'description',
'license',
'author',
'keywords'
];
let npmTag = '';
const currentElectronVersion = getElectronVersion();
const isNightlyElectronVersion = currentElectronVersion.includes('nightly');
const targetRepo = getRepo();
function getRepo () {
if (process.env.IS_GHA_RELEASE) return 'test-releases';
return isNightlyElectronVersion ? 'nightlies' : 'electron';
}
new Promise((resolve, reject) => {
temp.mkdir('electron-npm', (err, dirPath) => {
if (err) {
reject(err);
} else {
resolve(dirPath);
}
});
})
.then((dirPath) => {
tempDir = dirPath;
// copy files from `/npm` to temp directory
for (const name of files) {
const noThirdSegment = name === 'README.md' || name === 'LICENSE';
fs.writeFileSync(
path.join(tempDir, name),
fs.readFileSync(path.join(ELECTRON_DIR, noThirdSegment ? '' : 'npm', name))
);
}
// copy from root package.json to temp/package.json
const packageJson = require(path.join(tempDir, 'package.json'));
for (const fieldName of jsonFields) {
packageJson[fieldName] = rootPackageJson[fieldName];
}
packageJson.version = currentElectronVersion;
fs.writeFileSync(
path.join(tempDir, 'package.json'),
JSON.stringify(packageJson, null, 2)
);
return octokit.repos.listReleases({
owner: 'electron',
repo: targetRepo
});
})
.then((releases) => {
// download electron.d.ts from release
const release = releases.data.find(
(release) => release.tag_name === `v${currentElectronVersion}`
);
if (!release) {
throw new Error(`cannot find release with tag v${currentElectronVersion}`);
}
return release;
})
.then(async (release) => {
const tsdAsset = release.assets.find((asset) => asset.name === 'electron.d.ts');
if (!tsdAsset) {
throw new Error(`cannot find electron.d.ts from v${currentElectronVersion} release assets`);
}
const typingsContent = await getAssetContents(
targetRepo,
tsdAsset.id
);
fs.writeFileSync(path.join(tempDir, 'electron.d.ts'), typingsContent);
return release;
})
.then(async (release) => {
const checksumsAsset = release.assets.find((asset) => asset.name === 'SHASUMS256.txt');
if (!checksumsAsset) {
throw new Error(`cannot find SHASUMS256.txt from v${currentElectronVersion} release assets`);
}
const checksumsContent = await getAssetContents(
targetRepo,
checksumsAsset.id
);
const checksumsObject = {};
for (const line of checksumsContent.trim().split('\n')) {
const [checksum, file] = line.split(' *');
checksumsObject[file] = checksum;
}
fs.writeFileSync(path.join(tempDir, 'checksums.json'), JSON.stringify(checksumsObject, null, 2));
return release;
})
.then(async (release) => {
const currentBranch = await getCurrentBranch();
if (isNightlyElectronVersion) {
// Nightlies get published to their own module, so they should be tagged as latest
npmTag = currentBranch === 'main' ? 'latest' : `nightly-${currentBranch}`;
const currentJson = JSON.parse(fs.readFileSync(path.join(tempDir, 'package.json'), 'utf8'));
currentJson.name = 'electron-nightly';
rootPackageJson.name = 'electron-nightly';
fs.writeFileSync(
path.join(tempDir, 'package.json'),
JSON.stringify(currentJson, null, 2)
);
} else {
if (currentBranch === 'main') {
// This should never happen, main releases should be nightly releases
// this is here just-in-case
throw new Error('Unreachable release phase, can\'t tag a non-nightly release on the main branch');
} else if (!release.prerelease) {
// Tag the release with a `2-0-x` style tag
npmTag = currentBranch;
} else if (release.tag_name.indexOf('alpha') > 0) {
// Tag the release with an `alpha-3-0-x` style tag
npmTag = `alpha-${currentBranch}`;
} else {
// Tag the release with a `beta-3-0-x` style tag
npmTag = `beta-${currentBranch}`;
}
}
})
.then(() => childProcess.execSync('npm pack', { cwd: tempDir }))
.then(() => {
// test that the package can install electron prebuilt from github release
const tarballPath = path.join(tempDir, `${rootPackageJson.name}-${currentElectronVersion}.tgz`);
return new Promise((resolve, reject) => {
const result = childProcess.spawnSync('npm', ['install', tarballPath, '--force', '--silent'], {
env: { ...process.env, electron_config_cache: tempDir },
cwd: tempDir,
stdio: 'inherit'
});
if (result.status !== 0) {
return reject(new Error(`npm install failed with status ${result.status}`));
}
try {
const electronPath = require(path.resolve(tempDir, 'node_modules', rootPackageJson.name));
if (typeof electronPath !== 'string') {
return reject(new Error(`path to electron binary (${electronPath}) returned by the ${rootPackageJson.name} module is not a string`));
}
if (!fs.existsSync(electronPath)) {
return reject(new Error(`path to electron binary (${electronPath}) returned by the ${rootPackageJson.name} module does not exist on disk`));
}
} catch (e) {
console.error(e);
return reject(new Error(`loading the generated ${rootPackageJson.name} module failed with an error`));
}
resolve(tarballPath);
});
})
.then((tarballPath) => {
const existingVersionJSON = childProcess.execSync(`npx npm@7 view ${rootPackageJson.name}@${currentElectronVersion} --json`).toString('utf-8');
// It's possible this is a re-run and we already have published the package, if not we just publish like normal
if (!existingVersionJSON) {
childProcess.execSync(`npm publish ${tarballPath} --tag ${npmTag} --otp=${process.env.ELECTRON_NPM_OTP}`);
}
})
.then(() => {
const currentTags = JSON.parse(childProcess.execSync('npm show electron dist-tags --json').toString());
const parsedLocalVersion = semver.parse(currentElectronVersion);
if (rootPackageJson.name === 'electron') {
// We should only customly add dist tags for non-nightly releases where the package name is still
// "electron"
if (parsedLocalVersion.prerelease.length === 0 &&
semver.gt(currentElectronVersion, currentTags.latest)) {
childProcess.execSync(`npm dist-tag add electron@${currentElectronVersion} latest --otp=${process.env.ELECTRON_NPM_OTP}`);
}
if (parsedLocalVersion.prerelease[0] === 'beta' &&
semver.gt(currentElectronVersion, currentTags.beta)) {
childProcess.execSync(`npm dist-tag add electron@${currentElectronVersion} beta --otp=${process.env.ELECTRON_NPM_OTP}`);
}
if (parsedLocalVersion.prerelease[0] === 'alpha' &&
semver.gt(currentElectronVersion, currentTags.alpha)) {
childProcess.execSync(`npm dist-tag add electron@${currentElectronVersion} alpha --otp=${process.env.ELECTRON_NPM_OTP}`);
}
}
})
.catch((err) => {
console.error('Error:', err);
process.exit(1);
});