eitri-cli 1.0.2

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 (122) hide show
  1. package/README.md +54 -0
  2. package/bitbucket-pipelines.toBeChanged.yml +64 -0
  3. package/boilerplate/mini-app-cafe-madeira.zip +0 -0
  4. package/check-version.js +12 -0
  5. package/config/default-eitri.js +1 -0
  6. package/config/dev.js +91 -0
  7. package/config/k8s-eitri.js +92 -0
  8. package/config/loc-eitri.js +92 -0
  9. package/config/prod-eitri.js +92 -0
  10. package/config/test-eitri.js +2 -0
  11. package/index-eitri.js +6 -0
  12. package/index.js +182 -0
  13. package/install-dev.bat +1 -0
  14. package/install-dev.sh +1 -0
  15. package/jest.config.js +6 -0
  16. package/jsconfig.json +9 -0
  17. package/package.json +80 -0
  18. package/publisher.js +53 -0
  19. package/src/cmd/clean.js +179 -0
  20. package/src/cmd/create.js +281 -0
  21. package/src/cmd/credentials.js +105 -0
  22. package/src/cmd/invite.js +87 -0
  23. package/src/cmd/list.js +36 -0
  24. package/src/cmd/login.js +51 -0
  25. package/src/cmd/manage-env.js +129 -0
  26. package/src/cmd/open-share.js +6 -0
  27. package/src/cmd/order-details.js +6 -0
  28. package/src/cmd/push-version.js +182 -0
  29. package/src/cmd/show-message-if-outdated-package.js +32 -0
  30. package/src/cmd/signup.js +69 -0
  31. package/src/cmd/start.js +171 -0
  32. package/src/cmd/tail-logs.js +26 -0
  33. package/src/cmd/test-initialization-params.js +6 -0
  34. package/src/cmd/validate.js +170 -0
  35. package/src/cmd/version.js +29 -0
  36. package/src/enum/WatcherOpts.js +3 -0
  37. package/src/enum/target.js +6 -0
  38. package/src/helpers/request-listener-helper.js +55 -0
  39. package/src/model/Payload.js +45 -0
  40. package/src/model/Target.js +76 -0
  41. package/src/model/User.js +11 -0
  42. package/src/service/AuthConfig.js +88 -0
  43. package/src/service/BlindGuardian.js +134 -0
  44. package/src/service/CliLogin.js +47 -0
  45. package/src/service/ConfigService.js +16 -0
  46. package/src/service/CredentialsService.js +47 -0
  47. package/src/service/GATrackingStrategy.js +16 -0
  48. package/src/service/HashFolder.js +79 -0
  49. package/src/service/Http.js +234 -0
  50. package/src/service/InviteService.js +45 -0
  51. package/src/service/ManageEnvService.js +10 -0
  52. package/src/service/MiniLog.js +132 -0
  53. package/src/service/QRCodeFactory.js +43 -0
  54. package/src/service/Server.js +239 -0
  55. package/src/service/StarterService.js +31 -0
  56. package/src/service/TagTree.js +101 -0
  57. package/src/service/TargetService.js +97 -0
  58. package/src/service/TrackService.js +11 -0
  59. package/src/service/TrackingEitriAnalytics.js +32 -0
  60. package/src/service/TrackingService.js +183 -0
  61. package/src/service/ValidateResult.js +57 -0
  62. package/src/service/Watcher.js +119 -0
  63. package/src/service/Workspace.js +1069 -0
  64. package/src/service/WorkspaceManager.js +74 -0
  65. package/src/service/factories/DoubtsStarterFactory.js +25 -0
  66. package/src/service/factories/MiniWebAppFactory.js +43 -0
  67. package/src/service/factories/QRCodeStarterFactory.js +78 -0
  68. package/src/service/factories/WebStarterFactory.js +88 -0
  69. package/src/service/factories/WoodCoffeeFactory.js +230 -0
  70. package/src/util/AboutTemplate.jsx +14 -0
  71. package/src/util/UrlUtils.js +12 -0
  72. package/src/util/UserLocalCredential.js +122 -0
  73. package/src/util/error-messages/error-messages.js +24 -0
  74. package/src/util/error-messages/invite/invite-error-messages.js +14 -0
  75. package/src/util/getCliVersion.js +13 -0
  76. package/src/util/getCreateFactory.js +9 -0
  77. package/src/util/ipv4.js +19 -0
  78. package/src/util/manage-env.js +65 -0
  79. package/src/util/open-docs.js +7 -0
  80. package/src/util/os.js +39 -0
  81. package/src/util/server-url.js +15 -0
  82. package/src/util/template-utils.js +21 -0
  83. package/src/view/index.html +19 -0
  84. package/targetMobileStickyness.md +47 -0
  85. package/targetWebStickyness.md +40 -0
  86. package/test/_fixtures/factory.js +30 -0
  87. package/test/_fixtures/miniWebApp/miniapp.conf.js +4 -0
  88. package/test/_fixtures/miniapp.conf.js +5 -0
  89. package/test/_fixtures/server/HelloWorldBackend.js +7 -0
  90. package/test/_fixtures/src/Home.js +5 -0
  91. package/test/_fixtures/src/Home2.js +5 -0
  92. package/test/_fixtures/src/commons/util.js +3 -0
  93. package/test/_fixtures/src/components/TagA.jsx +4 -0
  94. package/test/_fixtures/src/components/TagB.jsx +4 -0
  95. package/test/_fixtures/src/components/TagC.jsx +3 -0
  96. package/test/_fixtures/src/components/TagD.jsx +3 -0
  97. package/test/_fixtures/src/server/foo.js +7 -0
  98. package/test/_fixtures/src/views/AboutTemplate.jsx +14 -0
  99. package/test/_fixtures/woodcoffee/miniapp.conf.js +3 -0
  100. package/test/ame.conf.js +3 -0
  101. package/test/cmd/clean.test.js +66 -0
  102. package/test/cmd/create.test.js +252 -0
  103. package/test/cmd/credentials.test.js +159 -0
  104. package/test/cmd/list.test.js +74 -0
  105. package/test/cmd/manage-env.test.js +168 -0
  106. package/test/cmd/signup.test.js +20 -0
  107. package/test/cmd/start.test.js +5 -0
  108. package/test/miniapp.conf.js +3 -0
  109. package/test/model/Payload.test.js +35 -0
  110. package/test/service/BlindGuardian.test.js +84 -0
  111. package/test/service/CheckAmeConf.test.js +313 -0
  112. package/test/service/Http.test.js +312 -0
  113. package/test/service/InviteService.test.js +117 -0
  114. package/test/service/MiniWebAppFactory.test.js +40 -0
  115. package/test/service/TagTree.test.js +81 -0
  116. package/test/service/TargetService.test.js +48 -0
  117. package/test/service/TrackingService.test.js +105 -0
  118. package/test/service/UserAmeConf.test.js +47 -0
  119. package/test/service/WoodCoffeeFactory.test.js +148 -0
  120. package/test/service/Workspace.test.js +364 -0
  121. package/thinQrCode.md +58 -0
  122. package/v1.5.0.md +3 -0
