@ajclarkson/homerun 0.0.1-edge.90fa3ca → 0.0.1-edge.a233fa3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/framework/action-runtime.d.ts +0 -2
- package/dist/src/framework/action-runtime.js +0 -9
- package/dist/src/framework/api-server.d.ts +0 -6
- package/dist/src/framework/api-server.js +0 -17
- package/dist/src/framework/config.d.ts +0 -3
- package/dist/src/framework/config.js +0 -3
- package/dist/src/framework/pipeline.d.ts +0 -2
- package/dist/src/framework/pipeline.js +0 -4
- package/dist/src/framework/trigger-engine.d.ts +1 -3
- package/dist/src/framework/trigger-engine.js +1 -4
- package/dist/src/index.js +2 -7
- package/package.json +3 -7
- package/dist/scripts/generate-ha-services.d.ts +0 -16
- package/dist/scripts/generate-ha-services.js +0 -111
- package/dist/src/framework/metrics-prom.d.ts +0 -16
- package/dist/src/framework/metrics-prom.js +0 -82
- package/dist/src/framework/metrics.d.ts +0 -8
- package/dist/src/framework/metrics.js +0 -4
|
@@ -3,7 +3,6 @@ import type { Action } from '../types/actions.js';
|
|
|
3
3
|
import type { HAClient } from './ha-client.js';
|
|
4
4
|
import type { TimerManager } from './timer-manager.js';
|
|
5
5
|
import type { EventPublisher } from './event-publisher.js';
|
|
6
|
-
import type { MetricsBackend } from './metrics.js';
|
|
7
6
|
export interface ExecutionContext {
|
|
8
7
|
correlationId: string;
|
|
9
8
|
automationId: string;
|
|
@@ -16,7 +15,6 @@ interface Deps {
|
|
|
16
15
|
timerManager: TimerManager;
|
|
17
16
|
eventPublisher: EventPublisher;
|
|
18
17
|
dryRun: boolean;
|
|
19
|
-
metrics?: MetricsBackend;
|
|
20
18
|
}
|
|
21
19
|
export declare class ActionRuntime {
|
|
22
20
|
private readonly deps;
|
|
@@ -9,23 +9,14 @@ export class ActionRuntime {
|
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
11
|
async runAction(action, ctx) {
|
|
12
|
-
const labels = { location: ctx.location, action_type: action.type };
|
|
13
12
|
this.deps.eventPublisher.publishActionEvent(this.makeEvent(ctx, 'action_started', action));
|
|
14
|
-
this.deps.metrics?.incrementCounter('homerun_actions_dispatched_total', labels);
|
|
15
|
-
const start = performance.now();
|
|
16
13
|
try {
|
|
17
14
|
if (!this.deps.dryRun) {
|
|
18
15
|
await this.dispatch(action);
|
|
19
16
|
}
|
|
20
|
-
const duration = (performance.now() - start) / 1000;
|
|
21
|
-
this.deps.metrics?.observeHistogram('homerun_action_duration_seconds', duration, labels);
|
|
22
|
-
this.deps.metrics?.incrementCounter('homerun_actions_succeeded_total', labels);
|
|
23
17
|
this.deps.eventPublisher.publishActionEvent(this.makeEvent(ctx, 'action_result', action, { reason: 'ok' }));
|
|
24
18
|
}
|
|
25
19
|
catch (err) {
|
|
26
|
-
const duration = (performance.now() - start) / 1000;
|
|
27
|
-
this.deps.metrics?.observeHistogram('homerun_action_duration_seconds', duration, labels);
|
|
28
|
-
this.deps.metrics?.incrementCounter('homerun_actions_failed_total', labels);
|
|
29
20
|
const reason = err instanceof Error ? err.message : String(err);
|
|
30
21
|
this.deps.eventPublisher.publishActionEvent(this.makeEvent(ctx, 'action_result', action, { reason }));
|
|
31
22
|
}
|
|
@@ -2,10 +2,6 @@ import type { AutomationRegistry } from './registry.js';
|
|
|
2
2
|
import type { EventPublisher } from './event-publisher.js';
|
|
3
3
|
import type { Automation } from '../types/automation.js';
|
|
4
4
|
import type { TriggerEvent } from '../types/triggers.js';
|
|
5
|
-
export interface MetricsProvider {
|
|
6
|
-
getMetrics(): Promise<string>;
|
|
7
|
-
contentType: string;
|
|
8
|
-
}
|
|
9
5
|
export interface ApiServerDeps {
|
|
10
6
|
registry: AutomationRegistry;
|
|
11
7
|
onTrigger: (automation: Automation<unknown>, event: TriggerEvent) => void;
|
|
@@ -14,7 +10,6 @@ export interface ApiServerDeps {
|
|
|
14
10
|
entityCount: () => number;
|
|
15
11
|
eventPublisher: EventPublisher;
|
|
16
12
|
dryRun?: boolean;
|
|
17
|
-
metrics?: MetricsProvider;
|
|
18
13
|
}
|
|
19
14
|
export declare class ApiServer {
|
|
20
15
|
private readonly deps;
|
|
@@ -30,6 +25,5 @@ export declare class ApiServer {
|
|
|
30
25
|
private postReload;
|
|
31
26
|
private getHealthLive;
|
|
32
27
|
private getHealthReady;
|
|
33
|
-
private getMetrics;
|
|
34
28
|
private getEvents;
|
|
35
29
|
}
|
|
@@ -41,8 +41,6 @@ export class ApiServer {
|
|
|
41
41
|
return this.getHealthReady(res);
|
|
42
42
|
if (method === 'GET' && url === '/events')
|
|
43
43
|
return this.getEvents(req, res);
|
|
44
|
-
if (method === 'GET' && url === '/metrics')
|
|
45
|
-
return this.getMetrics(res);
|
|
46
44
|
const triggerMatch = method === 'POST' && url.match(/^\/automations\/(.+)\/trigger$/);
|
|
47
45
|
if (triggerMatch)
|
|
48
46
|
return this.postTrigger(triggerMatch[1], res);
|
|
@@ -90,21 +88,6 @@ export class ApiServer {
|
|
|
90
88
|
...(this.deps.dryRun && { dry_run: true }),
|
|
91
89
|
});
|
|
92
90
|
}
|
|
93
|
-
getMetrics(res) {
|
|
94
|
-
if (!this.deps.metrics) {
|
|
95
|
-
json(res, 404, { error: 'metrics not enabled' });
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
this.deps.metrics.getMetrics()
|
|
99
|
-
.then((output) => {
|
|
100
|
-
res.writeHead(200, { 'Content-Type': this.deps.metrics.contentType });
|
|
101
|
-
res.end(output);
|
|
102
|
-
})
|
|
103
|
-
.catch((err) => {
|
|
104
|
-
console.error('[ApiServer] metrics scrape failed:', err);
|
|
105
|
-
json(res, 500, { error: 'metrics scrape failed' });
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
91
|
getEvents(req, res) {
|
|
109
92
|
res.writeHead(200, {
|
|
110
93
|
'Content-Type': 'text/event-stream',
|
|
@@ -17,9 +17,6 @@ declare const ConfigSchema: z.ZodObject<{
|
|
|
17
17
|
options: z.ZodDefault<z.ZodObject<{
|
|
18
18
|
dry_run: z.ZodDefault<z.ZodBoolean>;
|
|
19
19
|
}, z.core.$strip>>;
|
|
20
|
-
metrics: z.ZodDefault<z.ZodObject<{
|
|
21
|
-
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
22
|
-
}, z.core.$strip>>;
|
|
23
20
|
}, z.core.$strip>;
|
|
24
21
|
export type HomerunConfig = z.infer<typeof ConfigSchema>;
|
|
25
22
|
export declare function parseConfig(configContent: string, secretsContent?: string): HomerunConfig;
|
|
@@ -29,9 +29,6 @@ const ConfigSchema = z.object({
|
|
|
29
29
|
options: z.object({
|
|
30
30
|
dry_run: z.boolean().default(false),
|
|
31
31
|
}).default({ dry_run: false }),
|
|
32
|
-
metrics: z.object({
|
|
33
|
-
enabled: z.boolean().default(false),
|
|
34
|
-
}).default({ enabled: false }),
|
|
35
32
|
});
|
|
36
33
|
function resolveSecrets(value, secrets) {
|
|
37
34
|
if (isSecretRef(value)) {
|
|
@@ -3,12 +3,10 @@ import type { TriggerEvent } from '../types/triggers.js';
|
|
|
3
3
|
import type { HAClient } from './ha-client.js';
|
|
4
4
|
import type { EventPublisher } from './event-publisher.js';
|
|
5
5
|
import type { ActionRuntime } from './action-runtime.js';
|
|
6
|
-
import type { MetricsBackend } from './metrics.js';
|
|
7
6
|
interface Deps {
|
|
8
7
|
eventPublisher: EventPublisher;
|
|
9
8
|
actionRuntime: ActionRuntime;
|
|
10
9
|
dryRun?: boolean;
|
|
11
|
-
metrics?: MetricsBackend;
|
|
12
10
|
}
|
|
13
11
|
export declare function runPipeline(automation: Automation<unknown>, event: TriggerEvent, haClient: HAClient, deps: Deps): Promise<void>;
|
|
14
12
|
export {};
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import { isAbort } from '../types/automation.js';
|
|
2
2
|
export async function runPipeline(automation, event, haClient, deps) {
|
|
3
|
-
deps.metrics?.incrementCounter('homerun_pipeline_runs_total', {
|
|
4
|
-
location: automation.location,
|
|
5
|
-
trigger_type: event.type,
|
|
6
|
-
});
|
|
7
3
|
const correlationId = event.correlation_id;
|
|
8
4
|
const timestamp = new Date().toISOString();
|
|
9
5
|
const base = {
|
|
@@ -3,7 +3,6 @@ import type { Automation } from '../types/automation.js';
|
|
|
3
3
|
import type { TriggerEvent } from '../types/triggers.js';
|
|
4
4
|
import type { HAClient } from './ha-client.js';
|
|
5
5
|
import type { AutomationRegistry } from './registry.js';
|
|
6
|
-
import type { MetricsBackend } from './metrics.js';
|
|
7
6
|
export declare function parseButtonAction(state: string): {
|
|
8
7
|
button?: string;
|
|
9
8
|
pressType: 'short' | 'hold';
|
|
@@ -13,11 +12,10 @@ export declare class TriggerEngine {
|
|
|
13
12
|
private readonly haClient;
|
|
14
13
|
private readonly onMatch;
|
|
15
14
|
private readonly mqttClient?;
|
|
16
|
-
private readonly metrics?;
|
|
17
15
|
private readonly buttonHandlers;
|
|
18
16
|
private regexButtonTriggers;
|
|
19
17
|
private readonly durationTimers;
|
|
20
|
-
constructor(registry: AutomationRegistry, haClient: HAClient, onMatch: (automation: Automation<unknown>, event: TriggerEvent) => void, mqttClient?: MqttClient | undefined
|
|
18
|
+
constructor(registry: AutomationRegistry, haClient: HAClient, onMatch: (automation: Automation<unknown>, event: TriggerEvent) => void, mqttClient?: MqttClient | undefined);
|
|
21
19
|
private rebuildButtonHandlers;
|
|
22
20
|
start(): void;
|
|
23
21
|
dispatch(event: TriggerEvent): void;
|
|
@@ -91,16 +91,14 @@ export class TriggerEngine {
|
|
|
91
91
|
haClient;
|
|
92
92
|
onMatch;
|
|
93
93
|
mqttClient;
|
|
94
|
-
metrics;
|
|
95
94
|
buttonHandlers = new Map();
|
|
96
95
|
regexButtonTriggers = [];
|
|
97
96
|
durationTimers = new Map();
|
|
98
|
-
constructor(registry, haClient, onMatch, mqttClient
|
|
97
|
+
constructor(registry, haClient, onMatch, mqttClient) {
|
|
99
98
|
this.registry = registry;
|
|
100
99
|
this.haClient = haClient;
|
|
101
100
|
this.onMatch = onMatch;
|
|
102
101
|
this.mqttClient = mqttClient;
|
|
103
|
-
this.metrics = metrics;
|
|
104
102
|
this.rebuildButtonHandlers();
|
|
105
103
|
registry.onChange(() => this.rebuildButtonHandlers());
|
|
106
104
|
}
|
|
@@ -136,7 +134,6 @@ export class TriggerEngine {
|
|
|
136
134
|
this.haClient.ready
|
|
137
135
|
.then(() => {
|
|
138
136
|
this.haClient.on('state_changed', (event) => {
|
|
139
|
-
this.metrics?.incrementCounter('homerun_ha_events_received_total', { event_type: 'state_changed' });
|
|
140
137
|
let handler = this.buttonHandlers.get(event.entity_id);
|
|
141
138
|
if (!handler) {
|
|
142
139
|
const matchingGestures = new Set();
|
package/dist/src/index.js
CHANGED
|
@@ -11,8 +11,6 @@ import { Scheduler } from './framework/scheduler.js';
|
|
|
11
11
|
import { rescanAutomations, startHotReload } from './framework/hot-reload.js';
|
|
12
12
|
import { runPipeline } from './framework/pipeline.js';
|
|
13
13
|
import { ApiServer } from './framework/api-server.js';
|
|
14
|
-
import { PromMetricsBackend } from './framework/metrics-prom.js';
|
|
15
|
-
import { NoopMetricsBackend } from './framework/metrics.js';
|
|
16
14
|
process.on('uncaughtException', (err) => {
|
|
17
15
|
console.error('[homerun] uncaughtException:', err);
|
|
18
16
|
});
|
|
@@ -22,7 +20,6 @@ process.on('unhandledRejection', (reason) => {
|
|
|
22
20
|
// 0. Load and validate configuration before anything else.
|
|
23
21
|
const config = await loadConfig();
|
|
24
22
|
const { dry_run: dryRun } = config.options;
|
|
25
|
-
const metricsBackend = config.metrics.enabled ? new PromMetricsBackend(true) : new NoopMetricsBackend();
|
|
26
23
|
const lwtTopic = dryRun ? 'homerun/dev/status' : 'homerun/status';
|
|
27
24
|
const lwtPayload = JSON.stringify({ status: 'offline', timestamp: new Date().toISOString() });
|
|
28
25
|
// 1. Connect MQTT before anything else (EventPublisher and ActionRuntime need it).
|
|
@@ -47,7 +44,6 @@ const actionRuntime = new ActionRuntime({
|
|
|
47
44
|
timerManager,
|
|
48
45
|
eventPublisher,
|
|
49
46
|
dryRun,
|
|
50
|
-
metrics: metricsBackend,
|
|
51
47
|
});
|
|
52
48
|
// 3. Initial automation load — must complete before the engine and scheduler start.
|
|
53
49
|
const automationsDir = path.resolve(config.automations.dir);
|
|
@@ -62,7 +58,7 @@ function dispatchPipeline(automation, event) {
|
|
|
62
58
|
if (shuttingDown)
|
|
63
59
|
return;
|
|
64
60
|
inFlight++;
|
|
65
|
-
runPipeline(automation, event, haClient, { eventPublisher, actionRuntime, dryRun
|
|
61
|
+
runPipeline(automation, event, haClient, { eventPublisher, actionRuntime, dryRun })
|
|
66
62
|
.catch((err) => { console.error('[homerun] pipeline error:', err); })
|
|
67
63
|
.finally(() => {
|
|
68
64
|
inFlight--;
|
|
@@ -72,7 +68,7 @@ function dispatchPipeline(automation, event) {
|
|
|
72
68
|
}
|
|
73
69
|
});
|
|
74
70
|
}
|
|
75
|
-
engine = new TriggerEngine(registry, haClient, dispatchPipeline, mqtt
|
|
71
|
+
engine = new TriggerEngine(registry, haClient, dispatchPipeline, mqtt);
|
|
76
72
|
const scheduler = new Scheduler(registry.getAll(), (e) => engine.dispatch(e), haClient.ready);
|
|
77
73
|
engine.start();
|
|
78
74
|
scheduler.start();
|
|
@@ -100,7 +96,6 @@ const apiServer = new ApiServer({
|
|
|
100
96
|
entityCount: () => haClient.entityCount,
|
|
101
97
|
eventPublisher,
|
|
102
98
|
dryRun,
|
|
103
|
-
metrics: config.metrics.enabled ? metricsBackend : undefined,
|
|
104
99
|
});
|
|
105
100
|
await apiServer.start(config.server.port);
|
|
106
101
|
// 7. Connect to HA last — state_changed events start flowing once ready resolves.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ajclarkson/homerun",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.1-edge.
|
|
4
|
+
"version": "0.0.1-edge.a233fa3",
|
|
5
5
|
"description": "TypeScript automation framework for Home Assistant",
|
|
6
6
|
"files": [
|
|
7
7
|
"dist"
|
|
@@ -24,14 +24,11 @@
|
|
|
24
24
|
"test": "vitest",
|
|
25
25
|
"test:run": "vitest run",
|
|
26
26
|
"generate:ha-types": "tsx scripts/generate-ha-types.ts",
|
|
27
|
-
"generate:ha-types:compiled": "homerun-generate-ha-types"
|
|
28
|
-
"generate:ha-services": "tsx scripts/generate-ha-services.ts",
|
|
29
|
-
"generate:ha-services:compiled": "homerun-generate-ha-services"
|
|
27
|
+
"generate:ha-types:compiled": "homerun-generate-ha-types"
|
|
30
28
|
},
|
|
31
29
|
"bin": {
|
|
32
30
|
"homerun": "./dist/src/index.js",
|
|
33
|
-
"homerun-generate-ha-types": "./dist/scripts/generate-ha-types.js"
|
|
34
|
-
"homerun-generate-ha-services": "./dist/scripts/generate-ha-services.js"
|
|
31
|
+
"homerun-generate-ha-types": "./dist/scripts/generate-ha-types.js"
|
|
35
32
|
},
|
|
36
33
|
"repository": {
|
|
37
34
|
"type": "git",
|
|
@@ -48,7 +45,6 @@
|
|
|
48
45
|
"js-yaml": "^5.2.1",
|
|
49
46
|
"mqtt": "^5.15.1",
|
|
50
47
|
"node-cron": "^4.4.1",
|
|
51
|
-
"prom-client": "^15.1.3",
|
|
52
48
|
"zod": "^4.4.3"
|
|
53
49
|
},
|
|
54
50
|
"devDependencies": {
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
export interface HAServicePayload {
|
|
3
|
-
domain: string;
|
|
4
|
-
services: Record<string, HAServiceDef>;
|
|
5
|
-
}
|
|
6
|
-
export interface HAServiceDef {
|
|
7
|
-
fields: Record<string, HAServiceField>;
|
|
8
|
-
target?: unknown;
|
|
9
|
-
}
|
|
10
|
-
export interface HAServiceField {
|
|
11
|
-
required?: boolean;
|
|
12
|
-
selector?: Record<string, unknown>;
|
|
13
|
-
advanced?: boolean;
|
|
14
|
-
}
|
|
15
|
-
export declare function inferFieldType(field: HAServiceField): string;
|
|
16
|
-
export declare function generateFileContent(services: HAServicePayload[], domains?: string[]): string;
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { writeFile, mkdir } from 'node:fs/promises';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
// ---------- Type inference ----------
|
|
5
|
-
export function inferFieldType(field) {
|
|
6
|
-
const selector = field.selector;
|
|
7
|
-
if (!selector)
|
|
8
|
-
return 'unknown';
|
|
9
|
-
const key = Object.keys(selector)[0];
|
|
10
|
-
if (!key)
|
|
11
|
-
return 'unknown';
|
|
12
|
-
switch (key) {
|
|
13
|
-
case 'number':
|
|
14
|
-
case 'color_temp':
|
|
15
|
-
return 'number';
|
|
16
|
-
case 'boolean':
|
|
17
|
-
return 'boolean';
|
|
18
|
-
case 'color_rgb':
|
|
19
|
-
return '[number, number, number]';
|
|
20
|
-
case 'duration':
|
|
21
|
-
return '{ hours?: number; minutes?: number; seconds?: number }';
|
|
22
|
-
case 'object':
|
|
23
|
-
return 'Record<string, unknown>';
|
|
24
|
-
default:
|
|
25
|
-
return 'string';
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
// ---------- Service builder generation ----------
|
|
29
|
-
function generateServiceBuilder(domain, service, def) {
|
|
30
|
-
const hasTarget = !!def.target;
|
|
31
|
-
const fieldEntries = Object.entries(def.fields ?? {});
|
|
32
|
-
const hasFields = fieldEntries.length > 0;
|
|
33
|
-
const params = [];
|
|
34
|
-
if (hasTarget)
|
|
35
|
-
params.push('target: { entity_id: string }');
|
|
36
|
-
let anyRequired = false;
|
|
37
|
-
if (hasFields) {
|
|
38
|
-
anyRequired = fieldEntries.some(([, f]) => f.required);
|
|
39
|
-
const fieldDefs = fieldEntries
|
|
40
|
-
.map(([name, field]) => `${name}${field.required ? '' : '?'}: ${inferFieldType(field)}`)
|
|
41
|
-
.join('; ');
|
|
42
|
-
params.push(`data${anyRequired ? '' : '?'}: { ${fieldDefs} }`);
|
|
43
|
-
}
|
|
44
|
-
const bodyParts = [
|
|
45
|
-
`type: 'ha.call_service'`,
|
|
46
|
-
`domain: '${domain}'`,
|
|
47
|
-
`service: '${service}'`,
|
|
48
|
-
];
|
|
49
|
-
if (hasTarget)
|
|
50
|
-
bodyParts.push('target');
|
|
51
|
-
if (hasFields) {
|
|
52
|
-
bodyParts.push(`data: data as Record<string, unknown>${anyRequired ? '' : ' | undefined'}`);
|
|
53
|
-
}
|
|
54
|
-
return ` ${service}: (${params.join(', ')}): Action => ({ ${bodyParts.join(', ')} })`;
|
|
55
|
-
}
|
|
56
|
-
// ---------- File generation ----------
|
|
57
|
-
export function generateFileContent(services, domains) {
|
|
58
|
-
const filtered = domains
|
|
59
|
-
? services.filter(({ domain }) => domains.includes(domain))
|
|
60
|
-
: services;
|
|
61
|
-
const domainBlocks = filtered.map(({ domain, services: svcMap }) => {
|
|
62
|
-
const builders = Object.entries(svcMap)
|
|
63
|
-
.map(([service, def]) => generateServiceBuilder(domain, service, def))
|
|
64
|
-
.join(',\n');
|
|
65
|
-
return ` ${domain}: {\n${builders},\n }`;
|
|
66
|
-
});
|
|
67
|
-
const domainsNote = domains ? `\n// domains: ${domains.join(', ')}` : '';
|
|
68
|
-
const blockStr = domainBlocks.length > 0 ? `\n${domainBlocks.join(',\n')},\n` : '';
|
|
69
|
-
return `// generated — do not edit — run: npm run generate:ha-services${domainsNote}
|
|
70
|
-
import type { Action } from '@ajclarkson/homerun';
|
|
71
|
-
|
|
72
|
-
export const Services = {${blockStr}};
|
|
73
|
-
`;
|
|
74
|
-
}
|
|
75
|
-
// ---------- CLI ----------
|
|
76
|
-
async function main() {
|
|
77
|
-
const url = process.env.HA_URL;
|
|
78
|
-
const token = process.env.HA_TOKEN;
|
|
79
|
-
if (!url || !token) {
|
|
80
|
-
console.error('Error: HA_URL and HA_TOKEN must be set');
|
|
81
|
-
process.exit(1);
|
|
82
|
-
}
|
|
83
|
-
const domainsArg = process.argv.find((a) => a.startsWith('--domains='));
|
|
84
|
-
const domains = domainsArg ? domainsArg.slice('--domains='.length).split(',') : undefined;
|
|
85
|
-
const res = await fetch(`${url}/api/services`, {
|
|
86
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
87
|
-
});
|
|
88
|
-
if (!res.ok) {
|
|
89
|
-
console.error(`Error: HA API returned ${res.status} ${res.statusText}`);
|
|
90
|
-
process.exit(1);
|
|
91
|
-
}
|
|
92
|
-
const allServices = (await res.json());
|
|
93
|
-
const content = generateFileContent(allServices, domains);
|
|
94
|
-
const outPath = path.join(process.cwd(), 'types', 'ha-services.ts');
|
|
95
|
-
await mkdir(path.dirname(outPath), { recursive: true });
|
|
96
|
-
await writeFile(outPath, content, 'utf8');
|
|
97
|
-
const filtered = domains
|
|
98
|
-
? allServices.filter(({ domain }) => domains.includes(domain))
|
|
99
|
-
: allServices;
|
|
100
|
-
const serviceCount = filtered.reduce((n, { services: s }) => n + Object.keys(s).length, 0);
|
|
101
|
-
const domainLabel = domains ? `${domains.length} selected` : `all ${allServices.length}`;
|
|
102
|
-
console.log(`Written ${serviceCount} services across ${domainLabel} domains to ${outPath}`);
|
|
103
|
-
}
|
|
104
|
-
if (process.argv[1]?.endsWith('generate-ha-services.ts') ||
|
|
105
|
-
process.argv[1]?.endsWith('generate-ha-services.js') ||
|
|
106
|
-
process.argv[1]?.endsWith('homerun-generate-ha-services')) {
|
|
107
|
-
main().catch((err) => {
|
|
108
|
-
console.error('Fatal:', err);
|
|
109
|
-
process.exit(1);
|
|
110
|
-
});
|
|
111
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { Registry } from 'prom-client';
|
|
2
|
-
import type { MetricsBackend } from './metrics.js';
|
|
3
|
-
export declare class PromMetricsBackend implements MetricsBackend {
|
|
4
|
-
readonly registry: Registry<"text/plain; version=0.0.4; charset=utf-8">;
|
|
5
|
-
private readonly haEventsTotal;
|
|
6
|
-
private readonly pipelineRunsTotal;
|
|
7
|
-
private readonly actionsDispatchedTotal;
|
|
8
|
-
private readonly actionsSucceededTotal;
|
|
9
|
-
private readonly actionsFailedTotal;
|
|
10
|
-
private readonly actionDurationSeconds;
|
|
11
|
-
constructor(collectDefaults?: boolean);
|
|
12
|
-
incrementCounter(name: string, labels?: Record<string, string>): void;
|
|
13
|
-
observeHistogram(name: string, value: number, labels?: Record<string, string>): void;
|
|
14
|
-
getMetrics(): Promise<string>;
|
|
15
|
-
get contentType(): string;
|
|
16
|
-
}
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { Registry, Counter, Histogram, collectDefaultMetrics } from 'prom-client';
|
|
2
|
-
export class PromMetricsBackend {
|
|
3
|
-
registry = new Registry();
|
|
4
|
-
haEventsTotal;
|
|
5
|
-
pipelineRunsTotal;
|
|
6
|
-
actionsDispatchedTotal;
|
|
7
|
-
actionsSucceededTotal;
|
|
8
|
-
actionsFailedTotal;
|
|
9
|
-
actionDurationSeconds;
|
|
10
|
-
constructor(collectDefaults = false) {
|
|
11
|
-
if (collectDefaults) {
|
|
12
|
-
collectDefaultMetrics({ register: this.registry });
|
|
13
|
-
}
|
|
14
|
-
this.haEventsTotal = new Counter({
|
|
15
|
-
name: 'homerun_ha_events_received_total',
|
|
16
|
-
help: 'Total HA and MQTT events received by homerun',
|
|
17
|
-
labelNames: ['event_type'],
|
|
18
|
-
registers: [this.registry],
|
|
19
|
-
});
|
|
20
|
-
this.pipelineRunsTotal = new Counter({
|
|
21
|
-
name: 'homerun_pipeline_runs_total',
|
|
22
|
-
help: 'Total automation pipeline runs',
|
|
23
|
-
labelNames: ['location', 'trigger_type'],
|
|
24
|
-
registers: [this.registry],
|
|
25
|
-
});
|
|
26
|
-
this.actionsDispatchedTotal = new Counter({
|
|
27
|
-
name: 'homerun_actions_dispatched_total',
|
|
28
|
-
help: 'Total actions dispatched by the action runtime',
|
|
29
|
-
labelNames: ['location', 'action_type'],
|
|
30
|
-
registers: [this.registry],
|
|
31
|
-
});
|
|
32
|
-
this.actionsSucceededTotal = new Counter({
|
|
33
|
-
name: 'homerun_actions_succeeded_total',
|
|
34
|
-
help: 'Total actions that completed successfully',
|
|
35
|
-
labelNames: ['location', 'action_type'],
|
|
36
|
-
registers: [this.registry],
|
|
37
|
-
});
|
|
38
|
-
this.actionsFailedTotal = new Counter({
|
|
39
|
-
name: 'homerun_actions_failed_total',
|
|
40
|
-
help: 'Total actions that failed with an error',
|
|
41
|
-
labelNames: ['location', 'action_type'],
|
|
42
|
-
registers: [this.registry],
|
|
43
|
-
});
|
|
44
|
-
this.actionDurationSeconds = new Histogram({
|
|
45
|
-
name: 'homerun_action_duration_seconds',
|
|
46
|
-
help: 'Duration of action execution in seconds',
|
|
47
|
-
labelNames: ['location', 'action_type'],
|
|
48
|
-
buckets: [0.01, 0.05, 0.1, 0.5, 1, 2, 5],
|
|
49
|
-
registers: [this.registry],
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
incrementCounter(name, labels = {}) {
|
|
53
|
-
switch (name) {
|
|
54
|
-
case 'homerun_ha_events_received_total':
|
|
55
|
-
this.haEventsTotal.inc(labels);
|
|
56
|
-
break;
|
|
57
|
-
case 'homerun_pipeline_runs_total':
|
|
58
|
-
this.pipelineRunsTotal.inc(labels);
|
|
59
|
-
break;
|
|
60
|
-
case 'homerun_actions_dispatched_total':
|
|
61
|
-
this.actionsDispatchedTotal.inc(labels);
|
|
62
|
-
break;
|
|
63
|
-
case 'homerun_actions_succeeded_total':
|
|
64
|
-
this.actionsSucceededTotal.inc(labels);
|
|
65
|
-
break;
|
|
66
|
-
case 'homerun_actions_failed_total':
|
|
67
|
-
this.actionsFailedTotal.inc(labels);
|
|
68
|
-
break;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
observeHistogram(name, value, labels = {}) {
|
|
72
|
-
if (name === 'homerun_action_duration_seconds') {
|
|
73
|
-
this.actionDurationSeconds.observe(labels, value);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
async getMetrics() {
|
|
77
|
-
return this.registry.metrics();
|
|
78
|
-
}
|
|
79
|
-
get contentType() {
|
|
80
|
-
return this.registry.contentType;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export interface MetricsBackend {
|
|
2
|
-
incrementCounter(name: string, labels?: Record<string, string>): void;
|
|
3
|
-
observeHistogram(name: string, value: number, labels?: Record<string, string>): void;
|
|
4
|
-
}
|
|
5
|
-
export declare class NoopMetricsBackend implements MetricsBackend {
|
|
6
|
-
incrementCounter(): void;
|
|
7
|
-
observeHistogram(): void;
|
|
8
|
-
}
|