@mitre/hdf-utilities 3.0.1 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/xml/index.js CHANGED
@@ -1,187 +1,157 @@
1
+ import { findValuesByKey } from "../object/index.js";
2
+ import { XMLBuilder, XMLParser, XMLValidator } from "fast-xml-parser";
3
+ //#region src/xml/index.ts
1
4
  /**
2
- * XML parsing utilities for HDF converters
3
- * Provides XML parsing with sensible defaults for security tool output
4
- */
5
- import { XMLParser, XMLBuilder, XMLValidator } from 'fast-xml-parser';
6
- export { findValuesByKey as findXmlValues } from '../object/index.js';
5
+ * XML parsing utilities for HDF converters
6
+ * Provides XML parsing with sensible defaults for security tool output
7
+ */
7
8
  /**
8
- * Default options for XML parsing optimized for security tool converters.
9
- *
10
- * **XXE Safety**: fast-xml-parser v5.x does not process DTD or external entities
11
- * by default. The `processEntities: false` option is set as defense-in-depth to
12
- * ensure entity expansion is disabled even if future versions change defaults.
13
- * See: https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/docs/v5/2.XMLparseOptions.md
14
- */
9
+ * Default options for XML parsing optimized for security tool converters.
10
+ *
11
+ * **XXE Safety**: fast-xml-parser v5.x does not process DTD or external entities
12
+ * by default. The `processEntities: false` option is set as defense-in-depth to
13
+ * ensure entity expansion is disabled even if future versions change defaults.
14
+ * See: https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/docs/v5/2.XMLparseOptions.md
15
+ */
15
16
  const DEFAULT_PARSE_OPTIONS = {
16
- attributeNamePrefix: '',
17
- textNodeName: '#text',
18
- ignoreAttributes: false,
19
- ignoreDeclaration: true,
20
- parseAttributeValue: false,
21
- parseTagValue: false,
22
- removeNSPrefix: true,
23
- processEntities: false,
17
+ attributeNamePrefix: "",
18
+ textNodeName: "#text",
19
+ ignoreAttributes: false,
20
+ ignoreDeclaration: true,
21
+ parseAttributeValue: false,
22
+ parseTagValue: false,
23
+ removeNSPrefix: true,
24
+ processEntities: false
24
25
  };
25
26
  /**
26
- * Default options for XML building
27
- */
27
+ * Default options for XML building
28
+ */
28
29
  const DEFAULT_BUILD_OPTIONS = {
29
- attributeNamePrefix: '',
30
- textNodeName: '#text',
31
- ignoreAttributes: false,
32
- format: true,
33
- indentBy: ' ',
34
- suppressEmptyNode: false,
30
+ attributeNamePrefix: "",
31
+ textNodeName: "#text",
32
+ ignoreAttributes: false,
33
+ format: true,
34
+ indentBy: " ",
35
+ suppressEmptyNode: false
35
36
  };
