Skip to content

Commit 7269236

Browse files
committed
feat(cli): unify configuration parameters
now envs are treated as config parameters
1 parent f49662f commit 7269236

10 files changed

Lines changed: 173 additions & 80 deletions

File tree

.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
WOT_SERVIENT_HTTP_PORT=8080

ggg.mjs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/********************************************************************************
2+
* Copyright (c) 2025 Contributors to the Eclipse Foundation
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information regarding copyright ownership.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Eclipse Public License v. 2.0 which is available at
9+
* http://www.eclipse.org/legal/epl-2.0, or the W3C Software Notice and
10+
* Document License (2015-05-13) which is available at
11+
* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document.
12+
*
13+
* SPDX-License-Identifier: EPL-2.0 OR W3C-20150513
14+
********************************************************************************/
15+
//@ts-expect-error
16+
console.log("gg", WoT);
17+
18+
export const gg = 123;

packages/cli/src/cli.ts

Lines changed: 28 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -54,65 +54,25 @@ Run a WoT Servient in the current directory.
5454
program.addHelpText(
5555
"after",
5656
`
57-
wot-servient.conf.json syntax:
57+
Configuration
58+
59+
Settings can be applied through three methods, in order of precedence (highest to lowest):
60+
61+
1. Command-Line Parameters (-p path.to.set=value)
62+
2. Environment Variables (NODE_WOT_PATH_TO_SET=value) (supports .env files too)
63+
3. Configuration File
64+
65+
For the complete list of available configuration fields and their data types, run:
66+
67+
wot-servient schema
68+
69+
In your configuration files you can the following to enable IDE config validation:
70+
5871
{
59-
"servient": {
60-
"clientOnly": CLIENTONLY,
61-
"staticAddress": STATIC,
62-
"scriptAction": RUNSCRIPT
63-
},
64-
"http": {
65-
"port": HPORT,
66-
"address": HADDRESS,
67-
"baseUri": HBASEURI,
68-
"urlRewrite": HURLREWRITE,
69-
"proxy": PROXY,
70-
"allowSelfSigned": ALLOW
71-
},
72-
"mqtt" : {
73-
"broker": BROKER-URL,
74-
"username": BROKER-USERNAME,
75-
"password": BROKER-PASSWORD,
76-
"clientId": BROKER-UNIQUEID,
77-
"protocolVersion": MQTT_VERSION
78-
},
79-
"credentials": {
80-
THING_ID1: {
81-
"token": TOKEN
82-
},
83-
THING_ID2: {
84-
"username": USERNAME,
85-
"password": PASSWORD
86-
}
87-
}
72+
"$schema": "./node_modules/@node-wot/cli/dist/wot-servient-schema.conf.json"
73+
...
8874
}
89-
90-
wot-servient.conf.json fields:
91-
CLIENTONLY : boolean setting if no servers shall be started (default=false)
92-
STATIC : string with hostname or IP literal for static address config
93-
RUNSCRIPT : boolean to activate the 'runScript' Action (default=false)
94-
HPORT : integer defining the HTTP listening port
95-
HADDRESS : string defining HTTP address
96-
HBASEURI : string defining HTTP base URI
97-
HURLREWRITE : map (from URL -> to URL) defining HTTP URL rewrites
98-
PROXY : object with "href" field for the proxy URI,
99-
"scheme" field for either "basic" or "bearer", and
100-
corresponding credential fields as defined below
101-
ALLOW : boolean whether self-signed certificates should be allowed
102-
BROKER-URL : URL to an MQTT broker that publisher and subscribers will use
103-
BROKER-UNIQUEID : unique id set by MQTT client while connecting to the broker
104-
MQTT_VERSION : number indicating the MQTT protocol version to be used (3, 4, or 5)
105-
THING_IDx : string with TD "id" for which credentials should be configured
106-
TOKEN : string for providing a Bearer token
107-
USERNAME : string for providing a Basic Auth username
108-
PASSWORD : string for providing a Basic Auth password
109-
---------------------------------------------------------------------------
110-
111-
Environment variables must be provided in a .env file in the current working directory.
112-
113-
Example:
114-
VAR1=Value1
115-
VAR2=Value2`
75+
`
11676
);
11777

11878
// CLI options declaration
@@ -127,8 +87,10 @@ program
12787
"choose the desired log level. WARNING: if DEBUG env variable is specified this option gets overridden."
12888
).choices(["debug", "info", "warn", "error"])
12989
)
130-
.option("-f, --config-file <file>", "load configuration from specified file", (value, previous) =>
131-
parseConfigFile(value, previous)
90+
.option(
91+
"-f, --config-file <file>",
92+
"load configuration from specified file (default: $(pwd)/wot-servient.conf.json",
93+
(value, previous) => parseConfigFile(value, previous)
13294
)
13395
.option(
13496
"-p, --config-params <param...>",
@@ -144,6 +106,13 @@ program.addArgument(
144106
)
145107
);
146108

109+
program
110+
.command("schema")
111+
.description("prints the json schema for the configuration file")
112+
.action(() => {
113+
console.log(JSON.stringify(ConfigSchema, null, 2));
114+
});
115+
147116
program.action(async function (_, options, cmd) {
148117
// Allow user to personalized the env
149118
if (process.env.DEBUG == null) {

packages/cli/src/configuration.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ import { DotenvParseOutput } from "dotenv";
1919
import _ from "lodash";
2020
import { readFile } from "fs/promises";
2121
import { ValidateFunction, ValidationError } from "ajv";
22+
import { stringToJSValue } from "./utils";
23+
import { createLoggers } from "@node-wot/core";
24+
25+
const { debug } = createLoggers("cli", "cli-default-servient");
2226

2327
type Merge<T, U> = { [K in keyof T as K extends keyof U ? never : K]: T[K] } & {
2428
[L in keyof U & keyof T]: Merge<T[L], U[L]>;
@@ -41,7 +45,6 @@ export type Configuration = FromSchema<typeof schema>;
4145
export const defaultConfiguration = Object.freeze({
4246
servient: {
4347
clientOnly: false,
44-
scriptAction: false,
4548
},
4649
http: {
4750
port: 8080,
@@ -56,6 +59,32 @@ export const defaultConfiguration = Object.freeze({
5659

5760
export type ConfigurationAfterDefaults = Merge<Configuration, Generalize<Mutable<typeof defaultConfiguration>>>;
5861

62+
/**
63+
* Helper function to convert an ENV key to a camelCased path
64+
* using the schema as reference (e.g., SERVIENT_STATICADDRESS -> servient.staticAddress)
65+
*/
66+
function envKeyToConfigPath(envKey: string): string {
67+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
68+
let properties: { [x: string]: any } = schema.properties;
69+
const pathParts = envKey.toLowerCase().replace(/__/g, ".").replace(/_/g, ".").split(".");
70+
let path = "";
71+
for (const part of pathParts) {
72+
const matchedProperty = Object.keys(properties).find((prop) => prop.toLowerCase() === part);
73+
if (matchedProperty != null) {
74+
path += (path.length > 0 ? "." : "") + matchedProperty;
75+
const nextProperty = properties[matchedProperty as keyof typeof properties];
76+
if ("properties" in nextProperty) {
77+
properties = nextProperty.properties;
78+
}
79+
} else {
80+
// If no matching property is found, append the original part we are going to catch
81+
// errors in the validation phase later
82+
path += (path.length > 0 ? "." : "") + part;
83+
}
84+
}
85+
return path;
86+
}
87+
5988
export async function buildConfig(
6089
options: Record<string, unknown>,
6190
configuration: Configuration,
@@ -65,7 +94,10 @@ export async function buildConfig(
6594
let config = configuration;
6695

6796
for (const [key, value] of Object.entries(dotEnvConfigParameters)) {
68-
const obj = _.set({}, key, value);
97+
debug("Applying env variable %s=%s", key, value);
98+
const path = envKeyToConfigPath(key);
99+
debug("Mapped to config path %s", path);
100+
const obj = _.set({}, path, stringToJSValue(value));
69101
config = _.merge(config, obj);
70102
}
71103

packages/cli/src/parsers/config-params-parser.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,18 @@
1515
import { ErrorObject, ValidateFunction } from "ajv";
1616
import { InvalidArgumentError } from "commander";
1717
import _ from "lodash";
18+
import { stringToJSValue } from "../utils";
1819

1920
export function parseConfigParams(param: string, previous: unknown, validator: ValidateFunction<unknown>) {
2021
// Validate key-value pair
2122
if (!/^([a-zA-Z0-9_.]+):=([a-zA-Z0-9_]+)$/.test(param)) {
2223
throw new InvalidArgumentError("Invalid key-value pair");
2324
}
2425
const fieldNamePath = param.split(":=")[0];
25-
const fieldNameValue = param.split(":=")[1];
26-
let fieldNameValueCast;
27-
if (Number(fieldNameValue)) {
28-
fieldNameValueCast = +fieldNameValue;
29-
} else if (fieldNameValue === "true" || fieldNameValue === "false") {
30-
fieldNameValueCast = Boolean(fieldNameValue);
31-
} else {
32-
fieldNameValueCast = fieldNamePath;
33-
}
26+
const fieldNameValue = stringToJSValue(param.split(":=")[1]);
3427

3528
// Build object using dot-notation JSON path
36-
const obj = _.set({}, fieldNamePath, fieldNameValueCast);
29+
const obj = _.set({}, fieldNamePath, fieldNameValue);
3730
if (!validator(obj)) {
3831
throw new InvalidArgumentError(
3932
`Config parameter '${param}' is not valid: ${(validator.errors ?? [])

packages/cli/src/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@
1414
********************************************************************************/
1515
export * from "./load-env-variables";
1616
export * from "./set-log-level";
17+
export * from "./string-to-js-value";

packages/cli/src/utils/load-env-variables.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,20 @@
1515
import * as dotenv from "dotenv";
1616
import ErrnoException = NodeJS.ErrnoException;
1717

18-
export function loadEnvVariables() {
18+
export function loadEnvVariables(prefix: string = "WOT_SERVIENT_"): { [key: string]: string } {
1919
const env: dotenv.DotenvConfigOutput = dotenv.config();
20-
const errorNoException: ErrnoException | undefined = env.error;
20+
const errornoException: ErrnoException | undefined = env.error;
2121
// ignore file not found but throw otherwise
22-
if (errorNoException?.code !== "ENOENT") {
22+
if (errornoException != null && errornoException.code !== "ENOENT") {
2323
throw env.error;
2424
}
25-
return env.parsed ?? {};
25+
26+
// Filter out not node-wot related variables
27+
return Object.keys(process.env)
28+
.filter((key) => key.startsWith(prefix))
29+
.reduce((obj: { [key: string]: string }, key: string) => {
30+
const shortKey = key.substring(prefix.length);
31+
obj[shortKey] = process.env[key] as string;
32+
return obj;
33+
}, {});
2634
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/********************************************************************************
2+
* Copyright (c) 2025 Contributors to the Eclipse Foundation
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information regarding copyright ownership.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Eclipse Public License v. 2.0 which is available at
9+
* http://www.eclipse.org/legal/epl-2.0, or the W3C Software Notice and
10+
* Document License (2015-05-13) which is available at
11+
* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document.
12+
*
13+
* SPDX-License-Identifier: EPL-2.0 OR W3C-20150513
14+
********************************************************************************/
15+
16+
/**
17+
* Converts a string to a Number, Boolean, or return the original string.
18+
* Useful for parsing envs or CLI arguments.
19+
*
20+
* @param value - The string value to convert.
21+
* @returns The converted value as Number, Boolean, or String.
22+
*/
23+
export function stringToJSValue(value: string) {
24+
if (Number(value)) {
25+
return +value;
26+
} else if (value === "true" || value === "false") {
27+
return Boolean(value);
28+
} else {
29+
return value;
30+
}
31+
}

packages/cli/src/wot-servient-schema.conf.json

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,13 @@
88
"type": "object",
99
"properties": {
1010
"clientOnly": {
11+
"description": "setting if no servers shall be started",
1112
"type": "boolean",
1213
"default": false
1314
},
1415
"staticAddress": {
15-
"type": "string"
16-
},
17-
"scriptAction": {
18-
"type": "boolean",
19-
"default": false
16+
"type": "string",
17+
"description": "hostname or IP literal for static address config"
2018
}
2119
},
2220
"additionalProperties": false
@@ -35,10 +33,12 @@
3533
},
3634
"urlRewrite": {
3735
"type": "object",
36+
"description": "map (from URL -> to URL) defining HTTP URL rewrites",
3837
"additionalProperties": { "type": "string" }
3938
},
4039
"proxy": {
4140
"type": "object",
41+
"description": "object with 'href' field for the proxy URI, scheme field for either 'basic' or 'bearer', and corresponding credential fields as defined below",
4242
"required": ["href"],
4343
"properties": {
4444
"href": {
@@ -60,6 +60,7 @@
6060
"additionalProperties": false
6161
},
6262
"allowSelfSigned": {
63+
"description": "whether self-signed certificates should be allowed",
6364
"type": "boolean"
6465
},
6566
"serverKey": {

test.mjs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/********************************************************************************
2+
* Copyright (c) 2025 Contributors to the Eclipse Foundation
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information regarding copyright ownership.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Eclipse Public License v. 2.0 which is available at
9+
* http://www.eclipse.org/legal/epl-2.0, or the W3C Software Notice and
10+
* Document License (2015-05-13) which is available at
11+
* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document.
12+
*
13+
* SPDX-License-Identifier: EPL-2.0 OR W3C-20150513
14+
********************************************************************************/
15+
import { gg } from "./ggg.mjs";
16+
// eslint-disable-next-line import/no-extraneous-dependencies
17+
import Ajv from "ajv";
18+
const ajv = new Ajv();
19+
const schema = {
20+
type: "object",
21+
properties: {
22+
foo: { type: "string" },
23+
bar: { type: "number" },
24+
},
25+
required: ["foo", "bar"],
26+
additionalProperties: false,
27+
};
28+
29+
const validate = ajv.compile(schema);
30+
31+
const validData = { foo: "hello", bar: 42 };
32+
const invalidData = { foo: "hello", bar: "not a number" };
33+
34+
console.log("validData is valid:", validate(validData));
35+
console.log("invalidData is valid:", validate(invalidData));
36+
if (!validate(invalidData)) {
37+
console.log("Validation errors:", validate.errors);
38+
}
39+
console.log("gg", gg);

0 commit comments

Comments
 (0)