@sasjs/cli 6.5.0 → 6.7.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 (53) hide show
  1. package/README.md +2 -1
  2. package/build/commands/build/build.js +66 -29
  3. package/build/commands/build/spec/build.spec.js +31 -1
  4. package/build/commands/compile/compile.js +168 -33
  5. package/build/commands/compile/compileCommand.js +1 -1
  6. package/build/commands/compile/internal/checkCompileStatus.js +30 -11
  7. package/build/commands/compile/internal/compileFile.js +16 -7
  8. package/build/commands/compile/internal/compileManifest.d.ts +36 -0
  9. package/build/commands/compile/internal/compileManifest.js +610 -0
  10. package/build/commands/compile/internal/index.d.ts +2 -0
  11. package/build/commands/compile/internal/index.js +2 -0
  12. package/build/commands/compile/internal/passThrough.d.ts +12 -0
  13. package/build/commands/compile/internal/passThrough.js +17 -0
  14. package/build/commands/compile/internal/spec/compileTestFile.spec.js +9 -6
  15. package/build/commands/compile/spec/checkCompileStatus.spec.d.ts +1 -0
  16. package/build/commands/compile/spec/checkCompileStatus.spec.js +221 -0
  17. package/build/commands/compile/spec/compileCommand.spec.js +9 -2
  18. package/build/commands/compile/spec/compileManifest.spec.d.ts +1 -0
  19. package/build/commands/compile/spec/compileManifest.spec.js +401 -0
  20. package/build/commands/compile/spec/passThrough.spec.d.ts +1 -0
  21. package/build/commands/compile/spec/passThrough.spec.js +160 -0
  22. package/build/commands/deploy/spec/cbd.spec.js +11 -7
  23. package/build/commands/help/help.js +10 -0
  24. package/build/commands/index.d.ts +1 -0
  25. package/build/commands/index.js +3 -1
  26. package/build/commands/logcheck/collectLogs.d.ts +60 -0
  27. package/build/commands/logcheck/collectLogs.js +386 -0
  28. package/build/commands/logcheck/logCheckConfig.d.ts +72 -0
  29. package/build/commands/logcheck/logCheckConfig.js +283 -0
  30. package/build/commands/logcheck/logcheckCommand.d.ts +6 -0
  31. package/build/commands/logcheck/logcheckCommand.js +200 -0
  32. package/build/commands/logcheck/processLogCheck.d.ts +44 -0
  33. package/build/commands/logcheck/processLogCheck.js +242 -0
  34. package/build/commands/logcheck/renderLogCheck.d.ts +128 -0
  35. package/build/commands/logcheck/renderLogCheck.js +423 -0
  36. package/build/commands/logcheck/spec/collectLogs.spec.d.ts +1 -0
  37. package/build/commands/logcheck/spec/collectLogs.spec.js +477 -0
  38. package/build/commands/logcheck/spec/logCheckConfig.spec.d.ts +1 -0
  39. package/build/commands/logcheck/spec/logCheckConfig.spec.js +323 -0
  40. package/build/commands/logcheck/spec/logcheckCommand.spec.d.ts +1 -0
  41. package/build/commands/logcheck/spec/logcheckCommand.spec.js +360 -0
  42. package/build/commands/logcheck/spec/processLogCheck.spec.d.ts +1 -0
  43. package/build/commands/logcheck/spec/processLogCheck.spec.js +354 -0
  44. package/build/commands/logcheck/spec/renderLogCheck.spec.d.ts +1 -0
  45. package/build/commands/logcheck/spec/renderLogCheck.spec.js +426 -0
  46. package/build/commands/logcheck/types.d.ts +98 -0
  47. package/build/commands/logcheck/types.js +14 -0
  48. package/build/commands/testing/spec/testing.spec.js +8 -2
  49. package/build/types/command/commandAliases.js +1 -0
  50. package/build/types/command/commandFactory.js +2 -0
  51. package/build/types/command/returnCode.d.ts +2 -1
  52. package/build/types/command/returnCode.js +4 -0
  53. package/package.json +3 -3
