@sasjs/cli 6.6.0 → 6.8.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/README.md +2 -1
- package/build/commands/flow/flowCommand.js +11 -1
- package/build/commands/flow/internal/executeFlow.js +44 -19
- package/build/commands/flow/internal/saveLog.d.ts +12 -1
- package/build/commands/flow/internal/saveLog.js +2 -2
- package/build/commands/flow/internal/spec/executeFlow.spec.js +44 -1
- package/build/commands/flow/spec/flowCommand.spec.js +44 -0
- package/build/commands/help/help.js +10 -0
- package/build/commands/index.d.ts +1 -0
- package/build/commands/index.js +3 -1
- package/build/commands/job/internal/execute/sas9.js +16 -13
- package/build/commands/job/internal/execute/sasjs.js +12 -9
- package/build/commands/job/internal/execute/viya.js +41 -36
- package/build/commands/job/jobCommand.js +13 -0
- package/build/commands/job/spec/executeLogOrder.spec.d.ts +1 -0
- package/build/commands/job/spec/executeLogOrder.spec.js +119 -0
- package/build/commands/job/spec/jobCommand.spec.js +32 -0
- package/build/commands/logcheck/collectLogs.d.ts +60 -0
- package/build/commands/logcheck/collectLogs.js +386 -0
- package/build/commands/logcheck/logCheckConfig.d.ts +83 -0
- package/build/commands/logcheck/logCheckConfig.js +343 -0
- package/build/commands/logcheck/logcheckCommand.d.ts +6 -0
- package/build/commands/logcheck/logcheckCommand.js +200 -0
- package/build/commands/logcheck/processLogCheck.d.ts +44 -0
- package/build/commands/logcheck/processLogCheck.js +242 -0
- package/build/commands/logcheck/renderLogCheck.d.ts +129 -0
- package/build/commands/logcheck/renderLogCheck.js +424 -0
- package/build/commands/logcheck/spec/collectLogs.spec.d.ts +1 -0
- package/build/commands/logcheck/spec/collectLogs.spec.js +477 -0
- package/build/commands/logcheck/spec/logCheckConfig.spec.d.ts +1 -0
- package/build/commands/logcheck/spec/logCheckConfig.spec.js +384 -0
- package/build/commands/logcheck/spec/logcheckCommand.spec.d.ts +1 -0
- package/build/commands/logcheck/spec/logcheckCommand.spec.js +360 -0
- package/build/commands/logcheck/spec/processLogCheck.spec.d.ts +1 -0
- package/build/commands/logcheck/spec/processLogCheck.spec.js +354 -0
- package/build/commands/logcheck/spec/renderLogCheck.spec.d.ts +1 -0
- package/build/commands/logcheck/spec/renderLogCheck.spec.js +426 -0
- package/build/commands/logcheck/types.d.ts +106 -0
- package/build/commands/logcheck/types.js +14 -0
- package/build/commands/request/request.js +66 -27
- package/build/commands/request/requestCommand.js +11 -1
- package/build/commands/request/spec/requestCommand.spec.js +40 -0
- package/build/commands/run/run.d.ts +1 -3
- package/build/commands/run/run.js +20 -5
- package/build/commands/run/runCommand.js +9 -0
- package/build/commands/run/spec/runCommand.spec.js +50 -0
- package/build/types/command/commandAliases.js +1 -0
- package/build/types/command/commandFactory.js +2 -0
- package/build/types/command/returnCode.d.ts +2 -1
- package/build/types/command/returnCode.js +4 -0
- package/build/utils/index.d.ts +1 -0
- package/build/utils/index.js +1 -0
- package/build/utils/logcheck.d.ts +44 -0
- package/build/utils/logcheck.js +224 -0
- package/build/utils/spec/logcheck.spec.d.ts +1 -0
- package/build/utils/spec/logcheck.spec.js +297 -0
- package/package.json +3 -3
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __assign = (this && this.__assign) || function () {
|
|
3
|
+
__assign = Object.assign || function(t) {
|
|
4
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5
|
+
s = arguments[i];
|
|
6
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
7
|
+
t[p] = s[p];
|
|
8
|
+
}
|
|
9
|
+
return t;
|
|
10
|
+
};
|
|
11
|
+
return __assign.apply(this, arguments);
|
|
12
|
+
};
|
|
13
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
14
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
15
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
16
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
17
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
18
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
19
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
23
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
24
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
25
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
26
|
+
function step(op) {
|
|
27
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
28
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
29
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
30
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
31
|
+
switch (op[0]) {
|
|
32
|
+
case 0: case 1: t = op; break;
|
|
33
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
34
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
35
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
36
|
+
default:
|
|
37
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
38
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
39
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
40
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
41
|
+
if (t[2]) _.ops.pop();
|
|
42
|
+
_.trys.pop(); continue;
|
|
43
|
+
}
|
|
44
|
+
op = body.call(thisArg, _);
|
|
45
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
46
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
var __values = (this && this.__values) || function(o) {
|
|
50
|
+
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
|
51
|
+
if (m) return m.call(o);
|
|
52
|
+
if (o && typeof o.length === "number") return {
|
|
53
|
+
next: function () {
|
|
54
|
+
if (o && i >= o.length) o = void 0;
|
|
55
|
+
return { value: o && o[i++], done: !o };
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
59
|
+
};
|
|
60
|
+
var __read = (this && this.__read) || function (o, n) {
|
|
61
|
+
var m = typeof Symbol === "function" && o[Symbol.iterator];
|
|
62
|
+
if (!m) return o;
|
|
63
|
+
var i = m.call(o), r, ar = [], e;
|
|
64
|
+
try {
|
|
65
|
+
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
|
|
66
|
+
}
|
|
67
|
+
catch (error) { e = { error: error }; }
|
|
68
|
+
finally {
|
|
69
|
+
try {
|
|
70
|
+
if (r && !r.done && (m = i["return"])) m.call(i);
|
|
71
|
+
}
|
|
72
|
+
finally { if (e) throw e.error; }
|
|
73
|
+
}
|
|
74
|
+
return ar;
|
|
75
|
+
};
|
|
76
|
+
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
77
|
+
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
78
|
+
if (ar || !(i in from)) {
|
|
79
|
+
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
80
|
+
ar[i] = from[i];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return to.concat(ar || Array.prototype.slice.call(from));
|
|
84
|
+
};
|
|
85
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
86
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
87
|
+
};
|
|
88
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
89
|
+
exports.toSkippedFile = exports.countIssues = exports.partitionIssues = exports.scanLogFile = exports.toDisplayPath = exports.collectLogFiles = exports.MAX_LOG_BYTES = void 0;
|
|
90
|
+
var path_1 = __importDefault(require("path"));
|
|
91
|
+
var promises_1 = require("fs/promises");
|
|
92
|
+
var logs_1 = require("@sasjs/utils/logs");
|
|
93
|
+
var logCheckConfig_1 = require("./logCheckConfig");
|
|
94
|
+
var types_1 = require("./types");
|
|
95
|
+
/** A log bigger than this is reported and skipped rather than read. */
|
|
96
|
+
exports.MAX_LOG_BYTES = 25 * 1024 * 1024;
|
|
97
|
+
/** Folders never walked when a directory is given. */
|
|
98
|
+
var IGNORED_FOLDERS = ['node_modules', '.git'];
|
|
99
|
+
/**
|
|
100
|
+
* Expands the paths a user named into the files to read.
|
|
101
|
+
*
|
|
102
|
+
* A file is taken as it is; a folder is walked, because the reason a user
|
|
103
|
+
* points at `sasjsresults` is that they do not know which run left which log.
|
|
104
|
+
* Order is sorted so that two runs over the same folder produce the same
|
|
105
|
+
* report, which is what makes a diff of two reports readable.
|
|
106
|
+
*/
|
|
107
|
+
var collectLogFiles = function (paths) { return __awaiter(void 0, void 0, void 0, function () {
|
|
108
|
+
var files, missing, unreadable, paths_1, paths_1_1, candidate, resolved, info, _a, _b, found, e_1_1, e_2_1;
|
|
109
|
+
var e_2, _c, e_1, _d;
|
|
110
|
+
return __generator(this, function (_e) {
|
|
111
|
+
switch (_e.label) {
|
|
112
|
+
case 0:
|
|
113
|
+
files = new Set();
|
|
114
|
+
missing = [];
|
|
115
|
+
unreadable = [];
|
|
116
|
+
_e.label = 1;
|
|
117
|
+
case 1:
|
|
118
|
+
_e.trys.push([1, 15, 16, 17]);
|
|
119
|
+
paths_1 = __values(paths), paths_1_1 = paths_1.next();
|
|
120
|
+
_e.label = 2;
|
|
121
|
+
case 2:
|
|
122
|
+
if (!!paths_1_1.done) return [3 /*break*/, 14];
|
|
123
|
+
candidate = paths_1_1.value;
|
|
124
|
+
resolved = path_1.default.resolve(candidate);
|
|
125
|
+
return [4 /*yield*/, (0, promises_1.stat)(resolved).catch(function () { return undefined; })];
|
|
126
|
+
case 3:
|
|
127
|
+
info = _e.sent();
|
|
128
|
+
if (!info) {
|
|
129
|
+
missing.push(candidate);
|
|
130
|
+
return [3 /*break*/, 13];
|
|
131
|
+
}
|
|
132
|
+
if (!info.isDirectory()) return [3 /*break*/, 12];
|
|
133
|
+
_e.label = 4;
|
|
134
|
+
case 4:
|
|
135
|
+
_e.trys.push([4, 9, 10, 11]);
|
|
136
|
+
e_1 = void 0;
|
|
137
|
+
return [4 /*yield*/, walk(resolved, unreadable)];
|
|
138
|
+
case 5:
|
|
139
|
+
_a = (__values.apply(void 0, [_e.sent()])), _b = _a.next();
|
|
140
|
+
_e.label = 6;
|
|
141
|
+
case 6:
|
|
142
|
+
if (!!_b.done) return [3 /*break*/, 8];
|
|
143
|
+
found = _b.value;
|
|
144
|
+
files.add(found);
|
|
145
|
+
_e.label = 7;
|
|
146
|
+
case 7:
|
|
147
|
+
_b = _a.next();
|
|
148
|
+
return [3 /*break*/, 6];
|
|
149
|
+
case 8: return [3 /*break*/, 11];
|
|
150
|
+
case 9:
|
|
151
|
+
e_1_1 = _e.sent();
|
|
152
|
+
e_1 = { error: e_1_1 };
|
|
153
|
+
return [3 /*break*/, 11];
|
|
154
|
+
case 10:
|
|
155
|
+
try {
|
|
156
|
+
if (_b && !_b.done && (_d = _a.return)) _d.call(_a);
|
|
157
|
+
}
|
|
158
|
+
finally { if (e_1) throw e_1.error; }
|
|
159
|
+
return [7 /*endfinally*/];
|
|
160
|
+
case 11: return [3 /*break*/, 13];
|
|
161
|
+
case 12:
|
|
162
|
+
files.add(resolved);
|
|
163
|
+
_e.label = 13;
|
|
164
|
+
case 13:
|
|
165
|
+
paths_1_1 = paths_1.next();
|
|
166
|
+
return [3 /*break*/, 2];
|
|
167
|
+
case 14: return [3 /*break*/, 17];
|
|
168
|
+
case 15:
|
|
169
|
+
e_2_1 = _e.sent();
|
|
170
|
+
e_2 = { error: e_2_1 };
|
|
171
|
+
return [3 /*break*/, 17];
|
|
172
|
+
case 16:
|
|
173
|
+
try {
|
|
174
|
+
if (paths_1_1 && !paths_1_1.done && (_c = paths_1.return)) _c.call(paths_1);
|
|
175
|
+
}
|
|
176
|
+
finally { if (e_2) throw e_2.error; }
|
|
177
|
+
return [7 /*endfinally*/];
|
|
178
|
+
case 17: return [2 /*return*/, {
|
|
179
|
+
files: Array.from(files).sort(function (a, b) { return a.localeCompare(b); }),
|
|
180
|
+
missing: missing,
|
|
181
|
+
unreadable: unreadable
|
|
182
|
+
}];
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}); };
|
|
186
|
+
exports.collectLogFiles = collectLogFiles;
|
|
187
|
+
/**
|
|
188
|
+
* Recursively lists the files under a folder, recording any folder it cannot
|
|
189
|
+
* read rather than treating it as empty.
|
|
190
|
+
*
|
|
191
|
+
* Symlinks are not followed: a link back up the tree would make the walk
|
|
192
|
+
* endless, and a log reached through a link is not something to report on
|
|
193
|
+
* twice.
|
|
194
|
+
*/
|
|
195
|
+
var walk = function (folder_1) {
|
|
196
|
+
var args_1 = [];
|
|
197
|
+
for (var _i = 1; _i < arguments.length; _i++) {
|
|
198
|
+
args_1[_i - 1] = arguments[_i];
|
|
199
|
+
}
|
|
200
|
+
return __awaiter(void 0, __spreadArray([folder_1], __read(args_1), false), void 0, function (folder, unreadable) {
|
|
201
|
+
var entries, found, entries_1, entries_1_1, entry, fullPath, _a, _b, _c, _d, e_3_1;
|
|
202
|
+
var e_3, _e;
|
|
203
|
+
if (unreadable === void 0) { unreadable = []; }
|
|
204
|
+
return __generator(this, function (_f) {
|
|
205
|
+
switch (_f.label) {
|
|
206
|
+
case 0: return [4 /*yield*/, (0, promises_1.readdir)(folder, { withFileTypes: true }).catch(function (error) {
|
|
207
|
+
unreadable.push({
|
|
208
|
+
displayPath: (0, exports.toDisplayPath)(folder),
|
|
209
|
+
reason: (error === null || error === void 0 ? void 0 : error.code)
|
|
210
|
+
? "could not be listed (".concat(error.code, ")")
|
|
211
|
+
: 'could not be listed'
|
|
212
|
+
});
|
|
213
|
+
return [];
|
|
214
|
+
})];
|
|
215
|
+
case 1:
|
|
216
|
+
entries = _f.sent();
|
|
217
|
+
found = [];
|
|
218
|
+
_f.label = 2;
|
|
219
|
+
case 2:
|
|
220
|
+
_f.trys.push([2, 8, 9, 10]);
|
|
221
|
+
entries_1 = __values(entries), entries_1_1 = entries_1.next();
|
|
222
|
+
_f.label = 3;
|
|
223
|
+
case 3:
|
|
224
|
+
if (!!entries_1_1.done) return [3 /*break*/, 7];
|
|
225
|
+
entry = entries_1_1.value;
|
|
226
|
+
if (entry.isSymbolicLink())
|
|
227
|
+
return [3 /*break*/, 6];
|
|
228
|
+
fullPath = path_1.default.join(folder, entry.name);
|
|
229
|
+
if (!entry.isDirectory()) return [3 /*break*/, 5];
|
|
230
|
+
if (IGNORED_FOLDERS.includes(entry.name))
|
|
231
|
+
return [3 /*break*/, 6];
|
|
232
|
+
_b = (_a = found.push).apply;
|
|
233
|
+
_c = [found];
|
|
234
|
+
_d = [[]];
|
|
235
|
+
return [4 /*yield*/, walk(fullPath, unreadable)];
|
|
236
|
+
case 4:
|
|
237
|
+
_b.apply(_a, _c.concat([__spreadArray.apply(void 0, _d.concat([__read.apply(void 0, [(_f.sent())]), false]))]));
|
|
238
|
+
return [3 /*break*/, 6];
|
|
239
|
+
case 5:
|
|
240
|
+
if (entry.isFile())
|
|
241
|
+
found.push(fullPath);
|
|
242
|
+
_f.label = 6;
|
|
243
|
+
case 6:
|
|
244
|
+
entries_1_1 = entries_1.next();
|
|
245
|
+
return [3 /*break*/, 3];
|
|
246
|
+
case 7: return [3 /*break*/, 10];
|
|
247
|
+
case 8:
|
|
248
|
+
e_3_1 = _f.sent();
|
|
249
|
+
e_3 = { error: e_3_1 };
|
|
250
|
+
return [3 /*break*/, 10];
|
|
251
|
+
case 9:
|
|
252
|
+
try {
|
|
253
|
+
if (entries_1_1 && !entries_1_1.done && (_e = entries_1.return)) _e.call(entries_1);
|
|
254
|
+
}
|
|
255
|
+
finally { if (e_3) throw e_3.error; }
|
|
256
|
+
return [7 /*endfinally*/];
|
|
257
|
+
case 10: return [2 /*return*/, found];
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
};
|
|
262
|
+
/**
|
|
263
|
+
* The path a report shows for a file: relative to the project folder where
|
|
264
|
+
* that is shorter and does not climb out of it, absolute otherwise.
|
|
265
|
+
*
|
|
266
|
+
* Forward slashes throughout, so a report generated on Windows and one
|
|
267
|
+
* generated on Linux describe the same log the same way, and so a path can be
|
|
268
|
+
* dropped into a CI annotation without escaping.
|
|
269
|
+
*/
|
|
270
|
+
var toDisplayPath = function (filePath) {
|
|
271
|
+
var base = process.projectDir || process.cwd();
|
|
272
|
+
var relative = path_1.default.relative(base, filePath);
|
|
273
|
+
return relative && !relative.startsWith('..')
|
|
274
|
+
? (0, logCheckConfig_1.toPosix)(relative)
|
|
275
|
+
: (0, logCheckConfig_1.toPosix)(filePath);
|
|
276
|
+
};
|
|
277
|
+
exports.toDisplayPath = toDisplayPath;
|
|
278
|
+
/**
|
|
279
|
+
* Parses one log and splits its issues into the ones reported and the ones
|
|
280
|
+
* suppressed by message code.
|
|
281
|
+
*
|
|
282
|
+
* A file that is not a SAS log is skipped rather than reported as clean: a
|
|
283
|
+
* Python traceback and a shell build log both begin lines with `ERROR:`, and
|
|
284
|
+
* `isSasLog` is what tells them from the real thing.
|
|
285
|
+
*/
|
|
286
|
+
var scanLogFile = function (filePath, config) { return __awaiter(void 0, void 0, void 0, function () {
|
|
287
|
+
var displayPath, blank, info, text, _a, issues, suppressed;
|
|
288
|
+
return __generator(this, function (_b) {
|
|
289
|
+
switch (_b.label) {
|
|
290
|
+
case 0:
|
|
291
|
+
displayPath = (0, exports.toDisplayPath)(filePath);
|
|
292
|
+
blank = {
|
|
293
|
+
filePath: filePath,
|
|
294
|
+
displayPath: displayPath,
|
|
295
|
+
issues: [],
|
|
296
|
+
suppressed: [],
|
|
297
|
+
counts: (0, types_1.emptyCounts)()
|
|
298
|
+
};
|
|
299
|
+
return [4 /*yield*/, (0, promises_1.stat)(filePath).catch(function () { return undefined; })];
|
|
300
|
+
case 1:
|
|
301
|
+
info = _b.sent();
|
|
302
|
+
if (!info) {
|
|
303
|
+
return [2 /*return*/, __assign(__assign({}, blank), { skippedReason: 'could not be read' })];
|
|
304
|
+
}
|
|
305
|
+
if (!info.isFile()) {
|
|
306
|
+
return [2 /*return*/, __assign(__assign({}, blank), { skippedReason: 'not a file' })];
|
|
307
|
+
}
|
|
308
|
+
if (info.size > exports.MAX_LOG_BYTES) {
|
|
309
|
+
return [2 /*return*/, __assign(__assign({}, blank), { skippedReason: "larger than ".concat(Math.round(exports.MAX_LOG_BYTES / (1024 * 1024)), " MB") })];
|
|
310
|
+
}
|
|
311
|
+
return [4 /*yield*/, (0, promises_1.readFile)(filePath, 'utf8').catch(function () { return undefined; })];
|
|
312
|
+
case 2:
|
|
313
|
+
text = _b.sent();
|
|
314
|
+
if (text === undefined) {
|
|
315
|
+
return [2 /*return*/, __assign(__assign({}, blank), { skippedReason: 'could not be read' })];
|
|
316
|
+
}
|
|
317
|
+
if (!(0, logs_1.isSasLog)(text)) {
|
|
318
|
+
return [2 /*return*/, __assign(__assign({}, blank), { skippedReason: 'not a SAS log' })];
|
|
319
|
+
}
|
|
320
|
+
_a = (0, exports.partitionIssues)((0, logs_1.parseLog)(text), config.ignoreCodes), issues = _a.issues, suppressed = _a.suppressed;
|
|
321
|
+
return [2 /*return*/, __assign(__assign({}, blank), { issues: issues, suppressed: suppressed, counts: (0, exports.countIssues)(issues) })];
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
}); };
|
|
325
|
+
exports.scanLogFile = scanLogFile;
|
|
326
|
+
/**
|
|
327
|
+
* Splits parsed issues by the suppressed message codes.
|
|
328
|
+
*
|
|
329
|
+
* Matching is on the code alone, because that is what SAS keeps stable: the
|
|
330
|
+
* text of a note carries values from the data and changes between runs, while
|
|
331
|
+
* `49-169` means the same thing every time.
|
|
332
|
+
*/
|
|
333
|
+
var partitionIssues = function (issues, ignoreCodes) {
|
|
334
|
+
var e_4, _a;
|
|
335
|
+
var codes = new Set(ignoreCodes.map(function (code) { return code.trim(); }));
|
|
336
|
+
var kept = [];
|
|
337
|
+
var suppressed = [];
|
|
338
|
+
try {
|
|
339
|
+
for (var issues_1 = __values(issues), issues_1_1 = issues_1.next(); !issues_1_1.done; issues_1_1 = issues_1.next()) {
|
|
340
|
+
var issue = issues_1_1.value;
|
|
341
|
+
if (issue.code && codes.has(issue.code))
|
|
342
|
+
suppressed.push(issue);
|
|
343
|
+
else
|
|
344
|
+
kept.push(issue);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
catch (e_4_1) { e_4 = { error: e_4_1 }; }
|
|
348
|
+
finally {
|
|
349
|
+
try {
|
|
350
|
+
if (issues_1_1 && !issues_1_1.done && (_a = issues_1.return)) _a.call(issues_1);
|
|
351
|
+
}
|
|
352
|
+
finally { if (e_4) throw e_4.error; }
|
|
353
|
+
}
|
|
354
|
+
return { issues: kept, suppressed: suppressed };
|
|
355
|
+
};
|
|
356
|
+
exports.partitionIssues = partitionIssues;
|
|
357
|
+
var countIssues = function (issues) {
|
|
358
|
+
var e_5, _a;
|
|
359
|
+
var counts = (0, types_1.emptyCounts)();
|
|
360
|
+
try {
|
|
361
|
+
for (var issues_2 = __values(issues), issues_2_1 = issues_2.next(); !issues_2_1.done; issues_2_1 = issues_2.next()) {
|
|
362
|
+
var issue = issues_2_1.value;
|
|
363
|
+
if (issue.severity === 'error')
|
|
364
|
+
counts.errors++;
|
|
365
|
+
else if (issue.severity === 'warning')
|
|
366
|
+
counts.warnings++;
|
|
367
|
+
else
|
|
368
|
+
counts.notes++;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
catch (e_5_1) { e_5 = { error: e_5_1 }; }
|
|
372
|
+
finally {
|
|
373
|
+
try {
|
|
374
|
+
if (issues_2_1 && !issues_2_1.done && (_a = issues_2.return)) _a.call(issues_2);
|
|
375
|
+
}
|
|
376
|
+
finally { if (e_5) throw e_5.error; }
|
|
377
|
+
}
|
|
378
|
+
return counts;
|
|
379
|
+
};
|
|
380
|
+
exports.countIssues = countIssues;
|
|
381
|
+
/** A file the check declined to open, as the report shows it. */
|
|
382
|
+
var toSkippedFile = function (log) { return ({
|
|
383
|
+
displayPath: log.displayPath,
|
|
384
|
+
reason: log.skippedReason || 'skipped'
|
|
385
|
+
}); };
|
|
386
|
+
exports.toSkippedFile = toSkippedFile;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { LogCheckConfig } from './types';
|
|
2
|
+
/** The section of `sasjsconfig.json` this command reads. */
|
|
3
|
+
export declare const CONFIG_KEY = "logCheck";
|
|
4
|
+
/** Folder checked when the command is given no path of its own. */
|
|
5
|
+
export declare const DEFAULT_FOLDER = "sasjsresults";
|
|
6
|
+
/**
|
|
7
|
+
* Builds a config from an untrusted `logCheck` section.
|
|
8
|
+
*
|
|
9
|
+
* Values a user gets wrong are refused rather than ignored: a typo in
|
|
10
|
+
* `failOn` that silently fell back to the default would leave a gate that
|
|
11
|
+
* never fails, which is the one failure mode a quality gate must not have.
|
|
12
|
+
*
|
|
13
|
+
* @param section - the parsed `logCheck` object from `sasjsconfig.json`.
|
|
14
|
+
* @param source - the file it came from, named in the error message.
|
|
15
|
+
*/
|
|
16
|
+
export declare const parseLogCheckSection: (section: unknown, source?: string) => LogCheckConfig;
|
|
17
|
+
/**
|
|
18
|
+
* Where a project's config may be, in the order they are tried.
|
|
19
|
+
*
|
|
20
|
+
* `sasjs/sasjsconfig.json` is the SASjs convention and the one the rest of the
|
|
21
|
+
* CLI reads, so it wins. A `sasjsconfig.json` at the project root is still
|
|
22
|
+
* honoured, because a folder holding a single log and a hand-written config is
|
|
23
|
+
* exactly the case this command is meant to serve - but the two are never
|
|
24
|
+
* confused for each other, and the convention cannot be shadowed by a stray
|
|
25
|
+
* file next to it.
|
|
26
|
+
*/
|
|
27
|
+
export declare const configCandidates: (projectDir: string) => string[];
|
|
28
|
+
/**
|
|
29
|
+
* Reads the `logCheck` policy from the project's `sasjsconfig.json`.
|
|
30
|
+
*
|
|
31
|
+
* A missing file, or one without the section, is not an error - the command
|
|
32
|
+
* has to work in a folder that is not a SASjs project at all, which is the
|
|
33
|
+
* whole point of checking a log somebody else produced.
|
|
34
|
+
*/
|
|
35
|
+
export declare const readLogCheckConfig: () => Promise<LogCheckConfig>;
|
|
36
|
+
/** Flags a user can set on the command line, all optional. */
|
|
37
|
+
export interface ConfigOverrides {
|
|
38
|
+
failOn?: string;
|
|
39
|
+
maxWarnings?: number;
|
|
40
|
+
ignoreCodes?: string[];
|
|
41
|
+
ignorePaths?: string[];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Layers the command line over the config file. Suppressions are added, never
|
|
45
|
+
* replaced: a flag silences one more code for one run, it does not un-silence
|
|
46
|
+
* the codes a team has already agreed to accept.
|
|
47
|
+
*/
|
|
48
|
+
export declare const mergeConfig: (base: LogCheckConfig, overrides?: ConfigOverrides) => LogCheckConfig;
|
|
49
|
+
/**
|
|
50
|
+
* Whether a log path is excluded by one of the `ignorePaths` entries.
|
|
51
|
+
*
|
|
52
|
+
* An entry with no wildcard is matched against whole path segments, not as a
|
|
53
|
+
* bare substring of the text: `test` covers `test/run.log` and
|
|
54
|
+
* `sasjsresults/test/run.log`, but not `latest.log`, and `logs` does not cover
|
|
55
|
+
* `nested-logs/x.log`. A suppression rule that quietly covers files the author
|
|
56
|
+
* did not name is the failure mode this whole config guards against, so an
|
|
57
|
+
* entry has to name a path, not a fragment of one.
|
|
58
|
+
*
|
|
59
|
+
* An entry with a wildcard is a glob over the display path, anchored at both
|
|
60
|
+
* ends, where `**/` crosses folders and `*` does not.
|
|
61
|
+
*/
|
|
62
|
+
export declare const matchesPath: (displayPath: string, pattern: string) => boolean;
|
|
63
|
+
/**
|
|
64
|
+
* A non-glob entry matches when it names the path itself, or a run of whole
|
|
65
|
+
* segments inside it.
|
|
66
|
+
*
|
|
67
|
+
* Padding both sides with a separator makes one `includes` do the work: the
|
|
68
|
+
* needle can only be found where it begins at a segment boundary and ends at
|
|
69
|
+
* one, which is exactly the difference between `test` and `latest`.
|
|
70
|
+
*/
|
|
71
|
+
export declare const matchesSegments: (subject: string, candidate: string) => boolean;
|
|
72
|
+
export declare const isIgnoredPath: (displayPath: string, patterns: string[]) => boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Compiles a glob into an expression anchored at both ends.
|
|
75
|
+
*
|
|
76
|
+
* `**/` at the start matches zero or more leading folders so that
|
|
77
|
+
* `**/build.log` covers `build.log` in the project folder as well as one
|
|
78
|
+
* several folders down - a pattern that only matched the deep case would make
|
|
79
|
+
* the obvious spelling useless.
|
|
80
|
+
*/
|
|
81
|
+
export declare const globToRegExp: (pattern: string) => RegExp;
|
|
82
|
+
/** Path text as it appears in a report: forward slashes, no wrapping quotes. */
|
|
83
|
+
export declare const toPosix: (value: string) => string;
|