Skip to content

Commit

Permalink
feat(ruleset-migrator): use Content-Type header to detect ruleset format
Browse files Browse the repository at this point in the history
  • Loading branch information
P0lip committed Oct 24, 2022
1 parent bf608bf commit 909e337
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 6 deletions.
41 changes: 39 additions & 2 deletions packages/ruleset-migrator/src/__tests__/ruleset.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ afterAll(() => {
vol.reset();
});

function createFetchMockSandbox() {
// something is off with default module interop in Karma :man_shrugging:
return ((fetchMock as { default?: typeof import('fetch-mock') }).default ?? fetchMock).sandbox();
}

const scenarios = Object.keys(fixtures)
.filter(key => path.basename(key) === 'output.mjs')
.map(key => path.dirname(key));
Expand Down Expand Up @@ -99,8 +104,7 @@ describe('migrator', () => {
});

it('should accept custom fetch implementation', async () => {
// something is off with default module interop in Karma :man_shrugging:
const fetch = ((fetchMock as { default?: typeof import('fetch-mock') }).default ?? fetchMock).sandbox();
const fetch = createFetchMockSandbox();

await vol.promises.writeFile(
path.join(cwd, 'ruleset.json'),
Expand All @@ -123,6 +127,9 @@ describe('migrator', () => {
},
},
},
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
});

expect(
Expand Down Expand Up @@ -218,6 +225,36 @@ export default {
`);
});

it('should use Content-Type detection', async () => {
const fetch = createFetchMockSandbox();

await vol.promises.writeFile(
path.join(cwd, 'ruleset.json'),
JSON.stringify({
extends: ['https://spectral.stoplight.io/ruleset'],
}),
);

fetch.get('https://spectral.stoplight.io/ruleset', {
body: `export default { rules: {} }`,
headers: {
'Content-Type': 'application/javascript; charset=utf-8',
},
});

expect(
await migrateRuleset(path.join(cwd, 'ruleset.json'), {
format: 'esm',
fs: vol as any,
fetch,
}),
).toEqual(`import ruleset_ from "https://spectral.stoplight.io/ruleset";
export default {
"extends": [ruleset_]
};
`);
});

describe('custom npm registry', () => {
it('should be supported', async () => {
serveAssets({
Expand Down
2 changes: 2 additions & 0 deletions packages/ruleset-migrator/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { ExpressionKind } from 'ast-types/gen/kinds';
import { assertRuleset } from './validation';
import { Ruleset } from './validation/types';

export { isBasicRuleset } from './utils/isBasicRuleset';

async function read(filepath: string, fs: MigrationOptions['fs'], fetch: Fetch): Promise<Ruleset> {
const input = isURL(filepath) ? await (await fetch(filepath)).text() : await fs.promises.readFile(filepath, 'utf8');

Expand Down
7 changes: 3 additions & 4 deletions packages/ruleset-migrator/src/transformers/extends.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@ import * as path from '@stoplight/path';
import { Transformer, TransformerCtx } from '../types';
import { Ruleset } from '../validation/types';
import { assertArray } from '../validation';
import { process } from '..';
import { process } from '../index';
import { isBasicRuleset } from '../utils/isBasicRuleset';

const REPLACEMENTS = {
'spectral:oas': 'oas',
'spectral:asyncapi': 'asyncapi',
};

const KNOWN_JS_EXTS = /^\.[cm]?js$/;

export { transformer as default };

async function processExtend(
Expand All @@ -24,7 +23,7 @@ async function processExtend(

const filepath = ctx.tree.resolveModule(name, ctx, 'ruleset');

if (KNOWN_JS_EXTS.test(path.extname(filepath))) {
if (!(await isBasicRuleset(filepath, ctx.opts.fetch))) {
return ctx.tree.addImport(`${path.basename(filepath, true)}_${path.extname(filepath)}`, filepath, true);
}

Expand Down
34 changes: 34 additions & 0 deletions packages/ruleset-migrator/src/utils/isBasicRuleset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { fetch as defaultFetch } from '@stoplight/spectral-runtime';
import { isURL, extname } from '@stoplight/path';
import type { Fetch } from '../types';

function stripSearchFromUrl(url: string): string {
try {
const { href, search } = new URL(url);
return href.slice(0, href.length - search.length);
} catch {
return url;
}
}

const CONTENT_TYPE_REGEXP = /^(?:application|text)\/(?:yaml|json)(?:;|$)/i;
const EXT_REGEXP = /\.(json|ya?ml)$/i;

export async function isBasicRuleset(uri: string, fetch: Fetch = defaultFetch): Promise<boolean> {
const ext = extname(isURL(uri) ? stripSearchFromUrl(uri) : uri);

if (EXT_REGEXP.test(ext)) {
return true;
}

if (!isURL(uri)) {
return false;
}

try {
const contentType = (await fetch(uri)).headers.get('Content-Type');
return contentType !== null && CONTENT_TYPE_REGEXP.test(contentType);
} catch {
return false;
}
}

0 comments on commit 909e337

Please sign in to comment.