@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.
Files changed (57) hide show
  1. package/README.md +2 -1
  2. package/build/commands/flow/flowCommand.js +11 -1
  3. package/build/commands/flow/internal/executeFlow.js +44 -19
  4. package/build/commands/flow/internal/saveLog.d.ts +12 -1
  5. package/build/commands/flow/internal/saveLog.js +2 -2
  6. package/build/commands/flow/internal/spec/executeFlow.spec.js +44 -1
  7. package/build/commands/flow/spec/flowCommand.spec.js +44 -0
  8. package/build/commands/help/help.js +10 -0
  9. package/build/commands/index.d.ts +1 -0
  10. package/build/commands/index.js +3 -1
  11. package/build/commands/job/internal/execute/sas9.js +16 -13
  12. package/build/commands/job/internal/execute/sasjs.js +12 -9
  13. package/build/commands/job/internal/execute/viya.js +41 -36
  14. package/build/commands/job/jobCommand.js +13 -0
  15. package/build/commands/job/spec/executeLogOrder.spec.d.ts +1 -0
  16. package/build/commands/job/spec/executeLogOrder.spec.js +119 -0
  17. package/build/commands/job/spec/jobCommand.spec.js +32 -0
  18. package/build/commands/logcheck/collectLogs.d.ts +60 -0
  19. package/build/commands/logcheck/collectLogs.js +386 -0
  20. package/build/commands/logcheck/logCheckConfig.d.ts +83 -0
  21. package/build/commands/logcheck/logCheckConfig.js +343 -0
  22. package/build/commands/logcheck/logcheckCommand.d.ts +6 -0
  23. package/build/commands/logcheck/logcheckCommand.js +200 -0
  24. package/build/commands/logcheck/processLogCheck.d.ts +44 -0
  25. package/build/commands/logcheck/processLogCheck.js +242 -0
  26. package/build/commands/logcheck/renderLogCheck.d.ts +129 -0
  27. package/build/commands/logcheck/renderLogCheck.js +424 -0
  28. package/build/commands/logcheck/spec/collectLogs.spec.d.ts +1 -0
  29. package/build/commands/logcheck/spec/collectLogs.spec.js +477 -0
  30. package/build/commands/logcheck/spec/logCheckConfig.spec.d.ts +1 -0
  31. package/build/commands/logcheck/spec/logCheckConfig.spec.js +384 -0
  32. package/build/commands/logcheck/spec/logcheckCommand.spec.d.ts +1 -0
  33. package/build/commands/logcheck/spec/logcheckCommand.spec.js +360 -0
  34. package/build/commands/logcheck/spec/processLogCheck.spec.d.ts +1 -0
  35. package/build/commands/logcheck/spec/processLogCheck.spec.js +354 -0
  36. package/build/commands/logcheck/spec/renderLogCheck.spec.d.ts +1 -0
  37. package/build/commands/logcheck/spec/renderLogCheck.spec.js +426 -0
  38. package/build/commands/logcheck/types.d.ts +106 -0
  39. package/build/commands/logcheck/types.js +14 -0
  40. package/build/commands/request/request.js +66 -27
  41. package/build/commands/request/requestCommand.js +11 -1
  42. package/build/commands/request/spec/requestCommand.spec.js +40 -0
  43. package/build/commands/run/run.d.ts +1 -3
  44. package/build/commands/run/run.js +20 -5
  45. package/build/commands/run/runCommand.js +9 -0
  46. package/build/commands/run/spec/runCommand.spec.js +50 -0
  47. package/build/types/command/commandAliases.js +1 -0
  48. package/build/types/command/commandFactory.js +2 -0
  49. package/build/types/command/returnCode.d.ts +2 -1
  50. package/build/types/command/returnCode.js +4 -0
  51. package/build/utils/index.d.ts +1 -0
  52. package/build/utils/index.js +1 -0
  53. package/build/utils/logcheck.d.ts +44 -0
  54. package/build/utils/logcheck.js +224 -0
  55. package/build/utils/spec/logcheck.spec.d.ts +1 -0
  56. package/build/utils/spec/logcheck.spec.js +297 -0
  57. package/package.json +3 -3
