Skip to content

Commit

Permalink
feat: port ruleset loading logic from spectral-cli
Browse files Browse the repository at this point in the history
  • Loading branch information
P0lip committed Jan 20, 2023
1 parent 6764380 commit a8cf48e
Showing 1 changed file with 65 additions and 6 deletions.
71 changes: 65 additions & 6 deletions src/getRuleset.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import * as fs from 'fs';
import * as process from 'process';
import { createRequire } from 'module';
import type { Optional } from '@stoplight/types';
import { Ruleset } from '@stoplight/spectral-core';
import { Ruleset, RulesetDefinition } from '@stoplight/spectral-core';
import { info, error } from '@actions/core';
import { isError, isObject } from 'lodash';
import * as path from '@stoplight/path';
import { fetch } from '@stoplight/spectral-runtime';
import type { IO } from '@stoplight/spectral-ruleset-bundler';
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 { builtins } from '@stoplight/spectral-ruleset-bundler/plugins/builtins';
import { commonjs } from '@stoplight/spectral-ruleset-bundler/plugins/commonjs';
import { bundleAndLoadRuleset } from '@stoplight/spectral-ruleset-bundler/with-loader';
import { stdin } from '@stoplight/spectral-ruleset-bundler/plugins/stdin';

async function getDefaultRulesetFile(): Promise<Optional<string>> {
const cwd = process.cwd();
Expand All @@ -21,6 +25,10 @@ async function getDefaultRulesetFile(): Promise<Optional<string>> {
return;
}

function isErrorWithCode(error: Error | (Error & { code: unknown })): error is Error & { code: string } {
return 'code' in error && typeof error.code === 'string';
}

export async function getRuleset(rulesetFile: Optional<string>): Promise<Ruleset> {
if (!rulesetFile) {
rulesetFile = await getDefaultRulesetFile();
Expand All @@ -36,12 +44,63 @@ export async function getRuleset(rulesetFile: Optional<string>): Promise<Ruleset

info(`Loading ruleset '${rulesetFile}'...`);

const io: IO = { fetch, fs };
let ruleset: string;

try {
return await bundleAndLoadRuleset(rulesetFile, io, [builtins(), commonjs()]);
if (await isBasicRuleset(rulesetFile)) {
const migratedRuleset = await migrateRuleset(rulesetFile, {
format: 'esm',
fs,
});

rulesetFile = path.join(path.dirname(rulesetFile), '.spectral.js');

ruleset = await bundleRuleset(rulesetFile, {
target: 'node',
format: 'commonjs',
plugins: [stdin(migratedRuleset, rulesetFile), builtins(), commonjs(), ...node({ fs, fetch })],
});
} else {
ruleset = await bundleRuleset(rulesetFile, {
target: 'node',
format: 'commonjs',
plugins: [builtins(), commonjs(), ...node({ fs, fetch })],
});
}

return new Ruleset(load(ruleset, rulesetFile), {
severity: 'recommended',
source: rulesetFile,
});
} catch (e) {
error(`Failed to load ruleset '${rulesetFile}'... Error: ${String(e)}`);
if (!isError(e) || !isErrorWithCode(e) || e.code !== 'UNRESOLVED_ENTRY') {
error(`Could not load ${rulesetFile} ruleset`);
} else {
error(`Could not load ${rulesetFile} ruleset. ${e.message}`);
}

throw e;
}
}

function load(source: string, uri: string): RulesetDefinition {
const actualUri = path.isURL(uri) ? uri.replace(/^https?:\//, '') : uri;
// we could use plain `require`, but this approach has a number of benefits:
// - it is bundler-friendly
// - ESM compliant
// - and we have no warning raised by pkg.
const req = createRequire(actualUri);
const m: { exports?: RulesetDefinition } = {};
const paths = [path.dirname(uri), __dirname];

const _require = (id: string): unknown => req(req.resolve(id, { paths }));

// eslint-disable-next-line @typescript-eslint/no-implied-eval
Function('module, require', source)(m, _require);

if (!isObject(m.exports)) {
throw new Error('No valid export found');
}

return m.exports;
}

0 comments on commit a8cf48e

Please sign in to comment.