package.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. /* eslint-env es2017 */
  2. const child_process = require('child_process');
  3. const crypto = require('crypto');
  4. const http = require('http');
  5. const https = require('https');
  6. const fs = require('fs');
  7. const os = require('os');
  8. const path = require('path');
  9. let configuration = null;
  10. const load = () => {
  11. const file = path.join(__dirname, 'package.json');
  12. const content = fs.readFileSync(file, 'utf-8');
  13. configuration = JSON.parse(content);
  14. };
  15. const write = (message) => {
  16. if (process.stdout.write) {
  17. process.stdout.write(message + os.EOL);
  18. }
  19. };
  20. const rm = (...args) => {
  21. write('rm ' + path.join(...args));
  22. const dir = path.join(__dirname, ...args);
  23. fs.rmSync(dir, { recursive: true, force: true });
  24. };
  25. const mkdir = (...args) => {
  26. write('mkdir ' + path.join(...args));
  27. const dir = path.join(__dirname, ...args);
  28. fs.mkdirSync(dir, { recursive: true });
  29. return dir;
  30. };
  31. const exec = (command) => {
  32. try {
  33. child_process.execSync(command, { cwd: __dirname, stdio: [ 0,1,2 ] });
  34. } catch (error) {
  35. process.exit(1);
  36. }
  37. };
  38. const sleep = (delay) => {
  39. return new Promise((resolve) => {
  40. setTimeout(resolve, delay);
  41. });
  42. };
  43. const install = () => {
  44. const node_modules = path.join(__dirname, 'node_modules');
  45. if (!fs.existsSync(node_modules)) {
  46. const options = { cwd: __dirname, stdio: [ 0,1,2 ] };
  47. child_process.execSync('npm install', options);
  48. }
  49. };
  50. const clean = () => {
  51. rm('dist');
  52. rm('node_modules');
  53. rm('package-lock.json');
  54. };
  55. const reset = () => {
  56. clean();
  57. rm('third_party', 'env');
  58. rm('third_party', 'source');
  59. };
  60. const build = async (target) => {
  61. switch (target || read()) {
  62. case 'web': {
  63. write('build web');
  64. rm('dist', 'web');
  65. mkdir('dist', 'web');
  66. write('cp source/dir dist/dir');
  67. const source_dir = path.join(__dirname, 'source');
  68. const dist_dir = path.join(__dirname, 'dist', 'web');
  69. const extensions = new Set([ 'html', 'css', 'js', 'json', 'ico', 'png' ]);
  70. for (const file of fs.readdirSync(source_dir)) {
  71. if (extensions.has(file.split('.').pop())) {
  72. fs.copyFileSync(path.join(source_dir, file), path.join(dist_dir, file));
  73. }
  74. }
  75. rm('dist', 'web', 'app.js');
  76. rm('dist', 'web', 'electron.js');
  77. const manifestFile = path.join(__dirname, 'package.json');
  78. const contentFile = path.join(__dirname, 'dist', 'web', 'index.html');
  79. const manifest = JSON.parse(fs.readFileSync(manifestFile, 'utf-8'));
  80. let content = fs.readFileSync(contentFile, 'utf-8');
  81. content = content.replace(/(<meta\s*name="version"\s*content=")(.*)(">)/m, (match, p1, p2, p3) => {
  82. return p1 + manifest.version + p3;
  83. });
  84. content = content.replace(/(<meta\s*name="date"\s*content=")(.*)(">)/m, (match, p1, p2, p3) => {
  85. return p1 + manifest.date + p3;
  86. });
  87. fs.writeFileSync(contentFile, content, 'utf-8');
  88. break;
  89. }
  90. case 'electron': {
  91. write('build electron');
  92. install();
  93. exec('npx electron-builder install-app-deps');
  94. exec('npx electron-builder install-app-deps');
  95. exec('npx electron-builder --mac --universal --publish never -c.mac.identity=null');
  96. exec('npx electron-builder --win --x64 --arm64 --publish never');
  97. exec('npx electron-builder --linux appimage --x64 --publish never');
  98. exec('npx electron-builder --linux snap --x64 --publish never');
  99. break;
  100. }
  101. case 'python': {
  102. write('build python');
  103. exec('python package.py build version');
  104. exec('python -m pip install --user build wheel --quiet');
  105. exec('python -m build --no-isolation --wheel --outdir dist/pypi dist/pypi');
  106. if (read('install')) {
  107. exec('python -m pip install --force-reinstall dist/pypi/*.whl');
  108. }
  109. break;
  110. }
  111. default: {
  112. write('build');
  113. rm('dist');
  114. install();
  115. await build('web');
  116. await build('electron');
  117. await build('python');
  118. break;
  119. }
  120. }
  121. };
  122. const publish = async (target) => {
  123. const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
  124. const GITHUB_USER = process.env.GITHUB_USER;
  125. switch (target || read()) {
  126. case 'web': {
  127. write('publish web');
  128. build('web');
  129. rm('dist', 'gh-pages');
  130. const url = 'https://x-access-token:' + GITHUB_TOKEN + '@github.com/' + GITHUB_USER + '/netron.git';
  131. exec('git clone --depth=1 ' + url + ' --branch gh-pages ./dist/gh-pages 2>&1 > /dev/null');
  132. write('cp dist/web dist/gh-pages');
  133. const source_dir = path.join(__dirname, 'dist', 'web');
  134. const target_dir = path.join(__dirname, 'dist', 'gh-pages');
  135. for (const file of fs.readdirSync(target_dir).filter((file) => file !== '.git')) {
  136. fs.unlinkSync(path.join(target_dir, file));
  137. }
  138. for (const file of fs.readdirSync(source_dir)) {
  139. fs.copyFileSync(path.join(source_dir, file), path.join(target_dir, file));
  140. }
  141. exec('git -C dist/gh-pages add --all');
  142. exec('git -C dist/gh-pages commit --amend --no-edit');
  143. exec('git -C dist/gh-pages push --force origin gh-pages');
  144. break;
  145. }
  146. case 'electron': {
  147. write('publish electron');
  148. install();
  149. exec('npx electron-builder install-app-deps');
  150. exec('npx electron-builder --mac --universal --publish always');
  151. exec('npx electron-builder --win --x64 --arm64 --publish always');
  152. exec('npx electron-builder --linux appimage --x64 --publish always');
  153. exec('npx electron-builder --linux snap --x64 --publish always');
  154. break;
  155. }
  156. case 'python': {
  157. write('publish python');
  158. build('python');
  159. exec('python -m pip install --user twine');
  160. exec('python -m twine upload --non-interactive --skip-existing --verbose dist/pypi/*.whl');
  161. break;
  162. }
  163. case 'cask': {
  164. write('publish cask');
  165. const authorization = 'Authorization: token ' + GITHUB_TOKEN;
  166. exec('curl -s -H "' + authorization + '" -X "DELETE" https://api.github.com/repos/' + GITHUB_USER + '/homebrew-cask 2>&1 > /dev/null');
  167. await sleep(4000);
  168. exec('curl -s -H "' + authorization + '"' + " https://api.github.com/repos/Homebrew/homebrew-cask/forks -d '' 2>&1 > /dev/null");
  169. rm('dist', 'homebrew-cask');
  170. exec('git clone --depth=2 https://x-access-token:' + GITHUB_TOKEN + '@github.com/' + GITHUB_USER + '/homebrew-cask.git ./dist/homebrew-cask');
  171. const repository = 'https://github.com/' + configuration.repository;
  172. const url = repository + '/releases/download/v#{version}/' + configuration.productName + '-#{version}-mac.zip';
  173. const location = url.replace(/#{version}/g, configuration.version);
  174. const sha256 = crypto.createHash('sha256').update(await get(location)).digest('hex').toLowerCase();
  175. const file = path.join(mkdir('dist', 'homebrew-cask', 'Casks'), 'netron.rb');
  176. fs.writeFileSync(file, [
  177. 'cask "' + configuration.name + '" do',
  178. ' version "' + configuration.version + '"',
  179. ' sha256 "' + sha256 + '"',
  180. '',
  181. ' url "' + url + '"',
  182. ' name "' + configuration.productName + '"',
  183. ' desc "' + configuration.description + '"',
  184. ' homepage "' + repository + '"',
  185. '',
  186. ' auto_updates true',
  187. '',
  188. ' app "' + configuration.productName + '.app"',
  189. 'end',
  190. ''
  191. ].join('\n'));
  192. exec('git -C dist/homebrew-cask add --all');
  193. exec('git -C dist/homebrew-cask commit -m "Update ' + configuration.name + ' to ' + configuration.version + '"');
  194. exec('git -C dist/homebrew-cask push');
  195. exec('curl -H "' + authorization + '"' + ' https://api.github.com/repos/Homebrew/homebrew-cask/pulls -d "{\\"title\\":\\"Update ' + configuration.name + ' to ' + configuration.version + '\\",\\"base\\":\\"master\\",\\"head\\":\\"' + GITHUB_USER + ':master\\",\\"body\\":\\"Update version and sha256.\\"}" 2>&1 > /dev/null');
  196. rm('dist', 'homebrew-cask');
  197. break;
  198. }
  199. case 'winget': {
  200. write('publish winget');
  201. const authorization = 'Authorization: token ' + GITHUB_TOKEN;
  202. write('delete github winget-pkgs');
  203. exec('curl -s -H "' + authorization + '" -X "DELETE" https://api.github.com/repos/' + GITHUB_USER + '/winget-pkgs');
  204. await sleep(4000);
  205. write('create github winget-pkgs');
  206. exec('curl -s -H "' + authorization + '" https://api.github.com/repos/microsoft/winget-pkgs/forks -d ""');
  207. rm('dist', 'winget-pkgs');
  208. await sleep(4000);
  209. write('clone github winget-pkgs');
  210. exec('git clone --depth=2 https://x-access-token:' + GITHUB_TOKEN + '@github.com/' + GITHUB_USER + '/winget-pkgs.git dist/winget-pkgs');
  211. const name = configuration.name;
  212. const version = configuration.version;
  213. const product = configuration.productName;
  214. const publisher = configuration.author.name;
  215. const identifier = publisher.replace(' ', '') + '.' + product;
  216. const copyright = 'Copyright (c) ' + publisher;
  217. const repository = 'https://github.com/' + configuration.repository;
  218. const url = repository + '/releases/download/v' + version + '/' + product + '-Setup-' + version + '.exe';
  219. const extensions = configuration.build.fileAssociations.map((entry) => '- ' + entry.ext).sort().join('\n');
  220. write('download ' + url);
  221. const sha256 = crypto.createHash('sha256').update(await get(url)).digest('hex').toUpperCase();
  222. const paths = [ 'dist', 'winget-pkgs', 'manifests', publisher[0].toLowerCase(), publisher.replace(' ', ''), product ];
  223. // rm(...paths);
  224. // exec('git -C dist/winget-pkgs add --all');
  225. // exec('git -C dist/winget-pkgs commit -m "Remove ' + configuration.name + '"');
  226. paths.push(version);
  227. mkdir(...paths);
  228. write('create manifest');
  229. const manifestFile = path.join(__dirname, ...paths, identifier);
  230. fs.writeFileSync(manifestFile + '.yaml', [
  231. '# yaml-language-server: $schema=https://aka.ms/winget-manifest.version.1.2.0.schema.json',
  232. 'PackageIdentifier: ' + identifier,
  233. 'PackageVersion: ' + version,
  234. 'DefaultLocale: en-US',
  235. 'ManifestType: version',
  236. 'ManifestVersion: 1.2.0',
  237. ''
  238. ].join('\n'));
  239. fs.writeFileSync(manifestFile + '.installer.yaml', [
  240. '# yaml-language-server: $schema=https://aka.ms/winget-manifest.installer.1.2.0.schema.json',
  241. 'PackageIdentifier: ' + identifier,
  242. 'PackageVersion: ' + version,
  243. 'Platform:',
  244. '- Windows.Desktop',
  245. 'InstallModes:',
  246. '- silent',
  247. '- silentWithProgress',
  248. 'Installers:',
  249. '- Architecture: x86',
  250. ' Scope: user',
  251. ' InstallerType: nullsoft',
  252. ' InstallerUrl: ' + url,
  253. ' InstallerSha256: ' + sha256,
  254. ' InstallerLocale: en-US',
  255. ' InstallerSwitches:',
  256. ' Custom: /NORESTART',
  257. ' UpgradeBehavior: install',
  258. '- Architecture: arm64',
  259. ' Scope: user',
  260. ' InstallerType: nullsoft',
  261. ' InstallerUrl: ' + url,
  262. ' InstallerSha256: ' + sha256,
  263. ' InstallerLocale: en-US',
  264. ' InstallerSwitches:',
  265. ' Custom: /NORESTART',
  266. ' UpgradeBehavior: install',
  267. 'FileExtensions:',
  268. extensions,
  269. 'ManifestType: installer',
  270. 'ManifestVersion: 1.2.0',
  271. ''
  272. ].join('\n'));
  273. fs.writeFileSync(manifestFile + '.locale.en-US.yaml', [
  274. '# yaml-language-server: $schema=https://aka.ms/winget-manifest.defaultLocale.1.2.0.schema.json',
  275. 'PackageIdentifier: ' + identifier,
  276. 'PackageVersion: ' + version,
  277. 'PackageName: ' + product,
  278. 'PackageLocale: en-US',
  279. 'PackageUrl: ' + repository,
  280. 'Publisher: ' + publisher,
  281. 'PublisherUrl: ' + repository,
  282. 'PublisherSupportUrl: ' + repository + '/issues',
  283. 'Author: ' + publisher,
  284. 'License: ' + configuration.license,
  285. 'Copyright: ' + copyright,
  286. 'CopyrightUrl: ' + repository + '/blob/main/LICENSE',
  287. 'ShortDescription: ' + configuration.description,
  288. 'Description: ' + configuration.description,
  289. 'Moniker: ' + name,
  290. 'Tags:',
  291. '- machine-learning',
  292. '- deep-learning',
  293. '- neural-network',
  294. 'ManifestType: defaultLocale',
  295. 'ManifestVersion: 1.2.0',
  296. ''
  297. ].join('\n'));
  298. write('commit manifest');
  299. exec('git -C dist/winget-pkgs add --all');
  300. exec('git -C dist/winget-pkgs commit -m "Update ' + configuration.name + ' to ' + configuration.version + '"');
  301. exec('git -C dist/winget-pkgs push');
  302. exec('curl -H "' + authorization + '" https://api.github.com/repos/microsoft/winget-pkgs/pulls -d "{\\"title\\":\\"Update ' + configuration.productName + ' to ' + configuration.version + '\\",\\"base\\":\\"master\\",\\"head\\":\\"' + GITHUB_USER + ':master\\",\\"body\\":\\"\\"}"');
  303. rm('dist', 'winget-pkgs');
  304. break;
  305. }
  306. default: {
  307. write('publish');
  308. rm('dist');
  309. install();
  310. await publish('web');
  311. await publish('electron');
  312. await publish('python');
  313. await publish('cask');
  314. await publish('winget');
  315. break;
  316. }
  317. }
  318. };
  319. const lint = () => {
  320. install();
  321. write('eslint');
  322. exec('npx eslint source/*.js test/*.js publish/*.js tools/*.js');
  323. write('pylint');
  324. exec('python -m pip install --upgrade --quiet pylint');
  325. exec('python -m pylint -sn --recursive=y source test publish tools');
  326. };
  327. const update = () => {
  328. const targets = process.argv.length > 3 ? process.argv.slice(3) : [
  329. 'armnn',
  330. 'bigdl',
  331. 'caffe',
  332. 'circle',
  333. 'cntk',
  334. 'coreml',
  335. 'dlc',
  336. 'dnn',
  337. 'mnn',
  338. 'mslite',
  339. 'megengine',
  340. 'nnabla',
  341. 'onnx',
  342. 'om',
  343. 'paddle',
  344. 'pytorch',
  345. 'rknn',
  346. 'sklearn',
  347. 'tf',
  348. 'uff',
  349. 'xmodel'
  350. ];
  351. for (const target of targets) {
  352. exec('tools/' + target + ' sync install schema metadata');
  353. }
  354. };
  355. const get = (url) => {
  356. return new Promise((resolve, reject) => {
  357. const httpModule = url.split(':').shift() === 'https' ? https : http;
  358. const request = httpModule.request(url, {}, (response) => {
  359. if (response.statusCode === 200) {
  360. const data = [];
  361. let position = 0;
  362. response.on('data', (chunk) => {
  363. data.push(chunk);
  364. position += chunk.length;
  365. process.stdout.write(' ' + position + ' bytes\r');
  366. });
  367. response.on('err', (err) => {
  368. reject(err);
  369. });
  370. response.on('end', () => {
  371. resolve(Buffer.concat(data));
  372. });
  373. } else if (response.statusCode === 302) {
  374. get(response.headers.location).then((data) => {
  375. resolve(data);
  376. }).catch((err) => {
  377. reject(err);
  378. });
  379. } else {
  380. const err = new Error("The web request failed with status code " + response.statusCode + " at '" + url + "'.");
  381. err.type = 'error';
  382. err.url = url;
  383. err.status = response.statusCode;
  384. reject(err);
  385. }
  386. });
  387. request.on("error", (err) => {
  388. reject(err);
  389. });
  390. request.end();
  391. });
  392. };
  393. const pull = () => {
  394. exec('git fetch --prune origin "refs/tags/*:refs/tags/*"');
  395. exec('git pull --prune --rebase');
  396. };
  397. const coverage = () => {
  398. rm('.nyc_output');
  399. rm('coverage');
  400. rm('dist', 'nyc');
  401. mkdir('dist', 'nyc');
  402. exec('cp package.json dist/nyc');
  403. exec('cp -R source dist/nyc');
  404. exec('nyc instrument --compact false source dist/nyc/source');
  405. exec('nyc --reporter=lcov --instrument npx electron ./dist/nyc');
  406. };
  407. const analyze = () => {
  408. if (!fs.existsSync('third_party/tools/codeql')) {
  409. exec('git clone --depth=1 https://github.com/github/codeql.git third_party/tools/codeql');
  410. }
  411. rm('dist', 'codeql');
  412. mkdir('dist', 'codeql', 'netron');
  413. exec('cp -r publish source test tools dist/codeql/netron/');
  414. exec('codeql database create dist/codeql/database --source-root dist/codeql/netron --language=javascript --threads=3');
  415. exec('codeql database analyze dist/codeql/database ./third_party/tools/codeql/javascript/ql/src/codeql-suites/javascript-security-and-quality.qls --format=csv --output=dist/codeql/results.csv --threads=3');
  416. exec('cat dist/codeql/results.csv');
  417. };
  418. const version = () => {
  419. const file = path.join(__dirname, 'package.json');
  420. let content = fs.readFileSync(file, 'utf-8');
  421. content = content.replace(/(\s*"version":\s")(\d\.\d\.\d)(",)/m, (match, p1, p2, p3) => {
  422. const version = Array.from((parseInt(p2.split('.').join(''), 10) + 1).toString()).join('.');
  423. return p1 + version + p3;
  424. });
  425. content = content.replace(/(\s*"date":\s")(.*)(",)/m, (match, p1, p2, p3) => {
  426. const date = new Date().toISOString().split('.').shift().split('T').join(' ');
  427. return p1 + date + p3;
  428. });
  429. fs.writeFileSync(file, content, 'utf-8');
  430. load();
  431. exec('git add package.json');
  432. exec('git commit -m "Update to ' + configuration.version + '"');
  433. exec('git tag v' + configuration.version);
  434. exec('git push');
  435. exec('git push --tags');
  436. };
  437. const args = process.argv.slice(2);
  438. const read = (match) => {
  439. if (args.length > 0 || (!match || args[0] === match)) {
  440. return args.shift();
  441. }
  442. return null;
  443. };
  444. const next = async () => {
  445. try {
  446. const task = read();
  447. switch (task) {
  448. case 'install': clean(); break;
  449. case 'clean': clean(); break;
  450. case 'reset': reset(); break;
  451. case 'build': await build(); break;
  452. case 'publish': await publish(); break;
  453. case 'version': version(); break;
  454. case 'lint': lint(); break;
  455. case 'update': update(); break;
  456. case 'pull': pull(); break;
  457. case 'analyze': analyze(); break;
  458. case 'coverage': coverage(); break;
  459. default: throw new Error("Unsupported task '" + task + "'.");
  460. }
  461. } catch (err) {
  462. write(err.message);
  463. }
  464. };
  465. load();
  466. next();