@@ -0,0 +1,283 @@
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 __read = (this && this.__read) || function (o, n) {
50
+ var m = typeof Symbol === "function" && o[Symbol.iterator];
51
+ if (!m) return o;
52
+ var i = m.call(o), r, ar = [], e;
53
+ try {
54
+ while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
55
+ }
56
+ catch (error) { e = { error: error }; }
57
+ finally {
58
+ try {
59
+ if (r && !r.done && (m = i["return"])) m.call(i);
60
+ }
61
+ finally { if (e) throw e.error; }
62
+ }
63
+ return ar;
64
+ };
65
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
66
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
67
+ if (ar || !(i in from)) {
68
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
69
+ ar[i] = from[i];
70
+ }
71
+ }
72
+ return to.concat(ar || Array.prototype.slice.call(from));
73
+ };
74
+ var __importDefault = (this && this.__importDefault) || function (mod) {
75
+ return (mod && mod.__esModule) ? mod : { "default": mod };
76
+ };
77
+ Object.defineProperty(exports, "__esModule", { value: true });
78
+ exports.toPosix = exports.globToRegExp = exports.isIgnoredPath = exports.matchesSegments = exports.matchesPath = exports.mergeConfig = exports.readLogCheckConfig = exports.parseLogCheckSection = exports.DEFAULT_FOLDER = exports.CONFIG_KEY = void 0;
79
+ var path_1 = __importDefault(require("path"));
80
+ var utils_1 = require("@sasjs/utils");
81
+ var types_1 = require("./types");
82
+ /** The section of `sasjsconfig.json` this command reads. */
83
+ exports.CONFIG_KEY = 'logCheck';
84
+ /** Folder checked when the command is given no path of its own. */
85
+ exports.DEFAULT_FOLDER = 'sasjsresults';
86
+ var failOnValues = ['error', 'warning', 'note', 'never'];
87
+ /**
88
+ * Builds a config from an untrusted `logCheck` section.
89
+ *
90
+ * Values a user gets wrong are refused rather than ignored: a typo in
91
+ * `failOn` that silently fell back to the default would leave a gate that
92
+ * never fails, which is the one failure mode a quality gate must not have.
93
+ *
94
+ * @param section - the parsed `logCheck` object from `sasjsconfig.json`.
95
+ * @param source - the file it came from, named in the error message.
96
+ */
97
+ var parseLogCheckSection = function (section, source) {
98
+ if (source === void 0) { source = 'sasjsconfig.json'; }
99
+ var config = __assign(__assign({}, types_1.defaultLogCheckConfig), { ignoreCodes: [], ignorePaths: [] });
100
+ if (section === undefined || section === null)
101
+ return config;
102
+ if (typeof section !== 'object' || Array.isArray(section)) {
103
+ throw new Error("The `".concat(exports.CONFIG_KEY, "` property in ").concat(source, " must be an object."));
104
+ }
105
+ var _a = section, failOn = _a.failOn, maxWarnings = _a.maxWarnings, ignoreCodes = _a.ignoreCodes, ignorePaths = _a.ignorePaths, folder = _a.folder;
106
+ if (failOn !== undefined) {
107
+ if (!failOnValues.includes(failOn)) {
108
+ throw new Error("The `".concat(exports.CONFIG_KEY, ".failOn` property in ").concat(source, " must be one of ").concat(failOnValues.join(', '), " - received `").concat(String(failOn), "`."));
109
+ }
110
+ config.failOn = failOn;
111
+ }
112
+ if (maxWarnings !== undefined) {
113
+ if (typeof maxWarnings !== 'number' ||
114
+ !Number.isInteger(maxWarnings) ||
115
+ maxWarnings < 0) {
116
+ throw new Error("The `".concat(exports.CONFIG_KEY, ".maxWarnings` property in ").concat(source, " must be a whole number of zero or more - received `").concat(String(maxWarnings), "`."));
117
+ }
118
+ config.maxWarnings = maxWarnings;
119
+ }
120
+ if (ignoreCodes !== undefined) {
121
+ config.ignoreCodes = stringList(ignoreCodes, "".concat(exports.CONFIG_KEY, ".ignoreCodes"), source);
122
+ }
123
+ if (ignorePaths !== undefined) {
124
+ config.ignorePaths = stringList(ignorePaths, "".concat(exports.CONFIG_KEY, ".ignorePaths"), source);
125
+ }
126
+ if (folder !== undefined) {
127
+ if (typeof folder !== 'string' || !folder.trim()) {
128
+ throw new Error("The `".concat(exports.CONFIG_KEY, ".folder` property in ").concat(source, " must be a non-empty string."));
129
+ }
130
+ config.folder = folder.trim();
131
+ }
132
+ return config;
133
+ };
134
+ exports.parseLogCheckSection = parseLogCheckSection;
135
+ /**
136
+ * Reads the `logCheck` policy from the project's `sasjsconfig.json`.
137
+ *
138
+ * A missing file, or one without the section, is not an error - the command
139
+ * has to work in a folder that is not a SASjs project at all, which is the
140
+ * whole point of checking a log somebody else produced.
141
+ */
142
+ var readLogCheckConfig = function () { return __awaiter(void 0, void 0, void 0, function () {
143
+ var projectDir, configPath, raw, parsed, section, config, resultsFolder;
144
+ var _a, _b, _c, _d;
145
+ return __generator(this, function (_e) {
146
+ switch (_e.label) {
147
+ case 0:
148
+ projectDir = process.projectDir || process.cwd();
149
+ configPath = path_1.default.join(projectDir, 'sasjsconfig.json');
150
+ return [4 /*yield*/, (0, utils_1.readFile)(configPath).catch(function () { return undefined; })];
151
+ case 1:
152
+ raw = _e.sent();
153
+ if (!raw)
154
+ return [2 /*return*/, __assign(__assign({}, types_1.defaultLogCheckConfig), { ignoreCodes: [], ignorePaths: [] })];
155
+ try {
156
+ parsed = JSON.parse(raw);
157
+ }
158
+ catch (error) {
159
+ throw new Error("Could not parse ".concat(configPath, ": ").concat(error.message));
160
+ }
161
+ section = (_b = (_a = parsed === null || parsed === void 0 ? void 0 : parsed.config) === null || _a === void 0 ? void 0 : _a[exports.CONFIG_KEY]) !== null && _b !== void 0 ? _b : parsed === null || parsed === void 0 ? void 0 : parsed[exports.CONFIG_KEY];
162
+ config = (0, exports.parseLogCheckSection)(section, configPath);
163
+ // A project may have moved its results folder; the bare command follows it,
164
+ // unless the logCheck section names a folder of its own.
165
+ if (!config.folder) {
166
+ resultsFolder = (_d = (_c = parsed === null || parsed === void 0 ? void 0 : parsed.config) === null || _c === void 0 ? void 0 : _c.sasjsResultsFolder) !== null && _d !== void 0 ? _d : parsed === null || parsed === void 0 ? void 0 : parsed.sasjsResultsFolder;
167
+ if (typeof resultsFolder === 'string' && resultsFolder.trim()) {
168
+ config.folder = resultsFolder.trim();
169
+ }
170
+ }
171
+ return [2 /*return*/, config];
172
+ }
173
+ });
174
+ }); };
175
+ exports.readLogCheckConfig = readLogCheckConfig;
176
+ /**
177
+ * Layers the command line over the config file. Suppressions are added, never
178
+ * replaced: a flag silences one more code for one run, it does not un-silence
179
+ * the codes a team has already agreed to accept.
180
+ */
181
+ var mergeConfig = function (base, overrides) {
182
+ var _a, _b;
183
+ if (overrides === void 0) { overrides = {}; }
184
+ var merged = __assign(__assign({}, base), { ignoreCodes: __spreadArray([], __read(base.ignoreCodes), false), ignorePaths: __spreadArray([], __read(base.ignorePaths), false) });
185
+ if (overrides.failOn !== undefined) {
186
+ merged.failOn = overrides.failOn;
187
+ }
188
+ if (overrides.maxWarnings !== undefined) {
189
+ merged.maxWarnings = overrides.maxWarnings;
190
+ }
191
+ if ((_a = overrides.ignoreCodes) === null || _a === void 0 ? void 0 : _a.length) {
192
+ merged.ignoreCodes = unique(__spreadArray(__spreadArray([], __read(merged.ignoreCodes), false), __read(overrides.ignoreCodes), false));
193
+ }
194
+ if ((_b = overrides.ignorePaths) === null || _b === void 0 ? void 0 : _b.length) {
195
+ merged.ignorePaths = unique(__spreadArray(__spreadArray([], __read(merged.ignorePaths), false), __read(overrides.ignorePaths), false));
196
+ }
197
+ return merged;
198
+ };
199
+ exports.mergeConfig = mergeConfig;
200
+ /**
201
+ * Whether a log path is excluded by one of the `ignorePaths` entries.
202
+ *
203
+ * An entry with no wildcard is matched against whole path segments, not as a
204
+ * bare substring of the text: `test` covers `test/run.log` and
205
+ * `sasjsresults/test/run.log`, but not `latest.log`, and `logs` does not cover
206
+ * `nested-logs/x.log`. A suppression rule that quietly covers files the author
207
+ * did not name is the failure mode this whole config guards against, so an
208
+ * entry has to name a path, not a fragment of one.
209
+ *
210
+ * An entry with a wildcard is a glob over the display path, anchored at both
211
+ * ends, where `**&#47;` crosses folders and `*` does not.
212
+ */
213
+ var matchesPath = function (displayPath, pattern) {
214
+ var subject = (0, exports.toPosix)(displayPath);
215
+ var candidate = normalisePattern((0, exports.toPosix)(pattern));
216
+ if (!candidate)
217
+ return false;
218
+ if (!candidate.includes('*') && !candidate.includes('?')) {
219
+ return (0, exports.matchesSegments)(subject, candidate);
220
+ }
221
+ return (0, exports.globToRegExp)(candidate).test(subject);
222
+ };
223
+ exports.matchesPath = matchesPath;
224
+ /**
225
+ * A non-glob entry matches when it names the path itself, or a run of whole
226
+ * segments inside it.
227
+ *
228
+ * Padding both sides with a separator makes one `includes` do the work: the
229
+ * needle can only be found where it begins at a segment boundary and ends at
230
+ * one, which is exactly the difference between `test` and `latest`.
231
+ */
232
+ var matchesSegments = function (subject, candidate) {
233
+ var haystack = "/".concat(stripSlashes(subject), "/");
234
+ var needle = "/".concat(stripSlashes(candidate), "/");
235
+ return haystack.includes(needle);
236
+ };
237
+ exports.matchesSegments = matchesSegments;
238
+ var isIgnoredPath = function (displayPath, patterns) { return patterns.some(function (pattern) { return (0, exports.matchesPath)(displayPath, pattern); }); };
239
+ exports.isIgnoredPath = isIgnoredPath;
240
+ /**
241
+ * Compiles a glob into an expression anchored at both ends.
242
+ *
243
+ * `**&#47;` at the start matches zero or more leading folders so that
244
+ * `**&#47;build.log` covers `build.log` in the project folder as well as one
245
+ * several folders down - a pattern that only matched the deep case would make
246
+ * the obvious spelling useless.
247
+ */
248
+ var globToRegExp = function (pattern) {
249
+ var source = pattern
250
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
251
+ .replace(/\*\*\//g, '\u0000')
252
+ .replace(/\*\*/g, '\u0001')
253
+ .replace(/\*/g, '[^/]*')
254
+ .replace(/\?/g, '[^/]')
255
+ .replace(/\u0000/g, '(?:.*/)?')
256
+ .replace(/\u0001/g, '.*');
257
+ return new RegExp("^".concat(source, "$"));
258
+ };
259
+ exports.globToRegExp = globToRegExp;
260
+ /** Path text as it appears in a report: forward slashes, no wrapping quotes. */
261
+ var toPosix = function (value) {
262
+ return value.split(path_1.default.sep).join('/');
263
+ };
264
+ exports.toPosix = toPosix;
265
+ /**
266
+ * A suppression entry as it is compared: no `./` lead-in, no trailing
267
+ * separators, so `./logs/` and `logs` are one entry rather than two.
268
+ */
269
+ var normalisePattern = function (value) {
270
+ return stripSlashes(value.trim().replace(/^(?:\.\/)+/, ''));
271
+ };
272
+ var stripSlashes = function (value) {
273
+ return value.replace(/^\/+/, '').replace(/\/+$/, '');
274
+ };
275
+ var stringList = function (value, name, source) {
276
+ if (!Array.isArray(value) || value.some(function (item) { return typeof item !== 'string'; })) {
277
+ throw new Error("The `".concat(name, "` property in ").concat(source, " must be an array of strings."));
278
+ }
279
+ return unique(value);
280
+ };
281
+ var unique = function (values) {
282
+ return Array.from(new Set(values.map(function (value) { return value.trim(); }).filter(Boolean)));
283
+ };
@@ -0,0 +1,6 @@
1
+ import { ReturnCode } from '../../types/command';
2
+ import { CommandBase } from '../../types';
3
+ export declare class LogCheckCommand extends CommandBase {
4
+ constructor(args: string[]);
5
+ execute(): Promise<ReturnCode.Success | ReturnCode.InvalidCommand | ReturnCode.InternalError | ReturnCode.LogCheckError>;
6
+ }
@@ -0,0 +1,200 @@
1
+ "use strict";
2
+ var __extends = (this && this.__extends) || (function () {
3
+ var extendStatics = function (d, b) {
4
+ extendStatics = Object.setPrototypeOf ||
5
+ ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
6
+ function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
7
+ return extendStatics(d, b);
8
+ };
9
+ return function (d, b) {
10
+ if (typeof b !== "function" && b !== null)
11
+ throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
12
+ extendStatics(d, b);
13
+ function __() { this.constructor = d; }
14
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
15
+ };
16
+ })();
17
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
18
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
19
+ return new (P || (P = Promise))(function (resolve, reject) {
20
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
21
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
22
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
23
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
24
+ });
25
+ };
26
+ var __generator = (this && this.__generator) || function (thisArg, body) {
27
+ 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);
28
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
29
+ function verb(n) { return function (v) { return step([n, v]); }; }
30
+ function step(op) {
31
+ if (f) throw new TypeError("Generator is already executing.");
32
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
33
+ 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;
34
+ if (y = 0, t) op = [op[0] & 2, t.value];
35
+ switch (op[0]) {
36
+ case 0: case 1: t = op; break;
37
+ case 4: _.label++; return { value: op[1], done: false };
38
+ case 5: _.label++; y = op[1]; op = [0]; continue;
39
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
40
+ default:
41
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
42
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
43
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
44
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
45
+ if (t[2]) _.ops.pop();
46
+ _.trys.pop(); continue;
47
+ }
48
+ op = body.call(thisArg, _);
49
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
50
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
51
+ }
52
+ };
53
+ Object.defineProperty(exports, "__esModule", { value: true });
54
+ exports.LogCheckCommand = void 0;
55
+ var command_1 = require("../../types/command");
56
+ var types_1 = require("../../types");
57
+ var utils_1 = require("../../utils");
58
+ var logCheckConfig_1 = require("./logCheckConfig");
59
+ var processLogCheck_1 = require("./processLogCheck");
60
+ var renderLogCheck_1 = require("./renderLogCheck");
61
+ var failOnValues = ['error', 'warning', 'note', 'never'];
62
+ var formats = ['human', 'json', 'annotations'];
63
+ var syntax = 'logcheck [paths..]';
64
+ var aliases = ['lc'];
65
+ var usage = 'sasjs logcheck [path..] [--fail-on error|warning|note|never] [--format human|json|annotations]';
66
+ var description = "Checks SAS logs for what SAS itself printed - ERRORs, WARNINGs and the notes that flag a problem - and fails when the failure policy is broken.\n" +
67
+ "With no path, the results folder of the current project is checked, which is where run, job and test leave their logs.\n" +
68
+ "No target, no login and no project are needed: a log from Enterprise Guide, or from a server somewhere else, checks the same way.";
69
+ var examples = [
70
+ {
71
+ command: 'sasjs logcheck',
72
+ description: "Checks every log in the project's sasjsresults folder and fails on any ERROR."
73
+ },
74
+ {
75
+ command: 'sasjs logcheck ./logs',
76
+ description: 'Checks every file under ./logs, skipping the ones that are not SAS logs.'
77
+ },
78
+ {
79
+ command: 'sasjs logcheck run.log --fail-on warning',
80
+ description: 'Checks one log, and fails on a WARNING as well as an ERROR.'
81
+ },
82
+ {
83
+ command: 'sasjs logcheck --max-warnings 10',
84
+ description: 'Fails when the checked logs carry more than ten warnings in total.'
85
+ },
86
+ {
87
+ command: 'sasjs logcheck --ignore-code 49-169',
88
+ description: 'Reports everything except the notes carrying the SAS message code 49-169.'
89
+ },
90
+ {
91
+ command: 'sasjs logcheck --format json',
92
+ description: 'Prints the result as JSON, for a pipeline to read.'
93
+ },
94
+ {
95
+ command: 'sasjs logcheck --format annotations',
96
+ description: 'Prints GitHub Actions annotations, underlining each issue in the workflow log.'
97
+ }
98
+ ];
99
+ var LogCheckCommand = /** @class */ (function (_super) {
100
+ __extends(LogCheckCommand, _super);
101
+ function LogCheckCommand(args) {
102
+ return _super.call(this, args, {
103
+ parseOptions: {
104
+ 'fail-on': {
105
+ type: 'string',
106
+ description: "Lowest severity that fails the check: ".concat(failOnValues.join(', '), ". Defaults to the logCheck.failOn setting in sasjsconfig.json, or error.")
107
+ },
108
+ 'max-warnings': {
109
+ type: 'number',
110
+ description: 'Number of warnings tolerated before the check fails, whatever --fail-on says.'
111
+ },
112
+ 'ignore-code': {
113
+ type: 'array',
114
+ description: 'SAS message code to suppress, e.g. 49-169. Repeatable, and added to any codes already suppressed in sasjsconfig.json.'
115
+ },
116
+ 'ignore-path': {
117
+ type: 'array',
118
+ description: 'Log path to leave out, as a substring or a glob. Repeatable.'
119
+ },
120
+ format: {
121
+ type: 'string',
122
+ default: 'human',
123
+ description: "How to print the result: ".concat(formats.join(', '), ". Defaults to human.")
124
+ },
125
+ json: {
126
+ type: 'boolean',
127
+ default: false,
128
+ description: 'Shorthand for --format json.'
129
+ },
130
+ verbose: {
131
+ type: 'boolean',
132
+ alias: 'v',
133
+ default: false,
134
+ description: 'List the files that were skipped, and why.'
135
+ }
136
+ },
137
+ syntax: syntax,
138
+ aliases: aliases,
139
+ usage: usage,
140
+ description: description,
141
+ examples: examples
142
+ }) || this;
143
+ }
144
+ LogCheckCommand.prototype.execute = function () {
145
+ return __awaiter(this, void 0, void 0, function () {
146
+ var _a, paths, failOn, maxWarnings, ignoreCode, ignorePath, format, json, verbose, config, _b, summary, error_1;
147
+ return __generator(this, function (_c) {
148
+ switch (_c.label) {
149
+ case 0:
150
+ _a = this.parsed, paths = _a.paths, failOn = _a.failOn, maxWarnings = _a.maxWarnings, ignoreCode = _a.ignoreCode, ignorePath = _a.ignorePath, format = _a.format, json = _a.json, verbose = _a.verbose;
151
+ if (failOn !== undefined && !failOnValues.includes(failOn)) {
152
+ (0, utils_1.displayError)(new Error("Invalid value for --fail-on: ".concat(failOn, ". Use one of ").concat(failOnValues.join(', '), ".")));
153
+ return [2 /*return*/, command_1.ReturnCode.InvalidCommand];
154
+ }
155
+ if (format !== undefined && !formats.includes(format)) {
156
+ (0, utils_1.displayError)(new Error("Invalid value for --format: ".concat(format, ". Use one of ").concat(formats.join(', '), ".")));
157
+ return [2 /*return*/, command_1.ReturnCode.InvalidCommand];
158
+ }
159
+ _c.label = 1;
160
+ case 1:
161
+ _c.trys.push([1, 4, , 5]);
162
+ _b = logCheckConfig_1.mergeConfig;
163
+ return [4 /*yield*/, (0, logCheckConfig_1.readLogCheckConfig)()];
164
+ case 2:
165
+ config = _b.apply(void 0, [_c.sent(), {
166
+ failOn: failOn,
167
+ maxWarnings: maxWarnings,
168
+ ignoreCodes: toStringArray(ignoreCode),
169
+ ignorePaths: toStringArray(ignorePath)
170
+ }]);
171
+ return [4 /*yield*/, (0, processLogCheck_1.processLogCheck)({
172
+ paths: toStringArray(paths),
173
+ config: config
174
+ })];
175
+ case 3:
176
+ summary = _c.sent();
177
+ (0, renderLogCheck_1.renderLogCheck)(summary, config, {
178
+ format: json ? 'json' : format,
179
+ verbose: !!verbose
180
+ });
181
+ return [2 /*return*/, summary.failure ? command_1.ReturnCode.LogCheckError : command_1.ReturnCode.Success];
182
+ case 4:
183
+ error_1 = _c.sent();
184
+ (0, utils_1.displayError)(error_1, 'An error has occurred when checking SAS logs.');
185
+ return [2 /*return*/, command_1.ReturnCode.InternalError];
186
+ case 5: return [2 /*return*/];
187
+ }
188
+ });
189
+ });
190
+ };
191
+ return LogCheckCommand;
192
+ }(types_1.CommandBase));
193
+ exports.LogCheckCommand = LogCheckCommand;
194
+ /**
195
+ * yargs hands back an array for a repeatable option, and nothing at all when it
196
+ * is not given, so every consumer gets a list.
197
+ */
198
+ var toStringArray = function (value) {
199
+ return Array.isArray(value) ? value.map(function (item) { return String(item); }) : [];
200
+ };
@@ -0,0 +1,44 @@
1
+ import { LogCheckConfig, LogCheckSummary, ScannedLog } from './types';
2
+ export interface LogCheckOptions {
3
+ /** Paths named on the command line. Empty means the project's results folder. */
4
+ paths?: string[];
5
+ config: LogCheckConfig;
6
+ }
7
+ /**
8
+ * Runs the check: expands the input, parses every log, applies the policy and
9
+ * says whether the run passes.
10
+ *
11
+ * The report is built here rather than in the renderer so that the three
12
+ * output formats cannot disagree about what was found - the summary is the
13
+ * single answer, and human, JSON and CI annotations are three ways of printing
14
+ * it.
15
+ */
16
+ export declare const processLogCheck: ({ paths, config }: LogCheckOptions) => Promise<LogCheckSummary>;
17
+ /**
18
+ * Where a run with no arguments looks. The project's results folder is where
19
+ * `run`, `job run` and `test` leave their logs, which makes the bare command
20
+ * do the useful thing inside a project.
21
+ */
22
+ export declare const defaultInputPath: (config: LogCheckConfig) => string;
23
+ /**
24
+ * Adds up what the run found. Skipped logs contribute nothing but are counted
25
+ * so the report can say how many files it passed over.
26
+ */
27
+ export declare const sumTotals: (scanned: ScannedLog[], fileCount?: number) => LogCheckSummary["totals"];
28
+ /**
29
+ * The verdict, as a phrase for the report.
30
+ *
31
+ * An ERROR always fails unless the policy is `never`; warnings and notes fail
32
+ * only when the policy asks for them, because notable notes are what legacy
33
+ * code emits in bulk and a gate that is red on its first run is a gate that
34
+ * gets commented out.
35
+ *
36
+ * A warnings budget is checked first and applies even under `never`: writing
37
+ * `maxWarnings: 0` is a specific instruction, and a specific instruction
38
+ * outranks a blanket one. `never` therefore means "do not fail on what you
39
+ * find", not "ignore the budget I also asked for".
40
+ *
41
+ * A path that could not be checked fails the run ahead of everything else: the
42
+ * answer to "is this log clean" is unknown, and unknown is not a pass.
43
+ */
44
+ export declare const describeFailure: (totals: LogCheckSummary["totals"], config: LogCheckConfig, missingCount?: number, unreadableCount?: number) => string | undefined;