@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
@@ -0,0 +1,354 @@
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 __importDefault = (this && this.__importDefault) || function (mod) {
50
+ return (mod && mod.__esModule) ? mod : { "default": mod };
51
+ };
52
+ Object.defineProperty(exports, "__esModule", { value: true });
53
+ var path_1 = __importDefault(require("path"));
54
+ var os_1 = __importDefault(require("os"));
55
+ var fs_1 = require("fs");
56
+ var processLogCheck_1 = require("../processLogCheck");
57
+ var types_1 = require("../types");
58
+ var sasLog = function (note) {
59
+ if (note === void 0) { note = 'NOTE: Variable flag is uninitialized.'; }
60
+ return "NOTE: Copyright (c) 2016 by SAS Institute Inc., Cary, NC, USA.\n\n1 data work.x;\n2 set work.y;\n3 run;\nERROR 22-322: Syntax error.\nWARNING: Apparent symbolic reference X not resolved.\n".concat(note, "\n");
61
+ };
62
+ var cleanLog = "NOTE: Copyright (c) 2016 by SAS Institute Inc., Cary, NC, USA.\n\n1 data work.x;\n2 x = 1;\n3 run;\n";
63
+ var warningsOnly = "NOTE: Copyright (c) 2016 by SAS Institute Inc., Cary, NC, USA.\n\n1 data work.x;\n2 set work.y;\n3 run;\nWARNING: Apparent symbolic reference X not resolved.\n";
64
+ var buildLog = "> tsc -p .\nERROR: Cannot find module './config'\n";
65
+ var tempFolder = function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
66
+ switch (_a.label) {
67
+ case 0: return [4 /*yield*/, fs_1.promises.mkdtemp(path_1.default.join(os_1.default.tmpdir(), 'sasjs-logcheck-process-'))];
68
+ case 1: return [2 /*return*/, _a.sent()];
69
+ }
70
+ }); }); };
71
+ var config = function (overrides) {
72
+ if (overrides === void 0) { overrides = {}; }
73
+ return (__assign(__assign(__assign({}, types_1.defaultLogCheckConfig), { ignoreCodes: [], ignorePaths: [] }), overrides));
74
+ };
75
+ describe('processLogCheck', function () {
76
+ var originalProjectDir = process.projectDir;
77
+ afterEach(function () {
78
+ process.projectDir = originalProjectDir;
79
+ });
80
+ it('checks every log in the results folder when given no path', function () { return __awaiter(void 0, void 0, void 0, function () {
81
+ var folder, summary;
82
+ return __generator(this, function (_a) {
83
+ switch (_a.label) {
84
+ case 0: return [4 /*yield*/, tempFolder()];
85
+ case 1:
86
+ folder = _a.sent();
87
+ process.projectDir = folder;
88
+ return [4 /*yield*/, fs_1.promises.mkdir(path_1.default.join(folder, 'sasjsresults'))];
89
+ case 2:
90
+ _a.sent();
91
+ return [4 /*yield*/, fs_1.promises.writeFile(path_1.default.join(folder, 'sasjsresults', 'run.log'), sasLog())];
92
+ case 3:
93
+ _a.sent();
94
+ return [4 /*yield*/, fs_1.promises.writeFile(path_1.default.join(folder, 'sasjsresults', 'build.log'), buildLog)];
95
+ case 4:
96
+ _a.sent();
97
+ return [4 /*yield*/, (0, processLogCheck_1.processLogCheck)({ config: config() })];
98
+ case 5:
99
+ summary = _a.sent();
100
+ expect(summary.usedDefaultInput).toBe(true);
101
+ expect(summary.searched).toEqual(['sasjsresults']);
102
+ expect(summary.logs).toHaveLength(2);
103
+ expect(summary.totals).toEqual({
104
+ errors: 1,
105
+ warnings: 1,
106
+ notes: 1,
107
+ suppressed: 0,
108
+ logs: 1,
109
+ skipped: 1
110
+ });
111
+ expect(summary.skipped).toEqual([
112
+ { displayPath: 'sasjsresults/build.log', reason: 'not a SAS log' }
113
+ ]);
114
+ expect(summary.failure).toEqual('1 error');
115
+ return [2 /*return*/];
116
+ }
117
+ });
118
+ }); });
119
+ it('checks only the paths it was given', function () { return __awaiter(void 0, void 0, void 0, function () {
120
+ var folder, summary;
121
+ return __generator(this, function (_a) {
122
+ switch (_a.label) {
123
+ case 0: return [4 /*yield*/, tempFolder()];
124
+ case 1:
125
+ folder = _a.sent();
126
+ process.projectDir = folder;
127
+ return [4 /*yield*/, fs_1.promises.writeFile(path_1.default.join(folder, 'one.log'), sasLog())];
128
+ case 2:
129
+ _a.sent();
130
+ return [4 /*yield*/, fs_1.promises.writeFile(path_1.default.join(folder, 'two.log'), sasLog())];
131
+ case 3:
132
+ _a.sent();
133
+ return [4 /*yield*/, (0, processLogCheck_1.processLogCheck)({
134
+ paths: [path_1.default.join(folder, 'one.log')],
135
+ config: config()
136
+ })];
137
+ case 4:
138
+ summary = _a.sent();
139
+ expect(summary.usedDefaultInput).toBe(false);
140
+ expect(summary.logs).toHaveLength(1);
141
+ expect(summary.logs[0].displayPath).toEqual('one.log');
142
+ return [2 /*return*/];
143
+ }
144
+ });
145
+ }); });
146
+ it('counts a log excluded by ignorePaths as skipped, not as clean', function () { return __awaiter(void 0, void 0, void 0, function () {
147
+ var folder, summary;
148
+ return __generator(this, function (_a) {
149
+ switch (_a.label) {
150
+ case 0: return [4 /*yield*/, tempFolder()];
151
+ case 1:
152
+ folder = _a.sent();
153
+ process.projectDir = folder;
154
+ return [4 /*yield*/, fs_1.promises.writeFile(path_1.default.join(folder, 'keep.log'), cleanLog)];
155
+ case 2:
156
+ _a.sent();
157
+ return [4 /*yield*/, fs_1.promises.writeFile(path_1.default.join(folder, 'legacy.log'), sasLog())];
158
+ case 3:
159
+ _a.sent();
160
+ return [4 /*yield*/, (0, processLogCheck_1.processLogCheck)({
161
+ paths: [folder],
162
+ config: config({ ignorePaths: ['legacy.log'] })
163
+ })];
164
+ case 4:
165
+ summary = _a.sent();
166
+ expect(summary.totals.logs).toEqual(1);
167
+ expect(summary.skipped).toEqual([
168
+ { displayPath: 'legacy.log', reason: 'excluded by ignorePaths' }
169
+ ]);
170
+ // The count in the summary and the entries in the report describe the same
171
+ // run: the excluded file is a file that was passed over, not a file that
172
+ // never existed.
173
+ expect(summary.totals.skipped).toEqual(summary.skipped.length);
174
+ expect(summary.failure).toBeUndefined();
175
+ return [2 /*return*/];
176
+ }
177
+ });
178
+ }); });
179
+ it('sets a suppressed issue aside without counting it', function () { return __awaiter(void 0, void 0, void 0, function () {
180
+ var folder, summary;
181
+ return __generator(this, function (_a) {
182
+ switch (_a.label) {
183
+ case 0: return [4 /*yield*/, tempFolder()];
184
+ case 1:
185
+ folder = _a.sent();
186
+ process.projectDir = folder;
187
+ return [4 /*yield*/, fs_1.promises.writeFile(path_1.default.join(folder, 'run.log'), sasLog())];
188
+ case 2:
189
+ _a.sent();
190
+ return [4 /*yield*/, (0, processLogCheck_1.processLogCheck)({
191
+ paths: [folder],
192
+ config: config({ ignoreCodes: ['22-322'] })
193
+ })];
194
+ case 3:
195
+ summary = _a.sent();
196
+ expect(summary.totals.errors).toEqual(0);
197
+ expect(summary.totals.suppressed).toEqual(1);
198
+ expect(summary.failure).toBeUndefined();
199
+ return [2 /*return*/];
200
+ }
201
+ });
202
+ }); });
203
+ it('reports a path that does not exist as a failure', function () { return __awaiter(void 0, void 0, void 0, function () {
204
+ var folder, summary;
205
+ return __generator(this, function (_a) {
206
+ switch (_a.label) {
207
+ case 0: return [4 /*yield*/, tempFolder()];
208
+ case 1:
209
+ folder = _a.sent();
210
+ process.projectDir = folder;
211
+ return [4 /*yield*/, (0, processLogCheck_1.processLogCheck)({
212
+ paths: [path_1.default.join(folder, 'nope.log')],
213
+ config: config()
214
+ })];
215
+ case 2:
216
+ summary = _a.sent();
217
+ expect(summary.missing).toEqual([path_1.default.join(folder, 'nope.log')]);
218
+ expect(summary.failure).toEqual('1 path not found');
219
+ return [2 /*return*/];
220
+ }
221
+ });
222
+ }); });
223
+ it('fails on a folder it could not list', function () { return __awaiter(void 0, void 0, void 0, function () {
224
+ var folder, readdir, summary;
225
+ return __generator(this, function (_a) {
226
+ switch (_a.label) {
227
+ case 0: return [4 /*yield*/, tempFolder()];
228
+ case 1:
229
+ folder = _a.sent();
230
+ process.projectDir = folder;
231
+ return [4 /*yield*/, fs_1.promises.mkdir(path_1.default.join(folder, 'logs'))];
232
+ case 2:
233
+ _a.sent();
234
+ readdir = jest
235
+ .spyOn(require('fs/promises'), 'readdir')
236
+ .mockRejectedValue(Object.assign(new Error('denied'), { code: 'EACCES' }));
237
+ return [4 /*yield*/, (0, processLogCheck_1.processLogCheck)({
238
+ paths: [path_1.default.join(folder, 'logs')],
239
+ config: config()
240
+ })];
241
+ case 3:
242
+ summary = _a.sent();
243
+ readdir.mockRestore();
244
+ expect(summary.unreadable).toEqual([
245
+ { displayPath: 'logs', reason: 'could not be listed (EACCES)' }
246
+ ]);
247
+ expect(summary.failure).toEqual('1 folder could not be read');
248
+ return [2 /*return*/];
249
+ }
250
+ });
251
+ }); });
252
+ it('reports the default folder as missing rather than throwing', function () { return __awaiter(void 0, void 0, void 0, function () {
253
+ var folder, summary;
254
+ return __generator(this, function (_a) {
255
+ switch (_a.label) {
256
+ case 0: return [4 /*yield*/, tempFolder()];
257
+ case 1:
258
+ folder = _a.sent();
259
+ process.projectDir = folder;
260
+ return [4 /*yield*/, (0, processLogCheck_1.processLogCheck)({ config: config() })];
261
+ case 2:
262
+ summary = _a.sent();
263
+ expect(summary.usedDefaultInput).toBe(true);
264
+ expect(summary.failure).toEqual('1 path not found');
265
+ return [2 /*return*/];
266
+ }
267
+ });
268
+ }); });
269
+ });
270
+ describe('defaultInputPath', function () {
271
+ var originalProjectDir = process.projectDir;
272
+ afterEach(function () {
273
+ process.projectDir = originalProjectDir;
274
+ });
275
+ it('is sasjsresults under the project folder', function () {
276
+ process.projectDir = path_1.default.join('some', 'project');
277
+ expect((0, processLogCheck_1.defaultInputPath)(config())).toEqual(path_1.default.resolve(path_1.default.join('some', 'project'), 'sasjsresults'));
278
+ });
279
+ it('falls back to the working directory when no project is set', function () {
280
+ process.projectDir = undefined;
281
+ expect((0, processLogCheck_1.defaultInputPath)(config())).toEqual(path_1.default.resolve(process.cwd(), 'sasjsresults'));
282
+ });
283
+ it('honours the folder the config names', function () {
284
+ process.projectDir = 'some/project';
285
+ expect((0, processLogCheck_1.defaultInputPath)(config({ folder: 'logs' }))).toEqual(path_1.default.resolve('some/project', 'logs'));
286
+ });
287
+ });
288
+ describe('describeFailure', function () {
289
+ var totals = function (errors, warnings, notes) { return ({
290
+ errors: errors,
291
+ warnings: warnings,
292
+ notes: notes,
293
+ suppressed: 0,
294
+ logs: 1,
295
+ skipped: 0
296
+ }); };
297
+ it('fails on an error under the default policy', function () {
298
+ expect((0, processLogCheck_1.describeFailure)(totals(1, 0, 0), config())).toEqual('1 error');
299
+ });
300
+ it('passes a warning under the default policy', function () {
301
+ expect((0, processLogCheck_1.describeFailure)(totals(0, 3, 0), config())).toBeUndefined();
302
+ });
303
+ it('fails on a warning when the policy says warning', function () {
304
+ expect((0, processLogCheck_1.describeFailure)(totals(0, 3, 0), config({ failOn: 'warning' }))).toEqual('3 warnings');
305
+ });
306
+ it('fails on a note only when the policy says note', function () {
307
+ expect((0, processLogCheck_1.describeFailure)(totals(0, 0, 2), config({ failOn: 'warning' }))).toBeUndefined();
308
+ expect((0, processLogCheck_1.describeFailure)(totals(0, 0, 2), config({ failOn: 'note' }))).toEqual('2 notes');
309
+ });
310
+ it('never fails under the never policy, when no budget is set', function () {
311
+ expect((0, processLogCheck_1.describeFailure)(totals(9, 9, 9), config({ failOn: 'never' }))).toBeUndefined();
312
+ });
313
+ it('still fails on an exceeded budget under the never policy', function () {
314
+ expect((0, processLogCheck_1.describeFailure)(totals(0, 3, 0), config({ failOn: 'never', maxWarnings: 0 }))).toEqual('3 warnings (budget 0)');
315
+ });
316
+ it('fails when the warnings exceed the budget', function () {
317
+ expect((0, processLogCheck_1.describeFailure)(totals(0, 3, 0), config({ maxWarnings: 2 }))).toEqual('3 warnings (budget 2)');
318
+ expect((0, processLogCheck_1.describeFailure)(totals(0, 2, 0), config({ maxWarnings: 2 }))).toBeUndefined();
319
+ });
320
+ it('fails on a folder it could not read, before any issue', function () {
321
+ expect((0, processLogCheck_1.describeFailure)(totals(4, 0, 0), config(), 0, 1)).toEqual('1 folder could not be read');
322
+ expect((0, processLogCheck_1.describeFailure)(totals(0, 0, 0), config(), 0, 2)).toEqual('2 folders could not be read');
323
+ });
324
+ it('reports a missing path before any issue', function () {
325
+ expect((0, processLogCheck_1.describeFailure)(totals(4, 0, 0), config(), 2)).toEqual('2 paths not found');
326
+ });
327
+ });
328
+ describe('sumTotals', function () {
329
+ it('treats every log it was given as a file it read', function () {
330
+ expect((0, processLogCheck_1.sumTotals)([
331
+ { counts: { errors: 0, warnings: 0, notes: 0 }, suppressed: [] }
332
+ ]).skipped).toEqual(0);
333
+ });
334
+ it('adds the counts of the logs it was given', function () {
335
+ var logs = [
336
+ {
337
+ counts: { errors: 2, warnings: 1, notes: 0 },
338
+ suppressed: [{}]
339
+ },
340
+ {
341
+ counts: { errors: 0, warnings: 0, notes: 3 },
342
+ suppressed: []
343
+ }
344
+ ];
345
+ expect((0, processLogCheck_1.sumTotals)(logs, 3)).toEqual({
346
+ errors: 2,
347
+ warnings: 1,
348
+ notes: 3,
349
+ suppressed: 1,
350
+ logs: 2,
351
+ skipped: 1
352
+ });
353
+ });
354
+ });