From df038235968ee49017fd4f05bd52b9ec5d6eec11 Mon Sep 17 00:00:00 2001 From: Florian Keller Date: Mon, 29 Apr 2019 19:14:09 +0200 Subject: [PATCH] feat: Add version check (#70) --- README.md | 27 +++++++++- package.json | 2 +- schemas/json-schemas.lock | 16 +++--- settings.json | 5 ++ src/SchemaGenerator.ts | 106 +++++++++++++++++++++++++------------- src/cli.ts | 93 +++++++++++++++++++-------------- versions.js | 11 ++++ 7 files changed, 174 insertions(+), 86 deletions(-) create mode 100644 versions.js diff --git a/README.md b/README.md index 9dcb4e09..7645e8dc 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,26 @@ -# schemastore-updater [![Build Status](https://api.travis-ci.org/ffflorian/schemastore-updater.svg?branch=master)](https://travis-ci.org/ffflorian/schemastore-updater/) [![Dependabot Status](https://api.dependabot.com/badges/status?host=github&repo=ffflorian/schemastore-updater)](https://dependabot.com) +# schemastore-updater [![Dependabot Status](https://api.dependabot.com/badges/status?host=github&repo=ffflorian/schemastore-updater)](https://dependabot.com) -A tool to load schema files from [@SchemaStore/schemastore](https://github.com/SchemaStore/schemastore) and publish them as TypeScript definitions [on npm](https://www.npmjs.com/search?q=schemastore). +A tool to load schema files from [@SchemaStore/schemastore](https://github.com/SchemaStore/schemastore) and publish them as TypeScript definitions [on npm](https://www.npmjs.com/search?q=@schemastore/). + +## Usage + +``` +Usage: schemastore-updater [options] [command] + +Options: + -V, --version output the version number + -s, --settings Specify a settings file + -h, --help output usage information + +Commands: + update [options] + version-check +``` + +``` +Usage: update [options] + +Options: + -f, --force Force re-generating all schemas + -h, --help output usage information +``` diff --git a/package.json b/package.json index 24b430aa..20b1036c 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "lint:other": "yarn prettier --list-different", "lint:ts": "tslint --project tsconfig.json", "prettier": "prettier --ignore-path .gitignore \"*.{json,md}\"", - "start": "cross-env NODE_DEBUG='schemastore-updater/*' ts-node src/cli.ts", + "start": "rimraf temp && cross-env NODE_DEBUG='schemastore-updater/*' ts-node src/cli.ts", "test": "exit 0" }, "version": "1.1.0" diff --git a/schemas/json-schemas.lock b/schemas/json-schemas.lock index 314d3f26..88666272 100644 --- a/schemas/json-schemas.lock +++ b/schemas/json-schemas.lock @@ -445,30 +445,30 @@ }, "appsscript.json": { "hash": "379d95dc88cc7f9423397861fae2979d5bb2e7fb3a5381d7b865c3df38c862b8", - "version": "0.0.1" + "version": "1.0.1" }, "bozr.json": { "hash": "1df26248def39755c4bd1665ef5a0fe89861ec7f74c0f890d081801c1c7d0e90", - "version": "0.0.1" + "version": "1.0.1" }, "bukkit-plugin.json": { "hash": "5ef6e55e702d20431df4e88059bc3ba5edeb6902881174417c11fa16e501a3f0", - "version": "0.0.1" + "version": "1.0.1" }, "bungee-plugin.json": { "hash": "782ee73bfded5335de76d2e77cd0a6e8b0477467b3a19cf7ff458e292062c363", - "version": "0.0.1" + "version": "1.0.1" }, "clasp.json": { "hash": "e54d46c347f1a6e2150e8a07806e02feb88d34a634a26a48fa7a481eaa85e111", - "version": "0.0.1" + "version": "1.0.1" }, "gitlab-ci.json": { "hash": "00ffc235209fdac3fc80edeb03c0de02171b3050a2f060226213b762d1e3a83b", - "version": "0.0.1" + "version": "1.0.1" }, "nlu.json": { "hash": "4085ae872de074f028a24e0c57e097a129f55ec4ae09852096b9a001c99480ab", - "version": "0.0.1" + "version": "1.0.2" } -} \ No newline at end of file +} diff --git a/settings.json b/settings.json index edde7204..a0006ab1 100644 --- a/settings.json +++ b/settings.json @@ -6,6 +6,8 @@ "ansible-stable-2.3.json", "ansible-stable-2.4.json", "ansible-stable-2.5.json", + "ansible-stable-2.6.json", + "ansible-stable-2.7.json", "babelrc.json", "bootstraprc.json", "chrome-manifest.json", @@ -18,7 +20,10 @@ "host.json", "htmlhint.json", "jscsrc.json", + "lsdlschema-1.0.json", + "lsdlschema.json", "modernizrrc.json", + "pocketmine-plugin.json", "project-1.0.0-beta4.json", "project-1.0.0-beta8.json", "project-1.0.0-rc1.json", diff --git a/src/SchemaGenerator.ts b/src/SchemaGenerator.ts index a08568cc..5ca09b92 100644 --- a/src/SchemaGenerator.ts +++ b/src/SchemaGenerator.ts @@ -5,25 +5,37 @@ import * as logdown from 'logdown'; import * as path from 'path'; import * as semver from 'semver'; import * as simpleGit from 'simple-git/promise'; -import {BuildResult, SchemaData, SchemaHashes} from './interfaces'; +import {BuildResult, FileSettings, SchemaData, SchemaHashes} from './interfaces'; + +interface SchemaGeneratorOptions extends Partial { + force?: boolean; +} + +const defaultOptions: Required = { + disabledSchemas: [], + force: false, + lockFile: 'schemas/json-schemas.lock', + schemaStoreRepo: 'https://github.com/SchemaStore/schemastore', +}; class SchemaGenerator { private readonly git: simpleGit.SimpleGit; private readonly jsonSchemasDir: string; private readonly logger: logdown.Logger; private readonly schemaStoreDirResolved: string; + private readonly options: Required; + private readonly lockFile: string; - constructor( - private readonly disabledSchemas: string[], - private readonly schemaStoreRepo: string, - private readonly lockFile = 'schemas/json-schemas.lock', - private readonly force = false - ) { + constructor(options?: SchemaGeneratorOptions) { + this.options = { + ...defaultOptions, + ...options, + }; this.git = simpleGit('.'); - this.schemaStoreDirResolved = path.join('temp', 'schemastore'); - this.jsonSchemasDir = path.join(this.schemaStoreDirResolved, 'src', 'schemas', 'json'); - this.lockFile = path.resolve(this.lockFile); - this.logger = logdown('schemastore-updater/index', { + this.schemaStoreDirResolved = path.join('temp/schemastore'); + this.jsonSchemasDir = path.join(this.schemaStoreDirResolved, 'src/schemas/json'); + this.lockFile = path.resolve(this.options.lockFile); + this.logger = logdown('schemastore-updater/SchemaGenerator', { logger: console, markdown: false, }); @@ -36,7 +48,7 @@ class SchemaGenerator { this.logger.info(`Using lockfile "${this.lockFile}".`); - if (this.force) { + if (this.options.force) { this.logger.info(`Force is set. Will re-generate all schemas.`); } } @@ -51,9 +63,7 @@ class SchemaGenerator { } private async generateLockFile(fileName: string, data: SchemaHashes): Promise { - await fs.writeFile(path.resolve(fileName), JSON.stringify(data, null, 2), { - encoding: 'utf8', - }); + await fs.writeJson(path.resolve(fileName), data, {spaces: 2}); } private async generateSchemas(jsonData: SchemaHashes): Promise { @@ -64,6 +74,7 @@ class SchemaGenerator { const schemaName = fileName.replace('.json', ''); const fileNameResolved = path.resolve(this.jsonSchemasDir, fileName); this.logger.info(`Processing "${schemaName}" ...`); + let newSchema = ''; try { @@ -73,19 +84,21 @@ class SchemaGenerator { disabledSchemas.push(fileName); continue; } + const schemaDirResolved = path.resolve('schemas', schemaName); + await fs.ensureDir(schemaDirResolved); - await fs.writeFile(path.resolve(schemaDirResolved, 'index.d.ts'), newSchema, {encoding: 'utf8'}); + await fs.writeFile(path.join(schemaDirResolved, 'index.d.ts'), newSchema, 'utf8'); + const packageJson = this.generatePackageJson(schemaName, jsonData[fileName]); - await fs.writeFile(path.resolve(schemaDirResolved, 'package.json'), packageJson, {encoding: 'utf8'}); - const readMe = this.generateReadme(schemaName, jsonData[fileName].version); - await fs.writeFile(path.resolve(schemaDirResolved, 'README.md'), readMe, { - encoding: 'utf8', - }); + await fs.writeFile(path.join(schemaDirResolved, 'package.json'), packageJson, 'utf8'); + + const readMe = this.generateReadme(schemaName); + await fs.writeFile(path.join(schemaDirResolved, 'README.md'), readMe, 'utf8'); + const license = this.generateLicense(); - await fs.writeFile(path.resolve(schemaDirResolved, 'LICENSE'), license, { - encoding: 'utf8', - }); + await fs.writeFile(path.join(schemaDirResolved, 'LICENSE'), license, 'utf8'); + generatedSchemas.push(schemaName); } @@ -126,7 +139,7 @@ SOFTWARE "dependencies": {}, "description": "TypeScript definitions for ${schemaName}.", "license": "MIT", - "main": "", + "main": "index.d.ts", "name": "@schemastore/${schemaName}", "repository": "https://github.com/ffflorian/schemastore-updater/tree/master/schemas/${schemaName}", "scripts": {}, @@ -138,7 +151,7 @@ SOFTWARE `; } - private generateReadme(schemaName: string, schemaVersion: string) { + private generateReadme(schemaName: string): string { const timeStamp = new Date().toLocaleDateString('en-US', { day: '2-digit', hour: '2-digit', @@ -167,33 +180,51 @@ Files were exported from https://github.com/ffflorian/schemastore-updater/tree/m private async removeAndClone(): Promise { await fs.remove(this.schemaStoreDirResolved); - this.logger.info(`Cloning "${this.schemaStoreRepo}" to "${this.schemaStoreDirResolved}" ...`); - await this.git.clone(this.schemaStoreRepo, this.schemaStoreDirResolved, ['--depth=1']); + this.logger.info(`Cloning "${this.options.schemaStoreRepo}" to "${this.schemaStoreDirResolved}" ...`); + await this.git.clone(this.options.schemaStoreRepo, this.schemaStoreDirResolved, ['--depth=1']); + } + + public async checkVersions(): Promise { + const lockFileData: SchemaHashes = await fs.readJSON(this.lockFile); + const invalidEntries = []; + + for (const entry of Object.keys(lockFileData)) { + const name = entry.replace('.json', ''); + const packageJson = fs.readJsonSync(`./schemas/${name}/package.json`); + const lockFileVersion = lockFileData[entry].version; + if (lockFileVersion !== packageJson.version) { + invalidEntries.push(entry); + } + } + + if (invalidEntries.length) { + throw new Error(`Invalid version entries: "${invalidEntries.join('", "')}".`); + } } - public async start(): Promise { + public async generate(): Promise { const lockFileData: SchemaHashes = await fs.readJSON(this.lockFile); await this.removeAndClone(); const allFiles = await fs.readdir(this.jsonSchemasDir); const jsonFiles = allFiles.filter(fileName => { - const schemaIsDisabled = this.disabledSchemas.includes(fileName); + const schemaIsDisabled = this.options.disabledSchemas.includes(fileName); return fileName.endsWith('.json') && !schemaIsDisabled; }); - this.logger.info(`Loaded ${jsonFiles.length} enabled schemas and ${this.disabledSchemas.length} disabled schemas.`); + this.logger.info( + `Loaded ${jsonFiles.length} enabled schemas and ${this.options.disabledSchemas.length} disabled schemas.` + ); - const fileHashes: {[fileName: string]: string} = {}; + const fileHashes: Record = {}; for (const fileName of jsonFiles) { const fileNameResolved = path.resolve(this.jsonSchemasDir, fileName); const fileIsReadable = await this.fileIsReadable(fileNameResolved); if (fileIsReadable) { - const fileContent = await fs.readFile(fileNameResolved, { - encoding: 'utf8', - }); + const fileContent = await fs.readFile(fileNameResolved, 'utf8'); const sha256 = crypto .createHash('sha256') .update(fileContent) @@ -215,7 +246,10 @@ Files were exported from https://github.com/ffflorian/schemastore-updater/tree/m version: '0.0.1', }; updatedHashes[fileName] = lockFileData[fileName]; - } else if (lockFileData[fileName] && (fileHashes[fileName] !== lockFileData[fileName].hash || this.force)) { + } else if ( + lockFileData[fileName] && + (fileHashes[fileName] !== lockFileData[fileName].hash || this.options.force) + ) { this.logger.info(`Hash from "${fileName}" is outdated. Updating.`); const newVersion = semver.inc(lockFileData[fileName].version, 'patch'); diff --git a/src/cli.ts b/src/cli.ts index 478ae455..2afe96a3 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -4,6 +4,7 @@ import * as program from 'commander'; import * as fs from 'fs-extra'; import * as path from 'path'; import {promisify} from 'util'; + import {SchemaGenerator} from './'; import {FileSettings} from './interfaces'; @@ -21,47 +22,61 @@ program .name(name) .version(version) .description(description) - .option('-s, --settings ', 'Specify a settings file', path.resolve(__dirname, '..', 'settings.json')) - .option('-f, --force', 'Force re-generating all schemas') - .parse(process.argv); + .option('-s, --settings ', 'Specify a settings file', 'settings.json'); -const settingsFile = path.resolve(program.settings); -const {disabledSchemas, schemaStoreRepo, lockFile}: FileSettings = fs.readJSONSync(settingsFile); +const settingsFile = program.settings ? path.resolve(program.settings) : path.join(__dirname, '../settings.json'); -let generator: SchemaGenerator; -try { - generator = new SchemaGenerator(disabledSchemas, schemaStoreRepo, lockFile, program.force); -} catch (error) { - console.error(`Error: ${error.message}`); - process.exit(1); -} +program + .command('update') + .option('-f, --force', 'Force re-generating all schemas', false) + .action(force => { + return update(force).catch(error => { + console.error(`Error: ${error.message}`); + process.exit(1); + }); + }); -generator! - .start() - .then(({disabledSchemas, generatedSchemas}) => { - if (generatedSchemas.length) { - console.log('Generated schemas:', generatedSchemas); - const answer = getYesNo('Would you like to publish the generated schemes on npm?', true); - if (answer !== false) { - generatedSchemas.forEach(async schemaName => { - const {stderr, stdout} = await promisify(exec)(`npm publish ./schemas/${schemaName} --access=public`); - if (stderr) { - console.error(stderr); - } - if (stdout) { - console.log(stdout); - } - }); - } - } else { - console.log('No schemas generated.'); - } +program.command('version-check').action(() => { + return fs + .readJSON(settingsFile) + .then((fileSettings: FileSettings) => { + const generator = new SchemaGenerator({...fileSettings}); + return generator.checkVersions(); + }) + .catch(error => { + console.error(`Error: ${error.message}`); + process.exit(1); + }); +}); + +program.parse(process.argv); - if (disabledSchemas.length) { - console.log(`You should consider disabling these schemas: ${disabledSchemas}`); +async function update(force?: boolean): Promise { + const fileSettings: FileSettings = await fs.readJSON(settingsFile); + + const generator = new SchemaGenerator({...fileSettings, force}); + await generator.checkVersions(); + + const {disabledSchemas: newDisabledSchemas, generatedSchemas} = await generator.generate(); + if (generatedSchemas.length) { + console.log('Generated schemas:', generatedSchemas); + const answer = getYesNo('Would you like to publish the generated schemes on npm?', true); + if (answer !== false) { + generatedSchemas.forEach(async schemaName => { + const {stderr, stdout} = await promisify(exec)(`npm publish ./schemas/${schemaName} --access=public`); + if (stderr) { + console.error(stderr); + } + if (stdout) { + console.log(stdout); + } + }); } - }) - .catch(error => { - console.error(`Error: ${error.message}`); - process.exit(1); - }); + } else { + console.log('No schemas generated.'); + } + + if (newDisabledSchemas.length) { + console.log(`You should consider disabling these schemas: "${newDisabledSchemas.join(', ')}"`); + } +} diff --git a/versions.js b/versions.js new file mode 100644 index 00000000..6190b0cc --- /dev/null +++ b/versions.js @@ -0,0 +1,11 @@ +const fs = require('fs-extra'); +const lockFile = fs.readJsonSync('./schemas/json-schemas.lock'); + +for (const entryFile of Object.keys(lockFile)) { + const name = entryFile.replace('.json', '') + const packageJson = fs.readJsonSync(`./schemas/${name}/package.json`); + const lockFileVersion = lockFile[entryFile].version + if (lockFileVersion !== packageJson.version) { + console.warn(`Invalid version for "${name}"!`); + } +}