From 52efa69bf0aead6bfb3804fa3d4b7b876a2879d4 Mon Sep 17 00:00:00 2001 From: Lars Strojny Date: Thu, 10 Nov 2022 13:06:16 +0100 Subject: [PATCH] Automate code generation for config schema (#16) Use a fork of https://github.com/lstrojny/json-schema-to-zod to generate the boundary check for the config automatically. --- code-generation/config-scheme-gen.js | 29 ++++++++++++++++++++++++++++ code-generation/hap-gen.js | 7 ++++++- package-lock.json | 18 ++++++++--------- package.json | 4 ++-- src/boundaries/config.ts | 20 +++---------------- src/boundaries/index.ts | 6 +----- src/generated/config_boundary.ts | 21 ++++++++++++++++++++ 7 files changed, 71 insertions(+), 34 deletions(-) create mode 100755 code-generation/config-scheme-gen.js create mode 100644 src/generated/config_boundary.ts diff --git a/code-generation/config-scheme-gen.js b/code-generation/config-scheme-gen.js new file mode 100755 index 0000000..8989485 --- /dev/null +++ b/code-generation/config-scheme-gen.js @@ -0,0 +1,29 @@ +#!/usr/bin/env node + +const { parseSchema } = require('json-schema-to-zod-with-defaults') +const { schema } = require('../config.schema.json') +const { format } = require('prettier') +const { join, basename } = require('path') +const prettierConfig = require('../prettier.config') +const { writeFileSync } = require('fs') + +const file = join(__dirname, '../src/generated/config_boundary.ts') + +console.log(`Starting code generation for ${file}`) + +const zodSchema = parseSchema(schema, true) + +const code = format( + ` +// Auto-generated by "${join(basename(__dirname), basename(__filename))}", don’t manually edit + +import { z } from 'zod' + +export const ConfigBoundary = ${zodSchema} +`, + { filepath: 'codegen.ts', ...prettierConfig }, +) + +writeFileSync(file, code) + +console.log(`Finished code generation for ${file}`) diff --git a/code-generation/hap-gen.js b/code-generation/hap-gen.js index ffaab84..c8cccfb 100755 --- a/code-generation/hap-gen.js +++ b/code-generation/hap-gen.js @@ -7,6 +7,9 @@ const { join, basename } = require('path') const uuidToServiceMap = {} const serviceToUuidMap = {} +const file = join(__dirname, '../src/generated/services.ts') + +console.log(`Starting code generation for ${file}`) for (const [name, service] of Object.entries(hap.Service)) { if (typeof service !== 'function' || typeof service.UUID !== 'string') { @@ -28,4 +31,6 @@ export const Services: Record = ${JSON.stringify(serviceToUuidMap { filepath: 'codegen.ts', ...prettierConfig }, ) -writeFileSync(join(__dirname, '../src/generated/services.ts'), code) +writeFileSync(file, code) + +console.log(`Finished code generation for ${file}`) diff --git a/package-lock.json b/package-lock.json index 4c5ff2f..ed9b4fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "homebridge": "^1.3.5", "homebridge-cmdswitch2": "^0.2.10", "jest": "^29.3.0", - "json-schema-to-zod": "^0.2.0", + "json-schema-to-zod-with-defaults": "^0.2.1", "nodemon": "^2.0.13", "prettier": "^2.7.1", "rimraf": "^3.0.2", @@ -5328,10 +5328,10 @@ "node": ">=10" } }, - "node_modules/json-schema-to-zod": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/json-schema-to-zod/-/json-schema-to-zod-0.2.0.tgz", - "integrity": "sha512-UmbLC6VnWBt6dxA6s42Ku2l0vIhJMrYpPxB2QCWUx9gMXtW4TpT5p/BMXUGg7zmhwLElOmlsx9bONlkNvlukbQ==", + "node_modules/json-schema-to-zod-with-defaults": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/json-schema-to-zod-with-defaults/-/json-schema-to-zod-with-defaults-0.2.1.tgz", + "integrity": "sha512-5T4GUM4Koo7COTrOknBwsIgBrhp4FqM5BAhofWewLiBt3Q+ViWy8xNIeB6d3ZGHnEwKGVIbMaRaJyuwpM9x4SA==", "dev": true, "dependencies": { "@types/json-schema": "^7.0.9", @@ -11742,10 +11742,10 @@ "@apidevtools/json-schema-ref-parser": "9.0.9" } }, - "json-schema-to-zod": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/json-schema-to-zod/-/json-schema-to-zod-0.2.0.tgz", - "integrity": "sha512-UmbLC6VnWBt6dxA6s42Ku2l0vIhJMrYpPxB2QCWUx9gMXtW4TpT5p/BMXUGg7zmhwLElOmlsx9bONlkNvlukbQ==", + "json-schema-to-zod-with-defaults": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/json-schema-to-zod-with-defaults/-/json-schema-to-zod-with-defaults-0.2.1.tgz", + "integrity": "sha512-5T4GUM4Koo7COTrOknBwsIgBrhp4FqM5BAhofWewLiBt3Q+ViWy8xNIeB6d3ZGHnEwKGVIbMaRaJyuwpM9x4SA==", "dev": true, "requires": { "@types/json-schema": "^7.0.9", diff --git a/package.json b/package.json index df8217c..89fb1e6 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "test": "ifNotCi() { test \"$CI\" && echo \"$2\" || echo \"$1\"; }; npm run code-generation && `npm bin`/jest `ifNotCi --watchAll`", "link": "npm install --no-save file:///$PWD/", "build": "rimraf ./dist && npm run code-generation && tsc", - "code-generation": "./code-generation/hap-gen.js", + "code-generation": "./code-generation/hap-gen.js && ./code-generation/config-scheme-gen.js", "prepublishOnly": "npm run code-generation && npm run lint && npm run build", "release": "release() { test \"$1\" && test `git rev-parse --abbrev-ref HEAD` == \"develop\" && npm run code-generation && git pull origin develop --rebase && npm version $1 && npm publish && git push origin develop && git push origin --tags; }; release" }, @@ -45,7 +45,7 @@ "homebridge": "^1.3.5", "homebridge-cmdswitch2": "^0.2.10", "jest": "^29.3.0", - "json-schema-to-zod": "^0.2.0", + "json-schema-to-zod-with-defaults": "^0.2.1", "nodemon": "^2.0.13", "prettier": "^2.7.1", "rimraf": "^3.0.2", diff --git a/src/boundaries/config.ts b/src/boundaries/config.ts index bf05398..9f93dca 100644 --- a/src/boundaries/config.ts +++ b/src/boundaries/config.ts @@ -1,19 +1,5 @@ import { z } from 'zod' +import { ConfigBoundary as ConfigBoundaryWithoutPlatform } from '../generated/config_boundary' -export const ConfigBoundary = z.object({ - pin: z.string().regex(new RegExp('^\\d{3}-\\d{2}-\\d{3}$')).describe('Homebridge PIN for service authentication'), - debug: z.boolean().default(false), - prefix: z.string().default('homebridge'), - port: z.number().int().describe('TCP port for the prometheus probe server to listen to').default(36123), - refresh_interval: z.number().int().describe('Discover new services every seconds').default(60), - request_timeout: z - .number() - .int() - .describe('Request timeout when interacting with homebridge instances') - .default(10), - discovery_timeout: z - .number() - .int() - .describe('Discovery timeout after which the current discovery is considered failed') - .default(20), -}) +export const ConfigBoundary = z.intersection(ConfigBoundaryWithoutPlatform, z.object({ platform: z.string() })) +export type Config = z.infer diff --git a/src/boundaries/index.ts b/src/boundaries/index.ts index 298c893..d521932 100644 --- a/src/boundaries/index.ts +++ b/src/boundaries/index.ts @@ -1,7 +1,3 @@ -import z from 'zod' -import { ConfigBoundary as BaseConfigBoundary } from './config' - export * from './checker' export * from './hap' -export const ConfigBoundary = z.intersection(BaseConfigBoundary, z.object({ platform: z.string() })) -export type Config = z.infer +export * from './config' diff --git a/src/generated/config_boundary.ts b/src/generated/config_boundary.ts new file mode 100644 index 0000000..a59f2bf --- /dev/null +++ b/src/generated/config_boundary.ts @@ -0,0 +1,21 @@ +// Auto-generated by "code-generation/config-scheme-gen.js", don’t manually edit + +import { z } from 'zod' + +export const ConfigBoundary = z.object({ + pin: z.string().regex(new RegExp('^\\d{3}-\\d{2}-\\d{3}$')).describe('Homebridge PIN for service authentication'), + debug: z.boolean().default(false), + prefix: z.string().default('homebridge'), + port: z.number().int().describe('TCP port for the prometheus probe server to listen to').default(36123), + refresh_interval: z.number().int().describe('Discover new services every seconds').default(60), + request_timeout: z + .number() + .int() + .describe('Request timeout when interacting with homebridge instances') + .default(10), + discovery_timeout: z + .number() + .int() + .describe('Discovery timeout after which the current discovery is considered failed') + .default(20), +})