Repository: stoplightio/spectral (ts)

Issue:
Unable to use a spectral file located in Enterprise Github with a token parameter
**Describe the bug**
When trying to use a spectral lint file that requires a token param, Spectral throws an unexpected error

```
Error running Spectral!
Error #1: Unexpected token (Note that you need plugins to import files that are not JavaScript)
          at error            …hared/rollup.js:198  base = Object.assig…
          at error            …red/rollup.js:12555  return error(props);
          at tryParse         …red/rollup.js:12932  return this.error({ 
          at setSource        …red/rollup.js:12837  ast = this.tryParse…
          at addModuleSource  …red/rollup.js:22311  module.setSource(aw…
```

**To Reproduce**

1. Create a GitHub repository and place a spectral file there, like this:
    * style.yaml
```
extends: spectral:oas
rules:
  oas3-api-costcenter:
    description: There must be x-cost-center.
    given: $
    severity: error
    then:
      field: x-cost-center
      function: truthy

  oas3-api-businessid:
    description: There must be x-business-id.
    given: $
    severity: error
    then:
      field: x-business-id
      function: truthy
``` 
2. Go to the raw view of that spectral file and copy the url, it should have a token in it
2. Create a local folder with a "src" folder and a basic api-schema.json file that has an oas schema
3. Run this CLI command 'spectral lint "./src/api-schema.json" --ruleset {url with token from step 2} --verbose'
4. See error
```
Error running Spectral!
Error #1: Unexpected token (Note that you need plugins to import files that are not JavaScript)
          at error            …hared/rollup.js:198  base = Object.assig…
          at error            …red/rollup.js:12555  return error(props);
          at tryParse         …red/rollup.js:12932  return this.error({ 
          at setSource        …red/rollup.js:12837  ast = this.tryParse…
          at addModuleSource  …red/rollup.js:22311  module.setSource(aw…
```

**Expected behavior**
I would expect that Spectral would be able to ingest this file as a standard curl command shows the file as it should be.  Additionally, if I pull the file down locally and put it in the repo, it runs fine.

**Environment (remove any that are not applicable):**
 - Library version: Spectral v6.5.1
 - OS: Windows 10
 - Browser: n/a

**Additional context**
Add any other context about the problem here.


Pull request description:
feat(cli): use Content-Type header to detect ruleset format
Fixes #2270

**Checklist**

- [x] Tests added / updated
- [x] Docs added / updated

**Does this PR introduce a breaking change?**

- [ ] Yes
- [x] No




Patch:
diff --git a/packages/cli/package.json b/packages/cli/package.json
index e4a44039..9c5046b6 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -40,7 +40,7 @@
     "@stoplight/spectral-parsers": "^1.0.1",
     "@stoplight/spectral-ref-resolver": "1.0.2",
     "@stoplight/spectral-ruleset-bundler": "^1.0.0",
-    "@stoplight/spectral-ruleset-migrator": "^1.5.0",
+    "@stoplight/spectral-ruleset-migrator": "^1.9.0",
     "@stoplight/spectral-rulesets": ">=1",
     "@stoplight/spectral-runtime": "^1.1.0",
     "@stoplight/types": "12.3.0",
diff --git a/packages/cli/src/services/linter/utils/getRuleset.ts b/packages/cli/src/services/linter/utils/getRuleset.ts
index f2af0b7c..8bf4571a 100644
--- a/packages/cli/src/services/linter/utils/getRuleset.ts
+++ b/packages/cli/src/services/linter/utils/getRuleset.ts
@@ -5,7 +5,7 @@ import * as path from '@stoplight/path';
 import * as process from 'process';
 import { createRequire } from 'module';
 import { fetch } from '@stoplight/spectral-runtime';
-import { migrateRuleset } from '@stoplight/spectral-ruleset-migrator';
+import { migrateRuleset, isBasicRuleset } from '@stoplight/spectral-ruleset-migrator';
 import { bundleRuleset } from '@stoplight/spectral-ruleset-bundler';
 import { node } from '@stoplight/spectral-ruleset-bundler/presets/node';
 import { stdin } from '@stoplight/spectral-ruleset-bundler/plugins/stdin';
@@ -25,10 +25,6 @@ async function getDefaultRulesetFile(): Promise<Optional<string>> {
   return;
 }
 
