-
-
Notifications
You must be signed in to change notification settings - Fork 218
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ruleset-bundler): initial release (#1819)
* feat(ruleset-bundler): initial release * test(karma): setup alias for karma * style: simplify * docs: add some
- Loading branch information
Showing
30 changed files
with
1,351 additions
and
19 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# @stoplight/spectral-ruleset-bundler | ||
|
||
**WARNING** - for the time being, the following package is meant to be used internally. | ||
|
||
## Options | ||
|
||
- **plugins** - any other Rollup.js plugin, i.e. a minifier. | ||
- **target**: | ||
- `node` - a preset suitable for the Node.js runtime | ||
- `browser` - a preset tailored to Browsers | ||
- `runtime` - a preset you want to use if you want to bundle & execute the ruleset at the runtime | ||
- format - supported values are: `esm`, `commonjs`, `iife`. | ||
- treeshake - whether to enable tree shaking. False by default. | ||
|
||
**Bolded** options are required. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
{ | ||
"name": "@stoplight/spectral-ruleset-bundler", | ||
"version": "1.0.0", | ||
"homepage": "https://github.com/stoplightio/spectral", | ||
"bugs": "https://github.com/stoplightio/spectral/issues", | ||
"author": "Stoplight <support@stoplight.io>", | ||
"engines": { | ||
"node": ">=12.20" | ||
}, | ||
"license": "Apache-2.0", | ||
"main": "dist/index.js", | ||
"types": "dist/index.d.ts", | ||
"files": [ | ||
"/dist" | ||
], | ||
"type": "commonjs", | ||
"exports": { | ||
".": { | ||
"default": "./dist/index.js" | ||
}, | ||
"./presets/*": { | ||
"default": "./dist/presets/*.js" | ||
}, | ||
"./plugins/*": { | ||
"default": "./dist/plugins/*.js" | ||
} | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/stoplightio/spectral.git" | ||
}, | ||
"dependencies": { | ||
"@stoplight/path": "1.3.2", | ||
"@stoplight/spectral-core": ">=1", | ||
"@stoplight/spectral-functions": ">=1", | ||
"@stoplight/spectral-formats": ">=1", | ||
"@stoplight/spectral-parsers": ">=1", | ||
"@stoplight/spectral-ref-resolver": ">=1", | ||
"@stoplight/spectral-rulesets": ">=1", | ||
"@stoplight/spectral-runtime": "^1.1.0", | ||
"@stoplight/types": "12.3.0", | ||
"@types/node": "*", | ||
"rollup": "~2.56.3", | ||
"validate-npm-package-name": "3.0.0" | ||
}, | ||
"devDependencies": { | ||
"@types/validate-npm-package-name": "^3.0.3", | ||
"fetch-mock": "^9.11.0", | ||
"memfs": "^3.2.2", | ||
"prettier": "^2.3.2" | ||
} | ||
} |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { rollup, Plugin } from 'rollup'; | ||
import { isURL } from '@stoplight/path'; | ||
import { isValidPackageName } from './utils/isValidPackageName'; | ||
|
||
export type BundleOptions = { | ||
plugins: Plugin[]; | ||
target: 'node' | 'browser' | 'runtime'; | ||
format?: 'esm' | 'commonjs' | 'iife'; | ||
treeshake?: boolean; // false by default | ||
}; | ||
|
||
export async function bundleRuleset( | ||
input: string, | ||
{ target = 'browser', plugins, format, treeshake = false }: BundleOptions, | ||
): Promise<string> { | ||
const bundle = await rollup({ | ||
input, | ||
plugins, | ||
treeshake, | ||
watch: false, | ||
perf: false, | ||
onwarn(e, fn) { | ||
if (e.code === 'MISSING_NAME_OPTION_FOR_IIFE_EXPORT') { | ||
return; | ||
} | ||
|
||
fn(e); | ||
}, | ||
external: | ||
// the iife output is meant to be evaluated as a script type at the runtime, therefore it must not contain any import/exports, we must have the entire code ready to execute | ||
target === 'runtime' | ||
? [] | ||
: target === 'browser' | ||
? id => isURL(id) | ||
: (id, importer) => | ||
id.startsWith('node:') || | ||
(!isURL(id) && isValidPackageName(id) && (importer === void 0 || !isURL(importer))), | ||
}); | ||
|
||
return (await bundle.generate({ format: format ?? (target === 'runtime' ? 'iife' : 'esm'), exports: 'auto' })) | ||
.output[0].code; | ||
} |
197 changes: 197 additions & 0 deletions
197
packages/ruleset-bundler/src/plugins/__tests__/builtins.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
import * as fs from 'fs'; | ||
import { serveAssets } from '@stoplight/spectral-test-utils'; | ||
import * as runtime from '@stoplight/spectral-runtime'; | ||
import * as functions from '@stoplight/spectral-functions'; | ||
|
||
jest.mock?.('fs'); | ||
|
||
import { BundleOptions, bundleRuleset } from '../../index'; | ||
import type { IO } from '../../types'; | ||
import { virtualFs } from '../virtualFs'; | ||
import { builtins } from '../builtins'; | ||
|
||
describe('Builtins Plugin', () => { | ||
let io: IO; | ||
|
||
beforeEach(() => { | ||
io = { | ||
fs, | ||
fetch: runtime.fetch, | ||
}; | ||
}); | ||
|
||
describe.each<BundleOptions['target']>(['browser', 'runtime'])('given %s target', target => { | ||
it('should inline Spectral packages & expose it to the runtime', async () => { | ||
serveAssets({ | ||
'/tmp/input.js': `import { schema } from '@stoplight/spectral-functions'; | ||
import { oas } from '@stoplight/spectral-rulesets'; | ||
export default { | ||
extends: [oas], | ||
rules: { | ||
'my-rule': { | ||
given: '$', | ||
then: { | ||
function: schema, | ||
functionOptions: { | ||
schema: { | ||
type: 'object', | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
};`, | ||
}); | ||
|
||
const code = await bundleRuleset('/tmp/input.js', { | ||
format: 'esm', | ||
target, | ||
plugins: [builtins(), virtualFs(io)], | ||
}); | ||
|
||
expect(code) | ||
.toEqual(`const alphabetical = globalThis[Symbol.for('@stoplight/spectral-functions')]['alphabetical']; | ||
const casing = globalThis[Symbol.for('@stoplight/spectral-functions')]['casing']; | ||
const defined = globalThis[Symbol.for('@stoplight/spectral-functions')]['defined']; | ||
const enumeration = globalThis[Symbol.for('@stoplight/spectral-functions')]['enumeration']; | ||
const falsy = globalThis[Symbol.for('@stoplight/spectral-functions')]['falsy']; | ||
const length = globalThis[Symbol.for('@stoplight/spectral-functions')]['length']; | ||
const pattern = globalThis[Symbol.for('@stoplight/spectral-functions')]['pattern']; | ||
const schema = globalThis[Symbol.for('@stoplight/spectral-functions')]['schema']; | ||
const truthy = globalThis[Symbol.for('@stoplight/spectral-functions')]['truthy']; | ||
const undefined$1 = globalThis[Symbol.for('@stoplight/spectral-functions')]['undefined']; | ||
const unreferencedReusableObject = globalThis[Symbol.for('@stoplight/spectral-functions')]['unreferencedReusableObject']; | ||
const xor = globalThis[Symbol.for('@stoplight/spectral-functions')]['xor']; | ||
const oas = globalThis[Symbol.for('@stoplight/spectral-rulesets')]['oas']; | ||
const asyncapi = globalThis[Symbol.for('@stoplight/spectral-rulesets')]['asyncapi']; | ||
var input = { | ||
extends: [oas], | ||
rules: { | ||
'my-rule': { | ||
given: '$', | ||
then: { | ||
function: schema, | ||
functionOptions: { | ||
schema: { | ||
type: 'object', | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}; | ||
export { input as default }; | ||
`); | ||
|
||
expect(globalThis[Symbol.for('@stoplight/spectral-functions')]).toStrictEqual(functions); | ||
}); | ||
|
||
it('should support overrides', async () => { | ||
serveAssets({ | ||
'/tmp/input.js': `import { readFile } from '@stoplight/spectral-runtime'; | ||
readFile();`, | ||
}); | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-empty-function | ||
function readFile(): void {} | ||
|
||
const code = await bundleRuleset('/tmp/input.js', { | ||
format: 'esm', | ||
target, | ||
plugins: [ | ||
builtins({ | ||
'@stoplight/spectral-runtime': { | ||
readFile, | ||
}, | ||
}), | ||
virtualFs(io), | ||
], | ||
}); | ||
|
||
expect(code).toEqual(`const fetch = globalThis[Symbol.for('@stoplight/spectral-runtime')]['fetch']; | ||
const DEFAULT_REQUEST_OPTIONS = globalThis[Symbol.for('@stoplight/spectral-runtime')]['DEFAULT_REQUEST_OPTIONS']; | ||
const decodeSegmentFragment = globalThis[Symbol.for('@stoplight/spectral-runtime')]['decodeSegmentFragment']; | ||
const printError = globalThis[Symbol.for('@stoplight/spectral-runtime')]['printError']; | ||
const PrintStyle = globalThis[Symbol.for('@stoplight/spectral-runtime')]['PrintStyle']; | ||
const printPath = globalThis[Symbol.for('@stoplight/spectral-runtime')]['printPath']; | ||
const printValue = globalThis[Symbol.for('@stoplight/spectral-runtime')]['printValue']; | ||
const startsWithProtocol = globalThis[Symbol.for('@stoplight/spectral-runtime')]['startsWithProtocol']; | ||
const isAbsoluteRef = globalThis[Symbol.for('@stoplight/spectral-runtime')]['isAbsoluteRef']; | ||
const traverseObjUntilRef = globalThis[Symbol.for('@stoplight/spectral-runtime')]['traverseObjUntilRef']; | ||
const getEndRef = globalThis[Symbol.for('@stoplight/spectral-runtime')]['getEndRef']; | ||
const safePointerToPath = globalThis[Symbol.for('@stoplight/spectral-runtime')]['safePointerToPath']; | ||
const getClosestJsonPath = globalThis[Symbol.for('@stoplight/spectral-runtime')]['getClosestJsonPath']; | ||
const readFile = globalThis[Symbol.for('@stoplight/spectral-runtime')]['readFile']; | ||
const readParsable = globalThis[Symbol.for('@stoplight/spectral-runtime')]['readParsable']; | ||
readFile(); | ||
`); | ||
|
||
expect(globalThis[Symbol.for('@stoplight/spectral-runtime')]).toStrictEqual({ | ||
...runtime, | ||
readFile, | ||
}); | ||
}); | ||
}); | ||
|
||
describe('given node target', () => { | ||
it('should be a no-op', async () => { | ||
serveAssets({ | ||
'/tmp/input.js': `import { schema } from '@stoplight/spectral-functions'; | ||
import { oas } from '@stoplight/spectral-rulesets'; | ||
export default { | ||
extends: [oas], | ||
rules: { | ||
'my-rule': { | ||
given: '$', | ||
then: { | ||
function: schema, | ||
functionOptions: { | ||
schema: { | ||
type: 'object', | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
};`, | ||
}); | ||
|
||
const code = await bundleRuleset('/tmp/input.js', { | ||
target: 'node', | ||
plugins: [builtins(), virtualFs(io)], | ||
}); | ||
|
||
expect(code).toEqual(`import { schema } from '@stoplight/spectral-functions'; | ||
import { oas } from '@stoplight/spectral-rulesets'; | ||
var input = { | ||
extends: [oas], | ||
rules: { | ||
'my-rule': { | ||
given: '$', | ||
then: { | ||
function: schema, | ||
functionOptions: { | ||
schema: { | ||
type: 'object', | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}; | ||
export { input as default }; | ||
`); | ||
|
||
expect(globalThis[Symbol.for('@stoplight/spectral-functions')]).toStrictEqual(functions); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.