@@ -44,12 +44,14 @@ exports.getContextName = getContextName;
44
44
  var displayResult_1 = require("../../../../utils/displayResult");
45
45
  var file_1 = require("../../../../utils/file");
46
46
  var fetchLogFileContent_1 = require("../../../shared/fetchLogFileContent");
47
+ var logcheck_1 = require("../../../../utils/logcheck");
48
+ var utils_1 = require("../../../../utils/utils");
47
49
  var path_1 = __importDefault(require("path"));
48
50
  var node_1 = require("@sasjs/adapter/node");
49
- var utils_1 = require("@sasjs/utils");
50
- var utils_2 = require("../../../../utils/");
51
+ var utils_2 = require("@sasjs/utils");
52
+ var utils_3 = require("../../../../utils/");
51
53
  var command_1 = require("../../../../types/command");
52
- var utils_3 = require("../utils");
54
+ var utils_4 = require("../utils");
53
55
  /**
54
56
  * Triggers existing job for execution on Viya server.
55
57
  * @param {object} sasjs - configuration object of SAS adapter.
@@ -90,23 +92,23 @@ function executeJobViya(sasjs, authConfig, jobPath, target, waitForJob, output,
90
92
  if (!source) return [3 /*break*/, 5];
91
93
  if (!(0, file_1.isJsonFile)(source))
92
94
  throw 'Source file has to be JSON.';
