@engine9-io/input-tools 2.0.0 → 2.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.
- package/ForEachEntry.js +5 -7
- package/file/FileUtilities.js +903 -951
- package/file/tools.js +283 -312
- package/index.js +2 -2
- package/package.json +1 -1
- package/test/file.js +2 -2
package/file/tools.js
CHANGED
|
@@ -1,32 +1,31 @@
|
|
|
1
|
-
import fs from
|
|
2
|
-
import path from
|
|
3
|
-
import debug$0 from
|
|
4
|
-
import os from
|
|
5
|
-
import mkdirp
|
|
6
|
-
import nodestream from
|
|
7
|
-
import JSON5 from
|
|
8
|
-
import unzipper from
|
|
9
|
-
import dayjs from
|
|
10
|
-
import clientS3 from
|
|
11
|
-
import
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import debug$0 from 'debug';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import { mkdirp } from 'mkdirp';
|
|
6
|
+
import nodestream from 'node:stream';
|
|
7
|
+
import JSON5 from 'json5';
|
|
8
|
+
import unzipper from 'unzipper';
|
|
9
|
+
import dayjs from 'dayjs';
|
|
10
|
+
import clientS3 from '@aws-sdk/client-s3';
|
|
11
|
+
import { v7 as uuidv7 } from 'uuid';
|
|
12
|
+
|
|
12
13
|
const fsp = fs.promises;
|
|
13
14
|
const debug = debug$0('@engine9/input-tools');
|
|
14
|
-
|
|
15
|
+
|
|
15
16
|
const { Transform } = nodestream;
|
|
16
17
|
const { PassThrough } = nodestream;
|
|
17
18
|
const progress = debug$0('info:@engine9/input-tools');
|
|
18
19
|
const { S3Client, HeadObjectCommand, GetObjectCommand } = clientS3;
|
|
19
|
-
|
|
20
|
+
|
|
20
21
|
async function getTempDir({ accountId = 'engine9' }) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
return dir;
|
|
22
|
+
const dir = [os.tmpdir(), accountId, new Date().toISOString().substring(0, 10)].join(path.sep);
|
|
23
|
+
try {
|
|
24
|
+
await mkdirp(dir);
|
|
25
|
+
} catch (err) {
|
|
26
|
+
if (err.code !== 'EEXIST') throw err;
|
|
27
|
+
}
|
|
28
|
+
return dir;
|
|
30
29
|
}
|
|
31
30
|
/*
|
|
32
31
|
Get a new, timestamp based filename, creating any necessary directories
|
|
@@ -35,322 +34,294 @@ async function getTempDir({ accountId = 'engine9' }) {
|
|
|
35
34
|
source:source file, used to generate friendly name
|
|
36
35
|
*/
|
|
37
36
|
async function getTempFilename(options) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
// make a distinct directory, so we don't overwrite the file
|
|
46
|
-
dir = `${dir}/${new Date()
|
|
47
|
-
.toISOString()
|
|
48
|
-
.slice(0, -6)
|
|
49
|
-
.replace(/[^0-9]/g, '_')}`;
|
|
50
|
-
const newDir = await mkdirp(dir);
|
|
51
|
-
return `${newDir}/${target}`;
|
|
52
|
-
}
|
|
53
|
-
let { prefix } = options;
|
|
54
|
-
let { postfix } = options;
|
|
55
|
-
const { targetFormat } = options;
|
|
56
|
-
if (!postfix && targetFormat === 'csv')
|
|
57
|
-
postfix = '.csv';
|
|
58
|
-
if (options.source) {
|
|
59
|
-
postfix = `_${options.source.split('/').pop()}`;
|
|
60
|
-
postfix = postfix.replace(/['"\\]/g, '').replace(/[^a-zA-Z0-9_.-]/g, '_');
|
|
37
|
+
let dir = await getTempDir(options);
|
|
38
|
+
const target = options.targetFilename;
|
|
39
|
+
if (target) {
|
|
40
|
+
if (target.indexOf('/') === 0 || target.indexOf('\\') === 0) {
|
|
41
|
+
// assume a full directory path has been specified
|
|
42
|
+
return target;
|
|
61
43
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
44
|
+
// make a distinct directory, so we don't overwrite the file
|
|
45
|
+
dir = `${dir}/${new Date()
|
|
46
|
+
.toISOString()
|
|
47
|
+
.slice(0, -6)
|
|
48
|
+
.replace(/[^0-9]/g, '_')}`;
|
|
49
|
+
const newDir = await mkdirp(dir);
|
|
50
|
+
return `${newDir}/${target}`;
|
|
51
|
+
}
|
|
52
|
+
let { prefix } = options;
|
|
53
|
+
let { postfix } = options;
|
|
54
|
+
const { targetFormat } = options;
|
|
55
|
+
if (!postfix && targetFormat === 'csv') postfix = '.csv';
|
|
56
|
+
if (options.source) {
|
|
57
|
+
postfix = `_${options.source.split('/').pop()}`;
|
|
58
|
+
postfix = postfix.replace(/['"\\]/g, '').replace(/[^a-zA-Z0-9_.-]/g, '_');
|
|
59
|
+
}
|
|
60
|
+
if (prefix) prefix += '_';
|
|
61
|
+
const p = `${dir}/${prefix || ''}${uuidv7()}${postfix || '.txt'}`;
|
|
62
|
+
return p;
|
|
66
63
|
}
|
|
67
64
|
async function writeTempFile(options) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
65
|
+
const { content, postfix = '.txt' } = options;
|
|
66
|
+
const filename = await getTempFilename({ ...options, postfix });
|
|
67
|
+
await fsp.writeFile(filename, content);
|
|
68
|
+
return { filename };
|
|
72
69
|
}
|
|
73
70
|
async function getPacketFiles({ packet }) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
71
|
+
if (packet.indexOf('s3://') === 0) {
|
|
72
|
+
const parts = packet.split('/');
|
|
73
|
+
const Bucket = parts[2];
|
|
74
|
+
const Key = parts.slice(3).join('/');
|
|
75
|
+
const s3Client = new S3Client({});
|
|
76
|
+
debug('Getting ', { Bucket, Key });
|
|
77
|
+
// const directory = await unzipper.Open.s3(s3Client, { Bucket, Key });
|
|
78
|
+
let size = null;
|
|
79
|
+
const directory = await unzipper.Open.custom({
|
|
80
|
+
async size() {
|
|
81
|
+
const info = await s3Client.send(
|
|
82
|
+
new HeadObjectCommand({
|
|
83
|
+
Bucket,
|
|
84
|
+
Key
|
|
85
|
+
})
|
|
86
|
+
);
|
|
87
|
+
size = info.ContentLength;
|
|
88
|
+
progress(`Retrieving file of size ${size / (1024 * 1024)} MB`);
|
|
89
|
+
return info.ContentLength;
|
|
90
|
+
},
|
|
91
|
+
stream(offset, length) {
|
|
92
|
+
const ptStream = new PassThrough();
|
|
93
|
+
s3Client
|
|
94
|
+
.send(
|
|
95
|
+
new GetObjectCommand({
|
|
96
|
+
Bucket,
|
|
97
|
+
Key,
|
|
98
|
+
Range: `bytes=${offset}-${length ?? ''}`
|
|
99
|
+
})
|
|
100
|
+
)
|
|
101
|
+
.then((response) => {
|
|
102
|
+
response.Body.pipe(ptStream);
|
|
103
|
+
})
|
|
104
|
+
.catch((error) => {
|
|
105
|
+
ptStream.emit('error', error);
|
|
106
|
+
});
|
|
107
|
+
return ptStream;
|
|
108
|
+
}
|
|
109
|
+
});
|
|
112
110
|
return directory;
|
|
111
|
+
}
|
|
112
|
+
const directory = await unzipper.Open.file(packet);
|
|
113
|
+
return directory;
|
|
113
114
|
}
|
|
114
115
|
async function getManifest({ packet }) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
return manifest;
|
|
116
|
+
if (!packet) throw new Error('no packet option specififed');
|
|
117
|
+
const { files } = await getPacketFiles({ packet });
|
|
118
|
+
const file = files.find((d) => d.path === 'manifest.json');
|
|
119
|
+
const content = await file.buffer();
|
|
120
|
+
const manifest = JSON.parse(content.toString());
|
|
121
|
+
return manifest;
|
|
122
122
|
}
|
|
123
123
|
function getBatchTransform({ batchSize = 100 }) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
};
|
|
124
|
+
return {
|
|
125
|
+
transform: new Transform({
|
|
126
|
+
objectMode: true,
|
|
127
|
+
transform(chunk, encoding, cb) {
|
|
128
|
+
this.buffer = (this.buffer || []).concat(chunk);
|
|
129
|
+
if (this.buffer.length >= batchSize) {
|
|
130
|
+
this.push(this.buffer);
|
|
131
|
+
this.buffer = [];
|
|
132
|
+
}
|
|
133
|
+
cb();
|
|
134
|
+
},
|
|
135
|
+
flush(cb) {
|
|
136
|
+
if (this.buffer?.length > 0) this.push(this.buffer);
|
|
137
|
+
cb();
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
};
|
|
142
141
|
}
|
|
143
142
|
function getDebatchTransform() {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
143
|
+
return {
|
|
144
|
+
transform: new Transform({
|
|
145
|
+
objectMode: true,
|
|
146
|
+
transform(chunk, encoding, cb) {
|
|
147
|
+
chunk.forEach((c) => this.push(c));
|
|
148
|
+
cb();
|
|
149
|
+
}
|
|
150
|
+
})
|
|
151
|
+
};
|
|
153
152
|
}
|
|
154
153
|
async function getFile({ filename, packet, type }) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if (packet) {
|
|
160
|
-
const manifest = await getManifest({ packet });
|
|
161
|
-
const manifestFiles = manifest.files?.filter((d) => d.type === type);
|
|
162
|
-
if (!manifestFiles?.length)
|
|
163
|
-
throw new Error(`No files of type ${type} found in packet`);
|
|
164
|
-
if (manifestFiles?.length > 1)
|
|
165
|
-
throw new Error(`Multiple files of type ${type} found in packet`);
|
|
166
|
-
filePath = manifestFiles[0].path;
|
|
167
|
-
const { files } = await getPacketFiles({ packet });
|
|
168
|
-
const handle = files.find((d) => d.path === filePath);
|
|
169
|
-
const buffer = await handle.buffer();
|
|
170
|
-
content = await buffer.toString();
|
|
171
|
-
}
|
|
172
|
-
else {
|
|
173
|
-
content = await fsp.readFile(filename);
|
|
174
|
-
filePath = filename.split('/').pop();
|
|
175
|
-
}
|
|
176
|
-
if (filePath.slice(-5) === '.json' || filePath.slice(-6) === '.json5') {
|
|
177
|
-
try {
|
|
178
|
-
return JSON5.parse(content);
|
|
179
|
-
}
|
|
180
|
-
catch (e) {
|
|
181
|
-
debug(`Erroring parsing json content from ${path}`, content);
|
|
182
|
-
throw e;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
return content;
|
|
186
|
-
}
|
|
187
|
-
async function streamPacket({ packet, type }) {
|
|
188
|
-
if (!packet)
|
|
189
|
-
throw new Error('no packet option specififed');
|
|
154
|
+
if (!packet && !filename) throw new Error('no packet option specififed');
|
|
155
|
+
let content = null;
|
|
156
|
+
let filePath = null;
|
|
157
|
+
if (packet) {
|
|
190
158
|
const manifest = await getManifest({ packet });
|
|
191
159
|
const manifestFiles = manifest.files?.filter((d) => d.type === type);
|
|
192
|
-
if (!manifestFiles?.length)
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
throw new Error(`Multiple files of type ${type} found in packet`);
|
|
196
|
-
const filePath = manifestFiles[0].path;
|
|
160
|
+
if (!manifestFiles?.length) throw new Error(`No files of type ${type} found in packet`);
|
|
161
|
+
if (manifestFiles?.length > 1) throw new Error(`Multiple files of type ${type} found in packet`);
|
|
162
|
+
filePath = manifestFiles[0].path;
|
|
197
163
|
const { files } = await getPacketFiles({ packet });
|
|
198
164
|
const handle = files.find((d) => d.path === filePath);
|
|
199
|
-
|
|
165
|
+
const buffer = await handle.buffer();
|
|
166
|
+
content = await buffer.toString();
|
|
167
|
+
} else {
|
|
168
|
+
content = await fsp.readFile(filename);
|
|
169
|
+
filePath = filename.split('/').pop();
|
|
170
|
+
}
|
|
171
|
+
if (filePath.slice(-5) === '.json' || filePath.slice(-6) === '.json5') {
|
|
172
|
+
try {
|
|
173
|
+
return JSON5.parse(content);
|
|
174
|
+
} catch (e) {
|
|
175
|
+
debug(`Erroring parsing json content from ${path}`, content);
|
|
176
|
+
throw e;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return content;
|
|
180
|
+
}
|
|
181
|
+
async function streamPacket({ packet, type }) {
|
|
182
|
+
if (!packet) throw new Error('no packet option specififed');
|
|
183
|
+
const manifest = await getManifest({ packet });
|
|
184
|
+
const manifestFiles = manifest.files?.filter((d) => d.type === type);
|
|
185
|
+
if (!manifestFiles?.length) throw new Error(`No files of type ${type} found in packet`);
|
|
186
|
+
if (manifestFiles?.length > 1) throw new Error(`Multiple files of type ${type} found in packet`);
|
|
187
|
+
const filePath = manifestFiles[0].path;
|
|
188
|
+
const { files } = await getPacketFiles({ packet });
|
|
189
|
+
const handle = files.find((d) => d.path === filePath);
|
|
190
|
+
return { stream: handle.stream(), path: filePath };
|
|
200
191
|
}
|
|
201
192
|
async function downloadFile({ packet, type = 'person' }) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
193
|
+
const { stream: fileStream, path: filePath } = await streamPacket({ packet, type });
|
|
194
|
+
const filename = await getTempFilename({ targetFilename: filePath.split('/').pop() });
|
|
195
|
+
return new Promise((resolve, reject) => {
|
|
196
|
+
fileStream
|
|
197
|
+
.pipe(fs.createWriteStream(filename))
|
|
198
|
+
.on('error', reject)
|
|
199
|
+
.on('finish', () => {
|
|
200
|
+
resolve({ filename });
|
|
201
|
+
});
|
|
202
|
+
});
|
|
212
203
|
}
|
|
213
204
|
function isValidDate(d) {
|
|
214
|
-
|
|
215
|
-
|
|
205
|
+
// we WANT to use isNaN, not the Number.isNaN -- we're checking the date type
|
|
206
|
+
return d instanceof Date && !isNaN(d);
|
|
216
207
|
}
|
|
217
208
|
function bool(x, _defaultVal) {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
return true; // 0 will return false, but '1' is true
|
|
225
|
-
const y = x.toLowerCase();
|
|
226
|
-
return !!(y.indexOf('y') + 1) || !!(y.indexOf('t') + 1);
|
|
209
|
+
const defaultVal = _defaultVal === undefined ? false : _defaultVal;
|
|
210
|
+
if (x === undefined || x === null || x === '') return defaultVal;
|
|
211
|
+
if (typeof x !== 'string') return !!x;
|
|
212
|
+
if (x === '1') return true; // 0 will return false, but '1' is true
|
|
213
|
+
const y = x.toLowerCase();
|
|
214
|
+
return !!(y.indexOf('y') + 1) || !!(y.indexOf('t') + 1);
|
|
227
215
|
}
|
|
228
216
|
function getStringArray(s, nonZeroLength) {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
a = a.map((x) => x.toString().trim()).filter(Boolean);
|
|
237
|
-
if (nonZeroLength && a.length === 0)
|
|
238
|
-
a = [0];
|
|
239
|
-
return a;
|
|
217
|
+
let a = s || [];
|
|
218
|
+
if (typeof a === 'number') a = String(a);
|
|
219
|
+
if (typeof a === 'string') a = [a];
|
|
220
|
+
if (typeof s === 'string') a = s.split(',');
|
|
221
|
+
a = a.map((x) => x.toString().trim()).filter(Boolean);
|
|
222
|
+
if (nonZeroLength && a.length === 0) a = [0];
|
|
223
|
+
return a;
|
|
240
224
|
}
|
|
241
225
|
function relativeDate(s, _initialDate) {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
if (
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
226
|
+
let initialDate = _initialDate;
|
|
227
|
+
if (!s || s === 'none') return null;
|
|
228
|
+
if (typeof s.getMonth === 'function') return s;
|
|
229
|
+
// We actually want a double equals here to test strings as well
|
|
230
|
+
if (parseInt(s, 10) == s) {
|
|
231
|
+
const r = new Date(parseInt(s, 10));
|
|
232
|
+
if (!isValidDate(r)) throw new Error(`Invalid integer date:${s}`);
|
|
233
|
+
return r;
|
|
234
|
+
}
|
|
235
|
+
if (initialDate) {
|
|
236
|
+
initialDate = new Date(initialDate);
|
|
237
|
+
} else {
|
|
238
|
+
initialDate = new Date();
|
|
239
|
+
}
|
|
240
|
+
let r = s.match(/^([+-]{1})([0-9]+)([YyMwdhms]{1})([.a-z]*)$/);
|
|
241
|
+
if (r) {
|
|
242
|
+
let period = null;
|
|
243
|
+
switch (r[3]) {
|
|
244
|
+
case 'Y':
|
|
245
|
+
case 'y':
|
|
246
|
+
period = 'years';
|
|
247
|
+
break;
|
|
248
|
+
case 'M':
|
|
249
|
+
period = 'months';
|
|
250
|
+
break;
|
|
251
|
+
case 'w':
|
|
252
|
+
period = 'weeks';
|
|
253
|
+
break;
|
|
254
|
+
case 'd':
|
|
255
|
+
period = 'days';
|
|
256
|
+
break;
|
|
257
|
+
case 'h':
|
|
258
|
+
period = 'hours';
|
|
259
|
+
break;
|
|
260
|
+
case 'm':
|
|
261
|
+
period = 'minutes';
|
|
262
|
+
break;
|
|
263
|
+
case 's':
|
|
264
|
+
period = 'seconds';
|
|
265
|
+
break;
|
|
266
|
+
default:
|
|
267
|
+
period = 'minutes';
|
|
268
|
+
break;
|
|
259
269
|
}
|
|
260
|
-
let
|
|
261
|
-
if (r) {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
case 'y':
|
|
266
|
-
period = 'years';
|
|
267
|
-
break;
|
|
268
|
-
case 'M':
|
|
269
|
-
period = 'months';
|
|
270
|
-
break;
|
|
271
|
-
case 'w':
|
|
272
|
-
period = 'weeks';
|
|
273
|
-
break;
|
|
274
|
-
case 'd':
|
|
275
|
-
period = 'days';
|
|
276
|
-
break;
|
|
277
|
-
case 'h':
|
|
278
|
-
period = 'hours';
|
|
279
|
-
break;
|
|
280
|
-
case 'm':
|
|
281
|
-
period = 'minutes';
|
|
282
|
-
break;
|
|
283
|
-
case 's':
|
|
284
|
-
period = 'seconds';
|
|
285
|
-
break;
|
|
286
|
-
default:
|
|
287
|
-
period = 'minutes';
|
|
288
|
-
break;
|
|
289
|
-
}
|
|
290
|
-
let d = dayjs(initialDate);
|
|
291
|
-
if (r[1] === '+') {
|
|
292
|
-
d = d.add(parseInt(r[2], 10), period);
|
|
293
|
-
}
|
|
294
|
-
else {
|
|
295
|
-
d = d.subtract(parseInt(r[2], 10), period);
|
|
296
|
-
}
|
|
297
|
-
if (!isValidDate(d.toDate()))
|
|
298
|
-
throw new Error(`Invalid date configuration:${r}`);
|
|
299
|
-
if (r[4]) {
|
|
300
|
-
const opts = r[4].split('.').filter(Boolean);
|
|
301
|
-
if (opts[0] === 'start')
|
|
302
|
-
d = d.startOf(opts[1] || 'day');
|
|
303
|
-
else if (opts[0] === 'end')
|
|
304
|
-
d = d.endOf(opts[1] || 'day');
|
|
305
|
-
else
|
|
306
|
-
throw new Error(`Invalid relative date,unknown options:${r[4]}`);
|
|
307
|
-
}
|
|
308
|
-
return d.toDate();
|
|
270
|
+
let d = dayjs(initialDate);
|
|
271
|
+
if (r[1] === '+') {
|
|
272
|
+
d = d.add(parseInt(r[2], 10), period);
|
|
273
|
+
} else {
|
|
274
|
+
d = d.subtract(parseInt(r[2], 10), period);
|
|
309
275
|
}
|
|
310
|
-
if (
|
|
311
|
-
|
|
312
|
-
|
|
276
|
+
if (!isValidDate(d.toDate())) throw new Error(`Invalid date configuration:${r}`);
|
|
277
|
+
if (r[4]) {
|
|
278
|
+
const opts = r[4].split('.').filter(Boolean);
|
|
279
|
+
if (opts[0] === 'start') d = d.startOf(opts[1] || 'day');
|
|
280
|
+
else if (opts[0] === 'end') d = d.endOf(opts[1] || 'day');
|
|
281
|
+
else throw new Error(`Invalid relative date,unknown options:${r[4]}`);
|
|
313
282
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
283
|
+
return d.toDate();
|
|
284
|
+
}
|
|
285
|
+
if (s === 'now') {
|
|
286
|
+
r = dayjs(new Date()).toDate();
|
|
317
287
|
return r;
|
|
288
|
+
}
|
|
289
|
+
r = dayjs(new Date(s)).toDate();
|
|
290
|
+
if (!isValidDate(r)) throw new Error(`Invalid Date: ${s}`);
|
|
291
|
+
return r;
|
|
318
292
|
}
|
|
319
293
|
/*
|
|
320
294
|
When comparing two objects, some may come from a file (thus strings), and some from
|
|
321
295
|
a database or elsewhere (not strings), so for deduping make sure to make them all strings
|
|
322
296
|
*/
|
|
323
297
|
function makeStrings(o) {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
298
|
+
return Object.entries(o).reduce((a, [k, v]) => {
|
|
299
|
+
a[k] = typeof v === 'object' ? JSON.stringify(v) : String(v);
|
|
300
|
+
return a;
|
|
301
|
+
}, {});
|
|
328
302
|
}
|
|
329
303
|
function appendPostfix(filename, postfix) {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
return filenameParts.slice(0, -1).concat(targetFile).join('/');
|
|
304
|
+
const filenameParts = filename.split('/');
|
|
305
|
+
const fileParts = filenameParts
|
|
306
|
+
.slice(-1)[0]
|
|
307
|
+
.split('.')
|
|
308
|
+
.filter(Boolean)
|
|
309
|
+
.filter((d) => d !== postfix);
|
|
310
|
+
let targetFile = null;
|
|
311
|
+
if (fileParts.slice(-1)[0] === 'gz') {
|
|
312
|
+
targetFile = fileParts.slice(0, -2).concat(postfix).concat(fileParts.slice(-2)).join('.');
|
|
313
|
+
} else {
|
|
314
|
+
targetFile = fileParts.slice(0, -1).concat(postfix).concat(fileParts.slice(-1)).join('.');
|
|
315
|
+
}
|
|
316
|
+
return filenameParts.slice(0, -1).concat(targetFile).join('/');
|
|
344
317
|
}
|
|
345
318
|
function parseJSON5(o, defaultVal) {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
}
|
|
353
|
-
return defaultVal || o;
|
|
319
|
+
if (o) {
|
|
320
|
+
if (typeof o === 'object') return o;
|
|
321
|
+
if (typeof o === 'string') return JSON5.parse(o);
|
|
322
|
+
throw new Error(`Could not parse object:${o}`);
|
|
323
|
+
}
|
|
324
|
+
return defaultVal || o;
|
|
354
325
|
}
|
|
355
326
|
export { appendPostfix };
|
|
356
327
|
export { bool };
|
|
@@ -369,20 +340,20 @@ export { relativeDate };
|
|
|
369
340
|
export { streamPacket };
|
|
370
341
|
export { writeTempFile };
|
|
371
342
|
export default {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
343
|
+
appendPostfix,
|
|
344
|
+
bool,
|
|
345
|
+
downloadFile,
|
|
346
|
+
getTempFilename,
|
|
347
|
+
getTempDir,
|
|
348
|
+
getBatchTransform,
|
|
349
|
+
getDebatchTransform,
|
|
350
|
+
getFile,
|
|
351
|
+
getManifest,
|
|
352
|
+
getPacketFiles,
|
|
353
|
+
getStringArray,
|
|
354
|
+
makeStrings,
|
|
355
|
+
parseJSON5,
|
|
356
|
+
relativeDate,
|
|
357
|
+
streamPacket,
|
|
358
|
+
writeTempFile
|
|
388
359
|
};
|