Skip to content

Commit a8cf48e

Browse files
committed
feat: port ruleset loading logic from spectral-cli
1 parent 6764380 commit a8cf48e

File tree

1 file changed

+65
-6
lines changed

1 file changed

+65
-6
lines changed

src/getRuleset.ts

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
import * as fs from 'fs';
22
import * as process from 'process';
3+
import { createRequire } from 'module';
34
import type { Optional } from '@stoplight/types';
4-
import { Ruleset } from '@stoplight/spectral-core';
5+
import { Ruleset, RulesetDefinition } from '@stoplight/spectral-core';
56
import { info, error } from '@actions/core';
7+
import { isError, isObject } from 'lodash';
68
import * as path from '@stoplight/path';
79
import { fetch } from '@stoplight/spectral-runtime';
8-
import type { IO } from '@stoplight/spectral-ruleset-bundler';
10+
import { migrateRuleset, isBasicRuleset } from '@stoplight/spectral-ruleset-migrator';
11+
import { bundleRuleset } from '@stoplight/spectral-ruleset-bundler';
12+
import { node } from '@stoplight/spectral-ruleset-bundler/presets/node';
913
import { builtins } from '@stoplight/spectral-ruleset-bundler/plugins/builtins';
1014
import { commonjs } from '@stoplight/spectral-ruleset-bundler/plugins/commonjs';
11-
import { bundleAndLoadRuleset } from '@stoplight/spectral-ruleset-bundler/with-loader';
15+
import { stdin } from '@stoplight/spectral-ruleset-bundler/plugins/stdin';
1216

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

28+
function isErrorWithCode(error: Error | (Error & { code: unknown })): error is Error & { code: string } {
29+
return 'code' in error && typeof error.code === 'string';
30+
}
31+
2432
export async function getRuleset(rulesetFile: Optional<string>): Promise<Ruleset> {
2533
if (!rulesetFile) {
2634
rulesetFile = await getDefaultRulesetFile();
@@ -36,12 +44,63 @@ export async function getRuleset(rulesetFile: Optional<string>): Promise<Ruleset
3644

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

39-
const io: IO = { fetch, fs };
47+
let ruleset: string;
4048

4149
try {
42-
return await bundleAndLoadRuleset(rulesetFile, io, [builtins(), commonjs()]);
50+
if (await isBasicRuleset(rulesetFile)) {
51+
const migratedRuleset = await migrateRuleset(rulesetFile, {
52+
format: 'esm',
53+
fs,
54+
});
55+
56+
rulesetFile = path.join(path.dirname(rulesetFile), '.spectral.js');
57+
58+
ruleset = await bundleRuleset(rulesetFile, {
59+
target: 'node',
60+
format: 'commonjs',
61+
plugins: [stdin(migratedRuleset, rulesetFile), builtins(), commonjs(), ...node({ fs, fetch })],
62+
});
63+
} else {
64+
ruleset = await bundleRuleset(rulesetFile, {
65+
target: 'node',
66+
format: 'commonjs',
67+
plugins: [builtins(), commonjs(), ...node({ fs, fetch })],
68+
});
69+
}
70+
71+
return new Ruleset(load(ruleset, rulesetFile), {
72+
severity: 'recommended',
73+
source: rulesetFile,
74+
});
4375
} catch (e) {
44-
error(`Failed to load ruleset '${rulesetFile}'... Error: ${String(e)}`);
76+
if (!isError(e) || !isErrorWithCode(e) || e.code !== 'UNRESOLVED_ENTRY') {
77+
error(`Could not load ${rulesetFile} ruleset`);
78+
} else {
79+
error(`Could not load ${rulesetFile} ruleset. ${e.message}`);
80+
}
81+
4582
throw e;
4683
}
4784
}
85+
86+
function load(source: string, uri: string): RulesetDefinition {
87+
const actualUri = path.isURL(uri) ? uri.replace(/^https?:\//, '') : uri;
88+
// we could use plain `require`, but this approach has a number of benefits:
89+
// - it is bundler-friendly
90+
// - ESM compliant
91+
// - and we have no warning raised by pkg.
92+
const req = createRequire(actualUri);
93+
const m: { exports?: RulesetDefinition } = {};
94+
const paths = [path.dirname(uri), __dirname];
95+
96+
const _require = (id: string): unknown => req(req.resolve(id, { paths }));
97+
98+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
99+
Function('module, require', source)(m, _require);
100+
101+
if (!isObject(m.exports)) {
102+
throw new Error('No valid export found');
103+
}
104+
105+
return m.exports;
106+
}

0 commit comments

Comments
 (0)