93
- return [4 /*yield*/, (0, utils_1.fileExists)(source).catch(function (_) {
95
+ return [4 /*yield*/, (0, utils_2.fileExists)(source).catch(function (_) {
94
96
  throw 'Error while checking if source file exists.';
95
97
  })];
96
98
  case 3:
97
99
  _c.sent();
98
- return [4 /*yield*/, (0, utils_1.readFile)(source).catch(function (_) {
100
+ return [4 /*yield*/, (0, utils_2.readFile)(source).catch(function (_) {
99
101
  throw 'Error while reading source file.';
100
102
  })];
101
103
  case 4:
102
104
  source = _c.sent();
103
105
  macroVars = JSON.parse(source);
104
- if (!(0, utils_1.isMacroVars)(macroVars)) {
106
+ if (!(0, utils_2.isMacroVars)(macroVars)) {
105
107
  throw "Provided source is not valid. An example of valid source:\n{ macroVars: { name1: 'value1', name2: 'value2' } }";
106
108
  }
107
109
  _c.label = 5;
108
110
  case 5: return [4 /*yield*/, sasjs
109
- .startComputeJob(jobPath, null, { contextName: contextName }, authConfig, waitForJob || !!logFile, pollOptions, true, macroVars === null || macroVars === void 0 ? void 0 : macroVars.macroVars, undefined, (0, utils_2.persistTokensRefreshedByAdapter)(target))
111
+ .startComputeJob(jobPath, null, { contextName: contextName }, authConfig, waitForJob || !!logFile, pollOptions, true, macroVars === null || macroVars === void 0 ? void 0 : macroVars.macroVars, undefined, (0, utils_3.persistTokensRefreshedByAdapter)(target))
110
112
  .catch(function (err) { return __awaiter(_this, void 0, void 0, function () {
111
113
  var logData;
112
114
  var _a, _b;
@@ -121,7 +123,7 @@ function executeJobViya(sasjs, authConfig, jobPath, target, waitForJob, output,
121
123
  case 1:
122
124
  logData = _c.sent();
123
125
  // save log
124
- return [4 /*yield*/, (0, utils_3.saveLog)(logData, logFile, jobPath)];
126
+ return [4 /*yield*/, (0, utils_4.saveLog)(logData, logFile, jobPath)];
125
127
  case 2:
126
128
  // save log
127
129
  _c.sent();
@@ -129,11 +131,11 @@ function executeJobViya(sasjs, authConfig, jobPath, target, waitForJob, output,
129
131
  case 3:
130
132
  (0, displayResult_1.displayError)(err);
131
133
  // terminate process with status code 2
132
- (0, utils_2.terminateProcess)(2);
134
+ (0, utils_3.terminateProcess)(2);
133
135
  _c.label = 4;
134
136
  case 4:
135
137
  if (!(err === null || err === void 0 ? void 0 : err.log)) return [3 /*break*/, 6];
136
- return [4 /*yield*/, (0, utils_3.saveLog)(err.log, logFile, jobPath)
138
+ return [4 /*yield*/, (0, utils_4.saveLog)(err.log, logFile, jobPath)
137
139
  // handle use case when job wasn't found
138
140
  ];
139
141
  case 5:
@@ -174,13 +176,13 @@ function executeJobViya(sasjs, authConfig, jobPath, target, waitForJob, output,
174
176
  _c.sent();
175
177
  _c.label = 8;
176
178
  case 8:
177
- if (!(submittedJob && submittedJob.links)) return [3 /*break*/, 21];
179
+ if (!(submittedJob && submittedJob.links)) return [3 /*break*/, 22];
178
180
  if (!result)
179
181
  result = true;
180
- if (!(output || logFile)) return [3 /*break*/, 20];
182
+ if (!(output || logFile)) return [3 /*break*/, 21];
181
183
  _c.label = 9;
182
184
  case 9:
183
- _c.trys.push([9, 19, , 20]);
185
+ _c.trys.push([9, 20, , 21]);
184
186
  outputJson = JSON.stringify(submittedJob, null, 2);
185
187
  if (!(typeof output === 'string')) return [3 /*break*/, 14];
186
188
  currentDirPath = path_1.default.isAbsolute(output)
@@ -192,14 +194,14 @@ function executeJobViya(sasjs, authConfig, jobPath, target, waitForJob, output,
192
194
  folderPath = outputPath.split(path_1.default.sep);
193
195
  folderPath.pop();
194
196
  parentFolderPath = folderPath.join(path_1.default.sep);
195
- return [4 /*yield*/, (0, utils_1.folderExists)(parentFolderPath)];
197
+ return [4 /*yield*/, (0, utils_2.folderExists)(parentFolderPath)];
196
198
  case 10:
197
199
  if (!!(_c.sent())) return [3 /*break*/, 12];
198
- return [4 /*yield*/, (0, utils_1.createFolder)(parentFolderPath)];
200
+ return [4 /*yield*/, (0, utils_2.createFolder)(parentFolderPath)];
199
201
  case 11:
200
202
  _c.sent();
201
203
  _c.label = 12;
202
- case 12: return [4 /*yield*/, (0, utils_1.createFile)(outputPath, outputJson)];
204
+ case 12: return [4 /*yield*/, (0, utils_2.createFile)(outputPath, outputJson)];
203
205
  case 13:
204
206
  _c.sent();
205
207
  (0, displayResult_1.displaySuccess)("Output saved to: ".concat(outputPath));
@@ -212,50 +214,53 @@ function executeJobViya(sasjs, authConfig, jobPath, target, waitForJob, output,
212
214
  }
213
215
  _c.label = 15;
214
216
  case 15:
215
- if (!(logFile && !pollOptions.streamLog)) return [3 /*break*/, 18];
217
+ if (!(logFile && !pollOptions.streamLog)) return [3 /*break*/, 19];
216
218
  logObj = submittedJob.links.find(function (link) { return link.rel === 'log' && link.method === 'GET'; });
217
- if (!logObj) return [3 /*break*/, 18];
219
+ if (!logObj) return [3 /*break*/, 19];
218
220
  logUrl = target.serverUrl + logObj.href;
219
221
  logCount = submittedJob.logStatistics.lineCount;
220
222
  return [4 /*yield*/, (0, fetchLogFileContent_1.fetchLogFileContent)(sasjs, authConfig.access_token, logUrl, logCount)];
221
223
  case 16:
222
224
  logData = _c.sent();
223
- return [4 /*yield*/, (0, utils_3.saveLog)(logData, logFile, jobPath)];
225
+ return [4 /*yield*/, (0, utils_4.saveLog)(logData, logFile, jobPath)];
224
226
  case 17:
225
227
  _c.sent();
226
- _c.label = 18;
228
+ return [4 /*yield*/, (0, logcheck_1.checkLog)(typeof logData === 'string' ? logData : (0, utils_1.parseLogLines)(logData), jobPath)];
227
229
  case 18:
228
- result = submittedJob;
229
- return [3 /*break*/, 20];
230
+ _c.sent();
231
+ _c.label = 19;
230
232
  case 19:
233
+ result = submittedJob;
234
+ return [3 /*break*/, 21];
235
+ case 20:
231
236
  error_1 = _c.sent();
232
237
  // handle use case when submitted job can't be parsed
233
238
  result = false;
234
239
  (0, displayResult_1.displayError)(null, 'An error has occurred when parsing an output of the job.');
235
- return [3 /*break*/, 20];
236
- case 20:
240
+ return [3 /*break*/, 21];
241
+ case 21:
237
242
  // wait for job state and return status(0,1,2) based on it
238
243
  if (waitForJob && submittedJob.state) {
239
244
  switch (submittedJob.state) {
240
245
  case 'completed':
241
- (0, utils_2.terminateProcess)(0);
246
+ (0, utils_3.terminateProcess)(0);
242
247
  case 'warning':
243
248
  // if ignoreWarnings flag is present, process will be terminated with
244
249
  // status code 0, otherwise status code will be 1
245
- (0, utils_2.terminateProcess)(ignoreWarnings ? 0 : 1);
250
+ (0, utils_3.terminateProcess)(ignoreWarnings ? 0 : 1);
246
251
  case 'error':
247
- (0, utils_2.terminateProcess)(2);
252
+ (0, utils_3.terminateProcess)(2);
248
253
  default:
249
254
  break;
250
255
  }
251
256
  }
252
- return [3 /*break*/, 22];
253
- case 21:
257
+ return [3 /*break*/, 23];
258
+ case 22:
254
259
  if (result) {
255
- (0, utils_2.terminateProcess)(command_1.ReturnCode.InternalError);
260
+ (0, utils_3.terminateProcess)(command_1.ReturnCode.InternalError);
256
261
  }
257
- _c.label = 22;
258
- case 22:
262
+ _c.label = 23;
263
+ case 23:
259
264
  (_b = process.logger) === null || _b === void 0 ? void 0 : _b.info("This job was executed in ".concat((endTime - startTime) / 1000, " seconds"));
260
265
  return [2 /*return*/, result];
261
266
  }
@@ -271,7 +276,7 @@ function executeJobViya(sasjs, authConfig, jobPath, target, waitForJob, output,
271
276
  function getContextName(target) {
272
277
  var _a;
273
278
  // default execution context of @sasjs/cli
274
- var defaultContextName = utils_2.contextName;
279
+ var defaultContextName = utils_3.contextName;
275
280
  // get gontext name from target
276
281
  if (target === null || target === void 0 ? void 0 : target.contextName)
277
282
  return target.contextName;
@@ -311,14 +316,14 @@ function displayStatus(submittedJob_1, statusFile_1) {
311
316
  folderPath = statusFile.split(path_1.default.sep);
312
317
  folderPath.pop();
313
318
  parentFolderPath = folderPath.join(path_1.default.sep);
314
- return [4 /*yield*/, (0, utils_1.folderExists)(parentFolderPath)];
319
+ return [4 /*yield*/, (0, utils_2.folderExists)(parentFolderPath)];
315
320
  case 1:
316
321
  if (!!(_a.sent())) return [3 /*break*/, 3];
317
- return [4 /*yield*/, (0, utils_1.createFolder)(parentFolderPath)];
322
+ return [4 /*yield*/, (0, utils_2.createFolder)(parentFolderPath)];
318
323
  case 2:
319
324
  _a.sent();
320
325
  _a.label = 3;
321
- case 3: return [4 /*yield*/, (0, utils_1.createFile)(statusFile, status)
326
+ case 3: return [4 /*yield*/, (0, utils_2.createFile)(statusFile, status)
322
327
  // display file path to status file
323
328
  ];
324
329
  case 4:
@@ -62,6 +62,7 @@ var targetCommand_1 = require("../../types/command/targetCommand");
62
62
  var utils_2 = require("../../utils");
63
63
  var getLogFilePath_1 = require("../../utils/getLogFilePath");
64
64
  var prefixAppLoc_1 = require("../../utils/prefixAppLoc");
65
+ var logcheck_1 = require("../../utils/logcheck");
65
66
  var execute_1 = require("./internal/execute");
66
67
  var node_1 = __importDefault(require("@sasjs/adapter/node"));
67
68
  var JobSubCommand;
@@ -126,6 +127,11 @@ var executeParseOptions = {
126
127
  alias: 'v',
127
128
  default: false,
128
129
  description: "If present, CLI will return status 0, 1 or 2 together with HTTP response summaries. If set to 'bleached', HTTP response summaries will be logged without extra colors."
130
+ },
131
+ logcheck: {
132
+ type: 'boolean',
133
+ default: undefined,
134
+ description: 'Check the SAS log this job returned, and fail when logCheck in sasjsconfig.json says so. --no-logcheck switches both the report and the verdict off.'
129
135
  }
130
136
  };
131
137
  var JobCommand = /** @class */ (function (_super) {
@@ -160,6 +166,7 @@ var JobCommand = /** @class */ (function (_super) {
160
166
  return __generator(this, function (_f) {
161
167
  switch (_f.label) {
162
168
  case 0:
169
+ (0, logcheck_1.setLogCheckMode)((0, logcheck_1.logCheckModeOf)(this.parsed.logcheck));
163
170
  returnStatusOnly = !!this.parsed.returnStatusOnly;
164
171
  if (returnStatusOnly) {
165
172
  process.logger.warn('--returnStatusOnly (-r) flag is deprecated.');
@@ -257,6 +264,8 @@ var JobCommand = /** @class */ (function (_super) {
257
264
  .catch(function (err) {
258
265
  var _a;
259
266
  // handle job execution failure
267
+ if ((0, logcheck_1.isLogCheckFailure)(err))
268
+ return command_1.ReturnCode.LogCheckError;
260
269
  (_a = process.logger) === null || _a === void 0 ? void 0 : _a.error('Error executing job: ', err);
261
270
  return command_1.ReturnCode.InternalError;
262
271
  })];
@@ -293,6 +302,8 @@ var JobCommand = /** @class */ (function (_super) {
293
302
  .catch(function (err) {
294
303
  var _a;
295
304
  // handle job execution failure
305
+ if ((0, logcheck_1.isLogCheckFailure)(err))
306
+ return command_1.ReturnCode.LogCheckError;
296
307
  (_a = process.logger) === null || _a === void 0 ? void 0 : _a.error('Error executing job: ', err);
297
308
  return command_1.ReturnCode.InternalError;
298
309
  })];
@@ -334,6 +345,8 @@ var JobCommand = /** @class */ (function (_super) {
334
345
  .catch(function (err) {
335
346
  var _a;
336
347
  // handle job execution failure
348
+ if ((0, logcheck_1.isLogCheckFailure)(err))
349
+ return command_1.ReturnCode.LogCheckError;
337
350
  (_a = process.logger) === null || _a === void 0 ? void 0 : _a.error('Error executing job: ', err);
338
351
  return command_1.ReturnCode.InternalError;
339
352
  })];
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ var __generator = (this && this.__generator) || function (thisArg, body) {
45
+ 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);
46
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
47
+ function verb(n) { return function (v) { return step([n, v]); }; }
48
+ function step(op) {
49
+ if (f) throw new TypeError("Generator is already executing.");
50
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
51
+ 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;
52
+ if (y = 0, t) op = [op[0] & 2, t.value];
53
+ switch (op[0]) {
54
+ case 0: case 1: t = op; break;
55
+ case 4: _.label++; return { value: op[1], done: false };
56
+ case 5: _.label++; y = op[1]; op = [0]; continue;
57
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
58
+ default:
59
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
60
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
61
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
62
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
63
+ if (t[2]) _.ops.pop();
64
+ _.trys.pop(); continue;
65
+ }
66
+ op = body.call(thisArg, _);
67
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
68
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
69
+ }
70
+ };
71
+ var __importDefault = (this && this.__importDefault) || function (mod) {
72
+ return (mod && mod.__esModule) ? mod : { "default": mod };
73
+ };
74
+ Object.defineProperty(exports, "__esModule", { value: true });
75
+ var node_1 = __importDefault(require("@sasjs/adapter/node"));
76
+ var jobUtils = __importStar(require("../internal/utils"));
77
+ var logcheck = __importStar(require("../../../utils/logcheck"));
78
+ var sas9_1 = require("../internal/execute/sas9");
79
+ /**
80
+ * A run that fails the log check is the run whose log someone wants to read, so
81
+ * the file has to be written before the check can throw - otherwise `--log`
82
+ * produces nothing exactly when it is needed.
83
+ */
84
+ describe('job execute log handling', function () {
85
+ beforeEach(function () {
86
+ process.logger = {
87
+ success: jest.fn(),
88
+ info: jest.fn(),
89
+ warn: jest.fn(),
90
+ error: jest.fn()
91
+ };
92
+ });
93
+ it('writes the log before a failing check can throw', function () { return __awaiter(void 0, void 0, void 0, function () {
94
+ var sasjs, saveSpy, checkSpy;
95
+ return __generator(this, function (_a) {
96
+ switch (_a.label) {
97
+ case 0:
98
+ sasjs = new node_1.default();
99
+ jest.spyOn(sasjs, 'request').mockResolvedValue({
100
+ status: 200,
101
+ log: 'ERROR 22-322: Syntax error',
102
+ result: {}
103
+ });
104
+ saveSpy = jest
105
+ .spyOn(jobUtils, 'saveLog')
106
+ .mockResolvedValue(undefined);
107
+ checkSpy = jest
108
+ .spyOn(logcheck, 'checkLog')
109
+ .mockRejectedValue(new Error('SAS log check failed'));
110
+ return [4 /*yield*/, expect((0, sas9_1.executeJobSas9)(sasjs, {}, 'job/path', 'job.log')).rejects.toThrow('SAS log check failed')];
111
+ case 1:
112
+ _a.sent();
113
+ expect(saveSpy).toHaveBeenCalledWith('ERROR 22-322: Syntax error', 'job.log', 'job/path');
114
+ expect(saveSpy.mock.invocationCallOrder[0]).toBeLessThan(checkSpy.mock.invocationCallOrder[0]);
115
+ return [2 /*return*/];
116
+ }
117
+ });
118
+ }); });
119
+ });
@@ -106,6 +106,7 @@ var utils_1 = require("@sasjs/utils");
106
106
  var SasjsUtilsFilesModule = __importStar(require("@sasjs/utils/file"));
107
107
  var configUtils = __importStar(require("../../../utils/config"));
108
108
  var command_1 = require("../../../types/command");
109
+ var logcheck_1 = require("../../../utils/logcheck");
109
110
  var utilsModule = __importStar(require("../internal/utils"));
110
111
  var defaultArgs = ['node', 'sasjs', 'job', 'execute'];
111
112
  var targetViya = new utils_1.Target({
@@ -149,6 +150,37 @@ describe('JobCommand', function () {
149
150
  beforeEach(function () {
150
151
  setupMocksForViya();
151
152
  });
153
+ afterEach(function () { return (0, logcheck_1.setLogCheckMode)('auto'); });
154
+ it('should return the logcheck code when the log check failed the job', function () { return __awaiter(void 0, void 0, void 0, function () {
155
+ var args, _a;
156
+ return __generator(this, function (_b) {
157
+ switch (_b.label) {
158
+ case 0:
159
+ jest
160
+ .spyOn(viyaExecuteModule, 'executeJobViya')
161
+ .mockImplementation(function () {
162
+ return Promise.reject(new logcheck_1.LogCheckFailure('2 errors'));
163
+ });
164
+ args = __spreadArray(__spreadArray([], __read(defaultArgs), false), [jobPath], false);
165
+ _a = expect;
166
+ return [4 /*yield*/, new jobCommand_1.JobCommand(args).execute()];
167
+ case 1:
168
+ _a.apply(void 0, [_b.sent()]).toEqual(command_1.ReturnCode.LogCheckError);
169
+ return [2 /*return*/];
170
+ }
171
+ });
172
+ }); });
173
+ it('should switch the log check off for --no-logcheck', function () { return __awaiter(void 0, void 0, void 0, function () {
174
+ return __generator(this, function (_a) {
175
+ switch (_a.label) {
176
+ case 0: return [4 /*yield*/, executeCommandWrapper([jobPath, '--no-logcheck'])];
177
+ case 1:
178
+ _a.sent();
179
+ expect((0, logcheck_1.getLogCheckMode)()).toEqual('off');
180
+ return [2 /*return*/];
181
+ }
182
+ });
183
+ }); });
152
184
  it('should parse sasjs job execute command', function () { return __awaiter(void 0, void 0, void 0, function () {
153
185
  var _a;
154
186
  return __generator(this, function (_b) {
@@ -0,0 +1,60 @@
1
+ import { LogIssue } from '@sasjs/utils/logs';
2
+ import { LogCheckConfig, ScannedLog, SkippedFile } from './types';
3
+ /** A log bigger than this is reported and skipped rather than read. */
4
+ export declare const MAX_LOG_BYTES: number;
5
+ export interface CollectedFiles {
6
+ /** Absolute paths of the files that will be read, in report order. */
7
+ files: string[];
8
+ /** Paths the user named that do not exist. */
9
+ missing: string[];
10
+ /**
11
+ * Folders that could not be listed, with the reason.
12
+ *
13
+ * A folder the process cannot read is not an empty folder: the logs inside
14
+ * it were never looked at, so it has to reach the report and it has to fail
15
+ * the gate - a check that goes green on a directory it could not open is
16
+ * worse than no check.
17
+ */
18
+ unreadable: SkippedFile[];
19
+ }
20
+ /**
21
+ * Expands the paths a user named into the files to read.
22
+ *
23
+ * A file is taken as it is; a folder is walked, because the reason a user
24
+ * points at `sasjsresults` is that they do not know which run left which log.
25
+ * Order is sorted so that two runs over the same folder produce the same
26
+ * report, which is what makes a diff of two reports readable.
27
+ */
28
+ export declare const collectLogFiles: (paths: string[]) => Promise<CollectedFiles>;
29
+ /**
30
+ * The path a report shows for a file: relative to the project folder where
31
+ * that is shorter and does not climb out of it, absolute otherwise.
32
+ *
33
+ * Forward slashes throughout, so a report generated on Windows and one
34
+ * generated on Linux describe the same log the same way, and so a path can be
35
+ * dropped into a CI annotation without escaping.
36
+ */
37
+ export declare const toDisplayPath: (filePath: string) => string;
38
+ /**
39
+ * Parses one log and splits its issues into the ones reported and the ones
40
+ * suppressed by message code.
41
+ *
42
+ * A file that is not a SAS log is skipped rather than reported as clean: a
43
+ * Python traceback and a shell build log both begin lines with `ERROR:`, and
44
+ * `isSasLog` is what tells them from the real thing.
45
+ */
46
+ export declare const scanLogFile: (filePath: string, config: LogCheckConfig) => Promise<ScannedLog>;
47
+ /**
48
+ * Splits parsed issues by the suppressed message codes.
49
+ *
50
+ * Matching is on the code alone, because that is what SAS keeps stable: the
51
+ * text of a note carries values from the data and changes between runs, while
52
+ * `49-169` means the same thing every time.
53
+ */
54
+ export declare const partitionIssues: (issues: LogIssue[], ignoreCodes: string[]) => {
55
+ issues: LogIssue[];
56
+ suppressed: LogIssue[];
57
+ };
58
+ export declare const countIssues: (issues: LogIssue[]) => import("./types").IssueCounts;
59
+ /** A file the check declined to open, as the report shows it. */
60
+ export declare const toSkippedFile: (log: ScannedLog) => SkippedFile;