Add JSON schemas for DistroMeta and ServerMeta.

JSON schemas are used by editors to validate data and provide useful insights.
The JSON schemas will be generated by the init root command. They can also be generated
using the generate schemas command.

The JSON files will reference the schemas on the user's local disk rather than hosted versions.
This allows offline editing and ensures that the schema is exactly one-to-one with the local
version of Nebula.

Existing servers will have to manually add the schema property. To see how to do this, generate
a new server and copy the $\schema value.
The schema property will need to be added to any existing distrometa files. This is the same
format as the server meta, just replace ServerMeta with DistroMeta.

More information, including sample files with json schemas, is provided
on the README.
This commit is contained in:
Daniel Scalzi
2020-09-13 00:47:18 -04:00
parent 24b0923903
commit 42e47f4748
9 changed files with 207 additions and 21 deletions

View File

@@ -11,6 +11,7 @@ import { VersionSegmentedRegistry } from './util/VersionSegmentedRegistry'
import { VersionUtil } from './util/versionutil'
import { MinecraftVersion } from './util/MinecraftVersion'
import { LoggerUtil } from './util/LoggerUtil'
import { generateSchemas } from './util/SchemaUtil'
dotenv.config()
@@ -109,6 +110,7 @@ const initRootCommand: yargs.CommandModule = {
logger.debug(`Root set to ${argv.root}`)
logger.debug('Invoked init root.')
try {
await generateSchemas(argv.root as string)
await new DistributionStructure(argv.root as string, '').init()
logger.info(`Successfully created new root at ${argv.root}`)
} catch (error) {
@@ -238,6 +240,25 @@ const generateDistroCommand: yargs.CommandModule = {
}
}
const generateSchemasCommand: yargs.CommandModule = {
command: 'schemas',
describe: 'Generate json schemas.',
handler: async (argv) => {
argv.root = getRoot()
logger.debug(`Root set to ${argv.root}`)
logger.debug('Invoked generate schemas.')
try {
await generateSchemas(argv.root as string)
logger.info('Successfully generated schemas')
} catch (error) {
logger.error(`Failed to generate schemas with root ${argv.root}.`, error)
}
}
}
const generateCommand: yargs.CommandModule = {
command: 'generate',
aliases: ['g'],
@@ -246,6 +267,7 @@ const generateCommand: yargs.CommandModule = {
return yargs
.command(generateServerCommand)
.command(generateDistroCommand)
.command(generateSchemasCommand)
},
handler: (argv) => {
argv._handled = true

View File

@@ -2,6 +2,9 @@ import { Distribution } from 'helios-distribution-types'
export interface DistroMeta {
/**
* Distribution metadata to be forwarded to the distribution file.
*/
meta: {
rss: Distribution['rss']
discord?: Distribution['discord']

View File

@@ -2,8 +2,8 @@ import { Server } from 'helios-distribution-types'
export interface UntrackedFilesOption {
/**
* The subdirectory this applies to. Ex.
* [ 'files', 'forgemods' ]
* The subdirectory these patterns will be applied to. Ex.
* [ "files", "forgegemods" ]
*/
appliesTo: string[]
/**
@@ -57,6 +57,9 @@ export function getDefaultServerMeta(id: string, version: string, options?: Serv
export interface ServerMeta {
/**
* Server metadata to be forwarded to the distribution file.
*/
meta: {
version: Server['version']
name: Server['name']
@@ -67,14 +70,30 @@ export interface ServerMeta {
autoconnect: Server['autoconnect']
}
/**
* Properties related to Forge.
*/
forge?: {
/**
* The forge version. This does NOT include the minecraft version.
* Ex. 14.23.5.2854
*/
version: string
}
/**
* Properties related to liteloader.
*/
liteloader?: {
/**
* The liteloader version.
*/
version: string
}
/**
* A list of option objects defining patterns for untracked files.
*/
untrackedFiles?: UntrackedFilesOption[]
}

View File

@@ -1,9 +1,13 @@
import { mkdirs, writeFile, readFile } from 'fs-extra'
import { mkdirs, writeFile, readFile, pathExists } from 'fs-extra'
import { Distribution } from 'helios-distribution-types'
import { SpecModelStructure } from './SpecModelStructure'
import { ServerStructure } from './Server.struct'
import { join, resolve } from 'path'
import { DistroMeta, getDefaultDistroMeta } from '../../model/nebula/distrometa'
import { addSchemaToObject, SchemaTypes } from '../../util/SchemaUtil'
import { LoggerUtil } from '../../util/LoggerUtil'
const logger = LoggerUtil.getLogger('DistributionStructure')
export class DistributionStructure implements SpecModelStructure<Distribution> {
@@ -24,8 +28,18 @@ export class DistributionStructure implements SpecModelStructure<Distribution> {
await mkdirs(this.absoluteRoot)
await mkdirs(this.metaPath)
const distroMeta: DistroMeta = getDefaultDistroMeta()
await writeFile(resolve(this.metaPath, this.DISTRO_META_FILE), JSON.stringify(distroMeta, null, 2))
const distroMetaFile = resolve(this.metaPath, this.DISTRO_META_FILE)
if(await pathExists(distroMetaFile)) {
logger.warn(`Distro Meta file already exists at ${distroMetaFile}!`)
logger.warn('If you wish to regenerate this file, you must delete the existing one!')
} else {
const distroMeta: DistroMeta = addSchemaToObject(
getDefaultDistroMeta(),
SchemaTypes.DistroMetaSchema,
this.absoluteRoot
)
await writeFile(distroMetaFile, JSON.stringify(distroMeta, null, 2))
}
await this.serverStruct.init()
}

View File

@@ -9,6 +9,7 @@ import { MiscFileStructure } from './module/File.struct'
import { LiteModStructure } from './module/LiteMod.struct'
import { LibraryStructure } from './module/Library.struct'
import { MinecraftVersion } from '../../util/MinecraftVersion'
import { addSchemaToObject, SchemaTypes } from '../../util/SchemaUtil'
export class ServerStructure extends BaseModelStructure<Server> {
@@ -73,7 +74,11 @@ export class ServerStructure extends BaseModelStructure<Server> {
serverMetaOpts.liteloaderVersion = options.liteloaderVersion
}
const serverMeta: ServerMeta = getDefaultServerMeta(id, minecraftVersion.toString(), serverMetaOpts)
const serverMeta: ServerMeta = addSchemaToObject(
getDefaultServerMeta(id, minecraftVersion.toString(), serverMetaOpts),
SchemaTypes.ServerMetaSchema,
this.absoluteRoot
)
await writeFile(resolvePath(absoluteServerRoot, this.SERVER_META_FILE), JSON.stringify(serverMeta, null, 2))
const libS = new LibraryStructure(absoluteServerRoot, relativeServerRoot, this.baseUrl, minecraftVersion, [])

73
src/util/SchemaUtil.ts Normal file
View File

@@ -0,0 +1,73 @@
import { mkdirs, pathExists, remove, writeFile } from 'fs-extra'
import { join, resolve } from 'path'
import { createGenerator } from 'ts-json-schema-generator'
import { URL } from 'url'
import { DistroMeta } from '../model/nebula/distrometa'
import { ServerMeta } from '../model/nebula/servermeta'
import { LoggerUtil } from './LoggerUtil'
const logger = LoggerUtil.getLogger('SchemaUtil')
interface SchemaType {
/**
* URL to the JSON schema for this type of file.
* This is used by editors to validate and annotate the data.
*/
$schema?: string
}
export type DistroMetaSchema = DistroMeta & SchemaType
export type ServerMetaSchema = ServerMeta & SchemaType
export enum SchemaTypes {
DistroMetaSchema = 'DistroMetaSchema',
ServerMetaSchema = 'ServerMetaSchema'
}
function getSchemaFileName(typeName: string) {
return `${typeName}.schema.json`
}
function getSchemaDirectory(absoluteRoot: string): string {
return resolve(absoluteRoot, 'schemas')
}
function getSchemaLocation(typeName: string, absoluteRoot: string): string {
return resolve(getSchemaDirectory(absoluteRoot), getSchemaFileName(typeName))
}
export function addSchemaToObject<T>(obj: T, typeName: string, absoluteRoot: string): T {
return {
$schema: new URL(`file:${getSchemaLocation(typeName, absoluteRoot)}`).href,
...obj
}
}
export async function generateSchemas(absoluteRoot: string): Promise<void> {
const selfPath = __filename.replace('dist', 'src').replace('.js', '.ts')
const schemaDir = getSchemaDirectory(absoluteRoot)
if(await pathExists(schemaDir)) {
await remove(schemaDir)
}
await mkdirs(schemaDir)
for(const typeName of Object.values(SchemaTypes)) {
logger.info(`Generating schema for ${typeName}`)
const schema = createGenerator({
tsconfig: join(__dirname, '..', '..', 'tsconfig.json'),
path: selfPath,
type: typeName
}).createSchema(typeName)
const schemaString = JSON.stringify(schema)
const schemaLoc = getSchemaLocation(typeName, absoluteRoot)
await writeFile(schemaLoc, schemaString)
logger.info(`Schema for ${typeName} saved to ${schemaLoc}`)
}
}