@webos-tools/cli 3.0.6 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/.eslintignore +1 -1
  2. package/.eslintrc.js +52 -52
  3. package/APIs.js +79 -79
  4. package/CHANGELOG.md +157 -138
  5. package/LICENSE +201 -201
  6. package/README.md +218 -218
  7. package/bin/ares-config.js +199 -199
  8. package/bin/ares-device-info.js +30 -30
  9. package/bin/ares-device.js +219 -219
  10. package/bin/ares-generate.js +274 -270
  11. package/bin/ares-inspect.js +179 -179
  12. package/bin/ares-install.js +223 -223
  13. package/bin/ares-launch.js +318 -318
  14. package/bin/ares-log.js +255 -255
  15. package/bin/ares-novacom.js +220 -223
  16. package/bin/ares-package.js +336 -336
  17. package/bin/ares-pull.js +156 -156
  18. package/bin/ares-push.js +155 -155
  19. package/bin/ares-server.js +174 -174
  20. package/bin/ares-setup-device.js +528 -520
  21. package/bin/ares-shell.js +132 -132
  22. package/bin/ares.js +166 -166
  23. package/files/conf/ares.json +49 -49
  24. package/files/conf/command-service.json +73 -73
  25. package/files/conf/config.json +31 -22
  26. package/files/conf/ipk.json +30 -30
  27. package/files/conf/novacom-devices.json +35 -35
  28. package/files/conf/query/query-app.json +14 -14
  29. package/files/conf/query/query-hosted.json +18 -18
  30. package/files/conf/query/query-package.json +10 -10
  31. package/files/conf/query/query-service.json +6 -6
  32. package/files/conf/sdk.json +8 -8
  33. package/files/conf/template.json +57 -57
  34. package/files/conf/webos_emul +27 -27
  35. package/files/conf-base/env/sdk-ose.json +8 -8
  36. package/files/conf-base/env/sdk-tv.json +8 -8
  37. package/files/conf-base/profile/config-ose.json +29 -21
  38. package/files/conf-base/profile/config-tv.json +31 -22
  39. package/files/conf-base/query/query-app.json +14 -14
  40. package/files/conf-base/query/query-hosted.json +18 -18
  41. package/files/conf-base/query/query-package.json +10 -10
  42. package/files/conf-base/query/query-service.json +6 -6
  43. package/files/conf-base/template-conf/ose-templates.json +67 -67
  44. package/files/conf-base/template-conf/tv-sdk-templates.json +57 -57
  45. package/files/help/ares-config.help +43 -43
  46. package/files/help/ares-device.help +94 -94
  47. package/files/help/ares-generate.help +65 -65
  48. package/files/help/ares-inspect.help +70 -70
  49. package/files/help/ares-install.help +90 -90
  50. package/files/help/ares-launch.help +100 -100
  51. package/files/help/ares-log-pmlogd.help +84 -84
  52. package/files/help/ares-log.help +101 -101
  53. package/files/help/ares-novacom.help +68 -68
  54. package/files/help/ares-package.help +101 -101
  55. package/files/help/ares-pull.help +38 -38
  56. package/files/help/ares-push.help +38 -38
  57. package/files/help/ares-server.help +39 -39
  58. package/files/help/ares-setup-device.help +116 -75
  59. package/files/help/ares-shell.help +42 -42
  60. package/files/help/ares.help +47 -47
  61. package/files/help/readme.help +23 -23
  62. package/files/schema/ApplicationDescription.schema +319 -319
  63. package/files/schema/NovacomDevices.schema +61 -61
  64. package/files/templates/ose-sdk-templates/appinfo/appinfo.json +10 -10
  65. package/files/templates/ose-sdk-templates/bootplate-web/index.html +88 -88
  66. package/files/templates/ose-sdk-templates/hosted-webapp/index.html +13 -13
  67. package/files/templates/ose-sdk-templates/icon/icon.png +0 -0
  68. package/files/templates/ose-sdk-templates/js-service/helloclient.js +31 -31
  69. package/files/templates/ose-sdk-templates/js-service/helloworld_webos_service.js +188 -188
  70. package/files/templates/ose-sdk-templates/qml-app/main.qml +68 -68
  71. package/files/templates/ose-sdk-templates/qmlappinfo/appinfo.json +10 -10
  72. package/files/templates/ose-sdk-templates/serviceinfo/package.json +11 -11
  73. package/files/templates/ose-sdk-templates/serviceinfo/services.json +8 -8
  74. package/files/templates/tv-sdk-templates/appinfo/appinfo.json +10 -10
  75. package/files/templates/tv-sdk-templates/bootplate-web/index.html +58 -58
  76. package/files/templates/tv-sdk-templates/bootplate-web/webOSTVjs-1.2.10/LICENSE-2.0.txt +202 -202
  77. package/files/templates/tv-sdk-templates/hosted-webapp/index.html +14 -14
  78. package/files/templates/tv-sdk-templates/js-service/helloworld_service.js +39 -39
  79. package/files/templates/tv-sdk-templates/packageinfo/packageinfo.json +3 -3
  80. package/files/templates/tv-sdk-templates/serviceinfo/package.json +11 -11
  81. package/files/templates/tv-sdk-templates/serviceinfo/services.json +8 -8
  82. package/files/templates/tv-sdk-templates/webicon/icon.png +0 -0
  83. package/files/templates/tv-sdk-templates/webicon/largeIcon.png +0 -0
  84. package/lib/base/ares.html +40 -40
  85. package/lib/base/cli-appdata.js +290 -290
  86. package/lib/base/cli-control.js +44 -44
  87. package/lib/base/common-tools.js +29 -29
  88. package/lib/base/error-handler.js +265 -265
  89. package/lib/base/file-watcher.js +155 -155
  90. package/lib/base/help-format.js +147 -147
  91. package/lib/base/luna.js +178 -178
  92. package/lib/base/novacom.js +1202 -1191
  93. package/lib/base/sdkenv.js +59 -59
  94. package/lib/base/server.js +137 -137
  95. package/lib/base/setup-device.js +335 -328
  96. package/lib/base/version-tools.js +79 -79
  97. package/lib/device.js +1419 -1419
  98. package/lib/generator.js +377 -377
  99. package/lib/inspect.js +493 -494
  100. package/lib/install.js +463 -463
  101. package/lib/launch.js +605 -605
  102. package/lib/log.js +584 -584
  103. package/lib/package.js +2147 -2129
  104. package/lib/pull.js +231 -231
  105. package/lib/pusher.js +210 -210
  106. package/lib/session.js +74 -74
  107. package/lib/shell.js +193 -193
  108. package/lib/tar-filter-pack.js +62 -62
  109. package/lib/util/copy.js +31 -31
  110. package/lib/util/createFileName.js +40 -40
  111. package/lib/util/eof.js +30 -30
  112. package/lib/util/json.js +63 -63
  113. package/lib/util/merge.js +14 -14
  114. package/lib/util/objclone.js +40 -40
  115. package/lib/util/spinner.js +37 -37
  116. package/npm-shrinkwrap.json +9242 -9116
  117. package/package.json +100 -100
  118. package/scripts/postinstall.js +24 -24
  119. package/spec/helpers/reporter.js +65 -65
  120. package/spec/jsSpecs/apiTest/generator.spec.js +372 -372
  121. package/spec/jsSpecs/apiTest/inspector.spec.js +89 -89
  122. package/spec/jsSpecs/apiTest/installer.spec.js +67 -67
  123. package/spec/jsSpecs/apiTest/launcher.spec.js +150 -150
  124. package/spec/jsSpecs/apiTest/packager.spec.js +194 -194
  125. package/spec/jsSpecs/apiTest/puller.spec.js +101 -101
  126. package/spec/jsSpecs/apiTest/pusher.spec.js +103 -103
  127. package/spec/jsSpecs/apiTest/server.spec.js +115 -115
  128. package/spec/jsSpecs/apiTest/setupDevice.spec.js +93 -93
  129. package/spec/jsSpecs/apiTest/shell.spec.js +49 -49
  130. package/spec/jsSpecs/ares-config.spec.js +78 -78
  131. package/spec/jsSpecs/ares-device.spec.js +443 -443
  132. package/spec/jsSpecs/ares-generate.spec.js +397 -397
  133. package/spec/jsSpecs/ares-inspect.spec.js +252 -252
  134. package/spec/jsSpecs/ares-install.spec.js +150 -150
  135. package/spec/jsSpecs/ares-launch.spec.js +301 -301
  136. package/spec/jsSpecs/ares-log.spec.js +824 -824
  137. package/spec/jsSpecs/ares-novacom.spec.js +149 -149
  138. package/spec/jsSpecs/ares-package.spec.js +1211 -1211
  139. package/spec/jsSpecs/ares-pull.spec.js +157 -157
  140. package/spec/jsSpecs/ares-push.spec.js +146 -146
  141. package/spec/jsSpecs/ares-server.spec.js +160 -160
  142. package/spec/jsSpecs/ares-setup-device.spec.js +300 -281
  143. package/spec/jsSpecs/ares-shell.spec.js +220 -220
  144. package/spec/jsSpecs/ares.spec.js +83 -83
  145. package/spec/jsSpecs/common-spec.js +169 -169
  146. package/spec/support/jasmine.json +22 -22
  147. package/spec/tempFiles/nativeApp/auto/pkg_arm64/GLES2 +0 -0
  148. package/spec/tempFiles/nativeApp/auto/pkg_arm64/appinfo.json +9 -9
  149. package/spec/tempFiles/nativeApp/ose/pkg_arm/Hello +0 -0
  150. package/spec/tempFiles/nativeApp/ose/pkg_arm/appinfo.json +8 -8
  151. package/spec/tempFiles/nativeApp/ose/pkg_arm/package.properties +2 -2
  152. package/spec/tempFiles/nativeApp/oseEmul/pkg_x86/Hello +0 -0
  153. package/spec/tempFiles/nativeApp/oseEmul/pkg_x86/appinfo.json +9 -9
  154. package/spec/tempFiles/nativeApp/rsi/pkg_x86/GLES2 +0 -0
  155. package/spec/tempFiles/nativeApp/rsi/pkg_x86/appinfo.json +9 -9
  156. package/spec/tempFiles/sign/sign.crt +32 -32
  157. package/spec/tempFiles/sign/signPriv.key +52 -52
  158. package/spec/test_data/ares-generate.json +41 -41
  159. package/spec/test_data/ares.json +33 -33
