From c9e8d7a55d348eb2c5bc7bbbd9fcdbaa0a20dc9f Mon Sep 17 00:00:00 2001 From: Shannon Hochkins Date: Tue, 21 Nov 2023 15:13:19 +1100 Subject: [PATCH] Feature/small bugs (#107) * Deploy script template adjusted, changing useArea response, media card bug * fixing linting * Updating version --- CHANGELOG.md | 12 + package-lock.json | 26 +- packages/components/package.json | 4 +- .../src/Cards/AreaCard/AreaCard.stories.tsx | 12 +- .../MediaPlayer/MediaPlayerControls/index.tsx | 8 +- packages/core/package.json | 2 +- packages/core/src/hooks/useAreas/index.ts | 16 +- packages/core/src/types/supported-services.ts | 1102 +++++++++-------- packages/core/src/utils/colors.ts | 4 + packages/create-hakit/package.json | 3 +- packages/create-hakit/src/index.ts | 2 + .../create-hakit/template/scripts/deploy.ts | 47 +- 12 files changed, 656 insertions(+), 582 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1e2008b..95f715ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +# 1.1.1 +## create-hakit +- Adjustments to deploy script +- Adjustments to base template + +# 3.0.5 +## @hakit/components +- BUGFIX - Fixing bug for MediaPlayerCard where groups aren't available it was not rendering the cards + +## @hakit/core +- CHANGE - useAreas now has a new returned value called `deviceEntities` which are related children for a matched device. You can merge entities and deviceEntities if you want to display them all. + # 3.0.4 ## @hakit/components diff --git a/package-lock.json b/package-lock.json index a6036948..466fc3e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23732,7 +23732,7 @@ }, "packages/components": { "name": "@hakit/components", - "version": "3.0.3", + "version": "3.0.4", "license": "ISC", "devDependencies": { "@emotion/babel-plugin": "^11.x", @@ -23749,7 +23749,7 @@ "@emotion/react": ">=10.x", "@emotion/styled": ">=10.x", "@fullcalendar/react": "^6.1.9", - "@hakit/core": "^3.0.3", + "@hakit/core": "^3.0.4", "@use-gesture/react": ">=10.x", "autolinker": ">=4.x", "framer-motion": ">=10.x", @@ -23768,7 +23768,7 @@ }, "packages/core": { "name": "@hakit/core", - "version": "3.0.3", + "version": "3.0.4", "license": "ISC", "bin": { "hakit-sync-types": "dist/sync/cli/cli.js" @@ -23879,6 +23879,7 @@ "devDependencies": { "@types/minimist": "^1.2.2", "@types/prompts": "^2.4.4", + "chalk": "^5.3.0", "cross-spawn": "^7.0.3", "kolorist": "^1.8.0", "minimist": "^1.2.8", @@ -23895,6 +23896,18 @@ "url": "https://github.com/shannonhochkins/ha-component-kit?sponsor=1" } }, + "packages/create-hakit/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "packages/create-hakit/node_modules/rimraf": { "version": "5.0.1", "dev": true, @@ -30781,6 +30794,7 @@ "requires": { "@types/minimist": "^1.2.2", "@types/prompts": "^2.4.4", + "chalk": "*", "create-vite": "latest", "cross-spawn": "^7.0.3", "kolorist": "^1.8.0", @@ -30791,6 +30805,12 @@ "unbuild": "^2.0.0" }, "dependencies": { + "chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true + }, "rimraf": { "version": "5.0.1", "dev": true, diff --git a/packages/components/package.json b/packages/components/package.json index e5f8fbd4..6f039cda 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,7 +1,7 @@ { "name": "@hakit/components", "type": "module", - "version": "3.0.4", + "version": "3.0.5", "private": false, "keywords": [ "react", @@ -68,7 +68,7 @@ "@emotion/react": ">=10.x", "@emotion/styled": ">=10.x", "@fullcalendar/react": "^6.1.9", - "@hakit/core": "^3.0.4", + "@hakit/core": "^3.0.5", "@use-gesture/react": ">=10.x", "autolinker": ">=4.x", "framer-motion": ">=10.x", diff --git a/packages/components/src/Cards/AreaCard/AreaCard.stories.tsx b/packages/components/src/Cards/AreaCard/AreaCard.stories.tsx index 8f539c51..9d9949eb 100644 --- a/packages/components/src/Cards/AreaCard/AreaCard.stories.tsx +++ b/packages/components/src/Cards/AreaCard/AreaCard.stories.tsx @@ -2,7 +2,6 @@ import type { Meta, StoryObj } from "@storybook/react"; import { Title, Description, Primary, ArgTypes, Source } from "@storybook/blocks"; import { ThemeProvider, AreaCard, Row, ButtonCard } from "@components"; import type { AreaCardProps } from "@components"; -import { useHass } from "@hakit/core"; // @ts-expect-error - Don't have types for jsx-to-string import jsxToString from "jsx-to-string"; import { HassConnect } from "@hass-connect-fake"; @@ -70,7 +69,7 @@ function TemplateFull() { ); } -function UseHashExample() { +const hashExample = `function UseHashExample() { const { useStore } = useHass(); const setHash = useStore((store) => store.setHash); return ( @@ -86,7 +85,7 @@ function UseHashExample() { /> ); -} +}`; export default { title: "COMPONENTS/Cards/AreaCard", @@ -104,12 +103,7 @@ export default { You can set the hash programmatically from anywhere and the area will activate! There's a helper hook designed to help with this!

- +

Component Props

diff --git a/packages/components/src/Shared/Entity/MediaPlayer/MediaPlayerControls/index.tsx b/packages/components/src/Shared/Entity/MediaPlayer/MediaPlayerControls/index.tsx index 06bfb0d0..7fa4b592 100644 --- a/packages/components/src/Shared/Entity/MediaPlayer/MediaPlayerControls/index.tsx +++ b/packages/components/src/Shared/Entity/MediaPlayer/MediaPlayerControls/index.tsx @@ -65,7 +65,13 @@ export interface MediaPlayerControlsProps extends ColumnProps { onStateChange?: (state: string) => void; } -export const MediaPlayerControls = ({ groupedEntities, allEntityIds, onStateChange, entity, ...rest }: MediaPlayerControlsProps) => { +export const MediaPlayerControls = ({ + groupedEntities = [], + allEntityIds = [], + onStateChange, + entity, + ...rest +}: MediaPlayerControlsProps) => { const primaryEntity = useEntity(entity); const supportsGrouping = supportsFeatureFromAttributes(primaryEntity.attributes, 524288); // To get proper ordering of speakers, we need to flatten the groupedEntities array while keeping the order of the groups diff --git a/packages/core/package.json b/packages/core/package.json index 3ec771a7..02c01202 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@hakit/core", - "version": "3.0.4", + "version": "3.0.5", "private": false, "type": "module", "keywords": [ diff --git a/packages/core/src/hooks/useAreas/index.ts b/packages/core/src/hooks/useAreas/index.ts index 97b1fd56..47c50165 100644 --- a/packages/core/src/hooks/useAreas/index.ts +++ b/packages/core/src/hooks/useAreas/index.ts @@ -21,6 +21,8 @@ export interface Area { services: DeviceRegistryEntry[]; /** the entities linked to the area */ entities: HassEntity[]; + /** entities related to the matched devices */ + deviceEntities: HassEntity[]; } export function useAreas(): Area[] { @@ -52,9 +54,10 @@ export function useAreas(): Area[] { return useMemo(() => { return areas.map((area) => { - const matchedEntities = []; - const matchedDevices = []; - const matchedServices = []; + const matchedEntities: HassEntity[] = []; + const matchedDevices: DeviceRegistryEntry[] = []; + const matchedServices: DeviceRegistryEntry[] = []; + const deviceEntities: HassEntity[] = []; for (const device of devices) { if (device.area_id === area.area_id) { @@ -73,11 +76,11 @@ export function useAreas(): Area[] { // link the matchedDevices to an entity for (const device of matchedDevices) { if (entity.device_id === device.id) { - // first check if matchedEntities contains the entity already - const exists = matchedEntities.find((e) => e.entity_id === entity.entity_id); + // first check if deviceEntities contains the entity already + const exists = deviceEntities.find((e) => e.entity_id === entity.entity_id); if (!exists) { if (_entities[entity.entity_id]) { - matchedEntities.push(_entities[entity.entity_id]); + deviceEntities.push(_entities[entity.entity_id]); } } } @@ -89,6 +92,7 @@ export function useAreas(): Area[] { devices: matchedDevices, services: matchedServices, entities: matchedEntities, + deviceEntities, }; }); }, [areas, devices, joinHassUrl, entities, _entities]); diff --git a/packages/core/src/types/supported-services.ts b/packages/core/src/types/supported-services.ts index 010319bb..c7323e26 100644 --- a/packages/core/src/types/supported-services.ts +++ b/packages/core/src/types/supported-services.ts @@ -334,16 +334,116 @@ export interface DefaultServices { // Reloads history stats sensors from the YAML-configuration. reload: ServiceFunction; }; - restCommand: { - // - assistantRelay: ServiceFunction; - // - reload: ServiceFunction; - }; commandLine: { // Reloads command line configuration from the YAML-configuration. reload: ServiceFunction; }; + mediaPlayer: { + // Turns on the power of the media player. + turnOn: ServiceFunction; + // Turns off the power of the media player. + turnOff: ServiceFunction; + // Toggles a media player on/off. + toggle: ServiceFunction; + // Turns up the volume. + volumeUp: ServiceFunction; + // Turns down the volume. + volumeDown: ServiceFunction; + // Toggles play/pause. + mediaPlayPause: ServiceFunction; + // Starts playing. + mediaPlay: ServiceFunction; + // Pauses. + mediaPause: ServiceFunction; + // Stops playing. + mediaStop: ServiceFunction; + // Selects the next track. + mediaNextTrack: ServiceFunction; + // Selects the previous track. + mediaPreviousTrack: ServiceFunction; + // Clears the playlist. + clearPlaylist: ServiceFunction; + // Sets the volume level. + volumeSet: ServiceFunction< + T, + { + // The volume. 0 is inaudible, 1 is the maximum volume. + volume_level: number; + } + >; + // Mutes or unmutes the media player. + volumeMute: ServiceFunction< + T, + { + // Defines whether or not it is muted. + is_volume_muted: boolean; + } + >; + // Allows you to go to a different part of the media that is currently playing. + mediaSeek: ServiceFunction< + T, + { + // Target position in the currently playing media. The format is platform dependent. + seek_position: number; + } + >; + // Groups media players together for synchronous playback. Only works on supported multiroom audio systems. + join: ServiceFunction< + T, + { + // The players which will be synced with the playback specified in `target`. @example - media_player.multiroom_player2 - media_player.multiroom_player3 + group_members: string[]; + } + >; + // Sends the media player the command to change input source. + selectSource: ServiceFunction< + T, + { + // Name of the source to switch to. Platform dependent. @example video1 + source: string; + } + >; + // Selects a specific sound mode. + selectSoundMode: ServiceFunction< + T, + { + // Name of the sound mode to switch to. @example Music + sound_mode?: string; + } + >; + // Starts playing specified media. + playMedia: ServiceFunction< + T, + { + // The ID of the content to play. Platform dependent. @example https://home-assistant.io/images/cast/splash.png + media_content_id: string; + // The type of the content to play. Such as image, music, tv show, video, episode, channel, or playlist. @example music + media_content_type: string; + // If the content should be played now or be added to the queue. + enqueue?: "play" | "next" | "add" | "replace"; + // If the media should be played as an announcement. @example true + announce?: boolean; + } + >; + // Playback mode that selects the media in randomized order. + shuffleSet: ServiceFunction< + T, + { + // Whether or not shuffle mode is enabled. + shuffle: boolean; + } + >; + // Removes the player from a group. Only works on platforms which support player groups. + unjoin: ServiceFunction; + // Playback mode that plays the media in a loop. + repeatSet: ServiceFunction< + T, + { + // Repeat mode to set. + repeat: "off" | "all" | "one"; + } + >; + }; conversation: { // Launches a conversation from a transcribed text. process: ServiceFunction< @@ -368,21 +468,11 @@ export interface DefaultServices { } >; }; - logbook: { - // Creates a custom entry in the logbook. - log: ServiceFunction< - T, - { - // Custom name for an entity, can be referenced using an `entity_id`. @example Kitchen - name: string; - // Message of the logbook entry. @example is being used - message: string; - // Entity to reference in the logbook entry. - entity_id?: string; - // Determines which icon is used in the logbook entry. The icon illustrates the integration domain related to this logbook entry. @example light - domain?: string; - } - >; + restCommand: { + // + assistantRelay: ServiceFunction; + // + reload: ServiceFunction; }; light: { // Turn on one or more lights and adjust properties of the light, even when they are turned on already. @@ -764,38 +854,6 @@ export interface DefaultServices { } >; }; - group: { - // Reloads group configuration, entities, and notify services from YAML-configuration. - reload: ServiceFunction; - // Creates/Updates a user group. - set: ServiceFunction< - T, - { - // Object ID of this group. This object ID is used as part of the entity ID. Entity ID format: [domain].[object_id]. @example test_group - object_id: string; - // Name of the group. @example My test group - name?: string; - // Name of the icon for the group. @example mdi:camera - icon?: object; - // List of all members in the group. Cannot be used in combination with `Add entities` or `Remove entities`. @example domain.entity_id1, domain.entity_id2 - entities?: string; - // List of members to be added to the group. Cannot be used in combination with `Entities` or `Remove entities`. @example domain.entity_id1, domain.entity_id2 - add_entities?: string; - // List of members to be removed from a group. Cannot be used in combination with `Entities` or `Add entities`. @example domain.entity_id1, domain.entity_id2 - remove_entities?: string; - // Enable this option if the group should only be used when all entities are in state `on`. - all?: boolean; - } - >; - // Removes a group. - remove: ServiceFunction< - T, - { - // Object ID of this group. This object ID is used as part of the entity ID. Entity ID format: [domain].[object_id]. @example test_group - object_id: object; - } - >; - }; cover: { // Opens a cover. openCover: ServiceFunction; @@ -830,21 +888,53 @@ export interface DefaultServices { // Toggles a cover tilt open/closed. toggleCoverTilt: ServiceFunction; }; - inputNumber: { - // Reloads helpers from the YAML-configuration. + group: { + // Reloads group configuration, entities, and notify services from YAML-configuration. reload: ServiceFunction; - // Sets the value. - setValue: ServiceFunction< + // Creates/Updates a user group. + set: ServiceFunction< T, { - // The target value. - value: number; + // Object ID of this group. This object ID is used as part of the entity ID. Entity ID format: [domain].[object_id]. @example test_group + object_id: string; + // Name of the group. @example My test group + name?: string; + // Name of the icon for the group. @example mdi:camera + icon?: object; + // List of all members in the group. Cannot be used in combination with `Add entities` or `Remove entities`. @example domain.entity_id1, domain.entity_id2 + entities?: string; + // List of members to be added to the group. Cannot be used in combination with `Entities` or `Remove entities`. @example domain.entity_id1, domain.entity_id2 + add_entities?: string; + // List of members to be removed from a group. Cannot be used in combination with `Entities` or `Add entities`. @example domain.entity_id1, domain.entity_id2 + remove_entities?: string; + // Enable this option if the group should only be used when all entities are in state `on`. + all?: boolean; + } + >; + // Removes a group. + remove: ServiceFunction< + T, + { + // Object ID of this group. This object ID is used as part of the entity ID. Entity ID format: [domain].[object_id]. @example test_group + object_id: object; + } + >; + }; + logbook: { + // Creates a custom entry in the logbook. + log: ServiceFunction< + T, + { + // Custom name for an entity, can be referenced using an `entity_id`. @example Kitchen + name: string; + // Message of the logbook entry. @example is being used + message: string; + // Entity to reference in the logbook entry. + entity_id?: string; + // Determines which icon is used in the logbook entry. The icon illustrates the integration domain related to this logbook entry. @example light + domain?: string; } >; - // Increments the value by 1 step. - increment: ServiceFunction; - // Decrements the current value by 1 step. - decrement: ServiceFunction; }; inputButton: { // Reloads helpers from the YAML-configuration. @@ -874,111 +964,47 @@ export interface DefaultServices { // Reloads schedules from the YAML-configuration. reload: ServiceFunction; }; - mediaPlayer: { - // Turns on the power of the media player. - turnOn: ServiceFunction; - // Turns off the power of the media player. + switch: { + // Turns a switch off. turnOff: ServiceFunction; - // Toggles a media player on/off. + // Turns a switch on. + turnOn: ServiceFunction; + // Toggles a switch on/off. toggle: ServiceFunction; - // Turns up the volume. - volumeUp: ServiceFunction; - // Turns down the volume. - volumeDown: ServiceFunction; - // Toggles play/pause. - mediaPlayPause: ServiceFunction; - // Starts playing. - mediaPlay: ServiceFunction; - // Pauses. - mediaPause: ServiceFunction; - // Stops playing. - mediaStop: ServiceFunction; - // Selects the next track. - mediaNextTrack: ServiceFunction; - // Selects the previous track. - mediaPreviousTrack: ServiceFunction; - // Clears the playlist. - clearPlaylist: ServiceFunction; - // Sets the volume level. - volumeSet: ServiceFunction< + }; + inputDatetime: { + // Reloads helpers from the YAML-configuration. + reload: ServiceFunction; + // Sets the date and/or time. + setDatetime: ServiceFunction< T, { - // The volume. 0 is inaudible, 1 is the maximum volume. - volume_level: number; - } - >; - // Mutes or unmutes the media player. - volumeMute: ServiceFunction< - T, - { - // Defines whether or not it is muted. - is_volume_muted: boolean; - } - >; - // Allows you to go to a different part of the media that is currently playing. - mediaSeek: ServiceFunction< - T, - { - // Target position in the currently playing media. The format is platform dependent. - seek_position: number; - } - >; - // Groups media players together for synchronous playback. Only works on supported multiroom audio systems. - join: ServiceFunction< - T, - { - // The players which will be synced with the playback specified in `target`. @example - media_player.multiroom_player2 - media_player.multiroom_player3 - group_members: string[]; - } - >; - // Sends the media player the command to change input source. - selectSource: ServiceFunction< - T, - { - // Name of the source to switch to. Platform dependent. @example video1 - source: string; - } - >; - // Selects a specific sound mode. - selectSoundMode: ServiceFunction< - T, - { - // Name of the sound mode to switch to. @example Music - sound_mode?: string; - } - >; - // Starts playing specified media. - playMedia: ServiceFunction< - T, - { - // The ID of the content to play. Platform dependent. @example https://home-assistant.io/images/cast/splash.png - media_content_id: string; - // The type of the content to play. Such as image, music, tv show, video, episode, channel, or playlist. @example music - media_content_type: string; - // If the content should be played now or be added to the queue. - enqueue?: "play" | "next" | "add" | "replace"; - // If the media should be played as an announcement. @example true - announce?: boolean; - } - >; - // Playback mode that selects the media in randomized order. - shuffleSet: ServiceFunction< - T, - { - // Whether or not shuffle mode is enabled. - shuffle: boolean; + // The target date. @example '2019-04-20' + date?: string; + // The target time. @example '05:04:20' + time?: object; + // The target date & time. @example '2019-04-20 05:04:20' + datetime?: string; + // The target date & time, expressed by a UNIX timestamp. + timestamp?: number; } >; - // Removes the player from a group. Only works on platforms which support player groups. - unjoin: ServiceFunction; - // Playback mode that plays the media in a loop. - repeatSet: ServiceFunction< + }; + inputNumber: { + // Reloads helpers from the YAML-configuration. + reload: ServiceFunction; + // Sets the value. + setValue: ServiceFunction< T, { - // Repeat mode to set. - repeat: "off" | "all" | "one"; + // The target value. + value: number; } >; + // Increments the value by 1 step. + increment: ServiceFunction; + // Decrements the current value by 1 step. + decrement: ServiceFunction; }; inputSelect: { // Reloads helpers from the YAML-configuration. @@ -1024,24 +1050,6 @@ export interface DefaultServices { // Reloads zones from the YAML-configuration. reload: ServiceFunction; }; - inputDatetime: { - // Reloads helpers from the YAML-configuration. - reload: ServiceFunction; - // Sets the date and/or time. - setDatetime: ServiceFunction< - T, - { - // The target date. @example '2019-04-20' - date?: string; - // The target time. @example '05:04:20' - time?: object; - // The target date & time. @example '2019-04-20 05:04:20' - datetime?: string; - // The target date & time, expressed by a UNIX timestamp. - timestamp?: number; - } - >; - }; inputText: { // Reloads helpers from the YAML-configuration. reload: ServiceFunction; @@ -1054,62 +1062,6 @@ export interface DefaultServices { } >; }; - remote: { - // Turns the device off. - turnOff: ServiceFunction; - // Sends the power on command. - turnOn: ServiceFunction< - T, - { - // Activity ID or activity name to be started. @example BedroomTV - activity?: string; - } - >; - // Toggles a device on/off. - toggle: ServiceFunction; - // Sends a command or a list of commands to a device. - sendCommand: ServiceFunction< - T, - { - // Device ID to send command to. @example 32756745 - device?: string; - // A single command or a list of commands to send. @example Play - command: object; - // The number of times you want to repeat the commands. - num_repeats?: number; - // The time you want to wait in between repeated commands. - delay_secs?: number; - // The time you want to have it held before the release is send. - hold_secs?: number; - } - >; - // Learns a command or a list of commands from a device. - learnCommand: ServiceFunction< - T, - { - // Device ID to learn command from. @example television - device?: string; - // A single command or a list of commands to learn. @example Turn on - command?: object; - // The type of command to be learned. - command_type?: "ir" | "rf"; - // If code must be stored as an alternative. This is useful for discrete codes. Discrete codes are used for toggles that only perform one function. For example, a code to only turn a device on. If it is on already, sending the code won't change the state. - alternative?: boolean; - // Timeout for the command to be learned. - timeout?: number; - } - >; - // Deletes a command or a list of commands from the database. - deleteCommand: ServiceFunction< - T, - { - // Device from which commands will be deleted. @example television - device?: string; - // The single command or the list of commands to be deleted. @example Mute - command: object; - } - >; - }; script: { // Reloads all the available scripts. reload: ServiceFunction; @@ -1120,39 +1072,59 @@ export interface DefaultServices { // Toggle a script. Starts it, if isn't running, stops it otherwise. toggle: ServiceFunction; }; - scene: { - // Reloads the scenes from the YAML-configuration. - reload: ServiceFunction; - // Activates a scene with configuration. - apply: ServiceFunction< + profiler: { + // Starts the Profiler. + start: ServiceFunction< T, { - // List of entities and their target state. @example light.kitchen: 'on' light.ceiling: state: 'on' brightness: 80 - entities: object; - // Time it takes the devices to transition into the states defined in the scene. - transition?: number; + // The number of seconds to run the profiler. + seconds?: number; } >; - // Creates a new scene. - create: ServiceFunction< + // Starts the Memory Profiler. + memory: ServiceFunction< T, { - // The entity ID of the new scene. @example all_lights - scene_id: string; - // List of entities and their target state. If your entities are already in the target state right now, use `snapshot_entities` instead. @example light.tv_back_light: 'on' light.ceiling: state: 'on' brightness: 200 - entities?: object; - // List of entities to be included in the snapshot. By taking a snapshot, you record the current state of those entities. If you do not want to use the current state of all your entities for this scene, you can combine the `snapshot_entities` with `entities`. @example - light.ceiling - light.kitchen - snapshot_entities?: string; + // The number of seconds to run the memory profiler. + seconds?: number; } >; - // Activates a scene. - turnOn: ServiceFunction< + // Starts logging growth of objects in memory. + startLogObjects: ServiceFunction< T, { - // Time it takes the devices to transition into the states defined in the scene. - transition?: number; + // The number of seconds between logging objects. + scan_interval?: number; } >; + // Stops logging growth of objects in memory. + stopLogObjects: ServiceFunction; + // Starts logging sources of new objects in memory. + startLogObjectSources: ServiceFunction< + T, + { + // The number of seconds between logging objects. + scan_interval?: number; + // The maximum number of objects to log. + max_objects?: number; + } + >; + // Stops logging sources of new objects in memory. + stopLogObjectSources: ServiceFunction; + // Dumps the repr of all matching objects to the log. + dumpLogObjects: ServiceFunction< + T, + { + // The type of objects to dump to the log. @example State + type: string; + } + >; + // Logs the stats of all lru caches. + lruStats: ServiceFunction; + // Logs the current frames for all threads. + logThreadFrames: ServiceFunction; + // Logs what is scheduled in the event loop. + logEventLoopScheduled: ServiceFunction; }; timer: { // Reloads timers from the YAML-configuration. @@ -1180,6 +1152,42 @@ export interface DefaultServices { } >; }; + calendar: { + // Adds a new calendar event. + createEvent: ServiceFunction< + T, + { + // Defines the short summary or subject for the event. @example Department Party + summary: string; + // A more complete description of the event than the one provided by the summary. @example Meeting to provide technical review for 'Phoenix' design. + description?: string; + // The date and time the event should start. @example 2022-03-22 20:00:00 + start_date_time?: object; + // The date and time the event should end. @example 2022-03-22 22:00:00 + end_date_time?: object; + // The date the all-day event should start. @example 2022-03-22 + start_date?: object; + // The date the all-day event should end (exclusive). @example 2022-03-23 + end_date?: object; + // Days or weeks that you want to create the event in. @example {'days': 2} or {'weeks': 2} + in?: object; + // The location of the event. @example Conference Room - F123, Bldg. 002 + location?: string; + } + >; + // Lists events on a calendar within a time range. + listEvents: ServiceFunction< + T, + { + // Returns active events after this time (exclusive). When not set, defaults to now. @example 2022-03-22 20:00:00 + start_date_time?: object; + // Returns active events before this time (exclusive). Cannot be used with 'duration'. @example 2022-03-22 22:00:00 + end_date_time?: object; + // Returns active events from start_date_time until the specified duration. + duration?: object; + } + >; + }; inputBoolean: { // Reloads helpers from the YAML-configuration. reload: ServiceFunction; @@ -1190,77 +1198,37 @@ export interface DefaultServices { // Toggles the helper on/off. toggle: ServiceFunction; }; - weather: { - // Get weather forecast. - getForecast: ServiceFunction< - T, - { - // Forecast type: daily, hourly or twice daily. - type: "daily" | "hourly" | "twice_daily"; - } - >; - }; - automation: { - // Triggers the actions of an automation. - trigger: ServiceFunction< - T, - { - // Defines whether or not the conditions will be skipped. - skip_condition?: boolean; - } - >; - // Toggles (enable / disable) an automation. - toggle: ServiceFunction; - // Enables an automation. - turnOn: ServiceFunction; - // Disables an automation. - turnOff: ServiceFunction< - T, - { - // Stops currently running actions. - stop_actions?: boolean; - } - >; - // Reloads the automation configuration. + scene: { + // Reloads the scenes from the YAML-configuration. reload: ServiceFunction; - }; - camera: { - // Enables the motion detection. - enableMotionDetection: ServiceFunction; - // Disables the motion detection. - disableMotionDetection: ServiceFunction; - // Turns off the camera. - turnOff: ServiceFunction; - // Turns on the camera. - turnOn: ServiceFunction; - // Takes a snapshot from a camera. - snapshot: ServiceFunction< + // Activates a scene with configuration. + apply: ServiceFunction< T, { - // Template of a filename. Variable available is `entity_id`. @example /tmp/snapshot_{{ entity_id.name }}.jpg - filename: string; + // List of entities and their target state. @example light.kitchen: 'on' light.ceiling: state: 'on' brightness: 80 + entities: object; + // Time it takes the devices to transition into the states defined in the scene. + transition?: number; } >; - // Plays the camera stream on a supported media player. - playStream: ServiceFunction< + // Creates a new scene. + create: ServiceFunction< T, { - // Media players to stream to. - media_player: string; - // Stream format supported by the media player. - format?: "hls"; + // The entity ID of the new scene. @example all_lights + scene_id: string; + // List of entities and their target state. If your entities are already in the target state right now, use `snapshot_entities` instead. @example light.tv_back_light: 'on' light.ceiling: state: 'on' brightness: 200 + entities?: object; + // List of entities to be included in the snapshot. By taking a snapshot, you record the current state of those entities. If you do not want to use the current state of all your entities for this scene, you can combine the `snapshot_entities` with `entities`. @example - light.ceiling - light.kitchen + snapshot_entities?: string; } >; - // Creates a recording of a live camera feed. - record: ServiceFunction< + // Activates a scene. + turnOn: ServiceFunction< T, { - // Template of a filename. Variable available is `entity_id`. Must be mp4. @example /tmp/snapshot_{{ entity_id.name }}.mp4 - filename: string; - // Planned duration of the recording. The actual duration may vary. - duration?: number; - // Planned lookback period to include in the recording (in addition to the duration). Only available if there is currently an active HLS stream. The actual length of the lookback period may vary. - lookback?: number; + // Time it takes the devices to transition into the states defined in the scene. + transition?: number; } >; }; @@ -1408,25 +1376,87 @@ export interface DefaultServices { // Press the button entity. press: ServiceFunction; }; - deviceTracker: { - // Records a seen tracked device. - see: ServiceFunction< + lock: { + // Unlocks a lock. + unlock: ServiceFunction< T, { - // MAC address of the device. @example FF:FF:FF:FF:FF:FF - mac?: string; - // ID of the device (find the ID in `known_devices.yaml`). @example phonedave - dev_id?: string; - // Hostname of the device. @example Dave - host_name?: string; - // Name of the location where the device is located. The options are: `home`, `not_home`, or the name of the zone. @example home - location_name?: string; - // GPS coordinates where the device is located, specified by latitude and longitude (for example: [51.513845, -0.100539]). @example [51.509802, -0.086692] - gps?: object; - // Accuracy of the GPS coordinates. - gps_accuracy?: number; - // Battery level of the device. - battery?: number; + // Code used to unlock the lock. @example 1234 + code?: string; + } + >; + // Locks a lock. + lock: ServiceFunction< + T, + { + // Code used to lock the lock. @example 1234 + code?: string; + } + >; + // Opens a lock. + open: ServiceFunction< + T, + { + // Code used to open the lock. @example 1234 + code?: string; + } + >; + }; + siren: { + // Turns the siren on. + turnOn: ServiceFunction< + T, + { + // The tone to emit. When `available_tones` property is a map, either the key or the value can be used. Must be supported by the integration. @example fire + tone?: string; + // The volume. 0 is inaudible, 1 is the maximum volume. Must be supported by the integration. @example 0.5 + volume_level?: number; + // Number of seconds the sound is played. Must be supported by the integration. @example 15 + duration?: string; + } + >; + // Turns the siren off. + turnOff: ServiceFunction; + // Toggles the siren on/off. + toggle: ServiceFunction; + }; + number: { + // Sets the value of a number. + setValue: ServiceFunction< + T, + { + // The target value to set. @example 42 + value?: string; + } + >; + }; + select: { + // Selects the first option. + selectFirst: ServiceFunction; + // Selects the last option. + selectLast: ServiceFunction; + // Selects the next option. + selectNext: ServiceFunction< + T, + { + // If the option should cycle from the last to the first. + cycle?: boolean; + } + >; + // Selects an option. + selectOption: ServiceFunction< + T, + { + // Option to be selected. @example 'Item A' + option: string; + } + >; + // Selects the previous option. + selectPrevious: ServiceFunction< + T, + { + // If the option should cycle from the first to the last. + cycle?: boolean; } >; }; @@ -1494,37 +1524,155 @@ export interface DefaultServices { } >; }; - lawnMower: { - // Starts the mowing task. - startMowing: ServiceFunction; - // Pauses the mowing task. - pause: ServiceFunction; - // Stops the mowing task and returns to the dock. - dock: ServiceFunction; + remote: { + // Turns the device off. + turnOff: ServiceFunction; + // Sends the power on command. + turnOn: ServiceFunction< + T, + { + // Activity ID or activity name to be started. @example BedroomTV + activity?: string; + } + >; + // Toggles a device on/off. + toggle: ServiceFunction; + // Sends a command or a list of commands to a device. + sendCommand: ServiceFunction< + T, + { + // Device ID to send command to. @example 32756745 + device?: string; + // A single command or a list of commands to send. @example Play + command: object; + // The number of times you want to repeat the commands. + num_repeats?: number; + // The time you want to wait in between repeated commands. + delay_secs?: number; + // The time you want to have it held before the release is send. + hold_secs?: number; + } + >; + // Learns a command or a list of commands from a device. + learnCommand: ServiceFunction< + T, + { + // Device ID to learn command from. @example television + device?: string; + // A single command or a list of commands to learn. @example Turn on + command?: object; + // The type of command to be learned. + command_type?: "ir" | "rf"; + // If code must be stored as an alternative. This is useful for discrete codes. Discrete codes are used for toggles that only perform one function. For example, a code to only turn a device on. If it is on already, sending the code won't change the state. + alternative?: boolean; + // Timeout for the command to be learned. + timeout?: number; + } + >; + // Deletes a command or a list of commands from the database. + deleteCommand: ServiceFunction< + T, + { + // Device from which commands will be deleted. @example television + device?: string; + // The single command or the list of commands to be deleted. @example Mute + command: object; + } + >; }; - lock: { - // Unlocks a lock. - unlock: ServiceFunction< + weather: { + // Get weather forecast. + getForecast: ServiceFunction< T, { - // Code used to unlock the lock. @example 1234 - code?: string; + // Forecast type: daily, hourly or twice daily. + type: "daily" | "hourly" | "twice_daily"; } >; - // Locks a lock. - lock: ServiceFunction< + }; + camera: { + // Enables the motion detection. + enableMotionDetection: ServiceFunction; + // Disables the motion detection. + disableMotionDetection: ServiceFunction; + // Turns off the camera. + turnOff: ServiceFunction; + // Turns on the camera. + turnOn: ServiceFunction; + // Takes a snapshot from a camera. + snapshot: ServiceFunction< T, { - // Code used to lock the lock. @example 1234 - code?: string; + // Template of a filename. Variable available is `entity_id`. @example /tmp/snapshot_{{ entity_id.name }}.jpg + filename: string; } >; - // Opens a lock. - open: ServiceFunction< + // Plays the camera stream on a supported media player. + playStream: ServiceFunction< T, { - // Code used to open the lock. @example 1234 - code?: string; + // Media players to stream to. + media_player: string; + // Stream format supported by the media player. + format?: "hls"; + } + >; + // Creates a recording of a live camera feed. + record: ServiceFunction< + T, + { + // Template of a filename. Variable available is `entity_id`. Must be mp4. @example /tmp/snapshot_{{ entity_id.name }}.mp4 + filename: string; + // Planned duration of the recording. The actual duration may vary. + duration?: number; + // Planned lookback period to include in the recording (in addition to the duration). Only available if there is currently an active HLS stream. The actual length of the lookback period may vary. + lookback?: number; + } + >; + }; + automation: { + // Triggers the actions of an automation. + trigger: ServiceFunction< + T, + { + // Defines whether or not the conditions will be skipped. + skip_condition?: boolean; + } + >; + // Toggles (enable / disable) an automation. + toggle: ServiceFunction; + // Enables an automation. + turnOn: ServiceFunction; + // Disables an automation. + turnOff: ServiceFunction< + T, + { + // Stops currently running actions. + stop_actions?: boolean; + } + >; + // Reloads the automation configuration. + reload: ServiceFunction; + }; + deviceTracker: { + // Records a seen tracked device. + see: ServiceFunction< + T, + { + // MAC address of the device. @example FF:FF:FF:FF:FF:FF + mac?: string; + // ID of the device (find the ID in `known_devices.yaml`). @example phonedave + dev_id?: string; + // Hostname of the device. @example Dave + host_name?: string; + // Name of the location where the device is located. The options are: `home`, `not_home`, or the name of the zone. @example home + location_name?: string; + // GPS coordinates where the device is located, specified by latitude and longitude (for example: [51.513845, -0.100539]). @example [51.509802, -0.086692] + gps?: object; + // Accuracy of the GPS coordinates. + gps_accuracy?: number; + // Battery level of the device. + battery?: number; } >; }; @@ -1552,24 +1700,6 @@ export interface DefaultServices { } >; }; - siren: { - // Turns the siren on. - turnOn: ServiceFunction< - T, - { - // The tone to emit. When `available_tones` property is a map, either the key or the value can be used. Must be supported by the integration. @example fire - tone?: string; - // The volume. 0 is inaudible, 1 is the maximum volume. Must be supported by the integration. @example 0.5 - volume_level?: number; - // Number of seconds the sound is played. Must be supported by the integration. @example 15 - duration?: string; - } - >; - // Turns the siren off. - turnOff: ServiceFunction; - // Toggles the siren on/off. - toggle: ServiceFunction; - }; vacuum: { // Starts a new cleaning task. turnOn: ServiceFunction; @@ -1610,35 +1740,13 @@ export interface DefaultServices { } >; }; - select: { - // Selects the first option. - selectFirst: ServiceFunction; - // Selects the last option. - selectLast: ServiceFunction; - // Selects the next option. - selectNext: ServiceFunction< - T, - { - // If the option should cycle from the last to the first. - cycle?: boolean; - } - >; - // Selects an option. - selectOption: ServiceFunction< - T, - { - // Option to be selected. @example 'Item A' - option: string; - } - >; - // Selects the previous option. - selectPrevious: ServiceFunction< - T, - { - // If the option should cycle from the first to the last. - cycle?: boolean; - } - >; + lawnMower: { + // Starts the mowing task. + startMowing: ServiceFunction; + // Pauses the mowing task. + pause: ServiceFunction; + // Stops the mowing task and returns to the dock. + dock: ServiceFunction; }; waterHeater: { // Turns water heater on. @@ -1672,16 +1780,6 @@ export interface DefaultServices { } >; }; - number: { - // Sets the value of a number. - setValue: ServiceFunction< - T, - { - // The target value to set. @example 42 - value?: string; - } - >; - }; text: { // Sets the value. setValue: ServiceFunction< @@ -1692,103 +1790,23 @@ export interface DefaultServices { } >; }; - calendar: { - // Adds a new calendar event. - createEvent: ServiceFunction< - T, - { - // Defines the short summary or subject for the event. @example Department Party - summary: string; - // A more complete description of the event than the one provided by the summary. @example Meeting to provide technical review for 'Phoenix' design. - description?: string; - // The date and time the event should start. @example 2022-03-22 20:00:00 - start_date_time?: object; - // The date and time the event should end. @example 2022-03-22 22:00:00 - end_date_time?: object; - // The date the all-day event should start. @example 2022-03-22 - start_date?: object; - // The date the all-day event should end (exclusive). @example 2022-03-23 - end_date?: object; - // Days or weeks that you want to create the event in. @example {'days': 2} or {'weeks': 2} - in?: object; - // The location of the event. @example Conference Room - F123, Bldg. 002 - location?: string; - } - >; - // Lists events on a calendar within a time range. - listEvents: ServiceFunction< - T, - { - // Returns active events after this time (exclusive). When not set, defaults to now. @example 2022-03-22 20:00:00 - start_date_time?: object; - // Returns active events before this time (exclusive). Cannot be used with 'duration'. @example 2022-03-22 22:00:00 - end_date_time?: object; - // Returns active events from start_date_time until the specified duration. - duration?: object; - } - >; - }; - switch: { - // Turns a switch off. - turnOff: ServiceFunction; - // Turns a switch on. - turnOn: ServiceFunction; - // Toggles a switch on/off. - toggle: ServiceFunction; - }; - profiler: { - // Starts the Profiler. - start: ServiceFunction< - T, - { - // The number of seconds to run the profiler. - seconds?: number; - } - >; - // Starts the Memory Profiler. - memory: ServiceFunction< - T, - { - // The number of seconds to run the memory profiler. - seconds?: number; - } - >; - // Starts logging growth of objects in memory. - startLogObjects: ServiceFunction< - T, - { - // The number of seconds between logging objects. - scan_interval?: number; - } - >; - // Stops logging growth of objects in memory. - stopLogObjects: ServiceFunction; - // Starts logging sources of new objects in memory. - startLogObjectSources: ServiceFunction< - T, - { - // The number of seconds between logging objects. - scan_interval?: number; - // The maximum number of objects to log. - max_objects?: number; - } - >; - // Stops logging sources of new objects in memory. - stopLogObjectSources: ServiceFunction; - // Dumps the repr of all matching objects to the log. - dumpLogObjects: ServiceFunction< + mediaExtractor: { + // Downloads file from given URL. + playMedia: ServiceFunction< T, { - // The type of objects to dump to the log. @example State - type: string; + // The ID of the content to play. Platform dependent. @example https://soundcloud.com/bruttoband/brutto-11 + media_content_id: string; + // The type of the content to play. Must be one of MUSIC, TVSHOW, VIDEO, EPISODE, CHANNEL or PLAYLIST MUSIC. + media_content_type: + | "CHANNEL" + | "EPISODE" + | "PLAYLIST MUSIC" + | "MUSIC" + | "TVSHOW" + | "VIDEO"; } >; - // Logs the stats of all lru caches. - lruStats: ServiceFunction; - // Logs the current frames for all threads. - logThreadFrames: ServiceFunction; - // Logs what is scheduled in the event loop. - logEventLoopScheduled: ServiceFunction; }; notify: { // Sends a notification that is visible in the **Notifications** panel. @@ -1832,6 +1850,44 @@ export interface DefaultServices { } >; }; + mass: { + // Perform a global search on the Music Assistant library and all providers. + search: ServiceFunction< + T, + { + // The name/title to search for. @example We Are The Champions + name: string; + // The type of the content to search. Such as artist, album, track, radio or playlist. All types if omitted. @example playlist + media_type?: "artist" | "album" | "playlist" | "track" | "radio"; + // When specifying a track or album name in the name field, you can optionally restrict results by this artist name. @example Queen + artist?: string; + // When specifying a track name in the name field, you can optionally restrict results by this album name. @example News of the world + album?: string; + // Maximum number of items to return (per media type). @example 25 + limit?: number; + } + >; + // Play media on a Music Assistant player with more fine grained control options. + playMedia: ServiceFunction< + T, + { + // URI or name of the item you want to play. Specify a list if you want to play/enqueue multiple items. @example spotify://playlist/aabbccddeeff + media_id: object; + // The type of the content to play. Such as artist, album, track or playlist. Will be auto determined if omitted. @example playlist + media_type?: "artist" | "album" | "playlist" | "track" | "radio"; + // If the content should be played now or be added to the queue. Options are: play, replace, next, replace_next, add + enqueue?: "play" | "replace" | "next" | "replace_next" | "add"; + // If the media should be played as an announcement. @example true + announce?: boolean; + // When specifying a track or album by name in the Media ID field, you can optionally restrict results by this artist name. @example Queen + artist?: string; + // When specifying a track by name in the Media ID field, you can optionally restrict results by this album name. @example News of the world + album?: string; + // Enable radio mode to auto generate a playlist based on the selection. + radio_mode?: boolean; + } + >; + }; cast: { // Shows a dashboard view on a Chromecast device. showLovelaceView: ServiceFunction< @@ -1846,6 +1902,10 @@ export interface DefaultServices { } >; }; + template: { + // Reloads template entities from the YAML-configuration. + reload: ServiceFunction; + }; google: { // Adds a new calendar event. addEvent: ServiceFunction< @@ -1892,46 +1952,4 @@ export interface DefaultServices { } >; }; - template: { - // Reloads template entities from the YAML-configuration. - reload: ServiceFunction; - }; - mass: { - // Perform a global search on the Music Assistant library and all providers. - search: ServiceFunction< - T, - { - // The name/title to search for. @example We Are The Champions - name: string; - // The type of the content to search. Such as artist, album, track, radio or playlist. All types if omitted. @example playlist - media_type?: "artist" | "album" | "playlist" | "track" | "radio"; - // When specifying a track or album name in the name field, you can optionally restrict results by this artist name. @example Queen - artist?: string; - // When specifying a track name in the name field, you can optionally restrict results by this album name. @example News of the world - album?: string; - // Maximum number of items to return (per media type). @example 25 - limit?: number; - } - >; - // Play media on a Music Assistant player with more fine grained control options. - playMedia: ServiceFunction< - T, - { - // URI or name of the item you want to play. Specify a list if you want to play/enqueue multiple items. @example spotify://playlist/aabbccddeeff - media_id: object; - // The type of the content to play. Such as artist, album, track or playlist. Will be auto determined if omitted. @example playlist - media_type?: "artist" | "album" | "playlist" | "track" | "radio"; - // If the content should be played now or be added to the queue. Options are: play, replace, next, replace_next, add - enqueue?: "play" | "replace" | "next" | "replace_next" | "add"; - // If the media should be played as an announcement. @example true - announce?: boolean; - // When specifying a track or album by name in the Media ID field, you can optionally restrict results by this artist name. @example Queen - artist?: string; - // When specifying a track by name in the Media ID field, you can optionally restrict results by this album name. @example News of the world - album?: string; - // Enable radio mode to auto generate a playlist based on the selection. - radio_mode?: boolean; - } - >; - }; } diff --git a/packages/core/src/utils/colors.ts b/packages/core/src/utils/colors.ts index d2f1c284..7cafb4dd 100644 --- a/packages/core/src/utils/colors.ts +++ b/packages/core/src/utils/colors.ts @@ -27,6 +27,10 @@ function toRGB(entity: HassEntity): [number, number, number] | null { export function getCssColorValue(entity: HassEntity | null) { const color = entity ? toRGB(entity) : null; + // TODO - potentially return null here instead, and let @hakit/components determine + // the css variables if the value is null + // If a user is using @hakit/core, and not the ThemeProvider - these variables will do & mean nothing. + // FIX SHANNON, FIX! const hexColor = color ? rgb2hex(color) : "var(--ha-A400)"; const rgbColor = color ? `rgba(${color.join(", ")})` : "var(--ha-S500-contrast)"; const rgbaColor = color ? `rgba(${[...color, 0.35].join(", ")})` : "var(--ha-A200)"; diff --git a/packages/create-hakit/package.json b/packages/create-hakit/package.json index b06131a1..e39f150b 100644 --- a/packages/create-hakit/package.json +++ b/packages/create-hakit/package.json @@ -1,6 +1,6 @@ { "name": "create-hakit", - "version": "1.1.0", + "version": "1.1.1", "type": "module", "author": "Shannon Hochkins ", "license": "ISC", @@ -42,6 +42,7 @@ "devDependencies": { "@types/minimist": "^1.2.2", "@types/prompts": "^2.4.4", + "chalk": "^5.3.0", "cross-spawn": "^7.0.3", "kolorist": "^1.8.0", "minimist": "^1.2.8", diff --git a/packages/create-hakit/src/index.ts b/packages/create-hakit/src/index.ts index 04c2bb6a..f7908528 100644 --- a/packages/create-hakit/src/index.ts +++ b/packages/create-hakit/src/index.ts @@ -230,6 +230,7 @@ function updatePackageJson({ const prettierVersion = getLatestNpmVersion('prettier'); const dotenvVersion = getLatestNpmVersion('dotenv'); const nodeScpVersion = getLatestNpmVersion('node-scp'); + const chalk = getLatestNpmVersion('chalk'); const nodeTypesVersion = getLatestNpmVersion('@types/node'); const packageFile = path.resolve(targetDir, 'package.json'); const pkg = JSON.parse(fs.readFileSync(packageFile, 'utf-8')); @@ -244,6 +245,7 @@ function updatePackageJson({ "dotenv": `^${dotenvVersion}`, "@types/node": `^${nodeTypesVersion}`, "node-scp": `^${nodeScpVersion}`, + "chalk": `^${chalk}`, }; pkg.scripts = { ...pkg.scripts, diff --git a/packages/create-hakit/template/scripts/deploy.ts b/packages/create-hakit/template/scripts/deploy.ts index d6502390..8d342e4c 100644 --- a/packages/create-hakit/template/scripts/deploy.ts +++ b/packages/create-hakit/template/scripts/deploy.ts @@ -1,6 +1,7 @@ import { Client } from 'node-scp'; import * as dotenv from 'dotenv'; import { join } from 'path'; +import chalk from 'chalk'; import { access, constants } from 'fs/promises'; dotenv.config(); @@ -11,7 +12,7 @@ const HOST_OR_IP_ADDRESS = process.env.VITE_SSH_HOSTNAME; const PORT = 22; const REMOTE_FOLDER_NAME = process.env.VITE_FOLDER_NAME; const LOCAL_DIRECTORY = './dist'; -const REMOTE_PATH = `/config/www/${REMOTE_FOLDER_NAME}`; +const REMOTE_PATH = `/www/${REMOTE_FOLDER_NAME}`; async function checkDirectoryExists() { try { @@ -43,29 +44,41 @@ async function deploy() { if (!exists) { throw new Error('Missing ./dist directory, have you run `npm run build`?'); } - console.info(`Deploying to ${USERNAME}:${PASSWORD}@${HOST_OR_IP_ADDRESS}:${PORT}:${REMOTE_PATH}`) const client = await Client({ host: HOST_OR_IP_ADDRESS, port: PORT, username: USERNAME, password: PASSWORD, - }) - // empty the directory initially so we remove anything that doesn't need to be there - try { - await client.rmdir(REMOTE_PATH); - } catch (e) { - // directory may not exist, ignore + }); + // seems somewhere along the lines, home assistant decided to rename the config directory to homeassistant... + const directories = ['config', 'homeassistant']; + for (const dir of directories) { + const remote = `/${dir}${REMOTE_PATH}`; + const exists = await client.exists(remote); + if (exists) { + // empty the directory initially so we remove anything that doesn't need to be there + try { + await client.rmdir(remote); + } catch (e) { + // directory may not exist, ignore + } + console.log(chalk.blue('Uploading', `"${LOCAL_DIRECTORY}"`, 'to', `"${remote}"`)) + // upload the folder to your home assistant server + await client.uploadDir(LOCAL_DIRECTORY, remote); + client.close() // remember to close connection after you finish + console.info(chalk.green('\nSuccessfully deployed!')); + const url = join(HA_URL, '/local', REMOTE_FOLDER_NAME, '/index.html'); + console.info(chalk.blue(`\n\nVISIT the following URL to preview your dashboard:\n`)); + console.info(chalk.bgCyan(chalk.underline(url))); + console.info(chalk.yellow('\n\nAlternatively, follow the steps in the ha-component-kit repository to install the addon for Home Assistant so you can load your dashboard from the sidebar!\n\n')); + console.info('\n\n'); + break; + } } - // upload the folder to your home assistant server - await client.uploadDir(LOCAL_DIRECTORY, REMOTE_PATH); - client.close() // remember to close connection after you finish - console.info('\nSuccessfully deployed!'); - const url = join(HA_URL, '/local', REMOTE_FOLDER_NAME, '/index.html'); - console.info(`\n\nVISIT the following URL to preview your dashboard:\n`); - console.info(url); - console.info('\n\n'); } catch (e: unknown) { - console.error('Error:', e) + if (e instanceof Error) { + console.error(chalk.red('Error:', e.message ?? 'unknown error')); + } } }