36
37
  /**
37
- * Parse XML string to JavaScript object
38
- *
39
- * @param xml - XML string to parse
40
- * @param options - Optional parser configuration (merged with defaults)
41
- * @returns Parsed JavaScript object
42
- *
43
- * @example
44
- * ```typescript
45
- * const xmlString = '<root><item id="1">Test</item></root>';
46
- * const result = parseXml(xmlString);
47
- * // Returns: { root: { item: { id: '1', '#text': 'Test' } } }
48
- * ```
49
- */
50
- export function parseXml(xml, options) {
51
- if (options?.maxSize !== undefined && xml.length > options.maxSize) {
52
- throw new Error(`Input exceeds maximum allowed size: ${xml.length} bytes exceeds limit of ${options.maxSize} bytes`);
53
- }
54
- // Validate XML first
55
- const validation = XMLValidator.validate(xml);
56
- if (validation !== true) {
57
- throw new Error(`Invalid XML: ${validation.err.msg}`);
58
- }
59
- const mergedOptions = {
60
- ...DEFAULT_PARSE_OPTIONS,
61
- ...options,
62
- };
63
- const parser = new XMLParser(mergedOptions);
64
- return parser.parse(xml);
38
+ * Parse XML string to JavaScript object
39
+ *
40
+ * @param xml - XML string to parse
41
+ * @param options - Optional parser configuration (merged with defaults)
42
+ * @returns Parsed JavaScript object
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * const xmlString = '<root><item id="1">Test</item></root>';
47
+ * const result = parseXml(xmlString);
48
+ * // Returns: { root: { item: { id: '1', '#text': 'Test' } } }
49
+ * ```
50
+ */
51
+ function parseXml(xml, options) {
52
+ if (options?.maxSize !== void 0 && xml.length > options.maxSize) throw new Error(`Input exceeds maximum allowed size: ${xml.length} bytes exceeds limit of ${options.maxSize} bytes`);
53
+ const validation = XMLValidator.validate(xml);
54
+ if (validation !== true) throw new Error(`Invalid XML: ${validation.err.msg}`);
55
+ return new XMLParser({
56
+ ...DEFAULT_PARSE_OPTIONS,
57
+ ...options
58
+ }).parse(xml);
65
59
  }
66
60
  /**
67
- * Build XML string from JavaScript object
68
- *
69
- * @param obj - JavaScript object to convert to XML
70
- * @param options - Optional builder configuration (merged with defaults)
71
- * @returns XML string
72
- *
73
- * @example
74
- * ```typescript
75
- * const obj = { root: { item: { id: '1', '#text': 'Test' } } };
76
- * const xml = buildXml(obj);
77
- * // Returns formatted XML string
78
- * ```
79
- */
80
- export function buildXml(obj, options) {
81
- const mergedOptions = {
82
- ...DEFAULT_BUILD_OPTIONS,
83
- ...options,
84
- };
85
- const builder = new XMLBuilder(mergedOptions);
86
- return builder.build(obj);
61
+ * Build XML string from JavaScript object
62
+ *
63
+ * @param obj - JavaScript object to convert to XML
64
+ * @param options - Optional builder configuration (merged with defaults)
65
+ * @returns XML string
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * const obj = { root: { item: { id: '1', '#text': 'Test' } } };
70
+ * const xml = buildXml(obj);
71
+ * // Returns formatted XML string
72
+ * ```
73
+ */
74
+ function buildXml(obj, options) {
75
+ return new XMLBuilder({
76
+ ...DEFAULT_BUILD_OPTIONS,
77
+ ...options
78
+ }).build(obj);
87
79
  }
88
80
  /**
89
- * Validate if a string is well-formed XML
90
- *
91
- * @param xml - String to validate
92
- * @returns True if valid XML, false otherwise
93
- *
94
- * @example
95
- * ```typescript
96
- * isValidXml('<root><item>Test</item></root>'); // true
97
- * isValidXml('<root><item>Test</root>'); // false (mismatched tags)
98
- * isValidXml('not xml'); // false
99
- * ```
100
- */
101
- export function isValidXml(xml) {
102
- if (!xml || xml.trim().length === 0) {
103
- return false;
104
- }
105
- const result = XMLValidator.validate(xml);
106
- return result === true;
81
+ * Validate if a string is well-formed XML
82
+ *
83
+ * @param xml - String to validate
84
+ * @returns True if valid XML, false otherwise
85
+ *
86
+ * @example
87
+ * ```typescript
88
+ * isValidXml('<root><item>Test</item></root>'); // true
89
+ * isValidXml('<root><item>Test</root>'); // false (mismatched tags)
90
+ * isValidXml('not xml'); // false
91
+ * ```
92
+ */
93
+ function isValidXml(xml) {
94
+ if (!xml || xml.trim().length === 0) return false;
95
+ return XMLValidator.validate(xml) === true;
107
96
  }
108
97
  /**
109
- * Parse XML with array handling for repeated elements
110
- * Forces specified tag names to always be arrays, even if only one element exists
111
- *
112
- * @param xml - XML string to parse
113
- * @param arrayTags - Array of tag names that should always be parsed as arrays
114
- * @returns Parsed JavaScript object with forced arrays
115
- *
116
- * @example
117
- * ```typescript
118
- * const xml = '<root><item>Test</item></root>';
119
- * const result = parseXmlWithArrays(xml, ['item']);
120
- * // Returns: { root: { item: ['Test'] } }
121
- * // Without arrayTags, would return: { root: { item: 'Test' } }
122
- * ```
123
- */
124
- export function parseXmlWithArrays(xml, arrayTags, options) {
125
- if (options?.maxSize !== undefined && xml.length > options.maxSize) {
126
- throw new Error(`Input exceeds maximum allowed size: ${xml.length} bytes exceeds limit of ${options.maxSize} bytes`);
127
- }
128
- // Validate XML first (consistency with parseXml)
129
- const validation = XMLValidator.validate(xml);
130
- if (validation !== true) {
131
- throw new Error(`Invalid XML: ${validation.err.msg}`);
132
- }
133
- const mergedOptions = {
134
- ...DEFAULT_PARSE_OPTIONS,
135
- ...options,
136
- isArray: (tagName) => arrayTags.includes(tagName),
137
- };
138
- const parser = new XMLParser(mergedOptions);
139
- return parser.parse(xml);
98
+ * Parse XML with array handling for repeated elements
99
+ * Forces specified tag names to always be arrays, even if only one element exists
100
+ *
101
+ * @param xml - XML string to parse
102
+ * @param arrayTags - Array of tag names that should always be parsed as arrays
103
+ * @returns Parsed JavaScript object with forced arrays
104
+ *
105
+ * @example
106
+ * ```typescript
107
+ * const xml = '<root><item>Test</item></root>';
108
+ * const result = parseXmlWithArrays(xml, ['item']);
109
+ * // Returns: { root: { item: ['Test'] } }
110
+ * // Without arrayTags, would return: { root: { item: 'Test' } }
111
+ * ```
112
+ */
113
+ function parseXmlWithArrays(xml, arrayTags, options) {
114
+ if (options?.maxSize !== void 0 && xml.length > options.maxSize) throw new Error(`Input exceeds maximum allowed size: ${xml.length} bytes exceeds limit of ${options.maxSize} bytes`);
115
+ const validation = XMLValidator.validate(xml);
116
+ if (validation !== true) throw new Error(`Invalid XML: ${validation.err.msg}`);
117
+ return new XMLParser({
118
+ ...DEFAULT_PARSE_OPTIONS,
119
+ ...options,
120
+ isArray: (tagName) => arrayTags.includes(tagName)
121
+ }).parse(xml);
140
122
  }
141
123
  /**
142
- * Extract text content from XML, stripping all tags
143
- *
144
- * @param xml - XML string
145
- * @returns Plain text content with tags removed
146
- *
147
- * @example
148
- * ```typescript
149
- * const xml = '<root><b>Bold</b> and <i>italic</i></root>';
150
- * const text = extractTextFromXml(xml);
151
- * // Returns: 'Bold and italic'
152
- * ```
153
- */
154
- export function extractTextFromXml(xml) {
155
- if (!isValidXml(xml)) {
156
- return '';
157
- }
158
- try {
159
- const parsed = parseXml(xml, { ignoreAttributes: true });
160
- return extractTextRecursive(parsed).trim();
161
- }
162
- catch {
163
- return '';
164
- }
124
+ * Extract text content from XML, stripping all tags
125
+ *
126
+ * @param xml - XML string
127
+ * @returns Plain text content with tags removed
128
+ *
129
+ * @example
130
+ * ```typescript
131
+ * const xml = '<root><b>Bold</b> and <i>italic</i></root>';
132
+ * const text = extractTextFromXml(xml);
133
+ * // Returns: 'Bold and italic'
134
+ * ```
135
+ */
136
+ function extractTextFromXml(xml) {
137
+ if (!isValidXml(xml)) return "";
138
+ try {
139
+ return extractTextRecursive(parseXml(xml, { ignoreAttributes: true })).trim();
140
+ } catch {
141
+ return "";
142
+ }
165
143
  }
166
144
  /**
167
- * Recursively extract text from parsed XML object
168
- * @private
169
- */
145
+ * Recursively extract text from parsed XML object
146
+ * @private
147
+ */
170
148
  function extractTextRecursive(obj) {
171
- if (typeof obj === 'string' || typeof obj === 'number') {
172
- return String(obj);
173
- }
174
- if (Array.isArray(obj)) {
175
- return obj.map(extractTextRecursive).join(' ');
176
- }
177
- if (typeof obj === 'object' && obj !== null) {
178
- const record = obj;
179
- // Recursively extract text from all properties
180
- return Object.values(record)
181
- .map(extractTextRecursive)
182
- .filter(text => text.length > 0)
183
- .join(' ');
184
- }
185
- return '';
149
+ if (typeof obj === "string" || typeof obj === "number") return String(obj);
150
+ if (Array.isArray(obj)) return obj.map(extractTextRecursive).join(" ");
151
+ if (typeof obj === "object" && obj !== null) return Object.values(obj).map(extractTextRecursive).filter((text) => text.length > 0).join(" ");
152
+ return "";
186
153
  }
154
+ //#endregion
155
+ export { buildXml, extractTextFromXml, findValuesByKey as findXmlValues, isValidXml, parseXml, parseXmlWithArrays };
156
+
187
157
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/xml/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/xml/index.ts"],"sourcesContent":["/**\n * XML parsing utilities for HDF converters\n * Provides XML parsing with sensible defaults for security tool output\n */\n\nimport { XMLParser, XMLBuilder, XMLValidator } from 'fast-xml-parser';\nimport type { X2jOptions, XmlBuilderOptions } from 'fast-xml-parser';\n\nexport { findValuesByKey as findXmlValues } from '../object/index.js';\n\n/**\n * Default options for XML parsing optimized for security tool converters.\n *\n * **XXE Safety**: fast-xml-parser v5.x does not process DTD or external entities\n * by default. The `processEntities: false` option is set as defense-in-depth to\n * ensure entity expansion is disabled even if future versions change defaults.\n * See: https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/docs/v5/2.XMLparseOptions.md\n */\nconst DEFAULT_PARSE_OPTIONS: Partial<X2jOptions> = {\n attributeNamePrefix: '',\n textNodeName: '#text',\n ignoreAttributes: false,\n ignoreDeclaration: true,\n parseAttributeValue: false,\n parseTagValue: false,\n removeNSPrefix: true,\n processEntities: false,\n};\n\n/**\n * Default options for XML building\n */\nconst DEFAULT_BUILD_OPTIONS: Partial<XmlBuilderOptions> = {\n attributeNamePrefix: '',\n textNodeName: '#text',\n ignoreAttributes: false,\n format: true,\n indentBy: ' ',\n suppressEmptyNode: false,\n};\n\n/**\n * Parse XML string to JavaScript object\n *\n * @param xml - XML string to parse\n * @param options - Optional parser configuration (merged with defaults)\n * @returns Parsed JavaScript object\n *\n * @example\n * ```typescript\n * const xmlString = '<root><item id=\"1\">Test</item></root>';\n * const result = parseXml(xmlString);\n * // Returns: { root: { item: { id: '1', '#text': 'Test' } } }\n * ```\n */\nexport function parseXml(\n xml: string,\n options?: Partial<X2jOptions> & { maxSize?: number }\n): Record<string, unknown> {\n if (options?.maxSize !== undefined && xml.length > options.maxSize) {\n throw new Error(\n `Input exceeds maximum allowed size: ${xml.length} bytes exceeds limit of ${options.maxSize} bytes`\n );\n }\n\n // Validate XML first\n const validation = XMLValidator.validate(xml);\n if (validation !== true) {\n throw new Error(`Invalid XML: ${validation.err.msg}`);\n }\n\n const mergedOptions = {\n ...DEFAULT_PARSE_OPTIONS,\n ...options,\n };\n\n const parser = new XMLParser(mergedOptions);\n return parser.parse(xml) as Record<string, unknown>;\n}\n\n/**\n * Build XML string from JavaScript object\n *\n * @param obj - JavaScript object to convert to XML\n * @param options - Optional builder configuration (merged with defaults)\n * @returns XML string\n *\n * @example\n * ```typescript\n * const obj = { root: { item: { id: '1', '#text': 'Test' } } };\n * const xml = buildXml(obj);\n * // Returns formatted XML string\n * ```\n */\nexport function buildXml(\n obj: Record<string, unknown>,\n options?: Partial<XmlBuilderOptions>\n): string {\n const mergedOptions = {\n ...DEFAULT_BUILD_OPTIONS,\n ...options,\n };\n\n const builder = new XMLBuilder(mergedOptions);\n return builder.build(obj) as string;\n}\n\n/**\n * Validate if a string is well-formed XML\n *\n * @param xml - String to validate\n * @returns True if valid XML, false otherwise\n *\n * @example\n * ```typescript\n * isValidXml('<root><item>Test</item></root>'); // true\n * isValidXml('<root><item>Test</root>'); // false (mismatched tags)\n * isValidXml('not xml'); // false\n * ```\n */\nexport function isValidXml(xml: string): boolean {\n if (!xml || xml.trim().length === 0) {\n return false;\n }\n\n const result = XMLValidator.validate(xml);\n return result === true;\n}\n\n/**\n * Parse XML with array handling for repeated elements\n * Forces specified tag names to always be arrays, even if only one element exists\n *\n * @param xml - XML string to parse\n * @param arrayTags - Array of tag names that should always be parsed as arrays\n * @returns Parsed JavaScript object with forced arrays\n *\n * @example\n * ```typescript\n * const xml = '<root><item>Test</item></root>';\n * const result = parseXmlWithArrays(xml, ['item']);\n * // Returns: { root: { item: ['Test'] } }\n * // Without arrayTags, would return: { root: { item: 'Test' } }\n * ```\n */\nexport function parseXmlWithArrays(\n xml: string,\n arrayTags: string[],\n options?: Partial<X2jOptions> & { maxSize?: number }\n): Record<string, unknown> {\n if (options?.maxSize !== undefined && xml.length > options.maxSize) {\n throw new Error(\n `Input exceeds maximum allowed size: ${xml.length} bytes exceeds limit of ${options.maxSize} bytes`\n );\n }\n\n // Validate XML first (consistency with parseXml)\n const validation = XMLValidator.validate(xml);\n if (validation !== true) {\n throw new Error(`Invalid XML: ${validation.err.msg}`);\n }\n\n const mergedOptions: Partial<X2jOptions> = {\n ...DEFAULT_PARSE_OPTIONS,\n ...options,\n isArray: (tagName: string) => arrayTags.includes(tagName),\n };\n\n const parser = new XMLParser(mergedOptions);\n return parser.parse(xml) as Record<string, unknown>;\n}\n\n/**\n * Extract text content from XML, stripping all tags\n *\n * @param xml - XML string\n * @returns Plain text content with tags removed\n *\n * @example\n * ```typescript\n * const xml = '<root><b>Bold</b> and <i>italic</i></root>';\n * const text = extractTextFromXml(xml);\n * // Returns: 'Bold and italic'\n * ```\n */\nexport function extractTextFromXml(xml: string): string {\n if (!isValidXml(xml)) {\n return '';\n }\n\n try {\n const parsed = parseXml(xml, { ignoreAttributes: true });\n return extractTextRecursive(parsed).trim();\n } catch {\n return '';\n }\n}\n\n/**\n * Recursively extract text from parsed XML object\n * @private\n */\nfunction extractTextRecursive(obj: unknown): string {\n if (typeof obj === 'string' || typeof obj === 'number') {\n return String(obj);\n }\n\n if (Array.isArray(obj)) {\n return obj.map(extractTextRecursive).join(' ');\n }\n\n if (typeof obj === 'object' && obj !== null) {\n const record = obj as Record<string, unknown>;\n\n // Recursively extract text from all properties\n return Object.values(record)\n .map(extractTextRecursive)\n .filter(text => text.length > 0)\n .join(' ');\n }\n\n return '';\n}\n"],"mappings":";;;;;;;;;;;;;;;AAkBA,MAAM,wBAA6C;CACjD,qBAAqB;CACrB,cAAc;CACd,kBAAkB;CAClB,mBAAmB;CACnB,qBAAqB;CACrB,eAAe;CACf,gBAAgB;CAChB,iBAAiB;CAClB;;;;AAKD,MAAM,wBAAoD;CACxD,qBAAqB;CACrB,cAAc;CACd,kBAAkB;CAClB,QAAQ;CACR,UAAU;CACV,mBAAmB;CACpB;;;;;;;;;;;;;;;AAgBD,SAAgB,SACd,KACA,SACyB;AACzB,KAAI,SAAS,YAAY,KAAA,KAAa,IAAI,SAAS,QAAQ,QACzD,OAAM,IAAI,MACR,uCAAuC,IAAI,OAAO,0BAA0B,QAAQ,QAAQ,QAC7F;CAIH,MAAM,aAAa,aAAa,SAAS,IAAI;AAC7C,KAAI,eAAe,KACjB,OAAM,IAAI,MAAM,gBAAgB,WAAW,IAAI,MAAM;AASvD,QADe,IAAI,UALG;EACpB,GAAG;EACH,GAAG;EACJ,CAE0C,CAC7B,MAAM,IAAI;;;;;;;;;;;;;;;;AAiB1B,SAAgB,SACd,KACA,SACQ;AAOR,QADgB,IAAI,WALE;EACpB,GAAG;EACH,GAAG;EACJ,CAE4C,CAC9B,MAAM,IAAI;;;;;;;;;;;;;;;AAgB3B,SAAgB,WAAW,KAAsB;AAC/C,KAAI,CAAC,OAAO,IAAI,MAAM,CAAC,WAAW,EAChC,QAAO;AAIT,QADe,aAAa,SAAS,IAAI,KACvB;;;;;;;;;;;;;;;;;;AAmBpB,SAAgB,mBACd,KACA,WACA,SACyB;AACzB,KAAI,SAAS,YAAY,KAAA,KAAa,IAAI,SAAS,QAAQ,QACzD,OAAM,IAAI,MACR,uCAAuC,IAAI,OAAO,0BAA0B,QAAQ,QAAQ,QAC7F;CAIH,MAAM,aAAa,aAAa,SAAS,IAAI;AAC7C,KAAI,eAAe,KACjB,OAAM,IAAI,MAAM,gBAAgB,WAAW,IAAI,MAAM;AAUvD,QADe,IAAI,UANwB;EACzC,GAAG;EACH,GAAG;EACH,UAAU,YAAoB,UAAU,SAAS,QAAQ;EAC1D,CAE0C,CAC7B,MAAM,IAAI;;;;;;;;;;;;;;;AAgB1B,SAAgB,mBAAmB,KAAqB;AACtD,KAAI,CAAC,WAAW,IAAI,CAClB,QAAO;AAGT,KAAI;AAEF,SAAO,qBADQ,SAAS,KAAK,EAAE,kBAAkB,MAAM,CAAC,CACrB,CAAC,MAAM;SACpC;AACN,SAAO;;;;;;;AAQX,SAAS,qBAAqB,KAAsB;AAClD,KAAI,OAAO,QAAQ,YAAY,OAAO,QAAQ,SAC5C,QAAO,OAAO,IAAI;AAGpB,KAAI,MAAM,QAAQ,IAAI,CACpB,QAAO,IAAI,IAAI,qBAAqB,CAAC,KAAK,IAAI;AAGhD,KAAI,OAAO,QAAQ,YAAY,QAAQ,KAIrC,QAAO,OAAO,OAHC,IAGa,CACzB,IAAI,qBAAqB,CACzB,QAAO,SAAQ,KAAK,SAAS,EAAE,CAC/B,KAAK,IAAI;AAGd,QAAO"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mitre/hdf-utilities",
3
- "version": "3.0.1",
3
+ "version": "3.1.0",
4
4
  "description": "Utility functions for HDF libraries (JSON parsing, validation helpers)",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -10,49 +10,37 @@
10
10
  "types": "./dist/index.d.ts",
11
11
  "exports": {
12
12
  ".": {
13
- "import": "./dist/index.js",
14
- "types": "./dist/index.d.ts"
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js"
15
15
  },
16
16
  "./json": {
17
- "import": "./dist/json/index.js",
18
- "types": "./dist/json/index.d.ts"
17
+ "types": "./dist/json/index.d.ts",
18
+ "import": "./dist/json/index.js"
19
19
  },
20
20
  "./hash": {
21
- "import": "./dist/hash/index.js",
22
- "types": "./dist/hash/index.d.ts"
21
+ "types": "./dist/hash/index.d.ts",
22
+ "import": "./dist/hash/index.js"
23
23
  },
24
24
  "./xml": {
25
- "import": "./dist/xml/index.js",
26
- "types": "./dist/xml/index.d.ts"
25
+ "types": "./dist/xml/index.d.ts",
26
+ "import": "./dist/xml/index.js"
27
27
  },
28
28
  "./csv": {
29
- "import": "./dist/csv/index.js",
30
- "types": "./dist/csv/index.d.ts"
29
+ "types": "./dist/csv/index.d.ts",
30
+ "import": "./dist/csv/index.js"
31
31
  },
32
32
  "./object": {
33
- "import": "./dist/object/index.js",
34
- "types": "./dist/object/index.d.ts"
33
+ "types": "./dist/object/index.d.ts",
34
+ "import": "./dist/object/index.js"
35
35
  },
36
36
  "./string": {
37
- "import": "./dist/string/index.js",
38
- "types": "./dist/string/index.d.ts"
37
+ "types": "./dist/string/index.d.ts",
38
+ "import": "./dist/string/index.js"
39
39
  }
40
40
  },
41
41
  "files": [
42
42
  "dist"
43
43
  ],
44
- "scripts": {
45
- "build": "pnpm clean && tsc",
46
- "clean": "rimraf dist",
47
- "test": "pnpm run test:ts",
48
- "test:ts": "vitest run",
49
- "test:go": "echo 'No Go tests in hdf-utilities'",
50
- "test:watch": "vitest",
51
- "test:coverage": "vitest run --coverage",
52
- "type-check": "tsc --noEmit",
53
- "lint": "eslint src test",
54
- "lint:fix": "eslint src test --fix"
55
- },
56
44
  "repository": {
57
45
  "type": "git",
58
46
  "url": "https://github.com/mitre/hdf-libs.git",
@@ -62,10 +50,10 @@
62
50
  "license": "Apache-2.0",
63
51
  "dependencies": {
64
52
  "d3-dsv": "^3.0.1",
65
- "fast-xml-parser": "5.5.7"
53
+ "fast-xml-parser": "5.7.1"
66
54
  },
67
55
  "engines": {
68
- "node": ">=20.0.0"
56
+ "node": ">=22.0.0"
69
57
  },
70
58
  "keywords": [
71
59
  "hdf",
@@ -78,5 +66,17 @@
78
66
  "@stryker-mutator/core": "^9.5.1",
79
67
  "@stryker-mutator/vitest-runner": "^9.5.1",
80
68
  "@types/d3-dsv": "^3.0.7"
69
+ },
70
+ "scripts": {
71
+ "build": "tsdown",
72
+ "clean": "rimraf dist",
73
+ "test": "pnpm run test:ts",
74
+ "test:ts": "vitest run",
75
+ "test:go": "echo 'No Go tests in hdf-utilities'",
76
+ "test:watch": "vitest",
77
+ "test:coverage": "vitest run --coverage",
78
+ "type-check": "tsc --noEmit",
79
+ "lint": "eslint src test",
80
+ "lint:fix": "eslint src test --fix"
81
81
  }
82
- }
82
+ }
@@ -1,29 +0,0 @@
1
- /**
2
- * Severity-to-impact mapping utilities.
3
- *
4
- * These define the canonical bidirectional mapping between severity labels
5
- * (critical, high, medium, low, informational) and HDF impact scores (0.0–1.0),
6
- * aligned with CVSS 3.x severity bands normalized to 0–1.
7
- *
8
- * Threshold ranges (used by impactToSeverity):
9
- * >=0.9 = critical (CVSS 9.0–10.0)
10
- * [0.7, 0.9) = high (CVSS 7.0–8.9)
11
- * [0.4, 0.7) = medium (CVSS 4.0–6.9)
12
- * (0.0, 0.4) = low (CVSS 0.1–3.9)
13
- * 0.0 = informational (CVSS 0.0)
14
- */
15
- /**
16
- * Map a severity string to an impact score.
17
- *
18
- * @param severity - Severity level (case-insensitive)
19
- * @returns Impact score between 0.0 and 1.0; defaults to 0.5 for unrecognized values
20
- */
21
- export declare function severityToImpact(severity: string): number;
22
- /**
23
- * Map an impact score to a severity string.
24
- *
25
- * @param impact - Impact score (0.0 to 1.0)
26
- * @returns Severity level string
27
- */
28
- export declare function impactToSeverity(impact: number): string;
29
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/severity/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAaH;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGzD;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAMvD"}
@@ -1,52 +0,0 @@
1
- /**
2
- * Severity-to-impact mapping utilities.
3
- *
4
- * These define the canonical bidirectional mapping between severity labels
5
- * (critical, high, medium, low, informational) and HDF impact scores (0.0–1.0),
6
- * aligned with CVSS 3.x severity bands normalized to 0–1.
7
- *
8
- * Threshold ranges (used by impactToSeverity):
9
- * >=0.9 = critical (CVSS 9.0–10.0)
10
- * [0.7, 0.9) = high (CVSS 7.0–8.9)
11
- * [0.4, 0.7) = medium (CVSS 4.0–6.9)
12
- * (0.0, 0.4) = low (CVSS 0.1–3.9)
13
- * 0.0 = informational (CVSS 0.0)
14
- */
15
- const severityMap = {
16
- critical: 0.9,
17
- high: 0.7,
18
- medium: 0.5,
19
- low: 0.3,
20
- info: 0.0,
21
- none: 0.0,
22
- informational: 0.0,
23
- information: 0.0,
24
- };
25
- /**
26
- * Map a severity string to an impact score.
27
- *
28
- * @param severity - Severity level (case-insensitive)
29
- * @returns Impact score between 0.0 and 1.0; defaults to 0.5 for unrecognized values
30
- */
31
- export function severityToImpact(severity) {
32
- const normalized = severity.toLowerCase();
33
- return severityMap[normalized] ?? 0.5;
34
- }
35
- /**
36
- * Map an impact score to a severity string.
37
- *
38
- * @param impact - Impact score (0.0 to 1.0)
39
- * @returns Severity level string
40
- */
41
- export function impactToSeverity(impact) {
42
- if (impact >= 0.9)
43
- return 'critical';
44
- if (impact >= 0.7)
45
- return 'high';
46
- if (impact >= 0.4)
47
- return 'medium';
48
- if (impact > 0.0)
49
- return 'low';
50
- return 'informational';
51
- }
52
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/severity/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,GAA2B;IAC1C,QAAQ,EAAE,GAAG;IACb,IAAI,EAAE,GAAG;IACT,MAAM,EAAE,GAAG;IACX,GAAG,EAAE,GAAG;IACR,IAAI,EAAE,GAAG;IACT,IAAI,EAAE,GAAG;IACT,aAAa,EAAE,GAAG;IAClB,WAAW,EAAE,GAAG;CACjB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,MAAM,UAAU,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;IAC1C,OAAO,WAAW,CAAC,UAAU,CAAC,IAAI,GAAG,CAAC;AACxC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAc;IAC7C,IAAI,MAAM,IAAI,GAAG;QAAE,OAAO,UAAU,CAAC;IACrC,IAAI,MAAM,IAAI,GAAG;QAAE,OAAO,MAAM,CAAC;IACjC,IAAI,MAAM,IAAI,GAAG;QAAE,OAAO,QAAQ,CAAC;IACnC,IAAI,MAAM,GAAG,GAAG;QAAE,OAAO,KAAK,CAAC;IAC/B,OAAO,eAAe,CAAC;AACzB,CAAC"}