@makefully/dutifully 1.10.1 → 1.11.0
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.
- package/CHANGELOG.md +9 -5
- package/LICENSE +21 -21
- package/README.md +216 -214
- package/cheer.js +36 -36
- package/package.json +63 -63
- package/say.js +15 -15
- package/src/classes/Cheer.js +254 -254
- package/src/classes/ElevenLabs.js +16 -16
- package/src/classes/FFMPEG.js +12 -12
- package/src/classes/LipSync.js +132 -132
- package/src/classes/Polly.js +9 -9
- package/src/classes/Rasterize.js +12 -12
- package/src/classes/TextToSpeech.js +204 -204
- package/src/classes/Transcript.js +72 -68
- package/src/classes/Transcription.js +162 -162
- package/src/formats/File.js +26 -26
- package/src/formats/JSONFile.js +39 -39
- package/src/formats/LRC.js +37 -37
- package/src/formats/MP3.js +59 -59
- package/src/formats/SAMI.js +58 -58
- package/src/formats/SRT.js +39 -39
- package/src/formats/VTT.js +37 -37
- package/src/helpers/getCaptions.js +43 -43
- package/src/helpers/getFileData.js +32 -32
- package/src/helpers/getJSON.js +32 -32
- package/src/index.js +58 -58
- package/src/say.js +141 -141
package/package.json
CHANGED
|
@@ -1,63 +1,63 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@makefully/dutifully",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Client tools for Cheerfully service.",
|
|
5
|
-
"main": "./src/index.js",
|
|
6
|
-
"bin": {
|
|
7
|
-
"cheer": "./cheer.js",
|
|
8
|
-
"say": "./say.js"
|
|
9
|
-
},
|
|
10
|
-
"files": [
|
|
11
|
-
"cheer.js",
|
|
12
|
-
"say.js",
|
|
13
|
-
"src",
|
|
14
|
-
"CHANGELOG.md",
|
|
15
|
-
"LICENSE",
|
|
16
|
-
"README.md"
|
|
17
|
-
],
|
|
18
|
-
"scripts": {
|
|
19
|
-
"test-allosaurus": "node ./cheer.js -service allosaurus",
|
|
20
|
-
"test-elevenlabs": "node ./cheer.js -service elevenlabs",
|
|
21
|
-
"test-ffmpeg": "node ./cheer.js -service ffmpeg",
|
|
22
|
-
"test-polly": "node ./cheer.js -service polly",
|
|
23
|
-
"test-rasterize": "node ./cheer.js -service rasterize",
|
|
24
|
-
"test-rhubarb": "node ./cheer.js -service rhubarb",
|
|
25
|
-
"test-transcription": "node ./cheer.js -service transcription",
|
|
26
|
-
"say": "node ./say.js"
|
|
27
|
-
},
|
|
28
|
-
"repository": {
|
|
29
|
-
"type": "git",
|
|
30
|
-
"url": "git+https://github.com/Makefully-Studios/dutifully.git"
|
|
31
|
-
},
|
|
32
|
-
"bugs": {
|
|
33
|
-
"url": "https://github.com/Makefully-Studios/dutifully/issues"
|
|
34
|
-
},
|
|
35
|
-
"homepage": "https://github.com/Makefully-Studios/dutifully#readme",
|
|
36
|
-
"keywords": [
|
|
37
|
-
"cheerfully",
|
|
38
|
-
"dutifully",
|
|
39
|
-
"tts",
|
|
40
|
-
"elevenlabs",
|
|
41
|
-
"polly",
|
|
42
|
-
"lipsync",
|
|
43
|
-
"transcription",
|
|
44
|
-
"captions"
|
|
45
|
-
],
|
|
46
|
-
"author": "Derek Detweiler <derek@makefullystudios.com>",
|
|
47
|
-
"contributors": [
|
|
48
|
-
"Derek Detweiler <derek@makefullystudios.com>"
|
|
49
|
-
],
|
|
50
|
-
"license": "MIT",
|
|
51
|
-
"publishConfig": {
|
|
52
|
-
"access": "public"
|
|
53
|
-
},
|
|
54
|
-
"engines": {
|
|
55
|
-
"node": ">=18"
|
|
56
|
-
},
|
|
57
|
-
"dependencies": {
|
|
58
|
-
"archiver": "^4.0.1",
|
|
59
|
-
"node-id3": "^0.2.6",
|
|
60
|
-
"node-web-audio-api": "^2.0.0",
|
|
61
|
-
"unzip-stream": "^0.3.0"
|
|
62
|
-
}
|
|
63
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@makefully/dutifully",
|
|
3
|
+
"version": "1.11.0",
|
|
4
|
+
"description": "Client tools for Cheerfully service.",
|
|
5
|
+
"main": "./src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"cheer": "./cheer.js",
|
|
8
|
+
"say": "./say.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"cheer.js",
|
|
12
|
+
"say.js",
|
|
13
|
+
"src",
|
|
14
|
+
"CHANGELOG.md",
|
|
15
|
+
"LICENSE",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"test-allosaurus": "node ./cheer.js -service allosaurus",
|
|
20
|
+
"test-elevenlabs": "node ./cheer.js -service elevenlabs",
|
|
21
|
+
"test-ffmpeg": "node ./cheer.js -service ffmpeg",
|
|
22
|
+
"test-polly": "node ./cheer.js -service polly",
|
|
23
|
+
"test-rasterize": "node ./cheer.js -service rasterize",
|
|
24
|
+
"test-rhubarb": "node ./cheer.js -service rhubarb",
|
|
25
|
+
"test-transcription": "node ./cheer.js -service transcription",
|
|
26
|
+
"say": "node ./say.js"
|
|
27
|
+
},
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/Makefully-Studios/dutifully.git"
|
|
31
|
+
},
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/Makefully-Studios/dutifully/issues"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/Makefully-Studios/dutifully#readme",
|
|
36
|
+
"keywords": [
|
|
37
|
+
"cheerfully",
|
|
38
|
+
"dutifully",
|
|
39
|
+
"tts",
|
|
40
|
+
"elevenlabs",
|
|
41
|
+
"polly",
|
|
42
|
+
"lipsync",
|
|
43
|
+
"transcription",
|
|
44
|
+
"captions"
|
|
45
|
+
],
|
|
46
|
+
"author": "Derek Detweiler <derek@makefullystudios.com>",
|
|
47
|
+
"contributors": [
|
|
48
|
+
"Derek Detweiler <derek@makefullystudios.com>"
|
|
49
|
+
],
|
|
50
|
+
"license": "MIT",
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public"
|
|
53
|
+
},
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=18"
|
|
56
|
+
},
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"archiver": "^4.0.1",
|
|
59
|
+
"node-id3": "^0.2.6",
|
|
60
|
+
"node-web-audio-api": "^2.0.0",
|
|
61
|
+
"unzip-stream": "^0.3.0"
|
|
62
|
+
}
|
|
63
|
+
}
|
package/say.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
say = require('./src/say'),
|
|
5
|
-
text = process.argv.slice(2).join(' ').trim();
|
|
6
|
-
|
|
7
|
-
if (!text) {
|
|
8
|
-
console.error('Usage: say "Hello world"');
|
|
9
|
-
process.exit(1);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
say(text).catch((err) => {
|
|
13
|
-
console.error(err.message ?? err);
|
|
14
|
-
process.exit(1);
|
|
15
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const
|
|
4
|
+
say = require('./src/say'),
|
|
5
|
+
text = process.argv.slice(2).join(' ').trim();
|
|
6
|
+
|
|
7
|
+
if (!text) {
|
|
8
|
+
console.error('Usage: say "Hello world"');
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
say(text).catch((err) => {
|
|
13
|
+
console.error(err.message ?? err);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
});
|
package/src/classes/Cheer.js
CHANGED
|
@@ -1,255 +1,255 @@
|
|
|
1
|
-
const
|
|
2
|
-
archiver = require('archiver'),
|
|
3
|
-
fs = require('fs'),
|
|
4
|
-
getJSON = require('../helpers/getJSON'),
|
|
5
|
-
http = require('http'),
|
|
6
|
-
https = require('https'),
|
|
7
|
-
unzipper = require('unzip-stream'),
|
|
8
|
-
cleanPath = (path) => path[path.length - 1] === '/' ? path.substring(0, path.length - 1) : path,
|
|
9
|
-
postStream = (url, stream) => new Promise ((resolve, reject) => {
|
|
10
|
-
const
|
|
11
|
-
protocol = url.startsWith('https') ? https : http;
|
|
12
|
-
req = protocol.request(url, {
|
|
13
|
-
method: 'post',
|
|
14
|
-
headers: {
|
|
15
|
-
'Content-Type': 'application/zip'
|
|
16
|
-
}
|
|
17
|
-
}, async (res) => {
|
|
18
|
-
let data = '';
|
|
19
|
-
res.on('data', (chunk) => {
|
|
20
|
-
data += chunk;
|
|
21
|
-
});
|
|
22
|
-
res.on('close', () => {
|
|
23
|
-
let json = null;
|
|
24
|
-
|
|
25
|
-
try {
|
|
26
|
-
json = JSON.parse(data);
|
|
27
|
-
} catch (e) {
|
|
28
|
-
const
|
|
29
|
-
pre = [data.indexOf('<pre>'), data.indexOf('</pre>')],
|
|
30
|
-
htmlError = pre[0] > -1 ? data.substring(pre[0] + 5, pre[1]) : data;
|
|
31
|
-
|
|
32
|
-
json = {
|
|
33
|
-
errors: [htmlError]
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
const
|
|
37
|
-
{choreId, errors} = json;
|
|
38
|
-
|
|
39
|
-
if (errors) {
|
|
40
|
-
console.warn(errors.length === 1 ? `Error: ${errors[0]}` : `${errors.length} Errors`, errors);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (choreId) {
|
|
44
|
-
const
|
|
45
|
-
shareStatus = (status) => {
|
|
46
|
-
if (status !== lastStatus) {
|
|
47
|
-
console.log(status);
|
|
48
|
-
lastStatus = status;
|
|
49
|
-
}
|
|
50
|
-
},
|
|
51
|
-
checkStatus = async () => {
|
|
52
|
-
let response = null;
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
response = await fetchStream(`${url}/${choreId}`);
|
|
56
|
-
} catch (e) {
|
|
57
|
-
console.warn(e);
|
|
58
|
-
|
|
59
|
-
setTimeout(checkStatus, 10000);
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (response.json) {
|
|
64
|
-
if (response.json.errors) {
|
|
65
|
-
console.warn('Error', response.json.errors);
|
|
66
|
-
} else if (response.json.status) {
|
|
67
|
-
shareStatus(response.json.status);
|
|
68
|
-
setTimeout(checkStatus, 10000);
|
|
69
|
-
} else {
|
|
70
|
-
shareStatus(response.json);
|
|
71
|
-
setTimeout(checkStatus, 10000);
|
|
72
|
-
}
|
|
73
|
-
} else {
|
|
74
|
-
resolve(response.stream);
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
let lastStatus = '';
|
|
78
|
-
|
|
79
|
-
checkStatus();
|
|
80
|
-
} else {
|
|
81
|
-
reject(errors[0] ?? 'A valid chore id was not returned.');
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
req.on('error', reject);
|
|
87
|
-
|
|
88
|
-
stream.pipe(req);
|
|
89
|
-
}),
|
|
90
|
-
fetchStream = (url) => new Promise((resolve, reject) => {
|
|
91
|
-
const
|
|
92
|
-
protocol = url.startsWith('https') ? https : http;
|
|
93
|
-
|
|
94
|
-
protocol.get(url, async (res) => {
|
|
95
|
-
if (res.headers['content-type']?.indexOf('application/json') >= 0) {
|
|
96
|
-
let data = '';
|
|
97
|
-
res.on('data', (chunk) => {
|
|
98
|
-
data += chunk;
|
|
99
|
-
});
|
|
100
|
-
res.on('close', () => {
|
|
101
|
-
resolve({
|
|
102
|
-
json: JSON.parse(data)
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
} else {
|
|
106
|
-
resolve({
|
|
107
|
-
stream: res
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
}).on('error', (err) => reject(err));
|
|
111
|
-
}),
|
|
112
|
-
archive = async function (parser) {
|
|
113
|
-
const
|
|
114
|
-
archive = archiver('zip', {
|
|
115
|
-
zlib: {
|
|
116
|
-
level: 0
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
// good practice to catch warnings (ie stat failures and other non-blocking errors)
|
|
121
|
-
archive.on('warning', function (err) {
|
|
122
|
-
if (err.code === 'ENOENT') {
|
|
123
|
-
console.log(err);
|
|
124
|
-
} else {
|
|
125
|
-
throw err;
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
archive.on('error', function (err) {
|
|
129
|
-
throw err;
|
|
130
|
-
});
|
|
131
|
-
// 'close' event is fired only when a file descriptor is involved
|
|
132
|
-
archive.on('close', function () {
|
|
133
|
-
console.log('Zipped ' + archive.pointer() + ' total bytes');
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
await parser(archive);
|
|
137
|
-
|
|
138
|
-
archive.finalize();
|
|
139
|
-
|
|
140
|
-
return archive;
|
|
141
|
-
},
|
|
142
|
-
Cheer = class {
|
|
143
|
-
constructor ({config, contents}) {
|
|
144
|
-
this.config = config;
|
|
145
|
-
this.contents = contents;
|
|
146
|
-
this.service = contents.service;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
prepare ({difference = true, extract = true}) {
|
|
150
|
-
if (difference) {
|
|
151
|
-
if (!extract) {
|
|
152
|
-
console.warn('Warning: Unable to run difference if not extracted: running all.');
|
|
153
|
-
} else {
|
|
154
|
-
return this.checkDifference();
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
async replaceConfigPathWithJSON (pathKey, JSONKey) {
|
|
160
|
-
const
|
|
161
|
-
{config} = this,
|
|
162
|
-
path = config[pathKey];
|
|
163
|
-
|
|
164
|
-
if (path) {
|
|
165
|
-
delete config[pathKey];
|
|
166
|
-
config[JSONKey] = {
|
|
167
|
-
...config[JSONKey] ?? {}, // so if it already exists, we combine.
|
|
168
|
-
...await getJSON(path) ?? {}
|
|
169
|
-
};
|
|
170
|
-
return true;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return false;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
async checkDifference () {
|
|
177
|
-
console.warn('A difference check is not implemented for this service: running all.');
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
async send ({instanceId}) {
|
|
181
|
-
const
|
|
182
|
-
{config, contents, service} = this,
|
|
183
|
-
{accessToken = '', extract = true, output = './output/', server} = {...contents, ...config},
|
|
184
|
-
mkdir = await fs.promises.mkdir(output, { recursive: true }),
|
|
185
|
-
dst = extract ? unzipper.Extract({
|
|
186
|
-
path: output,
|
|
187
|
-
concurrency: 1
|
|
188
|
-
}) : fs.createWriteStream(`${output}${instanceId}.zip`);
|
|
189
|
-
|
|
190
|
-
return new Promise(async (resolve, reject) => {
|
|
191
|
-
const
|
|
192
|
-
finish = async (handler) => {
|
|
193
|
-
try {
|
|
194
|
-
await handler.call(this, {instanceId});
|
|
195
|
-
resolve();
|
|
196
|
-
} catch (e) {
|
|
197
|
-
reject(e);
|
|
198
|
-
}
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
if (extract) {
|
|
202
|
-
dst.on('close', () => finish(this.afterExport));
|
|
203
|
-
} else {
|
|
204
|
-
dst.on('finish', () => finish(this.afterWrite));
|
|
205
|
-
}
|
|
206
|
-
dst.on('error', reject);
|
|
207
|
-
|
|
208
|
-
try {
|
|
209
|
-
const
|
|
210
|
-
archiveStream = await archive((archive) => this.beforeSend(archive)),
|
|
211
|
-
data = await postStream(`${cleanPath(server)}/yap/${service}/${accessToken}`, archiveStream);
|
|
212
|
-
|
|
213
|
-
// listen for all archive data to be written
|
|
214
|
-
archiveStream.on('close', function () {
|
|
215
|
-
console.log('completed send');
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
data.on('error', function (err) {
|
|
219
|
-
if (err.code === 'ECONNREFUSED') {
|
|
220
|
-
console.warn(`Cannot connect to Cheerfully server "${server}"`);
|
|
221
|
-
reject(err);
|
|
222
|
-
} else {
|
|
223
|
-
reject(err);
|
|
224
|
-
}
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
data.pipe(dst);
|
|
228
|
-
} catch (e) {
|
|
229
|
-
console.warn(`Error handling "${instanceId}": ${e}`);
|
|
230
|
-
reject(e);
|
|
231
|
-
}
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
beforeSend (archive) {
|
|
236
|
-
const
|
|
237
|
-
{config, service} = this;
|
|
238
|
-
|
|
239
|
-
archive.append(JSON.stringify(config, null, 4), {name: `${service}.json`});
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
afterExport ({instanceId}) {
|
|
243
|
-
this.onComplete(instanceId);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
afterWrite ({instanceId}) {
|
|
247
|
-
this.onComplete(instanceId);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
onComplete (instanceId) {
|
|
251
|
-
console.log(`Cheerfully completed "${instanceId}"`);
|
|
252
|
-
}
|
|
253
|
-
};
|
|
254
|
-
|
|
1
|
+
const
|
|
2
|
+
archiver = require('archiver'),
|
|
3
|
+
fs = require('fs'),
|
|
4
|
+
getJSON = require('../helpers/getJSON'),
|
|
5
|
+
http = require('http'),
|
|
6
|
+
https = require('https'),
|
|
7
|
+
unzipper = require('unzip-stream'),
|
|
8
|
+
cleanPath = (path) => path[path.length - 1] === '/' ? path.substring(0, path.length - 1) : path,
|
|
9
|
+
postStream = (url, stream) => new Promise ((resolve, reject) => {
|
|
10
|
+
const
|
|
11
|
+
protocol = url.startsWith('https') ? https : http;
|
|
12
|
+
req = protocol.request(url, {
|
|
13
|
+
method: 'post',
|
|
14
|
+
headers: {
|
|
15
|
+
'Content-Type': 'application/zip'
|
|
16
|
+
}
|
|
17
|
+
}, async (res) => {
|
|
18
|
+
let data = '';
|
|
19
|
+
res.on('data', (chunk) => {
|
|
20
|
+
data += chunk;
|
|
21
|
+
});
|
|
22
|
+
res.on('close', () => {
|
|
23
|
+
let json = null;
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
json = JSON.parse(data);
|
|
27
|
+
} catch (e) {
|
|
28
|
+
const
|
|
29
|
+
pre = [data.indexOf('<pre>'), data.indexOf('</pre>')],
|
|
30
|
+
htmlError = pre[0] > -1 ? data.substring(pre[0] + 5, pre[1]) : data;
|
|
31
|
+
|
|
32
|
+
json = {
|
|
33
|
+
errors: [htmlError]
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const
|
|
37
|
+
{choreId, errors} = json;
|
|
38
|
+
|
|
39
|
+
if (errors) {
|
|
40
|
+
console.warn(errors.length === 1 ? `Error: ${errors[0]}` : `${errors.length} Errors`, errors);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (choreId) {
|
|
44
|
+
const
|
|
45
|
+
shareStatus = (status) => {
|
|
46
|
+
if (status !== lastStatus) {
|
|
47
|
+
console.log(status);
|
|
48
|
+
lastStatus = status;
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
checkStatus = async () => {
|
|
52
|
+
let response = null;
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
response = await fetchStream(`${url}/${choreId}`);
|
|
56
|
+
} catch (e) {
|
|
57
|
+
console.warn(e);
|
|
58
|
+
|
|
59
|
+
setTimeout(checkStatus, 10000);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (response.json) {
|
|
64
|
+
if (response.json.errors) {
|
|
65
|
+
console.warn('Error', response.json.errors);
|
|
66
|
+
} else if (response.json.status) {
|
|
67
|
+
shareStatus(response.json.status);
|
|
68
|
+
setTimeout(checkStatus, 10000);
|
|
69
|
+
} else {
|
|
70
|
+
shareStatus(response.json);
|
|
71
|
+
setTimeout(checkStatus, 10000);
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
resolve(response.stream);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
let lastStatus = '';
|
|
78
|
+
|
|
79
|
+
checkStatus();
|
|
80
|
+
} else {
|
|
81
|
+
reject(errors[0] ?? 'A valid chore id was not returned.');
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
req.on('error', reject);
|
|
87
|
+
|
|
88
|
+
stream.pipe(req);
|
|
89
|
+
}),
|
|
90
|
+
fetchStream = (url) => new Promise((resolve, reject) => {
|
|
91
|
+
const
|
|
92
|
+
protocol = url.startsWith('https') ? https : http;
|
|
93
|
+
|
|
94
|
+
protocol.get(url, async (res) => {
|
|
95
|
+
if (res.headers['content-type']?.indexOf('application/json') >= 0) {
|
|
96
|
+
let data = '';
|
|
97
|
+
res.on('data', (chunk) => {
|
|
98
|
+
data += chunk;
|
|
99
|
+
});
|
|
100
|
+
res.on('close', () => {
|
|
101
|
+
resolve({
|
|
102
|
+
json: JSON.parse(data)
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
} else {
|
|
106
|
+
resolve({
|
|
107
|
+
stream: res
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}).on('error', (err) => reject(err));
|
|
111
|
+
}),
|
|
112
|
+
archive = async function (parser) {
|
|
113
|
+
const
|
|
114
|
+
archive = archiver('zip', {
|
|
115
|
+
zlib: {
|
|
116
|
+
level: 0
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// good practice to catch warnings (ie stat failures and other non-blocking errors)
|
|
121
|
+
archive.on('warning', function (err) {
|
|
122
|
+
if (err.code === 'ENOENT') {
|
|
123
|
+
console.log(err);
|
|
124
|
+
} else {
|
|
125
|
+
throw err;
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
archive.on('error', function (err) {
|
|
129
|
+
throw err;
|
|
130
|
+
});
|
|
131
|
+
// 'close' event is fired only when a file descriptor is involved
|
|
132
|
+
archive.on('close', function () {
|
|
133
|
+
console.log('Zipped ' + archive.pointer() + ' total bytes');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
await parser(archive);
|
|
137
|
+
|
|
138
|
+
archive.finalize();
|
|
139
|
+
|
|
140
|
+
return archive;
|
|
141
|
+
},
|
|
142
|
+
Cheer = class {
|
|
143
|
+
constructor ({config, contents}) {
|
|
144
|
+
this.config = config;
|
|
145
|
+
this.contents = contents;
|
|
146
|
+
this.service = contents.service;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
prepare ({difference = true, extract = true}) {
|
|
150
|
+
if (difference) {
|
|
151
|
+
if (!extract) {
|
|
152
|
+
console.warn('Warning: Unable to run difference if not extracted: running all.');
|
|
153
|
+
} else {
|
|
154
|
+
return this.checkDifference();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async replaceConfigPathWithJSON (pathKey, JSONKey) {
|
|
160
|
+
const
|
|
161
|
+
{config} = this,
|
|
162
|
+
path = config[pathKey];
|
|
163
|
+
|
|
164
|
+
if (path) {
|
|
165
|
+
delete config[pathKey];
|
|
166
|
+
config[JSONKey] = {
|
|
167
|
+
...config[JSONKey] ?? {}, // so if it already exists, we combine.
|
|
168
|
+
...await getJSON(path) ?? {}
|
|
169
|
+
};
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async checkDifference () {
|
|
177
|
+
console.warn('A difference check is not implemented for this service: running all.');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async send ({instanceId}) {
|
|
181
|
+
const
|
|
182
|
+
{config, contents, service} = this,
|
|
183
|
+
{accessToken = '', extract = true, output = './output/', server} = {...contents, ...config},
|
|
184
|
+
mkdir = await fs.promises.mkdir(output, { recursive: true }),
|
|
185
|
+
dst = extract ? unzipper.Extract({
|
|
186
|
+
path: output,
|
|
187
|
+
concurrency: 1
|
|
188
|
+
}) : fs.createWriteStream(`${output}${instanceId}.zip`);
|
|
189
|
+
|
|
190
|
+
return new Promise(async (resolve, reject) => {
|
|
191
|
+
const
|
|
192
|
+
finish = async (handler) => {
|
|
193
|
+
try {
|
|
194
|
+
await handler.call(this, {instanceId});
|
|
195
|
+
resolve();
|
|
196
|
+
} catch (e) {
|
|
197
|
+
reject(e);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
if (extract) {
|
|
202
|
+
dst.on('close', () => finish(this.afterExport));
|
|
203
|
+
} else {
|
|
204
|
+
dst.on('finish', () => finish(this.afterWrite));
|
|
205
|
+
}
|
|
206
|
+
dst.on('error', reject);
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
const
|
|
210
|
+
archiveStream = await archive((archive) => this.beforeSend(archive)),
|
|
211
|
+
data = await postStream(`${cleanPath(server)}/yap/${service}/${accessToken}`, archiveStream);
|
|
212
|
+
|
|
213
|
+
// listen for all archive data to be written
|
|
214
|
+
archiveStream.on('close', function () {
|
|
215
|
+
console.log('completed send');
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
data.on('error', function (err) {
|
|
219
|
+
if (err.code === 'ECONNREFUSED') {
|
|
220
|
+
console.warn(`Cannot connect to Cheerfully server "${server}"`);
|
|
221
|
+
reject(err);
|
|
222
|
+
} else {
|
|
223
|
+
reject(err);
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
data.pipe(dst);
|
|
228
|
+
} catch (e) {
|
|
229
|
+
console.warn(`Error handling "${instanceId}": ${e}`);
|
|
230
|
+
reject(e);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
beforeSend (archive) {
|
|
236
|
+
const
|
|
237
|
+
{config, service} = this;
|
|
238
|
+
|
|
239
|
+
archive.append(JSON.stringify(config, null, 4), {name: `${service}.json`});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
afterExport ({instanceId}) {
|
|
243
|
+
this.onComplete(instanceId);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
afterWrite ({instanceId}) {
|
|
247
|
+
this.onComplete(instanceId);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
onComplete (instanceId) {
|
|
251
|
+
console.log(`Cheerfully completed "${instanceId}"`);
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
|
|
255
255
|
module.exports = Cheer;
|