Repository: Redocly/openapi-cli (ts)

Issue:
bundle issue when files use multiple dots
We're having a similar issue, in that our object files are named `foo.bar.baz.v1.yaml` - we've verified that the bundler behaves sensibly if we rename the files/references to `foo-bar-baz-v1.yaml`, however it would be nice(r) for the bundler not to assume that the first period it encounters is the filename and everything after it is the extension.

As it is, `foo.bar.baz.v1.yaml`, `foo.bar.qux.v1.yaml` and `foo.quux.quuz.v1.yaml` get folded into the components collection as `foo-1`, `foo-2` and `foo-3` - along with a load of "duplicate name" errors.

_Originally posted by @jasonbstanding in https://github.com/Redocly/openapi-cli/issues/260#issuecomment-1041396491_

Pull request description:
fix: improper handling of filenames with multiple dots
## What/Why/How?
Fixes the issue when `foo.bar.baz.v1.yaml`, `foo.bar.qux.v1.yaml` and `foo.quux.quuz.v1.yaml` would get folded into the components collection as foo-1, foo-2 and foo-3 - along with a load of "duplicate name" errors.

This happened because it treated everthing after the **first** dot as a file extension. Now it matches the **last** dot of the filename.

## Reference
closes #560 
## Testing

## Screenshots (optional)

## Check yourself

- [x] Code is linted
- [x] Tested with redoc/reference-docs/workflows
- [x] All new/updated code is covered with tests

## Security

- [x] Security impact of change has been considered
- [x] Code follows company security practices and guidelines


Patch:
diff --git a/packages/core/src/ref-utils.ts b/packages/core/src/ref-utils.ts
index b91cfbc0..663baeab 100644
--- a/packages/core/src/ref-utils.ts
+++ b/packages/core/src/ref-utils.ts
@@ -60,7 +60,7 @@ export function pointerBaseName(pointer: string) {
 
 export function refBaseName(ref: string) {
   const parts = ref.split(/[\/\\]/); // split by '\' and '/'
-  return parts[parts.length - 1].split('.')[0];
+  return parts[parts.length - 1].replace(/\.[^.]+$/, ''); // replace extension with empty string
 }
 
 export function isAbsoluteUrl(ref: string) {


Test patch:
diff --git a/packages/core/__tests__/__snapshots__/bundle.test.ts.snap b/packages/core/__tests__/__snapshots__/bundle.test.ts.snap
index 42af04ee..a4069fd6 100644
--- a/packages/core/__tests__/__snapshots__/bundle.test.ts.snap
+++ b/packages/core/__tests__/__snapshots__/bundle.test.ts.snap
@@ -144,10 +144,24 @@ paths:
           content:
             application/json:
               schema:
-                $ref: '#/components/schemas/vendor'
+                $ref: '#/components/schemas/vendor.schema'
 components:
   schemas:
     vendor:
+      $ref: '#/components/schemas/vendor.schema'
+    myvendor:
+      $ref: '#/components/schemas/vendor.schema'
+    simple:
+      type: string
+    A:
+      type: string
+    test:
+      $ref: '#/components/schemas/rename-2'
+    rename:
+      type: string
+    rename-2:
+      type: number
+    vendor.schema:
       title: vendor
       type: object
       description: Vendors
@@ -171,18 +185,6 @@ components:
           type: boolean
           description: One-time use
           default: false
-    myvendor:
-      $ref: '#/components/schemas/vendor'
-    simple:
-      type: string
-    A:
-      type: string
-    test:
-      $ref: '#/components/schemas/rename-2'
-    rename:
-      type: string
-    rename-2:
-      type: number
 
 `;
 
diff --git a/packages/core/__tests__/ref-utils.test.ts b/packages/core/__tests__/ref-utils.test.ts
index 0df177ac..42fc61a7 100644
--- a/packages/core/__tests__/ref-utils.test.ts
+++ b/packages/core/__tests__/ref-utils.test.ts
@@ -1,6 +1,6 @@
 import outdent from 'outdent';
 import { parseYamlToDocument } from './utils';
-import { parseRef } from '../src/ref-utils';
+import { parseRef, refBaseName } from '../src/ref-utils';
 import { lintDocument } from '../src/lint';
 import { LintConfig } from '../src/config/config';
 import { BaseResolver } from '../src/resolve';
@@ -95,4 +95,26 @@ describe('ref-utils', () => {
 
     expect(result).toMatchInlineSnapshot(`Array []`);
   });
+
+  describe('refBaseName', () => {
+    it("returns base name for file reference", () => {
+      expect(refBaseName("../testcase/Pet.yaml")).toStrictEqual("Pet");
+    });
+
+    it("returns base name for local file reference", () => {
+      expect(refBaseName("Cat.json")).toStrictEqual("Cat");
+    });
+
+    it("returns base name for url reference", () => {
+      expect(refBaseName("http://example.com/tests/crocodile.json")).toStrictEqual("crocodile");
+    });
+
+    it("returns base name for file with multiple dots in name", () => {
+      expect(refBaseName("feline.tiger.v1.yaml")).toStrictEqual("feline.tiger.v1");
+    });
+
+    it("returns base name for file without any dots in name", () => {
+      expect(refBaseName("abcdefg")).toStrictEqual("abcdefg");
+    });
+  });
 });