-function isBasicRuleset(filepath: string): boolean {
-  return /\.(json|ya?ml)$/.test(path.extname(filepath));
-}
-
 function isErrorWithCode(error: Error | (Error & { code: unknown })): error is Error & { code: string } {
   return 'code' in error && typeof error.code === 'string';
 }
@@ -49,7 +45,7 @@ export async function getRuleset(rulesetFile: Optional<string>): Promise<Ruleset
   let ruleset: string;
 
   try {
-    if (isBasicRuleset(rulesetFile)) {
+    if (await isBasicRuleset(rulesetFile)) {
       const migratedRuleset = await migrateRuleset(rulesetFile, {
         format: 'esm',
         fs,
diff --git a/yarn.lock b/yarn.lock
index fd456c0d..90c177b4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2514,7 +2514,7 @@ __metadata:
     "@stoplight/spectral-parsers": ^1.0.1
     "@stoplight/spectral-ref-resolver": 1.0.2
     "@stoplight/spectral-ruleset-bundler": ^1.0.0
-    "@stoplight/spectral-ruleset-migrator": ^1.5.0
+    "@stoplight/spectral-ruleset-migrator": ^1.9.0
     "@stoplight/spectral-rulesets": ">=1"
     "@stoplight/spectral-runtime": ^1.1.0
     "@stoplight/types": 12.3.0
@@ -2660,7 +2660,7 @@ __metadata:
   languageName: unknown
   linkType: soft
 
-"@stoplight/spectral-ruleset-migrator@^1.5.0, @stoplight/spectral-ruleset-migrator@^1.7.4, @stoplight/spectral-ruleset-migrator@workspace:packages/ruleset-migrator":
+"@stoplight/spectral-ruleset-migrator@^1.7.4, @stoplight/spectral-ruleset-migrator@^1.9.0, @stoplight/spectral-ruleset-migrator@workspace:packages/ruleset-migrator":
   version: 0.0.0-use.local
   resolution: "@stoplight/spectral-ruleset-migrator@workspace:packages/ruleset-migrator"
   dependencies:


Test patch:
diff --git a/packages/cli/src/services/__tests__/linter.test.ts b/packages/cli/src/services/__tests__/linter.test.ts
index 97f17507..e17a1b77 100644
--- a/packages/cli/src/services/__tests__/linter.test.ts
+++ b/packages/cli/src/services/__tests__/linter.test.ts
@@ -313,7 +313,7 @@ describe('Linter service', () => {
           .persist()
           .get('/ruleset.json')
           .replyWithFile(200, join(__dirname, '__fixtures__/ruleset.json'), {
-            'Content-Type': 'application/yaml',
+            'Content-Type': 'application/json',
           });
 
         const output = await run(`lint ${validOas3SpecPath} -r http://foo.local/ruleset.json`);
@@ -326,6 +326,45 @@ describe('Linter service', () => {
           ]),
         );
       });
+
+      it('fallbacks to Content-Type', async () => {
+        nock('http://foo.local')
+          .persist()
+          .get('/ruleset')
+          .replyWithFile(200, join(__dirname, '__fixtures__/ruleset.json'), {
+            'Content-Type': 'application/json',
+          });
+
+        const output = await run(`lint ${validOas3SpecPath} -r http://foo.local/ruleset`);
+        expect(output).toEqual(expect.arrayContaining([expect.objectContaining({ code: 'info-matches-stoplight' })]));
+        expect(output).toEqual(
+          expect.not.arrayContaining([
+            expect.objectContaining({
+              message: 'Info object should contain `contact` object',
+            }),
+          ]),
+        );
+      });
+
+      it('ignores query parameters', async () => {
+        nock('http://foo.local')
+          .persist()
+          .get('/ruleset.json')
+          .query({ token: 'bar' })
+          .replyWithFile(200, join(__dirname, '__fixtures__/ruleset.json'), {
+            'Content-Type': 'text/plain', // GitHub raw like
+          });
+
+        const output = await run(`lint ${validOas3SpecPath} -r http://foo.local/ruleset.json?token=bar`);
+        expect(output).toEqual(expect.arrayContaining([expect.objectContaining({ code: 'info-matches-stoplight' })]));
+        expect(output).toEqual(
+          expect.not.arrayContaining([
+            expect.objectContaining({
+              message: 'Info object should contain `contact` object',
+            }),
+          ]),
+        );
+      });
     });
   });