Create basic scaffolding of working CLI interface

This commit is contained in:
2024-11-15 18:27:46 +01:00
parent 5fc9e9cbf2
commit 333bbdd26b
10 changed files with 98 additions and 33 deletions

View File

@@ -12,10 +12,9 @@ export abstract class Adapter<C = unknown> {
}
#validateRequiredFields(config: Partial<C>): C {
for(const field of this.requiredFields) {
if (!config[field]) {
throw new Error(`Required config field '${field.toString()}' is missing in ${this.name} profile`);
}
const missingField = this.requiredFields.find(field => !config.hasOwnProperty(field));
if (missingField) {
throw new Error(`The required '${missingField.toString()}' is missing in '${this.name}' source config`);
}
return config as C;

View File

@@ -1,6 +1,6 @@
import { program } from "commander";
import { CLIOptions } from "@types";
import { CLIOptions, ImportOptions } from "@types";
import { parseConfig } from "../config";
import { importThings } from "../importer";
@@ -10,6 +10,9 @@ const getOptions = () => program
.requiredOption("-c, --config <file>", "sets the path to the YAML file with configuration")
.option("-x, --set <arg>", "overrides the config option for this specific run (arg: <key>=<name>, i.e. mqtt.brokerURL=mqtt://localhost:1883", (v: string, prev: string[]) => prev.concat([v]), [])
.option("-s, --sources [source...]", "forces the given sources to be queried for new things")
.option("-f, --force", "overrides the existing things with new schemas", false)
.option("-n, --dry-run", "disables all mutations against OpenHAB (data creating, updating or deletion), only data retrieval will be allowed", false)
.option("-j, --json", "prints things to be imported as a JSON", false)
.parse()
.opts<CLIOptions>();
@@ -36,6 +39,9 @@ export const run = async () => {
});
}
const things = await importThings(config, options.sources ?? []);
console.log(things);
const things = await importThings(config, options);
if(options.json) {
console.log(JSON.stringify(things, undefined, 3));
}
}

View File

@@ -2,4 +2,13 @@ import { Config } from '@types';
import { readFileSync } from 'fs';
import { parse } from 'yaml';
export const parseConfig = (filePath: string) => parse(readFileSync(filePath, 'utf8')) as Config;
export const parseConfig = (filePath: string) => {
const config = parse(readFileSync(filePath, 'utf8')) as Partial<Config>;
const missingField = ['openHAB', 'sources'].find(field => !config.hasOwnProperty(field));
if (missingField) {
throw new Error(`The required '${missingField}' is missing in global config`);
}
return config as Config;
};

View File

@@ -1,15 +1,13 @@
import axios from "axios";
import { Thing, Config } from "@types";
import { Config, ImportOptions } from "@types";
import { adapters } from "../adapters";
import { DummyOpenHAB, OpenHAB } from "../openhab";
export const importThings = async (config: Config, sources: string[]) => {
const openhab = axios.create({
baseURL: config.baseURL,
headers: {
Authorization: `Bearer ${config.token}`
}
});
export const importThings = async (config: Config, options: ImportOptions) => {
const OH = options.dryRun ? DummyOpenHAB : OpenHAB;
const openhab = new OH(config.openHAB);
const sources = options.sources ?? Object.keys(config.sources);
const things = (await Promise.all(sources.map(source => {
const { type, config: cfg } = config.sources[source];
@@ -23,17 +21,13 @@ export const importThings = async (config: Config, sources: string[]) => {
return new constructor(source, cfg).loadThings();
}))).flat();
if (config.override) {
// things.forEach(t => openhab.delete(`/things/${t.UID}`));
if (options.force) {
await openhab.deleteThings(...things.map(t => t.UID));
}
// const getThingsResponse = await openhab.get<Thing[]>('/things');
// const existingThingsUIDs = getThingsResponse.data.map(t => t.UID);
const existingThings = await openhab.getThings();
const existingThingsUIDs = existingThings.map(t => t.UID);
const thingsToImport = things.filter(t => !existingThingsUIDs.includes(t.UID));
// const thingsToImport = things.filter(t => !existingThingsUIDs.includes(t.UID));
// openhab.post('/things', thingsToImport);
// return thingsToImport;
return things;
return thingsToImport;
}

42
src/openhab/index.ts Normal file
View File

@@ -0,0 +1,42 @@
import { OpenHABConfig, Thing } from "@types";
import axios, { AxiosInstance } from "axios";
export class OpenHAB {
#api: AxiosInstance;
constructor({ baseURL, token }: Partial<OpenHABConfig>) {
if (!baseURL || !token) {
throw new Error(`Both 'baseURL' and 'token' properties are required on OpenHAB configuration.`);
}
this.#api = axios.create({
baseURL: `${baseURL}/rest`,
headers: {
Authorization: `Bearer ${token}`
}
});
}
async getThings(): Promise<Thing[]> {
const response = await this.#api.get<Thing[]>('/things');
return response.data;
}
async deleteThings(...UIDs: string[]): Promise<void> {
await Promise.all(UIDs.map(uid => this.#api.delete(`/things/${uid}`)));
}
async createThings(...things: Thing[]): Promise<Thing[]> {
const response = await this.#api.post<Thing[]>('/things');
return response.data;
}
}
export class DummyOpenHAB extends OpenHAB {
override async createThings(...things: Thing[]): Promise<Thing[]> {
return [];
}
override async deleteThings(...UIDs: string[]): Promise<void> {
}
}

View File

@@ -1,5 +1,7 @@
export type CLIOptions = {
import { ImportOptions } from "./importer";
export type CLIOptions = ImportOptions & {
config: string;
set: string[];
sources?: string[];
json: boolean;
};

View File

@@ -1,8 +1,10 @@
import { OpenHABConfig } from "./openhab";
export type Config = {
baseURL: string;
token: string;
override?: boolean;
openHAB: OpenHABConfig;
sources: Record<string, SourceConfig>;
};

5
src/types/importer.ts Normal file
View File

@@ -0,0 +1,5 @@
export type ImportOptions = {
force: boolean;
dryRun: boolean;
sources?: string[];
};

View File

@@ -2,3 +2,4 @@ export * from "./openhab";
export * from "./z2m";
export * from "./config";
export * from "./cli";
export * from "./importer";

View File

@@ -1,3 +1,8 @@
export type OpenHABConfig = {
baseURL: string;
token: string;
};
export type Thing = {
label: string;
bridgeUID: string;