squirrel-update-win.ts 3.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. import { spawn, ChildProcessWithoutNullStreams } from 'child_process';
  2. import * as fs from 'fs';
  3. import * as path from 'path';
  4. // i.e. my-app/app-0.1.13/
  5. const appFolder = path.dirname(process.execPath);
  6. // i.e. my-app/Update.exe
  7. const updateExe = path.resolve(appFolder, '..', 'Update.exe');
  8. const exeName = path.basename(process.execPath);
  9. let spawnedArgs: string[] = [];
  10. let spawnedProcess: ChildProcessWithoutNullStreams | undefined;
  11. const isSameArgs = (args: string[]) => args.length === spawnedArgs.length && args.every((e, i) => e === spawnedArgs[i]);
  12. // Spawn a command and invoke the callback when it completes with an error
  13. // and the output from standard out.
  14. const spawnUpdate = async function (args: string[], options: { detached: boolean }): Promise<string> {
  15. return new Promise((resolve, reject) => {
  16. // Ensure we don't spawn multiple squirrel processes
  17. // Process spawned, same args: Attach events to already running process
  18. // Process spawned, different args: Return with error
  19. // No process spawned: Spawn new process
  20. if (spawnedProcess && !isSameArgs(args)) {
  21. throw new Error(`AutoUpdater process with arguments ${args} is already running`);
  22. } else if (!spawnedProcess) {
  23. spawnedProcess = spawn(updateExe, args, {
  24. detached: options.detached,
  25. windowsHide: true
  26. });
  27. spawnedArgs = args || [];
  28. }
  29. let stdout = '';
  30. let stderr = '';
  31. spawnedProcess.stdout.on('data', (data) => { stdout += data; });
  32. spawnedProcess.stderr.on('data', (data) => { stderr += data; });
  33. spawnedProcess.on('error', (error) => {
  34. spawnedProcess = undefined;
  35. spawnedArgs = [];
  36. reject(error);
  37. });
  38. spawnedProcess.on('exit', function (code, signal) {
  39. spawnedProcess = undefined;
  40. spawnedArgs = [];
  41. if (code !== 0) {
  42. // Process terminated with error.
  43. reject(new Error(`Command failed: ${signal ?? code}\n${stderr}`));
  44. } else {
  45. // Success.
  46. resolve(stdout);
  47. }
  48. });
  49. });
  50. };
  51. // Start an instance of the installed app.
  52. export function processStart () {
  53. spawnUpdate(['--processStartAndWait', exeName], { detached: true });
  54. }
  55. // Download the releases specified by the URL and write new results to stdout.
  56. export async function checkForUpdate (updateURL: string): Promise<any> {
  57. const stdout = await spawnUpdate(['--checkForUpdate', updateURL], { detached: false });
  58. try {
  59. // Last line of output is the JSON details about the releases
  60. const json = stdout.trim().split('\n').pop();
  61. return JSON.parse(json!)?.releasesToApply?.pop?.();
  62. } catch {
  63. throw new Error(`Invalid result:\n${stdout}`);
  64. }
  65. }
  66. // Update the application to the latest remote version specified by URL.
  67. export async function update (updateURL: string): Promise<void> {
  68. await spawnUpdate(['--update', updateURL], { detached: false });
  69. }
  70. // Is the Update.exe installed with the current application?
  71. export function supported () {
  72. try {
  73. fs.accessSync(updateExe, fs.constants.R_OK);
  74. return true;
  75. } catch {
  76. return false;
  77. }
  78. }