publish-to-npm.ts 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. import { Octokit } from '@octokit/rest';
  2. import * as semver from 'semver';
  3. import * as temp from 'temp';
  4. import * as childProcess from 'node:child_process';
  5. import * as fs from 'node:fs';
  6. import * as path from 'node:path';
  7. import { getElectronVersion } from '../../lib/get-version';
  8. import { getCurrentBranch, ELECTRON_DIR } from '../../lib/utils';
  9. import { getAssetContents } from '../get-asset';
  10. import { createGitHubTokenStrategy } from '../github-token';
  11. import { ELECTRON_ORG, ELECTRON_REPO, ElectronReleaseRepo, NIGHTLY_REPO } from '../types';
  12. const rootPackageJson = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../../../package.json'), 'utf-8'));
  13. if (!process.env.ELECTRON_NPM_OTP) {
  14. console.error('Please set ELECTRON_NPM_OTP');
  15. process.exit(1);
  16. }
  17. let tempDir: string;
  18. temp.track(); // track and cleanup files at exit
  19. const files = [
  20. 'cli.js',
  21. 'index.js',
  22. 'install.js',
  23. 'package.json',
  24. 'README.md',
  25. 'LICENSE'
  26. ];
  27. const jsonFields = [
  28. 'name',
  29. 'repository',
  30. 'description',
  31. 'license',
  32. 'author',
  33. 'keywords'
  34. ];
  35. let npmTag = '';
  36. const currentElectronVersion = getElectronVersion();
  37. const isNightlyElectronVersion = currentElectronVersion.includes('nightly');
  38. const targetRepo = getRepo();
  39. const octokit = new Octokit({
  40. userAgent: 'electron-npm-publisher',
  41. authStrategy: createGitHubTokenStrategy(targetRepo)
  42. });
  43. function getRepo (): ElectronReleaseRepo {
  44. return isNightlyElectronVersion ? NIGHTLY_REPO : ELECTRON_REPO;
  45. }
  46. new Promise<string>((resolve, reject) => {
  47. temp.mkdir('electron-npm', (err, dirPath) => {
  48. if (err) {
  49. reject(err);
  50. } else {
  51. resolve(dirPath);
  52. }
  53. });
  54. })
  55. .then((dirPath) => {
  56. tempDir = dirPath;
  57. // copy files from `/npm` to temp directory
  58. for (const name of files) {
  59. const noThirdSegment = name === 'README.md' || name === 'LICENSE';
  60. fs.writeFileSync(
  61. path.join(tempDir, name),
  62. fs.readFileSync(path.join(ELECTRON_DIR, noThirdSegment ? '' : 'npm', name))
  63. );
  64. }
  65. // copy from root package.json to temp/package.json
  66. const packageJson = require(path.join(tempDir, 'package.json'));
  67. for (const fieldName of jsonFields) {
  68. packageJson[fieldName] = rootPackageJson[fieldName];
  69. }
  70. packageJson.version = currentElectronVersion;
  71. fs.writeFileSync(
  72. path.join(tempDir, 'package.json'),
  73. JSON.stringify(packageJson, null, 2)
  74. );
  75. return octokit.repos.listReleases({
  76. owner: ELECTRON_ORG,
  77. repo: targetRepo
  78. });
  79. })
  80. .then((releases) => {
  81. // download electron.d.ts from release
  82. const release = releases.data.find(
  83. (release) => release.tag_name === `v${currentElectronVersion}`
  84. );
  85. if (!release) {
  86. throw new Error(`cannot find release with tag v${currentElectronVersion}`);
  87. }
  88. return release;
  89. })
  90. .then(async (release) => {
  91. const tsdAsset = release.assets.find((asset) => asset.name === 'electron.d.ts');
  92. if (!tsdAsset) {
  93. throw new Error(`cannot find electron.d.ts from v${currentElectronVersion} release assets`);
  94. }
  95. const typingsContent = await getAssetContents(
  96. targetRepo,
  97. tsdAsset.id
  98. );
  99. fs.writeFileSync(path.join(tempDir, 'electron.d.ts'), typingsContent);
  100. return release;
  101. })
  102. .then(async (release) => {
  103. const checksumsAsset = release.assets.find((asset) => asset.name === 'SHASUMS256.txt');
  104. if (!checksumsAsset) {
  105. throw new Error(`cannot find SHASUMS256.txt from v${currentElectronVersion} release assets`);
  106. }
  107. const checksumsContent = await getAssetContents(
  108. targetRepo,
  109. checksumsAsset.id
  110. );
  111. const checksumsObject: Record<string, string> = Object.create(null);
  112. for (const line of checksumsContent.trim().split('\n')) {
  113. const [checksum, file] = line.split(' *');
  114. checksumsObject[file] = checksum;
  115. }
  116. fs.writeFileSync(path.join(tempDir, 'checksums.json'), JSON.stringify(checksumsObject, null, 2));
  117. return release;
  118. })
  119. .then(async (release) => {
  120. const currentBranch = await getCurrentBranch();
  121. if (isNightlyElectronVersion) {
  122. // Nightlies get published to their own module, so they should be tagged as latest
  123. npmTag = currentBranch === 'main' ? 'latest' : `nightly-${currentBranch}`;
  124. const currentJson = JSON.parse(fs.readFileSync(path.join(tempDir, 'package.json'), 'utf8'));
  125. currentJson.name = 'electron-nightly';
  126. rootPackageJson.name = 'electron-nightly';
  127. fs.writeFileSync(
  128. path.join(tempDir, 'package.json'),
  129. JSON.stringify(currentJson, null, 2)
  130. );
  131. } else {
  132. if (currentBranch === 'main') {
  133. // This should never happen, main releases should be nightly releases
  134. // this is here just-in-case
  135. throw new Error('Unreachable release phase, can\'t tag a non-nightly release on the main branch');
  136. } else if (!release.prerelease) {
  137. // Tag the release with a `2-0-x` style tag
  138. npmTag = currentBranch;
  139. } else if (release.tag_name.indexOf('alpha') > 0) {
  140. // Tag the release with an `alpha-3-0-x` style tag
  141. npmTag = `alpha-${currentBranch}`;
  142. } else {
  143. // Tag the release with a `beta-3-0-x` style tag
  144. npmTag = `beta-${currentBranch}`;
  145. }
  146. }
  147. })
  148. .then(() => childProcess.execSync('npm pack', { cwd: tempDir }))
  149. .then(() => {
  150. // test that the package can install electron prebuilt from github release
  151. const tarballPath = path.join(tempDir, `${rootPackageJson.name}-${currentElectronVersion}.tgz`);
  152. return new Promise((resolve, reject) => {
  153. const result = childProcess.spawnSync('npm', ['install', tarballPath, '--force', '--silent'], {
  154. env: { ...process.env, electron_config_cache: tempDir },
  155. cwd: tempDir,
  156. stdio: 'inherit'
  157. });
  158. if (result.status !== 0) {
  159. return reject(new Error(`npm install failed with status ${result.status}`));
  160. }
  161. try {
  162. const electronPath = require(path.resolve(tempDir, 'node_modules', rootPackageJson.name));
  163. if (typeof electronPath !== 'string') {
  164. return reject(new Error(`path to electron binary (${electronPath}) returned by the ${rootPackageJson.name} module is not a string`));
  165. }
  166. if (!fs.existsSync(electronPath)) {
  167. return reject(new Error(`path to electron binary (${electronPath}) returned by the ${rootPackageJson.name} module does not exist on disk`));
  168. }
  169. } catch (e) {
  170. console.error(e);
  171. return reject(new Error(`loading the generated ${rootPackageJson.name} module failed with an error`));
  172. }
  173. resolve(tarballPath);
  174. });
  175. })
  176. .then((tarballPath) => {
  177. const existingVersionJSON = childProcess.execSync(`npx npm@7 view ${rootPackageJson.name}@${currentElectronVersion} --json`).toString('utf-8');
  178. // It's possible this is a re-run and we already have published the package, if not we just publish like normal
  179. if (!existingVersionJSON) {
  180. childProcess.execSync(`npm publish ${tarballPath} --tag ${npmTag} --otp=${process.env.ELECTRON_NPM_OTP}`);
  181. }
  182. })
  183. .then(() => {
  184. const currentTags = JSON.parse(childProcess.execSync('npm show electron dist-tags --json').toString());
  185. const parsedLocalVersion = semver.parse(currentElectronVersion)!;
  186. if (rootPackageJson.name === 'electron') {
  187. // We should only customly add dist tags for non-nightly releases where the package name is still
  188. // "electron"
  189. if (parsedLocalVersion.prerelease.length === 0 &&
  190. semver.gt(currentElectronVersion, currentTags.latest)) {
  191. childProcess.execSync(`npm dist-tag add electron@${currentElectronVersion} latest --otp=${process.env.ELECTRON_NPM_OTP}`);
  192. }
  193. if (parsedLocalVersion.prerelease[0] === 'beta' &&
  194. semver.gt(currentElectronVersion, currentTags.beta)) {
  195. childProcess.execSync(`npm dist-tag add electron@${currentElectronVersion} beta --otp=${process.env.ELECTRON_NPM_OTP}`);
  196. }
  197. if (parsedLocalVersion.prerelease[0] === 'alpha' &&
  198. semver.gt(currentElectronVersion, currentTags.alpha)) {
  199. childProcess.execSync(`npm dist-tag add electron@${currentElectronVersion} alpha --otp=${process.env.ELECTRON_NPM_OTP}`);
  200. }
  201. }
  202. })
  203. .catch((err) => {
  204. console.error('Error:', err);
  205. process.exit(1);
  206. });