@mitre/inspec-objects 1.0.1 → 2.0.1

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.
@@ -1,10 +1,23 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createDiffMarkdown = void 0;
3
+ exports.createDiffMarkdown = createDiffMarkdown;
4
4
  const tslib_1 = require("tslib");
5
5
  const mustache_1 = tslib_1.__importDefault(require("mustache"));
6
6
  const lodash_1 = tslib_1.__importDefault(require("lodash"));
7
7
  const automatticUpdateTemplate_json_1 = tslib_1.__importDefault(require("../resources/automatticUpdateTemplate.json"));
8
+ /**
9
+ * Generates a markdown representation of the differences between two profiles.
10
+ *
11
+ * The function processes the differences to create a renderable data structure
12
+ * that includes added controls, renamed controls, and updated properties such as
13
+ * checks, fixes, impacts, titles, and descriptions. It then uses a mustache template
14
+ * to render the markdown output.
15
+ *
16
+ * @param diff - An object containing the differences between two profiles.
17
+ * @param diff.ignoreFormattingDiff - The profile differences ignoring formatting changes.
18
+ * @param diff.rawDiff - The raw differences between the profiles.
19
+ * @returns A string containing the markdown representation of the differences.
20
+ */
8
21
  function createDiffMarkdown(diff) {
9
22
  const renderableDiffData = {
10
23
  addedControls: Object.values(diff.ignoreFormattingDiff.addedControls),
@@ -26,8 +39,8 @@ function createDiffMarkdown(diff) {
26
39
  Object.entries(diff.rawDiff.changedControls).forEach(([id, controlDiff]) => {
27
40
  var _a, _b;
28
41
  if ((_a = controlDiff.descs) === null || _a === void 0 ? void 0 : _a.check) {
29
- const oldCheck = lodash_1.default.get(controlDiff.descs.check, '__old');
30
- const newCheck = lodash_1.default.get(controlDiff.descs.check, '__new');
42
+ const oldCheck = lodash_1.default.get(controlDiff.descs.check, '__old', 'undefined');
43
+ const newCheck = lodash_1.default.get(controlDiff.descs.check, '__new', 'undefined');
31
44
  if (oldCheck.replace(/\n/g, '').replace(/\W/g, '') !==
32
45
  newCheck.replace(/\n/g, '').replace(/\W/g, '')) {
33
46
  renderableDiffData.updatedChecks.push({
@@ -38,8 +51,8 @@ function createDiffMarkdown(diff) {
38
51
  }
39
52
  }
40
53
  if ((_b = controlDiff.descs) === null || _b === void 0 ? void 0 : _b.fix) {
41
- const oldFix = lodash_1.default.get(controlDiff.descs.fix, '__old');
42
- const newFix = lodash_1.default.get(controlDiff.descs.fix, '__new');
54
+ const oldFix = lodash_1.default.get(controlDiff.descs.fix, '__old', 'undefined');
55
+ const newFix = lodash_1.default.get(controlDiff.descs.fix, '__new', 'undefined');
43
56
  if (oldFix.replace(/\n/g, '').replace(/\W/g, '') !==
44
57
  newFix.replace(/\n/g, '').replace(/\W/g, '')) {
45
58
  renderableDiffData.updatedFixes.push({
@@ -50,8 +63,8 @@ function createDiffMarkdown(diff) {
50
63
  }
51
64
  }
52
65
  if (controlDiff.impact) {
53
- const oldImpact = lodash_1.default.get(controlDiff.impact, '__old');
54
- const newImpact = lodash_1.default.get(controlDiff.impact, '__new');
66
+ const oldImpact = lodash_1.default.get(controlDiff.impact, '__old', 'undefined');
67
+ const newImpact = lodash_1.default.get(controlDiff.impact, '__new', 'undefined');
55
68
  if (oldImpact !== newImpact) {
56
69
  renderableDiffData.updatedImpacts.push({
57
70
  id: id,
@@ -61,8 +74,8 @@ function createDiffMarkdown(diff) {
61
74
  }
62
75
  }
63
76
  if (controlDiff.title) {
64
- const oldTitle = lodash_1.default.get(controlDiff.title, '__old');
65
- const newTitle = lodash_1.default.get(controlDiff.title, '__new');
77
+ const oldTitle = lodash_1.default.get(controlDiff.title, '__old', 'undefined');
78
+ const newTitle = lodash_1.default.get(controlDiff.title, '__new', 'undefined');
66
79
  if (oldTitle !== newTitle) {
67
80
  renderableDiffData.updatedTitles.push({
68
81
  id: id,
@@ -72,8 +85,8 @@ function createDiffMarkdown(diff) {
72
85
  }
73
86
  }
74
87
  if (controlDiff.desc) {
75
- const oldDesc = lodash_1.default.get(controlDiff.desc, '__old');
76
- const newDesc = lodash_1.default.get(controlDiff.desc, '__new');
88
+ const oldDesc = lodash_1.default.get(controlDiff.desc, '__old', 'undefined');
89
+ const newDesc = lodash_1.default.get(controlDiff.desc, '__new', 'undefined');
77
90
  if (oldDesc !== newDesc) {
78
91
  renderableDiffData.updatedDescriptions.push({
79
92
  id: id,
@@ -86,4 +99,3 @@ function createDiffMarkdown(diff) {
86
99
  // Render output
87
100
  return mustache_1.default.render(automatticUpdateTemplate_json_1.default.data, renderableDiffData);
88
101
  }
89
- exports.createDiffMarkdown = createDiffMarkdown;
@@ -1,7 +1,60 @@
1
+ /**
2
+ * Wraps a given string to a specified line length by inserting newline characters.
3
+ *
4
+ * @param s - The string to be wrapped.
5
+ * @param lineLength - The maximum length of each line before wrapping. Defaults to 80.
6
+ * @returns The wrapped string with newline characters inserted at appropriate positions.
7
+ */
1
8
  export declare function wrap(s: string, lineLength?: number): string;
9
+ /**
10
+ * Removes newline characters and excessive whitespace from a given string.
11
+ *
12
+ * This function replaces all newline characters (`\n` and `\\n`) with a single space,
13
+ * and collapses multiple spaces or tabs into a single space.
14
+ *
15
+ * @param s - The input string to be unformatted.
16
+ * @returns The unformatted string with newline characters and excessive whitespace removed.
17
+ */
2
18
  export declare function unformatText(s: string): string;
19
+ /**
20
+ * Removes all whitespace characters from the given input string.
21
+ *
22
+ * @param input - The string from which to remove whitespace.
23
+ * @returns A new string with all whitespace characters removed.
24
+ */
3
25
  export declare function removeWhitespace(input: string): string;
26
+ /**
27
+ * Escapes quotes in a given string based on the presence of single and double quotes.
28
+ *
29
+ * - If the string contains both single and double quotes, it wraps the string in `%q()` and escapes special case backslashes.
30
+ * - If the string contains only single quotes, it wraps the string in double quotes and escapes double quotes.
31
+ * - If the string contains only double quotes or no quotes, it wraps the string in single quotes and escapes single quotes.
32
+ *
33
+ * @param s - The input string to escape quotes in.
34
+ * @returns The string with appropriately escaped quotes.
35
+ */
4
36
  export declare function escapeQuotes(s: string): string;
37
+ /**
38
+ * Replaces all instances of the placeholder `{{{{newlineHERE}}}}` in the given string with newline characters.
39
+ *
40
+ * @param s - The string containing the placeholders to be replaced.
41
+ * @returns The modified string with placeholders replaced by newline characters.
42
+ */
5
43
  export declare function removeNewlinePlaceholders(s: string): string;
44
+ /**
45
+ * Retrieves the value from the first path in the provided paths array that exists in the given object.
46
+ *
47
+ * @param object - The object to search for the paths.
48
+ * @param paths - An array of string paths to check in the object.
49
+ * @returns The value from the first existing path in the object as a string.
50
+ * @throws Will throw an error if none of the paths exist in the object.
51
+ */
6
52
  export declare function getFirstPath(object: Record<string, unknown>, paths: string[]): string;
53
+ /**
54
+ * Checks if the given file object has any of the specified paths.
55
+ *
56
+ * @param file - The object to check for the presence of the path(s).
57
+ * @param path - A string or an array of strings representing the path(s) to check.
58
+ * @returns `true` if any of the specified paths exist in the object, otherwise `false`.
59
+ */
7
60
  export declare function hasPath(file: Record<string, unknown>, path: string | string[]): boolean;
@@ -1,9 +1,21 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.hasPath = exports.getFirstPath = exports.removeNewlinePlaceholders = exports.escapeQuotes = exports.removeWhitespace = exports.unformatText = exports.wrap = void 0;
3
+ exports.wrap = wrap;
4
+ exports.unformatText = unformatText;
5
+ exports.removeWhitespace = removeWhitespace;
6
+ exports.escapeQuotes = escapeQuotes;
7
+ exports.removeNewlinePlaceholders = removeNewlinePlaceholders;
8
+ exports.getFirstPath = getFirstPath;
9
+ exports.hasPath = hasPath;
4
10
  const tslib_1 = require("tslib");
5
11
  const lodash_1 = tslib_1.__importDefault(require("lodash"));
6
- // Breaks lines down to lineLength number of characters
12
+ /**
13
+ * Wraps a given string to a specified line length by inserting newline characters.
14
+ *
15
+ * @param s - The string to be wrapped.
16
+ * @param lineLength - The maximum length of each line before wrapping. Defaults to 80.
17
+ * @returns The wrapped string with newline characters inserted at appropriate positions.
18
+ */
7
19
  function wrap(s, lineLength = 80) {
8
20
  let newString = '';
9
21
  let currentLength = 0;
@@ -34,25 +46,75 @@ function wrap(s, lineLength = 80) {
34
46
  }
35
47
  return newString;
36
48
  }
37
- exports.wrap = wrap;
38
- // Remove new lines and tabs
49
+ /**
50
+ * Removes newline characters and excessive whitespace from a given string.
51
+ *
52
+ * This function replaces all newline characters (`\n` and `\\n`) with a single space,
53
+ * and collapses multiple spaces or tabs into a single space.
54
+ *
55
+ * @param s - The input string to be unformatted.
56
+ * @returns The unformatted string with newline characters and excessive whitespace removed.
57
+ */
39
58
  function unformatText(s) {
40
59
  return s.replace(/\n/g, ' ').replace(/\\n/g, ' ').replace(/( +|\t)/g, ' ');
41
60
  }
42
- exports.unformatText = unformatText;
61
+ /**
62
+ * Removes all whitespace characters from the given input string.
63
+ *
64
+ * @param input - The string from which to remove whitespace.
65
+ * @returns A new string with all whitespace characters removed.
66
+ */
43
67
  function removeWhitespace(input) {
44
68
  return input.replace(/\s/gi, '');
45
69
  }
46
- exports.removeWhitespace = removeWhitespace;
70
+ /**
71
+ * Escapes backslashes that precede closing parentheses in a given string.
72
+ *
73
+ * This function searches for occurrences of backslashes followed by a closing
74
+ * parenthesis in the input string and replaces each occurrence with two
75
+ * backslashes followed by a closing parenthesis. This effectively escapes
76
+ * the backslashes in such cases.
77
+ *
78
+ * @param s - The input string in which to escape backslashes.
79
+ * @returns A new string with the specified backslashes escaped.
80
+ */
47
81
  const escapeSpecialCaseBackslashes = (s) => {
48
- return s.replace(/\\\)/g, '\\\\)'); // Escape backslashes if preceding close parentheses
82
+ return s.replace(/\\\)/g, '\\\\)');
49
83
  };
84
+ /**
85
+ * Escapes single quotes and backslashes in a given string.
86
+ *
87
+ * This function replaces all backslashes (`\`) with double backslashes (`\\`)
88
+ * and all single quotes (`'`) with escaped single quotes (`\'`).
89
+ *
90
+ * @param s - The input string to be escaped.
91
+ * @returns The escaped string with single quotes and backslashes properly escaped.
92
+ */
50
93
  const escapeSingleQuotes = (s) => {
51
- return s.replace(/\\/g, '\\\\').replace(/'/g, "\\'"); // Escape backslashes and quotes
94
+ return s.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
52
95
  };
96
+ /**
97
+ * Escapes backslashes and double quotes in a given string.
98
+ *
99
+ * This function replaces all backslashes (`\`) with double backslashes (`\\`)
100
+ * and all double quotes (`"`) with escaped double quotes (`\"`).
101
+ *
102
+ * @param s - The input string to be escaped.
103
+ * @returns The escaped string with backslashes and double quotes properly escaped.
104
+ */
53
105
  const escapeDoubleQuotes = (s) => {
54
- return s.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); // Escape backslashes and double quotes
106
+ return s.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
55
107
  };
108
+ /**
109
+ * Escapes quotes in a given string based on the presence of single and double quotes.
110
+ *
111
+ * - If the string contains both single and double quotes, it wraps the string in `%q()` and escapes special case backslashes.
112
+ * - If the string contains only single quotes, it wraps the string in double quotes and escapes double quotes.
113
+ * - If the string contains only double quotes or no quotes, it wraps the string in single quotes and escapes single quotes.
114
+ *
115
+ * @param s - The input string to escape quotes in.
116
+ * @returns The string with appropriately escaped quotes.
117
+ */
56
118
  function escapeQuotes(s) {
57
119
  if (s.includes("'") && s.includes('"')) {
58
120
  return `%q(${escapeSpecialCaseBackslashes(removeNewlinePlaceholders(s))})`;
@@ -64,11 +126,23 @@ function escapeQuotes(s) {
64
126
  return `'${escapeSingleQuotes(removeNewlinePlaceholders(s))}'`;
65
127
  }
66
128
  }
67
- exports.escapeQuotes = escapeQuotes;
129
+ /**
130
+ * Replaces all instances of the placeholder `{{{{newlineHERE}}}}` in the given string with newline characters.
131
+ *
132
+ * @param s - The string containing the placeholders to be replaced.
133
+ * @returns The modified string with placeholders replaced by newline characters.
134
+ */
68
135
  function removeNewlinePlaceholders(s) {
69
136
  return s.replace(/\{\{\{\{newlineHERE\}\}\}\}/g, '\n');
70
137
  }
71
- exports.removeNewlinePlaceholders = removeNewlinePlaceholders;
138
+ /**
139
+ * Retrieves the value from the first path in the provided paths array that exists in the given object.
140
+ *
141
+ * @param object - The object to search for the paths.
142
+ * @param paths - An array of string paths to check in the object.
143
+ * @returns The value from the first existing path in the object as a string.
144
+ * @throws Will throw an error if none of the paths exist in the object.
145
+ */
72
146
  function getFirstPath(object, paths) {
73
147
  const index = lodash_1.default.findIndex(paths, (p) => hasPath(object, p));
74
148
  if (index === -1) {
@@ -78,7 +152,13 @@ function getFirstPath(object, paths) {
78
152
  return lodash_1.default.get(object, paths[index]);
79
153
  }
80
154
  }
81
- exports.getFirstPath = getFirstPath;
155
+ /**
156
+ * Checks if the given file object has any of the specified paths.
157
+ *
158
+ * @param file - The object to check for the presence of the path(s).
159
+ * @param path - A string or an array of strings representing the path(s) to check.
160
+ * @returns `true` if any of the specified paths exist in the object, otherwise `false`.
161
+ */
82
162
  function hasPath(file, path) {
83
163
  let pathArray;
84
164
  if (typeof path === 'string') {
@@ -89,4 +169,3 @@ function hasPath(file, path) {
89
169
  }
90
170
  return lodash_1.default.some(pathArray, (p) => lodash_1.default.has(file, p));
91
171
  }
92
- exports.hasPath = hasPath;
@@ -1,2 +1,2 @@
1
1
  import winston from 'winston';
2
- export declare function createWinstonLogger(level?: string): winston.Logger;
2
+ export declare function createWinstonLogger(mapperName: string, level?: string): winston.Logger;
@@ -1,15 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createWinstonLogger = void 0;
3
+ exports.createWinstonLogger = createWinstonLogger;
4
4
  const tslib_1 = require("tslib");
5
5
  const winston_1 = tslib_1.__importDefault(require("winston"));
6
- function createWinstonLogger(level = 'debug') {
6
+ function createWinstonLogger(mapperName, level = 'debug') {
7
7
  return winston_1.default.createLogger({
8
8
  transports: [new winston_1.default.transports.Console()],
9
9
  level: level,
10
- format: winston_1.default.format.combine(winston_1.default.format.timestamp({
10
+ format: winston_1.default.format.combine(winston_1.default.format.simple(), winston_1.default.format.timestamp({
11
11
  format: 'MMM-DD-YYYY HH:mm:ss Z',
12
- }), winston_1.default.format.printf(info => `[${[info.timestamp]}] ${info.message}`)),
12
+ }), winston_1.default.format.printf(
13
+ // Using the ANSI escape code sequence initiator (\xb[) to change output colors
14
+ // Colors used are: 33m (yellow) and 34m (blue)
15
+ // \x1b[0m : Resets the color settings to the default
16
+ info => `\x1b[33m[${[info.timestamp]} -> ${mapperName}]:\x1b[0m \x1b[34m${info.message}\x1b[0m`)),
13
17
  });
14
18
  }
15
- exports.createWinstonLogger = createWinstonLogger;
@@ -3,7 +3,18 @@ import Control from '../objects/control';
3
3
  import Profile from '../objects/profile';
4
4
  import { ProfileDiff } from '../types/diff';
5
5
  import { OvalDefinitionValue } from '../types/oval';
6
- export declare type UpdatedProfileReturn = {
6
+ /**
7
+ * Represents the return type of an updated profile operation.
8
+ *
9
+ * @typedef UpdatedProfileReturn
10
+ *
11
+ * @property {Profile} profile - The updated profile object.
12
+ * @property {Object} diff - The differences between the original and updated profiles.
13
+ * @property {ProfileDiff} diff.ignoreFormattingDiff - The differences ignoring formatting changes.
14
+ * @property {Record<string, unknown>} diff.rawDiff - The raw differences as a record.
15
+ * @property {string} markdown - The markdown representation of the differences.
16
+ */
17
+ export type UpdatedProfileReturn = {
7
18
  profile: Profile;
8
19
  diff: {
9
20
  ignoreFormattingDiff: ProfileDiff;
@@ -11,8 +22,56 @@ export declare type UpdatedProfileReturn = {
11
22
  };
12
23
  markdown: string;
13
24
  };
25
+ /**
26
+ * This is the most likely thing to break if you are getting code formatting issues.
27
+ *
28
+ * Extracts the `describe` block (what is actually run by inspec for validation)
29
+ * from an InSpec control object, collapsing multi-line strings.
30
+ *
31
+ * @param control - The InSpec control object containing the code to extract the `describe` block from.
32
+ * @returns The extracted `describe` block as a string, or an empty string if the control has no code.
33
+ */
14
34
  export declare function getExistingDescribeFromControl(control: Control): string;
35
+ /**
36
+ * Finds an updated control from a list of updated controls by matching all possible identifiers.
37
+ *
38
+ * This function attempts to find a matching control in the `updatedControls` array by comparing
39
+ * the `id` of the `existingControl` with the `id` of each control in the `updatedControls` array.
40
+ * If no match is found based on `id`, it then tries to match based on legacy identifiers found
41
+ * in the `tags.legacy` property of each control in the `updatedControls` array.
42
+ *
43
+ * @param existingControl - The control to find a match for in the updated controls.
44
+ * @param updatedControls - An array of updated controls to search through.
45
+ * @returns The matching updated control if found, otherwise `undefined`.
46
+ */
15
47
  export declare function findUpdatedControlByAllIdentifiers(existingControl: Control, updatedControls: Control[]): Control | undefined;
48
+ /**
49
+ * Updates a given control object with the provided partial update and logs the process.
50
+ *
51
+ * @param {Control} from - The original control object to be updated.
52
+ * @param {Partial<Control>} update - An object containing the properties to update in the original control.
53
+ * @param {winston.Logger} logger - A logger instance to log debug information.
54
+ * @returns {Control} - The updated control object.
55
+ */
16
56
  export declare function updateControl(from: Control, update: Partial<Control>, logger: winston.Logger): Control;
57
+ /**
58
+ * Updates the describe block of a control with the describe block from another control.
59
+ *
60
+ * @param from - The control from which to get the existing describe block.
61
+ * @param update - The partial control data to update.
62
+ * @param logger - The logger instance to use for logging debug information.
63
+ * @returns The updated control with the describe block from the `from` control.
64
+ */
65
+ export declare function updateControlDescribeBlock(from: Control, update: Partial<Control>, logger: winston.Logger): Control;
66
+ /**
67
+ * Updates a given profile with new metadata and controls from another profile.
68
+ *
69
+ * @param from - The original profile to be updated.
70
+ * @param using - The profile containing the new metadata and controls.
71
+ * @param logger - A winston logger instance for logging debug information.
72
+ * @returns An object containing the updated profile and the diff between the original and updated profiles, excluding markdown.
73
+ *
74
+ * @throws Will throw an error if a new control is added but the control data is not available.
75
+ */
17
76
  export declare function updateProfile(from: Profile, using: Profile, logger: winston.Logger): Omit<UpdatedProfileReturn, 'markdown'>;
18
77
  export declare function updateProfileUsingXCCDF(from: Profile, using: string, id: 'group' | 'rule' | 'version' | 'cis', logger: winston.Logger, ovalDefinitions?: Record<string, OvalDefinitionValue>): UpdatedProfileReturn;
@@ -2,13 +2,39 @@
2
2
  // Utilities to update a profile or control with new metadata
3
3
  // The ultimate goal is to preserve all the metadata that is already there and only add what is new
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
- exports.updateProfileUsingXCCDF = exports.updateProfile = exports.updateControl = exports.findUpdatedControlByAllIdentifiers = exports.getExistingDescribeFromControl = void 0;
5
+ exports.getExistingDescribeFromControl = getExistingDescribeFromControl;
6
+ exports.findUpdatedControlByAllIdentifiers = findUpdatedControlByAllIdentifiers;
7
+ exports.updateControl = updateControl;
8
+ exports.updateControlDescribeBlock = updateControlDescribeBlock;
9
+ exports.updateProfile = updateProfile;
10
+ exports.updateProfileUsingXCCDF = updateProfileUsingXCCDF;
6
11
  const tslib_1 = require("tslib");
7
12
  const lodash_1 = tslib_1.__importDefault(require("lodash"));
8
13
  const diff_1 = require("./diff");
14
+ const control_1 = tslib_1.__importDefault(require("../objects/control"));
9
15
  const profile_1 = tslib_1.__importDefault(require("../objects/profile"));
10
16
  const xccdf_1 = require("../parsers/xccdf");
11
17
  const diffMarkdown_1 = require("./diffMarkdown");
18
+ /**
19
+ * Projects values from the source object onto the destination object,
20
+ * updating the destination object in place.
21
+ *
22
+ * @param dst - The destination object to be updated.
23
+ * @param src - The source object containing new values.
24
+ * @param currentPath - The current path being processed (used for nested objects).
25
+ * @returns The updated destination object.
26
+ *
27
+ * @remarks
28
+ * - If a value in the source object is an object and the corresponding value
29
+ * in the destination object is also an object, the function will recursively
30
+ * update the nested object.
31
+ * - If a value in the source object is a string, it will be trimmed before
32
+ * being set in the destination object.
33
+ * - If a value in the source object is a number, it will be directly set in
34
+ * the destination object.
35
+ * - If a value in the source object is an array, the function will merge it with
36
+ * the corresponding array in the destination object, ensuring unique values.
37
+ */
12
38
  function projectValuesOntoExistingObj(dst, src, currentPath = '') {
13
39
  for (const updatedValue in src) {
14
40
  const existingValue = lodash_1.default.get(dst, updatedValue);
@@ -31,28 +57,32 @@ function projectValuesOntoExistingObj(dst, src, currentPath = '') {
31
57
  }
32
58
  return dst;
33
59
  }
60
+ /**
61
+ * Returns an array containing two numerical indices (i.e., start and stop
62
+ * line numbers) for each string or multi-line comment, given raw text as
63
+ * an input parameter. The raw text is a string containing the entirety of an
64
+ * InSpec control.
65
+ *
66
+ * The function utilizes a pair of stacks (i.e., `stack`, `rangeStack`) to keep
67
+ * track of string delimiters and their associated line numbers, respectively.
68
+ *
69
+ * Combinations Handled:
70
+ * - Single quotes (')
71
+ * - Double quotes (")
72
+ * - Back ticks (`)
73
+ * - Mixed quotes ("`'")
74
+ * - Percent strings (%; keys: q, Q, r, i, I, w, W, x; delimiters: (), {},
75
+ * [], <>, most non-alphanumeric characters); (e.g., "%q()")
76
+ * - Percent literals (%; delimiters: (), {}, [], <>, most non-
77
+ * alphanumeric characters); (e.g., "%()")
78
+ * - Multi-line comments (e.g., =begin\nSome comment\n=end)
79
+ * - Variable delimiters (i.e., parenthesis: (); array: []; hash: {})
80
+ *
81
+ * @param text - The raw text containing the entirety of an InSpec control.
82
+ * @returns An array of arrays, each containing two numerical indices representing
83
+ * the start and stop line numbers for each string or multi-line comment.
84
+ */
34
85
  function getRangesForLines(text) {
35
- /*
36
- Returns an array containing two numerical indices (i.e., start and stop
37
- line numbers) for each string or multi-line comment, given raw text as
38
- an input parameter. The raw text is a string containing the entirety of an
39
- InSpec control.
40
-
41
- Algorithm utilizes a pair of stacks (i.e., `stack`, `rangeStack`) to keep
42
- track of string delimiters and their associated line numbers, respectively.
43
-
44
- Combinations Handled:
45
- - Single quotes (')
46
- - Double quotes (")
47
- - Back ticks (`)
48
- - Mixed quotes ("`'")
49
- - Percent strings (%; keys: q, Q, r, i, I, w, W, x; delimiters: (), {},
50
- [], <>, most non-alphanumeric characters); (e.g., "%q()")
51
- - Percent literals (%; delimiters: (), {}, [], <>, most non-
52
- alphanumeric characters); (e.g., "%()")
53
- - Multi-line comments (e.g., =begin\nSome comment\n=end)
54
- - Variable delimiters (i.e., parenthesis: (); array: []; hash: {})
55
- */
56
86
  const stringDelimiters = { '(': ')', '{': '}', '[': ']', '<': '>' };
57
87
  const variableDelimiters = { '(': ')', '{': '}', '[': ']' };
58
88
  const quotes = '\'"`';
@@ -145,11 +175,17 @@ function getRangesForLines(text) {
145
175
  }
146
176
  return ranges;
147
177
  }
178
+ /**
179
+ * Joins lines of text from specified ranges and returns an array of strings.
180
+ * Each range is specified by a start and stop index, and the lines within
181
+ * those ranges are joined together.
182
+ *
183
+ * @param text - The raw text input to be processed.
184
+ * @param ranges - An array of ranges, where each range is a tuple containing
185
+ * the start and stop indices (inclusive) of the lines to be joined.
186
+ * @returns An array of strings, where lines within the specified ranges are joined.
187
+ */
148
188
  function joinMultiLineStringsFromRanges(text, ranges) {
149
- /*
150
- Returns an array of strings and joined strings at specified ranges, given
151
- raw text as an input parameter.
152
- */
153
189
  const originalLines = text.split('\n');
154
190
  const joinedLines = [];
155
191
  let i = 0;
@@ -170,11 +206,16 @@ function joinMultiLineStringsFromRanges(text, ranges) {
170
206
  }
171
207
  return joinedLines;
172
208
  }
209
+ /**
210
+ * Filters out ranges that span only a single line. There is,
211
+ * drops ranges with the same start and stop line numbers (i.e., strings
212
+ * that populate a single line)
213
+ *
214
+ * @param ranges - An array of ranges, where each range is represented by a
215
+ * tuple of start and stop line numbers.
216
+ * @returns An array of ranges that span multiple lines.
217
+ */
173
218
  function getMultiLineRanges(ranges) {
174
- /*
175
- Drops ranges with the same start and stop line numbers (i.e., strings
176
- that populate a single line)
177
- */
178
219
  const multiLineRanges = [];
179
220
  for (const [start, stop] of ranges) {
180
221
  if (start !== stop) {
@@ -183,10 +224,15 @@ function getMultiLineRanges(ranges) {
183
224
  }
184
225
  return multiLineRanges;
185
226
  }
186
- /*
187
- This is the most likely thing to break if you are getting code formatting issues.
188
- Extract the existing describe blocks (what is actually run by inspec for validation)
189
- */
227
+ /**
228
+ * This is the most likely thing to break if you are getting code formatting issues.
229
+ *
230
+ * Extracts the `describe` block (what is actually run by inspec for validation)
231
+ * from an InSpec control object, collapsing multi-line strings.
232
+ *
233
+ * @param control - The InSpec control object containing the code to extract the `describe` block from.
234
+ * @returns The extracted `describe` block as a string, or an empty string if the control has no code.
235
+ */
190
236
  function getExistingDescribeFromControl(control) {
191
237
  if (control.code) {
192
238
  // Join multi-line strings in InSpec control.
@@ -214,13 +260,28 @@ function getExistingDescribeFromControl(control) {
214
260
  }
215
261
  }
216
262
  // Return synthesized logic as describe block
217
- return describeBlock.slice(0, describeBlock.lastIndexOf('end')).join('\n'); // Drop trailing ['end', '\n'] from Control block.
263
+ const lastIndex = (describeBlock.lastIndexOf('end') === -1)
264
+ ? describeBlock.lastIndexOf('end\r')
265
+ : describeBlock.lastIndexOf('end');
266
+ // Drop trailing ['end', '\n'] from Control block.
267
+ return describeBlock.slice(0, lastIndex).join('\n');
218
268
  }
219
269
  else {
220
270
  return '';
221
271
  }
222
272
  }
223
- exports.getExistingDescribeFromControl = getExistingDescribeFromControl;
273
+ /**
274
+ * Finds an updated control from a list of updated controls by matching all possible identifiers.
275
+ *
276
+ * This function attempts to find a matching control in the `updatedControls` array by comparing
277
+ * the `id` of the `existingControl` with the `id` of each control in the `updatedControls` array.
278
+ * If no match is found based on `id`, it then tries to match based on legacy identifiers found
279
+ * in the `tags.legacy` property of each control in the `updatedControls` array.
280
+ *
281
+ * @param existingControl - The control to find a match for in the updated controls.
282
+ * @param updatedControls - An array of updated controls to search through.
283
+ * @returns The matching updated control if found, otherwise `undefined`.
284
+ */
224
285
  function findUpdatedControlByAllIdentifiers(existingControl, updatedControls) {
225
286
  // Try to match based on IDs
226
287
  let updatedControl = updatedControls.find((updatedControl) => {
@@ -242,7 +303,14 @@ function findUpdatedControlByAllIdentifiers(existingControl, updatedControls) {
242
303
  }
243
304
  return undefined;
244
305
  }
245
- exports.findUpdatedControlByAllIdentifiers = findUpdatedControlByAllIdentifiers;
306
+ /**
307
+ * Updates a given control object with the provided partial update and logs the process.
308
+ *
309
+ * @param {Control} from - The original control object to be updated.
310
+ * @param {Partial<Control>} update - An object containing the properties to update in the original control.
311
+ * @param {winston.Logger} logger - A logger instance to log debug information.
312
+ * @returns {Control} - The updated control object.
313
+ */
246
314
  function updateControl(from, update, logger) {
247
315
  const existingDescribeBlock = getExistingDescribeFromControl(from);
248
316
  logger.debug(`Existing describe block for control ${from.id}: ${JSON.stringify(existingDescribeBlock)}`);
@@ -250,7 +318,31 @@ function updateControl(from, update, logger) {
250
318
  projectedControl.describe = existingDescribeBlock;
251
319
  return projectedControl;
252
320
  }
253
- exports.updateControl = updateControl;
321
+ /**
322
+ * Updates the describe block of a control with the describe block from another control.
323
+ *
324
+ * @param from - The control from which to get the existing describe block.
325
+ * @param update - The partial control data to update.
326
+ * @param logger - The logger instance to use for logging debug information.
327
+ * @returns The updated control with the describe block from the `from` control.
328
+ */
329
+ function updateControlDescribeBlock(from, update, logger) {
330
+ const existingDescribeBlock = getExistingDescribeFromControl(from);
331
+ logger.debug(`Updating control ${update.id} with this describe block: ${JSON.stringify(existingDescribeBlock)}`);
332
+ const projectedControl = new control_1.default(update);
333
+ projectedControl.describe = existingDescribeBlock;
334
+ return projectedControl;
335
+ }
336
+ /**
337
+ * Updates a given profile with new metadata and controls from another profile.
338
+ *
339
+ * @param from - The original profile to be updated.
340
+ * @param using - The profile containing the new metadata and controls.
341
+ * @param logger - A winston logger instance for logging debug information.
342
+ * @returns An object containing the updated profile and the diff between the original and updated profiles, excluding markdown.
343
+ *
344
+ * @throws Will throw an error if a new control is added but the control data is not available.
345
+ */
254
346
  function updateProfile(from, using, logger) {
255
347
  // Update the profile with the new metadata
256
348
  const to = new profile_1.default(lodash_1.default.omit(from, 'controls'));
@@ -285,9 +377,8 @@ function updateProfile(from, using, logger) {
285
377
  diff,
286
378
  };
287
379
  }
288
- exports.updateProfile = updateProfile;
289
380
  function updateProfileUsingXCCDF(from, using, id, logger, ovalDefinitions) {
290
- logger.debug(`Updating profile ${from.name} with control IDs: ${id}`);
381
+ logger.info(`Updating profile ${from.name} with control IDs type: ${id}`);
291
382
  // Parse the XCCDF benchmark and convert it into a Profile
292
383
  logger.debug('Loading XCCDF File');
293
384
  const xccdfProfile = (0, xccdf_1.processXCCDF)(using, false, id, ovalDefinitions);
@@ -305,4 +396,3 @@ function updateProfileUsingXCCDF(from, using, id, logger, ovalDefinitions) {
305
396
  markdown: markdown
306
397
  };
307
398
  }
308
- exports.updateProfileUsingXCCDF = updateProfileUsingXCCDF;