publish-to-npm.js 7.9 KB

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