UNPKG

4.22 kBJavaScriptView Raw
1'use strict'
2
3const fs = require('fs')
4const fetch = require('node-fetch')
5const FormData = require('form-data')
6const xmlToJson = require('xml-to-json-stream')
7const Package = require('./package')
8const log = require('./log')
9const defaults = require('./defaults')
10
11class Pipeline {
12 constructor (opts = {}) {
13 this.lock = false
14 this.queue = []
15 this.checkBeforePush = opts.checkBeforePush || defaults.checkBeforePush
16 this.packmgrPath = opts.packmgrPath || defaults.packmgrPath
17 this.targets = opts.targets || defaults.target
18 this.interval = opts.interval || defaults.interval
19 this.exclude = opts.exclude || defaults.exclude
20 this.onPushEnd = opts.onPushEnd || function () {}
21 }
22
23 start () {
24 setInterval(async () => {
25 await this._processQueue()
26 }, this.interval)
27 }
28
29 enqueue (localPath) {
30 log.debug(`Changed: ${localPath}`)
31 this.queue.push(localPath)
32 }
33
34 async push (pathToPush) {
35 this.enqueue(pathToPush)
36 return this._processQueue()
37 }
38
39 async _processQueue () {
40 // Wait for the previous package to install.
41 // Otherwise an error may occur if two concurrent packages try to make
42 // changes to the same node.
43 if (this.lock === true || this.queue.length < 1) {
44 return null
45 }
46
47 // Lock the queue.
48 this.lock = true
49
50 // Create package.
51 const pack = new Package(this.exclude)
52 while (this.queue.length > 0) {
53 const localPath = this.queue.pop()
54 const item = pack.add(localPath)
55 item && log.info(item.exists ? '+' : '-', item.zipPath)
56 }
57
58 // Push package to targets (if any entries detected).
59 log.group()
60 const archivePath = pack.save()
61 if (archivePath) {
62 for (const target of this.targets) {
63 const result = await this._post(archivePath, target)
64 this.onPushEnd(result.err, result.target, result.log)
65 log.info(log.gray(target + ' >'), log.gray(result.err ? result.err.message : 'OK'))
66 }
67 }
68 log.groupEnd()
69
70 // Release lock.
71 this.lock = false
72
73 return pack
74 }
75
76 async _post (archivePath, target) {
77 const url = target + this.packmgrPath
78 const form = new FormData()
79 form.append('file', fs.createReadStream(archivePath))
80 form.append('force', 'true')
81 form.append('install', 'true')
82
83 // Check if AEM is up and runnig.
84 if (this.checkBeforePush && !await this._check(target)) {
85 return { target, err: new Error('AEM not ready') }
86 }
87
88 const result = { target }
89 try {
90 const res = await fetch(url, { method: 'POST', body: form })
91
92 if (res.ok) {
93 const text = await res.text()
94 log.debug('Response text:')
95 log.group()
96 log.debug(text)
97 log.groupEnd()
98
99 // Handle errors with AEM response.
100 try {
101 const obj = await this._parseXml(text)
102 result.log = obj.crx.response.data.log
103 const errorLines = [...new Set(result.log.split('\n').filter(line => line.startsWith('E')))]
104
105 // Errors when installing selected nodes.
106 if (errorLines.length) {
107 result.err = new Error('Error installing nodes:\n' + errorLines.join('\n'))
108 // Error code in status.
109 } else if (obj.crx.response.status.code !== '200') {
110 result.err = new Error(obj.crx.response.status.textNode)
111 }
112 } catch (err) {
113 // Unexpected response format.
114 throw new Error('Unexpected response text format')
115 }
116 } else {
117 // Handle errors with the failed request.
118 result.err = new Error(res.statusText)
119 }
120 } catch (err) {
121 // Handle unexpeted errors.
122 result.err = err
123 }
124
125 return result
126 }
127
128 async _check (target) {
129 try {
130 const res = await fetch(target)
131 return res.status === 200
132 } catch (err) {
133 log.debug(err.message)
134 return false
135 }
136 }
137
138 _parseXml (xml) {
139 return new Promise(resolve => {
140 xmlToJson().xmlToJson(xml, (err, json) => err ? resolve({}) : resolve(json))
141 })
142 }
143}
144
145module.exports = Pipeline