@@ -0,0 +1,1069 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+ const Http = require('./Http')
4
+ const Base64 = require('base-64')
5
+ const TagTree = require('./TagTree')
6
+ const FormData = require('form-data')
7
+ const { v4: uuidv4 } = require('uuid')
8
+ const config = require('config')
9
+ const AdmZip = require('adm-zip')
10
+ const BlindGuardian = require('./BlindGuardian')
11
+ const HashFolder = require('./HashFolder')
12
+ const packageJson = require(path.resolve(__dirname, '..', '..', 'package.json'))
13
+ const tempFolderPath = require('../util/os').OS_MAPPER
14
+ const TargetService = require('../service/TargetService')
15
+ const WatcherOpts = require('../enum/WatcherOpts')
16
+ const { default: axios } = require('../../node_modules/axios/index')
17
+ const ipv4 = require('../util/ipv4')
18
+ const { compressToBase64 } = require('lz-string')
19
+ const Buffer = require('buffer')
20
+ const DEFAULT_ENV = 'dev'
21
+ const cliProgress = require('cli-progress')
22
+ class Workspace {
23
+ constructor(blindGuardian, hashFolder) {
24
+ this.config = config.get('workspace')
25
+ this.basePath = this.config.basePath
26
+ this.blindGuardian = blindGuardian
27
+ this.hashFolder = hashFolder
28
+ this.folder2watch = ''
29
+ this.resourceFolders2watch = []
30
+ this.http = new Http(this.blindGuardian)
31
+ this.upsertQueue = []
32
+ this.requestNumber = 0
33
+ this.publishing = false
34
+ if (!this.config.colossus) {
35
+ console.warn('Missing colossus config inside workspace.')
36
+ }
37
+ this.shareApi = config.get('shareApi')
38
+ this.webhooks = []
39
+ this.targets = []
40
+
41
+ /**
42
+ * @type {cliProgress.SingleBar}
43
+ * @private
44
+ */
45
+ this.progressBar
46
+ this.progressBarCounter = 0
47
+ }
48
+
49
+ getTargets() {
50
+ return this.targets
51
+ }
52
+
53
+ setTargets(targets) {
54
+ this.targets = targets
55
+ }
56
+
57
+ getWebhooks () {
58
+ return this.webhooks
59
+ }
60
+
61
+ setWebhooks(webhooks) {
62
+ this.webhooks = webhooks
63
+ }
64
+
65
+ setServerUrl(url) {
66
+ this.serverUrl = url
67
+ }
68
+
69
+ setQrCodeUrl(qrCodeUrl) {
70
+ this.qrCodeUrl = qrCodeUrl
71
+ }
72
+
73
+ setFolder2Watch(folderPath) {
74
+ this.folder2watch = folderPath
75
+ }
76
+
77
+ setResourceFolder2Watch(resourceFolders2watch) {
78
+ this.resourceFolders2watch = resourceFolders2watch
79
+ }
80
+
81
+ getResourceFolder2Watch() {
82
+ return this.resourceFolders2watch
83
+ }
84
+
85
+ async getTargetConfig(id, target) {
86
+ try{
87
+ const configs = await this.getAllTargetConfigs(target)
88
+ const config = configs.find(cfg => cfg.id === id)
89
+
90
+ if(!config) {
91
+ console.log(`TargetConfig [${target}] não encontrado, utilize o comando --list-target-configs para ver as configurações disponíveis`)
92
+ process.exit(1)
93
+ }
94
+
95
+ return [config]
96
+ } catch (e) {
97
+ console.log(`Erro ao buscar configuração: ${e}`)
98
+ process.exit(1)
99
+ }
100
+ }
101
+
102
+ async getAllTargetConfigs(target) {
103
+ const targetUrl = `${this.config.url}${this.config.targetPath}?name=${target}`
104
+ try{
105
+ const response = await axios.get(targetUrl)
106
+ const fullTarget = response.data
107
+ const configs = fullTarget[0].targetConfig
108
+
109
+ return configs
110
+ } catch (e) {
111
+ console.log(`Erro ao buscar configuração: ${e}`)
112
+ process.exit(1)
113
+ }
114
+ }
115
+
116
+ getTarget() {
117
+
118
+ if(!this.target) {
119
+ console.warn('Target não foi detectado no setup()')
120
+ }
121
+
122
+ return this.target
123
+ }
124
+
125
+ async init() {
126
+ const token = await this.blindGuardian.getToken()
127
+ this.http.setToken(token)
128
+ this.userEmail = token.email
129
+ }
130
+
131
+ getMiniConf() {
132
+ if (!this._miniConf) {
133
+ const miniAppConfPath = path.resolve(this.folder2watch, '../miniapp.conf.js')
134
+ if (!fs.existsSync(miniAppConfPath)) {
135
+ throw new Error('Por favor, verifique se você está dentro da pasta de um projeto eitri-app.')
136
+ }
137
+ this._miniConf = require(miniAppConfPath)
138
+ if (!this._miniConf.version) {
139
+ throw Error('Arquivo miniapp.conf.js sem version')
140
+ }
141
+ }
142
+ return this._miniConf
143
+ }
144
+
145
+ setMiniConf(miniConf) {
146
+ this._miniConf = miniConf
147
+ return new Promise((resolve, reject) => {
148
+ const miniAppConfPath = path.resolve(this.folder2watch, '../miniapp.conf.js')
149
+ const jsonContent = JSON.stringify(miniConf, null, 4)
150
+ fs.writeFile(miniAppConfPath, `module.exports = ${jsonContent}`, (err) => {
151
+ if (err) {
152
+ console.log(err)
153
+ return reject(err)
154
+ }
155
+ resolve()
156
+ })
157
+ })
158
+ }
159
+
160
+ async setup() {
161
+ const miniConf = this.getMiniConf()
162
+ const headers = {
163
+ 'accept': this.config.libs.updateLibsEndpointVersion
164
+ }
165
+ const setupResponse = await this.http.post(`${this.serverUrl}/${this.basePath}/setup`, miniConf, headers) // somente para garantir que a pasta do usuario existe
166
+
167
+ console.log('Preparando compilador')
168
+ await this.http.put(`${this.serverUrl}/${this.basePath}/version`, miniConf, headers)// TODO PDV Setup fara isso aqui
169
+ console.log('Compilador pronto para uso contínuo')
170
+
171
+ const setupData = setupResponse.data
172
+
173
+ const { state, target: remoteTarget } = setupData
174
+
175
+ this.printLibsVersion(state)
176
+
177
+ await this.loadTarget(remoteTarget)
178
+
179
+ return setupData
180
+ }
181
+
182
+ async loadTarget(remoteTarget) {
183
+ const targets = await this.availableTargets()
184
+ this.target = targets.find(t => t.name === remoteTarget.name)
185
+
186
+ if(!this.target) {
187
+ throw new Error('Workspace.loadTarget.internalError#missingTarget')
188
+ }
189
+ }
190
+
191
+ printLibsVersion(state) {
192
+ const miniConf = this.getMiniConf()
193
+ let libVersionsOutput = ''
194
+ state.libs.forEach(lib => {
195
+ let version = miniConf[lib.name]
196
+ if (version) {
197
+ // Captura a versao da lib quando estamos usando uma url
198
+ // "https://eitri.s3.amazonaws.com/eitri-app-client-0.8.0-alpha.tgz".match(/(\d+\.\d+\.\d+([-a-z]*))/)
199
+ const m = version.match(/(\d+\.\d+\.\d+([-a-z]*))/)
200
+ if (m) {
201
+ version = m[1]
202
+ }
203
+ libVersionsOutput += ` ${lib.name} [${version}]\n`
204
+ }
205
+ })
206
+ console.log(libVersionsOutput)
207
+ }
208
+
209
+ async checkVersions() {
210
+ try {
211
+ const miniConf = this.getMiniConf()
212
+ const headers = {
213
+ 'accept': this.config.libs.updateLibsEndpointVersion
214
+ }
215
+
216
+ // somente para garantir que a pasta do usuario existe
217
+ await this.http.post(`${this.serverUrl}/${this.basePath}/setup`, miniConf, headers)
218
+ console.log('Preparando ambiente de desenvolvimento')
219
+ const response = await this.http.put(`${this.serverUrl}/${this.basePath}/version?command=push-version`, miniConf, headers)
220
+ miniConf.target = response.target
221
+ const availableTargets = await this.availableTargets()
222
+ let libs = TargetService.getLibsFromMiniConf(miniConf, availableTargets)
223
+ console.log('Ambiente pronto para compilação')
224
+ console.log(`${libs.components} [${miniConf[libs.components]}]\n${libs.superApp} [${miniConf[libs.superApp]}]`)
225
+ return {target: response.target}
226
+ } catch (error) {
227
+ console.log(`\x1b[1m\x1b[31m${error.message}\x1b[0m`);
228
+ process.exit(1)
229
+ }
230
+ }
231
+
232
+ isDiff(miniConf, onlineVersion, packageName) {
233
+ return (
234
+ miniConf[packageName] &&
235
+ onlineVersion[packageName] !== miniConf[packageName]
236
+ )
237
+ }
238
+
239
+ async getWorkspaceConnectionData(args, serverUrl) {
240
+ const cookie = await this.http.getCookieSession(serverUrl)
241
+ const miniConf = this.getMiniConf()
242
+ const viewHtml = (args.view || 'index')
243
+ let environment = process.env.NODE_ENV || DEFAULT_ENV
244
+ if (environment === 'prod') environment = 'prd'
245
+ let miniAppData = {
246
+ developerEmail: this.userEmail,
247
+ environment: environment,
248
+ albCookie: cookie.cookieString(),
249
+ orderId: args.orderId,
250
+ shareId: args.shareId,
251
+ slug: miniConf.slug,
252
+ publicKey: miniConf['public-key'] || miniConf.publicKey,
253
+ view: viewHtml
254
+ }
255
+ // delete miniAppData.albCookie
256
+ return Base64.encode(JSON.stringify(miniAppData))
257
+ }
258
+
259
+ async getWorkspaceURL(args, targetConfig) {
260
+ args = args || {}
261
+ const cookie = await this.http.getCookieSession(this.serverUrl)
262
+ let paramOrderId = ''
263
+ let paramShareId = ''
264
+ let cid
265
+ const viewHtml = (args.view || 'index') + '.html'
266
+ const miniAppConf = this.getMiniConf()
267
+ const data = {
268
+ publicKey: miniAppConf['public-key'],
269
+ metadata: miniAppConf['metadata'],
270
+ slug: miniAppConf['slug'],
271
+ title: miniAppConf['title']
272
+ }
273
+ if (args.orderId) {
274
+ paramOrderId = '&orderId=' + encodeURIComponent(args.orderId)
275
+ }
276
+ if (args.shareId) {
277
+ paramShareId = '&shareId=' + encodeURIComponent(args.shareId)
278
+ }
279
+ if (args.initializationParams) {
280
+ data.initializationParams = args.initializationParams
281
+ }
282
+ const encodedData = Base64.encode(JSON.stringify(data))
283
+ if (cookie) {
284
+ cid = Base64.encode(cookie.cookieString())
285
+ }
286
+
287
+ if(!targetConfig.bootstrapBaseUrl) {
288
+ throw new Error('Workspace.getWorkspaceURL.internalError#missingBootstrapBaseUrl')
289
+ }
290
+
291
+ let miniAppUrl = `${this.getBootstrapURL(targetConfig.bootstrapBaseUrl)}/${this.config.basePath || 'workspace'}/user/${this.userEmail.replace(
292
+ '@',
293
+ '/',
294
+ )}/${viewHtml}?data=${encodedData}${paramOrderId}${paramShareId}&devmode=true`
295
+
296
+ if (cid) {
297
+ miniAppUrl = miniAppUrl + '&cid=' + cid
298
+ }
299
+ return miniAppUrl
300
+ }
301
+
302
+ /**
303
+ *
304
+ * @param {string} bootstrapBaseUrl
305
+ */
306
+ getBootstrapURL(bootstrapBaseUrl) {
307
+ const LOCALHOST = 'http://localhost:'
308
+ if (bootstrapBaseUrl.startsWith(LOCALHOST) && process.env.NODE_ENV === 'loc') {
309
+ const HOST = ipv4()[0]
310
+ const PORT = bootstrapBaseUrl.split(LOCALHOST)[1]
311
+ return `http://${HOST}:${PORT}`
312
+ }
313
+ return bootstrapBaseUrl
314
+ }
315
+
316
+ async getNewWorkspaceURL(args, targetConfig) {
317
+ const qrCodeConfig = await this.getQrCodeConfig(args, targetConfig)
318
+ const stateData = await this.saveShareState(qrCodeConfig)
319
+ const stateId = stateData.id
320
+ const miniAppUrl = `${this.qrCodeUrl}/${stateId}?environment=${(process.env.NODE_ENV === 'hmlv2' ? 'hml' : process.env.NODE_ENV) || 'hml'}`
321
+ return {
322
+ miniAppConf: {
323
+ ...qrCodeConfig,
324
+ shareId: stateId
325
+ },
326
+ miniAppUrl
327
+ }
328
+ }
329
+
330
+ async saveShareState(qrCodeConfig) {
331
+ const createWorkspaceStateUrl = `${this.serverUrl}/${this.basePath}/share`
332
+ const requestBody = {
333
+ id: uuidv4(),
334
+ state: qrCodeConfig
335
+ }
336
+ const response = await this.http.post(createWorkspaceStateUrl, requestBody)
337
+ return response.data
338
+ }
339
+
340
+ async getQrCodeConfig(args, targetConfig) {
341
+ args = args || {}
342
+ const cookie = await this.http.getCookieSession(this.serverUrl)
343
+ let cid
344
+ const miniAppConf = this.getMiniConf()
345
+ const view = (args.view || 'index') + '.html'
346
+ const initializationParams = args.initializationParams
347
+ const orderId = args.orderId
348
+
349
+ if (cookie) {
350
+ cid = Base64.encode(cookie.cookieString())
351
+ }
352
+
353
+ const config = {
354
+ developerEmail: this.userEmail,
355
+ publicKey: miniAppConf['public-key'],
356
+ metadata: miniAppConf['metadata'],
357
+ slug: miniAppConf['slug'],
358
+ title: miniAppConf['title'],
359
+ permissions: miniAppConf['permissions'],
360
+
361
+ // Se nao enviarmos essa url pronta, o superapp tera que monta-la
362
+ // Essa url chama a funcao users.js do workspace-api
363
+ miniAppBootstrapUrl: await this.getWorkspaceURL(args, targetConfig),
364
+ initializationParams,
365
+ orderId,
366
+ view,
367
+ extras: {
368
+ cid // superapp joga tudo do extras na query string do bootstrapUrl .... Pq ja nao mandamos na queryString ja daqui?
369
+ }
370
+ }
371
+
372
+ return config
373
+ }
374
+
375
+ getRelativePath(filePath) {
376
+ return filePath
377
+ .replace(this.folder2watch, '')
378
+ .replace(this.folder2watch.replace(/\\/g, '/'), '')
379
+ .replace(process.cwd(), '')
380
+ }
381
+
382
+ async delete(filePath) {
383
+ const relPath = this.getRelativePath(filePath)
384
+ const url = `${this.serverUrl}/${this.basePath}/fileupload`
385
+ const params = {
386
+ filepath: relPath,
387
+ }
388
+ await this.http.delete(url, { params })
389
+ }
390
+
391
+ hasAccent(fileName) {
392
+ const fileRegex = /[À-ź]/g
393
+ const isValidFileName = !fileRegex.test(fileName)
394
+ return !isValidFileName
395
+ }
396
+
397
+
398
+ async upsert(filesPath, opts = {}, showProgressBar = false) {
399
+ if(showProgressBar) {
400
+ return await this.upsertConcurrency(filesPath, opts, showProgressBar)
401
+ }
402
+ return await this.upsertSequencial(filesPath, opts)
403
+ }
404
+
405
+ async upsertSequencial(filePath, opts = {}) {
406
+ const fileName = path.basename(filePath)
407
+
408
+ const {isInvalid, promise} = await this.validateFileName(fileName, filePath)
409
+ if(isInvalid) return await promise
410
+
411
+ const holder = this.createPromiseHolder()
412
+ this.upsertQueue.push({
413
+ filePath,
414
+ opts,
415
+ holder,
416
+ requestNumber: this.requestNumber++,
417
+ })
418
+
419
+ const isSaving = opts.why === WatcherOpts.SAVE
420
+
421
+ if (!this.uploading) {
422
+ await this.execUpsertSequencial(isSaving)
423
+ }
424
+
425
+ return holder.promise
426
+ }
427
+
428
+ async upsertConcurrency(filesPath, opts = {}, showProgressBar = false) {
429
+ const promises = []
430
+ const isSaving = opts.why === WatcherOpts.SAVE
431
+ if(showProgressBar) {
432
+ this.createProgressBar(showProgressBar)
433
+ this.progressBar.start(filesPath.length, 0)
434
+ this.progressBar.update(this.progressBarCounter)
435
+ }
436
+
437
+ for(const filePath of filesPath) {
438
+ const fileName = path.basename(filePath)
439
+
440
+ const {isInvalid, promise} = await this.validateFileName(fileName, filePath)
441
+ if(isInvalid) return await promise
442
+
443
+ const upsertFile = {
444
+ filePath,
445
+ opts,
446
+ requestNumber: this.requestNumber++,
447
+ }
448
+ promises.push(this.execUpsertConcurrency(upsertFile, isSaving, showProgressBar))
449
+ }
450
+
451
+ const promisesChunks = this.spliceArray(promises, 5)
452
+ const data = []
453
+ for (const promiseArray of promisesChunks) {
454
+ const result = await Promise.all(promiseArray)
455
+ data.push(...result)
456
+ }
457
+
458
+ if(isSaving) return data[0]
459
+
460
+ if(showProgressBar) {
461
+ this.progressBar.update(filesPath.length)
462
+ this.progressBar.stop()
463
+ }
464
+ return data
465
+ }
466
+
467
+ async postFile(filePath, isSaving, opts) {
468
+ const start = new Date().getTime()
469
+ const relativePath = this.getRelativePath(filePath)
470
+ const fileName = path.basename(filePath)
471
+ if (relativePath.startsWith(this.config.colossus.watchUserDir)) {
472
+ const allFiles = await this.listFilesInCorrectOrder()
473
+ const commomModulesPaths = allFiles.filter(f => f.includes('src/commons'))
474
+ await this.postLambda(filePath, relativePath, isSaving, commomModulesPaths)
475
+ } else {
476
+ await this.postFormData(filePath, relativePath, opts)
477
+ }
478
+ return {
479
+ start,
480
+ relativePath,
481
+ fileName
482
+ }
483
+ }
484
+
485
+ async execUpsertConcurrency(upsertFile, isSaving, showProgressBar = false) {
486
+ const { filePath, opts, requestNumber } = upsertFile
487
+
488
+ const {fileName, relativePath, start} = await this.postFile(filePath, isSaving, opts)
489
+
490
+ if(showProgressBar) {
491
+ this.progressBarCounter++
492
+ this.progressBar.update(this.progressBarCounter)
493
+ }
494
+ return {
495
+ filePath,
496
+ relativePath,
497
+ requestNumber,
498
+ time: new Date().getTime() - start,
499
+ baseName: fileName
500
+ }
501
+
502
+ }
503
+
504
+ async execUpsertSequencial(isSaving) {
505
+ this.uploading = true
506
+ for (let i = 0; i < this.upsertQueue.length; i++) {
507
+ const { filePath, opts, holder, requestNumber } = this.upsertQueue[i]
508
+
509
+ try {
510
+ const {relativePath, start} = await this.postFile(filePath, isSaving, opts)
511
+
512
+ holder.resolve({
513
+ filePath,
514
+ relativePath,
515
+ requestNumber,
516
+ time: new Date().getTime() - start,
517
+ })
518
+ } catch (e) {
519
+ holder.reject(e)
520
+ break // na duvida se deve abortar a fila toda
521
+ }
522
+ }
523
+ this.upsertQueue = []
524
+ this.uploading = false
525
+ }
526
+
527
+ async validateFileName(fileName, filePath) {
528
+ const hasAccent = this.hasAccent(fileName)
529
+ let promise
530
+ if (hasAccent) {
531
+ const message = ` - No nome do arquivo possui um ou mais caracteres não permitidos: ${fileName}`
532
+ console.log(message)
533
+
534
+ promise = Promise.resolve({
535
+ filePath,
536
+ relativePath: this.getRelativePath(filePath),
537
+ time: 0,
538
+ requestNumber: this.requestNumber++,
539
+ message
540
+ })
541
+ }
542
+ return {
543
+ isInvalid: hasAccent,
544
+ promise
545
+ }
546
+ }
547
+
548
+ createPromiseHolder() {
549
+ const holder = {}
550
+ holder.promise = new Promise((resolve, reject) => {
551
+ holder.resolve = resolve
552
+ holder.reject = reject
553
+ })
554
+ return holder
555
+ }
556
+
557
+ async postFormData(filePath, relativePath, opts) {
558
+ let conf = this.getMiniConf()
559
+ const formData = new FormData()
560
+ formData.append('filepath', relativePath)
561
+ formData.append('why', opts.why || '')
562
+ formData.append('file', fs.createReadStream(filePath))
563
+ formData.append('miniAppVersion', conf.version)
564
+ if (opts.forceTranspile) {
565
+ formData.append('forceTranspile', opts.forceTranspile)
566
+ }
567
+ const url = `${this.serverUrl}/${this.basePath}/fileupload`
568
+ return await this.http.postForm(url, formData)
569
+ }
570
+
571
+ getEnvCode() {
572
+ if (!this.publishing) {
573
+ return 'ws'
574
+ }
575
+ if ([ 'dev', 'loc' ].includes(process.env.NODE_ENV)) {
576
+ return 'dev'
577
+ } else {
578
+ return 'hml'
579
+ }
580
+ }
581
+
582
+ async postLambda(filePath, relativePath, isSaving, commomModulesPaths = []) {
583
+
584
+ let commomModules = commomModulesPaths.map(commomFilePath => {
585
+ let code = fs.readFileSync(commomFilePath, 'utf8')
586
+
587
+ let name = path.basename(commomFilePath)
588
+
589
+ let compressionResult = this.compressCode(code)
590
+
591
+ return { name, ...compressionResult }
592
+ })
593
+
594
+ let code = fs.readFileSync(filePath, 'utf8')
595
+
596
+ let compressionResult = this.compressCode(code)
597
+
598
+ let conf = this.getMiniConf()
599
+ const envCode = this.getEnvCode()
600
+ const fileName = path.basename(relativePath)
601
+ let data = {
602
+ slug: conf.slug,
603
+ envCode: envCode,
604
+ miniAppVersion: conf.version,
605
+ name: fileName,
606
+ code: compressionResult.code,
607
+ compressionType: compressionResult.compressionType,
608
+ publishing: this.publishing,
609
+ isSaving: isSaving,
610
+ restrictedLibs: conf['restricted-libs'],
611
+ webhooks: this.getCandidatedWebhook(fileName, conf),
612
+ commomModules
613
+ }
614
+ let url = `${this.config.colossus.url}`
615
+ const {data: responseData} = await this.http.post(url, data)
616
+
617
+ const {hookData} = responseData
618
+ if(hookData) {
619
+ const _webhooks = [...this.webhooks, hookData]
620
+ this.setWebhooks(_webhooks)
621
+ }
622
+ return responseData
623
+ }
624
+
625
+ getCandidatedWebhook(fileName, conf) {
626
+ const webhooks = conf['webhooks']
627
+ if(!webhooks || webhooks.length === 0) return undefined
628
+ const fileIsCandidatedToWebhook = webhooks.some(hook => hook.fileName === fileName)
629
+ const hook = [{fileName}]
630
+ return fileIsCandidatedToWebhook ? hook : undefined
631
+ }
632
+
633
+ async isSynchronized() {
634
+ const res = await Promise.all([
635
+ this.hashFolder.calculate(),
636
+ this.http.get(`${this.serverUrl}/${this.basePath}/fileupload`),
637
+ ])
638
+ const folderHashValue = res[0]['hash']
639
+ const serverHash = res[1]['hash']
640
+ return folderHashValue === serverHash
641
+ }
642
+
643
+ async rmAllServerFiles() {
644
+ await this.delete('/')
645
+ }
646
+
647
+ async listFilesInCorrectOrder() {
648
+ const allFiles = await this.walk(this.folder2watch)
649
+ const componentsPath = path.join(this.folder2watch, 'components')
650
+ const componentTagRegExp = new RegExp(`${componentsPath}/[^/]+.jsx`)
651
+ const filesWithoutComponentTags = allFiles.filter(fileName => {
652
+ return !fileName.match(componentTagRegExp)
653
+ })
654
+ let files = filesWithoutComponentTags.sort((a, b) => {
655
+ if (a.includes('components') && !b.includes('components')) return -1
656
+ if (!a.includes('components') && b.includes('components')) return 1
657
+ if (a.includes('folder.hash')) return 1
658
+ if (b.includes('folder.hash')) return -1
659
+ return 0
660
+ })
661
+ const tagsFiles = await new TagTree().tagsFilesSorted(componentsPath)
662
+ files = tagsFiles.map(fileName => path.join(componentsPath, fileName)).concat(files)
663
+ return files
664
+ }
665
+
666
+ async getAllResourceFiles(resources) {
667
+ let allFiles = []
668
+ resources.forEach(async dir => {
669
+ const files = await this.walk(dir)
670
+ allFiles.push(...files)
671
+ })
672
+ return allFiles
673
+ }
674
+
675
+ async uploadAll() {
676
+ await this.mkdirs([this.folder2watch, ...this.resourceFolders2watch])
677
+ await this.rmAllServerFiles()
678
+ await this.hashFolder.updateHashFile()
679
+
680
+ const srcFiles = await this.listFilesInCorrectOrder()
681
+ const filesfromSrc = srcFiles.filter(f => !f.endsWith('folder.hash'))
682
+ const srcFolderHash = srcFiles.filter(f => f.endsWith('folder.hash'))
683
+
684
+ const resourceFiles = await this.getAllResourceFiles(this.resourceFolders2watch)
685
+
686
+ let files = resourceFiles.concat(filesfromSrc)
687
+ const componentFiles = files.filter(file => file.includes('/components'))
688
+
689
+ const opts = {
690
+ why: 'save',
691
+ log: false,
692
+ skipCompile: true,
693
+ }
694
+ const showProgressBar = true
695
+ if(componentFiles && componentFiles.length > 0) {
696
+ await this.preUpsert(componentFiles, opts)
697
+ }
698
+ await this.upsert(files, opts, showProgressBar)
699
+ await this.transpile(srcFolderHash)
700
+ }
701
+
702
+ async preUpsert(componentsFiles, opts) {
703
+ for (const filePath of componentsFiles) {
704
+ await this.upsertSequencial(filePath, opts)
705
+ }
706
+ }
707
+
708
+ async uploadZip() {
709
+ const admZip = new AdmZip()
710
+ await this.mkdirs([this.folder2watch, ...this.resourceFolders2watch])
711
+ await this.rmAllServerFiles()
712
+ await this.hashFolder.updateHashFile()
713
+ const allFiles = await this.listFilesInCorrectOrder()
714
+ const files = allFiles.filter(f => !f.endsWith('folder.hash'))
715
+ const folderHash = allFiles.filter(f => f.endsWith('folder.hash'))
716
+ const commomModulesPaths = allFiles.filter(f => f.includes('src/commons'))
717
+
718
+ for (let i = 0; i < files.length; i++) {
719
+ const filePath = files[i]
720
+ const filename = path.basename(filePath)
721
+
722
+ if (this.hasAccent(filename)) throw new Error(`Arquivo com um ou mais caracteres inválidos ${filename}`)
723
+
724
+ const relativePath = this.getRelativePath(filePath)
725
+ if (relativePath.startsWith(this.config.colossus.watchUserDir)) {
726
+ await this.postLambda(filePath, relativePath, false, commomModulesPaths)
727
+ }
728
+ }
729
+
730
+ admZip.addFile('__files_order.txt', allFiles.map(f => f.split(this.folder2watch)[1].replace(/\\/g, '/')).join('\n'))
731
+ admZip.addLocalFolder('./src')
732
+
733
+ console.log('Construindo')
734
+
735
+ const platform = process.platform
736
+
737
+ const zipPath = await this.resolveOs(platform)
738
+ admZip.writeZip(zipPath)
739
+ await this.sendZip(zipPath)
740
+
741
+ await this.transpile(folderHash)
742
+
743
+ console.log('Pronto')
744
+ }
745
+
746
+ async resolveOs(platform) {
747
+ let tempDir = ''
748
+ if (tempFolderPath[platform]) {
749
+ tempDir = await tempFolderPath[platform]()
750
+ }
751
+ else {
752
+ tempDir = '.'
753
+ }
754
+ return tempDir + '/miniApp.zip'
755
+ }
756
+
757
+ async sendFiles(publishMessage) {
758
+ const miniConf = this.getMiniConf()
759
+ const formData = new FormData()
760
+
761
+ formData.append('miniAppName', miniConf.name || 'sem nome')
762
+ formData.append('miniAppSlug', miniConf.slug || 'sem-slug')
763
+ formData.append('miniAppTitle', miniConf.title || 'Sem Titulo')
764
+ formData.append('miniAppVersion', miniConf.miniAppVersion || miniConf.version || 'v1')
765
+ formData.append('organizationName', miniConf.organization.name)
766
+ formData.append('publishMessage', publishMessage || '')
767
+
768
+ const url = `${this.serverUrl}/${this.basePath}/publish?async=true`
769
+ await this.http.postForm(url, formData)
770
+ }
771
+
772
+ async sendZip(zip) {
773
+ const formData = new FormData()
774
+ formData.append('file', fs.createReadStream(zip))
775
+ const url = `${this.serverUrl}/${this.basePath}/uploadAll`
776
+ await this.http.postForm(url, formData)
777
+ }
778
+
779
+ async transpile(files) {
780
+ if (files.length > 0) {
781
+ return await this.upsert(...files, {
782
+ why: 'uploadAll',
783
+ log: false,
784
+ })
785
+ }
786
+ }
787
+
788
+ mkdirs(dirnames) {
789
+ return new Promise((resolve, reject) => {
790
+ dirnames.forEach(dirname => {
791
+ fs.mkdir(dirname, { recursive: true }, (err) => {
792
+ if (err) {
793
+ return reject(err)
794
+ }
795
+ resolve()
796
+ })
797
+ })
798
+ })
799
+ }
800
+
801
+ async getFileNames(dir) {
802
+ const folder = path.resolve(this.folder2watch, dir)
803
+ const fileList = await this.walk(folder)
804
+
805
+ const fileNames = fileList.map(filePath => {
806
+ return path.basename(filePath)
807
+ })
808
+
809
+ return fileNames
810
+ }
811
+
812
+ async walk(dir) {
813
+ let results = []
814
+ const list = fs.readdirSync(dir)
815
+ for (let i = 0; i < list.length; i++) {
816
+ let file = list[i]
817
+ file = path.join(dir, file)
818
+ const stat = fs.statSync(file)
819
+ if (stat && stat.isDirectory()) {
820
+ /* Recurse into a subdirectory */
821
+ results = results.concat(await this.walk(file))
822
+ } else {
823
+ /* Is a file */
824
+ results.push(file)
825
+ }
826
+ }
827
+ return results
828
+ }
829
+
830
+ //TODO: ESSE MÉTODO DEVE SER APAGADO, QUANDO NÃO EXISTIR MAIS O COMANDO PUBLISH
831
+ async publish(publishMessage) {
832
+ const miniConf = this.getMiniConf()
833
+ const formData = new FormData()
834
+
835
+ formData.append('miniAppName', miniConf.name || 'sem nome')
836
+ formData.append('miniAppSlug', miniConf.slug || 'sem-slug')
837
+ formData.append('miniAppTitle', miniConf.title || 'Sem Titulo')
838
+ formData.append('miniAppVersion', miniConf.miniAppVersion || miniConf.version || '0.1.0')
839
+ formData.append('organizationName', miniConf.organization.name)
840
+ formData.append('publishMessage', publishMessage || '')
841
+
842
+ const url = `${this.serverUrl}/${this.basePath}/publish?async=true`
843
+ await this.http.postForm(url, formData)
844
+ }
845
+
846
+ async build(buildMessage) {
847
+ const miniConf = this.getMiniConf()
848
+ const formData = new FormData()
849
+
850
+ formData.append('miniAppName', miniConf.name || 'sem nome')
851
+ formData.append('miniAppSlug', miniConf.slug || 'sem-slug')
852
+ formData.append('miniAppTitle', miniConf.title || 'Sem Titulo')
853
+ formData.append('miniAppVersion', miniConf.miniAppVersion || miniConf.version || 'v1')
854
+ formData.append('organizationName', miniConf.organization.name)
855
+ formData.append('publishMessage', buildMessage || '')
856
+
857
+ const url = `${this.serverUrl}/${this.basePath}/publish?async=true`
858
+ await this.http.postForm(url, formData)
859
+ }
860
+
861
+ /**
862
+ * @deprecated Prefira {@link Workspace#pushVersionAsJson}
863
+ */
864
+ async pushVersionAsForm(buildMessage) {
865
+ const miniConf = this.getMiniConf()
866
+ const formData = new FormData()
867
+
868
+ let attrValue
869
+
870
+ formData.append('miniAppName', miniConf.name || 'sem nome')
871
+ formData.append('miniAppSlug', miniConf.slug || 'sem-slug')
872
+ formData.append('miniAppTitle', miniConf.title || 'Sem Titulo')
873
+ formData.append('miniAppVersion', miniConf.miniAppVersion || miniConf.version || 'v1')
874
+ formData.append('organizationName', miniConf.organization.name)
875
+ formData.append('miniAppSubSetName', miniConf.name)
876
+ formData.append('miniAppSubSetTitle', miniConf.title)
877
+ if ((attrValue = miniConf['eitri-app-components'])) formData.append('cliComponentsVersion', attrValue)
878
+ if ((attrValue = miniConf['eitri-app-client'])) formData.append('cliSuperAppClientVersion', attrValue)
879
+ if ((attrValue = packageJson.version)) formData.append('cliVersion', attrValue)
880
+ if ((attrValue = buildMessage)) formData.append('publishMessage', attrValue)
881
+ if ((attrValue = miniConf.privacy)) formData.append('privacy', JSON.stringify(attrValue))
882
+
883
+ const url = `${this.serverUrl}/${this.basePath}/publish?async=true`
884
+ await this.http.postForm(url, formData)
885
+ }
886
+
887
+ async pushVersionAsJson(buildMessage) {
888
+ let headers = {}
889
+ if(this.config.apiVersion && this.config.apiVersion.v2Header) {
890
+ headers = {
891
+ 'accept': this.config.apiVersion.v2Header
892
+ }
893
+ }
894
+ const miniConf = this.getMiniConf()
895
+ const body = { miniConf, buildMessage, cliVersion: packageJson.version }
896
+ const url = `${this.serverUrl}/${this.basePath}/publish?async=true`
897
+ await this.http.post(url, body, headers)
898
+ }
899
+
900
+ async create(conf) {
901
+ const url = `${this.serverUrl}/${this.basePath}/create`
902
+ return await this.http.post(url, conf)
903
+ .then(res => res.data)
904
+ .catch(err => {
905
+ if (err.isAxiosError && err.response && err.response.status === 409 && err.response.data && err.response.data.error) {
906
+ console.log('Error: ' + err.response.data.error)
907
+ let dupError = new Error(err.response.data.error)
908
+ dupError.isDuplicatedError = true
909
+ dupError.field = err.response.data.field
910
+ throw dupError
911
+ }
912
+ console.log(err.message)
913
+ if (err && err.response) {
914
+ console.log(err.response.data)
915
+ }
916
+ throw err
917
+ })
918
+ }
919
+
920
+ async applyRolloutEnvironmentIfNeeded() {
921
+ const start = Date.now()
922
+ try {
923
+ const { slug } = this.getMiniConf()
924
+ const url = `${this.serverUrl}/${this.basePath}/users/miniapps/${slug}/actions/WORKSPACE_ROLLOUT`
925
+ const response = await this.http.get(url)
926
+ if (response.result) {
927
+ process.env['NODE_ENV'] = 'beta'
928
+ this.rollout = response.result
929
+
930
+ console.log('\n')
931
+ console.log('\t╔═══════════════════════════════════════╗')
932
+ console.log('\t║ Atenção: Utilizando Beta Workspace ║')
933
+ console.log('\t╚═══════════════════════════════════════╝\n')
934
+ }
935
+ } catch (e) {
936
+ if (process.env.LOG_LEVEL === 'full') {
937
+ console.log(e)
938
+ }
939
+ }
940
+
941
+ if (process.env.LOG_LEVEL === 'full') {
942
+ console.log('Check rollout time', Date.now() - start)
943
+ }
944
+ }
945
+
946
+ isRollout() {
947
+ return this.rollout
948
+ }
949
+
950
+ // bff
951
+ async availableTargets() {
952
+ if(this.getTargets().length === 0) {
953
+ const url = `${this.serverUrl}/${this.basePath}/targets`
954
+ const options = {
955
+ headers: {
956
+ 'Accept': 'application/vnd.workspace.api.v2+json'
957
+ }
958
+ }
959
+ const targets = await this.http.get(url, options)
960
+ this.setTargets(targets)
961
+ return targets
962
+ }
963
+ return this.getTargets()
964
+ }
965
+
966
+ // local
967
+ getKnownAvailableTargets() {
968
+ return this.config.target.knownTargets
969
+ }
970
+
971
+ async getDefaultTarget() {
972
+ await this.availableTargets()
973
+ return this.getTargets().find(value => value.default)
974
+ }
975
+
976
+ // client http configurado conforme as necessidades do workspace
977
+ getHttp() {
978
+ return this.http
979
+ }
980
+
981
+ async comparePermissions(target) {
982
+ const miniConf = this.getMiniConf()
983
+ const permissions = miniConf.permissions || []
984
+ const permissionsWithTarget = permissions.map(permission => ({...permission, target}))
985
+ const slug = miniConf.slug
986
+ const {data} = await this.http.post(`${this.serverUrl}/${this.basePath}/miniapps/${slug}/permissions/compare`, permissionsWithTarget)
987
+ const addedPermissions = []
988
+ const removedPermissions = []
989
+ if(data.diffCount > 0) {
990
+ data.result.forEach(result => {
991
+ if(result.status === 'added') {
992
+ addedPermissions.push(result.entity.permission)
993
+ } else {
994
+ removedPermissions.push(result.entity.permission)
995
+ }
996
+ })
997
+ }
998
+ return {addedPermissions, removedPermissions}
999
+ }
1000
+
1001
+ async clean() {
1002
+ await this.getHttp().delete(`${this.serverUrl}/${this.basePath}/sources`, {})
1003
+ }
1004
+
1005
+ compressCode(code) {
1006
+ const {SERVERLESS_CODE_COMPRESSION: compressionType, LOG_LEVEL} = process.env
1007
+
1008
+ let compressedCode, codeLength = code.length
1009
+ let maxPreviewLenth = 50
1010
+ let previewLength = codeLength > maxPreviewLenth ? maxPreviewLenth : codeLength
1011
+
1012
+ if(compressionType === 'base64') {
1013
+ compressedCode = this.toBase64(code)
1014
+
1015
+ if(LOG_LEVEL === 'full') {
1016
+ console.log(`Enviando arquivo sem compactacao em base64: ${code.substring(0, previewLength)}, codeLenth: ${codeLength}`)
1017
+ }
1018
+ } else if(compressionType === 'none'){
1019
+ if(LOG_LEVEL === 'full') {
1020
+ console.log(`Enviando arquivo sem qualquer compressao: ${code.substring(0, previewLength)}, codeLenth: ${codeLength}`)
1021
+ }
1022
+ } else if (!compressionType || compressionType === 'shrink') {
1023
+ compressedCode = compressToBase64(code)
1024
+
1025
+ if(LOG_LEVEL === 'full') {
1026
+ console.log(`File preview: ${code.substring(0, previewLength)}, Before compression: ${codeLength}, After compression: ${compressedCode.length}`)
1027
+ }
1028
+ }
1029
+
1030
+
1031
+ return { code: compressedCode || code, compressionType: compressionType || 'shrink' }
1032
+ }
1033
+
1034
+ toBase64(content) {
1035
+ return Buffer.from(content).toString('base64')
1036
+ }
1037
+
1038
+ createProgressBar(showProgressBar) {
1039
+ if(showProgressBar) {
1040
+ this.progressBar = new cliProgress.SingleBar(
1041
+ {
1042
+ format:
1043
+ 'Sincronizando [{bar}] {percentage}% | ETA: {eta}s | {value}/{total} Arquivos sincronizados...',
1044
+ },
1045
+ cliProgress.Presets.shades_classic,
1046
+ )
1047
+ }
1048
+ }
1049
+
1050
+ /**
1051
+ *
1052
+ * @param {unknown[]} array
1053
+ * @param {number} chunkCount
1054
+ * @returns {unknown[][]}
1055
+ */
1056
+ spliceArray(array, chunkCount){
1057
+ var chunks = [], iterator, auxiliar
1058
+ for (iterator = 0, auxiliar = array.length; iterator < auxiliar; iterator += chunkCount) {
1059
+ chunks.push(array.slice(iterator, iterator + chunkCount))
1060
+ }
1061
+ return chunks
1062
+ }
1063
+
1064
+ }
1065
+
1066
+ module.exports = {
1067
+ workspace: new Workspace(new BlindGuardian(), new HashFolder()),
1068
+ Workspace
1069
+ }