package.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703
  1. import * as child_process from 'child_process';
  2. import * as crypto from 'crypto';
  3. import * as fs from 'fs/promises';
  4. import * as os from 'os';
  5. import * as path from 'path';
  6. import * as url from 'url';
  7. const args = process.argv.slice(2);
  8. const read = (match) => {
  9. if (args.length > 0 || (!match || args[0] === match)) {
  10. return args.shift();
  11. }
  12. return null;
  13. };
  14. let configuration = null;
  15. const dirname = (...args) => {
  16. const file = url.fileURLToPath(import.meta.url);
  17. const dir = path.dirname(file);
  18. return path.join(dir, ...args);
  19. };
  20. const load = async () => {
  21. const file = dirname('package.json');
  22. const content = await fs.readFile(file, 'utf-8');
  23. configuration = JSON.parse(content);
  24. };
  25. const clearLine = () => {
  26. if (process.stdout.clearLine) {
  27. process.stdout.clearLine();
  28. }
  29. };
  30. const write = (message) => {
  31. if (process.stdout.write) {
  32. process.stdout.write(message);
  33. }
  34. };
  35. const writeLine = (message) => {
  36. write(message + os.EOL);
  37. };
  38. const access = async (path) => {
  39. try {
  40. await fs.access(path);
  41. return true;
  42. } catch {
  43. return false;
  44. }
  45. };
  46. const rm = async (...args) => {
  47. const dir = dirname(...args);
  48. const exists = await access(dir);
  49. if (exists) {
  50. const paths = path.join(...args);
  51. writeLine(`rm ${paths}`);
  52. const options = { recursive: true, force: true };
  53. await fs.rm(dir, options);
  54. }
  55. };
  56. const mkdir = async (...args) => {
  57. const dir = dirname(...args);
  58. const exists = await access(dir);
  59. if (!exists) {
  60. const paths = path.join(...args);
  61. writeLine(`mkdir ${paths}`);
  62. const options = { recursive: true };
  63. await fs.mkdir(dir, options);
  64. }
  65. return dir;
  66. };
  67. const copy = async (source, target, filter) => {
  68. let files = await fs.readdir(source);
  69. files = filter ? files.filter((file) => filter(file)) : files;
  70. const promises = files.map((file) => fs.copyFile(path.join(source, file), path.join(target, file)));
  71. await Promise.all(promises);
  72. };
  73. const unlink = async (dir, filter) => {
  74. let files = await fs.readdir(dir);
  75. files = filter ? files.filter((file) => filter(file)) : files;
  76. const promises = files.map((file) => fs.unlink(path.join(dir, file)));
  77. await Promise.all(promises);
  78. };
  79. const exec = async (command, encoding, cwd) => {
  80. cwd = cwd || dirname();
  81. if (encoding) {
  82. return child_process.execSync(command, { cwd, encoding });
  83. }
  84. child_process.execSync(command, { cwd, stdio: [0,1,2] });
  85. return '';
  86. /*
  87. return new Promise((resolve, reject) => {
  88. const child = child_process.exec(command, { cwd: dirname() }, (error, stdout, stderr) => {
  89. if (error) {
  90. stderr = '\n' + stderr ;
  91. if (error.message && error.message.endsWith(stderr)) {
  92. error.message = error.message.slice(0, -stderr.length);
  93. }
  94. reject(error);
  95. } else {
  96. resolve(stdout);
  97. }
  98. });
  99. child.stdout.pipe(process.stdout);
  100. child.stderr.pipe(process.stderr);
  101. });
  102. */
  103. };
  104. const sleep = (delay) => {
  105. return new Promise((resolve) => {
  106. setTimeout(resolve, delay);
  107. });
  108. };
  109. const request = async (url, init, status) => {
  110. const response = await fetch(url, init);
  111. if (status !== false && !response.ok) {
  112. throw new Error(response.status.toString());
  113. }
  114. if (response.body) {
  115. const reader = response.body.getReader();
  116. let position = 0;
  117. const stream = new ReadableStream({
  118. start(controller) {
  119. const read = async () => {
  120. try {
  121. const result = await reader.read();
  122. if (result.done) {
  123. clearLine();
  124. controller.close();
  125. } else {
  126. position += result.value.length;
  127. write(` ${position} bytes\r`);
  128. controller.enqueue(result.value);
  129. read();
  130. }
  131. } catch (error) {
  132. controller.error(error);
  133. }
  134. };
  135. read();
  136. }
  137. });
  138. return new Response(stream, {
  139. status: response.status,
  140. statusText: response.statusText,
  141. headers: response.headers
  142. });
  143. }
  144. return response;
  145. };
  146. const download = async (url) => {
  147. writeLine(`download ${url}`);
  148. const response = await request(url);
  149. return response.arrayBuffer().then((buffer) => new Uint8Array(buffer));
  150. };
  151. const hash = async (url, algorithm) => {
  152. const data = await download(url);
  153. const hash = crypto.createHash(algorithm);
  154. hash.update(data);
  155. return hash.digest('hex');
  156. };
  157. const fork = async (organization, repository) => {
  158. const headers = {
  159. Authorization: `Bearer ${process.env.GITHUB_TOKEN}`
  160. };
  161. writeLine(`github delete ${repository}`);
  162. await request(`https://api.github.com/repos/${process.env.GITHUB_USER}/${repository}`, {
  163. method: 'DELETE',
  164. headers
  165. }, false);
  166. await sleep(4000);
  167. writeLine(`github fork ${repository}`);
  168. await request(`https://api.github.com/repos/${organization}/${repository}/forks`, {
  169. method: 'POST',
  170. headers,
  171. body: ''
  172. });
  173. await sleep(4000);
  174. await rm('dist', repository);
  175. writeLine(`github clone ${repository}`);
  176. await exec(`git clone --depth=2 https://x-access-token:${process.env.GITHUB_TOKEN}@github.com/${process.env.GITHUB_USER}/${repository}.git dist/${repository}`);
  177. };
  178. const pullrequest = async (organization, repository, body) => {
  179. writeLine(`github push ${repository}`);
  180. await exec(`git -C dist/${repository} push`);
  181. writeLine(`github pullrequest ${repository}`);
  182. const headers = {
  183. Authorization: `Bearer ${process.env.GITHUB_TOKEN}`
  184. };
  185. await request(`https://api.github.com/repos/${organization}/${repository}/pulls`, {
  186. method: 'POST',
  187. headers,
  188. body: JSON.stringify(body)
  189. });
  190. };
  191. const clean = async () => {
  192. await rm('dist');
  193. await rm('node_modules');
  194. await rm('package-lock.json');
  195. await rm('yarn.lock');
  196. };
  197. const install = async () => {
  198. const node_modules = dirname('node_modules');
  199. let exists = await access(node_modules);
  200. if (exists) {
  201. const dependencies = { ...configuration.dependencies, ...configuration.devDependencies };
  202. const matches = await Promise.all(Object.entries(dependencies).map(async ([name, version]) => {
  203. const file = path.join('node_modules', name, 'package.json');
  204. const exists = await access(file);
  205. if (exists) {
  206. const content = await fs.readFile(file, 'utf8');
  207. const obj = JSON.parse(content);
  208. return obj.version === version;
  209. }
  210. return false;
  211. }));
  212. exists = matches.every((match) => match);
  213. if (!exists) {
  214. await clean();
  215. }
  216. }
  217. exists = await access(node_modules);
  218. if (!exists) {
  219. await exec('npm install');
  220. }
  221. try {
  222. await exec('python --version', 'utf-8');
  223. await exec('python -m pip install --upgrade --quiet setuptools ruff');
  224. } catch {
  225. // continue regardless of error
  226. }
  227. };
  228. const start = async () => {
  229. await install();
  230. await exec('npx electron .');
  231. };
  232. const purge = async () => {
  233. await clean();
  234. await rm('third_party', 'bin');
  235. await rm('third_party', 'env');
  236. await rm('third_party', 'source');
  237. };
  238. const build = async (target) => {
  239. switch (target || read()) {
  240. case 'web': {
  241. writeLine('build web');
  242. await rm('dist', 'web');
  243. await mkdir('dist', 'web');
  244. writeLine('cp source/dir dist/dir');
  245. const source_dir = dirname('source');
  246. const dist_dir = dirname('dist', 'web');
  247. const extensions = new Set(['html', 'css', 'js', 'json', 'ico', 'png']);
  248. await copy(source_dir, dist_dir, (file) => extensions.has(file.split('.').pop()));
  249. await rm('dist', 'web', 'app.js');
  250. await rm('dist', 'web', 'node.js');
  251. await rm('dist', 'web', 'desktop.mjs');
  252. const contentFile = dirname('dist', 'web', 'index.html');
  253. let content = await fs.readFile(contentFile, 'utf-8');
  254. content = content.replace(/(<meta\s*name="version"\s*content=")(.*)(">)/m, (match, p1, p2, p3) => {
  255. return p1 + configuration.version + p3;
  256. });
  257. content = content.replace(/(<meta\s*name="date"\s*content=")(.*)(">)/m, (match, p1, p2, p3) => {
  258. return p1 + configuration.date + p3;
  259. });
  260. await fs.writeFile(contentFile, content, 'utf-8');
  261. break;
  262. }
  263. case 'electron': {
  264. const key = [read(), read()].filter((x) => x).join(' ');
  265. const target = key ? `electron ${key}` : 'electron';
  266. writeLine(`build ${target}`);
  267. await install();
  268. await exec('npx electron-builder install-app-deps');
  269. const table = new Map([
  270. ['mac', 'npx electron-builder --mac --universal --publish never -c.mac.identity=null'],
  271. ['windows', 'npx electron-builder --win --x64 --arm64 --publish never'],
  272. ['linux appimage', 'npx electron-builder --linux appimage --x64 --publish never'],
  273. ['linux snap', 'npx electron-builder --linux snap --x64 --publish never'],
  274. ]);
  275. const targets = table.has(key) ? [table.get(key)] : Array.from(table.values());
  276. for (const target of targets) {
  277. /* eslint-disable no-await-in-loop */
  278. await exec(target);
  279. /* eslint-enable no-await-in-loop */
  280. }
  281. break;
  282. }
  283. case 'python': {
  284. writeLine('build python');
  285. await exec('python package.py build version');
  286. await exec('python -m pip install --user build wheel --quiet');
  287. await exec('python -m build --wheel --outdir dist/pypi dist/pypi');
  288. if (read('install')) {
  289. await exec('python -m pip install --force-reinstall dist/pypi/*.whl');
  290. }
  291. break;
  292. }
  293. default: {
  294. writeLine('build');
  295. await rm('dist');
  296. await install();
  297. await build('web');
  298. await build('electron');
  299. await build('python');
  300. break;
  301. }
  302. }
  303. };
  304. const publish = async (target) => {
  305. const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
  306. const GITHUB_USER = process.env.GITHUB_USER;
  307. switch (target || read()) {
  308. case 'web': {
  309. writeLine('publish web');
  310. await build('web');
  311. await rm('dist', 'gh-pages');
  312. const url = `https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_USER}/netron.git`;
  313. await exec(`git clone --depth=1 ${url} --branch gh-pages ./dist/gh-pages 2>&1 > /dev/null`);
  314. writeLine('cp dist/web dist/gh-pages');
  315. const source_dir = dirname('dist', 'web');
  316. const target_dir = dirname('dist', 'gh-pages');
  317. await unlink(target_dir, (file) => file !== '.git');
  318. await copy(source_dir, target_dir);
  319. await exec('git -C dist/gh-pages add --all');
  320. await exec('git -C dist/gh-pages commit --amend --no-edit');
  321. await exec('git -C dist/gh-pages push --force origin gh-pages');
  322. break;
  323. }
  324. case 'electron': {
  325. writeLine('publish electron');
  326. await install();
  327. await exec('npx electron-builder install-app-deps');
  328. await exec('npx electron-builder --mac --universal --publish always');
  329. await exec('npx electron-builder --win --x64 --arm64 --publish always');
  330. await exec('npx electron-builder --linux appimage --x64 --publish always');
  331. await exec('npx electron-builder --linux snap --x64 --publish always');
  332. break;
  333. }
  334. case 'python': {
  335. writeLine('publish python');
  336. await build('python');
  337. await exec('python -m pip install --user twine');
  338. await exec('python -m twine upload --non-interactive --skip-existing --verbose dist/pypi/*.whl');
  339. break;
  340. }
  341. case 'cask': {
  342. writeLine('publish cask');
  343. await fork('Homebrew', 'homebrew-cask');
  344. const repository = `https://github.com/${configuration.repository}`;
  345. const url = `${repository}/releases/download/v#{version}/${configuration.productName}-#{version}-mac.zip`;
  346. const sha256 = await hash(url.replace(/#{version}/g, configuration.version), 'sha256');
  347. writeLine('update manifest');
  348. const dir = await mkdir('dist', 'homebrew-cask', 'Casks', 'n');
  349. const file = path.join(dir, 'netron.rb');
  350. await fs.writeFile(file, [
  351. `cask "${configuration.name}" do`,
  352. ` version "${configuration.version}"`,
  353. ` sha256 "${sha256.toLowerCase()}"`,
  354. '',
  355. ` url "${url}"`,
  356. ` name "${configuration.productName}"`,
  357. ` desc "${configuration.description.replace('Visualizer', 'Visualiser')}"`,
  358. ` homepage "${repository}"`,
  359. '',
  360. ' auto_updates true',
  361. '',
  362. ` app "${configuration.productName}.app"`,
  363. '',
  364. ' zap trash: [',
  365. ` "~/Library/Application Support/${configuration.productName}",`,
  366. ` "~/Library/Preferences/${configuration.build.appId}.plist",`,
  367. ` "~/Library/Saved Application State/${configuration.build.appId}.savedState",`,
  368. ' ]',
  369. 'end',
  370. ''
  371. ].join('\n'));
  372. writeLine('git push homebrew-cask');
  373. await exec('git -C dist/homebrew-cask add --all');
  374. await exec(`git -C dist/homebrew-cask commit -m "${configuration.name} ${configuration.version}"`);
  375. await pullrequest('Homebrew', 'homebrew-cask', {
  376. title: `${configuration.name} ${configuration.version}`,
  377. body: 'Update version and sha256',
  378. head: `${process.env.GITHUB_USER}:master`,
  379. base: 'master'
  380. });
  381. await rm('dist', 'homebrew-cask');
  382. break;
  383. }
  384. case 'winget': {
  385. writeLine('publish winget');
  386. await fork('microsoft', 'winget-pkgs');
  387. const name = configuration.name;
  388. const version = configuration.version;
  389. const product = configuration.productName;
  390. const publisher = configuration.author.name;
  391. const identifier = `${publisher.replace(' ', '')}.${product}`;
  392. const copyright = `Copyright (c) ${publisher}`;
  393. const repository = `https://github.com/${configuration.repository}`;
  394. const url = `${repository}/releases/download/v${version}/${product}-Setup-${version}.exe`;
  395. const content = await fs.readFile(configuration.build.extends, 'utf-8');
  396. const builder = JSON.parse(content);
  397. const extensions = builder.fileAssociations.map((entry) => `- ${entry.ext}`).sort().join('\n');
  398. const sha256 = await hash(url, 'sha256');
  399. const paths = ['dist', 'winget-pkgs', 'manifests', publisher[0].toLowerCase(), publisher.replace(' ', ''), product, version];
  400. await mkdir(...paths);
  401. writeLine('update manifest');
  402. const manifestFile = dirname(...paths, identifier);
  403. await fs.writeFile(`${manifestFile}.yaml`, [
  404. '# yaml-language-server: $schema=https://aka.ms/winget-manifest.version.1.6.0.schema.json',
  405. `PackageIdentifier: ${identifier}`,
  406. `PackageVersion: ${version}`,
  407. 'DefaultLocale: en-US',
  408. 'ManifestType: version',
  409. 'ManifestVersion: 1.6.0',
  410. ''
  411. ].join('\n'));
  412. await fs.writeFile(`${manifestFile}.installer.yaml`, [
  413. '# yaml-language-server: $schema=https://aka.ms/winget-manifest.installer.1.6.0.schema.json',
  414. `PackageIdentifier: ${identifier}`,
  415. `PackageVersion: ${version}`,
  416. 'Platform:',
  417. '- Windows.Desktop',
  418. 'InstallModes:',
  419. '- silent',
  420. '- silentWithProgress',
  421. 'Installers:',
  422. '- Architecture: x86',
  423. ' Scope: user',
  424. ' InstallerType: nullsoft',
  425. ` InstallerUrl: ${url}`,
  426. ` InstallerSha256: ${sha256.toUpperCase()}`,
  427. ' InstallerLocale: en-US',
  428. ' InstallerSwitches:',
  429. ' Custom: /NORESTART',
  430. ' UpgradeBehavior: install',
  431. '- Architecture: arm64',
  432. ' Scope: user',
  433. ' InstallerType: nullsoft',
  434. ` InstallerUrl: ${url}`,
  435. ` InstallerSha256: ${sha256.toUpperCase()}`,
  436. ' InstallerLocale: en-US',
  437. ' InstallerSwitches:',
  438. ' Custom: /NORESTART',
  439. ' UpgradeBehavior: install',
  440. 'FileExtensions:',
  441. extensions,
  442. 'ManifestType: installer',
  443. 'ManifestVersion: 1.6.0',
  444. ''
  445. ].join('\n'));
  446. await fs.writeFile(`${manifestFile}.locale.en-US.yaml`, [
  447. '# yaml-language-server: $schema=https://aka.ms/winget-manifest.defaultLocale.1.6.0.schema.json',
  448. `PackageIdentifier: ${identifier}`,
  449. `PackageVersion: ${version}`,
  450. `PackageName: ${product}`,
  451. 'PackageLocale: en-US',
  452. `PackageUrl: ${repository}`,
  453. `Publisher: ${publisher}`,
  454. `PublisherUrl: ${repository}`,
  455. `PublisherSupportUrl: ${repository}/issues`,
  456. `Author: ${publisher}`,
  457. `License: ${configuration.license}`,
  458. `Copyright: ${copyright}`,
  459. `CopyrightUrl: ${repository}/blob/main/LICENSE`,
  460. `ShortDescription: ${configuration.description}`,
  461. `Description: ${configuration.description}`,
  462. `Moniker: ${name}`,
  463. 'Tags:',
  464. '- machine-learning',
  465. '- deep-learning',
  466. '- neural-network',
  467. 'ManifestType: defaultLocale',
  468. 'ManifestVersion: 1.6.0',
  469. ''
  470. ].join('\n'));
  471. writeLine('git push winget-pkgs');
  472. await exec('git -C dist/winget-pkgs add --all');
  473. await exec(`git -C dist/winget-pkgs commit -m "Update ${configuration.name} to ${configuration.version}"`);
  474. await pullrequest('microsoft', 'winget-pkgs', {
  475. title: `Update ${configuration.productName} to ${configuration.version}`,
  476. body: '',
  477. head: `${process.env.GITHUB_USER}:master`,
  478. base: 'master'
  479. });
  480. await rm('dist', 'winget-pkgs');
  481. break;
  482. }
  483. default: {
  484. writeLine('publish');
  485. await rm('dist');
  486. await install();
  487. await publish('web');
  488. await publish('electron');
  489. await publish('python');
  490. await publish('cask');
  491. await publish('winget');
  492. break;
  493. }
  494. }
  495. };
  496. const lint = async () => {
  497. await install();
  498. writeLine('eslint');
  499. await exec('npx eslint *.*js source/*.*js test/*.*js publish/*.*js tools/*.js --cache --cache-location ./dist/lint/.eslintcache');
  500. writeLine('ruff');
  501. await exec('python -m ruff check . --quiet');
  502. };
  503. const validate = async() => {
  504. writeLine('lint');
  505. await lint();
  506. writeLine('test');
  507. await exec('node test/models.js tag:validation');
  508. };
  509. const update = async () => {
  510. const dependencies = { ...configuration.dependencies, ...configuration.devDependencies };
  511. for (const name of Object.keys(dependencies)) {
  512. writeLine(name);
  513. /* eslint-disable no-await-in-loop */
  514. await exec(`npm install --quiet --no-progress --silent --save-exact ${name}@latest`);
  515. /* eslint-enable no-await-in-loop */
  516. }
  517. await install();
  518. const targets = process.argv.length > 3 ? process.argv.slice(3) : [
  519. 'armnn',
  520. 'bigdl',
  521. 'caffe', 'circle', 'cntk', 'coreml',
  522. 'dlc', 'dnn',
  523. 'executorch',
  524. 'gguf',
  525. 'kann', 'keras',
  526. 'mnn', 'mslite', 'megengine',
  527. 'nnabla',
  528. 'onnx', 'om',
  529. 'paddle', 'pytorch',
  530. 'rknn',
  531. 'sentencepiece', 'sklearn',
  532. 'tf',
  533. 'uff',
  534. 'xmodel'
  535. ];
  536. for (const target of targets) {
  537. /* eslint-disable no-await-in-loop */
  538. await exec(`tools/${target} sync install schema metadata`);
  539. /* eslint-enable no-await-in-loop */
  540. }
  541. };
  542. const pull = async () => {
  543. await exec('git fetch --prune origin "refs/tags/*:refs/tags/*"');
  544. const before = await exec('git rev-parse HEAD', 'utf-8');
  545. try {
  546. await exec('git pull --prune --rebase --autostash');
  547. } catch (error) {
  548. writeLine(error.message);
  549. }
  550. const after = await exec('git rev-parse HEAD', 'utf-8');
  551. if (before.trim() !== after.trim()) {
  552. const output = await exec(`git diff --name-only ${before.trim()} ${after.trim()}`, 'utf-8');
  553. const files = new Set(output.split('\n'));
  554. if (files.has('package.json')) {
  555. await clean();
  556. await install();
  557. }
  558. }
  559. };
  560. const coverage = async () => {
  561. await rm('dist', 'nyc');
  562. await mkdir('dist', 'nyc');
  563. await exec('cp package.json dist/nyc');
  564. await exec('cp -R source dist/nyc');
  565. await exec('nyc instrument --compact false source dist/nyc/source');
  566. await exec('nyc --instrument npx electron ./dist/nyc');
  567. };
  568. const forge = async() => {
  569. const command = read();
  570. switch (command) {
  571. case 'install': {
  572. const packages = [
  573. '@electron-forge/cli',
  574. '@electron-forge/core',
  575. '@electron-forge/maker-snap',
  576. '@electron-forge/maker-dmg',
  577. '@electron-forge/maker-zip'
  578. ];
  579. await exec(`npm install ${packages.join(' ')} --no-save`);
  580. break;
  581. }
  582. case 'update': {
  583. const cwd = path.join(dirname(), '..', 'forge');
  584. const node_modules = path.join(cwd, 'node_modules');
  585. const links = path.join(cwd, '.links');
  586. const exists = await access(node_modules);
  587. if (!exists) {
  588. await exec('yarn', null, cwd);
  589. }
  590. await exec('yarn build', null, cwd);
  591. await exec('yarn link:prepare', null, cwd);
  592. await exec(`yarn link @electron-forge/core --link-folder=${links}`);
  593. break;
  594. }
  595. case 'build': {
  596. await exec('npx electron-forge make');
  597. break;
  598. }
  599. default: {
  600. throw new Error(`Unsupported forge command ${command}.`);
  601. }
  602. }
  603. };
  604. const analyze = async () => {
  605. const exists = await access('third_party/tools/codeql');
  606. if (!exists) {
  607. await exec('git clone --depth=1 https://github.com/github/codeql.git third_party/tools/codeql');
  608. }
  609. await rm('dist', 'codeql');
  610. await mkdir('dist', 'codeql', 'netron');
  611. await exec('cp -r publish source test tools dist/codeql/netron/');
  612. await exec('codeql database create dist/codeql/database --source-root dist/codeql/netron --language=javascript --threads=3');
  613. await 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');
  614. await exec('cat dist/codeql/results.csv');
  615. };
  616. const version = async () => {
  617. await pull();
  618. const file = dirname('package.json');
  619. let content = await fs.readFile(file, 'utf-8');
  620. content = content.replace(/(\s*"version":\s")(\d\.\d\.\d)(",)/m, (match, p1, p2, p3) => {
  621. const version = Array.from((parseInt(p2.split('.').join(''), 10) + 1).toString()).join('.');
  622. return p1 + version + p3;
  623. });
  624. content = content.replace(/(\s*"date":\s")(.*)(",)/m, (match, p1, p2, p3) => {
  625. const date = new Date().toISOString().split('.').shift().split('T').join(' ');
  626. return p1 + date + p3;
  627. });
  628. await fs.writeFile(file, content, 'utf-8');
  629. await exec('npm install --package-lock-only');
  630. await load();
  631. await exec('git add package.json');
  632. await exec('git add package-lock.json');
  633. await exec(`git commit -m "Update to ${configuration.version}"`);
  634. await exec(`git tag v${configuration.version}`);
  635. await exec('git push');
  636. await exec('git push --tags');
  637. };
  638. const main = async () => {
  639. await load();
  640. try {
  641. const task = read();
  642. switch (task) {
  643. case 'start': await start(); break;
  644. case 'clean': await clean(); break;
  645. case 'purge': await purge(); break;
  646. case 'install': await install(); break;
  647. case 'build': await build(); break;
  648. case 'publish': await publish(); break;
  649. case 'version': await version(); break;
  650. case 'lint': await lint(); break;
  651. case 'validate': await validate(); break;
  652. case 'update': await update(); break;
  653. case 'pull': await pull(); break;
  654. case 'analyze': await analyze(); break;
  655. case 'coverage': await coverage(); break;
  656. case 'forge': await forge(); break;
  657. default: throw new Error(`Unsupported task '${task}'.`);
  658. }
  659. } catch (error) {
  660. if (process.stdout.write) {
  661. process.stdout.write(error.message + os.EOL);
  662. }
  663. process.exit(1);
  664. }
  665. };
  666. await main();