diff --git a/package.json b/package.json index bdd746a0f..6559da12c 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "build": "tsc -p ./tsconfig.build.json", "cli": "node -r ts-node/register -r tsconfig-paths/register src/cli/index.ts", "cli:debug": "node -r ts-node/register -r tsconfig-paths/register --inspect-brk src/cli/index.ts", - "generate-assets": "node ./scripts/generate-assets.js", + "generate-assets": "node -r ts-node/register ./scripts/generate-assets.ts", "inline-version": "./scripts/inline-version.js", "lint.fix": "yarn lint --fix", "lint": "tsc --noEmit && tslint 'src/**/*.ts'", @@ -49,7 +49,7 @@ "prebuild": "yarn build.clean && copyfiles -u 1 \"src/rulesets/oas*/**/*.json\" dist && copyfiles -u 1 \"src/rulesets/oas*/**/*.json\" ./ && yarn copy.html-templates", "prebuild.binary": "yarn build", "pretest.karma": "node ./scripts/generate-karma-fixtures.js && yarn pretest", - "pretest": "node ./scripts/generate-assets.js", + "pretest": "yarn generate-assets", "schema.update": "yarn typescript-json-schema --id \"http://stoplight.io/schemas/rule.schema.json\" --required tsconfig.json IRule --out ./src/meta/rule.schema.json", "test.harness": "jest -c ./jest.harness.config.js", "test.karma": "karma start", diff --git a/scripts/generate-assets.js b/scripts/generate-assets.js deleted file mode 100755 index c910ef4a7..000000000 --- a/scripts/generate-assets.js +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env node -/** - * This script generates a list of assets that are needed to load spectral:oas ruleset. - * It contains all OAS custom functions and *resolved* rulesets. - * The assets are stores in a single filed call assets.json in the following format: - * `: ` - * where the `require-call-path` is the path you'd normally pass to require(), i.e. `@stoplight/spectral/rulesets/oas/index.js` and `content` is the text data. - * Assets can be loaded using Spectral#registerStaticAssets statc method, i.e. `Spectral.registerStaticAssets(require('@stoplight/spectral/rulesets/assets/assets.json'))`; - * If you execute the code above, ruleset will be loaded fully offline, without a need to make any request. - */ - -const path = require('@stoplight/path'); -const fs = require('fs'); -const { promisify } = require('util'); -const { parse } = require('@stoplight/yaml'); -const { httpAndFileResolver } = require('../dist/resolvers/http-and-file'); - -const readFileAsync = promisify(fs.readFile); -const writeFileAsync = promisify(fs.writeFile); -const readdirAsync = promisify(fs.readdir); -const statAsync = promisify(fs.stat); - -const baseDir = path.join(__dirname, '../rulesets/assets/'); - -if (!fs.existsSync(baseDir)) { - fs.mkdirSync(baseDir); -} - -const target = path.join(baseDir, `assets.json`); -const assets = {}; - -(async () => { - await processDirectory(assets, path.join(__dirname, '../rulesets/oas')); - await writeFileAsync(target, JSON.stringify(assets, null, 2)); -})(); - -async function processDirectory(assets, dir) { - await Promise.all((await readdirAsync(dir)).map(async name => { - if (name === 'schemas') return; - const target = path.join(dir, name); - const stats = await statAsync(target); - if (stats.isDirectory()) { - return processDirectory(assets, target); - } else { - let content = await readFileAsync(target, 'utf8'); - if (path.extname(name) === '.json') { - content = JSON.stringify((await httpAndFileResolver.resolve(JSON.parse(content), { - dereferenceRemote: true, - dereferenceInline: false, - baseUri: target, - parseResolveResult(opts) { - opts.result = parse(opts.result); - return opts; - }, - })).result); - } - - assets[path.join('@stoplight/spectral', path.relative(path.join(__dirname, '..'), target))] = content; - } - })); -} diff --git a/scripts/generate-assets.ts b/scripts/generate-assets.ts new file mode 100644 index 000000000..b68995a76 --- /dev/null +++ b/scripts/generate-assets.ts @@ -0,0 +1,68 @@ +/** + * This script generates a list of assets that are needed to load spectral:oas ruleset. + * It contains all OAS custom functions and *resolved* rulesets. + * The assets are stores in a single filed call assets.json in the following format: + * `: ` + * where the `require-call-path` is the path you'd normally pass to require(), i.e. `@stoplight/spectral/rulesets/oas/index.js` and `content` is the text data. + * Assets can be loaded using Spectral#registerStaticAssets statc method, i.e. `Spectral.registerStaticAssets(require('@stoplight/spectral/rulesets/assets/assets.json'))`; + * If you execute the code above, ruleset will be loaded fully offline, without a need to make any request. + */ + +import { IUriParserResult } from '@stoplight/json-ref-resolver/types'; +import * as path from '@stoplight/path'; +import { parse } from '@stoplight/yaml'; +import * as fs from 'fs'; +import { promisify } from 'util'; +import { httpAndFileResolver } from '../dist/resolvers/http-and-file'; + +const readFileAsync = promisify(fs.readFile); +const writeFileAsync = promisify(fs.writeFile); +const readdirAsync = promisify(fs.readdir); +const statAsync = promisify(fs.stat); + +const baseDir = path.join(__dirname, '../rulesets/assets/'); + +if (!fs.existsSync(baseDir)) { + fs.mkdirSync(baseDir); +} + +const assetsPath = path.join(baseDir, `assets.json`); +const generatedAssets = {}; + +(async () => { + await processDirectory(generatedAssets, path.join(__dirname, '../rulesets/oas')); + await writeFileAsync(assetsPath, JSON.stringify(generatedAssets, null, 2)); +})(); + +async function processDirectory(assets: Record, dir: string) { + await Promise.all( + (await readdirAsync(dir)).map(async (name: string) => { + if (name === 'schemas') return; + const target = path.join(dir, name); + const stats = await statAsync(target); + if (stats.isDirectory()) { + return processDirectory(assets, target); + } else { + let content = await readFileAsync(target, 'utf8'); + if (path.extname(name) === '.json') { + content = JSON.stringify( + ( + await httpAndFileResolver.resolve(JSON.parse(content), { + dereferenceRemote: true, + dereferenceInline: false, + baseUri: target, + parseResolveResult(opts) { + return new Promise(resolve => { + resolve({ result: parse(opts.result) }); + }); + }, + }) + ).result, + ); + } + + assets[path.join('@stoplight/spectral', path.relative(path.join(__dirname, '..'), target))] = content; + } + }), + ); +} diff --git a/scripts/generate-karma-fixtures.js b/scripts/generate-karma-fixtures.js index 9bef59baa..9503fc2a1 100755 --- a/scripts/generate-karma-fixtures.js +++ b/scripts/generate-karma-fixtures.js @@ -8,9 +8,9 @@ if (!fs.existsSync(baseDir)) { fs.mkdirSync(baseDir); } -for (const spec of ['', '2', '3']) { - const target = path.join(baseDir, `oas${spec}-functions.json`); - const fnsPath = path.join(__dirname, `../rulesets/oas${spec}/functions`); +for (const rulesetName of ['oas']) { + const target = path.join(baseDir, `${rulesetName}-functions.json`); + const fnsPath = path.join(__dirname, `../rulesets/${rulesetName}/functions`); const bundledFns = {}; if (fs.existsSync(fnsPath)) { diff --git a/src/__tests__/generate-assets.jest.test.ts b/src/__tests__/generate-assets.jest.test.ts new file mode 100644 index 000000000..f9609b16e --- /dev/null +++ b/src/__tests__/generate-assets.jest.test.ts @@ -0,0 +1,38 @@ +import { existsSync, readFileSync } from 'fs'; + +describe('generate-assets', () => { + let assets: Record; + + beforeAll(() => { + const path = __dirname + '/../../rulesets/assets/assets.json'; + if (!existsSync(path)) { + fail('Missing assets.json file. Please run `yarn generate-assets`.'); + } + + const content = readFileSync(path, { encoding: 'utf8' }); + assets = JSON.parse(content); + expect(assets).not.toBeUndefined(); + expect(Object.keys(assets).length).toBeGreaterThan(1); + }); + + describe('produces properly serialized built-in rulesets', () => { + const testCases = [ + ['oas', 'oas2-schema', 'title', 'A JSON Schema for Swagger 2.0 API.'], + ['oas', 'oas3-schema', 'description', 'Validation schema for OpenAPI Specification 3.0.X.'], + ]; + + it.each(testCases)( + "Ruleset '%s' contains a rule '%s' with an inlined schema bearing a '%s' property", + (ruleset: string, ruleName: string, schemaKey: string, schemaValue: string) => { + const key = `@stoplight/spectral/rulesets/${ruleset}/index.json`; + expect(Object.keys(assets)).toContain(key); + const content = JSON.parse(assets[key]); + const rule = content.rules[ruleName]; + expect(rule).not.toBeUndefined(); + const schema = rule.then.functionOptions.schema; + expect(schema[schemaKey]).not.toBeUndefined(); + expect(schema[schemaKey]).toEqual(schemaValue); + }, + ); + }); +}); diff --git a/src/rulesets/__tests__/offline.schemas.jest.test.ts b/src/rulesets/__tests__/offline.schemas.jest.test.ts index d037f41d9..227c086fe 100644 --- a/src/rulesets/__tests__/offline.schemas.jest.test.ts +++ b/src/rulesets/__tests__/offline.schemas.jest.test.ts @@ -85,7 +85,7 @@ describe('Online vs Offline context', () => { test('all rulesets are accounted for', async () => { const dir = path.join(__dirname, '../../../rulesets/'); - // Would that fail, run `yarn generate-assets` ;-) + // Would that fail, run `yarn build` ;-) expect(fs.existsSync(dir)).toBeTruthy(); const discoveredRulesets: string[] = [];