@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
- const watcher = watch(target, { ignoreInitial: true, ignored: [/node_modules/, /\.test\.ts$/] });
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 {};
@@ -9,7 +9,6 @@ export async function runPipeline(automation, event, haClient, deps) {
9
9
  location: automation.location,
10
10
  subsystem: automation.subsystem,
11
11
  timestamp,
12
- ...(deps.dryRun ? { dry_run: true } : {}),
13
12
  };
14
13
  // Step 2: Context
15
14
  let ctx;
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 { rescanAutomations, startHotReload } from './framework/hot-reload.js';
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
- await rescanAutomations(automationsDir, registry);
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, dryRun: process.env.DRY_RUN === 'true' }).catch((err) => {
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 rescanAutomations(automationsDir, registry);
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, dryRun: process.env.DRY_RUN === 'true' }).catch((err) => {
88
+ runPipeline(automation, event, haClient, { observability, actionRuntime }).catch((err) => {
72
89
  console.error('[homerun] pipeline error (http trigger):', err);
73
90
  });
74
91
  },
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.f81eb62",
4
+ "version": "0.0.1",
5
5
  "description": "TypeScript automation framework for Home Assistant",
6
6
  "files": [
7
7
  "dist"