package/lib/package.js CHANGED
@@ -1,2129 +1,2147 @@
1
- /*
2
- * Copyright (c) 2020-2024 LG Electronics Inc.
3
- *
4
- * SPDX-License-Identifier: Apache-2.0
5
- */
6
-
7
- const ar = require('ar-async'),
8
- async = require('async'),
9
- chardet = require('chardet'),
10
- CombinedStream = require('combined-stream'),
11
- crypto = require('crypto'),
12
- decompress = require('decompress'),
13
- decompressTargz = require('decompress-targz'),
14
- ElfParser = require('elfy').Parser,
15
- encoding = require('encoding'),
16
- fs = require('fs'),
17
- fstream = require('fstream'),
18
- Validator = require('jsonschema').Validator,
19
- mkdirp = require('mkdirp'),
20
- log = require('npmlog'),
21
- path = require('path'),
22
- rimraf = require('rimraf'),
23
- shelljs = require('shelljs'),
24
- stripbom = require('strip-bom'),
25
- temp = require('temp'),
26
- uglify = require('terser'),
27
- util = require('util'),
28
- zlib = require('zlib'),
29
- tarFilterPack = require('./tar-filter-pack'),
30
- errHndl = require('./base/error-handler');
31
-
32
- (function() {
33
- log.heading = 'packager';
34
- log.level = 'warn';
35
-
36
- const servicePkgMethod = 'id',
37
- defaultAssetsFields = {
38
- "main": true,
39
- "icon": true,
40
- "largeIcon": true,
41
- "bgImage": true,
42
- "splashBackground": true,
43
- "imageForRecents": true,
44
- "sysAssetsBasePath": true
45
- },
46
- FILE_TYPE = {
47
- file: 'file',
48
- dir: 'dir',
49
- symlink: 'symlink'
50
- },
51
- packager = {};
52
- let objectCounter = 0;
53
-
54
- if (typeof module !== 'undefined' && module.exports) {
55
- module.exports = packager;
56
- }
57
-
58
- function Packager() {
59
- this.objectId = objectCounter++;
60
- this.verbose = false;
61
- this.silent = true;
62
- this.noclean = false;
63
- this.nativecmd = false;
64
- this.minify = true;
65
- this.excludeFiles = [];
66
- this.rom = false;
67
- this.encrypt = false;
68
- this.sign = "";
69
- this.certificate = "";
70
- this.appCount = 0;
71
- this.services = [];
72
- this.pkgServiceNames = [];
73
- this.rscCount = 0;
74
- this.resources = [];
75
- this.pkgResourceNames = [];
76
- this.pkgId;
77
- this.pkginfofile;
78
- }
79
-
80
- packager.Packager = Packager;
81
-
82
- Packager.prototype = {
83
- prepareOptions: function(options, next) {
84
- if (options && options.level) {
85
- log.level = options.level;
86
- if (['warn', 'error'].indexOf(options.level) !== -1) {
87
- this.silent = false;
88
- }
89
- }
90
-
91
- if (options && options.noclean === true) {
92
- this.noclean = true;
93
- }
94
-
95
- if (options && options.nativecmd === true) {
96
- this.nativecmd = true;
97
- }
98
-
99
- if (options && Object.prototype.hasOwnProperty.call(options, 'minify')) {
100
- this.minify = options.minify;
101
- }
102
-
103
- if (options && Object.prototype.hasOwnProperty.call(options, 'excludefiles')) {
104
- if (options.excludefiles instanceof Array) {
105
- this.excludeFiles = options.excludefiles;
106
- } else {
107
- this.excludeFiles.push(options.excludefiles);
108
- }
109
-
110
- this.excludeFiles.forEach(function(excl_file) {
111
- if (excl_file === "appinfo.json") {
112
- return next(errHndl.getErrMsg("NOT_EXCLUDE_APPINFO"));
113
- }
114
- });
115
- }
116
-
117
- if (options && Object.prototype.hasOwnProperty.call(options, 'rom')) {
118
- this.rom = options.rom;
119
- }
120
-
121
- if (options && Object.prototype.hasOwnProperty.call(options, 'encrypt')) {
122
- this.encrypt = options.encrypt;
123
- }
124
-
125
- if (options && Object.prototype.hasOwnProperty.call(options, 'sign')) {
126
- if (!fs.existsSync(path.resolve(options.sign))) {
127
- return next(errHndl.getErrMsg("NOT_EXIST_PATH", options.sign));
128
- }
129
- this.sign = options.sign;
130
- }
131
-
132
- if (options && Object.prototype.hasOwnProperty.call(options, 'certificate')) {
133
- if (!fs.existsSync(path.resolve(options.certificate))) {
134
- return next(errHndl.getErrMsg("NOT_EXIST_PATH", options.certificate));
135
- }
136
- this.certificate = options.certificate;
137
- }
138
-
139
- // check sign option must be used with certificate option
140
- if ((this.sign && !this.certificate) ||
141
- (this.certificate && !this.sign)) {
142
- return next(errHndl.getErrMsg("USE_WITH_OPTIONS", "sign, certificate"));
143
- }
144
-
145
- log.verbose("package#Packager()", "Packager id:" + this.objectId);
146
-
147
- this.pkgVersion = options.pkgversion;
148
-
149
- if (options && Object.prototype.hasOwnProperty.call(options, 'pkgid')) {
150
- this.pkgId = options.pkgid;
151
- }
152
-
153
- if (options && Object.prototype.hasOwnProperty.call(options, 'pkginfofile')) {
154
- this.pkginfofile = options.pkginfofile;
155
- }
156
-
157
- if (this.pkgId && this.pkginfofile) {
158
- return next(errHndl.getErrMsg("NOT_USE_WITH_OPTIONS", "pkginfofile, pkgid"));
159
- }
160
- this.appCount = 0;
161
- },
162
- checkInputDirectories: function(inDirs, options, next) {
163
- log.verbose("package#Packager#checkInputDirectories()", "input directory:", inDirs);
164
- this.prepareOptions(options, next);
165
-
166
- async.forEachSeries(inDirs, checkDirectory.bind(this, options), function(err) {
167
- if (err) {
168
- setImmediate(next, err);
169
- return;
170
- }
171
- setImmediate(next);
172
- });
173
- return {app: this.appCount, resource: this.rscCount};
174
- },
175
- servicePackaging: function(inDirs, destination, options, middleCb, next) {
176
- log.info("package#Packager#servicePackaging()");
177
-
178
- if (!Object.hasOwnProperty.call(options, 'pkgid') && !Object.hasOwnProperty.call(options, 'pkginfofile')) {
179
- return next(errHndl.getErrMsg("USE_PKGID_PKGINFO"));
180
- }
181
-
182
- async.series([
183
- this.checkInputDirectories.bind(this, inDirs, options),
184
- setUmask.bind(this, 0),
185
- loadPkgInfo.bind(this),
186
- createTmpDir.bind(this),
187
- excludeIpkFileFromApp.bind(this),
188
- createPackageDir.bind(this),
189
- fillPackageDir.bind(this),
190
- findServiceDir.bind(this, this.services),
191
- loadServiceInfo.bind(this),
192
- checkServiceInfo.bind(this),
193
- createServiceDir.bind(this),
194
- copyService.bind(this),
195
- addServiceInPkgInfo.bind(this),
196
- copyData.bind(this, inDirs, options.force),
197
- loadPackageProperties.bind(this, inDirs),
198
- excludeFromApp.bind(this, middleCb),
199
- outputPackage.bind(this, destination),
200
- encryptPackage.bind(this),
201
- copyOutputToDst.bind(this, destination, middleCb),
202
- recoverUmask.bind(this),
203
- cleanupTmpDir.bind(this, middleCb)
204
- ], function(err) {
205
- if (err) {
206
- // TODO: call cleanupTmpDir() before returning
207
- setImmediate(next, err);
208
- return;
209
- }
210
- // TODO: probably some more checkings are needed
211
- setImmediate(next, null, {ipk: this.ipk, msg: "Success"});
212
- }.bind(this));
213
- },
214
- resourcePackaging: function(inDirs, destination, options, middleCb, next) {
215
- log.info("package#Packager#resourcePackaging()");
216
- this.dataCopyCount = 0;
217
- async.series([
218
- this.checkInputDirectories.bind(this, inDirs, options),
219
- setUmask.bind(this, 0),
220
- loadPkgInfo.bind(this),
221
- createTmpDir.bind(this),
222
- excludeIpkFileFromApp.bind(this),
223
- createPackageDir.bind(this),
224
- fillPackageDir.bind(this),
225
- loadResourceInfo.bind(this),
226
- checkResourceInfo.bind(this),
227
- createResourceDir.bind(this),
228
- copyResource.bind(this),
229
- addResourceInPkgInfo.bind(this),
230
- copyData.bind(this, inDirs, options.force),
231
- loadPackageProperties.bind(this, inDirs),
232
- excludeFromApp.bind(this, middleCb),
233
- outputPackage.bind(this, destination),
234
- encryptPackage.bind(this),
235
- copyOutputToDst.bind(this, destination, middleCb),
236
- recoverUmask.bind(this),
237
- cleanupTmpDir.bind(this, middleCb)
238
- ], function(err) {
239
- if (err) {
240
- setImmediate(next, err);
241
- return;
242
- }
243
- setImmediate(next, null, {ipk: this.ipk, msg: "Success"});
244
- }.bind(this));
245
- },
246
- generatePackage: function(inDirs, destination, options, middleCb, next) {
247
- log.info("package#Packager#generatePackage()", "from ", inDirs);
248
- // check whether app or service directories are copied or not
249
- this.dataCopyCount = 0;
250
- this.minifyDone = !this.minify;
251
-
252
- async.series([
253
- this.checkInputDirectories.bind(this, inDirs, options),
254
- setUmask.bind(this, 0),
255
- loadPkgInfo.bind(this),
256
- loadAppInfo.bind(this),
257
- checkAppInfo.bind(this),
258
- createTmpDir.bind(this),
259
- createAppDir.bind(this),
260
- checkELFHeader.bind(this),
261
- fillAssetsField.bind(this),
262
- copyAssets.bind(this),
263
- copyApp.bind(this),
264
- excludeIpkFileFromApp.bind(this),
265
- createPackageDir.bind(this),
266
- fillPackageDir.bind(this),
267
- findServiceDir.bind(this, this.services),
268
- loadServiceInfo.bind(this),
269
- checkServiceInfo.bind(this),
270
- createServiceDir.bind(this),
271
- copyService.bind(this),
272
- addServiceInPkgInfo.bind(this),
273
- removeServiceFromAppDir.bind(this, middleCb),
274
- copyData.bind(this, inDirs, options.force),
275
- loadPackageProperties.bind(this, inDirs),
276
- excludeFromApp.bind(this, middleCb),
277
- outputPackage.bind(this, destination),
278
- encryptPackage.bind(this),
279
- copyOutputToDst.bind(this, destination, middleCb),
280
- recoverUmask.bind(this),
281
- cleanupTmpDir.bind(this, middleCb)
282
- ], function(err) {
283
- if (err) {
284
- // TODO: call cleanupTmpDir() before returning
285
- setImmediate(next, err);
286
- return;
287
- }
288
- // TODO: probably some more checkings are needed
289
- setImmediate(next, null, {ipk: this.ipk, msg: "Success"});
290
- }.bind(this));
291
- },
292
- analyzeIPK: function(options, next) {
293
- log.info("package#Packager#analyzeIPK()");
294
- let ipkConfigFile, ipkFile, ipkConfig, tmpDirPath;
295
- this.prepareOptions(options);
296
-
297
- async.series([
298
- _setConfig,
299
- _unpackIpk,
300
- _unpackTar,
301
- _analyzeMetaFile,
302
- _removeTmpDir,
303
- ], function(err, results) {
304
- log.silly("package#analyzeIPK()", "err:", err, ", results:", results);
305
- if (err) {
306
- return next(err);
307
- }
308
- return next(null, {msg: results[3].trim()});
309
- });
310
-
311
- function _setConfig(next) {
312
- log.info("package#analyzeIPK()#_setConfig()");
313
- if (options.info === 'true') {
314
- return next(errHndl.getErrMsg("EMPTY_VALUE", 'info'));
315
- } else if (options.infodetail === 'true') {
316
- return next(errHndl.getErrMsg("EMPTY_VALUE", 'info-detail'));
317
- }
318
- ipkFile = options.info ? options.info : options.infodetail;
319
- ipkConfigFile = path.resolve(__dirname, "../files/conf/ipk.json");
320
-
321
- if (path.extname(ipkFile) !== ".ipk") {
322
- return next(errHndl.getErrMsg("SUPPORT_ONLY_IPK", ipkFile));
323
- }
324
- if (!fs.existsSync(ipkFile)) {
325
- return next(errHndl.getErrMsg("NOT_EXIST_PATH", ipkFile));
326
- }
327
- if (!fs.existsSync(ipkConfigFile)) {
328
- return next(errHndl.getErrMsg("NOT_EXIST_PATH", ipkConfigFile));
329
- }
330
- ipkConfig = JSON.parse(fs.readFileSync(ipkConfigFile));
331
- tmpDirPath = temp.path(ipkConfig.tmpPath);
332
-
333
- if (!fs.existsSync(tmpDirPath)) {
334
- fs.mkdirSync(tmpDirPath);
335
- }
336
- next();
337
- }
338
-
339
- function _unpackIpk(next) {
340
- log.info("package#analyzeIPK()#_unpackIpk()");
341
- const reader = new ar.ArReader(ipkFile);
342
-
343
- reader.on("entry", function(entry, next) {
344
- const name = entry.fileName();
345
- entry.fileData()
346
- .pipe(fs.createWriteStream(path.resolve(tmpDirPath, name)))
347
- .on("finish", next);
348
- });
349
- reader.on("error", function(err) {
350
- return next(err);
351
- });
352
- reader.on("close", function() {
353
- log.verbose("package#analyzeIPK()#_unpackIpk()", "unpack ipk close");
354
- next();
355
- });
356
- }
357
-
358
- function _unpackTar(next) {
359
- log.info("package#analyzeIPK()#_unpackTar()");
360
- if (fs.existsSync(path.resolve(tmpDirPath, "control.tar.gz"))) {
361
- (async function() {
362
- await decompress(path.resolve(tmpDirPath, "control.tar.gz"), tmpDirPath, {
363
- plugins: [
364
- decompressTargz()
365
- ]
366
- });
367
- log.verbose("package#analyzeIPK()#_unpackTar()", "control.tar.gz decompressed");
368
- })();
369
- } else if (fs.existsSync(path.resolve(tmpDirPath, "control.tar.xz"))) {
370
- return next(errHndl.getErrMsg("NOT_SUPPORT_XZ"));
371
- } else {
372
- return next(errHndl.getErrMsg("NO_COMPONENT_FILE", ", control tar file"));
373
- }
374
-
375
- if (fs.existsSync(path.resolve(tmpDirPath, "data.tar.gz"))) {
376
- (async function() {
377
- await decompress(path.resolve(tmpDirPath, "data.tar.gz"), tmpDirPath, {
378
- plugins: [
379
- decompressTargz()
380
- ]
381
- });
382
- log.verbose("package#analyzeIPK()#_unpackTar()", "data.tar.gz decompressed");
383
- next();
384
- })();
385
- } else if (fs.existsSync(path.resolve(tmpDirPath, "data.tar.xz"))) {
386
- return next(errHndl.getErrMsg("NOT_SUPPORT_XZ"));
387
- } else {
388
- return next(errHndl.getErrMsg("NO_COMPONENT_FILE", "data tar file"));
389
- }
390
- }
391
-
392
- function _analyzeMetaFile(next) {
393
- log.info("package#analyzeIPK()#_analyzeMetaFile()");
394
- let result = "", targetFile, tmpTxt;
395
-
396
- ipkConfig.webOSMetaFiles.forEach(function(item) {
397
- const targetPath = path.resolve(tmpDirPath, ipkConfig[item].path);
398
- // Analyze control file
399
- if (item === "control") {
400
- targetFile = path.resolve(targetPath, ipkConfig[item].fileName);
401
- if (!fs.existsSync(targetFile)) {
402
- return next(errHndl.getErrMsg("NO_COMPONENT_FILE", "control file"));
403
- }
404
-
405
- if (options.infodetail) {
406
- result = "\n\n< " + ipkConfig[item].fileName + " >\n";
407
- result += fs.readFileSync(targetFile).toString().trim();
408
- } else {
409
- result = ipkConfig[item].heading + "\n";
410
- tmpTxt = fs.readFileSync(targetFile).toString().trim();
411
- ipkConfig[item].info.forEach(function(field) {
412
- if (tmpTxt.match(new RegExp(field + ": (.*)", "gi"))) {
413
- result += tmpTxt.match(new RegExp(field + ": (.*)", "gi"))[0] + "\n";
414
- }
415
- });
416
- }
417
- // Analyze appinfo.json, packageinfo.json, services.json, package.json files
418
- } else if (fs.existsSync(targetPath)) {
419
- const dirArr = fs.readdirSync(targetPath);
420
- let beforeDir = dirArr[0];
421
-
422
- dirArr.forEach(function(dir) {
423
- if (ipkConfig[item].fileName) {
424
- ipkConfig[item].fileName.forEach(function(file) {
425
- targetFile = path.resolve(targetPath, dir, file);
426
- if (fs.existsSync(targetFile)) {
427
- if (options.infodetail) {
428
- result += "\n\n< " + file + " >\n";
429
- result += fs.readFileSync(targetFile).toString().trim();
430
- } else {
431
- if (ipkConfig[item].heading && beforeDir !== path.join(ipkConfig[item].path, dir)) {
432
- result += "\n" + ipkConfig[item].heading + "\n";
433
- }
434
- tmpTxt = fs.readFileSync(targetFile).toString().trim();
435
- const tmpJson = JSON.parse(tmpTxt);
436
- ipkConfig[item].info.forEach(function(field) {
437
- if (tmpJson[field]) {
438
- const fieldValue = tmpJson[field];
439
- if (typeof fieldValue === "object") {
440
- if (ipkConfig[item][field]) { // Case of services.json, services_name field
441
- const subField = ipkConfig[item][field],
442
- subResult = [];
443
- fieldValue.forEach(function(subItem) {
444
- subResult.push(subItem[subField]);
445
- });
446
- result += field + ": " + JSON.stringify(subResult) + "\n";
447
- } else { // Case of packageinfo.json, services field
448
- result += field + ": " + JSON.stringify(fieldValue) + "\n";
449
- }
450
- } else {
451
- result += field + ": " + fieldValue + "\n";
452
- }
453
- }
454
- });
455
- beforeDir = path.join(ipkConfig[item].path, dir);
456
- }
457
- }
458
- });
459
- }
460
- });
461
- }
462
- });
463
- next(null, result);
464
- }
465
-
466
- function _removeTmpDir(next) {
467
- log.info("package#analyzeIPK()#_removeTmpDir()");
468
- rimraf(tmpDirPath, function(err) {
469
- log.verbose("package#analyzeIPK()#_removeTmpDir()", "removed " + tmpDirPath);
470
- next(err);
471
- });
472
- }
473
- }
474
- };
475
-
476
- function Service() {
477
- this.srcDir = "";
478
- this.dstDirs = [];
479
- this.valid = false;
480
- this.serviceInfo = "";
481
- this.dirName = "";
482
- }
483
-
484
- // Private functions
485
- function loadPkgInfo(next) {
486
- log.verbose("loadPkgInfo");
487
- let data;
488
-
489
- if (this.pkginfofile) {
490
- log.silly("Use pkginfofile option");
491
- if (fs.existsSync(this.pkginfofile)) {
492
- if ("packageinfo.json" !== path.basename(this.pkginfofile)) {
493
- return setImmediate(next, errHndl.getErrMsg("INVALID_FILE", "packageinfo.json"));
494
- }
495
- data = rewriteFileWoBOMAsUtf8(this.pkginfofile, true);
496
- try {
497
- log.verbose("PKGINFO >>" + data + "<<");
498
- this.pkginfo = JSON.parse(data);
499
-
500
- if (!Object.prototype.hasOwnProperty.call(this.pkginfo, 'id')) {
501
- return setImmediate(next, errHndl.getErrMsg("REQUIRED_FIELD", "id"));
502
- }
503
-
504
- this.pkgId = this.pkginfo.id;
505
- this.pkgVersion = this.pkgVersion || this.pkginfo.version;
506
- setImmediate(next);
507
- }
508
- catch (err) {
509
- return setImmediate(next, errHndl.getErrMsg("INVALID_JSON_FORMAT", "packageinfo.json"));
510
- }
511
- } else {
512
- return setImmediate(next, errHndl.getErrMsg("NOT_EXIST_PATH", this.pkginfofile));
513
- }
514
- } else if (this.pkgDir) {
515
- log.silly("Use packageinfo.json from PKG_DIR");
516
- data = rewriteFileWoBOMAsUtf8(path.join(this.pkgDir, "packageinfo.json"));
517
- try {
518
- log.verbose("PKGINFO >>" + data + "<<");
519
- this.pkginfo = JSON.parse(data);
520
-
521
- if (!Object.prototype.hasOwnProperty.call(this.pkginfo, 'id')) {
522
- return setImmediate(next, errHndl.getErrMsg("REQUIRED_FIELD", "id"));
523
- }
524
- this.pkgId = this.pkginfo.id;
525
- this.pkgVersion = this.pkgVersion || this.pkginfo.version;
526
- setImmediate(next);
527
- }
528
- catch (err) {
529
- return setImmediate(next, errHndl.getErrMsg("INVALID_JSON_FORMAT", "packageinfo.json"));
530
- }
531
- } else {
532
- return setImmediate(next);
533
- }
534
- }
535
-
536
- function loadAppInfo(next) {
537
- log.verbose("loadAppInfo");
538
- if (this.appCount === 0) {
539
- return setImmediate(next);
540
- }
541
-
542
- const filepath = path.join(this.appDir, "appinfo.json"),
543
- data = rewriteFileWoBOMAsUtf8(filepath, true);
544
- try {
545
- this.appinfo = JSON.parse(data);
546
- log.silly("package#loadAppInfo()", "content of appinfo.json:", this.appinfo);
547
-
548
- if (!this.appinfo.version || this.appinfo.version === undefined) {
549
- this.appinfo.version = "1.0.0";
550
- }
551
-
552
- this.pkgVersion = this.pkgVersion || this.appinfo.version;
553
- setImmediate(next);
554
- } catch(err) {
555
- setImmediate(next, err);
556
- }
557
- }
558
-
559
- function checkAppInfo(next) {
560
- if (this.appCount === 0) {
561
- return setImmediate(next);
562
- }
563
-
564
- // check enyo app
565
- if (this.pkgJSExist && this.appinfo.main && this.appinfo.main.match(/(\.html|\.htm)$/gi)) {
566
- const mainFile = path.join(this.appDir, this.appinfo.main);
567
- if (!fs.existsSync(mainFile)) {
568
- return setImmediate(next, errHndl.getErrMsg("NOT_EXIST_PATH", this.appinfo.main));
569
- }
570
-
571
- const regex = new RegExp("(<script[^>]*src[ \t]*=[ \t]*['\"])[^'\"]*/enyo.js(['\"])"),
572
- data = fs.readFileSync(mainFile);
573
- if (data.toString().match(regex)) {
574
- // If enyo app, stop packaging.
575
- return setImmediate(next, errHndl.getErrMsg("NOT_SUPPORT_ENYO"));
576
- }
577
- }
578
-
579
- if (!this.appinfo.id || this.appinfo.id === undefined) {
580
- return setImmediate(next, errHndl.getErrMsg("REQUIRED_FIELD", "id"));
581
- }
582
- if (this.appinfo.id.length < 1 || !(/^[a-z0-9.+-]*$/.test(this.appinfo.id))) {
583
- log.error(errHndl.getErrMsg("INVALID_VALUE", "id", this.appinfo.id));
584
- return setImmediate(next, errHndl.getErrMsg("INVALID_ID_RULE"));
585
- }
586
- if (this.pkgId && this.appinfo.id.indexOf(this.pkgId) !== 0) {
587
- log.error(errHndl.getErrMsg("INVALID_VALUE", "id", this.appinfo.id));
588
- return setImmediate(next, errHndl.getErrMsg("INVALID_APPID", this.pkgId));
589
- }
590
- if (!this.appinfo.version || this.appinfo.version === undefined) {
591
- return setImmediate(next, errHndl.getErrMsg("REQUIRED_FIELD", "version"));
592
- }
593
- if (this.appinfo.version.length < 1 || !(/^([1-9]\d{0,8}|\d)\.([1-9]\d{0,8}|\d)\.([1-9]\d{0,8}|\d)$/.test(this.appinfo.version))) {
594
- log.error(errHndl.getErrMsg("INVALID_VALUE", "version", this.appinfo.version));
595
- return setImmediate(next, errHndl.getErrMsg("INVALID_VERSION_RULE"));
596
- }
597
- if (this.appinfo.type && this.appinfo.type.match(/clock/gi)) {
598
- return setImmediate(next);
599
- }
600
-
601
- const schemaFile = path.resolve(__dirname, "../files/schema/ApplicationDescription.schema");
602
- async.waterfall([
603
- fs.readFile.bind(this, schemaFile, "utf-8"),
604
- function getSchema(data, next) {
605
- try {
606
- const schema = JSON.parse(data);
607
- /* "required" keyword is redefined in draft 4.
608
- But current jsonschema lib support only draft 3.
609
- So this line changes "required" attribute according to the draft 3.
610
- */
611
- const reqKeys = schema.required;
612
- if (reqKeys) {
613
- for (const key in schema.properties) {
614
- if (reqKeys.indexOf(key) !== -1) {
615
- schema.properties[key].required = true;
616
- }
617
- }
618
- }
619
- next(null, schema);
620
- } catch(err) {
621
- next(errHndl.getErrMsg("INVALID_JSON_FORMAT", "AppDescription schema"));
622
- }
623
- },
624
- function checkValid(schema, next) {
625
- try {
626
- next(null, new Validator().validate(this.appinfo, schema));
627
- } catch (err) {
628
- log.error(err);
629
- next(errHndl.getErrMsg("INVALID_JSON_FORMAT"));
630
- }
631
- }.bind(this)
632
- ], function(err, result) {
633
- if (err) {
634
- setImmediate(next, err);
635
- } else {
636
- if (result && result.errors.length > 0) {
637
- const errFile = "appinfo.json";
638
- let errMsg = "";
639
- for (const idx in result.errors) {
640
- let errMsgLine = result.errors[idx].property + " " + result.errors[idx].message;
641
- if (errMsgLine.indexOf("instance.") > -1) {
642
- errMsgLine = errMsgLine.substring("instance.".length);
643
- errMsg = errMsg.concat("\n");
644
- errMsg = errMsg.concat(errMsgLine);
645
- }
646
- }
647
- errMsg = errHndl.getErrMsg("INVALID_FILE", errFile, errMsg);
648
- return setImmediate(next, errMsg);
649
- } else {
650
- log.verbose("package#checkAppInfo()", "APPINFO is valid");
651
- }
652
- setImmediate(next);
653
- }
654
- });
655
- }
656
-
657
- function fillAssetsField(next) {
658
- if (this.appCount === 0) {
659
- return setImmediate(next);
660
- }
661
- // make appinfo.assets to have default values so that they can be copied into the package
662
- this.appinfo.assets = this.appinfo.assets || [];
663
- for (const i in this.appinfo) {
664
- if (Object.prototype.hasOwnProperty.call(this.appinfo, i) && defaultAssetsFields[i]) {
665
- // no duplicated adding & value should not null string & file/dir should exist
666
- if ((this.appinfo.assets.indexOf(this.appinfo[i]) === -1) && this.appinfo[i]) {
667
- this.appinfo.assets.push(this.appinfo[i]);
668
- }
669
- }
670
- }
671
-
672
- // refer to appinfo.json files in localization directory.
673
- const appInfoPath = this.originAppDir,
674
- checkDir = path.join(this.originAppDir, "resources"),
675
- foundFilePath = [],
676
- resourcesAssets = [];
677
-
678
- try {
679
- const stat = fs.lstatSync(checkDir);
680
- if (!stat.isDirectory()) {
681
- return setImmediate(next, null);
682
- }
683
- } catch(err) {
684
- if (err.code === "ENOENT") {
685
- return setImmediate(next, null);
686
- }
687
- }
688
-
689
- async.series([
690
- walkFolder.bind(null, checkDir, "appinfo.json", foundFilePath, 1),
691
- function(next) {
692
- async.forEach(foundFilePath, function(filePath, next) {
693
- rewriteFileWoBOMAsUtf8(filePath, true, function(err, data) {
694
- try {
695
- const appInfo = JSON.parse(data),
696
- dirPath = path.dirname(filePath);
697
- for (const i in appInfo) {
698
- if (Object.prototype.hasOwnProperty.call(appInfo, i) && defaultAssetsFields[i]) {
699
- if (appInfo[i]) {
700
- const itemPath = path.join(dirPath, appInfo[i]),
701
- relPath = path.relative(appInfoPath, itemPath);
702
- // no duplicated adding & value should not null string & file/dir should exist
703
- if ((resourcesAssets.indexOf(relPath) === -1)) {
704
- resourcesAssets.push(relPath);
705
- }
706
- }
707
- }
708
- }
709
- setImmediate(next, null);
710
- } catch(error) {
711
- setImmediate(next, errHndl.getErrMsg("INVALID_JSON_FORMAT", filePath));
712
- }
713
- });
714
- }, function(err) {
715
- setImmediate(next, err);
716
- });
717
- },
718
- function(next) {
719
- this.appinfo.assets = this.appinfo.assets.concat(resourcesAssets);
720
- setImmediate(next, null);
721
- }.bind(this)
722
- ], function(err) {
723
- setImmediate(next, err);
724
- });
725
- }
726
-
727
- function createTmpDir(next) {
728
- this.tempDir = temp.path({prefix: 'com.palm.ares.hermes.bdOpenwebOS'}) + '.d';
729
- log.verbose("package#createTmpDir()", "temp dir:", this.tempDir);
730
- mkdirp(this.tempDir, next);
731
- }
732
-
733
- function createAppDir(next) {
734
- if (this.appCount === 0) {
735
- return setImmediate(next);
736
- }
737
-
738
- try {
739
- this.applicationDir = path.resolve(this.tempDir, "data/usr/palm/applications", this.appinfo.id);
740
- log.info("package#createAppDir()", "application dir:" + this.applicationDir);
741
- mkdirp(this.applicationDir, next);
742
- } catch (err) {
743
- return setImmediate(next, err);
744
- }
745
- }
746
-
747
- function copySrcToDst(src, dst, next) {
748
- const fileList = [],
749
- self = this,
750
- requireMinify = !!((!!self.minify && !self.minifyDone));
751
- src = path.normalize(path.resolve(src));
752
- dst = path.normalize(path.resolve(dst));
753
-
754
- async.series([
755
- function(next) {
756
- const stat = fs.statSync(src);
757
- if (stat.isFile()) {
758
- _pushList(fileList, 'file', path.dirname(src), path.basename(src), true, null);
759
- setImmediate(next);
760
- } else {
761
- _getFileList(src, src, fileList, next);
762
- }
763
- },
764
- _copySrcToDst.bind(null, fileList, dst, requireMinify)
765
- ], function(err) {
766
- next(err);
767
- });
768
-
769
- function _pushList(list, type, basePath, relPath, isSubPath, indRelPath) {
770
- if (!FILE_TYPE[type]) {
771
- return;
772
- }
773
- list.push({
774
- type: type,
775
- basePath: basePath,
776
- relPath: relPath,
777
- isSubPath: isSubPath,
778
- indRelPath: indRelPath
779
- });
780
- }
781
-
782
- function _getFileList(dirPath, basePath, files, next) {
783
- // TODO: the following code should be more concise.
784
- // Handling symbolic links
785
- // if the path sym-link indicates is a sub-path of source directory, treat a sym-link as it is.
786
- // otherwise the files sym-link indicates should be copied
787
- async.waterfall([
788
- fs.readdir.bind(null, dirPath),
789
- function(fileNames, next) {
790
- if (fileNames.length === 0) {
791
- _pushList(files, 'dir', basePath, path.relative(basePath, dirPath), true, null);
792
- return setImmediate(next);
793
- }
794
-
795
- async.forEachSeries(fileNames, function(fileName, next) {
796
- const filePath = path.join(dirPath, fileName),
797
- relPath = path.relative(basePath, filePath);
798
-
799
- async.waterfall([
800
- fs.lstat.bind(null, filePath),
801
- function(lstat, next) {
802
- if (lstat.isSymbolicLink()) {
803
- let indicateFullPath;
804
- try {
805
- indicateFullPath = fs.realpathSync(filePath);
806
- } catch (err) {
807
- if (err.code === 'ENOENT') {
808
- log.warn("The file for symbolic link (" + filePath + ") is missing...");
809
- return setImmediate(next);
810
- }
811
- return setImmediate(next, err);
812
- }
813
-
814
- const indicateRelPath = fs.readlinkSync(filePath);
815
- if (indicateFullPath.indexOf(basePath) !== -1) {
816
- _pushList(files, 'symlink', basePath, relPath, true, indicateRelPath);
817
- } else {
818
- const stat = fs.statSync(filePath);
819
- if (stat.isDirectory()) {
820
- return _getFileList(filePath, basePath, files, next);
821
- } else if (stat.isFile()) {
822
- _pushList(files, 'file', basePath, relPath, true, null);
823
- }
824
- }
825
- setImmediate(next);
826
- } else if (lstat.isDirectory()) {
827
- return _getFileList(filePath, basePath, files, next);
828
- } else if (lstat.isFile()) {
829
- _pushList(files, 'file', basePath, relPath, true, null);
830
- setImmediate(next);
831
- } else {
832
- setImmediate(next);
833
- }
834
- }
835
- ], next); // async.waterfall
836
- }, next); // async.forEach
837
- }
838
- ], function(err) {
839
- return setImmediate(next, err);
840
- }); // async.waterfall
841
- }
842
-
843
- function _copySrcToDst(files, dstPath, minify, next) {
844
- try {
845
- async.forEachSeries(files, function(file, next) {
846
- if (!FILE_TYPE[file.type]) {
847
- log.verbose("package#copySrcToDst()#_copySrcToDst()", "ignore 'unknown file type'("+file.type+")");
848
- return;
849
- }
850
-
851
- if (!file.relPath) {
852
- log.verbose("package#copySrcToDst()#_copySrcToDst()", "ignore 'unknown path'");
853
- return setImmediate(next);
854
- }
855
-
856
- if (file.type === FILE_TYPE.dir) {
857
- mkdirp.sync(path.join(dstPath, file.relPath));
858
- return setImmediate(next);
859
- }
860
-
861
- const dstDirPath = path.dirname(path.join(dstPath, file.relPath));
862
- if (!fs.existsSync(dstDirPath)) {
863
- mkdirp.sync(dstDirPath);
864
- }
865
-
866
- if (file.type === FILE_TYPE.symlink) {
867
- if (file.isSubPath && file.indRelPath) {
868
- const linkFile = path.join(dstPath, file.relPath);
869
- if (fs.existsSync(linkFile)) {
870
- if (fs.lstatSync(linkFile).isSymbolicLink()) {
871
- fs.unlinkSync(linkFile);
872
- }
873
- }
874
- fs.symlinkSync(file.indRelPath, linkFile, null);
875
- }
876
- } else {
877
- const sourceFile = path.join(file.basePath, file.relPath);
878
- if (fs.existsSync(sourceFile)) {
879
- if (minify && '.js' === path.extname(sourceFile) && file.relPath.indexOf('node_modules') === -1) {
880
- log.verbose("package#copySrcToDst()#_copySrcToDst()", "require minification # sourceFile:", sourceFile);
881
- try {
882
- const data = uglify.minify(fs.readFileSync(sourceFile,'utf8'));
883
- if (data.error) {
884
- throw data.error;
885
- }
886
- fs.writeFileSync(path.join(dstPath, file.relPath), data.code, 'utf8');
887
- } catch (e) {
888
- log.verbose("package#copySrcToDst()#_copySrcToDst()", util.format('Failed to uglify code %s: %s', sourceFile, e.stack));
889
- return setImmediate(next, errHndl.getErrMsg("FAILED_MINIFY", sourceFile));
890
- }
891
- } else {
892
- shelljs.cp('-Rf', sourceFile, path.join(dstPath, file.relPath, '..'));
893
- }
894
- } else {
895
- log.verbose("package#copySrcToDst()#_copySrcToDst()", "ignore '" + file.relPath + "'");
896
- }
897
- }
898
- setImmediate(next);
899
- }, function(err) {
900
- if (!err && minify) {
901
- self.minifyDone = true;
902
- }
903
- setImmediate(next, err);
904
- });
905
- } catch(err) {
906
- setImmediate(next, err);
907
- }
908
- }
909
- }
910
-
911
- function checkELFHeader(next) {
912
- const self = this,
913
- ELF_HEADER_LEN = 64,
914
- buf = Buffer.alloc(ELF_HEADER_LEN),
915
- mainFile = path.resolve(path.join(this.appDir, this.appinfo.main));
916
-
917
- if (!fs.existsSync(mainFile)) {
918
- return setImmediate(next, errHndl.getErrMsg("NOT_EXIST_PATH", mainFile));
919
- }
920
-
921
- const fd = fs.openSync(mainFile, 'r'),
922
- stats = fs.fstatSync(fd),
923
- elfParser = new ElfParser(),
924
- _isELF = function(_buf) {
925
- if (_buf.slice(0, 4).toString() !== '\x7fELF') {
926
- return false;
927
- } else {
928
- return true;
929
- }
930
- };
931
-
932
- if (stats.size < ELF_HEADER_LEN) {
933
- log.verbose("package#checkELFHeader()", "file size is smaller than ELF Header size");
934
- return setImmediate(next);
935
- }
936
-
937
- fs.read(fd, buf, 0, ELF_HEADER_LEN, 0, function(err, bytesRead, _buf) {
938
- if (bytesRead < ELF_HEADER_LEN || err) {
939
- log.silly("package#checkELFHeader()", "err:", err, ", bytesRead:", bytesRead);
940
- log.silly("package#checkELFHeader()", "readBuf to parse ELF header is small or error occurred during reading file.");
941
- return setImmediate(next);
942
- }
943
-
944
- if (!_isELF(_buf)) {
945
- log.silly("package#checkELFHeader()", mainFile + " is not ELF format");
946
- } else {
947
- log.silly("package#checkELFHeader()", mainFile + " is ELF format");
948
- try {
949
- const elfHeader = elfParser.parseHeader(_buf);
950
- log.silly("package#checkELFHeader()", "elfHeader:", elfHeader);
951
-
952
- if (elfHeader.machine && elfHeader.machine.match(/86$/)) {
953
- // current emulator opkg is allowing only all, noarch and i586.
954
- // when it is used with --offline-root.
955
- self.architecture = 'i586';
956
- } else if (elfHeader.machine && elfHeader.machine.match(/amd64$/)) {
957
- // change amd64 to x86_64
958
- self.architecture = 'x86_64';
959
- } else if (elfHeader.machine && elfHeader.machine.match(/AArch64$/)) {
960
- // change AArch64 to aarch64
961
- self.architecture = 'aarch64';
962
- } else {
963
- self.architecture = elfHeader.machine;
964
- }
965
- } catch(e) {
966
- log.verbose("package#checkELFHeader()", "exception:", e);
967
- }
968
- }
969
- log.verbose("package#checkELFHeader()", "machine:", self.architecture);
970
- fs.close(fd);
971
- setImmediate(next);
972
- });
973
- }
974
-
975
- function copyApp(next) {
976
- if (this.appCount === 0) {
977
- return setImmediate(next);
978
- }
979
-
980
- this.dataCopyCount++;
981
- log.info("package#copyApp()", "copy " + this.appDir + " ==> " + this.applicationDir);
982
- copySrcToDst.call(this, this.appDir, this.applicationDir, next);
983
- }
984
-
985
- function copyAssets(next) {
986
- if (this.appCount === 0) {
987
- return setImmediate(next);
988
- }
989
-
990
- try {
991
- async.forEachSeries(this.appinfo.assets, _handleAssets.bind(this), next);
992
- } catch (err) {
993
- return setImmediate(next, err);
994
- }
995
-
996
- function _handleAssets(file, next) {
997
- if (path.resolve(this.originAppDir) === path.resolve(this.appDir)) {
998
- _checkAppinfo.call(this, file, next);
999
- } else {
1000
- async.series([
1001
- _checkAppinfo.bind(this, file),
1002
- _copyAssets.bind(this, file)
1003
- ], next);
1004
- }
1005
- }
1006
-
1007
- function _checkAppinfo(file, next) {
1008
- let source;
1009
- if (path.resolve(file) === path.normalize(file)) {
1010
- return next(errHndl.getErrMsg("NOT_RELATIVE_PATH_APPINFO", file));
1011
- } else {
1012
- source = path.join(this.originAppDir, file);
1013
- }
1014
-
1015
- if (path.resolve(source).indexOf(this.originAppDir) !== 0) {
1016
- return next(errHndl.getErrMsg("NOT_RELATIVE_PATH_APPINFO", file));
1017
- }
1018
-
1019
- if (!fs.existsSync(source) && !this.rom) {
1020
- const msg = errHndl.getErrMsg("NOT_EXIST_PATH", file);
1021
- if (path.basename(source).indexOf('$') === 0) {
1022
- // ignore property with starting $ prefix (dynamic property handling in the platform)
1023
- return setImmediate(next);
1024
- } else {
1025
- return setImmediate(next, msg);
1026
- }
1027
- }
1028
- setImmediate(next);
1029
- }
1030
-
1031
- function _copyAssets(file, next) {
1032
- log.verbose("package#copyAssets()#_copyAssets", "'" + file + "' will be located in app directory");
1033
- const source = path.join(this.originAppDir, file),
1034
- destination = this.appDir;
1035
-
1036
- async.series([
1037
- function(next) {
1038
- if (!fs.existsSync(destination)) {
1039
- mkdirp(destination, next);
1040
- } else {
1041
- setImmediate(next);
1042
- }
1043
- }
1044
- ], function(err) {
1045
- if (err) {
1046
- return setImmediate(next, err);
1047
- }
1048
- shelljs.cp('-Rf', source, destination);
1049
- setImmediate(next);
1050
- });
1051
- }
1052
- }
1053
-
1054
- function excludeIpkFileFromApp(next) {
1055
- // Exclude a pre-built .ipk file
1056
- this.excludeFiles = this.excludeFiles.concat([
1057
- // eslint-disable-next-line no-useless-escape
1058
- "[.]*[\\.]ipk",
1059
- ".DS_Store",
1060
- // ".vscode",
1061
- // ".reloadignore",
1062
- // ".launchparams.json"
1063
- ]);
1064
- setImmediate(next);
1065
- }
1066
-
1067
- function _retrieve(list, regExp, dirPath, depth, next) {
1068
- async.waterfall([
1069
- fs.readdir.bind(null, dirPath), function(fileNames, next) {
1070
- async.forEach(fileNames, function(fileName, next) {
1071
- const filePath = path.join(dirPath, fileName);
1072
- async.waterfall([
1073
- fs.lstat.bind(null, filePath), function(stat, next) {
1074
- let result = false;
1075
- if (depth > 1 && regExp.test(fileName)) {
1076
- result = true;
1077
- list.push(filePath);
1078
- }
1079
-
1080
- if (!result && stat.isDirectory()) {
1081
- _retrieve(list, regExp, filePath, depth + 1, next);
1082
- } else {
1083
- setImmediate(next);
1084
- }
1085
- }
1086
- ], next);
1087
- }, next);
1088
- }
1089
- ], function(err) {
1090
- setImmediate(next, err);
1091
- });
1092
- }
1093
-
1094
- function excludeFromApp(middleCb, next) {
1095
- let excludes;
1096
- if (this.appCount === 0) {
1097
- excludes = this.excludeFiles;
1098
- } else {
1099
- excludes = this.excludeFiles.concat(this.appinfo.exclude || []);
1100
- }
1101
-
1102
- const regExpQueries = excludes.map(function(exclude) {
1103
- return exclude.replace(/^\./g,"^\\.").replace(/^\*/g,"").replace(/$/g,"$");
1104
- }, this),
1105
- strRegExp = regExpQueries.join("|"),
1106
- regExp = new RegExp(strRegExp, "i"),
1107
- excludeList = [],
1108
- tempDirPrefix = path.resolve(this.tempDir, "data/usr/palm");
1109
-
1110
- async.series([
1111
- _retrieve.bind(this, excludeList, regExp, tempDirPrefix, 0), function(next) {
1112
- const meta_files = ["appinfo.json", "services.json", "packageinfo.json", "resourceinfo.json"],
1113
- unexcludable = [],
1114
- excluded = [];
1115
- try {
1116
- excludeList.forEach(function(file) {
1117
- if (meta_files.includes(path.basename(file))) {
1118
- unexcludable.push(path.basename(file));
1119
- } else {
1120
- const sliced = file.slice(tempDirPrefix.length + 1);
1121
- excluded.push(sliced.slice(sliced.indexOf(path.sep) + 1));
1122
- shelljs.rm('-rf', file);
1123
- }
1124
- });
1125
- if (unexcludable.length > 0) {
1126
- middleCb("Cannot exclude: " + unexcludable); // FIXME
1127
- }
1128
- if (excluded.length > 0) {
1129
- middleCb("Excluded: " + excluded); // FIXME
1130
- }
1131
- setImmediate(next);
1132
- } catch(err) {
1133
- setImmediate(next, err);
1134
- }
1135
- }
1136
- ], function(err) {
1137
- if (err) {
1138
- return setImmediate(next, err);
1139
- }
1140
- setImmediate(next);
1141
- });
1142
- }
1143
-
1144
- function createPackageDir(next) {
1145
- if (!this.rom) {
1146
- const pkgDirName = this.pkgId || this.appinfo.id;
1147
- this.packageDir = path.join(this.tempDir, "data/usr/palm/packages", pkgDirName);
1148
- log.verbose("package#createPackageDir", "directory:", this.packageDir);
1149
- mkdirp(this.packageDir, next);
1150
- } else {
1151
- setImmediate(next);
1152
- }
1153
- }
1154
-
1155
- function fillPackageDir(next) {
1156
- log.verbose("fillPackageDir");
1157
-
1158
- if (!this.rom) {
1159
- let data = "";
1160
- if (this.pkginfo) {
1161
- // Use pkginfofile option or PKG_DIR
1162
- if (!this.pkginfo.version) {
1163
- this.pkginfo.version = this.pkgVersion || "1.0.0";
1164
- }
1165
- if (this.appinfo) {
1166
- this.pkginfo.app = this.appinfo.id;
1167
- }
1168
- _checkPkgInfo(this.pkginfo.id, this.pkginfo.version, next);
1169
- data = JSON.stringify(this.pkginfo, null, 2) + "\n";
1170
- } else {
1171
- // Use pkgid option or no option
1172
- const pkginfo = {
1173
- "id": this.pkgId || this.appinfo.id,
1174
- "version": this.pkgVersion || "1.0.0"
1175
- };
1176
- if (this.appinfo) {
1177
- pkginfo.app = this.appinfo.id;
1178
- }
1179
- _checkPkgInfo(pkginfo.id, pkginfo.version, next);
1180
- data = JSON.stringify(pkginfo, null, 2) + "\n";
1181
- }
1182
- log.verbose("Generating packageinfo.json: " + data);
1183
- fs.writeFile(path.join(this.packageDir, "packageinfo.json"), data, next);
1184
- } else {
1185
- setImmediate(next);
1186
- }
1187
-
1188
- function _checkPkgInfo(id, version, next) {
1189
- if (!(/^[a-z0-9.+-]*$/.test(id))) {
1190
- log.error(errHndl.getErrMsg("INVALID_VALUE", "pkg id", id));
1191
- return setImmediate(next, errHndl.getErrMsg("INVALID_ID_RULE"));
1192
- }
1193
-
1194
- if (!(/^([1-9]\d{0,8}|\d)\.([1-9]\d{0,8}|\d)\.([1-9]\d{0,8}|\d)$/.test(version))) {
1195
- log.error(errHndl.getErrMsg("INVALID_VALUE", "pkg version", version));
1196
- return setImmediate(next, errHndl.getErrMsg("INVALID_VERSION_RULE"));
1197
- }
1198
- }
1199
- }
1200
-
1201
-
1202
- function loadPackageProperties(inDirs, next) {
1203
- const self = this;
1204
- self.packageProperties = {};
1205
-
1206
- async.forEach(inDirs, function(inDir, next) {
1207
- const filename = path.join(inDir, "package.properties");
1208
-
1209
- function _checkFileMode(file) {
1210
- file = file.replace(/\\/g, "/").trim();
1211
- const idx = file.lastIndexOf("/");
1212
- file = (idx !== -1) ? file.slice(idx + 1) : file;
1213
- self.packageProperties[file] = this.fileMode;
1214
- }
1215
-
1216
- if (fs.existsSync(filename)) {
1217
- const data = fs.readFileSync(filename);
1218
- try {
1219
- const lines = data.toString().split("\n");
1220
- let seperatorIndex;
1221
-
1222
- for (const i in lines) {
1223
- if (lines[i].indexOf("filemode.") === 0) {
1224
- seperatorIndex = lines[i].indexOf("=");
1225
- const fileList = lines[i].slice(seperatorIndex + 1).trim(),
1226
- fileArray = fileList.split(",");
1227
- this.fileMode = lines[i].slice(9, seperatorIndex).trim();
1228
-
1229
- fileArray.forEach(_checkFileMode);
1230
- }
1231
- }
1232
- setImmediate(next);
1233
- } catch (error) {
1234
- setImmediate(next, error);
1235
- }
1236
- } else {
1237
- setImmediate(next);
1238
- }
1239
- }, function(err) {
1240
- // Exclude package.propeties from ipk file
1241
- self.excludeFiles = self.excludeFiles.concat([
1242
- "package.properties"
1243
- ]);
1244
- setImmediate(next, err);
1245
- });
1246
- }
1247
-
1248
- function outputPackage(destination, next) {
1249
- log.info("package#outputPackage()");
1250
-
1251
- // Check that the directories exist
1252
- if (fs.existsSync(destination)) {
1253
- const stats = fs.statSync(destination);
1254
- if (!stats.isDirectory()) {
1255
- return next(errHndl.getErrMsg("NOT_DIRTYPE_PATH", destination));
1256
- }
1257
- } else {
1258
- log.verbose("setOutputDir()", "creating directory '" + destination + "' ...");
1259
- mkdirp.sync(destination);
1260
- }
1261
- destination = fs.realpathSync(destination);
1262
-
1263
- if (this.rom) {
1264
- copySrcToDst.call(this, path.join(this.tempDir, 'data'), destination, next);
1265
- } else {
1266
- const tempDir = this.tempDir,
1267
- tempCtrlDir = path.join(tempDir, 'ctrl'),
1268
- ctrlTgzFile = path.join(tempDir, 'control.tar.gz'),
1269
- tempDataDir = path.join(tempDir, 'data'),
1270
- dataTgzFile = path.join(tempDir, 'data.tar.gz');
1271
-
1272
- async.series([
1273
- decidePkgName.bind(this, this.pkgId, this.pkgVersion),
1274
- getFileSize.bind(this, tempDataDir),
1275
- makeTgz.bind(this, tempDataDir, dataTgzFile),
1276
- createDir.bind(this, tempCtrlDir),
1277
- createControlFile.bind(this, tempCtrlDir, false),
1278
- createSign.bind(this, tempCtrlDir, dataTgzFile),
1279
- makeTgz.bind(this, tempCtrlDir, ctrlTgzFile),
1280
- createDebianBinary.bind(this, tempDir),
1281
- setIpkFileName.bind(this),
1282
- removeExistingIpk.bind(this, destination),
1283
- makeIpk.bind(this, tempDir)
1284
- ], function(err) {
1285
- if (err) {
1286
- setImmediate(next, err);
1287
- return;
1288
- }
1289
- setImmediate(next);
1290
- });
1291
- }
1292
- }
1293
-
1294
- function decidePkgName(pkgName, pkgVersion, next) {
1295
- if (this.appCount !== 0) {
1296
- this.pkg = {
1297
- name: pkgName || this.appinfo.id,
1298
- version: pkgVersion || this.appinfo.version
1299
- };
1300
- } else if (this.services.length > 0) {
1301
- this.pkg = {
1302
- name: pkgName || this.services[0].serviceInfo.id || this.services[0].serviceInfo.services[0].name,
1303
- version: pkgVersion || "1.0.0"
1304
- };
1305
- } else {
1306
- this.pkg = {
1307
- name: pkgName || "unknown",
1308
- version: pkgVersion || "1.0.0"
1309
- };
1310
- }
1311
- setImmediate(next);
1312
- }
1313
-
1314
- function getFileSize(srcDir, next) {
1315
- const self = this;
1316
-
1317
- async.waterfall([
1318
- _readSizeRecursive.bind(this, srcDir)
1319
- ], function(err, size) {
1320
- if (!err && size) {
1321
- log.verbose("package#getFileSize()", "Installed-Size:", size);
1322
- self.size = size;
1323
- }
1324
- setImmediate(next, err);
1325
- });
1326
-
1327
- function _readSizeRecursive(item, next) {
1328
- fs.lstat(item, function(err, stats) {
1329
- let total = stats.size;
1330
-
1331
- if (!err && stats.isDirectory()) {
1332
- fs.readdir(item, function(error, list) {
1333
- if (error) {
1334
- return next(error);
1335
- }
1336
-
1337
- async.forEach(list, function(diritem, callback) {
1338
- _readSizeRecursive(path.join(item, diritem), function(_err, size) {
1339
- total += size;
1340
- callback(_err);
1341
- });
1342
- }, function(e) {
1343
- next(e, total);
1344
- }
1345
- );
1346
- });
1347
- } else {
1348
- next(err, total);
1349
- }
1350
- });
1351
- }
1352
- }
1353
-
1354
- function createDir(dstPath, next) {
1355
- log.verbose("package#createDir()", "createDir:" + dstPath);
1356
- mkdirp(dstPath, next);
1357
- }
1358
-
1359
- function createControlFile(dstDir, encInfo, next) {
1360
- const dstFilePath = path.join(dstDir, 'control');
1361
- log.verbose("package#createControlFile()", "createControlFile:" + dstFilePath);
1362
-
1363
- const lines = [
1364
- "Package: " + this.pkg.name,
1365
- "Version: " + this.pkg.version,
1366
- "Section: misc",
1367
- "Priority: optional",
1368
- "Architecture: " + (this.architecture || "all"),
1369
- "Installed-Size: " + (this.size || 1234), // TODO: TBC
1370
- "Maintainer: N/A <nobody@example.com>", // TODO: TBC
1371
- "Description: This is a webOS application.",
1372
- "webOS-Package-Format-Version: 2", // TODO: TBC
1373
- "webOS-Packager-Version: x.y.x" // TODO: TBC
1374
- ];
1375
-
1376
- if (encInfo) {
1377
- lines.push("Encrypt-Algorithm: AES-256-CBC");
1378
- }
1379
- lines.push(''); // for the trailing \n
1380
- fs.writeFile(dstFilePath, lines.join("\n"), next);
1381
- }
1382
-
1383
- function createSign(dstDir, dataTgzPath, next) {
1384
- if ((!this.sign) || (!this.certificate)) {
1385
- log.verbose("package#createSign()", "App signing is skipped");
1386
- return setImmediate(next);
1387
- }
1388
-
1389
- const sigFilePath = path.join(dstDir, 'data.tar.gz.sha256.txt'),
1390
- keyPath = path.resolve(this.sign),
1391
- crtPath = path.resolve(this.certificate);
1392
-
1393
- log.verbose("package#createSign()", "dataTgzPath:" + dataTgzPath + ", sigfile:" + sigFilePath);
1394
- log.verbose("package#createSign()", "keyPath:" + keyPath + ", crtPath:" + crtPath);
1395
-
1396
- try {
1397
- // Create certificate to tmp/ctrl directory
1398
- shelljs.cp('-f', crtPath, dstDir);
1399
-
1400
- // Create signature and write data.tar.gz.sha256.txt
1401
- const privateKey = fs.readFileSync(keyPath, 'utf-8'),
1402
- dataFile = fs.readFileSync(dataTgzPath), // data.tar.gz
1403
- signer = crypto.createSign('sha256');
1404
-
1405
- signer.update(dataFile);
1406
- signer.end();
1407
-
1408
- const signature = signer.sign(privateKey),
1409
- buff = Buffer.from(signature),
1410
- base64data = buff.toString('base64');
1411
-
1412
- fs.writeFile(sigFilePath, base64data, next);
1413
- } catch (err) {
1414
- setImmediate(next, err);
1415
- }
1416
- }
1417
-
1418
- function createDebianBinary(dstDir, next) {
1419
- const dstFilePath = path.join(dstDir, "debian-binary");
1420
- log.verbose("package#createDebianBinary()", dstFilePath);
1421
- fs.writeFile(dstFilePath, "2.0\n", next);
1422
- }
1423
-
1424
- function makeTgz(srcDir, dstDir, next) {
1425
- log.verbose("package#makeTgz()", "makeTgz " + dstDir + " from " + srcDir);
1426
- const pkgServiceNames = this.pkgServiceNames;
1427
- // @see https://github.com/isaacs/node-tar/issues/7
1428
- // it is a workaround for packaged ipk on windows can set +x into directory
1429
- const fixupDirs = function(entry) {
1430
- // Make sure readable directories have execute permission
1431
- if (entry.props.type === "Directory") {
1432
- let maskingBits = 201; // 0311
1433
- // special case for service directory should have writable permission.
1434
- if (pkgServiceNames.indexOf(entry.props.basename) !== -1) {
1435
- maskingBits = 219; // 0333
1436
- }
1437
- entry.props.mode |= (entry.props.mode >>> 2) & maskingBits;
1438
- } else if (entry.props.type === "File") {
1439
- // Add other user's readable permission to all files
1440
- entry.props.mode |= 4; // 04
1441
- }
1442
- return true;
1443
- };
1444
-
1445
- // TODO: when this PR (https://github.com/npm/node-tar/pull/73) is merged, need to update node-tar
1446
- fstream
1447
- .Reader({path: srcDir, type: 'Directory', filter: fixupDirs })
1448
- .pipe(tarFilterPack({ noProprietary: true, fromBase: true, permission: this.packageProperties }))
1449
- // .pipe(tarFilterPack({ noProprietary: true, pathFilter: filter, permission: this.packageProperties }))
1450
- .pipe(zlib.createGzip())
1451
- .pipe(fs.createWriteStream(dstDir))
1452
- .on("close", next)
1453
- .on('error', next);
1454
- }
1455
-
1456
- function setIpkFileName(next) {
1457
- let filename = this.pkg.name;
1458
- if (this.pkg.version) {
1459
- // This is asked to replace 'x86' from 'i586' as a file suffix (From NDK)
1460
- const archSuffix = ('i586' === this.architecture) ? 'x86' : (this.architecture || 'all');
1461
- filename = filename.concat("_" + this.pkg.version + "_" + archSuffix + ".ipk");
1462
- } else {
1463
- filename = filename.concat(".ipk");
1464
- }
1465
- this.ipkFileName = filename;
1466
- setImmediate(next);
1467
- }
1468
-
1469
- function removeExistingIpk(destination, next) {
1470
- if (this.appCount === 0) {
1471
- return setImmediate(next);
1472
- }
1473
-
1474
- const filename = path.join(destination, this.ipkFileName);
1475
- fs.exists(filename, function(exists) {
1476
- if (exists) {
1477
- fs.unlink(filename, next);
1478
- } else {
1479
- setImmediate(next); // Nothing to do
1480
- }
1481
- });
1482
- }
1483
-
1484
- function padSpace(input, length) {
1485
- // max field length in ar is 16
1486
- const ret = String(input + ' ');
1487
- return ret.slice(0, length);
1488
- }
1489
-
1490
- function arFileHeader(name, size) {
1491
- const epoch = Math.floor(Date.now() / 1000) ;
1492
- return padSpace(name, 16) +
1493
- padSpace(epoch, 12) +
1494
- "0 " + // UID, 6 bytes
1495
- "0 " + // GID, 6 bytes
1496
- "100644 " + // file mode, 8 bytes
1497
- padSpace(size, 10) +
1498
- "\x60\x0A"; // don't ask
1499
- }
1500
-
1501
- function makeIpk(srcDir, next) {
1502
- this.IpkDir = srcDir;
1503
- this.ipk = path.join(srcDir, this.ipkFileName);
1504
- log.info("package#makeIpk()", "makeIpk in dir " + this.IpkDir + " file " + this.ipkFileName);
1505
-
1506
- if (this.nativecmd) { // TODO: TBR
1507
- shelljs.cd(this.IpkDir);
1508
- shelljs.exec("ar -q " + this.ipk + " debian-binary control.tar.gz data.tar.gz", {silent: this.silent});
1509
-
1510
- setImmediate(next);
1511
- return;
1512
- }
1513
-
1514
- // global header, see http://en.wikipedia.org/wiki/Ar_%28Unix%29
1515
- const header = "!<arch>\n",
1516
- debBinary = arFileHeader("debian-binary",4) + "2.0\n",
1517
- that = this,
1518
- arStream = CombinedStream.create(),
1519
- pkgFiles = [ 'control.tar.gz', 'data.tar.gz' ],
1520
- ipkStream = fstream.Writer(this.ipk);
1521
-
1522
- arStream.append(header + debBinary);
1523
- pkgFiles.forEach(function(f) {
1524
- const fpath = path.join(that.IpkDir, f),
1525
- s = fstream.Reader({ path: fpath, type: 'File'}),
1526
- stat = fs.statSync(fpath); // TODO: move to asynchronous processing
1527
-
1528
- arStream.append(arFileHeader(f, stat.size));
1529
- arStream.append(s);
1530
- if ((stat.size % 2) !== 0) {
1531
- log.verbose("package#makeIpk()", 'adding a filler for file ' + f);
1532
- arStream.append('\n');
1533
- }
1534
- }, this);
1535
-
1536
- arStream.pipe(ipkStream);
1537
- ipkStream.on('close', function() {
1538
- setImmediate(next);
1539
- });
1540
- ipkStream.on('error', next);
1541
- }
1542
-
1543
- function cleanupTmpDir(middleCb, next) {
1544
- if (this.noclean) {
1545
- middleCb("Skipping removal of " + this.tempDir);
1546
- setImmediate(next);
1547
- } else {
1548
- rimraf(this.tempDir, function(err) {
1549
- log.verbose("package#cleanupTmpDir()", "removed " + this.tempDir);
1550
- setImmediate(next, err);
1551
- }.bind(this));
1552
- }
1553
- }
1554
-
1555
- function checkDirectory(options, directory, callback) {
1556
- log.verbose("package#checkDirectory()", directory);
1557
- if (fs.existsSync(directory)) { // TODO: move to asynchronous processing
1558
- const stat = fs.statSync(directory);
1559
- if (!stat.isDirectory()) {
1560
- callback(errHndl.getErrMsg("NOT_DIRTYPE_PATH", directory));
1561
- return;
1562
- }
1563
- directory = fs.realpathSync(directory);
1564
- } else {
1565
- callback(errHndl.getErrMsg("NOT_EXIST_PATH", directory));
1566
- return;
1567
- }
1568
-
1569
- if (options.force) {
1570
- return callback();
1571
- }
1572
-
1573
- if (fs.existsSync(path.join(directory, "packageinfo.json"))) {
1574
- return callback(errHndl.getErrMsg("NOT_PACKAGE_WITH_PKGDIR"));
1575
- }
1576
-
1577
- if (fs.existsSync(path.join(directory, "appinfo.json"))) {// TODO: move to asynchronous processing
1578
- this.appCount++;
1579
- log.verbose("package#checkDirectory()", "FOUND appinfo.json, appCount " + this.appCount);
1580
-
1581
- if (this.appCount > 1) {
1582
- callback(errHndl.getErrMsg("OVER_APPCOUNT"));
1583
- } else if (this.rscCount > 0) {
1584
- callback(errHndl.getErrMsg("NOT_PACKAGE_WITH_RESOURCE"));
1585
- } else {
1586
- this.appDir = directory;
1587
- this.originAppDir = directory;
1588
- if (fs.existsSync(path.join(directory, "package.js"))) {
1589
- this.pkgJSExist = true;
1590
- }
1591
- callback();
1592
- }
1593
- } else if (fs.existsSync(path.join(directory, "services.json"))) {
1594
- if (this.rscCount > 0) {
1595
- callback(errHndl.getErrMsg("NOT_PACKAGE_WITH_RESOURCE"));
1596
- }
1597
- this.svcDir = this.svcDir || [];
1598
- this.svcDir = this.svcDir.concat(directory);
1599
- callback();
1600
- } else if (fs.existsSync(path.join(directory, "resourceinfo.json"))) {
1601
- this.rscCount++;
1602
- log.verbose("FOUND resourceinfo.json, rscCount " + this.rscCount);
1603
- if (this.appCount > 0 || this.svcDir && this.svcDir.length > 0) {
1604
- callback(errHndl.getErrMsg("NOT_PACKAGE_WITH_RESOURCE"));
1605
- }
1606
- this.resources = this.resources || [];
1607
- const rsc = {};
1608
- rsc.dir = directory;
1609
- this.resources = this.resources.concat(rsc);
1610
- callback();
1611
- } else if (fs.existsSync(path.join(directory, "account-templates.json"))) {
1612
- callback(errHndl.getErrMsg("NO_ACCOUNT"));
1613
- } else {
1614
- // find service directory recursively
1615
- const foundSvcDirs = [];
1616
- this.svcDir = this.svcDir || [];
1617
- this.svcDir = this.svcDir.concat(directory);
1618
-
1619
- findServiceDir.call(this, foundSvcDirs, function() {
1620
- if (foundSvcDirs.length > 0) {
1621
- if (this.rscCount > 0) {
1622
- callback(errHndl.getErrMsg("NOT_PACKAGE_WITH_RESOURCE"));
1623
- }
1624
- callback();
1625
- } else {
1626
- callback(errHndl.getErrMsg("NO_METAFILE", "APP_DIR/SVC_DIR", directory));
1627
- }
1628
- });
1629
- }
1630
- }
1631
-
1632
- // find service directories checking if directory has services.json file
1633
- function findServiceDir(services, next) {
1634
- const checkDirs = [].concat(this.svcDir || this.originAppDir || []),
1635
- foundFilePath = [];
1636
- if (checkDirs.length === 0) {
1637
- return setImmediate(next);
1638
- }
1639
-
1640
- async.forEach(checkDirs, function(checkDir, next) {
1641
- walkFolder(checkDir, "services.json", foundFilePath, 3, function(err) {
1642
- if (err) {
1643
- return setImmediate(next, err);
1644
- }
1645
-
1646
- foundFilePath.forEach(function(filePath) {
1647
- const svc = new Service();
1648
- svc.srcDir = path.dirname(filePath);
1649
- svc.dirName = path.basename(svc.srcDir);
1650
- services.push(svc);
1651
- });
1652
- foundFilePath.pop();
1653
- setImmediate(next, err);
1654
- });
1655
- }, function(err) {
1656
- setImmediate(next, err);
1657
- });
1658
- }
1659
-
1660
- function walkFolder(dirPath, findFileName, foundFilePath, depth, next) {
1661
- if (depth <= 0) {
1662
- return next();
1663
- }
1664
- async.waterfall([
1665
- fs.readdir.bind(null, dirPath),
1666
- function(fileNames, next) {
1667
- async.forEach(fileNames, function(fileName, next) {
1668
- const filePath = path.join(dirPath, fileName);
1669
- async.waterfall([
1670
- fs.lstat.bind(null, filePath),
1671
- function(stat, next) {
1672
- if (stat.isFile()) {
1673
- if (fileName === findFileName) {
1674
- foundFilePath.push(filePath);
1675
- }
1676
- next();
1677
- } else if (stat.isDirectory()) {
1678
- walkFolder(filePath, findFileName, foundFilePath, (depth-1), next);
1679
- } else {
1680
- next();
1681
- }
1682
- }
1683
- ], next); // async.waterfall
1684
- }, next); // async.forEach
1685
- }
1686
- ], function(err) {
1687
- next(err);
1688
- }); // async.waterfall
1689
- }
1690
-
1691
- // read services.json recursivly
1692
- function loadServiceInfo(next) {
1693
- for (const idx in this.services) {
1694
- const filename = path.join(this.services[idx].srcDir, "services.json");
1695
- try {
1696
- const data = fs.readFileSync(filename),
1697
- info = JSON.parse(data);
1698
- if (!(Object.prototype.hasOwnProperty.call(info, 'id') &&
1699
- Object.prototype.hasOwnProperty.call(info, 'services'))) {
1700
- continue;
1701
- }
1702
- this.services[idx].serviceInfo = info;
1703
- this.services[idx].valid = true;
1704
- } catch (err) {
1705
- return setImmediate(next, err);
1706
- }
1707
- }
1708
- log.info("package#loadServiceInfo()", "num of serviceInfo: " + this.services.length);
1709
- setImmediate(next);
1710
- }
1711
-
1712
- // check services.json recursivly
1713
- function checkServiceInfo(next) {
1714
- const pkgId = this.pkgId || this.appinfo.id,
1715
- svcIds = [];
1716
- let errFlag = false;
1717
-
1718
- this.services.forEach(function(service) {
1719
- if (service.valid === false) {
1720
- return;
1721
- }
1722
-
1723
- svcIds.push(getPkgServiceNames(service.serviceInfo)[0]);
1724
- });
1725
-
1726
- svcIds.forEach(function(svcId) {
1727
- if (svcId.indexOf(pkgId + ".") !== 0) {
1728
- errFlag = true;
1729
- }
1730
- });
1731
-
1732
- if (errFlag) {
1733
- return setImmediate(next, errHndl.getErrMsg("INVALID_SERVICEID", pkgId));
1734
- }
1735
- setImmediate(next);
1736
- }
1737
-
1738
- // create dir with each service's name under (tmp) + data/usr/palm/services/
1739
- function createServiceDir(next) {
1740
- this.services.forEach(function(service) {
1741
- if (service.valid === false) {
1742
- return;
1743
- }
1744
-
1745
- getPkgServiceNames(service.serviceInfo).forEach(function(serviceName) {
1746
- const serviceDir = path.join(this.tempDir, "data/usr/palm/services", serviceName);
1747
- service.dstDirs.push(serviceDir);
1748
- try {
1749
- log.info("package#createServiceDir()", "service dir:" + serviceDir);
1750
- mkdirp.sync(serviceDir);
1751
- } catch (err) {
1752
- return setImmediate(next, err);
1753
- }
1754
- }.bind(this));
1755
- }.bind(this));
1756
- setImmediate(next);
1757
- }
1758
-
1759
- // copy service files into each serviceInfos[x].id directory.
1760
- function copyService(next) {
1761
- log.info("package#copyService()");
1762
- const self = this,
1763
- validServices = this.services.filter(function(service) {
1764
- return service.valid;
1765
- });
1766
- try {
1767
- async.forEachSeries(validServices, function(service, next) {
1768
- async.forEach(service.dstDirs, function(dstDir, next) {
1769
- self.dataCopyCount++;
1770
- self.minifyDone = !self.minify;
1771
- copySrcToDst.call(self, service.srcDir, dstDir, next);
1772
- }, next);
1773
- }, next);
1774
- } catch (err) {
1775
- setImmediate(next, err);
1776
- }
1777
- }
1778
-
1779
- // add service info into packageinfo.json.
1780
- function addServiceInPkgInfo(next) {
1781
- if (!this.rom) {
1782
- const filename = path.join(this.packageDir, "packageinfo.json");
1783
- let pkginfo, validServiceCount;
1784
- try {
1785
- const data = fs.readFileSync(filename);
1786
- validServiceCount = 0;
1787
- pkginfo = JSON.parse(data);
1788
- log.silly("package#addServiceInPkgInfo()", "PACKAGEINFO:", pkginfo);
1789
- } catch (err) {
1790
- console.error(err);
1791
- setImmediate(next, err);
1792
- }
1793
- this.services.filter(function(s) {
1794
- return s.valid;
1795
- }).forEach(function(service) {
1796
- getPkgServiceNames(service.serviceInfo).forEach(function(serviceName) {
1797
- this.pkgServiceNames.push(serviceName);
1798
- validServiceCount++;
1799
- }.bind(this));
1800
- }.bind(this));
1801
-
1802
- if (validServiceCount > 0) {
1803
- pkginfo.services = this.pkgServiceNames;
1804
- const data = JSON.stringify(pkginfo, null, 2) + "\n";
1805
- log.silly("package#addServiceInPkgInfo()", "Modified package.json:" + data);
1806
- fs.writeFile(path.join(this.packageDir, "packageinfo.json"), data, next);
1807
- } else {
1808
- setImmediate(next);
1809
- }
1810
- } else {
1811
- setImmediate(next);
1812
- }
1813
- }
1814
-
1815
- // remove service dir from tmp source dir before packaging
1816
- function removeServiceFromAppDir(middleCb, next) {
1817
- if (this.appCount === 0) {
1818
- return setImmediate(next);
1819
- }
1820
-
1821
- let checkDir = this.applicationDir,
1822
- needRmCheckDir = false;
1823
- const fileList = fs.readdirSync(checkDir);
1824
-
1825
- function _checkDir(dir) {
1826
- if (this.dirName === dir) {
1827
- try {
1828
- const rmDir = path.join(this.applicationDir, this.dirName);
1829
- shelljs.rm('-rf', rmDir);
1830
- } catch (err) {
1831
- next(Error("ERROR" + err), null);
1832
- }
1833
- }
1834
- }
1835
-
1836
- if (fileList.indexOf('services') !== -1) {
1837
- checkDir = path.join(this.applicationDir, 'services');
1838
- const stats = fs.statSync(checkDir);
1839
- if (stats.isDirectory()) {
1840
- needRmCheckDir = true;
1841
- }
1842
- }
1843
- if (needRmCheckDir === true) {
1844
- try {
1845
- shelljs.rm('-rf', checkDir);
1846
- } catch (err) {
1847
- middleCb("ERROR:" + err);
1848
- }
1849
- } else {
1850
- for (const idx in this.services) {
1851
- this.dirName = this.services[idx].dirName;
1852
- fileList.forEach(_checkDir, this);
1853
- }
1854
- }
1855
- setImmediate(next);
1856
- }
1857
-
1858
- function loadResourceInfo(next) {
1859
- log.verbose("loadResourceInfo");
1860
- if (this.rscCount === 0) {
1861
- return setImmediate(next);
1862
- }
1863
-
1864
- this.resources.forEach(function(resource) {
1865
- const filePath = path.join(resource.dir, "resourceinfo.json");
1866
- try {
1867
- const data = fs.readFileSync(filePath),
1868
- info = JSON.parse(data);
1869
- resource.info = info;
1870
- } catch (err) {
1871
- return setImmediate(next, errHndl.getErrMsg("INVALID_JSON_FORMAT", "resourceinfo.json"));
1872
- }
1873
- });
1874
- log.verbose("num of resourceInfo: " + this.resources.length);
1875
- setImmediate(next);
1876
- }
1877
-
1878
- function checkResourceInfo(next) {
1879
- log.verbose("checkResourceInfo");
1880
- const pkgId = this.pkgId;
1881
-
1882
- this.resources.forEach(function(resource) {
1883
- if (!resource.info.id) {
1884
- return setImmediate(next, errHndl.getErrMsg("REQUIRED_FIELD", "id"));
1885
- } else if (resource.info.id.indexOf(pkgId) !== 0) {
1886
- return setImmediate(next, errHndl.getErrMsg("INVALID_RESOURCEID", pkgId));
1887
- } else if (resource.info.id.length < 1 || !(/^[a-z0-9.+-]*$/.test(resource.info.id))) {
1888
- log.error(errHndl.getErrMsg("INVALID_VALUE", "id", resource.info.id));
1889
- return setImmediate(next, errHndl.getErrMsg("INVALID_ID_RULE"));
1890
- }
1891
- });
1892
- setImmediate(next);
1893
- }
1894
-
1895
- function createResourceDir(next) {
1896
- log.verbose("createResourceDir");
1897
- this.resources.forEach(function(resource) {
1898
- try {
1899
- const resourceDir = path.join(this.tempDir, "data/usr/palm/resources", resource.info.id);
1900
- log.verbose("resource dir = " + resourceDir);
1901
- resource.dstDir = resourceDir;
1902
- mkdirp.sync(resourceDir);
1903
- } catch (err) {
1904
- return setImmediate(next, err);
1905
- }
1906
- }.bind(this));
1907
- log.silly("this.resources:", this.resources);
1908
- setImmediate(next);
1909
- }
1910
-
1911
- function copyResource(next) {
1912
- log.verbose("copyResource()");
1913
- if (this.rscCount === 0) {
1914
- return setImmediate(next);
1915
- }
1916
-
1917
- const self = this;
1918
- try {
1919
- async.forEachSeries(this.resources, function(resource, next) {
1920
- self.dataCopyCount++;
1921
- log.verbose("copyResource(), copy " + resource.dir + " ==> " + resource.dstDir);
1922
- copySrcToDst.call(self, resource.dir, resource.dstDir, next);
1923
- }, next);
1924
- } catch (err) {
1925
- setImmediate(next, err);
1926
- }
1927
- }
1928
-
1929
- function addResourceInPkgInfo(next) {
1930
- log.verbose("addResourceInPkgInfo");
1931
- if (!this.rom && this.rscCount > 0) {
1932
- const filename = path.join(this.packageDir, "packageinfo.json");
1933
- let pkginfo, data;
1934
- try {
1935
- data = fs.readFileSync(filename);
1936
- log.verbose("PACKAGEINFO: " + data);
1937
- pkginfo = JSON.parse(data);
1938
- } catch (err) {
1939
- console.error(err);
1940
- setImmediate(next, err);
1941
- }
1942
-
1943
- this.resources.forEach(function(resource) {
1944
- this.pkgResourceNames.push(resource.info.id);
1945
- }.bind(this));
1946
-
1947
- pkginfo.resources = this.pkgResourceNames;
1948
- data = JSON.stringify(pkginfo, null, 2) + "\n";
1949
- log.verbose("Modified package.json: " + data);
1950
- fs.writeFile(path.join(this.packageDir, "packageinfo.json"), data, next);
1951
- } else {
1952
- setImmediate(next);
1953
- }
1954
- }
1955
-
1956
- function copyData(inDirs, forceCopy, next) {
1957
- log.verbose("package#copyData()", "only run when force packaging");
1958
- if (forceCopy && this.dataCopyCount === 0) {
1959
- const dst = path.join(this.tempDir, "data");
1960
- async.forEachSeries(inDirs, function(src, next) {
1961
- copySrcToDst.call(this, src, dst, next);
1962
- }, function(err) {
1963
- setImmediate(next, err);
1964
- });
1965
- } else {
1966
- return setImmediate(next);
1967
- }
1968
- }
1969
-
1970
- function getPkgServiceNames(serviceInfo) {
1971
- let serviceNames = [];
1972
- if (servicePkgMethod === "id") {
1973
- serviceNames = [serviceInfo.id];
1974
- } else if (serviceInfo.services) {
1975
- const serviceProps = (serviceInfo.services instanceof Array)
1976
- ? serviceInfo.services : [serviceInfo.services];
1977
- serviceNames = serviceProps.map(function(serviceProp) {
1978
- return serviceProp.name;
1979
- });
1980
- }
1981
- return serviceNames;
1982
- }
1983
-
1984
- function setUmask(mask, next) {
1985
- this.oldmask = process.umask(mask);
1986
- setImmediate(next);
1987
- }
1988
-
1989
- function recoverUmask(next) {
1990
- if (this.oldmask) {
1991
- process.umask(this.oldmask);
1992
- }
1993
- setImmediate(next);
1994
- }
1995
-
1996
- function rewriteFileWoBOMAsUtf8(filePath, rewriteFlag, next) {
1997
- let data = fs.readFileSync(filePath);
1998
- const encodingFormat = chardet.detect(Buffer.from(data));
1999
-
2000
- if (['UTF-8', 'ISO-8895-1'].indexOf(encodingFormat) === -1) {
2001
- log.silly("package#rewriteFileWoBOMAsUtf8()", "current encoding type:" + encodingFormat);
2002
- data = encoding.convert(data, "UTF-8", encodingFormat);
2003
- }
2004
- data = stripbom(data);
2005
-
2006
- if (rewriteFlag) {
2007
- fs.writeFileSync(filePath,
2008
- data, { encoding: "utf8" }
2009
- );
2010
- }
2011
-
2012
- if (next !== 'undefined' && typeof next === 'function') {
2013
- setImmediate(next, null, data);
2014
- }
2015
- return data;
2016
- }
2017
-
2018
- function encryptPackage(next) {
2019
- if (this.rom || !this.encrypt) { // Do not encrypt when -rom option is given
2020
- setImmediate(next);
2021
- return;
2022
- } else {
2023
- this.encryptDir = path.join(this.tempDir, "encrypt");
2024
- const encryptDir = this.encryptDir,
2025
- encryptCtrlDir = path.join(encryptDir, 'ctrl'),
2026
- ctrlTgzFile = path.join(encryptDir, 'control.tar.gz'),
2027
- encryptDataDir = path.join(encryptDir, 'data'),
2028
- dataTgzFile = path.join(encryptDir, 'data.tar.gz');
2029
-
2030
- async.series([
2031
- createDir.bind(this, encryptDir),
2032
- createDir.bind(this, encryptCtrlDir),
2033
- createDir.bind(this, encryptDataDir),
2034
- createkeyIVfile.bind(this, encryptCtrlDir),
2035
- encryptIpk.bind(this, encryptDataDir),
2036
- makeTgz.bind(this, encryptDataDir, dataTgzFile),
2037
- createControlFile.bind(this, encryptCtrlDir, true),
2038
- makeTgz.bind(this, encryptCtrlDir, ctrlTgzFile),
2039
- createDebianBinary.bind(this, encryptDir),
2040
- makeIpk.bind(this, encryptDir)
2041
- ], function(err) {
2042
- if (err) {
2043
- setImmediate(next, err);
2044
- return;
2045
- }
2046
- log.verbose("package#encryptPackage()", "success to encrypt pacakge");
2047
- setImmediate(next);
2048
- });
2049
- }
2050
- }
2051
-
2052
- function createkeyIVfile(dstPath, next) {
2053
- // generate random key& IV
2054
- this.key = Buffer.from(crypto.randomBytes(32), 'base64');
2055
- this.iv = Buffer.from(crypto.randomBytes(16), 'base64');
2056
-
2057
- try {
2058
- // Read public key
2059
- const publickeyPath = path.join(__dirname, '../', 'files', 'conf', 'pubkey.pem'),
2060
- publickey = fs.readFileSync(publickeyPath, 'utf8');
2061
-
2062
- // Encrypt key, iv by publickey
2063
- const encryptedKey= crypto.publicEncrypt({
2064
- key: publickey,
2065
- padding: 4 /* crypto.constants.RSA_PKCS1_OAEP_PADDING */
2066
- }, Buffer.from(this.key.toString('base64')));
2067
-
2068
- const encryptedIV= crypto.publicEncrypt({
2069
- key: publickey,
2070
- padding: 4 /* crypto.constants.RSA_PKCS1_OAEP_PADDING */
2071
- }, Buffer.from(this.iv.toString('base64')));
2072
-
2073
- const keyFilePath = path.join(dstPath, "key");
2074
- fs.writeFileSync(keyFilePath, encryptedKey, 'binary');
2075
-
2076
- // write iv file on encrypt/control
2077
- const ivFilePath = path.join(dstPath , "iv");
2078
- fs.writeFileSync(ivFilePath, encryptedIV, 'binary');
2079
-
2080
- } catch (err) {
2081
- setImmediate(next, errHndl.getErrMsg(err));
2082
- }
2083
- setImmediate(next);
2084
- }
2085
-
2086
- function copyOutputToDst(destination, middleCb, next) {
2087
- // copy data directory to destination
2088
- if (this.rom) {
2089
- middleCb("Create output directory to " + destination);
2090
- copySrcToDst.call(this, path.join(this.tempDir, 'data'), destination, next);
2091
- } else if (this.encrypt) {
2092
- // copy encrypted ipk to destination
2093
- middleCb("Create encrypted " + this.ipkFileName + " to " + destination);
2094
- copySrcToDst.call(this, path.join(this.encryptDir, this.ipkFileName), destination, next);
2095
- } else {
2096
- // copy plain ipk to destination
2097
- let outmsg = "Create ";
2098
- if (this.sign && this.certificate) {
2099
- outmsg = outmsg.concat("signed ");
2100
- }
2101
- middleCb(outmsg + this.ipkFileName + " to " + destination);
2102
- copySrcToDst.call(this, path.join(this.tempDir, this.ipkFileName), destination, next);
2103
- }
2104
- }
2105
-
2106
- function encryptIpk(dstPath, next) {
2107
- log.verbose("package#encryptPackage()#encrypIpk()", "encrypt plain ipk to /encrypt/data");
2108
- const plainIpkPath = path.join(this.tempDir, this.ipkFileName),
2109
- encrypedIpkPath = path.join(dstPath, this.ipkFileName);
2110
-
2111
- try {
2112
- const input = fs.createReadStream(plainIpkPath),
2113
- output = fs.createWriteStream(encrypedIpkPath),
2114
- cipher = crypto.createCipheriv('aes-256-cbc', this.key, this.iv);
2115
-
2116
- output.on('close', function() {
2117
- log.verbose("package#encryptPackage()#encrypIpk()", "encrypted Ipk to " + encrypedIpkPath);
2118
- setImmediate(next);
2119
- }).
2120
- on('error', function(err) {
2121
- setImmediate(next, err);
2122
- });
2123
-
2124
- input.pipe(cipher).pipe(output);
2125
- } catch (err) {
2126
- setImmediate(next, err);
2127
- }
2128
- }
2129
- }());
1
+ /*
2
+ * Copyright (c) 2020-2024 LG Electronics Inc.
3
+ *
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ const ar = require('ar-async'),
8
+ async = require('async'),
9
+ chardet = require('chardet'),
10
+ CombinedStream = require('combined-stream'),
11
+ crypto = require('crypto'),
12
+ decompress = require('decompress'),
13
+ decompressTargz = require('decompress-targz'),
14
+ ElfParser = require('elfy').Parser,
15
+ encoding = require('encoding'),
16
+ fs = require('fs'),
17
+ fstream = require('fstream'),
18
+ Validator = require('jsonschema').Validator,
19
+ mkdirp = require('mkdirp'),
20
+ log = require('npmlog'),
21
+ path = require('path'),
22
+ rimraf = require('rimraf'),
23
+ shelljs = require('shelljs'),
24
+ stripbom = require('strip-bom'),
25
+ temp = require('temp'),
26
+ uglify = require('terser'),
27
+ util = require('util'),
28
+ errHndl = require('./base/error-handler'),
29
+ tar = require('tar');
30
+
31
+ (function() {
32
+ log.heading = 'packager';
33
+ log.level = 'warn';
34
+
35
+ const servicePkgMethod = 'id',
36
+ defaultAssetsFields = {
37
+ "main": true,
38
+ "icon": true,
39
+ "largeIcon": true,
40
+ "bgImage": true,
41
+ "splashBackground": true,
42
+ "imageForRecents": true,
43
+ "sysAssetsBasePath": true
44
+ },
45
+ FILE_TYPE = {
46
+ file: 'file',
47
+ dir: 'dir',
48
+ symlink: 'symlink'
49
+ },
50
+ packager = {};
51
+ let objectCounter = 0;
52
+
53
+ if (typeof module !== 'undefined' && module.exports) {
54
+ module.exports = packager;
55
+ }
56
+
57
+ function Packager() {
58
+ this.objectId = objectCounter++;
59
+ this.verbose = false;
60
+ this.silent = true;
61
+ this.noclean = false;
62
+ this.nativecmd = false;
63
+ this.minify = true;
64
+ this.excludeFiles = [];
65
+ this.rom = false;
66
+ this.encrypt = false;
67
+ this.sign = "";
68
+ this.certificate = "";
69
+ this.appCount = 0;
70
+ this.services = [];
71
+ this.pkgServiceNames = [];
72
+ this.rscCount = 0;
73
+ this.resources = [];
74
+ this.pkgResourceNames = [];
75
+ this.pkgId;
76
+ this.pkginfofile;
77
+ }
78
+
79
+ packager.Packager = Packager;
80
+
81
+ Packager.prototype = {
82
+ prepareOptions: function(options, next) {
83
+ if (options && options.level) {
84
+ log.level = options.level;
85
+ if (['warn', 'error'].indexOf(options.level) !== -1) {
86
+ this.silent = false;
87
+ }
88
+ }
89
+
90
+ if (options && options.noclean === true) {
91
+ this.noclean = true;
92
+ }
93
+
94
+ if (options && options.nativecmd === true) {
95
+ this.nativecmd = true;
96
+ }
97
+
98
+ if (options && Object.prototype.hasOwnProperty.call(options, 'minify')) {
99
+ this.minify = options.minify;
100
+ }
101
+
102
+ if (options && Object.prototype.hasOwnProperty.call(options, 'excludefiles')) {
103
+ if (options.excludefiles instanceof Array) {
104
+ this.excludeFiles = options.excludefiles;
105
+ } else {
106
+ this.excludeFiles.push(options.excludefiles);
107
+ }
108
+
109
+ this.excludeFiles.forEach(function(excl_file) {
110
+ if (excl_file === "appinfo.json") {
111
+ return next(errHndl.getErrMsg("NOT_EXCLUDE_APPINFO"));
112
+ }
113
+ });
114
+ }
115
+
116
+ if (options && Object.prototype.hasOwnProperty.call(options, 'rom')) {
117
+ this.rom = options.rom;
118
+ }
119
+
120
+ if (options && Object.prototype.hasOwnProperty.call(options, 'encrypt')) {
121
+ this.encrypt = options.encrypt;
122
+ }
123
+
124
+ if (options && Object.prototype.hasOwnProperty.call(options, 'sign')) {
125
+ if (!fs.existsSync(path.resolve(options.sign))) {
126
+ return next(errHndl.getErrMsg("NOT_EXIST_PATH", options.sign));
127
+ }
128
+ this.sign = options.sign;
129
+ }
130
+
131
+ if (options && Object.prototype.hasOwnProperty.call(options, 'certificate')) {
132
+ if (!fs.existsSync(path.resolve(options.certificate))) {
133
+ return next(errHndl.getErrMsg("NOT_EXIST_PATH", options.certificate));
134
+ }
135
+ this.certificate = options.certificate;
136
+ }
137
+
138
+ // check sign option must be used with certificate option
139
+ if ((this.sign && !this.certificate) ||
140
+ (this.certificate && !this.sign)) {
141
+ return next(errHndl.getErrMsg("USE_WITH_OPTIONS", "sign, certificate"));
142
+ }
143
+
144
+ log.verbose("package#Packager()", "Packager id:" + this.objectId);
145
+
146
+ this.pkgVersion = options.pkgversion;
147
+
148
+ if (options && Object.prototype.hasOwnProperty.call(options, 'pkgid')) {
149
+ this.pkgId = options.pkgid;
150
+ }
151
+
152
+ if (options && Object.prototype.hasOwnProperty.call(options, 'pkginfofile')) {
153
+ this.pkginfofile = options.pkginfofile;
154
+ }
155
+
156
+ if (this.pkgId && this.pkginfofile) {
157
+ return next(errHndl.getErrMsg("NOT_USE_WITH_OPTIONS", "pkginfofile, pkgid"));
158
+ }
159
+ this.appCount = 0;
160
+ },
161
+ checkInputDirectories: function(inDirs, options, next) {
162
+ log.verbose("package#Packager#checkInputDirectories()", "input directory:", inDirs);
163
+ this.prepareOptions(options, next);
164
+
165
+ async.forEachSeries(inDirs, checkDirectory.bind(this, options), function(err) {
166
+ if (err) {
167
+ setImmediate(next, err);
168
+ return;
169
+ }
170
+ setImmediate(next);
171
+ });
172
+ return {app: this.appCount, resource: this.rscCount};
173
+ },
174
+ servicePackaging: function(inDirs, destination, options, middleCb, next) {
175
+ log.info("package#Packager#servicePackaging()");
176
+
177
+ if (!Object.hasOwnProperty.call(options, 'pkgid') && !Object.hasOwnProperty.call(options, 'pkginfofile')) {
178
+ return next(errHndl.getErrMsg("USE_PKGID_PKGINFO"));
179
+ }
180
+
181
+ async.series([
182
+ this.checkInputDirectories.bind(this, inDirs, options),
183
+ setUmask.bind(this, 0),
184
+ loadPkgInfo.bind(this),
185
+ createTmpDir.bind(this),
186
+ excludeIpkFileFromApp.bind(this),
187
+ createPackageDir.bind(this),
188
+ fillPackageDir.bind(this),
189
+ findServiceDir.bind(this, this.services),
190
+ loadServiceInfo.bind(this),
191
+ checkServiceInfo.bind(this),
192
+ createServiceDir.bind(this),
193
+ copyService.bind(this),
194
+ addServiceInPkgInfo.bind(this),
195
+ copyData.bind(this, inDirs, options.force),
196
+ loadPackageProperties.bind(this, inDirs),
197
+ excludeFromApp.bind(this, middleCb),
198
+ outputPackage.bind(this, destination),
199
+ encryptPackage.bind(this),
200
+ copyOutputToDst.bind(this, destination, middleCb),
201
+ recoverUmask.bind(this),
202
+ cleanupTmpDir.bind(this, middleCb)
203
+ ], function(err) {
204
+ if (err) {
205
+ // TODO: call cleanupTmpDir() before returning
206
+ setImmediate(next, err);
207
+ return;
208
+ }
209
+ // TODO: probably some more checkings are needed
210
+ setImmediate(next, null, {ipk: this.ipk, msg: "Success"});
211
+ }.bind(this));
212
+ },
213
+ resourcePackaging: function(inDirs, destination, options, middleCb, next) {
214
+ log.info("package#Packager#resourcePackaging()");
215
+ this.dataCopyCount = 0;
216
+ async.series([
217
+ this.checkInputDirectories.bind(this, inDirs, options),
218
+ setUmask.bind(this, 0),
219
+ loadPkgInfo.bind(this),
220
+ createTmpDir.bind(this),
221
+ excludeIpkFileFromApp.bind(this),
222
+ createPackageDir.bind(this),
223
+ fillPackageDir.bind(this),
224
+ loadResourceInfo.bind(this),
225
+ checkResourceInfo.bind(this),
226
+ createResourceDir.bind(this),
227
+ copyResource.bind(this),
228
+ addResourceInPkgInfo.bind(this),
229
+ copyData.bind(this, inDirs, options.force),
230
+ loadPackageProperties.bind(this, inDirs),
231
+ excludeFromApp.bind(this, middleCb),
232
+ outputPackage.bind(this, destination),
233
+ encryptPackage.bind(this),
234
+ copyOutputToDst.bind(this, destination, middleCb),
235
+ recoverUmask.bind(this),
236
+ cleanupTmpDir.bind(this, middleCb)
237
+ ], function(err) {
238
+ if (err) {
239
+ setImmediate(next, err);
240
+ return;
241
+ }
242
+ setImmediate(next, null, {ipk: this.ipk, msg: "Success"});
243
+ }.bind(this));
244
+ },
245
+ generatePackage: function(inDirs, destination, options, middleCb, next) {
246
+ log.info("package#Packager#generatePackage()", "from ", inDirs);
247
+ // check whether app or service directories are copied or not
248
+ this.dataCopyCount = 0;
249
+ this.minifyDone = !this.minify;
250
+
251
+ async.series([
252
+ this.checkInputDirectories.bind(this, inDirs, options),
253
+ setUmask.bind(this, 0),
254
+ loadPkgInfo.bind(this),
255
+ loadAppInfo.bind(this),
256
+ checkAppInfo.bind(this),
257
+ createTmpDir.bind(this),
258
+ createAppDir.bind(this),
259
+ checkELFHeader.bind(this),
260
+ fillAssetsField.bind(this),
261
+ copyAssets.bind(this),
262
+ copyApp.bind(this),
263
+ excludeIpkFileFromApp.bind(this),
264
+ createPackageDir.bind(this),
265
+ fillPackageDir.bind(this),
266
+ findServiceDir.bind(this, this.services),
267
+ loadServiceInfo.bind(this),
268
+ checkServiceInfo.bind(this),
269
+ createServiceDir.bind(this),
270
+ copyService.bind(this),
271
+ addServiceInPkgInfo.bind(this),
272
+ removeServiceFromAppDir.bind(this, middleCb),
273
+ copyData.bind(this, inDirs, options.force),
274
+ loadPackageProperties.bind(this, inDirs),
275
+ excludeFromApp.bind(this, middleCb),
276
+ outputPackage.bind(this, destination),
277
+ encryptPackage.bind(this),
278
+ copyOutputToDst.bind(this, destination, middleCb),
279
+ recoverUmask.bind(this),
280
+ cleanupTmpDir.bind(this, middleCb)
281
+ ], function(err) {
282
+ if (err) {
283
+ // TODO: call cleanupTmpDir() before returning
284
+ setImmediate(next, err);
285
+ return;
286
+ }
287
+ // TODO: probably some more checkings are needed
288
+ setImmediate(next, null, {ipk: this.ipk, msg: "Success"});
289
+ }.bind(this));
290
+ },
291
+ analyzeIPK: function(options, next) {
292
+ log.info("package#Packager#analyzeIPK()");
293
+ let ipkConfigFile, ipkFile, ipkConfig, tmpDirPath;
294
+ this.prepareOptions(options);
295
+
296
+ async.series([
297
+ _setConfig,
298
+ _unpackIpk,
299
+ _unpackTar,
300
+ _analyzeMetaFile,
301
+ _removeTmpDir,
302
+ ], function(err, results) {
303
+ log.silly("package#analyzeIPK()", "err:", err, ", results:", results);
304
+ if (err) {
305
+ return next(err);
306
+ }
307
+ return next(null, {msg: results[3].trim()});
308
+ });
309
+
310
+ function _setConfig(next) {
311
+ log.info("package#analyzeIPK()#_setConfig()");
312
+ if (options.info === 'true') {
313
+ return next(errHndl.getErrMsg("EMPTY_VALUE", 'info'));
314
+ } else if (options.infodetail === 'true') {
315
+ return next(errHndl.getErrMsg("EMPTY_VALUE", 'info-detail'));
316
+ }
317
+ ipkFile = options.info ? options.info : options.infodetail;
318
+ ipkConfigFile = path.resolve(__dirname, "../files/conf/ipk.json");
319
+
320
+ if (path.extname(ipkFile) !== ".ipk") {
321
+ return next(errHndl.getErrMsg("SUPPORT_ONLY_IPK", ipkFile));
322
+ }
323
+ if (!fs.existsSync(ipkFile)) {
324
+ return next(errHndl.getErrMsg("NOT_EXIST_PATH", ipkFile));
325
+ }
326
+ if (!fs.existsSync(ipkConfigFile)) {
327
+ return next(errHndl.getErrMsg("NOT_EXIST_PATH", ipkConfigFile));
328
+ }
329
+ ipkConfig = JSON.parse(fs.readFileSync(ipkConfigFile));
330
+ tmpDirPath = temp.path(ipkConfig.tmpPath);
331
+
332
+ if (!fs.existsSync(tmpDirPath)) {
333
+ fs.mkdirSync(tmpDirPath);
334
+ }
335
+ next();
336
+ }
337
+
338
+ function _unpackIpk(next) {
339
+ log.info("package#analyzeIPK()#_unpackIpk()");
340
+ const reader = new ar.ArReader(ipkFile);
341
+
342
+ reader.on("entry", function(entry, next) {
343
+ const name = entry.fileName();
344
+ entry.fileData()
345
+ .pipe(fs.createWriteStream(path.resolve(tmpDirPath, name)))
346
+ .on("finish", next);
347
+ });
348
+ reader.on("error", function(err) {
349
+ return next(err);
350
+ });
351
+ reader.on("close", function() {
352
+ log.verbose("package#analyzeIPK()#_unpackIpk()", "unpack ipk close");
353
+ next();
354
+ });
355
+ }
356
+
357
+ function _unpackTar(next) {
358
+ log.info("package#analyzeIPK()#_unpackTar()");
359
+ if (fs.existsSync(path.resolve(tmpDirPath, "control.tar.gz"))) {
360
+ (async function() {
361
+ await decompress(path.resolve(tmpDirPath, "control.tar.gz"), tmpDirPath, {
362
+ plugins: [
363
+ decompressTargz()
364
+ ]
365
+ });
366
+ log.verbose("package#analyzeIPK()#_unpackTar()", "control.tar.gz decompressed");
367
+ })();
368
+ } else if (fs.existsSync(path.resolve(tmpDirPath, "control.tar.xz"))) {
369
+ return next(errHndl.getErrMsg("NOT_SUPPORT_XZ"));
370
+ } else {
371
+ return next(errHndl.getErrMsg("NO_COMPONENT_FILE", ", control tar file"));
372
+ }
373
+
374
+ if (fs.existsSync(path.resolve(tmpDirPath, "data.tar.gz"))) {
375
+ (async function() {
376
+ await decompress(path.resolve(tmpDirPath, "data.tar.gz"), tmpDirPath, {
377
+ plugins: [
378
+ decompressTargz()
379
+ ]
380
+ });
381
+ log.verbose("package#analyzeIPK()#_unpackTar()", "data.tar.gz decompressed");
382
+ next();
383
+ })();
384
+ } else if (fs.existsSync(path.resolve(tmpDirPath, "data.tar.xz"))) {
385
+ return next(errHndl.getErrMsg("NOT_SUPPORT_XZ"));
386
+ } else {
387
+ return next(errHndl.getErrMsg("NO_COMPONENT_FILE", "data tar file"));
388
+ }
389
+ }
390
+
391
+ function _analyzeMetaFile(next) {
392
+ log.info("package#analyzeIPK()#_analyzeMetaFile()");
393
+ let result = "", targetFile, tmpTxt;
394
+
395
+ ipkConfig.webOSMetaFiles.forEach(function(item) {
396
+ const targetPath = path.resolve(tmpDirPath, ipkConfig[item].path);
397
+ // Analyze control file
398
+ if (item === "control") {
399
+ targetFile = path.resolve(targetPath, ipkConfig[item].fileName);
400
+ if (!fs.existsSync(targetFile)) {
401
+ return next(errHndl.getErrMsg("NO_COMPONENT_FILE", "control file"));
402
+ }
403
+
404
+ if (options.infodetail) {
405
+ result = "\n\n< " + ipkConfig[item].fileName + " >\n";
406
+ result += fs.readFileSync(targetFile).toString().trim();
407
+ } else {
408
+ result = ipkConfig[item].heading + "\n";
409
+ tmpTxt = fs.readFileSync(targetFile).toString().trim();
410
+ ipkConfig[item].info.forEach(function(field) {
411
+ if (tmpTxt.match(new RegExp(field + ": (.*)", "gi"))) {
412
+ result += tmpTxt.match(new RegExp(field + ": (.*)", "gi"))[0] + "\n";
413
+ }
414
+ });
415
+ }
416
+ // Analyze appinfo.json, packageinfo.json, services.json, package.json files
417
+ } else if (fs.existsSync(targetPath)) {
418
+ const dirArr = fs.readdirSync(targetPath);
419
+ let beforeDir = dirArr[0];
420
+
421
+ dirArr.forEach(function(dir) {
422
+ if (ipkConfig[item].fileName) {
423
+ ipkConfig[item].fileName.forEach(function(file) {
424
+ targetFile = path.resolve(targetPath, dir, file);
425
+ if (fs.existsSync(targetFile)) {
426
+ if (options.infodetail) {
427
+ result += "\n\n< " + file + " >\n";
428
+ result += fs.readFileSync(targetFile).toString().trim();
429
+ } else {
430
+ if (ipkConfig[item].heading && beforeDir !== path.join(ipkConfig[item].path, dir)) {
431
+ result += "\n" + ipkConfig[item].heading + "\n";
432
+ }
433
+ tmpTxt = fs.readFileSync(targetFile).toString().trim();
434
+ const tmpJson = JSON.parse(tmpTxt);
435
+ ipkConfig[item].info.forEach(function(field) {
436
+ if (tmpJson[field]) {
437
+ const fieldValue = tmpJson[field];
438
+ if (typeof fieldValue === "object") {
439
+ if (ipkConfig[item][field]) { // Case of services.json, services_name field
440
+ const subField = ipkConfig[item][field],
441
+ subResult = [];
442
+ fieldValue.forEach(function(subItem) {
443
+ subResult.push(subItem[subField]);
444
+ });
445
+ result += field + ": " + JSON.stringify(subResult) + "\n";
446
+ } else { // Case of packageinfo.json, services field
447
+ result += field + ": " + JSON.stringify(fieldValue) + "\n";
448
+ }
449
+ } else {
450
+ result += field + ": " + fieldValue + "\n";
451
+ }
452
+ }
453
+ });
454
+ beforeDir = path.join(ipkConfig[item].path, dir);
455
+ }
456
+ }
457
+ });
458
+ }
459
+ });
460
+ }
461
+ });
462
+ next(null, result);
463
+ }
464
+
465
+ function _removeTmpDir(next) {
466
+ log.info("package#analyzeIPK()#_removeTmpDir()");
467
+ rimraf(tmpDirPath, function(err) {
468
+ log.verbose("package#analyzeIPK()#_removeTmpDir()", "removed " + tmpDirPath);
469
+ next(err);
470
+ });
471
+ }
472
+ }
473
+ };
474
+
475
+ function Service() {
476
+ this.srcDir = "";
477
+ this.dstDirs = [];
478
+ this.valid = false;
479
+ this.serviceInfo = "";
480
+ this.dirName = "";
481
+ }
482
+
483
+ // Private functions
484
+ function loadPkgInfo(next) {
485
+ log.verbose("loadPkgInfo");
486
+ let data;
487
+
488
+ if (this.pkginfofile) {
489
+ log.silly("Use pkginfofile option");
490
+ if (fs.existsSync(this.pkginfofile)) {
491
+ if ("packageinfo.json" !== path.basename(this.pkginfofile)) {
492
+ return setImmediate(next, errHndl.getErrMsg("INVALID_FILE", "packageinfo.json"));
493
+ }
494
+ data = rewriteFileWoBOMAsUtf8(this.pkginfofile, true);
495
+ try {
496
+ log.verbose("PKGINFO >>" + data + "<<");
497
+ this.pkginfo = JSON.parse(data);
498
+
499
+ if (!Object.prototype.hasOwnProperty.call(this.pkginfo, 'id')) {
500
+ return setImmediate(next, errHndl.getErrMsg("REQUIRED_FIELD", "id"));
501
+ }
502
+
503
+ this.pkgId = this.pkginfo.id;
504
+ this.pkgVersion = this.pkgVersion || this.pkginfo.version;
505
+ setImmediate(next);
506
+ }
507
+ catch (err) {
508
+ return setImmediate(next, errHndl.getErrMsg("INVALID_JSON_FORMAT", "packageinfo.json"));
509
+ }
510
+ } else {
511
+ return setImmediate(next, errHndl.getErrMsg("NOT_EXIST_PATH", this.pkginfofile));
512
+ }
513
+ } else if (this.pkgDir) {
514
+ log.silly("Use packageinfo.json from PKG_DIR");
515
+ data = rewriteFileWoBOMAsUtf8(path.join(this.pkgDir, "packageinfo.json"));
516
+ try {
517
+ log.verbose("PKGINFO >>" + data + "<<");
518
+ this.pkginfo = JSON.parse(data);
519
+
520
+ if (!Object.prototype.hasOwnProperty.call(this.pkginfo, 'id')) {
521
+ return setImmediate(next, errHndl.getErrMsg("REQUIRED_FIELD", "id"));
522
+ }
523
+ this.pkgId = this.pkginfo.id;
524
+ this.pkgVersion = this.pkgVersion || this.pkginfo.version;
525
+ setImmediate(next);
526
+ }
527
+ catch (err) {
528
+ return setImmediate(next, errHndl.getErrMsg("INVALID_JSON_FORMAT", "packageinfo.json"));
529
+ }
530
+ } else {
531
+ return setImmediate(next);
532
+ }
533
+ }
534
+
535
+ function loadAppInfo(next) {
536
+ log.verbose("loadAppInfo");
537
+ if (this.appCount === 0) {
538
+ return setImmediate(next);
539
+ }
540
+
541
+ const filepath = path.join(this.appDir, "appinfo.json"),
542
+ data = rewriteFileWoBOMAsUtf8(filepath, true);
543
+ try {
544
+ this.appinfo = JSON.parse(data);
545
+ log.silly("package#loadAppInfo()", "content of appinfo.json:", this.appinfo);
546
+
547
+ if (!this.appinfo.version || this.appinfo.version === undefined) {
548
+ this.appinfo.version = "1.0.0";
549
+ }
550
+
551
+ this.pkgVersion = this.pkgVersion || this.appinfo.version;
552
+ setImmediate(next);
553
+ } catch(err) {
554
+ setImmediate(next, err);
555
+ }
556
+ }
557
+
558
+ function checkAppInfo(next) {
559
+ if (this.appCount === 0) {
560
+ return setImmediate(next);
561
+ }
562
+
563
+ // check enyo app
564
+ if (this.pkgJSExist && this.appinfo.main && this.appinfo.main.match(/(\.html|\.htm)$/gi)) {
565
+ const mainFile = path.join(this.appDir, this.appinfo.main);
566
+ if (!fs.existsSync(mainFile)) {
567
+ return setImmediate(next, errHndl.getErrMsg("NOT_EXIST_PATH", this.appinfo.main));
568
+ }
569
+
570
+ const regex = new RegExp("(<script[^>]*src[ \t]*=[ \t]*['\"])[^'\"]*/enyo.js(['\"])"),
571
+ data = fs.readFileSync(mainFile);
572
+ if (data.toString().match(regex)) {
573
+ // If enyo app, stop packaging.
574
+ return setImmediate(next, errHndl.getErrMsg("NOT_SUPPORT_ENYO"));
575
+ }
576
+ }
577
+
578
+ if (!this.appinfo.id || this.appinfo.id === undefined) {
579
+ return setImmediate(next, errHndl.getErrMsg("REQUIRED_FIELD", "id"));
580
+ }
581
+ if (this.appinfo.id.length < 1 || !(/^[a-z0-9.+-]*$/.test(this.appinfo.id))) {
582
+ log.error(errHndl.getErrMsg("INVALID_VALUE", "id", this.appinfo.id));
583
+ return setImmediate(next, errHndl.getErrMsg("INVALID_ID_RULE"));
584
+ }
585
+ if (this.pkgId && this.appinfo.id.indexOf(this.pkgId) !== 0) {
586
+ log.error(errHndl.getErrMsg("INVALID_VALUE", "id", this.appinfo.id));
587
+ return setImmediate(next, errHndl.getErrMsg("INVALID_APPID", this.pkgId));
588
+ }
589
+ if (!this.appinfo.version || this.appinfo.version === undefined) {
590
+ return setImmediate(next, errHndl.getErrMsg("REQUIRED_FIELD", "version"));
591
+ }
592
+ if (this.appinfo.version.length < 1 || !(/^([1-9]\d{0,8}|\d)\.([1-9]\d{0,8}|\d)\.([1-9]\d{0,8}|\d)$/.test(this.appinfo.version))) {
593
+ log.error(errHndl.getErrMsg("INVALID_VALUE", "version", this.appinfo.version));
594
+ return setImmediate(next, errHndl.getErrMsg("INVALID_VERSION_RULE"));
595
+ }
596
+ if (this.appinfo.type && this.appinfo.type.match(/clock/gi)) {
597
+ return setImmediate(next);
598
+ }
599
+
600
+ const schemaFile = path.resolve(__dirname, "../files/schema/ApplicationDescription.schema");
601
+ async.waterfall([
602
+ fs.readFile.bind(this, schemaFile, "utf-8"),
603
+ function getSchema(data, next) {
604
+ try {
605
+ const schema = JSON.parse(data);
606
+ /* "required" keyword is redefined in draft 4.
607
+ But current jsonschema lib support only draft 3.
608
+ So this line changes "required" attribute according to the draft 3.
609
+ */
610
+ const reqKeys = schema.required;
611
+ if (reqKeys) {
612
+ for (const key in schema.properties) {
613
+ if (reqKeys.indexOf(key) !== -1) {
614
+ schema.properties[key].required = true;
615
+ }
616
+ }
617
+ }
618
+ next(null, schema);
619
+ } catch(err) {
620
+ next(errHndl.getErrMsg("INVALID_JSON_FORMAT", "AppDescription schema"));
621
+ }
622
+ },
623
+ function checkValid(schema, next) {
624
+ try {
625
+ next(null, new Validator().validate(this.appinfo, schema));
626
+ } catch (err) {
627
+ log.error(err);
628
+ next(errHndl.getErrMsg("INVALID_JSON_FORMAT"));
629
+ }
630
+ }.bind(this)
631
+ ], function(err, result) {
632
+ if (err) {
633
+ setImmediate(next, err);
634
+ } else {
635
+ if (result && result.errors.length > 0) {
636
+ const errFile = "appinfo.json";
637
+ let errMsg = "";
638
+ for (const idx in result.errors) {
639
+ let errMsgLine = result.errors[idx].property + " " + result.errors[idx].message;
640
+ if (errMsgLine.indexOf("instance.") > -1) {
641
+ errMsgLine = errMsgLine.substring("instance.".length);
642
+ errMsg = errMsg.concat("\n");
643
+ errMsg = errMsg.concat(errMsgLine);
644
+ }
645
+ }
646
+ errMsg = errHndl.getErrMsg("INVALID_FILE", errFile, errMsg);
647
+ return setImmediate(next, errMsg);
648
+ } else {
649
+ log.verbose("package#checkAppInfo()", "APPINFO is valid");
650
+ }
651
+ setImmediate(next);
652
+ }
653
+ });
654
+ }
655
+
656
+ function fillAssetsField(next) {
657
+ if (this.appCount === 0) {
658
+ return setImmediate(next);
659
+ }
660
+ // make appinfo.assets to have default values so that they can be copied into the package
661
+ this.appinfo.assets = this.appinfo.assets || [];
662
+ for (const i in this.appinfo) {
663
+ if (Object.prototype.hasOwnProperty.call(this.appinfo, i) && defaultAssetsFields[i]) {
664
+ // no duplicated adding & value should not null string & file/dir should exist
665
+ if ((this.appinfo.assets.indexOf(this.appinfo[i]) === -1) && this.appinfo[i]) {
666
+ this.appinfo.assets.push(this.appinfo[i]);
667
+ }
668
+ }
669
+ }
670
+
671
+ // refer to appinfo.json files in localization directory.
672
+ const appInfoPath = this.originAppDir,
673
+ checkDir = path.join(this.originAppDir, "resources"),
674
+ foundFilePath = [],
675
+ resourcesAssets = [];
676
+
677
+ try {
678
+ const stat = fs.lstatSync(checkDir);
679
+ if (!stat.isDirectory()) {
680
+ return setImmediate(next, null);
681
+ }
682
+ } catch(err) {
683
+ if (err.code === "ENOENT") {
684
+ return setImmediate(next, null);
685
+ }
686
+ }
687
+
688
+ async.series([
689
+ walkFolder.bind(null, checkDir, "appinfo.json", foundFilePath, 1),
690
+ function(next) {
691
+ async.forEach(foundFilePath, function(filePath, next) {
692
+ rewriteFileWoBOMAsUtf8(filePath, true, function(err, data) {
693
+ try {
694
+ const appInfo = JSON.parse(data),
695
+ dirPath = path.dirname(filePath);
696
+ for (const i in appInfo) {
697
+ if (Object.prototype.hasOwnProperty.call(appInfo, i) && defaultAssetsFields[i]) {
698
+ if (appInfo[i]) {
699
+ const itemPath = path.join(dirPath, appInfo[i]),
700
+ relPath = path.relative(appInfoPath, itemPath);
701
+ // no duplicated adding & value should not null string & file/dir should exist
702
+ if ((resourcesAssets.indexOf(relPath) === -1)) {
703
+ resourcesAssets.push(relPath);
704
+ }
705
+ }
706
+ }
707
+ }
708
+ setImmediate(next, null);
709
+ } catch(error) {
710
+ setImmediate(next, errHndl.getErrMsg("INVALID_JSON_FORMAT", filePath));
711
+ }
712
+ });
713
+ }, function(err) {
714
+ setImmediate(next, err);
715
+ });
716
+ },
717
+ function(next) {
718
+ this.appinfo.assets = this.appinfo.assets.concat(resourcesAssets);
719
+ setImmediate(next, null);
720
+ }.bind(this)
721
+ ], function(err) {
722
+ setImmediate(next, err);
723
+ });
724
+ }
725
+
726
+ function createTmpDir(next) {
727
+ this.tempDir = temp.path({prefix: 'com.palm.ares.hermes.bdOpenwebOS'}) + '.d';
728
+ log.verbose("package#createTmpDir()", "temp dir:", this.tempDir);
729
+ mkdirp(this.tempDir, next);
730
+ }
731
+
732
+ function createAppDir(next) {
733
+ if (this.appCount === 0) {
734
+ return setImmediate(next);
735
+ }
736
+
737
+ try {
738
+ this.applicationDir = path.resolve(this.tempDir, "data/usr/palm/applications", this.appinfo.id);
739
+ log.info("package#createAppDir()", "application dir:" + this.applicationDir);
740
+ mkdirp(this.applicationDir, next);
741
+ } catch (err) {
742
+ return setImmediate(next, err);
743
+ }
744
+ }
745
+
746
+ function copySrcToDst(src, dst, next) {
747
+ const fileList = [],
748
+ self = this,
749
+ requireMinify = !!((!!self.minify && !self.minifyDone));
750
+ src = path.normalize(path.resolve(src));
751
+ dst = path.normalize(path.resolve(dst));
752
+
753
+ async.series([
754
+ function(next) {
755
+ const stat = fs.statSync(src);
756
+ if (stat.isFile()) {
757
+ _pushList(fileList, 'file', path.dirname(src), path.basename(src), true, null);
758
+ setImmediate(next);
759
+ } else {
760
+ _getFileList(src, src, fileList, next);
761
+ }
762
+ },
763
+ _copySrcToDst.bind(null, fileList, dst, requireMinify)
764
+ ], function(err) {
765
+ next(err);
766
+ });
767
+
768
+ function _pushList(list, type, basePath, relPath, isSubPath, indRelPath) {
769
+ if (!FILE_TYPE[type]) {
770
+ return;
771
+ }
772
+ list.push({
773
+ type: type,
774
+ basePath: basePath,
775
+ relPath: relPath,
776
+ isSubPath: isSubPath,
777
+ indRelPath: indRelPath
778
+ });
779
+ }
780
+
781
+ function _getFileList(dirPath, basePath, files, next) {
782
+ // TODO: the following code should be more concise.
783
+ // Handling symbolic links
784
+ // if the path sym-link indicates is a sub-path of source directory, treat a sym-link as it is.
785
+ // otherwise the files sym-link indicates should be copied
786
+ async.waterfall([
787
+ fs.readdir.bind(null, dirPath),
788
+ function(fileNames, next) {
789
+ if (fileNames.length === 0) {
790
+ _pushList(files, 'dir', basePath, path.relative(basePath, dirPath), true, null);
791
+ return setImmediate(next);
792
+ }
793
+
794
+ async.forEachSeries(fileNames, function(fileName, next) {
795
+ const filePath = path.join(dirPath, fileName),
796
+ relPath = path.relative(basePath, filePath);
797
+
798
+ async.waterfall([
799
+ fs.lstat.bind(null, filePath),
800
+ function(lstat, next) {
801
+ if (lstat.isSymbolicLink()) {
802
+ let indicateFullPath;
803
+ try {
804
+ indicateFullPath = fs.realpathSync(filePath);
805
+ } catch (err) {
806
+ if (err.code === 'ENOENT') {
807
+ log.warn("The file for symbolic link (" + filePath + ") is missing...");
808
+ return setImmediate(next);
809
+ }
810
+ return setImmediate(next, err);
811
+ }
812
+
813
+ const indicateRelPath = fs.readlinkSync(filePath);
814
+ if (indicateFullPath.indexOf(basePath) !== -1) {
815
+ _pushList(files, 'symlink', basePath, relPath, true, indicateRelPath);
816
+ } else {
817
+ const stat = fs.statSync(filePath);
818
+ if (stat.isDirectory()) {
819
+ return _getFileList(filePath, basePath, files, next);
820
+ } else if (stat.isFile()) {
821
+ _pushList(files, 'file', basePath, relPath, true, null);
822
+ }
823
+ }
824
+ setImmediate(next);
825
+ } else if (lstat.isDirectory()) {
826
+ return _getFileList(filePath, basePath, files, next);
827
+ } else if (lstat.isFile()) {
828
+ _pushList(files, 'file', basePath, relPath, true, null);
829
+ setImmediate(next);
830
+ } else {
831
+ setImmediate(next);
832
+ }
833
+ }
834
+ ], next); // async.waterfall
835
+ }, next); // async.forEach
836
+ }
837
+ ], function(err) {
838
+ return setImmediate(next, err);
839
+ }); // async.waterfall
840
+ }
841
+
842
+ function _copySrcToDst(files, dstPath, minify, next) {
843
+ try {
844
+ async.forEachSeries(files, function(file, next) {
845
+ if (!FILE_TYPE[file.type]) {
846
+ log.verbose("package#copySrcToDst()#_copySrcToDst()", "ignore 'unknown file type'("+file.type+")");
847
+ return;
848
+ }
849
+
850
+ if (!file.relPath) {
851
+ log.verbose("package#copySrcToDst()#_copySrcToDst()", "ignore 'unknown path'");
852
+ return setImmediate(next);
853
+ }
854
+
855
+ if (file.type === FILE_TYPE.dir) {
856
+ mkdirp.sync(path.join(dstPath, file.relPath));
857
+ return setImmediate(next);
858
+ }
859
+
860
+ const dstDirPath = path.dirname(path.join(dstPath, file.relPath));
861
+ if (!fs.existsSync(dstDirPath)) {
862
+ mkdirp.sync(dstDirPath);
863
+ }
864
+
865
+ if (file.type === FILE_TYPE.symlink) {
866
+ if (file.isSubPath && file.indRelPath) {
867
+ const linkFile = path.join(dstPath, file.relPath);
868
+ if (fs.existsSync(linkFile)) {
869
+ if (fs.lstatSync(linkFile).isSymbolicLink()) {
870
+ fs.unlinkSync(linkFile);
871
+ }
872
+ }
873
+ fs.symlinkSync(file.indRelPath, linkFile, null);
874
+ }
875
+ } else {
876
+ const sourceFile = path.join(file.basePath, file.relPath);
877
+ if (fs.existsSync(sourceFile)) {
878
+ if (minify && '.js' === path.extname(sourceFile) && file.relPath.indexOf('node_modules') === -1) {
879
+ log.verbose("package#copySrcToDst()#_copySrcToDst()", "require minification # sourceFile:", sourceFile);
880
+ try {
881
+ const data = uglify.minify(fs.readFileSync(sourceFile,'utf8'));
882
+ if (data.error) {
883
+ throw data.error;
884
+ }
885
+ fs.writeFileSync(path.join(dstPath, file.relPath), data.code, 'utf8');
886
+ } catch (e) {
887
+ log.verbose("package#copySrcToDst()#_copySrcToDst()", util.format('Failed to uglify code %s: %s', sourceFile, e.stack));
888
+ return setImmediate(next, errHndl.getErrMsg("FAILED_MINIFY", sourceFile));
889
+ }
890
+ } else {
891
+ shelljs.cp('-Rf', sourceFile, path.join(dstPath, file.relPath, '..'));
892
+ }
893
+ } else {
894
+ log.verbose("package#copySrcToDst()#_copySrcToDst()", "ignore '" + file.relPath + "'");
895
+ }
896
+ }
897
+ setImmediate(next);
898
+ }, function(err) {
899
+ if (!err && minify) {
900
+ self.minifyDone = true;
901
+ }
902
+ setImmediate(next, err);
903
+ });
904
+ } catch(err) {
905
+ setImmediate(next, err);
906
+ }
907
+ }
908
+ }
909
+
910
+ function checkELFHeader(next) {
911
+ const self = this,
912
+ ELF_HEADER_LEN = 64,
913
+ buf = Buffer.alloc(ELF_HEADER_LEN),
914
+ mainFile = path.resolve(path.join(this.appDir, this.appinfo.main));
915
+
916
+ if (!fs.existsSync(mainFile)) {
917
+ return setImmediate(next, errHndl.getErrMsg("NOT_EXIST_PATH", mainFile));
918
+ }
919
+
920
+ const fd = fs.openSync(mainFile, 'r'),
921
+ stats = fs.fstatSync(fd),
922
+ elfParser = new ElfParser(),
923
+ _isELF = function(_buf) {
924
+ if (_buf.slice(0, 4).toString() !== '\x7fELF') {
925
+ return false;
926
+ } else {
927
+ return true;
928
+ }
929
+ };
930
+
931
+ if (stats.size < ELF_HEADER_LEN) {
932
+ log.verbose("package#checkELFHeader()", "file size is smaller than ELF Header size");
933
+ return setImmediate(next);
934
+ }
935
+
936
+ fs.read(fd, buf, 0, ELF_HEADER_LEN, 0, function(err, bytesRead, _buf) {
937
+ if (bytesRead < ELF_HEADER_LEN || err) {
938
+ log.silly("package#checkELFHeader()", "err:", err, ", bytesRead:", bytesRead);
939
+ log.silly("package#checkELFHeader()", "readBuf to parse ELF header is small or error occurred during reading file.");
940
+ return setImmediate(next);
941
+ }
942
+
943
+ if (!_isELF(_buf)) {
944
+ log.silly("package#checkELFHeader()", mainFile + " is not ELF format");
945
+ } else {
946
+ log.silly("package#checkELFHeader()", mainFile + " is ELF format");
947
+ try {
948
+ const elfHeader = elfParser.parseHeader(_buf);
949
+ log.silly("package#checkELFHeader()", "elfHeader:", elfHeader);
950
+
951
+ if (elfHeader.machine && elfHeader.machine.match(/86$/)) {
952
+ // current emulator opkg is allowing only all, noarch and i586.
953
+ // when it is used with --offline-root.
954
+ self.architecture = 'i586';
955
+ } else if (elfHeader.machine && elfHeader.machine.match(/amd64$/)) {
956
+ // change amd64 to x86_64
957
+ self.architecture = 'x86_64';
958
+ } else if (elfHeader.machine && elfHeader.machine.match(/AArch64$/)) {
959
+ // change AArch64 to aarch64
960
+ self.architecture = 'aarch64';
961
+ } else {
962
+ self.architecture = elfHeader.machine;
963
+ }
964
+ } catch(e) {
965
+ log.verbose("package#checkELFHeader()", "exception:", e);
966
+ }
967
+ }
968
+ log.verbose("package#checkELFHeader()", "machine:", self.architecture);
969
+ fs.close(fd);
970
+ setImmediate(next);
971
+ });
972
+ }
973
+
974
+ function copyApp(next) {
975
+ if (this.appCount === 0) {
976
+ return setImmediate(next);
977
+ }
978
+
979
+ this.dataCopyCount++;
980
+ log.info("package#copyApp()", "copy " + this.appDir + " ==> " + this.applicationDir);
981
+ copySrcToDst.call(this, this.appDir, this.applicationDir, next);
982
+ }
983
+
984
+ function copyAssets(next) {
985
+ if (this.appCount === 0) {
986
+ return setImmediate(next);
987
+ }
988
+
989
+ try {
990
+ async.forEachSeries(this.appinfo.assets, _handleAssets.bind(this), next);
991
+ } catch (err) {
992
+ return setImmediate(next, err);
993
+ }
994
+
995
+ function _handleAssets(file, next) {
996
+ if (path.resolve(this.originAppDir) === path.resolve(this.appDir)) {
997
+ _checkAppinfo.call(this, file, next);
998
+ } else {
999
+ async.series([
1000
+ _checkAppinfo.bind(this, file),
1001
+ _copyAssets.bind(this, file)
1002
+ ], next);
1003
+ }
1004
+ }
1005
+
1006
+ function _checkAppinfo(file, next) {
1007
+ let source;
1008
+ if (path.resolve(file) === path.normalize(file)) {
1009
+ return next(errHndl.getErrMsg("NOT_RELATIVE_PATH_APPINFO", file));
1010
+ } else {
1011
+ source = path.join(this.originAppDir, file);
1012
+ }
1013
+
1014
+ if (path.resolve(source).indexOf(this.originAppDir) !== 0) {
1015
+ return next(errHndl.getErrMsg("NOT_RELATIVE_PATH_APPINFO", file));
1016
+ }
1017
+
1018
+ if (!fs.existsSync(source) && !this.rom) {
1019
+ const msg = errHndl.getErrMsg("NOT_EXIST_PATH", file);
1020
+ if (path.basename(source).indexOf('$') === 0) {
1021
+ // ignore property with starting $ prefix (dynamic property handling in the platform)
1022
+ return setImmediate(next);
1023
+ } else {
1024
+ return setImmediate(next, msg);
1025
+ }
1026
+ }
1027
+ setImmediate(next);
1028
+ }
1029
+
1030
+ function _copyAssets(file, next) {
1031
+ log.verbose("package#copyAssets()#_copyAssets", "'" + file + "' will be located in app directory");
1032
+ const source = path.join(this.originAppDir, file),
1033
+ destination = this.appDir;
1034
+
1035
+ async.series([
1036
+ function(next) {
1037
+ if (!fs.existsSync(destination)) {
1038
+ mkdirp(destination, next);
1039
+ } else {
1040
+ setImmediate(next);
1041
+ }
1042
+ }
1043
+ ], function(err) {
1044
+ if (err) {
1045
+ return setImmediate(next, err);
1046
+ }
1047
+ shelljs.cp('-Rf', source, destination);
1048
+ setImmediate(next);
1049
+ });
1050
+ }
1051
+ }
1052
+
1053
+ function excludeIpkFileFromApp(next) {
1054
+ // Exclude a pre-built .ipk file
1055
+ this.excludeFiles = this.excludeFiles.concat([
1056
+ // eslint-disable-next-line no-useless-escape
1057
+ "[.]*[\\.]ipk",
1058
+ ".DS_Store",
1059
+ // ".vscode",
1060
+ // ".reloadignore",
1061
+ // ".launchparams.json"
1062
+ ]);
1063
+ setImmediate(next);
1064
+ }
1065
+
1066
+ function _retrieve(list, regExp, dirPath, depth, next) {
1067
+ async.waterfall([
1068
+ fs.readdir.bind(null, dirPath), function(fileNames, next) {
1069
+ async.forEach(fileNames, function(fileName, next) {
1070
+ const filePath = path.join(dirPath, fileName);
1071
+ async.waterfall([
1072
+ fs.lstat.bind(null, filePath), function(stat, next) {
1073
+ let result = false;
1074
+ if (depth > 1 && regExp.test(fileName)) {
1075
+ result = true;
1076
+ list.push(filePath);
1077
+ }
1078
+
1079
+ if (!result && stat.isDirectory()) {
1080
+ _retrieve(list, regExp, filePath, depth + 1, next);
1081
+ } else {
1082
+ setImmediate(next);
1083
+ }
1084
+ }
1085
+ ], next);
1086
+ }, next);
1087
+ }
1088
+ ], function(err) {
1089
+ setImmediate(next, err);
1090
+ });
1091
+ }
1092
+
1093
+ function excludeFromApp(middleCb, next) {
1094
+ let excludes;
1095
+ if (this.appCount === 0) {
1096
+ excludes = this.excludeFiles;
1097
+ } else {
1098
+ excludes = this.excludeFiles.concat(this.appinfo.exclude || []);
1099
+ }
1100
+
1101
+ const regExpQueries = excludes.map(function(exclude) {
1102
+ return exclude.replace(/^\./g,"^\\.").replace(/^\*/g,"").replace(/$/g,"$");
1103
+ }, this),
1104
+ strRegExp = regExpQueries.join("|"),
1105
+ regExp = new RegExp(strRegExp, "i"),
1106
+ excludeList = [],
1107
+ tempDirPrefix = path.resolve(this.tempDir, "data/usr/palm");
1108
+
1109
+ async.series([
1110
+ _retrieve.bind(this, excludeList, regExp, tempDirPrefix, 0), function(next) {
1111
+ const meta_files = ["appinfo.json", "services.json", "packageinfo.json", "resourceinfo.json"],
1112
+ unexcludable = [],
1113
+ excluded = [];
1114
+ try {
1115
+ excludeList.forEach(function(file) {
1116
+ if (meta_files.includes(path.basename(file))) {
1117
+ unexcludable.push(path.basename(file));
1118
+ } else {
1119
+ const sliced = file.slice(tempDirPrefix.length + 1);
1120
+ excluded.push(sliced.slice(sliced.indexOf(path.sep) + 1));
1121
+ shelljs.rm('-rf', file);
1122
+ }
1123
+ });
1124
+ if (unexcludable.length > 0) {
1125
+ middleCb("Cannot exclude: " + unexcludable); // FIXME
1126
+ }
1127
+ if (excluded.length > 0) {
1128
+ middleCb("Excluded: " + excluded); // FIXME
1129
+ }
1130
+ setImmediate(next);
1131
+ } catch(err) {
1132
+ setImmediate(next, err);
1133
+ }
1134
+ }
1135
+ ], function(err) {
1136
+ if (err) {
1137
+ return setImmediate(next, err);
1138
+ }
1139
+ setImmediate(next);
1140
+ });
1141
+ }
1142
+
1143
+ function createPackageDir(next) {
1144
+ if (!this.rom) {
1145
+ const pkgDirName = this.pkgId || this.appinfo.id;
1146
+ this.packageDir = path.join(this.tempDir, "data/usr/palm/packages", pkgDirName);
1147
+ log.verbose("package#createPackageDir", "directory:", this.packageDir);
1148
+ mkdirp(this.packageDir, next);
1149
+ } else {
1150
+ setImmediate(next);
1151
+ }
1152
+ }
1153
+
1154
+ function fillPackageDir(next) {
1155
+ log.verbose("fillPackageDir");
1156
+
1157
+ if (!this.rom) {
1158
+ let data = "";
1159
+ if (this.pkginfo) {
1160
+ // Use pkginfofile option or PKG_DIR
1161
+ if (!this.pkginfo.version) {
1162
+ this.pkginfo.version = this.pkgVersion || "1.0.0";
1163
+ }
1164
+ if (this.appinfo) {
1165
+ this.pkginfo.app = this.appinfo.id;
1166
+ }
1167
+ _checkPkgInfo(this.pkginfo.id, this.pkginfo.version, next);
1168
+ data = JSON.stringify(this.pkginfo, null, 2) + "\n";
1169
+ } else {
1170
+ // Use pkgid option or no option
1171
+ const pkginfo = {
1172
+ "id": this.pkgId || this.appinfo.id,
1173
+ "version": this.pkgVersion || "1.0.0"
1174
+ };
1175
+ if (this.appinfo) {
1176
+ pkginfo.app = this.appinfo.id;
1177
+ }
1178
+ _checkPkgInfo(pkginfo.id, pkginfo.version, next);
1179
+ data = JSON.stringify(pkginfo, null, 2) + "\n";
1180
+ }
1181
+ log.verbose("Generating packageinfo.json: " + data);
1182
+ fs.writeFile(path.join(this.packageDir, "packageinfo.json"), data, next);
1183
+ } else {
1184
+ setImmediate(next);
1185
+ }
1186
+
1187
+ function _checkPkgInfo(id, version, next) {
1188
+ if (!(/^[a-z0-9.+-]*$/.test(id))) {
1189
+ log.error(errHndl.getErrMsg("INVALID_VALUE", "pkg id", id));
1190
+ return setImmediate(next, errHndl.getErrMsg("INVALID_ID_RULE"));
1191
+ }
1192
+
1193
+ if (!(/^([1-9]\d{0,8}|\d)\.([1-9]\d{0,8}|\d)\.([1-9]\d{0,8}|\d)$/.test(version))) {
1194
+ log.error(errHndl.getErrMsg("INVALID_VALUE", "pkg version", version));
1195
+ return setImmediate(next, errHndl.getErrMsg("INVALID_VERSION_RULE"));
1196
+ }
1197
+ }
1198
+ }
1199
+
1200
+
1201
+ function loadPackageProperties(inDirs, next) {
1202
+ const self = this;
1203
+ self.packageProperties = {};
1204
+
1205
+ async.forEach(inDirs, function(inDir, next) {
1206
+ const filename = path.join(inDir, "package.properties");
1207
+
1208
+ function _checkFileMode(file) {
1209
+ file = file.replace(/\\/g, "/").trim();
1210
+ const idx = file.lastIndexOf("/");
1211
+ file = (idx !== -1) ? file.slice(idx + 1) : file;
1212
+ self.packageProperties[file] = this.fileMode;
1213
+ }
1214
+
1215
+ if (fs.existsSync(filename)) {
1216
+ const data = fs.readFileSync(filename);
1217
+ try {
1218
+ const lines = data.toString().split("\n");
1219
+ let seperatorIndex;
1220
+
1221
+ for (const i in lines) {
1222
+ if (lines[i].indexOf("filemode.") === 0) {
1223
+ seperatorIndex = lines[i].indexOf("=");
1224
+ const fileList = lines[i].slice(seperatorIndex + 1).trim(),
1225
+ fileArray = fileList.split(",");
1226
+ this.fileMode = lines[i].slice(9, seperatorIndex).trim();
1227
+
1228
+ fileArray.forEach(_checkFileMode);
1229
+ }
1230
+ }
1231
+ setImmediate(next);
1232
+ } catch (error) {
1233
+ setImmediate(next, error);
1234
+ }
1235
+ } else {
1236
+ setImmediate(next);
1237
+ }
1238
+ }, function(err) {
1239
+ // Exclude package.propeties from ipk file
1240
+ self.excludeFiles = self.excludeFiles.concat([
1241
+ "package.properties"
1242
+ ]);
1243
+ setImmediate(next, err);
1244
+ });
1245
+ }
1246
+
1247
+ function outputPackage(destination, next) {
1248
+ log.info("package#outputPackage()");
1249
+
1250
+ // Check that the directories exist
1251
+ if (fs.existsSync(destination)) {
1252
+ const stats = fs.statSync(destination);
1253
+ if (!stats.isDirectory()) {
1254
+ return next(errHndl.getErrMsg("NOT_DIRTYPE_PATH", destination));
1255
+ }
1256
+ } else {
1257
+ log.verbose("setOutputDir()", "creating directory '" + destination + "' ...");
1258
+ mkdirp.sync(destination);
1259
+ }
1260
+ destination = fs.realpathSync(destination);
1261
+
1262
+ if (this.rom) {
1263
+ copySrcToDst.call(this, path.join(this.tempDir, 'data'), destination, next);
1264
+ } else {
1265
+ const tempDir = this.tempDir,
1266
+ tempCtrlDir = path.join(tempDir, 'ctrl'),
1267
+ ctrlTgzFile = path.join(tempDir, 'control.tar.gz'),
1268
+ tempDataDir = path.join(tempDir, 'data'),
1269
+ dataTgzFile = path.join(tempDir, 'data.tar.gz');
1270
+
1271
+ async.series([
1272
+ decidePkgName.bind(this, this.pkgId, this.pkgVersion),
1273
+ getFileSize.bind(this, tempDataDir),
1274
+ makeTgz.bind(this, tempDataDir, dataTgzFile),
1275
+ createDir.bind(this, tempCtrlDir),
1276
+ createControlFile.bind(this, tempCtrlDir, false),
1277
+ createSign.bind(this, tempCtrlDir, dataTgzFile),
1278
+ makeTgz.bind(this, tempCtrlDir, ctrlTgzFile),
1279
+ createDebianBinary.bind(this, tempDir),
1280
+ setIpkFileName.bind(this),
1281
+ removeExistingIpk.bind(this, destination),
1282
+ makeIpk.bind(this, tempDir)
1283
+ ], function(err) {
1284
+ if (err) {
1285
+ setImmediate(next, err);
1286
+ return;
1287
+ }
1288
+ setImmediate(next);
1289
+ });
1290
+ }
1291
+ }
1292
+
1293
+ function decidePkgName(pkgName, pkgVersion, next) {
1294
+ if (this.appCount !== 0) {
1295
+ this.pkg = {
1296
+ name: pkgName || this.appinfo.id,
1297
+ version: pkgVersion || this.appinfo.version
1298
+ };
1299
+ } else if (this.services.length > 0) {
1300
+ this.pkg = {
1301
+ name: pkgName || this.services[0].serviceInfo.id || this.services[0].serviceInfo.services[0].name,
1302
+ version: pkgVersion || "1.0.0"
1303
+ };
1304
+ } else {
1305
+ this.pkg = {
1306
+ name: pkgName || "unknown",
1307
+ version: pkgVersion || "1.0.0"
1308
+ };
1309
+ }
1310
+ setImmediate(next);
1311
+ }
1312
+
1313
+ function getFileSize(srcDir, next) {
1314
+ const self = this;
1315
+
1316
+ async.waterfall([
1317
+ _readSizeRecursive.bind(this, srcDir)
1318
+ ], function(err, size) {
1319
+ if (!err && size) {
1320
+ log.verbose("package#getFileSize()", "Installed-Size:", size);
1321
+ self.size = size;
1322
+ }
1323
+ setImmediate(next, err);
1324
+ });
1325
+
1326
+ function _readSizeRecursive(item, next) {
1327
+ fs.lstat(item, function(err, stats) {
1328
+ let total = stats.size;
1329
+
1330
+ if (!err && stats.isDirectory()) {
1331
+ fs.readdir(item, function(error, list) {
1332
+ if (error) {
1333
+ return next(error);
1334
+ }
1335
+
1336
+ async.forEach(list, function(diritem, callback) {
1337
+ _readSizeRecursive(path.join(item, diritem), function(_err, size) {
1338
+ total += size;
1339
+ callback(_err);
1340
+ });
1341
+ }, function(e) {
1342
+ next(e, total);
1343
+ }
1344
+ );
1345
+ });
1346
+ } else {
1347
+ next(err, total);
1348
+ }
1349
+ });
1350
+ }
1351
+ }
1352
+
1353
+ function createDir(dstPath, next) {
1354
+ log.verbose("package#createDir()", "createDir:" + dstPath);
1355
+ mkdirp(dstPath, next);
1356
+ }
1357
+
1358
+ function createControlFile(dstDir, encInfo, next) {
1359
+ const dstFilePath = path.join(dstDir, 'control');
1360
+ log.verbose("package#createControlFile()", "createControlFile:" + dstFilePath);
1361
+
1362
+ const lines = [
1363
+ "Package: " + this.pkg.name,
1364
+ "Version: " + this.pkg.version,
1365
+ "Section: misc",
1366
+ "Priority: optional",
1367
+ "Architecture: " + (this.architecture || "all"),
1368
+ "Installed-Size: " + (this.size || 1234), // TODO: TBC
1369
+ "Maintainer: N/A <nobody@example.com>", // TODO: TBC
1370
+ "Description: This is a webOS application.",
1371
+ "webOS-Package-Format-Version: 2", // TODO: TBC
1372
+ "webOS-Packager-Version: x.y.x" // TODO: TBC
1373
+ ];
1374
+
1375
+ if (encInfo) {
1376
+ lines.push("Encrypt-Algorithm: AES-256-CBC");
1377
+ }
1378
+ lines.push(''); // for the trailing \n
1379
+ fs.writeFile(dstFilePath, lines.join("\n"), next);
1380
+ }
1381
+
1382
+ function createSign(dstDir, dataTgzPath, next) {
1383
+ if ((!this.sign) || (!this.certificate)) {
1384
+ log.verbose("package#createSign()", "App signing is skipped");
1385
+ return setImmediate(next);
1386
+ }
1387
+
1388
+ const sigFilePath = path.join(dstDir, 'data.tar.gz.sha256.txt'),
1389
+ keyPath = path.resolve(this.sign),
1390
+ crtPath = path.resolve(this.certificate);
1391
+
1392
+ log.verbose("package#createSign()", "dataTgzPath:" + dataTgzPath + ", sigfile:" + sigFilePath);
1393
+ log.verbose("package#createSign()", "keyPath:" + keyPath + ", crtPath:" + crtPath);
1394
+
1395
+ try {
1396
+ // Create certificate to tmp/ctrl directory
1397
+ shelljs.cp('-f', crtPath, dstDir);
1398
+
1399
+ // Create signature and write data.tar.gz.sha256.txt
1400
+ const privateKey = fs.readFileSync(keyPath, 'utf-8'),
1401
+ dataFile = fs.readFileSync(dataTgzPath), // data.tar.gz
1402
+ signer = crypto.createSign('sha256');
1403
+
1404
+ signer.update(dataFile);
1405
+ signer.end();
1406
+
1407
+ const signature = signer.sign(privateKey),
1408
+ buff = Buffer.from(signature),
1409
+ base64data = buff.toString('base64');
1410
+
1411
+ fs.writeFile(sigFilePath, base64data, next);
1412
+ } catch (err) {
1413
+ setImmediate(next, err);
1414
+ }
1415
+ }
1416
+
1417
+ function createDebianBinary(dstDir, next) {
1418
+ const dstFilePath = path.join(dstDir, "debian-binary");
1419
+ log.verbose("package#createDebianBinary()", dstFilePath);
1420
+ fs.writeFile(dstFilePath, "2.0\n", next);
1421
+ }
1422
+
1423
+ function makeTgz(srcDir, dstDir, next) {
1424
+ log.verbose("package#makeTgz()", "makeTgz " + dstDir + " from " + srcDir);
1425
+ const pkgServiceNames = this.pkgServiceNames;
1426
+ // @see https://github.com/isaacs/node-tar/issues/7
1427
+ // it is a workaround for packaged ipk on windows can set +x into directory
1428
+ const fixupDirs = function (filePath, entry) {
1429
+ const fileOrFolderName = filePath.split("/").pop();
1430
+ // opkg does not support Posix Tar fully
1431
+ if (fileOrFolderName.length !== Buffer.byteLength(fileOrFolderName)) {
1432
+ const errMsg = "Please use the file name in english letters. \n\t\t (" + filePath + ")",
1433
+ em = new (require('events').EventEmitter)();
1434
+ em.emit('error', new Error(errMsg));
1435
+ }
1436
+ // Make sure readable directories have execute permission
1437
+ if (entry.isDirectory()) {
1438
+ let maskingBits = 201; // 0311
1439
+ // special case for service directory should have writable permission.
1440
+ if (pkgServiceNames.indexOf(fileOrFolderName) !== -1) {
1441
+ maskingBits = 219; // 0333
1442
+ }
1443
+ entry.mode |= (entry.mode >>> 2) & maskingBits;
1444
+ } else if (entry.isFile()) {
1445
+ // Add other user's readable permission to all files
1446
+ entry.mode |= 4; // 04
1447
+ }
1448
+ if (entry.uid > 0o7777777) {
1449
+ entry.uid = 0;
1450
+ }
1451
+
1452
+ if (entry.gid > 0o7777777) {
1453
+ entry.gid = 0;
1454
+ }
1455
+
1456
+ return true;
1457
+ };
1458
+
1459
+ fs.readdir(srcDir, function (err, fileList) {
1460
+ if (err) setImmediate(next, err);
1461
+ else {
1462
+ tar.c({
1463
+ file: dstDir,
1464
+ gzip: true,
1465
+ cwd: srcDir,
1466
+ filter: fixupDirs
1467
+ }, fileList)
1468
+ .then(() => setImmediate(next))
1469
+ .catch(error => setImmediate(next, error));
1470
+ }
1471
+ });
1472
+ }
1473
+
1474
+ function setIpkFileName(next) {
1475
+ let filename = this.pkg.name;
1476
+ if (this.pkg.version) {
1477
+ // This is asked to replace 'x86' from 'i586' as a file suffix (From NDK)
1478
+ const archSuffix = ('i586' === this.architecture) ? 'x86' : (this.architecture || 'all');
1479
+ filename = filename.concat("_" + this.pkg.version + "_" + archSuffix + ".ipk");
1480
+ } else {
1481
+ filename = filename.concat(".ipk");
1482
+ }
1483
+ this.ipkFileName = filename;
1484
+ setImmediate(next);
1485
+ }
1486
+
1487
+ function removeExistingIpk(destination, next) {
1488
+ if (this.appCount === 0) {
1489
+ return setImmediate(next);
1490
+ }
1491
+
1492
+ const filename = path.join(destination, this.ipkFileName);
1493
+ fs.exists(filename, function(exists) {
1494
+ if (exists) {
1495
+ fs.unlink(filename, next);
1496
+ } else {
1497
+ setImmediate(next); // Nothing to do
1498
+ }
1499
+ });
1500
+ }
1501
+
1502
+ function padSpace(input, length) {
1503
+ // max field length in ar is 16
1504
+ const ret = String(input + ' ');
1505
+ return ret.slice(0, length);
1506
+ }
1507
+
1508
+ function arFileHeader(name, size) {
1509
+ const epoch = Math.floor(Date.now() / 1000) ;
1510
+ return padSpace(name, 16) +
1511
+ padSpace(epoch, 12) +
1512
+ "0 " + // UID, 6 bytes
1513
+ "0 " + // GID, 6 bytes
1514
+ "100644 " + // file mode, 8 bytes
1515
+ padSpace(size, 10) +
1516
+ "\x60\x0A"; // don't ask
1517
+ }
1518
+
1519
+ function makeIpk(srcDir, next) {
1520
+ this.IpkDir = srcDir;
1521
+ this.ipk = path.join(srcDir, this.ipkFileName);
1522
+ log.info("package#makeIpk()", "makeIpk in dir " + this.IpkDir + " file " + this.ipkFileName);
1523
+
1524
+ if (this.nativecmd) { // TODO: TBR
1525
+ shelljs.cd(this.IpkDir);
1526
+ shelljs.exec("ar -q " + this.ipk + " debian-binary control.tar.gz data.tar.gz", {silent: this.silent});
1527
+
1528
+ setImmediate(next);
1529
+ return;
1530
+ }
1531
+
1532
+ // global header, see http://en.wikipedia.org/wiki/Ar_%28Unix%29
1533
+ const header = "!<arch>\n",
1534
+ debBinary = arFileHeader("debian-binary",4) + "2.0\n",
1535
+ that = this,
1536
+ arStream = CombinedStream.create(),
1537
+ pkgFiles = [ 'control.tar.gz', 'data.tar.gz' ],
1538
+ ipkStream = fstream.Writer(this.ipk);
1539
+
1540
+ arStream.append(header + debBinary);
1541
+ pkgFiles.forEach(function(f) {
1542
+ const fpath = path.join(that.IpkDir, f),
1543
+ s = fstream.Reader({ path: fpath, type: 'File'}),
1544
+ stat = fs.statSync(fpath); // TODO: move to asynchronous processing
1545
+
1546
+ arStream.append(arFileHeader(f, stat.size));
1547
+ arStream.append(s);
1548
+ if ((stat.size % 2) !== 0) {
1549
+ log.verbose("package#makeIpk()", 'adding a filler for file ' + f);
1550
+ arStream.append('\n');
1551
+ }
1552
+ }, this);
1553
+
1554
+ arStream.pipe(ipkStream);
1555
+ ipkStream.on('close', function() {
1556
+ setImmediate(next);
1557
+ });
1558
+ ipkStream.on('error', next);
1559
+ }
1560
+
1561
+ function cleanupTmpDir(middleCb, next) {
1562
+ if (this.noclean) {
1563
+ middleCb("Skipping removal of " + this.tempDir);
1564
+ setImmediate(next);
1565
+ } else {
1566
+ rimraf(this.tempDir, function(err) {
1567
+ log.verbose("package#cleanupTmpDir()", "removed " + this.tempDir);
1568
+ setImmediate(next, err);
1569
+ }.bind(this));
1570
+ }
1571
+ }
1572
+
1573
+ function checkDirectory(options, directory, callback) {
1574
+ log.verbose("package#checkDirectory()", directory);
1575
+ if (fs.existsSync(directory)) { // TODO: move to asynchronous processing
1576
+ const stat = fs.statSync(directory);
1577
+ if (!stat.isDirectory()) {
1578
+ callback(errHndl.getErrMsg("NOT_DIRTYPE_PATH", directory));
1579
+ return;
1580
+ }
1581
+ directory = fs.realpathSync(directory);
1582
+ } else {
1583
+ callback(errHndl.getErrMsg("NOT_EXIST_PATH", directory));
1584
+ return;
1585
+ }
1586
+
1587
+ if (options.force) {
1588
+ return callback();
1589
+ }
1590
+
1591
+ if (fs.existsSync(path.join(directory, "packageinfo.json"))) {
1592
+ return callback(errHndl.getErrMsg("NOT_PACKAGE_WITH_PKGDIR"));
1593
+ }
1594
+
1595
+ if (fs.existsSync(path.join(directory, "appinfo.json"))) {// TODO: move to asynchronous processing
1596
+ this.appCount++;
1597
+ log.verbose("package#checkDirectory()", "FOUND appinfo.json, appCount " + this.appCount);
1598
+
1599
+ if (this.appCount > 1) {
1600
+ callback(errHndl.getErrMsg("OVER_APPCOUNT"));
1601
+ } else if (this.rscCount > 0) {
1602
+ callback(errHndl.getErrMsg("NOT_PACKAGE_WITH_RESOURCE"));
1603
+ } else {
1604
+ this.appDir = directory;
1605
+ this.originAppDir = directory;
1606
+ if (fs.existsSync(path.join(directory, "package.js"))) {
1607
+ this.pkgJSExist = true;
1608
+ }
1609
+ callback();
1610
+ }
1611
+ } else if (fs.existsSync(path.join(directory, "services.json"))) {
1612
+ if (this.rscCount > 0) {
1613
+ callback(errHndl.getErrMsg("NOT_PACKAGE_WITH_RESOURCE"));
1614
+ }
1615
+ this.svcDir = this.svcDir || [];
1616
+ this.svcDir = this.svcDir.concat(directory);
1617
+ callback();
1618
+ } else if (fs.existsSync(path.join(directory, "resourceinfo.json"))) {
1619
+ this.rscCount++;
1620
+ log.verbose("FOUND resourceinfo.json, rscCount " + this.rscCount);
1621
+ if (this.appCount > 0 || this.svcDir && this.svcDir.length > 0) {
1622
+ return callback(errHndl.getErrMsg("NOT_PACKAGE_WITH_RESOURCE"));
1623
+ }
1624
+ this.resources = this.resources || [];
1625
+ const rsc = {};
1626
+ rsc.dir = directory;
1627
+ this.resources = this.resources.concat(rsc);
1628
+ callback();
1629
+ } else if (fs.existsSync(path.join(directory, "account-templates.json"))) {
1630
+ callback(errHndl.getErrMsg("NO_ACCOUNT"));
1631
+ } else {
1632
+ // find service directory recursively
1633
+ const foundSvcDirs = [];
1634
+ this.svcDir = this.svcDir || [];
1635
+ this.svcDir = this.svcDir.concat(directory);
1636
+
1637
+ findServiceDir.call(this, foundSvcDirs, function() {
1638
+ if (foundSvcDirs.length > 0) {
1639
+ if (this.rscCount > 0) {
1640
+ callback(errHndl.getErrMsg("NOT_PACKAGE_WITH_RESOURCE"));
1641
+ }
1642
+ callback();
1643
+ } else {
1644
+ callback(errHndl.getErrMsg("NO_METAFILE", "APP_DIR/SVC_DIR", directory));
1645
+ }
1646
+ });
1647
+ }
1648
+ }
1649
+
1650
+ // find service directories checking if directory has services.json file
1651
+ function findServiceDir(services, next) {
1652
+ const checkDirs = [].concat(this.svcDir || this.originAppDir || []),
1653
+ foundFilePath = [];
1654
+ if (checkDirs.length === 0) {
1655
+ return setImmediate(next);
1656
+ }
1657
+
1658
+ async.forEach(checkDirs, function(checkDir, next) {
1659
+ walkFolder(checkDir, "services.json", foundFilePath, 3, function(err) {
1660
+ if (err) {
1661
+ return setImmediate(next, err);
1662
+ }
1663
+
1664
+ foundFilePath.forEach(function(filePath) {
1665
+ const svc = new Service();
1666
+ svc.srcDir = path.dirname(filePath);
1667
+ svc.dirName = path.basename(svc.srcDir);
1668
+ services.push(svc);
1669
+ });
1670
+ foundFilePath.pop();
1671
+ setImmediate(next, err);
1672
+ });
1673
+ }, function(err) {
1674
+ setImmediate(next, err);
1675
+ });
1676
+ }
1677
+
1678
+ function walkFolder(dirPath, findFileName, foundFilePath, depth, next) {
1679
+ if (depth <= 0) {
1680
+ return next();
1681
+ }
1682
+ async.waterfall([
1683
+ fs.readdir.bind(null, dirPath),
1684
+ function(fileNames, next) {
1685
+ async.forEach(fileNames, function(fileName, next) {
1686
+ const filePath = path.join(dirPath, fileName);
1687
+ async.waterfall([
1688
+ fs.lstat.bind(null, filePath),
1689
+ function(stat, next) {
1690
+ if (stat.isFile()) {
1691
+ if (fileName === findFileName) {
1692
+ foundFilePath.push(filePath);
1693
+ }
1694
+ next();
1695
+ } else if (stat.isDirectory()) {
1696
+ walkFolder(filePath, findFileName, foundFilePath, (depth-1), next);
1697
+ } else {
1698
+ next();
1699
+ }
1700
+ }
1701
+ ], next); // async.waterfall
1702
+ }, next); // async.forEach
1703
+ }
1704
+ ], function(err) {
1705
+ next(err);
1706
+ }); // async.waterfall
1707
+ }
1708
+
1709
+ // read services.json recursivly
1710
+ function loadServiceInfo(next) {
1711
+ for (const idx in this.services) {
1712
+ const filename = path.join(this.services[idx].srcDir, "services.json");
1713
+ try {
1714
+ const data = fs.readFileSync(filename),
1715
+ info = JSON.parse(data);
1716
+ if (!(Object.prototype.hasOwnProperty.call(info, 'id') &&
1717
+ Object.prototype.hasOwnProperty.call(info, 'services'))) {
1718
+ continue;
1719
+ }
1720
+ this.services[idx].serviceInfo = info;
1721
+ this.services[idx].valid = true;
1722
+ } catch (err) {
1723
+ return setImmediate(next, err);
1724
+ }
1725
+ }
1726
+ log.info("package#loadServiceInfo()", "num of serviceInfo: " + this.services.length);
1727
+ setImmediate(next);
1728
+ }
1729
+
1730
+ // check services.json recursivly
1731
+ function checkServiceInfo(next) {
1732
+ const pkgId = this.pkgId || this.appinfo.id,
1733
+ svcIds = [];
1734
+ let errFlag = false;
1735
+
1736
+ this.services.forEach(function(service) {
1737
+ if (service.valid === false) {
1738
+ return;
1739
+ }
1740
+
1741
+ svcIds.push(getPkgServiceNames(service.serviceInfo)[0]);
1742
+ });
1743
+
1744
+ svcIds.forEach(function(svcId) {
1745
+ if (svcId.indexOf(pkgId + ".") !== 0) {
1746
+ errFlag = true;
1747
+ }
1748
+ });
1749
+
1750
+ if (errFlag) {
1751
+ return setImmediate(next, errHndl.getErrMsg("INVALID_SERVICEID", pkgId));
1752
+ }
1753
+ setImmediate(next);
1754
+ }
1755
+
1756
+ // create dir with each service's name under (tmp) + data/usr/palm/services/
1757
+ function createServiceDir(next) {
1758
+ this.services.forEach(function(service) {
1759
+ if (service.valid === false) {
1760
+ return;
1761
+ }
1762
+
1763
+ getPkgServiceNames(service.serviceInfo).forEach(function(serviceName) {
1764
+ const serviceDir = path.join(this.tempDir, "data/usr/palm/services", serviceName);
1765
+ service.dstDirs.push(serviceDir);
1766
+ try {
1767
+ log.info("package#createServiceDir()", "service dir:" + serviceDir);
1768
+ mkdirp.sync(serviceDir);
1769
+ } catch (err) {
1770
+ return setImmediate(next, err);
1771
+ }
1772
+ }.bind(this));
1773
+ }.bind(this));
1774
+ setImmediate(next);
1775
+ }
1776
+
1777
+ // copy service files into each serviceInfos[x].id directory.
1778
+ function copyService(next) {
1779
+ log.info("package#copyService()");
1780
+ const self = this,
1781
+ validServices = this.services.filter(function(service) {
1782
+ return service.valid;
1783
+ });
1784
+ try {
1785
+ async.forEachSeries(validServices, function(service, next) {
1786
+ async.forEach(service.dstDirs, function(dstDir, next) {
1787
+ self.dataCopyCount++;
1788
+ self.minifyDone = !self.minify;
1789
+ copySrcToDst.call(self, service.srcDir, dstDir, next);
1790
+ }, next);
1791
+ }, next);
1792
+ } catch (err) {
1793
+ setImmediate(next, err);
1794
+ }
1795
+ }
1796
+
1797
+ // add service info into packageinfo.json.
1798
+ function addServiceInPkgInfo(next) {
1799
+ if (!this.rom) {
1800
+ const filename = path.join(this.packageDir, "packageinfo.json");
1801
+ let pkginfo, validServiceCount;
1802
+ try {
1803
+ const data = fs.readFileSync(filename);
1804
+ validServiceCount = 0;
1805
+ pkginfo = JSON.parse(data);
1806
+ log.silly("package#addServiceInPkgInfo()", "PACKAGEINFO:", pkginfo);
1807
+ } catch (err) {
1808
+ console.error(err);
1809
+ setImmediate(next, err);
1810
+ }
1811
+ this.services.filter(function(s) {
1812
+ return s.valid;
1813
+ }).forEach(function(service) {
1814
+ getPkgServiceNames(service.serviceInfo).forEach(function(serviceName) {
1815
+ this.pkgServiceNames.push(serviceName);
1816
+ validServiceCount++;
1817
+ }.bind(this));
1818
+ }.bind(this));
1819
+
1820
+ if (validServiceCount > 0) {
1821
+ pkginfo.services = this.pkgServiceNames;
1822
+ const data = JSON.stringify(pkginfo, null, 2) + "\n";
1823
+ log.silly("package#addServiceInPkgInfo()", "Modified package.json:" + data);
1824
+ fs.writeFile(path.join(this.packageDir, "packageinfo.json"), data, next);
1825
+ } else {
1826
+ setImmediate(next);
1827
+ }
1828
+ } else {
1829
+ setImmediate(next);
1830
+ }
1831
+ }
1832
+
1833
+ // remove service dir from tmp source dir before packaging
1834
+ function removeServiceFromAppDir(middleCb, next) {
1835
+ if (this.appCount === 0) {
1836
+ return setImmediate(next);
1837
+ }
1838
+
1839
+ let checkDir = this.applicationDir,
1840
+ needRmCheckDir = false;
1841
+ const fileList = fs.readdirSync(checkDir);
1842
+
1843
+ function _checkDir(dir) {
1844
+ if (this.dirName === dir) {
1845
+ try {
1846
+ const rmDir = path.join(this.applicationDir, this.dirName);
1847
+ shelljs.rm('-rf', rmDir);
1848
+ } catch (err) {
1849
+ next(Error("ERROR" + err), null);
1850
+ }
1851
+ }
1852
+ }
1853
+
1854
+ if (fileList.indexOf('services') !== -1) {
1855
+ checkDir = path.join(this.applicationDir, 'services');
1856
+ const stats = fs.statSync(checkDir);
1857
+ if (stats.isDirectory()) {
1858
+ needRmCheckDir = true;
1859
+ }
1860
+ }
1861
+ if (needRmCheckDir === true) {
1862
+ try {
1863
+ shelljs.rm('-rf', checkDir);
1864
+ } catch (err) {
1865
+ middleCb("ERROR:" + err);
1866
+ }
1867
+ } else {
1868
+ for (const idx in this.services) {
1869
+ this.dirName = this.services[idx].dirName;
1870
+ fileList.forEach(_checkDir, this);
1871
+ }
1872
+ }
1873
+ setImmediate(next);
1874
+ }
1875
+
1876
+ function loadResourceInfo(next) {
1877
+ log.verbose("loadResourceInfo");
1878
+ if (this.rscCount === 0) {
1879
+ return setImmediate(next);
1880
+ }
1881
+
1882
+ this.resources.forEach(function(resource) {
1883
+ const filePath = path.join(resource.dir, "resourceinfo.json");
1884
+ try {
1885
+ const data = fs.readFileSync(filePath),
1886
+ info = JSON.parse(data);
1887
+ resource.info = info;
1888
+ } catch (err) {
1889
+ return setImmediate(next, errHndl.getErrMsg("INVALID_JSON_FORMAT", "resourceinfo.json"));
1890
+ }
1891
+ });
1892
+ log.verbose("num of resourceInfo: " + this.resources.length);
1893
+ setImmediate(next);
1894
+ }
1895
+
1896
+ function checkResourceInfo(next) {
1897
+ log.verbose("checkResourceInfo");
1898
+ const pkgId = this.pkgId;
1899
+
1900
+ this.resources.forEach(function(resource) {
1901
+ if (!resource.info.id) {
1902
+ return setImmediate(next, errHndl.getErrMsg("REQUIRED_FIELD", "id"));
1903
+ } else if (resource.info.id.indexOf(pkgId) !== 0) {
1904
+ return setImmediate(next, errHndl.getErrMsg("INVALID_RESOURCEID", pkgId));
1905
+ } else if (resource.info.id.length < 1 || !(/^[a-z0-9.+-]*$/.test(resource.info.id))) {
1906
+ log.error(errHndl.getErrMsg("INVALID_VALUE", "id", resource.info.id));
1907
+ return setImmediate(next, errHndl.getErrMsg("INVALID_ID_RULE"));
1908
+ }
1909
+ });
1910
+ setImmediate(next);
1911
+ }
1912
+
1913
+ function createResourceDir(next) {
1914
+ log.verbose("createResourceDir");
1915
+ this.resources.forEach(function(resource) {
1916
+ try {
1917
+ const resourceDir = path.join(this.tempDir, "data/usr/palm/resources", resource.info.id);
1918
+ log.verbose("resource dir = " + resourceDir);
1919
+ resource.dstDir = resourceDir;
1920
+ mkdirp.sync(resourceDir);
1921
+ } catch (err) {
1922
+ return setImmediate(next, err);
1923
+ }
1924
+ }.bind(this));
1925
+ log.silly("this.resources:", this.resources);
1926
+ setImmediate(next);
1927
+ }
1928
+
1929
+ function copyResource(next) {
1930
+ log.verbose("copyResource()");
1931
+ if (this.rscCount === 0) {
1932
+ return setImmediate(next);
1933
+ }
1934
+
1935
+ const self = this;
1936
+ try {
1937
+ async.forEachSeries(this.resources, function(resource, next) {
1938
+ self.dataCopyCount++;
1939
+ log.verbose("copyResource(), copy " + resource.dir + " ==> " + resource.dstDir);
1940
+ copySrcToDst.call(self, resource.dir, resource.dstDir, next);
1941
+ }, next);
1942
+ } catch (err) {
1943
+ setImmediate(next, err);
1944
+ }
1945
+ }
1946
+
1947
+ function addResourceInPkgInfo(next) {
1948
+ log.verbose("addResourceInPkgInfo");
1949
+ if (!this.rom && this.rscCount > 0) {
1950
+ const filename = path.join(this.packageDir, "packageinfo.json");
1951
+ let pkginfo, data;
1952
+ try {
1953
+ data = fs.readFileSync(filename);
1954
+ log.verbose("PACKAGEINFO: " + data);
1955
+ pkginfo = JSON.parse(data);
1956
+ } catch (err) {
1957
+ console.error(err);
1958
+ setImmediate(next, err);
1959
+ }
1960
+
1961
+ this.resources.forEach(function(resource) {
1962
+ this.pkgResourceNames.push(resource.info.id);
1963
+ }.bind(this));
1964
+
1965
+ pkginfo.resources = this.pkgResourceNames;
1966
+ data = JSON.stringify(pkginfo, null, 2) + "\n";
1967
+ log.verbose("Modified package.json: " + data);
1968
+ fs.writeFile(path.join(this.packageDir, "packageinfo.json"), data, next);
1969
+ } else {
1970
+ setImmediate(next);
1971
+ }
1972
+ }
1973
+
1974
+ function copyData(inDirs, forceCopy, next) {
1975
+ log.verbose("package#copyData()", "only run when force packaging");
1976
+ if (forceCopy && this.dataCopyCount === 0) {
1977
+ const dst = path.join(this.tempDir, "data");
1978
+ async.forEachSeries(inDirs, function(src, next) {
1979
+ copySrcToDst.call(this, src, dst, next);
1980
+ }, function(err) {
1981
+ setImmediate(next, err);
1982
+ });
1983
+ } else {
1984
+ return setImmediate(next);
1985
+ }
1986
+ }
1987
+
1988
+ function getPkgServiceNames(serviceInfo) {
1989
+ let serviceNames = [];
1990
+ if (servicePkgMethod === "id") {
1991
+ serviceNames = [serviceInfo.id];
1992
+ } else if (serviceInfo.services) {
1993
+ const serviceProps = (serviceInfo.services instanceof Array)
1994
+ ? serviceInfo.services : [serviceInfo.services];
1995
+ serviceNames = serviceProps.map(function(serviceProp) {
1996
+ return serviceProp.name;
1997
+ });
1998
+ }
1999
+ return serviceNames;
2000
+ }
2001
+
2002
+ function setUmask(mask, next) {
2003
+ this.oldmask = process.umask(mask);
2004
+ setImmediate(next);
2005
+ }
2006
+
2007
+ function recoverUmask(next) {
2008
+ if (this.oldmask) {
2009
+ process.umask(this.oldmask);
2010
+ }
2011
+ setImmediate(next);
2012
+ }
2013
+
2014
+ function rewriteFileWoBOMAsUtf8(filePath, rewriteFlag, next) {
2015
+ let data = fs.readFileSync(filePath);
2016
+ const encodingFormat = chardet.detect(Buffer.from(data));
2017
+
2018
+ if (['UTF-8', 'ISO-8895-1'].indexOf(encodingFormat) === -1) {
2019
+ log.silly("package#rewriteFileWoBOMAsUtf8()", "current encoding type:" + encodingFormat);
2020
+ data = encoding.convert(data, "UTF-8", encodingFormat);
2021
+ }
2022
+ data = stripbom(data);
2023
+
2024
+ if (rewriteFlag) {
2025
+ fs.writeFileSync(filePath,
2026
+ data, { encoding: "utf8" }
2027
+ );
2028
+ }
2029
+
2030
+ if (next !== 'undefined' && typeof next === 'function') {
2031
+ setImmediate(next, null, data);
2032
+ }
2033
+ return data;
2034
+ }
2035
+
2036
+ function encryptPackage(next) {
2037
+ if (this.rom || !this.encrypt) { // Do not encrypt when -rom option is given
2038
+ setImmediate(next);
2039
+ return;
2040
+ } else {
2041
+ this.encryptDir = path.join(this.tempDir, "encrypt");
2042
+ const encryptDir = this.encryptDir,
2043
+ encryptCtrlDir = path.join(encryptDir, 'ctrl'),
2044
+ ctrlTgzFile = path.join(encryptDir, 'control.tar.gz'),
2045
+ encryptDataDir = path.join(encryptDir, 'data'),
2046
+ dataTgzFile = path.join(encryptDir, 'data.tar.gz');
2047
+
2048
+ async.series([
2049
+ createDir.bind(this, encryptDir),
2050
+ createDir.bind(this, encryptCtrlDir),
2051
+ createDir.bind(this, encryptDataDir),
2052
+ createkeyIVfile.bind(this, encryptCtrlDir),
2053
+ encryptIpk.bind(this, encryptDataDir),
2054
+ makeTgz.bind(this, encryptDataDir, dataTgzFile),
2055
+ createControlFile.bind(this, encryptCtrlDir, true),
2056
+ makeTgz.bind(this, encryptCtrlDir, ctrlTgzFile),
2057
+ createDebianBinary.bind(this, encryptDir),
2058
+ makeIpk.bind(this, encryptDir)
2059
+ ], function(err) {
2060
+ if (err) {
2061
+ setImmediate(next, err);
2062
+ return;
2063
+ }
2064
+ log.verbose("package#encryptPackage()", "success to encrypt pacakge");
2065
+ setImmediate(next);
2066
+ });
2067
+ }
2068
+ }
2069
+
2070
+ function createkeyIVfile(dstPath, next) {
2071
+ // generate random key& IV
2072
+ this.key = Buffer.from(crypto.randomBytes(32), 'base64');
2073
+ this.iv = Buffer.from(crypto.randomBytes(16), 'base64');
2074
+
2075
+ try {
2076
+ // Read public key
2077
+ const publickeyPath = path.join(__dirname, '../', 'files', 'conf', 'pubkey.pem'),
2078
+ publickey = fs.readFileSync(publickeyPath, 'utf8');
2079
+
2080
+ // Encrypt key, iv by publickey
2081
+ const encryptedKey= crypto.publicEncrypt({
2082
+ key: publickey,
2083
+ padding: 4 /* crypto.constants.RSA_PKCS1_OAEP_PADDING */
2084
+ }, Buffer.from(this.key.toString('base64')));
2085
+
2086
+ const encryptedIV= crypto.publicEncrypt({
2087
+ key: publickey,
2088
+ padding: 4 /* crypto.constants.RSA_PKCS1_OAEP_PADDING */
2089
+ }, Buffer.from(this.iv.toString('base64')));
2090
+
2091
+ const keyFilePath = path.join(dstPath, "key");
2092
+ fs.writeFileSync(keyFilePath, encryptedKey, 'binary');
2093
+
2094
+ // write iv file on encrypt/control
2095
+ const ivFilePath = path.join(dstPath , "iv");
2096
+ fs.writeFileSync(ivFilePath, encryptedIV, 'binary');
2097
+
2098
+ } catch (err) {
2099
+ return setImmediate(next, errHndl.getErrMsg(err));
2100
+ }
2101
+ setImmediate(next);
2102
+ }
2103
+
2104
+ function copyOutputToDst(destination, middleCb, next) {
2105
+ // copy data directory to destination
2106
+ if (this.rom) {
2107
+ middleCb("Create output directory to " + destination);
2108
+ copySrcToDst.call(this, path.join(this.tempDir, 'data'), destination, next);
2109
+ } else if (this.encrypt) {
2110
+ // copy encrypted ipk to destination
2111
+ middleCb("Create encrypted " + this.ipkFileName + " to " + destination);
2112
+ copySrcToDst.call(this, path.join(this.encryptDir, this.ipkFileName), destination, next);
2113
+ } else {
2114
+ // copy plain ipk to destination
2115
+ let outmsg = "Create ";
2116
+ if (this.sign && this.certificate) {
2117
+ outmsg = outmsg.concat("signed ");
2118
+ }
2119
+ middleCb(outmsg + this.ipkFileName + " to " + destination);
2120
+ copySrcToDst.call(this, path.join(this.tempDir, this.ipkFileName), destination, next);
2121
+ }
2122
+ }
2123
+
2124
+ function encryptIpk(dstPath, next) {
2125
+ log.verbose("package#encryptPackage()#encrypIpk()", "encrypt plain ipk to /encrypt/data");
2126
+ const plainIpkPath = path.join(this.tempDir, this.ipkFileName),
2127
+ encrypedIpkPath = path.join(dstPath, this.ipkFileName);
2128
+
2129
+ try {
2130
+ const input = fs.createReadStream(plainIpkPath),
2131
+ output = fs.createWriteStream(encrypedIpkPath),
2132
+ cipher = crypto.createCipheriv('aes-256-cbc', this.key, this.iv);
2133
+
2134
+ output.on('close', function() {
2135
+ log.verbose("package#encryptPackage()#encrypIpk()", "encrypted Ipk to " + encrypedIpkPath);
2136
+ setImmediate(next);
2137
+ }).
2138
+ on('error', function(err) {
2139
+ setImmediate(next, err);
2140
+ });
2141
+
2142
+ input.pipe(cipher).pipe(output);
2143
+ } catch (err) {
2144
+ setImmediate(next, err);
2145
+ }
2146
+ }
2147
+ }());