@mablhq/mabl-cli 2.100.6 → 2.101.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/execution/index.js +2 -2
- package/mablscript/steps/DownloadStep.js +2 -0
- package/package.json +1 -1
- package/trainer/sharedUtils/mablscript/canonical/DownloadBaseStep.js +22 -3
- package/trainer/sharedUtils/mablscript/canonical/DownloadPdfStep.js +6 -0
- package/trainer/sharedUtils/mablscript/canonical/DownloadStep.js +1 -0
- package/util/downloadUtil.js +34 -4
- package/util/fileNameMatchUtil.js +27 -0
|
@@ -30,6 +30,7 @@ class DownloadStep extends MablStepV2_1.MablStepV2 {
|
|
|
30
30
|
file_size_bytes_max: descriptor.sizeBytesMax,
|
|
31
31
|
file_size_bytes_min: descriptor.sizeBytesMin,
|
|
32
32
|
ai_prompt: descriptor.aiPrompt,
|
|
33
|
+
file_name_match_mode: descriptor.fileNameMatchMode,
|
|
33
34
|
};
|
|
34
35
|
}
|
|
35
36
|
getStepName() {
|
|
@@ -57,6 +58,7 @@ class DownloadStep extends MablStepV2_1.MablStepV2 {
|
|
|
57
58
|
fileMd5: fileAttributes.file_md5,
|
|
58
59
|
fileSha256: fileAttributes.file_sha256,
|
|
59
60
|
aiPrompt: fileAttributes.ai_prompt,
|
|
61
|
+
fileNameMatchMode: fileAttributes.file_name_match_mode,
|
|
60
62
|
};
|
|
61
63
|
const isPdfDownload = awaitDownloadAction instanceof AwaitPDFDownloadAction_1.AwaitPDFDownloadAction;
|
|
62
64
|
if (isPdfDownload) {
|
package/package.json
CHANGED
|
@@ -16,6 +16,7 @@ const type_utils_1 = require("../../type-utils");
|
|
|
16
16
|
const variable_names_util_1 = require("../../variable-names-util");
|
|
17
17
|
const BaseStep_1 = __importDefault(require("./BaseStep"));
|
|
18
18
|
const DownloadStep_1 = require("../../../../mablscript/steps/DownloadStep");
|
|
19
|
+
const fileNameMatchUtil_1 = require("../../../../util/fileNameMatchUtil");
|
|
19
20
|
const yaml_utils_1 = require("../yaml-utils");
|
|
20
21
|
exports.DOWNLOAD_COMPLETE_POLLING_INTERVAL_MILLISECONDS = 200;
|
|
21
22
|
const MISSING_DATA_STATUS = 'MISSING DATA';
|
|
@@ -90,6 +91,7 @@ class DownloadBaseStep extends BaseStep_1.default {
|
|
|
90
91
|
this.stepSymbol = 'assert_download';
|
|
91
92
|
this.awaitParams = values.awaitParameters || {};
|
|
92
93
|
this.aiPrompt = values.aiPrompt;
|
|
94
|
+
this.fileNameMatchMode = values.fileNameMatchMode;
|
|
93
95
|
this.options = {
|
|
94
96
|
editable: true,
|
|
95
97
|
executionContext: 'background',
|
|
@@ -146,6 +148,7 @@ class DownloadBaseStep extends BaseStep_1.default {
|
|
|
146
148
|
fileMd5: getAssertionValue(DownloadProperty.FileMd5),
|
|
147
149
|
fileSha256: getAssertionValue(DownloadProperty.FileSha256),
|
|
148
150
|
aiPrompt: this.aiPrompt,
|
|
151
|
+
fileNameMatchMode: this.fileNameMatchMode,
|
|
149
152
|
detected: {
|
|
150
153
|
file_mime_type: this.detectedProps?.file_mime_type,
|
|
151
154
|
file_name: this.detectedProps?.file_name,
|
|
@@ -168,6 +171,9 @@ class DownloadBaseStep extends BaseStep_1.default {
|
|
|
168
171
|
if (this.aiPrompt) {
|
|
169
172
|
stepParams.ai_prompt = this.aiPrompt;
|
|
170
173
|
}
|
|
174
|
+
if (this.fileNameMatchMode && this.fileNameMatchMode !== 'equals') {
|
|
175
|
+
stepParams.file_name_match_mode = this.fileNameMatchMode;
|
|
176
|
+
}
|
|
171
177
|
this.assertions
|
|
172
178
|
.filter((assertion) => assertion.expected)
|
|
173
179
|
.forEach((assertion) => {
|
|
@@ -235,11 +241,14 @@ function fileMimeTypeChecker(assertion, downloadData) {
|
|
|
235
241
|
}
|
|
236
242
|
return MISSING_DATA_STATUS;
|
|
237
243
|
}
|
|
238
|
-
function fileNameChecker(assertion, downloadData) {
|
|
244
|
+
function fileNameChecker(assertion, downloadData, fileNameMatchMode) {
|
|
239
245
|
if (downloadData?.[DownloadProperty.FileName]) {
|
|
240
246
|
const filename = removeFileAutoNumbering(DownloadBaseStep.downloadPathToFilename(downloadData[DownloadProperty.FileName]) ?? '');
|
|
241
247
|
const expected = removeFileAutoNumbering(assertion.expected);
|
|
242
|
-
|
|
248
|
+
if (expected === '') {
|
|
249
|
+
return exports.SUCCESS_STATUS;
|
|
250
|
+
}
|
|
251
|
+
return (0, fileNameMatchUtil_1.evaluateFileNameMatch)(filename, expected, fileNameMatchMode)
|
|
243
252
|
? exports.SUCCESS_STATUS
|
|
244
253
|
: generateFail(expected, filename, 'Unexpected file name');
|
|
245
254
|
}
|
|
@@ -297,7 +306,12 @@ function humanizeDownloadBaseStep(step) {
|
|
|
297
306
|
(!assertion.expected || !cleanFilename)) {
|
|
298
307
|
return 'Assert file downloaded';
|
|
299
308
|
}
|
|
300
|
-
|
|
309
|
+
const matchVerb = assertion.name === DownloadProperty.FileName &&
|
|
310
|
+
typeof step.fileNameMatchMode === 'string' &&
|
|
311
|
+
step.fileNameMatchMode !== 'equals'
|
|
312
|
+
? step.fileNameMatchMode.replace('_', ' ')
|
|
313
|
+
: 'equals';
|
|
314
|
+
return `Assert ${assertion.desc} ${matchVerb} ${assertionTarget} for download ${cleanFilename}`;
|
|
301
315
|
})
|
|
302
316
|
.join('\t');
|
|
303
317
|
}
|
|
@@ -313,6 +327,11 @@ function downloadBaseStepToAIString(step) {
|
|
|
313
327
|
result.assertions = activeAssertions.map((a) => ({
|
|
314
328
|
property: a.name,
|
|
315
329
|
expected: a.expected,
|
|
330
|
+
...(a.name === DownloadProperty.FileName &&
|
|
331
|
+
step.fileNameMatchMode &&
|
|
332
|
+
step.fileNameMatchMode !== 'equals'
|
|
333
|
+
? { match_mode: step.fileNameMatchMode }
|
|
334
|
+
: {}),
|
|
316
335
|
}));
|
|
317
336
|
}
|
|
318
337
|
if (step.aiPrompt?.userPrompt) {
|
|
@@ -97,6 +97,7 @@ class DownloadPdfStep extends DownloadBaseStep_1.default {
|
|
|
97
97
|
assertions,
|
|
98
98
|
awaitParameters,
|
|
99
99
|
aiPrompt: mablDownloadStep.descriptor.aiPrompt,
|
|
100
|
+
fileNameMatchMode: mablDownloadStep.descriptor.fileNameMatchMode,
|
|
100
101
|
});
|
|
101
102
|
}
|
|
102
103
|
static create(descriptor) {
|
|
@@ -121,6 +122,11 @@ function downloadPdfStepToAIString(step) {
|
|
|
121
122
|
result.assertions = activeAssertions.map((a) => ({
|
|
122
123
|
property: a.name,
|
|
123
124
|
expected: a.expected,
|
|
125
|
+
...(a.name === DownloadBaseStep_1.DownloadProperty.FileName &&
|
|
126
|
+
step.fileNameMatchMode &&
|
|
127
|
+
step.fileNameMatchMode !== 'equals'
|
|
128
|
+
? { match_mode: step.fileNameMatchMode }
|
|
129
|
+
: {}),
|
|
124
130
|
}));
|
|
125
131
|
}
|
|
126
132
|
if (step.aiPrompt?.userPrompt) {
|
package/util/downloadUtil.js
CHANGED
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.PROCESSED_DIR_NAME = void 0;
|
|
37
|
+
exports.createFilenameMatcher = createFilenameMatcher;
|
|
37
38
|
exports.getAwaitDownloadTimeout = getAwaitDownloadTimeout;
|
|
38
39
|
exports.awaitDownload = awaitDownload;
|
|
39
40
|
exports.getLatestFile = getLatestFile;
|
|
@@ -47,15 +48,41 @@ const AWAIT_DOWNLOAD_MAX_TIMEOUT_MILLIS = 60_000;
|
|
|
47
48
|
const FILE_MIN_STABLE_TIME_MILLIS = 3000;
|
|
48
49
|
const AWAIT_DOWNLOAD_POLLING_INTERVAL_MILLISECONDS = 500;
|
|
49
50
|
exports.PROCESSED_DIR_NAME = 'Processed';
|
|
51
|
+
function createFilenameMatcher(pattern, mode) {
|
|
52
|
+
if (!pattern) {
|
|
53
|
+
return () => true;
|
|
54
|
+
}
|
|
55
|
+
switch (mode) {
|
|
56
|
+
case 'contains':
|
|
57
|
+
return (fullPath) => path.basename(fullPath).includes(pattern);
|
|
58
|
+
case 'starts_with':
|
|
59
|
+
return (fullPath) => path.basename(fullPath).startsWith(pattern);
|
|
60
|
+
case 'ends_with':
|
|
61
|
+
return (fullPath) => path.basename(fullPath).endsWith(pattern);
|
|
62
|
+
case 'matches_regex': {
|
|
63
|
+
let regex;
|
|
64
|
+
try {
|
|
65
|
+
regex = new RegExp(pattern);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return () => false;
|
|
69
|
+
}
|
|
70
|
+
return (fullPath) => regex.test(path.basename(fullPath));
|
|
71
|
+
}
|
|
72
|
+
case 'equals':
|
|
73
|
+
default:
|
|
74
|
+
return (fullPath) => fullPath.toLowerCase().endsWith(pattern.toLowerCase());
|
|
75
|
+
}
|
|
76
|
+
}
|
|
50
77
|
function getAwaitDownloadTimeout() {
|
|
51
78
|
return AWAIT_DOWNLOAD_MAX_TIMEOUT_MILLIS;
|
|
52
79
|
}
|
|
53
|
-
async function awaitDownload(downloadDirectory,
|
|
80
|
+
async function awaitDownload(downloadDirectory, fileMatcher, timeoutMillis = getAwaitDownloadTimeout()) {
|
|
54
81
|
const startTime = Date.now();
|
|
55
82
|
let foundDownloadPath;
|
|
56
83
|
let downloadComplete = false;
|
|
57
84
|
while (Date.now() - timeoutMillis < startTime && !downloadComplete) {
|
|
58
|
-
const latestFile = await getLatestFile(downloadDirectory,
|
|
85
|
+
const latestFile = await getLatestFile(downloadDirectory, fileMatcher);
|
|
59
86
|
if (latestFile) {
|
|
60
87
|
downloadComplete = true;
|
|
61
88
|
foundDownloadPath = latestFile;
|
|
@@ -66,7 +93,10 @@ async function awaitDownload(downloadDirectory, filePathSuffix, timeoutMillis =
|
|
|
66
93
|
}
|
|
67
94
|
return foundDownloadPath;
|
|
68
95
|
}
|
|
69
|
-
async function getLatestFile(downloadDirectory,
|
|
96
|
+
async function getLatestFile(downloadDirectory, fileMatcher) {
|
|
97
|
+
const matchFn = typeof fileMatcher === 'string'
|
|
98
|
+
? (fullPath) => fullPath.toLowerCase().endsWith(fileMatcher.toLowerCase())
|
|
99
|
+
: fileMatcher;
|
|
70
100
|
const readDirectoryAsync = util.promisify(fs.readdir);
|
|
71
101
|
const filesInDownloadDir = await readDirectoryAsync(downloadDirectory);
|
|
72
102
|
let latestFileTime = 0;
|
|
@@ -79,7 +109,7 @@ async function getLatestFile(downloadDirectory, filePathSuffix) {
|
|
|
79
109
|
stats.ctimeMs > latestFileTime &&
|
|
80
110
|
Date.now() - stats.mtimeMs >= FILE_MIN_STABLE_TIME_MILLIS &&
|
|
81
111
|
!fullPath.includes('.crDownload') &&
|
|
82
|
-
fullPath
|
|
112
|
+
matchFn(fullPath)) {
|
|
83
113
|
latestFileTime = stats.ctimeMs;
|
|
84
114
|
latestFilePath = fullPath;
|
|
85
115
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.evaluateFileNameMatch = evaluateFileNameMatch;
|
|
4
|
+
function evaluateFileNameMatch(actualFileName, expectedPattern, mode) {
|
|
5
|
+
if (!expectedPattern) {
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
switch (mode) {
|
|
9
|
+
case 'contains':
|
|
10
|
+
return actualFileName.includes(expectedPattern);
|
|
11
|
+
case 'starts_with':
|
|
12
|
+
return actualFileName.startsWith(expectedPattern);
|
|
13
|
+
case 'ends_with':
|
|
14
|
+
return actualFileName.endsWith(expectedPattern);
|
|
15
|
+
case 'matches_regex': {
|
|
16
|
+
try {
|
|
17
|
+
return new RegExp(expectedPattern).test(actualFileName);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
case 'equals':
|
|
24
|
+
default:
|
|
25
|
+
return actualFileName === expectedPattern;
|
|
26
|
+
}
|
|
27
|
+
}
|