@ajclarkson/homerun 0.0.1-edge.f81eb62 → 0.0.1
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.
|
@@ -3,7 +3,5 @@ type Importer = (dataUri: string) => Promise<{
|
|
|
3
3
|
default: unknown;
|
|
4
4
|
}>;
|
|
5
5
|
export declare function _reloadFile(filePath: string, registry: AutomationRegistry, importer?: Importer, fileToIds?: Map<string, string[]>): Promise<void>;
|
|
6
|
-
export declare function _deleteFile(filePath: string, registry: AutomationRegistry, fileToIds?: Map<string, string[]>): void;
|
|
7
|
-
export declare function rescanAutomations(automationsDir: string, registry: AutomationRegistry, fileToIds?: Map<string, string[]>, importer?: Importer): Promise<void>;
|
|
8
6
|
export declare function startHotReload(automationsDir: string, registry: AutomationRegistry): void;
|
|
9
7
|
export {};
|
|
@@ -12,10 +12,7 @@ export async function _reloadFile(filePath, registry, importer = defaultImporter
|
|
|
12
12
|
platform: 'node',
|
|
13
13
|
format: 'esm',
|
|
14
14
|
write: false,
|
|
15
|
-
alias: {
|
|
16
|
-
'@ajclarkson/homerun/testing': path.resolve(import.meta.dirname, '../testing.js'),
|
|
17
|
-
'@ajclarkson/homerun': path.resolve(import.meta.dirname, '../lib.js'),
|
|
18
|
-
},
|
|
15
|
+
alias: { homerun: path.resolve(import.meta.dirname, '../lib.js') },
|
|
19
16
|
});
|
|
20
17
|
const code = result.outputFiles[0].text;
|
|
21
18
|
const dataUri = `data:text/javascript;base64,${Buffer.from(code).toString('base64')}`;
|
|
@@ -39,49 +36,13 @@ export async function _reloadFile(filePath, registry, importer = defaultImporter
|
|
|
39
36
|
console.error(`[hot-reload] failed to reload ${filePath}:`, err);
|
|
40
37
|
}
|
|
41
38
|
}
|
|
42
|
-
export function _deleteFile(filePath, registry, fileToIds = moduleFileToIds) {
|
|
43
|
-
for (const id of fileToIds.get(filePath) ?? []) {
|
|
44
|
-
registry.unregister(id);
|
|
45
|
-
}
|
|
46
|
-
fileToIds.delete(filePath);
|
|
47
|
-
}
|
|
48
|
-
const isAutomationFile = (f) => f.endsWith('.ts') &&
|
|
49
|
-
!f.endsWith('.test.ts') &&
|
|
50
|
-
!f.includes('node_modules') &&
|
|
51
|
-
!f.includes('.d.ts') &&
|
|
52
|
-
!f.split(path.sep).includes('types');
|
|
53
|
-
export async function rescanAutomations(automationsDir, registry, fileToIds = moduleFileToIds, importer = defaultImporter) {
|
|
54
|
-
let files = [];
|
|
55
|
-
try {
|
|
56
|
-
const { readdir } = await import('node:fs/promises');
|
|
57
|
-
files = (await readdir(automationsDir, { recursive: true }));
|
|
58
|
-
}
|
|
59
|
-
catch {
|
|
60
|
-
console.warn(`[homerun] AUTOMATIONS_DIR not found: ${automationsDir} — starting with no automations`);
|
|
61
|
-
}
|
|
62
|
-
const currentPaths = new Set(files.filter(isAutomationFile).map((f) => path.join(automationsDir, f)));
|
|
63
|
-
for (const trackedPath of [...fileToIds.keys()]) {
|
|
64
|
-
if (!currentPaths.has(trackedPath)) {
|
|
65
|
-
_deleteFile(trackedPath, registry, fileToIds);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
for (const filePath of currentPaths) {
|
|
69
|
-
await _reloadFile(filePath, registry, importer, fileToIds);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
39
|
export function startHotReload(automationsDir, registry) {
|
|
73
40
|
const target = process.env.AUTOMATION
|
|
74
41
|
? path.join(automationsDir, `${process.env.AUTOMATION}.ts`)
|
|
75
42
|
: `${automationsDir}/**/*.ts`;
|
|
76
|
-
|
|
77
|
-
const reload = (filePath) => {
|
|
43
|
+
watch(target, { ignoreInitial: true, ignored: /node_modules/ }).on('change', (filePath) => {
|
|
78
44
|
_reloadFile(filePath, registry).catch((err) => {
|
|
79
45
|
console.error('[hot-reload] unexpected error:', err);
|
|
80
46
|
});
|
|
81
|
-
};
|
|
82
|
-
watcher.on('add', reload);
|
|
83
|
-
watcher.on('change', reload);
|
|
84
|
-
watcher.on('unlink', (filePath) => {
|
|
85
|
-
_deleteFile(filePath, registry);
|
|
86
47
|
});
|
|
87
48
|
}
|
|
@@ -6,7 +6,6 @@ import type { ActionRuntime } from './action-runtime.js';
|
|
|
6
6
|
interface Deps {
|
|
7
7
|
observability: Observability;
|
|
8
8
|
actionRuntime: ActionRuntime;
|
|
9
|
-
dryRun?: boolean;
|
|
10
9
|
}
|
|
11
10
|
export declare function runPipeline(automation: Automation<unknown>, event: TriggerEvent, haClient: HAClient, deps: Deps): Promise<void>;
|
|
12
11
|
export {};
|
package/dist/src/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import 'dotenv/config';
|
|
2
|
+
import { readdir } from 'node:fs/promises';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { connect } from 'mqtt';
|
|
4
5
|
import { HAClient } from './framework/ha-client.js';
|
|
@@ -8,7 +9,7 @@ import { TimerManager } from './framework/timer-manager.js';
|
|
|
8
9
|
import { ActionRuntime } from './framework/action-runtime.js';
|
|
9
10
|
import { TriggerEngine } from './framework/trigger-engine.js';
|
|
10
11
|
import { Scheduler } from './framework/scheduler.js';
|
|
11
|
-
import {
|
|
12
|
+
import { _reloadFile, startHotReload } from './framework/hot-reload.js';
|
|
12
13
|
import { runPipeline } from './framework/pipeline.js';
|
|
13
14
|
import { ApiServer } from './framework/api-server.js';
|
|
14
15
|
process.on('uncaughtException', (err) => {
|
|
@@ -40,11 +41,27 @@ const actionRuntime = new ActionRuntime({
|
|
|
40
41
|
});
|
|
41
42
|
// 3. Initial automation load — must complete before the engine and scheduler start.
|
|
42
43
|
const automationsDir = path.resolve(process.env.AUTOMATIONS_DIR);
|
|
43
|
-
|
|
44
|
+
const isAutomation = (f) => f.endsWith('.ts') &&
|
|
45
|
+
!f.includes('node_modules') &&
|
|
46
|
+
!f.includes('.d.ts') &&
|
|
47
|
+
!f.split(path.sep).includes('types');
|
|
48
|
+
async function loadAutomations() {
|
|
49
|
+
let files = [];
|
|
50
|
+
try {
|
|
51
|
+
files = (await readdir(automationsDir, { recursive: true }));
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
console.warn(`[homerun] AUTOMATIONS_DIR not found: ${automationsDir} — starting with no automations`);
|
|
55
|
+
}
|
|
56
|
+
for (const file of files.filter(isAutomation)) {
|
|
57
|
+
await _reloadFile(path.join(automationsDir, file), registry);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
await loadAutomations();
|
|
44
61
|
console.log(`[homerun] loaded ${registry.getAll().length} automation(s)`);
|
|
45
62
|
// 4. Wire up the engine and scheduler.
|
|
46
63
|
engine = new TriggerEngine(registry, haClient, (automation, event) => {
|
|
47
|
-
runPipeline(automation, event, haClient, { observability, actionRuntime
|
|
64
|
+
runPipeline(automation, event, haClient, { observability, actionRuntime }).catch((err) => {
|
|
48
65
|
console.error('[homerun] pipeline error:', err);
|
|
49
66
|
});
|
|
50
67
|
}, mqtt);
|
|
@@ -54,7 +71,7 @@ scheduler.start();
|
|
|
54
71
|
// 5. Start hot-reload watcher (dev) and SIGUSR1 rescan (git-sync sidecar in K8s).
|
|
55
72
|
startHotReload(automationsDir, registry);
|
|
56
73
|
async function reload() {
|
|
57
|
-
await
|
|
74
|
+
await loadAutomations();
|
|
58
75
|
console.log(`[homerun] rescan complete — ${registry.getAll().length} automation(s) registered`);
|
|
59
76
|
}
|
|
60
77
|
process.on('SIGUSR1', () => {
|
|
@@ -68,7 +85,7 @@ let haReady = false;
|
|
|
68
85
|
const apiServer = new ApiServer({
|
|
69
86
|
registry,
|
|
70
87
|
onTrigger: (automation, event) => {
|
|
71
|
-
runPipeline(automation, event, haClient, { observability, actionRuntime
|
|
88
|
+
runPipeline(automation, event, haClient, { observability, actionRuntime }).catch((err) => {
|
|
72
89
|
console.error('[homerun] pipeline error (http trigger):', err);
|
|
73
90
|
});
|
|
74
91
|
},
|