Create basic scaffolding of working CLI interface
This commit is contained in:
@@ -12,10 +12,9 @@ export abstract class Adapter<C = unknown> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#validateRequiredFields(config: Partial<C>): C {
|
#validateRequiredFields(config: Partial<C>): C {
|
||||||
for(const field of this.requiredFields) {
|
const missingField = this.requiredFields.find(field => !config.hasOwnProperty(field));
|
||||||
if (!config[field]) {
|
if (missingField) {
|
||||||
throw new Error(`Required config field '${field.toString()}' is missing in ${this.name} profile`);
|
throw new Error(`The required '${missingField.toString()}' is missing in '${this.name}' source config`);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return config as C;
|
return config as C;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { program } from "commander";
|
import { program } from "commander";
|
||||||
|
|
||||||
import { CLIOptions } from "@types";
|
import { CLIOptions, ImportOptions } from "@types";
|
||||||
import { parseConfig } from "../config";
|
import { parseConfig } from "../config";
|
||||||
import { importThings } from "../importer";
|
import { importThings } from "../importer";
|
||||||
|
|
||||||
@@ -10,6 +10,9 @@ const getOptions = () => program
|
|||||||
.requiredOption("-c, --config <file>", "sets the path to the YAML file with configuration")
|
.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("-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("-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()
|
.parse()
|
||||||
.opts<CLIOptions>();
|
.opts<CLIOptions>();
|
||||||
|
|
||||||
@@ -36,6 +39,9 @@ export const run = async () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const things = await importThings(config, options.sources ?? []);
|
const things = await importThings(config, options);
|
||||||
console.log(things);
|
|
||||||
|
if(options.json) {
|
||||||
|
console.log(JSON.stringify(things, undefined, 3));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -2,4 +2,13 @@ import { Config } from '@types';
|
|||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
import { parse } from 'yaml';
|
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;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
import axios from "axios";
|
import { Config, ImportOptions } from "@types";
|
||||||
import { Thing, Config } from "@types";
|
|
||||||
import { adapters } from "../adapters";
|
import { adapters } from "../adapters";
|
||||||
|
import { DummyOpenHAB, OpenHAB } from "../openhab";
|
||||||
|
|
||||||
|
|
||||||
export const importThings = async (config: Config, sources: string[]) => {
|
export const importThings = async (config: Config, options: ImportOptions) => {
|
||||||
const openhab = axios.create({
|
const OH = options.dryRun ? DummyOpenHAB : OpenHAB;
|
||||||
baseURL: config.baseURL,
|
const openhab = new OH(config.openHAB);
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${config.token}`
|
const sources = options.sources ?? Object.keys(config.sources);
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const things = (await Promise.all(sources.map(source => {
|
const things = (await Promise.all(sources.map(source => {
|
||||||
const { type, config: cfg } = config.sources[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();
|
return new constructor(source, cfg).loadThings();
|
||||||
}))).flat();
|
}))).flat();
|
||||||
|
|
||||||
if (config.override) {
|
if (options.force) {
|
||||||
// things.forEach(t => openhab.delete(`/things/${t.UID}`));
|
await openhab.deleteThings(...things.map(t => t.UID));
|
||||||
}
|
}
|
||||||
|
|
||||||
// const getThingsResponse = await openhab.get<Thing[]>('/things');
|
const existingThings = await openhab.getThings();
|
||||||
// const existingThingsUIDs = getThingsResponse.data.map(t => t.UID);
|
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 thingsToImport;
|
|
||||||
return things;
|
|
||||||
}
|
}
|
||||||
42
src/openhab/index.ts
Normal file
42
src/openhab/index.ts
Normal 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> {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
export type CLIOptions = {
|
import { ImportOptions } from "./importer";
|
||||||
|
|
||||||
|
export type CLIOptions = ImportOptions & {
|
||||||
config: string;
|
config: string;
|
||||||
set: string[];
|
set: string[];
|
||||||
sources?: string[];
|
json: boolean;
|
||||||
};
|
};
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
export type Config = {
|
import { OpenHABConfig } from "./openhab";
|
||||||
baseURL: string;
|
|
||||||
token: string;
|
export type Config = {
|
||||||
override?: boolean;
|
override?: boolean;
|
||||||
|
|
||||||
|
openHAB: OpenHABConfig;
|
||||||
|
|
||||||
sources: Record<string, SourceConfig>;
|
sources: Record<string, SourceConfig>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
5
src/types/importer.ts
Normal file
5
src/types/importer.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export type ImportOptions = {
|
||||||
|
force: boolean;
|
||||||
|
dryRun: boolean;
|
||||||
|
sources?: string[];
|
||||||
|
};
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
export * from "./openhab";
|
export * from "./openhab";
|
||||||
export * from "./z2m";
|
export * from "./z2m";
|
||||||
export * from "./config";
|
export * from "./config";
|
||||||
export * from "./cli";
|
export * from "./cli";
|
||||||
|
export * from "./importer";
|
||||||
@@ -1,3 +1,8 @@
|
|||||||
|
export type OpenHABConfig = {
|
||||||
|
baseURL: string;
|
||||||
|
token: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type Thing = {
|
export type Thing = {
|
||||||
label: string;
|
label: string;
|
||||||
bridgeUID: string;
|
bridgeUID: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user