-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: environment precedence mechanism (#21)
* chore: upgrade deps * feat: env precedence * chore: remove inspect * chore: upgrade node
- Loading branch information
1 parent
fb5a0df
commit c740772
Showing
16 changed files
with
3,095 additions
and
1,392 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,3 +9,6 @@ | |
/.vscode | ||
/.env* | ||
!/.env*.dist | ||
|
||
# DigitalAlchemy | ||
/synapse_storage.db |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,10 +9,11 @@ | |
"dev": "bun --hot --watch src/main.ts", | ||
"play": "docker-compose -f playground/docker-compose.yml up", | ||
"endplay": "docker-compose -f playground/docker-compose.yml down", | ||
"sync": "yarn up \"@digital-alchemy/*\" && bunx --env-file .env type-writer", | ||
"type-writer": "type-writer", | ||
"build": "bun --env-file .env build:docker", | ||
"build:dist": "bun build src/main.ts --compile --minify --outfile dist/server", | ||
"build:docker": "docker build . --build-arg HASS_TOKEN=$HASS_TOKEN --build-arg HASS_BASE_URL=$HASS_BASE_URL -t automation-prod", | ||
"upgrade": "yarn up \"@digital-alchemy/*\"", | ||
"start": "docker run --env-file .env automation-prod", | ||
"test": "vitest", | ||
"coverage": "vitest --coverage", | ||
|
@@ -35,22 +36,26 @@ | |
"*.@(ts|tsx|mts|js|jsx|mjs|cjs|json|jsonc|json5|md|mdx|yaml|yml)": "prettier --write" | ||
}, | ||
"dependencies": { | ||
"@digital-alchemy/core": "^0.3.11", | ||
"@digital-alchemy/hass": "^0.3.14", | ||
"@digital-alchemy/synapse": "^0.3.5", | ||
"dayjs": "^1.11.10" | ||
"@digital-alchemy/automation": "^24.7.1", | ||
"@digital-alchemy/core": "^24.7.2", | ||
"@digital-alchemy/fastify-extension": "^24.7.1", | ||
"@digital-alchemy/hass": "^24.8.1", | ||
"@digital-alchemy/mqtt-extension": "^24.7.1", | ||
"@digital-alchemy/synapse": "^24.8.1", | ||
"@digital-alchemy/type-writer": "^24.7.2", | ||
"dayjs": "^1.11.12" | ||
}, | ||
"devDependencies": { | ||
"@cspell/eslint-plugin": "^8.7.0", | ||
"@digital-alchemy/type-writer": "^0.3.8", | ||
"@types/async": "^3.2.24", | ||
"@types/bun": "^1.1.0", | ||
"@types/bun": "^1.1.6", | ||
"@types/jest": "^29.5.12", | ||
"@types/node": "^20.12.7", | ||
"@typescript-eslint/eslint-plugin": "7.6.0", | ||
"@typescript-eslint/parser": "7.6.0", | ||
"@types/node": "^22.2.0", | ||
"@typescript-eslint/eslint-plugin": "7.18.0", | ||
"@typescript-eslint/parser": "7.18.0", | ||
"@vitest/coverage-v8": "^1.5.0", | ||
"bun": "^1.1.22", | ||
"bun": "^1.1.20", | ||
"cross-env": "^7.0.3", | ||
"eslint": "8.57.0", | ||
"eslint-config-prettier": "9.1.0", | ||
"eslint-plugin-import": "^2.29.1", | ||
|
@@ -73,7 +78,7 @@ | |
"vitest": "^1.5.0" | ||
}, | ||
"volta": { | ||
"node": "20.16.0", | ||
"node": "22.6.0", | ||
"yarn": "4.4.0" | ||
}, | ||
"packageManager": "[email protected]" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { TServiceParams } from '@digital-alchemy/core' | ||
|
||
export function RuntimePrecedence({ logger, config, hass, lifecycle }: TServiceParams) { | ||
// Whether this runtime is in development mode or not | ||
const isDevelop = config.homeAutomation.NODE_ENV === 'development' | ||
|
||
// When developing locally, the production runtime will pause and the development runtime will take over | ||
// @ts-expect-error - Entity will be created by setting the state here. | ||
const isDevelopmentActive = hass.refBy.id('binary_sensor.is_development_runtime_active') | ||
|
||
// Block outgoing commands and most incoming messages in prod when dev overrides it. | ||
isDevelopmentActive.onUpdate(() => { | ||
if (isDevelopmentActive.state === 'on') { | ||
logger.info('Development runtime takes over') | ||
// dev takes over, prod pauses | ||
hass.socket.pauseMessages = !isDevelop | ||
} else { | ||
logger.info('Resuming production runtime') | ||
// prod resumes, dev pauses | ||
hass.socket.pauseMessages = isDevelop | ||
} | ||
}) | ||
|
||
// Update the state on startup | ||
lifecycle.onReady(() => { | ||
if (isDevelop) isDevelopmentActive.state = 'on' | ||
}) | ||
|
||
// Give the go ahead for production to take over again when shutting down | ||
lifecycle.onPreShutdown(async () => { | ||
if (!isDevelop) return | ||
|
||
isDevelopmentActive.state = 'off' | ||
|
||
const result = await isDevelopmentActive.nextState(5000) | ||
if (!result) return logger.error(`Unable to verify that production runtime has taken over.`) | ||
|
||
logger.info(`Production runtime has taken over. Development: ${result.state}`) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { TServiceParams } from '@digital-alchemy/core' | ||
import { Database } from 'bun:sqlite' | ||
|
||
// This service will be loaded first. Use it to do any global setup. | ||
export function Setup({ synapse }: TServiceParams) { | ||
synapse.sqlite.setDriver(Database) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
/* eslint-disable unicorn/prefer-export-from */ | ||
import dayjs from 'dayjs' | ||
import advancedFormat from 'dayjs/plugin/advancedFormat' | ||
import isBetween from 'dayjs/plugin/isBetween' | ||
import timezone from 'dayjs/plugin/timezone' | ||
import utc from 'dayjs/plugin/utc' | ||
import weekOfYear from 'dayjs/plugin/weekOfYear' | ||
|
||
dayjs.extend(weekOfYear) | ||
dayjs.extend(advancedFormat) | ||
dayjs.extend(isBetween) | ||
dayjs.extend(utc) | ||
dayjs.extend(timezone) | ||
|
||
export { dayjs } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { TServiceParams } from '@digital-alchemy/core' | ||
|
||
export function Helpers({ logger, config, hass }: TServiceParams) { | ||
const theSun = hass.refBy.id('sun.sun') | ||
|
||
const doStuff = (): string => { | ||
logger.info('doStuff was called!') | ||
|
||
return config.homeAutomation.MY_CONFIG_SETTING | ||
} | ||
|
||
return { theSun, doStuff } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,79 +1,58 @@ | ||
import { CreateApplication } from '@digital-alchemy/core' | ||
import { LIB_AUTOMATION } from '@digital-alchemy/automation' | ||
import { CreateApplication, StringConfig } from '@digital-alchemy/core' | ||
import { LIB_HASS } from '@digital-alchemy/hass' | ||
import { LIB_SYNAPSE } from '@digital-alchemy/synapse' | ||
|
||
import { EntityList } from './entity-list' | ||
import { HelperFile } from './helper' | ||
import { RuntimePrecedence } from './core/runtime-precedence' | ||
import { Setup } from './core/setup' | ||
import { Helpers } from './helpers' | ||
import { Office } from './office' | ||
|
||
type AutomationEnvironments = 'development' | 'production' | 'test' | ||
|
||
const HOME_AUTOMATION = CreateApplication({ | ||
/** | ||
* keep your secrets out of the code! | ||
* these variables will be loaded from your configuration file | ||
*/ | ||
name: 'homeAutomation', | ||
configuration: { | ||
EXAMPLE_CONFIGURATION: { | ||
NODE_ENV: { | ||
type: 'string', | ||
default: 'development', | ||
enum: ['development', 'production', 'test'], | ||
description: "Code runner addon can set with it's own NODE_ENV", | ||
} satisfies StringConfig<AutomationEnvironments>, | ||
|
||
MY_CONFIG_SETTING: { | ||
default: 'foo', | ||
description: 'A configuration defined as an example', | ||
type: 'string', | ||
}, | ||
}, | ||
|
||
/** | ||
* Adding to this array will provide additional elements in TServiceParams | ||
* for your code to use | ||
*/ | ||
libraries: [ | ||
/** | ||
* LIB_HASS provides basic interactions for Home Assistant | ||
* | ||
* Will automatically start websocket as part of bootstrap | ||
*/ | ||
LIB_HASS, | ||
], | ||
|
||
/** | ||
* must match key used in LoadedModules | ||
* affects: | ||
* - import name in TServiceParams | ||
* - and files used for configuration | ||
* - log context | ||
*/ | ||
name: 'home_automation', | ||
|
||
/** | ||
* Need a service to be loaded first? Add to this list | ||
*/ | ||
priorityInit: ['helper'], | ||
// Plugins for TSServiceParams | ||
libraries: [LIB_HASS, LIB_SYNAPSE, LIB_AUTOMATION], | ||
|
||
/** | ||
* Add additional services here | ||
* No guaranteed loading order unless added to priority list | ||
* | ||
* context: ServiceFunction | ||
*/ | ||
// Service initialization order | ||
priorityInit: ['setup', 'runtimePrecedence', 'helpers'], | ||
services: { | ||
entity_list: EntityList, | ||
helper: HelperFile, | ||
setup: Setup, | ||
runtimePrecedence: RuntimePrecedence, | ||
helpers: Helpers, | ||
office: Office, | ||
}, | ||
}) | ||
|
||
// Load the type definitions | ||
// Do some magic to make all the types work | ||
declare module '@digital-alchemy/core' { | ||
export interface LoadedModules { | ||
home_automation: typeof HOME_AUTOMATION | ||
homeAutomation: typeof HOME_AUTOMATION | ||
} | ||
} | ||
|
||
// Kick off the application! | ||
// bootstrap application | ||
setImmediate( | ||
async () => | ||
await HOME_AUTOMATION.bootstrap({ | ||
/** | ||
* override library defined defaults | ||
* not a substitute for config files | ||
*/ | ||
configuration: { | ||
// default value: trace | ||
boilerplate: { LOG_LEVEL: 'debug' }, | ||
boilerplate: { LOG_LEVEL: 'info' }, | ||
}, | ||
}), | ||
) |
Oops, something went wrong.