From 8af350c03f9294bb1e211d897ad609bdfc1302f9 Mon Sep 17 00:00:00 2001 From: Tim Haselaars Date: Wed, 17 Jun 2026 23:20:20 +0200 Subject: [PATCH 1/5] feat: add `yamlCompat` option for YAML 1.1 --- bin/__snapshots__/cli.test.js.snap | 1 + bin/cli.js | 1 + bin/cli.test.js | 28 +++++++++++ readme.md | 2 + schemas/openapi-format.schema.json | 5 ++ test/util-file.test.js | 80 ++++++++++++++++++++++++++++++ types/openapi-format.d.ts | 1 + utils/file.js | 10 +++- 8 files changed, 126 insertions(+), 2 deletions(-) diff --git a/bin/__snapshots__/cli.test.js.snap b/bin/__snapshots__/cli.test.js.snap index 0d3a635..604435f 100644 --- a/bin/__snapshots__/cli.test.js.snap +++ b/bin/__snapshots__/cli.test.js.snap @@ -117,6 +117,7 @@ Options: --no-sort don't sort the OpenAPI file --keepComments don't remove the comments from the OpenAPI YAML file (default: false) --yamlQuoteStyle YAML quote style: single, double, or detect + --yamlCompat YAML compatibility schema for stringifying (yaml-1.1) --sortComponentsFile the file with components to sort alphabetically --sortComponentsProps sort properties within schema components alphabetically (default: false) --lineWidth max line width of YAML output (default: -1) diff --git a/bin/cli.js b/bin/cli.js index 73e8652..67266ca 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -27,6 +27,7 @@ program .option('--no-sort', `don't sort the OpenAPI file`) .option('--keepComments', `don't remove the comments from the OpenAPI YAML file`, false) .option('--yamlQuoteStyle ', 'YAML quote style: single, double, or detect') + .option('--yamlCompat ', 'YAML compatibility schema for stringifying (yaml-1.1)') .option('--sortComponentsFile ', 'the file with components to sort alphabetically') .option('--sortComponentsProps', 'sort properties within schema components alphabetically', false) .option('--lineWidth ', 'max line width of YAML output', -1) diff --git a/bin/cli.test.js b/bin/cli.test.js index 0e31443..7d7e556 100644 --- a/bin/cli.test.js +++ b/bin/cli.test.js @@ -246,6 +246,34 @@ describe('openapi-format CLI command', () => { expect(sanitize(result.stderr)).toStrictEqual(sanitize(output)); }); + it('should apply yamlCompat from config files', async () => { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'openapi-format-yaml-compat-')); + const inputFile = path.join(tempDir, 'input.yaml'); + const configFile = path.join(tempDir, 'options.json'); + const outputFile = path.join(tempDir, 'output.yaml'); + + fs.writeFileSync( + inputFile, + `openapi: 3.0.3\ninfo:\n title: t\n version: 1.0.0\npaths: {}\ncomponents:\n schemas:\n Feature:\n type: object\n properties:\n status:\n type: string\n enum:\n - "yes"\n - "no"\n - "on"\n - "off"\n example:\n status: "on"\n` + ); + fs.writeFileSync(configFile, JSON.stringify({yamlCompat: 'yaml-1.1', sort: false}, null, 2)); + + const result = await testUtils.cli(['input.yaml', '--configFile options.json', `--output ${outputFile}`], tempDir); + + expect(result.code).toBe(0); + expect(result.stdout).toContain('formatted successfully'); + const output = fs.readFileSync(outputFile, 'utf8'); + expect(output).toMatch(/-\s+["']yes["']/); + expect(output).toMatch(/-\s+["']no["']/); + expect(output).toMatch(/-\s+["']on["']/); + expect(output).toMatch(/-\s+["']off["']/); + expect(output).toMatch(/status:\s+["']on["']/); + expect(output).not.toMatch(/-\s+yes\b/); + expect(output).not.toMatch(/-\s+no\b/); + expect(output).not.toMatch(/-\s+on\b/); + expect(output).not.toMatch(/-\s+off\b/); + }); + it('should load the default .openapiformatrc if configFile is not provided', async () => { // Mock the existence of the .openapiformatrc file const defaultConfigPath = '.openapiformatrc'; diff --git a/readme.md b/readme.md index 867ae61..f2f98b5 100644 --- a/readme.md +++ b/readme.md @@ -169,6 +169,7 @@ Options: --no-sort Don't sort the OpenAPI file [boolean] --keepComments Don't remove the comments from the OpenAPI YAML file [boolean] --yamlQuoteStyle Preferred YAML quote style: single, double, detect [string] + --yamlCompat YAML compatibility schema for stringifying [string] --sortComponentsFile The file with components to sort alphabetically [path] --sortComponentsProps Sort properties within schema components alphabetically [boolean] @@ -206,6 +207,7 @@ Options: | --no-sort | | don't sort the OpenAPI file | boolean | FALSE | optional | | --keepComments | | don't remove the comments from the OpenAPI YAML file | boolean | FALSE | optional | | --yamlQuoteStyle | | preferred YAML quote style for YAML output (`single`, `double`, `detect`) | string | detect | optional | +| --yamlCompat | | compatibility schema for YAML stringification (`yaml-1.1`) | string | | optional | | --sortComponentsFile | | sort the items of the components (schemas, parameters, ...) by alphabet | path to file | defaultSortComponents.json | optional | | --sortComponentsProps | | sort properties within schema components alphabetically | boolean | FALSE | optional | | --no-bundle | | don't bundle the local and remote $ref in the OpenAPI document | boolean | FALSE | optional | diff --git a/schemas/openapi-format.schema.json b/schemas/openapi-format.schema.json index bd9ea63..9c6a962 100644 --- a/schemas/openapi-format.schema.json +++ b/schemas/openapi-format.schema.json @@ -418,6 +418,11 @@ "enum": ["single", "double", "detect"], "default": "detect" }, + "yamlCompat": { + "type": "string", + "description": "Compatibility schema to use when stringifying YAML. Enables YAML 1.1-safe quoting for compatibility with older parsers.", + "enum": ["yaml-1.1"] + }, "lineWidth": { "type": "integer", "description": "Maximum line width of YAML output. Use -1 for unlimited (default).", diff --git a/test/util-file.test.js b/test/util-file.test.js index 85a0070..e45ec22 100644 --- a/test/util-file.test.js +++ b/test/util-file.test.js @@ -248,6 +248,78 @@ describe('openapi-format CLI file tests', () => { expect(result).not.toContain('name: "John"'); }); + test('should leave YAML 1.2 boolean-like strings unquoted by default', async () => { + const obj = { + components: { + schemas: { + Feature: { + type: 'object', + properties: { + status: { + type: 'string', + enum: ['yes', 'no', 'on', 'off'] + } + }, + example: { + status: 'on' + } + } + } + } + }; + + const result = await stringify(obj, { + format: 'yaml' + }); + + expect(result).toContain('enum:'); + expect(result).toContain('- yes'); + expect(result).toContain('- no'); + expect(result).toContain('- on'); + expect(result).toContain('- off'); + expect(result).toContain('status: on'); + expect(result).not.toContain("'yes'"); + expect(result).not.toContain("'no'"); + expect(result).not.toContain("'on'"); + expect(result).not.toContain("'off'"); + }); + + test('should quote YAML 1.1 boolean-like strings when yamlCompat is yaml-1.1', async () => { + const obj = { + components: { + schemas: { + Feature: { + type: 'object', + properties: { + status: { + type: 'string', + enum: ['yes', 'no', 'on', 'off'] + } + }, + example: { + status: 'on' + } + } + } + } + }; + + const result = await stringify(obj, { + format: 'yaml', + yamlCompat: 'yaml-1.1' + }); + + expect(result).toContain(" - 'yes'"); + expect(result).toContain(" - 'no'"); + expect(result).toContain(" - 'on'"); + expect(result).toContain(" - 'off'"); + expect(result).toContain("status: 'on'"); + expect(result).not.toContain('- yes'); + expect(result).not.toContain('- no'); + expect(result).not.toContain('- on'); + expect(result).not.toContain('- off'); + }); + test('should preserve comments while honoring the resolved quote style', async () => { const parsed = yaml.parseDocument('name: "Hello: world" # person\ncity: London\n'); const options = { @@ -289,6 +361,14 @@ describe('openapi-format CLI file tests', () => { expect(result).toEqual(JSON.stringify(obj, null, 2)); }); + + test('should leave JSON output unchanged when yamlCompat is set', async () => { + const obj = {name: 'John'}; + + const result = await stringify(obj, {format: 'json', yamlCompat: 'yaml-1.1'}); + + expect(result).toEqual(JSON.stringify(obj, null, 2)); + }); }); describe('parseString', () => { diff --git a/types/openapi-format.d.ts b/types/openapi-format.d.ts index 1fa8d00..4780483 100644 --- a/types/openapi-format.d.ts +++ b/types/openapi-format.d.ts @@ -184,6 +184,7 @@ declare module 'openapi-format' { json?: boolean; keepComments?: boolean; yamlQuoteStyle?: 'single' | 'double' | 'detect'; + yamlCompat?: 'yaml-1.1'; yamlComments?: Record; lineWidth?: string | number; mode?: string; diff --git a/utils/file.js b/utils/file.js index 2433fc7..6fb5f27 100644 --- a/utils/file.js +++ b/utils/file.js @@ -169,14 +169,20 @@ function resolveYamlQuoteStyle(options = {}) { * Build YAML stringify/toString options, including quote-style control. * @param {number} lineWidth * @param {object} [options] - * @returns {{lineWidth: number, singleQuote: boolean}} + * @returns {{lineWidth: number, singleQuote: boolean, compat?: 'yaml-1.1'}} */ function buildYamlStringifyOptions(lineWidth, options = {}) { const style = resolveYamlQuoteStyle(options); - return { + const yamlStringifyOptions = { lineWidth, singleQuote: style !== YAML_QUOTE_STYLE.DOUBLE }; + + if (options.yamlCompat === 'yaml-1.1') { + yamlStringifyOptions.compat = 'yaml-1.1'; + } + + return yamlStringifyOptions; } /** From 391ca6bd9ca3524c8ff2b13031dc8b54e597c7de Mon Sep 17 00:00:00 2001 From: Tim Haselaars Date: Wed, 17 Jun 2026 23:33:29 +0200 Subject: [PATCH 2/5] feat: add `yamlCompat` option for YAML 1.1 --- bin/cli.test.js | 20 ++++++++++---------- test/util-file.test.js | 30 +++++++++++++++++++++--------- test/yaml-compat/input.yaml | 25 +++++++++++++++++++++++++ test/yaml-compat/options.yaml | 4 ++++ test/yaml-compat/output.yaml | 25 +++++++++++++++++++++++++ 5 files changed, 85 insertions(+), 19 deletions(-) create mode 100644 test/yaml-compat/input.yaml create mode 100644 test/yaml-compat/options.yaml create mode 100644 test/yaml-compat/output.yaml diff --git a/bin/cli.test.js b/bin/cli.test.js index 7d7e556..4a33846 100644 --- a/bin/cli.test.js +++ b/bin/cli.test.js @@ -254,7 +254,7 @@ describe('openapi-format CLI command', () => { fs.writeFileSync( inputFile, - `openapi: 3.0.3\ninfo:\n title: t\n version: 1.0.0\npaths: {}\ncomponents:\n schemas:\n Feature:\n type: object\n properties:\n status:\n type: string\n enum:\n - "yes"\n - "no"\n - "on"\n - "off"\n example:\n status: "on"\n` + `openapi: 3.0.3\ninfo:\n title: t\n version: 1.0.0\npaths: {}\ncomponents:\n schemas:\n CountryCode:\n type: string\n enum:\n - ca\n - "no"\n - us\n Feature:\n type: object\n properties:\n status:\n type: string\n enum:\n - "yes"\n - "no"\n - "on"\n - "off"\n example:\n status: "on"\n` ); fs.writeFileSync(configFile, JSON.stringify({yamlCompat: 'yaml-1.1', sort: false}, null, 2)); @@ -263,15 +263,15 @@ describe('openapi-format CLI command', () => { expect(result.code).toBe(0); expect(result.stdout).toContain('formatted successfully'); const output = fs.readFileSync(outputFile, 'utf8'); - expect(output).toMatch(/-\s+["']yes["']/); - expect(output).toMatch(/-\s+["']no["']/); - expect(output).toMatch(/-\s+["']on["']/); - expect(output).toMatch(/-\s+["']off["']/); - expect(output).toMatch(/status:\s+["']on["']/); - expect(output).not.toMatch(/-\s+yes\b/); - expect(output).not.toMatch(/-\s+no\b/); - expect(output).not.toMatch(/-\s+on\b/); - expect(output).not.toMatch(/-\s+off\b/); + expect(output).toContain('CountryCode:'); + expect(output).toContain('Feature:'); + expect(output).toContain('- ca'); + expect(output).toContain('- us'); + expect(output).toContain('- "no"'); + expect(output).toContain('- "yes"'); + expect(output).toContain('- "on"'); + expect(output).toContain('- "off"'); + expect(output).toContain('status: "on"'); }); it('should load the default .openapiformatrc if configFile is not provided', async () => { diff --git a/test/util-file.test.js b/test/util-file.test.js index e45ec22..7322551 100644 --- a/test/util-file.test.js +++ b/test/util-file.test.js @@ -252,6 +252,10 @@ describe('openapi-format CLI file tests', () => { const obj = { components: { schemas: { + CountryCode: { + type: 'string', + enum: ['ca', 'no', 'us'] + }, Feature: { type: 'object', properties: { @@ -272,22 +276,26 @@ describe('openapi-format CLI file tests', () => { format: 'yaml' }); + expect(result).toContain('CountryCode:'); expect(result).toContain('enum:'); - expect(result).toContain('- yes'); + expect(result).toContain('- ca'); expect(result).toContain('- no'); + expect(result).toContain('- us'); + expect(result).toContain('Feature:'); + expect(result).toContain('- yes'); expect(result).toContain('- on'); expect(result).toContain('- off'); expect(result).toContain('status: on'); - expect(result).not.toContain("'yes'"); - expect(result).not.toContain("'no'"); - expect(result).not.toContain("'on'"); - expect(result).not.toContain("'off'"); }); test('should quote YAML 1.1 boolean-like strings when yamlCompat is yaml-1.1', async () => { const obj = { components: { schemas: { + CountryCode: { + type: 'string', + enum: ['ca', 'no', 'us'] + }, Feature: { type: 'object', properties: { @@ -309,10 +317,14 @@ describe('openapi-format CLI file tests', () => { yamlCompat: 'yaml-1.1' }); - expect(result).toContain(" - 'yes'"); - expect(result).toContain(" - 'no'"); - expect(result).toContain(" - 'on'"); - expect(result).toContain(" - 'off'"); + expect(result).toContain('CountryCode:'); + expect(result).toContain('- ca'); + expect(result).toContain("- 'no'"); + expect(result).toContain('- us'); + expect(result).toContain('Feature:'); + expect(result).toContain("- 'yes'"); + expect(result).toContain("- 'on'"); + expect(result).toContain("- 'off'"); expect(result).toContain("status: 'on'"); expect(result).not.toContain('- yes'); expect(result).not.toContain('- no'); diff --git a/test/yaml-compat/input.yaml b/test/yaml-compat/input.yaml new file mode 100644 index 0000000..f402626 --- /dev/null +++ b/test/yaml-compat/input.yaml @@ -0,0 +1,25 @@ +openapi: 3.0.3 +info: + title: t + version: 1.0.0 +paths: {} +components: + schemas: + CountryCode: + type: string + enum: + - ca + - "no" + - us + Feature: + type: object + properties: + status: + type: string + enum: + - "yes" + - "no" + - "on" + - "off" + example: + status: "on" diff --git a/test/yaml-compat/options.yaml b/test/yaml-compat/options.yaml new file mode 100644 index 0000000..3681e57 --- /dev/null +++ b/test/yaml-compat/options.yaml @@ -0,0 +1,4 @@ +verbose: true +no-sort: true +output: output.yaml +yamlCompat: yaml-1.1 diff --git a/test/yaml-compat/output.yaml b/test/yaml-compat/output.yaml new file mode 100644 index 0000000..f402626 --- /dev/null +++ b/test/yaml-compat/output.yaml @@ -0,0 +1,25 @@ +openapi: 3.0.3 +info: + title: t + version: 1.0.0 +paths: {} +components: + schemas: + CountryCode: + type: string + enum: + - ca + - "no" + - us + Feature: + type: object + properties: + status: + type: string + enum: + - "yes" + - "no" + - "on" + - "off" + example: + status: "on" From 6771c720c79e72388b49bf1c17d092300ea4bf03 Mon Sep 17 00:00:00 2001 From: Tim Haselaars Date: Wed, 17 Jun 2026 23:39:25 +0200 Subject: [PATCH 3/5] feat: add `yamlCompat` option for YAML 1.1 --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index db90a26..b5eca7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## unreleased +- CLI: add yamlCompat option for YAML 1.1 + ## [1.33.2] - 2026-06-17 - Config: Ship a JSON Schema for the configuration file (`schemas/openapi-format.schema.json`) (#223) @@ -20,7 +22,7 @@ ## [1.32.0] - 2026-05-23 -- Casing - Configure characters to keep +- Casing: Configure characters to keep - CLI: Fix YAML output to preserve x-version number formatting ## [1.31.0] - 2026-04-12 From ef9d688855982a6116fe7b8eedaea6fffd51f1ba Mon Sep 17 00:00:00 2001 From: Tim Haselaars Date: Fri, 19 Jun 2026 08:38:07 +0200 Subject: [PATCH 4/5] feat: add `yamlCompat` option for YAML 1.1 --- bin/cli.js | 2 +- readme.md | 46 +++++++++++++++++++++++----------------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/bin/cli.js b/bin/cli.js index 67266ca..a33cbb8 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -27,7 +27,7 @@ program .option('--no-sort', `don't sort the OpenAPI file`) .option('--keepComments', `don't remove the comments from the OpenAPI YAML file`, false) .option('--yamlQuoteStyle ', 'YAML quote style: single, double, or detect') - .option('--yamlCompat ', 'YAML compatibility schema for stringifying (yaml-1.1)') + .option('--yamlCompat ', 'YAML compatibility schema for older OpenAPI tools (yaml-1.1)') .option('--sortComponentsFile ', 'the file with components to sort alphabetically') .option('--sortComponentsProps', 'sort properties within schema components alphabetically', false) .option('--lineWidth ', 'max line width of YAML output', -1) diff --git a/readme.md b/readme.md index f2f98b5..f01be3c 100644 --- a/readme.md +++ b/readme.md @@ -158,39 +158,39 @@ Arguments: Options: - --output, -o Save the formatted OpenAPI file as JSON/YAML [path] + --output, -o Save the formatted OpenAPI file as JSON/YAML [path] - --sortFile The file to specify custom OpenAPI fields ordering [path] - --casingFile The file to specify casing rules [path] - --generateFile The file to specify generate rules [path] - --filterFile The file to specify filter rules [path] - --overlayFile The file to specify OpenAPI overlay actions [path] + --sortFile The file to specify custom OpenAPI fields ordering [path] + --casingFile The file to specify casing rules [path] + --generateFile The file to specify generate rules [path] + --filterFile The file to specify filter rules [path] + --overlayFile The file to specify OpenAPI overlay actions [path] - --no-sort Don't sort the OpenAPI file [boolean] - --keepComments Don't remove the comments from the OpenAPI YAML file [boolean] - --yamlQuoteStyle Preferred YAML quote style: single, double, detect [string] - --yamlCompat YAML compatibility schema for stringifying [string] - --sortComponentsFile The file with components to sort alphabetically [path] - --sortComponentsProps Sort properties within schema components alphabetically [boolean] + --no-sort Don't sort the OpenAPI file [boolean] + --keepComments Don't remove the comments from the OpenAPI YAML file [boolean] + --yamlQuoteStyle Preferred YAML quote style: single, double, detect [string] + --yamlCompat YAML compatibility schema for older OpenAPI tools [string] + --sortComponentsFile The file with components to sort alphabetically [path] + --sortComponentsProps Sort properties within schema components alphabetically [boolean] - --no-bundle Don't bundle the local and remote $ref [boolean] - --split Split OpenAPI document into a multi-file structure [boolean] + --no-bundle Don't bundle the local and remote $ref [boolean] + --split Split OpenAPI document into a multi-file structure [boolean] - --rename Rename the OpenAPI title [string] + --rename Rename the OpenAPI title [string] - --convertTo convert the OpenAPI document to OpenAPI version 3.1 or 3.2 [string] + --convertTo convert the OpenAPI document to OpenAPI version 3.1 or 3.2 [string] - --configFile The file with the OpenAPI-format CLI options [path] + --configFile The file with the OpenAPI-format CLI options [path] - --lineWidth Max line width of YAML output [number] + --lineWidth Max line width of YAML output [number] - --json Prints the file to stdout as JSON [boolean] - --yaml Prints the file to stdout as YAML [boolean] + --json Prints the file to stdout as JSON [boolean] + --yaml Prints the file to stdout as YAML [boolean] --playground, -p Open config in online playground - --help Show help [boolean] + --help Show help [boolean] --version Output the version number - --verbose Output more details of the filter process [count] + --verbose Output more details of the filter process [count] ``` ## OpenAPI format CLI options @@ -207,7 +207,7 @@ Options: | --no-sort | | don't sort the OpenAPI file | boolean | FALSE | optional | | --keepComments | | don't remove the comments from the OpenAPI YAML file | boolean | FALSE | optional | | --yamlQuoteStyle | | preferred YAML quote style for YAML output (`single`, `double`, `detect`) | string | detect | optional | -| --yamlCompat | | compatibility schema for YAML stringification (`yaml-1.1`) | string | | optional | +| --yamlCompat | | YAML compatibility schema for older OpenAPI tools (`yaml-1.1`) | string | | optional | | --sortComponentsFile | | sort the items of the components (schemas, parameters, ...) by alphabet | path to file | defaultSortComponents.json | optional | | --sortComponentsProps | | sort properties within schema components alphabetically | boolean | FALSE | optional | | --no-bundle | | don't bundle the local and remote $ref in the OpenAPI document | boolean | FALSE | optional | From a8ea6ede42427f7ac70ff66d0ec5ab54132a962e Mon Sep 17 00:00:00 2001 From: Tim Haselaars Date: Fri, 19 Jun 2026 08:41:20 +0200 Subject: [PATCH 5/5] feat: add `yamlCompat` option for YAML 1.1 --- bin/__snapshots__/cli.test.js.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/__snapshots__/cli.test.js.snap b/bin/__snapshots__/cli.test.js.snap index 604435f..dd41ab6 100644 --- a/bin/__snapshots__/cli.test.js.snap +++ b/bin/__snapshots__/cli.test.js.snap @@ -117,7 +117,7 @@ Options: --no-sort don't sort the OpenAPI file --keepComments don't remove the comments from the OpenAPI YAML file (default: false) --yamlQuoteStyle YAML quote style: single, double, or detect - --yamlCompat YAML compatibility schema for stringifying (yaml-1.1) + --yamlCompat YAML compatibility schema for older OpenAPI tools (yaml-1.1) --sortComponentsFile the file with components to sort alphabetically --sortComponentsProps sort properties within schema components alphabetically (default: false) --lineWidth max line width of YAML output (default: -1)