@mitre/hdf-generators 2.0.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/LICENSE.md ADDED
@@ -0,0 +1,55 @@
1
+ # License
2
+
3
+ Copyright © 2025 The MITRE Corporation.
4
+
5
+ Approved for Public Release; Distribution Unlimited. Case Number 18-3678.
6
+
7
+ Licensed under the Apache License, Version 2.0 (the "License"); you may
8
+ not use this file except in compliance with the License. You may obtain a
9
+ copy of the License at
10
+
11
+ http://www.apache.org/licenses/LICENSE-2.0
12
+
13
+ Unless required by applicable law or agreed to in writing, software
14
+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15
+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16
+ License for the specific language governing permissions and limitations
17
+ under the License.
18
+
19
+ ## Redistribution Terms
20
+
21
+ Redistribution and use in source and binary forms, with or without
22
+ modification, are permitted provided that the following conditions are
23
+ met:
24
+
25
+ - Redistributions of source code must retain the above copyright/digital
26
+ rights legend, this list of conditions and the following Notice.
27
+ - Redistributions in binary form must reproduce the above
28
+ copyright/digital rights legend, this list of conditions and the
29
+ following Notice in the documentation and/or other materials provided
30
+ with the distribution.
31
+ - Neither the name of The MITRE Corporation nor the names of its contributors
32
+ may be used to endorse or promote products derived from this software
33
+ without specific prior written permission.
34
+
35
+ ## Notice
36
+
37
+ The MITRE Corporation grants permission to reproduce, distribute, modify, and
38
+ otherwise use this software to the extent permitted by the licensed terms
39
+ provided in the LICENSE file included with this project.
40
+
41
+ This software was produced by The MITRE Corporation for the U.S. Government
42
+ under contract. As such the U.S. Government has certain use and data
43
+ rights in this software. No use other than those granted to the U.S.
44
+ Government, or to those acting on behalf of the U.S. Government, under
45
+ these contract arrangements is authorized without the express written
46
+ permission of The MITRE Corporation.
47
+
48
+ Some files in this codebase were generated by generative AI, under the
49
+ direction and review of The MITRE Corporation employees, for the purpose of
50
+ development efficiency. All AI-generated code functionality was validated
51
+ by standard quality and assurance testing.
52
+
53
+ For further information, please contact The MITRE Corporation,
54
+ Contracts Management Office, 7515 Colshire Drive, McLean, VA 22102-7539,
55
+ (703) 983-6000.
package/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # @mitre/hdf-generators
2
+
3
+ Generate InSpec profile stubs from HDF Baseline definitions.
4
+
5
+ ## What it does
6
+
7
+ Takes an HDF Baseline JSON document (requirement definitions with metadata, descriptions, and tags) and generates a complete InSpec profile directory structure:
8
+
9
+ - `inspec.yml` — profile metadata (name, maintainer, license, version, platform supports)
10
+ - `controls/*.rb` — one control file per requirement with `describe` blocks, tags, and impact
11
+
12
+ This bridges from HDF's tool-agnostic baseline format to InSpec's executable compliance-as-code format.
13
+
14
+ ## Relationship to other packages
15
+
16
+ | Package | Relationship |
17
+ |---------|-------------|
18
+ | **hdf-schema** | Provides the `HDFBaseline` type that generators consume |
19
+ | **hdf-converters** | Converters produce baselines (e.g., XCCDF benchmark → HDF baseline) that generators can then turn into InSpec profiles |
20
+ | **hdf-cli** | `hdf generate inspec-profile` command wraps this library |
21
+ | **hdf-mappings** | Baselines may contain NIST/CCI tags from hdf-mappings |
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ npm install @mitre/hdf-generators
27
+ ```
28
+
29
+ ## Usage (TypeScript)
30
+
31
+ ```typescript
32
+ import { generateInSpecProfile } from '@mitre/hdf-generators';
33
+ import type { HDFBaseline } from '@mitre/hdf-schema';
34
+
35
+ const baseline: HDFBaseline = JSON.parse(fs.readFileSync('baseline.json', 'utf8'));
36
+
37
+ const profile = generateInSpecProfile(baseline, {
38
+ maintainer: 'MITRE SAF',
39
+ copyright: 'MITRE Corporation',
40
+ license: 'Apache-2.0',
41
+ });
42
+
43
+ // profile.inspecYml — string content for inspec.yml
44
+ // profile.controls — Map<string, string> of filename → Ruby control code
45
+ ```
46
+
47
+ ### Individual stubs
48
+
49
+ ```typescript
50
+ import { generateControlStub, generateInSpecYml, escapeQuotes } from '@mitre/hdf-generators';
51
+
52
+ // Generate a single control file
53
+ const ruby = generateControlStub(requirement);
54
+
55
+ // Generate inspec.yml
56
+ const yml = generateInSpecYml(baseline, { maintainer: 'Team', license: 'Apache-2.0' });
57
+ ```
58
+
59
+ ## Usage (Go)
60
+
61
+ ```go
62
+ import generators "github.com/mitre/hdf-generators"
63
+
64
+ profile := generators.GenerateInSpecProfile(baseline, generators.ProfileMetadata{
65
+ Maintainer: "MITRE SAF",
66
+ License: "Apache-2.0",
67
+ })
68
+ // profile.InspecYml — string
69
+ // profile.Controls — map[string]string
70
+ ```
71
+
72
+ ## CLI usage
73
+
74
+ ```bash
75
+ hdf generate inspec-profile baseline.json output-dir/
76
+ hdf generate inspec-profile baseline.json output-dir/ --maintainer "MITRE SAF"
77
+ hdf generate inspec-profile baseline.json output-dir/ --single-file
78
+ ```
79
+
80
+ ## License
81
+
82
+ Apache-2.0 © MITRE Corporation
@@ -0,0 +1,16 @@
1
+ import type { BaselineRequirement } from '@mitre/hdf-schema';
2
+ /**
3
+ * Generate a Ruby InSpec control stub from an HDF BaselineRequirement.
4
+ *
5
+ * Output follows the InSpec DSL ordering convention:
6
+ * control 'ID' do
7
+ * title ...
8
+ * desc ...
9
+ * desc 'check', ...
10
+ * impact ...
11
+ * tag key: value
12
+ * <code or stub comment>
13
+ * end
14
+ */
15
+ export declare function generateControlStub(req: BaselineRequirement): string;
16
+ //# sourceMappingURL=control-stub.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"control-stub.d.ts","sourceRoot":"","sources":["../src/control-stub.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAG7D;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,mBAAmB,GAAG,MAAM,CAsDpE"}
@@ -0,0 +1,81 @@
1
+ import { escapeQuotes } from './ruby-escape.js';
2
+ /**
3
+ * Generate a Ruby InSpec control stub from an HDF BaselineRequirement.
4
+ *
5
+ * Output follows the InSpec DSL ordering convention:
6
+ * control 'ID' do
7
+ * title ...
8
+ * desc ...
9
+ * desc 'check', ...
10
+ * impact ...
11
+ * tag key: value
12
+ * <code or stub comment>
13
+ * end
14
+ */
15
+ export function generateControlStub(req) {
16
+ const lines = [];
17
+ lines.push(`control '${req.id}' do`);
18
+ // Title
19
+ if (req.title) {
20
+ lines.push(` title ${escapeQuotes(req.title)}`);
21
+ }
22
+ // Descriptions: default first (as bare `desc`), then labeled
23
+ const defaultDesc = req.descriptions.find((d) => d.label === 'default');
24
+ if (defaultDesc) {
25
+ lines.push(` desc ${escapeQuotes(defaultDesc.data)}`);
26
+ }
27
+ const seenDefault = new Set();
28
+ for (const d of req.descriptions) {
29
+ if (d.label === 'default') {
30
+ if (seenDefault.has('default')) {
31
+ continue; // skip duplicate default
32
+ }
33
+ seenDefault.add('default');
34
+ continue; // already emitted above as bare desc
35
+ }
36
+ lines.push(` desc '${d.label}', ${escapeQuotes(d.data)}`);
37
+ }
38
+ // Impact — always render with at least one decimal place for 0 and whole numbers
39
+ if (req.impact !== undefined) {
40
+ const impactStr = Number.isInteger(req.impact)
41
+ ? req.impact.toFixed(1)
42
+ : String(req.impact);
43
+ lines.push(` impact ${impactStr}`);
44
+ }
45
+ // Tags
46
+ for (const [key, value] of Object.entries(req.tags)) {
47
+ lines.push(` ${formatTag(key, value)}`);
48
+ }
49
+ // Code body or stub placeholder
50
+ if (req.code) {
51
+ lines.push('');
52
+ lines.push(req.code);
53
+ }
54
+ else {
55
+ lines.push('');
56
+ lines.push(' # TODO: Add InSpec test code here');
57
+ }
58
+ lines.push('end');
59
+ lines.push(''); // trailing newline
60
+ return lines.join('\n');
61
+ }
62
+ /** Format a single tag key-value pair as Ruby DSL. */
63
+ function formatTag(key, value) {
64
+ if (value === null || value === undefined) {
65
+ return `tag ${key}: nil`;
66
+ }
67
+ if (Array.isArray(value)) {
68
+ // Ruby array syntax: tag key: ['val1', 'val2']
69
+ const items = value.map((v) => `'${String(v)}'`).join(', ');
70
+ return `tag ${key}: [${items}]`;
71
+ }
72
+ if (typeof value === 'boolean') {
73
+ return `tag ${key}: ${value}`;
74
+ }
75
+ if (typeof value === 'string') {
76
+ return `tag ${key}: ${escapeQuotes(value)}`;
77
+ }
78
+ // Fallback for other types (numbers, objects)
79
+ return `tag ${key}: ${JSON.stringify(value)}`;
80
+ }
81
+ //# sourceMappingURL=control-stub.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"control-stub.js","sourceRoot":"","sources":["../src/control-stub.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export type { GeneratorOptions, ProfileMetadata, InSpecProfile } from './types.js';
2
+ export { escapeQuotes } from './ruby-escape.js';
3
+ export { generateControlStub } from './control-stub.js';
4
+ export { generateInSpecYml } from './inspec-yml.js';
5
+ export { generateInSpecProfile } from './profile-generator.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACnF,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { escapeQuotes } from './ruby-escape.js';
2
+ export { generateControlStub } from './control-stub.js';
3
+ export { generateInSpecYml } from './inspec-yml.js';
4
+ export { generateInSpecProfile } from './profile-generator.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { HdfBaseline } from '@mitre/hdf-schema';
2
+ import type { GeneratorOptions } from './types.js';
3
+ /**
4
+ * Generate an inspec.yml YAML string from an HDF Baseline and options.
5
+ *
6
+ * Uses string interpolation — no YAML library needed since the structure
7
+ * is fixed and shallow.
8
+ */
9
+ export declare function generateInSpecYml(baseline: HdfBaseline, options?: GeneratorOptions): string;
10
+ //# sourceMappingURL=inspec-yml.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspec-yml.d.ts","sourceRoot":"","sources":["../src/inspec-yml.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,WAAW,EACrB,OAAO,CAAC,EAAE,gBAAgB,GACzB,MAAM,CAoGR"}
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Generate an inspec.yml YAML string from an HDF Baseline and options.
3
+ *
4
+ * Uses string interpolation — no YAML library needed since the structure
5
+ * is fixed and shallow.
6
+ */
7
+ export function generateInSpecYml(baseline, options) {
8
+ const lines = [];
9
+ const meta = options?.metadata;
10
+ // Required: name
11
+ lines.push(`name: ${baseline.name}`);
12
+ // Optional metadata fields
13
+ if (baseline.title) {
14
+ lines.push(`title: ${baseline.title}`);
15
+ }
16
+ const maintainer = meta?.maintainer ?? baseline.maintainer;
17
+ if (maintainer) {
18
+ lines.push(`maintainer: ${maintainer}`);
19
+ }
20
+ const copyright = meta?.copyright ?? baseline.copyright;
21
+ if (copyright) {
22
+ lines.push(`copyright: ${copyright}`);
23
+ }
24
+ const license = meta?.license ?? baseline.license;
25
+ if (license) {
26
+ lines.push(`license: ${license}`);
27
+ }
28
+ if (baseline.summary) {
29
+ lines.push(`summary: ${baseline.summary}`);
30
+ }
31
+ // Version: metadata override takes priority
32
+ const version = meta?.version ?? baseline.version;
33
+ if (version) {
34
+ lines.push(`version: '${version}'`);
35
+ }
36
+ // InSpec version constraint
37
+ const inspecVersion = options?.inspecVersion ?? '~>6.0';
38
+ lines.push(`inspec_version: '${inspecVersion}'`);
39
+ // Supports array
40
+ if (baseline.supports && baseline.supports.length > 0) {
41
+ lines.push('supports:');
42
+ for (const support of baseline.supports) {
43
+ const entries = [];
44
+ if (support.platformName) {
45
+ entries.push(` platform-name: ${support.platformName}`);
46
+ }
47
+ if (support.platformFamily) {
48
+ entries.push(` platform-family: ${support.platformFamily}`);
49
+ }
50
+ if (support.platform) {
51
+ entries.push(` platform: ${support.platform}`);
52
+ }
53
+ if (support.release) {
54
+ entries.push(` release: ${support.release}`);
55
+ }
56
+ if (entries.length > 0) {
57
+ lines.push(`- ${entries[0].trimStart()}`);
58
+ for (let i = 1; i < entries.length; i++) {
59
+ lines.push(entries[i]);
60
+ }
61
+ }
62
+ }
63
+ }
64
+ // Depends array
65
+ if (baseline.depends && baseline.depends.length > 0) {
66
+ lines.push('depends:');
67
+ for (const dep of baseline.depends) {
68
+ const entries = [];
69
+ if (dep.name)
70
+ entries.push(`name: ${dep.name}`);
71
+ if (dep.git)
72
+ entries.push(`git: ${dep.git}`);
73
+ if (dep.url)
74
+ entries.push(`url: ${dep.url}`);
75
+ if (dep.path)
76
+ entries.push(`path: ${dep.path}`);
77
+ if (dep.branch)
78
+ entries.push(`branch: ${dep.branch}`);
79
+ if (dep.compliance)
80
+ entries.push(`compliance: ${dep.compliance}`);
81
+ if (dep.supermarket)
82
+ entries.push(`supermarket: ${dep.supermarket}`);
83
+ if (entries.length > 0) {
84
+ lines.push(`- ${entries[0]}`);
85
+ for (let i = 1; i < entries.length; i++) {
86
+ lines.push(` ${entries[i]}`);
87
+ }
88
+ }
89
+ }
90
+ }
91
+ // Inputs array
92
+ if (baseline.inputs && baseline.inputs.length > 0) {
93
+ lines.push('inputs:');
94
+ for (const input of baseline.inputs) {
95
+ for (const [key, value] of Object.entries(input)) {
96
+ lines.push(`- ${key}: ${formatYamlValue(value)}`);
97
+ }
98
+ }
99
+ }
100
+ lines.push(''); // trailing newline
101
+ return lines.join('\n');
102
+ }
103
+ /** Format a value for inline YAML output. */
104
+ function formatYamlValue(value) {
105
+ if (typeof value === 'boolean')
106
+ return String(value);
107
+ if (typeof value === 'number')
108
+ return String(value);
109
+ if (typeof value === 'string')
110
+ return value;
111
+ return JSON.stringify(value);
112
+ }
113
+ //# sourceMappingURL=inspec-yml.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspec-yml.js","sourceRoot":"","sources":["../src/inspec-yml.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { HdfBaseline } from '@mitre/hdf-schema';
2
+ import type { GeneratorOptions, InSpecProfile } from './types.js';
3
+ /**
4
+ * Generate an in-memory InSpec profile from an HDF Baseline.
5
+ *
6
+ * Returns an InSpecProfile with inspec.yml content and a Map of
7
+ * control filenames to Ruby source code. No file I/O — the CLI
8
+ * is responsible for writing files to disk.
9
+ */
10
+ export declare function generateInSpecProfile(baseline: HdfBaseline, options?: GeneratorOptions): InSpecProfile;
11
+ //# sourceMappingURL=profile-generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile-generator.d.ts","sourceRoot":"","sources":["../src/profile-generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAIlE;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,WAAW,EACrB,OAAO,CAAC,EAAE,gBAAgB,GACzB,aAAa,CAsBf"}
@@ -0,0 +1,31 @@
1
+ import { generateControlStub } from './control-stub.js';
2
+ import { generateInSpecYml } from './inspec-yml.js';
3
+ /**
4
+ * Generate an in-memory InSpec profile from an HDF Baseline.
5
+ *
6
+ * Returns an InSpecProfile with inspec.yml content and a Map of
7
+ * control filenames to Ruby source code. No file I/O — the CLI
8
+ * is responsible for writing files to disk.
9
+ */
10
+ export function generateInSpecProfile(baseline, options) {
11
+ const inspecYml = generateInSpecYml(baseline, options);
12
+ const controls = new Map();
13
+ if (baseline.requirements.length === 0) {
14
+ return { inspecYml, controls };
15
+ }
16
+ if (options?.singleFile) {
17
+ // All controls in a single file
18
+ const stubs = baseline.requirements.map((req) => generateControlStub(req));
19
+ controls.set('controls/controls.rb', stubs.join('\n'));
20
+ }
21
+ else {
22
+ // One file per control — sanitize ID for safe filenames
23
+ for (const req of baseline.requirements) {
24
+ const safeId = req.id.replace(/\.\./g, '').replace(/[/\\]/g, '') || 'unknown';
25
+ const filename = `controls/${safeId}.rb`;
26
+ controls.set(filename, generateControlStub(req));
27
+ }
28
+ }
29
+ return { inspecYml, controls };
30
+ }
31
+ //# sourceMappingURL=profile-generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile-generator.js","sourceRoot":"","sources":["../src/profile-generator.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEpD;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CACnC,QAAqB,EACrB,OAA0B;IAE1B,MAAM,SAAS,GAAG,iBAAiB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACvD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;IAE3C,IAAI,QAAQ,CAAC,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvC,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;IACjC,CAAC;IAED,IAAI,OAAO,EAAE,UAAU,EAAE,CAAC;QACxB,gCAAgC;QAChC,MAAM,KAAK,GAAG,QAAQ,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC,CAAC;QAC3E,QAAQ,CAAC,GAAG,CAAC,sBAAsB,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACzD,CAAC;SAAM,CAAC;QACN,wDAAwD;QACxD,KAAK,MAAM,GAAG,IAAI,QAAQ,CAAC,YAAY,EAAE,CAAC;YACxC,MAAM,MAAM,GAAG,GAAG,CAAC,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,SAAS,CAAC;YAC9E,MAAM,QAAQ,GAAG,YAAY,MAAM,KAAK,CAAC;YACzC,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,mBAAmB,CAAC,GAAG,CAAC,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;IAED,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;AACjC,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Escape a string for use as a Ruby string literal.
3
+ *
4
+ * Strategy:
5
+ * - If the string contains both single and double quotes → %q() wrapper
6
+ * - If the string contains only single quotes → double-quoted with escaping
7
+ * - Otherwise → single-quoted with escaping
8
+ *
9
+ * Ported from ts-inspec-objects escapeQuotes(), cross-referenced with
10
+ * inspec-parser's RubyRebuilder.
11
+ */
12
+ export declare function escapeQuotes(s: string): string;
13
+ //# sourceMappingURL=ruby-escape.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ruby-escape.d.ts","sourceRoot":"","sources":["../src/ruby-escape.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAgB9C"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Escape a string for use as a Ruby string literal.
3
+ *
4
+ * Strategy:
5
+ * - If the string contains both single and double quotes → %q() wrapper
6
+ * - If the string contains only single quotes → double-quoted with escaping
7
+ * - Otherwise → single-quoted with escaping
8
+ *
9
+ * Ported from ts-inspec-objects escapeQuotes(), cross-referenced with
10
+ * inspec-parser's RubyRebuilder.
11
+ */
12
+ export function escapeQuotes(s) {
13
+ const hasSingle = s.includes("'");
14
+ const hasDouble = s.includes('"');
15
+ if (hasSingle && hasDouble) {
16
+ // %q() — escape backslashes before ) so Ruby doesn't treat \) as escaped delimiter
17
+ return `%q(${s.replace(/\\\)/g, '\\\\)')})`;
18
+ }
19
+ if (hasSingle) {
20
+ // Double-quoted: escape backslashes, then double quotes
21
+ return `"${s.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
22
+ }
23
+ // Single-quoted: escape backslashes, then single quotes
24
+ return `'${s.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
25
+ }
26
+ //# sourceMappingURL=ruby-escape.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ruby-escape.js","sourceRoot":"","sources":["../src/ruby-escape.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY,CAAC,CAAS;IACpC,MAAM,SAAS,GAAG,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,SAAS,GAAG,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IAElC,IAAI,SAAS,IAAI,SAAS,EAAE,CAAC;QAC3B,mFAAmF;QACnF,OAAO,MAAM,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC;IAC9C,CAAC;IAED,IAAI,SAAS,EAAE,CAAC;QACd,wDAAwD;QACxD,OAAO,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC;IAC9D,CAAC;IAED,wDAAwD;IACxD,OAAO,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC;AAC9D,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Options for controlling InSpec profile generation.
3
+ */
4
+ export interface GeneratorOptions {
5
+ /** Put all controls in a single controls.rb file instead of one file per control. */
6
+ singleFile?: boolean;
7
+ /** Override baseline metadata in the generated inspec.yml. */
8
+ metadata?: ProfileMetadata;
9
+ /** InSpec version constraint for inspec.yml. Default: '~>6.0'. */
10
+ inspecVersion?: string;
11
+ }
12
+ /**
13
+ * Metadata overrides for the generated inspec.yml.
14
+ */
15
+ export interface ProfileMetadata {
16
+ maintainer?: string;
17
+ copyright?: string;
18
+ license?: string;
19
+ version?: string;
20
+ }
21
+ /**
22
+ * An in-memory InSpec profile. No file I/O — the CLI is responsible for writing.
23
+ */
24
+ export interface InSpecProfile {
25
+ /** The inspec.yml content as a YAML string. */
26
+ inspecYml: string;
27
+ /** Map of filename (e.g. 'controls/SV-238196.rb') to Ruby source code. */
28
+ controls: Map<string, string>;
29
+ }
30
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,qFAAqF;IACrF,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,kEAAkE;IAClE,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/B"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@mitre/hdf-generators",
3
+ "version": "2.0.0",
4
+ "description": "Generate InSpec profile stubs from HDF Baseline definitions",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "type": "module",
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/index.js",
14
+ "types": "./dist/index.d.ts"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/mitre/hdf-libs.git",
23
+ "directory": "hdf-generators"
24
+ },
25
+ "author": "MITRE Corporation",
26
+ "license": "Apache-2.0",
27
+ "dependencies": {
28
+ "@mitre/hdf-schema": "2.0.0"
29
+ },
30
+ "engines": {
31
+ "node": ">=20.0.0"
32
+ },
33
+ "keywords": [
34
+ "hdf",
35
+ "heimdall",
36
+ "inspec",
37
+ "generator",
38
+ "profile",
39
+ "stig"
40
+ ],
41
+ "scripts": {
42
+ "build": "pnpm clean && tsc",
43
+ "clean": "rimraf dist",
44
+ "test": "pnpm run test:ts",
45
+ "test:ts": "vitest run",
46
+ "test:watch": "vitest",
47
+ "test:coverage": "vitest run --coverage",
48
+ "type-check": "tsc --noEmit",
49
+ "lint": "eslint src test",
50
+ "lint:fix": "eslint src test --fix"
51
+ }
52
+ }