graceful-updater 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
package/README.en.md ADDED
@@ -0,0 +1,145 @@
1
+ # graceful-updater
2
+
3
+ [![NPM version][npm-image]][npm-url]
4
+ [![CI][ci-image]][ci-url]
5
+ [![node version][node-image]][node-url]
6
+ [![npm download][download-image]][download-url]
7
+
8
+ [npm-image]: https://img.shields.io/npm/v/graceful-updater.svg
9
+ [npm-url]: https://npmjs.org/package/graceful-updater
10
+ [ci-image]: https://github.com/electron-modules/graceful-updater/actions/workflows/ci.yml/badge.svg
11
+ [ci-url]: https://github.com/electron-modules/graceful-updater/actions/workflows/ci.yml
12
+ [node-image]: https://img.shields.io/badge/node.js-%3E=_16-green.svg
13
+ [node-url]: http://nodejs.org/download/
14
+ [download-image]: https://img.shields.io/npm/dm/graceful-updater.svg
15
+ [download-url]: https://npmjs.org/package/graceful-updater
16
+
17
+ > Software updates solution for Electron applications, It is convenient to complete full software update and dynamic update.
18
+ ## Installment
19
+
20
+ ```bash
21
+ $ npm i graceful-updater --save
22
+ ```
23
+
24
+ ## Sample
25
+
26
+ please visit: https://github.com/electron-modules/electron-modules-sample
27
+
28
+ ```typescript
29
+ // 1. 构造 options
30
+ const options = {
31
+ url: getFeedUrl(),
32
+ logger: console, // logger
33
+ productName: 'demo',
34
+ updateInfoFormatter: (res) => {
35
+ return res;
36
+ },
37
+ ifNeedUpdate: (res) => {
38
+ console.log('local version', currentVersion);
39
+ console.log('local project version', currentBuildNumber);
40
+ console.log('remote version', res.version);
41
+ console.log('remote project version', res.project_version);
42
+ return semver.gt(res.version, currentVersion) ||
43
+ res.project_version > currentBuildNumber;
44
+ },
45
+ };
46
+ // 2. 初始化 updator 实例
47
+ const electronUpdator = new MacUpdator(options);
48
+ // 3. 绑定全局事件
49
+ electronUpdator.on(EventType.UPDATE_DOWNLOADED, (...args) => {
50
+ console.log('updator >> %s, args: %j', EventType.UPDATE_DOWNLOADED, args);
51
+ });
52
+ electronUpdator.on(EventType.CHECKING_FOR_UPDATE, (...args) => {
53
+ console.log('updator >> %s, args: %j', EventType.CHECKING_FOR_UPDATE, args);
54
+ });
55
+ electronUpdator.on(EventType.UPDATE_AVAILABLE, (data) => {
56
+ const { version, project_version } = data?.updateInfo || {};
57
+ const message = [
58
+ 'available',
59
+ `local version: ${currentVersion}`,
60
+ `local project version: ${currentBuildNumber}`,
61
+ `remote version: ${version}`,
62
+ `remote project version: ${project_version}`,
63
+ ].join('\n');
64
+ dialog.showMessageBoxSync({
65
+ message,
66
+ });
67
+ });
68
+ electronUpdator.on(EventType.UPDATE_NOT_AVAILABLE, (data) => {
69
+ const { version, project_version } = data?.updateInfo || {};
70
+ const message = [
71
+ 'not available',
72
+ `local version: ${currentVersion}`,
73
+ `local project version: ${currentBuildNumber}`,
74
+ `remote version: ${version}`,
75
+ `remote project version: ${project_version}`,
76
+ ].join('\n');
77
+ dialog.showMessageBoxSync({
78
+ message,
79
+ });
80
+ });
81
+ electronUpdator.on(EventType.ERROR, (...args) => {
82
+ console.log('updator >> %s, args: %j', EventType.ERROR, args);
83
+ });
84
+ electronUpdator.on(EventType.UPDATE_DOWNLOAD_PROGRESS, (data) => {
85
+ const { status, progress } = data;
86
+ console.log('updator >> %s, status: %s, progress: %d', EventType.UPDATE_DOWNLOAD_PROGRESS, status, progress);
87
+ app.windowManager.get('updator').webContents.send('updator:updateDownloadProgress', { status, progress });
88
+ });
89
+ ```
90
+
91
+ ## Documents
92
+
93
+ ### Options
94
+
95
+ | 字段 | 类型 | 是否必须 | 描述 | 备注 |
96
+ | --- | --- | --- | --- | --- |
97
+ | url | String | 必须 | 检测更新的远程地址,返回数据遵循 UpdateInfo 字段 | |
98
+ | ifNeedUpdate | Function | 非必须 | 返回是否需要更新 | |
99
+ | updateInfoFormatter | Function | 非必须 | 服务端返回数据格式适配 | 如果返回的格式无法与需要的字段相匹配时,参数进行格式化 |
100
+ | logger | Object | 非必须 | 日志 | |
101
+ | productName | String | 产品名 | 应用名 | |
102
+
103
+ ### UpdateInfo
104
+
105
+ | 字段 | 类型 | 是否必须 | 描述 | 备注 |
106
+ | --- | --- | --- | --- | --- |
107
+ | version | String | 必须 | 版本号 | |
108
+ | projectVersion | Number | 必须 | 构建号 | |
109
+ | files | Array<Object> | 必须 | 需要下载的文件列表 | |
110
+ | updateType | Enum<String> | 必须 | 更新类型,是否动态替换更新 | Package or Asar |
111
+ | releaseNotes | Array<String> | 必须 | 更新日志 | |
112
+
113
+ ### Methods
114
+
115
+ 1. checkForUpdates()
116
+
117
+ 检测是否有需要更新的内容
118
+
119
+ 2. downloadUpdate()
120
+
121
+ 开始下载
122
+
123
+ 3. quitAndInstall()
124
+
125
+ ### Events
126
+
127
+ 1. checking-for-update
128
+
129
+ 当开始检查更新的时候触发
130
+
131
+ <!-- GITCONTRIBUTOR_START -->
132
+
133
+ ## Contributors
134
+
135
+ |[<img src="https://avatars.githubusercontent.com/u/4081746?v=4" width="100px;"/><br/><sub><b>zlyi</b></sub>](https://github.com/zlyi)<br/>|[<img src="https://avatars.githubusercontent.com/u/1011681?v=4" width="100px;"/><br/><sub><b>xudafeng</b></sub>](https://github.com/xudafeng)<br/>|[<img src="https://avatars.githubusercontent.com/u/52845048?v=4" width="100px;"/><br/><sub><b>snapre</b></sub>](https://github.com/snapre)<br/>|
136
+ | :---: | :---: | :---: |
137
+
138
+
139
+ This project follows the git-contributor [spec](https://github.com/xudafeng/git-contributor), auto updated at `Tue Jan 31 2023 14:31:40 GMT+0800`.
140
+
141
+ <!-- GITCONTRIBUTOR_END -->
142
+
143
+ ## License
144
+
145
+ The MIT License (MIT)
package/README.md ADDED
@@ -0,0 +1,145 @@
1
+ # graceful-updater
2
+
3
+ [![NPM version][npm-image]][npm-url]
4
+ [![CI][ci-image]][ci-url]
5
+ [![node version][node-image]][node-url]
6
+ [![npm download][download-image]][download-url]
7
+
8
+ [npm-image]: https://img.shields.io/npm/v/graceful-updater.svg
9
+ [npm-url]: https://npmjs.org/package/graceful-updater
10
+ [ci-image]: https://github.com/electron-modules/graceful-updater/actions/workflows/ci.yml/badge.svg
11
+ [ci-url]: https://github.com/electron-modules/graceful-updater/actions/workflows/ci.yml
12
+ [node-image]: https://img.shields.io/badge/node.js-%3E=_16-green.svg
13
+ [node-url]: http://nodejs.org/download/
14
+ [download-image]: https://img.shields.io/npm/dm/graceful-updater.svg
15
+ [download-url]: https://npmjs.org/package/graceful-updater
16
+
17
+ > Software updates solution for Electron applications, It is convenient to complete full software update and dynamic update.
18
+ ## Installment
19
+
20
+ ```bash
21
+ $ npm i graceful-updater --save
22
+ ```
23
+
24
+ ## Sample
25
+
26
+ please visit: https://github.com/electron-modules/electron-modules-sample
27
+
28
+ ```typescript
29
+ // 1. 构造 options
30
+ const options = {
31
+ url: getFeedUrl(),
32
+ logger: console, // logger
33
+ productName: 'demo',
34
+ updateInfoFormatter: (res) => {
35
+ return res;
36
+ },
37
+ ifNeedUpdate: (res) => {
38
+ console.log('local version', currentVersion);
39
+ console.log('local project version', currentBuildNumber);
40
+ console.log('remote version', res.version);
41
+ console.log('remote project version', res.project_version);
42
+ return semver.gt(res.version, currentVersion) ||
43
+ res.project_version > currentBuildNumber;
44
+ },
45
+ };
46
+ // 2. 初始化 updator 实例
47
+ const electronUpdator = new MacUpdator(options);
48
+ // 3. 绑定全局事件
49
+ electronUpdator.on(EventType.UPDATE_DOWNLOADED, (...args) => {
50
+ console.log('updator >> %s, args: %j', EventType.UPDATE_DOWNLOADED, args);
51
+ });
52
+ electronUpdator.on(EventType.CHECKING_FOR_UPDATE, (...args) => {
53
+ console.log('updator >> %s, args: %j', EventType.CHECKING_FOR_UPDATE, args);
54
+ });
55
+ electronUpdator.on(EventType.UPDATE_AVAILABLE, (data) => {
56
+ const { version, project_version } = data?.updateInfo || {};
57
+ const message = [
58
+ 'available',
59
+ `local version: ${currentVersion}`,
60
+ `local project version: ${currentBuildNumber}`,
61
+ `remote version: ${version}`,
62
+ `remote project version: ${project_version}`,
63
+ ].join('\n');
64
+ dialog.showMessageBoxSync({
65
+ message,
66
+ });
67
+ });
68
+ electronUpdator.on(EventType.UPDATE_NOT_AVAILABLE, (data) => {
69
+ const { version, project_version } = data?.updateInfo || {};
70
+ const message = [
71
+ 'not available',
72
+ `local version: ${currentVersion}`,
73
+ `local project version: ${currentBuildNumber}`,
74
+ `remote version: ${version}`,
75
+ `remote project version: ${project_version}`,
76
+ ].join('\n');
77
+ dialog.showMessageBoxSync({
78
+ message,
79
+ });
80
+ });
81
+ electronUpdator.on(EventType.ERROR, (...args) => {
82
+ console.log('updator >> %s, args: %j', EventType.ERROR, args);
83
+ });
84
+ electronUpdator.on(EventType.UPDATE_DOWNLOAD_PROGRESS, (data) => {
85
+ const { status, progress } = data;
86
+ console.log('updator >> %s, status: %s, progress: %d', EventType.UPDATE_DOWNLOAD_PROGRESS, status, progress);
87
+ app.windowManager.get('updator').webContents.send('updator:updateDownloadProgress', { status, progress });
88
+ });
89
+ ```
90
+
91
+ ## Documents
92
+
93
+ ### Options
94
+
95
+ | 字段 | 类型 | 是否必须 | 描述 | 备注 |
96
+ | --- | --- | --- | --- | --- |
97
+ | url | String | 必须 | 检测更新的远程地址,返回数据遵循 UpdateInfo 字段 | |
98
+ | ifNeedUpdate | Function | 非必须 | 返回是否需要更新 | |
99
+ | updateInfoFormatter | Function | 非必须 | 服务端返回数据格式适配 | 如果返回的格式无法与需要的字段相匹配时,参数进行格式化 |
100
+ | logger | Object | 非必须 | 日志 | |
101
+ | productName | String | 产品名 | 应用名 | |
102
+
103
+ ### UpdateInfo
104
+
105
+ | 字段 | 类型 | 是否必须 | 描述 | 备注 |
106
+ | --- | --- | --- | --- | --- |
107
+ | version | String | 必须 | 版本号 | |
108
+ | projectVersion | Number | 必须 | 构建号 | |
109
+ | files | Array<Object> | 必须 | 需要下载的文件列表 | |
110
+ | updateType | Enum<String> | 必须 | 更新类型,是否动态替换更新 | Package or Asar |
111
+ | releaseNotes | Array<String> | 必须 | 更新日志 | |
112
+
113
+ ### Methods
114
+
115
+ 1. checkForUpdates()
116
+
117
+ 检测是否有需要更新的内容
118
+
119
+ 2. downloadUpdate()
120
+
121
+ 开始下载
122
+
123
+ 3. quitAndInstall()
124
+
125
+ ### Events
126
+
127
+ 1. checking-for-update
128
+
129
+ 当开始检查更新的时候触发
130
+
131
+ <!-- GITCONTRIBUTOR_START -->
132
+
133
+ ## Contributors
134
+
135
+ |[<img src="https://avatars.githubusercontent.com/u/4081746?v=4" width="100px;"/><br/><sub><b>zlyi</b></sub>](https://github.com/zlyi)<br/>|[<img src="https://avatars.githubusercontent.com/u/1011681?v=4" width="100px;"/><br/><sub><b>xudafeng</b></sub>](https://github.com/xudafeng)<br/>|[<img src="https://avatars.githubusercontent.com/u/52845048?v=4" width="100px;"/><br/><sub><b>snapre</b></sub>](https://github.com/snapre)<br/>|
136
+ | :---: | :---: | :---: |
137
+
138
+
139
+ This project follows the git-contributor [spec](https://github.com/xudafeng/git-contributor), auto updated at `Tue Jan 31 2023 14:31:40 GMT+0800`.
140
+
141
+ <!-- GITCONTRIBUTOR_END -->
142
+
143
+ ## License
144
+
145
+ The MIT License (MIT)
@@ -0,0 +1,247 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.AppUpdator = void 0;
7
+ const eventemitter3_1 = require("eventemitter3");
8
+ const nanoid_1 = require("nanoid");
9
+ const path_1 = __importDefault(require("path"));
10
+ const constants_1 = require("./common/constants");
11
+ const download_file_1 = require("./utils/download-file");
12
+ const utils_1 = require("./utils");
13
+ const elelctron_app_adapter_1 = require("./elelctron-app-adapter");
14
+ const DEFAULT_EXEFILE_DIR = path_1.default.join(__dirname, '..', 'helper');
15
+ class AppUpdator extends eventemitter3_1.EventEmitter {
16
+ constructor(options, app) {
17
+ super();
18
+ this.state = constants_1.StateType.Idle;
19
+ this.options = options;
20
+ this._windowHelperExeDir = this.options?.getWindowsHelperExeDir?.() || DEFAULT_EXEFILE_DIR;
21
+ this.logger = this._wrapLogger(options.logger);
22
+ this.app = app || new elelctron_app_adapter_1.ElectronAppAdapter();
23
+ this.startUuid = this._getStartUuid();
24
+ this.logger.info('constructor');
25
+ this.availableUpdate = {
26
+ resourcePath: '',
27
+ latestAsarPath: '',
28
+ downloadTargetDir: '',
29
+ };
30
+ }
31
+ _getStartUuid() {
32
+ return `${(0, nanoid_1.nanoid)(16)}${Date.now()}`;
33
+ }
34
+ _wrapLogger(logger) {
35
+ if (!logger) {
36
+ logger = console;
37
+ }
38
+ const _logger = { ...logger };
39
+ const _wrap = (message, args, callback) => {
40
+ callback(`[ElectronUpdator][${this.startUuid}]#${message}`, ...args);
41
+ };
42
+ _logger.error = (message, ...args) => {
43
+ _wrap(message, args, logger.error);
44
+ };
45
+ _logger.info = (message, ...args) => {
46
+ _wrap(message, args, logger.info);
47
+ };
48
+ _logger.warn = (message, ...args) => {
49
+ _wrap(message, args, logger.warn);
50
+ };
51
+ return _logger;
52
+ }
53
+ setState(state) {
54
+ this.logger.info(`setState:${state}`);
55
+ this.state = state;
56
+ }
57
+ setFeedUrl(url) {
58
+ this.logger.info(`setFeedUrl:url is ${url}`);
59
+ if (url && this.options) {
60
+ this.options.url = url;
61
+ }
62
+ }
63
+ async checkForUpdates(executeType = constants_1.ExecuteType.Auto) {
64
+ this.logger.info(`checkForUpdates:state is ${this.state}`);
65
+ this.setState(constants_1.StateType.Idle);
66
+ try {
67
+ // 新一轮更新流程,更新 startUuid
68
+ this.startUuid = this._getStartUuid();
69
+ this.setState(constants_1.StateType.CheckingForUpdate);
70
+ this.emit(constants_1.EventType.CHECKING_FOR_UPDATE);
71
+ const updateInfoResponse = await (0, utils_1.requestUpdateInfo)(this.options);
72
+ this.updateInfo = (this.options?.updateInfoFormatter ? this.options?.updateInfoFormatter(updateInfoResponse) : updateInfoResponse);
73
+ const ifNeedUpdate = this.options?.ifNeedUpdate(updateInfoResponse);
74
+ if (!ifNeedUpdate) {
75
+ this.logger.info(`checkForUpdates:updateInfo is ${JSON.stringify(this.updateInfo)},ifNeedUpdate is false`);
76
+ this.emit(constants_1.EventType.UPDATE_NOT_AVAILABLE, {
77
+ updateInfo: this.updateInfo,
78
+ executeType,
79
+ });
80
+ this.setState(constants_1.StateType.Idle);
81
+ return;
82
+ }
83
+ this.logger.info('checkForUpdates:ifNeedUpdate is true');
84
+ this.availableUpdate = this.doGetAvailableUpdateInfo(this.updateInfo);
85
+ if (!this.options?.autoDownload || executeType === constants_1.ExecuteType.User) {
86
+ this.logger.info('checkForUpdates:emit UPDATE_AVAILABLE');
87
+ this.emit(constants_1.EventType.UPDATE_AVAILABLE, {
88
+ updateInfo: this.updateInfo,
89
+ executeType,
90
+ });
91
+ return;
92
+ }
93
+ this.downloadUpdate(executeType);
94
+ }
95
+ catch (e) {
96
+ e.customMessage = e.customMessage ? e.customMessage : `${constants_1.InstallResultType.CheckForUpdatesError}_${e.message}`;
97
+ this.logError(e);
98
+ this.setState(constants_1.StateType.Idle);
99
+ }
100
+ }
101
+ async downloadUpdate(executeType = constants_1.ExecuteType.User) {
102
+ this.logger.info(`downloadUpdate:executeType is ${executeType}`);
103
+ await this.downloadUpdateFile(this.updateInfo, executeType);
104
+ const result = await this.preCheck(executeType);
105
+ if (result.success) {
106
+ this.logger.info('downloadUpdate:emit UPDATE_DOWNLOADED');
107
+ this.emit(constants_1.EventType.UPDATE_DOWNLOADED, {
108
+ executeType,
109
+ });
110
+ }
111
+ else {
112
+ this.logError(result.error);
113
+ this.setState(constants_1.StateType.Idle);
114
+ }
115
+ }
116
+ async quitAndInstall() {
117
+ this.logger.info(`quitAndInstall:state is ${this.state}`);
118
+ if (this.state !== constants_1.StateType.Downloaded) {
119
+ this.downloadUpdate();
120
+ return;
121
+ }
122
+ this.setState(constants_1.StateType.Idle);
123
+ try {
124
+ let result = { success: false };
125
+ this.emit(constants_1.EventType.BEFORE_QUIT_FOR_UPDATE);
126
+ if (this.updateInfo?.updateType === constants_1.UpdateType.Package) {
127
+ result = await this.doQuitAndInstallPackage();
128
+ }
129
+ else {
130
+ result = await this.doQuitAndInstallAsar();
131
+ }
132
+ if (!result.success) {
133
+ result.message = `error: ${result.error?.message}`;
134
+ this.dispatchError(result.error);
135
+ }
136
+ }
137
+ catch (e) {
138
+ this.dispatchError(e);
139
+ }
140
+ }
141
+ async preCheckForAsar() {
142
+ this.logger.info('preCheckForAsar');
143
+ return await this.unzip();
144
+ }
145
+ async preCheck(executeType) {
146
+ this.logger.info('preCheck');
147
+ const { resourcePath } = this.availableUpdate;
148
+ if (this.state !== constants_1.StateType.Downloaded) {
149
+ return {
150
+ success: false,
151
+ error: new Error(`preCheck:update status(${this.state}) error`),
152
+ };
153
+ }
154
+ // 清理老包
155
+ try {
156
+ this.logger.info('preCheck:cleanOldArchive');
157
+ await (0, utils_1.cleanOldArchive)(resourcePath);
158
+ }
159
+ catch (e) {
160
+ this.logError(e);
161
+ }
162
+ let result = { success: true };
163
+ const { downloadTargetDir } = this.availableUpdate;
164
+ try {
165
+ const hasLatestFile = await (0, utils_1.existsAsync)(downloadTargetDir);
166
+ // 下载失败返回提示
167
+ if (!hasLatestFile) {
168
+ return {
169
+ success: false,
170
+ error: new Error('file is notfound'),
171
+ };
172
+ }
173
+ }
174
+ catch (e) {
175
+ return {
176
+ success: false,
177
+ error: e,
178
+ };
179
+ }
180
+ if (this.updateInfo?.updateType === constants_1.UpdateType.Package) {
181
+ if (executeType === constants_1.ExecuteType.Auto) {
182
+ // 自动安装时,才进行预检查,提升安装效率
183
+ result = await this.doPreCheckForPackage();
184
+ }
185
+ }
186
+ else {
187
+ result = await this.preCheckForAsar();
188
+ }
189
+ return result;
190
+ }
191
+ async downloadUpdateFile(updateInfo, executeType) {
192
+ if (this.state !== constants_1.StateType.CheckingForUpdate) {
193
+ throw new Error(`downloadUpdateFile:update status(${this.state}) error`);
194
+ }
195
+ const { url, signature } = updateInfo.files[0];
196
+ const { downloadTargetDir } = this.availableUpdate;
197
+ this.setState(constants_1.StateType.Downloading);
198
+ try {
199
+ await (0, download_file_1.downloadFile)({
200
+ logger: this.logger,
201
+ url,
202
+ signature,
203
+ targetDir: downloadTargetDir,
204
+ emit: this.emit,
205
+ progressHandle: (data) => {
206
+ this.emit(constants_1.EventType.UPDATE_DOWNLOAD_PROGRESS, { ...data, executeType });
207
+ },
208
+ });
209
+ this.logger.info('downloadUpdateFile:Downloaded');
210
+ this.setState(constants_1.StateType.Downloaded);
211
+ }
212
+ catch (e) {
213
+ this.setState(constants_1.StateType.Idle);
214
+ e.customMessage = `${constants_1.InstallResultType.DownloadError}_${e.message}`;
215
+ this.logError(e);
216
+ }
217
+ }
218
+ async unzip() {
219
+ this.logger.info('unzip:start');
220
+ try {
221
+ const result = await this.doUnzip();
222
+ if (!result.success) {
223
+ return result;
224
+ }
225
+ const { latestAsarPath } = this.availableUpdate;
226
+ const exist = await (0, utils_1.existFile)(latestAsarPath);
227
+ return Promise.resolve({ success: exist });
228
+ }
229
+ catch (error) {
230
+ error.customMessage = constants_1.InstallResultType.UpdateUnzipError;
231
+ return Promise.resolve({
232
+ success: false,
233
+ error,
234
+ });
235
+ }
236
+ }
237
+ dispatchError(e) {
238
+ this.logError(e);
239
+ this.emit(constants_1.EventType.ERROR, e.message);
240
+ }
241
+ logError(e) {
242
+ const message = (e?.stack || e).toString();
243
+ this.logger.error(message);
244
+ }
245
+ }
246
+ exports.AppUpdator = AppUpdator;
247
+ //# sourceMappingURL=app-updator.js.map
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EventType = exports.DownloadProgressStatus = exports.ExecuteType = exports.UpdateType = exports.StateType = exports.InstallResultType = exports.OldArchivePrefix = void 0;
4
+ /**
5
+ * 更新包备份前缀
6
+ */
7
+ exports.OldArchivePrefix = 'old-';
8
+ /**
9
+ * 安装结果类型
10
+ */
11
+ var InstallResultType;
12
+ (function (InstallResultType) {
13
+ InstallResultType["UpdateUnzipError"] = "update-unzip-error";
14
+ InstallResultType["DownloadError"] = "download-error";
15
+ InstallResultType["UpdateSuccess"] = "update-success";
16
+ InstallResultType["CheckForUpdatesError"] = "check-for-updates-error";
17
+ })(InstallResultType = exports.InstallResultType || (exports.InstallResultType = {}));
18
+ /**
19
+ * 更新状态
20
+ */
21
+ var StateType;
22
+ (function (StateType) {
23
+ /**
24
+ * 空闲
25
+ */
26
+ StateType["Idle"] = "idle";
27
+ /**
28
+ * 检查更新中
29
+ */
30
+ StateType["CheckingForUpdate"] = "checking-for-update";
31
+ /**
32
+ * 下载中
33
+ */
34
+ StateType["Downloading"] = "downloading";
35
+ /**
36
+ * 下载完成
37
+ */
38
+ StateType["Downloaded"] = "downloaded";
39
+ })(StateType = exports.StateType || (exports.StateType = {}));
40
+ /**
41
+ * 更新类型
42
+ */
43
+ var UpdateType;
44
+ (function (UpdateType) {
45
+ /** 全量更新 */
46
+ UpdateType["Package"] = "package";
47
+ /** 动态更新 */
48
+ UpdateType["Asar"] = "asar";
49
+ /** 更新包更新 */
50
+ UpdateType["Assets"] = "assets";
51
+ })(UpdateType = exports.UpdateType || (exports.UpdateType = {}));
52
+ /**
53
+ * 执行类型
54
+ */
55
+ var ExecuteType;
56
+ (function (ExecuteType) {
57
+ /**
58
+ * 自动触发
59
+ */
60
+ ExecuteType["User"] = "user";
61
+ /**
62
+ * 手动触发
63
+ */
64
+ ExecuteType["Auto"] = "auto";
65
+ })(ExecuteType = exports.ExecuteType || (exports.ExecuteType = {}));
66
+ /**
67
+ * 执行类型
68
+ */
69
+ var DownloadProgressStatus;
70
+ (function (DownloadProgressStatus) {
71
+ /**
72
+ * 开始
73
+ */
74
+ DownloadProgressStatus["Begin"] = "begin";
75
+ /**
76
+ * 下载中
77
+ */
78
+ DownloadProgressStatus["Downloading"] = "downloading";
79
+ /**
80
+ * 结束
81
+ */
82
+ DownloadProgressStatus["End"] = "end";
83
+ })(DownloadProgressStatus = exports.DownloadProgressStatus || (exports.DownloadProgressStatus = {}));
84
+ /**
85
+ * 事件类型
86
+ */
87
+ var EventType;
88
+ (function (EventType) {
89
+ /**
90
+ * 下载完成
91
+ */
92
+ EventType["UPDATE_DOWNLOADED"] = "update-downloaded";
93
+ /**
94
+ * 下载进度
95
+ */
96
+ EventType["UPDATE_DOWNLOAD_PROGRESS"] = "update-download-progress";
97
+ /**
98
+ * 用户调用 quitAndInstall 之后发出,可在次事件后完成关闭窗口、重启等操作
99
+ */
100
+ EventType["BEFORE_QUIT_FOR_UPDATE"] = "before-quit-for-update";
101
+ /**
102
+ * 有可用更新
103
+ */
104
+ EventType["UPDATE_AVAILABLE"] = "update-available";
105
+ /**
106
+ * 开始检查更新
107
+ */
108
+ EventType["CHECKING_FOR_UPDATE"] = "checking-for-update";
109
+ /**
110
+ * 无可用更新
111
+ */
112
+ EventType["UPDATE_NOT_AVAILABLE"] = "update-not-available";
113
+ /**
114
+ * 错误
115
+ */
116
+ EventType["ERROR"] = "error";
117
+ })(EventType = exports.EventType || (exports.EventType = {}));
118
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ElectronAppAdapter = void 0;
4
+ const electron_1 = require("electron");
5
+ class ElectronAppAdapter {
6
+ constructor(app = electron_1.app) {
7
+ this.app = app;
8
+ }
9
+ get name() {
10
+ return this.app.getName();
11
+ }
12
+ get isPackaged() {
13
+ return this.app.isPackaged === true;
14
+ }
15
+ get userDataPath() {
16
+ return this.app.getPath('userData');
17
+ }
18
+ get exePath() {
19
+ return this.app.getPath('exe');
20
+ }
21
+ exit(code) {
22
+ this.app.exit(code);
23
+ }
24
+ relaunch(code) {
25
+ this.app.relaunch();
26
+ this.app.exit(code);
27
+ }
28
+ quit() {
29
+ this.app.quit();
30
+ }
31
+ }
32
+ exports.ElectronAppAdapter = ElectronAppAdapter;
33
+ //# sourceMappingURL=elelctron-app-adapter.js.map
Binary file
Binary file
package/build/index.js ADDED
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const constants_1 = require("./common/constants");
4
+ const mac_updator_1 = require("./mac-updator");
5
+ const windows_updator_1 = require("./windows-updator");
6
+ exports.default = {
7
+ UpdateType: constants_1.UpdateType,
8
+ EventType: constants_1.EventType,
9
+ ExecuteType: constants_1.ExecuteType,
10
+ StateType: constants_1.StateType,
11
+ MacUpdator: mac_updator_1.MacUpdator,
12
+ WindowsUpdator: windows_updator_1.WindowsUpdator,
13
+ DownloadProgressStatus: constants_1.DownloadProgressStatus,
14
+ };
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.MacUpdator = void 0;
7
+ const path_1 = __importDefault(require("path"));
8
+ const app_updator_1 = require("./app-updator");
9
+ const constants_1 = require("./common/constants");
10
+ const install_macos_dmg_1 = __importDefault(require("./utils/install-macos-dmg"));
11
+ const utils_1 = require("./utils");
12
+ class MacUpdator extends app_updator_1.AppUpdator {
13
+ doGetAvailableUpdateInfo(updateInfo) {
14
+ this.logger.info('ElectronUpdator#MacUpdator#doGetAvailableUpdateInfo:start');
15
+ const exePath = this.app.exePath;
16
+ const resourcePath = path_1.default.resolve(exePath, '..', '..', 'Resources');
17
+ const latestAsarPath = path_1.default.resolve(resourcePath, 'latest.asar');
18
+ const latestAppPath = path_1.default.resolve(resourcePath, 'latest');
19
+ let downloadTargetDir = `${latestAsarPath}.zip`;
20
+ if (updateInfo.updateType === constants_1.UpdateType.Package) {
21
+ downloadTargetDir = `${latestAppPath}.dmg`;
22
+ }
23
+ return {
24
+ resourcePath,
25
+ downloadTargetDir,
26
+ latestAsarPath,
27
+ };
28
+ }
29
+ async doPreCheckForPackage() {
30
+ this.logger.info('ElectronUpdator#MacUpdator#doPreCheckForPackage:start');
31
+ // Mac 全量安装前,先进行 dmg 安装检查
32
+ return await (0, install_macos_dmg_1.default)(this.options, this.logger, this.availableUpdate, this.updateInfo, true);
33
+ }
34
+ /**
35
+ * 资源解压
36
+ * @return
37
+ */
38
+ async doUnzip() {
39
+ this.logger.info('MacUpdator#doUnzip:start');
40
+ const { resourcePath, downloadTargetDir } = this.availableUpdate;
41
+ try {
42
+ // 直接解压
43
+ await (0, utils_1.execAsync)(`unzip -o ${downloadTargetDir}`, {
44
+ cwd: resourcePath,
45
+ maxBuffer: 2 ** 28,
46
+ });
47
+ return {
48
+ success: true,
49
+ };
50
+ }
51
+ catch (error) {
52
+ return {
53
+ success: false,
54
+ error,
55
+ };
56
+ }
57
+ }
58
+ async doQuitAndInstallAsar() {
59
+ this.logger.info('ElectronUpdator#MacUpdator#doQuitAndInstallAsar:start');
60
+ if (!this.availableUpdate) {
61
+ this.logger.error('ElectronUpdator#MacUpdator#doQuitAndInstallAsar:not availableUpdate');
62
+ return Promise.resolve({ success: false });
63
+ }
64
+ const { resourcePath, latestAsarPath } = this.availableUpdate;
65
+ const oldAsarPath = path_1.default.resolve(resourcePath, `${constants_1.OldArchivePrefix}${new Date().getTime()}.asar`);
66
+ const currentAsarPath = path_1.default.resolve(resourcePath, 'app.asar');
67
+ try {
68
+ // 将老包改名 app.asar => old-xxxx.asar
69
+ if (await (0, utils_1.existsAsync)(currentAsarPath)) {
70
+ await (0, utils_1.renameAsync)(currentAsarPath, oldAsarPath);
71
+ }
72
+ }
73
+ catch (error) {
74
+ return {
75
+ success: false,
76
+ error,
77
+ };
78
+ }
79
+ try {
80
+ // 新包替换
81
+ await (0, utils_1.renameAsync)(latestAsarPath, currentAsarPath);
82
+ }
83
+ catch (error) {
84
+ // 替换出错,需要把老包还原
85
+ await (0, utils_1.renameAsync)(oldAsarPath, currentAsarPath);
86
+ return {
87
+ success: false,
88
+ error,
89
+ };
90
+ }
91
+ this.logger.warn('quitAndInstall:install success');
92
+ this.app.relaunch();
93
+ return {
94
+ success: true,
95
+ };
96
+ }
97
+ async doQuitAndInstallPackage() {
98
+ this.logger.info('ElectronUpdator#doQuitAndInstallPackage:start');
99
+ const result = await (0, install_macos_dmg_1.default)(this.options, this.logger, this.availableUpdate, this.updateInfo);
100
+ if (result.success) {
101
+ this.logger.warn('quitAndInstall:install success');
102
+ this.app.relaunch();
103
+ }
104
+ return result;
105
+ }
106
+ }
107
+ exports.MacUpdator = MacUpdator;
108
+ //# sourceMappingURL=mac-updator.js.map
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.downloadFile = void 0;
7
+ const urllib_1 = __importDefault(require("urllib"));
8
+ const constants_1 = require("../common/constants");
9
+ const _1 = require(".");
10
+ /**
11
+ * 文件下载
12
+ * @param updator 更新实例
13
+ * @param url 下载地址
14
+ * @param signature 下载签名
15
+ * @param targeDir 下载目录
16
+ * @param progressHandle 下载状态回调
17
+ * @return
18
+ */
19
+ const downloadFile = async ({ logger, url, signature, targetDir, progressHandle }) => {
20
+ logger.info('downloadFile#downloadFile (start)');
21
+ const writeStream = (0, _1.createWriteStream)(targetDir);
22
+ let currentLength = 0;
23
+ let currentProgress = 0;
24
+ progressHandle({
25
+ status: constants_1.DownloadProgressStatus.Begin,
26
+ });
27
+ return new Promise((resolve, reject) => {
28
+ urllib_1.default
29
+ .request(url, {
30
+ streaming: true,
31
+ followRedirect: true,
32
+ timeout: 10 * 60 * 1000,
33
+ })
34
+ .then((res) => {
35
+ const totalLength = res.headers['content-length'];
36
+ logger.info(`downloadFile#downloadFile (then),totalLength is ${totalLength}`);
37
+ res.res.on('data', (data) => {
38
+ try {
39
+ currentLength += data.length;
40
+ const progress = (currentLength / totalLength) * 100;
41
+ if (progress > currentProgress) {
42
+ currentProgress++;
43
+ }
44
+ progressHandle({
45
+ status: constants_1.DownloadProgressStatus.Downloading,
46
+ progress: currentProgress,
47
+ url,
48
+ signature,
49
+ data,
50
+ });
51
+ writeStream.write(data);
52
+ }
53
+ catch (e) {
54
+ reject(e);
55
+ }
56
+ });
57
+ res.res.on('end', () => {
58
+ // 推迟调用 end(): https://stackoverflow.com/a/53878933
59
+ process.nextTick(() => writeStream.end());
60
+ try {
61
+ progressHandle({
62
+ status: constants_1.DownloadProgressStatus.End,
63
+ url,
64
+ signature,
65
+ });
66
+ logger.info('downloadFile#download file success, url:%s, to %s', url, targetDir);
67
+ resolve();
68
+ }
69
+ catch (e) {
70
+ reject(e);
71
+ }
72
+ });
73
+ res.res.on('error', reject);
74
+ });
75
+ });
76
+ };
77
+ exports.downloadFile = downloadFile;
78
+ //# sourceMappingURL=download-file.js.map
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getExecuteFile = exports.requestUpdateInfo = exports.existFile = exports.cleanOldArchive = exports.spawnAsync = exports.waitUntil = exports.sleep = exports.getMacOSAppPath = exports.sudoPrompt = exports.existsSync = exports.createWriteStream = exports.isMac = exports.isWin = exports.execAsync = exports.rimrafAsync = exports.readdirAsync = exports.existsAsync = exports.renameAsync = void 0;
7
+ const path_1 = __importDefault(require("path"));
8
+ const util_1 = __importDefault(require("util"));
9
+ const urllib_1 = __importDefault(require("urllib"));
10
+ const sudo_prompt_alt_1 = __importDefault(require("sudo-prompt-alt"));
11
+ exports.sudoPrompt = sudo_prompt_alt_1.default;
12
+ const rimraf_alt_1 = __importDefault(require("rimraf-alt"));
13
+ const child_process_1 = require("child_process");
14
+ const original_fs_1 = require("original-fs");
15
+ Object.defineProperty(exports, "existsSync", { enumerable: true, get: function () { return original_fs_1.existsSync; } });
16
+ Object.defineProperty(exports, "createWriteStream", { enumerable: true, get: function () { return original_fs_1.createWriteStream; } });
17
+ const constants_1 = require("../common/constants");
18
+ exports.renameAsync = util_1.default.promisify(original_fs_1.rename);
19
+ exports.existsAsync = util_1.default.promisify(original_fs_1.exists);
20
+ exports.readdirAsync = util_1.default.promisify(original_fs_1.readdir);
21
+ exports.rimrafAsync = util_1.default.promisify(rimraf_alt_1.default);
22
+ exports.execAsync = util_1.default.promisify(child_process_1.exec);
23
+ exports.isWin = process.platform === 'win32';
24
+ exports.isMac = process.platform === 'darwin';
25
+ const getMacOSAppPath = () => {
26
+ const sep = path_1.default.sep;
27
+ const execPath = process.execPath;
28
+ const contentPath = ['', 'Contents', 'MacOS'].join(sep);
29
+ return execPath.split(contentPath)[0];
30
+ };
31
+ exports.getMacOSAppPath = getMacOSAppPath;
32
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
33
+ exports.sleep = sleep;
34
+ const waitUntil = async (handle, options = { retryTime: 10, ms: 1000 }) => {
35
+ let retryTime = 0;
36
+ const p = async () => {
37
+ const isOk = handle();
38
+ if (!isOk) {
39
+ if (retryTime === options.retryTime) {
40
+ return false;
41
+ }
42
+ retryTime++;
43
+ await (0, exports.sleep)(options.ms);
44
+ return await p();
45
+ }
46
+ return true;
47
+ };
48
+ return await p();
49
+ };
50
+ exports.waitUntil = waitUntil;
51
+ const spawnAsync = (command, options) => {
52
+ let stdout = '';
53
+ let stderr = '';
54
+ const child = (0, child_process_1.spawn)(command, options);
55
+ child.stdout.on('data', (data) => {
56
+ stdout += data;
57
+ });
58
+ child.stderr.on('data', (data) => {
59
+ stderr += data;
60
+ });
61
+ return new Promise((resolve, reject) => {
62
+ child.on('error', reject);
63
+ child.on('close', (code) => {
64
+ if (code === 0) {
65
+ return resolve(stdout.toString());
66
+ }
67
+ const err = new Error(`child exited with code ${code}`);
68
+ err.code = code;
69
+ err.stderr = stderr.toString();
70
+ reject(err);
71
+ });
72
+ });
73
+ };
74
+ exports.spawnAsync = spawnAsync;
75
+ const cleanOldArchive = async (resourcePath) => {
76
+ const resourceArray = await (0, exports.readdirAsync)(resourcePath);
77
+ if (!resourceArray || !resourceArray.length) {
78
+ return;
79
+ }
80
+ const oldArchiveSource = resourceArray.filter((item) => item.startsWith(constants_1.OldArchivePrefix));
81
+ return await Promise.all(oldArchiveSource.map(async (file) => {
82
+ const filePath = path_1.default.join(resourcePath, file);
83
+ if (await (0, exports.existsAsync)(filePath)) {
84
+ await (0, exports.rimrafAsync)(filePath);
85
+ }
86
+ }));
87
+ };
88
+ exports.cleanOldArchive = cleanOldArchive;
89
+ const existFile = async (path) => {
90
+ const _existFile = await (0, exports.waitUntil)(() => (0, original_fs_1.existsSync)(path), {
91
+ ms: 1000,
92
+ retryTime: 30,
93
+ });
94
+ return _existFile;
95
+ };
96
+ exports.existFile = existFile;
97
+ const requestUpdateInfo = async (options) => {
98
+ const { url } = options;
99
+ if (!url) {
100
+ throw new Error('request url can\'t be empty');
101
+ }
102
+ let res = null;
103
+ try {
104
+ res = await urllib_1.default.request(url, {
105
+ dataType: 'json',
106
+ timeout: 10 * 1000,
107
+ });
108
+ }
109
+ catch (e) {
110
+ throw e;
111
+ }
112
+ if (!res || res.status !== 200) {
113
+ throw new Error(`request failure,status is ${res.status}`);
114
+ }
115
+ return res.data;
116
+ };
117
+ exports.requestUpdateInfo = requestUpdateInfo;
118
+ const getExecuteFile = (helperExeFileDir, file) => {
119
+ return path_1.default.join(helperExeFileDir, file);
120
+ };
121
+ exports.getExecuteFile = getExecuteFile;
122
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const os_1 = __importDefault(require("os"));
7
+ const path_1 = __importDefault(require("path"));
8
+ const _1 = require(".");
9
+ const index_1 = require("./index");
10
+ const _log = (logger, e) => {
11
+ const message = (e.stack || e).toString();
12
+ logger.error?.(message);
13
+ };
14
+ exports.default = async (updatorOptions, logger, availableUpdate, updateInfo, preCheck = false) => {
15
+ const { productName } = updatorOptions || {};
16
+ const { downloadTargetDir } = availableUpdate || {};
17
+ const appPath = (0, _1.getMacOSAppPath)();
18
+ const tmpPath = path_1.default.join(os_1.default.tmpdir(), String(new Date().getTime())); // 本地临时文件使用的目录
19
+ const volumesPath = path_1.default.join('/Volumes', updatorOptions.dmgTitleFormatter ? updatorOptions.dmgTitleFormatter(updatorOptions, updateInfo) : productName);
20
+ const volumesAppPath = path_1.default.join(volumesPath, `${productName}.app`);
21
+ // step 1: 检测是否支持 hdiutil;
22
+ try {
23
+ const res = (await (0, index_1.spawnAsync)('which', ['hdiutil']));
24
+ if (!res.includes('/bin/hdiutil')) {
25
+ throw new Error('hdiutil not found');
26
+ }
27
+ }
28
+ catch (error) {
29
+ if (preCheck) {
30
+ _log(logger, error);
31
+ }
32
+ return {
33
+ success: false,
34
+ error,
35
+ };
36
+ }
37
+ // step 2: eject 一次 volume 下的 app,兜底,避免上一次结束时执行失败
38
+ try {
39
+ await (0, index_1.spawnAsync)('hdiutil', ['eject', volumesPath]);
40
+ }
41
+ catch (e) {
42
+ // 如果前一次更新时 eject 成功,这里文件不存在会报错,是个正常的 case,不用处理上报
43
+ }
44
+ finally {
45
+ const volumeAppNotExist = await (0, _1.waitUntil)(() => !(0, index_1.existsSync)(volumesAppPath), {
46
+ ms: 300,
47
+ retryTime: 5,
48
+ });
49
+ if (!volumeAppNotExist) {
50
+ const error = new Error('volume not exists');
51
+ if (preCheck) {
52
+ _log(logger, error);
53
+ }
54
+ // eslint-disable-next-line
55
+ return {
56
+ success: false,
57
+ error,
58
+ };
59
+ }
60
+ }
61
+ // step 3: 执行 hdiutil attach,挂载 dmg
62
+ try {
63
+ await (0, index_1.spawnAsync)('hdiutil', ['attach', downloadTargetDir]);
64
+ }
65
+ catch (error) {
66
+ _log(logger, error);
67
+ }
68
+ finally {
69
+ // 判断有没有新的 dmg 文件,没有的话提示用户重新下载
70
+ const volumeAppExist = await (0, _1.waitUntil)(() => (0, index_1.existsSync)(volumesAppPath), {
71
+ ms: 300,
72
+ retryTime: 5,
73
+ });
74
+ if (!volumeAppExist) {
75
+ const error = new Error('attach fail');
76
+ if (preCheck) {
77
+ _log(logger, error);
78
+ }
79
+ // eslint-disable-next-line
80
+ return {
81
+ success: false,
82
+ error,
83
+ };
84
+ }
85
+ }
86
+ if (preCheck) {
87
+ // 如果是预检查,直接返回
88
+ try {
89
+ await (0, index_1.spawnAsync)('hdiutil', ['eject', volumesPath]);
90
+ }
91
+ catch (error) {
92
+ _log(logger, error);
93
+ }
94
+ return {
95
+ success: true,
96
+ };
97
+ }
98
+ // step 4: 将当前目录下的 app 移到临时目录,如果后续操作失败了兜底用
99
+ try {
100
+ await (0, index_1.spawnAsync)('mv', [appPath, tmpPath]);
101
+ }
102
+ catch (error) {
103
+ error.customMessage = 'step4 mv to tmp path error';
104
+ _log(logger, error);
105
+ }
106
+ finally {
107
+ // 看临时目录文件是否移动成功
108
+ const tmpPathExist = await (0, _1.waitUntil)(() => (0, index_1.existsSync)(tmpPath), {
109
+ ms: 300,
110
+ retryTime: 5,
111
+ });
112
+ if (!tmpPathExist) {
113
+ const error = new Error('cp to tmp path fail');
114
+ // eslint-disable-next-line
115
+ return {
116
+ success: false,
117
+ error,
118
+ };
119
+ }
120
+ }
121
+ // step 5: 将新的 app 文件移动到 Applications 目录下,如果失败 or 查不到移入文件,将临时目录下的文件移动回来
122
+ try {
123
+ await (0, index_1.spawnAsync)('cp', ['-R', volumesAppPath, appPath]);
124
+ }
125
+ catch (error) {
126
+ _log(logger, error);
127
+ }
128
+ finally {
129
+ const appExist = await (0, _1.waitUntil)(() => (0, index_1.existsSync)(appPath), {
130
+ ms: 300,
131
+ retryTime: 5,
132
+ });
133
+ // 查不到新移入的 dmg,将原 dmg 再移动回来
134
+ if (!appExist) {
135
+ const error = new Error('cp to app fail');
136
+ await (0, index_1.spawnAsync)('mv', [tmpPath, appPath]);
137
+ // eslint-disable-next-line
138
+ return {
139
+ success: false,
140
+ error,
141
+ };
142
+ }
143
+ }
144
+ // step 6: 执行 hdiutil eject,推出
145
+ try {
146
+ await (0, index_1.spawnAsync)('hdiutil', ['eject', volumesPath]);
147
+ }
148
+ catch (error) {
149
+ _log(logger, error);
150
+ }
151
+ return {
152
+ success: true,
153
+ };
154
+ };
155
+ //# sourceMappingURL=install-macos-dmg.js.map
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sudoPromptExec = void 0;
4
+ const index_1 = require("./index");
5
+ function sudoPromptExec(logger, shell) {
6
+ const options = {
7
+ name: 'SoftwareUpdate',
8
+ };
9
+ return new Promise((resolve, reject) => {
10
+ logger.warn(`sudoPromptExec#_shell_${shell}`);
11
+ index_1.sudoPrompt.exec(shell, options, (error, stdout) => {
12
+ if (error) {
13
+ reject(error);
14
+ logger.error(`sudoPromptExec#error_${error}`);
15
+ return;
16
+ }
17
+ resolve(stdout);
18
+ logger.warn(`sudoPromptExec#stdout_${stdout}`);
19
+ });
20
+ });
21
+ }
22
+ exports.sudoPromptExec = sudoPromptExec;
23
+ //# sourceMappingURL=sudo-prompt-exec.js.map
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.WindowsUpdator = void 0;
7
+ const path_1 = __importDefault(require("path"));
8
+ const electron_1 = require("electron");
9
+ const constants_1 = require("./common/constants");
10
+ const sudo_prompt_exec_1 = require("./utils/sudo-prompt-exec");
11
+ const app_updator_1 = require("./app-updator");
12
+ const utils_1 = require("./utils");
13
+ class WindowsUpdator extends app_updator_1.AppUpdator {
14
+ doGetAvailableUpdateInfo(updateInfo) {
15
+ this.logger.info('WindowsUpdator#doGetAvailableUpdateInfo:start');
16
+ const resourcePath = path_1.default.resolve(this.app.userDataPath);
17
+ const latestAsarPath = path_1.default.resolve(resourcePath, 'latest.asar');
18
+ const latestAppPath = path_1.default.resolve(resourcePath, 'latest');
19
+ let downloadTargetDir = `${latestAsarPath}.zip`;
20
+ if (updateInfo.updateType === constants_1.UpdateType.Package) {
21
+ downloadTargetDir = `${latestAppPath}.exe`;
22
+ }
23
+ return {
24
+ resourcePath,
25
+ downloadTargetDir,
26
+ latestAsarPath,
27
+ };
28
+ }
29
+ async doPreCheckForPackage() {
30
+ this.logger.info('WindowsUpdator#doPreCheckForPackage:start');
31
+ // Windows 全量安装默认预检正常
32
+ return Promise.resolve({ success: true });
33
+ }
34
+ async doUnzip() {
35
+ this.logger.info('WindowsUpdator#doUnzip:start');
36
+ try {
37
+ const { downloadTargetDir, resourcePath } = this.availableUpdate;
38
+ const unzipExe = (0, utils_1.getExecuteFile)(this._windowHelperExeDir, 'unzip.exe');
39
+ const executeCommand = `"${unzipExe}" -o "${downloadTargetDir}" -d "${resourcePath}"`;
40
+ await (0, utils_1.execAsync)(executeCommand);
41
+ }
42
+ catch (error) {
43
+ return {
44
+ success: false,
45
+ error,
46
+ };
47
+ }
48
+ this.logger.info('WindowsUpdator#doUnzip:success');
49
+ return {
50
+ success: true,
51
+ };
52
+ }
53
+ async doQuitAndInstallPackage() {
54
+ this.logger.info('WindowsUpdator#doQuitAndInstallPackage:success');
55
+ const { downloadTargetDir } = this.availableUpdate;
56
+ try {
57
+ // Windows 全量安装
58
+ const shellOpen = electron_1.shell.openItem || electron_1.shell.openPath;
59
+ shellOpen(downloadTargetDir);
60
+ setTimeout(() => {
61
+ this.app.exit(0);
62
+ }, 30);
63
+ this.logger.warn('WindowsUpdator#quitAndInstall:install success');
64
+ return Promise.resolve({ success: true });
65
+ }
66
+ catch (error) {
67
+ return Promise.resolve({ success: false, error });
68
+ }
69
+ }
70
+ async doQuitAndInstallAsar() {
71
+ this.logger.info('WindowsUpdator#doQuitAndInstallAsar:start');
72
+ const productName = this.options?.productName;
73
+ const { resourcePath } = this.availableUpdate;
74
+ const exePath = this.app.exePath;
75
+ const updateExePath = (0, utils_1.getExecuteFile)(this._windowHelperExeDir, 'installer.exe');
76
+ const targetPath = path_1.default.resolve(exePath, '..', 'resources');
77
+ const executeCommand = `"${updateExePath}" "${targetPath}" "${resourcePath}" "${productName}.exe" "${exePath}"`;
78
+ try {
79
+ await (0, sudo_prompt_exec_1.sudoPromptExec)(this.logger, executeCommand);
80
+ this.logger.warn('WindowsUpdator#quitAndInstall:install success');
81
+ return Promise.resolve({ success: true });
82
+ }
83
+ catch (error) {
84
+ return Promise.resolve({ success: false, error });
85
+ }
86
+ }
87
+ }
88
+ exports.WindowsUpdator = WindowsUpdator;
89
+ //# sourceMappingURL=windows-updator.js.map
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "graceful-updater",
3
+ "version": "1.0.5",
4
+ "description": "graceful-updater is a software updator management solution for Electron applications, It is convenient to complete full software update and dynamic update.",
5
+ "scripts": {
6
+ "build": "sh ./build.sh",
7
+ "test": "egg-bin test test/**/index.test.ts",
8
+ "lint": "eslint --fix --quiet --ext .js,.ts ./src",
9
+ "prepublishOnly": "npm run build",
10
+ "contributor": "git-contributor",
11
+ "dev": "ttsc -p tsconfig.json -watch"
12
+ },
13
+ "main": "./build",
14
+ "keywords": [
15
+ "electron",
16
+ "updator",
17
+ "auto-updator"
18
+ ],
19
+ "files": [
20
+ "build/**/*.js",
21
+ "build/**/*.exe"
22
+ ],
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/electron-modules/graceful-updater"
26
+ },
27
+ "dependencies": {
28
+ "eventemitter3": "^4.0.0",
29
+ "lodash": "4",
30
+ "moment": "2",
31
+ "nanoid": "^3.3.4",
32
+ "rimraf-alt": "*",
33
+ "sudo-prompt-alt": "9",
34
+ "urllib": "2"
35
+ },
36
+ "devDependencies": {
37
+ "@applint/spec": "^1.2.0",
38
+ "@types/lodash": "^4.14.181",
39
+ "@types/mocha": "^10.0.1",
40
+ "@types/mock-require": "^2.0.1",
41
+ "@types/react-dom": "^17.0.16",
42
+ "@typescript-eslint/parser": "^5.19.0",
43
+ "babel-plugin-module-resolver": "^4.1.0",
44
+ "concurrently": "^5.3.0",
45
+ "copyfiles": "^2.4.1",
46
+ "cross-env": "^7.0.3",
47
+ "detect-port": "1",
48
+ "egg-bin": "^5.9.0",
49
+ "electron": "18",
50
+ "electron-windows": "18",
51
+ "eslint": "^7.32.0",
52
+ "eslint-config-egg": "^12.1.0",
53
+ "eslint-plugin-no-only-tests": "^3.1.0",
54
+ "git-contributor": "*",
55
+ "husky": "^7.0.4",
56
+ "mm": "^3.0.2",
57
+ "mock-require": "^3.0.3",
58
+ "prettier": "^2.6.2",
59
+ "prop-types": "^15.7.2",
60
+ "style-loader": "^1.1.2",
61
+ "ts-loader": "^9.2.8",
62
+ "ts-node": "^10.7.0",
63
+ "ttypescript": "^1.5.15",
64
+ "typescript": "^4.6.3",
65
+ "typescript-transform-paths": "^3.3.1"
66
+ },
67
+ "husky": {
68
+ "hooks": {
69
+ "pre-commit": "npm run lint"
70
+ }
71
+ },
72
+ "license": "MIT"
73
+ }